From e99e0dac9e0e66b1a356aae8c8707c997d26c577 Mon Sep 17 00:00:00 2001 From: Michael Lustig Date: Wed, 13 Mar 2024 14:20:36 -0400 Subject: [PATCH 1/8] build: Add xstate dependency Added xstate "^5.9.1" as a dependency in package.json for state management. --- package-lock.json | 17 ++++++++++++++++- package.json | 3 ++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9610e3d..ea4e397 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.0.0", "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "tslib": "^2.3.0", + "xstate": "^5.9.1" }, "devDependencies": { "@nx/eslint": "18.0.8", @@ -10871,6 +10872,15 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/xstate": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.9.1.tgz", + "integrity": "sha512-85edx7iMqRJSRlEPevDwc98EWDYUlT5zEQ54AXuRVR+G76gFbcVTAUdtAeqOVxy8zYnUr9FBB5114iK6enljjw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/xstate" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -18921,6 +18931,11 @@ "signal-exit": "^3.0.7" } }, + "xstate": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.9.1.tgz", + "integrity": "sha512-85edx7iMqRJSRlEPevDwc98EWDYUlT5zEQ54AXuRVR+G76gFbcVTAUdtAeqOVxy8zYnUr9FBB5114iK6enljjw==" + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 67b9c2f..232b2e4 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "scripts": {}, "private": true, "dependencies": { - "tslib": "^2.3.0" + "tslib": "^2.3.0", + "xstate": "^5.9.1" }, "devDependencies": { "@nx/eslint": "18.0.8", From f773209b7bc2b64a7cabcf02401fbe3fd4886606 Mon Sep 17 00:00:00 2001 From: Michael Lustig Date: Wed, 13 Mar 2024 14:21:30 -0400 Subject: [PATCH 2/8] feat(permission): Add permission monitoring machine Added a permission monitoring machine with states for idle, checkingPermission, permissionGranted, permissionDenied, and permissionRevoked. The machine includes logic to check permissions asynchronously and update the permission state accordingly. This will help in monitoring and handling permission changes in the application. --- .../src/lib/permission-logic.ts | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 libs/permissions/permissionLogic/src/lib/permission-logic.ts diff --git a/libs/permissions/permissionLogic/src/lib/permission-logic.ts b/libs/permissions/permissionLogic/src/lib/permission-logic.ts new file mode 100644 index 0000000..e337e90 --- /dev/null +++ b/libs/permissions/permissionLogic/src/lib/permission-logic.ts @@ -0,0 +1,57 @@ +// export function permissionLogic(): string { +// return 'permissionLogic'; +// } + +// permissionMonitoringMachine.ts +import { actions, createMachine, interpret } from 'xstate'; + +const permissionMonitoringMachine = createMachine({ + id: 'permissionMonitoring', + initial: 'idle', + context: { + permissionState: undefined as 'granted' | 'denied' | 'revoked' | undefined, + }, + states: { + idle: { + on: { + REQUEST_PERMISSION: 'checkingPermission', + }, + }, + checkingPermission: { + invoke: { + src: 'checkPermission', + onDone: [ + { + target: 'permissionGranted', + cond: (_, event) => event.data === 'granted', + actions: actions.assign({ permissionState: 'granted' }), + }, + { + target: 'permissionDenied', + cond: (_, event) => event.data === 'denied', + actions: actions.assign({ permissionState: 'denied' }), + }, + ], + onError: 'permissionRevoked', + }, + }, + permissionGranted: {}, + permissionDenied: {}, + permissionRevoked: { + entry: actions.assign({ permissionState: 'revoked' }), + }, + }, +}); + +// Simulating the permission check +const checkPermission = async () => { + // Simulating the permission prompt + const permissionResult = 'granted'; + return permissionResult; +}; + +const permissionMonitoringService = interpret(permissionMonitoringMachine, { + services: { checkPermission }, +}).start(); + +export { permissionMonitoringMachine, permissionMonitoringService }; From 531d0127b10e12fb6b26a8f3d9dfeca7cc68606e Mon Sep 17 00:00:00 2001 From: Michael Lustig Date: Wed, 13 Mar 2024 17:28:09 -0400 Subject: [PATCH 3/8] claude attempt 2 --- .../src/lib/permission-logic.ts | 116 ++++++++++-------- 1 file changed, 68 insertions(+), 48 deletions(-) diff --git a/libs/permissions/permissionLogic/src/lib/permission-logic.ts b/libs/permissions/permissionLogic/src/lib/permission-logic.ts index e337e90..cc55947 100644 --- a/libs/permissions/permissionLogic/src/lib/permission-logic.ts +++ b/libs/permissions/permissionLogic/src/lib/permission-logic.ts @@ -1,57 +1,77 @@ -// export function permissionLogic(): string { -// return 'permissionLogic'; -// } +import { assign, createMachine } from 'xstate'; -// permissionMonitoringMachine.ts -import { actions, createMachine, interpret } from 'xstate'; +type PermissionEvent = + | { type: 'REQUEST_PERMISSION' } + | { type: 'PERMISSION_UPDATED'; permissionState: PermissionState }; -const permissionMonitoringMachine = createMachine({ - id: 'permissionMonitoring', - initial: 'idle', - context: { - permissionState: undefined as 'granted' | 'denied' | 'revoked' | undefined, - }, - states: { - idle: { - on: { - REQUEST_PERMISSION: 'checkingPermission', - }, +type PermissionState = 'granted' | 'denied' | 'revoked' | undefined; + +interface PermissionContext { + permissionState: PermissionState; +} + +const permissionMonitoringMachine = createMachine( + { + id: 'permissionMonitoring', + initial: 'idle', + context: { + permissionState: undefined, }, - checkingPermission: { - invoke: { - src: 'checkPermission', - onDone: [ - { - target: 'permissionGranted', - cond: (_, event) => event.data === 'granted', - actions: actions.assign({ permissionState: 'granted' }), - }, - { - target: 'permissionDenied', - cond: (_, event) => event.data === 'denied', - actions: actions.assign({ permissionState: 'denied' }), - }, - ], - onError: 'permissionRevoked', + states: { + idle: { + on: { + REQUEST_PERMISSION: 'checkingPermission', + }, + }, + checkingPermission: { + invoke: { + src: 'checkPermission', + onDone: [ + { + target: 'permissionGranted', + cond: (_, event) => event.data === 'granted', + actions: assign({ permissionState: (_, event) => event.data }), + }, + { + target: 'permissionDenied', + cond: (_, event) => event.data === 'denied', + actions: assign({ permissionState: (_, event) => event.data }), + }, + ], + onError: 'permissionRevoked', + }, + }, + permissionGranted: {}, + permissionDenied: {}, + permissionRevoked: { + entry: assign({ permissionState: 'revoked' }), }, }, - permissionGranted: {}, - permissionDenied: {}, - permissionRevoked: { - entry: actions.assign({ permissionState: 'revoked' }), + on: { + PERMISSION_UPDATED: { + actions: assign({ + permissionState: (_, event) => event.permissionState, + }), + }, }, }, -}); - -// Simulating the permission check -const checkPermission = async () => { - // Simulating the permission prompt - const permissionResult = 'granted'; - return permissionResult; -}; + { + services: { + checkPermission: async () => { + // Simulating the permission check + const permissionResult: PermissionState = 'granted'; + return permissionResult; + }, + }, + } +); -const permissionMonitoringService = interpret(permissionMonitoringMachine, { - services: { checkPermission }, -}).start(); +// Usage +/* +const permissionMonitoringService = createActor(permissionMonitoringMachine, { + id: 'permissionMonitoring', +}); -export { permissionMonitoringMachine, permissionMonitoringService }; +permissionMonitoringService.start(); +permissionMonitoringService.send({ type: 'REQUEST_PERMISSION' }); +*/ From eaa64e34c445207cb2c9038b9b639b07447e639b Mon Sep 17 00:00:00 2001 From: Michael Lustig Date: Mon, 18 Mar 2024 11:14:07 -0400 Subject: [PATCH 4/8] feat: Restructure permission monitoring machine using `setup` from xstate - Replaced `createMachine` with `setup` for defining the permissionMonitoringMachine - Updated the context and events types to include permissionStatuses and inc/dec events - Added increment and decrement actions for handling count updates - Changed the permissionState context to permissionStatuses using PermissionStatusMap - Updated the machine on events to trigger inc and dec actions This commit enhances the readability and organization of the permission monitoring machine by utilizing xstate's `setup` function. --- .../src/lib/permission-logic.ts | 110 +++++++----------- 1 file changed, 39 insertions(+), 71 deletions(-) diff --git a/libs/permissions/permissionLogic/src/lib/permission-logic.ts b/libs/permissions/permissionLogic/src/lib/permission-logic.ts index cc55947..b8cd063 100644 --- a/libs/permissions/permissionLogic/src/lib/permission-logic.ts +++ b/libs/permissions/permissionLogic/src/lib/permission-logic.ts @@ -1,77 +1,45 @@ -import { assign, createMachine } from 'xstate'; +import { assign, setup } from 'xstate'; -type PermissionEvent = - | { type: 'REQUEST_PERMISSION' } - | { type: 'PERMISSION_UPDATED'; permissionState: PermissionState }; +export const Permissions = { + bluetooth: 'bluetooth', + microphone: 'microphone', +} as const; +export type Permission = (typeof Permissions)[keyof typeof Permissions]; +export const PermissionStatuses = { + unasked: 'unasked', + granted: 'granted', + denied: 'denied', + blocked: 'blocked', +}; +export type PermissionStatus = + (typeof PermissionStatuses)[keyof typeof PermissionStatuses]; -type PermissionState = 'granted' | 'denied' | 'revoked' | undefined; +type PermissionStatusMapType = Record; +const PermissionStatusMap: PermissionStatusMapType = { + [Permissions.bluetooth]: PermissionStatuses.unasked, + [Permissions.microphone]: PermissionStatuses.unasked, +} as const; -interface PermissionContext { - permissionState: PermissionState; -} - -const permissionMonitoringMachine = createMachine( - { - id: 'permissionMonitoring', - initial: 'idle', - context: { - permissionState: undefined, - }, - states: { - idle: { - on: { - REQUEST_PERMISSION: 'checkingPermission', - }, - }, - checkingPermission: { - invoke: { - src: 'checkPermission', - onDone: [ - { - target: 'permissionGranted', - cond: (_, event) => event.data === 'granted', - actions: assign({ permissionState: (_, event) => event.data }), - }, - { - target: 'permissionDenied', - cond: (_, event) => event.data === 'denied', - actions: assign({ permissionState: (_, event) => event.data }), - }, - ], - onError: 'permissionRevoked', - }, - }, - permissionGranted: {}, - permissionDenied: {}, - permissionRevoked: { - entry: assign({ permissionState: 'revoked' }), - }, - }, - on: { - PERMISSION_UPDATED: { - actions: assign({ - permissionState: (_, event) => event.permissionState, - }), - }, - }, +const permissionMonitoringMachine = setup({ + types: { + context: {} as { permissionStatuses: PermissionStatusMapType }, + events: {} as { type: 'inc' } | { type: 'dec' }, + }, + actions: { + increment: assign({ + count: ({ context }) => context.count + 1, + }), + decrement: assign({ + count: ({ context }) => context.count - 1, + }), + }, + actors: {}, +}).createMachine({ + context: { permissionStatuses: PermissionStatusMap }, + on: { + inc: { actions: 'increment' }, + dec: { actions: 'decrement' }, }, - { - services: { - checkPermission: async () => { - // Simulating the permission check - const permissionResult: PermissionState = 'granted'; - return permissionResult; - }, - }, - } -); - -// Usage -/* -const permissionMonitoringService = createActor(permissionMonitoringMachine, { - id: 'permissionMonitoring', }); -permissionMonitoringService.start(); -permissionMonitoringService.send({ type: 'REQUEST_PERMISSION' }); -*/ +console.log(permissionMonitoringMachine); From e375f293a9674606374f56f0427cdc737c886d28 Mon Sep 17 00:00:00 2001 From: Michael Lustig Date: Mon, 18 Mar 2024 11:40:08 -0400 Subject: [PATCH 5/8] feat: Add test for happy path scenario in Permission Monitoring Machine --- .../permissionLogic/src/lib/permission-logic.spec.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts diff --git a/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts b/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts new file mode 100644 index 0000000..b0ce7e7 --- /dev/null +++ b/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts @@ -0,0 +1,9 @@ +// permissionMonitoringMachine.test.ts + +describe('Permission Monitoring Machine', () => { + describe('Happy path', () => { + it('should work', () => { + expect(true).toEqual(true); + }); + }); +}); From 93caf50d09a8c7c95a86cd2eebdb28a9c7076b97 Mon Sep 17 00:00:00 2001 From: Michael Lustig Date: Mon, 18 Mar 2024 15:17:16 -0400 Subject: [PATCH 6/8] thinking about where to provide implementations of specific apis --- .../src/lib/permission-logic.ts | 115 +++++++++++++++--- 1 file changed, 100 insertions(+), 15 deletions(-) diff --git a/libs/permissions/permissionLogic/src/lib/permission-logic.ts b/libs/permissions/permissionLogic/src/lib/permission-logic.ts index b8cd063..24caa85 100644 --- a/libs/permissions/permissionLogic/src/lib/permission-logic.ts +++ b/libs/permissions/permissionLogic/src/lib/permission-logic.ts @@ -1,4 +1,4 @@ -import { assign, setup } from 'xstate'; +import { fromCallback, raise, setup } from 'xstate'; export const Permissions = { bluetooth: 'bluetooth', @@ -10,7 +10,7 @@ export const PermissionStatuses = { granted: 'granted', denied: 'denied', blocked: 'blocked', -}; +} as const; export type PermissionStatus = (typeof PermissionStatuses)[keyof typeof PermissionStatuses]; @@ -20,26 +20,111 @@ const PermissionStatusMap: PermissionStatusMapType = { [Permissions.microphone]: PermissionStatuses.unasked, } as const; +type PermissionMonitoringMachineContext = { + permissionStatuses: PermissionStatusMapType; +}; +type PermissionMonitoringMachineEvents = + | { type: 'checkPermissions' } + | { + type: 'permissionChecked'; + permission: Permission; + status: PermissionStatus; + } + | { type: 'applicationForegrounded' } + | { type: 'applicationBackgrounded' }; + +const ApplicationLifecycleEvents = { + applicationForegrounded: 'applicationForegrounded', + applicationBackgrounded: 'applicationBackgrounded', +} as const; + +type ApplicationLifecycleEvent = + (typeof ApplicationLifecycleEvents)[keyof typeof ApplicationLifecycleEvents]; + +// type PermissionMonitoringMachineInput = { +// subscribeToApplicationLifecycleEvents: ( +// event: ApplicationLifecycleEvent +// ) => void; +// checkBluetoothPermission: () => Promise; +// checkMicrophonePermission: () => Promise; +// requestBluetoothPermission: () => Promise; +// requestMicrophonePermission: () => Promise; +// }; + const permissionMonitoringMachine = setup({ types: { - context: {} as { permissionStatuses: PermissionStatusMapType }, - events: {} as { type: 'inc' } | { type: 'dec' }, + // input: {} as PermissionMonitoringMachineInput, + context: {} as PermissionMonitoringMachineContext, + events: {} as PermissionMonitoringMachineEvents, }, actions: { - increment: assign({ - count: ({ context }) => context.count + 1, - }), - decrement: assign({ - count: ({ context }) => context.count - 1, - }), + triggerPermissionCheck: raise({ type: 'checkPermissions' }), + // decrement: assign({ + // count: ({ context }) => context.count - 1, + // }), + }, + actors: { + subscribeToApplicationLifecycleEvents: fromCallback( + ({ input, sendBack, receive, self, system }) => { + // ... + // i have to have a default implementation here... what should it be? + // I'm leaning towards unimplemented to avoid confusion + /* + can't "forward" input to child actor... + + input.subscribeToApplicationLifecycleEvents((event) => { + if (event === 'applicationForegrounded') { + sendBack({ type: 'applicationForegrounded' }); + } else if (event === 'applicationBackgrounded') { + sendBack({ type: 'applicationBackgrounded' }); + } + }); + */ + } + ), + bluetoothPermissionActor: fromCallback( + ({ input, sendBack, receive, self, system }) => { + const checkPermission = (): Promise => { + return Promise.resolve(PermissionStatuses.granted); + }; + + const requestPermission = (): Promise => { + return Promise.resolve(PermissionStatuses.granted); + }; + + receive(async (event) => { + if (event.type === 'checkPermissions') { + const result = await checkPermission(); + sendBack({ + type: 'permissionChecked', + permission: Permissions.bluetooth, + status: result, + }); + } else if (event.type === 'requestPermission') { + const result = await requestPermission(); + sendBack({ + type: 'permissionChecked', + permission: Permissions.bluetooth, + status: result, + }); + } + }); + // ... + // needs to listen for 'checkPermissions' and 'requestPermission' events and then + // return results back to parent actor + // also needs to ensure mapping of library type -> permission status type that we recognize and respond to + } + ), }, - actors: {}, }).createMachine({ + invoke: { + src: 'subscribeToApplicationLifecycleEvents', + id: 'applicationLifecycleEventsSubscriber', + input: ({ context }) => input.subscribeToApplicationLifecycleEvents, + }, context: { permissionStatuses: PermissionStatusMap }, on: { - inc: { actions: 'increment' }, - dec: { actions: 'decrement' }, + applicationForegrounded: { actions: 'triggerPermissionCheck' }, + applicationBackgrounded: {}, }, }); - -console.log(permissionMonitoringMachine); From cca72abb6463940bcb56ef011b3ff28b7b12d642 Mon Sep 17 00:00:00 2001 From: Michael Lustig Date: Mon, 18 Mar 2024 16:55:14 -0400 Subject: [PATCH 7/8] feat: Add initial tests and setup for Permission Monitoring Machine Test is logging out the correct result but failing for some reason... - Added initial test cases to check the starting state and permission checking functionality in the Permission Monitoring Machine. - Introduced constants for permissions, permission statuses, and machine states. - Defined context, events, and actions for the Permission Monitoring Machine setup. - Implemented actors for subscribing to application lifecycle events and checking Bluetooth permission status. --- .../src/lib/permission-logic.spec.ts | 270 +++++++++++++++++- .../src/lib/permission-logic.ts | 12 +- 2 files changed, 276 insertions(+), 6 deletions(-) diff --git a/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts b/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts index b0ce7e7..6cf232b 100644 --- a/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts +++ b/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts @@ -1,9 +1,273 @@ // permissionMonitoringMachine.test.ts describe('Permission Monitoring Machine', () => { - describe('Happy path', () => { - it('should work', () => { - expect(true).toEqual(true); + it('should start in application foregrounded state', () => { + const permissionMonitoringActor = createActor(permissionMonitoringMachine); + permissionMonitoringActor.start(); + + expect(permissionMonitoringActor.getSnapshot().value).toBe( + 'application is in foreground' + ); + }); + + it('should check permissions once invoked', () => { + const permissionMonitoringActor = createActor(permissionMonitoringMachine); + const initialPermissionMap: PermissionStatusMapType = { + bluetooth: 'unasked', + microphone: 'unasked', + }; + const expectedFinalPermissionMap: PermissionStatusMapType = { + bluetooth: 'granted', + microphone: 'granted', + }; + + expect(permissionMonitoringActor.getSnapshot().context).toStrictEqual({ + permissionStatuses: initialPermissionMap, + }); + + permissionMonitoringActor.start(); + + waitFor(permissionMonitoringActor, (state) => { + // console.log({ state }); + console.log(state.context.permissionStatuses); + return ( + state.context.permissionStatuses.bluetooth === 'granted' && + state.context.permissionStatuses.microphone === 'granted' + ); }); + + expect( + permissionMonitoringActor.getSnapshot().context.permissionStatuses + .microphone + ).toBe('granted'); }); }); + +import { + assign, + createActor, + fromCallback, + raise, + sendTo, + setup, + waitFor, +} from 'xstate'; + +export const Permissions = { + bluetooth: 'bluetooth', + microphone: 'microphone', +} as const; +export type Permission = (typeof Permissions)[keyof typeof Permissions]; +export const PermissionStatuses = { + unasked: 'unasked', + granted: 'granted', + denied: 'denied', + revoked: 'revoked', + blocked: 'blocked', +} as const; +export type PermissionStatus = + (typeof PermissionStatuses)[keyof typeof PermissionStatuses]; + +type PermissionStatusMapType = Record; +const PermissionStatusMap: PermissionStatusMapType = { + [Permissions.bluetooth]: PermissionStatuses.unasked, + [Permissions.microphone]: PermissionStatuses.unasked, +} as const; + +const PermissionMonitoringMachineStates = { + applicationInForeground: 'application is in foreground', + applicationInBackground: 'application is in background', +} as const; + +type PermissionMonitoringMachineContext = { + permissionStatuses: PermissionStatusMapType; +}; +type PermissionMonitoringMachineEvents = + | { type: 'checkPermissions' } + | { + type: 'permissionChecked'; + permission: Permission; + status: PermissionStatus; + } + | { type: 'applicationForegrounded' } + | { type: 'applicationBackgrounded' }; + +const ApplicationLifecycleEvents = { + applicationForegrounded: 'applicationForegrounded', + applicationBackgrounded: 'applicationBackgrounded', +} as const; + +type ApplicationLifecycleEvent = + (typeof ApplicationLifecycleEvents)[keyof typeof ApplicationLifecycleEvents]; + +// type PermissionMonitoringMachineInput = { +// subscribeToApplicationLifecycleEvents: ( +// event: ApplicationLifecycleEvent +// ) => void; +// checkBluetoothPermission: () => Promise; +// checkMicrophonePermission: () => Promise; +// requestBluetoothPermission: () => Promise; +// requestMicrophonePermission: () => Promise; +// }; + +const permissionMonitoringMachine = setup({ + types: { + // input: {} as PermissionMonitoringMachineInput, + context: {} as PermissionMonitoringMachineContext, + events: {} as PermissionMonitoringMachineEvents, + }, + actions: { + triggerPermissionCheck: raise({ type: 'checkPermissions' }), + }, + actors: { + subscribeToApplicationLifecycleEvents: fromCallback( + ({ input, sendBack, receive, self, system }) => { + // ... + // i have to have a default implementation here... what should it be? + // I'm leaning towards unimplemented to avoid confusion + /* + can't "forward" input to child actor... + + input.subscribeToApplicationLifecycleEvents((event) => { + if (event === 'applicationForegrounded') { + sendBack({ type: 'applicationForegrounded' }); + } else if (event === 'applicationBackgrounded') { + sendBack({ type: 'applicationBackgrounded' }); + } + }); + */ + } + ), + bluetoothPermissionActor: fromCallback( + ({ input, sendBack, receive, self, system }) => { + const checkPermission = (): Promise => { + return Promise.resolve(PermissionStatuses.granted); + }; + + const requestPermission = (): Promise => { + return Promise.resolve(PermissionStatuses.granted); + }; + + console.log('1'); + receive((event) => { + console.log({ event }); + if (event.type === 'checkPermissions') { + // const result = await (); + checkPermission().then((result) => { + console.log({ result }); + + sendBack({ + type: 'permissionChecked', + permission: Permissions.bluetooth, + status: result, + }); + }); + // sendBack({ + // type: 'permissionChecked', + // permission: Permissions.bluetooth, + // status: result + // }); + } else if (event.type === 'requestPermission') { + // const result = await requestPermission(); + // sendBack({ + // type: 'permissionChecked', + // permission: Permissions.bluetooth, + // status: result + // }); + } + }); + // ... + // needs to listen for 'checkPermissions' and 'requestPermission' events and then + // return results back to parent actor + // also needs to ensure mapping of library type -> permission status type that we recognize and respond to + } + ), + microphonePermissionActor: fromCallback( + ({ input, sendBack, receive, self, system }) => { + const checkPermission = (): Promise => { + return Promise.resolve(PermissionStatuses.granted); + }; + + const requestPermission = (): Promise => { + return Promise.resolve(PermissionStatuses.granted); + }; + + console.log('1'); + receive((event) => { + console.log({ event }); + if (event.type === 'checkPermissions') { + checkPermission().then((result) => { + sendBack({ + type: 'permissionChecked', + permission: Permissions.microphone, + status: result, + }); + }); + } else if (event.type === 'requestPermission') { + // const result = await requestPermission(); + // sendBack({ + // type: 'permissionChecked', + // permission: Permissions.bluetooth, + // status: result, + // }); + } + }); + // ... + // needs to listen for 'checkPermissions' and 'requestPermission' events and then + // return results back to parent actor + // also needs to ensure mapping of library type -> permission status type that we recognize and respond to + } + ), + }, +}).createMachine({ + initial: PermissionMonitoringMachineStates.applicationInForeground, + invoke: [ + { + src: 'subscribeToApplicationLifecycleEvents', + id: 'applicationLifecycleEventsSubscriber', + }, + { + id: 'bluetoothPermissionActor', + src: 'bluetoothPermissionActor', + }, + { + id: 'microphonePermissionActor', + src: 'microphonePermissionActor', + }, + ], + context: { permissionStatuses: PermissionStatusMap }, + on: { + applicationForegrounded: { + target: '.' + PermissionMonitoringMachineStates.applicationInForeground, + }, + applicationBackgrounded: { + target: '.' + PermissionMonitoringMachineStates.applicationInBackground, + }, + permissionChecked: { + actions: [ + () => { + console.log('hi'); + }, + assign({ + permissionStatuses: ({ context, event }) => { + console.log({ event }); + return { + ...context.permissionStatuses, + [event.permission]: event.status, + }; + }, + }), + ], + }, + }, + states: { + [PermissionMonitoringMachineStates.applicationInForeground]: { + entry: [ + 'triggerPermissionCheck', + sendTo('bluetoothPermissionActor', { type: 'checkPermissions' }), + sendTo('microphonePermissionActor', { type: 'checkPermissions' }), + ], + }, + [PermissionMonitoringMachineStates.applicationInBackground]: {}, + }, +}); diff --git a/libs/permissions/permissionLogic/src/lib/permission-logic.ts b/libs/permissions/permissionLogic/src/lib/permission-logic.ts index 24caa85..08be9f2 100644 --- a/libs/permissions/permissionLogic/src/lib/permission-logic.ts +++ b/libs/permissions/permissionLogic/src/lib/permission-logic.ts @@ -9,6 +9,7 @@ export const PermissionStatuses = { unasked: 'unasked', granted: 'granted', denied: 'denied', + revoked: 'revoked', blocked: 'blocked', } as const; export type PermissionStatus = @@ -120,11 +121,16 @@ const permissionMonitoringMachine = setup({ invoke: { src: 'subscribeToApplicationLifecycleEvents', id: 'applicationLifecycleEventsSubscriber', - input: ({ context }) => input.subscribeToApplicationLifecycleEvents, }, context: { permissionStatuses: PermissionStatusMap }, on: { - applicationForegrounded: { actions: 'triggerPermissionCheck' }, - applicationBackgrounded: {}, + applicationForegrounded: { target: 'application is in foreground' }, + applicationBackgrounded: { target: 'application is in background' }, + }, + states: { + 'application is in foreground': { + entry: ['triggerPermissionCheck'], + }, + 'application is in background': {}, }, }); From 800eb9eb6b2074a6269214665dc8271a0b2ff9a8 Mon Sep 17 00:00:00 2001 From: Michael Lustig Date: Mon, 18 Mar 2024 17:00:02 -0400 Subject: [PATCH 8/8] feat: Add async/await to permission check in Permission Monitoring Machine Updated the test case in the Permission Monitoring Machine to include async/await when checking permissions once invoked. This change was necessary to properly handle the asynchronous nature of the permission check operation. This ensures that the test case waits for the permission check to be completed before continuing, providing accurate results. --- .../permissionLogic/src/lib/permission-logic.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts b/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts index 6cf232b..96898c5 100644 --- a/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts +++ b/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts @@ -10,7 +10,7 @@ describe('Permission Monitoring Machine', () => { ); }); - it('should check permissions once invoked', () => { + it('should check permissions once invoked', async () => { const permissionMonitoringActor = createActor(permissionMonitoringMachine); const initialPermissionMap: PermissionStatusMapType = { bluetooth: 'unasked', @@ -27,7 +27,7 @@ describe('Permission Monitoring Machine', () => { permissionMonitoringActor.start(); - waitFor(permissionMonitoringActor, (state) => { + await waitFor(permissionMonitoringActor, (state) => { // console.log({ state }); console.log(state.context.permissionStatuses); return (