@kehto/services 0.21.0 → 0.21.2
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.
- package/README.md +43 -7
- package/dist/index.d.ts +21 -14
- package/dist/index.js +45 -35
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ Current draft posture:
|
|
|
43
43
|
- `createKeysService` and `createMediaService` ship real reference backends as of v1.4 (see the dedicated sections below). `createKeysService` attaches a document-level `keydown` listener by default and delivers `keys.action` push envelopes to registered napplets; `createMediaService` mirrors session metadata and playback state to `navigator.mediaSession` and emits `media.command` push envelopes on OS transport events. Both accept a host-bridge option (`HostKeysBridge` / `HostMediaBridge`) so Electron / Tauri / native shells can swap in OS-level backends without re-implementing the wire-protocol bookkeeping.
|
|
44
44
|
- `createNotifyService` handles direct `notify.*` envelopes. It is not an INC
|
|
45
45
|
topic handler.
|
|
46
|
-
- `createOutboxService` supports `outbox.getEvent` from draft NAP-OUTBOX. Single-event lookups run through shell-owned relay routing and only return events whose ID matches the request. The draft wire contract keeps `outbox.query` one-shot and `outbox.subscribe` streaming; Kehto's concrete `createRelayPoolOutboxRouter` additionally exposes host-side `queryStream()` so verified query events can arrive before asynchronous NIP-65 discovery completes.
|
|
46
|
+
- `createOutboxService` supports `outbox.getEvent` from draft NAP-OUTBOX. Single-event lookups run through shell-owned relay routing and only return events whose ID matches the request. Hosts can provide `getReadRouter` to scope all event-returning reads (`getEvent`, query, and subscription) to the authenticated source window, plus `getQueryRouter` when one-shot queries need an additional decorator. The draft wire contract keeps `outbox.query` one-shot and `outbox.subscribe` streaming; Kehto's concrete `createRelayPoolOutboxRouter` additionally exposes host-side `queryStream()` so verified query events can arrive before asynchronous NIP-65 discovery completes.
|
|
47
47
|
- `createResourceService` implements NAP-RESOURCE at exact draft head `9511232f69313aa7953d110e35d32cc28d506f66` without choosing a runtime's network policy. Single requests carry optional advisory Blossom `servers`; bulk requests use canonical per-resource `requests: [{ url, servers? }]` entries. The service forwards hints to the injected resolver only for `blossom:`, preserves order and per-entry failures, and exposes optional `resource.info.maxServers`. By default it delegates every syntactically valid URL to the resolver; `resource.info.schemes` remains advisory. Runtimes that want hierarchical-origin grants can supply `isOriginGranted`, `getConnectGrants`, and `resolveIdentity` together, while scheme resolvers retain responsibility for hint validation, SSRF protection, redirects, and fallback policy. The service enforces supplied size/bulk caps, scopes cancellation to the requesting window, drops cancelled terminal envelopes, never forwards response headers, and preserves explicit `ResourceServiceError` codes.
|
|
48
48
|
- `createUploadService` supports `upload.info` from draft NAP-UPLOAD. Hosts may expose configured rails, return URL forms, maximum bytes, and MIME type policy without requiring napplets to start an upload.
|
|
49
49
|
- `createConfigService` implements draft NAP-CONFIG's shell-writer boundary: recursive bounded-subset schema validation, per-window schema/subscription state, deterministic defaults, invalid/orphan removal, version rollback rejection, scoped host persistence hooks, and settings-UI commits. Reads before schema registration fail closed with `no-schema`.
|
|
@@ -251,6 +251,8 @@ export interface HostKeysBridge {
|
|
|
251
251
|
* - invoke `callback` exactly once per matching chord event (implementations
|
|
252
252
|
* are responsible for any OS-autorepeat filtering)
|
|
253
253
|
* - invoke `callback` synchronously during the event delivery
|
|
254
|
+
* - remove only this subscription when its unsubscribe handle is called;
|
|
255
|
+
* other callbacks for the same chord must remain live
|
|
254
256
|
* - accept the string chord format documented by @napplet/nap/keys
|
|
255
257
|
* (e.g. `'Ctrl+Shift+K'`, `'Cmd+P'`)
|
|
256
258
|
*/
|
|
@@ -300,16 +302,33 @@ keys.destroy();
|
|
|
300
302
|
Custom bridge path — swap in Electron's `globalShortcut`:
|
|
301
303
|
|
|
302
304
|
```ts
|
|
303
|
-
import { createKeysService, type HostKeysBridge } from '@kehto/services';
|
|
305
|
+
import { createKeysService, type HostKeysBridge, type HostKeyEvent } from '@kehto/services';
|
|
304
306
|
import { globalShortcut } from 'electron';
|
|
305
307
|
|
|
308
|
+
const subscribers = new Map<string, Set<(event: HostKeyEvent) => void>>();
|
|
306
309
|
const electronBridge: HostKeysBridge = {
|
|
307
310
|
subscribe(chord, cb) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
let callbacks = subscribers.get(chord);
|
|
312
|
+
if (!callbacks) {
|
|
313
|
+
const group = new Set<(event: HostKeyEvent) => void>();
|
|
314
|
+
const registered = globalShortcut.register(chord, () => {
|
|
315
|
+
for (const callback of group) callback({
|
|
316
|
+
key: '', code: '',
|
|
317
|
+
ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
if (!registered) throw new Error(`Cannot register ${chord}`);
|
|
321
|
+
callbacks = group;
|
|
322
|
+
subscribers.set(chord, callbacks);
|
|
323
|
+
}
|
|
324
|
+
callbacks.add(cb);
|
|
325
|
+
return () => {
|
|
326
|
+
if (!callbacks.delete(cb)) return;
|
|
327
|
+
if (callbacks.size === 0) {
|
|
328
|
+
globalShortcut.unregister(chord);
|
|
329
|
+
subscribers.delete(chord);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
313
332
|
},
|
|
314
333
|
};
|
|
315
334
|
|
|
@@ -322,6 +341,23 @@ Plug a `HostKeysBridge` when the default document listener is insufficient: Elec
|
|
|
322
341
|
|
|
323
342
|
A napplet drives this end to end via `@napplet/sdk` — `keys.registerAction` to claim a chord and `keys.onAction` to receive dispatches against the real backend. After a successful bound registration, the service pushes a complete `keys.bindings` list for that napplet/window, using entries shaped as `{ actionId, key }`. It pushes the complete list again after `keys.unregisterAction` removes a binding, including an empty list when no bindings remain. Injected shims use that list to suppress locally-bound keydowns before forwarding.
|
|
324
343
|
|
|
344
|
+
Action IDs are local to the trusted window passed by the runtime. Two windows may
|
|
345
|
+
each register `game.a`, including with the same suggested key, without replacing
|
|
346
|
+
one another's bindings. Unregistering or destroying one window removes only its
|
|
347
|
+
actions and bridge subscriptions. Binding lists and `keys.action` messages retain
|
|
348
|
+
the original app-local action ID; callers do not need to invent window-specific IDs.
|
|
349
|
+
Re-registering an action in the same window retains the existing rebind behavior:
|
|
350
|
+
a valid available chord replaces that window's binding, a reserved chord removes
|
|
351
|
+
its previous binding, and a malformed chord or failed bridge subscription leaves
|
|
352
|
+
the previous binding intact.
|
|
353
|
+
|
|
354
|
+
This ownership scope is an explicit Kehto policy for the scope left implicit by
|
|
355
|
+
[NAP-KEYS PR #9 at `cecb642`](https://github.com/napplet/naps/blob/cecb64257e0ac29926bb746832a477c553ab307c/naps/NAP-KEYS.md).
|
|
356
|
+
The draft describes duplicate-action errors but does not mandate service-global
|
|
357
|
+
IDs or forbid same-owner updates. Window-scoped ownership preserves its per-napplet
|
|
358
|
+
binding delivery and adds no wire fields. Chord conflicts retain the existing
|
|
359
|
+
behavior: matching registered actions receive their own `keys.action` push.
|
|
360
|
+
|
|
325
361
|
### Reserved Chords
|
|
326
362
|
|
|
327
363
|
Shell-reserved chords let a host application (window manager, launcher shell, tiling WM) claim specific chords for its own dispatch regardless of what napplets subscribe to. Declare the reserved set once at service construction via the `reservedChords` option on [`KeysServiceOptions`](#keysserviceoptions):
|
package/dist/index.d.ts
CHANGED
|
@@ -507,7 +507,7 @@ declare function createCoordinatedRelay(options: CoordinatedRelayOptions): Servi
|
|
|
507
507
|
* - keys.forward -> invokes options.onForward (hotkey passthrough, fire-and-forget)
|
|
508
508
|
* - keys.registerAction -> parses action.defaultKey into a normalized chord binding,
|
|
509
509
|
* stores bound subscriptions in an in-memory registry keyed
|
|
510
|
-
* by
|
|
510
|
+
* by windowId + actionId so onWindowDestroyed
|
|
511
511
|
* can auto-unsubscribe, echoes { actionId, binding } as .result
|
|
512
512
|
* when a binding is assigned, and pushes the window's complete
|
|
513
513
|
* keys.bindings list
|
|
@@ -577,17 +577,8 @@ interface HostKeyEvent {
|
|
|
577
577
|
*
|
|
578
578
|
* @example
|
|
579
579
|
* ```ts
|
|
580
|
-
*
|
|
581
|
-
* const
|
|
582
|
-
* subscribe(chord, cb) {
|
|
583
|
-
* const handle = globalShortcut.register(chord, () => cb({ key: '', code: '', ctrlKey: false, altKey: false, shiftKey: false, metaKey: false }));
|
|
584
|
-
* return () => globalShortcut.unregister(chord);
|
|
585
|
-
* },
|
|
586
|
-
* registerGlobalHotkey: (chord) => globalShortcut.register(chord, () => {}),
|
|
587
|
-
* onGlobalHotkey: (cb) => globalHotkeyBridge.on('global-hotkey', (_, chord) => cb(chord)),
|
|
588
|
-
* };
|
|
589
|
-
*
|
|
590
|
-
* const keys = createKeysService({ hostBridge: electronBridge });
|
|
580
|
+
* declare const hostBridge: HostKeysBridge;
|
|
581
|
+
* const keys = createKeysService({ hostBridge });
|
|
591
582
|
* runtime.registerService('keys', keys);
|
|
592
583
|
* ```
|
|
593
584
|
*/
|
|
@@ -599,6 +590,8 @@ interface HostKeysBridge {
|
|
|
599
590
|
* - invoke `callback` exactly once per matching chord event (implementations
|
|
600
591
|
* are responsible for any OS-autorepeat filtering)
|
|
601
592
|
* - invoke `callback` synchronously during the event delivery
|
|
593
|
+
* - remove only this subscription when its unsubscribe handle is called;
|
|
594
|
+
* other callbacks for the same chord must remain live
|
|
602
595
|
* - accept the string chord format documented by @napplet/nap/keys
|
|
603
596
|
* (e.g. `'Ctrl+Shift+K'`, `'Cmd+P'`)
|
|
604
597
|
*/
|
|
@@ -665,7 +658,7 @@ interface KeysServiceOptions {
|
|
|
665
658
|
/**
|
|
666
659
|
* Optional pluggable backend for chord subscription. When provided, the
|
|
667
660
|
* service delegates `keys.registerAction` → `bridge.subscribe(chord, cb)`
|
|
668
|
-
* and stores the returned unsubscribe handle keyed on
|
|
661
|
+
* and stores the returned unsubscribe handle keyed on windowId + actionId. The
|
|
669
662
|
* default document-listener path is NOT attached when `hostBridge` is
|
|
670
663
|
* provided — the bridge is authoritative. See {@link HostKeysBridge}.
|
|
671
664
|
*/
|
|
@@ -1416,6 +1409,8 @@ interface ResourceFetchInit {
|
|
|
1416
1409
|
headers?: Record<string, string>;
|
|
1417
1410
|
/** Request-scoped cancellation signal. */
|
|
1418
1411
|
signal: AbortSignal;
|
|
1412
|
+
/** Authenticated source window, for runtime-private scoped policy state. */
|
|
1413
|
+
windowId?: string;
|
|
1419
1414
|
/** Advisory Blossom server origins for this resource only. */
|
|
1420
1415
|
servers?: readonly string[];
|
|
1421
1416
|
}
|
|
@@ -1715,7 +1710,19 @@ interface OutboxServiceOptions {
|
|
|
1715
1710
|
/** The outbox router the shell uses to reach relays. Required. */
|
|
1716
1711
|
router: OutboxRouter;
|
|
1717
1712
|
/**
|
|
1718
|
-
* Optionally selects a
|
|
1713
|
+
* Optionally selects a source-scoped router for event-returning reads.
|
|
1714
|
+
*
|
|
1715
|
+
* The selected router is used for `outbox.getEvent`, `outbox.query`, and
|
|
1716
|
+
* `outbox.subscribe`. A query-specific router takes precedence when both
|
|
1717
|
+
* hooks are supplied.
|
|
1718
|
+
*
|
|
1719
|
+
* @param windowId - The authenticated source window for the request.
|
|
1720
|
+
* @param context - Narrow runtime context for source-bound policy checks.
|
|
1721
|
+
* @returns Router used for that source's event-returning reads.
|
|
1722
|
+
*/
|
|
1723
|
+
getReadRouter?(windowId: string, context: ServiceRuntimeContext | undefined): OutboxRouter;
|
|
1724
|
+
/**
|
|
1725
|
+
* Optionally selects a query-specific router for a live napplet source.
|
|
1719
1726
|
*
|
|
1720
1727
|
* @param windowId - The authenticated source window for the request.
|
|
1721
1728
|
* @param context - Narrow runtime context for source-bound policy checks.
|
package/dist/index.js
CHANGED
|
@@ -727,6 +727,9 @@ function createCoordinatedRelay(options) {
|
|
|
727
727
|
}
|
|
728
728
|
|
|
729
729
|
// src/keys-service-internals.ts
|
|
730
|
+
function actionRegistryKey(windowId, actionId) {
|
|
731
|
+
return JSON.stringify([windowId, actionId]);
|
|
732
|
+
}
|
|
730
733
|
var MODIFIER_ALIASES = {
|
|
731
734
|
ctrl: "ctrl",
|
|
732
735
|
control: "ctrl",
|
|
@@ -795,7 +798,7 @@ function bindingsForWindow(windowId, registry, windowIndex) {
|
|
|
795
798
|
if (!actionIds) return [];
|
|
796
799
|
const bindings = [];
|
|
797
800
|
for (const actionId of actionIds) {
|
|
798
|
-
const entry = registry.get(actionId);
|
|
801
|
+
const entry = registry.get(actionRegistryKey(windowId, actionId));
|
|
799
802
|
if (!entry || entry.windowId !== windowId) continue;
|
|
800
803
|
bindings.push({ actionId, key: entry.chordString });
|
|
801
804
|
}
|
|
@@ -808,10 +811,9 @@ function pushBindings(windowId, registry, windowIndex, send) {
|
|
|
808
811
|
};
|
|
809
812
|
send(payload);
|
|
810
813
|
}
|
|
811
|
-
function removeActionFromWindowIndex(actionId, windowIndex) {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}
|
|
814
|
+
function removeActionFromWindowIndex(windowId, actionId, windowIndex) {
|
|
815
|
+
const actions = windowIndex.get(windowId);
|
|
816
|
+
if (actions?.delete(actionId) && actions.size === 0) windowIndex.delete(windowId);
|
|
815
817
|
}
|
|
816
818
|
|
|
817
819
|
// src/keys-service.ts
|
|
@@ -821,7 +823,7 @@ function rememberActionForWindow(windowIndex, windowId, actionId) {
|
|
|
821
823
|
windowIndex.get(windowId).add(actionId);
|
|
822
824
|
}
|
|
823
825
|
function bindActionEntry(registry, windowIndex, actionId, windowId, chord, chordString) {
|
|
824
|
-
registry.set(actionId, { chord, chordString, windowId });
|
|
826
|
+
registry.set(actionRegistryKey(windowId, actionId), { actionId, chord, chordString, windowId });
|
|
825
827
|
rememberActionForWindow(windowIndex, windowId, actionId);
|
|
826
828
|
}
|
|
827
829
|
function bindActionAndMarkChanged(registry, windowIndex, changedWindowIds, actionId, windowId, chord, chordString) {
|
|
@@ -894,6 +896,7 @@ function createKeysService(options = {}) {
|
|
|
894
896
|
const bridgeSendHandles = /* @__PURE__ */ new Map();
|
|
895
897
|
const unsubscribeHandles = /* @__PURE__ */ new Map();
|
|
896
898
|
const handleBridgeRegisterAction = (windowId, m, send) => {
|
|
899
|
+
const registryKey = actionRegistryKey(windowId, m.action.id);
|
|
897
900
|
bridgeSendHandles.set(windowId, send);
|
|
898
901
|
let binding;
|
|
899
902
|
const changedWindowIds = /* @__PURE__ */ new Set();
|
|
@@ -901,8 +904,8 @@ function createKeysService(options = {}) {
|
|
|
901
904
|
try {
|
|
902
905
|
const chord = parseChord(m.action.defaultKey);
|
|
903
906
|
const normalizedChord = formatChord(chord);
|
|
904
|
-
const existingEntry = bridgeActionRegistry.get(
|
|
905
|
-
const existing = unsubscribeHandles.get(
|
|
907
|
+
const existingEntry = bridgeActionRegistry.get(registryKey);
|
|
908
|
+
const existing = unsubscribeHandles.get(registryKey);
|
|
906
909
|
let nextUnsubscribe;
|
|
907
910
|
if (!isUnavailableBinding(chord)) {
|
|
908
911
|
nextUnsubscribe = bridge.subscribe(normalizedChord, (ev) => {
|
|
@@ -928,13 +931,13 @@ function createKeysService(options = {}) {
|
|
|
928
931
|
existing();
|
|
929
932
|
} catch {
|
|
930
933
|
}
|
|
931
|
-
unsubscribeHandles.delete(
|
|
934
|
+
unsubscribeHandles.delete(registryKey);
|
|
932
935
|
}
|
|
933
936
|
if (existingEntry) changedWindowIds.add(existingEntry.windowId);
|
|
934
|
-
removeActionFromWindowIndex(m.action.id, bridgeWindowActions);
|
|
935
|
-
bridgeActionRegistry.delete(
|
|
937
|
+
removeActionFromWindowIndex(windowId, m.action.id, bridgeWindowActions);
|
|
938
|
+
bridgeActionRegistry.delete(registryKey);
|
|
936
939
|
if (nextUnsubscribe) {
|
|
937
|
-
unsubscribeHandles.set(
|
|
940
|
+
unsubscribeHandles.set(registryKey, nextUnsubscribe);
|
|
938
941
|
binding = bindActionAndMarkChanged(
|
|
939
942
|
bridgeActionRegistry,
|
|
940
943
|
bridgeWindowActions,
|
|
@@ -964,18 +967,19 @@ function createKeysService(options = {}) {
|
|
|
964
967
|
send
|
|
965
968
|
);
|
|
966
969
|
};
|
|
967
|
-
const handleBridgeUnregisterAction = (
|
|
970
|
+
const handleBridgeUnregisterAction = (windowId, m, send) => {
|
|
968
971
|
if (!m.actionId) return;
|
|
969
|
-
const
|
|
970
|
-
const
|
|
972
|
+
const registryKey = actionRegistryKey(windowId, m.actionId);
|
|
973
|
+
const unsubscribe = unsubscribeHandles.get(registryKey);
|
|
974
|
+
const entry = bridgeActionRegistry.get(registryKey);
|
|
971
975
|
if (!unsubscribe) return;
|
|
972
976
|
try {
|
|
973
977
|
unsubscribe();
|
|
974
978
|
} catch {
|
|
975
979
|
}
|
|
976
|
-
unsubscribeHandles.delete(
|
|
977
|
-
bridgeActionRegistry.delete(
|
|
978
|
-
removeActionFromWindowIndex(m.actionId, bridgeWindowActions);
|
|
980
|
+
unsubscribeHandles.delete(registryKey);
|
|
981
|
+
bridgeActionRegistry.delete(registryKey);
|
|
982
|
+
removeActionFromWindowIndex(windowId, m.actionId, bridgeWindowActions);
|
|
979
983
|
if (entry) {
|
|
980
984
|
const ownerSend = bridgeSendHandles.get(entry.windowId) ?? send;
|
|
981
985
|
pushBindings(entry.windowId, bridgeActionRegistry, bridgeWindowActions, ownerSend);
|
|
@@ -995,15 +999,16 @@ function createKeysService(options = {}) {
|
|
|
995
999
|
const actions = bridgeWindowActions.get(windowId);
|
|
996
1000
|
if (!actions) return;
|
|
997
1001
|
for (const actionId of actions) {
|
|
998
|
-
const
|
|
1002
|
+
const registryKey = actionRegistryKey(windowId, actionId);
|
|
1003
|
+
const unsubscribe = unsubscribeHandles.get(registryKey);
|
|
999
1004
|
if (unsubscribe) {
|
|
1000
1005
|
try {
|
|
1001
1006
|
unsubscribe();
|
|
1002
1007
|
} catch {
|
|
1003
1008
|
}
|
|
1004
1009
|
}
|
|
1005
|
-
unsubscribeHandles.delete(
|
|
1006
|
-
bridgeActionRegistry.delete(
|
|
1010
|
+
unsubscribeHandles.delete(registryKey);
|
|
1011
|
+
bridgeActionRegistry.delete(registryKey);
|
|
1007
1012
|
}
|
|
1008
1013
|
bridgeWindowActions.delete(windowId);
|
|
1009
1014
|
bridgeSendHandles.delete(windowId);
|
|
@@ -1056,13 +1061,13 @@ function createKeysService(options = {}) {
|
|
|
1056
1061
|
});
|
|
1057
1062
|
}
|
|
1058
1063
|
if (isReserved) return;
|
|
1059
|
-
for (const
|
|
1064
|
+
for (const entry of actionRegistry.values()) {
|
|
1060
1065
|
if (chordMatches(entry.chord, ev)) {
|
|
1061
1066
|
const send = sendHandles.get(entry.windowId);
|
|
1062
1067
|
if (send) {
|
|
1063
1068
|
const payload = {
|
|
1064
1069
|
type: "keys.action",
|
|
1065
|
-
actionId
|
|
1070
|
+
actionId: entry.actionId
|
|
1066
1071
|
};
|
|
1067
1072
|
send(payload);
|
|
1068
1073
|
}
|
|
@@ -1071,6 +1076,7 @@ function createKeysService(options = {}) {
|
|
|
1071
1076
|
};
|
|
1072
1077
|
target.addEventListener("keydown", listener);
|
|
1073
1078
|
const handleDocumentRegisterAction = (windowId, m, send) => {
|
|
1079
|
+
const registryKey = actionRegistryKey(windowId, m.action.id);
|
|
1074
1080
|
sendHandles.set(windowId, send);
|
|
1075
1081
|
let binding;
|
|
1076
1082
|
const changedWindowIds = /* @__PURE__ */ new Set();
|
|
@@ -1078,11 +1084,11 @@ function createKeysService(options = {}) {
|
|
|
1078
1084
|
try {
|
|
1079
1085
|
const chord = parseChord(m.action.defaultKey);
|
|
1080
1086
|
const normalizedChord = formatChord(chord);
|
|
1081
|
-
const existing = actionRegistry.get(
|
|
1087
|
+
const existing = actionRegistry.get(registryKey);
|
|
1082
1088
|
if (existing) {
|
|
1083
1089
|
changedWindowIds.add(existing.windowId);
|
|
1084
|
-
removeActionFromWindowIndex(m.action.id, windowActions);
|
|
1085
|
-
actionRegistry.delete(
|
|
1090
|
+
removeActionFromWindowIndex(windowId, m.action.id, windowActions);
|
|
1091
|
+
actionRegistry.delete(registryKey);
|
|
1086
1092
|
}
|
|
1087
1093
|
if (!isUnavailableBinding(chord)) {
|
|
1088
1094
|
binding = bindActionAndMarkChanged(actionRegistry, windowActions, changedWindowIds, m.action.id, windowId, chord, normalizedChord);
|
|
@@ -1100,10 +1106,12 @@ function createKeysService(options = {}) {
|
|
|
1100
1106
|
sendRegisterActionResult(send, m, binding);
|
|
1101
1107
|
pushChangedBindings(changedWindowIds, actionRegistry, windowActions, sendHandles, send);
|
|
1102
1108
|
};
|
|
1103
|
-
const handleDocumentUnregisterAction = (
|
|
1104
|
-
if (!m.actionId
|
|
1105
|
-
const
|
|
1106
|
-
actionRegistry.
|
|
1109
|
+
const handleDocumentUnregisterAction = (windowId, m, send) => {
|
|
1110
|
+
if (!m.actionId) return;
|
|
1111
|
+
const registryKey = actionRegistryKey(windowId, m.actionId);
|
|
1112
|
+
const entry = actionRegistry.get(registryKey);
|
|
1113
|
+
if (!entry) return;
|
|
1114
|
+
actionRegistry.delete(registryKey);
|
|
1107
1115
|
const set = windowActions.get(entry.windowId);
|
|
1108
1116
|
if (set) {
|
|
1109
1117
|
set.delete(m.actionId);
|
|
@@ -1123,7 +1131,7 @@ function createKeysService(options = {}) {
|
|
|
1123
1131
|
onWindowDestroyed(windowId) {
|
|
1124
1132
|
const actions = windowActions.get(windowId);
|
|
1125
1133
|
if (actions) {
|
|
1126
|
-
for (const actionId of actions) actionRegistry.delete(actionId);
|
|
1134
|
+
for (const actionId of actions) actionRegistry.delete(actionRegistryKey(windowId, actionId));
|
|
1127
1135
|
windowActions.delete(windowId);
|
|
1128
1136
|
}
|
|
1129
1137
|
sendHandles.delete(windowId);
|
|
@@ -2405,6 +2413,7 @@ async function fetchResourceItem(options, windowId, info, request, signal) {
|
|
|
2405
2413
|
const response = await options.fetch(url, {
|
|
2406
2414
|
method: "GET",
|
|
2407
2415
|
signal,
|
|
2416
|
+
windowId,
|
|
2408
2417
|
...parsedUrl.protocol === "blossom:" && request.servers !== void 0 ? { servers: [...request.servers] } : {}
|
|
2409
2418
|
});
|
|
2410
2419
|
const buffer = await response.arrayBuffer();
|
|
@@ -2720,7 +2729,7 @@ function createOutboxService(options) {
|
|
|
2720
2729
|
if (!options || typeof options.router !== "object" || options.router === null) {
|
|
2721
2730
|
throw new Error("createOutboxService: options.router is required");
|
|
2722
2731
|
}
|
|
2723
|
-
const { router, getQueryRouter } = options;
|
|
2732
|
+
const { router, getReadRouter, getQueryRouter } = options;
|
|
2724
2733
|
let runtimeContext;
|
|
2725
2734
|
const subscriptions = /* @__PURE__ */ new Map();
|
|
2726
2735
|
function handleQuery(windowId, msg, send) {
|
|
@@ -2731,7 +2740,7 @@ function createOutboxService(options) {
|
|
|
2731
2740
|
send({ type: "outbox.query.result", id, events: [], error: "invalid filter" });
|
|
2732
2741
|
return;
|
|
2733
2742
|
}
|
|
2734
|
-
const queryRouter = getQueryRouter?.(windowId, runtimeContext) ?? router;
|
|
2743
|
+
const queryRouter = getQueryRouter?.(windowId, runtimeContext) ?? getReadRouter?.(windowId, runtimeContext) ?? router;
|
|
2735
2744
|
void queryRouter.query(filters, sanitizeQueryOptions(m.options)).then(
|
|
2736
2745
|
(result2) => send({
|
|
2737
2746
|
type: "outbox.query.result",
|
|
@@ -2763,7 +2772,8 @@ function createOutboxService(options) {
|
|
|
2763
2772
|
send({ type: "outbox.closed", subId, ...reason === void 0 ? {} : { reason } });
|
|
2764
2773
|
}
|
|
2765
2774
|
};
|
|
2766
|
-
|
|
2775
|
+
const readRouter = getReadRouter?.(windowId, runtimeContext) ?? router;
|
|
2776
|
+
subscriptions.set(subKey, readRouter.subscribe(filters, sanitizeSubscribeOptions(m.options), sink));
|
|
2767
2777
|
}
|
|
2768
2778
|
function handleClose(windowId, msg, send) {
|
|
2769
2779
|
const m = msg;
|
|
@@ -2818,7 +2828,7 @@ function createOutboxService(options) {
|
|
|
2818
2828
|
handleMessage(windowId, message, send) {
|
|
2819
2829
|
switch (message.type) {
|
|
2820
2830
|
case "outbox.getEvent":
|
|
2821
|
-
handleGetEvent(router, message, send);
|
|
2831
|
+
handleGetEvent(getReadRouter?.(windowId, runtimeContext) ?? router, message, send);
|
|
2822
2832
|
return;
|
|
2823
2833
|
case "outbox.query":
|
|
2824
2834
|
handleQuery(windowId, message, send);
|