about summary refs log tree commit diff
path: root/src/state/geolocation.tsx
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2025-09-04 11:07:12 -0500
committerGitHub <noreply@github.com>2025-09-04 11:07:12 -0500
commitf8ae0540a062e6346baf9fbf0481f769fb23a120 (patch)
treefc888e55258169e8e7246a1c099aab78fc7d9c99 /src/state/geolocation.tsx
parent625b4e61dbf11c1d485bf8e8265df4d5af0c9657 (diff)
downloadvoidsky-f8ae0540a062e6346baf9fbf0481f769fb23a120.tar.zst
Provide geo-gated users optional GPS fallback for precise location data (#8973)
Diffstat (limited to 'src/state/geolocation.tsx')
-rw-r--r--src/state/geolocation.tsx226
1 files changed, 0 insertions, 226 deletions
diff --git a/src/state/geolocation.tsx b/src/state/geolocation.tsx
deleted file mode 100644
index c4d8cb946..000000000
--- a/src/state/geolocation.tsx
+++ /dev/null
@@ -1,226 +0,0 @@
-import React from 'react'
-import EventEmitter from 'eventemitter3'
-
-import {networkRetry} from '#/lib/async/retry'
-import {logger} from '#/logger'
-import {type Device, device} from '#/storage'
-
-const IPCC_URL = `https://bsky.app/ipcc`
-const BAPP_CONFIG_URL = `https://ip.bsky.app/config`
-
-const events = new EventEmitter()
-const EVENT = 'geolocation-updated'
-const emitGeolocationUpdate = (geolocation: Device['geolocation']) => {
-  events.emit(EVENT, geolocation)
-}
-const onGeolocationUpdate = (
-  listener: (geolocation: Device['geolocation']) => void,
-) => {
-  events.on(EVENT, listener)
-  return () => {
-    events.off(EVENT, listener)
-  }
-}
-
-/**
- * Default geolocation value. IF undefined, we fail closed and apply all
- * additional mod authorities.
- */
-export const DEFAULT_GEOLOCATION: Device['geolocation'] = {
-  countryCode: undefined,
-  isAgeBlockedGeo: undefined,
-  isAgeRestrictedGeo: false,
-}
-
-function sanitizeGeolocation(
-  geolocation: Device['geolocation'],
-): Device['geolocation'] {
-  return {
-    countryCode: geolocation?.countryCode ?? undefined,
-    isAgeBlockedGeo: geolocation?.isAgeBlockedGeo ?? false,
-    isAgeRestrictedGeo: geolocation?.isAgeRestrictedGeo ?? false,
-  }
-}
-
-async function getGeolocation(url: string): Promise<Device['geolocation']> {
-  const res = await fetch(url)
-
-  if (!res.ok) {
-    throw new Error(`geolocation: lookup failed ${res.status}`)
-  }
-
-  const json = await res.json()
-
-  if (json.countryCode) {
-    return {
-      countryCode: json.countryCode,
-      isAgeBlockedGeo: json.isAgeBlockedGeo ?? false,
-      isAgeRestrictedGeo: json.isAgeRestrictedGeo ?? false,
-      // @ts-ignore
-      regionCode: json.regionCode ?? undefined,
-    }
-  } else {
-    return undefined
-  }
-}
-
-async function compareWithIPCC(bapp: Device['geolocation']) {
-  try {
-    const ipcc = await getGeolocation(IPCC_URL)
-
-    if (!ipcc || !bapp) return
-
-    logger.metric(
-      'geo:debug',
-      {
-        bappCountryCode: bapp.countryCode,
-        // @ts-ignore
-        bappRegionCode: bapp.regionCode,
-        bappIsAgeBlockedGeo: bapp.isAgeBlockedGeo,
-        bappIsAgeRestrictedGeo: bapp.isAgeRestrictedGeo,
-        ipccCountryCode: ipcc.countryCode,
-        ipccIsAgeBlockedGeo: ipcc.isAgeBlockedGeo,
-        ipccIsAgeRestrictedGeo: ipcc.isAgeRestrictedGeo,
-      },
-      {
-        statsig: false,
-      },
-    )
-  } catch {}
-}
-
-/**
- * Local promise used within this file only.
- */
-let geolocationResolution: Promise<{success: boolean}> | undefined
-
-/**
- * Begin the process of resolving geolocation. This should be called once at
- * app start.
- *
- * THIS METHOD SHOULD NEVER THROW.
- *
- * This method is otherwise not used for any purpose. To ensure geolocation is
- * resolved, use {@link ensureGeolocationResolved}
- */
-export function beginResolveGeolocation() {
-  /**
-   * In dev, IP server is unavailable, so we just set the default geolocation
-   * and fail closed.
-   */
-  if (__DEV__) {
-    geolocationResolution = new Promise(y => y({success: true}))
-    if (!device.get(['geolocation'])) {
-      device.set(['geolocation'], DEFAULT_GEOLOCATION)
-    }
-    return
-  }
-
-  geolocationResolution = new Promise(async resolve => {
-    let success = true
-
-    try {
-      // Try once, fail fast
-      const geolocation = await getGeolocation(BAPP_CONFIG_URL)
-      if (geolocation) {
-        device.set(['geolocation'], sanitizeGeolocation(geolocation))
-        emitGeolocationUpdate(geolocation)
-        logger.debug(`geolocation: success`, {geolocation})
-        compareWithIPCC(geolocation)
-      } else {
-        // endpoint should throw on all failures, this is insurance
-        throw new Error(`geolocation: nothing returned from initial request`)
-      }
-    } catch (e: any) {
-      success = false
-
-      logger.debug(`geolocation: failed initial request`, {
-        safeMessage: e.message,
-      })
-
-      // set to default
-      device.set(['geolocation'], DEFAULT_GEOLOCATION)
-
-      // retry 3 times, but don't await, proceed with default
-      networkRetry(3, () => getGeolocation(BAPP_CONFIG_URL))
-        .then(geolocation => {
-          if (geolocation) {
-            device.set(['geolocation'], sanitizeGeolocation(geolocation))
-            emitGeolocationUpdate(geolocation)
-            logger.debug(`geolocation: success`, {geolocation})
-            success = true
-            compareWithIPCC(geolocation)
-          } else {
-            // endpoint should throw on all failures, this is insurance
-            throw new Error(`geolocation: nothing returned from retries`)
-          }
-        })
-        .catch((e: any) => {
-          // complete fail closed
-          logger.debug(`geolocation: failed retries`, {safeMessage: e.message})
-        })
-    } finally {
-      resolve({success})
-    }
-  })
-}
-
-/**
- * Ensure that geolocation has been resolved, or at the very least attempted
- * once. Subsequent retries will not be captured by this `await`. Those will be
- * reported via {@link events}.
- */
-export async function ensureGeolocationResolved() {
-  if (!geolocationResolution) {
-    throw new Error(`geolocation: beginResolveGeolocation not called yet`)
-  }
-
-  const cached = device.get(['geolocation'])
-  if (cached) {
-    logger.debug(`geolocation: using cache`, {cached})
-  } else {
-    logger.debug(`geolocation: no cache`)
-    const {success} = await geolocationResolution
-    if (success) {
-      logger.debug(`geolocation: resolved`, {
-        resolved: device.get(['geolocation']),
-      })
-    } else {
-      logger.error(`geolocation: failed to resolve`)
-    }
-  }
-}
-
-type Context = {
-  geolocation: Device['geolocation']
-}
-
-const context = React.createContext<Context>({
-  geolocation: DEFAULT_GEOLOCATION,
-})
-context.displayName = 'GeolocationContext'
-
-export function Provider({children}: {children: React.ReactNode}) {
-  const [geolocation, setGeolocation] = React.useState(() => {
-    const initial = device.get(['geolocation']) || DEFAULT_GEOLOCATION
-    return initial
-  })
-
-  React.useEffect(() => {
-    return onGeolocationUpdate(geolocation => {
-      setGeolocation(geolocation!)
-    })
-  }, [])
-
-  const ctx = React.useMemo(() => {
-    return {
-      geolocation,
-    }
-  }, [geolocation])
-
-  return <context.Provider value={ctx}>{children}</context.Provider>
-}
-
-export function useGeolocation() {
-  return React.useContext(context)
-}