@kehto/services 0.12.3 → 0.13.2
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 +25 -2
- package/dist/index.d.ts +378 -11
- package/dist/index.js +897 -100
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
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,
|
|
234
|
+
function sendProviderError(send, result, fallback, err2) {
|
|
235
235
|
send({
|
|
236
236
|
...result,
|
|
237
|
-
error:
|
|
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((
|
|
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,
|
|
260
|
-
sendIdentityError(send, id, typeBase,
|
|
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((
|
|
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((
|
|
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
|
|
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
|
-
},
|
|
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
|
|
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
|
-
},
|
|
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 (
|
|
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: ${
|
|
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 (
|
|
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: ${
|
|
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 (
|
|
1745
|
-
const isAbort = signal.aborted ||
|
|
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:
|
|
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
|
-
(
|
|
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
|
-
(
|
|
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
|
-
(
|
|
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(
|
|
2034
|
-
if (
|
|
2035
|
-
if (typeof
|
|
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,
|
|
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
|
|
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 >=
|
|
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 (
|
|
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((
|
|
2227
|
-
if (!sub.closed) sink.closed(
|
|
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 (
|
|
2256
|
-
return { ok: false, error:
|
|
2375
|
+
} catch (err2) {
|
|
2376
|
+
return { ok: false, error: err2 instanceof Error ? err2.message : "sign failed" };
|
|
2257
2377
|
}
|
|
2258
|
-
const
|
|
2259
|
-
if (
|
|
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,
|
|
2263
|
-
} catch (
|
|
2264
|
-
return { ok: false, event: signed, eventId: signed.id, error:
|
|
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,
|
|
2429
|
+
function normalizePublishResult(res, relayUrls2) {
|
|
2309
2430
|
const out = {};
|
|
2310
2431
|
if (res && typeof res === "object") {
|
|
2311
|
-
for (const url of
|
|
2432
|
+
for (const url of relayUrls2) out[url] = res[url] ?? false;
|
|
2312
2433
|
} else {
|
|
2313
|
-
for (const url of
|
|
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((
|
|
2504
|
+
}).catch((err2) => {
|
|
2360
2505
|
entries.delete(key);
|
|
2361
|
-
send({ type: "upload.upload.result", id, error: toErrorMessage2(
|
|
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
|
-
(
|
|
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(
|
|
2415
|
-
if (
|
|
2416
|
-
if (typeof
|
|
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 (
|
|
2449
|
-
return failed(ctx.uploadId, rail, toErrorMessage3(
|
|
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(
|
|
2585
|
-
if (
|
|
2586
|
-
if (typeof
|
|
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(
|
|
2598
|
-
if (
|
|
2599
|
-
if (typeof
|
|
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 (
|
|
2618
|
-
send({ type: resultType, id, error: toErrorMessage4(
|
|
2765
|
+
} catch (err2) {
|
|
2766
|
+
send({ type: resultType, id, error: toErrorMessage4(err2) });
|
|
2619
2767
|
return;
|
|
2620
2768
|
}
|
|
2621
|
-
pending.then((value) => send(onValue(value))).catch((
|
|
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
|
-
(
|
|
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
|
-
(
|
|
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((
|
|
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(
|
|
2929
|
-
if (
|
|
2930
|
-
if (typeof
|
|
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(
|
|
3010
|
-
if (
|
|
3011
|
-
if (typeof
|
|
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 (
|
|
3019
|
-
send(okFalse(errorMessage(
|
|
3166
|
+
} catch (err2) {
|
|
3167
|
+
send(okFalse(errorMessage(err2, "lists request failed")));
|
|
3020
3168
|
return;
|
|
3021
3169
|
}
|
|
3022
|
-
pending.then((value) => send(onValue(value))).catch((
|
|
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(
|
|
3092
|
-
if (
|
|
3093
|
-
if (typeof
|
|
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 (
|
|
3101
|
-
send(okFalse(errorMessage2(
|
|
3248
|
+
} catch (err2) {
|
|
3249
|
+
send(okFalse(errorMessage2(err2, "serial request failed")));
|
|
3102
3250
|
return;
|
|
3103
3251
|
}
|
|
3104
|
-
pending.then((value) => send(onValue(value))).catch((
|
|
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(
|
|
3181
|
-
if (
|
|
3182
|
-
if (typeof
|
|
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 (
|
|
3190
|
-
send(okFalse(errorMessage3(
|
|
3337
|
+
} catch (err2) {
|
|
3338
|
+
send(okFalse(errorMessage3(err2, "ble request failed")));
|
|
3191
3339
|
return;
|
|
3192
3340
|
}
|
|
3193
|
-
pending.then((value) => send(onValue(value))).catch((
|
|
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 {
|
|
@@ -3330,20 +3478,20 @@ var WEBRTC_DESCRIPTOR = {
|
|
|
3330
3478
|
version: WEBRTC_SERVICE_VERSION,
|
|
3331
3479
|
description: "NAP-WEBRTC reference handler for shell-mediated WebRTC sessions"
|
|
3332
3480
|
};
|
|
3333
|
-
function errorMessage4(
|
|
3334
|
-
if (
|
|
3335
|
-
if (typeof
|
|
3481
|
+
function errorMessage4(err2, fallback) {
|
|
3482
|
+
if (err2 instanceof Error) return err2.message;
|
|
3483
|
+
if (typeof err2 === "string") return err2;
|
|
3336
3484
|
return fallback;
|
|
3337
3485
|
}
|
|
3338
3486
|
function settle4(call, send, okFalse, onValue) {
|
|
3339
3487
|
let pending;
|
|
3340
3488
|
try {
|
|
3341
3489
|
pending = Promise.resolve(call());
|
|
3342
|
-
} catch (
|
|
3343
|
-
send(okFalse(errorMessage4(
|
|
3490
|
+
} catch (err2) {
|
|
3491
|
+
send(okFalse(errorMessage4(err2, "webrtc request failed")));
|
|
3344
3492
|
return;
|
|
3345
3493
|
}
|
|
3346
|
-
pending.then((value) => send(onValue(value))).catch((
|
|
3494
|
+
pending.then((value) => send(onValue(value))).catch((err2) => send(okFalse(errorMessage4(err2, "webrtc request failed"))));
|
|
3347
3495
|
}
|
|
3348
3496
|
function unsupported3(resultType, id) {
|
|
3349
3497
|
return {
|
|
@@ -3437,9 +3585,9 @@ var COMMON_DESCRIPTOR = {
|
|
|
3437
3585
|
version: COMMON_SERVICE_VERSION,
|
|
3438
3586
|
description: "NAP-COMMON reference handler for shell-mediated social helpers"
|
|
3439
3587
|
};
|
|
3440
|
-
function errorMessage5(
|
|
3441
|
-
if (
|
|
3442
|
-
if (typeof
|
|
3588
|
+
function errorMessage5(err2, fallback) {
|
|
3589
|
+
if (err2 instanceof Error) return err2.message;
|
|
3590
|
+
if (typeof err2 === "string") return err2;
|
|
3443
3591
|
return fallback;
|
|
3444
3592
|
}
|
|
3445
3593
|
function encodeNip19(input) {
|
|
@@ -3468,8 +3616,8 @@ function encodeNip19(input) {
|
|
|
3468
3616
|
default:
|
|
3469
3617
|
return { ok: false, error: "unsupported NIP-19 type" };
|
|
3470
3618
|
}
|
|
3471
|
-
} catch (
|
|
3472
|
-
return { ok: false, error: errorMessage5(
|
|
3619
|
+
} catch (err2) {
|
|
3620
|
+
return { ok: false, error: errorMessage5(err2, "NIP-19 encode failed") };
|
|
3473
3621
|
}
|
|
3474
3622
|
}
|
|
3475
3623
|
function decodeNip19Value(value) {
|
|
@@ -3502,9 +3650,9 @@ function decodeNip19Value(value) {
|
|
|
3502
3650
|
default:
|
|
3503
3651
|
return { ok: false, error: "unsupported NIP-19 type" };
|
|
3504
3652
|
}
|
|
3505
|
-
} catch (
|
|
3653
|
+
} catch (err2) {
|
|
3506
3654
|
if (value.startsWith("nrelay1")) return decodeNrelay(value);
|
|
3507
|
-
return { ok: false, error: errorMessage5(
|
|
3655
|
+
return { ok: false, error: errorMessage5(err2, "NIP-19 decode failed") };
|
|
3508
3656
|
}
|
|
3509
3657
|
}
|
|
3510
3658
|
function decodeNrelay(value) {
|
|
@@ -3520,8 +3668,8 @@ function decodeNrelay(value) {
|
|
|
3520
3668
|
const payloadWords = words.slice(0, -6);
|
|
3521
3669
|
const bytes = convertBits(payloadWords, 5, 8, false);
|
|
3522
3670
|
return { ok: true, nip19Type: "nrelay", relay: new TextDecoder().decode(Uint8Array.from(bytes)) };
|
|
3523
|
-
} catch (
|
|
3524
|
-
return { ok: false, error: errorMessage5(
|
|
3671
|
+
} catch (err2) {
|
|
3672
|
+
return { ok: false, error: errorMessage5(err2, "invalid nrelay value") };
|
|
3525
3673
|
}
|
|
3526
3674
|
}
|
|
3527
3675
|
function convertBits(data, fromBits, toBits, pad) {
|
|
@@ -3550,11 +3698,11 @@ function settle5(call, send, okFalse, onValue) {
|
|
|
3550
3698
|
let pending;
|
|
3551
3699
|
try {
|
|
3552
3700
|
pending = Promise.resolve(call());
|
|
3553
|
-
} catch (
|
|
3554
|
-
send(okFalse(errorMessage5(
|
|
3701
|
+
} catch (err2) {
|
|
3702
|
+
send(okFalse(errorMessage5(err2, "common request failed")));
|
|
3555
3703
|
return;
|
|
3556
3704
|
}
|
|
3557
|
-
pending.then((value) => send(onValue(value))).catch((
|
|
3705
|
+
pending.then((value) => send(onValue(value))).catch((err2) => send(okFalse(errorMessage5(err2, "common request failed"))));
|
|
3558
3706
|
}
|
|
3559
3707
|
function createCommonService(options = {}) {
|
|
3560
3708
|
return {
|
|
@@ -3661,7 +3809,650 @@ function settleAction(action, pubkeys, context, send, resultType, id, unavailabl
|
|
|
3661
3809
|
(result) => ({ type: resultType, id, ...result })
|
|
3662
3810
|
);
|
|
3663
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
|
+
}
|
|
3664
4454
|
export {
|
|
4455
|
+
DmMemoryStore,
|
|
3665
4456
|
createAudioService,
|
|
3666
4457
|
createBleService,
|
|
3667
4458
|
createBrowserMediaBridge,
|
|
@@ -3670,7 +4461,10 @@ export {
|
|
|
3670
4461
|
createCommonService,
|
|
3671
4462
|
createConfigService,
|
|
3672
4463
|
createCoordinatedRelay,
|
|
4464
|
+
createCordnDmAdapter,
|
|
4465
|
+
createCordnRelayCoordinatorClient,
|
|
3673
4466
|
createCvmService,
|
|
4467
|
+
createDmService,
|
|
3674
4468
|
createHttpUploader,
|
|
3675
4469
|
createIdentityService,
|
|
3676
4470
|
createIntentService,
|
|
@@ -3678,6 +4472,9 @@ export {
|
|
|
3678
4472
|
createLinkService,
|
|
3679
4473
|
createListsService,
|
|
3680
4474
|
createMediaService,
|
|
4475
|
+
createNdrDmAdapter,
|
|
4476
|
+
createNdrRelayTransport,
|
|
4477
|
+
createNip17DmAdapter,
|
|
3681
4478
|
createNotificationService,
|
|
3682
4479
|
createNotifyService,
|
|
3683
4480
|
createOutboxService,
|