@kehto/shell 0.2.0 → 0.6.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
@@ -12,24 +12,22 @@ function hexToBytes(hex) {
12
12
  }
13
13
  return bytes;
14
14
  }
15
- function adaptHooks(shellHooks, deps) {
16
- const { originRegistry: originRegistry2 } = deps;
17
- const sendToNapplet = (windowId, msg) => {
15
+ function createSendToNapplet(originRegistry2) {
16
+ return (windowId, msg) => {
18
17
  const win = originRegistry2.getIframeWindow(windowId);
19
18
  if (win) win.postMessage(msg, "*");
20
19
  };
21
- const relayPool = {
20
+ }
21
+ function createRelayPoolAdapter(shellHooks, originRegistry2) {
22
+ return {
22
23
  subscribe(filters, callback, relayUrls) {
23
24
  const pool = shellHooks.relayPool.getRelayPool();
24
25
  if (!pool) return { unsubscribe() {
25
26
  } };
26
27
  const urls = relayUrls ?? shellHooks.relayPool.selectRelayTier(filters);
27
28
  const sub = pool.subscription(urls, filters).subscribe((item) => {
28
- if (item === "EOSE") {
29
- callback("EOSE");
30
- } else {
31
- callback(item);
32
- }
29
+ if (item === "EOSE") callback("EOSE");
30
+ else callback(item);
33
31
  });
34
32
  return { unsubscribe: () => sub.unsubscribe() };
35
33
  },
@@ -48,7 +46,7 @@ function adaptHooks(shellHooks, deps) {
48
46
  untrackSubscription(subKey) {
49
47
  shellHooks.relayPool.untrackSubscription(subKey);
50
48
  },
51
- openScopedRelay(windowId, relayUrl, subId, filters, sendFn) {
49
+ openScopedRelay(windowId, relayUrl, subId, filters, _sendFn) {
52
50
  const win = originRegistry2.getIframeWindow(windowId);
53
51
  if (win) shellHooks.relayPool.openScopedRelay(windowId, relayUrl, subId, filters, win);
54
52
  },
@@ -62,7 +60,9 @@ function adaptHooks(shellHooks, deps) {
62
60
  return shellHooks.relayPool.getRelayPool() !== null;
63
61
  }
64
62
  };
65
- const cache2 = {
63
+ }
64
+ function createCacheAdapter(shellHooks) {
65
+ return {
66
66
  async query(filters) {
67
67
  const workerRelay = shellHooks.workerRelay.getWorkerRelay();
68
68
  if (!workerRelay) return [];
@@ -82,7 +82,9 @@ function adaptHooks(shellHooks, deps) {
82
82
  return shellHooks.workerRelay.getWorkerRelay() !== null;
83
83
  }
84
84
  };
85
- const auth = {
85
+ }
86
+ function createAuthAdapter(shellHooks) {
87
+ return {
86
88
  getUserPubkey() {
87
89
  return shellHooks.auth.getUserPubkey();
88
90
  },
@@ -90,17 +92,23 @@ function adaptHooks(shellHooks, deps) {
90
92
  return shellHooks.auth.getSigner();
91
93
  }
92
94
  };
93
- const config = {
95
+ }
96
+ function createConfigAdapter(shellHooks) {
97
+ return {
94
98
  getNappUpdateBehavior() {
95
99
  return shellHooks.config.getNappUpdateBehavior();
96
100
  }
97
101
  };
98
- const hotkeys = {
102
+ }
103
+ function createHotkeyAdapter(shellHooks) {
104
+ return {
99
105
  executeHotkeyFromForward(event) {
100
106
  shellHooks.hotkeys.executeHotkeyFromForward(event);
101
107
  }
102
108
  };
103
- const cryptoHooks = {
109
+ }
110
+ function createCryptoAdapter(shellHooks) {
111
+ return {
104
112
  async verifyEvent(event) {
105
113
  return shellHooks.crypto.verifyEvent(event);
106
114
  },
@@ -113,7 +121,9 @@ function adaptHooks(shellHooks, deps) {
113
121
  return bytes;
114
122
  }
115
123
  };
116
- const aclPersistence = {
124
+ }
125
+ function createAclPersistence() {
126
+ return {
117
127
  persist(data) {
118
128
  try {
119
129
  localStorage.setItem("napplet:acl", data);
@@ -128,7 +138,9 @@ function adaptHooks(shellHooks, deps) {
128
138
  }
129
139
  }
130
140
  };
131
- const manifestPersistence = {
141
+ }
142
+ function createManifestPersistence() {
143
+ return {
132
144
  persist(data) {
133
145
  try {
134
146
  localStorage.setItem("napplet:manifest-cache", data);
@@ -143,7 +155,9 @@ function adaptHooks(shellHooks, deps) {
143
155
  }
144
156
  }
145
157
  };
146
- const statePersistence = {
158
+ }
159
+ function createStatePersistence() {
160
+ return {
147
161
  get(scopedKey) {
148
162
  try {
149
163
  return localStorage.getItem(scopedKey);
@@ -204,12 +218,16 @@ function adaptHooks(shellHooks, deps) {
204
218
  }
205
219
  }
206
220
  };
207
- const windowManager = {
221
+ }
222
+ function createWindowManagerAdapter(shellHooks) {
223
+ return {
208
224
  createWindow(options) {
209
225
  return shellHooks.windowManager.createWindow(options);
210
226
  }
211
227
  };
212
- const relayConfig = {
228
+ }
229
+ function createRelayConfigAdapter(shellHooks) {
230
+ return {
213
231
  addRelay(tier, url) {
214
232
  shellHooks.relayConfig.addRelay(tier, url);
215
233
  },
@@ -223,7 +241,9 @@ function adaptHooks(shellHooks, deps) {
223
241
  return shellHooks.relayConfig.getNip66Suggestions();
224
242
  }
225
243
  };
226
- const shellSecretPersistence = {
244
+ }
245
+ function createShellSecretPersistence() {
246
+ return {
227
247
  get() {
228
248
  try {
229
249
  const hex = localStorage.getItem("napplet-shell-secret");
@@ -240,7 +260,9 @@ function adaptHooks(shellHooks, deps) {
240
260
  }
241
261
  }
242
262
  };
243
- const guidPersistence = {
263
+ }
264
+ function createGuidPersistence() {
265
+ return {
244
266
  get(windowId) {
245
267
  try {
246
268
  return localStorage.getItem(`napplet-guid:${windowId}`);
@@ -261,27 +283,32 @@ function adaptHooks(shellHooks, deps) {
261
283
  }
262
284
  }
263
285
  };
264
- const dm = shellHooks.dm ? {
286
+ }
287
+ function createDmAdapter(shellHooks) {
288
+ return shellHooks.dm ? {
265
289
  sendDm(recipientPubkey, message) {
266
290
  return shellHooks.dm.sendDm(recipientPubkey, message);
267
291
  }
268
292
  } : void 0;
293
+ }
294
+ function adaptHooks(shellHooks, deps) {
295
+ const { originRegistry: originRegistry2 } = deps;
269
296
  return {
270
- sendToNapplet,
271
- relayPool,
272
- cache: cache2,
273
- auth,
274
- config,
275
- hotkeys,
276
- crypto: cryptoHooks,
277
- aclPersistence,
278
- manifestPersistence,
279
- statePersistence,
280
- windowManager,
281
- relayConfig,
282
- dm,
283
- shellSecretPersistence,
284
- guidPersistence,
297
+ sendToNapplet: createSendToNapplet(originRegistry2),
298
+ relayPool: createRelayPoolAdapter(shellHooks, originRegistry2),
299
+ cache: createCacheAdapter(shellHooks),
300
+ auth: createAuthAdapter(shellHooks),
301
+ config: createConfigAdapter(shellHooks),
302
+ hotkeys: createHotkeyAdapter(shellHooks),
303
+ crypto: createCryptoAdapter(shellHooks),
304
+ aclPersistence: createAclPersistence(),
305
+ manifestPersistence: createManifestPersistence(),
306
+ statePersistence: createStatePersistence(),
307
+ windowManager: createWindowManagerAdapter(shellHooks),
308
+ relayConfig: createRelayConfigAdapter(shellHooks),
309
+ dm: createDmAdapter(shellHooks),
310
+ shellSecretPersistence: createShellSecretPersistence(),
311
+ guidPersistence: createGuidPersistence(),
285
312
  onAclCheck: shellHooks.onAclCheck,
286
313
  onHashMismatch: shellHooks.onHashMismatch,
287
314
  services: shellHooks.services,
@@ -503,7 +530,8 @@ var CAP_BITS = {
503
530
  "media:control": 1024,
504
531
  "notify:send": 2048,
505
532
  "notify:channel": 4096,
506
- "theme:read": 8192
533
+ "theme:read": 8192,
534
+ "identity:decrypt": 65536
507
535
  };
508
536
  function capArrayToBitfield(caps) {
509
537
  let bits = 0;
@@ -864,7 +892,7 @@ var audioManager = {
864
892
  if (iframeWindow) {
865
893
  const muteEvent = {
866
894
  kind: 29e3,
867
- // IPC_PEER — inlined numeric after Phase 24 DRIFT-01 shim removal
895
+ // IFC_PEER — inlined numeric after Phase 24 DRIFT-01 shim removal
868
896
  created_at: Math.floor(Date.now() / 1e3),
869
897
  tags: [["t", "napplet:audio-muted"]],
870
898
  content: JSON.stringify({ muted }),
@@ -916,21 +944,6 @@ var audioManager = {
916
944
  }
917
945
  };
918
946
 
919
- // src/shell-init.ts
920
- var CANONICAL_NUB_DOMAINS = [
921
- "identity",
922
- "storage",
923
- "ifc",
924
- "theme",
925
- "keys",
926
- "media",
927
- "notify"
928
- ];
929
- function buildShellCapabilities(hooks) {
930
- const nubs = hooks.relayPool ? ["relay", ...CANONICAL_NUB_DOMAINS] : [...CANONICAL_NUB_DOMAINS];
931
- return { nubs, sandbox: [] };
932
- }
933
-
934
947
  // src/keys-forwarder.ts
935
948
  function createKeysForwarder(deps) {
936
949
  const target = deps.target ?? (typeof window !== "undefined" ? window : new EventTarget());
@@ -961,6 +974,186 @@ function createKeysForwarder(deps) {
961
974
  };
962
975
  }
963
976
 
977
+ // src/connect-store.ts
978
+ var STORAGE_KEY3 = "napplet:connect";
979
+ function connectKey(dTag, aggregateHash) {
980
+ return `${dTag}:${aggregateHash}`;
981
+ }
982
+ var store2 = /* @__PURE__ */ new Map();
983
+ var connectStore = {
984
+ check(dTag, aggregateHash, origin) {
985
+ const entry = store2.get(connectKey(dTag, aggregateHash));
986
+ if (!entry) return false;
987
+ return entry.origins.includes(origin);
988
+ },
989
+ getOrigins(dTag, aggregateHash) {
990
+ const entry = store2.get(connectKey(dTag, aggregateHash));
991
+ return entry ? [...entry.origins] : [];
992
+ },
993
+ grant(dTag, aggregateHash, origins) {
994
+ const key = connectKey(dTag, aggregateHash);
995
+ const sorted = [...new Set(origins)].sort();
996
+ store2.set(key, { key, dTag, aggregateHash, origins: sorted, grantedAt: Date.now() });
997
+ connectStore.persist();
998
+ },
999
+ revoke(dTag, aggregateHash) {
1000
+ store2.delete(connectKey(dTag, aggregateHash));
1001
+ connectStore.persist();
1002
+ },
1003
+ getAllGrants() {
1004
+ return Array.from(store2.values()).map((e) => ({
1005
+ dTag: e.dTag,
1006
+ aggregateHash: e.aggregateHash,
1007
+ origins: [...e.origins]
1008
+ }));
1009
+ },
1010
+ persist() {
1011
+ try {
1012
+ const entries = Array.from(store2.entries()).map(([k, v]) => [k, {
1013
+ dTag: v.dTag,
1014
+ aggregateHash: v.aggregateHash,
1015
+ origins: v.origins,
1016
+ grantedAt: v.grantedAt
1017
+ }]);
1018
+ localStorage.setItem(STORAGE_KEY3, JSON.stringify(entries));
1019
+ } catch {
1020
+ }
1021
+ },
1022
+ load() {
1023
+ try {
1024
+ const raw = localStorage.getItem(STORAGE_KEY3);
1025
+ if (!raw) return;
1026
+ const entries = JSON.parse(raw);
1027
+ store2.clear();
1028
+ for (const [key, val] of entries) {
1029
+ if (!Array.isArray(val.origins)) continue;
1030
+ store2.set(key, {
1031
+ key,
1032
+ dTag: val.dTag,
1033
+ aggregateHash: val.aggregateHash,
1034
+ origins: [...val.origins],
1035
+ grantedAt: val.grantedAt ?? 0
1036
+ });
1037
+ }
1038
+ } catch {
1039
+ store2.clear();
1040
+ }
1041
+ },
1042
+ clear() {
1043
+ store2.clear();
1044
+ try {
1045
+ localStorage.removeItem(STORAGE_KEY3);
1046
+ } catch {
1047
+ }
1048
+ }
1049
+ };
1050
+ function connectGrantKey(dTag, aggregateHash) {
1051
+ return `${dTag}:${aggregateHash}`;
1052
+ }
1053
+
1054
+ // src/shell-init.ts
1055
+ var CANONICAL_NUB_DOMAINS = [
1056
+ "identity",
1057
+ "storage",
1058
+ "ifc",
1059
+ "theme",
1060
+ "keys",
1061
+ "media",
1062
+ "notify",
1063
+ "config",
1064
+ "resource",
1065
+ "connect",
1066
+ "class",
1067
+ "cvm"
1068
+ ];
1069
+ var SUPPORTED_IFC_PROTOCOLS = [
1070
+ "ifc:NAP-01",
1071
+ "ifc:NUB-01",
1072
+ "ifc:NUB-02",
1073
+ "ifc:NUB-03",
1074
+ "ifc:NUB-04",
1075
+ "ifc:NUB-05",
1076
+ "ifc:NUB-06"
1077
+ ];
1078
+ function buildShellCapabilities(hooks) {
1079
+ const nubs = hooks.relayPool ? ["relay", "outbox", ...CANONICAL_NUB_DOMAINS, ...SUPPORTED_IFC_PROTOCOLS] : [...CANONICAL_NUB_DOMAINS, ...SUPPORTED_IFC_PROTOCOLS];
1080
+ return { nubs, sandbox: [] };
1081
+ }
1082
+
1083
+ // src/shell-ready.ts
1084
+ function handleShellReady({
1085
+ hooks,
1086
+ origin,
1087
+ runtime,
1088
+ windowId
1089
+ }) {
1090
+ const capabilities = buildShellCapabilities(hooks);
1091
+ registerNip5dSessionIfNeeded({ hooks, origin, runtime, windowId });
1092
+ postShellInit(runtime, windowId, capabilities, Object.keys(hooks.services ?? {}));
1093
+ }
1094
+ function registerNip5dSessionIfNeeded({
1095
+ hooks,
1096
+ origin,
1097
+ runtime,
1098
+ windowId
1099
+ }) {
1100
+ if (runtime.sessionRegistry.getEntryByWindowId(windowId)) {
1101
+ return;
1102
+ }
1103
+ const identity = resolveNip5dIdentity(hooks, windowId);
1104
+ if (!identity) {
1105
+ return;
1106
+ }
1107
+ const entry = {
1108
+ pubkey: "",
1109
+ windowId,
1110
+ origin,
1111
+ type: "nip5d",
1112
+ dTag: identity.dTag,
1113
+ aggregateHash: identity.aggregateHash,
1114
+ registeredAt: Date.now(),
1115
+ instanceId: crypto.randomUUID(),
1116
+ provenance: "nip-5d",
1117
+ class: identity.class
1118
+ };
1119
+ runtime.sessionRegistry.register(windowId, entry);
1120
+ }
1121
+ function resolveNip5dIdentity(hooks, windowId) {
1122
+ const hookIdentity = hooks.onNip5dIframeCreate?.(windowId);
1123
+ if (hookIdentity !== null && hookIdentity !== void 0) {
1124
+ return {
1125
+ dTag: hookIdentity.dTag,
1126
+ aggregateHash: hookIdentity.aggregateHash,
1127
+ class: hookIdentity.class
1128
+ };
1129
+ }
1130
+ const win = originRegistry.getIframeWindow(windowId);
1131
+ if (!win) {
1132
+ return null;
1133
+ }
1134
+ const originIdentity = originRegistry.getIdentity(win);
1135
+ if (!originIdentity) {
1136
+ return null;
1137
+ }
1138
+ return {
1139
+ dTag: originIdentity.dTag,
1140
+ aggregateHash: originIdentity.aggregateHash,
1141
+ class: null
1142
+ };
1143
+ }
1144
+ function postShellInit(runtime, windowId, capabilities, services) {
1145
+ const sessionEntry = runtime.sessionRegistry.getEntryByWindowId(windowId);
1146
+ const resolvedClass = sessionEntry?.class ?? null;
1147
+ const initMsg = {
1148
+ type: "shell.init",
1149
+ capabilities,
1150
+ services,
1151
+ class: resolvedClass
1152
+ };
1153
+ const win = originRegistry.getIframeWindow(windowId);
1154
+ if (win) win.postMessage(initMsg, "*");
1155
+ }
1156
+
964
1157
  // src/shell-bridge.ts
965
1158
  function createShellBridge(hooks) {
966
1159
  const runtimeHooks = adaptHooks(hooks, {
@@ -971,6 +1164,14 @@ function createShellBridge(hooks) {
971
1164
  nappKeyRegistry
972
1165
  });
973
1166
  const runtime = createRuntime(runtimeHooks);
1167
+ function broadcastToNapplets(envelope) {
1168
+ const windowIds = originRegistry.getAllWindowIds();
1169
+ for (const windowId of windowIds) {
1170
+ const win = originRegistry.getIframeWindow(windowId);
1171
+ if (!win) continue;
1172
+ win.postMessage(envelope, "*");
1173
+ }
1174
+ }
974
1175
  let keysForwarder = null;
975
1176
  if (typeof window !== "undefined") {
976
1177
  try {
@@ -997,14 +1198,7 @@ function createShellBridge(hooks) {
997
1198
  const msg = event.data;
998
1199
  if (typeof msg !== "object" || msg === null || typeof msg.type !== "string") return;
999
1200
  if (msg.type === "shell.ready") {
1000
- const capabilities = buildShellCapabilities(hooks);
1001
- const initMsg = {
1002
- type: "shell.init",
1003
- capabilities,
1004
- services: Object.keys(hooks.services ?? {})
1005
- };
1006
- const win = originRegistry.getIframeWindow(windowId);
1007
- if (win) win.postMessage(initMsg, "*");
1201
+ handleShellReady({ hooks, origin: event.origin, runtime, windowId });
1008
1202
  return;
1009
1203
  }
1010
1204
  runtime.handleMessage(windowId, msg);
@@ -1021,15 +1215,17 @@ function createShellBridge(hooks) {
1021
1215
  },
1022
1216
  publishTheme(theme) {
1023
1217
  const envelope = { type: "theme.changed", theme };
1024
- const windowIds = originRegistry.getAllWindowIds();
1025
- for (const windowId of windowIds) {
1026
- const win = originRegistry.getIframeWindow(windowId);
1027
- if (!win) continue;
1028
- win.postMessage(envelope, "*");
1029
- }
1218
+ broadcastToNapplets(envelope);
1219
+ },
1220
+ publishIdentityChanged(pubkey) {
1221
+ const envelope = { type: "identity.changed", pubkey };
1222
+ broadcastToNapplets(envelope);
1030
1223
  },
1031
1224
  get runtime() {
1032
1225
  return runtime;
1226
+ },
1227
+ get connectStore() {
1228
+ return connectStore;
1033
1229
  }
1034
1230
  };
1035
1231
  }
@@ -1111,6 +1307,8 @@ export {
1111
1307
  adaptHooks,
1112
1308
  audioManager,
1113
1309
  buildShellCapabilities,
1310
+ connectGrantKey,
1311
+ connectStore,
1114
1312
  createEnforceGate,
1115
1313
  createIdentityProxy,
1116
1314
  createKeysForwarder,