@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.js
ADDED
|
@@ -0,0 +1,1129 @@
|
|
|
1
|
+
// src/shell-bridge.ts
|
|
2
|
+
import { createRuntime } from "@kehto/runtime";
|
|
3
|
+
|
|
4
|
+
// src/hooks-adapter.ts
|
|
5
|
+
function bytesToHex(bytes) {
|
|
6
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
7
|
+
}
|
|
8
|
+
function hexToBytes(hex) {
|
|
9
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
10
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
11
|
+
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
12
|
+
}
|
|
13
|
+
return bytes;
|
|
14
|
+
}
|
|
15
|
+
function adaptHooks(shellHooks, deps) {
|
|
16
|
+
const { originRegistry: originRegistry2 } = deps;
|
|
17
|
+
const sendToNapplet = (windowId, msg) => {
|
|
18
|
+
const win = originRegistry2.getIframeWindow(windowId);
|
|
19
|
+
if (win) win.postMessage(msg, "*");
|
|
20
|
+
};
|
|
21
|
+
const relayPool = {
|
|
22
|
+
subscribe(filters, callback, relayUrls) {
|
|
23
|
+
const pool = shellHooks.relayPool.getRelayPool();
|
|
24
|
+
if (!pool) return { unsubscribe() {
|
|
25
|
+
} };
|
|
26
|
+
const urls = relayUrls ?? shellHooks.relayPool.selectRelayTier(filters);
|
|
27
|
+
const sub = pool.subscription(urls, filters).subscribe((item) => {
|
|
28
|
+
if (item === "EOSE") {
|
|
29
|
+
callback("EOSE");
|
|
30
|
+
} else {
|
|
31
|
+
callback(item);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return { unsubscribe: () => sub.unsubscribe() };
|
|
35
|
+
},
|
|
36
|
+
publish(event) {
|
|
37
|
+
const pool = shellHooks.relayPool.getRelayPool();
|
|
38
|
+
if (!pool) return;
|
|
39
|
+
const relayUrls = shellHooks.relayPool.selectRelayTier([]);
|
|
40
|
+
pool.publish(relayUrls, event);
|
|
41
|
+
},
|
|
42
|
+
selectRelayTier(filters) {
|
|
43
|
+
return shellHooks.relayPool.selectRelayTier(filters);
|
|
44
|
+
},
|
|
45
|
+
trackSubscription(subKey, cleanup) {
|
|
46
|
+
shellHooks.relayPool.trackSubscription(subKey, cleanup);
|
|
47
|
+
},
|
|
48
|
+
untrackSubscription(subKey) {
|
|
49
|
+
shellHooks.relayPool.untrackSubscription(subKey);
|
|
50
|
+
},
|
|
51
|
+
openScopedRelay(windowId, relayUrl, subId, filters, sendFn) {
|
|
52
|
+
const win = originRegistry2.getIframeWindow(windowId);
|
|
53
|
+
if (win) shellHooks.relayPool.openScopedRelay(windowId, relayUrl, subId, filters, win);
|
|
54
|
+
},
|
|
55
|
+
closeScopedRelay(windowId) {
|
|
56
|
+
shellHooks.relayPool.closeScopedRelay(windowId);
|
|
57
|
+
},
|
|
58
|
+
publishToScopedRelay(windowId, event) {
|
|
59
|
+
return shellHooks.relayPool.publishToScopedRelay(windowId, event);
|
|
60
|
+
},
|
|
61
|
+
isAvailable() {
|
|
62
|
+
return shellHooks.relayPool.getRelayPool() !== null;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const cache2 = {
|
|
66
|
+
async query(filters) {
|
|
67
|
+
const workerRelay = shellHooks.workerRelay.getWorkerRelay();
|
|
68
|
+
if (!workerRelay) return [];
|
|
69
|
+
const subId = crypto.randomUUID();
|
|
70
|
+
return workerRelay.query(["REQ", subId, ...filters]);
|
|
71
|
+
},
|
|
72
|
+
store(event) {
|
|
73
|
+
const workerRelay = shellHooks.workerRelay.getWorkerRelay();
|
|
74
|
+
if (!workerRelay) return;
|
|
75
|
+
try {
|
|
76
|
+
workerRelay.event(event)?.catch?.(() => {
|
|
77
|
+
});
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
isAvailable() {
|
|
82
|
+
return shellHooks.workerRelay.getWorkerRelay() !== null;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const auth = {
|
|
86
|
+
getUserPubkey() {
|
|
87
|
+
return shellHooks.auth.getUserPubkey();
|
|
88
|
+
},
|
|
89
|
+
getSigner() {
|
|
90
|
+
return shellHooks.auth.getSigner();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const config = {
|
|
94
|
+
getNappUpdateBehavior() {
|
|
95
|
+
return shellHooks.config.getNappUpdateBehavior();
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const hotkeys = {
|
|
99
|
+
executeHotkeyFromForward(event) {
|
|
100
|
+
shellHooks.hotkeys.executeHotkeyFromForward(event);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const cryptoHooks = {
|
|
104
|
+
async verifyEvent(event) {
|
|
105
|
+
return shellHooks.crypto.verifyEvent(event);
|
|
106
|
+
},
|
|
107
|
+
randomUUID() {
|
|
108
|
+
return crypto.randomUUID();
|
|
109
|
+
},
|
|
110
|
+
randomBytes(length) {
|
|
111
|
+
const bytes = new Uint8Array(length);
|
|
112
|
+
crypto.getRandomValues(bytes);
|
|
113
|
+
return bytes;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const aclPersistence = {
|
|
117
|
+
persist(data) {
|
|
118
|
+
try {
|
|
119
|
+
localStorage.setItem("napplet:acl", data);
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
load() {
|
|
124
|
+
try {
|
|
125
|
+
return localStorage.getItem("napplet:acl");
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const manifestPersistence = {
|
|
132
|
+
persist(data) {
|
|
133
|
+
try {
|
|
134
|
+
localStorage.setItem("napplet:manifest-cache", data);
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
load() {
|
|
139
|
+
try {
|
|
140
|
+
return localStorage.getItem("napplet:manifest-cache");
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const statePersistence = {
|
|
147
|
+
get(scopedKey) {
|
|
148
|
+
try {
|
|
149
|
+
return localStorage.getItem(scopedKey);
|
|
150
|
+
} catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
set(scopedKey, value) {
|
|
155
|
+
try {
|
|
156
|
+
localStorage.setItem(scopedKey, value);
|
|
157
|
+
return true;
|
|
158
|
+
} catch {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
remove(scopedKey) {
|
|
163
|
+
try {
|
|
164
|
+
localStorage.removeItem(scopedKey);
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
clear(prefix) {
|
|
169
|
+
try {
|
|
170
|
+
const keysToRemove = [];
|
|
171
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
172
|
+
const key = localStorage.key(i);
|
|
173
|
+
if (key?.startsWith(prefix)) keysToRemove.push(key);
|
|
174
|
+
}
|
|
175
|
+
for (const key of keysToRemove) localStorage.removeItem(key);
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
keys(prefix) {
|
|
180
|
+
try {
|
|
181
|
+
const result = [];
|
|
182
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
183
|
+
const key = localStorage.key(i);
|
|
184
|
+
if (key?.startsWith(prefix)) result.push(key);
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
} catch {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
calculateBytes(prefix, excludeKey) {
|
|
192
|
+
try {
|
|
193
|
+
let total = 0;
|
|
194
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
195
|
+
const key = localStorage.key(i);
|
|
196
|
+
if (!key?.startsWith(prefix)) continue;
|
|
197
|
+
if (excludeKey && key === excludeKey) continue;
|
|
198
|
+
const value = localStorage.getItem(key) ?? "";
|
|
199
|
+
total += new TextEncoder().encode(key + value).length;
|
|
200
|
+
}
|
|
201
|
+
return total;
|
|
202
|
+
} catch {
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const windowManager = {
|
|
208
|
+
createWindow(options) {
|
|
209
|
+
return shellHooks.windowManager.createWindow(options);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
const relayConfig = {
|
|
213
|
+
addRelay(tier, url) {
|
|
214
|
+
shellHooks.relayConfig.addRelay(tier, url);
|
|
215
|
+
},
|
|
216
|
+
removeRelay(tier, url) {
|
|
217
|
+
shellHooks.relayConfig.removeRelay(tier, url);
|
|
218
|
+
},
|
|
219
|
+
getRelayConfig() {
|
|
220
|
+
return shellHooks.relayConfig.getRelayConfig();
|
|
221
|
+
},
|
|
222
|
+
getNip66Suggestions() {
|
|
223
|
+
return shellHooks.relayConfig.getNip66Suggestions();
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const shellSecretPersistence = {
|
|
227
|
+
get() {
|
|
228
|
+
try {
|
|
229
|
+
const hex = localStorage.getItem("napplet-shell-secret");
|
|
230
|
+
if (!hex) return null;
|
|
231
|
+
return hexToBytes(hex);
|
|
232
|
+
} catch {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
set(secret) {
|
|
237
|
+
try {
|
|
238
|
+
localStorage.setItem("napplet-shell-secret", bytesToHex(secret));
|
|
239
|
+
} catch {
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
const guidPersistence = {
|
|
244
|
+
get(windowId) {
|
|
245
|
+
try {
|
|
246
|
+
return localStorage.getItem(`napplet-guid:${windowId}`);
|
|
247
|
+
} catch {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
set(windowId, guid) {
|
|
252
|
+
try {
|
|
253
|
+
localStorage.setItem(`napplet-guid:${windowId}`, guid);
|
|
254
|
+
} catch {
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
remove(windowId) {
|
|
258
|
+
try {
|
|
259
|
+
localStorage.removeItem(`napplet-guid:${windowId}`);
|
|
260
|
+
} catch {
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
const dm = shellHooks.dm ? {
|
|
265
|
+
sendDm(recipientPubkey, message) {
|
|
266
|
+
return shellHooks.dm.sendDm(recipientPubkey, message);
|
|
267
|
+
}
|
|
268
|
+
} : void 0;
|
|
269
|
+
return {
|
|
270
|
+
sendToNapplet,
|
|
271
|
+
relayPool,
|
|
272
|
+
cache: cache2,
|
|
273
|
+
auth,
|
|
274
|
+
config,
|
|
275
|
+
hotkeys,
|
|
276
|
+
crypto: cryptoHooks,
|
|
277
|
+
aclPersistence,
|
|
278
|
+
manifestPersistence,
|
|
279
|
+
statePersistence,
|
|
280
|
+
windowManager,
|
|
281
|
+
relayConfig,
|
|
282
|
+
dm,
|
|
283
|
+
shellSecretPersistence,
|
|
284
|
+
guidPersistence,
|
|
285
|
+
onAclCheck: shellHooks.onAclCheck,
|
|
286
|
+
onHashMismatch: shellHooks.onHashMismatch,
|
|
287
|
+
services: shellHooks.services,
|
|
288
|
+
getConfigOverrides: shellHooks.getConfigOverrides
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/origin-registry.ts
|
|
293
|
+
var registry = /* @__PURE__ */ new Map();
|
|
294
|
+
var originRegistry = {
|
|
295
|
+
/**
|
|
296
|
+
* Register a window reference with a windowId and optional identity metadata.
|
|
297
|
+
*
|
|
298
|
+
* @param win - The iframe's contentWindow reference
|
|
299
|
+
* @param windowId - The unique identifier for this napplet window
|
|
300
|
+
* @param identity - Optional NIP-5D identity metadata (dTag and aggregateHash)
|
|
301
|
+
*/
|
|
302
|
+
register(win, windowId, identity) {
|
|
303
|
+
registry.set(win, {
|
|
304
|
+
windowId,
|
|
305
|
+
dTag: identity?.dTag,
|
|
306
|
+
aggregateHash: identity?.aggregateHash
|
|
307
|
+
});
|
|
308
|
+
},
|
|
309
|
+
/**
|
|
310
|
+
* Unregister a window by its windowId, removing the mapping.
|
|
311
|
+
*
|
|
312
|
+
* @param windowId - The window identifier to remove
|
|
313
|
+
*/
|
|
314
|
+
unregister(windowId) {
|
|
315
|
+
for (const [win, entry] of registry.entries()) {
|
|
316
|
+
if (entry.windowId === windowId) {
|
|
317
|
+
registry.delete(win);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
/**
|
|
322
|
+
* Look up the windowId for a given Window reference.
|
|
323
|
+
*
|
|
324
|
+
* @param win - The Window reference (typically from event.source)
|
|
325
|
+
* @returns The windowId string, or undefined if not registered
|
|
326
|
+
*/
|
|
327
|
+
getWindowId(win) {
|
|
328
|
+
return registry.get(win)?.windowId;
|
|
329
|
+
},
|
|
330
|
+
/**
|
|
331
|
+
* Look up the Window reference for a given windowId.
|
|
332
|
+
*
|
|
333
|
+
* @param windowId - The window identifier to look up
|
|
334
|
+
* @returns The Window reference, or null if not found
|
|
335
|
+
*/
|
|
336
|
+
getIframeWindow(windowId) {
|
|
337
|
+
for (const [win, entry] of registry.entries()) {
|
|
338
|
+
if (entry.windowId === windowId) return win;
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
},
|
|
342
|
+
/**
|
|
343
|
+
* Get all registered windowId strings.
|
|
344
|
+
*
|
|
345
|
+
* @returns Array of all registered window identifiers
|
|
346
|
+
*/
|
|
347
|
+
getAllWindowIds() {
|
|
348
|
+
return Array.from(registry.values()).map((entry) => entry.windowId);
|
|
349
|
+
},
|
|
350
|
+
/**
|
|
351
|
+
* Get identity metadata for a registered Window.
|
|
352
|
+
*
|
|
353
|
+
* @param win - The Window reference to look up
|
|
354
|
+
* @returns Identity metadata, or undefined if not registered or no identity set
|
|
355
|
+
*/
|
|
356
|
+
getIdentity(win) {
|
|
357
|
+
const entry = registry.get(win);
|
|
358
|
+
if (!entry?.dTag || !entry?.aggregateHash) return void 0;
|
|
359
|
+
return { dTag: entry.dTag, aggregateHash: entry.aggregateHash };
|
|
360
|
+
},
|
|
361
|
+
/** Clear all registrations. */
|
|
362
|
+
clear() {
|
|
363
|
+
registry.clear();
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// src/session-registry.ts
|
|
368
|
+
var byWindowId = /* @__PURE__ */ new Map();
|
|
369
|
+
var byPubkey = /* @__PURE__ */ new Map();
|
|
370
|
+
var pendingUpdates = /* @__PURE__ */ new Map();
|
|
371
|
+
var _pendingVersion = 0;
|
|
372
|
+
var sessionRegistry = {
|
|
373
|
+
/**
|
|
374
|
+
* Register a napplet entry, mapping windowId to pubkey and vice versa.
|
|
375
|
+
*
|
|
376
|
+
* @param windowId - The window identifier
|
|
377
|
+
* @param entry - The verified napplet session entry from AUTH handshake
|
|
378
|
+
*/
|
|
379
|
+
register(windowId, entry) {
|
|
380
|
+
byWindowId.set(windowId, entry.pubkey);
|
|
381
|
+
byPubkey.set(entry.pubkey, entry);
|
|
382
|
+
},
|
|
383
|
+
/**
|
|
384
|
+
* Unregister a napplet by windowId, removing both mappings.
|
|
385
|
+
*
|
|
386
|
+
* @param windowId - The window identifier to remove
|
|
387
|
+
*/
|
|
388
|
+
unregister(windowId) {
|
|
389
|
+
const pubkey = byWindowId.get(windowId);
|
|
390
|
+
if (pubkey) {
|
|
391
|
+
byPubkey.delete(pubkey);
|
|
392
|
+
byWindowId.delete(windowId);
|
|
393
|
+
}
|
|
394
|
+
pendingUpdates.delete(windowId);
|
|
395
|
+
},
|
|
396
|
+
/**
|
|
397
|
+
* Get the pubkey associated with a windowId.
|
|
398
|
+
*
|
|
399
|
+
* @param windowId - The window identifier
|
|
400
|
+
* @returns The napplet's pubkey, or undefined if not registered
|
|
401
|
+
*/
|
|
402
|
+
getPubkey(windowId) {
|
|
403
|
+
return byWindowId.get(windowId);
|
|
404
|
+
},
|
|
405
|
+
/**
|
|
406
|
+
* Get the full entry for a napplet pubkey.
|
|
407
|
+
*
|
|
408
|
+
* @param pubkey - The napplet's pubkey
|
|
409
|
+
* @returns The full SessionEntry, or undefined if not found
|
|
410
|
+
*/
|
|
411
|
+
getEntry(pubkey) {
|
|
412
|
+
return byPubkey.get(pubkey);
|
|
413
|
+
},
|
|
414
|
+
/**
|
|
415
|
+
* Get the windowId for a napplet pubkey.
|
|
416
|
+
*
|
|
417
|
+
* @param pubkey - The napplet's pubkey
|
|
418
|
+
* @returns The windowId, or undefined if not found
|
|
419
|
+
*/
|
|
420
|
+
getWindowId(pubkey) {
|
|
421
|
+
return byPubkey.get(pubkey)?.windowId;
|
|
422
|
+
},
|
|
423
|
+
/**
|
|
424
|
+
* Check if a windowId has a registered napplet.
|
|
425
|
+
*
|
|
426
|
+
* @param windowId - The window identifier
|
|
427
|
+
* @returns True if the windowId has a registered napplet
|
|
428
|
+
*/
|
|
429
|
+
isRegistered(windowId) {
|
|
430
|
+
return byWindowId.has(windowId);
|
|
431
|
+
},
|
|
432
|
+
/**
|
|
433
|
+
* Get all registered napplet entries.
|
|
434
|
+
*
|
|
435
|
+
* @returns Array of all SessionEntry objects
|
|
436
|
+
*/
|
|
437
|
+
getAllEntries() {
|
|
438
|
+
return Array.from(byPubkey.values());
|
|
439
|
+
},
|
|
440
|
+
/**
|
|
441
|
+
* Set a pending update for a window (napplet reconnected with different hash).
|
|
442
|
+
*
|
|
443
|
+
* @param windowId - The window identifier
|
|
444
|
+
* @param update - The pending update details with resolve callback
|
|
445
|
+
*/
|
|
446
|
+
setPendingUpdate(windowId, update) {
|
|
447
|
+
pendingUpdates.set(windowId, update);
|
|
448
|
+
_pendingVersion++;
|
|
449
|
+
if (typeof window !== "undefined") {
|
|
450
|
+
window.dispatchEvent(new CustomEvent("napplet:pending-update", { detail: { windowId } }));
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
/**
|
|
454
|
+
* Get a pending update for a window.
|
|
455
|
+
*
|
|
456
|
+
* @param windowId - The window identifier
|
|
457
|
+
* @returns The pending update, or undefined if none
|
|
458
|
+
*/
|
|
459
|
+
getPendingUpdate(windowId) {
|
|
460
|
+
return pendingUpdates.get(windowId);
|
|
461
|
+
},
|
|
462
|
+
/**
|
|
463
|
+
* Clear a pending update for a window.
|
|
464
|
+
*
|
|
465
|
+
* @param windowId - The window identifier
|
|
466
|
+
*/
|
|
467
|
+
clearPendingUpdate(windowId) {
|
|
468
|
+
pendingUpdates.delete(windowId);
|
|
469
|
+
_pendingVersion++;
|
|
470
|
+
if (typeof window !== "undefined") {
|
|
471
|
+
window.dispatchEvent(new CustomEvent("napplet:pending-update", { detail: { windowId } }));
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
/** Clear all registrations and pending updates. */
|
|
475
|
+
clear() {
|
|
476
|
+
byWindowId.clear();
|
|
477
|
+
byPubkey.clear();
|
|
478
|
+
pendingUpdates.clear();
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
var nappKeyRegistry = sessionRegistry;
|
|
482
|
+
|
|
483
|
+
// src/acl-store.ts
|
|
484
|
+
import { ALL_CAPABILITIES } from "@kehto/runtime";
|
|
485
|
+
import { migrateAclState } from "@kehto/acl";
|
|
486
|
+
var STORAGE_KEY = "napplet:acl";
|
|
487
|
+
var DEFAULT_STATE_QUOTA = 512 * 1024;
|
|
488
|
+
function aclKey(_pubkey, dTag, aggregateHash) {
|
|
489
|
+
return `${dTag}:${aggregateHash}`;
|
|
490
|
+
}
|
|
491
|
+
var store = /* @__PURE__ */ new Map();
|
|
492
|
+
var CAP_BITS = {
|
|
493
|
+
"relay:read": 1,
|
|
494
|
+
"relay:write": 2,
|
|
495
|
+
"cache:read": 4,
|
|
496
|
+
"cache:write": 8,
|
|
497
|
+
"hotkey:forward": 16,
|
|
498
|
+
"identity:read": 32,
|
|
499
|
+
"keys:bind": 64,
|
|
500
|
+
"keys:forward": 128,
|
|
501
|
+
"state:read": 256,
|
|
502
|
+
"state:write": 512,
|
|
503
|
+
"media:control": 1024,
|
|
504
|
+
"notify:send": 2048,
|
|
505
|
+
"notify:channel": 4096,
|
|
506
|
+
"theme:read": 8192
|
|
507
|
+
};
|
|
508
|
+
function capArrayToBitfield(caps) {
|
|
509
|
+
let bits = 0;
|
|
510
|
+
for (const cap of caps) bits |= CAP_BITS[cap] ?? 0;
|
|
511
|
+
return bits;
|
|
512
|
+
}
|
|
513
|
+
function bitfieldToCapArray(bits) {
|
|
514
|
+
return Object.entries(CAP_BITS).filter(([, bit]) => (bits & bit) !== 0).map(([name]) => name);
|
|
515
|
+
}
|
|
516
|
+
function getOrCreate(pubkey, dTag, aggregateHash) {
|
|
517
|
+
const key = aclKey(pubkey, dTag, aggregateHash);
|
|
518
|
+
let entry = store.get(key);
|
|
519
|
+
if (!entry) {
|
|
520
|
+
entry = {
|
|
521
|
+
key,
|
|
522
|
+
pubkey,
|
|
523
|
+
dTag,
|
|
524
|
+
aggregateHash,
|
|
525
|
+
capabilities: new Set(ALL_CAPABILITIES),
|
|
526
|
+
blocked: false,
|
|
527
|
+
stateQuota: DEFAULT_STATE_QUOTA
|
|
528
|
+
};
|
|
529
|
+
store.set(key, entry);
|
|
530
|
+
}
|
|
531
|
+
return entry;
|
|
532
|
+
}
|
|
533
|
+
var aclStore = {
|
|
534
|
+
/**
|
|
535
|
+
* Check if a napp identity has a specific capability.
|
|
536
|
+
* Returns true for unknown identities (permissive default).
|
|
537
|
+
*
|
|
538
|
+
* @param pubkey - The napp's pubkey
|
|
539
|
+
* @param dTag - The napp's dTag
|
|
540
|
+
* @param aggregateHash - The napp's build hash
|
|
541
|
+
* @param capability - The capability to check
|
|
542
|
+
* @returns True if the capability is granted and the napp is not blocked
|
|
543
|
+
*/
|
|
544
|
+
check(pubkey, dTag, aggregateHash, capability) {
|
|
545
|
+
const key = aclKey(pubkey, dTag, aggregateHash);
|
|
546
|
+
const entry = store.get(key);
|
|
547
|
+
if (!entry) return true;
|
|
548
|
+
if (entry.blocked) return false;
|
|
549
|
+
return entry.capabilities.has(capability);
|
|
550
|
+
},
|
|
551
|
+
/**
|
|
552
|
+
* Grant a capability to a napp identity.
|
|
553
|
+
*
|
|
554
|
+
* @param pubkey - The napp's pubkey
|
|
555
|
+
* @param dTag - The napp's dTag
|
|
556
|
+
* @param aggregateHash - The napp's build hash
|
|
557
|
+
* @param capability - The capability to grant
|
|
558
|
+
*/
|
|
559
|
+
grant(pubkey, dTag, aggregateHash, capability) {
|
|
560
|
+
getOrCreate(pubkey, dTag, aggregateHash).capabilities.add(capability);
|
|
561
|
+
},
|
|
562
|
+
/**
|
|
563
|
+
* Revoke a capability from a napp identity.
|
|
564
|
+
*
|
|
565
|
+
* @param pubkey - The napp's pubkey
|
|
566
|
+
* @param dTag - The napp's dTag
|
|
567
|
+
* @param aggregateHash - The napp's build hash
|
|
568
|
+
* @param capability - The capability to revoke
|
|
569
|
+
*/
|
|
570
|
+
revoke(pubkey, dTag, aggregateHash, capability) {
|
|
571
|
+
getOrCreate(pubkey, dTag, aggregateHash).capabilities.delete(capability);
|
|
572
|
+
},
|
|
573
|
+
/**
|
|
574
|
+
* Block a napp identity entirely (all capabilities denied).
|
|
575
|
+
*
|
|
576
|
+
* @param pubkey - The napp's pubkey
|
|
577
|
+
* @param dTag - The napp's dTag
|
|
578
|
+
* @param aggregateHash - The napp's build hash
|
|
579
|
+
*/
|
|
580
|
+
block(pubkey, dTag, aggregateHash) {
|
|
581
|
+
getOrCreate(pubkey, dTag, aggregateHash).blocked = true;
|
|
582
|
+
},
|
|
583
|
+
/**
|
|
584
|
+
* Unblock a napp identity.
|
|
585
|
+
*
|
|
586
|
+
* @param pubkey - The napp's pubkey
|
|
587
|
+
* @param dTag - The napp's dTag
|
|
588
|
+
* @param aggregateHash - The napp's build hash
|
|
589
|
+
*/
|
|
590
|
+
unblock(pubkey, dTag, aggregateHash) {
|
|
591
|
+
getOrCreate(pubkey, dTag, aggregateHash).blocked = false;
|
|
592
|
+
},
|
|
593
|
+
/**
|
|
594
|
+
* Check if a napp identity is blocked.
|
|
595
|
+
*
|
|
596
|
+
* @param pubkey - The napp's pubkey
|
|
597
|
+
* @param dTag - The napp's dTag
|
|
598
|
+
* @param aggregateHash - The napp's build hash
|
|
599
|
+
* @returns True if the identity is blocked
|
|
600
|
+
*/
|
|
601
|
+
isBlocked(pubkey, dTag, aggregateHash) {
|
|
602
|
+
const key = aclKey(pubkey, dTag, aggregateHash);
|
|
603
|
+
return store.get(key)?.blocked ?? false;
|
|
604
|
+
},
|
|
605
|
+
/**
|
|
606
|
+
* Get the external ACL entry for a napp identity.
|
|
607
|
+
*
|
|
608
|
+
* @param pubkey - The napp's pubkey
|
|
609
|
+
* @param dTag - The napp's dTag
|
|
610
|
+
* @param aggregateHash - The napp's build hash
|
|
611
|
+
* @returns The ACL entry, or undefined if no explicit entry exists
|
|
612
|
+
*/
|
|
613
|
+
getEntry(pubkey, dTag, aggregateHash) {
|
|
614
|
+
const key = aclKey(pubkey, dTag, aggregateHash);
|
|
615
|
+
const internal = store.get(key);
|
|
616
|
+
if (!internal) return void 0;
|
|
617
|
+
return {
|
|
618
|
+
pubkey: internal.pubkey,
|
|
619
|
+
capabilities: Array.from(internal.capabilities),
|
|
620
|
+
blocked: internal.blocked,
|
|
621
|
+
stateQuota: internal.stateQuota
|
|
622
|
+
};
|
|
623
|
+
},
|
|
624
|
+
/**
|
|
625
|
+
* Get all ACL entries.
|
|
626
|
+
*
|
|
627
|
+
* @returns Array of all ACL entries
|
|
628
|
+
*/
|
|
629
|
+
getAllEntries() {
|
|
630
|
+
return Array.from(store.values()).map((e) => ({
|
|
631
|
+
pubkey: e.pubkey,
|
|
632
|
+
capabilities: Array.from(e.capabilities),
|
|
633
|
+
blocked: e.blocked,
|
|
634
|
+
stateQuota: e.stateQuota
|
|
635
|
+
}));
|
|
636
|
+
},
|
|
637
|
+
/** Persist the ACL store to localStorage. */
|
|
638
|
+
persist() {
|
|
639
|
+
try {
|
|
640
|
+
const entries = Array.from(store.entries()).map(([key, val]) => [
|
|
641
|
+
key,
|
|
642
|
+
{
|
|
643
|
+
pubkey: val.pubkey,
|
|
644
|
+
dTag: val.dTag,
|
|
645
|
+
aggregateHash: val.aggregateHash,
|
|
646
|
+
capabilities: Array.from(val.capabilities),
|
|
647
|
+
blocked: val.blocked,
|
|
648
|
+
stateQuota: val.stateQuota
|
|
649
|
+
}
|
|
650
|
+
]);
|
|
651
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));
|
|
652
|
+
} catch {
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
/** Load the ACL store from localStorage. Migrates old 3-segment keys to 2-segment format. */
|
|
656
|
+
load() {
|
|
657
|
+
try {
|
|
658
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
659
|
+
if (!raw) return;
|
|
660
|
+
let entries = JSON.parse(raw);
|
|
661
|
+
const hasOldKeys = entries.some(([key]) => key.split(":").length === 3);
|
|
662
|
+
if (hasOldKeys) {
|
|
663
|
+
const tempState = {
|
|
664
|
+
defaultPolicy: "permissive",
|
|
665
|
+
entries: Object.fromEntries(
|
|
666
|
+
entries.map(([key, val]) => [key, {
|
|
667
|
+
caps: capArrayToBitfield(val.capabilities),
|
|
668
|
+
blocked: val.blocked,
|
|
669
|
+
quota: val.stateQuota ?? DEFAULT_STATE_QUOTA
|
|
670
|
+
}])
|
|
671
|
+
)
|
|
672
|
+
};
|
|
673
|
+
const migrated = migrateAclState(tempState);
|
|
674
|
+
if (migrated !== tempState) {
|
|
675
|
+
entries = Object.entries(migrated.entries).map(([key, entry]) => {
|
|
676
|
+
const parts = key.split(":");
|
|
677
|
+
return [key, {
|
|
678
|
+
pubkey: "",
|
|
679
|
+
dTag: parts[0] ?? "",
|
|
680
|
+
aggregateHash: parts[1] ?? "",
|
|
681
|
+
capabilities: bitfieldToCapArray(entry.caps),
|
|
682
|
+
blocked: entry.blocked,
|
|
683
|
+
stateQuota: entry.quota
|
|
684
|
+
}];
|
|
685
|
+
});
|
|
686
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
store.clear();
|
|
690
|
+
for (const [key, val] of entries) {
|
|
691
|
+
if (val.dTag === void 0 || val.aggregateHash === void 0) continue;
|
|
692
|
+
store.set(key, {
|
|
693
|
+
key,
|
|
694
|
+
pubkey: val.pubkey,
|
|
695
|
+
dTag: val.dTag,
|
|
696
|
+
aggregateHash: val.aggregateHash,
|
|
697
|
+
capabilities: new Set(val.capabilities),
|
|
698
|
+
blocked: val.blocked,
|
|
699
|
+
stateQuota: val.stateQuota ?? DEFAULT_STATE_QUOTA
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
} catch {
|
|
703
|
+
store.clear();
|
|
704
|
+
}
|
|
705
|
+
},
|
|
706
|
+
/**
|
|
707
|
+
* Get the state quota for a napp identity.
|
|
708
|
+
*
|
|
709
|
+
* @param pubkey - The napp's pubkey
|
|
710
|
+
* @param dTag - The napp's dTag
|
|
711
|
+
* @param aggregateHash - The napp's build hash
|
|
712
|
+
* @returns The quota in bytes (defaults to DEFAULT_STATE_QUOTA)
|
|
713
|
+
*/
|
|
714
|
+
getStateQuota(pubkey, dTag, aggregateHash) {
|
|
715
|
+
const key = aclKey(pubkey, dTag, aggregateHash);
|
|
716
|
+
return store.get(key)?.stateQuota ?? DEFAULT_STATE_QUOTA;
|
|
717
|
+
},
|
|
718
|
+
/** Clear all ACL entries and remove from localStorage. */
|
|
719
|
+
clear() {
|
|
720
|
+
store.clear();
|
|
721
|
+
try {
|
|
722
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
723
|
+
} catch {
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
// src/manifest-cache.ts
|
|
729
|
+
var STORAGE_KEY2 = "napplet:manifest-cache";
|
|
730
|
+
var cache = /* @__PURE__ */ new Map();
|
|
731
|
+
function cacheKey(pubkey, dTag) {
|
|
732
|
+
return `${pubkey}:${dTag}`;
|
|
733
|
+
}
|
|
734
|
+
var manifestCache = {
|
|
735
|
+
/**
|
|
736
|
+
* Get a cached manifest entry by pubkey and dTag.
|
|
737
|
+
*
|
|
738
|
+
* @param pubkey - The napp's pubkey
|
|
739
|
+
* @param dTag - The napp's dTag
|
|
740
|
+
* @returns The cached entry, or undefined if not found
|
|
741
|
+
*/
|
|
742
|
+
get(pubkey, dTag) {
|
|
743
|
+
return cache.get(cacheKey(pubkey, dTag));
|
|
744
|
+
},
|
|
745
|
+
/**
|
|
746
|
+
* Set (upsert) a manifest cache entry and persist to localStorage.
|
|
747
|
+
*
|
|
748
|
+
* @param entry - The manifest entry to cache
|
|
749
|
+
*/
|
|
750
|
+
set(entry) {
|
|
751
|
+
cache.set(cacheKey(entry.pubkey, entry.dTag), entry);
|
|
752
|
+
manifestCache.persist();
|
|
753
|
+
},
|
|
754
|
+
/**
|
|
755
|
+
* Check if a specific hash is cached for a pubkey/dTag combination.
|
|
756
|
+
*
|
|
757
|
+
* @param pubkey - The napp's pubkey
|
|
758
|
+
* @param dTag - The napp's dTag
|
|
759
|
+
* @param hash - The aggregateHash to check
|
|
760
|
+
* @returns True if the exact hash matches the cached entry
|
|
761
|
+
*/
|
|
762
|
+
has(pubkey, dTag, hash) {
|
|
763
|
+
const entry = cache.get(cacheKey(pubkey, dTag));
|
|
764
|
+
return !!entry && entry.aggregateHash === hash;
|
|
765
|
+
},
|
|
766
|
+
/**
|
|
767
|
+
* Remove a cached entry for a pubkey/dTag and persist.
|
|
768
|
+
*
|
|
769
|
+
* @param pubkey - The napp's pubkey
|
|
770
|
+
* @param dTag - The napp's dTag
|
|
771
|
+
*/
|
|
772
|
+
remove(pubkey, dTag) {
|
|
773
|
+
cache.delete(cacheKey(pubkey, dTag));
|
|
774
|
+
manifestCache.persist();
|
|
775
|
+
},
|
|
776
|
+
/** Load the cache from localStorage. */
|
|
777
|
+
load() {
|
|
778
|
+
try {
|
|
779
|
+
const raw = localStorage.getItem(STORAGE_KEY2);
|
|
780
|
+
if (!raw) return;
|
|
781
|
+
const entries = JSON.parse(raw);
|
|
782
|
+
cache.clear();
|
|
783
|
+
for (const [key, val] of entries) cache.set(key, val);
|
|
784
|
+
} catch {
|
|
785
|
+
cache.clear();
|
|
786
|
+
}
|
|
787
|
+
},
|
|
788
|
+
/** Persist the cache to localStorage. */
|
|
789
|
+
persist() {
|
|
790
|
+
try {
|
|
791
|
+
localStorage.setItem(STORAGE_KEY2, JSON.stringify(Array.from(cache.entries())));
|
|
792
|
+
} catch {
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
/** Clear all cached entries and remove from localStorage. */
|
|
796
|
+
clear() {
|
|
797
|
+
cache.clear();
|
|
798
|
+
try {
|
|
799
|
+
localStorage.removeItem(STORAGE_KEY2);
|
|
800
|
+
} catch {
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
// src/audio-manager.ts
|
|
806
|
+
var sources = /* @__PURE__ */ new Map();
|
|
807
|
+
var version = 0;
|
|
808
|
+
function bump() {
|
|
809
|
+
version++;
|
|
810
|
+
if (typeof window !== "undefined") {
|
|
811
|
+
window.dispatchEvent(new CustomEvent("napplet:audio-changed"));
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
var audioManager = {
|
|
815
|
+
/**
|
|
816
|
+
* Register a new audio source for a window.
|
|
817
|
+
*
|
|
818
|
+
* @param windowId - The window identifier
|
|
819
|
+
* @param nappletClass - The napplet class/type (e.g., 'music-player')
|
|
820
|
+
* @param title - Human-readable title for the audio source
|
|
821
|
+
*/
|
|
822
|
+
register(windowId, nappletClass, title) {
|
|
823
|
+
sources.set(windowId, { windowId, nappletClass, title, muted: false });
|
|
824
|
+
bump();
|
|
825
|
+
},
|
|
826
|
+
/**
|
|
827
|
+
* Unregister an audio source for a window.
|
|
828
|
+
*
|
|
829
|
+
* @param windowId - The window identifier to remove
|
|
830
|
+
*/
|
|
831
|
+
unregister(windowId) {
|
|
832
|
+
if (sources.delete(windowId)) bump();
|
|
833
|
+
},
|
|
834
|
+
/**
|
|
835
|
+
* Update the state of an audio source (e.g., change title).
|
|
836
|
+
*
|
|
837
|
+
* @param windowId - The window identifier
|
|
838
|
+
* @param update - Partial update with optional title
|
|
839
|
+
*/
|
|
840
|
+
updateState(windowId, update) {
|
|
841
|
+
const src = sources.get(windowId);
|
|
842
|
+
if (!src) return;
|
|
843
|
+
if (update.title !== void 0) src.title = update.title;
|
|
844
|
+
bump();
|
|
845
|
+
},
|
|
846
|
+
/**
|
|
847
|
+
* Mute or unmute an audio source and notify the napplet via postMessage.
|
|
848
|
+
*
|
|
849
|
+
* @param windowId - The window identifier
|
|
850
|
+
* @param muted - True to mute, false to unmute
|
|
851
|
+
* @example
|
|
852
|
+
* ```ts
|
|
853
|
+
* audioManager.mute('win-1', true); // mute
|
|
854
|
+
* audioManager.mute('win-1', false); // unmute
|
|
855
|
+
* ```
|
|
856
|
+
*/
|
|
857
|
+
mute(windowId, muted) {
|
|
858
|
+
const src = sources.get(windowId);
|
|
859
|
+
if (src) {
|
|
860
|
+
src.muted = muted;
|
|
861
|
+
bump();
|
|
862
|
+
}
|
|
863
|
+
const iframeWindow = originRegistry.getIframeWindow(windowId);
|
|
864
|
+
if (iframeWindow) {
|
|
865
|
+
const muteEvent = {
|
|
866
|
+
kind: 29e3,
|
|
867
|
+
// IPC_PEER — inlined numeric after Phase 24 DRIFT-01 shim removal
|
|
868
|
+
created_at: Math.floor(Date.now() / 1e3),
|
|
869
|
+
tags: [["t", "napplet:audio-muted"]],
|
|
870
|
+
content: JSON.stringify({ muted }),
|
|
871
|
+
pubkey: "__shell__",
|
|
872
|
+
id: `audio-mute-${windowId}-${Date.now()}`,
|
|
873
|
+
sig: ""
|
|
874
|
+
};
|
|
875
|
+
iframeWindow.postMessage(["EVENT", "__shell__", muteEvent], "*");
|
|
876
|
+
}
|
|
877
|
+
},
|
|
878
|
+
/**
|
|
879
|
+
* Check if a window has a registered audio source.
|
|
880
|
+
*
|
|
881
|
+
* @param windowId - The window identifier
|
|
882
|
+
* @returns True if the window has an active audio source
|
|
883
|
+
*/
|
|
884
|
+
has(windowId) {
|
|
885
|
+
return sources.has(windowId);
|
|
886
|
+
},
|
|
887
|
+
/**
|
|
888
|
+
* Get the audio source for a window.
|
|
889
|
+
*
|
|
890
|
+
* @param windowId - The window identifier
|
|
891
|
+
* @returns The AudioSource, or undefined if not found
|
|
892
|
+
*/
|
|
893
|
+
get(windowId) {
|
|
894
|
+
return sources.get(windowId);
|
|
895
|
+
},
|
|
896
|
+
/**
|
|
897
|
+
* Get a snapshot of all audio sources.
|
|
898
|
+
*
|
|
899
|
+
* @returns A new Map of all active audio sources
|
|
900
|
+
*/
|
|
901
|
+
getSources() {
|
|
902
|
+
return new Map(sources);
|
|
903
|
+
},
|
|
904
|
+
/** Current version counter (incremented on every change). */
|
|
905
|
+
get version() {
|
|
906
|
+
return version;
|
|
907
|
+
},
|
|
908
|
+
/** Number of active audio sources. */
|
|
909
|
+
get count() {
|
|
910
|
+
return sources.size;
|
|
911
|
+
},
|
|
912
|
+
/** Clear all audio sources and reset version counter. */
|
|
913
|
+
clear() {
|
|
914
|
+
sources.clear();
|
|
915
|
+
version = 0;
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
// src/shell-init.ts
|
|
920
|
+
var CANONICAL_NUB_DOMAINS = [
|
|
921
|
+
"identity",
|
|
922
|
+
"storage",
|
|
923
|
+
"ifc",
|
|
924
|
+
"theme",
|
|
925
|
+
"keys",
|
|
926
|
+
"media",
|
|
927
|
+
"notify"
|
|
928
|
+
];
|
|
929
|
+
function buildShellCapabilities(hooks) {
|
|
930
|
+
const nubs = hooks.relayPool ? ["relay", ...CANONICAL_NUB_DOMAINS] : [...CANONICAL_NUB_DOMAINS];
|
|
931
|
+
return { nubs, sandbox: [] };
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// src/keys-forwarder.ts
|
|
935
|
+
function createKeysForwarder(deps) {
|
|
936
|
+
const target = deps.target ?? (typeof window !== "undefined" ? window : new EventTarget());
|
|
937
|
+
const listener = (ev) => {
|
|
938
|
+
const ke = ev;
|
|
939
|
+
const entries = deps.sessionRegistry.getAllEntries();
|
|
940
|
+
for (const entry of entries) {
|
|
941
|
+
if (!deps.hasKeysForwardCap(entry.pubkey)) continue;
|
|
942
|
+
const iframe = deps.originRegistry.getIframeWindow(entry.windowId);
|
|
943
|
+
if (!iframe) continue;
|
|
944
|
+
const envelope = {
|
|
945
|
+
type: "keys.forward",
|
|
946
|
+
key: ke.key ?? "",
|
|
947
|
+
code: ke.code ?? "",
|
|
948
|
+
ctrl: ke.ctrlKey ?? false,
|
|
949
|
+
alt: ke.altKey ?? false,
|
|
950
|
+
shift: ke.shiftKey ?? false,
|
|
951
|
+
meta: ke.metaKey ?? false
|
|
952
|
+
};
|
|
953
|
+
iframe.postMessage(envelope, "*");
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
target.addEventListener("keydown", listener);
|
|
957
|
+
return {
|
|
958
|
+
destroy() {
|
|
959
|
+
target.removeEventListener("keydown", listener);
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/shell-bridge.ts
|
|
965
|
+
function createShellBridge(hooks) {
|
|
966
|
+
const runtimeHooks = adaptHooks(hooks, {
|
|
967
|
+
originRegistry,
|
|
968
|
+
manifestCache,
|
|
969
|
+
aclStore,
|
|
970
|
+
audioManager,
|
|
971
|
+
nappKeyRegistry
|
|
972
|
+
});
|
|
973
|
+
const runtime = createRuntime(runtimeHooks);
|
|
974
|
+
let keysForwarder = null;
|
|
975
|
+
if (typeof window !== "undefined") {
|
|
976
|
+
try {
|
|
977
|
+
keysForwarder = createKeysForwarder({
|
|
978
|
+
originRegistry,
|
|
979
|
+
sessionRegistry,
|
|
980
|
+
hasKeysForwardCap: (pubkey) => {
|
|
981
|
+
const entry = sessionRegistry.getEntry(pubkey);
|
|
982
|
+
if (!entry) return false;
|
|
983
|
+
const acl = aclStore.getEntry(entry.pubkey, entry.dTag, entry.aggregateHash);
|
|
984
|
+
return acl?.capabilities.includes("keys:forward") ?? false;
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
} catch {
|
|
988
|
+
keysForwarder = null;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return {
|
|
992
|
+
handleMessage(event) {
|
|
993
|
+
const sourceWindow = event.source;
|
|
994
|
+
if (!sourceWindow) return;
|
|
995
|
+
const windowId = originRegistry.getWindowId(sourceWindow);
|
|
996
|
+
if (!windowId) return;
|
|
997
|
+
const msg = event.data;
|
|
998
|
+
if (typeof msg !== "object" || msg === null || typeof msg.type !== "string") return;
|
|
999
|
+
if (msg.type === "shell.ready") {
|
|
1000
|
+
const capabilities = buildShellCapabilities(hooks);
|
|
1001
|
+
const initMsg = {
|
|
1002
|
+
type: "shell.init",
|
|
1003
|
+
capabilities,
|
|
1004
|
+
services: Object.keys(hooks.services ?? {})
|
|
1005
|
+
};
|
|
1006
|
+
const win = originRegistry.getIframeWindow(windowId);
|
|
1007
|
+
if (win) win.postMessage(initMsg, "*");
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
runtime.handleMessage(windowId, msg);
|
|
1011
|
+
},
|
|
1012
|
+
injectEvent(topic, payload) {
|
|
1013
|
+
runtime.injectEvent(topic, payload);
|
|
1014
|
+
},
|
|
1015
|
+
destroy() {
|
|
1016
|
+
keysForwarder?.destroy();
|
|
1017
|
+
runtime.destroy();
|
|
1018
|
+
},
|
|
1019
|
+
registerConsentHandler(handler) {
|
|
1020
|
+
runtime.registerConsentHandler(handler);
|
|
1021
|
+
},
|
|
1022
|
+
publishTheme(theme) {
|
|
1023
|
+
const envelope = { type: "theme.changed", theme };
|
|
1024
|
+
const windowIds = originRegistry.getAllWindowIds();
|
|
1025
|
+
for (const windowId of windowIds) {
|
|
1026
|
+
const win = originRegistry.getIframeWindow(windowId);
|
|
1027
|
+
if (!win) continue;
|
|
1028
|
+
win.postMessage(envelope, "*");
|
|
1029
|
+
}
|
|
1030
|
+
},
|
|
1031
|
+
get runtime() {
|
|
1032
|
+
return runtime;
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// src/index.ts
|
|
1038
|
+
import { ALL_CAPABILITIES as ALL_CAPABILITIES2 } from "@kehto/runtime";
|
|
1039
|
+
import { createEnforceGate, createNubEnforceGate, formatDenialReason } from "@kehto/runtime";
|
|
1040
|
+
|
|
1041
|
+
// src/topics.ts
|
|
1042
|
+
import { TOPICS } from "@napplet/core";
|
|
1043
|
+
|
|
1044
|
+
// src/identity-proxy.ts
|
|
1045
|
+
function createIdentityProxy(deps) {
|
|
1046
|
+
return {
|
|
1047
|
+
dispatch(windowId, envelope) {
|
|
1048
|
+
deps.runtime.handleMessage(windowId, envelope);
|
|
1049
|
+
},
|
|
1050
|
+
emit(windowId, envelope) {
|
|
1051
|
+
const win = deps.originRegistry.getIframeWindow(windowId);
|
|
1052
|
+
if (win) win.postMessage(envelope, "*");
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// src/theme-proxy.ts
|
|
1058
|
+
function createThemeProxy(deps) {
|
|
1059
|
+
return {
|
|
1060
|
+
dispatch(windowId, envelope) {
|
|
1061
|
+
deps.runtime.handleMessage(windowId, envelope);
|
|
1062
|
+
},
|
|
1063
|
+
emit(windowId, envelope) {
|
|
1064
|
+
const win = deps.originRegistry.getIframeWindow(windowId);
|
|
1065
|
+
if (win) win.postMessage(envelope, "*");
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// src/keys-proxy.ts
|
|
1071
|
+
function createKeysProxy(deps) {
|
|
1072
|
+
return {
|
|
1073
|
+
dispatch(windowId, envelope) {
|
|
1074
|
+
deps.runtime.handleMessage(windowId, envelope);
|
|
1075
|
+
},
|
|
1076
|
+
emit(windowId, envelope) {
|
|
1077
|
+
const win = deps.originRegistry.getIframeWindow(windowId);
|
|
1078
|
+
if (win) win.postMessage(envelope, "*");
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// src/media-proxy.ts
|
|
1084
|
+
function createMediaProxy(deps) {
|
|
1085
|
+
return {
|
|
1086
|
+
dispatch(windowId, envelope) {
|
|
1087
|
+
deps.runtime.handleMessage(windowId, envelope);
|
|
1088
|
+
},
|
|
1089
|
+
emit(windowId, envelope) {
|
|
1090
|
+
const win = deps.originRegistry.getIframeWindow(windowId);
|
|
1091
|
+
if (win) win.postMessage(envelope, "*");
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// src/notify-proxy.ts
|
|
1097
|
+
function createNotifyProxy(deps) {
|
|
1098
|
+
return {
|
|
1099
|
+
dispatch(windowId, envelope) {
|
|
1100
|
+
deps.runtime.handleMessage(windowId, envelope);
|
|
1101
|
+
},
|
|
1102
|
+
emit(windowId, envelope) {
|
|
1103
|
+
const win = deps.originRegistry.getIframeWindow(windowId);
|
|
1104
|
+
if (win) win.postMessage(envelope, "*");
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
export {
|
|
1109
|
+
ALL_CAPABILITIES2 as ALL_CAPABILITIES,
|
|
1110
|
+
TOPICS,
|
|
1111
|
+
adaptHooks,
|
|
1112
|
+
audioManager,
|
|
1113
|
+
buildShellCapabilities,
|
|
1114
|
+
createEnforceGate,
|
|
1115
|
+
createIdentityProxy,
|
|
1116
|
+
createKeysForwarder,
|
|
1117
|
+
createKeysProxy,
|
|
1118
|
+
createMediaProxy,
|
|
1119
|
+
createNotifyProxy,
|
|
1120
|
+
createNubEnforceGate,
|
|
1121
|
+
createShellBridge,
|
|
1122
|
+
createThemeProxy,
|
|
1123
|
+
formatDenialReason,
|
|
1124
|
+
manifestCache,
|
|
1125
|
+
nappKeyRegistry,
|
|
1126
|
+
originRegistry,
|
|
1127
|
+
sessionRegistry
|
|
1128
|
+
};
|
|
1129
|
+
//# sourceMappingURL=index.js.map
|