@kehto/services 0.12.1 → 0.13.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,10 +231,10 @@ 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) {
234
+ function sendProviderError(send, result, fallback, err2) {
235
235
  send({
236
236
  ...result,
237
- error: err?.message ?? fallback
237
+ error: err2?.message ?? fallback
238
238
  });
239
239
  }
240
240
  async function getCurrentPubkey(options) {
@@ -251,13 +251,13 @@ function sendOptionalProviderResult(options, send, fallbackResult, errorFallback
251
251
  send(fallbackResult);
252
252
  return;
253
253
  }
254
- Promise.resolve(getCurrentPubkey(options)).then((pubkey) => buildResult(pubkey)).then((result) => send(result)).catch((err) => sendProviderError(send, fallbackResult, errorFallback, err));
254
+ Promise.resolve(getCurrentPubkey(options)).then((pubkey) => buildResult(pubkey)).then((result) => send(result)).catch((err2) => sendProviderError(send, fallbackResult, errorFallback, err2));
255
255
  }
256
256
  function sendIdentityError(send, id, typeBase, error) {
257
257
  send({ type: `${typeBase}.error`, id, error });
258
258
  }
259
- function sendSignerError(send, id, typeBase, fallback, err) {
260
- sendIdentityError(send, id, typeBase, err?.message ?? fallback);
259
+ function sendSignerError(send, id, typeBase, fallback, err2) {
260
+ sendIdentityError(send, id, typeBase, err2?.message ?? fallback);
261
261
  }
262
262
  function handleGetPublicKey(options, id, send) {
263
263
  const signer = options.getSigner();
@@ -277,7 +277,7 @@ function handleGetPublicKey(options, id, send) {
277
277
  pubkey: pubkey ?? ""
278
278
  };
279
279
  send(result);
280
- }).catch((err) => sendSignerError(send, id, "identity.getPublicKey", "getPublicKey failed", err));
280
+ }).catch((err2) => sendSignerError(send, id, "identity.getPublicKey", "getPublicKey failed", err2));
281
281
  }
282
282
  function handleGetRelays(options, id, send) {
283
283
  const signer = options.getSigner();
@@ -292,7 +292,7 @@ function handleGetRelays(options, id, send) {
292
292
  relays
293
293
  };
294
294
  send(result);
295
- }).catch((err) => sendSignerError(send, id, "identity.getRelays", "getRelays failed", err));
295
+ }).catch((err2) => sendSignerError(send, id, "identity.getRelays", "getRelays failed", err2));
296
296
  }
297
297
  function handleReadProvider(options, id, message, send) {
298
298
  switch (message.type) {
@@ -453,7 +453,7 @@ function createRelayPoolService(options) {
453
453
  return;
454
454
  }
455
455
  const relayHint = typeof relayMessage.relay === "string" && relayMessage.relay.length > 0 ? relayMessage.relay : void 0;
456
- const relayUrls = relayHint ? [relayHint] : options.selectRelayTier(filters);
456
+ const relayUrls2 = relayHint ? [relayHint] : options.selectRelayTier(filters);
457
457
  let eoseSent = false;
458
458
  const eoseTimer = setTimeout(() => {
459
459
  if (!eoseSent) {
@@ -471,7 +471,7 @@ function createRelayPoolService(options) {
471
471
  return;
472
472
  }
473
473
  send({ type: "relay.event", subId, event: item });
474
- }, relayUrls);
474
+ }, relayUrls2);
475
475
  tracked.set(subKey, { handle, eoseTimer });
476
476
  return;
477
477
  }
@@ -634,7 +634,7 @@ function createCoordinatedRelay(options) {
634
634
  }
635
635
  }, timeoutMs);
636
636
  const relayHint = typeof relayMessage.relay === "string" && relayMessage.relay.length > 0 ? relayMessage.relay : void 0;
637
- const relayUrls = relayHint ? [relayHint] : options.relayPool.selectRelayTier(filters);
637
+ const relayUrls2 = relayHint ? [relayHint] : options.relayPool.selectRelayTier(filters);
638
638
  tracked.relayHandle = options.relayPool.subscribe(filters, (item) => {
639
639
  if (item === "EOSE") {
640
640
  clearTimeout(tracked.eoseTimer);
@@ -649,7 +649,7 @@ function createCoordinatedRelay(options) {
649
649
  } catch {
650
650
  }
651
651
  }
652
- }, relayUrls);
652
+ }, relayUrls2);
653
653
  }
654
654
  return;
655
655
  }
@@ -797,12 +797,12 @@ function createKeysService(options = {}) {
797
797
  unsubscribeHandles.set(m.action.id, unsubscribe);
798
798
  if (!bridgeWindowActions.has(windowId)) bridgeWindowActions.set(windowId, /* @__PURE__ */ new Set());
799
799
  bridgeWindowActions.get(windowId).add(m.action.id);
800
- } catch (err) {
800
+ } catch (err2) {
801
801
  const id = m.id ?? "";
802
802
  send({
803
803
  type: "keys.registerAction.error",
804
804
  id,
805
- error: `bridge subscribe failed: ${err.message}`
805
+ error: `bridge subscribe failed: ${err2.message}`
806
806
  });
807
807
  return;
808
808
  }
@@ -944,12 +944,12 @@ function createKeysService(options = {}) {
944
944
  });
945
945
  if (!windowActions.has(windowId)) windowActions.set(windowId, /* @__PURE__ */ new Set());
946
946
  windowActions.get(windowId).add(m.action.id);
947
- } catch (err) {
947
+ } catch (err2) {
948
948
  const id = m.id ?? "";
949
949
  send({
950
950
  type: "keys.registerAction.error",
951
951
  id,
952
- error: `invalid chord: ${err.message}`
952
+ error: `invalid chord: ${err2.message}`
953
953
  });
954
954
  return;
955
955
  }
@@ -1629,6 +1629,9 @@ function arrayBufferToBase64(buf) {
1629
1629
  }
1630
1630
  return btoa(binary);
1631
1631
  }
1632
+ var DEFAULT_RESOURCE_INFO = {
1633
+ schemes: [{ scheme: "https", enabled: true }]
1634
+ };
1632
1635
  function assertResourceOptions(options) {
1633
1636
  if (typeof options?.fetch !== "function" || typeof options?.isOriginGranted !== "function" || typeof options?.getConnectGrants !== "function" || typeof options?.resolveIdentity !== "function") {
1634
1637
  throw new Error(
@@ -1700,6 +1703,35 @@ function requestIdFromMessage(message) {
1700
1703
  if (typeof message.requestId === "string" && message.requestId.length > 0) return message.requestId;
1701
1704
  return null;
1702
1705
  }
1706
+ function normalizeResourceInfo(info) {
1707
+ return {
1708
+ schemes: Array.isArray(info.schemes) ? info.schemes.filter((scheme) => typeof scheme?.scheme === "string").map((scheme) => ({ scheme: scheme.scheme, enabled: scheme.enabled !== false })) : [],
1709
+ ...typeof info.maxBytes === "number" ? { maxBytes: info.maxBytes } : {},
1710
+ ...typeof info.maxUrls === "number" ? { maxUrls: info.maxUrls } : {}
1711
+ };
1712
+ }
1713
+ async function resolveResourceInfo(options, windowId) {
1714
+ const configured = options.resourceInfo ?? DEFAULT_RESOURCE_INFO;
1715
+ const info = typeof configured === "function" ? await configured({ windowId, identity: options.resolveIdentity(windowId) }) : configured;
1716
+ return normalizeResourceInfo(info);
1717
+ }
1718
+ async function handleInfo(options, windowId, requestId, send) {
1719
+ try {
1720
+ const info = await resolveResourceInfo(options, windowId);
1721
+ send({
1722
+ type: "resource.info.result",
1723
+ id: requestId,
1724
+ info
1725
+ });
1726
+ } catch (err2) {
1727
+ send({
1728
+ type: "resource.info.error",
1729
+ id: requestId,
1730
+ error: "unavailable",
1731
+ message: err2 instanceof Error ? err2.message : String(err2)
1732
+ });
1733
+ }
1734
+ }
1703
1735
  function resourceInvalidRequest(url, message) {
1704
1736
  return {
1705
1737
  ok: false,
@@ -1741,14 +1773,14 @@ async function fetchResourceItem(options, identity, url, init, signal) {
1741
1773
  headers,
1742
1774
  bodyBase64: arrayBufferToBase64(buffer)
1743
1775
  };
1744
- } catch (err) {
1745
- const isAbort = signal.aborted || err instanceof Error && (err.name === "AbortError" || err.name === "DOMException");
1776
+ } catch (err2) {
1777
+ const isAbort = signal.aborted || err2 instanceof Error && (err2.name === "AbortError" || err2.name === "DOMException");
1746
1778
  return {
1747
1779
  ok: false,
1748
1780
  url,
1749
1781
  error: isAbort ? "timeout" : "network-error",
1750
1782
  code: isAbort ? "canceled" : "network-error",
1751
- message: err instanceof Error ? err.message : String(err)
1783
+ message: err2 instanceof Error ? err2.message : String(err2)
1752
1784
  };
1753
1785
  }
1754
1786
  }
@@ -1866,6 +1898,14 @@ function createResourceService(options) {
1866
1898
  });
1867
1899
  return;
1868
1900
  }
1901
+ case "resource.info": {
1902
+ const m = message;
1903
+ const requestId = requestIdFromMessage(m);
1904
+ if (!requestId) return;
1905
+ handleInfo(options, windowId, requestId, send).catch(() => {
1906
+ });
1907
+ return;
1908
+ }
1869
1909
  case "resource.bytesMany": {
1870
1910
  const m = message;
1871
1911
  const requestId = requestIdFromMessage(m);
@@ -1896,7 +1936,7 @@ var OUTBOX_SERVICE_VERSION = "1.0.0";
1896
1936
  var OUTBOX_DESCRIPTOR = {
1897
1937
  name: "outbox",
1898
1938
  version: OUTBOX_SERVICE_VERSION,
1899
- description: "NAP-OUTBOX outbox-aware relay routing \u2014 query/subscribe/publish/resolveRelays"
1939
+ description: "NAP-OUTBOX outbox-aware relay routing \u2014 getEvent/query/subscribe/publish/resolveRelays"
1900
1940
  };
1901
1941
  function normalizeFilters(raw) {
1902
1942
  if (Array.isArray(raw)) {
@@ -1906,6 +1946,59 @@ function normalizeFilters(raw) {
1906
1946
  if (typeof raw === "object" && raw !== null) return [raw];
1907
1947
  return null;
1908
1948
  }
1949
+ function buildEventQuery(eventId, eventOptions) {
1950
+ const filter = { ids: [eventId] };
1951
+ const queryOptions = { limit: 1 };
1952
+ if (typeof eventOptions?.author === "string" && eventOptions.author.length > 0) {
1953
+ filter.authors = [eventOptions.author];
1954
+ queryOptions.authors = [eventOptions.author];
1955
+ }
1956
+ if (Array.isArray(eventOptions?.relays)) queryOptions.relays = eventOptions.relays;
1957
+ if (eventOptions?.strategy !== void 0) queryOptions.strategy = eventOptions.strategy;
1958
+ if (eventOptions?.timeoutMs !== void 0) queryOptions.timeoutMs = eventOptions.timeoutMs;
1959
+ return { filter, queryOptions };
1960
+ }
1961
+ function eventResultFromQuery(eventId, result) {
1962
+ const event = result.events.find((candidate) => candidate.id === eventId);
1963
+ const eventResult = {
1964
+ relays: event ? result.relays[eventId] ?? [] : []
1965
+ };
1966
+ if (event) eventResult.event = event;
1967
+ if (!event) eventResult.error = result.error ?? "not found";
1968
+ else if (result.error !== void 0) eventResult.error = result.error;
1969
+ if (result.incomplete !== void 0) eventResult.incomplete = result.incomplete;
1970
+ return eventResult;
1971
+ }
1972
+ function fallbackGetEvent(router, eventId, eventOptions) {
1973
+ const { filter, queryOptions } = buildEventQuery(eventId, eventOptions);
1974
+ return router.query([filter], queryOptions).then((result) => eventResultFromQuery(eventId, result));
1975
+ }
1976
+ function sendEventResult(id, eventId, result, send) {
1977
+ if (result.event && result.event.id !== eventId) {
1978
+ send({ type: "outbox.getEvent.result", id, relays: [], error: "not found" });
1979
+ return;
1980
+ }
1981
+ send({
1982
+ type: "outbox.getEvent.result",
1983
+ id,
1984
+ ...result.event === void 0 ? {} : { event: result.event },
1985
+ relays: result.relays,
1986
+ ...result.incomplete === void 0 ? {} : { incomplete: result.incomplete },
1987
+ ...result.error === void 0 ? {} : { error: result.error }
1988
+ });
1989
+ }
1990
+ function handleGetEvent(router, msg, send) {
1991
+ const m = msg;
1992
+ const id = m.id ?? "";
1993
+ if (typeof m.eventId !== "string" || m.eventId.length === 0) {
1994
+ send({ type: "outbox.getEvent.result", id, relays: [], error: "invalid filter" });
1995
+ return;
1996
+ }
1997
+ const getEvent = router.getEvent ?? ((eventId, eventOptions) => fallbackGetEvent(router, eventId, eventOptions));
1998
+ void getEvent(m.eventId, m.options).then((result) => sendEventResult(id, m.eventId, result, send)).catch(
1999
+ (err2) => send({ type: "outbox.getEvent.result", id, relays: [], error: toErrorMessage(err2) })
2000
+ );
2001
+ }
1909
2002
  function createOutboxService(options) {
1910
2003
  if (!options || typeof options.router !== "object" || options.router === null) {
1911
2004
  throw new Error("createOutboxService: options.router is required");
@@ -1930,7 +2023,7 @@ function createOutboxService(options) {
1930
2023
  ...result.error === void 0 ? {} : { error: result.error }
1931
2024
  })
1932
2025
  ).catch(
1933
- (err) => send({ type: "outbox.query.result", id, events: [], relays: {}, error: toErrorMessage(err) })
2026
+ (err2) => send({ type: "outbox.query.result", id, events: [], relays: {}, error: toErrorMessage(err2) })
1934
2027
  );
1935
2028
  }
1936
2029
  function handleSubscribe(windowId, msg, send) {
@@ -1982,7 +2075,7 @@ function createOutboxService(options) {
1982
2075
  ...result.error === void 0 ? {} : { error: result.error }
1983
2076
  })
1984
2077
  ).catch(
1985
- (err) => send({ type: "outbox.publish.result", id, ok: false, error: toErrorMessage(err) })
2078
+ (err2) => send({ type: "outbox.publish.result", id, ok: false, error: toErrorMessage(err2) })
1986
2079
  );
1987
2080
  }
1988
2081
  function handleResolveRelays(msg, send) {
@@ -1993,13 +2086,16 @@ function createOutboxService(options) {
1993
2086
  return;
1994
2087
  }
1995
2088
  void router.resolveRelays(m.target).then((plan) => send({ type: "outbox.resolveRelays.result", id, plan })).catch(
1996
- (err) => send({ type: "outbox.resolveRelays.result", id, error: toErrorMessage(err) })
2089
+ (err2) => send({ type: "outbox.resolveRelays.result", id, error: toErrorMessage(err2) })
1997
2090
  );
1998
2091
  }
1999
2092
  return {
2000
2093
  descriptor: OUTBOX_DESCRIPTOR,
2001
2094
  handleMessage(windowId, message, send) {
2002
2095
  switch (message.type) {
2096
+ case "outbox.getEvent":
2097
+ handleGetEvent(router, message, send);
2098
+ return;
2003
2099
  case "outbox.query":
2004
2100
  handleQuery(message, send);
2005
2101
  return;
@@ -2030,9 +2126,9 @@ function createOutboxService(options) {
2030
2126
  }
2031
2127
  };
2032
2128
  }
2033
- function toErrorMessage(err) {
2034
- if (err instanceof Error) return err.message;
2035
- if (typeof err === "string") return err;
2129
+ function toErrorMessage(err2) {
2130
+ if (err2 instanceof Error) return err2.message;
2131
+ if (typeof err2 === "string") return err2;
2036
2132
  return "outbox request failed";
2037
2133
  }
2038
2134
 
@@ -2115,7 +2211,7 @@ function buildCollectResult(collector, timedOut) {
2115
2211
  for (const event of events) relayObj[event.id] = [...collector.relayMap.get(event.id) ?? []];
2116
2212
  return { events, relayMap: relayObj, incomplete: timedOut };
2117
2213
  }
2118
- function collectFromRelays(ctx, filters, relayUrls, timeoutMs) {
2214
+ function collectFromRelays(ctx, filters, relayUrls2, timeoutMs) {
2119
2215
  return new Promise((resolve) => {
2120
2216
  const collector = { seen: /* @__PURE__ */ new Map(), relayMap: /* @__PURE__ */ new Map(), verifications: [] };
2121
2217
  const handles = [];
@@ -2138,19 +2234,19 @@ function collectFromRelays(ctx, filters, relayUrls, timeoutMs) {
2138
2234
  timedOut = true;
2139
2235
  finalize();
2140
2236
  }, timeoutMs);
2141
- for (const relayUrl of relayUrls) {
2237
+ for (const relayUrl of relayUrls2) {
2142
2238
  handles.push(relayPoolSubscribe(ctx, filters, relayUrl, (item) => {
2143
2239
  if (finished) return;
2144
2240
  if (item === "EOSE") {
2145
2241
  eoseCount += 1;
2146
- if (eoseCount >= relayUrls.length) finalize();
2242
+ if (eoseCount >= relayUrls2.length) finalize();
2147
2243
  return;
2148
2244
  }
2149
2245
  recordRelay(collector, item.id, relayUrl);
2150
2246
  admitEvent(ctx, collector, item);
2151
2247
  }));
2152
2248
  }
2153
- if (relayUrls.length === 0) finalize();
2249
+ if (relayUrls2.length === 0) finalize();
2154
2250
  });
2155
2251
  }
2156
2252
  function relayPoolSubscribe(ctx, filters, relayUrl, cb) {
@@ -2177,6 +2273,30 @@ async function queryImpl(ctx, filters, options) {
2177
2273
  if (incomplete) result.incomplete = true;
2178
2274
  return result;
2179
2275
  }
2276
+ async function getEventImpl(ctx, eventId, options) {
2277
+ if (typeof eventId !== "string" || eventId.length === 0) {
2278
+ return { relays: [], error: "invalid filter" };
2279
+ }
2280
+ const filter = { ids: [eventId] };
2281
+ const queryOptions = { limit: 1 };
2282
+ if (typeof options?.author === "string" && options.author.length > 0) {
2283
+ filter.authors = [options.author];
2284
+ queryOptions.authors = [options.author];
2285
+ }
2286
+ if (Array.isArray(options?.relays)) queryOptions.relays = options.relays;
2287
+ if (options?.strategy !== void 0) queryOptions.strategy = options.strategy;
2288
+ if (options?.timeoutMs !== void 0) queryOptions.timeoutMs = options.timeoutMs;
2289
+ const queryResult = await queryImpl(ctx, [filter], queryOptions);
2290
+ const event = queryResult.events.find((candidate) => candidate.id === eventId);
2291
+ const result = {
2292
+ relays: event ? queryResult.relays[eventId] ?? [] : []
2293
+ };
2294
+ if (event) result.event = event;
2295
+ if (!event) result.error = queryResult.error ?? "not found";
2296
+ else if (queryResult.error !== void 0) result.error = queryResult.error;
2297
+ if (queryResult.incomplete !== void 0) result.incomplete = queryResult.incomplete;
2298
+ return result;
2299
+ }
2180
2300
  function closeLiveSub(sub) {
2181
2301
  for (const handle of sub.handles) {
2182
2302
  try {
@@ -2223,8 +2343,8 @@ function startSubscription(ctx, filters, options, sink) {
2223
2343
  }
2224
2344
  sub.relayCount = plan.relays.length;
2225
2345
  for (const relayUrl of plan.relays) attachLiveRelay(ctx, sub, filters, relayUrl, live, sink);
2226
- }).catch((err) => {
2227
- if (!sub.closed) sink.closed(err instanceof Error ? err.message : "subscribe failed");
2346
+ }).catch((err2) => {
2347
+ if (!sub.closed) sink.closed(err2 instanceof Error ? err2.message : "subscribe failed");
2228
2348
  });
2229
2349
  return {
2230
2350
  close() {
@@ -2252,16 +2372,16 @@ async function publishImpl(ctx, template, options) {
2252
2372
  let signed;
2253
2373
  try {
2254
2374
  signed = await ctx.signEvent(template);
2255
- } catch (err) {
2256
- return { ok: false, error: err instanceof Error ? err.message : "sign failed" };
2375
+ } catch (err2) {
2376
+ return { ok: false, error: err2 instanceof Error ? err2.message : "sign failed" };
2257
2377
  }
2258
- const relayUrls = await resolvePublishTargets(ctx, signed, options);
2259
- if (relayUrls.length === 0) return { ok: false, event: signed, eventId: signed.id, error: "relay list unavailable" };
2378
+ const relayUrls2 = await resolvePublishTargets(ctx, signed, options);
2379
+ if (relayUrls2.length === 0) return { ok: false, event: signed, eventId: signed.id, error: "relay list unavailable" };
2260
2380
  let relays;
2261
2381
  try {
2262
- relays = normalizePublishResult(await ctx.relayPool.publish(signed, relayUrls), relayUrls);
2263
- } catch (err) {
2264
- return { ok: false, event: signed, eventId: signed.id, error: err instanceof Error ? err.message : "publish failed" };
2382
+ relays = normalizePublishResult(await ctx.relayPool.publish(signed, relayUrls2), relayUrls2);
2383
+ } catch (err2) {
2384
+ return { ok: false, event: signed, eventId: signed.id, error: err2 instanceof Error ? err2.message : "publish failed" };
2265
2385
  }
2266
2386
  const ok = Object.values(relays).some(Boolean);
2267
2387
  const result = { ok, event: signed, eventId: signed.id, relays };
@@ -2296,6 +2416,7 @@ function createRelayPoolOutboxRouter(options) {
2296
2416
  }
2297
2417
  };
2298
2418
  return {
2419
+ getEvent: (eventId, eventOptions) => getEventImpl(ctx, eventId, eventOptions),
2299
2420
  query: (filters, queryOptions) => queryImpl(ctx, filters, queryOptions),
2300
2421
  subscribe: (filters, subscribeOptions, sink) => startSubscription(ctx, filters, subscribeOptions, sink),
2301
2422
  publish: (template, publishOptions) => publishImpl(ctx, template, publishOptions),
@@ -2305,12 +2426,12 @@ function createRelayPoolOutboxRouter(options) {
2305
2426
  }
2306
2427
  };
2307
2428
  }
2308
- function normalizePublishResult(res, relayUrls) {
2429
+ function normalizePublishResult(res, relayUrls2) {
2309
2430
  const out = {};
2310
2431
  if (res && typeof res === "object") {
2311
- for (const url of relayUrls) out[url] = res[url] ?? false;
2432
+ for (const url of relayUrls2) out[url] = res[url] ?? false;
2312
2433
  } else {
2313
- for (const url of relayUrls) out[url] = true;
2434
+ for (const url of relayUrls2) out[url] = true;
2314
2435
  }
2315
2436
  return out;
2316
2437
  }
@@ -2322,14 +2443,38 @@ var UPLOAD_DESCRIPTOR = {
2322
2443
  version: UPLOAD_SERVICE_VERSION,
2323
2444
  description: "NAP-UPLOAD shell-mediated file/blob upload \u2014 upload/status with progress pushes"
2324
2445
  };
2446
+ var DEFAULT_UPLOAD_INFO = {
2447
+ rails: []
2448
+ };
2325
2449
  function createUploadService(options) {
2326
2450
  if (!options || typeof options.uploader !== "object" || options.uploader === null) {
2327
2451
  throw new Error("createUploadService: options.uploader is required");
2328
2452
  }
2329
2453
  const { uploader } = options;
2454
+ const uploadInfo = options.uploadInfo ?? DEFAULT_UPLOAD_INFO;
2330
2455
  const generateId2 = options.generateId ?? (() => crypto.randomUUID());
2331
2456
  const now = options.now ?? (() => Date.now());
2332
2457
  const entries = /* @__PURE__ */ new Map();
2458
+ function normalizeUploadInfo(info) {
2459
+ return {
2460
+ rails: Array.isArray(info.rails) ? info.rails.filter((rail) => typeof rail?.rail === "string").map((rail) => ({
2461
+ rail: rail.rail,
2462
+ enabled: rail.enabled !== false,
2463
+ ...Array.isArray(rail.returns) ? { returns: rail.returns.filter((value) => typeof value === "string") } : {}
2464
+ })) : [],
2465
+ ...typeof info.maxBytes === "number" ? { maxBytes: info.maxBytes } : {},
2466
+ ...Array.isArray(info.mimeTypes) ? { mimeTypes: info.mimeTypes.filter((value) => typeof value === "string") } : {}
2467
+ };
2468
+ }
2469
+ function handleInfo2(windowId, msg, send) {
2470
+ const m = msg;
2471
+ const id = m.id ?? "";
2472
+ void Promise.resolve().then(() => typeof uploadInfo === "function" ? uploadInfo({ windowId }) : uploadInfo).then((info) => {
2473
+ send({ type: "upload.info.result", id, info: normalizeUploadInfo(info) });
2474
+ }).catch((err2) => {
2475
+ send({ type: "upload.info.result", id, error: toErrorMessage2(err2) });
2476
+ });
2477
+ }
2333
2478
  function handleUpload(windowId, msg, send) {
2334
2479
  const m = msg;
2335
2480
  const id = m.id ?? "";
@@ -2356,9 +2501,9 @@ function createUploadService(options) {
2356
2501
  const entry = entries.get(key);
2357
2502
  if (entry) entry.status = { ...stamped, updatedAt: now() };
2358
2503
  send({ type: "upload.upload.result", id, result: stamped });
2359
- }).catch((err) => {
2504
+ }).catch((err2) => {
2360
2505
  entries.delete(key);
2361
- send({ type: "upload.upload.result", id, error: toErrorMessage2(err) });
2506
+ send({ type: "upload.upload.result", id, error: toErrorMessage2(err2) });
2362
2507
  });
2363
2508
  }
2364
2509
  function handleStatus(windowId, msg, send) {
@@ -2380,7 +2525,7 @@ function createUploadService(options) {
2380
2525
  status ? { type: "upload.status.result", id, status } : { type: "upload.status.result", id, error: "unknown upload" }
2381
2526
  )
2382
2527
  ).catch(
2383
- (err) => send({ type: "upload.status.result", id, error: toErrorMessage2(err) })
2528
+ (err2) => send({ type: "upload.status.result", id, error: toErrorMessage2(err2) })
2384
2529
  );
2385
2530
  return;
2386
2531
  }
@@ -2390,6 +2535,9 @@ function createUploadService(options) {
2390
2535
  descriptor: UPLOAD_DESCRIPTOR,
2391
2536
  handleMessage(windowId, message, send) {
2392
2537
  switch (message.type) {
2538
+ case "upload.info":
2539
+ handleInfo2(windowId, message, send);
2540
+ return;
2393
2541
  case "upload.upload":
2394
2542
  handleUpload(windowId, message, send);
2395
2543
  return;
@@ -2411,9 +2559,9 @@ function createUploadService(options) {
2411
2559
  }
2412
2560
  };
2413
2561
  }
2414
- function toErrorMessage2(err) {
2415
- if (err instanceof Error) return err.message;
2416
- if (typeof err === "string") return err;
2562
+ function toErrorMessage2(err2) {
2563
+ if (err2 instanceof Error) return err2.message;
2564
+ if (typeof err2 === "string") return err2;
2417
2565
  return "upload request failed";
2418
2566
  }
2419
2567
 
@@ -2445,8 +2593,8 @@ function createHttpUploader(options) {
2445
2593
  const sha256 = await digest(bytes);
2446
2594
  try {
2447
2595
  return rail === "nip96" ? await uploadNip96({ request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS }) : await uploadBlossom({ request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS });
2448
- } catch (err) {
2449
- return failed(ctx.uploadId, rail, toErrorMessage3(err), sha256);
2596
+ } catch (err2) {
2597
+ return failed(ctx.uploadId, rail, toErrorMessage3(err2), sha256);
2450
2598
  }
2451
2599
  }
2452
2600
  return { upload };
@@ -2581,9 +2729,9 @@ async function defaultDigestSha256(bytes) {
2581
2729
  const buf = await crypto.subtle.digest("SHA-256", bytesToArrayBuffer(bytes));
2582
2730
  return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
2583
2731
  }
2584
- function toErrorMessage3(err) {
2585
- if (err instanceof Error) return err.message;
2586
- if (typeof err === "string") return err;
2732
+ function toErrorMessage3(err2) {
2733
+ if (err2 instanceof Error) return err2.message;
2734
+ if (typeof err2 === "string") return err2;
2587
2735
  return "upload failed";
2588
2736
  }
2589
2737
 
@@ -2594,9 +2742,9 @@ var INTENT_DESCRIPTOR = {
2594
2742
  version: INTENT_SERVICE_VERSION,
2595
2743
  description: "NAP-INTENT archetype intent dispatch \u2014 invoke/available/handlers"
2596
2744
  };
2597
- function toErrorMessage4(err) {
2598
- if (err instanceof Error) return err.message;
2599
- if (typeof err === "string") return err;
2745
+ function toErrorMessage4(err2) {
2746
+ if (err2 instanceof Error) return err2.message;
2747
+ if (typeof err2 === "string") return err2;
2600
2748
  return "intent request failed";
2601
2749
  }
2602
2750
  function createIntentService(options) {
@@ -2614,11 +2762,11 @@ function createIntentService(options) {
2614
2762
  let pending;
2615
2763
  try {
2616
2764
  pending = Promise.resolve(call());
2617
- } catch (err) {
2618
- send({ type: resultType, id, error: toErrorMessage4(err) });
2765
+ } catch (err2) {
2766
+ send({ type: resultType, id, error: toErrorMessage4(err2) });
2619
2767
  return;
2620
2768
  }
2621
- pending.then((value) => send(onValue(value))).catch((err) => send({ type: resultType, id, error: toErrorMessage4(err) }));
2769
+ pending.then((value) => send(onValue(value))).catch((err2) => send({ type: resultType, id, error: toErrorMessage4(err2) }));
2622
2770
  }
2623
2771
  function handleInvoke(windowId, msg, send) {
2624
2772
  const m = msg;
@@ -2867,7 +3015,7 @@ function createCvmService(options) {
2867
3015
  const m = msg;
2868
3016
  const id = m.id ?? "";
2869
3017
  void transport.discover(m.query).then((servers) => send({ type: "cvm.discover.result", id, servers })).catch(
2870
- (err) => send({ type: "cvm.discover.result", id, servers: [], error: toErrorMessage5(err) })
3018
+ (err2) => send({ type: "cvm.discover.result", id, servers: [], error: toErrorMessage5(err2) })
2871
3019
  );
2872
3020
  }
2873
3021
  function handleRequest(windowId, msg, send) {
@@ -2883,7 +3031,7 @@ function createCvmService(options) {
2883
3031
  }
2884
3032
  openSession(windowId, m.server, send);
2885
3033
  void transport.request(m.server, m.message, m.options).then((message) => send({ type: "cvm.request.result", id, message })).catch(
2886
- (err) => send({ type: "cvm.request.result", id, error: toErrorMessage5(err) })
3034
+ (err2) => send({ type: "cvm.request.result", id, error: toErrorMessage5(err2) })
2887
3035
  );
2888
3036
  }
2889
3037
  function handleClose(windowId, msg, send) {
@@ -2894,7 +3042,7 @@ function createCvmService(options) {
2894
3042
  return;
2895
3043
  }
2896
3044
  closeSession(windowId, m.server.pubkey);
2897
- void transport.close(m.server).then(() => send({ type: "cvm.close.result", id })).catch((err) => send({ type: "cvm.close.result", id, error: toErrorMessage5(err) }));
3045
+ void transport.close(m.server).then(() => send({ type: "cvm.close.result", id })).catch((err2) => send({ type: "cvm.close.result", id, error: toErrorMessage5(err2) }));
2898
3046
  }
2899
3047
  return {
2900
3048
  descriptor: CVM_DESCRIPTOR,
@@ -2925,9 +3073,9 @@ function createCvmService(options) {
2925
3073
  }
2926
3074
  };
2927
3075
  }
2928
- function toErrorMessage5(err) {
2929
- if (err instanceof Error) return err.message;
2930
- if (typeof err === "string") return err;
3076
+ function toErrorMessage5(err2) {
3077
+ if (err2 instanceof Error) return err2.message;
3078
+ if (typeof err2 === "string") return err2;
2931
3079
  return "cvm request failed";
2932
3080
  }
2933
3081
 
@@ -3006,20 +3154,20 @@ var LISTS_DESCRIPTOR = {
3006
3154
  version: LISTS_SERVICE_VERSION,
3007
3155
  description: "NAP-LISTS reference handler for shell-mediated NIP-51 list mutations"
3008
3156
  };
3009
- function errorMessage(err, fallback) {
3010
- if (err instanceof Error) return err.message;
3011
- if (typeof err === "string") return err;
3157
+ function errorMessage(err2, fallback) {
3158
+ if (err2 instanceof Error) return err2.message;
3159
+ if (typeof err2 === "string") return err2;
3012
3160
  return fallback;
3013
3161
  }
3014
3162
  function settle(call, send, okFalse, onValue) {
3015
3163
  let pending;
3016
3164
  try {
3017
3165
  pending = Promise.resolve(call());
3018
- } catch (err) {
3019
- send(okFalse(errorMessage(err, "lists request failed")));
3166
+ } catch (err2) {
3167
+ send(okFalse(errorMessage(err2, "lists request failed")));
3020
3168
  return;
3021
3169
  }
3022
- pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage(err, "lists request failed"))));
3170
+ pending.then((value) => send(onValue(value))).catch((err2) => send(okFalse(errorMessage(err2, "lists request failed"))));
3023
3171
  }
3024
3172
  function unsupportedMutation(resultType, id) {
3025
3173
  return {
@@ -3088,20 +3236,20 @@ var SERIAL_DESCRIPTOR = {
3088
3236
  version: SERIAL_SERVICE_VERSION,
3089
3237
  description: "NAP-SERIAL reference handler for shell-mediated serial sessions"
3090
3238
  };
3091
- function errorMessage2(err, fallback) {
3092
- if (err instanceof Error) return err.message;
3093
- if (typeof err === "string") return err;
3239
+ function errorMessage2(err2, fallback) {
3240
+ if (err2 instanceof Error) return err2.message;
3241
+ if (typeof err2 === "string") return err2;
3094
3242
  return fallback;
3095
3243
  }
3096
3244
  function settle2(call, send, okFalse, onValue) {
3097
3245
  let pending;
3098
3246
  try {
3099
3247
  pending = Promise.resolve(call());
3100
- } catch (err) {
3101
- send(okFalse(errorMessage2(err, "serial request failed")));
3248
+ } catch (err2) {
3249
+ send(okFalse(errorMessage2(err2, "serial request failed")));
3102
3250
  return;
3103
3251
  }
3104
- pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage2(err, "serial request failed"))));
3252
+ pending.then((value) => send(onValue(value))).catch((err2) => send(okFalse(errorMessage2(err2, "serial request failed"))));
3105
3253
  }
3106
3254
  function unsupported(resultType, id) {
3107
3255
  return {
@@ -3177,20 +3325,20 @@ var BLE_DESCRIPTOR = {
3177
3325
  version: BLE_SERVICE_VERSION,
3178
3326
  description: "NAP-BLE reference handler for shell-mediated BLE/GATT sessions"
3179
3327
  };
3180
- function errorMessage3(err, fallback) {
3181
- if (err instanceof Error) return err.message;
3182
- if (typeof err === "string") return err;
3328
+ function errorMessage3(err2, fallback) {
3329
+ if (err2 instanceof Error) return err2.message;
3330
+ if (typeof err2 === "string") return err2;
3183
3331
  return fallback;
3184
3332
  }
3185
3333
  function settle3(call, send, okFalse, onValue) {
3186
3334
  let pending;
3187
3335
  try {
3188
3336
  pending = Promise.resolve(call());
3189
- } catch (err) {
3190
- send(okFalse(errorMessage3(err, "ble request failed")));
3337
+ } catch (err2) {
3338
+ send(okFalse(errorMessage3(err2, "ble request failed")));
3191
3339
  return;
3192
3340
  }
3193
- pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage3(err, "ble request failed"))));
3341
+ pending.then((value) => send(onValue(value))).catch((err2) => send(okFalse(errorMessage3(err2, "ble request failed"))));
3194
3342
  }
3195
3343
  function unsupported2(resultType, id) {
3196
3344
  return {
@@ -3199,12 +3347,20 @@ function unsupported2(resultType, id) {
3199
3347
  error: `${resultType.replace(".result", "")} unavailable`
3200
3348
  };
3201
3349
  }
3350
+ function createContext(windowId, send) {
3351
+ return {
3352
+ windowId,
3353
+ emit(event) {
3354
+ send({ type: "ble.event", event });
3355
+ }
3356
+ };
3357
+ }
3202
3358
  function createBleService(options = {}) {
3203
3359
  return {
3204
3360
  descriptor: BLE_DESCRIPTOR,
3205
3361
  handleMessage(windowId, message, send) {
3206
3362
  const id = message.id ?? "";
3207
- const context = { windowId };
3363
+ const context = createContext(windowId, send);
3208
3364
  if (message.type === "ble.open") {
3209
3365
  if (!options.open) {
3210
3366
  send(unsupported2("ble.open.result", id));
@@ -3322,20 +3478,20 @@ var WEBRTC_DESCRIPTOR = {
3322
3478
  version: WEBRTC_SERVICE_VERSION,
3323
3479
  description: "NAP-WEBRTC reference handler for shell-mediated WebRTC sessions"
3324
3480
  };
3325
- function errorMessage4(err, fallback) {
3326
- if (err instanceof Error) return err.message;
3327
- if (typeof err === "string") return err;
3481
+ function errorMessage4(err2, fallback) {
3482
+ if (err2 instanceof Error) return err2.message;
3483
+ if (typeof err2 === "string") return err2;
3328
3484
  return fallback;
3329
3485
  }
3330
3486
  function settle4(call, send, okFalse, onValue) {
3331
3487
  let pending;
3332
3488
  try {
3333
3489
  pending = Promise.resolve(call());
3334
- } catch (err) {
3335
- send(okFalse(errorMessage4(err, "webrtc request failed")));
3490
+ } catch (err2) {
3491
+ send(okFalse(errorMessage4(err2, "webrtc request failed")));
3336
3492
  return;
3337
3493
  }
3338
- pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage4(err, "webrtc request failed"))));
3494
+ pending.then((value) => send(onValue(value))).catch((err2) => send(okFalse(errorMessage4(err2, "webrtc request failed"))));
3339
3495
  }
3340
3496
  function unsupported3(resultType, id) {
3341
3497
  return {
@@ -3344,7 +3500,7 @@ function unsupported3(resultType, id) {
3344
3500
  error: `${resultType.replace(".result", "")} unavailable`
3345
3501
  };
3346
3502
  }
3347
- function createContext(windowId, send) {
3503
+ function createContext2(windowId, send) {
3348
3504
  return {
3349
3505
  windowId,
3350
3506
  emit(event) {
@@ -3357,7 +3513,7 @@ function createWebrtcService(options = {}) {
3357
3513
  descriptor: WEBRTC_DESCRIPTOR,
3358
3514
  handleMessage(windowId, message, send) {
3359
3515
  const id = message.id ?? "";
3360
- const context = createContext(windowId, send);
3516
+ const context = createContext2(windowId, send);
3361
3517
  if (message.type === "webrtc.open") {
3362
3518
  if (!options.open) {
3363
3519
  send(unsupported3("webrtc.open.result", id));
@@ -3429,9 +3585,9 @@ var COMMON_DESCRIPTOR = {
3429
3585
  version: COMMON_SERVICE_VERSION,
3430
3586
  description: "NAP-COMMON reference handler for shell-mediated social helpers"
3431
3587
  };
3432
- function errorMessage5(err, fallback) {
3433
- if (err instanceof Error) return err.message;
3434
- if (typeof err === "string") return err;
3588
+ function errorMessage5(err2, fallback) {
3589
+ if (err2 instanceof Error) return err2.message;
3590
+ if (typeof err2 === "string") return err2;
3435
3591
  return fallback;
3436
3592
  }
3437
3593
  function encodeNip19(input) {
@@ -3460,8 +3616,8 @@ function encodeNip19(input) {
3460
3616
  default:
3461
3617
  return { ok: false, error: "unsupported NIP-19 type" };
3462
3618
  }
3463
- } catch (err) {
3464
- return { ok: false, error: errorMessage5(err, "NIP-19 encode failed") };
3619
+ } catch (err2) {
3620
+ return { ok: false, error: errorMessage5(err2, "NIP-19 encode failed") };
3465
3621
  }
3466
3622
  }
3467
3623
  function decodeNip19Value(value) {
@@ -3494,9 +3650,9 @@ function decodeNip19Value(value) {
3494
3650
  default:
3495
3651
  return { ok: false, error: "unsupported NIP-19 type" };
3496
3652
  }
3497
- } catch (err) {
3653
+ } catch (err2) {
3498
3654
  if (value.startsWith("nrelay1")) return decodeNrelay(value);
3499
- return { ok: false, error: errorMessage5(err, "NIP-19 decode failed") };
3655
+ return { ok: false, error: errorMessage5(err2, "NIP-19 decode failed") };
3500
3656
  }
3501
3657
  }
3502
3658
  function decodeNrelay(value) {
@@ -3512,8 +3668,8 @@ function decodeNrelay(value) {
3512
3668
  const payloadWords = words.slice(0, -6);
3513
3669
  const bytes = convertBits(payloadWords, 5, 8, false);
3514
3670
  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") };
3671
+ } catch (err2) {
3672
+ return { ok: false, error: errorMessage5(err2, "invalid nrelay value") };
3517
3673
  }
3518
3674
  }
3519
3675
  function convertBits(data, fromBits, toBits, pad) {
@@ -3542,11 +3698,11 @@ function settle5(call, send, okFalse, onValue) {
3542
3698
  let pending;
3543
3699
  try {
3544
3700
  pending = Promise.resolve(call());
3545
- } catch (err) {
3546
- send(okFalse(errorMessage5(err, "common request failed")));
3701
+ } catch (err2) {
3702
+ send(okFalse(errorMessage5(err2, "common request failed")));
3547
3703
  return;
3548
3704
  }
3549
- pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage5(err, "common request failed"))));
3705
+ pending.then((value) => send(onValue(value))).catch((err2) => send(okFalse(errorMessage5(err2, "common request failed"))));
3550
3706
  }
3551
3707
  function createCommonService(options = {}) {
3552
3708
  return {
@@ -3653,7 +3809,650 @@ function settleAction(action, pubkeys, context, send, resultType, id, unavailabl
3653
3809
  (result) => ({ type: resultType, id, ...result })
3654
3810
  );
3655
3811
  }
3812
+
3813
+ // src/dm-service.ts
3814
+ var DM_DESCRIPTOR = {
3815
+ name: "dm",
3816
+ version: "1.0.0",
3817
+ description: "NAP-DM runtime-mediated direct messages"
3818
+ };
3819
+ function err(error, fallback = "dm request failed") {
3820
+ if (error instanceof Error) return error.message;
3821
+ if (typeof error === "string") return error;
3822
+ return fallback;
3823
+ }
3824
+ function createDmService(options) {
3825
+ if (!options || typeof options.adapter !== "object" || options.adapter === null) {
3826
+ throw new Error("createDmService: options.adapter is required");
3827
+ }
3828
+ const { adapter } = options;
3829
+ const ownedSubscriptions = /* @__PURE__ */ new Map();
3830
+ function remember(windowId, subscriptionId) {
3831
+ let subs = ownedSubscriptions.get(windowId);
3832
+ if (!subs) {
3833
+ subs = /* @__PURE__ */ new Set();
3834
+ ownedSubscriptions.set(windowId, subs);
3835
+ }
3836
+ subs.add(subscriptionId);
3837
+ }
3838
+ function forget(windowId, subscriptionId) {
3839
+ const subs = ownedSubscriptions.get(windowId);
3840
+ if (!subs) return;
3841
+ subs.delete(subscriptionId);
3842
+ if (subs.size === 0) ownedSubscriptions.delete(windowId);
3843
+ }
3844
+ return {
3845
+ descriptor: DM_DESCRIPTOR,
3846
+ handleMessage(windowId, message, send) {
3847
+ const id = message.id;
3848
+ const requestId = typeof id === "string" ? id : "";
3849
+ switch (message.type) {
3850
+ case "dm.status":
3851
+ void Promise.resolve(adapter.status()).then((status) => send({ type: "dm.status.result", id: requestId, ...status })).catch((error) => send({ type: "dm.status.result", id: requestId, error: err(error) }));
3852
+ return;
3853
+ case "dm.conversations":
3854
+ void Promise.resolve(adapter.conversations(message)).then((page) => send({ type: "dm.conversations.result", id: requestId, ...page })).catch(
3855
+ (error) => send({ type: "dm.conversations.result", id: requestId, conversations: [], error: err(error) })
3856
+ );
3857
+ return;
3858
+ case "dm.messages":
3859
+ void Promise.resolve(adapter.messages(message)).then((page) => send({ type: "dm.messages.result", id: requestId, ...page })).catch(
3860
+ (error) => send({ type: "dm.messages.result", id: requestId, messages: [], error: err(error) })
3861
+ );
3862
+ return;
3863
+ case "dm.send":
3864
+ void adapter.send(message).then((result) => send({ type: "dm.send.result", id: requestId, ...result })).catch((error) => send({ type: "dm.send.result", id: requestId, error: err(error) }));
3865
+ return;
3866
+ case "dm.subscribe": {
3867
+ let activeSubscriptionId = "";
3868
+ void Promise.resolve(
3869
+ adapter.subscribe(message, (dmMessage) => {
3870
+ if (activeSubscriptionId.length > 0) {
3871
+ send({ type: "dm.message", subscriptionId: activeSubscriptionId, message: dmMessage });
3872
+ }
3873
+ })
3874
+ ).then((subscription) => {
3875
+ remember(windowId, subscription.subscriptionId);
3876
+ activeSubscriptionId = subscription.subscriptionId;
3877
+ send({ type: "dm.subscribe.result", id: requestId, ...subscription });
3878
+ }).catch((error) => {
3879
+ send({ type: "dm.subscribe.result", id: requestId, error: err(error) });
3880
+ });
3881
+ return;
3882
+ }
3883
+ case "dm.unsubscribe": {
3884
+ const subId = message.subscriptionId;
3885
+ if (typeof subId !== "string" || subId.length === 0) {
3886
+ send({ type: "dm.unsubscribe.result", id: requestId, ok: false, error: "subscription not found" });
3887
+ return;
3888
+ }
3889
+ void Promise.resolve(adapter.unsubscribe(subId)).then((ok) => {
3890
+ forget(windowId, subId);
3891
+ send({ type: "dm.unsubscribe.result", id: requestId, ...ok });
3892
+ }).catch((error) => send({ type: "dm.unsubscribe.result", id: requestId, ok: false, error: err(error) }));
3893
+ return;
3894
+ }
3895
+ default:
3896
+ return;
3897
+ }
3898
+ },
3899
+ onWindowDestroyed(windowId) {
3900
+ const subs = ownedSubscriptions.get(windowId);
3901
+ if (!subs) return;
3902
+ for (const subId of subs) void Promise.resolve(adapter.unsubscribe(subId)).catch(() => void 0);
3903
+ ownedSubscriptions.delete(windowId);
3904
+ },
3905
+ dispose() {
3906
+ adapter.close?.();
3907
+ ownedSubscriptions.clear();
3908
+ }
3909
+ };
3910
+ }
3911
+
3912
+ // src/dm-nip17-adapter.ts
3913
+ import { getPublicKey } from "nostr-tools/pure";
3914
+ import * as nip17 from "nostr-tools/nip17";
3915
+
3916
+ // src/dm-memory-store.ts
3917
+ var DmMemoryStore = class {
3918
+ messagesByConversation = /* @__PURE__ */ new Map();
3919
+ participantsByConversation = /* @__PURE__ */ new Map();
3920
+ conversationIdFor(participants) {
3921
+ return `direct:${[...new Set(participants)].sort().join(",")}`;
3922
+ }
3923
+ upsertMessage(message, participants) {
3924
+ const peers = [...new Set(participants)].sort().map((pubkey) => ({ pubkey }));
3925
+ this.participantsByConversation.set(message.conversationId, peers);
3926
+ const list = this.messagesByConversation.get(message.conversationId) ?? [];
3927
+ const existing = list.findIndex((entry) => entry.id === message.id);
3928
+ if (existing >= 0) list[existing] = message;
3929
+ else list.push(message);
3930
+ list.sort((a, b) => a.createdAt - b.createdAt);
3931
+ this.messagesByConversation.set(message.conversationId, list);
3932
+ return message;
3933
+ }
3934
+ conversations(query = {}) {
3935
+ const offset = query.cursor ? Number.parseInt(query.cursor, 10) || 0 : 0;
3936
+ const limit = query.limit ?? 50;
3937
+ const conversations = [...this.messagesByConversation.entries()].map(([id, messages]) => {
3938
+ const latest = messages[messages.length - 1];
3939
+ return {
3940
+ id,
3941
+ kind: id.startsWith("group:") ? "group" : "direct",
3942
+ participants: this.participantsByConversation.get(id) ?? [],
3943
+ unread: messages.filter((message) => message.status === "received").length,
3944
+ ...latest ? { updatedAt: latest.createdAt } : {}
3945
+ };
3946
+ }).sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
3947
+ const page = conversations.slice(offset, offset + limit);
3948
+ const next = offset + page.length < conversations.length ? String(offset + page.length) : void 0;
3949
+ return { conversations: page, ...next ? { cursor: next } : {} };
3950
+ }
3951
+ messages(query) {
3952
+ const offset = query.cursor ? Number.parseInt(query.cursor, 10) || 0 : 0;
3953
+ const limit = query.limit ?? 100;
3954
+ const all = this.messagesByConversation.get(query.conversationId) ?? [];
3955
+ const page = all.slice(offset, offset + limit);
3956
+ const next = offset + page.length < all.length ? String(offset + page.length) : void 0;
3957
+ return { messages: page, ...next ? { cursor: next } : {} };
3958
+ }
3959
+ };
3960
+
3961
+ // src/dm-nip17-adapter.ts
3962
+ var NIP17_GIFT_WRAP_KIND = 1059;
3963
+ var subscriptionCounter = 0;
3964
+ function tagsFor(tags, name) {
3965
+ return tags.filter((tag) => tag[0] === name && typeof tag[1] === "string").map((tag) => tag[1]);
3966
+ }
3967
+ function nowSeconds() {
3968
+ return Math.floor(Date.now() / 1e3);
3969
+ }
3970
+ function createNip17DmAdapter(options) {
3971
+ const ownerPubkey = getPublicKey(options.ownerSecretKey);
3972
+ const store = options.store ?? new DmMemoryStore();
3973
+ const live = /* @__PURE__ */ new Map();
3974
+ function relaysFor2(filters) {
3975
+ return options.relays && options.relays.length > 0 ? options.relays : options.relayPool.selectRelayTier(filters);
3976
+ }
3977
+ function normalizeRumor(rumor, status) {
3978
+ if (rumor.kind !== 14) return null;
3979
+ const participants = [.../* @__PURE__ */ new Set([ownerPubkey, rumor.pubkey, ...tagsFor(rumor.tags, "p")])];
3980
+ const conversationId = store.conversationIdFor(participants);
3981
+ return store.upsertMessage(
3982
+ {
3983
+ id: rumor.id,
3984
+ conversationId,
3985
+ senderPubkey: rumor.pubkey,
3986
+ createdAt: rumor.created_at,
3987
+ content: rumor.content,
3988
+ status
3989
+ },
3990
+ participants
3991
+ );
3992
+ }
3993
+ return {
3994
+ status() {
3995
+ return {
3996
+ available: options.relayPool.isAvailable(),
3997
+ ownerPubkey,
3998
+ implementations: ["nip17"],
3999
+ capabilities: ["send", "receive", "subscribe", "history"]
4000
+ };
4001
+ },
4002
+ conversations(query) {
4003
+ return store.conversations(query);
4004
+ },
4005
+ messages(query) {
4006
+ return store.messages(query);
4007
+ },
4008
+ async send(request) {
4009
+ if (!options.relayPool.isAvailable()) throw new Error("relay unavailable");
4010
+ if (!Array.isArray(request.recipients) || request.recipients.length === 0) {
4011
+ throw new Error("recipient required");
4012
+ }
4013
+ if (typeof request.content !== "string" || request.content.length === 0) {
4014
+ throw new Error("content required");
4015
+ }
4016
+ const recipients = [...new Set(request.recipients)];
4017
+ const wraps = nip17.wrapManyEvents(
4018
+ options.ownerSecretKey,
4019
+ recipients.map((publicKey) => ({ publicKey })),
4020
+ request.content
4021
+ );
4022
+ for (const wrap of wraps) await Promise.resolve(options.relayPool.publish(wrap));
4023
+ const conversationId = request.conversationId ?? store.conversationIdFor([ownerPubkey, ...recipients]);
4024
+ const message = store.upsertMessage(
4025
+ {
4026
+ id: request.clientMessageId ?? wraps[0]?.id ?? `dm-${nowSeconds()}`,
4027
+ conversationId,
4028
+ senderPubkey: ownerPubkey,
4029
+ createdAt: nowSeconds(),
4030
+ content: request.content,
4031
+ status: "sent"
4032
+ },
4033
+ [ownerPubkey, ...recipients]
4034
+ );
4035
+ return { ok: true, message };
4036
+ },
4037
+ subscribe(request, onMessage) {
4038
+ if (!options.relayPool.isAvailable()) throw new Error("relay unavailable");
4039
+ const subscriptionId = `dm-nip17-${++subscriptionCounter}`;
4040
+ const filters = [{ kinds: [NIP17_GIFT_WRAP_KIND], "#p": [ownerPubkey] }];
4041
+ const handle = options.relayPool.subscribe(
4042
+ filters,
4043
+ (item) => {
4044
+ if (item === "EOSE") return;
4045
+ let rumor;
4046
+ try {
4047
+ rumor = nip17.unwrapEvent(item, options.ownerSecretKey);
4048
+ } catch {
4049
+ return;
4050
+ }
4051
+ const message = normalizeRumor(rumor, "received");
4052
+ if (!message) return;
4053
+ for (const sub of live.values()) {
4054
+ if (!sub.conversationId || sub.conversationId === message.conversationId) sub.onMessage(message);
4055
+ }
4056
+ },
4057
+ relaysFor2(filters)
4058
+ );
4059
+ live.set(subscriptionId, { handle, conversationId: request.conversationId, onMessage });
4060
+ return { subscriptionId };
4061
+ },
4062
+ unsubscribe(subscriptionId) {
4063
+ const sub = live.get(subscriptionId);
4064
+ if (!sub) return { ok: false };
4065
+ sub.handle.unsubscribe();
4066
+ live.delete(subscriptionId);
4067
+ return { ok: true };
4068
+ },
4069
+ close() {
4070
+ for (const sub of live.values()) sub.handle.unsubscribe();
4071
+ live.clear();
4072
+ }
4073
+ };
4074
+ }
4075
+
4076
+ // src/dm-ndr-adapter.ts
4077
+ import { finalizeEvent } from "nostr-tools/pure";
4078
+ var ndrSubCounter = 0;
4079
+ function nowSeconds2() {
4080
+ return Math.floor(Date.now() / 1e3);
4081
+ }
4082
+ function relaysFor(options, filter) {
4083
+ return options.relays && options.relays.length > 0 ? options.relays : options.relayPool.selectRelayTier([filter]);
4084
+ }
4085
+ function signedEvent(event, secretKey) {
4086
+ if (typeof event.id === "string" && typeof event.pubkey === "string" && typeof event.sig === "string" && typeof event.kind === "number" && typeof event.created_at === "number" && Array.isArray(event.tags) && typeof event.content === "string") {
4087
+ return event;
4088
+ }
4089
+ if (!secretKey) throw new Error("publishSecretKey required for unsigned NDR events");
4090
+ return finalizeEvent(
4091
+ {
4092
+ kind: event.kind ?? 1060,
4093
+ created_at: event.created_at ?? nowSeconds2(),
4094
+ tags: event.tags ?? [],
4095
+ content: event.content ?? ""
4096
+ },
4097
+ secretKey
4098
+ );
4099
+ }
4100
+ function createNdrRelayTransport(options) {
4101
+ return {
4102
+ nostrSubscribe(filter, onEvent) {
4103
+ const handle = options.relayPool.subscribe(
4104
+ [filter],
4105
+ (item) => {
4106
+ if (item !== "EOSE") onEvent(item);
4107
+ },
4108
+ relaysFor(options, filter)
4109
+ );
4110
+ return () => handle.unsubscribe();
4111
+ },
4112
+ nostrFetch(filter) {
4113
+ const events = [];
4114
+ return new Promise((resolve) => {
4115
+ let done = false;
4116
+ let handle;
4117
+ const finish = () => {
4118
+ if (done) return;
4119
+ done = true;
4120
+ clearTimeout(timer);
4121
+ handle?.unsubscribe();
4122
+ resolve(events);
4123
+ };
4124
+ const timer = setTimeout(finish, options.fetchTimeoutMs ?? 500);
4125
+ handle = options.relayPool.subscribe(
4126
+ [filter],
4127
+ (item) => {
4128
+ if (item === "EOSE") finish();
4129
+ else events.push(item);
4130
+ },
4131
+ relaysFor(options, filter)
4132
+ );
4133
+ });
4134
+ },
4135
+ async nostrPublish(event) {
4136
+ const signed = signedEvent(event, options.publishSecretKey);
4137
+ await Promise.resolve(options.relayPool.publish(signed));
4138
+ return signed;
4139
+ }
4140
+ };
4141
+ }
4142
+ function createNdrDmAdapter(options) {
4143
+ const store = options.store ?? new DmMemoryStore();
4144
+ const live = /* @__PURE__ */ new Map();
4145
+ const detach = options.runtime.onSessionEvent((event, from) => {
4146
+ const participants = [options.ownerPubkey, from];
4147
+ const conversationId = store.conversationIdFor(participants);
4148
+ const message = store.upsertMessage(
4149
+ {
4150
+ id: event.id,
4151
+ conversationId,
4152
+ senderPubkey: from,
4153
+ createdAt: event.created_at ?? Math.floor(Date.now() / 1e3),
4154
+ content: event.content ?? "",
4155
+ status: "received"
4156
+ },
4157
+ participants
4158
+ );
4159
+ for (const sub of live.values()) {
4160
+ if (!sub.conversationId || sub.conversationId === message.conversationId) sub.onMessage(message);
4161
+ }
4162
+ });
4163
+ return {
4164
+ status() {
4165
+ return {
4166
+ available: options.available ?? true,
4167
+ ownerPubkey: options.ownerPubkey,
4168
+ implementations: ["ndr"],
4169
+ capabilities: ["send", "receive", "subscribe", "history", "double-ratchet"]
4170
+ };
4171
+ },
4172
+ conversations(query) {
4173
+ return store.conversations(query);
4174
+ },
4175
+ messages(query) {
4176
+ return store.messages(query);
4177
+ },
4178
+ async send(request) {
4179
+ const recipient = request.recipients[0];
4180
+ if (!recipient) throw new Error("recipient required");
4181
+ await options.runtime.setupUser?.(recipient, options.ownerPubkey);
4182
+ const rumor = await options.runtime.sendMessage(recipient, request.content, void 0, options.ownerPubkey);
4183
+ const message = store.upsertMessage(
4184
+ {
4185
+ id: request.clientMessageId ?? rumor.id,
4186
+ conversationId: request.conversationId ?? store.conversationIdFor([options.ownerPubkey, recipient]),
4187
+ senderPubkey: options.ownerPubkey,
4188
+ createdAt: rumor.created_at ?? Math.floor(Date.now() / 1e3),
4189
+ content: request.content,
4190
+ status: "sent"
4191
+ },
4192
+ [options.ownerPubkey, recipient]
4193
+ );
4194
+ return { ok: true, message };
4195
+ },
4196
+ subscribe(request, onMessage) {
4197
+ const subscriptionId = `dm-ndr-${++ndrSubCounter}`;
4198
+ live.set(subscriptionId, { conversationId: request.conversationId, onMessage });
4199
+ return { subscriptionId };
4200
+ },
4201
+ unsubscribe(subscriptionId) {
4202
+ return { ok: live.delete(subscriptionId) };
4203
+ },
4204
+ close() {
4205
+ live.clear();
4206
+ detach();
4207
+ options.runtime.close?.();
4208
+ }
4209
+ };
4210
+ }
4211
+
4212
+ // src/dm-cordn-adapter.ts
4213
+ import { finalizeEvent as finalizeEvent2, getPublicKey as getPublicKey2 } from "nostr-tools/pure";
4214
+ var CORDN_RELAY_GROUP_MESSAGE_KIND = 30383;
4215
+ var cordnSubCounter = 0;
4216
+ function nowSeconds3() {
4217
+ return Math.floor(Date.now() / 1e3);
4218
+ }
4219
+ function encodeBase64(value) {
4220
+ return btoa(String.fromCharCode(...new TextEncoder().encode(value)));
4221
+ }
4222
+ function decodeBase64(value) {
4223
+ return new TextDecoder().decode(Uint8Array.from(atob(value), (char) => char.charCodeAt(0)));
4224
+ }
4225
+ function groupIdFor(request, defaultGroupId) {
4226
+ if (request.conversationId?.startsWith("group:")) return request.conversationId.slice("group:".length);
4227
+ if (request.conversationId) return request.conversationId;
4228
+ if (defaultGroupId) return defaultGroupId;
4229
+ const firstRecipient = request.recipients[0];
4230
+ if (firstRecipient) return firstRecipient;
4231
+ throw new Error("group id required");
4232
+ }
4233
+ function defaultEncodeMessage(request, ownerPubkey, defaultGroupId) {
4234
+ const gid = groupIdFor(request, defaultGroupId);
4235
+ return {
4236
+ gid,
4237
+ msg_64: encodeBase64(JSON.stringify({
4238
+ gid,
4239
+ pubkey: ownerPubkey ?? "",
4240
+ created_at: nowSeconds3(),
4241
+ kind: 9,
4242
+ tags: [],
4243
+ content: request.content
4244
+ }))
4245
+ };
4246
+ }
4247
+ function defaultDecodeMessage(message, ownerPubkey) {
4248
+ let payload = {};
4249
+ try {
4250
+ payload = JSON.parse(decodeBase64(message.msg_64));
4251
+ } catch {
4252
+ payload = { content: decodeBase64(message.msg_64) };
4253
+ }
4254
+ return {
4255
+ id: `cordn:${message.gid}:${message.cursor}`,
4256
+ conversationId: `group:${message.gid}`,
4257
+ senderPubkey: payload.pubkey || ownerPubkey || "",
4258
+ createdAt: payload.created_at ?? message.at,
4259
+ content: payload.content ?? "",
4260
+ status: payload.pubkey && payload.pubkey === ownerPubkey ? "sent" : "received"
4261
+ };
4262
+ }
4263
+ function relayUrls(options, filter) {
4264
+ return options.relays && options.relays.length > 0 ? options.relays : options.relayPool.selectRelayTier([filter]);
4265
+ }
4266
+ function parseRelayGroupMessage(event) {
4267
+ try {
4268
+ const parsed = JSON.parse(event.content);
4269
+ if (typeof parsed.gid !== "string" || typeof parsed.cursor !== "number" || typeof parsed.msg_64 !== "string") return null;
4270
+ return parsed;
4271
+ } catch {
4272
+ return null;
4273
+ }
4274
+ }
4275
+ function createCordnRelayCoordinatorClient(options) {
4276
+ const ownerPubkey = getPublicKey2(options.ownerSecretKey);
4277
+ const kind = options.kind ?? CORDN_RELAY_GROUP_MESSAGE_KIND;
4278
+ const cursors = /* @__PURE__ */ new Map();
4279
+ async function fetchGroup(gid, after = 0) {
4280
+ const filter = { kinds: [kind], "#g": [gid] };
4281
+ const records = [];
4282
+ await new Promise((resolve) => {
4283
+ let done = false;
4284
+ let handle;
4285
+ const finish = () => {
4286
+ if (done) return;
4287
+ done = true;
4288
+ clearTimeout(timer);
4289
+ handle?.unsubscribe();
4290
+ resolve();
4291
+ };
4292
+ const timer = setTimeout(finish, options.fetchTimeoutMs ?? 500);
4293
+ handle = options.relayPool.subscribe(
4294
+ [filter],
4295
+ (item) => {
4296
+ if (item === "EOSE") finish();
4297
+ else {
4298
+ const parsed = parseRelayGroupMessage(item);
4299
+ if (parsed && parsed.cursor > after) records.push(parsed);
4300
+ }
4301
+ },
4302
+ relayUrls(options, filter)
4303
+ );
4304
+ });
4305
+ return records.sort((a, b) => a.cursor - b.cursor);
4306
+ }
4307
+ return {
4308
+ async PostGroupMessage(input) {
4309
+ const decoded = JSON.parse(decodeBase64(input.msg_64));
4310
+ const gid = decoded.gid;
4311
+ if (!gid) throw new Error("group id required");
4312
+ const cursor = (cursors.get(gid) ?? 0) + 1;
4313
+ cursors.set(gid, cursor);
4314
+ const at = nowSeconds3();
4315
+ const record = { cursor, gid, msg_64: input.msg_64, at };
4316
+ const event = finalizeEvent2(
4317
+ {
4318
+ kind,
4319
+ created_at: at,
4320
+ tags: [["g", gid], ["p", ownerPubkey]],
4321
+ content: JSON.stringify(record)
4322
+ },
4323
+ options.ownerSecretKey
4324
+ );
4325
+ await Promise.resolve(options.relayPool.publish(event));
4326
+ return { cursor, gid, at };
4327
+ },
4328
+ async FetchGroupMessages(input) {
4329
+ return { messages: await fetchGroup(input.gid, input.after ?? 0) };
4330
+ },
4331
+ async SubscribeGroupMessages(input) {
4332
+ const filter = { kinds: [kind], "#g": [input.gid] };
4333
+ const queue = [];
4334
+ const waiters = [];
4335
+ let closed = false;
4336
+ const push = (message) => {
4337
+ if (message.cursor <= (input.after ?? 0)) return;
4338
+ const waiter = waiters.shift();
4339
+ if (waiter) waiter({ value: message, done: false });
4340
+ else queue.push(message);
4341
+ };
4342
+ const handle = options.relayPool.subscribe(
4343
+ [filter],
4344
+ (item) => {
4345
+ if (item === "EOSE") return;
4346
+ const message = parseRelayGroupMessage(item);
4347
+ if (message) push(message);
4348
+ },
4349
+ relayUrls(options, filter)
4350
+ );
4351
+ return {
4352
+ result: Promise.resolve({ subscribed: true }),
4353
+ stream: {
4354
+ [Symbol.asyncIterator]() {
4355
+ return {
4356
+ next() {
4357
+ const existing = queue.shift();
4358
+ if (existing) return Promise.resolve({ value: existing, done: false });
4359
+ if (closed) return Promise.resolve({ value: void 0, done: true });
4360
+ return new Promise((resolve) => waiters.push(resolve));
4361
+ }
4362
+ };
4363
+ }
4364
+ },
4365
+ async abort() {
4366
+ closed = true;
4367
+ handle.unsubscribe();
4368
+ for (const waiter of waiters.splice(0)) waiter({ value: void 0, done: true });
4369
+ }
4370
+ };
4371
+ }
4372
+ };
4373
+ }
4374
+ function createCordnDmAdapter(options) {
4375
+ const store = options.store ?? new DmMemoryStore();
4376
+ const live = /* @__PURE__ */ new Map();
4377
+ const ownerPubkey = options.ownerPubkey;
4378
+ if (!options.client && !options.coordinator) {
4379
+ throw new Error("createCordnDmAdapter: client or coordinator is required");
4380
+ }
4381
+ return {
4382
+ async status() {
4383
+ const status = await options.client?.status?.();
4384
+ return {
4385
+ available: status?.available ?? true,
4386
+ ownerPubkey: status?.ownerPubkey ?? ownerPubkey,
4387
+ implementations: ["cordn", ...status?.implementations ?? []],
4388
+ capabilities: ["send", "receive", "subscribe", "history", "contextvm", ...status?.capabilities ?? []]
4389
+ };
4390
+ },
4391
+ conversations(query) {
4392
+ if (options.client) return options.client.conversations(query);
4393
+ return store.conversations(query);
4394
+ },
4395
+ async messages(query) {
4396
+ if (options.client) return options.client.messages(query);
4397
+ const gid = query.conversationId.startsWith("group:") ? query.conversationId.slice("group:".length) : query.conversationId;
4398
+ const fetched = await options.coordinator.FetchGroupMessages({ gid });
4399
+ for (const record of fetched.messages) store.upsertMessage(
4400
+ (options.decodeMessage ?? defaultDecodeMessage)(record, ownerPubkey),
4401
+ [ownerPubkey ?? "", gid]
4402
+ );
4403
+ return store.messages(query);
4404
+ },
4405
+ async send(request) {
4406
+ if (options.client) return options.client.send(request);
4407
+ const encoded = (options.encodeMessage ?? ((req, pk) => defaultEncodeMessage(req, pk, options.defaultGroupId)))(request, ownerPubkey);
4408
+ const posted = await options.coordinator.PostGroupMessage({ msg_64: encoded.msg_64 });
4409
+ const record = { ...posted, msg_64: encoded.msg_64 };
4410
+ const message = store.upsertMessage((options.decodeMessage ?? defaultDecodeMessage)(record, ownerPubkey), [ownerPubkey ?? "", encoded.gid]);
4411
+ return { ok: true, message: { ...message, status: "sent" } };
4412
+ },
4413
+ async subscribe(request, onMessage) {
4414
+ if (options.client) return options.client.subscribe(request, onMessage);
4415
+ const conversationId = request.conversationId ?? (options.defaultGroupId ? `group:${options.defaultGroupId}` : void 0);
4416
+ if (!conversationId) throw new Error("group id required");
4417
+ const gid = conversationId.startsWith("group:") ? conversationId.slice("group:".length) : conversationId;
4418
+ const fetched = await options.coordinator.FetchGroupMessages({ gid });
4419
+ let after = 0;
4420
+ for (const record of fetched.messages) {
4421
+ after = Math.max(after, record.cursor);
4422
+ const message = store.upsertMessage((options.decodeMessage ?? defaultDecodeMessage)(record, ownerPubkey), [ownerPubkey ?? "", gid]);
4423
+ onMessage(message);
4424
+ }
4425
+ const subscription = await options.coordinator.SubscribeGroupMessages({ gid, after });
4426
+ const subscriptionId = `dm-cordn-${++cordnSubCounter}`;
4427
+ live.set(subscriptionId, { abort: () => subscription.abort("dm.unsubscribe"), conversationId });
4428
+ void (async () => {
4429
+ for await (const record of subscription.stream) {
4430
+ const message = store.upsertMessage((options.decodeMessage ?? defaultDecodeMessage)(record, ownerPubkey), [ownerPubkey ?? "", gid]);
4431
+ onMessage(message);
4432
+ }
4433
+ })();
4434
+ return { subscriptionId };
4435
+ },
4436
+ unsubscribe(subscriptionId) {
4437
+ if (options.client) return options.client.unsubscribe(subscriptionId);
4438
+ const sub = live.get(subscriptionId);
4439
+ if (!sub) return { ok: false };
4440
+ live.delete(subscriptionId);
4441
+ void sub.abort();
4442
+ return { ok: true };
4443
+ },
4444
+ close() {
4445
+ for (const [id, sub] of live) {
4446
+ live.delete(id);
4447
+ void sub.abort();
4448
+ }
4449
+ options.client?.close?.();
4450
+ options.coordinator?.close?.();
4451
+ }
4452
+ };
4453
+ }
3656
4454
  export {
4455
+ DmMemoryStore,
3657
4456
  createAudioService,
3658
4457
  createBleService,
3659
4458
  createBrowserMediaBridge,
@@ -3662,7 +4461,10 @@ export {
3662
4461
  createCommonService,
3663
4462
  createConfigService,
3664
4463
  createCoordinatedRelay,
4464
+ createCordnDmAdapter,
4465
+ createCordnRelayCoordinatorClient,
3665
4466
  createCvmService,
4467
+ createDmService,
3666
4468
  createHttpUploader,
3667
4469
  createIdentityService,
3668
4470
  createIntentService,
@@ -3670,6 +4472,9 @@ export {
3670
4472
  createLinkService,
3671
4473
  createListsService,
3672
4474
  createMediaService,
4475
+ createNdrDmAdapter,
4476
+ createNdrRelayTransport,
4477
+ createNip17DmAdapter,
3673
4478
  createNotificationService,
3674
4479
  createNotifyService,
3675
4480
  createOutboxService,