@kehto/shell 0.1.0 → 0.5.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 +18 -13
- package/dist/index.d.ts +336 -117
- package/dist/index.js +267 -69
- package/dist/index.js.map +1 -1
- package/package.json +18 -32
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Browser adapter over @kehto/runtime — ShellBridge, domain proxies, keys-forwarder.
|
|
4
4
|
|
|
5
|
+
> **Alpha status:** Kehto is an early runtime implementation for a draft NIP-5D
|
|
6
|
+
> protocol. NUB contracts, `supports()` behavior, and shell capabilities are not
|
|
7
|
+
> final; treat this package as current implementation guidance.
|
|
8
|
+
|
|
5
9
|
## Install
|
|
6
10
|
|
|
7
11
|
```bash
|
|
@@ -14,11 +18,11 @@ pnpm add @kehto/shell
|
|
|
14
18
|
|
|
15
19
|
The primary entry point is `createShellBridge()` — it owns the postMessage listener, AUTH handshake, manifest verification, and every dispatch back into the runtime engine.
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
Current draft behaviors this package enforces:
|
|
18
22
|
|
|
19
23
|
- The shell does not inject a host-provided nostr object into napplets — NIP-5D explicitly forbids napplet-visible signing. Napplets call `relay.publish` / `relay.publishEncrypted` and the shell mediates the signing flow internally (NIP-44 default, NIP-04 opt-in for encrypted envelopes).
|
|
20
24
|
- `shell.supports(capability)` uses the `perm:<permission>` namespace for sandbox permissions, not the v1.1 bare capability list.
|
|
21
|
-
- Five optional per-domain proxies — `createIdentityProxy`, `createThemeProxy`, `createKeysProxy`, `createMediaProxy`, `createNotifyProxy` — can be composed between napplet and runtime to intercept or augment traffic per NUB. They are NOT wired by default (
|
|
25
|
+
- Five optional per-domain proxies — `createIdentityProxy`, `createThemeProxy`, `createKeysProxy`, `createMediaProxy`, `createNotifyProxy` — can be composed between napplet and runtime to intercept or augment traffic per NUB. They are NOT wired by default (Kehto's runtime already owns dispatch for the currently supported domains); they exist as host-app composition seams.
|
|
22
26
|
- The keys-forwarder pumps host keydown events into `keys.forward` envelopes for napplets that hold the `keys:forward` capability.
|
|
23
27
|
|
|
24
28
|
## Quick Start
|
|
@@ -43,24 +47,24 @@ bridge.runtime.registerService(
|
|
|
43
47
|
## Public API
|
|
44
48
|
|
|
45
49
|
### Bridge factory
|
|
46
|
-
-
|
|
50
|
+
- `createShellBridge` — primary entry point; returns a `ShellBridge` (exposed `runtime`, `shell.ready`, lifecycle hooks)
|
|
47
51
|
- `ShellBridge` — interface type for the returned bridge
|
|
48
52
|
|
|
49
53
|
### Hooks adapter
|
|
50
|
-
-
|
|
54
|
+
- `adaptHooks` — convert a `ShellAdapter` + `BrowserDeps` into the canonical `RuntimeAdapter` hook bag consumed by `@kehto/runtime`
|
|
51
55
|
|
|
52
56
|
### Shell init
|
|
53
|
-
-
|
|
57
|
+
- `buildShellCapabilities` — construct the current draft `ShellCapabilities` payload emitted during the `shell.ready` / `shell.init` handshake
|
|
54
58
|
|
|
55
59
|
### Domain proxies (NIP-5D composition seams)
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
60
|
+
- `createIdentityProxy` — intercept `identity.getProfile/getFollows/...` traffic
|
|
61
|
+
- `createThemeProxy` — intercept `theme.get/theme.changed`
|
|
62
|
+
- `createKeysProxy` — intercept `keys.bind/unbind/bindings`
|
|
63
|
+
- `createMediaProxy` — intercept `media.*` playback control
|
|
64
|
+
- `createNotifyProxy` — intercept `notify.send/list/read/dismiss`
|
|
61
65
|
|
|
62
66
|
### Keys forwarder
|
|
63
|
-
-
|
|
67
|
+
- `createKeysForwarder` — host-keydown pump into `keys.forward` envelopes; auto-attached by `createShellBridge`, also exported for hosts that manage their own forwarder instance
|
|
64
68
|
|
|
65
69
|
### Session / origin registry
|
|
66
70
|
- `sessionRegistry` — canonical windowId ↔ verified-napplet registry singleton
|
|
@@ -88,13 +92,14 @@ Exported for host-app integration: `ShellAdapter`, `ShellCapabilities`, `RelayPo
|
|
|
88
92
|
|
|
89
93
|
### Compat re-exports (DRIFT-CORE-06)
|
|
90
94
|
|
|
91
|
-
Retained for
|
|
95
|
+
Retained for migration consumers; new integrations should use current NIP-5D envelope types from `@napplet/core`. Slated for removal once upstream restores those exports.
|
|
92
96
|
|
|
93
97
|
Re-exported from `@kehto/runtime`: the v1.1 bus-kind enum, auth event kind, shell bridge URI constant, protocol version string, the full capability list, destructive-kind set, and the replay window seconds constant. Re-exported types cover the v1.1 capability union and bus-kind numeric union. See the typedoc API reference below for the exact identifier list.
|
|
94
98
|
|
|
95
99
|
## API Reference
|
|
96
100
|
|
|
97
|
-
Full
|
|
101
|
+
Full package docs: [`docs/packages/shell.md`](../../docs/packages/shell.md).
|
|
102
|
+
Generated API module: `docs/api/modules/_kehto_shell.html` (run `pnpm docs:api`).
|
|
98
103
|
|
|
99
104
|
## License
|
|
100
105
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,45 +1,9 @@
|
|
|
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';
|
|
1
|
+
import { Capability, NappletClass, ServiceRegistry, RuntimeConfigOverrides, ConsentRequest, Runtime, SessionEntry, PendingUpdate, RuntimeAdapter } from '@kehto/runtime';
|
|
2
|
+
export { ALL_CAPABILITIES, AclChecker, Capability, ConsentRequest, EnforceConfig, EnforceResult, IdentityResolver, NappKeyEntry, NappletClass, NubEnforceConfig, NubMessage, PendingUpdate, ServiceDescriptor, ServiceHandler, ServiceRegistry, SessionEntry, createEnforceGate, createNubEnforceGate, formatDenialReason } from '@kehto/runtime';
|
|
3
3
|
import { NappletMessage, NostrEvent, NostrFilter } from '@napplet/core';
|
|
4
4
|
export { NappletMessage, NostrEvent, NostrFilter, TOPICS, TopicKey, TopicValue } from '@napplet/core';
|
|
5
|
-
import { Theme } from '@napplet/nub
|
|
5
|
+
import { Theme } from '@napplet/nub/theme/types';
|
|
6
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
7
|
/**
|
|
44
8
|
* ACL entry controlling what a napplet pubkey is permitted to do.
|
|
45
9
|
* @example
|
|
@@ -198,8 +162,9 @@ interface AclCheckEvent {
|
|
|
198
162
|
message?: NappletMessage | unknown[];
|
|
199
163
|
}
|
|
200
164
|
/**
|
|
201
|
-
* Static capability set
|
|
202
|
-
* Used by `window.napplet.shell.supports()` for
|
|
165
|
+
* Static capability set sent to napplet iframes through the shell.ready /
|
|
166
|
+
* shell.init handshake. Used by hosted `window.napplet.shell.supports()` for
|
|
167
|
+
* synchronous capability queries after the shim consumes shell.init.
|
|
203
168
|
*
|
|
204
169
|
* Per canonical NIP-5D (specs/NIP-5D.md lines 81-94), supports() distinguishes
|
|
205
170
|
* two namespaces:
|
|
@@ -216,9 +181,9 @@ interface AclCheckEvent {
|
|
|
216
181
|
*/
|
|
217
182
|
interface ShellCapabilities {
|
|
218
183
|
/**
|
|
219
|
-
* NUB domain prefixes the shell handles.
|
|
220
|
-
* relay, identity, storage, ifc, theme, keys, media, notify
|
|
221
|
-
*
|
|
184
|
+
* NUB domain prefixes the shell handles. Kehto's hosted playground set is:
|
|
185
|
+
* relay, identity, storage, ifc, theme, keys, media, notify, config,
|
|
186
|
+
* resource, connect, class. `relay` is conditional on RelayPoolHooks.
|
|
222
187
|
*
|
|
223
188
|
* Entries are bare domain names — `'relay'`, `'identity'`, etc. They MUST NOT
|
|
224
189
|
* carry the `perm:` prefix; that prefix is reserved for the `sandbox` array.
|
|
@@ -272,12 +237,18 @@ interface ShellAdapter {
|
|
|
272
237
|
onHashMismatch?: (dTag: string, claimed: string, computed: string) => void;
|
|
273
238
|
/**
|
|
274
239
|
* Called at iframe creation for NIP-5D napplets.
|
|
275
|
-
* Returns identity metadata for originRegistry.register()
|
|
276
|
-
*
|
|
240
|
+
* Returns identity metadata for originRegistry.register(), INCLUDING the
|
|
241
|
+
* class posture (CLASS-01, breaking v1.7). Returning `class: null` selects
|
|
242
|
+
* the permissive default (D2). Returning null overall means "not NIP-5D /
|
|
243
|
+
* skip registration" (unchanged semantics from v1.6).
|
|
244
|
+
*
|
|
245
|
+
* BREAKING v1.7: previously `{ dTag, aggregateHash } | null`; now requires
|
|
246
|
+
* `class` in the non-null branch. See .changeset/class-01-breaking-hook.md.
|
|
277
247
|
*/
|
|
278
248
|
onNip5dIframeCreate?: (windowId: string) => {
|
|
279
249
|
dTag: string;
|
|
280
250
|
aggregateHash: string;
|
|
251
|
+
class: NappletClass;
|
|
281
252
|
} | null;
|
|
282
253
|
/**
|
|
283
254
|
* Optional service extensions. Each key is a service name (e.g., 'audio',
|
|
@@ -305,17 +276,144 @@ interface ShellAdapter {
|
|
|
305
276
|
}
|
|
306
277
|
|
|
307
278
|
/**
|
|
308
|
-
*
|
|
279
|
+
* connect-store.ts — NUB-CONNECT grant registry keyed on (dTag, aggregateHash).
|
|
280
|
+
*
|
|
281
|
+
* Mirrors acl-store.ts pattern: module-level singleton, localStorage persistence,
|
|
282
|
+
* composite-key Map. Grants are per-napplet-build: changing the aggregateHash
|
|
283
|
+
* (a rebuild) invalidates prior grants (CONNECT-06) — the composite key makes
|
|
284
|
+
* this structural, not policy-driven.
|
|
309
285
|
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
* (
|
|
313
|
-
* lives in @kehto/runtime.
|
|
286
|
+
* Default policy is RESTRICTIVE (opposite of aclStore): unknown (dTag, hash, origin)
|
|
287
|
+
* combinations return `false` from check() — napplets must be explicitly granted
|
|
288
|
+
* origins via grant(). This is the NUB-CONNECT security invariant.
|
|
314
289
|
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
|
|
290
|
+
* @see packages/shell/src/types/internal-connect.ts for the wire types.
|
|
291
|
+
* @see docs/policies/SHELL-CONNECT-POLICY.md (Plan 39-05) for the full policy.
|
|
292
|
+
*/
|
|
293
|
+
/**
|
|
294
|
+
* Public interface for the NUB-CONNECT grant store singleton.
|
|
295
|
+
*
|
|
296
|
+
* All methods are keyed on (dTag, aggregateHash). A rebuild with a new
|
|
297
|
+
* aggregateHash is a distinct identity — CONNECT-06 hash-upgrade semantics
|
|
298
|
+
* are structurally guaranteed: check(dTag, newHash, origin) returns false
|
|
299
|
+
* because newHash has no entry in the store.
|
|
300
|
+
*/
|
|
301
|
+
interface ConnectStore {
|
|
302
|
+
/**
|
|
303
|
+
* Check whether an origin has been granted for a specific (dTag, aggregateHash).
|
|
304
|
+
*
|
|
305
|
+
* Returns false for unknown (dTag, aggregateHash) pairs — RESTRICTIVE default.
|
|
306
|
+
* Returns false for unknown origins even if the (dTag, hash) pair exists.
|
|
307
|
+
*
|
|
308
|
+
* @param dTag - The napplet's dTag identifier
|
|
309
|
+
* @param aggregateHash - The napplet's build aggregate hash
|
|
310
|
+
* @param origin - The origin to check (e.g., 'https://relay.example.com')
|
|
311
|
+
* @returns true if origin is in the grant set; false otherwise
|
|
312
|
+
*/
|
|
313
|
+
check(dTag: string, aggregateHash: string, origin: string): boolean;
|
|
314
|
+
/**
|
|
315
|
+
* Get all granted origins for a (dTag, aggregateHash) pair.
|
|
316
|
+
*
|
|
317
|
+
* Returns an empty array for unknown (dTag, aggregateHash) pairs.
|
|
318
|
+
*
|
|
319
|
+
* @param dTag - The napplet's dTag identifier
|
|
320
|
+
* @param aggregateHash - The napplet's build aggregate hash
|
|
321
|
+
* @returns Readonly array of granted origin strings; empty if not granted
|
|
322
|
+
*/
|
|
323
|
+
getOrigins(dTag: string, aggregateHash: string): readonly string[];
|
|
324
|
+
/**
|
|
325
|
+
* Grant a set of origins to a (dTag, aggregateHash) pair.
|
|
326
|
+
*
|
|
327
|
+
* Deduplicates and sorts origins for idempotent CSP header output (Plan 39-03).
|
|
328
|
+
* Replaces any existing grant for the same (dTag, aggregateHash) pair.
|
|
329
|
+
* Persists to localStorage automatically.
|
|
330
|
+
*
|
|
331
|
+
* @param dTag - The napplet's dTag identifier
|
|
332
|
+
* @param aggregateHash - The napplet's build aggregate hash
|
|
333
|
+
* @param origins - Origins to grant (e.g., ['https://relay.example.com', 'wss://relay2.example.com'])
|
|
334
|
+
*/
|
|
335
|
+
grant(dTag: string, aggregateHash: string, origins: readonly string[]): void;
|
|
336
|
+
/**
|
|
337
|
+
* Revoke all granted origins for a (dTag, aggregateHash) pair.
|
|
338
|
+
*
|
|
339
|
+
* After revocation, check() returns false and getOrigins() returns [].
|
|
340
|
+
* Persists to localStorage automatically.
|
|
341
|
+
*
|
|
342
|
+
* @param dTag - The napplet's dTag identifier
|
|
343
|
+
* @param aggregateHash - The napplet's build aggregate hash
|
|
344
|
+
*/
|
|
345
|
+
revoke(dTag: string, aggregateHash: string): void;
|
|
346
|
+
/**
|
|
347
|
+
* Get all current grants across all (dTag, aggregateHash) pairs.
|
|
348
|
+
*
|
|
349
|
+
* Used by the Vite CSP plugin (Plan 39-03) to build the connect-src
|
|
350
|
+
* header for each napplet on dev-server startup.
|
|
351
|
+
*
|
|
352
|
+
* @returns Readonly array of all grants
|
|
353
|
+
*/
|
|
354
|
+
getAllGrants(): ReadonlyArray<{
|
|
355
|
+
dTag: string;
|
|
356
|
+
aggregateHash: string;
|
|
357
|
+
origins: readonly string[];
|
|
358
|
+
}>;
|
|
359
|
+
/**
|
|
360
|
+
* Persist the current store state to localStorage under 'napplet:connect'.
|
|
361
|
+
*
|
|
362
|
+
* Tolerates unavailable localStorage (e.g., SSR, private browsing with
|
|
363
|
+
* storage disabled). Called automatically by grant() and revoke().
|
|
364
|
+
*/
|
|
365
|
+
persist(): void;
|
|
366
|
+
/**
|
|
367
|
+
* Load the store state from localStorage.
|
|
368
|
+
*
|
|
369
|
+
* Validates shape before populating; clears store on corrupt data.
|
|
370
|
+
* Should be called once on shell startup before handling any napplet messages.
|
|
371
|
+
*/
|
|
372
|
+
load(): void;
|
|
373
|
+
/**
|
|
374
|
+
* Clear all grants and remove the localStorage entry.
|
|
375
|
+
*
|
|
376
|
+
* Best-effort localStorage removal (tolerates unavailability).
|
|
377
|
+
*/
|
|
378
|
+
clear(): void;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* NUB-CONNECT grant store singleton.
|
|
382
|
+
*
|
|
383
|
+
* Module-level instance — import and use directly. Persists under localStorage
|
|
384
|
+
* key 'napplet:connect'. Call `connectStore.load()` on shell startup to restore
|
|
385
|
+
* persisted grants from a previous session.
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* ```ts
|
|
389
|
+
* import { connectStore } from '@kehto/shell';
|
|
390
|
+
*
|
|
391
|
+
* // On shell startup:
|
|
392
|
+
* connectStore.load();
|
|
393
|
+
*
|
|
394
|
+
* // On consent approval:
|
|
395
|
+
* connectStore.grant(dTag, aggregateHash, ['https://relay.example.com']);
|
|
396
|
+
*
|
|
397
|
+
* // In Vite CSP plugin (Plan 39-03):
|
|
398
|
+
* const origins = connectStore.getOrigins(dTag, aggregateHash);
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
401
|
+
declare const connectStore: ConnectStore;
|
|
402
|
+
/**
|
|
403
|
+
* Compose the canonical `<dTag>:<aggregateHash>` composite key used by the
|
|
404
|
+
* connect-store. Exported for consumers that need to key their own maps by
|
|
405
|
+
* the same identity (e.g., Vite CSP plugin — Plan 39-03).
|
|
406
|
+
*
|
|
407
|
+
* @param dTag - The napplet's dTag identifier
|
|
408
|
+
* @param aggregateHash - The napplet's build aggregate hash
|
|
409
|
+
* @returns Composite key string in `<dTag>:<aggregateHash>` format
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* ```ts
|
|
413
|
+
* const key = connectGrantKey('chat', 'abc123'); // 'chat:abc123'
|
|
414
|
+
* ```
|
|
318
415
|
*/
|
|
416
|
+
declare function connectGrantKey(dTag: string, aggregateHash: string): string;
|
|
319
417
|
|
|
320
418
|
/**
|
|
321
419
|
* Shell-side message bridge that handles NIP-5D communication with napplet iframes.
|
|
@@ -354,17 +452,19 @@ interface ShellBridge {
|
|
|
354
452
|
*/
|
|
355
453
|
handleMessage(event: MessageEvent): void;
|
|
356
454
|
/**
|
|
357
|
-
* Inject a shell-originated event into subscription delivery.
|
|
455
|
+
* Inject a shell-originated event into subscription delivery. Under NIP-5D,
|
|
456
|
+
* shell-originated events are forwarded to napplets as ifc.event envelope
|
|
457
|
+
* messages. The runtime's injectEvent() handles the per-session routing.
|
|
358
458
|
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
459
|
+
* v1.10 hard-removed the v1.8 soft-rename compatibility branch for the
|
|
460
|
+
* old `auth:identity-changed` topic. Use the canonical `identity:changed`
|
|
461
|
+
* topic for identity-change pushes.
|
|
362
462
|
*
|
|
363
|
-
* @param topic - The event topic tag value
|
|
463
|
+
* @param topic - The event topic tag value. Forwarded exactly once.
|
|
364
464
|
* @param payload - The event content
|
|
365
465
|
* @example
|
|
366
466
|
* ```ts
|
|
367
|
-
* bridge.injectEvent('
|
|
467
|
+
* bridge.injectEvent('identity:changed', { pubkey: userPubkey });
|
|
368
468
|
* ```
|
|
369
469
|
*/
|
|
370
470
|
injectEvent(topic: string, payload: unknown): void;
|
|
@@ -417,12 +517,36 @@ interface ShellBridge {
|
|
|
417
517
|
* ```
|
|
418
518
|
*/
|
|
419
519
|
publishTheme(theme: Theme): void;
|
|
520
|
+
/**
|
|
521
|
+
* Publish the current shell-user identity to every loaded napplet.
|
|
522
|
+
*
|
|
523
|
+
* Posts an `identity.changed` envelope (shell → napplet push) with the
|
|
524
|
+
* current user pubkey. An empty pubkey means no signer/user identity is
|
|
525
|
+
* currently connected. This is distinct from NIP-5D napplet session identity,
|
|
526
|
+
* which remains source-bound at iframe creation.
|
|
527
|
+
*
|
|
528
|
+
* @param pubkey - Current user's hex pubkey, or empty string when signed out.
|
|
529
|
+
*/
|
|
530
|
+
publishIdentityChanged(pubkey: string): void;
|
|
420
531
|
/**
|
|
421
532
|
* Access the underlying runtime instance for advanced use cases.
|
|
422
533
|
* Provides direct access to the runtime's sessionRegistry, aclState,
|
|
423
534
|
* and manifestCache.
|
|
424
535
|
*/
|
|
425
536
|
readonly runtime: Runtime;
|
|
537
|
+
/**
|
|
538
|
+
* Access the NUB-CONNECT grant store. Per-napplet connect grants are
|
|
539
|
+
* keyed on (dTag, aggregateHash) and persisted under localStorage key
|
|
540
|
+
* 'napplet:connect'. Public API surface for the Vite CSP middleware
|
|
541
|
+
* (Plan 39-03) and the consent flow (Plan 39-04).
|
|
542
|
+
*
|
|
543
|
+
* Default policy is RESTRICTIVE: check() returns false for any origin
|
|
544
|
+
* not explicitly granted. Call load() on shell startup to restore
|
|
545
|
+
* persisted grants from a previous session.
|
|
546
|
+
*
|
|
547
|
+
* @see SHELL-CONNECT-POLICY.md for the full policy.
|
|
548
|
+
*/
|
|
549
|
+
readonly connectStore: ConnectStore;
|
|
426
550
|
}
|
|
427
551
|
/**
|
|
428
552
|
* Create a ShellBridge instance with dependency injection via hooks.
|
|
@@ -451,13 +575,6 @@ interface ShellBridge {
|
|
|
451
575
|
*/
|
|
452
576
|
declare function createShellBridge(hooks: ShellAdapter): ShellBridge;
|
|
453
577
|
|
|
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
578
|
/**
|
|
462
579
|
* Bidirectional registry mapping Window references to windowId strings.
|
|
463
580
|
* Optionally stores NIP-5D identity metadata (dTag and aggregateHash) per window.
|
|
@@ -592,16 +709,6 @@ declare const manifestCache: {
|
|
|
592
709
|
clear(): void;
|
|
593
710
|
};
|
|
594
711
|
|
|
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
712
|
/**
|
|
606
713
|
* ACL store — manages capability grants, revocations, and blocks for napp identities.
|
|
607
714
|
* Persists to localStorage and uses a permissive default policy (all capabilities granted).
|
|
@@ -806,25 +913,6 @@ declare const audioManager: {
|
|
|
806
913
|
* verified pubkey here. Both mappings are kept in sync.
|
|
807
914
|
*/
|
|
808
915
|
|
|
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
916
|
/**
|
|
829
917
|
* Bidirectional registry mapping windowIds to verified napplet pubkeys.
|
|
830
918
|
* Maintained by ShellBridge after successful AUTH handshakes.
|
|
@@ -1024,25 +1112,13 @@ interface BrowserDeps {
|
|
|
1024
1112
|
*/
|
|
1025
1113
|
declare function adaptHooks(shellHooks: ShellAdapter, deps: BrowserDeps): RuntimeAdapter;
|
|
1026
1114
|
|
|
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
1115
|
/**
|
|
1042
1116
|
* Build the shell's static capability set from adapter configuration.
|
|
1043
1117
|
*
|
|
1044
|
-
* NUB capabilities =
|
|
1045
|
-
*
|
|
1118
|
+
* NUB capabilities = Kehto-hosted domain list from @napplet/nub subpaths,
|
|
1119
|
+
* plus relay (gated on hooks.relayPool):
|
|
1120
|
+
* relay (gated on hooks.relayPool), identity, storage, ifc, theme,
|
|
1121
|
+
* keys, media, notify, config, resource, connect, class.
|
|
1046
1122
|
*
|
|
1047
1123
|
* Sandbox permissions are left empty by default — host apps may extend after
|
|
1048
1124
|
* construction. Sandbox entries returned here (and any host-app extensions)
|
|
@@ -1056,7 +1132,7 @@ declare function adaptHooks(shellHooks: ShellAdapter, deps: BrowserDeps): Runtim
|
|
|
1056
1132
|
* @example
|
|
1057
1133
|
* ```ts
|
|
1058
1134
|
* const caps = buildShellCapabilities(hooks);
|
|
1059
|
-
* // caps.nubs => ['relay','identity','storage','ifc','theme','keys','media','notify']
|
|
1135
|
+
* // caps.nubs => ['relay','identity','storage','ifc','theme','keys','media','notify','config','resource','connect','class','cvm']
|
|
1060
1136
|
* // (relay present when hooks.relayPool is provided; bare names only)
|
|
1061
1137
|
* // caps.sandbox => [] // host app may extend with 'perm:popups', etc.
|
|
1062
1138
|
* ```
|
|
@@ -1346,7 +1422,7 @@ declare function createKeysProxy(deps: KeysProxyDeps): KeysProxy;
|
|
|
1346
1422
|
/**
|
|
1347
1423
|
* media-proxy.ts — Shell-side per-domain proxy for media.* envelopes.
|
|
1348
1424
|
*
|
|
1349
|
-
* Establishes the shell-side composition seam for `@napplet/nub
|
|
1425
|
+
* Establishes the shell-side composition seam for `@napplet/nub/media`
|
|
1350
1426
|
* session-control envelopes. Shape mirrors identity-proxy (Plan 12-11):
|
|
1351
1427
|
*
|
|
1352
1428
|
* - `dispatch(windowId, envelope)` routes napplet→shell media requests
|
|
@@ -1434,7 +1510,7 @@ declare function createMediaProxy(deps: MediaProxyDeps): MediaProxy;
|
|
|
1434
1510
|
/**
|
|
1435
1511
|
* notify-proxy.ts — Shell-side per-domain proxy for notify.* envelopes.
|
|
1436
1512
|
*
|
|
1437
|
-
* Establishes the shell-side composition seam for `@napplet/nub
|
|
1513
|
+
* Establishes the shell-side composition seam for `@napplet/nub/notify`
|
|
1438
1514
|
* notification envelopes. Shape mirrors identity-proxy (Plan 12-11):
|
|
1439
1515
|
*
|
|
1440
1516
|
* - `dispatch(windowId, envelope)` routes napplet→shell notify requests
|
|
@@ -1524,7 +1600,7 @@ declare function createNotifyProxy(deps: NotifyProxyDeps): NotifyProxy;
|
|
|
1524
1600
|
* to registered napplets as `keys.forward` envelopes (Plan 12-11, NUB-05
|
|
1525
1601
|
* shell-side half).
|
|
1526
1602
|
*
|
|
1527
|
-
* Per `@napplet/nub
|
|
1603
|
+
* Per `@napplet/nub/keys`, `keys.forward` is fire-and-forget (no result
|
|
1528
1604
|
* envelope, no correlation id). Field names follow the nub convention:
|
|
1529
1605
|
* `{ ctrl, alt, shift, meta }` — NOT the DOM-style `ctrlKey`/etc.
|
|
1530
1606
|
*
|
|
@@ -1614,4 +1690,147 @@ interface KeysForwarder {
|
|
|
1614
1690
|
*/
|
|
1615
1691
|
declare function createKeysForwarder(deps: KeysForwarderDeps): KeysForwarder;
|
|
1616
1692
|
|
|
1617
|
-
|
|
1693
|
+
/**
|
|
1694
|
+
* @file internal-connect.ts
|
|
1695
|
+
*
|
|
1696
|
+
* Kehto-internal shell-side connect-store + consent-flow types. Per
|
|
1697
|
+
* PROJECT.md Decision #31, this is NOT a staging-ground duplicate of upstream
|
|
1698
|
+
* `@napplet/nub/connect`: upstream exports a napplet-side accessor interface
|
|
1699
|
+
* (`NappletConnect = { granted, origins }`) plus the shared
|
|
1700
|
+
* `normalizeConnectOrigin` pure validator. Kehto's types here describe the
|
|
1701
|
+
* shell's grant-store records (`ConnectGrant`, `ConnectGrantKey`,
|
|
1702
|
+
* `ConsentResult`), used by the connect-store singleton and the Vite CSP
|
|
1703
|
+
* plugin's consent flow.
|
|
1704
|
+
*
|
|
1705
|
+
* The two surfaces are complementary: upstream's accessor type ships at the
|
|
1706
|
+
* napplet boundary; kehto's grant-store types live shell-side. No retirement
|
|
1707
|
+
* planned. For canonical origin validation (kehto has no local impl;
|
|
1708
|
+
* Decision #32), consume `@napplet/nub/connect`'s `normalizeConnectOrigin`
|
|
1709
|
+
* directly.
|
|
1710
|
+
*
|
|
1711
|
+
* Downstream consumers (Phase 39 `connect-store.ts`, Vite CSP middleware,
|
|
1712
|
+
* consent flow UI) import from this module.
|
|
1713
|
+
*/
|
|
1714
|
+
/**
|
|
1715
|
+
* Grant key composed from the napplet's dTag and build aggregateHash. A hash
|
|
1716
|
+
* upgrade invalidates prior grants (CONNECT-06).
|
|
1717
|
+
*/
|
|
1718
|
+
interface ConnectGrantKey {
|
|
1719
|
+
dTag: string;
|
|
1720
|
+
aggregateHash: string;
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* A persisted connect grant. Stored in the shell connect-store singleton,
|
|
1724
|
+
* surfaced as the CSP `connect-src` origin list for the corresponding napplet.
|
|
1725
|
+
*/
|
|
1726
|
+
interface ConnectGrant {
|
|
1727
|
+
/** Composite key `"<dTag>:<aggregateHash>"` under which this grant is stored. */
|
|
1728
|
+
key: string;
|
|
1729
|
+
/** The set of origins (WebSocket and HTTPS) the napplet is granted to connect to. */
|
|
1730
|
+
origins: readonly string[];
|
|
1731
|
+
/** Epoch millis when the grant was approved by the user. */
|
|
1732
|
+
grantedAt: number;
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Consent flow outcome. `'dismiss'` resolves to deny (MUST NOT default to allow).
|
|
1736
|
+
* `'timeout'` also resolves to deny.
|
|
1737
|
+
*/
|
|
1738
|
+
type ConsentResult = 'approve' | 'deny' | 'dismiss' | 'timeout';
|
|
1739
|
+
/**
|
|
1740
|
+
* Inbound consent request from a napplet requesting connect access to one or
|
|
1741
|
+
* more origins.
|
|
1742
|
+
*/
|
|
1743
|
+
interface ConnectConsentRequest {
|
|
1744
|
+
dTag: string;
|
|
1745
|
+
aggregateHash: string;
|
|
1746
|
+
/** Origins the napplet is asking to be granted access to. */
|
|
1747
|
+
requestedOrigins: readonly string[];
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
/**
|
|
1751
|
+
* @file internal-resource.ts
|
|
1752
|
+
*
|
|
1753
|
+
* Kehto-internal shell-side resource wire types. Per PROJECT.md Decision #31,
|
|
1754
|
+
* this is NOT a staging-ground duplicate of upstream `@napplet/nub/resource`:
|
|
1755
|
+
* Phase 44 audit confirmed the two surfaces diverge substantively — different
|
|
1756
|
+
* field names (kehto's `requestId` vs upstream `id`; kehto's `bodyBase64` vs
|
|
1757
|
+
* upstream `blob` + `mime`), different message-type names
|
|
1758
|
+
* (`ResourceBytesRequest` vs `ResourceBytesMessage`), and disjoint error
|
|
1759
|
+
* vocabularies (kehto: 5 codes `{denied, canceled, network-error, invalid-url,
|
|
1760
|
+
* class-forbidden}`; upstream: 8 codes `{not-found, blocked-by-policy,
|
|
1761
|
+
* timeout, too-large, unsupported-scheme, decode-failed, network-error,
|
|
1762
|
+
* quota-exceeded}`).
|
|
1763
|
+
*
|
|
1764
|
+
* Future migration to upstream's surface is its own phase. For now this file
|
|
1765
|
+
* owns the wire shapes kehto's `resource-service.ts` and resource-demo napplet
|
|
1766
|
+
* already implement.
|
|
1767
|
+
*
|
|
1768
|
+
* Canonical 4-message protocol (RESOURCE-03):
|
|
1769
|
+
* Inbound: resource.bytes, resource.cancel
|
|
1770
|
+
* Outbound: resource.bytes.result, resource.bytes.error
|
|
1771
|
+
*/
|
|
1772
|
+
/**
|
|
1773
|
+
* Unique id for correlating a `resource.bytes` request to its later result /
|
|
1774
|
+
* error / cancel envelope.
|
|
1775
|
+
*/
|
|
1776
|
+
type ResourceRequestId = string;
|
|
1777
|
+
/**
|
|
1778
|
+
* Inbound: napplet requests bytes from an origin. Shell consults
|
|
1779
|
+
* getConnectGrants(dTag, aggregateHash) before proxying; ungranted origins
|
|
1780
|
+
* receive a `denied` error (RESOURCE-01 H-03 prevention).
|
|
1781
|
+
*/
|
|
1782
|
+
interface ResourceBytesRequest {
|
|
1783
|
+
type: 'resource.bytes';
|
|
1784
|
+
requestId: ResourceRequestId;
|
|
1785
|
+
url: string;
|
|
1786
|
+
/** Optional subset of fetch init (method, headers). Body bytes are shell-proxy-internal. */
|
|
1787
|
+
init?: {
|
|
1788
|
+
method?: string;
|
|
1789
|
+
headers?: Readonly<Record<string, string>>;
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Inbound: napplet cancels a previously-issued bytes request. Shell correlates
|
|
1794
|
+
* to the in-flight request by requestId and emits a `resource.bytes.error`
|
|
1795
|
+
* with `code: 'canceled'`.
|
|
1796
|
+
*/
|
|
1797
|
+
interface ResourceCancelRequest {
|
|
1798
|
+
type: 'resource.cancel';
|
|
1799
|
+
requestId: ResourceRequestId;
|
|
1800
|
+
}
|
|
1801
|
+
/**
|
|
1802
|
+
* Outbound: successful fetch result.
|
|
1803
|
+
*/
|
|
1804
|
+
interface ResourceBytesResult {
|
|
1805
|
+
type: 'resource.bytes.result';
|
|
1806
|
+
requestId: ResourceRequestId;
|
|
1807
|
+
status: number;
|
|
1808
|
+
headers: Readonly<Record<string, string>>;
|
|
1809
|
+
/** Raw response bytes, base64-encoded for the postMessage wire. */
|
|
1810
|
+
bodyBase64: string;
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Canonical typed-error codes for RESOURCE-03 cancel correlation + H-03
|
|
1814
|
+
* ungranted-origin refusal.
|
|
1815
|
+
*/
|
|
1816
|
+
type ResourceErrorCode = 'denied' | 'canceled' | 'network-error' | 'invalid-url' | 'class-forbidden';
|
|
1817
|
+
/**
|
|
1818
|
+
* Outbound: error result — used for both grant-refusal (RESOURCE-01) and
|
|
1819
|
+
* cancel-correlation (RESOURCE-03) cases.
|
|
1820
|
+
*/
|
|
1821
|
+
interface ResourceBytesError {
|
|
1822
|
+
type: 'resource.bytes.error';
|
|
1823
|
+
requestId: ResourceRequestId;
|
|
1824
|
+
code: ResourceErrorCode;
|
|
1825
|
+
message: string;
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* Union of all inbound resource wire messages (napplet -> shell).
|
|
1829
|
+
*/
|
|
1830
|
+
type ResourceInbound = ResourceBytesRequest | ResourceCancelRequest;
|
|
1831
|
+
/**
|
|
1832
|
+
* Union of all outbound resource wire messages (shell -> napplet).
|
|
1833
|
+
*/
|
|
1834
|
+
type ResourceOutbound = ResourceBytesResult | ResourceBytesError;
|
|
1835
|
+
|
|
1836
|
+
export { type AclCheckEvent, type AclEntry, type AudioSource, type AuthHooks, type BrowserDeps, type ConfigHooks, type ConnectConsentRequest, type ConnectGrant, type ConnectGrantKey, type ConnectStore, type ConsentResult, 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 NotifyProxy, type NotifyProxyDeps, type ProxyOriginRegistry, type RelayConfigHooks, type RelayPoolHooks, type RelayPoolLike, type ResourceBytesError, type ResourceBytesRequest, type ResourceBytesResult, type ResourceCancelRequest, type ResourceErrorCode, type ResourceInbound, type ResourceOutbound, type ResourceRequestId, type ShellAdapter, type ShellBridge, type ShellCapabilities, type ThemeProxy, type ThemeProxyDeps, type WindowManagerHooks, type WorkerRelayHooks, type WorkerRelayLike, adaptHooks, audioManager, buildShellCapabilities, connectGrantKey, connectStore, createIdentityProxy, createKeysForwarder, createKeysProxy, createMediaProxy, createNotifyProxy, createShellBridge, createThemeProxy, manifestCache, nappKeyRegistry, originRegistry, sessionRegistry };
|