@kehto/shell 0.22.0 → 0.23.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 +28 -22
- package/dist/index.d.ts +33 -1
- package/dist/index.js +237 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,24 +76,23 @@ the prior opener-only interpretation is obsolete.
|
|
|
76
76
|
|
|
77
77
|
### NAP-INTENT binding and delivery
|
|
78
78
|
|
|
79
|
-
Kehto
|
|
80
|
-
`
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
parent-originated correlated
|
|
86
|
-
|
|
87
|
-
Successful results
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
archived planning rather than presenting it as active guidance.
|
|
79
|
+
Kehto follows [NAP-INTENT PR #106](https://github.com/napplet/naps/blob/fc121fc264615482143eda86125863d2e1f741a2/naps/NAP-INTENT.md) at `fc121fc264615482143eda86125863d2e1f741a2`. The protected binding supports
|
|
80
|
+
`window.napplet.intent.invoke('napplet:profile/open?pubkey=abc%2B123')` and
|
|
81
|
+
`invoke(uri, { payload, handler, handlerHint, behavior })`. Query fields decode
|
|
82
|
+
once into text; `+` remains literal. An explicit payload cannot accompany an
|
|
83
|
+
inner query, and a `#naddr` recommendation cannot accompany `handlerHint`.
|
|
84
|
+
Unsupported options and caller-supplied sender data are rejected. Only a
|
|
85
|
+
parent-originated correlated result can settle the call.
|
|
86
|
+
|
|
87
|
+
Successful results identify retained delivery responsibility with `ok`,
|
|
88
|
+
`archetype`, `action`, `convention`, and an opaque `handler`. A target receives
|
|
89
|
+
one parent-attested `intent.deliver`, buffered by `onDelivery` until registration.
|
|
90
|
+
Acceptance is separate from later target completion.
|
|
91
|
+
|
|
92
|
+
Legacy object-form `invoke(request)` and `open(archetype, payload?, opts?)`
|
|
93
|
+
remain bounded compatibility adapters and warn once per iframe with
|
|
94
|
+
`KEHTO_COMPAT_INTENT_OBJECT_INVOKE`. Their removal conditions are tracked in
|
|
95
|
+
[compatibility](https://github.com/kehto/web/blob/main/docs/compatibility.md).
|
|
97
96
|
|
|
98
97
|
## Quick Start
|
|
99
98
|
|
|
@@ -256,8 +255,8 @@ MIT
|
|
|
256
255
|
|
|
257
256
|
## NIP-5D event compatibility
|
|
258
257
|
|
|
259
|
-
Current manifests use a direct artifact `x` hash, plain-text `content`,
|
|
260
|
-
`z`/`i`
|
|
258
|
+
Current manifests use a direct artifact `x` hash, plain-text `content`, role-matched
|
|
259
|
+
`z`/`i` intent declarations, and required `R` / optional `O` domains. Kehto also
|
|
261
260
|
accepts legacy aggregate events through an isolated compatibility adapter.
|
|
262
261
|
Existing `aggregateHash` host/cache/ACL fields carry the verified artifact hash
|
|
263
262
|
for current events; legacy identities keep their original aggregate. Both paths
|
|
@@ -275,5 +274,12 @@ A throwing listener does not prevent delivery to other listeners.
|
|
|
275
274
|
When no canonical listener is registered, matching legacy INC listeners can
|
|
276
275
|
consume the delivery locally. This emits `KEHTO_COMPAT_INTENT_INC` once per
|
|
277
276
|
iframe; a delivery is never replayed to both APIs. See [compatibility tracking](https://github.com/kehto/web/blob/main/docs/compatibility.md).
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
Delivery and URI invocation follow NAP-INTENT PR #106 at
|
|
278
|
+
`fc121fc264615482143eda86125863d2e1f741a2`; the packaged SDK still trails that
|
|
279
|
+
contract, as documented in the conformance policy.
|
|
280
|
+
|
|
281
|
+
## Paja URL integration
|
|
282
|
+
|
|
283
|
+
The shell normalizer also powers Paja intent links. See the
|
|
284
|
+
[Paja guide](https://kehto.github.io/web/docs/packages/paja.html#nap-intent-links-and-lifecycle)
|
|
285
|
+
for outer URL encoding, review, routing, and verified launcher behavior.
|
package/dist/index.d.ts
CHANGED
|
@@ -639,6 +639,38 @@ declare function renderNappletNamespacePrelude(options: NappletNamespacePreludeO
|
|
|
639
639
|
*/
|
|
640
640
|
declare function injectNappletNamespacePrelude(html: string, options: NappletNamespacePreludeOptions): string;
|
|
641
641
|
|
|
642
|
+
/** Canonical request fields produced from a NAP-INTENT convention URI. */
|
|
643
|
+
interface NormalizedIntentUri {
|
|
644
|
+
archetype: string;
|
|
645
|
+
action: string;
|
|
646
|
+
convention: string;
|
|
647
|
+
payload?: unknown;
|
|
648
|
+
handlerHint?: {
|
|
649
|
+
address: string;
|
|
650
|
+
relays?: string[];
|
|
651
|
+
};
|
|
652
|
+
handler?: string;
|
|
653
|
+
behavior?: {
|
|
654
|
+
focus?: boolean;
|
|
655
|
+
reuse?: boolean;
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Normalize a NAP-INTENT convention URI.
|
|
660
|
+
*
|
|
661
|
+
* The host and injected NIP-5D binding use the same normalizer factory.
|
|
662
|
+
*
|
|
663
|
+
* @param uri - Complete NAP-INTENT convention URI.
|
|
664
|
+
* @param options - Optional payload, handler, recommendation, and behavior hints.
|
|
665
|
+
* @returns The canonical request fields for `intent.invoke`.
|
|
666
|
+
* @example
|
|
667
|
+
* ```ts
|
|
668
|
+
* normalizeIntentUri('napplet:profile/open?pubkey=abc%2B123');
|
|
669
|
+
* // { archetype: 'profile', action: 'open', convention: 'napplet:profile/open', payload: { pubkey: 'abc+123' } }
|
|
670
|
+
* ```
|
|
671
|
+
*/
|
|
672
|
+
declare const normalizeIntentUri: (uri: unknown, options?: unknown) => NormalizedIntentUri;
|
|
673
|
+
|
|
642
674
|
/** Source-list directives supported by the shell's meta-delivered CSP. */
|
|
643
675
|
type NappletCspDirective = 'default-src' | 'script-src' | 'script-src-elem' | 'script-src-attr' | 'style-src' | 'style-src-elem' | 'style-src-attr' | 'img-src' | 'font-src' | 'connect-src' | 'worker-src' | 'child-src' | 'frame-src' | 'media-src' | 'object-src' | 'manifest-src' | 'base-uri' | 'form-action';
|
|
644
676
|
/** Host replacements for individual CSP source lists; omitted directives retain defaults. */
|
|
@@ -1528,4 +1560,4 @@ type ResourceInbound = ResourceInfoRequest | ResourceBytesRequest | ResourceByte
|
|
|
1528
1560
|
*/
|
|
1529
1561
|
type ResourceOutbound = ResourceInfoResult | ResourceInfoError | ResourceBytesResult | ResourceBytesError | ResourceBytesManyResult | ResourceBytesManyError;
|
|
1530
1562
|
|
|
1531
|
-
export { type AclCheckEvent, type AclEntry, type AclStore, type AudioManager, type AudioSource, type AuthHooks, type BleHooks, type BrowserDeps, type CapabilityHooks, type CommonHooks, type ConfigHooks, type CryptoHooks, type DmHooks, type HotkeyHooks, type IdentityProxy, type IdentityProxyDeps, type IntentHooks, type KeysProxy, type KeysProxyDeps, type LinkHooks, type ListsHooks, type ManifestCache, type ManifestCacheEntry, type MediaProxy, type MediaProxyDeps, type NappletCspDirective, type NappletCspDirectives, type NappletCspOptions, type NappletNamespacePreludeOptions, type NappletSrcdocOptions, type NotifyProxy, type NotifyProxyDeps, type OriginIdentity, type OriginRegistry, type ProxyOriginRegistry, type RelayConfigHooks, type RelayPoolHooks, type RelayPoolLike, type ResourceBytesError, type ResourceBytesManyError, type ResourceBytesManyItem, type ResourceBytesManyRequest, type ResourceBytesManyResult, type ResourceBytesRequest, type ResourceBytesResult, type ResourceCancelRequest, type ResourceErrorCode, type ResourceInbound, type ResourceInfoError, type ResourceInfoRequest, type ResourceInfoResult, type ResourceOutbound, type ResourceRequestId, type SerialHooks, type SessionRegistry, type ShellAdapter, type ShellBridge, type ShellCapabilities, type ShellEnvironment, type ThemeProxy, type ThemeProxyDeps, type UnroutedMessageInfo, type UploadBackendLike, type UploadHooks, type WebrtcHooks, type WindowManagerHooks, type WorkerRelayHooks, type WorkerRelayLike, adaptHooks, audioManager, buildNappletCsp, buildShellCapabilities, createIdentityProxy, createKeysProxy, createMediaProxy, createNotifyProxy, createShellBridge, createThemeProxy, injectNappletCsp, injectNappletNamespacePrelude, manifestCache, nappKeyRegistry, originRegistry, prepareNappletSrcdoc, renderNappletCspMeta, renderNappletNamespacePrelude, resolveShellEnvironment, sessionRegistry };
|
|
1563
|
+
export { type AclCheckEvent, type AclEntry, type AclStore, type AudioManager, type AudioSource, type AuthHooks, type BleHooks, type BrowserDeps, type CapabilityHooks, type CommonHooks, type ConfigHooks, type CryptoHooks, type DmHooks, type HotkeyHooks, type IdentityProxy, type IdentityProxyDeps, type IntentHooks, type KeysProxy, type KeysProxyDeps, type LinkHooks, type ListsHooks, type ManifestCache, type ManifestCacheEntry, type MediaProxy, type MediaProxyDeps, type NappletCspDirective, type NappletCspDirectives, type NappletCspOptions, type NappletNamespacePreludeOptions, type NappletSrcdocOptions, type NormalizedIntentUri, type NotifyProxy, type NotifyProxyDeps, type OriginIdentity, type OriginRegistry, type ProxyOriginRegistry, type RelayConfigHooks, type RelayPoolHooks, type RelayPoolLike, type ResourceBytesError, type ResourceBytesManyError, type ResourceBytesManyItem, type ResourceBytesManyRequest, type ResourceBytesManyResult, type ResourceBytesRequest, type ResourceBytesResult, type ResourceCancelRequest, type ResourceErrorCode, type ResourceInbound, type ResourceInfoError, type ResourceInfoRequest, type ResourceInfoResult, type ResourceOutbound, type ResourceRequestId, type SerialHooks, type SessionRegistry, type ShellAdapter, type ShellBridge, type ShellCapabilities, type ShellEnvironment, type ThemeProxy, type ThemeProxyDeps, type UnroutedMessageInfo, type UploadBackendLike, type UploadHooks, type WebrtcHooks, type WindowManagerHooks, type WorkerRelayHooks, type WorkerRelayLike, adaptHooks, audioManager, buildNappletCsp, buildShellCapabilities, createIdentityProxy, createKeysProxy, createMediaProxy, createNotifyProxy, createShellBridge, createThemeProxy, injectNappletCsp, injectNappletNamespacePrelude, manifestCache, nappKeyRegistry, normalizeIntentUri, originRegistry, prepareNappletSrcdoc, renderNappletCspMeta, renderNappletNamespacePrelude, resolveShellEnvironment, sessionRegistry };
|
package/dist/index.js
CHANGED
|
@@ -1246,6 +1246,181 @@ function createShellBridge(hooks) {
|
|
|
1246
1246
|
};
|
|
1247
1247
|
}
|
|
1248
1248
|
|
|
1249
|
+
// src/intent-uri.ts
|
|
1250
|
+
function createIntentUriNormalizer() {
|
|
1251
|
+
const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
1252
|
+
const fail = (message) => {
|
|
1253
|
+
throw new TypeError(message);
|
|
1254
|
+
};
|
|
1255
|
+
const decode = (value, label) => {
|
|
1256
|
+
try {
|
|
1257
|
+
return decodeURIComponent(value);
|
|
1258
|
+
} catch {
|
|
1259
|
+
return fail(`Intent URI has malformed percent-encoding in ${label}`);
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
const decodeUtf8 = (bytes) => {
|
|
1263
|
+
try {
|
|
1264
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(bytes));
|
|
1265
|
+
} catch {
|
|
1266
|
+
return fail("Intent URI fragment contains invalid UTF-8");
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
const decodeHint = (fragment) => {
|
|
1270
|
+
if (!fragment.startsWith("naddr") || fragment !== fragment.toLowerCase()) {
|
|
1271
|
+
return fail("Intent URI fragment must be a bare lowercase naddr");
|
|
1272
|
+
}
|
|
1273
|
+
const separator = fragment.lastIndexOf("1");
|
|
1274
|
+
if (separator <= 0 || separator + 7 > fragment.length) fail("Intent URI fragment is not a valid naddr");
|
|
1275
|
+
const alphabet = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
1276
|
+
const values = [];
|
|
1277
|
+
for (const character of fragment.slice(separator + 1)) {
|
|
1278
|
+
const value = alphabet.indexOf(character);
|
|
1279
|
+
if (value < 0) fail("Intent URI fragment is not a valid naddr");
|
|
1280
|
+
values.push(value);
|
|
1281
|
+
}
|
|
1282
|
+
const polymod = (input) => {
|
|
1283
|
+
let checksum = 1;
|
|
1284
|
+
for (const value of input) {
|
|
1285
|
+
const top = checksum >>> 25;
|
|
1286
|
+
checksum = (checksum & 33554431) << 5 ^ value;
|
|
1287
|
+
if (top & 1) checksum ^= 996825010;
|
|
1288
|
+
if (top & 2) checksum ^= 642813549;
|
|
1289
|
+
if (top & 4) checksum ^= 513874426;
|
|
1290
|
+
if (top & 8) checksum ^= 1027748829;
|
|
1291
|
+
if (top & 16) checksum ^= 705979059;
|
|
1292
|
+
}
|
|
1293
|
+
return checksum;
|
|
1294
|
+
};
|
|
1295
|
+
const hrp = fragment.slice(0, separator);
|
|
1296
|
+
const hrpValues = [...hrp].map((character) => character.charCodeAt(0) >>> 5).concat([0], [...hrp].map((character) => character.charCodeAt(0) & 31));
|
|
1297
|
+
if (hrp !== "naddr" || polymod(hrpValues.concat(values)) !== 1) fail("Intent URI fragment is not a valid naddr");
|
|
1298
|
+
const words = values.slice(0, -6);
|
|
1299
|
+
const bytes = [];
|
|
1300
|
+
let accumulator = 0;
|
|
1301
|
+
let bits = 0;
|
|
1302
|
+
for (const word of words) {
|
|
1303
|
+
accumulator = accumulator << 5 | word;
|
|
1304
|
+
bits += 5;
|
|
1305
|
+
while (bits >= 8) {
|
|
1306
|
+
bits -= 8;
|
|
1307
|
+
bytes.push(accumulator >>> bits & 255);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
if (bits >= 5 || (accumulator << 8 - bits & 255) !== 0) fail("Intent URI fragment is not a valid naddr");
|
|
1311
|
+
let index = 0;
|
|
1312
|
+
let identifier;
|
|
1313
|
+
let pubkey;
|
|
1314
|
+
let kind;
|
|
1315
|
+
const relays = [];
|
|
1316
|
+
while (index < bytes.length) {
|
|
1317
|
+
if (index + 2 > bytes.length) fail("Intent URI fragment is not a valid naddr");
|
|
1318
|
+
const type = bytes[index++];
|
|
1319
|
+
const length = bytes[index++];
|
|
1320
|
+
if (index + length > bytes.length) fail("Intent URI fragment is not a valid naddr");
|
|
1321
|
+
const value = bytes.slice(index, index + length);
|
|
1322
|
+
index += length;
|
|
1323
|
+
if (type === 0) {
|
|
1324
|
+
if (identifier !== void 0) fail("Intent URI fragment repeats its identifier");
|
|
1325
|
+
identifier = decodeUtf8(value);
|
|
1326
|
+
} else if (type === 1) {
|
|
1327
|
+
const relay = decodeUtf8(value);
|
|
1328
|
+
if (!relay) fail("Intent URI fragment has an empty relay hint");
|
|
1329
|
+
relays.push(relay);
|
|
1330
|
+
} else if (type === 2) {
|
|
1331
|
+
if (pubkey !== void 0 || value.length !== 32) fail("Intent URI fragment has an invalid pubkey");
|
|
1332
|
+
pubkey = value.map((part) => part.toString(16).padStart(2, "0")).join("");
|
|
1333
|
+
} else if (type === 3) {
|
|
1334
|
+
if (kind !== void 0 || value.length !== 4) fail("Intent URI fragment has an invalid kind");
|
|
1335
|
+
kind = (value[0] << 24 >>> 0 | value[1] << 16 | value[2] << 8 | value[3]) >>> 0;
|
|
1336
|
+
} else {
|
|
1337
|
+
fail("Intent URI fragment has unsupported naddr data");
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
if (!identifier || !pubkey || kind !== 35129) fail("Intent URI fragment must recommend a named 35129 napplet");
|
|
1341
|
+
return { address: `${kind}:${pubkey}:${identifier}`, ...relays.length > 0 ? { relays } : {} };
|
|
1342
|
+
};
|
|
1343
|
+
const normalizeHint = (value) => {
|
|
1344
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail("Intent handlerHint must be an object");
|
|
1345
|
+
const hint = value;
|
|
1346
|
+
if (Object.keys(hint).some((key) => key !== "address" && key !== "relays")) fail("Intent handlerHint contains unsupported fields");
|
|
1347
|
+
const address = hint.address;
|
|
1348
|
+
if (typeof address !== "string" || !/^35129:[0-9a-f]{64}:.+$/u.test(address)) {
|
|
1349
|
+
fail("Intent handlerHint address must be a named 35129 coordinate");
|
|
1350
|
+
}
|
|
1351
|
+
const relays = hint.relays;
|
|
1352
|
+
if (relays !== void 0 && (!Array.isArray(relays) || relays.some((relay) => typeof relay !== "string" || relay.length === 0))) {
|
|
1353
|
+
fail("Intent handlerHint relays must be non-empty text");
|
|
1354
|
+
}
|
|
1355
|
+
return { address, ...relays === void 0 ? {} : { relays: [...relays] } };
|
|
1356
|
+
};
|
|
1357
|
+
return function normalizeIntentUri2(uri, options) {
|
|
1358
|
+
if (typeof uri !== "string") throw new TypeError("Intent URI must be text");
|
|
1359
|
+
const match = /^napplet:([^/?#\s]+)\/([^/?#\s]+)(?:\?([^#]*))?(?:#(.*))?$/u.exec(uri);
|
|
1360
|
+
if (match === null) return fail("Intent URI must be napplet:<archetype>/<action>");
|
|
1361
|
+
const archetype = match[1];
|
|
1362
|
+
const action = match[2];
|
|
1363
|
+
if (!/^[a-z0-9][a-z0-9-]*$/u.test(archetype) || !/^[a-z0-9][a-z0-9-]*$/u.test(action)) {
|
|
1364
|
+
fail("Intent URI archetype and action must be lowercase slugs");
|
|
1365
|
+
}
|
|
1366
|
+
const query = match[3];
|
|
1367
|
+
const fragment = match[4];
|
|
1368
|
+
const supplied = options === void 0 ? {} : options;
|
|
1369
|
+
if (!supplied || typeof supplied !== "object" || Array.isArray(supplied)) fail("Intent options must be an object");
|
|
1370
|
+
const parsedOptions = supplied;
|
|
1371
|
+
if (Object.keys(parsedOptions).some((key) => key !== "payload" && key !== "handler" && key !== "handlerHint" && key !== "behavior")) {
|
|
1372
|
+
fail("Intent options contain unsupported fields");
|
|
1373
|
+
}
|
|
1374
|
+
const handler = parsedOptions.handler;
|
|
1375
|
+
if (handler !== void 0 && (typeof handler !== "string" || handler.length === 0)) {
|
|
1376
|
+
fail("Intent handler must be non-empty text");
|
|
1377
|
+
}
|
|
1378
|
+
let behavior;
|
|
1379
|
+
if (parsedOptions.behavior !== void 0) {
|
|
1380
|
+
if (!parsedOptions.behavior || typeof parsedOptions.behavior !== "object" || Array.isArray(parsedOptions.behavior)) fail("Intent behavior must be an object");
|
|
1381
|
+
const value = parsedOptions.behavior;
|
|
1382
|
+
if (Object.keys(value).some((key) => key !== "focus" && key !== "reuse") || hasOwn(value, "focus") && typeof value.focus !== "boolean" || hasOwn(value, "reuse") && typeof value.reuse !== "boolean") fail("Intent behavior contains unsupported fields");
|
|
1383
|
+
behavior = {
|
|
1384
|
+
...hasOwn(value, "focus") ? { focus: value.focus } : {},
|
|
1385
|
+
...hasOwn(value, "reuse") ? { reuse: value.reuse } : {}
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
let payload;
|
|
1389
|
+
if (query !== void 0) {
|
|
1390
|
+
if (hasOwn(parsedOptions, "payload")) fail("Intent URI query cannot accompany options.payload");
|
|
1391
|
+
if (query.length > 0) {
|
|
1392
|
+
const fields = /* @__PURE__ */ Object.create(null);
|
|
1393
|
+
for (const field of query.split("&")) {
|
|
1394
|
+
const equal = field.indexOf("=");
|
|
1395
|
+
if (equal <= 0 || field.indexOf("=", equal + 1) >= 0) fail("Intent URI query fields must be name=value");
|
|
1396
|
+
const name = decode(field.slice(0, equal), "a query name");
|
|
1397
|
+
const value = decode(field.slice(equal + 1), "a query value");
|
|
1398
|
+
if (!name || hasOwn(fields, name)) fail("Intent URI query names must be unique after decoding");
|
|
1399
|
+
Object.defineProperty(fields, name, { value, enumerable: true, writable: true, configurable: true });
|
|
1400
|
+
}
|
|
1401
|
+
payload = fields;
|
|
1402
|
+
}
|
|
1403
|
+
} else if (hasOwn(parsedOptions, "payload")) {
|
|
1404
|
+
payload = parsedOptions.payload;
|
|
1405
|
+
}
|
|
1406
|
+
if (fragment !== void 0) {
|
|
1407
|
+
if (!fragment) fail("Intent URI fragment must be a bare naddr");
|
|
1408
|
+
if (parsedOptions.handlerHint !== void 0) fail("Intent URI fragment cannot accompany options.handlerHint");
|
|
1409
|
+
}
|
|
1410
|
+
const handlerHint = fragment === void 0 ? parsedOptions.handlerHint === void 0 ? void 0 : normalizeHint(parsedOptions.handlerHint) : decodeHint(fragment);
|
|
1411
|
+
return {
|
|
1412
|
+
archetype,
|
|
1413
|
+
action,
|
|
1414
|
+
convention: `napplet:${archetype}/${action}`,
|
|
1415
|
+
...payload === void 0 ? {} : { payload },
|
|
1416
|
+
...handlerHint === void 0 ? {} : { handlerHint },
|
|
1417
|
+
...handler === void 0 ? {} : { handler },
|
|
1418
|
+
...behavior === void 0 ? {} : { behavior }
|
|
1419
|
+
};
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
var normalizeIntentUri = createIntentUriNormalizer();
|
|
1423
|
+
|
|
1249
1424
|
// src/napplet-namespace.ts
|
|
1250
1425
|
function uniqueBareDomains(domains) {
|
|
1251
1426
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1265,7 +1440,7 @@ function scriptJson(value) {
|
|
|
1265
1440
|
}
|
|
1266
1441
|
function renderNappletNamespacePrelude(options) {
|
|
1267
1442
|
const domains = uniqueBareDomains(["shell", ...options.domains]);
|
|
1268
|
-
return `<script data-kehto-nip5d-injection>(${nappletNamespacePrelude.toString()})(${scriptJson(domains)});</script>`;
|
|
1443
|
+
return `<script data-kehto-nip5d-injection>(${nappletNamespacePrelude.toString()})(${scriptJson(domains)}, (${createIntentUriNormalizer.toString()})());</script>`;
|
|
1269
1444
|
}
|
|
1270
1445
|
function injectNappletNamespacePrelude(html, options) {
|
|
1271
1446
|
const prelude = renderNappletNamespacePrelude(options);
|
|
@@ -1281,7 +1456,7 @@ function injectNappletNamespacePrelude(html, options) {
|
|
|
1281
1456
|
}
|
|
1282
1457
|
return `${prelude}${html}`;
|
|
1283
1458
|
}
|
|
1284
|
-
function nappletNamespacePrelude(domains) {
|
|
1459
|
+
function nappletNamespacePrelude(domains, normalizeIntentUri2) {
|
|
1285
1460
|
const target = window;
|
|
1286
1461
|
const allowed = new Set(domains);
|
|
1287
1462
|
const requestTimeoutMs = 3e4;
|
|
@@ -2324,7 +2499,14 @@ function nappletNamespacePrelude(domains) {
|
|
|
2324
2499
|
}
|
|
2325
2500
|
function makeIntent() {
|
|
2326
2501
|
const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
2327
|
-
|
|
2502
|
+
let warnedLegacyObject = false;
|
|
2503
|
+
const warnLegacyObject = () => {
|
|
2504
|
+
if (warnedLegacyObject) return;
|
|
2505
|
+
warnedLegacyObject = true;
|
|
2506
|
+
console.warn("[KEHTO_COMPAT_INTENT_OBJECT_INVOKE] Object-form intent.invoke/open is deprecated; use intent.invoke(uri, options) or intent.open(uri, options). Warns once per napplet. See docs/compatibility.md.");
|
|
2507
|
+
};
|
|
2508
|
+
const normalizeLegacyRequest = (value) => {
|
|
2509
|
+
warnLegacyObject();
|
|
2328
2510
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2329
2511
|
throw new TypeError("Intent request must be an object");
|
|
2330
2512
|
}
|
|
@@ -2332,7 +2514,7 @@ function nappletNamespacePrelude(domains) {
|
|
|
2332
2514
|
if (hasOwn(supplied, "sender")) {
|
|
2333
2515
|
throw new TypeError("Intent callers cannot supply sender");
|
|
2334
2516
|
}
|
|
2335
|
-
const allowed2 = ["archetype", "action", "convention", "payload", "handler", "behavior"];
|
|
2517
|
+
const allowed2 = ["archetype", "action", "convention", "payload", "handler", "handlerHint", "behavior"];
|
|
2336
2518
|
if (Object.keys(supplied).some((field) => !allowed2.includes(field))) {
|
|
2337
2519
|
throw new TypeError("Intent request contains unsupported fields");
|
|
2338
2520
|
}
|
|
@@ -2343,51 +2525,63 @@ function nappletNamespacePrelude(domains) {
|
|
|
2343
2525
|
if (typeof action !== "string" || action.length === 0) {
|
|
2344
2526
|
throw new TypeError("Intent action must be a non-empty string");
|
|
2345
2527
|
}
|
|
2346
|
-
|
|
2347
|
-
|
|
2528
|
+
const uri = supplied.convention === void 0 ? `napplet:${supplied.archetype}/${action}` : supplied.convention;
|
|
2529
|
+
if (typeof uri !== "string") throw new TypeError("Intent convention must be text");
|
|
2530
|
+
if (uri.includes("?") || uri.includes("#")) throw new TypeError("Legacy intent convention must be queryless");
|
|
2531
|
+
return normalizeIntentUri2(uri, {
|
|
2532
|
+
...hasOwn(supplied, "payload") ? { payload: supplied.payload } : {},
|
|
2533
|
+
...hasOwn(supplied, "handler") ? { handler: supplied.handler } : {},
|
|
2534
|
+
...hasOwn(supplied, "handlerHint") ? { handlerHint: supplied.handlerHint } : {},
|
|
2535
|
+
...hasOwn(supplied, "behavior") ? { behavior: supplied.behavior } : {}
|
|
2536
|
+
});
|
|
2537
|
+
};
|
|
2538
|
+
const requestInvoke = (normalized) => request(
|
|
2539
|
+
{
|
|
2540
|
+
type: "intent.invoke",
|
|
2541
|
+
request: normalized
|
|
2542
|
+
},
|
|
2543
|
+
"intent.invoke.result",
|
|
2544
|
+
(msg) => fieldOrThrow(msg, "result", "intent.invoke.result missing result")
|
|
2545
|
+
);
|
|
2546
|
+
const invoke = (uri, options) => {
|
|
2547
|
+
const normalized = typeof uri === "string" ? normalizeIntentUri2(uri, options) : normalizeLegacyRequest(uri);
|
|
2548
|
+
return requestInvoke(normalized);
|
|
2549
|
+
};
|
|
2550
|
+
const open = (uri, options, legacyOptions) => {
|
|
2551
|
+
if (typeof uri === "string" && uri.startsWith("napplet:") && legacyOptions === void 0) {
|
|
2552
|
+
const normalized2 = normalizeIntentUri2(uri, options);
|
|
2553
|
+
if (normalized2.action !== "open") throw new TypeError("intent.open requires a /open URI");
|
|
2554
|
+
return requestInvoke(normalized2);
|
|
2348
2555
|
}
|
|
2349
|
-
if (
|
|
2350
|
-
|
|
2556
|
+
if (typeof uri !== "string" || !uri) throw new TypeError("Intent archetype must be non-empty text");
|
|
2557
|
+
warnLegacyObject();
|
|
2558
|
+
if (legacyOptions !== void 0 && (!legacyOptions || typeof legacyOptions !== "object" || Array.isArray(legacyOptions))) {
|
|
2559
|
+
throw new TypeError("Intent options must be an object");
|
|
2351
2560
|
}
|
|
2352
|
-
const
|
|
2353
|
-
const
|
|
2354
|
-
if (
|
|
2355
|
-
throw new TypeError("
|
|
2561
|
+
const legacy = legacyOptions;
|
|
2562
|
+
const convention = legacy?.convention === void 0 ? `napplet:${uri}/open` : legacy.convention;
|
|
2563
|
+
if (typeof convention !== "string" || convention.includes("?") || convention.includes("#")) {
|
|
2564
|
+
throw new TypeError("Legacy intent convention must be queryless");
|
|
2356
2565
|
}
|
|
2357
|
-
const
|
|
2358
|
-
...
|
|
2359
|
-
...
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
...
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
const invoke = (value) => {
|
|
2372
|
-
const normalized = normalizeRequest(value);
|
|
2373
|
-
return request(
|
|
2374
|
-
{
|
|
2375
|
-
type: "intent.invoke",
|
|
2376
|
-
request: normalized
|
|
2377
|
-
},
|
|
2378
|
-
"intent.invoke.result",
|
|
2379
|
-
(msg) => fieldOrThrow(msg, "result", "intent.invoke.result missing result")
|
|
2380
|
-
);
|
|
2566
|
+
const normalized = normalizeIntentUri2(`napplet:${uri}/open`, {
|
|
2567
|
+
...options === void 0 ? {} : { payload: options },
|
|
2568
|
+
...legacy === void 0 ? {} : Object.fromEntries(
|
|
2569
|
+
Object.entries(legacy).filter(([key]) => key !== "convention")
|
|
2570
|
+
)
|
|
2571
|
+
});
|
|
2572
|
+
const legacyNormalized = convention === `napplet:${uri}/open` ? normalized : normalizeIntentUri2(convention, {
|
|
2573
|
+
...options === void 0 ? {} : { payload: options },
|
|
2574
|
+
...legacy === void 0 ? {} : Object.fromEntries(
|
|
2575
|
+
Object.entries(legacy).filter(([key]) => key !== "convention")
|
|
2576
|
+
)
|
|
2577
|
+
});
|
|
2578
|
+
if (legacyNormalized.action !== "open") throw new TypeError("intent.open requires a /open URI");
|
|
2579
|
+
return requestInvoke(legacyNormalized);
|
|
2381
2580
|
};
|
|
2382
2581
|
return {
|
|
2383
2582
|
onDelivery: intentDelivery.onDelivery,
|
|
2384
2583
|
invoke,
|
|
2385
|
-
open
|
|
2386
|
-
archetype,
|
|
2387
|
-
action: "open",
|
|
2388
|
-
...payload === void 0 ? {} : { payload },
|
|
2389
|
-
...opts
|
|
2390
|
-
}),
|
|
2584
|
+
open,
|
|
2391
2585
|
available: (archetype) => request(
|
|
2392
2586
|
{ type: "intent.available", archetype },
|
|
2393
2587
|
"intent.available.result",
|
|
@@ -2905,6 +3099,7 @@ export {
|
|
|
2905
3099
|
injectNappletNamespacePrelude,
|
|
2906
3100
|
manifestCache,
|
|
2907
3101
|
nappKeyRegistry,
|
|
3102
|
+
normalizeIntentUri,
|
|
2908
3103
|
originRegistry,
|
|
2909
3104
|
prepareNappletSrcdoc,
|
|
2910
3105
|
renderNappletCspMeta,
|