From 63f239d3d913e2c79b1867864dc6cb76956ee6dd Mon Sep 17 00:00:00 2001 From: Michael Lustig Date: Thu, 4 Apr 2024 16:19:17 -0400 Subject: [PATCH] Tim is da bomb.com --- .../src/lib/lifecycle/lifecycle.types.ts | 1 - .../src/lib/permission-logic.spec.ts | 148 ++++++++++++++---- .../lib/permissionCheckAndRequestMachine.ts | 5 +- .../src/lib/permissionMonitor.machine.ts | 19 +-- 4 files changed, 128 insertions(+), 45 deletions(-) diff --git a/libs/permissions/permissionLogic/src/lib/lifecycle/lifecycle.types.ts b/libs/permissions/permissionLogic/src/lib/lifecycle/lifecycle.types.ts index 058c5ef..0f81bb7 100644 --- a/libs/permissions/permissionLogic/src/lib/lifecycle/lifecycle.types.ts +++ b/libs/permissions/permissionLogic/src/lib/lifecycle/lifecycle.types.ts @@ -23,7 +23,6 @@ export type ApplicationStateChangeHandler = ( export const stubSubscribeToApplicationStateChanges = ( handleApplicationStateChange: ApplicationStateChangeHandler ) => { - console.log('subscribed to fake handler'); handleApplicationStateChange('applicationForegrounded'); return () => { diff --git a/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts b/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts index 109cf49..e0ba398 100644 --- a/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts +++ b/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts @@ -1,8 +1,11 @@ // permissionMonitoringMachine.test.ts import { ActorSystemIds } from './actorIds'; +import { createSkyInspector } from '@statelyai/inspect'; +import { WebSocket } from 'ws'; import { AnyActorRef, + AnyEventObject, assign, createActor, enqueueActions, @@ -340,13 +343,16 @@ describe('Permission Monitoring Machine', () => { EmptyPermissionSubscriberMap ); }); + const permissionReportingMachine = setup({ types: { input: {} as { permissions: Array; + parent: AnyActorRef; }, context: {} as { permissions: Array; + parent: AnyActorRef; }, }, actions: { @@ -364,24 +370,71 @@ describe('Permission Monitoring Machine', () => { }) ), // satisfies /*TODO type these events to the receiving machine event type*/ AnyEventObject); + checkedSendParent: enqueueActions( + ({ context, enqueue }, event: AnyEventObject) => { + if (!context.parent) { + console.log( + 'WARN: an attempt to send an event to a non-existent parent' + ); + return; + } + + enqueue.sendTo(context.parent, event); + } + ), }, }).createMachine({ description: "This actor's job is to report permission statuses to the actors that have invoked it. We abstract away this functionality so that it is reusable by any actor that needs it and so they don't need to know how permissions are checked. This keeps control centralized and easy to modify the behavior of.", id: ActorSystemIds.permissionReporting, - context: ({ input }) => ({ permissions: input.permissions }), + context: ({ input }) => ({ + permissions: input.permissions, + parent: input.parent, + }), entry: [ 'sendSubscriptionRequestForStatusUpdates', log('subscribe to status updates'), ], on: { permissionStatusChanged: { + description: + 'Whenever the Permission Monitoring machine reports that a permission status has changed, we receive this event and can process and share with our siblings.', // We eventually want to communicate this to the actors that have invoked us actions: [ log( ({ event }) => - event.permission + ' status changed' + ' to ' + event.status + event.permission + ' status << { on: { goToWaitingForPermission: 'waitingForPermission' }, }, waitingForPermission: { - entry: raise({ type: 'goToWaitingForPermission' }), on: { 'permission.granted.bluetooth': { target: 'bluetoothGranted' }, 'permission.denied.bluetooth': { target: 'bluetoothDenied' }, @@ -421,28 +473,10 @@ describe('Permission Monitoring Machine', () => { invoke: { id: 'permissionHandler', src: 'permissionReportingMachine', - input: { permissions: [Permissions.bluetooth] }, - }, - on: { - permissionStatusChanged: { - actions: [ - enqueueActions(({ context, event, enqueue }) => { - const { permission, status } = event; - console.log({ permission, status }); - if (permission === Permissions.bluetooth) { - if (status === PermissionStatuses.granted) { - enqueue.raise({ - type: 'permission.bluetooth.granted', - }); - } - } - }), - log( - ({ event }) => - event.permission + ' status changed' + ' to ' + event.status - ), - ], - }, + input: ({ self }) => ({ + permissions: [Permissions.bluetooth], + parent: self, + }), }, }, }, @@ -465,28 +499,74 @@ describe('Permission Monitoring Machine', () => { expect( state.context.permissionSubscribers[Permissions.bluetooth].length ).toEqual(1); + + const id = + state.context.permissionSubscribers[Permissions.bluetooth][0].id; + console.log({ id }); }); - it('should notify subscribers of changes to permissions', () => { - const actor = createActor( + it('should notify subscribers of changes to permissions', async () => { + const permissionMonitorActor = createActor( permissionMonitoringMachine.provide({ actors: { features: someFeatureMachine, }, }), { - parent: undefined, systemId: ActorSystemIds.permissionMonitoring, + inspect: createSkyInspector( + // @ts-expect-error + { inspectorType: 'node', WebSocket: WebSocket, autoStart: true } + ).inspect, } ).start(); - const state = actor.getSnapshot(); + const state = permissionMonitorActor.getSnapshot(); expect( state.context.permissionSubscribers[Permissions.bluetooth].length ).toEqual(1); - const child = Object.keys(actor.getSnapshot().children); - console.log({ child }); + const featureMachineActor = + permissionMonitorActor.getSnapshot().children.featuresMachineId; + expect(featureMachineActor?.getSnapshot().value).toStrictEqual({ + foo: 'waitingForPermission', + handlingPermissions: {}, + }); + + // featureMachineActor?.send({ + // type: 'sendPermissionRequest', + // permission: Permissions.bluetooth, + // }); + + expect(permissionMonitorActor.getSnapshot().value).toStrictEqual({ + applicationLifecycle: 'applicationIsInForeground', + permissions: {}, + }); + expect( + permissionMonitorActor.getSnapshot().context.permissionsStatuses + ).toStrictEqual({ + bluetooth: 'unasked', + microphone: 'unasked', + }); + + const permissionCheckerActor = + permissionMonitorActor.getSnapshot().children[ + ActorSystemIds.permissionCheckerAndRequester + ]; + + expect(permissionCheckerActor?.getSnapshot().value).toBe( + 'checkingPermissions' + ); + + await waitFor(permissionCheckerActor, (state) => { + return state.value === 'idle'; + }); + + expect(permissionCheckerActor?.getSnapshot().value).toBe('idle'); + expect(featureMachineActor?.getSnapshot().value).toStrictEqual({ + foo: 'bluetoothDenied', + handlingPermissions: {}, + }); }); describe('Edge Cases', () => { @@ -571,7 +651,7 @@ describe('Permission Monitoring Machine', () => { await waitFor(actorRef, (state) => { return ( - state.children.permissionCheckerAndRequesterMachineId.getSnapshot() + state.children.permissionCheckerAndRequesterMachineId!.getSnapshot() .value === 'idle' ); }); @@ -589,12 +669,12 @@ describe('Permission Monitoring Machine', () => { expect( actorRef .getSnapshot() - .children.permissionCheckerAndRequesterMachineId.getSnapshot().value + .children.permissionCheckerAndRequesterMachineId!.getSnapshot().value ).toBe('requestingPermission'); await waitFor(actorRef, (state) => { return ( - state.children.permissionCheckerAndRequesterMachineId.getSnapshot() + state.children.permissionCheckerAndRequesterMachineId!.getSnapshot() .value === 'idle' ); }); diff --git a/libs/permissions/permissionLogic/src/lib/permissionCheckAndRequestMachine.ts b/libs/permissions/permissionLogic/src/lib/permissionCheckAndRequestMachine.ts index 2aace56..f7db61a 100644 --- a/libs/permissions/permissionLogic/src/lib/permissionCheckAndRequestMachine.ts +++ b/libs/permissions/permissionLogic/src/lib/permissionCheckAndRequestMachine.ts @@ -65,6 +65,7 @@ export const permissionCheckerAndRequesterMachine = setup({ const result = // TODO how can i make this implementation more injectable and still ergnomic await unimplementedPermissionMachineActions.checkAllPermissions(); + console.log({ result }); return result; }), @@ -154,7 +155,9 @@ export const permissionCheckerAndRequesterMachine = setup({ checkingPermissions: { invoke: { src: 'checkAllPermissions', - onError: {}, + onError: { + actions: log('an error occurred checking permissions'), + }, onDone: { target: 'idle', actions: [ diff --git a/libs/permissions/permissionLogic/src/lib/permissionMonitor.machine.ts b/libs/permissions/permissionLogic/src/lib/permissionMonitor.machine.ts index 60ada7a..1fbc3ea 100644 --- a/libs/permissions/permissionLogic/src/lib/permissionMonitor.machine.ts +++ b/libs/permissions/permissionLogic/src/lib/permissionMonitor.machine.ts @@ -1,9 +1,3 @@ -import { - Permission, - PermissionMonitoringMachineEvents, - Permissions, - PermissionStatusMapType, -} from './permission.types'; import { AnyActorRef, assertEvent, @@ -14,11 +8,17 @@ import { sendTo, setup, } from 'xstate'; +import { ActorSystemIds } from './actorIds'; import { stubApplicationLifecycleReportingActorLogic } from './lifecycle/lifecycle.stubs'; -import { InitialPermissionStatusMap } from './permission.fixtures'; import { PermissionSubscriberMap } from './permission-logic.spec'; +import { InitialPermissionStatusMap } from './permission.fixtures'; +import { + Permission, + PermissionMonitoringMachineEvents, + PermissionStatusMapType, + Permissions, +} from './permission.types'; import { permissionCheckerAndRequesterMachine } from './permissionCheckAndRequestMachine'; -import { ActorSystemIds } from './actorIds'; export const EmptyPermissionSubscriberMap: PermissionSubscriberMap = Object.values(Permissions).reduce( @@ -148,7 +148,6 @@ export const permissionMonitoringMachine = setup({ }), }, }, - // entry: raise({ type: 'subscribeToPermissionStatuses', permissions: [] }), states: { applicationLifecycle: { on: { @@ -162,6 +161,8 @@ export const permissionMonitoringMachine = setup({ }, initial: 'applicationIsInForeground', invoke: { + id: ActorSystemIds.lifecycleReporting, + systemId: ActorSystemIds.lifecycleReporting, src: 'applicationLifecycleReportingMachine', },