Skip to content

Commit 502cafd

Browse files
authored
Merge pull request Expensify#72891 from ShridharGoel/desktopUpdates
Follow up for location improvements in desktop
2 parents 3811b88 + d9edec9 commit 502cafd

7 files changed

Lines changed: 98 additions & 42 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,9 +305,10 @@ jobs:
305305
with:
306306
python-version: '3.12'
307307
cache: 'pip'
308+
cache-dependency-path: desktop/requirements.txt
308309

309310
- name: Ensure setuptools for node-gyp
310-
run: python -m pip install --upgrade pip setuptools
311+
run: pip install --upgrade -r desktop/requirements.txt
311312

312313
- name: Load Desktop credentials from 1Password
313314
id: load-credentials
@@ -332,7 +333,6 @@ jobs:
332333
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
333334
GCP_GEOLOCATION_API_KEY: ${{ secrets.GCP_GEOLOCATION_API_KEY_PRODUCTION }}
334335
S3_BUCKET: ${{ github.ref == 'refs/heads/production' && vars.PRODUCTION_S3_BUCKET || vars.STAGING_S3_BUCKET }}
335-
PYTHON: ${{ steps.setup-python.outputs.python-path }}
336336

337337
- name: Upload desktop sourcemaps artifact
338338
# v4

.github/workflows/testBuild.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,10 @@ jobs:
192192
with:
193193
python-version: '3.12'
194194
cache: 'pip'
195+
cache-dependency-path: desktop/requirements.txt
195196

196197
- name: Ensure setuptools for node-gyp
197-
run: python -m pip install --upgrade pip setuptools
198+
run: pip install --upgrade -r desktop/requirements.txt
198199

199200
- name: Load Desktop credentials from 1Password
200201
id: load-credentials
@@ -226,7 +227,6 @@ jobs:
226227
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
227228
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
228229
GCP_GEOLOCATION_API_KEY: ${{ secrets.GCP_GEOLOCATION_API_KEY_STAGING }}
229-
PYTHON: ${{ steps.setup-python.outputs.python-path }}
230230

231231
web:
232232
name: Build and deploy Web

cspell.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,8 @@
767767
"Dtype",
768768
"Areport",
769769
"mple",
770-
"Selec"
770+
"Selec",
771+
"setuptools"
771772
],
772773
"ignorePaths": [
773774
"src/languages/de.ts",

desktop/main.ts

Lines changed: 57 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import CONFIG from '@src/CONFIG';
1414
import CONST from '@src/CONST';
1515
import IntlStore from '@src/languages/IntlStore';
1616
import type {TranslationPaths} from '@src/languages/types';
17+
import type {LocationPermissionState} from '@src/libs/getCurrentPosition/locationPermission';
18+
import {LOCATION_PERMISSION_STATES} from '@src/libs/getCurrentPosition/locationPermission';
1719
import type PlatformSpecificUpdater from '@src/setup/platformSetup/types';
1820
import type {Locale} from '@src/types/onyx';
1921
import type {CreateDownloadQueueModule, DownloadItem} from './createDownloadQueue';
@@ -32,11 +34,53 @@ const MAC_PERMISSION_STATUSES = {
3234
NOT_DETERMINED: 'not determined',
3335
} as const;
3436

35-
const LOCATION_PERMISSION_STATES = {
36-
GRANTED: 'granted',
37-
DENIED: 'denied',
38-
PROMPT: 'prompt',
39-
} as const;
37+
type MacPermissionsModule = {
38+
getAuthStatus?: (authType: AuthType) => PermissionType | typeof MAC_PERMISSION_STATUSES.NOT_DETERMINED;
39+
};
40+
41+
type MacGetAuthStatus = MacPermissionsModule['getAuthStatus'];
42+
43+
let macGetAuthStatusPromise: Promise<MacGetAuthStatus | undefined> | undefined;
44+
45+
const logMacPermissionsWarning = (message: string, error?: unknown) => {
46+
if (error instanceof Error) {
47+
log.warn(message, error.message);
48+
} else if (typeof error === 'string') {
49+
log.warn(message, error);
50+
} else {
51+
log.warn(message);
52+
}
53+
};
54+
55+
const loadMacGetAuthStatus = async (): Promise<MacGetAuthStatus | undefined> => {
56+
if (!macGetAuthStatusPromise) {
57+
if (process.platform !== 'darwin') {
58+
macGetAuthStatusPromise = Promise.resolve<MacGetAuthStatus | undefined>(undefined);
59+
} else {
60+
try {
61+
macGetAuthStatusPromise = Promise.resolve(((await import('node-mac-permissions')) as MacPermissionsModule).getAuthStatus);
62+
} catch (error: unknown) {
63+
logMacPermissionsWarning('node-mac-permissions not available, defaulting to denied:', error);
64+
return undefined;
65+
}
66+
}
67+
}
68+
69+
return macGetAuthStatusPromise;
70+
};
71+
72+
const resolveLocationPermissionStatus = (status: PermissionType | typeof MAC_PERMISSION_STATUSES.NOT_DETERMINED): LocationPermissionState => {
73+
switch (status) {
74+
case MAC_PERMISSION_STATUSES.AUTHORIZED:
75+
return LOCATION_PERMISSION_STATES.GRANTED;
76+
case MAC_PERMISSION_STATUSES.NOT_DETERMINED:
77+
return LOCATION_PERMISSION_STATES.PROMPT;
78+
case MAC_PERMISSION_STATUSES.DENIED:
79+
case MAC_PERMISSION_STATUSES.RESTRICTED:
80+
default:
81+
return LOCATION_PERMISSION_STATES.DENIED;
82+
}
83+
};
4084

4185
// Setup google api key in process environment, we are setting it this way intentionally. It is required by the
4286
// geolocation api (window.navigator.geolocation.getCurrentPosition) to work on desktop.
@@ -390,34 +434,16 @@ const mainWindow = (): Promise<void> => {
390434
});
391435

392436
ipcMain.handle(ELECTRON_EVENTS.CHECK_LOCATION_PERMISSION, async () => {
437+
const getAuthStatus = await loadMacGetAuthStatus();
438+
439+
if (!getAuthStatus) {
440+
return LOCATION_PERMISSION_STATES.DENIED;
441+
}
442+
393443
try {
394-
type MacPermissionsModule = {
395-
getAuthStatus?: (authType: AuthType) => PermissionType | 'not determined';
396-
};
397-
const macPermissionsModule = (await import('node-mac-permissions')) as MacPermissionsModule | {default: MacPermissionsModule};
398-
const macPermissions: MacPermissionsModule = ('default' in macPermissionsModule ? macPermissionsModule.default : macPermissionsModule) ?? {};
399-
const {getAuthStatus} = macPermissions;
400-
401-
if (!getAuthStatus || typeof getAuthStatus !== 'function') {
402-
log.warn('node-mac-permissions not available or invalid, defaulting to denied');
403-
return LOCATION_PERMISSION_STATES.DENIED;
404-
}
405-
406-
const status = getAuthStatus('location');
407-
408-
switch (status) {
409-
case MAC_PERMISSION_STATUSES.AUTHORIZED:
410-
return LOCATION_PERMISSION_STATES.GRANTED;
411-
case MAC_PERMISSION_STATUSES.DENIED:
412-
case MAC_PERMISSION_STATUSES.RESTRICTED:
413-
return LOCATION_PERMISSION_STATES.DENIED;
414-
case MAC_PERMISSION_STATUSES.NOT_DETERMINED:
415-
return LOCATION_PERMISSION_STATES.PROMPT;
416-
default:
417-
return LOCATION_PERMISSION_STATES.DENIED;
418-
}
444+
return resolveLocationPermissionStatus(getAuthStatus('location'));
419445
} catch (error) {
420-
log.warn('node-mac-permissions not available, defaulting to denied:', (error as Error)?.message);
446+
log.warn('node-mac-permissions threw while checking location permission, defaulting to denied:', (error as Error)?.message);
421447
return LOCATION_PERMISSION_STATES.DENIED;
422448
}
423449
});

desktop/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
setuptools==80.9.0

src/libs/getCurrentPosition/index.desktop.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import type {ValueOf} from 'type-fest';
12
import type {GetCurrentPosition} from './getCurrentPosition.types';
23
import {GeolocationErrorCode} from './getCurrentPosition.types';
4+
import type {LocationPermissionState} from './locationPermission';
5+
import {LOCATION_PERMISSION_STATES} from './locationPermission';
36

4-
const makeError = (code: (typeof GeolocationErrorCode)[keyof typeof GeolocationErrorCode], message: string) => ({
7+
const makeError = (code: ValueOf<typeof GeolocationErrorCode>, message: string) => ({
58
code,
69
message,
710
PERMISSION_DENIED: GeolocationErrorCode.PERMISSION_DENIED,
@@ -10,12 +13,22 @@ const makeError = (code: (typeof GeolocationErrorCode)[keyof typeof GeolocationE
1013
NOT_SUPPORTED: GeolocationErrorCode.NOT_SUPPORTED,
1114
});
1215

16+
const isLocationPermissionState = (status: unknown): status is LocationPermissionState =>
17+
typeof status === 'string' && Object.values(LOCATION_PERMISSION_STATES).includes(status as LocationPermissionState);
18+
1319
const getCurrentPosition: GetCurrentPosition = (success, error, options) => {
1420
const doGeoRequest = () => {
1521
try {
1622
navigator.geolocation.getCurrentPosition(success, error, options);
17-
} catch (e) {
18-
error(makeError(GeolocationErrorCode.POSITION_UNAVAILABLE, String(e ?? 'Geolocation call failed')));
23+
} catch (caughtError) {
24+
let reason = 'Geolocation call failed';
25+
if (caughtError instanceof Error) {
26+
reason = caughtError.message;
27+
} else if (typeof caughtError === 'string') {
28+
reason = caughtError;
29+
}
30+
31+
error(makeError(GeolocationErrorCode.POSITION_UNAVAILABLE, reason));
1932
}
2033
};
2134

@@ -24,17 +37,20 @@ const getCurrentPosition: GetCurrentPosition = (success, error, options) => {
2437
window.electron
2538
.invoke('check-location-permission')
2639
.then((permissionStatus: unknown) => {
27-
const status = String(permissionStatus);
40+
if (!isLocationPermissionState(permissionStatus)) {
41+
error(makeError(GeolocationErrorCode.PERMISSION_DENIED, 'Unable to verify location permissions. Enable location access and try again.'));
42+
return;
43+
}
2844

29-
if (status === 'denied') {
45+
if (permissionStatus === LOCATION_PERMISSION_STATES.DENIED) {
3046
error(makeError(GeolocationErrorCode.PERMISSION_DENIED, 'Location access denied. Enable location permissions in system settings.'));
3147
return;
3248
}
3349

3450
doGeoRequest();
3551
})
3652
.catch(() => {
37-
doGeoRequest(); // Fallback to direct geolocation
53+
error(makeError(GeolocationErrorCode.PERMISSION_DENIED, 'Unable to verify location permissions. Enable location access and try again.'));
3854
});
3955

4056
return; // handled via IPC
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type {ValueOf} from 'type-fest';
2+
3+
const LOCATION_PERMISSION_STATES = {
4+
GRANTED: 'granted',
5+
DENIED: 'denied',
6+
PROMPT: 'prompt',
7+
} as const;
8+
9+
type LocationPermissionState = ValueOf<typeof LOCATION_PERMISSION_STATES>;
10+
11+
export type {LocationPermissionState};
12+
export {LOCATION_PERMISSION_STATES};

0 commit comments

Comments
 (0)