@kehto/services 0.11.1 → 0.12.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
@@ -1650,19 +1650,35 @@ function untrackRequest(state, requestId) {
1650
1650
  state.perWindow.get(entry.windowId)?.delete(requestId);
1651
1651
  }
1652
1652
  }
1653
- function sendResourceError(send, requestId, code, message) {
1653
+ function sendResourceError(send, requestId, code, message, error = toResourceError(code), type = "resource.bytes.error") {
1654
1654
  send({
1655
- type: "resource.bytes.error",
1655
+ type,
1656
+ id: requestId,
1656
1657
  requestId,
1658
+ error,
1657
1659
  code,
1658
1660
  message
1659
1661
  });
1660
1662
  }
1661
- function parseResourceUrl(send, requestId, url) {
1663
+ function sendBytesManyError(send, requestId, code, message, error) {
1664
+ sendResourceError(send, requestId, code, message, error, "resource.bytesMany.error");
1665
+ }
1666
+ function toResourceError(code) {
1667
+ switch (code) {
1668
+ case "denied":
1669
+ return "blocked-by-policy";
1670
+ case "invalid-url":
1671
+ return "invalid-request";
1672
+ case "canceled":
1673
+ return "timeout";
1674
+ case "network-error":
1675
+ return "network-error";
1676
+ }
1677
+ }
1678
+ function parseResourceUrl(url) {
1662
1679
  try {
1663
1680
  return new URL(url);
1664
1681
  } catch {
1665
- sendResourceError(send, requestId, "invalid-url", `invalid URL: ${url}`);
1666
1682
  return null;
1667
1683
  }
1668
1684
  }
@@ -1673,45 +1689,138 @@ function collectResponseHeaders(response) {
1673
1689
  });
1674
1690
  return headers;
1675
1691
  }
1676
- async function handleBytes(options, state, windowId, msg, send) {
1677
- const { requestId, url, init } = msg;
1678
- const identity = options.resolveIdentity(windowId);
1679
- if (!identity) {
1680
- sendResourceError(send, requestId, "denied", "napplet identity not resolvable");
1681
- return;
1682
- }
1683
- const parsedUrl = parseResourceUrl(send, requestId, url);
1684
- if (!parsedUrl) return;
1692
+ function responseMime(response) {
1693
+ return response.headers.get("content-type") || "application/octet-stream";
1694
+ }
1695
+ function responseBlob(buffer, mime) {
1696
+ return new Blob([buffer], { type: mime });
1697
+ }
1698
+ function requestIdFromMessage(message) {
1699
+ if (typeof message.id === "string" && message.id.length > 0) return message.id;
1700
+ if (typeof message.requestId === "string" && message.requestId.length > 0) return message.requestId;
1701
+ return null;
1702
+ }
1703
+ function resourceInvalidRequest(url, message) {
1704
+ return {
1705
+ ok: false,
1706
+ url,
1707
+ error: "invalid-request",
1708
+ code: "invalid-url",
1709
+ message
1710
+ };
1711
+ }
1712
+ async function fetchResourceItem(options, identity, url, init, signal) {
1713
+ const parsedUrl = parseResourceUrl(url);
1714
+ if (!parsedUrl) return resourceInvalidRequest(url, `invalid URL: ${url}`);
1685
1715
  const origin = parsedUrl.origin;
1686
1716
  const grants = options.getConnectGrants(identity.dTag, identity.aggregateHash);
1687
1717
  if (!options.isOriginGranted(origin, grants)) {
1688
- sendResourceError(send, requestId, "denied", `origin ${origin} not granted`);
1689
- return;
1718
+ return {
1719
+ ok: false,
1720
+ url,
1721
+ error: "blocked-by-policy",
1722
+ code: "denied",
1723
+ message: `origin ${origin} not granted`
1724
+ };
1690
1725
  }
1691
- const controller = new AbortController();
1692
- trackRequest(state, requestId, windowId, controller);
1693
1726
  try {
1694
1727
  const response = await options.fetch(url, {
1695
1728
  method: init?.method,
1696
1729
  headers: init?.headers ? { ...init.headers } : void 0,
1697
- signal: controller.signal
1730
+ signal
1698
1731
  });
1699
1732
  const buffer = await response.arrayBuffer();
1733
+ const headers = collectResponseHeaders(response);
1734
+ const mime = responseMime(response);
1735
+ return {
1736
+ ok: true,
1737
+ url,
1738
+ blob: responseBlob(buffer, mime),
1739
+ mime,
1740
+ status: response.status,
1741
+ headers,
1742
+ bodyBase64: arrayBufferToBase64(buffer)
1743
+ };
1744
+ } catch (err) {
1745
+ const isAbort = signal.aborted || err instanceof Error && (err.name === "AbortError" || err.name === "DOMException");
1746
+ return {
1747
+ ok: false,
1748
+ url,
1749
+ error: isAbort ? "timeout" : "network-error",
1750
+ code: isAbort ? "canceled" : "network-error",
1751
+ message: err instanceof Error ? err.message : String(err)
1752
+ };
1753
+ }
1754
+ }
1755
+ async function handleBytes(options, state, windowId, msg, send) {
1756
+ const { requestId, url, init } = msg;
1757
+ const identity = options.resolveIdentity(windowId);
1758
+ if (!identity) {
1759
+ sendResourceError(send, requestId, "denied", "napplet identity not resolvable", "blocked-by-policy");
1760
+ return;
1761
+ }
1762
+ const controller = new AbortController();
1763
+ trackRequest(state, requestId, windowId, controller);
1764
+ try {
1765
+ const item = await fetchResourceItem(options, identity, url, init, controller.signal);
1766
+ if (!item.ok) {
1767
+ sendResourceError(send, requestId, item.code, item.message, item.error);
1768
+ return;
1769
+ }
1700
1770
  send({
1701
1771
  type: "resource.bytes.result",
1772
+ id: requestId,
1702
1773
  requestId,
1703
- status: response.status,
1704
- headers: collectResponseHeaders(response),
1705
- bodyBase64: arrayBufferToBase64(buffer)
1774
+ blob: item.blob,
1775
+ mime: item.mime,
1776
+ status: item.status,
1777
+ headers: item.headers,
1778
+ bodyBase64: item.bodyBase64
1706
1779
  });
1707
- } catch (err) {
1708
- const isAbort = controller.signal.aborted || err instanceof Error && (err.name === "AbortError" || err.name === "DOMException");
1709
- sendResourceError(
1710
- send,
1780
+ } finally {
1781
+ untrackRequest(state, requestId);
1782
+ }
1783
+ }
1784
+ async function handleBytesMany(options, state, windowId, msg, send) {
1785
+ const { requestId, urls, init } = msg;
1786
+ if (!Array.isArray(urls) || urls.length === 0 || urls.some((url) => typeof url !== "string")) {
1787
+ sendBytesManyError(send, requestId, "invalid-url", "resource.bytesMany requires a non-empty urls array", "invalid-request");
1788
+ return;
1789
+ }
1790
+ const identity = options.resolveIdentity(windowId);
1791
+ if (!identity) {
1792
+ sendBytesManyError(send, requestId, "denied", "napplet identity not resolvable", "blocked-by-policy");
1793
+ return;
1794
+ }
1795
+ const controller = new AbortController();
1796
+ trackRequest(state, requestId, windowId, controller);
1797
+ try {
1798
+ const items = [];
1799
+ for (const url of urls) {
1800
+ const item = await fetchResourceItem(options, identity, url, init, controller.signal);
1801
+ if (item.ok) {
1802
+ items.push({
1803
+ url: item.url,
1804
+ ok: true,
1805
+ blob: item.blob,
1806
+ mime: item.mime
1807
+ });
1808
+ } else {
1809
+ items.push({
1810
+ url: item.url,
1811
+ ok: false,
1812
+ error: item.error,
1813
+ code: item.code,
1814
+ message: item.message
1815
+ });
1816
+ }
1817
+ }
1818
+ send({
1819
+ type: "resource.bytesMany.result",
1820
+ id: requestId,
1711
1821
  requestId,
1712
- isAbort ? "canceled" : "network-error",
1713
- err instanceof Error ? err.message : String(err)
1714
- );
1822
+ items
1823
+ });
1715
1824
  } finally {
1716
1825
  untrackRequest(state, requestId);
1717
1826
  }
@@ -1751,13 +1860,24 @@ function createResourceService(options) {
1751
1860
  switch (message.type) {
1752
1861
  case "resource.bytes": {
1753
1862
  const m = message;
1754
- handleBytes(options, state, windowId, m, send).catch(() => {
1863
+ const requestId = requestIdFromMessage(m);
1864
+ if (!requestId || typeof m.url !== "string") return;
1865
+ handleBytes(options, state, windowId, { requestId, url: m.url, init: m.init }, send).catch(() => {
1866
+ });
1867
+ return;
1868
+ }
1869
+ case "resource.bytesMany": {
1870
+ const m = message;
1871
+ const requestId = requestIdFromMessage(m);
1872
+ if (!requestId) return;
1873
+ handleBytesMany(options, state, windowId, { requestId, urls: m.urls ?? [], init: m.init }, send).catch(() => {
1755
1874
  });
1756
1875
  return;
1757
1876
  }
1758
1877
  case "resource.cancel": {
1759
1878
  const m = message;
1760
- handleCancel(state, m.requestId);
1879
+ const requestId = requestIdFromMessage(m);
1880
+ if (requestId) handleCancel(state, requestId);
1761
1881
  return;
1762
1882
  }
1763
1883
  default:
@@ -2490,7 +2610,7 @@ function createIntentService(options) {
2490
2610
  send({ type: "intent.changed", availability });
2491
2611
  }
2492
2612
  });
2493
- function settle(call, send, resultType, id, onValue) {
2613
+ function settle6(call, send, resultType, id, onValue) {
2494
2614
  let pending;
2495
2615
  try {
2496
2616
  pending = Promise.resolve(call());
@@ -2508,7 +2628,7 @@ function createIntentService(options) {
2508
2628
  send({ type: "intent.invoke.result", id, error: "invalid request" });
2509
2629
  return;
2510
2630
  }
2511
- settle(
2631
+ settle6(
2512
2632
  () => resolver.invoke(request, { windowId }),
2513
2633
  send,
2514
2634
  "intent.invoke.result",
@@ -2524,7 +2644,7 @@ function createIntentService(options) {
2524
2644
  return;
2525
2645
  }
2526
2646
  const archetype = m.archetype;
2527
- settle(
2647
+ settle6(
2528
2648
  () => resolver.available(archetype),
2529
2649
  send,
2530
2650
  "intent.available.result",
@@ -2535,7 +2655,7 @@ function createIntentService(options) {
2535
2655
  function handleHandlers(msg, send) {
2536
2656
  const m = msg;
2537
2657
  const id = m.id ?? "";
2538
- settle(
2658
+ settle6(
2539
2659
  () => resolver.handlers(),
2540
2660
  send,
2541
2661
  "intent.handlers.result",
@@ -2810,11 +2930,736 @@ function toErrorMessage5(err) {
2810
2930
  if (typeof err === "string") return err;
2811
2931
  return "cvm request failed";
2812
2932
  }
2933
+
2934
+ // src/link-service.ts
2935
+ var LINK_SERVICE_VERSION = "1.0.0";
2936
+ var DEFAULT_ALLOWED_PROTOCOLS = ["http:", "https:"];
2937
+ function denied(id, error) {
2938
+ return { type: "link.open.result", id, status: "denied", error };
2939
+ }
2940
+ function parseLinkUrl(id, rawUrl) {
2941
+ try {
2942
+ return new URL(rawUrl);
2943
+ } catch {
2944
+ return denied(id, "invalid-url");
2945
+ }
2946
+ }
2947
+ function isProtocolAllowed(url, allowedProtocols) {
2948
+ return allowedProtocols.includes(url.protocol);
2949
+ }
2950
+ async function handleOpen(options, windowId, message, send) {
2951
+ const parsed = parseLinkUrl(message.id, message.url);
2952
+ if ("type" in parsed) {
2953
+ send(parsed);
2954
+ return;
2955
+ }
2956
+ const allowedProtocols = options.allowedProtocols ?? DEFAULT_ALLOWED_PROTOCOLS;
2957
+ if (!isProtocolAllowed(parsed, allowedProtocols)) {
2958
+ send(denied(message.id, "unsupported-scheme"));
2959
+ return;
2960
+ }
2961
+ if (!options.open) {
2962
+ send(denied(message.id, "blocked-by-policy"));
2963
+ return;
2964
+ }
2965
+ try {
2966
+ const result = await options.open({ windowId, url: parsed, options: message.options });
2967
+ send({
2968
+ type: "link.open.result",
2969
+ id: message.id,
2970
+ status: result.status,
2971
+ ...result.status === "denied" ? { error: "blocked-by-policy" } : {}
2972
+ });
2973
+ } catch {
2974
+ send(denied(message.id, "blocked-by-policy"));
2975
+ }
2976
+ }
2977
+ function createLinkService(options = {}) {
2978
+ const descriptor = {
2979
+ name: "link",
2980
+ version: LINK_SERVICE_VERSION,
2981
+ description: "NAP-LINK reference handler for shell-mediated link opening"
2982
+ };
2983
+ return {
2984
+ descriptor,
2985
+ handleMessage(windowId, message, send) {
2986
+ if (message.type === "link.open") {
2987
+ void handleOpen(options, windowId, message, send);
2988
+ return;
2989
+ }
2990
+ const id = message.id ?? "";
2991
+ send({
2992
+ type: `${message.type}.error`,
2993
+ id,
2994
+ error: `Unknown link method: ${message.type}`
2995
+ });
2996
+ },
2997
+ onWindowDestroyed(_windowId) {
2998
+ }
2999
+ };
3000
+ }
3001
+
3002
+ // src/lists-service.ts
3003
+ var LISTS_SERVICE_VERSION = "1.0.0";
3004
+ var LISTS_DESCRIPTOR = {
3005
+ name: "lists",
3006
+ version: LISTS_SERVICE_VERSION,
3007
+ description: "NAP-LISTS reference handler for shell-mediated NIP-51 list mutations"
3008
+ };
3009
+ function errorMessage(err, fallback) {
3010
+ if (err instanceof Error) return err.message;
3011
+ if (typeof err === "string") return err;
3012
+ return fallback;
3013
+ }
3014
+ function settle(call, send, okFalse, onValue) {
3015
+ let pending;
3016
+ try {
3017
+ pending = Promise.resolve(call());
3018
+ } catch (err) {
3019
+ send(okFalse(errorMessage(err, "lists request failed")));
3020
+ return;
3021
+ }
3022
+ pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage(err, "lists request failed"))));
3023
+ }
3024
+ function unsupportedMutation(resultType, id) {
3025
+ return {
3026
+ type: resultType,
3027
+ id,
3028
+ ok: false,
3029
+ error: "unsupported",
3030
+ reason: `${resultType.replace(".result", "")} unavailable`,
3031
+ supported: []
3032
+ };
3033
+ }
3034
+ function settleMutation(hook, message, context, send, resultType) {
3035
+ if (!hook) {
3036
+ send(unsupportedMutation(resultType, message.id));
3037
+ return;
3038
+ }
3039
+ settle(
3040
+ () => hook(message.list, message.items, message.options, context),
3041
+ send,
3042
+ (error) => ({ type: resultType, id: message.id, ok: false, error: "list-unavailable", reason: error }),
3043
+ (result) => ({ type: resultType, id: message.id, ...result })
3044
+ );
3045
+ }
3046
+ function createListsService(options = {}) {
3047
+ return {
3048
+ descriptor: LISTS_DESCRIPTOR,
3049
+ handleMessage(windowId, message, send) {
3050
+ const id = message.id ?? "";
3051
+ const context = { windowId };
3052
+ if (message.type === "lists.supported") {
3053
+ if (!options.supported) {
3054
+ send({ type: "lists.supported.result", id, lists: [] });
3055
+ return;
3056
+ }
3057
+ settle(
3058
+ () => options.supported(context),
3059
+ send,
3060
+ (error) => ({ type: "lists.supported.result", id, error }),
3061
+ (lists) => ({ type: "lists.supported.result", id, lists: [...lists] })
3062
+ );
3063
+ return;
3064
+ }
3065
+ if (message.type === "lists.add") {
3066
+ settleMutation(options.add, message, context, send, "lists.add.result");
3067
+ return;
3068
+ }
3069
+ if (message.type === "lists.remove") {
3070
+ settleMutation(options.remove, message, context, send, "lists.remove.result");
3071
+ return;
3072
+ }
3073
+ send({
3074
+ type: `${message.type}.error`,
3075
+ id,
3076
+ error: `Unknown lists method: ${message.type}`
3077
+ });
3078
+ },
3079
+ onWindowDestroyed(_windowId) {
3080
+ }
3081
+ };
3082
+ }
3083
+
3084
+ // src/serial-service.ts
3085
+ var SERIAL_SERVICE_VERSION = "1.0.0";
3086
+ var SERIAL_DESCRIPTOR = {
3087
+ name: "serial",
3088
+ version: SERIAL_SERVICE_VERSION,
3089
+ description: "NAP-SERIAL reference handler for shell-mediated serial sessions"
3090
+ };
3091
+ function errorMessage2(err, fallback) {
3092
+ if (err instanceof Error) return err.message;
3093
+ if (typeof err === "string") return err;
3094
+ return fallback;
3095
+ }
3096
+ function settle2(call, send, okFalse, onValue) {
3097
+ let pending;
3098
+ try {
3099
+ pending = Promise.resolve(call());
3100
+ } catch (err) {
3101
+ send(okFalse(errorMessage2(err, "serial request failed")));
3102
+ return;
3103
+ }
3104
+ pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage2(err, "serial request failed"))));
3105
+ }
3106
+ function unsupported(resultType, id) {
3107
+ return {
3108
+ type: resultType,
3109
+ id,
3110
+ error: `${resultType.replace(".result", "")} unavailable`
3111
+ };
3112
+ }
3113
+ function createSerialService(options = {}) {
3114
+ return {
3115
+ descriptor: SERIAL_DESCRIPTOR,
3116
+ handleMessage(windowId, message, send) {
3117
+ const id = message.id ?? "";
3118
+ const context = { windowId };
3119
+ if (message.type === "serial.open") {
3120
+ if (!options.open) {
3121
+ send(unsupported("serial.open.result", id));
3122
+ return;
3123
+ }
3124
+ const serialMessage = message;
3125
+ settle2(
3126
+ () => options.open(serialMessage.request, context),
3127
+ send,
3128
+ (error) => ({ type: "serial.open.result", id, error }),
3129
+ (result) => ({ type: "serial.open.result", id, session: result.session })
3130
+ );
3131
+ return;
3132
+ }
3133
+ if (message.type === "serial.write") {
3134
+ if (!options.write) {
3135
+ send(unsupported("serial.write.result", id));
3136
+ return;
3137
+ }
3138
+ const serialMessage = message;
3139
+ settle2(
3140
+ () => options.write(serialMessage.sessionId, serialMessage.data, context),
3141
+ send,
3142
+ (error) => ({ type: "serial.write.result", id, error }),
3143
+ () => ({ type: "serial.write.result", id })
3144
+ );
3145
+ return;
3146
+ }
3147
+ if (message.type === "serial.close") {
3148
+ if (!options.close) {
3149
+ send(unsupported("serial.close.result", id));
3150
+ return;
3151
+ }
3152
+ const serialMessage = message;
3153
+ settle2(
3154
+ () => options.close(serialMessage.sessionId, serialMessage.reason, context),
3155
+ send,
3156
+ (error) => ({ type: "serial.close.result", id, error }),
3157
+ () => ({ type: "serial.close.result", id })
3158
+ );
3159
+ return;
3160
+ }
3161
+ send({
3162
+ type: `${message.type}.error`,
3163
+ id,
3164
+ error: `Unknown serial method: ${message.type}`
3165
+ });
3166
+ },
3167
+ onWindowDestroyed(windowId) {
3168
+ options.destroyWindow?.(windowId);
3169
+ }
3170
+ };
3171
+ }
3172
+
3173
+ // src/ble-service.ts
3174
+ var BLE_SERVICE_VERSION = "1.0.0";
3175
+ var BLE_DESCRIPTOR = {
3176
+ name: "ble",
3177
+ version: BLE_SERVICE_VERSION,
3178
+ description: "NAP-BLE reference handler for shell-mediated BLE/GATT sessions"
3179
+ };
3180
+ function errorMessage3(err, fallback) {
3181
+ if (err instanceof Error) return err.message;
3182
+ if (typeof err === "string") return err;
3183
+ return fallback;
3184
+ }
3185
+ function settle3(call, send, okFalse, onValue) {
3186
+ let pending;
3187
+ try {
3188
+ pending = Promise.resolve(call());
3189
+ } catch (err) {
3190
+ send(okFalse(errorMessage3(err, "ble request failed")));
3191
+ return;
3192
+ }
3193
+ pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage3(err, "ble request failed"))));
3194
+ }
3195
+ function unsupported2(resultType, id) {
3196
+ return {
3197
+ type: resultType,
3198
+ id,
3199
+ error: `${resultType.replace(".result", "")} unavailable`
3200
+ };
3201
+ }
3202
+ function createBleService(options = {}) {
3203
+ return {
3204
+ descriptor: BLE_DESCRIPTOR,
3205
+ handleMessage(windowId, message, send) {
3206
+ const id = message.id ?? "";
3207
+ const context = { windowId };
3208
+ if (message.type === "ble.open") {
3209
+ if (!options.open) {
3210
+ send(unsupported2("ble.open.result", id));
3211
+ return;
3212
+ }
3213
+ const bleMessage = message;
3214
+ settle3(
3215
+ () => options.open(bleMessage.request, context),
3216
+ send,
3217
+ (error) => ({ type: "ble.open.result", id, error }),
3218
+ (result) => ({ type: "ble.open.result", id, session: result.session })
3219
+ );
3220
+ return;
3221
+ }
3222
+ if (message.type === "ble.services") {
3223
+ if (!options.services) {
3224
+ send(unsupported2("ble.services.result", id));
3225
+ return;
3226
+ }
3227
+ const bleMessage = message;
3228
+ settle3(
3229
+ () => options.services(bleMessage.sessionId, context),
3230
+ send,
3231
+ (error) => ({ type: "ble.services.result", id, error }),
3232
+ (services) => ({ type: "ble.services.result", id, services })
3233
+ );
3234
+ return;
3235
+ }
3236
+ if (message.type === "ble.read") {
3237
+ if (!options.read) {
3238
+ send(unsupported2("ble.read.result", id));
3239
+ return;
3240
+ }
3241
+ const bleMessage = message;
3242
+ settle3(
3243
+ () => options.read(bleMessage.sessionId, bleMessage.target, context),
3244
+ send,
3245
+ (error) => ({ type: "ble.read.result", id, error }),
3246
+ (data) => ({ type: "ble.read.result", id, data })
3247
+ );
3248
+ return;
3249
+ }
3250
+ if (message.type === "ble.write") {
3251
+ if (!options.write) {
3252
+ send(unsupported2("ble.write.result", id));
3253
+ return;
3254
+ }
3255
+ const bleMessage = message;
3256
+ settle3(
3257
+ () => options.write(bleMessage.sessionId, bleMessage.target, bleMessage.data, bleMessage.options, context),
3258
+ send,
3259
+ (error) => ({ type: "ble.write.result", id, error }),
3260
+ () => ({ type: "ble.write.result", id })
3261
+ );
3262
+ return;
3263
+ }
3264
+ if (message.type === "ble.subscribe") {
3265
+ if (!options.subscribe) {
3266
+ send(unsupported2("ble.subscribe.result", id));
3267
+ return;
3268
+ }
3269
+ const bleMessage = message;
3270
+ settle3(
3271
+ () => options.subscribe(bleMessage.sessionId, bleMessage.target, context),
3272
+ send,
3273
+ (error) => ({ type: "ble.subscribe.result", id, error }),
3274
+ () => ({ type: "ble.subscribe.result", id })
3275
+ );
3276
+ return;
3277
+ }
3278
+ if (message.type === "ble.unsubscribe") {
3279
+ if (!options.unsubscribe) {
3280
+ send(unsupported2("ble.unsubscribe.result", id));
3281
+ return;
3282
+ }
3283
+ const bleMessage = message;
3284
+ settle3(
3285
+ () => options.unsubscribe(bleMessage.sessionId, bleMessage.target, context),
3286
+ send,
3287
+ (error) => ({ type: "ble.unsubscribe.result", id, error }),
3288
+ () => ({ type: "ble.unsubscribe.result", id })
3289
+ );
3290
+ return;
3291
+ }
3292
+ if (message.type === "ble.close") {
3293
+ if (!options.close) {
3294
+ send(unsupported2("ble.close.result", id));
3295
+ return;
3296
+ }
3297
+ const bleMessage = message;
3298
+ settle3(
3299
+ () => options.close(bleMessage.sessionId, bleMessage.reason, context),
3300
+ send,
3301
+ (error) => ({ type: "ble.close.result", id, error }),
3302
+ () => ({ type: "ble.close.result", id })
3303
+ );
3304
+ return;
3305
+ }
3306
+ send({
3307
+ type: `${message.type}.error`,
3308
+ id,
3309
+ error: `Unknown ble method: ${message.type}`
3310
+ });
3311
+ },
3312
+ onWindowDestroyed(windowId) {
3313
+ options.destroyWindow?.(windowId);
3314
+ }
3315
+ };
3316
+ }
3317
+
3318
+ // src/webrtc-service.ts
3319
+ var WEBRTC_SERVICE_VERSION = "1.0.0";
3320
+ var WEBRTC_DESCRIPTOR = {
3321
+ name: "webrtc",
3322
+ version: WEBRTC_SERVICE_VERSION,
3323
+ description: "NAP-WEBRTC reference handler for shell-mediated WebRTC sessions"
3324
+ };
3325
+ function errorMessage4(err, fallback) {
3326
+ if (err instanceof Error) return err.message;
3327
+ if (typeof err === "string") return err;
3328
+ return fallback;
3329
+ }
3330
+ function settle4(call, send, okFalse, onValue) {
3331
+ let pending;
3332
+ try {
3333
+ pending = Promise.resolve(call());
3334
+ } catch (err) {
3335
+ send(okFalse(errorMessage4(err, "webrtc request failed")));
3336
+ return;
3337
+ }
3338
+ pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage4(err, "webrtc request failed"))));
3339
+ }
3340
+ function unsupported3(resultType, id) {
3341
+ return {
3342
+ type: resultType,
3343
+ id,
3344
+ error: `${resultType.replace(".result", "")} unavailable`
3345
+ };
3346
+ }
3347
+ function createContext(windowId, send) {
3348
+ return {
3349
+ windowId,
3350
+ emit(event) {
3351
+ send({ type: "webrtc.event", event });
3352
+ }
3353
+ };
3354
+ }
3355
+ function createWebrtcService(options = {}) {
3356
+ return {
3357
+ descriptor: WEBRTC_DESCRIPTOR,
3358
+ handleMessage(windowId, message, send) {
3359
+ const id = message.id ?? "";
3360
+ const context = createContext(windowId, send);
3361
+ if (message.type === "webrtc.open") {
3362
+ if (!options.open) {
3363
+ send(unsupported3("webrtc.open.result", id));
3364
+ return;
3365
+ }
3366
+ const webrtcMessage = message;
3367
+ settle4(
3368
+ () => options.open(webrtcMessage.request, context),
3369
+ send,
3370
+ (error) => ({ type: "webrtc.open.result", id, error }),
3371
+ (result) => ({ type: "webrtc.open.result", id, session: result.session })
3372
+ );
3373
+ return;
3374
+ }
3375
+ if (message.type === "webrtc.send") {
3376
+ if (!options.send) {
3377
+ send(unsupported3("webrtc.send.result", id));
3378
+ return;
3379
+ }
3380
+ const webrtcMessage = message;
3381
+ settle4(
3382
+ () => options.send(webrtcMessage.sessionId, webrtcMessage.payload, context),
3383
+ send,
3384
+ (error) => ({ type: "webrtc.send.result", id, error }),
3385
+ () => ({ type: "webrtc.send.result", id })
3386
+ );
3387
+ return;
3388
+ }
3389
+ if (message.type === "webrtc.close") {
3390
+ if (!options.close) {
3391
+ send(unsupported3("webrtc.close.result", id));
3392
+ return;
3393
+ }
3394
+ const webrtcMessage = message;
3395
+ settle4(
3396
+ () => options.close(webrtcMessage.sessionId, webrtcMessage.reason, context),
3397
+ send,
3398
+ (error) => ({ type: "webrtc.close.result", id, error }),
3399
+ () => ({ type: "webrtc.close.result", id })
3400
+ );
3401
+ return;
3402
+ }
3403
+ send({
3404
+ type: `${message.type}.error`,
3405
+ id,
3406
+ error: `Unknown webrtc method: ${message.type}`
3407
+ });
3408
+ },
3409
+ onWindowDestroyed(windowId) {
3410
+ options.destroyWindow?.(windowId);
3411
+ }
3412
+ };
3413
+ }
3414
+
3415
+ // src/common-service.ts
3416
+ import {
3417
+ decode,
3418
+ encodeBytes,
3419
+ naddrEncode,
3420
+ neventEncode,
3421
+ noteEncode,
3422
+ nprofileEncode,
3423
+ npubEncode
3424
+ } from "nostr-tools/nip19";
3425
+ var COMMON_SERVICE_VERSION = "1.0.0";
3426
+ var BECH32_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
3427
+ var COMMON_DESCRIPTOR = {
3428
+ name: "common",
3429
+ version: COMMON_SERVICE_VERSION,
3430
+ description: "NAP-COMMON reference handler for shell-mediated social helpers"
3431
+ };
3432
+ function errorMessage5(err, fallback) {
3433
+ if (err instanceof Error) return err.message;
3434
+ if (typeof err === "string") return err;
3435
+ return fallback;
3436
+ }
3437
+ function encodeNip19(input) {
3438
+ try {
3439
+ switch (input.type) {
3440
+ case "npub":
3441
+ return { ok: true, value: npubEncode(input.hex), nip19Type: input.type };
3442
+ case "note":
3443
+ return { ok: true, value: noteEncode(input.hex), nip19Type: input.type };
3444
+ case "nprofile":
3445
+ return { ok: true, value: nprofileEncode({ pubkey: input.pubkey, relays: input.relays }), nip19Type: input.type };
3446
+ case "nevent":
3447
+ return {
3448
+ ok: true,
3449
+ value: neventEncode({ id: input.eventId, relays: input.relays, author: input.author, kind: input.kind }),
3450
+ nip19Type: input.type
3451
+ };
3452
+ case "naddr":
3453
+ return {
3454
+ ok: true,
3455
+ value: naddrEncode({ identifier: input.identifier, pubkey: input.pubkey, kind: input.kind, relays: input.relays }),
3456
+ nip19Type: input.type
3457
+ };
3458
+ case "nrelay":
3459
+ return { ok: true, value: encodeBytes("nrelay", new TextEncoder().encode(input.relay)), nip19Type: input.type };
3460
+ default:
3461
+ return { ok: false, error: "unsupported NIP-19 type" };
3462
+ }
3463
+ } catch (err) {
3464
+ return { ok: false, error: errorMessage5(err, "NIP-19 encode failed") };
3465
+ }
3466
+ }
3467
+ function decodeNip19Value(value) {
3468
+ try {
3469
+ const decoded = decode(value);
3470
+ switch (decoded.type) {
3471
+ case "npub":
3472
+ case "note":
3473
+ return { ok: true, nip19Type: decoded.type, hex: decoded.data };
3474
+ case "nprofile":
3475
+ return { ok: true, nip19Type: decoded.type, pubkey: decoded.data.pubkey, relays: decoded.data.relays };
3476
+ case "nevent":
3477
+ return {
3478
+ ok: true,
3479
+ nip19Type: decoded.type,
3480
+ eventId: decoded.data.id,
3481
+ relays: decoded.data.relays,
3482
+ author: decoded.data.author,
3483
+ kind: decoded.data.kind
3484
+ };
3485
+ case "naddr":
3486
+ return {
3487
+ ok: true,
3488
+ nip19Type: decoded.type,
3489
+ identifier: decoded.data.identifier,
3490
+ pubkey: decoded.data.pubkey,
3491
+ kind: decoded.data.kind,
3492
+ relays: decoded.data.relays
3493
+ };
3494
+ default:
3495
+ return { ok: false, error: "unsupported NIP-19 type" };
3496
+ }
3497
+ } catch (err) {
3498
+ if (value.startsWith("nrelay1")) return decodeNrelay(value);
3499
+ return { ok: false, error: errorMessage5(err, "NIP-19 decode failed") };
3500
+ }
3501
+ }
3502
+ function decodeNrelay(value) {
3503
+ try {
3504
+ const separator = value.lastIndexOf("1");
3505
+ if (separator <= 0 || separator + 7 > value.length) return { ok: false, error: "invalid nrelay value" };
3506
+ const dataPart = value.slice(separator + 1);
3507
+ const words = [...dataPart].map((char) => {
3508
+ const index = BECH32_CHARSET.indexOf(char);
3509
+ if (index === -1) throw new Error("invalid nrelay character");
3510
+ return index;
3511
+ });
3512
+ const payloadWords = words.slice(0, -6);
3513
+ const bytes = convertBits(payloadWords, 5, 8, false);
3514
+ return { ok: true, nip19Type: "nrelay", relay: new TextDecoder().decode(Uint8Array.from(bytes)) };
3515
+ } catch (err) {
3516
+ return { ok: false, error: errorMessage5(err, "invalid nrelay value") };
3517
+ }
3518
+ }
3519
+ function convertBits(data, fromBits, toBits, pad) {
3520
+ let acc = 0;
3521
+ let bits = 0;
3522
+ const result = [];
3523
+ const maxv = (1 << toBits) - 1;
3524
+ const maxAcc = (1 << fromBits + toBits - 1) - 1;
3525
+ for (const value of data) {
3526
+ if (value < 0 || value >> fromBits !== 0) throw new Error("invalid bech32 data");
3527
+ acc = (acc << fromBits | value) & maxAcc;
3528
+ bits += fromBits;
3529
+ while (bits >= toBits) {
3530
+ bits -= toBits;
3531
+ result.push(acc >> bits & maxv);
3532
+ }
3533
+ }
3534
+ if (pad) {
3535
+ if (bits > 0) result.push(acc << toBits - bits & maxv);
3536
+ } else if (bits >= fromBits || (acc << toBits - bits & maxv) !== 0) {
3537
+ throw new Error("invalid bech32 padding");
3538
+ }
3539
+ return result;
3540
+ }
3541
+ function settle5(call, send, okFalse, onValue) {
3542
+ let pending;
3543
+ try {
3544
+ pending = Promise.resolve(call());
3545
+ } catch (err) {
3546
+ send(okFalse(errorMessage5(err, "common request failed")));
3547
+ return;
3548
+ }
3549
+ pending.then((value) => send(onValue(value))).catch((err) => send(okFalse(errorMessage5(err, "common request failed"))));
3550
+ }
3551
+ function createCommonService(options = {}) {
3552
+ return {
3553
+ descriptor: COMMON_DESCRIPTOR,
3554
+ handleMessage(windowId, message, send) {
3555
+ const id = message.id ?? "";
3556
+ const context = { windowId };
3557
+ if (message.type === "common.encodeNip19") {
3558
+ const input = message.input;
3559
+ const result = input ? encodeNip19(input) : { ok: false, error: "invalid input" };
3560
+ send({ type: "common.encodeNip19.result", id, ...result });
3561
+ return;
3562
+ }
3563
+ if (message.type === "common.decodeNip19") {
3564
+ const value = message.value;
3565
+ const result = typeof value === "string" ? decodeNip19Value(value) : { ok: false, error: "invalid value" };
3566
+ send({ type: "common.decodeNip19.result", id, ...result });
3567
+ return;
3568
+ }
3569
+ if (message.type === "common.getProfile") {
3570
+ const target = message.target;
3571
+ if (!options.getProfile) {
3572
+ send({ type: "common.getProfile.result", id, ok: false, pubkey: target ?? "", error: "profile lookup unavailable" });
3573
+ return;
3574
+ }
3575
+ settle5(
3576
+ () => options.getProfile(target, context),
3577
+ send,
3578
+ (error) => ({ type: "common.getProfile.result", id, ok: false, pubkey: target ?? "", error }),
3579
+ (result) => ({ type: "common.getProfile.result", id, ...result })
3580
+ );
3581
+ return;
3582
+ }
3583
+ if (message.type === "common.follows") {
3584
+ if (!options.follows) {
3585
+ send({ type: "common.follows.result", id, ok: false, pubkeys: [], error: "follows lookup unavailable" });
3586
+ return;
3587
+ }
3588
+ settle5(
3589
+ () => options.follows(context),
3590
+ send,
3591
+ (error) => ({ type: "common.follows.result", id, ok: false, pubkeys: [], error }),
3592
+ (result) => ({ type: "common.follows.result", id, ...result })
3593
+ );
3594
+ return;
3595
+ }
3596
+ if (message.type === "common.follow") {
3597
+ const pubkeys = message.pubkeys ?? [];
3598
+ settleAction(options.follow, pubkeys, context, send, "common.follow.result", id, "follow unavailable");
3599
+ return;
3600
+ }
3601
+ if (message.type === "common.unfollow") {
3602
+ const pubkeys = message.pubkeys ?? [];
3603
+ settleAction(options.unfollow, pubkeys, context, send, "common.unfollow.result", id, "unfollow unavailable");
3604
+ return;
3605
+ }
3606
+ if (message.type === "common.react") {
3607
+ const m = message;
3608
+ if (!options.react) {
3609
+ send({ type: "common.react.result", id, ok: false, error: "react unavailable" });
3610
+ return;
3611
+ }
3612
+ settle5(
3613
+ () => options.react(m.targetEventId, m.reaction, m.customEmojiHref, context),
3614
+ send,
3615
+ (error) => ({ type: "common.react.result", id, ok: false, error }),
3616
+ (result) => ({ type: "common.react.result", id, ...result })
3617
+ );
3618
+ return;
3619
+ }
3620
+ if (message.type === "common.report") {
3621
+ const m = message;
3622
+ if (!options.report) {
3623
+ send({ type: "common.report.result", id, ok: false, error: "report unavailable" });
3624
+ return;
3625
+ }
3626
+ settle5(
3627
+ () => options.report(m.target, m.reason, m.text, context),
3628
+ send,
3629
+ (error) => ({ type: "common.report.result", id, ok: false, error }),
3630
+ (result) => ({ type: "common.report.result", id, ...result })
3631
+ );
3632
+ return;
3633
+ }
3634
+ send({
3635
+ type: `${message.type}.error`,
3636
+ id,
3637
+ error: `Unknown common method: ${message.type}`
3638
+ });
3639
+ },
3640
+ onWindowDestroyed(_windowId) {
3641
+ }
3642
+ };
3643
+ }
3644
+ function settleAction(action, pubkeys, context, send, resultType, id, unavailable) {
3645
+ if (!action) {
3646
+ send({ type: resultType, id, ok: false, error: unavailable });
3647
+ return;
3648
+ }
3649
+ settle5(
3650
+ () => action(pubkeys, context),
3651
+ send,
3652
+ (error) => ({ type: resultType, id, ok: false, error }),
3653
+ (result) => ({ type: resultType, id, ...result })
3654
+ );
3655
+ }
2813
3656
  export {
2814
3657
  createAudioService,
3658
+ createBleService,
2815
3659
  createBrowserMediaBridge,
2816
3660
  createCacheService,
2817
3661
  createCatalogIntentResolver,
3662
+ createCommonService,
2818
3663
  createConfigService,
2819
3664
  createCoordinatedRelay,
2820
3665
  createCvmService,
@@ -2822,6 +3667,8 @@ export {
2822
3667
  createIdentityService,
2823
3668
  createIntentService,
2824
3669
  createKeysService,
3670
+ createLinkService,
3671
+ createListsService,
2825
3672
  createMediaService,
2826
3673
  createNotificationService,
2827
3674
  createNotifyService,
@@ -2829,8 +3676,10 @@ export {
2829
3676
  createRelayPoolOutboxRouter,
2830
3677
  createRelayPoolService,
2831
3678
  createResourceService,
3679
+ createSerialService,
2832
3680
  createThemeService,
2833
3681
  createUploadService,
3682
+ createWebrtcService,
2834
3683
  manifestToIntentCatalogEntry
2835
3684
  };
2836
3685
  //# sourceMappingURL=index.js.map