diff --git a/libs/permissions/permissionLogic/src/lib/features/counting/counting.machine.ts b/libs/permissions/permissionLogic/src/lib/features/counting/counting.machine.ts index ed61ccc..97495ea 100644 --- a/libs/permissions/permissionLogic/src/lib/features/counting/counting.machine.ts +++ b/libs/permissions/permissionLogic/src/lib/features/counting/counting.machine.ts @@ -1,4 +1,4 @@ -import { assign, raise, sendTo, setup } from 'xstate'; +import { assign, log, not, raise, sendTo, setup } from 'xstate'; import { Permission, PermissionStatus, @@ -22,100 +22,121 @@ export const countingMachineThatNeedsPermissionAt3 = setup({ }, guards: { - requiresPermission: ({ context }) => + isPermissionRequiredToContinue: ({ context }) => context.count >= 3 && context.permissionStatus !== PermissionStatuses.granted, - countingCompleted: ({ context }) => context.count >= 5, + isCountingCompleted: ({ context }) => context.count >= 5, }, types: { context: {} as { count: number; permissionStatus: PermissionStatus }, - events: {} as + events: + {} as // TODO pull these out into their own discrete types and do a union here | { type: 'count.inc' } - | { type: 'permissionWasRequested'; permission: Permission } - | { - type: 'user.didTapBluetoothRequestPermission'; - permission: Permission; - }, + | { type: 'permissions.bluetooth.revoked' } + | { type: 'permissions.bluetooth.granted' } + | { type: 'permissions.bluetooth.denied' } + | { type: 'permissionWasRequested'; permission: Permission } + | { + type: 'user.didTapBluetoothRequestPermission'; + permission: Permission; + }, }, }).createMachine({ - /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOkwHsBXfAFwKhLH3QCMAbSAYguppIMwBtAAwBdRKAAO5WLjrl8EkAA9EAWgCMATgDMJACw6ArMOE6AbACZLRnVoDs+gDQgAnuq1aSN+wA4Hlhr6hlr6RgC+4S5oWHiEpDy09IzM7Fwi4kgg0rLyilmqCIEaJDoaRhrCGlbGxtUu7kX6JbYVRlYRUSAxOATEZFRJ+AxMrBwQnIIamVIycrgKSoXFpeWV1Za1FeYNiJZaviS+Rkb65r72ndEYvfEDvMkQuLBjXJJgAE6oz7IKJFAfdC0SAkdiUMA0cjkGjYDJKHLzRYFPblEimcwmPz2Lb1NyIYzmEgaS5+DQ2SLXWJ9BKDOjDEhPF5pCbvL4-Bb4BlMXAgsEQqEwuFZBF5JYooxo4QY4RYnE7PEIfRaYSrcldHpxfqJOkMRmvCaUWCfBm4CAAFXQkgAQmxwZDodgAEpgACO4NgNAACp9vrBfvghbNchyxUVUejMRc5bsECdLESdL4qlp2sTzPZzBTujdNTSHvS+faYQARblvH3sv4AoE0Xm2-kOwPZOai5FFbEGYlac6XGMYry2cxhexStMZrMa6n3IYMQsC7Cl-A8g1Gj4m82Wm12+fOt1wL0Vv0cpsikNtywd5r2bsXIwx+yBUrmbQZmqtSwTnNT7BAiBsejemyR4KLAnCsr6-oAOroLAu7urWEAni2Z6gIUSp6OY6LaCcxxBMIzgKloliEhoGxmBmyq+FRkRdPg5AQHASiTvE8LIUiqHqJY-gGMYpgWNYtgOARjRqBeejKjoD7aL46aeCmn5Unc2r0KxwbsSonHNDxJhmFYNh2I4MaaDJJBGEmSbdlhDgKbcWq0skozMqpiL5BxCCaERpRGAcMoOL4F7+L4MaJgYJwWNUxymMRMk2bm046iaTLjM5rZucIMb6NYBj+GZlijhm47ql+Sn2QW9ZFguZYQClKEaQg6UKichIOFo1TNJs76xVOyn0gAZgQzzYJANXqYUDWNOYrUGD2ZwaMcOjCBeXV3D++B-gBh7+vAwpsa5dWaJU3jYoY5TmGU6zCYg-jxqYSpJnYQTeRoNHhEAA */ - type: 'parallel', + /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAYgAcwAnVXWWXAe31gDoAjAGwFcwAXBhr2wtKYAG4MA1pADaABgC6iUGQb1ejfMpAAPRAFoAjADYArCwAshgJwAmABwBmC6YDst03YA0IAJ6JHQzlLazkrR1tHOUjHVwBfOJ80LDxCIhYwfHROSBJMBi58XhYCTHklJBBVdU1tPQR7IJZHY3tTW1tDQwtHa1NTH38EDosWe3s5Q1dJ02Mw21d4xJBknAJiDKyciBJy7WrcDSY6xGtrexZDRrlo+wtbMPvBxAXDFmiruVMJszlxhKSGDWaU22Q4uRkhgqKjUh1qlXqZwuVyCt3uj1szwQzmCrnsxiseIJ1kcpgBKyBqQ22HQ+AgHAIUAAClQaHRNLByKzaPQmCwoJRabxIOxuHwBEI9pUDkctAjEEFDOZ+u4LCS7v0OliOm8bMYHN97rYLHJnOTVlT0jS6Qz8MzueymJyKNQeZoWBBMrgRZwePxBNgpTCasd5QhumdmmZQk5rMZrPcLNr+s17LZ44Z0+FFubKet0r7xQGACJe3Iutm8-D8wVFH1i-2SxT7WGyk7htXWKOeP69eOJrGolh40kefGTOTGbq5lL50V+iXYUv4b07LiwKge3AQAAq6DIACEG4uAEpgACOPFgvBZrsd+CDVVb8NA9Sm7jGcjOU46E08Sb8BU3GHVxPBsNNHFiCwLCWQFZxBQtG2wM8JGkHYKzdPkBSFesFwDR8ZRfXREFMFoWDsaD4wcCI8VcLFAlcYdHHxRZwlMcCLBnYENkQ09xCkXJ103CBtz3Q9jwDM9LzgG8HSrAjn1DV8FScFhbHOQxmMmB4E2sbUphYYxAlCTx3FA4xjC4y15yLIQUIEtcN0oLdd33AA5dAxFwKB0GFHcGAAZT4DQ7VgBSQzlZThgM9w7msTTmNCLpjEHDpmjcExpjOVwpgJBJlnwBhPXgSoLXzFsIvbfROkcSwbGolx3E8TFAIQfR2ijKdzkCdSVVcTjljKkFMjBSAKrhJTiIaZj3jjWJnEg1pWnojxLlMNUv2iGMpisudrXpRlb0rDlxrbMNMwTd5JiiHVZnjbUIkuUlQm6rTJ1gil4J4iShGXVdTqIt98TeexznY6YLHGfU6Nasd3lMSdvnjAlNJcXaEJ+5D+LQgHJvqdNjDGIJ2i-eMvlCAChgY8jJwJSHGr6SzBrzEEADMCFobAxulRTIqmi7RhuTTok6O69NayDGJcTt02+frAjJfKgA */ + // type: 'parallel', context: { count: 0, permissionStatus: PermissionStatuses.unasked, }, + invoke: { + id: 'permissionReportingCounting', + systemId: 'permissionReportingCounting', + src: 'permissionReportingMachine', + input: ({ self }) => ({ + permissions: [Permissions.bluetooth], + parent: self, + }), + }, + on: { + 'permissions.bluetooth.revoked': { + target: '.bluetoothRevoked', + guard: not('isPermissionRequiredToContinue'), + }, + }, + + initial: 'enabled', + states: { - counting: { - initial: 'enabled', - states: { - enabled: { - always: [ - { - target: 'disabled', - guard: 'requiresPermission', - }, - { - target: 'finished', - guard: 'countingCompleted', - }, - ], - on: { - 'count.inc': { - actions: 'incrementCount', - }, - }, + enabled: { + always: [ + { + target: 'handlingPermissions', + guard: 'isPermissionRequiredToContinue', }, - disabled: { - id: 'countingDisabled', - on: { - 'permission.granted.bluetooth': { - target: 'enabled', - actions: 'assignBluetoothStatusGranted', - }, - 'permission.denied.bluetooth': { target: 'bluetoothDenied' }, - 'user.didTapBluetoothRequestPermission': { - actions: 'triggerBluetoothPermissionRequest', - }, - }, + { + target: 'finished', + guard: 'isCountingCompleted', }, - bluetoothDenied: { - on: { - 'permission.granted.bluetooth': { target: 'enabled' }, - 'user.didTapBluetoothRequestPermission': { - actions: 'triggerBluetoothPermissionRequest', - }, - }, - }, - finished: { - type: 'final', + ], + on: { + 'count.inc': { + actions: 'incrementCount', }, }, }, handlingPermissions: { on: { - permissionWasRequested: { + 'user.didTapBluetoothRequestPermission': { + actions: sendTo('permissionReportingCounting', ({ event }) => { + return { + type: 'requestPermission', + permission: Permissions.bluetooth, + }; + }), + }, + + 'permissions.bluetooth.granted': { + target: 'enabled', + actions: ['assignBluetoothStatusGranted'], + }, + 'permissions.bluetooth.denied': { target: 'bluetoothDenied' }, + }, + }, + + bluetoothDenied: { + on: { + 'permissions.bluetooth.granted': { + target: 'enabled', actions: [ - sendTo('permissionReportingCounting', ({ event }) => { - return { - type: 'requestPermission', - permission: event.permission, - }; + 'assignBluetoothStatusGranted', + log(({ event }) => { + console.log(JSON.stringify(event, null, 2)); }), ], }, + 'user.didTapBluetoothRequestPermission': { + actions: 'triggerBluetoothPermissionRequest', + }, }, - invoke: { - id: 'permissionReportingCounting', - systemId: 'permissionReportingCounting', - src: 'permissionReportingMachine', - input: ({ self }) => ({ - permissions: [Permissions.bluetooth], - parent: self, - }), + }, + + bluetoothRevoked: { + on: { + 'permissions.bluetooth.granted': { target: 'enabled' }, + 'user.didTapBluetoothRequestPermission': { + actions: 'triggerBluetoothPermissionRequest', + }, + 'user.didTapNavigateToSettings': { + actions: 'triggerNavigateToSettings', + }, }, }, + + finished: { + type: 'final', + }, }, }); diff --git a/libs/permissions/permissionLogic/src/lib/features/counting/counting.spec.ts b/libs/permissions/permissionLogic/src/lib/features/counting/counting.spec.ts index 90af2c9..14951d6 100644 --- a/libs/permissions/permissionLogic/src/lib/features/counting/counting.spec.ts +++ b/libs/permissions/permissionLogic/src/lib/features/counting/counting.spec.ts @@ -61,40 +61,33 @@ describe('Counting Machine That Needs Permission At 3', () => { const state: PermissionMonitoringSnapshot = permissionMonitorActor?.getSnapshot(); - console.log({ - v: state.context.permissionSubscribers[Permissions.bluetooth].map( - (s) => s.id - ), - }); - - expect( - state.context.permissionSubscribers[Permissions.bluetooth]?.length - ).toEqual(2); + // We should be able to find the permission coordinator for the Counting + // feature in the Permission Monitor's subscription map + const countingMachinePermissionCoordinator = + state.context.permissionSubscribers[Permissions.bluetooth]?.some( + (subscriber) => subscriber.id === 'permissionReportingCounting' + ); + expect(countingMachinePermissionCoordinator).toBeDefined(); const countingActor = applicationActor.system.get( ActorSystemIds.counting ); - expect(countingActor?.getSnapshot().value).toStrictEqual({ - counting: 'enabled', - handlingPermissions: {}, - }); + expect(countingActor?.getSnapshot().value).toStrictEqual('enabled'); countingActor.send({ type: 'count.inc' }); countingActor.send({ type: 'count.inc' }); countingActor.send({ type: 'count.inc' }); expect(countingActor.getSnapshot().context.count).toBe(3); - expect(countingActor.getSnapshot().value).toStrictEqual({ - counting: 'disabled', - handlingPermissions: {}, - }); + expect(countingActor.getSnapshot().value).toStrictEqual( + 'handlingPermissions' + ); countingActor.send({ type: 'count.inc' }); expect(countingActor.getSnapshot().context.count).toBe(3); - expect(countingActor.getSnapshot().value).toStrictEqual({ - counting: 'disabled', - handlingPermissions: {}, - }); + expect(countingActor.getSnapshot().value).toStrictEqual( + 'handlingPermissions' + ); // Configure the permission actor to grant permission const permissionCheckerActor = applicationActor.system.get( @@ -108,10 +101,7 @@ describe('Counting Machine That Needs Permission At 3', () => { (state) => state.value === 'idle' ); - expect(countingActor.getSnapshot().value).toStrictEqual({ - counting: 'enabled', - handlingPermissions: {}, - }); + expect(countingActor.getSnapshot().value).toStrictEqual('enabled'); expect(countingActor.getSnapshot().context.permissionStatus).toBe( PermissionStatuses.granted @@ -122,9 +112,9 @@ describe('Counting Machine That Needs Permission At 3', () => { countingActor.send({ type: 'count.inc' }); expect(countingActor.getSnapshot().context.count).toBe(5); - expect(countingActor.getSnapshot().value.counting).toStrictEqual( - 'finished' - ); + expect(countingActor.getSnapshot().value).toStrictEqual('finished'); + countingActor.send({ type: 'count.inc' }); + expect(countingActor.getSnapshot().context.count).toBe(5); ///* Required for debugging with stately inspector */ await new Promise((resolve) => setTimeout(resolve, vLongTime)); }, vLongTime @@ -136,10 +126,7 @@ describe('Counting Machine That Needs Permission At 3', () => { const countingActor = createActor( countingMachineThatNeedsPermissionAt3 ).start(); - expect(countingActor.getSnapshot().value).toStrictEqual({ - counting: 'enabled', - handlingPermissions: {}, - }); + expect(countingActor.getSnapshot().value).toStrictEqual('enabled'); }); it('should increment count', async () => { diff --git a/libs/permissions/permissionLogic/src/lib/features/features.machine.ts b/libs/permissions/permissionLogic/src/lib/features/features.machine.ts index a9b04a2..3db099d 100644 --- a/libs/permissions/permissionLogic/src/lib/features/features.machine.ts +++ b/libs/permissions/permissionLogic/src/lib/features/features.machine.ts @@ -1,18 +1,17 @@ import { log, setup } from 'xstate'; import { ActorSystemIds } from '../application/actorIds'; import { countingMachineThatNeedsPermissionAt3 } from './counting/counting.machine'; -import { someFeatureMachine } from './someFeature/someFeature.machine'; export const featuresMachine = setup({ types: {} as { children: { [ActorSystemIds.counting]: 'countingMachine'; - [ActorSystemIds.someFeature]: 'someFeatureMachine'; + // [ActorSystemIds.someFeature]: 'someFeatureMachine'; }; }, actors: { countingMachine: countingMachineThatNeedsPermissionAt3, - someFeatureMachine: someFeatureMachine, + // someFeatureMachine: someFeatureMachine, }, }).createMachine({ entry: log('Features started'), @@ -23,10 +22,10 @@ export const featuresMachine = setup({ systemId: ActorSystemIds.counting, src: 'countingMachine', }, - { - id: ActorSystemIds.someFeature, - systemId: ActorSystemIds.someFeature, - src: 'someFeatureMachine', - }, + // { + // id: ActorSystemIds.someFeature, + // systemId: ActorSystemIds.someFeature, + // src: 'someFeatureMachine', + // }, ], }); diff --git a/libs/permissions/permissionLogic/src/lib/permission/monitoring/permissionMonitor.machine.ts b/libs/permissions/permissionLogic/src/lib/permission/monitoring/permissionMonitor.machine.ts index 6621461..8e3c697 100644 --- a/libs/permissions/permissionLogic/src/lib/permission/monitoring/permissionMonitor.machine.ts +++ b/libs/permissions/permissionLogic/src/lib/permission/monitoring/permissionMonitor.machine.ts @@ -15,14 +15,14 @@ import { stubApplicationLifecycleReportingActorLogic } from '../../lifecycle/lif import { InitialPermissionStatusMap } from '../../permission.fixtures'; import { Permission } from '../../permission.types'; import { permissionCheckerAndRequesterMachine } from '../checkAndRequest/permissionCheckAndRequestMachine'; -import { - PermissionMonitoringMachineEvents, - PermissionSubscriberMap, -} from './permissionMonitor.types'; import { EmptyPermissionSubscriberMap, PermissionsMonitoringMachineContext, } from './permissionMonitor.fixtures'; +import { + PermissionMonitoringMachineEvents, + PermissionSubscriberMap, +} from './permissionMonitor.types'; export const permissionMonitoringMachine = setup({ types: {} as { @@ -42,6 +42,8 @@ export const permissionMonitoringMachine = setup({ actions: { assignPermissionCheckResultsToContext: assign({ permissionsStatuses: ({ event }) => { + console.log(JSON.stringify(event, null, 2)); + assertEvent(event, 'allPermissionsChecked'); return event.statuses; }, @@ -51,6 +53,10 @@ export const permissionMonitoringMachine = setup({ // TODO this should only send permission updates for the recently modified permissions // and is currently sending updates to all permissions to everyone Object.keys(context.permissionSubscribers).forEach((permission) => { + console.log(JSON.stringify({ permission }, null, 2)); + + console.log(JSON.stringify(context.permissionsStatuses, null, 2)); + context.permissionSubscribers[permission].forEach( (actorRef: AnyActorRef) => { enqueue.sendTo(actorRef, { @@ -65,6 +71,8 @@ export const permissionMonitoringMachine = setup({ ), assignPermissionRequestResultToContext: assign({ permissionsStatuses: ({ event, context }) => { + console.log(JSON.stringify(event, null, 2)); + assertEvent(event, 'permissionRequestCompleted'); return { ...context.permissionsStatuses, @@ -104,6 +112,8 @@ export const permissionMonitoringMachine = setup({ const { permissions } = event; const { permissionSubscribers } = context; + console.log(JSON.stringify({ event }, null, 2)); + // Create a new permissionSubscribers object to avoid mutating the original const updatedPermissionSubscribers: PermissionSubscriberMap = { ...permissionSubscribers, @@ -126,6 +136,8 @@ export const permissionMonitoringMachine = setup({ } }); + console.log(JSON.stringify({ updatedPermissionSubscribers }, null, 2)); + return { permissionSubscribers: updatedPermissionSubscribers, }; diff --git a/libs/permissions/permissionLogic/src/lib/permission/reporting/asdf b/libs/permissions/permissionLogic/src/lib/permission/reporting/asdf new file mode 100644 index 0000000..7524feb --- /dev/null +++ b/libs/permissions/permissionLogic/src/lib/permission/reporting/asdf @@ -0,0 +1,10 @@ +Feature needs to control + +in what state permission is required +when the thing is actually requeted + + + +Prereq machine needs to control +subscribe to permission updates from permission monitor machine +checking and updating its own context / context of the parent with up to pre req statuses \ No newline at end of file diff --git a/libs/permissions/permissionLogic/src/lib/permission/reporting/permissionReporting.machine.ts b/libs/permissions/permissionLogic/src/lib/permission/reporting/permissionReporting.machine.ts index 155dcb1..89a7ede 100644 --- a/libs/permissions/permissionLogic/src/lib/permission/reporting/permissionReporting.machine.ts +++ b/libs/permissions/permissionLogic/src/lib/permission/reporting/permissionReporting.machine.ts @@ -50,7 +50,7 @@ export const permissionReportingMachine = setup({ ), }, }).createMachine({ - /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAYgCcwBHAVzgBcAFMM1XWWXAe3wG0AGALqJQAB04c6XfMJAAPRAFoATADYAnADoA7AFYALDoDMevgb4AOIwBoQAT0QBGPRrVqlai050O3Sh1oBfAJs0LDxCUhFmVnYpAGU6dDpqWABhbHR8GAh+ISQQMQkpGXkEJSUXfzUtByUdHTVzKq0bewQFB0MdDSM6hrUHCzcVJSCQjBwCYg0Ad3RcSSySWVhEujANdAAzdbJkPlJQyYjZ+cWoXJlCheL80r0Wu0RvbvNTFXMVTsMftRUg4IgfCcCBwGRHcLEK7iG7cEqKBytRCGPgqDQOHS6FTGHSWQYqHRjEAQqZEU43LLQopwu6OHRo6p8GqDQw1PTmJRIhBfQwaJlNFHmQzmcx6B4AgJAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAYgCcwBHAVzgBcAFMM1XWWXAe3wG0AGALqJQAB04c6XfMJAAPRAFoATADYAnADoA7AFYALDoDMevgb4AOIwBoQAT0QBGPRrVqlai050O3Sh1oBfAJs0LDxCUhFmVnYpAGU6dDpqWABhbHR8GAh+ISQQMQkpGXkEQ3NNFRU+NX8+U3M+LS09G3sEZXMNbz4dJX0DPS1LJyCQjBwCYg0Ad3RcSSySWVhEujANdAAzdbJkPlJQyYjZ+cWoXJlCheL80qG2xG8dDUa9FXMVB0MfwzUVILBED4TgQOAyI7hYhXcQ3bglRQOR5lPgqDQOHS6HT6PzqIxjECQqZEU43LIworwu6OHRotRaJoOBx8QxaJzmJTIr6GDRNczfCzlcx6IaAgJAA */ 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.", context: ({ input }) => ({ @@ -114,7 +114,11 @@ machine. type: 'checkedSendParent', params({ event }) { const { permission, status } = event; - const permissionEventType = `permission.${status}.${permission}`; + console.log(JSON.stringify(event, null, 2)); + + const permissionEventType = `permissions.${permission}.${status}`; + console.log(JSON.stringify(permissionEventType, null, 2)); + return { type: permissionEventType }; }, },