@flighthq/permissions 0.3.0-next.906.07cea63 → 0.3.1-next.209.43ef1bc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,3 @@
1
+ export * from './enablePermissionGuards';
1
2
  export * from './permission';
2
3
  //# sourceMappingURL=contract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC"}
package/dist/contract.js CHANGED
@@ -1,2 +1,3 @@
1
+ export * from './enablePermissionGuards';
1
2
  export * from './permission';
2
3
  //# sourceMappingURL=contract.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function arePermissionGuardsEnabled(): boolean;
2
+ export declare function disablePermissionGuards(): void;
3
+ export declare function enablePermissionGuards(): void;
4
+ //# sourceMappingURL=enablePermissionGuards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enablePermissionGuards.d.ts","sourceRoot":"","sources":["../src/enablePermissionGuards.ts"],"names":[],"mappings":"AAMA,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED,wBAAgB,uBAAuB,IAAI,IAAI,CAG9C;AAMD,wBAAgB,sBAAsB,IAAI,IAAI,CAG7C"}
@@ -0,0 +1,29 @@
1
+ import { logOnce } from '@flighthq/log/contract';
2
+ import { LogLevel } from '@flighthq/types/contract';
3
+ import { setPermissionRequestFallbackGuard } from './permission';
4
+ export function arePermissionGuardsEnabled() {
5
+ return _enabled;
6
+ }
7
+ export function disablePermissionGuards() {
8
+ setPermissionRequestFallbackGuard(null);
9
+ _enabled = false;
10
+ }
11
+ // Installs diagnostics for the one silent degradation in this package: `requestPermission` on a name with
12
+ // no concrete request path falls back to a plain state query. Nothing throws, a plausible state comes back,
13
+ // and no OS prompt ever appears — so a caller waiting on a prompt waits forever with no signal that the
14
+ // name has no request route on this platform.
15
+ export function enablePermissionGuards() {
16
+ setPermissionRequestFallbackGuard(warnPermissionRequestFallback);
17
+ _enabled = true;
18
+ }
19
+ function warnPermissionRequestFallback(name, state) {
20
+ // Keyed per name: a second name with no request path is a different observation worth reporting, while
21
+ // the same name asked every frame is not.
22
+ logOnce(`permissions:request-fallback:${name}`, LogLevel.Warn, {
23
+ message: `requestPermission('${name}'): no request path for this name on this backend, so it fell back to a plain state query and NO prompt was shown; the resulting '${state}' is a read, not a decision`,
24
+ name,
25
+ state,
26
+ }, 'permissions');
27
+ }
28
+ let _enabled = false;
29
+ //# sourceMappingURL=enablePermissionGuards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enablePermissionGuards.js","sourceRoot":"","sources":["../src/enablePermissionGuards.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,EAAE,iCAAiC,EAAE,MAAM,cAAc,CAAC;AAEjE,MAAM,UAAU,0BAA0B;IACxC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,uBAAuB;IACrC,iCAAiC,CAAC,IAAI,CAAC,CAAC;IACxC,QAAQ,GAAG,KAAK,CAAC;AACnB,CAAC;AAED,0GAA0G;AAC1G,4GAA4G;AAC5G,wGAAwG;AACxG,8CAA8C;AAC9C,MAAM,UAAU,sBAAsB;IACpC,iCAAiC,CAAC,6BAA6B,CAAC,CAAC;IACjE,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC;AAED,SAAS,6BAA6B,CAAC,IAAoB,EAAE,KAAsB;IACjF,uGAAuG;IACvG,0CAA0C;IAC1C,OAAO,CACL,gCAAgC,IAAI,EAAE,EACtC,QAAQ,CAAC,IAAI,EACb;QACE,OAAO,EAAE,sBAAsB,IAAI,qIAAqI,KAAK,6BAA6B;QAC1M,IAAI;QACJ,KAAK;KACN,EACD,aAAa,CACd,CAAC;AACJ,CAAC;AAED,IAAI,QAAQ,GAAG,KAAK,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { getPermissionState, requestPermission } from './contract';
1
+ export { disablePermissionGuards, enablePermissionGuards, explainPermissionState, getPermissionState, getPermissionStates, requestPermission, } from './contract';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { getPermissionState, requestPermission } from './contract';
1
+ export { disablePermissionGuards, enablePermissionGuards, explainPermissionState, getPermissionState, getPermissionStates, requestPermission, } from './contract';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
@@ -1,7 +1,10 @@
1
- import type { PermissionBackend, PermissionName, PermissionState } from '@flighthq/types/contract';
1
+ import type { PermissionBackend, PermissionName, PermissionRequestFallbackGuard, PermissionState, PermissionStateExplanation } from '@flighthq/types/contract';
2
2
  export declare function createWebPermissionBackend(): PermissionBackend;
3
+ export declare function explainPermissionState(name: PermissionName): Promise<PermissionStateExplanation>;
3
4
  export declare function getPermissionBackend(): PermissionBackend;
4
5
  export declare function getPermissionState(name: PermissionName): Promise<PermissionState>;
6
+ export declare function getPermissionStates(names: readonly PermissionName[]): Promise<PermissionState[]>;
5
7
  export declare function requestPermission(name: PermissionName): Promise<PermissionState>;
6
8
  export declare function setPermissionBackend(backend: PermissionBackend | null): void;
9
+ export declare function setPermissionRequestFallbackGuard(guard: PermissionRequestFallbackGuard | null): void;
7
10
  //# sourceMappingURL=permission.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAOnG,wBAAgB,0BAA0B,IAAI,iBAAiB,CAS9D;AAGD,wBAAgB,oBAAoB,IAAI,iBAAiB,CAGxD;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAEjF;AAKD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAEhF;AAGD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAE5E"}
1
+ {"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,8BAA8B,EAC9B,eAAe,EACf,0BAA0B,EAC3B,MAAM,0BAA0B,CAAC;AAOlC,wBAAgB,0BAA0B,IAAI,iBAAiB,CAS9D;AAUD,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAiBtG;AAGD,wBAAgB,oBAAoB,IAAI,iBAAiB,CAMxD;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAEjF;AAMD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAEhG;AAKD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAEhF;AAGD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAE5E;AAKD,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,8BAA8B,GAAG,IAAI,GAAG,IAAI,CAEpG"}
@@ -13,10 +13,39 @@ export function createWebPermissionBackend() {
13
13
  },
14
14
  };
15
15
  }
16
+ // Reports WHY a state read came out the way it did, for the caller who got 'prompt' and cannot act on it.
17
+ // That one value means three different things — undecided, unqueryable name, or no Permissions API at all —
18
+ // and they have opposite remedies. Runs the same read as getPermissionState and reports which branch the
19
+ // web backend took, so it is a companion probe rather than a second source of truth.
20
+ //
21
+ // A custom backend has no branches to report: its answer is whatever it returned, described as 'decided'
22
+ // for a definite state and 'undecided' for 'prompt'. Only the built-in web backend can distinguish
23
+ // 'unqueryable' from 'unsupported', because only it knows whether navigator.permissions exists.
24
+ export async function explainPermissionState(name) {
25
+ const backend = getPermissionBackend();
26
+ if (backend !== _webBackend) {
27
+ const state = await backend.getState(name);
28
+ return { name, source: state === 'prompt' ? 'undecided' : 'decided', state };
29
+ }
30
+ const permissions = getWebPermissions();
31
+ if (permissions === null) {
32
+ return { name, source: 'unsupported', state: readWebFallbackPermissionState(name) };
33
+ }
34
+ try {
35
+ const status = await permissions.query({ name });
36
+ const state = status.state;
37
+ return { name, source: state === 'prompt' ? 'undecided' : 'decided', state };
38
+ }
39
+ catch {
40
+ return { name, source: 'unqueryable', state: readWebFallbackPermissionState(name) };
41
+ }
42
+ }
16
43
  // The active permission backend, lazily defaulting to the web backend. There is always a backend.
17
44
  export function getPermissionBackend() {
18
- if (_backend === null)
45
+ if (_backend === null) {
19
46
  _backend = createWebPermissionBackend();
47
+ _webBackend = _backend;
48
+ }
20
49
  return _backend;
21
50
  }
22
51
  // Resolves the current state of a named permission without prompting. Returns 'granted', 'denied',
@@ -24,6 +53,13 @@ export function getPermissionBackend() {
24
53
  export function getPermissionState(name) {
25
54
  return getPermissionBackend().getState(name);
26
55
  }
56
+ // Batch form of getPermissionState. Results are returned in INPUT ORDER as a parallel array rather than a
57
+ // keyed record: the caller may legitimately pass the same name twice, and an array preserves that where a
58
+ // record would silently collapse it. Queries run concurrently; a state read never throws, so neither does
59
+ // this.
60
+ export function getPermissionStates(names) {
61
+ return Promise.all(names.map((name) => getPermissionState(name)));
62
+ }
27
63
  // Requests a named permission, triggering the OS prompt where the platform supports it. Resolves to
28
64
  // the resulting state; a name with no request path falls back to a plain state query, and a missing
29
65
  // API resolves to a sentinel rather than throwing.
@@ -34,12 +70,17 @@ export function requestPermission(name) {
34
70
  export function setPermissionBackend(backend) {
35
71
  _backend = backend;
36
72
  }
37
- let _backend = null;
38
- // Maps a PermissionName to its Permissions-API query descriptor name. The built-in names are already
39
- // valid descriptor names, so this is identity for them; an unlisted name is queried under itself.
40
- function getPermissionQueryDescriptorName(name) {
41
- return _permissionQueryDescriptors[name] ?? name;
73
+ // Diagnostics seam for the silent degradation in requestPermission: a name with no concrete request path
74
+ // resolves to a plain state query, so no prompt ever appears and the caller cannot tell. Core stays
75
+ // message-free; enablePermissionGuards installs the reporter.
76
+ export function setPermissionRequestFallbackGuard(guard) {
77
+ _requestFallbackGuard = guard;
42
78
  }
79
+ let _backend = null;
80
+ let _requestFallbackGuard = null;
81
+ // Identity of the lazily-created default, so explainPermissionState can tell whether the active backend is
82
+ // the web one whose branches it knows how to describe.
83
+ let _webBackend = null;
43
84
  // The per-name fallback state when the Permissions API is absent or the name is unqueryable.
44
85
  // notifications is still readable synchronously via Notification.permission ('default' → 'prompt');
45
86
  // every other name resolves to the 'prompt' sentinel.
@@ -55,9 +96,7 @@ async function readWebPermissionState(name) {
55
96
  const permissions = getWebPermissions();
56
97
  if (permissions !== null) {
57
98
  try {
58
- const status = await permissions.query({
59
- name: getPermissionQueryDescriptorName(name),
60
- });
99
+ const status = await permissions.query({ name });
61
100
  return status.state;
62
101
  }
63
102
  catch {
@@ -109,6 +148,23 @@ async function requestWebNotificationPermission() {
109
148
  return 'prompt';
110
149
  }
111
150
  }
151
+ // Observes a MIDI access grant purely for the prompt; the access object is discarded, mirroring the
152
+ // discarded geolocation position. sysex is not requested — that is a strictly larger prompt than the
153
+ // 'midi' permission this name denotes.
154
+ async function requestWebMidiPermission() {
155
+ if (typeof navigator === 'undefined')
156
+ return 'prompt';
157
+ const request = navigator.requestMIDIAccess;
158
+ if (typeof request !== 'function')
159
+ return 'prompt';
160
+ try {
161
+ await request.call(navigator);
162
+ return 'granted';
163
+ }
164
+ catch {
165
+ return 'denied';
166
+ }
167
+ }
112
168
  async function requestWebPersistentStoragePermission() {
113
169
  const storage = getWebStorageManager();
114
170
  if (storage === null || typeof storage.persist !== 'function')
@@ -120,11 +176,33 @@ async function requestWebPersistentStoragePermission() {
120
176
  return 'prompt';
121
177
  }
122
178
  }
179
+ // Takes a screen wake lock and releases it immediately — the prompt is the only thing wanted, the same
180
+ // shape as getUserMedia's stop-tracks. Holding the lock would be a lasting side effect the caller did not
181
+ // ask for; releasing keeps the trigger observation-only.
182
+ async function requestWebScreenWakeLockPermission() {
183
+ if (typeof navigator === 'undefined')
184
+ return 'prompt';
185
+ const wakeLock = navigator
186
+ .wakeLock;
187
+ if (wakeLock === undefined || typeof wakeLock.request !== 'function')
188
+ return 'prompt';
189
+ try {
190
+ const sentinel = await wakeLock.request('screen');
191
+ if (typeof sentinel?.release === 'function')
192
+ await sentinel.release();
193
+ return 'granted';
194
+ }
195
+ catch {
196
+ return 'denied';
197
+ }
198
+ }
123
199
  async function requestWebPermission(name) {
124
200
  const router = _permissionRequestRouters[name];
125
201
  if (router !== undefined)
126
202
  return await router();
127
- return await readWebPermissionState(name);
203
+ const state = await readWebPermissionState(name);
204
+ _requestFallbackGuard?.(name, state);
205
+ return state;
128
206
  }
129
207
  // Stops every track of a granted media stream, releasing the device. Guards a missing getTracks so a
130
208
  // stubbed or partial stream never throws.
@@ -168,23 +246,13 @@ function getWebStorageManager() {
168
246
  return null;
169
247
  return navigator.storage ?? null;
170
248
  }
171
- const _permissionQueryDescriptors = {
172
- camera: 'camera',
173
- microphone: 'microphone',
174
- geolocation: 'geolocation',
175
- notifications: 'notifications',
176
- 'clipboard-read': 'clipboard-read',
177
- 'clipboard-write': 'clipboard-write',
178
- 'persistent-storage': 'persistent-storage',
179
- push: 'push',
180
- midi: 'midi',
181
- 'screen-wake-lock': 'screen-wake-lock',
182
- };
183
249
  const _permissionRequestRouters = {
184
250
  camera: () => requestWebMediaPermission('video'),
185
251
  microphone: () => requestWebMediaPermission('audio'),
186
252
  geolocation: requestWebGeolocationPermission,
187
253
  notifications: requestWebNotificationPermission,
188
254
  'persistent-storage': requestWebPersistentStoragePermission,
255
+ midi: requestWebMidiPermission,
256
+ 'screen-wake-lock': requestWebScreenWakeLockPermission,
189
257
  };
190
258
  //# sourceMappingURL=permission.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"permission.js","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"permission.js","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "name": "@flighthq/permissions",
3
- "version": "0.3.0-next.906.07cea63",
3
+ "version": "0.3.1-next.209.43ef1bc",
4
+ "author": "Joshua Granick and other contributors",
5
+ "license": "MIT",
4
6
  "repository": {
5
7
  "type": "git",
6
8
  "url": "https://github.com/flighthq/flight.git",
@@ -36,7 +38,8 @@
36
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
37
39
  },
38
40
  "dependencies": {
39
- "@flighthq/types": "0.3.0-next.906.07cea63"
41
+ "@flighthq/log": "0.3.1-next.209.43ef1bc",
42
+ "@flighthq/types": "0.3.1-next.209.43ef1bc"
40
43
  },
41
44
  "devDependencies": {
42
45
  "typescript": "^5.3.0"
@@ -0,0 +1,83 @@
1
+ import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log/contract';
2
+ import type { LogEntry } from '@flighthq/types/contract';
3
+ import { afterEach, describe, expect, it, vi } from 'vitest';
4
+
5
+ import { arePermissionGuardsEnabled, disablePermissionGuards, enablePermissionGuards } from './enablePermissionGuards';
6
+ import { requestPermission, setPermissionBackend } from './permission';
7
+
8
+ function captureLog(run: () => Promise<void>): Promise<readonly LogEntry[]> {
9
+ const sink = createMemoryLogSink(8);
10
+ addLogSink(sink.sink);
11
+ return run()
12
+ .then(() => getMemoryLogSinkEntries(sink))
13
+ .finally(() => removeLogSink(sink.sink));
14
+ }
15
+
16
+ function messageOf(entry: Readonly<LogEntry>): string {
17
+ const data = entry.data;
18
+ return typeof data === 'string' ? data : String(data.message);
19
+ }
20
+
21
+ afterEach(() => {
22
+ disablePermissionGuards();
23
+ setPermissionBackend(null);
24
+ vi.unstubAllGlobals();
25
+ });
26
+
27
+ describe('arePermissionGuardsEnabled', () => {
28
+ it('reports whether diagnostics are installed', () => {
29
+ expect(arePermissionGuardsEnabled()).toBe(false);
30
+ enablePermissionGuards();
31
+ expect(arePermissionGuardsEnabled()).toBe(true);
32
+ disablePermissionGuards();
33
+ expect(arePermissionGuardsEnabled()).toBe(false);
34
+ });
35
+ });
36
+
37
+ describe('disablePermissionGuards', () => {
38
+ it('returns the silent degradation to silence', async () => {
39
+ vi.stubGlobal('navigator', { permissions: { query: async () => ({ state: 'prompt' }) } });
40
+ enablePermissionGuards();
41
+ disablePermissionGuards();
42
+
43
+ const entries = await captureLog(async () => {
44
+ await requestPermission('acme.disabled-name');
45
+ });
46
+
47
+ expect(entries.length).toBe(0);
48
+ });
49
+ });
50
+
51
+ describe('enablePermissionGuards', () => {
52
+ // logOnce suppresses a key for the whole PROCESS, so the fire and the stays-quiet assertions live in
53
+ // ONE test in order, and every test in this file uses a distinct permission name.
54
+ it('WARNS that a request showed no prompt, then suppresses the repeat', async () => {
55
+ vi.stubGlobal('navigator', { permissions: { query: async () => ({ state: 'prompt' }) } });
56
+ enablePermissionGuards();
57
+
58
+ const entries = await captureLog(async () => {
59
+ await requestPermission('acme.no-request-path');
60
+ });
61
+ expect(entries.length).toBe(1);
62
+ // The consequence is the point: a caller waiting on a prompt waits forever.
63
+ expect(messageOf(entries[0])).toContain('NO prompt was shown');
64
+ expect(messageOf(entries[0])).toContain('acme.no-request-path');
65
+
66
+ const repeat = await captureLog(async () => {
67
+ await requestPermission('acme.no-request-path');
68
+ });
69
+ expect(repeat.length).toBe(0);
70
+ });
71
+
72
+ it('stays SILENT for a name that has a real request path', async () => {
73
+ vi.stubGlobal('navigator', { permissions: { query: async () => ({ state: 'prompt' }) } });
74
+ vi.stubGlobal('Notification', { permission: 'default', requestPermission: async () => 'granted' });
75
+ enablePermissionGuards();
76
+
77
+ const entries = await captureLog(async () => {
78
+ expect(await requestPermission('notifications')).toBe('granted');
79
+ });
80
+
81
+ expect(entries.length).toBe(0);
82
+ });
83
+ });
@@ -3,10 +3,13 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  import {
5
5
  createWebPermissionBackend,
6
+ explainPermissionState,
6
7
  getPermissionBackend,
7
8
  getPermissionState,
9
+ getPermissionStates,
8
10
  requestPermission,
9
11
  setPermissionBackend,
12
+ setPermissionRequestFallbackGuard,
10
13
  } from './permission';
11
14
 
12
15
  // A recording mock backend: relays a fixed state and captures the last name each method saw.
@@ -149,10 +152,95 @@ describe('createWebPermissionBackend', () => {
149
152
  });
150
153
 
151
154
  it('request falls back to a state query for a name with no request path', async () => {
155
+ // 'push' has no concrete web trigger; midi and screen-wake-lock now do, so they are no longer
156
+ // examples of this branch.
152
157
  const query = vi.fn(async () => ({ state: 'granted' }));
153
158
  vi.stubGlobal('navigator', { permissions: { query } });
159
+ expect(await createWebPermissionBackend().request('push')).toBe('granted');
160
+ expect(query).toHaveBeenCalledWith({ name: 'push' });
161
+ });
162
+
163
+ it('request routes midi through requestMIDIAccess, discarding the access object', async () => {
164
+ const requestMIDIAccess = vi.fn(async () => ({ inputs: 'discarded' }));
165
+ vi.stubGlobal('navigator', { requestMIDIAccess });
154
166
  expect(await createWebPermissionBackend().request('midi')).toBe('granted');
155
- expect(query).toHaveBeenCalledWith({ name: 'midi' });
167
+ expect(requestMIDIAccess).toHaveBeenCalledOnce();
168
+ });
169
+
170
+ it('request reports midi denial rather than throwing', async () => {
171
+ vi.stubGlobal('navigator', {
172
+ requestMIDIAccess: async () => {
173
+ throw new Error('denied');
174
+ },
175
+ });
176
+ expect(await createWebPermissionBackend().request('midi')).toBe('denied');
177
+ });
178
+
179
+ it('request takes a screen wake lock and RELEASES it immediately', async () => {
180
+ const release = vi.fn(async () => {});
181
+ const request = vi.fn(async () => ({ release }));
182
+ vi.stubGlobal('navigator', { wakeLock: { request } });
183
+ expect(await createWebPermissionBackend().request('screen-wake-lock')).toBe('granted');
184
+ expect(request).toHaveBeenCalledWith('screen');
185
+ // Holding the lock would be a lasting side effect the caller never asked for.
186
+ expect(release).toHaveBeenCalledOnce();
187
+ });
188
+
189
+ it('request resolves to a sentinel when the wake-lock API is absent, and denied when it rejects', async () => {
190
+ vi.stubGlobal('navigator', {});
191
+ expect(await createWebPermissionBackend().request('screen-wake-lock')).toBe('prompt');
192
+ vi.stubGlobal('navigator', {
193
+ wakeLock: {
194
+ request: async () => {
195
+ throw new Error('denied');
196
+ },
197
+ },
198
+ });
199
+ expect(await createWebPermissionBackend().request('screen-wake-lock')).toBe('denied');
200
+ });
201
+ });
202
+
203
+ describe('explainPermissionState', () => {
204
+ afterEach(() => {
205
+ setPermissionBackend(null);
206
+ vi.unstubAllGlobals();
207
+ });
208
+
209
+ it('separates the three meanings of a bare prompt: undecided, unqueryable, and unsupported', async () => {
210
+ // No Permissions API at all.
211
+ vi.stubGlobal('navigator', {});
212
+ expect(await explainPermissionState('camera')).toMatchObject({ source: 'unsupported', state: 'prompt' });
213
+
214
+ // Present but rejects this descriptor name.
215
+ vi.stubGlobal('navigator', {
216
+ permissions: {
217
+ query: () => Promise.reject(new Error('bad name')),
218
+ },
219
+ });
220
+ setPermissionBackend(null);
221
+ expect(await explainPermissionState('acme.unknown')).toMatchObject({ source: 'unqueryable' });
222
+
223
+ // Present, queryable, genuinely not decided.
224
+ vi.stubGlobal('navigator', { permissions: { query: () => Promise.resolve({ state: 'prompt' }) } });
225
+ setPermissionBackend(null);
226
+ expect(await explainPermissionState('camera')).toMatchObject({ source: 'undecided', state: 'prompt' });
227
+ });
228
+
229
+ it('reports a definite answer as decided, and carries the name through', async () => {
230
+ vi.stubGlobal('navigator', { permissions: { query: () => Promise.resolve({ state: 'granted' }) } });
231
+ expect(await explainPermissionState('geolocation')).toEqual({
232
+ name: 'geolocation',
233
+ source: 'decided',
234
+ state: 'granted',
235
+ });
236
+ });
237
+
238
+ it('describes a custom backend without claiming branches it cannot observe', async () => {
239
+ // A host backend has no Permissions API to inspect, so unqueryable/unsupported are not assertable.
240
+ setPermissionBackend(fakeBackend('prompt'));
241
+ expect(await explainPermissionState('camera')).toMatchObject({ source: 'undecided' });
242
+ setPermissionBackend(fakeBackend('denied'));
243
+ expect(await explainPermissionState('camera')).toMatchObject({ source: 'decided', state: 'denied' });
156
244
  });
157
245
  });
158
246
 
@@ -178,6 +266,27 @@ describe('getPermissionState', () => {
178
266
  });
179
267
  });
180
268
 
269
+ describe('getPermissionStates', () => {
270
+ afterEach(() => setPermissionBackend(null));
271
+
272
+ it('resolves in INPUT order, preserving a repeated name rather than collapsing it', async () => {
273
+ const states: Record<string, PermissionState> = { camera: 'granted', microphone: 'denied' };
274
+ setPermissionBackend({
275
+ getState: (name: PermissionName) => Promise.resolve(states[name] ?? 'prompt'),
276
+ request: () => Promise.resolve('prompt' as PermissionState),
277
+ });
278
+
279
+ expect(await getPermissionStates(['microphone', 'camera', 'microphone'])).toEqual(['denied', 'granted', 'denied']);
280
+ });
281
+
282
+ it('returns an empty array for no names, without touching the backend', async () => {
283
+ const backend = fakeBackend('granted');
284
+ setPermissionBackend(backend);
285
+ expect(await getPermissionStates([])).toEqual([]);
286
+ expect(backend.lastGetState).toBeNull();
287
+ });
288
+ });
289
+
181
290
  describe('requestPermission', () => {
182
291
  it('dispatches to the active backend and relays the resolved state', async () => {
183
292
  const backend = fakeBackend('granted');
@@ -198,3 +307,33 @@ describe('setPermissionBackend', () => {
198
307
  expect(restored.getState).toBeInstanceOf(Function);
199
308
  });
200
309
  });
310
+
311
+ describe('setPermissionRequestFallbackGuard', () => {
312
+ afterEach(() => {
313
+ setPermissionRequestFallbackGuard(null);
314
+ setPermissionBackend(null);
315
+ vi.unstubAllGlobals();
316
+ });
317
+
318
+ it('reports only the names that silently degraded to a query, and stops once cleared', async () => {
319
+ const seen: string[] = [];
320
+ // No request path for 'push'; 'notifications' has one via Notification.requestPermission.
321
+ vi.stubGlobal('navigator', { permissions: { query: () => Promise.resolve({ state: 'prompt' }) } });
322
+ vi.stubGlobal('Notification', {
323
+ permission: 'default',
324
+ requestPermission: () => Promise.resolve('granted'),
325
+ });
326
+ setPermissionRequestFallbackGuard((name) => seen.push(name));
327
+
328
+ expect(await requestPermission('push')).toBe('prompt');
329
+ expect(seen).toEqual(['push']);
330
+
331
+ // A name WITH a router prompts for real and must not be reported as a fallback.
332
+ expect(await requestPermission('notifications')).toBe('granted');
333
+ expect(seen).toEqual(['push']);
334
+
335
+ setPermissionRequestFallbackGuard(null);
336
+ await requestPermission('push');
337
+ expect(seen).toEqual(['push']);
338
+ });
339
+ });