@kehto/shell 0.17.2 → 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/README.md +96 -9
- package/dist/index.d.ts +110 -182
- package/dist/index.js +546 -182
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
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
|
*
|
|
@@ -962,113 +975,153 @@ var audioManager = {
|
|
|
962
975
|
};
|
|
963
976
|
|
|
964
977
|
// src/shell-init.ts
|
|
965
|
-
var
|
|
978
|
+
var RUNTIME_NATIVE_DOMAINS = [
|
|
979
|
+
"relay",
|
|
966
980
|
"identity",
|
|
967
981
|
"storage",
|
|
968
982
|
"inc",
|
|
969
983
|
"theme",
|
|
970
984
|
"keys",
|
|
971
985
|
"media",
|
|
972
|
-
"notify"
|
|
986
|
+
"notify"
|
|
987
|
+
];
|
|
988
|
+
var SERVICE_ONLY_DOMAINS = [
|
|
989
|
+
"outbox",
|
|
973
990
|
"config",
|
|
974
991
|
"resource",
|
|
975
|
-
"cvm"
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
"
|
|
979
|
-
"
|
|
980
|
-
"
|
|
981
|
-
"
|
|
982
|
-
"
|
|
983
|
-
"
|
|
992
|
+
"cvm",
|
|
993
|
+
"upload",
|
|
994
|
+
"intent",
|
|
995
|
+
"link",
|
|
996
|
+
"common",
|
|
997
|
+
"lists",
|
|
998
|
+
"serial",
|
|
999
|
+
"ble",
|
|
1000
|
+
"webrtc",
|
|
1001
|
+
"dm",
|
|
1002
|
+
"count"
|
|
984
1003
|
];
|
|
985
1004
|
function buildShellCapabilities(hooks) {
|
|
986
|
-
const
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
if (hooks.lists?.isAvailable()) domains.push("lists");
|
|
993
|
-
if (hooks.serial?.isAvailable()) domains.push("serial");
|
|
994
|
-
if (hooks.ble?.isAvailable()) domains.push("ble");
|
|
995
|
-
if (hooks.webrtc?.isAvailable()) domains.push("webrtc");
|
|
996
|
-
if (hooks.dm) domains.push("dm");
|
|
997
|
-
const sandbox = [];
|
|
998
|
-
const protocols = {};
|
|
999
|
-
for (const entry of NAP_INC_PROTOCOLS) {
|
|
1000
|
-
const [domain, protocol] = entry.split(":");
|
|
1001
|
-
(protocols[domain] ??= []).push(protocol);
|
|
1002
|
-
}
|
|
1003
|
-
const naps = hooks.relayPool ? ["relay", "outbox", ...NAP_DOMAINS, ...NAP_INC_PROTOCOLS] : [...NAP_DOMAINS, ...NAP_INC_PROTOCOLS];
|
|
1004
|
-
if (hooks.upload) naps.push("upload");
|
|
1005
|
-
if (hooks.intent?.isAvailable()) naps.push("intent");
|
|
1006
|
-
if (hooks.services?.count) naps.push("count");
|
|
1007
|
-
if (hooks.link?.isAvailable()) naps.push("link");
|
|
1008
|
-
if (hooks.common?.isAvailable()) naps.push("common");
|
|
1009
|
-
if (hooks.lists?.isAvailable()) naps.push("lists");
|
|
1010
|
-
if (hooks.serial?.isAvailable()) naps.push("serial");
|
|
1011
|
-
if (hooks.ble?.isAvailable()) naps.push("ble");
|
|
1012
|
-
if (hooks.webrtc?.isAvailable()) naps.push("webrtc");
|
|
1013
|
-
if (hooks.dm) naps.push("dm");
|
|
1014
|
-
return applyCapabilityOverrides(
|
|
1015
|
-
{ domains, protocols, naps, sandbox },
|
|
1016
|
-
hooks.capabilities?.disabledDomains ?? []
|
|
1017
|
-
);
|
|
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) });
|
|
1018
1011
|
}
|
|
1019
|
-
function
|
|
1020
|
-
|
|
1021
|
-
const disabled = new Set(disabledDomains);
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1024
|
-
|
|
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;
|
|
1025
1053
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
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]);
|
|
1032
1068
|
}
|
|
1033
1069
|
|
|
1034
1070
|
// src/shell-ready.ts
|
|
1035
|
-
|
|
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
|
+
}
|
|
1036
1088
|
function handleShellReady({
|
|
1037
1089
|
hooks,
|
|
1038
1090
|
origin,
|
|
1039
1091
|
runtime,
|
|
1092
|
+
state,
|
|
1040
1093
|
sourceRegistrationId,
|
|
1041
1094
|
sourceWindow,
|
|
1042
1095
|
windowId
|
|
1043
1096
|
}) {
|
|
1044
|
-
|
|
1045
|
-
hooks,
|
|
1046
|
-
origin,
|
|
1047
|
-
runtime,
|
|
1048
|
-
sourceRegistrationId,
|
|
1049
|
-
sourceWindow,
|
|
1050
|
-
windowId
|
|
1051
|
-
});
|
|
1052
|
-
if (initSent.get(sourceWindow) === sourceRegistrationId) {
|
|
1097
|
+
if (state.initSent.get(sourceWindow) === sourceRegistrationId) {
|
|
1053
1098
|
return;
|
|
1054
1099
|
}
|
|
1055
|
-
const
|
|
1056
|
-
|
|
1057
|
-
|
|
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);
|
|
1058
1107
|
}
|
|
1059
1108
|
function registerNip5dSessionIfNeeded({
|
|
1060
|
-
hooks,
|
|
1061
1109
|
origin,
|
|
1062
1110
|
runtime,
|
|
1063
|
-
|
|
1111
|
+
state,
|
|
1112
|
+
sourceRegistrationId,
|
|
1113
|
+
windowId,
|
|
1114
|
+
identity
|
|
1064
1115
|
}) {
|
|
1065
|
-
|
|
1116
|
+
const existing = runtime.sessionRegistry.getEntryByWindowId(windowId);
|
|
1117
|
+
const previousRegistration = state.sessionRegistration.get(windowId);
|
|
1118
|
+
if (existing && previousRegistration === sourceRegistrationId) {
|
|
1066
1119
|
return;
|
|
1067
1120
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
return;
|
|
1121
|
+
if (existing && previousRegistration !== void 0) {
|
|
1122
|
+
runtime.sessionRegistry.unregister(windowId);
|
|
1071
1123
|
}
|
|
1124
|
+
if (existing && previousRegistration === void 0) return;
|
|
1072
1125
|
const entry = {
|
|
1073
1126
|
pubkey: "",
|
|
1074
1127
|
windowId,
|
|
@@ -1081,33 +1134,30 @@ function registerNip5dSessionIfNeeded({
|
|
|
1081
1134
|
provenance: "nip-5d"
|
|
1082
1135
|
};
|
|
1083
1136
|
runtime.sessionRegistry.register(windowId, entry);
|
|
1137
|
+
state.sessionRegistration.set(windowId, sourceRegistrationId);
|
|
1084
1138
|
}
|
|
1085
|
-
function resolveNip5dIdentity(hooks, windowId) {
|
|
1139
|
+
function resolveNip5dIdentity(hooks, windowId, sourceWindow) {
|
|
1086
1140
|
const hookIdentity = hooks.onNip5dIframeCreate?.(windowId);
|
|
1087
1141
|
if (hookIdentity !== null && hookIdentity !== void 0) {
|
|
1088
|
-
return {
|
|
1142
|
+
return Object.freeze({
|
|
1089
1143
|
dTag: hookIdentity.dTag,
|
|
1090
1144
|
aggregateHash: hookIdentity.aggregateHash
|
|
1091
|
-
};
|
|
1092
|
-
}
|
|
1093
|
-
const win = originRegistry.getIframeWindow(windowId);
|
|
1094
|
-
if (!win) {
|
|
1095
|
-
return null;
|
|
1145
|
+
});
|
|
1096
1146
|
}
|
|
1097
|
-
const originIdentity = originRegistry.getIdentity(
|
|
1147
|
+
const originIdentity = originRegistry.getIdentity(sourceWindow);
|
|
1098
1148
|
if (!originIdentity) {
|
|
1099
1149
|
return null;
|
|
1100
1150
|
}
|
|
1101
|
-
return {
|
|
1151
|
+
return Object.freeze({
|
|
1102
1152
|
dTag: originIdentity.dTag,
|
|
1103
1153
|
aggregateHash: originIdentity.aggregateHash
|
|
1104
|
-
};
|
|
1154
|
+
});
|
|
1105
1155
|
}
|
|
1106
|
-
function postShellInit(win,
|
|
1156
|
+
function postShellInit(win, environment) {
|
|
1107
1157
|
const initMsg = {
|
|
1108
1158
|
type: "shell.init",
|
|
1109
|
-
capabilities,
|
|
1110
|
-
services
|
|
1159
|
+
capabilities: environment.capabilities,
|
|
1160
|
+
services: environment.services
|
|
1111
1161
|
};
|
|
1112
1162
|
win.postMessage(initMsg, "*");
|
|
1113
1163
|
}
|
|
@@ -1123,6 +1173,7 @@ function reportUnrouted(hooks, event, reason) {
|
|
|
1123
1173
|
}
|
|
1124
1174
|
}
|
|
1125
1175
|
function createShellBridge(hooks) {
|
|
1176
|
+
const shellReadyState = createShellReadyState();
|
|
1126
1177
|
const runtimeHooks = adaptHooks(hooks, {
|
|
1127
1178
|
originRegistry,
|
|
1128
1179
|
manifestCache,
|
|
@@ -1130,11 +1181,16 @@ function createShellBridge(hooks) {
|
|
|
1130
1181
|
audioManager,
|
|
1131
1182
|
nappKeyRegistry
|
|
1132
1183
|
});
|
|
1133
|
-
const runtime = createRuntime(
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
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);
|
|
1138
1194
|
if (!win) continue;
|
|
1139
1195
|
win.postMessage(envelope, "*");
|
|
1140
1196
|
}
|
|
@@ -1158,6 +1214,7 @@ function createShellBridge(hooks) {
|
|
|
1158
1214
|
hooks,
|
|
1159
1215
|
origin: event.origin,
|
|
1160
1216
|
runtime,
|
|
1217
|
+
state: shellReadyState,
|
|
1161
1218
|
sourceRegistrationId: originRegistry.getRegistrationId(sourceWindow) ?? 0,
|
|
1162
1219
|
sourceWindow,
|
|
1163
1220
|
windowId
|
|
@@ -1171,17 +1228,18 @@ function createShellBridge(hooks) {
|
|
|
1171
1228
|
},
|
|
1172
1229
|
destroy() {
|
|
1173
1230
|
runtime.destroy();
|
|
1231
|
+
shellReadyState.clear();
|
|
1174
1232
|
},
|
|
1175
1233
|
registerConsentHandler(handler) {
|
|
1176
1234
|
runtime.registerConsentHandler(handler);
|
|
1177
1235
|
},
|
|
1178
1236
|
publishTheme(theme) {
|
|
1179
1237
|
const envelope = { type: "theme.changed", theme };
|
|
1180
|
-
|
|
1238
|
+
publishToEligibleNapplets(envelope, "theme", "theme:read");
|
|
1181
1239
|
},
|
|
1182
1240
|
publishIdentityChanged(pubkey) {
|
|
1183
1241
|
const envelope = { type: "identity.changed", pubkey };
|
|
1184
|
-
|
|
1242
|
+
publishToEligibleNapplets(envelope, "identity", "identity:read");
|
|
1185
1243
|
},
|
|
1186
1244
|
get runtime() {
|
|
1187
1245
|
return runtime;
|
|
@@ -1276,6 +1334,41 @@ function nappletNamespacePrelude(domains) {
|
|
|
1276
1334
|
function fire(message) {
|
|
1277
1335
|
post(message);
|
|
1278
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
|
+
}
|
|
1279
1372
|
function resultOrError(msg) {
|
|
1280
1373
|
return msg;
|
|
1281
1374
|
}
|
|
@@ -1315,11 +1408,13 @@ function nappletNamespacePrelude(domains) {
|
|
|
1315
1408
|
if (typeof incoming !== "object" || incoming === null) return;
|
|
1316
1409
|
const message = incoming;
|
|
1317
1410
|
if (message.type !== "shell.init") return;
|
|
1318
|
-
const
|
|
1319
|
-
|
|
1320
|
-
);
|
|
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 : []);
|
|
1321
1416
|
environment = Object.freeze({
|
|
1322
|
-
capabilities:
|
|
1417
|
+
capabilities: Object.freeze({ domains: domains2 }),
|
|
1323
1418
|
services
|
|
1324
1419
|
});
|
|
1325
1420
|
off();
|
|
@@ -1327,20 +1422,18 @@ function nappletNamespacePrelude(domains) {
|
|
|
1327
1422
|
for (const handler of readyHandlers) handler(environment);
|
|
1328
1423
|
readyHandlers.clear();
|
|
1329
1424
|
});
|
|
1330
|
-
function
|
|
1331
|
-
|
|
1332
|
-
|
|
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);
|
|
1333
1432
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
const supported = protocols[domain];
|
|
1339
|
-
if (Array.isArray(supported) && supported.includes(protocol)) return true;
|
|
1340
|
-
}
|
|
1341
|
-
return Array.isArray(capabilities.naps) && capabilities.naps.includes(`${domain}:${protocol}`);
|
|
1342
|
-
}
|
|
1343
|
-
return Array.isArray(capabilities.domains) && capabilities.domains.includes(domain) || Array.isArray(capabilities.naps) && capabilities.naps.includes(domain);
|
|
1433
|
+
return Object.freeze(strings);
|
|
1434
|
+
}
|
|
1435
|
+
function supports(domain) {
|
|
1436
|
+
return typeof domain === "string" && environment?.capabilities.domains.includes(domain) === true;
|
|
1344
1437
|
}
|
|
1345
1438
|
const shell2 = {
|
|
1346
1439
|
ready: () => ready,
|
|
@@ -1424,38 +1517,205 @@ function nappletNamespacePrelude(domains) {
|
|
|
1424
1517
|
};
|
|
1425
1518
|
}
|
|
1426
1519
|
function makeInc() {
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
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;
|
|
1436
1602
|
}
|
|
1437
|
-
|
|
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
|
+
});
|
|
1438
1645
|
},
|
|
1439
1646
|
on(topic, callback) {
|
|
1440
|
-
const
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
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);
|
|
1452
1659
|
});
|
|
1453
|
-
|
|
1454
|
-
|
|
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);
|
|
1455
1675
|
return subscriptionHandle(() => {
|
|
1456
|
-
|
|
1457
|
-
|
|
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 });
|
|
1458
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
|
+
}
|
|
1459
1719
|
}
|
|
1460
1720
|
};
|
|
1461
1721
|
}
|
|
@@ -1493,14 +1753,22 @@ function nappletNamespacePrelude(domains) {
|
|
|
1493
1753
|
return { ...makeScope(), instance: makeScope("instance") };
|
|
1494
1754
|
}
|
|
1495
1755
|
function makeIdentity() {
|
|
1496
|
-
const read = (type, field, fallback) => request(
|
|
1497
|
-
{ type },
|
|
1756
|
+
const read = (type, field, fallback, payload = {}) => request(
|
|
1757
|
+
{ type, ...payload },
|
|
1498
1758
|
`${type}.result`,
|
|
1499
1759
|
(msg) => Object.prototype.hasOwnProperty.call(msg, field) ? msg[field] : fallback,
|
|
1500
1760
|
{ rejectOnError: type !== "identity.getPublicKey" }
|
|
1501
1761
|
);
|
|
1502
|
-
return {
|
|
1762
|
+
return Object.freeze({
|
|
1503
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", []),
|
|
1504
1772
|
onChanged(handler) {
|
|
1505
1773
|
const off = listen((event) => {
|
|
1506
1774
|
if (!isParentMessage(event)) return;
|
|
@@ -1509,20 +1777,8 @@ function nappletNamespacePrelude(domains) {
|
|
|
1509
1777
|
if (typeof msg.pubkey === "string") handler(msg.pubkey);
|
|
1510
1778
|
});
|
|
1511
1779
|
return subscriptionHandle(off);
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
getProfile: () => read("identity.getProfile", "profile", null),
|
|
1515
|
-
getFollows: () => read("identity.getFollows", "pubkeys", []),
|
|
1516
|
-
getList: (listType) => request(
|
|
1517
|
-
{ type: "identity.getList", listType },
|
|
1518
|
-
"identity.getList.result",
|
|
1519
|
-
(msg) => Array.isArray(msg.entries) ? msg.entries : []
|
|
1520
|
-
),
|
|
1521
|
-
getZaps: () => read("identity.getZaps", "zaps", []),
|
|
1522
|
-
getMutes: () => read("identity.getMutes", "pubkeys", []),
|
|
1523
|
-
getBlocked: () => read("identity.getBlocked", "pubkeys", []),
|
|
1524
|
-
getBadges: () => read("identity.getBadges", "badges", [])
|
|
1525
|
-
};
|
|
1780
|
+
}
|
|
1781
|
+
});
|
|
1526
1782
|
}
|
|
1527
1783
|
function makeTheme() {
|
|
1528
1784
|
return {
|
|
@@ -1961,19 +2217,85 @@ function nappletNamespacePrelude(domains) {
|
|
|
1961
2217
|
};
|
|
1962
2218
|
}
|
|
1963
2219
|
function makeIntent() {
|
|
1964
|
-
const
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
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
|
+
};
|
|
1969
2296
|
return {
|
|
1970
2297
|
invoke,
|
|
1971
|
-
open: (
|
|
1972
|
-
archetype,
|
|
1973
|
-
action: "open",
|
|
1974
|
-
payload,
|
|
1975
|
-
...opts && typeof opts === "object" ? opts : {}
|
|
1976
|
-
}),
|
|
2298
|
+
open: (uri, opts) => invoke(uri, opts, "open"),
|
|
1977
2299
|
available: (archetype) => request(
|
|
1978
2300
|
{ type: "intent.available", archetype },
|
|
1979
2301
|
"intent.available.result",
|
|
@@ -1988,7 +2310,14 @@ function nappletNamespacePrelude(domains) {
|
|
|
1988
2310
|
"intent.changed",
|
|
1989
2311
|
callback,
|
|
1990
2312
|
(msg) => msg.availability
|
|
1991
|
-
)
|
|
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
|
+
}
|
|
1992
2321
|
};
|
|
1993
2322
|
}
|
|
1994
2323
|
function makeWebrtc() {
|
|
@@ -2123,14 +2452,40 @@ function nappletNamespacePrelude(domains) {
|
|
|
2123
2452
|
};
|
|
2124
2453
|
}
|
|
2125
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
|
+
}
|
|
2126
2479
|
function makeDomain(domain, existing) {
|
|
2127
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();
|
|
2128
2485
|
if (existing && typeof existing === "object" && Object.keys(existing).length > 0) return existing;
|
|
2129
2486
|
switch (domain) {
|
|
2130
2487
|
case "relay":
|
|
2131
2488
|
return makeRelay();
|
|
2132
|
-
case "inc":
|
|
2133
|
-
return makeInc();
|
|
2134
2489
|
case "storage":
|
|
2135
2490
|
return makeStorage();
|
|
2136
2491
|
case "keys":
|
|
@@ -2139,10 +2494,6 @@ function nappletNamespacePrelude(domains) {
|
|
|
2139
2494
|
return makeMedia();
|
|
2140
2495
|
case "notify":
|
|
2141
2496
|
return makeNotify();
|
|
2142
|
-
case "identity":
|
|
2143
|
-
return makeIdentity();
|
|
2144
|
-
case "theme":
|
|
2145
|
-
return makeTheme();
|
|
2146
2497
|
case "config":
|
|
2147
2498
|
return makeConfig();
|
|
2148
2499
|
case "resource":
|
|
@@ -2153,8 +2504,6 @@ function nappletNamespacePrelude(domains) {
|
|
|
2153
2504
|
return makeOutbox();
|
|
2154
2505
|
case "upload":
|
|
2155
2506
|
return makeUpload();
|
|
2156
|
-
case "intent":
|
|
2157
|
-
return makeIntent();
|
|
2158
2507
|
case "webrtc":
|
|
2159
2508
|
return makeWebrtc();
|
|
2160
2509
|
case "ble":
|
|
@@ -2184,10 +2533,22 @@ function nappletNamespacePrelude(domains) {
|
|
|
2184
2533
|
},
|
|
2185
2534
|
defineProperty(obj, prop, descriptor) {
|
|
2186
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
|
+
}
|
|
2187
2544
|
return Reflect.defineProperty(obj, prop, {
|
|
2188
2545
|
...descriptor,
|
|
2189
2546
|
value: makeDomain(prop, descriptor.value)
|
|
2190
2547
|
});
|
|
2548
|
+
},
|
|
2549
|
+
deleteProperty(obj, prop) {
|
|
2550
|
+
if (typeof prop === "string" && allowed.has(prop) && isProtectedDomain(prop)) return true;
|
|
2551
|
+
return Reflect.deleteProperty(obj, prop);
|
|
2191
2552
|
}
|
|
2192
2553
|
});
|
|
2193
2554
|
}
|
|
@@ -2217,7 +2578,7 @@ function nappletNamespacePrelude(domains) {
|
|
|
2217
2578
|
root = buildNappletNamespace(value);
|
|
2218
2579
|
},
|
|
2219
2580
|
enumerable: false,
|
|
2220
|
-
configurable:
|
|
2581
|
+
configurable: false
|
|
2221
2582
|
});
|
|
2222
2583
|
}
|
|
2223
2584
|
|
|
@@ -2234,9 +2595,10 @@ function createIdentityProxy(deps) {
|
|
|
2234
2595
|
dispatch(windowId, envelope) {
|
|
2235
2596
|
deps.runtime.handleMessage(windowId, envelope);
|
|
2236
2597
|
},
|
|
2237
|
-
emit(
|
|
2238
|
-
|
|
2239
|
-
|
|
2598
|
+
emit(_windowId, _envelope) {
|
|
2599
|
+
throw new Error(
|
|
2600
|
+
"Direct identity proxy emit is prohibited; use ShellBridge.publishIdentityChanged()"
|
|
2601
|
+
);
|
|
2240
2602
|
}
|
|
2241
2603
|
};
|
|
2242
2604
|
}
|
|
@@ -2247,9 +2609,10 @@ function createThemeProxy(deps) {
|
|
|
2247
2609
|
dispatch(windowId, envelope) {
|
|
2248
2610
|
deps.runtime.handleMessage(windowId, envelope);
|
|
2249
2611
|
},
|
|
2250
|
-
emit(
|
|
2251
|
-
|
|
2252
|
-
|
|
2612
|
+
emit(_windowId, _envelope) {
|
|
2613
|
+
throw new Error(
|
|
2614
|
+
"Direct theme proxy emit is prohibited; use ShellBridge.publishTheme()"
|
|
2615
|
+
);
|
|
2253
2616
|
}
|
|
2254
2617
|
};
|
|
2255
2618
|
}
|
|
@@ -2312,6 +2675,7 @@ export {
|
|
|
2312
2675
|
nappKeyRegistry,
|
|
2313
2676
|
originRegistry,
|
|
2314
2677
|
renderNappletNamespacePrelude,
|
|
2678
|
+
resolveShellEnvironment,
|
|
2315
2679
|
sessionRegistry
|
|
2316
2680
|
};
|
|
2317
2681
|
//# sourceMappingURL=index.js.map
|