forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {type LocationGeocodedAddress} from 'expo-location'
2
3import {IS_ANDROID} from '#/env'
4import {logger} from '#/geolocation/logger'
5import {type Geolocation} from '#/geolocation/types'
6import {device} from '#/storage'
7
8/**
9 * Maps full US region names to their short codes.
10 *
11 * Context: in some cases, like on Android, we get the full region name instead
12 * of the short code. We may need to expand this in the future to other
13 * countries, hence the prefix.
14 */
15export const USRegionNameToRegionCode: {
16 [regionName: string]: string
17} = {
18 Alabama: 'AL',
19 Alaska: 'AK',
20 Arizona: 'AZ',
21 Arkansas: 'AR',
22 California: 'CA',
23 Colorado: 'CO',
24 Connecticut: 'CT',
25 Delaware: 'DE',
26 Florida: 'FL',
27 Georgia: 'GA',
28 Hawaii: 'HI',
29 Idaho: 'ID',
30 Illinois: 'IL',
31 Indiana: 'IN',
32 Iowa: 'IA',
33 Kansas: 'KS',
34 Kentucky: 'KY',
35 Louisiana: 'LA',
36 Maine: 'ME',
37 Maryland: 'MD',
38 Massachusetts: 'MA',
39 Michigan: 'MI',
40 Minnesota: 'MN',
41 Mississippi: 'MS',
42 Missouri: 'MO',
43 Montana: 'MT',
44 Nebraska: 'NE',
45 Nevada: 'NV',
46 ['New Hampshire']: 'NH',
47 ['New Jersey']: 'NJ',
48 ['New Mexico']: 'NM',
49 ['New York']: 'NY',
50 ['North Carolina']: 'NC',
51 ['North Dakota']: 'ND',
52 Ohio: 'OH',
53 Oklahoma: 'OK',
54 Oregon: 'OR',
55 Pennsylvania: 'PA',
56 ['Rhode Island']: 'RI',
57 ['South Carolina']: 'SC',
58 ['South Dakota']: 'SD',
59 Tennessee: 'TN',
60 Texas: 'TX',
61 Utah: 'UT',
62 Vermont: 'VT',
63 Virginia: 'VA',
64 Washington: 'WA',
65 ['West Virginia']: 'WV',
66 Wisconsin: 'WI',
67 Wyoming: 'WY',
68}
69
70/**
71 * Normalizes a `LocationGeocodedAddress` into a `Geolocation`.
72 *
73 * We don't want or care about the full location data, so we trim it down and
74 * normalize certain fields, like region, into the format we need.
75 */
76export function normalizeDeviceLocation(
77 location: LocationGeocodedAddress,
78): Geolocation {
79 let {isoCountryCode, region} = location
80 let regionCode: string | undefined = region ?? undefined
81
82 /*
83 * Android doesn't give us ISO 3166-2 short codes. We need these for US
84 */
85 if (IS_ANDROID) {
86 if (region && isoCountryCode === 'US') {
87 /*
88 * We need short codes for US states. If we can't remap it, just drop it
89 * entirely for now.
90 */
91 regionCode = USRegionNameToRegionCode[region] ?? undefined
92 } else {
93 /*
94 * Outside the US, we don't need regionCodes for now, so just drop it.
95 */
96 regionCode = undefined
97 }
98 }
99
100 return {
101 countryCode: isoCountryCode ?? undefined,
102 regionCode,
103 }
104}
105
106/**
107 * Combines precise location data with the geolocation config fetched from the
108 * IP service, with preference to the precise data.
109 */
110export function mergeGeolocations(
111 device?: Geolocation,
112 geolocationService?: Geolocation,
113): Geolocation {
114 let geolocation: Geolocation = {
115 countryCode: geolocationService?.countryCode ?? undefined,
116 regionCode: geolocationService?.regionCode ?? undefined,
117 }
118 // prefer GPS
119 if (device?.countryCode) {
120 geolocation = device
121 }
122 logger.debug('merged geolocation data', {
123 device,
124 service: geolocationService,
125 merged: geolocation,
126 })
127 return geolocation
128}
129
130/**
131 * Gets the IP-based geolocation as a string in the format of
132 * "countryCode-regionCode", or just "countryCode" if regionCode is not
133 * available.
134 *
135 * IMPORTANT: this method should only return IP-based data, not the user's GPS
136 * based data. IP-based data we can already infer from requests, but for
137 * consistency between frontend and backend, we sometimes want to share the
138 * value we have on the frontend with the backend.
139 */
140export function getIPGeolocationString() {
141 const geo = device.get(['geolocationServiceResponse'])
142 if (!geo) return
143 const {countryCode, regionCode} = geo
144 if (countryCode) {
145 if (regionCode) {
146 return `${countryCode}-${regionCode}`
147 } else {
148 return countryCode
149 }
150 }
151}