@kehto/shell 0.16.7 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -35,7 +35,7 @@ function createRelayPoolAdapter(shellHooks, originRegistry2) {
35
35
  const pool = shellHooks.relayPool.getRelayPool();
36
36
  if (!pool) return;
37
37
  const relayUrls = shellHooks.relayPool.selectRelayTier([]);
38
- pool.publish(relayUrls, event);
38
+ return pool.publish(relayUrls, event);
39
39
  },
40
40
  selectRelayTier(filters) {
41
41
  return shellHooks.relayPool.selectRelayTier(filters);
@@ -392,6 +392,19 @@ var originRegistry = {
392
392
  if (!entry?.dTag || !entry?.aggregateHash) return void 0;
393
393
  return { dTag: entry.dTag, aggregateHash: entry.aggregateHash };
394
394
  },
395
+ /** Store the immutable environment captured for this exact frame registration. */
396
+ setEnvironment(win, environment) {
397
+ const entry = registry.get(win);
398
+ if (!entry) return;
399
+ entry.environment = Object.freeze({
400
+ capabilities: Object.freeze({ domains: Object.freeze([...environment.capabilities.domains]) }),
401
+ services: Object.freeze([...environment.services])
402
+ });
403
+ },
404
+ /** Return the immutable environment captured before this frame executes. */
405
+ getEnvironment(win) {
406
+ return registry.get(win)?.environment;
407
+ },
395
408
  /**
396
409
  * Look up the monotonically increasing registration id for a Window.
397
410
  *
@@ -961,144 +974,154 @@ var audioManager = {
961
974
  }
962
975
  };
963
976
 
964
- // src/keys-forwarder.ts
965
- function createKeysForwarder(deps) {
966
- const target = deps.target ?? (typeof window !== "undefined" ? window : new EventTarget());
967
- const listener = (ev) => {
968
- const ke = ev;
969
- const entries = deps.sessionRegistry.getAllEntries();
970
- for (const entry of entries) {
971
- if (!deps.hasKeysForwardCap(entry.pubkey)) continue;
972
- const iframe = deps.originRegistry.getIframeWindow(entry.windowId);
973
- if (!iframe) continue;
974
- const envelope = {
975
- type: "keys.forward",
976
- key: ke.key ?? "",
977
- code: ke.code ?? "",
978
- ctrl: ke.ctrlKey ?? false,
979
- alt: ke.altKey ?? false,
980
- shift: ke.shiftKey ?? false,
981
- meta: ke.metaKey ?? false
982
- };
983
- iframe.postMessage(envelope, "*");
984
- }
985
- };
986
- target.addEventListener("keydown", listener);
987
- return {
988
- destroy() {
989
- target.removeEventListener("keydown", listener);
990
- }
991
- };
992
- }
993
-
994
977
  // src/shell-init.ts
995
- var NAP_DOMAINS = [
978
+ var RUNTIME_NATIVE_DOMAINS = [
979
+ "relay",
996
980
  "identity",
997
981
  "storage",
998
982
  "inc",
999
983
  "theme",
1000
984
  "keys",
1001
985
  "media",
1002
- "notify",
986
+ "notify"
987
+ ];
988
+ var SERVICE_ONLY_DOMAINS = [
989
+ "outbox",
1003
990
  "config",
1004
991
  "resource",
1005
- "cvm"
1006
- ];
1007
- var NAP_INC_PROTOCOLS = [
1008
- "inc:NAP-01",
1009
- "inc:NAP-02",
1010
- "inc:NAP-03",
1011
- "inc:NAP-04",
1012
- "inc:NAP-05",
1013
- "inc:NAP-06"
992
+ "cvm",
993
+ "upload",
994
+ "intent",
995
+ "link",
996
+ "common",
997
+ "lists",
998
+ "serial",
999
+ "ble",
1000
+ "webrtc",
1001
+ "dm",
1002
+ "count"
1014
1003
  ];
1015
1004
  function buildShellCapabilities(hooks) {
1016
- const domains = hooks.relayPool ? ["relay", "outbox", ...NAP_DOMAINS] : [...NAP_DOMAINS];
1017
- if (hooks.upload) domains.push("upload");
1018
- if (hooks.intent?.isAvailable()) domains.push("intent");
1019
- if (hooks.services?.count) domains.push("count");
1020
- if (hooks.link?.isAvailable()) domains.push("link");
1021
- if (hooks.common?.isAvailable()) domains.push("common");
1022
- if (hooks.lists?.isAvailable()) domains.push("lists");
1023
- if (hooks.serial?.isAvailable()) domains.push("serial");
1024
- if (hooks.ble?.isAvailable()) domains.push("ble");
1025
- if (hooks.webrtc?.isAvailable()) domains.push("webrtc");
1026
- if (hooks.dm) domains.push("dm");
1027
- const sandbox = [];
1028
- const protocols = {};
1029
- for (const entry of NAP_INC_PROTOCOLS) {
1030
- const [domain, protocol] = entry.split(":");
1031
- (protocols[domain] ??= []).push(protocol);
1032
- }
1033
- const naps = hooks.relayPool ? ["relay", "outbox", ...NAP_DOMAINS, ...NAP_INC_PROTOCOLS] : [...NAP_DOMAINS, ...NAP_INC_PROTOCOLS];
1034
- if (hooks.upload) naps.push("upload");
1035
- if (hooks.intent?.isAvailable()) naps.push("intent");
1036
- if (hooks.services?.count) naps.push("count");
1037
- if (hooks.link?.isAvailable()) naps.push("link");
1038
- if (hooks.common?.isAvailable()) naps.push("common");
1039
- if (hooks.lists?.isAvailable()) naps.push("lists");
1040
- if (hooks.serial?.isAvailable()) naps.push("serial");
1041
- if (hooks.ble?.isAvailable()) naps.push("ble");
1042
- if (hooks.webrtc?.isAvailable()) naps.push("webrtc");
1043
- if (hooks.dm) naps.push("dm");
1044
- return applyCapabilityOverrides(
1045
- { domains, protocols, naps, sandbox },
1046
- hooks.capabilities?.disabledDomains ?? []
1047
- );
1005
+ const disabled = new Set(hooks.capabilities?.disabledDomains ?? []);
1006
+ const domains = RUNTIME_NATIVE_DOMAINS.filter((domain) => !disabled.has(domain));
1007
+ for (const domain of SERVICE_ONLY_DOMAINS) {
1008
+ if (!disabled.has(domain) && isServiceDomainLive(hooks, domain)) domains.push(domain);
1009
+ }
1010
+ return Object.freeze({ domains: freezeList(domains) });
1048
1011
  }
1049
- function applyCapabilityOverrides(capabilities, disabledDomains) {
1050
- if (disabledDomains.length === 0) return capabilities;
1051
- const disabled = new Set(disabledDomains);
1052
- const protocols = {};
1053
- for (const [domain, supportedProtocols] of Object.entries(capabilities.protocols)) {
1054
- if (!disabled.has(domain)) protocols[domain] = supportedProtocols;
1012
+ function resolveShellEnvironment(hooks, identity) {
1013
+ const capabilities = buildShellCapabilities(hooks);
1014
+ const disabled = new Set(hooks.capabilities?.disabledDomains ?? []);
1015
+ const services = getLiveServices(hooks, capabilities.domains, disabled);
1016
+ const available = Object.freeze({
1017
+ domains: freezeList(capabilities.domains),
1018
+ services: freezeList(services)
1019
+ });
1020
+ const granted = hooks.capabilities?.resolveEnvironment?.(identity, available) ?? available;
1021
+ return Object.freeze({
1022
+ capabilities: Object.freeze({
1023
+ domains: freezeList(intersectInOrder(available.domains, granted.domains))
1024
+ }),
1025
+ services: freezeList(intersectInOrder(available.services, granted.services))
1026
+ });
1027
+ }
1028
+ function isServiceDomainLive(hooks, domain) {
1029
+ if (!hooks.services?.[domain]) return false;
1030
+ switch (domain) {
1031
+ case "outbox":
1032
+ return Boolean(hooks.relayPool);
1033
+ case "upload":
1034
+ return hooks.upload?.getUploader() !== null && hooks.upload?.getUploader() !== void 0;
1035
+ case "intent":
1036
+ return hooks.intent?.isAvailable() === true;
1037
+ case "link":
1038
+ return hooks.link?.isAvailable() === true;
1039
+ case "common":
1040
+ return hooks.common?.isAvailable() === true;
1041
+ case "lists":
1042
+ return hooks.lists?.isAvailable() === true;
1043
+ case "serial":
1044
+ return hooks.serial?.isAvailable() === true;
1045
+ case "ble":
1046
+ return hooks.ble?.isAvailable() === true;
1047
+ case "webrtc":
1048
+ return hooks.webrtc?.isAvailable() === true;
1049
+ case "dm":
1050
+ return hooks.dm !== void 0;
1051
+ default:
1052
+ return true;
1055
1053
  }
1056
- return {
1057
- domains: capabilities.domains.filter((entry) => !disabled.has(entry)),
1058
- protocols,
1059
- naps: capabilities.naps.filter((entry) => !disabled.has(entry.split(":")[0] ?? entry)),
1060
- sandbox: capabilities.sandbox
1061
- };
1054
+ }
1055
+ function getLiveServices(hooks, domains, disabled) {
1056
+ const liveDomains = new Set(domains);
1057
+ return Object.keys(hooks.services ?? {}).filter((name) => {
1058
+ if (disabled.has(name)) return false;
1059
+ return !SERVICE_ONLY_DOMAINS.includes(name) || liveDomains.has(name);
1060
+ });
1061
+ }
1062
+ function intersectInOrder(available, requested) {
1063
+ const requestedSet = new Set(requested);
1064
+ return available.filter((entry) => requestedSet.has(entry));
1065
+ }
1066
+ function freezeList(values) {
1067
+ return Object.freeze([...values]);
1062
1068
  }
1063
1069
 
1064
1070
  // src/shell-ready.ts
1065
- var initSent = /* @__PURE__ */ new WeakMap();
1071
+ function createShellReadyState() {
1072
+ const initSent = /* @__PURE__ */ new WeakMap();
1073
+ const sessionRegistration = /* @__PURE__ */ new Map();
1074
+ const environments = /* @__PURE__ */ new Map();
1075
+ return {
1076
+ initSent,
1077
+ sessionRegistration,
1078
+ environments,
1079
+ isDomainAllowed(windowId, domain) {
1080
+ return environments.get(windowId)?.capabilities.domains.includes(domain) ?? false;
1081
+ },
1082
+ clear() {
1083
+ sessionRegistration.clear();
1084
+ environments.clear();
1085
+ }
1086
+ };
1087
+ }
1066
1088
  function handleShellReady({
1067
1089
  hooks,
1068
1090
  origin,
1069
1091
  runtime,
1092
+ state,
1070
1093
  sourceRegistrationId,
1071
1094
  sourceWindow,
1072
1095
  windowId
1073
1096
  }) {
1074
- registerNip5dSessionIfNeeded({
1075
- hooks,
1076
- origin,
1077
- runtime,
1078
- sourceRegistrationId,
1079
- sourceWindow,
1080
- windowId
1081
- });
1082
- if (initSent.get(sourceWindow) === sourceRegistrationId) {
1097
+ if (state.initSent.get(sourceWindow) === sourceRegistrationId) {
1083
1098
  return;
1084
1099
  }
1085
- const capabilities = buildShellCapabilities(hooks);
1086
- postShellInit(sourceWindow, capabilities, Object.keys(hooks.services ?? {}));
1087
- initSent.set(sourceWindow, sourceRegistrationId);
1100
+ const identity = resolveNip5dIdentity(hooks, windowId, sourceWindow);
1101
+ if (!identity) return;
1102
+ registerNip5dSessionIfNeeded({ origin, runtime, state, sourceRegistrationId, windowId, identity });
1103
+ const environment = originRegistry.getEnvironment(sourceWindow) ?? resolveShellEnvironment(hooks, identity);
1104
+ state.environments.set(windowId, environment);
1105
+ postShellInit(sourceWindow, environment);
1106
+ state.initSent.set(sourceWindow, sourceRegistrationId);
1088
1107
  }
1089
1108
  function registerNip5dSessionIfNeeded({
1090
- hooks,
1091
1109
  origin,
1092
1110
  runtime,
1093
- windowId
1111
+ state,
1112
+ sourceRegistrationId,
1113
+ windowId,
1114
+ identity
1094
1115
  }) {
1095
- if (runtime.sessionRegistry.getEntryByWindowId(windowId)) {
1116
+ const existing = runtime.sessionRegistry.getEntryByWindowId(windowId);
1117
+ const previousRegistration = state.sessionRegistration.get(windowId);
1118
+ if (existing && previousRegistration === sourceRegistrationId) {
1096
1119
  return;
1097
1120
  }
1098
- const identity = resolveNip5dIdentity(hooks, windowId);
1099
- if (!identity) {
1100
- return;
1121
+ if (existing && previousRegistration !== void 0) {
1122
+ runtime.sessionRegistry.unregister(windowId);
1101
1123
  }
1124
+ if (existing && previousRegistration === void 0) return;
1102
1125
  const entry = {
1103
1126
  pubkey: "",
1104
1127
  windowId,
@@ -1111,33 +1134,30 @@ function registerNip5dSessionIfNeeded({
1111
1134
  provenance: "nip-5d"
1112
1135
  };
1113
1136
  runtime.sessionRegistry.register(windowId, entry);
1137
+ state.sessionRegistration.set(windowId, sourceRegistrationId);
1114
1138
  }
1115
- function resolveNip5dIdentity(hooks, windowId) {
1139
+ function resolveNip5dIdentity(hooks, windowId, sourceWindow) {
1116
1140
  const hookIdentity = hooks.onNip5dIframeCreate?.(windowId);
1117
1141
  if (hookIdentity !== null && hookIdentity !== void 0) {
1118
- return {
1142
+ return Object.freeze({
1119
1143
  dTag: hookIdentity.dTag,
1120
1144
  aggregateHash: hookIdentity.aggregateHash
1121
- };
1122
- }
1123
- const win = originRegistry.getIframeWindow(windowId);
1124
- if (!win) {
1125
- return null;
1145
+ });
1126
1146
  }
1127
- const originIdentity = originRegistry.getIdentity(win);
1147
+ const originIdentity = originRegistry.getIdentity(sourceWindow);
1128
1148
  if (!originIdentity) {
1129
1149
  return null;
1130
1150
  }
1131
- return {
1151
+ return Object.freeze({
1132
1152
  dTag: originIdentity.dTag,
1133
1153
  aggregateHash: originIdentity.aggregateHash
1134
- };
1154
+ });
1135
1155
  }
1136
- function postShellInit(win, capabilities, services) {
1156
+ function postShellInit(win, environment) {
1137
1157
  const initMsg = {
1138
1158
  type: "shell.init",
1139
- capabilities,
1140
- services
1159
+ capabilities: environment.capabilities,
1160
+ services: environment.services
1141
1161
  };
1142
1162
  win.postMessage(initMsg, "*");
1143
1163
  }
@@ -1153,6 +1173,7 @@ function reportUnrouted(hooks, event, reason) {
1153
1173
  }
1154
1174
  }
1155
1175
  function createShellBridge(hooks) {
1176
+ const shellReadyState = createShellReadyState();
1156
1177
  const runtimeHooks = adaptHooks(hooks, {
1157
1178
  originRegistry,
1158
1179
  manifestCache,
@@ -1160,32 +1181,20 @@ function createShellBridge(hooks) {
1160
1181
  audioManager,
1161
1182
  nappKeyRegistry
1162
1183
  });
1163
- const runtime = createRuntime(runtimeHooks);
1164
- function broadcastToNapplets(envelope) {
1165
- const windowIds = originRegistry.getAllWindowIds();
1166
- for (const windowId of windowIds) {
1167
- const win = originRegistry.getIframeWindow(windowId);
1184
+ const runtime = createRuntime({
1185
+ ...runtimeHooks,
1186
+ isDomainAllowed: shellReadyState.isDomainAllowed
1187
+ });
1188
+ function publishToEligibleNapplets(envelope, domain, capability) {
1189
+ for (const entry of runtime.sessionRegistry.getAllEntries()) {
1190
+ const environment = shellReadyState.environments.get(entry.windowId);
1191
+ if (!environment?.capabilities.domains.includes(domain)) continue;
1192
+ if (!runtime.aclState.check(entry.pubkey, entry.dTag, entry.aggregateHash, capability)) continue;
1193
+ const win = originRegistry.getIframeWindow(entry.windowId);
1168
1194
  if (!win) continue;
1169
1195
  win.postMessage(envelope, "*");
1170
1196
  }
1171
1197
  }
1172
- let keysForwarder = null;
1173
- if (typeof window !== "undefined") {
1174
- try {
1175
- keysForwarder = createKeysForwarder({
1176
- originRegistry,
1177
- sessionRegistry,
1178
- hasKeysForwardCap: (pubkey) => {
1179
- const entry = sessionRegistry.getEntry(pubkey);
1180
- if (!entry) return false;
1181
- const acl = aclStore.getEntry(entry.pubkey, entry.dTag, entry.aggregateHash);
1182
- return acl?.capabilities.includes("keys:forward") ?? false;
1183
- }
1184
- });
1185
- } catch {
1186
- keysForwarder = null;
1187
- }
1188
- }
1189
1198
  return {
1190
1199
  handleMessage(event) {
1191
1200
  const sourceWindow = event.source;
@@ -1205,6 +1214,7 @@ function createShellBridge(hooks) {
1205
1214
  hooks,
1206
1215
  origin: event.origin,
1207
1216
  runtime,
1217
+ state: shellReadyState,
1208
1218
  sourceRegistrationId: originRegistry.getRegistrationId(sourceWindow) ?? 0,
1209
1219
  sourceWindow,
1210
1220
  windowId
@@ -1217,19 +1227,19 @@ function createShellBridge(hooks) {
1217
1227
  runtime.injectEvent(topic, payload);
1218
1228
  },
1219
1229
  destroy() {
1220
- keysForwarder?.destroy();
1221
1230
  runtime.destroy();
1231
+ shellReadyState.clear();
1222
1232
  },
1223
1233
  registerConsentHandler(handler) {
1224
1234
  runtime.registerConsentHandler(handler);
1225
1235
  },
1226
1236
  publishTheme(theme) {
1227
1237
  const envelope = { type: "theme.changed", theme };
1228
- broadcastToNapplets(envelope);
1238
+ publishToEligibleNapplets(envelope, "theme", "theme:read");
1229
1239
  },
1230
1240
  publishIdentityChanged(pubkey) {
1231
1241
  const envelope = { type: "identity.changed", pubkey };
1232
- broadcastToNapplets(envelope);
1242
+ publishToEligibleNapplets(envelope, "identity", "identity:read");
1233
1243
  },
1234
1244
  get runtime() {
1235
1245
  return runtime;
@@ -1255,7 +1265,7 @@ function scriptJson(value) {
1255
1265
  return JSON.stringify(value).replace(/</g, "\\u003c");
1256
1266
  }
1257
1267
  function renderNappletNamespacePrelude(options) {
1258
- const domains = uniqueBareDomains(options.domains);
1268
+ const domains = uniqueBareDomains(["shell", ...options.domains]);
1259
1269
  return `<script data-kehto-nip5d-injection>(${nappletNamespacePrelude.toString()})(${scriptJson(domains)});</script>`;
1260
1270
  }
1261
1271
  function injectNappletNamespacePrelude(html, options) {
@@ -1324,6 +1334,41 @@ function nappletNamespacePrelude(domains) {
1324
1334
  function fire(message) {
1325
1335
  post(message);
1326
1336
  }
1337
+ function normalizeConventionUri(topic, hasExplicitPayload) {
1338
+ if (typeof topic !== "string") throw new TypeError("INC topic must be text");
1339
+ if (!topic.startsWith("napplet:")) return { topic };
1340
+ if (topic.includes("#")) throw new TypeError("Convention topics cannot contain fragments");
1341
+ const queryStart = topic.indexOf("?");
1342
+ if (queryStart === -1) {
1343
+ return { topic };
1344
+ }
1345
+ if (hasExplicitPayload) {
1346
+ throw new TypeError("Convention queries cannot be combined with an explicit payload");
1347
+ }
1348
+ const values = {};
1349
+ const names = /* @__PURE__ */ new Set();
1350
+ const query = topic.slice(queryStart + 1);
1351
+ if (query) {
1352
+ for (const pair of query.split("&")) {
1353
+ const separator = pair.indexOf("=");
1354
+ if (separator < 0) {
1355
+ throw new TypeError("Convention query parameters must use name=value form");
1356
+ }
1357
+ let name;
1358
+ let value;
1359
+ try {
1360
+ name = decodeURIComponent(pair.slice(0, separator));
1361
+ value = decodeURIComponent(pair.slice(separator + 1));
1362
+ } catch {
1363
+ throw new TypeError("Convention query contains malformed percent encoding");
1364
+ }
1365
+ if (names.has(name)) throw new TypeError("Convention query names must be unique");
1366
+ names.add(name);
1367
+ values[name] = value;
1368
+ }
1369
+ }
1370
+ return { topic: topic.slice(0, queryStart), payload: values };
1371
+ }
1327
1372
  function resultOrError(msg) {
1328
1373
  return msg;
1329
1374
  }
@@ -1349,6 +1394,67 @@ function nappletNamespacePrelude(domains) {
1349
1394
  }
1350
1395
  };
1351
1396
  }
1397
+ function makeShell() {
1398
+ const emptyServices = Object.freeze([]);
1399
+ const readyHandlers = /* @__PURE__ */ new Set();
1400
+ let environment;
1401
+ let resolveReady = () => void 0;
1402
+ const ready = new Promise((resolve) => {
1403
+ resolveReady = resolve;
1404
+ });
1405
+ const off = listen((event) => {
1406
+ if (!isParentMessage(event) || environment) return;
1407
+ const incoming = event.data;
1408
+ if (typeof incoming !== "object" || incoming === null) return;
1409
+ const message = incoming;
1410
+ if (message.type !== "shell.init") return;
1411
+ const rawCapabilities = message.capabilities;
1412
+ const rawDomains = typeof rawCapabilities === "object" && rawCapabilities !== null ? rawCapabilities.domains : void 0;
1413
+ if (!Array.isArray(rawDomains)) return;
1414
+ const domains2 = freezeStrings(rawDomains);
1415
+ const services = freezeStrings(Array.isArray(message.services) ? message.services : []);
1416
+ environment = Object.freeze({
1417
+ capabilities: Object.freeze({ domains: domains2 }),
1418
+ services
1419
+ });
1420
+ off();
1421
+ resolveReady(environment);
1422
+ for (const handler of readyHandlers) handler(environment);
1423
+ readyHandlers.clear();
1424
+ });
1425
+ function freezeStrings(values) {
1426
+ const seen = /* @__PURE__ */ new Set();
1427
+ const strings = [];
1428
+ for (const value of values) {
1429
+ if (typeof value !== "string" || value.length === 0 || seen.has(value)) continue;
1430
+ seen.add(value);
1431
+ strings.push(value);
1432
+ }
1433
+ return Object.freeze(strings);
1434
+ }
1435
+ function supports(domain) {
1436
+ return typeof domain === "string" && environment?.capabilities.domains.includes(domain) === true;
1437
+ }
1438
+ const shell2 = {
1439
+ ready: () => ready,
1440
+ supports,
1441
+ get services() {
1442
+ return environment?.services ?? emptyServices;
1443
+ },
1444
+ set services(_services) {
1445
+ },
1446
+ onReady(handler) {
1447
+ if (environment) {
1448
+ handler(environment);
1449
+ return subscriptionHandle(() => void 0);
1450
+ }
1451
+ readyHandlers.add(handler);
1452
+ return subscriptionHandle(() => readyHandlers.delete(handler));
1453
+ }
1454
+ };
1455
+ post({ type: "shell.ready" });
1456
+ return shell2;
1457
+ }
1352
1458
  function domainEventHandle(eventType, callback, select = (message) => message.event) {
1353
1459
  const off = listen((event) => {
1354
1460
  if (!isParentMessage(event)) return;
@@ -1411,38 +1517,205 @@ function nappletNamespacePrelude(domains) {
1411
1517
  };
1412
1518
  }
1413
1519
  function makeInc() {
1414
- return {
1415
- emit(topic, _extraTags, content) {
1416
- let payload = content;
1417
- if (typeof content === "string" && content.length > 0) {
1418
- try {
1419
- payload = JSON.parse(content);
1420
- } catch {
1421
- payload = content;
1422
- }
1520
+ const topicStates = /* @__PURE__ */ new Map();
1521
+ const maxRetainedEvents = 32;
1522
+ const channelStates = /* @__PURE__ */ new Map();
1523
+ const openedHandlers = /* @__PURE__ */ new Set();
1524
+ const pendingOpened = [];
1525
+ const unopenedEvents = /* @__PURE__ */ new Map();
1526
+ const unopenedClosed = /* @__PURE__ */ new Map();
1527
+ function stableSubscriptionTopic(topic) {
1528
+ if (typeof topic !== "string") throw new TypeError("INC topic must be text");
1529
+ if (topic.includes("?") || topic.includes("#")) {
1530
+ throw new TypeError("INC subscriptions require a queryless topic identity");
1531
+ }
1532
+ return topic;
1533
+ }
1534
+ function closeChannelState(state, closed, notifyShell) {
1535
+ if (state.closed) return;
1536
+ state.closed = closed;
1537
+ if (notifyShell) fire({ type: "inc.channel.close", channelId: state.channelId });
1538
+ for (const handler of state.closedHandlers) handler(closed);
1539
+ }
1540
+ function makeChannelHandle(state) {
1541
+ return {
1542
+ id: state.channelId,
1543
+ peer: state.peer,
1544
+ emit(payload) {
1545
+ if (state.closed) return;
1546
+ fire({ type: "inc.channel.emit", channelId: state.channelId, ...payload === void 0 ? {} : { payload } });
1547
+ },
1548
+ on(handler) {
1549
+ if (typeof handler !== "function") throw new TypeError("INC channel event handler must be a function");
1550
+ if (state.closed) return subscriptionHandle(() => void 0);
1551
+ state.eventHandlers.add(handler);
1552
+ const pending = state.pendingEvents.splice(0);
1553
+ for (const event of pending) handler(event);
1554
+ return subscriptionHandle(() => state.eventHandlers.delete(handler));
1555
+ },
1556
+ onClosed(handler) {
1557
+ if (typeof handler !== "function") throw new TypeError("INC channel close handler must be a function");
1558
+ state.closedHandlers.add(handler);
1559
+ if (state.closed) handler(state.closed);
1560
+ return subscriptionHandle(() => state.closedHandlers.delete(handler));
1561
+ },
1562
+ close() {
1563
+ closeChannelState(state, { channelId: state.channelId }, true);
1564
+ }
1565
+ };
1566
+ }
1567
+ function materializeChannel(channelId, peer) {
1568
+ const existing = channelStates.get(channelId);
1569
+ if (existing) return existing;
1570
+ const state = {
1571
+ channelId,
1572
+ peer,
1573
+ eventHandlers: /* @__PURE__ */ new Set(),
1574
+ closedHandlers: /* @__PURE__ */ new Set(),
1575
+ pendingEvents: unopenedEvents.get(channelId) ?? []
1576
+ };
1577
+ unopenedEvents.delete(channelId);
1578
+ channelStates.set(channelId, state);
1579
+ const closed = unopenedClosed.get(channelId);
1580
+ unopenedClosed.delete(channelId);
1581
+ if (closed) closeChannelState(state, closed, false);
1582
+ return state;
1583
+ }
1584
+ function deliverOpened(state) {
1585
+ const handle = makeChannelHandle(state);
1586
+ if (openedHandlers.size > 0) {
1587
+ for (const handler of openedHandlers) handler(handle);
1588
+ return;
1589
+ }
1590
+ pendingOpened.push(state);
1591
+ }
1592
+ function receiveChannelEvent(channelId, sender, payloadPresent, payload) {
1593
+ const event = payloadPresent ? { channelId, sender, payload } : { channelId, sender };
1594
+ const state = channelStates.get(channelId);
1595
+ if (!state) {
1596
+ const pending = unopenedEvents.get(channelId) ?? [];
1597
+ if (pending.length >= maxRetainedEvents) {
1598
+ unopenedEvents.delete(channelId);
1599
+ unopenedClosed.set(channelId, { channelId, reason: "buffer overflow" });
1600
+ fire({ type: "inc.channel.close", channelId });
1601
+ return;
1423
1602
  }
1424
- fire({ type: "inc.emit", topic, ...payload === void 0 || payload === "" ? {} : { payload } });
1603
+ pending.push(event);
1604
+ unopenedEvents.set(channelId, pending);
1605
+ return;
1606
+ }
1607
+ if (state.closed) return;
1608
+ if (state.eventHandlers.size > 0) {
1609
+ for (const handler of state.eventHandlers) handler(event);
1610
+ return;
1611
+ }
1612
+ if (state.pendingEvents.length >= maxRetainedEvents) {
1613
+ closeChannelState(state, { channelId, reason: "buffer overflow" }, true);
1614
+ return;
1615
+ }
1616
+ state.pendingEvents.push(event);
1617
+ }
1618
+ listen((event) => {
1619
+ if (!isParentMessage(event)) return;
1620
+ const msg = event.data;
1621
+ if (typeof msg !== "object" || msg === null || typeof msg.type !== "string") return;
1622
+ if (msg.type === "inc.channel.opened" && typeof msg.channelId === "string" && typeof msg.peer === "string") {
1623
+ deliverOpened(materializeChannel(msg.channelId, msg.peer));
1624
+ return;
1625
+ }
1626
+ if (msg.type === "inc.channel.event" && typeof msg.channelId === "string" && typeof msg.sender === "string") {
1627
+ receiveChannelEvent(msg.channelId, msg.sender, Object.prototype.hasOwnProperty.call(msg, "payload"), msg.payload);
1628
+ return;
1629
+ }
1630
+ if (msg.type === "inc.channel.closed" && typeof msg.channelId === "string") {
1631
+ const closed = typeof msg.reason === "string" ? { channelId: msg.channelId, reason: msg.reason } : { channelId: msg.channelId };
1632
+ const state = channelStates.get(msg.channelId);
1633
+ if (state) closeChannelState(state, closed, false);
1634
+ else unopenedClosed.set(msg.channelId, closed);
1635
+ }
1636
+ });
1637
+ return {
1638
+ emit(topic, payload) {
1639
+ const transposed = normalizeConventionUri(topic, arguments.length > 1);
1640
+ fire({
1641
+ type: "inc.emit",
1642
+ topic: transposed.topic,
1643
+ ...transposed.payload === void 0 && payload === void 0 ? {} : { payload: transposed.payload ?? payload }
1644
+ });
1425
1645
  },
1426
1646
  on(topic, callback) {
1427
- const off = listen((event) => {
1428
- if (!isParentMessage(event)) return;
1429
- const msg = event.data;
1430
- if (typeof msg !== "object" || msg === null || msg.type !== "inc.event" || msg.topic !== topic) return;
1431
- callback(msg.payload ?? {}, {
1432
- id: "",
1433
- pubkey: typeof msg.sender === "string" ? msg.sender : "",
1434
- created_at: Math.floor(Date.now() / 1e3),
1435
- kind: 0,
1436
- tags: [["t", topic]],
1437
- content: typeof msg.payload === "string" ? msg.payload : JSON.stringify(msg.payload ?? {}),
1438
- sig: ""
1647
+ const stableTopic = stableSubscriptionTopic(topic);
1648
+ if (typeof callback !== "function") throw new TypeError("INC subscription handler must be a function");
1649
+ let state = topicStates.get(stableTopic);
1650
+ if (!state) {
1651
+ const handlers = /* @__PURE__ */ new Set();
1652
+ const subscriptionId = id();
1653
+ const offEvent = listen((event) => {
1654
+ if (!isParentMessage(event)) return;
1655
+ const msg = event.data;
1656
+ if (typeof msg !== "object" || msg === null || msg.type !== "inc.event" || msg.topic !== stableTopic || typeof msg.sender !== "string") return;
1657
+ const delivered = Object.prototype.hasOwnProperty.call(msg, "payload") ? { topic: stableTopic, sender: msg.sender, payload: msg.payload } : { topic: stableTopic, sender: msg.sender };
1658
+ for (const handler of handlers) handler(delivered);
1439
1659
  });
1440
- });
1441
- fire({ type: "inc.subscribe", id: id(), topic });
1660
+ const offResult = listen((event) => {
1661
+ if (!isParentMessage(event)) return;
1662
+ const msg = event.data;
1663
+ if (typeof msg !== "object" || msg === null || msg.type !== "inc.subscribe.result" || msg.id !== subscriptionId) return;
1664
+ offResult();
1665
+ if (typeof msg.error === "string" && msg.error) {
1666
+ offEvent();
1667
+ topicStates.delete(stableTopic);
1668
+ }
1669
+ });
1670
+ state = { handlers, offEvent, offResult };
1671
+ topicStates.set(stableTopic, state);
1672
+ fire({ type: "inc.subscribe", id: subscriptionId, topic: stableTopic });
1673
+ }
1674
+ state.handlers.add(callback);
1442
1675
  return subscriptionHandle(() => {
1443
- fire({ type: "inc.unsubscribe", topic });
1444
- off();
1676
+ const current = topicStates.get(stableTopic);
1677
+ if (!current) return;
1678
+ current.handlers.delete(callback);
1679
+ if (current.handlers.size !== 0) return;
1680
+ current.offEvent();
1681
+ current.offResult();
1682
+ topicStates.delete(stableTopic);
1683
+ fire({ type: "inc.unsubscribe", topic: stableTopic });
1445
1684
  });
1685
+ },
1686
+ channel: {
1687
+ open(targetDTag) {
1688
+ return request(
1689
+ { type: "inc.channel.open", target: targetDTag },
1690
+ "inc.channel.open.result",
1691
+ (msg) => {
1692
+ if (typeof msg.channelId !== "string" || typeof msg.peer !== "string") {
1693
+ throw new Error("inc.channel.open.result missing channel identity");
1694
+ }
1695
+ return makeChannelHandle(materializeChannel(msg.channelId, msg.peer));
1696
+ }
1697
+ );
1698
+ },
1699
+ onOpened(handler) {
1700
+ if (typeof handler !== "function") throw new TypeError("INC opened handler must be a function");
1701
+ openedHandlers.add(handler);
1702
+ const pending = pendingOpened.splice(0);
1703
+ for (const state of pending) handler(makeChannelHandle(state));
1704
+ return subscriptionHandle(() => openedHandlers.delete(handler));
1705
+ },
1706
+ list() {
1707
+ return request(
1708
+ { type: "inc.channel.list" },
1709
+ "inc.channel.list.result",
1710
+ (msg) => Object.freeze((Array.isArray(msg.channels) ? msg.channels : []).flatMap((channel) => channel && typeof channel === "object" && typeof channel.id === "string" && typeof channel.peer === "string" ? [Object.freeze({
1711
+ id: channel.id,
1712
+ peer: channel.peer
1713
+ })] : []))
1714
+ );
1715
+ },
1716
+ broadcast(payload) {
1717
+ fire({ type: "inc.channel.broadcast", ...payload === void 0 ? {} : { payload } });
1718
+ }
1446
1719
  }
1447
1720
  };
1448
1721
  }
@@ -1480,14 +1753,22 @@ function nappletNamespacePrelude(domains) {
1480
1753
  return { ...makeScope(), instance: makeScope("instance") };
1481
1754
  }
1482
1755
  function makeIdentity() {
1483
- const read = (type, field, fallback) => request(
1484
- { type },
1756
+ const read = (type, field, fallback, payload = {}) => request(
1757
+ { type, ...payload },
1485
1758
  `${type}.result`,
1486
1759
  (msg) => Object.prototype.hasOwnProperty.call(msg, field) ? msg[field] : fallback,
1487
1760
  { rejectOnError: type !== "identity.getPublicKey" }
1488
1761
  );
1489
- return {
1762
+ return Object.freeze({
1490
1763
  getPublicKey: () => read("identity.getPublicKey", "pubkey", ""),
1764
+ getRelays: () => read("identity.getRelays", "relays", {}),
1765
+ getProfile: () => read("identity.getProfile", "profile", null),
1766
+ getFollows: () => read("identity.getFollows", "pubkeys", []),
1767
+ getList: (listType) => read("identity.getList", "entries", [], { listType }),
1768
+ getZaps: () => read("identity.getZaps", "zaps", []),
1769
+ getMutes: () => read("identity.getMutes", "pubkeys", []),
1770
+ getBlocked: () => read("identity.getBlocked", "pubkeys", []),
1771
+ getBadges: () => read("identity.getBadges", "badges", []),
1491
1772
  onChanged(handler) {
1492
1773
  const off = listen((event) => {
1493
1774
  if (!isParentMessage(event)) return;
@@ -1496,20 +1777,8 @@ function nappletNamespacePrelude(domains) {
1496
1777
  if (typeof msg.pubkey === "string") handler(msg.pubkey);
1497
1778
  });
1498
1779
  return subscriptionHandle(off);
1499
- },
1500
- getRelays: () => read("identity.getRelays", "relays", {}),
1501
- getProfile: () => read("identity.getProfile", "profile", null),
1502
- getFollows: () => read("identity.getFollows", "pubkeys", []),
1503
- getList: (listType) => request(
1504
- { type: "identity.getList", listType },
1505
- "identity.getList.result",
1506
- (msg) => Array.isArray(msg.entries) ? msg.entries : []
1507
- ),
1508
- getZaps: () => read("identity.getZaps", "zaps", []),
1509
- getMutes: () => read("identity.getMutes", "pubkeys", []),
1510
- getBlocked: () => read("identity.getBlocked", "pubkeys", []),
1511
- getBadges: () => read("identity.getBadges", "badges", [])
1512
- };
1780
+ }
1781
+ });
1513
1782
  }
1514
1783
  function makeTheme() {
1515
1784
  return {
@@ -1948,19 +2217,85 @@ function nappletNamespacePrelude(domains) {
1948
2217
  };
1949
2218
  }
1950
2219
  function makeIntent() {
1951
- const invoke = (requestPayload) => request(
1952
- { type: "intent.invoke", request: requestPayload },
1953
- "intent.invoke.result",
1954
- (msg) => fieldOrThrow(msg, "result", "intent.invoke.result missing result")
1955
- );
2220
+ const deliveryHandlers = /* @__PURE__ */ new Set();
2221
+ const pendingDeliveries = [];
2222
+ const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
2223
+ const normalizeDelivery = (value) => {
2224
+ if (!value || typeof value !== "object" || typeof value.sender !== "string" || typeof value.archetype !== "string" || typeof value.action !== "string" || typeof value.convention !== "string") {
2225
+ return void 0;
2226
+ }
2227
+ const delivery = value;
2228
+ return {
2229
+ sender: delivery.sender,
2230
+ archetype: delivery.archetype,
2231
+ action: delivery.action,
2232
+ convention: delivery.convention,
2233
+ ...hasOwn(delivery, "payload") ? { payload: delivery.payload } : {}
2234
+ };
2235
+ };
2236
+ listen((event) => {
2237
+ if (!isParentMessage(event)) return;
2238
+ const msg = event.data;
2239
+ if (typeof msg !== "object" || msg === null || msg.type !== "intent.deliver") return;
2240
+ const delivery = normalizeDelivery(msg.delivery);
2241
+ if (!delivery) return;
2242
+ if (deliveryHandlers.size === 0) pendingDeliveries.push(delivery);
2243
+ else for (const handler of deliveryHandlers) handler(delivery);
2244
+ });
2245
+ function normalizeIntentUri(uri, explicitPayload, actionMustBe) {
2246
+ if (typeof uri !== "string" || !uri.startsWith("napplet:")) {
2247
+ throw new TypeError("Intent convention URI must start with napplet:");
2248
+ }
2249
+ if (uri.includes("#")) throw new TypeError("Intent convention URI cannot contain fragments");
2250
+ const normalized = normalizeConventionUri(uri, explicitPayload);
2251
+ const match = /^napplet:([^/?#]+)\/([^/?#]+)$/.exec(normalized.topic);
2252
+ if (!match) throw new TypeError("Intent convention URI must be napplet:<archetype>/<intent>");
2253
+ if (actionMustBe !== void 0 && match[2] !== actionMustBe) {
2254
+ throw new TypeError(`Intent convention URI must use the ${actionMustBe} intent`);
2255
+ }
2256
+ return {
2257
+ archetype: match[1],
2258
+ action: match[2],
2259
+ convention: normalized.topic,
2260
+ ...normalized.payload === void 0 ? {} : { payload: normalized.payload }
2261
+ };
2262
+ }
2263
+ const invoke = (uri, opts, actionMustBe) => {
2264
+ const supplied = opts && typeof opts === "object" ? opts : void 0;
2265
+ const explicitPayload = supplied?.payload !== void 0;
2266
+ const normalized = normalizeIntentUri(uri, explicitPayload, actionMustBe);
2267
+ if (supplied && hasOwn(supplied, "sender")) {
2268
+ throw new TypeError("Intent callers cannot supply sender");
2269
+ }
2270
+ for (const field of ["archetype", "action", "convention"]) {
2271
+ if (supplied && hasOwn(supplied, field) && supplied[field] !== normalized[field]) {
2272
+ throw new TypeError(`Intent options cannot override URI-derived ${field}`);
2273
+ }
2274
+ }
2275
+ const behaviorValue = supplied?.behavior;
2276
+ const behavior = behaviorValue && typeof behaviorValue === "object" ? behaviorValue : void 0;
2277
+ const sanitizedBehavior = {
2278
+ ...typeof behavior?.focus === "boolean" ? { focus: behavior.focus } : {},
2279
+ ...typeof behavior?.reuse === "boolean" ? { reuse: behavior.reuse } : {}
2280
+ };
2281
+ const handler = typeof supplied?.handler === "string" ? supplied.handler : void 0;
2282
+ return request(
2283
+ {
2284
+ type: "intent.invoke",
2285
+ request: {
2286
+ ...normalized,
2287
+ ...explicitPayload ? { payload: supplied?.payload } : {},
2288
+ ...handler === void 0 ? {} : { handler },
2289
+ ...Object.keys(sanitizedBehavior).length === 0 ? {} : { behavior: sanitizedBehavior }
2290
+ }
2291
+ },
2292
+ "intent.invoke.result",
2293
+ (msg) => fieldOrThrow(msg, "result", "intent.invoke.result missing result")
2294
+ );
2295
+ };
1956
2296
  return {
1957
2297
  invoke,
1958
- open: (archetype, payload, opts) => invoke({
1959
- archetype,
1960
- action: "open",
1961
- payload,
1962
- ...opts && typeof opts === "object" ? opts : {}
1963
- }),
2298
+ open: (uri, opts) => invoke(uri, opts, "open"),
1964
2299
  available: (archetype) => request(
1965
2300
  { type: "intent.available", archetype },
1966
2301
  "intent.available.result",
@@ -1975,7 +2310,14 @@ function nappletNamespacePrelude(domains) {
1975
2310
  "intent.changed",
1976
2311
  callback,
1977
2312
  (msg) => msg.availability
1978
- )
2313
+ ),
2314
+ onDelivery: (callback) => {
2315
+ if (typeof callback !== "function") throw new TypeError("Intent delivery handler must be a function");
2316
+ deliveryHandlers.add(callback);
2317
+ const pending = pendingDeliveries.splice(0);
2318
+ for (const delivery of pending) callback(delivery);
2319
+ return subscriptionHandle(() => deliveryHandlers.delete(callback));
2320
+ }
1979
2321
  };
1980
2322
  }
1981
2323
  function makeWebrtc() {
@@ -2109,13 +2451,41 @@ function nappletNamespacePrelude(domains) {
2109
2451
  }
2110
2452
  };
2111
2453
  }
2454
+ const shell = makeShell();
2455
+ let inc;
2456
+ let identity;
2457
+ let theme;
2458
+ let intent;
2459
+ function makeProtectedInc(existing) {
2460
+ const extensions = existing && typeof existing === "object" ? existing : {};
2461
+ inc ??= makeInc();
2462
+ return { ...extensions, ...inc };
2463
+ }
2464
+ function makeProtectedIdentity() {
2465
+ identity ??= makeIdentity();
2466
+ return identity;
2467
+ }
2468
+ function makeProtectedTheme() {
2469
+ theme ??= Object.freeze(makeTheme());
2470
+ return theme;
2471
+ }
2472
+ function makeProtectedIntent() {
2473
+ intent ??= Object.freeze(makeIntent());
2474
+ return intent;
2475
+ }
2476
+ function isProtectedDomain(domain) {
2477
+ return domain === "shell" || domain === "inc" || domain === "identity" || domain === "theme" || domain === "intent";
2478
+ }
2112
2479
  function makeDomain(domain, existing) {
2480
+ if (domain === "shell") return shell;
2481
+ if (domain === "inc") return makeProtectedInc(existing);
2482
+ if (domain === "identity") return makeProtectedIdentity();
2483
+ if (domain === "theme") return makeProtectedTheme();
2484
+ if (domain === "intent") return makeProtectedIntent();
2113
2485
  if (existing && typeof existing === "object" && Object.keys(existing).length > 0) return existing;
2114
2486
  switch (domain) {
2115
2487
  case "relay":
2116
2488
  return makeRelay();
2117
- case "inc":
2118
- return makeInc();
2119
2489
  case "storage":
2120
2490
  return makeStorage();
2121
2491
  case "keys":
@@ -2124,10 +2494,6 @@ function nappletNamespacePrelude(domains) {
2124
2494
  return makeMedia();
2125
2495
  case "notify":
2126
2496
  return makeNotify();
2127
- case "identity":
2128
- return makeIdentity();
2129
- case "theme":
2130
- return makeTheme();
2131
2497
  case "config":
2132
2498
  return makeConfig();
2133
2499
  case "resource":
@@ -2138,8 +2504,6 @@ function nappletNamespacePrelude(domains) {
2138
2504
  return makeOutbox();
2139
2505
  case "upload":
2140
2506
  return makeUpload();
2141
- case "intent":
2142
- return makeIntent();
2143
2507
  case "webrtc":
2144
2508
  return makeWebrtc();
2145
2509
  case "ble":
@@ -2169,10 +2533,22 @@ function nappletNamespacePrelude(domains) {
2169
2533
  },
2170
2534
  defineProperty(obj, prop, descriptor) {
2171
2535
  if (typeof prop !== "string" || !allowed.has(prop)) return true;
2536
+ if (isProtectedDomain(prop)) {
2537
+ return Reflect.defineProperty(obj, prop, {
2538
+ value: makeDomain(prop, descriptor.value),
2539
+ enumerable: true,
2540
+ configurable: true,
2541
+ writable: true
2542
+ });
2543
+ }
2172
2544
  return Reflect.defineProperty(obj, prop, {
2173
2545
  ...descriptor,
2174
2546
  value: makeDomain(prop, descriptor.value)
2175
2547
  });
2548
+ },
2549
+ deleteProperty(obj, prop) {
2550
+ if (typeof prop === "string" && allowed.has(prop) && isProtectedDomain(prop)) return true;
2551
+ return Reflect.deleteProperty(obj, prop);
2176
2552
  }
2177
2553
  });
2178
2554
  }
@@ -2202,7 +2578,7 @@ function nappletNamespacePrelude(domains) {
2202
2578
  root = buildNappletNamespace(value);
2203
2579
  },
2204
2580
  enumerable: false,
2205
- configurable: true
2581
+ configurable: false
2206
2582
  });
2207
2583
  }
2208
2584
 
@@ -2219,9 +2595,10 @@ function createIdentityProxy(deps) {
2219
2595
  dispatch(windowId, envelope) {
2220
2596
  deps.runtime.handleMessage(windowId, envelope);
2221
2597
  },
2222
- emit(windowId, envelope) {
2223
- const win = deps.originRegistry.getIframeWindow(windowId);
2224
- if (win) win.postMessage(envelope, "*");
2598
+ emit(_windowId, _envelope) {
2599
+ throw new Error(
2600
+ "Direct identity proxy emit is prohibited; use ShellBridge.publishIdentityChanged()"
2601
+ );
2225
2602
  }
2226
2603
  };
2227
2604
  }
@@ -2232,9 +2609,10 @@ function createThemeProxy(deps) {
2232
2609
  dispatch(windowId, envelope) {
2233
2610
  deps.runtime.handleMessage(windowId, envelope);
2234
2611
  },
2235
- emit(windowId, envelope) {
2236
- const win = deps.originRegistry.getIframeWindow(windowId);
2237
- if (win) win.postMessage(envelope, "*");
2612
+ emit(_windowId, _envelope) {
2613
+ throw new Error(
2614
+ "Direct theme proxy emit is prohibited; use ShellBridge.publishTheme()"
2615
+ );
2238
2616
  }
2239
2617
  };
2240
2618
  }
@@ -2285,7 +2663,6 @@ export {
2285
2663
  buildShellCapabilities,
2286
2664
  createEnforceGate,
2287
2665
  createIdentityProxy,
2288
- createKeysForwarder,
2289
2666
  createKeysProxy,
2290
2667
  createMediaProxy,
2291
2668
  createNapEnforceGate,
@@ -2298,6 +2675,7 @@ export {
2298
2675
  nappKeyRegistry,
2299
2676
  originRegistry,
2300
2677
  renderNappletNamespacePrelude,
2678
+ resolveShellEnvironment,
2301
2679
  sessionRegistry
2302
2680
  };
2303
2681
  //# sourceMappingURL=index.js.map