@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/README.md +99 -15
- package/dist/index.d.ts +146 -621
- package/dist/index.js +593 -215
- 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
|
*
|
|
@@ -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
|
|
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
|
-
|
|
1008
|
-
"
|
|
1009
|
-
"
|
|
1010
|
-
"
|
|
1011
|
-
"
|
|
1012
|
-
"
|
|
1013
|
-
"
|
|
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
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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
|
|
1050
|
-
|
|
1051
|
-
const disabled = new Set(disabledDomains);
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
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
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1086
|
-
|
|
1087
|
-
|
|
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
|
-
|
|
1111
|
+
state,
|
|
1112
|
+
sourceRegistrationId,
|
|
1113
|
+
windowId,
|
|
1114
|
+
identity
|
|
1094
1115
|
}) {
|
|
1095
|
-
|
|
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
|
-
|
|
1099
|
-
|
|
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(
|
|
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,
|
|
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(
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
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
|
-
|
|
1238
|
+
publishToEligibleNapplets(envelope, "theme", "theme:read");
|
|
1229
1239
|
},
|
|
1230
1240
|
publishIdentityChanged(pubkey) {
|
|
1231
1241
|
const envelope = { type: "identity.changed", pubkey };
|
|
1232
|
-
|
|
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
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
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
|
-
|
|
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
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1444
|
-
|
|
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
|
-
|
|
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
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
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: (
|
|
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:
|
|
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(
|
|
2223
|
-
|
|
2224
|
-
|
|
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(
|
|
2236
|
-
|
|
2237
|
-
|
|
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
|