@kehto/shell 0.24.4 → 0.25.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 +16 -0
- package/dist/index.js +92 -24
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -304,3 +304,19 @@ for outer URL encoding, review, routing, and verified launcher behavior.
|
|
|
304
304
|
## GAMEPAD integration
|
|
305
305
|
|
|
306
306
|
This non-normative guidance defers to the living [NAP-GAMEPAD](https://github.com/napplet/naps/pull/108). Services export `createGamepadService` and `browserGamepadOptions`; shell exports `denyNappletControllerAccess`. Paja and the playground register one host reader and deny native gamepad/HID/USB paths before frame execution. Source-bound subscriptions receive immediate full snapshots; only the visible host's input owner gets live controls. Focus loss supplies neutral controls and timestamp zero; unsubscribe, window destruction, and service removal release polling. Non-browser or denied native readers report the proposal's unavailable reasons. See [Controller input](https://github.com/kehto/web/blob/main/docs/gamepad.md).
|
|
307
|
+
|
|
308
|
+
## UUID support on HTTP origins
|
|
309
|
+
|
|
310
|
+
Shell event injection, cache queries, and source-session registration use native
|
|
311
|
+
`crypto.randomUUID()` when available. On browsers that omit it (including
|
|
312
|
+
non-localhost plain HTTP origins), the shell generates UUID v4 identifiers from
|
|
313
|
+
16 `crypto.getRandomValues()` bytes. If both methods are unavailable, UUID
|
|
314
|
+
creation throws an explicit capability error. This internal host UUID helper
|
|
315
|
+
never falls back to weak randomness; other identifier generators are outside
|
|
316
|
+
its scope. It does not provide secure-context APIs such as `crypto.subtle` on
|
|
317
|
+
HTTP, so this change does not establish HTTP support for uploads or other domains.
|
|
318
|
+
|
|
319
|
+
This is a supported platform capability choice, not a deprecated caller API.
|
|
320
|
+
Both standard Web Crypto paths remain quiet. Source-bound identity and optional
|
|
321
|
+
shell readiness are unchanged (NAP-SHELL at `napplet/naps`
|
|
322
|
+
`1e1046bcadabbc552804f6d3354f565184f9fefb`).
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
// src/shell-bridge.ts
|
|
2
2
|
import { createRuntime } from "@kehto/runtime";
|
|
3
3
|
|
|
4
|
+
// src/random-uuid.ts
|
|
5
|
+
function randomUUID() {
|
|
6
|
+
const provider = globalThis.crypto;
|
|
7
|
+
if (typeof provider?.randomUUID === "function") return provider.randomUUID();
|
|
8
|
+
if (typeof provider?.getRandomValues !== "function") {
|
|
9
|
+
throw new Error("@kehto/shell requires crypto.randomUUID or crypto.getRandomValues for UUID generation");
|
|
10
|
+
}
|
|
11
|
+
const bytes = provider.getRandomValues(new Uint8Array(16));
|
|
12
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
13
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
14
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
15
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
4
18
|
// src/hooks-adapter.ts
|
|
5
19
|
function bytesToHex(bytes) {
|
|
6
20
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
@@ -66,7 +80,7 @@ function createCacheAdapter(shellHooks) {
|
|
|
66
80
|
async query(filters) {
|
|
67
81
|
const workerRelay = shellHooks.workerRelay.getWorkerRelay();
|
|
68
82
|
if (!workerRelay) return [];
|
|
69
|
-
const subId =
|
|
83
|
+
const subId = randomUUID();
|
|
70
84
|
return workerRelay.query(["REQ", subId, ...filters]);
|
|
71
85
|
},
|
|
72
86
|
store(event) {
|
|
@@ -113,7 +127,7 @@ function createCryptoAdapter(shellHooks) {
|
|
|
113
127
|
return shellHooks.crypto.verifyEvent(event);
|
|
114
128
|
},
|
|
115
129
|
randomUUID() {
|
|
116
|
-
return
|
|
130
|
+
return randomUUID();
|
|
117
131
|
},
|
|
118
132
|
randomBytes(length) {
|
|
119
133
|
const bytes = new Uint8Array(length);
|
|
@@ -1140,7 +1154,7 @@ function registerNip5dSessionIfNeeded({
|
|
|
1140
1154
|
dTag: identity.dTag,
|
|
1141
1155
|
aggregateHash: identity.aggregateHash,
|
|
1142
1156
|
registeredAt: Date.now(),
|
|
1143
|
-
instanceId:
|
|
1157
|
+
instanceId: randomUUID(),
|
|
1144
1158
|
provenance: "nip-5d"
|
|
1145
1159
|
};
|
|
1146
1160
|
runtime.sessionRegistry.register(windowId, entry);
|
|
@@ -2196,36 +2210,90 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
|
|
|
2196
2210
|
};
|
|
2197
2211
|
}
|
|
2198
2212
|
function makeMedia() {
|
|
2213
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
2214
|
+
const ended = /* @__PURE__ */ new Map();
|
|
2215
|
+
const retained = /* @__PURE__ */ new Map();
|
|
2216
|
+
const callbacks = /* @__PURE__ */ new Map();
|
|
2217
|
+
const warnings = /* @__PURE__ */ new Set();
|
|
2218
|
+
const warn = (name, replacement) => {
|
|
2219
|
+
if (warnings.has(name)) return;
|
|
2220
|
+
warnings.add(name);
|
|
2221
|
+
console.warn(`[KEHTO-MEDIA-${name}] Deprecated media API; use ${replacement}. Warned once per endpoint.`);
|
|
2222
|
+
};
|
|
2223
|
+
listen((event) => {
|
|
2224
|
+
if (!isParentMessage(event)) return;
|
|
2225
|
+
const msg = event.data;
|
|
2226
|
+
if (!msg || typeof msg !== "object" || typeof msg.sessionId !== "string") return;
|
|
2227
|
+
const sessionId = msg.sessionId;
|
|
2228
|
+
if (msg.type === "media.session.create.result" && !msg.error) {
|
|
2229
|
+
if (!ended.has(sessionId)) sessions.add(sessionId);
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
if (!sessions.has(sessionId) || ended.has(sessionId)) return;
|
|
2233
|
+
if (msg.type === "media.session.ended") {
|
|
2234
|
+
ended.set(sessionId, msg);
|
|
2235
|
+
retained.delete(sessionId);
|
|
2236
|
+
sessions.delete(sessionId);
|
|
2237
|
+
} else if (["media.state", "media.capabilities", "media.controls"].includes(msg.type)) {
|
|
2238
|
+
const cache2 = retained.get(sessionId) ?? /* @__PURE__ */ new Map();
|
|
2239
|
+
cache2.set(msg.type, msg);
|
|
2240
|
+
retained.set(sessionId, cache2);
|
|
2241
|
+
} else if (msg.type !== "media.command") return;
|
|
2242
|
+
const delivery = Array.from(callbacks.get(sessionId)?.get(msg.type) ?? []);
|
|
2243
|
+
for (const callback of delivery) callback(msg);
|
|
2244
|
+
if (ended.has(sessionId)) callbacks.delete(sessionId);
|
|
2245
|
+
});
|
|
2199
2246
|
const on = (type, sessionId, callback) => {
|
|
2200
|
-
const
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2247
|
+
const terminal = ended.get(sessionId);
|
|
2248
|
+
if (terminal) {
|
|
2249
|
+
if (type === "media.session.ended") callback(terminal);
|
|
2250
|
+
return subscriptionHandle(() => {
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
const byType = callbacks.get(sessionId) ?? /* @__PURE__ */ new Map();
|
|
2254
|
+
const handlers = byType.get(type) ?? /* @__PURE__ */ new Set();
|
|
2255
|
+
handlers.add(callback);
|
|
2256
|
+
byType.set(type, handlers);
|
|
2257
|
+
callbacks.set(sessionId, byType);
|
|
2258
|
+
const cached = retained.get(sessionId)?.get(type);
|
|
2259
|
+
if (cached) callback(cached);
|
|
2260
|
+
return subscriptionHandle(() => {
|
|
2261
|
+
handlers.delete(callback);
|
|
2204
2262
|
});
|
|
2205
|
-
return subscriptionHandle(off);
|
|
2206
2263
|
};
|
|
2207
|
-
|
|
2264
|
+
const command = (sessionId, value) => {
|
|
2265
|
+
if (ended.has(sessionId)) return;
|
|
2266
|
+
fire({ ...value && typeof value === "object" ? value : {}, type: "media.command", sessionId });
|
|
2267
|
+
};
|
|
2268
|
+
return Object.freeze({
|
|
2208
2269
|
createSession: (options) => request(
|
|
2209
|
-
{
|
|
2270
|
+
{ ...options && typeof options === "object" ? options : {}, type: "media.session.create" },
|
|
2210
2271
|
"media.session.create.result",
|
|
2211
2272
|
withoutEnvelope,
|
|
2212
2273
|
{ rejectOnError: false }
|
|
2213
2274
|
),
|
|
2214
2275
|
updateSession: (sessionId, metadata) => fire({ type: "media.session.update", sessionId, metadata }),
|
|
2215
2276
|
destroySession: (sessionId) => fire({ type: "media.session.destroy", sessionId }),
|
|
2216
|
-
reportState: (sessionId, state) => fire({
|
|
2277
|
+
reportState: (sessionId, state) => fire({ ...state && typeof state === "object" ? state : {}, type: "media.state", sessionId }),
|
|
2217
2278
|
reportCapabilities: (sessionId, actions) => fire({ type: "media.capabilities", sessionId, actions }),
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2279
|
+
command,
|
|
2280
|
+
sendCommand: (sessionId, action, value) => {
|
|
2281
|
+
warn("SEND-COMMAND", "media.command(sessionId, { action, value })");
|
|
2282
|
+
command(sessionId, { action, ...value === void 0 ? {} : { value } });
|
|
2283
|
+
},
|
|
2284
|
+
onCommand: (sessionId, callback) => {
|
|
2285
|
+
const legacy = callback.length >= 2;
|
|
2286
|
+
if (legacy) warn("COMMAND-CALLBACK", "media.onCommand(sessionId, command => ...)");
|
|
2287
|
+
return on("media.command", sessionId, (msg) => legacy ? callback(msg.action, msg.value) : callback({ action: msg.action, ...msg.value === void 0 ? {} : { value: msg.value }, ...msg.reason === void 0 ? {} : { reason: msg.reason } }));
|
|
2288
|
+
},
|
|
2289
|
+
onState: (sessionId, callback) => on("media.state", sessionId, (msg) => {
|
|
2290
|
+
const { type: _type, sessionId: _sessionId, ...state } = msg;
|
|
2291
|
+
callback(state);
|
|
2292
|
+
}),
|
|
2226
2293
|
onCapabilities: (sessionId, callback) => on("media.capabilities", sessionId, (msg) => callback(msg.actions)),
|
|
2227
|
-
onControls: (sessionId, callback) => on("media.controls", sessionId, (msg) => callback(msg.controls))
|
|
2228
|
-
|
|
2294
|
+
onControls: (sessionId, callback) => on("media.controls", sessionId, (msg) => callback(msg.controls)),
|
|
2295
|
+
onEnded: (sessionId, callback) => on("media.session.ended", sessionId, (msg) => callback({ sessionId: msg.sessionId, reason: msg.reason }))
|
|
2296
|
+
});
|
|
2229
2297
|
}
|
|
2230
2298
|
function makeNotify() {
|
|
2231
2299
|
const on = (type, callback) => {
|
|
@@ -2863,10 +2931,12 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
|
|
|
2863
2931
|
intent ??= Object.freeze(makeIntent());
|
|
2864
2932
|
return intent;
|
|
2865
2933
|
}
|
|
2934
|
+
let media;
|
|
2866
2935
|
function isProtectedDomain(domain) {
|
|
2867
|
-
return domain === "gamepad" || domain === "shell" || domain === "inc" || domain === "identity" || domain === "theme" || domain === "intent";
|
|
2936
|
+
return domain === "media" || domain === "gamepad" || domain === "shell" || domain === "inc" || domain === "identity" || domain === "theme" || domain === "intent";
|
|
2868
2937
|
}
|
|
2869
2938
|
function makeDomain(domain, existing) {
|
|
2939
|
+
if (domain === "media") return media ??= makeMedia();
|
|
2870
2940
|
if (domain === "gamepad") return gamepad ??= makeGamepad();
|
|
2871
2941
|
if (domain === "shell") return shell;
|
|
2872
2942
|
if (domain === "inc") return makeProtectedInc(existing);
|
|
@@ -2881,8 +2951,6 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
|
|
|
2881
2951
|
return makeStorage();
|
|
2882
2952
|
case "keys":
|
|
2883
2953
|
return makeKeys();
|
|
2884
|
-
case "media":
|
|
2885
|
-
return makeMedia();
|
|
2886
2954
|
case "notify":
|
|
2887
2955
|
return makeNotify();
|
|
2888
2956
|
case "config":
|