@kehto/services 0.11.0 → 0.12.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 +78 -4
- package/dist/index.d.ts +296 -16
- package/dist/index.js +1066 -146
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -231,6 +231,184 @@ function createNotificationService(options) {
|
|
|
231
231
|
|
|
232
232
|
// src/identity-service.ts
|
|
233
233
|
var IDENTITY_SERVICE_VERSION = "1.0.0";
|
|
234
|
+
function sendProviderError(send, result, fallback, err) {
|
|
235
|
+
send({
|
|
236
|
+
...result,
|
|
237
|
+
error: err?.message ?? fallback
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async function getCurrentPubkey(options) {
|
|
241
|
+
const currentSigner = options.getSigner();
|
|
242
|
+
if (!currentSigner?.getPublicKey) return "";
|
|
243
|
+
try {
|
|
244
|
+
return await currentSigner.getPublicKey() ?? "";
|
|
245
|
+
} catch {
|
|
246
|
+
return "";
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function sendOptionalProviderResult(options, send, fallbackResult, errorFallback, buildResult) {
|
|
250
|
+
if (!buildResult) {
|
|
251
|
+
send(fallbackResult);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
Promise.resolve(getCurrentPubkey(options)).then((pubkey) => buildResult(pubkey)).then((result) => send(result)).catch((err) => sendProviderError(send, fallbackResult, errorFallback, err));
|
|
255
|
+
}
|
|
256
|
+
function sendIdentityError(send, id, typeBase, error) {
|
|
257
|
+
send({ type: `${typeBase}.error`, id, error });
|
|
258
|
+
}
|
|
259
|
+
function sendSignerError(send, id, typeBase, fallback, err) {
|
|
260
|
+
sendIdentityError(send, id, typeBase, err?.message ?? fallback);
|
|
261
|
+
}
|
|
262
|
+
function handleGetPublicKey(options, id, send) {
|
|
263
|
+
const signer = options.getSigner();
|
|
264
|
+
if (!signer) {
|
|
265
|
+
const result = {
|
|
266
|
+
type: "identity.getPublicKey.result",
|
|
267
|
+
id,
|
|
268
|
+
pubkey: ""
|
|
269
|
+
};
|
|
270
|
+
send(result);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
Promise.resolve(signer.getPublicKey?.()).then((pubkey) => {
|
|
274
|
+
const result = {
|
|
275
|
+
type: "identity.getPublicKey.result",
|
|
276
|
+
id,
|
|
277
|
+
pubkey: pubkey ?? ""
|
|
278
|
+
};
|
|
279
|
+
send(result);
|
|
280
|
+
}).catch((err) => sendSignerError(send, id, "identity.getPublicKey", "getPublicKey failed", err));
|
|
281
|
+
}
|
|
282
|
+
function handleGetRelays(options, id, send) {
|
|
283
|
+
const signer = options.getSigner();
|
|
284
|
+
if (!signer) {
|
|
285
|
+
sendIdentityError(send, id, "identity.getRelays", "no signer configured");
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
Promise.resolve(signer.getRelays?.() ?? {}).then((relays) => {
|
|
289
|
+
const result = {
|
|
290
|
+
type: "identity.getRelays.result",
|
|
291
|
+
id,
|
|
292
|
+
relays
|
|
293
|
+
};
|
|
294
|
+
send(result);
|
|
295
|
+
}).catch((err) => sendSignerError(send, id, "identity.getRelays", "getRelays failed", err));
|
|
296
|
+
}
|
|
297
|
+
function handleReadProvider(options, id, message, send) {
|
|
298
|
+
switch (message.type) {
|
|
299
|
+
case "identity.getProfile":
|
|
300
|
+
sendOptionalProviderResult(
|
|
301
|
+
options,
|
|
302
|
+
send,
|
|
303
|
+
{ type: "identity.getProfile.result", id, profile: null },
|
|
304
|
+
"getProfile failed",
|
|
305
|
+
options.getProfile ? async (pubkey) => ({
|
|
306
|
+
type: "identity.getProfile.result",
|
|
307
|
+
id,
|
|
308
|
+
profile: await options.getProfile(pubkey, message)
|
|
309
|
+
}) : void 0
|
|
310
|
+
);
|
|
311
|
+
return true;
|
|
312
|
+
case "identity.getFollows":
|
|
313
|
+
sendOptionalProviderResult(
|
|
314
|
+
options,
|
|
315
|
+
send,
|
|
316
|
+
{ type: "identity.getFollows.result", id, pubkeys: [] },
|
|
317
|
+
"getFollows failed",
|
|
318
|
+
options.getFollows ? async (pubkey) => ({
|
|
319
|
+
type: "identity.getFollows.result",
|
|
320
|
+
id,
|
|
321
|
+
pubkeys: await options.getFollows(pubkey, message)
|
|
322
|
+
}) : void 0
|
|
323
|
+
);
|
|
324
|
+
return true;
|
|
325
|
+
case "identity.getList":
|
|
326
|
+
handleGetList(options, id, message, send);
|
|
327
|
+
return true;
|
|
328
|
+
case "identity.getZaps":
|
|
329
|
+
sendOptionalProviderResult(
|
|
330
|
+
options,
|
|
331
|
+
send,
|
|
332
|
+
{ type: "identity.getZaps.result", id, zaps: [] },
|
|
333
|
+
"getZaps failed",
|
|
334
|
+
options.getZaps ? async (pubkey) => ({
|
|
335
|
+
type: "identity.getZaps.result",
|
|
336
|
+
id,
|
|
337
|
+
zaps: await options.getZaps(pubkey, message)
|
|
338
|
+
}) : void 0
|
|
339
|
+
);
|
|
340
|
+
return true;
|
|
341
|
+
case "identity.getMutes":
|
|
342
|
+
sendOptionalProviderResult(
|
|
343
|
+
options,
|
|
344
|
+
send,
|
|
345
|
+
{ type: "identity.getMutes.result", id, pubkeys: [] },
|
|
346
|
+
"getMutes failed",
|
|
347
|
+
options.getMutes ? async (pubkey) => ({
|
|
348
|
+
type: "identity.getMutes.result",
|
|
349
|
+
id,
|
|
350
|
+
pubkeys: await options.getMutes(pubkey, message)
|
|
351
|
+
}) : void 0
|
|
352
|
+
);
|
|
353
|
+
return true;
|
|
354
|
+
case "identity.getBlocked":
|
|
355
|
+
sendOptionalProviderResult(
|
|
356
|
+
options,
|
|
357
|
+
send,
|
|
358
|
+
{ type: "identity.getBlocked.result", id, pubkeys: [] },
|
|
359
|
+
"getBlocked failed",
|
|
360
|
+
options.getBlocked ? async (pubkey) => ({
|
|
361
|
+
type: "identity.getBlocked.result",
|
|
362
|
+
id,
|
|
363
|
+
pubkeys: await options.getBlocked(pubkey, message)
|
|
364
|
+
}) : void 0
|
|
365
|
+
);
|
|
366
|
+
return true;
|
|
367
|
+
case "identity.getBadges":
|
|
368
|
+
sendOptionalProviderResult(
|
|
369
|
+
options,
|
|
370
|
+
send,
|
|
371
|
+
{ type: "identity.getBadges.result", id, badges: [] },
|
|
372
|
+
"getBadges failed",
|
|
373
|
+
options.getBadges ? async (pubkey) => ({
|
|
374
|
+
type: "identity.getBadges.result",
|
|
375
|
+
id,
|
|
376
|
+
badges: await options.getBadges(pubkey, message)
|
|
377
|
+
}) : void 0
|
|
378
|
+
);
|
|
379
|
+
return true;
|
|
380
|
+
default:
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function handleGetList(options, id, message, send) {
|
|
385
|
+
sendOptionalProviderResult(
|
|
386
|
+
options,
|
|
387
|
+
send,
|
|
388
|
+
{ type: "identity.getList.result", id, entries: [] },
|
|
389
|
+
"getList failed",
|
|
390
|
+
options.getList ? async (pubkey) => ({
|
|
391
|
+
type: "identity.getList.result",
|
|
392
|
+
id,
|
|
393
|
+
entries: await options.getList(message.listType, pubkey, message)
|
|
394
|
+
}) : void 0
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
function handleIdentityServiceMessage(options, message, send) {
|
|
398
|
+
const id = message.id ?? "";
|
|
399
|
+
switch (message.type) {
|
|
400
|
+
case "identity.getPublicKey":
|
|
401
|
+
handleGetPublicKey(options, id, send);
|
|
402
|
+
return;
|
|
403
|
+
case "identity.getRelays":
|
|
404
|
+
handleGetRelays(options, id, send);
|
|
405
|
+
return;
|
|
406
|
+
default:
|
|
407
|
+
if (!handleReadProvider(options, id, message, send)) {
|
|
408
|
+
sendIdentityError(send, id, message.type, `Unknown identity method: ${message.type}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
234
412
|
function createIdentityService(options) {
|
|
235
413
|
return {
|
|
236
414
|
descriptor: {
|
|
@@ -239,116 +417,7 @@ function createIdentityService(options) {
|
|
|
239
417
|
description: "NIP-5D identity NAP reference handler (9 read-only identity queries)"
|
|
240
418
|
},
|
|
241
419
|
handleMessage(_windowId, message, send) {
|
|
242
|
-
|
|
243
|
-
function sendError(typeBase, error) {
|
|
244
|
-
send({ type: `${typeBase}.error`, id, error });
|
|
245
|
-
}
|
|
246
|
-
function sendSignerError(typeBase, fallback, err) {
|
|
247
|
-
sendError(typeBase, err?.message ?? fallback);
|
|
248
|
-
}
|
|
249
|
-
const signer = options.getSigner();
|
|
250
|
-
switch (message.type) {
|
|
251
|
-
case "identity.getPublicKey": {
|
|
252
|
-
if (!signer) {
|
|
253
|
-
const result = {
|
|
254
|
-
type: "identity.getPublicKey.result",
|
|
255
|
-
id,
|
|
256
|
-
pubkey: ""
|
|
257
|
-
};
|
|
258
|
-
send(result);
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
Promise.resolve(signer.getPublicKey?.()).then((pubkey) => {
|
|
262
|
-
const result = {
|
|
263
|
-
type: "identity.getPublicKey.result",
|
|
264
|
-
id,
|
|
265
|
-
pubkey: pubkey ?? ""
|
|
266
|
-
};
|
|
267
|
-
send(result);
|
|
268
|
-
}).catch((err) => sendSignerError("identity.getPublicKey", "getPublicKey failed", err));
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
case "identity.getRelays": {
|
|
272
|
-
if (!signer) {
|
|
273
|
-
sendError("identity.getRelays", "no signer configured");
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
Promise.resolve(signer.getRelays?.() ?? {}).then((relays) => {
|
|
277
|
-
const result = {
|
|
278
|
-
type: "identity.getRelays.result",
|
|
279
|
-
id,
|
|
280
|
-
relays
|
|
281
|
-
};
|
|
282
|
-
send(result);
|
|
283
|
-
}).catch((err) => sendSignerError("identity.getRelays", "getRelays failed", err));
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
case "identity.getProfile": {
|
|
287
|
-
const result = {
|
|
288
|
-
type: "identity.getProfile.result",
|
|
289
|
-
id,
|
|
290
|
-
profile: null
|
|
291
|
-
};
|
|
292
|
-
send(result);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
case "identity.getFollows": {
|
|
296
|
-
const result = {
|
|
297
|
-
type: "identity.getFollows.result",
|
|
298
|
-
id,
|
|
299
|
-
pubkeys: []
|
|
300
|
-
};
|
|
301
|
-
send(result);
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
case "identity.getList": {
|
|
305
|
-
const result = {
|
|
306
|
-
type: "identity.getList.result",
|
|
307
|
-
id,
|
|
308
|
-
entries: []
|
|
309
|
-
};
|
|
310
|
-
send(result);
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
case "identity.getZaps": {
|
|
314
|
-
const result = {
|
|
315
|
-
type: "identity.getZaps.result",
|
|
316
|
-
id,
|
|
317
|
-
zaps: []
|
|
318
|
-
};
|
|
319
|
-
send(result);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
case "identity.getMutes": {
|
|
323
|
-
const result = {
|
|
324
|
-
type: "identity.getMutes.result",
|
|
325
|
-
id,
|
|
326
|
-
pubkeys: []
|
|
327
|
-
};
|
|
328
|
-
send(result);
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
case "identity.getBlocked": {
|
|
332
|
-
const result = {
|
|
333
|
-
type: "identity.getBlocked.result",
|
|
334
|
-
id,
|
|
335
|
-
pubkeys: []
|
|
336
|
-
};
|
|
337
|
-
send(result);
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
case "identity.getBadges": {
|
|
341
|
-
const result = {
|
|
342
|
-
type: "identity.getBadges.result",
|
|
343
|
-
id,
|
|
344
|
-
badges: []
|
|
345
|
-
};
|
|
346
|
-
send(result);
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
default:
|
|
350
|
-
sendError(message.type, `Unknown identity method: ${message.type}`);
|
|
351
|
-
}
|
|
420
|
+
handleIdentityServiceMessage(options, message, send);
|
|
352
421
|
},
|
|
353
422
|
// Identity service has no per-window state to clean up.
|
|
354
423
|
onWindowDestroyed(_windowId) {
|
|
@@ -383,7 +452,8 @@ function createRelayPoolService(options) {
|
|
|
383
452
|
send({ type: "relay.eose", subId });
|
|
384
453
|
return;
|
|
385
454
|
}
|
|
386
|
-
const
|
|
455
|
+
const relayHint = typeof relayMessage.relay === "string" && relayMessage.relay.length > 0 ? relayMessage.relay : void 0;
|
|
456
|
+
const relayUrls = relayHint ? [relayHint] : options.selectRelayTier(filters);
|
|
387
457
|
let eoseSent = false;
|
|
388
458
|
const eoseTimer = setTimeout(() => {
|
|
389
459
|
if (!eoseSent) {
|
|
@@ -563,7 +633,8 @@ function createCoordinatedRelay(options) {
|
|
|
563
633
|
maybeSendEose(subKey, subId, send);
|
|
564
634
|
}
|
|
565
635
|
}, timeoutMs);
|
|
566
|
-
const
|
|
636
|
+
const relayHint = typeof relayMessage.relay === "string" && relayMessage.relay.length > 0 ? relayMessage.relay : void 0;
|
|
637
|
+
const relayUrls = relayHint ? [relayHint] : options.relayPool.selectRelayTier(filters);
|
|
567
638
|
tracked.relayHandle = options.relayPool.subscribe(filters, (item) => {
|
|
568
639
|
if (item === "EOSE") {
|
|
569
640
|
clearTimeout(tracked.eoseTimer);
|
|
@@ -1579,19 +1650,35 @@ function untrackRequest(state, requestId) {
|
|
|
1579
1650
|
state.perWindow.get(entry.windowId)?.delete(requestId);
|
|
1580
1651
|
}
|
|
1581
1652
|
}
|
|
1582
|
-
function sendResourceError(send, requestId, code, message) {
|
|
1653
|
+
function sendResourceError(send, requestId, code, message, error = toResourceError(code), type = "resource.bytes.error") {
|
|
1583
1654
|
send({
|
|
1584
|
-
type
|
|
1655
|
+
type,
|
|
1656
|
+
id: requestId,
|
|
1585
1657
|
requestId,
|
|
1658
|
+
error,
|
|
1586
1659
|
code,
|
|
1587
1660
|
message
|
|
1588
1661
|
});
|
|
1589
1662
|
}
|
|
1590
|
-
function
|
|
1663
|
+
function sendBytesManyError(send, requestId, code, message, error) {
|
|
1664
|
+
sendResourceError(send, requestId, code, message, error, "resource.bytesMany.error");
|
|
1665
|
+
}
|
|
1666
|
+
function toResourceError(code) {
|
|
1667
|
+
switch (code) {
|
|
1668
|
+
case "denied":
|
|
1669
|
+
return "blocked-by-policy";
|
|
1670
|
+
case "invalid-url":
|
|
1671
|
+
return "invalid-request";
|
|
1672
|
+
case "canceled":
|
|
1673
|
+
return "timeout";
|
|
1674
|
+
case "network-error":
|
|
1675
|
+
return "network-error";
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
function parseResourceUrl(url) {
|
|
1591
1679
|
try {
|
|
1592
1680
|
return new URL(url);
|
|
1593
1681
|
} catch {
|
|
1594
|
-
sendResourceError(send, requestId, "invalid-url", `invalid URL: ${url}`);
|
|
1595
1682
|
return null;
|
|
1596
1683
|
}
|
|
1597
1684
|
}
|
|
@@ -1602,45 +1689,138 @@ function collectResponseHeaders(response) {
|
|
|
1602
1689
|
});
|
|
1603
1690
|
return headers;
|
|
1604
1691
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
if (
|
|
1692
|
+
function responseMime(response) {
|
|
1693
|
+
return response.headers.get("content-type") || "application/octet-stream";
|
|
1694
|
+
}
|
|
1695
|
+
function responseBlob(buffer, mime) {
|
|
1696
|
+
return new Blob([buffer], { type: mime });
|
|
1697
|
+
}
|
|
1698
|
+
function requestIdFromMessage(message) {
|
|
1699
|
+
if (typeof message.id === "string" && message.id.length > 0) return message.id;
|
|
1700
|
+
if (typeof message.requestId === "string" && message.requestId.length > 0) return message.requestId;
|
|
1701
|
+
return null;
|
|
1702
|
+
}
|
|
1703
|
+
function resourceInvalidRequest(url, message) {
|
|
1704
|
+
return {
|
|
1705
|
+
ok: false,
|
|
1706
|
+
url,
|
|
1707
|
+
error: "invalid-request",
|
|
1708
|
+
code: "invalid-url",
|
|
1709
|
+
message
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
async function fetchResourceItem(options, identity, url, init, signal) {
|
|
1713
|
+
const parsedUrl = parseResourceUrl(url);
|
|
1714
|
+
if (!parsedUrl) return resourceInvalidRequest(url, `invalid URL: ${url}`);
|
|
1614
1715
|
const origin = parsedUrl.origin;
|
|
1615
1716
|
const grants = options.getConnectGrants(identity.dTag, identity.aggregateHash);
|
|
1616
1717
|
if (!options.isOriginGranted(origin, grants)) {
|
|
1617
|
-
|
|
1618
|
-
|
|
1718
|
+
return {
|
|
1719
|
+
ok: false,
|
|
1720
|
+
url,
|
|
1721
|
+
error: "blocked-by-policy",
|
|
1722
|
+
code: "denied",
|
|
1723
|
+
message: `origin ${origin} not granted`
|
|
1724
|
+
};
|
|
1619
1725
|
}
|
|
1620
|
-
const controller = new AbortController();
|
|
1621
|
-
trackRequest(state, requestId, windowId, controller);
|
|
1622
1726
|
try {
|
|
1623
1727
|
const response = await options.fetch(url, {
|
|
1624
1728
|
method: init?.method,
|
|
1625
1729
|
headers: init?.headers ? { ...init.headers } : void 0,
|
|
1626
|
-
signal
|
|
1730
|
+
signal
|
|
1627
1731
|
});
|
|
1628
1732
|
const buffer = await response.arrayBuffer();
|
|
1733
|
+
const headers = collectResponseHeaders(response);
|
|
1734
|
+
const mime = responseMime(response);
|
|
1735
|
+
return {
|
|
1736
|
+
ok: true,
|
|
1737
|
+
url,
|
|
1738
|
+
blob: responseBlob(buffer, mime),
|
|
1739
|
+
mime,
|
|
1740
|
+
status: response.status,
|
|
1741
|
+
headers,
|
|
1742
|
+
bodyBase64: arrayBufferToBase64(buffer)
|
|
1743
|
+
};
|
|
1744
|
+
} catch (err) {
|
|
1745
|
+
const isAbort = signal.aborted || err instanceof Error && (err.name === "AbortError" || err.name === "DOMException");
|
|
1746
|
+
return {
|
|
1747
|
+
ok: false,
|
|
1748
|
+
url,
|
|
1749
|
+
error: isAbort ? "timeout" : "network-error",
|
|
1750
|
+
code: isAbort ? "canceled" : "network-error",
|
|
1751
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
async function handleBytes(options, state, windowId, msg, send) {
|
|
1756
|
+
const { requestId, url, init } = msg;
|
|
1757
|
+
const identity = options.resolveIdentity(windowId);
|
|
1758
|
+
if (!identity) {
|
|
1759
|
+
sendResourceError(send, requestId, "denied", "napplet identity not resolvable", "blocked-by-policy");
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
const controller = new AbortController();
|
|
1763
|
+
trackRequest(state, requestId, windowId, controller);
|
|
1764
|
+
try {
|
|
1765
|
+
const item = await fetchResourceItem(options, identity, url, init, controller.signal);
|
|
1766
|
+
if (!item.ok) {
|
|
1767
|
+
sendResourceError(send, requestId, item.code, item.message, item.error);
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1629
1770
|
send({
|
|
1630
1771
|
type: "resource.bytes.result",
|
|
1772
|
+
id: requestId,
|
|
1631
1773
|
requestId,
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1774
|
+
blob: item.blob,
|
|
1775
|
+
mime: item.mime,
|
|
1776
|
+
status: item.status,
|
|
1777
|
+
headers: item.headers,
|
|
1778
|
+
bodyBase64: item.bodyBase64
|
|
1635
1779
|
});
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1780
|
+
} finally {
|
|
1781
|
+
untrackRequest(state, requestId);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
async function handleBytesMany(options, state, windowId, msg, send) {
|
|
1785
|
+
const { requestId, urls, init } = msg;
|
|
1786
|
+
if (!Array.isArray(urls) || urls.length === 0 || urls.some((url) => typeof url !== "string")) {
|
|
1787
|
+
sendBytesManyError(send, requestId, "invalid-url", "resource.bytesMany requires a non-empty urls array", "invalid-request");
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
const identity = options.resolveIdentity(windowId);
|
|
1791
|
+
if (!identity) {
|
|
1792
|
+
sendBytesManyError(send, requestId, "denied", "napplet identity not resolvable", "blocked-by-policy");
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
const controller = new AbortController();
|
|
1796
|
+
trackRequest(state, requestId, windowId, controller);
|
|
1797
|
+
try {
|
|
1798
|
+
const items = [];
|
|
1799
|
+
for (const url of urls) {
|
|
1800
|
+
const item = await fetchResourceItem(options, identity, url, init, controller.signal);
|
|
1801
|
+
if (item.ok) {
|
|
1802
|
+
items.push({
|
|
1803
|
+
url: item.url,
|
|
1804
|
+
ok: true,
|
|
1805
|
+
blob: item.blob,
|
|
1806
|
+
mime: item.mime
|
|
1807
|
+
});
|
|
1808
|
+
} else {
|
|
1809
|
+
items.push({
|
|
1810
|
+
url: item.url,
|
|
1811
|
+
ok: false,
|
|
1812
|
+
error: item.error,
|
|
1813
|
+
code: item.code,
|
|
1814
|
+
message: item.message
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
send({
|
|
1819
|
+
type: "resource.bytesMany.result",
|
|
1820
|
+
id: requestId,
|
|
1640
1821
|
requestId,
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
);
|
|
1822
|
+
items
|
|
1823
|
+
});
|
|
1644
1824
|
} finally {
|
|
1645
1825
|
untrackRequest(state, requestId);
|
|
1646
1826
|
}
|
|
@@ -1680,13 +1860,24 @@ function createResourceService(options) {
|
|
|
1680
1860
|
switch (message.type) {
|
|
1681
1861
|
case "resource.bytes": {
|
|
1682
1862
|
const m = message;
|
|
1683
|
-
|
|
1863
|
+
const requestId = requestIdFromMessage(m);
|
|
1864
|
+
if (!requestId || typeof m.url !== "string") return;
|
|
1865
|
+
handleBytes(options, state, windowId, { requestId, url: m.url, init: m.init }, send).catch(() => {
|
|
1866
|
+
});
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
case "resource.bytesMany": {
|
|
1870
|
+
const m = message;
|
|
1871
|
+
const requestId = requestIdFromMessage(m);
|
|
1872
|
+
if (!requestId) return;
|
|
1873
|
+
handleBytesMany(options, state, windowId, { requestId, urls: m.urls ?? [], init: m.init }, send).catch(() => {
|
|
1684
1874
|
});
|
|
1685
1875
|
return;
|
|
1686
1876
|
}
|
|
1687
1877
|
case "resource.cancel": {
|
|
1688
1878
|
const m = message;
|
|
1689
|
-
|
|
1879
|
+
const requestId = requestIdFromMessage(m);
|
|
1880
|
+
if (requestId) handleCancel(state, requestId);
|
|
1690
1881
|
return;
|
|
1691
1882
|
}
|
|
1692
1883
|
default:
|
|
@@ -2419,7 +2610,7 @@ function createIntentService(options) {
|
|
|
2419
2610
|
send({ type: "intent.changed", availability });
|
|
2420
2611
|
}
|
|
2421
2612
|
});
|
|
2422
|
-
function
|
|
2613
|
+
function settle6(call, send, resultType, id, onValue) {
|
|
2423
2614
|
let pending;
|
|
2424
2615
|
try {
|
|
2425
2616
|
pending = Promise.resolve(call());
|
|
@@ -2437,7 +2628,7 @@ function createIntentService(options) {
|
|
|
2437
2628
|
send({ type: "intent.invoke.result", id, error: "invalid request" });
|
|
2438
2629
|
return;
|
|
2439
2630
|
}
|
|
2440
|
-
|
|
2631
|
+
settle6(
|
|
2441
2632
|
() => resolver.invoke(request, { windowId }),
|
|
2442
2633
|
send,
|
|
2443
2634
|
"intent.invoke.result",
|
|
@@ -2453,7 +2644,7 @@ function createIntentService(options) {
|
|
|
2453
2644
|
return;
|
|
2454
2645
|
}
|
|
2455
2646
|
const archetype = m.archetype;
|
|
2456
|
-
|
|
2647
|
+
settle6(
|
|
2457
2648
|
() => resolver.available(archetype),
|
|
2458
2649
|
send,
|
|
2459
2650
|
"intent.available.result",
|
|
@@ -2464,7 +2655,7 @@ function createIntentService(options) {
|
|
|
2464
2655
|
function handleHandlers(msg, send) {
|
|
2465
2656
|
const m = msg;
|
|
2466
2657
|
const id = m.id ?? "";
|
|
2467
|
-
|
|
2658
|
+
settle6(
|
|
2468
2659
|
() => resolver.handlers(),
|
|
2469
2660
|
send,
|
|
2470
2661
|
"intent.handlers.result",
|
|
@@ -2739,11 +2930,736 @@ function toErrorMessage5(err) {
|
|
|
2739
2930
|
if (typeof err === "string") return err;
|
|
2740
2931
|
return "cvm request failed";
|
|
2741
2932
|
}
|
|
2933
|
+
|
|
2934
|
+
// src/link-service.ts
|
|
2935
|
+
var LINK_SERVICE_VERSION = "1.0.0";
|
|
2936
|
+
var DEFAULT_ALLOWED_PROTOCOLS = ["http:", "https:"];
|
|
2937
|
+
function denied(id, error) {
|
|
2938
|
+
return { type: "link.open.result", id, status: "denied", error };
|
|
2939
|
+
}
|
|
2940
|
+
function parseLinkUrl(id, rawUrl) {
|
|
2941
|
+
try {
|
|
2942
|
+
return new URL(rawUrl);
|
|
2943
|
+
} catch {
|
|
2944
|
+
return denied(id, "invalid-url");
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
function isProtocolAllowed(url, allowedProtocols) {
|
|
2948
|
+
return allowedProtocols.includes(url.protocol);
|
|
2949
|
+
}
|
|
2950
|
+
async function handleOpen(options, windowId, message, send) {
|
|
2951
|
+
const parsed = parseLinkUrl(message.id, message.url);
|
|
2952
|
+
if ("type" in parsed) {
|
|
2953
|
+
send(parsed);
|
|
2954
|
+
return;
|
|
2955
|
+
}
|
|
2956
|
+
const allowedProtocols = options.allowedProtocols ?? DEFAULT_ALLOWED_PROTOCOLS;
|
|
2957
|
+
if (!isProtocolAllowed(parsed, allowedProtocols)) {
|
|
2958
|
+
send(denied(message.id, "unsupported-scheme"));
|
|
2959
|
+
return;
|
|
2960
|
+
}
|
|
2961
|
+
if (!options.open) {
|
|
2962
|
+
send(denied(message.id, "blocked-by-policy"));
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
2965
|
+
try {
|
|
2966
|
+
const result = await options.open({ windowId, url: parsed, options: message.options });
|
|
2967
|
+
send({
|
|
2968
|
+
type: "link.open.result",
|
|
2969
|
+
id: message.id,
|
|
2970
|
+
status: result.status,
|
|
2971
|
+
...result.status === "denied" ? { error: "blocked-by-policy" } : {}
|
|
2972
|
+
});
|
|
2973
|
+
} catch {
|
|
2974
|
+
send(denied(message.id, "blocked-by-policy"));
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
function createLinkService(options = {}) {
|
|
2978
|
+
const descriptor = {
|
|
2979
|
+
name: "link",
|
|
2980
|
+
version: LINK_SERVICE_VERSION,
|
|
2981
|
+
description: "NAP-LINK reference handler for shell-mediated link opening"
|
|
2982
|
+
};
|
|
2983
|
+
return {
|
|
2984
|
+
descriptor,
|
|
2985
|
+
handleMessage(windowId, message, send) {
|
|
2986
|
+
if (message.type === "link.open") {
|
|
2987
|
+
void handleOpen(options, windowId, message, send);
|
|
2988
|
+
return;
|
|
2989
|
+
}
|
|
2990
|
+
const id = message.id ?? "";
|
|
2991
|
+
send({
|
|
2992
|
+
type: `${message.type}.error`,
|
|
2993
|
+
id,
|
|
2994
|
+
error: `Unknown link method: ${message.type}`
|
|
2995
|
+
});
|
|
2996
|
+
},
|
|
2997
|
+
onWindowDestroyed(_windowId) {
|
|
2998
|
+
}
|
|
2999
|
+
};
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
// src/lists-service.ts
|
|
3003
|
+
var LISTS_SERVICE_VERSION = "1.0.0";
|
|
3004
|
+
var LISTS_DESCRIPTOR = {
|
|
3005
|
+
name: "lists",
|
|
3006
|
+
version: LISTS_SERVICE_VERSION,
|
|
3007
|
+
description: "NAP-LISTS reference handler for shell-mediated NIP-51 list mutations"
|
|
3008
|
+
};
|
|
3009
|
+
function errorMessage(err, fallback) {
|
|
3010
|
+
if (err instanceof Error) return err.message;
|
|
3011
|
+
if (typeof err === "string") return err;
|
|
3012
|
+
return fallback;
|
|
3013
|
+
}
|
|
3014
|
+
function settle(call, send, okFalse, onValue) {
|
|
3015
|
+
let pending;
|
|
3016
|
+
try {
|
|
3017
|
+
pending = Promise.resolve(call());
|
|
3018
|
+
} catch (err) {
|
|
3019
|
+
send(okFalse(errorMessage(err, "lists request failed")));
|
|
3020
|
+
return;
|
|
3021
|
+
}
|
|
3022
|
+
pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage(err, "lists request failed"))));
|
|
3023
|
+
}
|
|
3024
|
+
function unsupportedMutation(resultType, id) {
|
|
3025
|
+
return {
|
|
3026
|
+
type: resultType,
|
|
3027
|
+
id,
|
|
3028
|
+
ok: false,
|
|
3029
|
+
error: "unsupported",
|
|
3030
|
+
reason: `${resultType.replace(".result", "")} unavailable`,
|
|
3031
|
+
supported: []
|
|
3032
|
+
};
|
|
3033
|
+
}
|
|
3034
|
+
function settleMutation(hook, message, context, send, resultType) {
|
|
3035
|
+
if (!hook) {
|
|
3036
|
+
send(unsupportedMutation(resultType, message.id));
|
|
3037
|
+
return;
|
|
3038
|
+
}
|
|
3039
|
+
settle(
|
|
3040
|
+
() => hook(message.list, message.items, message.options, context),
|
|
3041
|
+
send,
|
|
3042
|
+
(error) => ({ type: resultType, id: message.id, ok: false, error: "list-unavailable", reason: error }),
|
|
3043
|
+
(result) => ({ type: resultType, id: message.id, ...result })
|
|
3044
|
+
);
|
|
3045
|
+
}
|
|
3046
|
+
function createListsService(options = {}) {
|
|
3047
|
+
return {
|
|
3048
|
+
descriptor: LISTS_DESCRIPTOR,
|
|
3049
|
+
handleMessage(windowId, message, send) {
|
|
3050
|
+
const id = message.id ?? "";
|
|
3051
|
+
const context = { windowId };
|
|
3052
|
+
if (message.type === "lists.supported") {
|
|
3053
|
+
if (!options.supported) {
|
|
3054
|
+
send({ type: "lists.supported.result", id, lists: [] });
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
3057
|
+
settle(
|
|
3058
|
+
() => options.supported(context),
|
|
3059
|
+
send,
|
|
3060
|
+
(error) => ({ type: "lists.supported.result", id, error }),
|
|
3061
|
+
(lists) => ({ type: "lists.supported.result", id, lists: [...lists] })
|
|
3062
|
+
);
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
3065
|
+
if (message.type === "lists.add") {
|
|
3066
|
+
settleMutation(options.add, message, context, send, "lists.add.result");
|
|
3067
|
+
return;
|
|
3068
|
+
}
|
|
3069
|
+
if (message.type === "lists.remove") {
|
|
3070
|
+
settleMutation(options.remove, message, context, send, "lists.remove.result");
|
|
3071
|
+
return;
|
|
3072
|
+
}
|
|
3073
|
+
send({
|
|
3074
|
+
type: `${message.type}.error`,
|
|
3075
|
+
id,
|
|
3076
|
+
error: `Unknown lists method: ${message.type}`
|
|
3077
|
+
});
|
|
3078
|
+
},
|
|
3079
|
+
onWindowDestroyed(_windowId) {
|
|
3080
|
+
}
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
// src/serial-service.ts
|
|
3085
|
+
var SERIAL_SERVICE_VERSION = "1.0.0";
|
|
3086
|
+
var SERIAL_DESCRIPTOR = {
|
|
3087
|
+
name: "serial",
|
|
3088
|
+
version: SERIAL_SERVICE_VERSION,
|
|
3089
|
+
description: "NAP-SERIAL reference handler for shell-mediated serial sessions"
|
|
3090
|
+
};
|
|
3091
|
+
function errorMessage2(err, fallback) {
|
|
3092
|
+
if (err instanceof Error) return err.message;
|
|
3093
|
+
if (typeof err === "string") return err;
|
|
3094
|
+
return fallback;
|
|
3095
|
+
}
|
|
3096
|
+
function settle2(call, send, okFalse, onValue) {
|
|
3097
|
+
let pending;
|
|
3098
|
+
try {
|
|
3099
|
+
pending = Promise.resolve(call());
|
|
3100
|
+
} catch (err) {
|
|
3101
|
+
send(okFalse(errorMessage2(err, "serial request failed")));
|
|
3102
|
+
return;
|
|
3103
|
+
}
|
|
3104
|
+
pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage2(err, "serial request failed"))));
|
|
3105
|
+
}
|
|
3106
|
+
function unsupported(resultType, id) {
|
|
3107
|
+
return {
|
|
3108
|
+
type: resultType,
|
|
3109
|
+
id,
|
|
3110
|
+
error: `${resultType.replace(".result", "")} unavailable`
|
|
3111
|
+
};
|
|
3112
|
+
}
|
|
3113
|
+
function createSerialService(options = {}) {
|
|
3114
|
+
return {
|
|
3115
|
+
descriptor: SERIAL_DESCRIPTOR,
|
|
3116
|
+
handleMessage(windowId, message, send) {
|
|
3117
|
+
const id = message.id ?? "";
|
|
3118
|
+
const context = { windowId };
|
|
3119
|
+
if (message.type === "serial.open") {
|
|
3120
|
+
if (!options.open) {
|
|
3121
|
+
send(unsupported("serial.open.result", id));
|
|
3122
|
+
return;
|
|
3123
|
+
}
|
|
3124
|
+
const serialMessage = message;
|
|
3125
|
+
settle2(
|
|
3126
|
+
() => options.open(serialMessage.request, context),
|
|
3127
|
+
send,
|
|
3128
|
+
(error) => ({ type: "serial.open.result", id, error }),
|
|
3129
|
+
(result) => ({ type: "serial.open.result", id, session: result.session })
|
|
3130
|
+
);
|
|
3131
|
+
return;
|
|
3132
|
+
}
|
|
3133
|
+
if (message.type === "serial.write") {
|
|
3134
|
+
if (!options.write) {
|
|
3135
|
+
send(unsupported("serial.write.result", id));
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
const serialMessage = message;
|
|
3139
|
+
settle2(
|
|
3140
|
+
() => options.write(serialMessage.sessionId, serialMessage.data, context),
|
|
3141
|
+
send,
|
|
3142
|
+
(error) => ({ type: "serial.write.result", id, error }),
|
|
3143
|
+
() => ({ type: "serial.write.result", id })
|
|
3144
|
+
);
|
|
3145
|
+
return;
|
|
3146
|
+
}
|
|
3147
|
+
if (message.type === "serial.close") {
|
|
3148
|
+
if (!options.close) {
|
|
3149
|
+
send(unsupported("serial.close.result", id));
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
const serialMessage = message;
|
|
3153
|
+
settle2(
|
|
3154
|
+
() => options.close(serialMessage.sessionId, serialMessage.reason, context),
|
|
3155
|
+
send,
|
|
3156
|
+
(error) => ({ type: "serial.close.result", id, error }),
|
|
3157
|
+
() => ({ type: "serial.close.result", id })
|
|
3158
|
+
);
|
|
3159
|
+
return;
|
|
3160
|
+
}
|
|
3161
|
+
send({
|
|
3162
|
+
type: `${message.type}.error`,
|
|
3163
|
+
id,
|
|
3164
|
+
error: `Unknown serial method: ${message.type}`
|
|
3165
|
+
});
|
|
3166
|
+
},
|
|
3167
|
+
onWindowDestroyed(windowId) {
|
|
3168
|
+
options.destroyWindow?.(windowId);
|
|
3169
|
+
}
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
// src/ble-service.ts
|
|
3174
|
+
var BLE_SERVICE_VERSION = "1.0.0";
|
|
3175
|
+
var BLE_DESCRIPTOR = {
|
|
3176
|
+
name: "ble",
|
|
3177
|
+
version: BLE_SERVICE_VERSION,
|
|
3178
|
+
description: "NAP-BLE reference handler for shell-mediated BLE/GATT sessions"
|
|
3179
|
+
};
|
|
3180
|
+
function errorMessage3(err, fallback) {
|
|
3181
|
+
if (err instanceof Error) return err.message;
|
|
3182
|
+
if (typeof err === "string") return err;
|
|
3183
|
+
return fallback;
|
|
3184
|
+
}
|
|
3185
|
+
function settle3(call, send, okFalse, onValue) {
|
|
3186
|
+
let pending;
|
|
3187
|
+
try {
|
|
3188
|
+
pending = Promise.resolve(call());
|
|
3189
|
+
} catch (err) {
|
|
3190
|
+
send(okFalse(errorMessage3(err, "ble request failed")));
|
|
3191
|
+
return;
|
|
3192
|
+
}
|
|
3193
|
+
pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage3(err, "ble request failed"))));
|
|
3194
|
+
}
|
|
3195
|
+
function unsupported2(resultType, id) {
|
|
3196
|
+
return {
|
|
3197
|
+
type: resultType,
|
|
3198
|
+
id,
|
|
3199
|
+
error: `${resultType.replace(".result", "")} unavailable`
|
|
3200
|
+
};
|
|
3201
|
+
}
|
|
3202
|
+
function createBleService(options = {}) {
|
|
3203
|
+
return {
|
|
3204
|
+
descriptor: BLE_DESCRIPTOR,
|
|
3205
|
+
handleMessage(windowId, message, send) {
|
|
3206
|
+
const id = message.id ?? "";
|
|
3207
|
+
const context = { windowId };
|
|
3208
|
+
if (message.type === "ble.open") {
|
|
3209
|
+
if (!options.open) {
|
|
3210
|
+
send(unsupported2("ble.open.result", id));
|
|
3211
|
+
return;
|
|
3212
|
+
}
|
|
3213
|
+
const bleMessage = message;
|
|
3214
|
+
settle3(
|
|
3215
|
+
() => options.open(bleMessage.request, context),
|
|
3216
|
+
send,
|
|
3217
|
+
(error) => ({ type: "ble.open.result", id, error }),
|
|
3218
|
+
(result) => ({ type: "ble.open.result", id, session: result.session })
|
|
3219
|
+
);
|
|
3220
|
+
return;
|
|
3221
|
+
}
|
|
3222
|
+
if (message.type === "ble.services") {
|
|
3223
|
+
if (!options.services) {
|
|
3224
|
+
send(unsupported2("ble.services.result", id));
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
3227
|
+
const bleMessage = message;
|
|
3228
|
+
settle3(
|
|
3229
|
+
() => options.services(bleMessage.sessionId, context),
|
|
3230
|
+
send,
|
|
3231
|
+
(error) => ({ type: "ble.services.result", id, error }),
|
|
3232
|
+
(services) => ({ type: "ble.services.result", id, services })
|
|
3233
|
+
);
|
|
3234
|
+
return;
|
|
3235
|
+
}
|
|
3236
|
+
if (message.type === "ble.read") {
|
|
3237
|
+
if (!options.read) {
|
|
3238
|
+
send(unsupported2("ble.read.result", id));
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
const bleMessage = message;
|
|
3242
|
+
settle3(
|
|
3243
|
+
() => options.read(bleMessage.sessionId, bleMessage.target, context),
|
|
3244
|
+
send,
|
|
3245
|
+
(error) => ({ type: "ble.read.result", id, error }),
|
|
3246
|
+
(data) => ({ type: "ble.read.result", id, data })
|
|
3247
|
+
);
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
if (message.type === "ble.write") {
|
|
3251
|
+
if (!options.write) {
|
|
3252
|
+
send(unsupported2("ble.write.result", id));
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3255
|
+
const bleMessage = message;
|
|
3256
|
+
settle3(
|
|
3257
|
+
() => options.write(bleMessage.sessionId, bleMessage.target, bleMessage.data, bleMessage.options, context),
|
|
3258
|
+
send,
|
|
3259
|
+
(error) => ({ type: "ble.write.result", id, error }),
|
|
3260
|
+
() => ({ type: "ble.write.result", id })
|
|
3261
|
+
);
|
|
3262
|
+
return;
|
|
3263
|
+
}
|
|
3264
|
+
if (message.type === "ble.subscribe") {
|
|
3265
|
+
if (!options.subscribe) {
|
|
3266
|
+
send(unsupported2("ble.subscribe.result", id));
|
|
3267
|
+
return;
|
|
3268
|
+
}
|
|
3269
|
+
const bleMessage = message;
|
|
3270
|
+
settle3(
|
|
3271
|
+
() => options.subscribe(bleMessage.sessionId, bleMessage.target, context),
|
|
3272
|
+
send,
|
|
3273
|
+
(error) => ({ type: "ble.subscribe.result", id, error }),
|
|
3274
|
+
() => ({ type: "ble.subscribe.result", id })
|
|
3275
|
+
);
|
|
3276
|
+
return;
|
|
3277
|
+
}
|
|
3278
|
+
if (message.type === "ble.unsubscribe") {
|
|
3279
|
+
if (!options.unsubscribe) {
|
|
3280
|
+
send(unsupported2("ble.unsubscribe.result", id));
|
|
3281
|
+
return;
|
|
3282
|
+
}
|
|
3283
|
+
const bleMessage = message;
|
|
3284
|
+
settle3(
|
|
3285
|
+
() => options.unsubscribe(bleMessage.sessionId, bleMessage.target, context),
|
|
3286
|
+
send,
|
|
3287
|
+
(error) => ({ type: "ble.unsubscribe.result", id, error }),
|
|
3288
|
+
() => ({ type: "ble.unsubscribe.result", id })
|
|
3289
|
+
);
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
if (message.type === "ble.close") {
|
|
3293
|
+
if (!options.close) {
|
|
3294
|
+
send(unsupported2("ble.close.result", id));
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
const bleMessage = message;
|
|
3298
|
+
settle3(
|
|
3299
|
+
() => options.close(bleMessage.sessionId, bleMessage.reason, context),
|
|
3300
|
+
send,
|
|
3301
|
+
(error) => ({ type: "ble.close.result", id, error }),
|
|
3302
|
+
() => ({ type: "ble.close.result", id })
|
|
3303
|
+
);
|
|
3304
|
+
return;
|
|
3305
|
+
}
|
|
3306
|
+
send({
|
|
3307
|
+
type: `${message.type}.error`,
|
|
3308
|
+
id,
|
|
3309
|
+
error: `Unknown ble method: ${message.type}`
|
|
3310
|
+
});
|
|
3311
|
+
},
|
|
3312
|
+
onWindowDestroyed(windowId) {
|
|
3313
|
+
options.destroyWindow?.(windowId);
|
|
3314
|
+
}
|
|
3315
|
+
};
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
// src/webrtc-service.ts
|
|
3319
|
+
var WEBRTC_SERVICE_VERSION = "1.0.0";
|
|
3320
|
+
var WEBRTC_DESCRIPTOR = {
|
|
3321
|
+
name: "webrtc",
|
|
3322
|
+
version: WEBRTC_SERVICE_VERSION,
|
|
3323
|
+
description: "NAP-WEBRTC reference handler for shell-mediated WebRTC sessions"
|
|
3324
|
+
};
|
|
3325
|
+
function errorMessage4(err, fallback) {
|
|
3326
|
+
if (err instanceof Error) return err.message;
|
|
3327
|
+
if (typeof err === "string") return err;
|
|
3328
|
+
return fallback;
|
|
3329
|
+
}
|
|
3330
|
+
function settle4(call, send, okFalse, onValue) {
|
|
3331
|
+
let pending;
|
|
3332
|
+
try {
|
|
3333
|
+
pending = Promise.resolve(call());
|
|
3334
|
+
} catch (err) {
|
|
3335
|
+
send(okFalse(errorMessage4(err, "webrtc request failed")));
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3338
|
+
pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage4(err, "webrtc request failed"))));
|
|
3339
|
+
}
|
|
3340
|
+
function unsupported3(resultType, id) {
|
|
3341
|
+
return {
|
|
3342
|
+
type: resultType,
|
|
3343
|
+
id,
|
|
3344
|
+
error: `${resultType.replace(".result", "")} unavailable`
|
|
3345
|
+
};
|
|
3346
|
+
}
|
|
3347
|
+
function createContext(windowId, send) {
|
|
3348
|
+
return {
|
|
3349
|
+
windowId,
|
|
3350
|
+
emit(event) {
|
|
3351
|
+
send({ type: "webrtc.event", event });
|
|
3352
|
+
}
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
function createWebrtcService(options = {}) {
|
|
3356
|
+
return {
|
|
3357
|
+
descriptor: WEBRTC_DESCRIPTOR,
|
|
3358
|
+
handleMessage(windowId, message, send) {
|
|
3359
|
+
const id = message.id ?? "";
|
|
3360
|
+
const context = createContext(windowId, send);
|
|
3361
|
+
if (message.type === "webrtc.open") {
|
|
3362
|
+
if (!options.open) {
|
|
3363
|
+
send(unsupported3("webrtc.open.result", id));
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3366
|
+
const webrtcMessage = message;
|
|
3367
|
+
settle4(
|
|
3368
|
+
() => options.open(webrtcMessage.request, context),
|
|
3369
|
+
send,
|
|
3370
|
+
(error) => ({ type: "webrtc.open.result", id, error }),
|
|
3371
|
+
(result) => ({ type: "webrtc.open.result", id, session: result.session })
|
|
3372
|
+
);
|
|
3373
|
+
return;
|
|
3374
|
+
}
|
|
3375
|
+
if (message.type === "webrtc.send") {
|
|
3376
|
+
if (!options.send) {
|
|
3377
|
+
send(unsupported3("webrtc.send.result", id));
|
|
3378
|
+
return;
|
|
3379
|
+
}
|
|
3380
|
+
const webrtcMessage = message;
|
|
3381
|
+
settle4(
|
|
3382
|
+
() => options.send(webrtcMessage.sessionId, webrtcMessage.payload, context),
|
|
3383
|
+
send,
|
|
3384
|
+
(error) => ({ type: "webrtc.send.result", id, error }),
|
|
3385
|
+
() => ({ type: "webrtc.send.result", id })
|
|
3386
|
+
);
|
|
3387
|
+
return;
|
|
3388
|
+
}
|
|
3389
|
+
if (message.type === "webrtc.close") {
|
|
3390
|
+
if (!options.close) {
|
|
3391
|
+
send(unsupported3("webrtc.close.result", id));
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
const webrtcMessage = message;
|
|
3395
|
+
settle4(
|
|
3396
|
+
() => options.close(webrtcMessage.sessionId, webrtcMessage.reason, context),
|
|
3397
|
+
send,
|
|
3398
|
+
(error) => ({ type: "webrtc.close.result", id, error }),
|
|
3399
|
+
() => ({ type: "webrtc.close.result", id })
|
|
3400
|
+
);
|
|
3401
|
+
return;
|
|
3402
|
+
}
|
|
3403
|
+
send({
|
|
3404
|
+
type: `${message.type}.error`,
|
|
3405
|
+
id,
|
|
3406
|
+
error: `Unknown webrtc method: ${message.type}`
|
|
3407
|
+
});
|
|
3408
|
+
},
|
|
3409
|
+
onWindowDestroyed(windowId) {
|
|
3410
|
+
options.destroyWindow?.(windowId);
|
|
3411
|
+
}
|
|
3412
|
+
};
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
// src/common-service.ts
|
|
3416
|
+
import {
|
|
3417
|
+
decode,
|
|
3418
|
+
encodeBytes,
|
|
3419
|
+
naddrEncode,
|
|
3420
|
+
neventEncode,
|
|
3421
|
+
noteEncode,
|
|
3422
|
+
nprofileEncode,
|
|
3423
|
+
npubEncode
|
|
3424
|
+
} from "nostr-tools/nip19";
|
|
3425
|
+
var COMMON_SERVICE_VERSION = "1.0.0";
|
|
3426
|
+
var BECH32_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
3427
|
+
var COMMON_DESCRIPTOR = {
|
|
3428
|
+
name: "common",
|
|
3429
|
+
version: COMMON_SERVICE_VERSION,
|
|
3430
|
+
description: "NAP-COMMON reference handler for shell-mediated social helpers"
|
|
3431
|
+
};
|
|
3432
|
+
function errorMessage5(err, fallback) {
|
|
3433
|
+
if (err instanceof Error) return err.message;
|
|
3434
|
+
if (typeof err === "string") return err;
|
|
3435
|
+
return fallback;
|
|
3436
|
+
}
|
|
3437
|
+
function encodeNip19(input) {
|
|
3438
|
+
try {
|
|
3439
|
+
switch (input.type) {
|
|
3440
|
+
case "npub":
|
|
3441
|
+
return { ok: true, value: npubEncode(input.hex), nip19Type: input.type };
|
|
3442
|
+
case "note":
|
|
3443
|
+
return { ok: true, value: noteEncode(input.hex), nip19Type: input.type };
|
|
3444
|
+
case "nprofile":
|
|
3445
|
+
return { ok: true, value: nprofileEncode({ pubkey: input.pubkey, relays: input.relays }), nip19Type: input.type };
|
|
3446
|
+
case "nevent":
|
|
3447
|
+
return {
|
|
3448
|
+
ok: true,
|
|
3449
|
+
value: neventEncode({ id: input.eventId, relays: input.relays, author: input.author, kind: input.kind }),
|
|
3450
|
+
nip19Type: input.type
|
|
3451
|
+
};
|
|
3452
|
+
case "naddr":
|
|
3453
|
+
return {
|
|
3454
|
+
ok: true,
|
|
3455
|
+
value: naddrEncode({ identifier: input.identifier, pubkey: input.pubkey, kind: input.kind, relays: input.relays }),
|
|
3456
|
+
nip19Type: input.type
|
|
3457
|
+
};
|
|
3458
|
+
case "nrelay":
|
|
3459
|
+
return { ok: true, value: encodeBytes("nrelay", new TextEncoder().encode(input.relay)), nip19Type: input.type };
|
|
3460
|
+
default:
|
|
3461
|
+
return { ok: false, error: "unsupported NIP-19 type" };
|
|
3462
|
+
}
|
|
3463
|
+
} catch (err) {
|
|
3464
|
+
return { ok: false, error: errorMessage5(err, "NIP-19 encode failed") };
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
function decodeNip19Value(value) {
|
|
3468
|
+
try {
|
|
3469
|
+
const decoded = decode(value);
|
|
3470
|
+
switch (decoded.type) {
|
|
3471
|
+
case "npub":
|
|
3472
|
+
case "note":
|
|
3473
|
+
return { ok: true, nip19Type: decoded.type, hex: decoded.data };
|
|
3474
|
+
case "nprofile":
|
|
3475
|
+
return { ok: true, nip19Type: decoded.type, pubkey: decoded.data.pubkey, relays: decoded.data.relays };
|
|
3476
|
+
case "nevent":
|
|
3477
|
+
return {
|
|
3478
|
+
ok: true,
|
|
3479
|
+
nip19Type: decoded.type,
|
|
3480
|
+
eventId: decoded.data.id,
|
|
3481
|
+
relays: decoded.data.relays,
|
|
3482
|
+
author: decoded.data.author,
|
|
3483
|
+
kind: decoded.data.kind
|
|
3484
|
+
};
|
|
3485
|
+
case "naddr":
|
|
3486
|
+
return {
|
|
3487
|
+
ok: true,
|
|
3488
|
+
nip19Type: decoded.type,
|
|
3489
|
+
identifier: decoded.data.identifier,
|
|
3490
|
+
pubkey: decoded.data.pubkey,
|
|
3491
|
+
kind: decoded.data.kind,
|
|
3492
|
+
relays: decoded.data.relays
|
|
3493
|
+
};
|
|
3494
|
+
default:
|
|
3495
|
+
return { ok: false, error: "unsupported NIP-19 type" };
|
|
3496
|
+
}
|
|
3497
|
+
} catch (err) {
|
|
3498
|
+
if (value.startsWith("nrelay1")) return decodeNrelay(value);
|
|
3499
|
+
return { ok: false, error: errorMessage5(err, "NIP-19 decode failed") };
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
function decodeNrelay(value) {
|
|
3503
|
+
try {
|
|
3504
|
+
const separator = value.lastIndexOf("1");
|
|
3505
|
+
if (separator <= 0 || separator + 7 > value.length) return { ok: false, error: "invalid nrelay value" };
|
|
3506
|
+
const dataPart = value.slice(separator + 1);
|
|
3507
|
+
const words = [...dataPart].map((char) => {
|
|
3508
|
+
const index = BECH32_CHARSET.indexOf(char);
|
|
3509
|
+
if (index === -1) throw new Error("invalid nrelay character");
|
|
3510
|
+
return index;
|
|
3511
|
+
});
|
|
3512
|
+
const payloadWords = words.slice(0, -6);
|
|
3513
|
+
const bytes = convertBits(payloadWords, 5, 8, false);
|
|
3514
|
+
return { ok: true, nip19Type: "nrelay", relay: new TextDecoder().decode(Uint8Array.from(bytes)) };
|
|
3515
|
+
} catch (err) {
|
|
3516
|
+
return { ok: false, error: errorMessage5(err, "invalid nrelay value") };
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
function convertBits(data, fromBits, toBits, pad) {
|
|
3520
|
+
let acc = 0;
|
|
3521
|
+
let bits = 0;
|
|
3522
|
+
const result = [];
|
|
3523
|
+
const maxv = (1 << toBits) - 1;
|
|
3524
|
+
const maxAcc = (1 << fromBits + toBits - 1) - 1;
|
|
3525
|
+
for (const value of data) {
|
|
3526
|
+
if (value < 0 || value >> fromBits !== 0) throw new Error("invalid bech32 data");
|
|
3527
|
+
acc = (acc << fromBits | value) & maxAcc;
|
|
3528
|
+
bits += fromBits;
|
|
3529
|
+
while (bits >= toBits) {
|
|
3530
|
+
bits -= toBits;
|
|
3531
|
+
result.push(acc >> bits & maxv);
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
if (pad) {
|
|
3535
|
+
if (bits > 0) result.push(acc << toBits - bits & maxv);
|
|
3536
|
+
} else if (bits >= fromBits || (acc << toBits - bits & maxv) !== 0) {
|
|
3537
|
+
throw new Error("invalid bech32 padding");
|
|
3538
|
+
}
|
|
3539
|
+
return result;
|
|
3540
|
+
}
|
|
3541
|
+
function settle5(call, send, okFalse, onValue) {
|
|
3542
|
+
let pending;
|
|
3543
|
+
try {
|
|
3544
|
+
pending = Promise.resolve(call());
|
|
3545
|
+
} catch (err) {
|
|
3546
|
+
send(okFalse(errorMessage5(err, "common request failed")));
|
|
3547
|
+
return;
|
|
3548
|
+
}
|
|
3549
|
+
pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage5(err, "common request failed"))));
|
|
3550
|
+
}
|
|
3551
|
+
function createCommonService(options = {}) {
|
|
3552
|
+
return {
|
|
3553
|
+
descriptor: COMMON_DESCRIPTOR,
|
|
3554
|
+
handleMessage(windowId, message, send) {
|
|
3555
|
+
const id = message.id ?? "";
|
|
3556
|
+
const context = { windowId };
|
|
3557
|
+
if (message.type === "common.encodeNip19") {
|
|
3558
|
+
const input = message.input;
|
|
3559
|
+
const result = input ? encodeNip19(input) : { ok: false, error: "invalid input" };
|
|
3560
|
+
send({ type: "common.encodeNip19.result", id, ...result });
|
|
3561
|
+
return;
|
|
3562
|
+
}
|
|
3563
|
+
if (message.type === "common.decodeNip19") {
|
|
3564
|
+
const value = message.value;
|
|
3565
|
+
const result = typeof value === "string" ? decodeNip19Value(value) : { ok: false, error: "invalid value" };
|
|
3566
|
+
send({ type: "common.decodeNip19.result", id, ...result });
|
|
3567
|
+
return;
|
|
3568
|
+
}
|
|
3569
|
+
if (message.type === "common.getProfile") {
|
|
3570
|
+
const target = message.target;
|
|
3571
|
+
if (!options.getProfile) {
|
|
3572
|
+
send({ type: "common.getProfile.result", id, ok: false, pubkey: target ?? "", error: "profile lookup unavailable" });
|
|
3573
|
+
return;
|
|
3574
|
+
}
|
|
3575
|
+
settle5(
|
|
3576
|
+
() => options.getProfile(target, context),
|
|
3577
|
+
send,
|
|
3578
|
+
(error) => ({ type: "common.getProfile.result", id, ok: false, pubkey: target ?? "", error }),
|
|
3579
|
+
(result) => ({ type: "common.getProfile.result", id, ...result })
|
|
3580
|
+
);
|
|
3581
|
+
return;
|
|
3582
|
+
}
|
|
3583
|
+
if (message.type === "common.follows") {
|
|
3584
|
+
if (!options.follows) {
|
|
3585
|
+
send({ type: "common.follows.result", id, ok: false, pubkeys: [], error: "follows lookup unavailable" });
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3588
|
+
settle5(
|
|
3589
|
+
() => options.follows(context),
|
|
3590
|
+
send,
|
|
3591
|
+
(error) => ({ type: "common.follows.result", id, ok: false, pubkeys: [], error }),
|
|
3592
|
+
(result) => ({ type: "common.follows.result", id, ...result })
|
|
3593
|
+
);
|
|
3594
|
+
return;
|
|
3595
|
+
}
|
|
3596
|
+
if (message.type === "common.follow") {
|
|
3597
|
+
const pubkeys = message.pubkeys ?? [];
|
|
3598
|
+
settleAction(options.follow, pubkeys, context, send, "common.follow.result", id, "follow unavailable");
|
|
3599
|
+
return;
|
|
3600
|
+
}
|
|
3601
|
+
if (message.type === "common.unfollow") {
|
|
3602
|
+
const pubkeys = message.pubkeys ?? [];
|
|
3603
|
+
settleAction(options.unfollow, pubkeys, context, send, "common.unfollow.result", id, "unfollow unavailable");
|
|
3604
|
+
return;
|
|
3605
|
+
}
|
|
3606
|
+
if (message.type === "common.react") {
|
|
3607
|
+
const m = message;
|
|
3608
|
+
if (!options.react) {
|
|
3609
|
+
send({ type: "common.react.result", id, ok: false, error: "react unavailable" });
|
|
3610
|
+
return;
|
|
3611
|
+
}
|
|
3612
|
+
settle5(
|
|
3613
|
+
() => options.react(m.targetEventId, m.reaction, m.customEmojiHref, context),
|
|
3614
|
+
send,
|
|
3615
|
+
(error) => ({ type: "common.react.result", id, ok: false, error }),
|
|
3616
|
+
(result) => ({ type: "common.react.result", id, ...result })
|
|
3617
|
+
);
|
|
3618
|
+
return;
|
|
3619
|
+
}
|
|
3620
|
+
if (message.type === "common.report") {
|
|
3621
|
+
const m = message;
|
|
3622
|
+
if (!options.report) {
|
|
3623
|
+
send({ type: "common.report.result", id, ok: false, error: "report unavailable" });
|
|
3624
|
+
return;
|
|
3625
|
+
}
|
|
3626
|
+
settle5(
|
|
3627
|
+
() => options.report(m.target, m.reason, m.text, context),
|
|
3628
|
+
send,
|
|
3629
|
+
(error) => ({ type: "common.report.result", id, ok: false, error }),
|
|
3630
|
+
(result) => ({ type: "common.report.result", id, ...result })
|
|
3631
|
+
);
|
|
3632
|
+
return;
|
|
3633
|
+
}
|
|
3634
|
+
send({
|
|
3635
|
+
type: `${message.type}.error`,
|
|
3636
|
+
id,
|
|
3637
|
+
error: `Unknown common method: ${message.type}`
|
|
3638
|
+
});
|
|
3639
|
+
},
|
|
3640
|
+
onWindowDestroyed(_windowId) {
|
|
3641
|
+
}
|
|
3642
|
+
};
|
|
3643
|
+
}
|
|
3644
|
+
function settleAction(action, pubkeys, context, send, resultType, id, unavailable) {
|
|
3645
|
+
if (!action) {
|
|
3646
|
+
send({ type: resultType, id, ok: false, error: unavailable });
|
|
3647
|
+
return;
|
|
3648
|
+
}
|
|
3649
|
+
settle5(
|
|
3650
|
+
() => action(pubkeys, context),
|
|
3651
|
+
send,
|
|
3652
|
+
(error) => ({ type: resultType, id, ok: false, error }),
|
|
3653
|
+
(result) => ({ type: resultType, id, ...result })
|
|
3654
|
+
);
|
|
3655
|
+
}
|
|
2742
3656
|
export {
|
|
2743
3657
|
createAudioService,
|
|
3658
|
+
createBleService,
|
|
2744
3659
|
createBrowserMediaBridge,
|
|
2745
3660
|
createCacheService,
|
|
2746
3661
|
createCatalogIntentResolver,
|
|
3662
|
+
createCommonService,
|
|
2747
3663
|
createConfigService,
|
|
2748
3664
|
createCoordinatedRelay,
|
|
2749
3665
|
createCvmService,
|
|
@@ -2751,6 +3667,8 @@ export {
|
|
|
2751
3667
|
createIdentityService,
|
|
2752
3668
|
createIntentService,
|
|
2753
3669
|
createKeysService,
|
|
3670
|
+
createLinkService,
|
|
3671
|
+
createListsService,
|
|
2754
3672
|
createMediaService,
|
|
2755
3673
|
createNotificationService,
|
|
2756
3674
|
createNotifyService,
|
|
@@ -2758,8 +3676,10 @@ export {
|
|
|
2758
3676
|
createRelayPoolOutboxRouter,
|
|
2759
3677
|
createRelayPoolService,
|
|
2760
3678
|
createResourceService,
|
|
3679
|
+
createSerialService,
|
|
2761
3680
|
createThemeService,
|
|
2762
3681
|
createUploadService,
|
|
3682
|
+
createWebrtcService,
|
|
2763
3683
|
manifestToIntentCatalogEntry
|
|
2764
3684
|
};
|
|
2765
3685
|
//# sourceMappingURL=index.js.map
|