@kehto/shell 0.28.0 → 0.29.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 CHANGED
@@ -126,6 +126,16 @@ survives same-frame navigation, but the port lives only in the verified document
126
126
  demand.
127
127
  - `ShellBridge.observeFrame(frame, windowId)` watches for the second load. Call
128
128
  it after the frame is in the DOM and before assigning `srcdoc`.
129
+ - Load counting cannot see a napplet that navigates its frame before its own
130
+ first `load`: only the replacement fires `load`, and browsers fire no
131
+ MessagePort `close` event. Lock the frame instead: put
132
+ `renderNappletFramePolicyMeta()` (`Content-Security-Policy: frame-src 'none'`,
133
+ exported as `NAPPLET_FRAME_POLICY`) in the host page `<head>`, or send the
134
+ equivalent HTTP header. Browsers check every navigation of a child frame
135
+ against the embedding page's `frame-src`, including one the frame starts
136
+ itself, while an unfetched `srcdoc` still loads. `hasNappletFramePolicy(document)`
137
+ reports whether the meta element is present, and `observeFrame` warns once
138
+ when it is not. This is host page policy, not napplet protocol surface.
129
139
  - `originRegistry.register()` throws for a `Window` that already handed over (or
130
140
  lost) its port. Create a fresh iframe for every reload or navigation.
131
141
  - `ShellAdapter.onNappletMessage(windowId, message)` observes each port envelope
package/dist/index.d.ts CHANGED
@@ -932,6 +932,42 @@ interface NappletSrcdocOptions extends NappletNamespacePreludeOptions {
932
932
  */
933
933
  declare function prepareNappletSrcdoc(html: string, options: NappletSrcdocOptions): string;
934
934
 
935
+ /**
936
+ * @kehto/shell -- the embedding-page policy that keeps a napplet frame on its verified document.
937
+ *
938
+ * NIP-5D treats a second `load`, a second `napplet.hello` or the port closing as evidence that the verified document
939
+ * was replaced. None of these can observe a napplet that navigates its frame before its own first `load`: only the
940
+ * replacement fires `load`, and browsers fire no MessagePort `close` event. CSP checks every navigation of a child
941
+ * frame against the embedding page's `frame-src`, including one the frame starts itself, while an unfetched `srcdoc`
942
+ * document still loads. Setting this policy on the host page therefore blocks the replacement outright.
943
+ * This is host page policy, not napplet protocol surface: https://github.com/nostr-protocol/nips/pull/2303
944
+ */
945
+ /** The `frame-src` directive a host page sets so napplet frames cannot navigate away from their `srcdoc`. */
946
+ declare const NAPPLET_FRAME_POLICY = "frame-src 'none'";
947
+ /**
948
+ * Render the host-page `<meta>` element carrying {@link NAPPLET_FRAME_POLICY}. Place it in the host page's `<head>`;
949
+ * a `meta` CSP outside `<head>` is ignored. An equivalent HTTP `Content-Security-Policy` header works too.
950
+ *
951
+ * @returns The meta element HTML
952
+ * @example
953
+ * ```ts
954
+ * const head = `<head>${renderNappletFramePolicyMeta()}</head>`;
955
+ * ```
956
+ */
957
+ declare function renderNappletFramePolicyMeta(): string;
958
+ /**
959
+ * Whether a host document carries {@link NAPPLET_FRAME_POLICY} in a `<meta>` CSP. A policy delivered only as an HTTP
960
+ * header is not visible here.
961
+ *
962
+ * @param doc - The host page document
963
+ * @returns True when a CSP meta element contains the directive
964
+ * @example
965
+ * ```ts
966
+ * if (!hasNappletFramePolicy(document)) throw new Error('napplet frames are not navigation-locked');
967
+ * ```
968
+ */
969
+ declare function hasNappletFramePolicy(doc: Pick<Document, 'querySelectorAll'>): boolean;
970
+
935
971
  /**
936
972
  * Manifest cache — persists verified NIP-5A aggregate hashes per napplet identity.
937
973
  */
@@ -1764,4 +1800,4 @@ type ResourceInbound = ResourceInfoRequest | ResourceBytesRequest | ResourceByte
1764
1800
  */
1765
1801
  type ResourceOutbound = ResourceInfoResult | ResourceInfoError | ResourceBytesResult | ResourceBytesError | ResourceBytesManyResult | ResourceBytesManyError;
1766
1802
 
1767
- 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 LlmHooks, type ManifestCache, type ManifestCacheEntry, type MediaProxy, type MediaProxyDeps, NAPPLET_HELLO, type NappletCspDirective, type NappletCspDirectives, type NappletCspOptions, type NappletMessageTarget, type NappletNamespacePreludeOptions, type NappletRevocation, type NappletRevocationReason, 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 ShellDomainState, 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, denyNappletControllerAccess, getShellDomainState, injectNappletCsp, injectNappletNamespacePrelude, manifestCache, nappKeyRegistry, normalizeIntentUri, originRegistry, prepareNappletSrcdoc, renderNappletCspMeta, renderNappletNamespacePrelude, resolveShellEnvironment, sessionRegistry };
1803
+ 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 LlmHooks, type ManifestCache, type ManifestCacheEntry, type MediaProxy, type MediaProxyDeps, NAPPLET_FRAME_POLICY, NAPPLET_HELLO, type NappletCspDirective, type NappletCspDirectives, type NappletCspOptions, type NappletMessageTarget, type NappletNamespacePreludeOptions, type NappletRevocation, type NappletRevocationReason, 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 ShellDomainState, 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, denyNappletControllerAccess, getShellDomainState, hasNappletFramePolicy, injectNappletCsp, injectNappletNamespacePrelude, manifestCache, nappKeyRegistry, normalizeIntentUri, originRegistry, prepareNappletSrcdoc, renderNappletCspMeta, renderNappletFramePolicyMeta, renderNappletNamespacePrelude, resolveShellEnvironment, sessionRegistry };
package/dist/index.js CHANGED
@@ -1280,6 +1280,15 @@ function postShellInit(windowId, environment) {
1280
1280
  originRegistry.postToNapplet(windowId, initMsg);
1281
1281
  }
1282
1282
 
1283
+ // src/frame-policy.ts
1284
+ var NAPPLET_FRAME_POLICY = "frame-src 'none'";
1285
+ function renderNappletFramePolicyMeta() {
1286
+ return `<meta http-equiv="Content-Security-Policy" content="${NAPPLET_FRAME_POLICY}">`;
1287
+ }
1288
+ function hasNappletFramePolicy(doc) {
1289
+ return [...doc.querySelectorAll('meta[http-equiv="Content-Security-Policy" i]')].some((meta) => meta.content.split(";").some((directive) => directive.trim() === NAPPLET_FRAME_POLICY));
1290
+ }
1291
+
1283
1292
  // src/nip5d-transport.ts
1284
1293
  var NAPPLET_HELLO = "napplet.hello";
1285
1294
  function reportUnrouted(hooks, event, reason) {
@@ -1302,6 +1311,15 @@ var REVOCATION_DETAIL = {
1302
1311
  function isHello(data) {
1303
1312
  return typeof data === "object" && data !== null && data.type === NAPPLET_HELLO;
1304
1313
  }
1314
+ var framePolicyWarned = false;
1315
+ function warnWithoutFramePolicy(frame) {
1316
+ const doc = frame.ownerDocument;
1317
+ if (framePolicyWarned || !doc || hasNappletFramePolicy(doc)) return;
1318
+ framePolicyWarned = true;
1319
+ console.warn(
1320
+ `[kehto] The host page has no "${NAPPLET_FRAME_POLICY}" CSP. A napplet that navigates its frame before its first load is invisible to NIP-5D's replacement evidence; add renderNappletFramePolicyMeta() to the host page <head>.`
1321
+ );
1322
+ }
1305
1323
  function createNip5dTransport({ hooks, runtime, state: shellReadyState }) {
1306
1324
  const bindings = /* @__PURE__ */ new Map();
1307
1325
  function currentBinding(windowId) {
@@ -1404,6 +1422,7 @@ function createNip5dTransport({ hooks, runtime, state: shellReadyState }) {
1404
1422
  bindPort(windowId, sourceWindow, event.origin, ports[0]);
1405
1423
  }
1406
1424
  function observeFrame(frame, windowId) {
1425
+ warnWithoutFramePolicy(frame);
1407
1426
  let loads = 0;
1408
1427
  const onLoad = () => {
1409
1428
  loads += 1;
@@ -1695,9 +1714,20 @@ function nappletNamespacePrelude(domains, normalizeIntentUri2) {
1695
1714
  const target = window;
1696
1715
  const allowed = new Set(domains);
1697
1716
  const requestTimeoutMs = 3e4;
1698
- for (const rtcCtor of ["RTCPeerConnection", "webkitRTCPeerConnection"]) {
1717
+ const rtcGlobals = /* @__PURE__ */ new Set([
1718
+ "webkitRTCPeerConnection",
1719
+ "mozRTCPeerConnection",
1720
+ "webkitRTCSessionDescription",
1721
+ "mozRTCSessionDescription",
1722
+ "webkitRTCIceCandidate",
1723
+ "mozRTCIceCandidate"
1724
+ ]);
1725
+ for (const key of Object.getOwnPropertyNames(target)) {
1726
+ if (key.startsWith("RTC")) rtcGlobals.add(key);
1727
+ }
1728
+ for (const rtcName of rtcGlobals) {
1699
1729
  try {
1700
- Object.defineProperty(target, rtcCtor, {
1730
+ Object.defineProperty(target, rtcName, {
1701
1731
  value: void 0,
1702
1732
  writable: false,
1703
1733
  configurable: false,
@@ -3494,6 +3524,7 @@ function createNotifyProxy(deps) {
3494
3524
  }
3495
3525
  export {
3496
3526
  ALL_CAPABILITIES2 as ALL_CAPABILITIES,
3527
+ NAPPLET_FRAME_POLICY,
3497
3528
  NAPPLET_HELLO,
3498
3529
  TOPICS,
3499
3530
  adaptHooks,
@@ -3511,6 +3542,7 @@ export {
3511
3542
  denyNappletControllerAccess,
3512
3543
  formatDenialReason,
3513
3544
  getShellDomainState,
3545
+ hasNappletFramePolicy,
3514
3546
  injectNappletCsp,
3515
3547
  injectNappletNamespacePrelude,
3516
3548
  manifestCache,
@@ -3519,6 +3551,7 @@ export {
3519
3551
  originRegistry,
3520
3552
  prepareNappletSrcdoc,
3521
3553
  renderNappletCspMeta,
3554
+ renderNappletFramePolicyMeta,
3522
3555
  renderNappletNamespacePrelude,
3523
3556
  resolveShellEnvironment,
3524
3557
  sessionRegistry