Files
Samuel Newman bd510d8468 Upgrade ESLint to v9 with flat config (#9680)
* Upgrade ESLint to v9 with flat config

- Upgrade eslint from v8 to v9.18.0
- Migrate from .eslintrc.js to eslint.config.mjs (flat config)
- Upgrade typescript-eslint to v8.20.0 (unified package)
- Replace eslint-plugin-import with eslint-plugin-import-x for flat config support
- Add globals package for environment globals
- Update eslint-plugin-bsky-internal with proper meta objects for ESLint v9
- Fix deprecated context.getScope() API usage
- Update bskyembed to use flat config
- Remove deprecated --ext flag from lint scripts
- Configure rules to maintain previous behavior while using new ESLint version

* Fix varsIgnorePattern to require character after underscore

Restore the original pattern `^_.+` instead of `^_` so that lingui's
`const { _ } = useLingui()` will still be flagged when unused.

* Update ESLint rule tests for flat config format

- Update RuleTester to use flat config languageOptions instead of
  eslintrc parser format
- Remove duplicate test case that ESLint v9 now detects
- Add Jest globals for test files

* update eslint package versions

* lint android a11y

* enable typechecked rules, switch them to warn

* fix yarn lock ci

* Fix CI failure

* Remove unused globals?

* Organize a bit, add quiet to main lint command

* Allow ternary

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Eric Bailey <git@esb.lol>
2026-01-16 16:24:28 -06:00

137 lines
3.8 KiB
TypeScript

import {useEffect, useState} from 'react'
import EventEmitter from 'eventemitter3'
import {networkRetry} from '#/lib/async/retry'
import {
FALLBACK_GEOLOCATION_SERVICE_RESPONSE,
GEOLOCATION_SERVICE_URL,
} from '#/geolocation/const'
import * as debug from '#/geolocation/debug'
import {logger} from '#/geolocation/logger'
import {type Geolocation} from '#/geolocation/types'
import {device} from '#/storage'
const events = new EventEmitter()
const EVENT = 'geolocation-service-response-updated'
const emitGeolocationServiceResponseUpdate = (data: Geolocation) => {
events.emit(EVENT, data)
}
const onGeolocationServiceResponseUpdate = (
listener: (data: Geolocation) => void,
) => {
events.on(EVENT, listener)
return () => {
events.off(EVENT, listener)
}
}
async function fetchGeolocationServiceData(
url: string,
): Promise<Geolocation | undefined> {
if (debug.enabled) return debug.resolve(debug.geolocation)
const res = await fetch(url)
if (!res.ok) {
throw new Error(`fetchGeolocationServiceData failed ${res.status}`)
}
return res.json() as Promise<Geolocation>
}
/**
* Local promise used within this file only.
*/
let geolocationServicePromise: Promise<{success: boolean}> | undefined
/**
* Begin the process of resolving geolocation config. This is called right away
* at app start, and the promise is awaited later before proceeding with app
* startup.
*/
export async function resolve() {
if (geolocationServicePromise) {
const cached = device.get(['geolocationServiceResponse'])
if (cached) {
logger.debug(`resolve(): using cache`)
} else {
logger.debug(`resolve(): no cache`)
const {success} = await geolocationServicePromise
if (success) {
logger.debug(`resolve(): resolved`)
} else {
logger.info(`resolve(): failed`)
}
}
} else {
logger.debug(`resolve(): initiating`)
/**
* THIS PROMISE SHOULD NEVER `reject()`! We want the app to proceed with
* startup, even if geolocation resolution fails.
*/
geolocationServicePromise = new Promise(async resolve => {
let success = false
function cacheResponseOrThrow(response: Geolocation | undefined) {
if (response) {
device.set(['geolocationServiceResponse'], response)
emitGeolocationServiceResponseUpdate(response)
} else {
// endpoint should throw on all failures, this is insurance
throw new Error(`fetchGeolocationServiceData returned no data`)
}
}
try {
// Try once, fail fast
const config = await fetchGeolocationServiceData(
GEOLOCATION_SERVICE_URL,
)
cacheResponseOrThrow(config)
success = true
} catch (e: any) {
logger.debug(
`resolve(): fetchGeolocationServiceData failed initial request`,
{
safeMessage: e.message,
},
)
// retry 3 times, but don't await, proceed with default
networkRetry(3, () =>
fetchGeolocationServiceData(GEOLOCATION_SERVICE_URL),
)
.then(config => {
cacheResponseOrThrow(config)
})
.catch((e: any) => {
// complete fail closed
logger.debug(
`resolve(): fetchGeolocationServiceData failed retries`,
{
safeMessage: e.message,
},
)
})
} finally {
resolve({success})
}
})
}
}
export function useGeolocationServiceResponse() {
const [config, setConfig] = useState(() => {
const initial =
device.get(['geolocationServiceResponse']) ||
FALLBACK_GEOLOCATION_SERVICE_RESPONSE
return initial
})
useEffect(() => {
return onGeolocationServiceResponseUpdate(config => {
setConfig(config)
})
}, [])
return config
}