@kehto/services 0.7.0 → 0.9.1

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/dist/index.js CHANGED
@@ -231,156 +231,6 @@ function createNotificationService(options) {
231
231
 
232
232
  // src/identity-service.ts
233
233
  var IDENTITY_SERVICE_VERSION = "1.0.0";
234
- var DECRYPT_ERROR_CODES = [
235
- "class-forbidden",
236
- "signer-denied",
237
- "signer-unavailable",
238
- "decrypt-failed",
239
- "malformed-wrap",
240
- "impersonation",
241
- "unsupported-encryption",
242
- "policy-denied"
243
- ];
244
- var DECRYPT_ERROR_CODE_SET = new Set(DECRYPT_ERROR_CODES);
245
- function isDecryptErrorCode(value) {
246
- return typeof value === "string" && DECRYPT_ERROR_CODE_SET.has(value);
247
- }
248
- function normalizeDecryptError(error) {
249
- if (isDecryptErrorCode(error)) return error;
250
- if (typeof error === "object" && error !== null) {
251
- const candidate = error;
252
- if (isDecryptErrorCode(candidate.code)) return candidate.code;
253
- if (isDecryptErrorCode(candidate.error)) return candidate.error;
254
- if (isDecryptErrorCode(candidate.message)) return candidate.message;
255
- }
256
- return "decrypt-failed";
257
- }
258
- function sendDecryptError(id, error, send) {
259
- const result = {
260
- type: "identity.decrypt.error",
261
- id,
262
- error
263
- };
264
- send(result);
265
- }
266
- function isStringArrayArray(value) {
267
- return Array.isArray(value) && value.every(
268
- (tag) => Array.isArray(tag) && tag.every((part) => typeof part === "string")
269
- );
270
- }
271
- function isNostrEvent(value) {
272
- const event = value;
273
- return typeof event === "object" && event !== null && typeof event.id === "string" && typeof event.pubkey === "string" && typeof event.created_at === "number" && typeof event.kind === "number" && isStringArrayArray(event.tags) && typeof event.content === "string" && typeof event.sig === "string";
274
- }
275
- function isRumor(value) {
276
- const rumor = value;
277
- return typeof rumor === "object" && rumor !== null && typeof rumor.id === "string" && typeof rumor.pubkey === "string" && typeof rumor.created_at === "number" && typeof rumor.kind === "number" && isStringArrayArray(rumor.tags) && typeof rumor.content === "string";
278
- }
279
- function isGiftWrapDecryptResult(value) {
280
- const result = value;
281
- return typeof result === "object" && result !== null && isNostrEvent(result.seal) && isRumor(result.rumor);
282
- }
283
- function firstDecodedByte(content) {
284
- const trimmed = content.trim();
285
- if (trimmed.length === 0) return null;
286
- const normalized = trimmed.replace(/-/g, "+").replace(/_/g, "/");
287
- const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
288
- try {
289
- const decoded = atob(padded);
290
- return decoded.length > 0 ? decoded.charCodeAt(0) : null;
291
- } catch {
292
- return null;
293
- }
294
- }
295
- function detectEncryptionMode(event) {
296
- if (event.kind === 4) return "nip04";
297
- if (event.kind === 1059) return "nip17";
298
- if (event.kind === 14 || firstDecodedByte(event.content) === 2) {
299
- return "nip44-direct";
300
- }
301
- return null;
302
- }
303
- function rumorFromSignedEvent(event, content) {
304
- return {
305
- id: event.id,
306
- pubkey: event.pubkey,
307
- kind: event.kind,
308
- tags: event.tags,
309
- created_at: event.created_at,
310
- content
311
- };
312
- }
313
- async function handleDecrypt(id, message, send, options) {
314
- const event = message.event;
315
- if (!isNostrEvent(event)) {
316
- sendDecryptError(id, "malformed-wrap", send);
317
- return;
318
- }
319
- const verifyEvent = options.verifyEvent ?? (() => true);
320
- let verified;
321
- try {
322
- verified = await Promise.resolve(verifyEvent(event));
323
- } catch {
324
- sendDecryptError(id, "malformed-wrap", send);
325
- return;
326
- }
327
- if (!verified) {
328
- sendDecryptError(id, "malformed-wrap", send);
329
- return;
330
- }
331
- const mode = detectEncryptionMode(event);
332
- if (!mode) {
333
- sendDecryptError(id, "unsupported-encryption", send);
334
- return;
335
- }
336
- const decryptor = options.getDecryptor?.() ?? null;
337
- if (!decryptor) {
338
- sendDecryptError(id, "signer-unavailable", send);
339
- return;
340
- }
341
- try {
342
- if (mode === "nip04") {
343
- const plaintext = await decryptor.nip04Decrypt(event.pubkey, event.content);
344
- const result2 = {
345
- type: "identity.decrypt.result",
346
- id,
347
- rumor: rumorFromSignedEvent(event, plaintext),
348
- sender: event.pubkey
349
- };
350
- send(result2);
351
- return;
352
- }
353
- if (mode === "nip44-direct") {
354
- const plaintext = await decryptor.nip44Decrypt(event.pubkey, event.content);
355
- const result2 = {
356
- type: "identity.decrypt.result",
357
- id,
358
- rumor: rumorFromSignedEvent(event, plaintext),
359
- sender: event.pubkey
360
- };
361
- send(result2);
362
- return;
363
- }
364
- const unwrapped = await decryptor.unwrapGiftWrap(event);
365
- if (!isGiftWrapDecryptResult(unwrapped)) {
366
- sendDecryptError(id, "malformed-wrap", send);
367
- return;
368
- }
369
- if (unwrapped.seal.pubkey !== unwrapped.rumor.pubkey) {
370
- sendDecryptError(id, "impersonation", send);
371
- return;
372
- }
373
- const result = {
374
- type: "identity.decrypt.result",
375
- id,
376
- rumor: unwrapped.rumor,
377
- sender: unwrapped.seal.pubkey
378
- };
379
- send(result);
380
- } catch (error) {
381
- sendDecryptError(id, normalizeDecryptError(error), send);
382
- }
383
- }
384
234
  function createIdentityService(options) {
385
235
  return {
386
236
  descriptor: {
@@ -496,10 +346,6 @@ function createIdentityService(options) {
496
346
  send(result);
497
347
  return;
498
348
  }
499
- case "identity.decrypt": {
500
- void handleDecrypt(id, message, send, options);
501
- return;
502
- }
503
349
  default:
504
350
  sendError(message.type, `Unknown identity method: ${message.type}`);
505
351
  }
@@ -2550,6 +2396,227 @@ function toErrorMessage3(err) {
2550
2396
  return "upload failed";
2551
2397
  }
2552
2398
 
2399
+ // src/intent-service.ts
2400
+ var INTENT_SERVICE_VERSION = "1.0.0";
2401
+ var INTENT_DESCRIPTOR = {
2402
+ name: "intent",
2403
+ version: INTENT_SERVICE_VERSION,
2404
+ description: "NAP-INTENT archetype intent dispatch \u2014 invoke/available/handlers"
2405
+ };
2406
+ function toErrorMessage4(err) {
2407
+ if (err instanceof Error) return err.message;
2408
+ if (typeof err === "string") return err;
2409
+ return "intent request failed";
2410
+ }
2411
+ function createIntentService(options) {
2412
+ if (!options || typeof options.resolver !== "object" || options.resolver === null) {
2413
+ throw new Error("createIntentService: options.resolver is required");
2414
+ }
2415
+ const { resolver } = options;
2416
+ const windows = /* @__PURE__ */ new Map();
2417
+ resolver.onChanged?.((availability) => {
2418
+ for (const send of windows.values()) {
2419
+ send({ type: "intent.changed", availability });
2420
+ }
2421
+ });
2422
+ function settle(call, send, resultType, id, onValue) {
2423
+ let pending;
2424
+ try {
2425
+ pending = Promise.resolve(call());
2426
+ } catch (err) {
2427
+ send({ type: resultType, id, error: toErrorMessage4(err) });
2428
+ return;
2429
+ }
2430
+ pending.then((value) => send(onValue(value))).catch((err) => send({ type: resultType, id, error: toErrorMessage4(err) }));
2431
+ }
2432
+ function handleInvoke(windowId, msg, send) {
2433
+ const m = msg;
2434
+ const id = m.id ?? "";
2435
+ const request = m.request;
2436
+ if (!request || typeof request !== "object" || typeof request.archetype !== "string" || request.archetype.length === 0) {
2437
+ send({ type: "intent.invoke.result", id, error: "invalid request" });
2438
+ return;
2439
+ }
2440
+ settle(
2441
+ () => resolver.invoke(request, { windowId }),
2442
+ send,
2443
+ "intent.invoke.result",
2444
+ id,
2445
+ (result) => ({ type: "intent.invoke.result", id, result })
2446
+ );
2447
+ }
2448
+ function handleAvailable(msg, send) {
2449
+ const m = msg;
2450
+ const id = m.id ?? "";
2451
+ if (typeof m.archetype !== "string" || m.archetype.length === 0) {
2452
+ send({ type: "intent.available.result", id, error: "invalid archetype" });
2453
+ return;
2454
+ }
2455
+ const archetype = m.archetype;
2456
+ settle(
2457
+ () => resolver.available(archetype),
2458
+ send,
2459
+ "intent.available.result",
2460
+ id,
2461
+ (availability) => ({ type: "intent.available.result", id, availability })
2462
+ );
2463
+ }
2464
+ function handleHandlers(msg, send) {
2465
+ const m = msg;
2466
+ const id = m.id ?? "";
2467
+ settle(
2468
+ () => resolver.handlers(),
2469
+ send,
2470
+ "intent.handlers.result",
2471
+ id,
2472
+ (handlers) => ({ type: "intent.handlers.result", id, handlers })
2473
+ );
2474
+ }
2475
+ return {
2476
+ descriptor: INTENT_DESCRIPTOR,
2477
+ handleMessage(windowId, message, send) {
2478
+ windows.set(windowId, send);
2479
+ switch (message.type) {
2480
+ case "intent.invoke":
2481
+ handleInvoke(windowId, message, send);
2482
+ return;
2483
+ case "intent.available":
2484
+ handleAvailable(message, send);
2485
+ return;
2486
+ case "intent.handlers":
2487
+ handleHandlers(message, send);
2488
+ return;
2489
+ default:
2490
+ return;
2491
+ }
2492
+ },
2493
+ onWindowDestroyed(windowId) {
2494
+ windows.delete(windowId);
2495
+ }
2496
+ };
2497
+ }
2498
+
2499
+ // src/catalog-intent-resolver.ts
2500
+ function candidatesFor(catalog, archetype, defaultHandler) {
2501
+ const candidates = [];
2502
+ for (const entry of catalog) {
2503
+ const support = entry.archetypes[archetype];
2504
+ if (!support) continue;
2505
+ candidates.push({
2506
+ dTag: entry.dTag,
2507
+ ...entry.title === void 0 ? {} : { title: entry.title },
2508
+ actions: support.actions,
2509
+ protocols: support.protocols,
2510
+ ...entry.dTag === defaultHandler ? { isDefault: true } : {}
2511
+ });
2512
+ }
2513
+ return candidates;
2514
+ }
2515
+ function fail(archetype, action, error) {
2516
+ return { ok: false, archetype, action, handled: false, error };
2517
+ }
2518
+ function createCatalogIntentResolver(options) {
2519
+ if (!options || typeof options.loadCatalog !== "function") {
2520
+ throw new Error("createCatalogIntentResolver: options.loadCatalog is required");
2521
+ }
2522
+ if (!options.windows || typeof options.windows.open !== "function") {
2523
+ throw new Error("createCatalogIntentResolver: options.windows is required");
2524
+ }
2525
+ const { loadCatalog, windows, getDefaultHandler, chooseHandler, defaultProtocol } = options;
2526
+ const listeners = /* @__PURE__ */ new Set();
2527
+ async function availabilityFor(archetype) {
2528
+ const catalog = await loadCatalog();
2529
+ const def = getDefaultHandler?.(archetype);
2530
+ const candidates = candidatesFor(catalog, archetype, def);
2531
+ return {
2532
+ archetype,
2533
+ available: candidates.length > 0,
2534
+ candidates,
2535
+ hasDefault: def !== void 0 && candidates.some((c) => c.dTag === def)
2536
+ };
2537
+ }
2538
+ async function pickHandler(archetype, candidates, preference, callerWindowId) {
2539
+ if (typeof preference === "string" && preference !== "default" && preference !== "choose") {
2540
+ return preference;
2541
+ }
2542
+ if (preference === "choose") {
2543
+ const picked = await chooseHandler?.(archetype, candidates, callerWindowId);
2544
+ return picked ?? null;
2545
+ }
2546
+ const def = getDefaultHandler?.(archetype);
2547
+ if (def !== void 0 && candidates.some((c) => c.dTag === def)) return def;
2548
+ if (candidates.length === 1) return candidates[0].dTag;
2549
+ if (chooseHandler) {
2550
+ const picked = await chooseHandler(archetype, candidates, callerWindowId);
2551
+ return picked ?? null;
2552
+ }
2553
+ return candidates[0].dTag;
2554
+ }
2555
+ async function invoke(request, context) {
2556
+ const archetype = request.archetype;
2557
+ const action = request.action ?? "open";
2558
+ const catalog = await loadCatalog();
2559
+ const candidates = candidatesFor(catalog, archetype, getDefaultHandler?.(archetype));
2560
+ if (candidates.length === 0) return fail(archetype, action, "no handler");
2561
+ const pickedDTag = await pickHandler(archetype, candidates, request.handler, context.windowId);
2562
+ if (pickedDTag === null) return fail(archetype, action, "user cancelled");
2563
+ const handler = candidates.find((c) => c.dTag === pickedDTag);
2564
+ if (!handler) return fail(archetype, action, "no handler");
2565
+ if (!handler.actions.includes(action)) return fail(archetype, action, "unsupported action");
2566
+ const protocol = request.protocol ?? defaultProtocol?.(archetype) ?? handler.protocols[0];
2567
+ if (protocol !== void 0 && handler.protocols.length > 0 && !handler.protocols.includes(protocol)) {
2568
+ return fail(archetype, action, "unsupported protocol");
2569
+ }
2570
+ let windowId;
2571
+ try {
2572
+ const opened = await windows.open({
2573
+ dTag: handler.dTag,
2574
+ archetype,
2575
+ action,
2576
+ ...protocol === void 0 ? {} : { protocol },
2577
+ ...request.payload === void 0 ? {} : { payload: request.payload },
2578
+ ...request.behavior === void 0 ? {} : { behavior: request.behavior },
2579
+ callerWindowId: context.windowId
2580
+ });
2581
+ windowId = opened.windowId;
2582
+ } catch {
2583
+ return fail(archetype, action, "invoke failed");
2584
+ }
2585
+ return {
2586
+ ok: true,
2587
+ archetype,
2588
+ action,
2589
+ handled: true,
2590
+ handler: handler.dTag,
2591
+ windowId,
2592
+ ...protocol === void 0 ? {} : { protocol }
2593
+ };
2594
+ }
2595
+ async function handlers() {
2596
+ const catalog = await loadCatalog();
2597
+ const archetypes = /* @__PURE__ */ new Set();
2598
+ for (const entry of catalog) {
2599
+ for (const slug of Object.keys(entry.archetypes)) archetypes.add(slug);
2600
+ }
2601
+ return Promise.all([...archetypes].map((a) => availabilityFor(a)));
2602
+ }
2603
+ return {
2604
+ invoke,
2605
+ available: availabilityFor,
2606
+ handlers,
2607
+ onChanged(listener) {
2608
+ listeners.add(listener);
2609
+ return () => listeners.delete(listener);
2610
+ },
2611
+ notifyChanged(archetype) {
2612
+ if (listeners.size === 0) return;
2613
+ void availabilityFor(archetype).then((availability) => {
2614
+ for (const listener of listeners) listener(availability);
2615
+ });
2616
+ }
2617
+ };
2618
+ }
2619
+
2553
2620
  // src/cvm-service.ts
2554
2621
  var CVM_SERVICE_VERSION = "1.0.0";
2555
2622
  var CVM_DESCRIPTOR = {
@@ -2592,7 +2659,7 @@ function createCvmService(options) {
2592
2659
  const m = msg;
2593
2660
  const id = m.id ?? "";
2594
2661
  void transport.discover(m.query).then((servers) => send({ type: "cvm.discover.result", id, servers })).catch(
2595
- (err) => send({ type: "cvm.discover.result", id, servers: [], error: toErrorMessage4(err) })
2662
+ (err) => send({ type: "cvm.discover.result", id, servers: [], error: toErrorMessage5(err) })
2596
2663
  );
2597
2664
  }
2598
2665
  function handleRequest(windowId, msg, send) {
@@ -2608,7 +2675,7 @@ function createCvmService(options) {
2608
2675
  }
2609
2676
  openSession(windowId, m.server, send);
2610
2677
  void transport.request(m.server, m.message, m.options).then((message) => send({ type: "cvm.request.result", id, message })).catch(
2611
- (err) => send({ type: "cvm.request.result", id, error: toErrorMessage4(err) })
2678
+ (err) => send({ type: "cvm.request.result", id, error: toErrorMessage5(err) })
2612
2679
  );
2613
2680
  }
2614
2681
  function handleClose(windowId, msg, send) {
@@ -2619,7 +2686,7 @@ function createCvmService(options) {
2619
2686
  return;
2620
2687
  }
2621
2688
  closeSession(windowId, m.server.pubkey);
2622
- void transport.close(m.server).then(() => send({ type: "cvm.close.result", id })).catch((err) => send({ type: "cvm.close.result", id, error: toErrorMessage4(err) }));
2689
+ void transport.close(m.server).then(() => send({ type: "cvm.close.result", id })).catch((err) => send({ type: "cvm.close.result", id, error: toErrorMessage5(err) }));
2623
2690
  }
2624
2691
  return {
2625
2692
  descriptor: CVM_DESCRIPTOR,
@@ -2650,7 +2717,7 @@ function createCvmService(options) {
2650
2717
  }
2651
2718
  };
2652
2719
  }
2653
- function toErrorMessage4(err) {
2720
+ function toErrorMessage5(err) {
2654
2721
  if (err instanceof Error) return err.message;
2655
2722
  if (typeof err === "string") return err;
2656
2723
  return "cvm request failed";
@@ -2659,11 +2726,13 @@ export {
2659
2726
  createAudioService,
2660
2727
  createBrowserMediaBridge,
2661
2728
  createCacheService,
2729
+ createCatalogIntentResolver,
2662
2730
  createConfigService,
2663
2731
  createCoordinatedRelay,
2664
2732
  createCvmService,
2665
2733
  createHttpUploader,
2666
2734
  createIdentityService,
2735
+ createIntentService,
2667
2736
  createKeysService,
2668
2737
  createMediaService,
2669
2738
  createNotificationService,