@kehto/shell 0.1.0
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 +101 -0
- package/dist/index.d.ts +1617 -0
- package/dist/index.js +1129 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1617 @@
|
|
|
1
|
+
import { Capability, ServiceRegistry, RuntimeConfigOverrides, ConsentRequest, Runtime, RuntimeAdapter } from '@kehto/runtime';
|
|
2
|
+
export { ALL_CAPABILITIES, AclChecker, Capability, ConsentRequest, EnforceConfig, EnforceResult, IdentityResolver, NubEnforceConfig, NubMessage, ServiceDescriptor, ServiceHandler, ServiceRegistry, createEnforceGate, createNubEnforceGate, formatDenialReason } from '@kehto/runtime';
|
|
3
|
+
import { NappletMessage, NostrEvent, NostrFilter } from '@napplet/core';
|
|
4
|
+
export { NappletMessage, NostrEvent, NostrFilter, TOPICS, TopicKey, TopicValue } from '@napplet/core';
|
|
5
|
+
import { Theme } from '@napplet/nub-theme';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Registry entry mapping a napplet's pubkey to its runtime metadata.
|
|
9
|
+
* Created after a successful NIP-42 AUTH handshake or NIP-5D origin registration.
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const entry: SessionEntry = {
|
|
13
|
+
* pubkey: 'abc123...', windowId: 'win-1', origin: '*',
|
|
14
|
+
* type: 'chat', dTag: '3chat', aggregateHash: 'deadbeef',
|
|
15
|
+
* registeredAt: Date.now(), instanceId: 'guid-123',
|
|
16
|
+
* identitySource: 'auth',
|
|
17
|
+
* };
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
interface SessionEntry {
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated NIP-5D: AUTH keypair no longer exists. Empty string for NIP-5D sessions.
|
|
23
|
+
* Kept for backward compatibility during legacy support period.
|
|
24
|
+
*/
|
|
25
|
+
pubkey: string;
|
|
26
|
+
windowId: string;
|
|
27
|
+
origin: string;
|
|
28
|
+
type: string;
|
|
29
|
+
dTag: string;
|
|
30
|
+
aggregateHash: string;
|
|
31
|
+
registeredAt: number;
|
|
32
|
+
/** Persistent GUID for this iframe instance, assigned by the runtime. Survives page reloads. */
|
|
33
|
+
instanceId: string;
|
|
34
|
+
/**
|
|
35
|
+
* How session identity was established.
|
|
36
|
+
* 'source' = NIP-5D (identity registered at iframe creation via originRegistry).
|
|
37
|
+
* 'auth' = legacy AUTH handshake (pubkey is the derived keypair pubkey).
|
|
38
|
+
*/
|
|
39
|
+
identitySource: 'auth' | 'source';
|
|
40
|
+
}
|
|
41
|
+
/** @deprecated Use SessionEntry. Will be removed in v0.9.0. */
|
|
42
|
+
type NappKeyEntry = SessionEntry;
|
|
43
|
+
/**
|
|
44
|
+
* ACL entry controlling what a napplet pubkey is permitted to do.
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* const entry: AclEntry = {
|
|
48
|
+
* pubkey: 'abc123...', capabilities: ['relay:read', 'relay:write'],
|
|
49
|
+
* blocked: false, stateQuota: 524288,
|
|
50
|
+
* };
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
interface AclEntry {
|
|
54
|
+
pubkey: string;
|
|
55
|
+
capabilities: Capability[];
|
|
56
|
+
blocked: boolean;
|
|
57
|
+
stateQuota?: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Hook for relay pool operations. Host app provides relay connectivity.
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* const relayPoolHooks: RelayPoolHooks = {
|
|
64
|
+
* getRelayPool: () => myPool,
|
|
65
|
+
* trackSubscription: (key, cleanup) => subscriptions.set(key, cleanup),
|
|
66
|
+
* // ...
|
|
67
|
+
* };
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
interface RelayPoolHooks {
|
|
71
|
+
/** Get the relay pool instance — returns null if no pool available. */
|
|
72
|
+
getRelayPool(): RelayPoolLike | null;
|
|
73
|
+
/** Track a subscription for lifecycle management. */
|
|
74
|
+
trackSubscription(subKey: string, cleanup: () => void): void;
|
|
75
|
+
/** Untrack and clean up a subscription. */
|
|
76
|
+
untrackSubscription(subKey: string): void;
|
|
77
|
+
/** Open a scoped relay connection (NIP-29 groups). */
|
|
78
|
+
openScopedRelay(windowId: string, relayUrl: string, subId: string, filters: NostrFilter[], sourceWindow: Window): void;
|
|
79
|
+
/** Close a scoped relay connection. */
|
|
80
|
+
closeScopedRelay(windowId: string): void;
|
|
81
|
+
/** Publish to a scoped relay. Returns false if no active scoped relay. */
|
|
82
|
+
publishToScopedRelay(windowId: string, event: NostrEvent): boolean;
|
|
83
|
+
/** Select relay URLs for a given set of filters. */
|
|
84
|
+
selectRelayTier(filters: NostrFilter[]): string[];
|
|
85
|
+
}
|
|
86
|
+
/** Minimal relay pool interface that the shell requires. */
|
|
87
|
+
interface RelayPoolLike {
|
|
88
|
+
subscription(relayUrls: string[], filters: any): {
|
|
89
|
+
subscribe(observer: (item: unknown) => void): {
|
|
90
|
+
unsubscribe(): void;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
publish(relayUrls: string[], event: any): void;
|
|
94
|
+
request(relayUrls: string[], filters: any): {
|
|
95
|
+
subscribe(observer: {
|
|
96
|
+
next: (event: unknown) => void;
|
|
97
|
+
complete: () => void;
|
|
98
|
+
error: () => void;
|
|
99
|
+
}): {
|
|
100
|
+
unsubscribe(): void;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** Hook for relay configuration. */
|
|
105
|
+
interface RelayConfigHooks {
|
|
106
|
+
/** Add a relay URL to a named tier. */
|
|
107
|
+
addRelay(tier: string, url: string): void;
|
|
108
|
+
/** Remove a relay URL from a named tier. */
|
|
109
|
+
removeRelay(tier: string, url: string): void;
|
|
110
|
+
/** Get the current relay configuration by tier. */
|
|
111
|
+
getRelayConfig(): {
|
|
112
|
+
discovery: string[];
|
|
113
|
+
super: string[];
|
|
114
|
+
outbox: string[];
|
|
115
|
+
};
|
|
116
|
+
/** Get NIP-66 relay suggestions. */
|
|
117
|
+
getNip66Suggestions(): unknown;
|
|
118
|
+
}
|
|
119
|
+
/** Hook for window management. */
|
|
120
|
+
interface WindowManagerHooks {
|
|
121
|
+
/** Create a new window. Returns the window ID or null on failure. */
|
|
122
|
+
createWindow(options: {
|
|
123
|
+
title: string;
|
|
124
|
+
class: string;
|
|
125
|
+
iframeSrc?: string;
|
|
126
|
+
}): string | null;
|
|
127
|
+
}
|
|
128
|
+
/** Hook for auth state and signing. */
|
|
129
|
+
interface AuthHooks {
|
|
130
|
+
/** Get the current user's pubkey, or null if not logged in. */
|
|
131
|
+
getUserPubkey(): string | null;
|
|
132
|
+
/** Get the NIP-07 compatible signer, or null if unavailable. */
|
|
133
|
+
getSigner(): any | null;
|
|
134
|
+
}
|
|
135
|
+
/** Hook for config. */
|
|
136
|
+
interface ConfigHooks {
|
|
137
|
+
/** Get the napp update behavior policy. */
|
|
138
|
+
getNappUpdateBehavior(): 'auto-grant' | 'banner' | 'silent-reprompt';
|
|
139
|
+
}
|
|
140
|
+
/** Hook for hotkey dispatch. */
|
|
141
|
+
interface HotkeyHooks {
|
|
142
|
+
/** Execute a forwarded hotkey from a napp. */
|
|
143
|
+
executeHotkeyFromForward(event: {
|
|
144
|
+
key: string;
|
|
145
|
+
code: string;
|
|
146
|
+
ctrlKey: boolean;
|
|
147
|
+
altKey: boolean;
|
|
148
|
+
shiftKey: boolean;
|
|
149
|
+
metaKey: boolean;
|
|
150
|
+
}): void;
|
|
151
|
+
}
|
|
152
|
+
/** Hook for worker relay (local cache). */
|
|
153
|
+
interface WorkerRelayHooks {
|
|
154
|
+
/** Get the worker relay instance, or null if unavailable. */
|
|
155
|
+
getWorkerRelay(): WorkerRelayLike | null;
|
|
156
|
+
}
|
|
157
|
+
/** Minimal worker relay interface. */
|
|
158
|
+
interface WorkerRelayLike {
|
|
159
|
+
event(event: NostrEvent): Promise<any>;
|
|
160
|
+
query(req: any): Promise<NostrEvent[]>;
|
|
161
|
+
count?(req: any): Promise<number>;
|
|
162
|
+
}
|
|
163
|
+
/** Hook for crypto verification. */
|
|
164
|
+
interface CryptoHooks {
|
|
165
|
+
/** Verify a nostr event's signature. */
|
|
166
|
+
verifyEvent(event: NostrEvent): Promise<boolean>;
|
|
167
|
+
}
|
|
168
|
+
/** Hook for DM sending (NIP-17 gift-wrap). */
|
|
169
|
+
interface DmHooks {
|
|
170
|
+
/** Send a direct message to a recipient. */
|
|
171
|
+
sendDm(recipientPubkey: string, message: string): Promise<{
|
|
172
|
+
success: boolean;
|
|
173
|
+
eventId?: string;
|
|
174
|
+
error?: string;
|
|
175
|
+
}>;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Event emitted on every ACL enforcement check.
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* hooks.onAclCheck = (event: AclCheckEvent) => {
|
|
182
|
+
* console.log(`${event.decision}: ${event.capability} for ${event.identity.pubkey}`);
|
|
183
|
+
* };
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
interface AclCheckEvent {
|
|
187
|
+
/** The identity being checked. */
|
|
188
|
+
identity: {
|
|
189
|
+
pubkey: string;
|
|
190
|
+
dTag: string;
|
|
191
|
+
hash: string;
|
|
192
|
+
};
|
|
193
|
+
/** The capability being checked (e.g., 'relay:write', 'state:read'). */
|
|
194
|
+
capability: string;
|
|
195
|
+
/** The enforcement decision. */
|
|
196
|
+
decision: 'allow' | 'deny';
|
|
197
|
+
/** The triggering message, if available. Accepts NIP-01 arrays or NIP-5D NappletMessage envelopes. */
|
|
198
|
+
message?: NappletMessage | unknown[];
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Static capability set injected into napplet iframes at creation time.
|
|
202
|
+
* Used by `window.napplet.shell.supports()` for synchronous capability queries.
|
|
203
|
+
*
|
|
204
|
+
* Per canonical NIP-5D (specs/NIP-5D.md lines 81-94), supports() distinguishes
|
|
205
|
+
* two namespaces:
|
|
206
|
+
*
|
|
207
|
+
* - Bare names (or optional `nub:` prefix) for NUB-capability lookups,
|
|
208
|
+
* resolved against the `nubs` array — e.g. `supports('relay')`,
|
|
209
|
+
* `supports('identity')`.
|
|
210
|
+
* - The `perm:<permission>` prefix for sandbox-permission lookups, resolved
|
|
211
|
+
* against the `sandbox` array — e.g. `supports('perm:popups')`,
|
|
212
|
+
* `supports('perm:modals')`.
|
|
213
|
+
*
|
|
214
|
+
* The two namespaces do not cross: a bare-name lookup never matches a sandbox
|
|
215
|
+
* entry and a `perm:`-prefixed lookup never matches a NUB entry.
|
|
216
|
+
*/
|
|
217
|
+
interface ShellCapabilities {
|
|
218
|
+
/**
|
|
219
|
+
* NUB domain prefixes the shell handles. Canonical 8-domain set from @napplet/nub-*:
|
|
220
|
+
* relay, identity, storage, ifc, theme, keys, media, notify. `relay` is conditional
|
|
221
|
+
* on the RelayPoolHooks being provided.
|
|
222
|
+
*
|
|
223
|
+
* Entries are bare domain names — `'relay'`, `'identity'`, etc. They MUST NOT
|
|
224
|
+
* carry the `perm:` prefix; that prefix is reserved for the `sandbox` array.
|
|
225
|
+
* Napplets query NUB support via `supports('<domain>')` (or, equivalently,
|
|
226
|
+
* `supports('nub:<domain>')`).
|
|
227
|
+
*/
|
|
228
|
+
nubs: string[];
|
|
229
|
+
/**
|
|
230
|
+
* Sandbox permissions under the `perm:<permission>` namespace. Each entry
|
|
231
|
+
* MUST begin with the literal prefix `'perm:'` — e.g. `'perm:popups'`,
|
|
232
|
+
* `'perm:modals'`, `'perm:downloads'`. Napplets call
|
|
233
|
+
* `shell.supports('perm:<permission>')` to check sandbox entitlements.
|
|
234
|
+
*
|
|
235
|
+
* The `perm:` prefix is what separates sandbox permissions from NUB
|
|
236
|
+
* capabilities; bare-name entries here violate the NIP-5D contract and will
|
|
237
|
+
* be unreachable through `supports()` (see specs/NIP-5D.md §6 and lines
|
|
238
|
+
* 81-94). NUB-capability lookups (on `nubs`) retain the bare-name
|
|
239
|
+
* convention and do NOT use the `perm:` prefix.
|
|
240
|
+
*/
|
|
241
|
+
sandbox: string[];
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* All adapters that the shell requires from the host application.
|
|
245
|
+
* @example
|
|
246
|
+
* ```ts
|
|
247
|
+
* const hooks: ShellAdapter = {
|
|
248
|
+
* relayPool: myRelayPoolHooks,
|
|
249
|
+
* relayConfig: myRelayConfigHooks,
|
|
250
|
+
* windowManager: myWindowManagerHooks,
|
|
251
|
+
* auth: myAuthHooks,
|
|
252
|
+
* config: myConfigHooks,
|
|
253
|
+
* hotkeys: myHotkeyHooks,
|
|
254
|
+
* workerRelay: myWorkerRelayHooks,
|
|
255
|
+
* crypto: myCryptoHooks,
|
|
256
|
+
* };
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
interface ShellAdapter {
|
|
260
|
+
relayPool: RelayPoolHooks;
|
|
261
|
+
relayConfig: RelayConfigHooks;
|
|
262
|
+
windowManager: WindowManagerHooks;
|
|
263
|
+
auth: AuthHooks;
|
|
264
|
+
config: ConfigHooks;
|
|
265
|
+
hotkeys: HotkeyHooks;
|
|
266
|
+
workerRelay: WorkerRelayHooks;
|
|
267
|
+
crypto: CryptoHooks;
|
|
268
|
+
dm?: DmHooks;
|
|
269
|
+
/** Called on every ACL enforcement check. Both allows and denials are reported. */
|
|
270
|
+
onAclCheck?: (event: AclCheckEvent) => void;
|
|
271
|
+
/** Called when aggregate hash verification fails (computed != declared). */
|
|
272
|
+
onHashMismatch?: (dTag: string, claimed: string, computed: string) => void;
|
|
273
|
+
/**
|
|
274
|
+
* Called at iframe creation for NIP-5D napplets.
|
|
275
|
+
* Returns identity metadata for originRegistry.register().
|
|
276
|
+
* Return null for non-NIP-5D (legacy) iframes.
|
|
277
|
+
*/
|
|
278
|
+
onNip5dIframeCreate?: (windowId: string) => {
|
|
279
|
+
dTag: string;
|
|
280
|
+
aggregateHash: string;
|
|
281
|
+
} | null;
|
|
282
|
+
/**
|
|
283
|
+
* Optional service extensions. Each key is a service name (e.g., 'audio',
|
|
284
|
+
* 'notifications'). Napplets discover available services via kind 29010
|
|
285
|
+
* service discovery events.
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```ts
|
|
289
|
+
* const hooks: ShellAdapter = {
|
|
290
|
+
* // ... required adapters ...
|
|
291
|
+
* services: {
|
|
292
|
+
* audio: myAudioServiceHandler,
|
|
293
|
+
* notifications: myNotificationServiceHandler,
|
|
294
|
+
* },
|
|
295
|
+
* };
|
|
296
|
+
* ```
|
|
297
|
+
*/
|
|
298
|
+
services?: ServiceRegistry;
|
|
299
|
+
/**
|
|
300
|
+
* Optional runtime behavior overrides — demo/debug use only.
|
|
301
|
+
* Called lazily on each relevant operation (replay check, buffer push),
|
|
302
|
+
* so changes take effect immediately without runtime recreation.
|
|
303
|
+
*/
|
|
304
|
+
getConfigOverrides?(): RuntimeConfigOverrides;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* shell-bridge.ts — Browser adapter over @kehto/runtime.
|
|
309
|
+
*
|
|
310
|
+
* Thin shell that converts browser MessageEvents into windowId-based
|
|
311
|
+
* NIP-5D envelope messages for the runtime engine. All protocol logic
|
|
312
|
+
* (NUB dispatch, ACL enforcement, subscription lifecycle, signer proxying)
|
|
313
|
+
* lives in @kehto/runtime.
|
|
314
|
+
*
|
|
315
|
+
* The only browser-specific concern here is extracting the source Window
|
|
316
|
+
* from a MessageEvent, mapping it to a windowId via originRegistry, and
|
|
317
|
+
* routing shell.ready/shell.init handshake locally.
|
|
318
|
+
*/
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Shell-side message bridge that handles NIP-5D communication with napplet iframes.
|
|
322
|
+
*
|
|
323
|
+
* The bridge acts as a browser adapter: it receives raw MessageEvents from
|
|
324
|
+
* window.addEventListener('message', ...), extracts the source Window, resolves
|
|
325
|
+
* it to a windowId via originRegistry, and delegates NIP-5D envelope messages
|
|
326
|
+
* to the runtime engine. The shell.ready/shell.init capability handshake is
|
|
327
|
+
* handled locally within the bridge and never forwarded to the runtime.
|
|
328
|
+
*
|
|
329
|
+
* @example
|
|
330
|
+
* ```ts
|
|
331
|
+
* import { createShellBridge } from '@kehto/shell';
|
|
332
|
+
*
|
|
333
|
+
* const bridge = createShellBridge(hooks);
|
|
334
|
+
* window.addEventListener('message', bridge.handleMessage);
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
interface ShellBridge {
|
|
338
|
+
/**
|
|
339
|
+
* Handle an incoming postMessage from a napplet iframe.
|
|
340
|
+
*
|
|
341
|
+
* Only NIP-5D envelope objects (plain objects with a `.type` string) are
|
|
342
|
+
* accepted. NIP-01 arrays and all other message shapes are silently dropped
|
|
343
|
+
* (clean break — no legacy array fallback).
|
|
344
|
+
*
|
|
345
|
+
* shell.ready messages are handled locally: the bridge responds with shell.init
|
|
346
|
+
* containing the capability set and registered service list. All other envelopes
|
|
347
|
+
* are delegated to the runtime's NUB domain dispatch.
|
|
348
|
+
*
|
|
349
|
+
* @param event - The raw MessageEvent from window.addEventListener('message', ...)
|
|
350
|
+
* @example
|
|
351
|
+
* ```ts
|
|
352
|
+
* window.addEventListener('message', bridge.handleMessage);
|
|
353
|
+
* ```
|
|
354
|
+
*/
|
|
355
|
+
handleMessage(event: MessageEvent): void;
|
|
356
|
+
/**
|
|
357
|
+
* Inject a shell-originated event into subscription delivery.
|
|
358
|
+
*
|
|
359
|
+
* Under NIP-5D, shell-originated events are forwarded to napplets as
|
|
360
|
+
* ifc.event envelope messages. The runtime's injectEvent() handles
|
|
361
|
+
* the per-session routing.
|
|
362
|
+
*
|
|
363
|
+
* @param topic - The event topic tag value (e.g., 'auth:identity-changed')
|
|
364
|
+
* @param payload - The event content
|
|
365
|
+
* @example
|
|
366
|
+
* ```ts
|
|
367
|
+
* bridge.injectEvent('auth:identity-changed', { pubkey: userPubkey });
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
injectEvent(topic: string, payload: unknown): void;
|
|
371
|
+
/**
|
|
372
|
+
* Destroy the bridge instance, cleaning up all internal state.
|
|
373
|
+
* Persists manifest cache and clears all subscriptions, buffers, and registries.
|
|
374
|
+
* Call when the shell is shutting down or the bridge is no longer needed.
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```ts
|
|
378
|
+
* bridge.destroy();
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
destroy(): void;
|
|
382
|
+
/**
|
|
383
|
+
* Register a handler for consent requests on destructive signing kinds.
|
|
384
|
+
* Called when a napplet requests signing for kinds 0, 3, 5, or 10002.
|
|
385
|
+
*
|
|
386
|
+
* @param handler - Callback receiving the consent request with a resolve function
|
|
387
|
+
* @example
|
|
388
|
+
* ```ts
|
|
389
|
+
* bridge.registerConsentHandler((request) => {
|
|
390
|
+
* const allowed = confirm(`Allow signing kind ${request.event.kind}?`);
|
|
391
|
+
* request.resolve(allowed);
|
|
392
|
+
* });
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
registerConsentHandler(handler: (request: ConsentRequest) => void): void;
|
|
396
|
+
/**
|
|
397
|
+
* Publish a theme update to every registered napplet.
|
|
398
|
+
*
|
|
399
|
+
* Posts a `theme.changed` envelope (shell → napplet push) to every
|
|
400
|
+
* window currently tracked by the runtime's sessionRegistry, using
|
|
401
|
+
* the browser-adapter originRegistry to resolve windowId → iframe.
|
|
402
|
+
* Napplets whose window cannot be resolved (stale sessions) are
|
|
403
|
+
* silently skipped; this method never throws.
|
|
404
|
+
*
|
|
405
|
+
* ACL is enforced BY THE RECIPIENT NAPPLET — the runtime's
|
|
406
|
+
* `themeMap` in @kehto/acl assigns `recipientCap: 'theme:read'` for
|
|
407
|
+
* `theme.changed`, and @napplet/shim drops pushes the napplet lacks
|
|
408
|
+
* the capability for. Hosts should not self-filter here.
|
|
409
|
+
*
|
|
410
|
+
* @param theme - The new theme payload to broadcast.
|
|
411
|
+
* @example
|
|
412
|
+
* ```ts
|
|
413
|
+
* bridge.publishTheme({
|
|
414
|
+
* colors: { background: '#0a0a0a', text: '#e0e0e0', primary: '#7aa2f7' },
|
|
415
|
+
* title: 'Dark',
|
|
416
|
+
* });
|
|
417
|
+
* ```
|
|
418
|
+
*/
|
|
419
|
+
publishTheme(theme: Theme): void;
|
|
420
|
+
/**
|
|
421
|
+
* Access the underlying runtime instance for advanced use cases.
|
|
422
|
+
* Provides direct access to the runtime's sessionRegistry, aclState,
|
|
423
|
+
* and manifestCache.
|
|
424
|
+
*/
|
|
425
|
+
readonly runtime: Runtime;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Create a ShellBridge instance with dependency injection via hooks.
|
|
429
|
+
*
|
|
430
|
+
* Internally creates a Runtime from @kehto/runtime and adapts the
|
|
431
|
+
* browser-oriented ShellAdapter into environment-agnostic RuntimeAdapter.
|
|
432
|
+
*
|
|
433
|
+
* @param hooks - Host application provides relay pool, auth, config, etc.
|
|
434
|
+
* @returns A ShellBridge instance ready to handle napplet messages
|
|
435
|
+
* @example
|
|
436
|
+
* ```ts
|
|
437
|
+
* import { createShellBridge, type ShellAdapter } from '@kehto/shell';
|
|
438
|
+
*
|
|
439
|
+
* const hooks: ShellAdapter = {
|
|
440
|
+
* relayPool: myRelayPoolHooks,
|
|
441
|
+
* relayConfig: myRelayConfigHooks,
|
|
442
|
+
* windowManager: myWindowManagerHooks,
|
|
443
|
+
* auth: myAuthHooks,
|
|
444
|
+
* config: myConfigHooks,
|
|
445
|
+
* hotkeys: myHotkeyHooks,
|
|
446
|
+
* workerRelay: myWorkerRelayHooks,
|
|
447
|
+
* crypto: myCryptoHooks,
|
|
448
|
+
* };
|
|
449
|
+
* const bridge = createShellBridge(hooks);
|
|
450
|
+
* ```
|
|
451
|
+
*/
|
|
452
|
+
declare function createShellBridge(hooks: ShellAdapter): ShellBridge;
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Origin Registry — Window reference to windowId mapping.
|
|
456
|
+
*
|
|
457
|
+
* Used by the ShellBridge to validate that postMessage senders are known
|
|
458
|
+
* napplet iframes. event.source (a Window reference) is the only unforgeable
|
|
459
|
+
* origin — never trust event.origin from the message payload.
|
|
460
|
+
*/
|
|
461
|
+
/**
|
|
462
|
+
* Bidirectional registry mapping Window references to windowId strings.
|
|
463
|
+
* Optionally stores NIP-5D identity metadata (dTag and aggregateHash) per window.
|
|
464
|
+
*
|
|
465
|
+
* @example
|
|
466
|
+
* ```ts
|
|
467
|
+
* import { originRegistry } from '@kehto/shell';
|
|
468
|
+
*
|
|
469
|
+
* originRegistry.register(iframe.contentWindow, 'napp-1');
|
|
470
|
+
* const id = originRegistry.getWindowId(iframe.contentWindow); // 'napp-1'
|
|
471
|
+
* ```
|
|
472
|
+
*/
|
|
473
|
+
declare const originRegistry: {
|
|
474
|
+
/**
|
|
475
|
+
* Register a window reference with a windowId and optional identity metadata.
|
|
476
|
+
*
|
|
477
|
+
* @param win - The iframe's contentWindow reference
|
|
478
|
+
* @param windowId - The unique identifier for this napplet window
|
|
479
|
+
* @param identity - Optional NIP-5D identity metadata (dTag and aggregateHash)
|
|
480
|
+
*/
|
|
481
|
+
register(win: Window, windowId: string, identity?: {
|
|
482
|
+
dTag: string;
|
|
483
|
+
aggregateHash: string;
|
|
484
|
+
}): void;
|
|
485
|
+
/**
|
|
486
|
+
* Unregister a window by its windowId, removing the mapping.
|
|
487
|
+
*
|
|
488
|
+
* @param windowId - The window identifier to remove
|
|
489
|
+
*/
|
|
490
|
+
unregister(windowId: string): void;
|
|
491
|
+
/**
|
|
492
|
+
* Look up the windowId for a given Window reference.
|
|
493
|
+
*
|
|
494
|
+
* @param win - The Window reference (typically from event.source)
|
|
495
|
+
* @returns The windowId string, or undefined if not registered
|
|
496
|
+
*/
|
|
497
|
+
getWindowId(win: Window): string | undefined;
|
|
498
|
+
/**
|
|
499
|
+
* Look up the Window reference for a given windowId.
|
|
500
|
+
*
|
|
501
|
+
* @param windowId - The window identifier to look up
|
|
502
|
+
* @returns The Window reference, or null if not found
|
|
503
|
+
*/
|
|
504
|
+
getIframeWindow(windowId: string): Window | null;
|
|
505
|
+
/**
|
|
506
|
+
* Get all registered windowId strings.
|
|
507
|
+
*
|
|
508
|
+
* @returns Array of all registered window identifiers
|
|
509
|
+
*/
|
|
510
|
+
getAllWindowIds(): string[];
|
|
511
|
+
/**
|
|
512
|
+
* Get identity metadata for a registered Window.
|
|
513
|
+
*
|
|
514
|
+
* @param win - The Window reference to look up
|
|
515
|
+
* @returns Identity metadata, or undefined if not registered or no identity set
|
|
516
|
+
*/
|
|
517
|
+
getIdentity(win: Window): {
|
|
518
|
+
dTag: string;
|
|
519
|
+
aggregateHash: string;
|
|
520
|
+
} | undefined;
|
|
521
|
+
/** Clear all registrations. */
|
|
522
|
+
clear(): void;
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Manifest cache — persists verified NIP-5A aggregate hashes per napplet identity.
|
|
527
|
+
*/
|
|
528
|
+
/**
|
|
529
|
+
* A cached manifest entry for a verified napplet build.
|
|
530
|
+
* @example
|
|
531
|
+
* ```ts
|
|
532
|
+
* const entry: ManifestCacheEntry = {
|
|
533
|
+
* pubkey: 'abc123...', dTag: '3chat',
|
|
534
|
+
* aggregateHash: 'deadbeef', verifiedAt: Date.now(),
|
|
535
|
+
* };
|
|
536
|
+
* ```
|
|
537
|
+
*/
|
|
538
|
+
interface ManifestCacheEntry {
|
|
539
|
+
pubkey: string;
|
|
540
|
+
dTag: string;
|
|
541
|
+
aggregateHash: string;
|
|
542
|
+
verifiedAt: number;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Cache for verified napplet manifest entries. Persists to localStorage.
|
|
546
|
+
* Used to detect napplet updates (aggregateHash changes) across sessions.
|
|
547
|
+
*
|
|
548
|
+
* @example
|
|
549
|
+
* ```ts
|
|
550
|
+
* import { manifestCache } from '@kehto/shell';
|
|
551
|
+
*
|
|
552
|
+
* manifestCache.set({ pubkey: 'abc...', dTag: 'chat', aggregateHash: 'dead', verifiedAt: Date.now() });
|
|
553
|
+
* const entry = manifestCache.get('abc...', 'chat');
|
|
554
|
+
* ```
|
|
555
|
+
*/
|
|
556
|
+
declare const manifestCache: {
|
|
557
|
+
/**
|
|
558
|
+
* Get a cached manifest entry by pubkey and dTag.
|
|
559
|
+
*
|
|
560
|
+
* @param pubkey - The napp's pubkey
|
|
561
|
+
* @param dTag - The napp's dTag
|
|
562
|
+
* @returns The cached entry, or undefined if not found
|
|
563
|
+
*/
|
|
564
|
+
get(pubkey: string, dTag: string): ManifestCacheEntry | undefined;
|
|
565
|
+
/**
|
|
566
|
+
* Set (upsert) a manifest cache entry and persist to localStorage.
|
|
567
|
+
*
|
|
568
|
+
* @param entry - The manifest entry to cache
|
|
569
|
+
*/
|
|
570
|
+
set(entry: ManifestCacheEntry): void;
|
|
571
|
+
/**
|
|
572
|
+
* Check if a specific hash is cached for a pubkey/dTag combination.
|
|
573
|
+
*
|
|
574
|
+
* @param pubkey - The napp's pubkey
|
|
575
|
+
* @param dTag - The napp's dTag
|
|
576
|
+
* @param hash - The aggregateHash to check
|
|
577
|
+
* @returns True if the exact hash matches the cached entry
|
|
578
|
+
*/
|
|
579
|
+
has(pubkey: string, dTag: string, hash: string): boolean;
|
|
580
|
+
/**
|
|
581
|
+
* Remove a cached entry for a pubkey/dTag and persist.
|
|
582
|
+
*
|
|
583
|
+
* @param pubkey - The napp's pubkey
|
|
584
|
+
* @param dTag - The napp's dTag
|
|
585
|
+
*/
|
|
586
|
+
remove(pubkey: string, dTag: string): void;
|
|
587
|
+
/** Load the cache from localStorage. */
|
|
588
|
+
load(): void;
|
|
589
|
+
/** Persist the cache to localStorage. */
|
|
590
|
+
persist(): void;
|
|
591
|
+
/** Clear all cached entries and remove from localStorage. */
|
|
592
|
+
clear(): void;
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* ACL Store — composite-keyed capability registry for napplet identity.
|
|
597
|
+
*
|
|
598
|
+
* ACL entries are keyed by dTag:aggregateHash — a 2-segment composite key
|
|
599
|
+
* that ties permissions to a specific napplet build (NIP-5D format).
|
|
600
|
+
* Old 3-segment keys (pubkey:dTag:hash) are automatically migrated via migrateAclState().
|
|
601
|
+
*
|
|
602
|
+
* Default policy is PERMISSIVE: unknown identities have all capabilities granted.
|
|
603
|
+
*/
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* ACL store — manages capability grants, revocations, and blocks for napp identities.
|
|
607
|
+
* Persists to localStorage and uses a permissive default policy (all capabilities granted).
|
|
608
|
+
*
|
|
609
|
+
* @example
|
|
610
|
+
* ```ts
|
|
611
|
+
* import { aclStore } from '@kehto/shell';
|
|
612
|
+
*
|
|
613
|
+
* aclStore.grant(pubkey, dTag, hash, 'relay:read');
|
|
614
|
+
* const allowed = aclStore.check(pubkey, dTag, hash, 'relay:read'); // true
|
|
615
|
+
* ```
|
|
616
|
+
*/
|
|
617
|
+
declare const aclStore: {
|
|
618
|
+
/**
|
|
619
|
+
* Check if a napp identity has a specific capability.
|
|
620
|
+
* Returns true for unknown identities (permissive default).
|
|
621
|
+
*
|
|
622
|
+
* @param pubkey - The napp's pubkey
|
|
623
|
+
* @param dTag - The napp's dTag
|
|
624
|
+
* @param aggregateHash - The napp's build hash
|
|
625
|
+
* @param capability - The capability to check
|
|
626
|
+
* @returns True if the capability is granted and the napp is not blocked
|
|
627
|
+
*/
|
|
628
|
+
check(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): boolean;
|
|
629
|
+
/**
|
|
630
|
+
* Grant a capability to a napp identity.
|
|
631
|
+
*
|
|
632
|
+
* @param pubkey - The napp's pubkey
|
|
633
|
+
* @param dTag - The napp's dTag
|
|
634
|
+
* @param aggregateHash - The napp's build hash
|
|
635
|
+
* @param capability - The capability to grant
|
|
636
|
+
*/
|
|
637
|
+
grant(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void;
|
|
638
|
+
/**
|
|
639
|
+
* Revoke a capability from a napp identity.
|
|
640
|
+
*
|
|
641
|
+
* @param pubkey - The napp's pubkey
|
|
642
|
+
* @param dTag - The napp's dTag
|
|
643
|
+
* @param aggregateHash - The napp's build hash
|
|
644
|
+
* @param capability - The capability to revoke
|
|
645
|
+
*/
|
|
646
|
+
revoke(pubkey: string, dTag: string, aggregateHash: string, capability: Capability): void;
|
|
647
|
+
/**
|
|
648
|
+
* Block a napp identity entirely (all capabilities denied).
|
|
649
|
+
*
|
|
650
|
+
* @param pubkey - The napp's pubkey
|
|
651
|
+
* @param dTag - The napp's dTag
|
|
652
|
+
* @param aggregateHash - The napp's build hash
|
|
653
|
+
*/
|
|
654
|
+
block(pubkey: string, dTag: string, aggregateHash: string): void;
|
|
655
|
+
/**
|
|
656
|
+
* Unblock a napp identity.
|
|
657
|
+
*
|
|
658
|
+
* @param pubkey - The napp's pubkey
|
|
659
|
+
* @param dTag - The napp's dTag
|
|
660
|
+
* @param aggregateHash - The napp's build hash
|
|
661
|
+
*/
|
|
662
|
+
unblock(pubkey: string, dTag: string, aggregateHash: string): void;
|
|
663
|
+
/**
|
|
664
|
+
* Check if a napp identity is blocked.
|
|
665
|
+
*
|
|
666
|
+
* @param pubkey - The napp's pubkey
|
|
667
|
+
* @param dTag - The napp's dTag
|
|
668
|
+
* @param aggregateHash - The napp's build hash
|
|
669
|
+
* @returns True if the identity is blocked
|
|
670
|
+
*/
|
|
671
|
+
isBlocked(pubkey: string, dTag: string, aggregateHash: string): boolean;
|
|
672
|
+
/**
|
|
673
|
+
* Get the external ACL entry for a napp identity.
|
|
674
|
+
*
|
|
675
|
+
* @param pubkey - The napp's pubkey
|
|
676
|
+
* @param dTag - The napp's dTag
|
|
677
|
+
* @param aggregateHash - The napp's build hash
|
|
678
|
+
* @returns The ACL entry, or undefined if no explicit entry exists
|
|
679
|
+
*/
|
|
680
|
+
getEntry(pubkey: string, dTag: string, aggregateHash: string): AclEntry | undefined;
|
|
681
|
+
/**
|
|
682
|
+
* Get all ACL entries.
|
|
683
|
+
*
|
|
684
|
+
* @returns Array of all ACL entries
|
|
685
|
+
*/
|
|
686
|
+
getAllEntries(): AclEntry[];
|
|
687
|
+
/** Persist the ACL store to localStorage. */
|
|
688
|
+
persist(): void;
|
|
689
|
+
/** Load the ACL store from localStorage. Migrates old 3-segment keys to 2-segment format. */
|
|
690
|
+
load(): void;
|
|
691
|
+
/**
|
|
692
|
+
* Get the state quota for a napp identity.
|
|
693
|
+
*
|
|
694
|
+
* @param pubkey - The napp's pubkey
|
|
695
|
+
* @param dTag - The napp's dTag
|
|
696
|
+
* @param aggregateHash - The napp's build hash
|
|
697
|
+
* @returns The quota in bytes (defaults to DEFAULT_STATE_QUOTA)
|
|
698
|
+
*/
|
|
699
|
+
getStateQuota(pubkey: string, dTag: string, aggregateHash: string): number;
|
|
700
|
+
/** Clear all ACL entries and remove from localStorage. */
|
|
701
|
+
clear(): void;
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* audio-manager.ts — Shell-side registry of active audio sources.
|
|
706
|
+
*
|
|
707
|
+
* Tracks which windows are producing audio. UI components read the registry
|
|
708
|
+
* reactively via the version counter and CustomEvent pattern.
|
|
709
|
+
*/
|
|
710
|
+
/**
|
|
711
|
+
* An active audio source registered by a napplet.
|
|
712
|
+
* @example
|
|
713
|
+
* ```ts
|
|
714
|
+
* const source: AudioSource = {
|
|
715
|
+
* windowId: 'win-1', nappletClass: 'music-player',
|
|
716
|
+
* title: 'Now Playing', muted: false,
|
|
717
|
+
* };
|
|
718
|
+
* ```
|
|
719
|
+
*/
|
|
720
|
+
interface AudioSource {
|
|
721
|
+
windowId: string;
|
|
722
|
+
nappletClass: string;
|
|
723
|
+
title: string;
|
|
724
|
+
muted: boolean;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Registry of active audio sources across all napplet windows.
|
|
728
|
+
* Emits 'napplet:audio-changed' CustomEvents when the registry changes.
|
|
729
|
+
*
|
|
730
|
+
* @example
|
|
731
|
+
* ```ts
|
|
732
|
+
* import { audioManager } from '@kehto/shell';
|
|
733
|
+
*
|
|
734
|
+
* audioManager.register('win-1', 'music', 'My Song');
|
|
735
|
+
* audioManager.mute('win-1', true);
|
|
736
|
+
* ```
|
|
737
|
+
*/
|
|
738
|
+
declare const audioManager: {
|
|
739
|
+
/**
|
|
740
|
+
* Register a new audio source for a window.
|
|
741
|
+
*
|
|
742
|
+
* @param windowId - The window identifier
|
|
743
|
+
* @param nappletClass - The napplet class/type (e.g., 'music-player')
|
|
744
|
+
* @param title - Human-readable title for the audio source
|
|
745
|
+
*/
|
|
746
|
+
register(windowId: string, nappletClass: string, title: string): void;
|
|
747
|
+
/**
|
|
748
|
+
* Unregister an audio source for a window.
|
|
749
|
+
*
|
|
750
|
+
* @param windowId - The window identifier to remove
|
|
751
|
+
*/
|
|
752
|
+
unregister(windowId: string): void;
|
|
753
|
+
/**
|
|
754
|
+
* Update the state of an audio source (e.g., change title).
|
|
755
|
+
*
|
|
756
|
+
* @param windowId - The window identifier
|
|
757
|
+
* @param update - Partial update with optional title
|
|
758
|
+
*/
|
|
759
|
+
updateState(windowId: string, update: {
|
|
760
|
+
title?: string;
|
|
761
|
+
}): void;
|
|
762
|
+
/**
|
|
763
|
+
* Mute or unmute an audio source and notify the napplet via postMessage.
|
|
764
|
+
*
|
|
765
|
+
* @param windowId - The window identifier
|
|
766
|
+
* @param muted - True to mute, false to unmute
|
|
767
|
+
* @example
|
|
768
|
+
* ```ts
|
|
769
|
+
* audioManager.mute('win-1', true); // mute
|
|
770
|
+
* audioManager.mute('win-1', false); // unmute
|
|
771
|
+
* ```
|
|
772
|
+
*/
|
|
773
|
+
mute(windowId: string, muted: boolean): void;
|
|
774
|
+
/**
|
|
775
|
+
* Check if a window has a registered audio source.
|
|
776
|
+
*
|
|
777
|
+
* @param windowId - The window identifier
|
|
778
|
+
* @returns True if the window has an active audio source
|
|
779
|
+
*/
|
|
780
|
+
has(windowId: string): boolean;
|
|
781
|
+
/**
|
|
782
|
+
* Get the audio source for a window.
|
|
783
|
+
*
|
|
784
|
+
* @param windowId - The window identifier
|
|
785
|
+
* @returns The AudioSource, or undefined if not found
|
|
786
|
+
*/
|
|
787
|
+
get(windowId: string): AudioSource | undefined;
|
|
788
|
+
/**
|
|
789
|
+
* Get a snapshot of all audio sources.
|
|
790
|
+
*
|
|
791
|
+
* @returns A new Map of all active audio sources
|
|
792
|
+
*/
|
|
793
|
+
getSources(): Map<string, AudioSource>;
|
|
794
|
+
/** Current version counter (incremented on every change). */
|
|
795
|
+
readonly version: number;
|
|
796
|
+
/** Number of active audio sources. */
|
|
797
|
+
readonly count: number;
|
|
798
|
+
/** Clear all audio sources and reset version counter. */
|
|
799
|
+
clear(): void;
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* SessionRegistry — windowId to verified napplet pubkey bidirectional mapping.
|
|
804
|
+
*
|
|
805
|
+
* After a successful AUTH handshake, the ShellBridge registers the napplet's
|
|
806
|
+
* verified pubkey here. Both mappings are kept in sync.
|
|
807
|
+
*/
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* A pending napplet update — raised when a napplet reconnects with a different aggregateHash.
|
|
811
|
+
* @example
|
|
812
|
+
* ```ts
|
|
813
|
+
* const update: PendingUpdate = {
|
|
814
|
+
* windowId: 'win-1', pubkey: 'abc...', dTag: '3chat',
|
|
815
|
+
* oldHash: 'aaa', newHash: 'bbb',
|
|
816
|
+
* resolve: (action) => { if (action === 'accept') { // apply } },
|
|
817
|
+
* };
|
|
818
|
+
* ```
|
|
819
|
+
*/
|
|
820
|
+
interface PendingUpdate {
|
|
821
|
+
windowId: string;
|
|
822
|
+
pubkey: string;
|
|
823
|
+
dTag: string;
|
|
824
|
+
oldHash: string;
|
|
825
|
+
newHash: string;
|
|
826
|
+
resolve: (action: 'accept' | 'block') => void;
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Bidirectional registry mapping windowIds to verified napplet pubkeys.
|
|
830
|
+
* Maintained by ShellBridge after successful AUTH handshakes.
|
|
831
|
+
*
|
|
832
|
+
* @example
|
|
833
|
+
* ```ts
|
|
834
|
+
* import { sessionRegistry } from '@kehto/shell';
|
|
835
|
+
*
|
|
836
|
+
* const pubkey = sessionRegistry.getPubkey('win-1');
|
|
837
|
+
* const entry = pubkey ? sessionRegistry.getEntry(pubkey) : undefined;
|
|
838
|
+
* ```
|
|
839
|
+
*/
|
|
840
|
+
declare const sessionRegistry: {
|
|
841
|
+
/**
|
|
842
|
+
* Register a napplet entry, mapping windowId to pubkey and vice versa.
|
|
843
|
+
*
|
|
844
|
+
* @param windowId - The window identifier
|
|
845
|
+
* @param entry - The verified napplet session entry from AUTH handshake
|
|
846
|
+
*/
|
|
847
|
+
register(windowId: string, entry: SessionEntry): void;
|
|
848
|
+
/**
|
|
849
|
+
* Unregister a napplet by windowId, removing both mappings.
|
|
850
|
+
*
|
|
851
|
+
* @param windowId - The window identifier to remove
|
|
852
|
+
*/
|
|
853
|
+
unregister(windowId: string): void;
|
|
854
|
+
/**
|
|
855
|
+
* Get the pubkey associated with a windowId.
|
|
856
|
+
*
|
|
857
|
+
* @param windowId - The window identifier
|
|
858
|
+
* @returns The napplet's pubkey, or undefined if not registered
|
|
859
|
+
*/
|
|
860
|
+
getPubkey(windowId: string): string | undefined;
|
|
861
|
+
/**
|
|
862
|
+
* Get the full entry for a napplet pubkey.
|
|
863
|
+
*
|
|
864
|
+
* @param pubkey - The napplet's pubkey
|
|
865
|
+
* @returns The full SessionEntry, or undefined if not found
|
|
866
|
+
*/
|
|
867
|
+
getEntry(pubkey: string): SessionEntry | undefined;
|
|
868
|
+
/**
|
|
869
|
+
* Get the windowId for a napplet pubkey.
|
|
870
|
+
*
|
|
871
|
+
* @param pubkey - The napplet's pubkey
|
|
872
|
+
* @returns The windowId, or undefined if not found
|
|
873
|
+
*/
|
|
874
|
+
getWindowId(pubkey: string): string | undefined;
|
|
875
|
+
/**
|
|
876
|
+
* Check if a windowId has a registered napplet.
|
|
877
|
+
*
|
|
878
|
+
* @param windowId - The window identifier
|
|
879
|
+
* @returns True if the windowId has a registered napplet
|
|
880
|
+
*/
|
|
881
|
+
isRegistered(windowId: string): boolean;
|
|
882
|
+
/**
|
|
883
|
+
* Get all registered napplet entries.
|
|
884
|
+
*
|
|
885
|
+
* @returns Array of all SessionEntry objects
|
|
886
|
+
*/
|
|
887
|
+
getAllEntries(): SessionEntry[];
|
|
888
|
+
/**
|
|
889
|
+
* Set a pending update for a window (napplet reconnected with different hash).
|
|
890
|
+
*
|
|
891
|
+
* @param windowId - The window identifier
|
|
892
|
+
* @param update - The pending update details with resolve callback
|
|
893
|
+
*/
|
|
894
|
+
setPendingUpdate(windowId: string, update: PendingUpdate): void;
|
|
895
|
+
/**
|
|
896
|
+
* Get a pending update for a window.
|
|
897
|
+
*
|
|
898
|
+
* @param windowId - The window identifier
|
|
899
|
+
* @returns The pending update, or undefined if none
|
|
900
|
+
*/
|
|
901
|
+
getPendingUpdate(windowId: string): PendingUpdate | undefined;
|
|
902
|
+
/**
|
|
903
|
+
* Clear a pending update for a window.
|
|
904
|
+
*
|
|
905
|
+
* @param windowId - The window identifier
|
|
906
|
+
*/
|
|
907
|
+
clearPendingUpdate(windowId: string): void;
|
|
908
|
+
/** Clear all registrations and pending updates. */
|
|
909
|
+
clear(): void;
|
|
910
|
+
};
|
|
911
|
+
/** @deprecated Use sessionRegistry. Will be removed in v0.9.0. */
|
|
912
|
+
declare const nappKeyRegistry: {
|
|
913
|
+
/**
|
|
914
|
+
* Register a napplet entry, mapping windowId to pubkey and vice versa.
|
|
915
|
+
*
|
|
916
|
+
* @param windowId - The window identifier
|
|
917
|
+
* @param entry - The verified napplet session entry from AUTH handshake
|
|
918
|
+
*/
|
|
919
|
+
register(windowId: string, entry: SessionEntry): void;
|
|
920
|
+
/**
|
|
921
|
+
* Unregister a napplet by windowId, removing both mappings.
|
|
922
|
+
*
|
|
923
|
+
* @param windowId - The window identifier to remove
|
|
924
|
+
*/
|
|
925
|
+
unregister(windowId: string): void;
|
|
926
|
+
/**
|
|
927
|
+
* Get the pubkey associated with a windowId.
|
|
928
|
+
*
|
|
929
|
+
* @param windowId - The window identifier
|
|
930
|
+
* @returns The napplet's pubkey, or undefined if not registered
|
|
931
|
+
*/
|
|
932
|
+
getPubkey(windowId: string): string | undefined;
|
|
933
|
+
/**
|
|
934
|
+
* Get the full entry for a napplet pubkey.
|
|
935
|
+
*
|
|
936
|
+
* @param pubkey - The napplet's pubkey
|
|
937
|
+
* @returns The full SessionEntry, or undefined if not found
|
|
938
|
+
*/
|
|
939
|
+
getEntry(pubkey: string): SessionEntry | undefined;
|
|
940
|
+
/**
|
|
941
|
+
* Get the windowId for a napplet pubkey.
|
|
942
|
+
*
|
|
943
|
+
* @param pubkey - The napplet's pubkey
|
|
944
|
+
* @returns The windowId, or undefined if not found
|
|
945
|
+
*/
|
|
946
|
+
getWindowId(pubkey: string): string | undefined;
|
|
947
|
+
/**
|
|
948
|
+
* Check if a windowId has a registered napplet.
|
|
949
|
+
*
|
|
950
|
+
* @param windowId - The window identifier
|
|
951
|
+
* @returns True if the windowId has a registered napplet
|
|
952
|
+
*/
|
|
953
|
+
isRegistered(windowId: string): boolean;
|
|
954
|
+
/**
|
|
955
|
+
* Get all registered napplet entries.
|
|
956
|
+
*
|
|
957
|
+
* @returns Array of all SessionEntry objects
|
|
958
|
+
*/
|
|
959
|
+
getAllEntries(): SessionEntry[];
|
|
960
|
+
/**
|
|
961
|
+
* Set a pending update for a window (napplet reconnected with different hash).
|
|
962
|
+
*
|
|
963
|
+
* @param windowId - The window identifier
|
|
964
|
+
* @param update - The pending update details with resolve callback
|
|
965
|
+
*/
|
|
966
|
+
setPendingUpdate(windowId: string, update: PendingUpdate): void;
|
|
967
|
+
/**
|
|
968
|
+
* Get a pending update for a window.
|
|
969
|
+
*
|
|
970
|
+
* @param windowId - The window identifier
|
|
971
|
+
* @returns The pending update, or undefined if none
|
|
972
|
+
*/
|
|
973
|
+
getPendingUpdate(windowId: string): PendingUpdate | undefined;
|
|
974
|
+
/**
|
|
975
|
+
* Clear a pending update for a window.
|
|
976
|
+
*
|
|
977
|
+
* @param windowId - The window identifier
|
|
978
|
+
*/
|
|
979
|
+
clearPendingUpdate(windowId: string): void;
|
|
980
|
+
/** Clear all registrations and pending updates. */
|
|
981
|
+
clear(): void;
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* hooks-adapter.ts — Converts ShellAdapter (browser-facing) to RuntimeAdapter (environment-agnostic).
|
|
986
|
+
*
|
|
987
|
+
* The adapter bridges the gap between the shell's browser-oriented ShellAdapter interfaces
|
|
988
|
+
* (Window references, localStorage, postMessage) and the runtime's abstract RuntimeAdapter
|
|
989
|
+
* (windowId strings, persistence interfaces, sendToNapplet callbacks).
|
|
990
|
+
*/
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Browser-specific singletons that the adapter bridges to the runtime.
|
|
994
|
+
* These use browser APIs (Window, localStorage, postMessage, CustomEvent)
|
|
995
|
+
* that the runtime cannot access directly.
|
|
996
|
+
*/
|
|
997
|
+
interface BrowserDeps {
|
|
998
|
+
originRegistry: typeof originRegistry;
|
|
999
|
+
manifestCache: typeof manifestCache;
|
|
1000
|
+
aclStore: typeof aclStore;
|
|
1001
|
+
audioManager: typeof audioManager;
|
|
1002
|
+
nappKeyRegistry: typeof sessionRegistry;
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Convert ShellAdapter (browser-facing) into RuntimeAdapter (environment-agnostic).
|
|
1006
|
+
*
|
|
1007
|
+
* The adapter is the single translation layer between browser APIs and the
|
|
1008
|
+
* runtime's abstract interfaces. It:
|
|
1009
|
+
* - Converts Window references to windowId strings via originRegistry
|
|
1010
|
+
* - Wraps localStorage-backed singletons into persistence interfaces
|
|
1011
|
+
* - Translates relay pool API shapes (Observable → callback)
|
|
1012
|
+
*
|
|
1013
|
+
* @param shellHooks - The browser-oriented ShellAdapter provided by the host app
|
|
1014
|
+
* @param deps - Browser-specific singletons (originRegistry, aclStore, etc.)
|
|
1015
|
+
* @returns RuntimeAdapter suitable for createRuntime()
|
|
1016
|
+
*
|
|
1017
|
+
* @example
|
|
1018
|
+
* ```ts
|
|
1019
|
+
* const runtimeHooks = adaptHooks(shellHooks, {
|
|
1020
|
+
* originRegistry, manifestCache, aclStore, audioManager, nappKeyRegistry,
|
|
1021
|
+
* });
|
|
1022
|
+
* const runtime = createRuntime(runtimeHooks);
|
|
1023
|
+
* ```
|
|
1024
|
+
*/
|
|
1025
|
+
declare function adaptHooks(shellHooks: ShellAdapter, deps: BrowserDeps): RuntimeAdapter;
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* shell-init.ts — Shell initialization utilities.
|
|
1029
|
+
*
|
|
1030
|
+
* Provides buildShellCapabilities() — derives the shell's static NUB capability
|
|
1031
|
+
* set from the ShellAdapter configuration, used in the shell.ready / shell.init
|
|
1032
|
+
* handshake so napplets can query shell.supports() synchronously.
|
|
1033
|
+
*
|
|
1034
|
+
* Canonical NIP-5D forbids the shell from injecting a NIP-07 proxy object on
|
|
1035
|
+
* the napplet iframe's global scope (specs/NIP-5D.md line 44 + Security §6).
|
|
1036
|
+
* This module therefore does NOT expose any signing proxy to napplet code.
|
|
1037
|
+
* Signing/encryption is mediated by the shell through relay.publish and
|
|
1038
|
+
* relay.publishEncrypted — never via a napplet-visible API surface.
|
|
1039
|
+
*/
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Build the shell's static capability set from adapter configuration.
|
|
1043
|
+
*
|
|
1044
|
+
* NUB capabilities = canonical 8-domain list from @napplet/nub-*:
|
|
1045
|
+
* relay (gated on hooks.relayPool), identity, storage, ifc, theme, keys, media, notify.
|
|
1046
|
+
*
|
|
1047
|
+
* Sandbox permissions are left empty by default — host apps may extend after
|
|
1048
|
+
* construction. Sandbox entries returned here (and any host-app extensions)
|
|
1049
|
+
* MUST use the canonical `perm:<permission>` form — e.g. `'perm:popups'`,
|
|
1050
|
+
* `'perm:modals'`, `'perm:downloads'`. Napplets rely on the `perm:` prefix to
|
|
1051
|
+
* distinguish sandbox permissions from NUB-capability lookups (bare names,
|
|
1052
|
+
* resolved against `caps.nubs`); see specs/NIP-5D.md lines 81-94.
|
|
1053
|
+
*
|
|
1054
|
+
* @param hooks - The ShellAdapter provided by the host app
|
|
1055
|
+
* @returns ShellCapabilities with nubs (bare-named) and sandbox (perm:-prefixed) arrays
|
|
1056
|
+
* @example
|
|
1057
|
+
* ```ts
|
|
1058
|
+
* const caps = buildShellCapabilities(hooks);
|
|
1059
|
+
* // caps.nubs => ['relay','identity','storage','ifc','theme','keys','media','notify']
|
|
1060
|
+
* // (relay present when hooks.relayPool is provided; bare names only)
|
|
1061
|
+
* // caps.sandbox => [] // host app may extend with 'perm:popups', etc.
|
|
1062
|
+
* ```
|
|
1063
|
+
*/
|
|
1064
|
+
declare function buildShellCapabilities(hooks: ShellAdapter): ShellCapabilities;
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* identity-proxy.ts — Shell-side per-domain proxy for identity.* envelopes.
|
|
1068
|
+
*
|
|
1069
|
+
* Establishes the canonical proxy shape for @kehto/shell (Plan 12-11): each
|
|
1070
|
+
* per-domain proxy exposes a `dispatch` method that delegates napplet→shell
|
|
1071
|
+
* requests to the runtime and an `emit` method that posts shell→napplet
|
|
1072
|
+
* push envelopes through the origin registry.
|
|
1073
|
+
*
|
|
1074
|
+
* By default, `createShellBridge()` does NOT compose this proxy into its
|
|
1075
|
+
* dispatch path — the runtime already owns identity.* dispatch per Plan
|
|
1076
|
+
* 12-03 (see @kehto/services identity-service). This module exists as an
|
|
1077
|
+
* optional composition point for host apps that want to intercept or
|
|
1078
|
+
* augment identity dispatch (e.g. custom logging, sandboxed rewrites, test
|
|
1079
|
+
* doubles).
|
|
1080
|
+
*
|
|
1081
|
+
* The canonical proxy shape — dispatch + emit — is mirrored verbatim by
|
|
1082
|
+
* theme-proxy, keys-proxy, media-proxy, and notify-proxy. Storage today is
|
|
1083
|
+
* served by `@kehto/runtime` state-handler directly; a storage-proxy using
|
|
1084
|
+
* this shape can be added later if host apps need a composition seam.
|
|
1085
|
+
*/
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* Minimal origin-registry contract used by per-domain proxies.
|
|
1089
|
+
*
|
|
1090
|
+
* Accepts the `@kehto/shell` singleton `originRegistry` as well as any test
|
|
1091
|
+
* double with a matching `getIframeWindow` method.
|
|
1092
|
+
*/
|
|
1093
|
+
interface ProxyOriginRegistry {
|
|
1094
|
+
/** Resolve a registered napplet windowId to its iframe Window, or null. */
|
|
1095
|
+
getIframeWindow(windowId: string): Window | null;
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Dependencies for `createIdentityProxy`.
|
|
1099
|
+
*
|
|
1100
|
+
* @example
|
|
1101
|
+
* ```ts
|
|
1102
|
+
* const proxy = createIdentityProxy({
|
|
1103
|
+
* runtime: shellBridge.runtime,
|
|
1104
|
+
* originRegistry,
|
|
1105
|
+
* });
|
|
1106
|
+
* ```
|
|
1107
|
+
*/
|
|
1108
|
+
interface IdentityProxyDeps {
|
|
1109
|
+
/** The runtime engine that owns identity.* dispatch (Plan 12-03). */
|
|
1110
|
+
runtime: Runtime;
|
|
1111
|
+
/** Origin registry for resolving windowId → iframe Window. */
|
|
1112
|
+
originRegistry: ProxyOriginRegistry;
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Per-domain proxy for `identity.*` envelopes.
|
|
1116
|
+
*
|
|
1117
|
+
* The canonical proxy shape: `dispatch` routes napplet→shell requests into
|
|
1118
|
+
* the runtime; `emit` pushes shell→napplet envelopes through the iframe's
|
|
1119
|
+
* Window.
|
|
1120
|
+
*/
|
|
1121
|
+
interface IdentityProxy {
|
|
1122
|
+
/**
|
|
1123
|
+
* Route a napplet-originated identity.* envelope into the runtime.
|
|
1124
|
+
*
|
|
1125
|
+
* Delegation only — the runtime already owns identity.* dispatch after
|
|
1126
|
+
* Plan 12-03. Override by wrapping or replacing this method.
|
|
1127
|
+
*
|
|
1128
|
+
* @param windowId - The source napplet's windowId
|
|
1129
|
+
* @param envelope - The NIP-5D NappletMessage envelope
|
|
1130
|
+
*/
|
|
1131
|
+
dispatch(windowId: string, envelope: NappletMessage): void;
|
|
1132
|
+
/**
|
|
1133
|
+
* Push a shell-initiated identity-domain envelope into a napplet iframe.
|
|
1134
|
+
*
|
|
1135
|
+
* No-op when the originRegistry cannot resolve the windowId (unknown or
|
|
1136
|
+
* unregistered napplet). Never throws.
|
|
1137
|
+
*
|
|
1138
|
+
* @param windowId - The target napplet's windowId
|
|
1139
|
+
* @param envelope - The NIP-5D NappletMessage envelope to deliver
|
|
1140
|
+
*/
|
|
1141
|
+
emit(windowId: string, envelope: NappletMessage): void;
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Factory for the canonical identity-domain proxy.
|
|
1145
|
+
*
|
|
1146
|
+
* @param deps - Runtime + origin registry
|
|
1147
|
+
* @returns An {@link IdentityProxy} ready to route identity.* envelopes
|
|
1148
|
+
* @example
|
|
1149
|
+
* ```ts
|
|
1150
|
+
* import { createIdentityProxy, originRegistry, createShellBridge } from '@kehto/shell';
|
|
1151
|
+
*
|
|
1152
|
+
* const bridge = createShellBridge(hooks);
|
|
1153
|
+
* const identityProxy = createIdentityProxy({
|
|
1154
|
+
* runtime: bridge.runtime,
|
|
1155
|
+
* originRegistry,
|
|
1156
|
+
* });
|
|
1157
|
+
*
|
|
1158
|
+
* // Optional composition: intercept napplet->shell identity requests
|
|
1159
|
+
* const originalDispatch = identityProxy.dispatch;
|
|
1160
|
+
* identityProxy.dispatch = (windowId, envelope) => {
|
|
1161
|
+
* console.log('identity dispatch', windowId, envelope.type);
|
|
1162
|
+
* originalDispatch(windowId, envelope);
|
|
1163
|
+
* };
|
|
1164
|
+
* ```
|
|
1165
|
+
*/
|
|
1166
|
+
declare function createIdentityProxy(deps: IdentityProxyDeps): IdentityProxy;
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* theme-proxy.ts — Shell-side per-domain proxy for theme.* envelopes.
|
|
1170
|
+
*
|
|
1171
|
+
* Establishes the shell-side shape that Phase 13 composes into. Phase 13 is
|
|
1172
|
+
* expected to add `theme-service.ts` (runtime) + the shell-side `theme.set`
|
|
1173
|
+
* API that emits `theme.changed` push envelopes to registered napplets;
|
|
1174
|
+
* this proxy is the canonical seam those pieces plug into.
|
|
1175
|
+
*
|
|
1176
|
+
* Shape mirrors identity-proxy (Plan 12-11):
|
|
1177
|
+
*
|
|
1178
|
+
* - `dispatch(windowId, envelope)` routes napplet→shell `theme.get` into
|
|
1179
|
+
* the runtime (where Phase 13's theme-service will answer).
|
|
1180
|
+
* - `emit(windowId, envelope)` posts shell→napplet `theme.changed`
|
|
1181
|
+
* envelopes through the origin registry.
|
|
1182
|
+
*
|
|
1183
|
+
* By default `createShellBridge()` does NOT compose this proxy into its
|
|
1184
|
+
* dispatch path — the runtime owns theme.* dispatch. This module is an
|
|
1185
|
+
* optional composition point for host apps or Phase 13 wiring.
|
|
1186
|
+
*/
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* Dependencies for `createThemeProxy`.
|
|
1190
|
+
*
|
|
1191
|
+
* @example
|
|
1192
|
+
* ```ts
|
|
1193
|
+
* const proxy = createThemeProxy({
|
|
1194
|
+
* runtime: shellBridge.runtime,
|
|
1195
|
+
* originRegistry,
|
|
1196
|
+
* });
|
|
1197
|
+
* ```
|
|
1198
|
+
*/
|
|
1199
|
+
interface ThemeProxyDeps {
|
|
1200
|
+
/** The runtime engine that will own theme.* dispatch (Phase 13). */
|
|
1201
|
+
runtime: Runtime;
|
|
1202
|
+
/** Origin registry for resolving windowId → iframe Window. */
|
|
1203
|
+
originRegistry: ProxyOriginRegistry;
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Per-domain proxy for `theme.*` envelopes.
|
|
1207
|
+
*
|
|
1208
|
+
* Shape: `dispatch` routes napplet→shell requests into the runtime; `emit`
|
|
1209
|
+
* pushes shell→napplet envelopes through the iframe's Window.
|
|
1210
|
+
*/
|
|
1211
|
+
interface ThemeProxy {
|
|
1212
|
+
/**
|
|
1213
|
+
* Route a napplet-originated theme.* envelope (e.g. `theme.get`) into
|
|
1214
|
+
* the runtime.
|
|
1215
|
+
*
|
|
1216
|
+
* @param windowId - The source napplet's windowId
|
|
1217
|
+
* @param envelope - The NIP-5D NappletMessage envelope
|
|
1218
|
+
*/
|
|
1219
|
+
dispatch(windowId: string, envelope: NappletMessage): void;
|
|
1220
|
+
/**
|
|
1221
|
+
* Push a shell-initiated theme-domain envelope (e.g. `theme.changed`)
|
|
1222
|
+
* into a napplet iframe.
|
|
1223
|
+
*
|
|
1224
|
+
* No-op when the originRegistry cannot resolve the windowId (unknown or
|
|
1225
|
+
* unregistered napplet). Never throws.
|
|
1226
|
+
*
|
|
1227
|
+
* @param windowId - The target napplet's windowId
|
|
1228
|
+
* @param envelope - The NIP-5D NappletMessage envelope to deliver
|
|
1229
|
+
*/
|
|
1230
|
+
emit(windowId: string, envelope: NappletMessage): void;
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Factory for the canonical theme-domain proxy.
|
|
1234
|
+
*
|
|
1235
|
+
* @param deps - Runtime + origin registry
|
|
1236
|
+
* @returns A {@link ThemeProxy} ready to route theme.* envelopes
|
|
1237
|
+
* @example
|
|
1238
|
+
* ```ts
|
|
1239
|
+
* import { createThemeProxy, originRegistry, createShellBridge } from '@kehto/shell';
|
|
1240
|
+
*
|
|
1241
|
+
* const bridge = createShellBridge(hooks);
|
|
1242
|
+
* const themeProxy = createThemeProxy({
|
|
1243
|
+
* runtime: bridge.runtime,
|
|
1244
|
+
* originRegistry,
|
|
1245
|
+
* });
|
|
1246
|
+
*
|
|
1247
|
+
* // Phase 13: broadcast theme.changed to every registered napplet
|
|
1248
|
+
* for (const entry of bridge.runtime.sessionRegistry.getAllEntries()) {
|
|
1249
|
+
* themeProxy.emit(entry.windowId, { type: 'theme.changed', theme: newTheme });
|
|
1250
|
+
* }
|
|
1251
|
+
* ```
|
|
1252
|
+
*/
|
|
1253
|
+
declare function createThemeProxy(deps: ThemeProxyDeps): ThemeProxy;
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* keys-proxy.ts — Shell-side per-domain proxy for keys.* envelopes.
|
|
1257
|
+
*
|
|
1258
|
+
* Per Plan 12-05, the runtime already dispatches `keys.*` (forward,
|
|
1259
|
+
* registerAction, unregisterAction) to the keys-service. This proxy is the
|
|
1260
|
+
* shell-side composition point for host apps that want to observe or
|
|
1261
|
+
* inject keys envelopes — it pairs with `keys-forwarder.ts` (Plan 12-11)
|
|
1262
|
+
* which covers the shell→napplet `keys.forward` push path.
|
|
1263
|
+
*
|
|
1264
|
+
* Shape mirrors identity-proxy (Plan 12-11):
|
|
1265
|
+
*
|
|
1266
|
+
* - `dispatch(windowId, envelope)` routes napplet→shell keys requests
|
|
1267
|
+
* into the runtime.
|
|
1268
|
+
* - `emit(windowId, envelope)` posts shell→napplet pushes (`keys.action`,
|
|
1269
|
+
* `keys.bindings`, `keys.registerAction.result`) through the origin
|
|
1270
|
+
* registry.
|
|
1271
|
+
*
|
|
1272
|
+
* By default `createShellBridge()` does NOT compose this proxy into its
|
|
1273
|
+
* dispatch path — the runtime owns keys.* dispatch. This module is an
|
|
1274
|
+
* optional composition point for host apps (e.g. global hotkey UIs).
|
|
1275
|
+
*/
|
|
1276
|
+
|
|
1277
|
+
/**
|
|
1278
|
+
* Dependencies for `createKeysProxy`.
|
|
1279
|
+
*
|
|
1280
|
+
* @example
|
|
1281
|
+
* ```ts
|
|
1282
|
+
* const proxy = createKeysProxy({
|
|
1283
|
+
* runtime: shellBridge.runtime,
|
|
1284
|
+
* originRegistry,
|
|
1285
|
+
* });
|
|
1286
|
+
* ```
|
|
1287
|
+
*/
|
|
1288
|
+
interface KeysProxyDeps {
|
|
1289
|
+
/** The runtime engine that owns keys.* dispatch (Plan 12-05). */
|
|
1290
|
+
runtime: Runtime;
|
|
1291
|
+
/** Origin registry for resolving windowId → iframe Window. */
|
|
1292
|
+
originRegistry: ProxyOriginRegistry;
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Per-domain proxy for `keys.*` envelopes.
|
|
1296
|
+
*
|
|
1297
|
+
* Shape: `dispatch` routes napplet→shell requests into the runtime; `emit`
|
|
1298
|
+
* pushes shell→napplet envelopes through the iframe's Window.
|
|
1299
|
+
*/
|
|
1300
|
+
interface KeysProxy {
|
|
1301
|
+
/**
|
|
1302
|
+
* Route a napplet-originated keys.* envelope (e.g. `keys.forward`,
|
|
1303
|
+
* `keys.registerAction`) into the runtime.
|
|
1304
|
+
*
|
|
1305
|
+
* @param windowId - The source napplet's windowId
|
|
1306
|
+
* @param envelope - The NIP-5D NappletMessage envelope
|
|
1307
|
+
*/
|
|
1308
|
+
dispatch(windowId: string, envelope: NappletMessage): void;
|
|
1309
|
+
/**
|
|
1310
|
+
* Push a shell-initiated keys-domain envelope (e.g. `keys.action`,
|
|
1311
|
+
* `keys.bindings`) into a napplet iframe.
|
|
1312
|
+
*
|
|
1313
|
+
* Paired with `keys-forwarder.ts`: the forwarder targets the DOM
|
|
1314
|
+
* `keydown` → `keys.forward` path; this `emit` covers the complementary
|
|
1315
|
+
* host-initiated pushes (binding updates, action triggers).
|
|
1316
|
+
*
|
|
1317
|
+
* No-op when the originRegistry cannot resolve the windowId (unknown or
|
|
1318
|
+
* unregistered napplet). Never throws.
|
|
1319
|
+
*
|
|
1320
|
+
* @param windowId - The target napplet's windowId
|
|
1321
|
+
* @param envelope - The NIP-5D NappletMessage envelope to deliver
|
|
1322
|
+
*/
|
|
1323
|
+
emit(windowId: string, envelope: NappletMessage): void;
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Factory for the canonical keys-domain proxy.
|
|
1327
|
+
*
|
|
1328
|
+
* @param deps - Runtime + origin registry
|
|
1329
|
+
* @returns A {@link KeysProxy} ready to route keys.* envelopes
|
|
1330
|
+
* @example
|
|
1331
|
+
* ```ts
|
|
1332
|
+
* import { createKeysProxy, originRegistry, createShellBridge } from '@kehto/shell';
|
|
1333
|
+
*
|
|
1334
|
+
* const bridge = createShellBridge(hooks);
|
|
1335
|
+
* const keysProxy = createKeysProxy({
|
|
1336
|
+
* runtime: bridge.runtime,
|
|
1337
|
+
* originRegistry,
|
|
1338
|
+
* });
|
|
1339
|
+
*
|
|
1340
|
+
* // Host-app-initiated action trigger:
|
|
1341
|
+
* keysProxy.emit('win-editor', { type: 'keys.action', actionId: 'editor.save' });
|
|
1342
|
+
* ```
|
|
1343
|
+
*/
|
|
1344
|
+
declare function createKeysProxy(deps: KeysProxyDeps): KeysProxy;
|
|
1345
|
+
|
|
1346
|
+
/**
|
|
1347
|
+
* media-proxy.ts — Shell-side per-domain proxy for media.* envelopes.
|
|
1348
|
+
*
|
|
1349
|
+
* Establishes the shell-side composition seam for `@napplet/nub-media`
|
|
1350
|
+
* session-control envelopes. Shape mirrors identity-proxy (Plan 12-11):
|
|
1351
|
+
*
|
|
1352
|
+
* - `dispatch(windowId, envelope)` routes napplet→shell media requests
|
|
1353
|
+
* (`media.session.create`, `media.session.update`, `media.session.destroy`,
|
|
1354
|
+
* `media.state`, `media.capabilities`) into the runtime (Plan 12-06).
|
|
1355
|
+
* - `emit(windowId, envelope)` posts shell→napplet pushes (`media.command`,
|
|
1356
|
+
* `media.controls`, `media.session.create.result`) through the origin
|
|
1357
|
+
* registry.
|
|
1358
|
+
*
|
|
1359
|
+
* By default `createShellBridge()` does NOT compose this proxy into its
|
|
1360
|
+
* dispatch path — the runtime owns media.* dispatch. This module is an
|
|
1361
|
+
* optional composition point for host apps (e.g. shell-rendered playback
|
|
1362
|
+
* UIs that want to send `media.command` pushes).
|
|
1363
|
+
*/
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* Dependencies for `createMediaProxy`.
|
|
1367
|
+
*
|
|
1368
|
+
* @example
|
|
1369
|
+
* ```ts
|
|
1370
|
+
* const proxy = createMediaProxy({
|
|
1371
|
+
* runtime: shellBridge.runtime,
|
|
1372
|
+
* originRegistry,
|
|
1373
|
+
* });
|
|
1374
|
+
* ```
|
|
1375
|
+
*/
|
|
1376
|
+
interface MediaProxyDeps {
|
|
1377
|
+
/** The runtime engine that owns media.* dispatch (Plan 12-06). */
|
|
1378
|
+
runtime: Runtime;
|
|
1379
|
+
/** Origin registry for resolving windowId → iframe Window. */
|
|
1380
|
+
originRegistry: ProxyOriginRegistry;
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Per-domain proxy for `media.*` envelopes.
|
|
1384
|
+
*
|
|
1385
|
+
* Shape: `dispatch` routes napplet→shell requests into the runtime; `emit`
|
|
1386
|
+
* pushes shell→napplet envelopes through the iframe's Window.
|
|
1387
|
+
*/
|
|
1388
|
+
interface MediaProxy {
|
|
1389
|
+
/**
|
|
1390
|
+
* Route a napplet-originated media.* envelope into the runtime.
|
|
1391
|
+
*
|
|
1392
|
+
* @param windowId - The source napplet's windowId
|
|
1393
|
+
* @param envelope - The NIP-5D NappletMessage envelope
|
|
1394
|
+
*/
|
|
1395
|
+
dispatch(windowId: string, envelope: NappletMessage): void;
|
|
1396
|
+
/**
|
|
1397
|
+
* Push a shell-initiated media-domain envelope (e.g. `media.command`,
|
|
1398
|
+
* `media.controls`) into a napplet iframe.
|
|
1399
|
+
*
|
|
1400
|
+
* No-op when the originRegistry cannot resolve the windowId (unknown or
|
|
1401
|
+
* unregistered napplet). Never throws.
|
|
1402
|
+
*
|
|
1403
|
+
* @param windowId - The target napplet's windowId
|
|
1404
|
+
* @param envelope - The NIP-5D NappletMessage envelope to deliver
|
|
1405
|
+
*/
|
|
1406
|
+
emit(windowId: string, envelope: NappletMessage): void;
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Factory for the canonical media-domain proxy.
|
|
1410
|
+
*
|
|
1411
|
+
* @param deps - Runtime + origin registry
|
|
1412
|
+
* @returns A {@link MediaProxy} ready to route media.* envelopes
|
|
1413
|
+
* @example
|
|
1414
|
+
* ```ts
|
|
1415
|
+
* import { createMediaProxy, originRegistry, createShellBridge } from '@kehto/shell';
|
|
1416
|
+
*
|
|
1417
|
+
* const bridge = createShellBridge(hooks);
|
|
1418
|
+
* const mediaProxy = createMediaProxy({
|
|
1419
|
+
* runtime: bridge.runtime,
|
|
1420
|
+
* originRegistry,
|
|
1421
|
+
* });
|
|
1422
|
+
*
|
|
1423
|
+
* // Shell-UI-initiated media command:
|
|
1424
|
+
* mediaProxy.emit('win-player', {
|
|
1425
|
+
* type: 'media.command',
|
|
1426
|
+
* sessionId: 's1',
|
|
1427
|
+
* action: 'seek',
|
|
1428
|
+
* value: 120,
|
|
1429
|
+
* });
|
|
1430
|
+
* ```
|
|
1431
|
+
*/
|
|
1432
|
+
declare function createMediaProxy(deps: MediaProxyDeps): MediaProxy;
|
|
1433
|
+
|
|
1434
|
+
/**
|
|
1435
|
+
* notify-proxy.ts — Shell-side per-domain proxy for notify.* envelopes.
|
|
1436
|
+
*
|
|
1437
|
+
* Establishes the shell-side composition seam for `@napplet/nub-notify`
|
|
1438
|
+
* notification envelopes. Shape mirrors identity-proxy (Plan 12-11):
|
|
1439
|
+
*
|
|
1440
|
+
* - `dispatch(windowId, envelope)` routes napplet→shell notify requests
|
|
1441
|
+
* (`notify.send`, `notify.dismiss`, `notify.badge`,
|
|
1442
|
+
* `notify.channel.register`, `notify.permission.request`) into the
|
|
1443
|
+
* runtime (Plan 12-07).
|
|
1444
|
+
* - `emit(windowId, envelope)` posts shell→napplet pushes
|
|
1445
|
+
* (`notify.send.result`, `notify.permission.result`, `notify.action`,
|
|
1446
|
+
* `notify.clicked`, `notify.dismissed`, `notify.controls`) through the
|
|
1447
|
+
* origin registry.
|
|
1448
|
+
*
|
|
1449
|
+
* By default `createShellBridge()` does NOT compose this proxy into its
|
|
1450
|
+
* dispatch path — the runtime owns notify.* dispatch. This module is an
|
|
1451
|
+
* optional composition point for host apps (e.g. custom notification UIs
|
|
1452
|
+
* that need to emit `notify.clicked` / `notify.action` pushes).
|
|
1453
|
+
*/
|
|
1454
|
+
|
|
1455
|
+
/**
|
|
1456
|
+
* Dependencies for `createNotifyProxy`.
|
|
1457
|
+
*
|
|
1458
|
+
* @example
|
|
1459
|
+
* ```ts
|
|
1460
|
+
* const proxy = createNotifyProxy({
|
|
1461
|
+
* runtime: shellBridge.runtime,
|
|
1462
|
+
* originRegistry,
|
|
1463
|
+
* });
|
|
1464
|
+
* ```
|
|
1465
|
+
*/
|
|
1466
|
+
interface NotifyProxyDeps {
|
|
1467
|
+
/** The runtime engine that owns notify.* dispatch (Plan 12-07). */
|
|
1468
|
+
runtime: Runtime;
|
|
1469
|
+
/** Origin registry for resolving windowId → iframe Window. */
|
|
1470
|
+
originRegistry: ProxyOriginRegistry;
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Per-domain proxy for `notify.*` envelopes.
|
|
1474
|
+
*
|
|
1475
|
+
* Shape: `dispatch` routes napplet→shell requests into the runtime; `emit`
|
|
1476
|
+
* pushes shell→napplet envelopes through the iframe's Window.
|
|
1477
|
+
*/
|
|
1478
|
+
interface NotifyProxy {
|
|
1479
|
+
/**
|
|
1480
|
+
* Route a napplet-originated notify.* envelope into the runtime.
|
|
1481
|
+
*
|
|
1482
|
+
* @param windowId - The source napplet's windowId
|
|
1483
|
+
* @param envelope - The NIP-5D NappletMessage envelope
|
|
1484
|
+
*/
|
|
1485
|
+
dispatch(windowId: string, envelope: NappletMessage): void;
|
|
1486
|
+
/**
|
|
1487
|
+
* Push a shell-initiated notify-domain envelope (e.g. `notify.action`,
|
|
1488
|
+
* `notify.clicked`) into a napplet iframe.
|
|
1489
|
+
*
|
|
1490
|
+
* No-op when the originRegistry cannot resolve the windowId (unknown or
|
|
1491
|
+
* unregistered napplet). Never throws.
|
|
1492
|
+
*
|
|
1493
|
+
* @param windowId - The target napplet's windowId
|
|
1494
|
+
* @param envelope - The NIP-5D NappletMessage envelope to deliver
|
|
1495
|
+
*/
|
|
1496
|
+
emit(windowId: string, envelope: NappletMessage): void;
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Factory for the canonical notify-domain proxy.
|
|
1500
|
+
*
|
|
1501
|
+
* @param deps - Runtime + origin registry
|
|
1502
|
+
* @returns A {@link NotifyProxy} ready to route notify.* envelopes
|
|
1503
|
+
* @example
|
|
1504
|
+
* ```ts
|
|
1505
|
+
* import { createNotifyProxy, originRegistry, createShellBridge } from '@kehto/shell';
|
|
1506
|
+
*
|
|
1507
|
+
* const bridge = createShellBridge(hooks);
|
|
1508
|
+
* const notifyProxy = createNotifyProxy({
|
|
1509
|
+
* runtime: bridge.runtime,
|
|
1510
|
+
* originRegistry,
|
|
1511
|
+
* });
|
|
1512
|
+
*
|
|
1513
|
+
* // Shell-UI notifies napplet that user clicked its toast:
|
|
1514
|
+
* notifyProxy.emit('win-chat', {
|
|
1515
|
+
* type: 'notify.clicked',
|
|
1516
|
+
* notificationId: 'shell-42',
|
|
1517
|
+
* });
|
|
1518
|
+
* ```
|
|
1519
|
+
*/
|
|
1520
|
+
declare function createNotifyProxy(deps: NotifyProxyDeps): NotifyProxy;
|
|
1521
|
+
|
|
1522
|
+
/**
|
|
1523
|
+
* keys-forwarder.ts — Shell-side host keydown listener that forwards events
|
|
1524
|
+
* to registered napplets as `keys.forward` envelopes (Plan 12-11, NUB-05
|
|
1525
|
+
* shell-side half).
|
|
1526
|
+
*
|
|
1527
|
+
* Per `@napplet/nub-keys`, `keys.forward` is fire-and-forget (no result
|
|
1528
|
+
* envelope, no correlation id). Field names follow the nub convention:
|
|
1529
|
+
* `{ ctrl, alt, shift, meta }` — NOT the DOM-style `ctrlKey`/etc.
|
|
1530
|
+
*
|
|
1531
|
+
* Capability gate: only forwards to napplets whose ACL grants the
|
|
1532
|
+
* `keys:forward` capability (per Plan 12-10 `resolveCapabilitiesNub` 'keys'
|
|
1533
|
+
* case). The caller wires the cap-lookup via the `hasKeysForwardCap` dep so
|
|
1534
|
+
* this module stays free of any direct ACL-store dependency.
|
|
1535
|
+
*
|
|
1536
|
+
* Lifecycle: `createShellBridge()` attaches a forwarder on construction and
|
|
1537
|
+
* detaches it inside `bridge.destroy()`.
|
|
1538
|
+
*/
|
|
1539
|
+
|
|
1540
|
+
/**
|
|
1541
|
+
* Minimal origin-registry contract used by the forwarder — matches the
|
|
1542
|
+
* `@kehto/shell` singleton `originRegistry` and test doubles alike.
|
|
1543
|
+
*/
|
|
1544
|
+
interface KeysForwarderOriginRegistry {
|
|
1545
|
+
/** Resolve a registered napplet windowId to its iframe Window, or null. */
|
|
1546
|
+
getIframeWindow(windowId: string): Window | null;
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* Minimal session-registry contract used by the forwarder — matches the
|
|
1550
|
+
* `@kehto/shell` singleton `sessionRegistry` and test doubles alike.
|
|
1551
|
+
*/
|
|
1552
|
+
interface KeysForwarderSessionRegistry {
|
|
1553
|
+
/** Return every registered napplet session entry. */
|
|
1554
|
+
getAllEntries(): SessionEntry[];
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Dependencies for `createKeysForwarder`.
|
|
1558
|
+
*
|
|
1559
|
+
* @example
|
|
1560
|
+
* ```ts
|
|
1561
|
+
* const forwarder = createKeysForwarder({
|
|
1562
|
+
* originRegistry,
|
|
1563
|
+
* sessionRegistry,
|
|
1564
|
+
* hasKeysForwardCap: (pubkey) =>
|
|
1565
|
+
* aclStore.getAclEntry(pubkey)?.capabilities.includes('keys:forward') ?? false,
|
|
1566
|
+
* });
|
|
1567
|
+
* ```
|
|
1568
|
+
*/
|
|
1569
|
+
interface KeysForwarderDeps {
|
|
1570
|
+
/** Origin registry for resolving windowId → iframe Window. */
|
|
1571
|
+
originRegistry: KeysForwarderOriginRegistry;
|
|
1572
|
+
/** Session registry for enumerating napplets to forward to. */
|
|
1573
|
+
sessionRegistry: KeysForwarderSessionRegistry;
|
|
1574
|
+
/**
|
|
1575
|
+
* Capability check: returns true when the given napplet pubkey holds the
|
|
1576
|
+
* `keys:forward` capability. Called per keydown per registered napplet —
|
|
1577
|
+
* keep the implementation cheap.
|
|
1578
|
+
*/
|
|
1579
|
+
hasKeysForwardCap(pubkey: string): boolean;
|
|
1580
|
+
/**
|
|
1581
|
+
* Optional EventTarget to attach to. Defaults to the global `window` when
|
|
1582
|
+
* running in a DOM environment. Passing a fresh `new EventTarget()` is
|
|
1583
|
+
* useful for unit tests.
|
|
1584
|
+
*/
|
|
1585
|
+
target?: EventTarget;
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Handle returned by `createKeysForwarder`. Call `destroy()` to remove the
|
|
1589
|
+
* keydown listener (e.g. inside `bridge.destroy()`).
|
|
1590
|
+
*/
|
|
1591
|
+
interface KeysForwarder {
|
|
1592
|
+
/** Detach the keydown listener and release resources. */
|
|
1593
|
+
destroy(): void;
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* Create a host-keydown forwarder that posts `keys.forward` envelopes to
|
|
1597
|
+
* every registered napplet granted the `keys:forward` capability.
|
|
1598
|
+
*
|
|
1599
|
+
* @param deps - Origin registry, session registry, cap checker, optional target
|
|
1600
|
+
* @returns A {@link KeysForwarder} — call `destroy()` to detach
|
|
1601
|
+
* @example
|
|
1602
|
+
* ```ts
|
|
1603
|
+
* // Inside createShellBridge():
|
|
1604
|
+
* const keysForwarder = createKeysForwarder({
|
|
1605
|
+
* originRegistry,
|
|
1606
|
+
* sessionRegistry,
|
|
1607
|
+
* hasKeysForwardCap: (pubkey) =>
|
|
1608
|
+
* aclStore.getAclEntry(pubkey)?.capabilities.includes('keys:forward') ?? false,
|
|
1609
|
+
* });
|
|
1610
|
+
* // ...
|
|
1611
|
+
* // Inside bridge.destroy():
|
|
1612
|
+
* keysForwarder.destroy();
|
|
1613
|
+
* ```
|
|
1614
|
+
*/
|
|
1615
|
+
declare function createKeysForwarder(deps: KeysForwarderDeps): KeysForwarder;
|
|
1616
|
+
|
|
1617
|
+
export { type AclCheckEvent, type AclEntry, type AudioSource, type AuthHooks, type BrowserDeps, type ConfigHooks, type CryptoHooks, type DmHooks, type HotkeyHooks, type IdentityProxy, type IdentityProxyDeps, type KeysForwarder, type KeysForwarderDeps, type KeysForwarderOriginRegistry, type KeysForwarderSessionRegistry, type KeysProxy, type KeysProxyDeps, type ManifestCacheEntry, type MediaProxy, type MediaProxyDeps, type NappKeyEntry, type NotifyProxy, type NotifyProxyDeps, type PendingUpdate, type ProxyOriginRegistry, type RelayConfigHooks, type RelayPoolHooks, type RelayPoolLike, type SessionEntry, type ShellAdapter, type ShellBridge, type ShellCapabilities, type ThemeProxy, type ThemeProxyDeps, type WindowManagerHooks, type WorkerRelayHooks, type WorkerRelayLike, adaptHooks, audioManager, buildShellCapabilities, createIdentityProxy, createKeysForwarder, createKeysProxy, createMediaProxy, createNotifyProxy, createShellBridge, createThemeProxy, manifestCache, nappKeyRegistry, originRegistry, sessionRegistry };
|