@kehto/paja 0.16.1 → 0.16.3

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
@@ -186,7 +186,8 @@ When `PAJA_UPLOAD_SERVERS` is set, the runtime enables the Blossom upload rail
186
186
  and also makes those servers explicit NAP-RESOURCE fallbacks. It is not needed
187
187
  for read-side discovery: resources learned through OUTBOX use their event hints
188
188
  and hinted-author/publisher server lists first, then the active shell user's
189
- published list.
189
+ published list, the current window's verified pointer-manifest servers, and
190
+ finally configured runtime fallbacks.
190
191
 
191
192
  ## Installed intent handlers and delivery
192
193
 
@@ -308,7 +309,9 @@ Paja's developer-runtime policy accepts arbitrary `http:` and `https:` resource
308
309
  URLs so a normal remote image does not look broken merely because its origin was
309
310
  not pre-granted. Paja resolves those URLs with browser `fetch`, omits credentials
310
311
  and referrer data, caps responses at 10 MiB, and classifies MIME from returned
311
- bytes. Browser network and CORS rules still apply: an unreadable response is the
312
+ bytes. Its byte classifier recognizes checksum-valid Game Boy ROM headers as
313
+ `application/vnd.nintendo.gb-rom`; it never trusts a server-supplied media type.
314
+ Browser network and CORS rules still apply: an unreadable response is the
312
315
  canonical `network-error`, while any CORS-readable response is returned as NAP
313
316
  bytes.
314
317
 
@@ -322,8 +325,8 @@ without prefetching bytes. Resolution tries request and
322
325
  event-local server hints first, then lazily queries hinted authors' and the
323
326
  event publisher's newest BUD-03 kind `10063` lists through the verified
324
327
  NIP-65-aware OUTBOX router, then queries the active shell user's BUD-03 list
325
- through that same router, then uses host-configured runtime-pointer and
326
- upload-runtime fallbacks. The user-list
328
+ through that same router, then uses the current window's verified
329
+ pointer-manifest servers, followed by upload-runtime fallbacks. The user-list
327
330
  lookup works independently of upload mode; an upload runtime may reuse the same
328
331
  servers when present. ROM-specific event and publisher locations retain
329
332
  priority over the user/runtime fallbacks. The combined list is capped at eight
@@ -331,8 +334,14 @@ candidates. The only accepted identifier is `blossom:sha256:<hex>`;
331
334
  Paja refuses redirects, verifies the requested SHA-256, and permits plain-HTTP
332
335
  transport only for configured loopback development defaults. Browser-only Paja
333
336
  cannot pin DNS results, so production runtimes must add the draft's DNS-time
334
- private-address checks. This behavior targets draft
335
- [NAP-RESOURCE at `9511232`](https://github.com/napplet/naps/blob/9511232f69313aa7953d110e35d32cc28d506f66/naps/NAP-RESOURCE.md), with the merged package implementation
337
+ private-address checks. The current
338
+ [NAP-RESOURCE draft at `fa6bcc6`](https://github.com/napplet/naps/blob/fa6bcc6935aa19e7b70ab2a2c721dafca77c78e1/naps/NAP-RESOURCE.md)
339
+ assigns fetching and policy to the runtime and defines no wire-level Blossom
340
+ server-hint field. Retaining verified manifest servers per window is therefore
341
+ Paja host policy, informed by the current
342
+ [NIP-5D draft at `24711d9`](https://github.com/nostr-protocol/nips/blob/24711d9c47bbdd07908bf1d52bf677d9cbc530f0/5D.md),
343
+ which defines manifest `server` tags. The existing request-server compatibility
344
+ path comes from the earlier NAP-RESOURCE draft at `9511232`, with the merged package implementation
336
345
  [`napplet/web#206@19e0029b`](https://github.com/napplet/web/pull/206) released as
337
346
  `@napplet/core`/`@napplet/nap` 0.32.0, `@napplet/shim` 0.30.0, and
338
347
  `@napplet/sdk` 0.28.0. Publisher discovery follows
@@ -16869,6 +16869,56 @@ var PAJA_RESOURCE_MAX_URLS = 100;
16869
16869
  var PAJA_RESOURCE_MAX_SERVERS = 8;
16870
16870
  var UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
16871
16871
  var BLOSSOM_RESOURCE_PATTERN = /^blossom:sha256:([0-9a-f]{64})$/i;
16872
+ var GAME_BOY_NINTENDO_LOGO = [
16873
+ 206,
16874
+ 237,
16875
+ 102,
16876
+ 102,
16877
+ 204,
16878
+ 13,
16879
+ 0,
16880
+ 11,
16881
+ 3,
16882
+ 115,
16883
+ 0,
16884
+ 131,
16885
+ 0,
16886
+ 12,
16887
+ 0,
16888
+ 13,
16889
+ 0,
16890
+ 8,
16891
+ 17,
16892
+ 31,
16893
+ 136,
16894
+ 137,
16895
+ 0,
16896
+ 14,
16897
+ 220,
16898
+ 204,
16899
+ 110,
16900
+ 230,
16901
+ 221,
16902
+ 221,
16903
+ 217,
16904
+ 153,
16905
+ 187,
16906
+ 187,
16907
+ 103,
16908
+ 99,
16909
+ 110,
16910
+ 14,
16911
+ 236,
16912
+ 204,
16913
+ 221,
16914
+ 220,
16915
+ 153,
16916
+ 159,
16917
+ 187,
16918
+ 185,
16919
+ 51,
16920
+ 62
16921
+ ];
16872
16922
  function createPajaResourceFetch(options = {}) {
16873
16923
  return async (value, init) => {
16874
16924
  if (init.signal.aborted) throw new DOMException("Resource request cancelled", "AbortError");
@@ -17127,8 +17177,17 @@ function sniffSafeResourceMime(bytes) {
17127
17177
  if (ascii(bytes, 4, 8) === "ftyp") return "video/mp4";
17128
17178
  if (ascii(bytes, 0, 4) === "wOFF") return "font/woff";
17129
17179
  if (ascii(bytes, 0, 4) === "wOF2") return "font/woff2";
17180
+ if (isGameBoyRom(bytes)) return "application/vnd.nintendo.gb-rom";
17130
17181
  return sniffSafeTextMime(bytes);
17131
17182
  }
17183
+ function isGameBoyRom(bytes) {
17184
+ if (bytes.byteLength < 336 || !startsWith(bytes, GAME_BOY_NINTENDO_LOGO, 260)) return false;
17185
+ let checksum = 0;
17186
+ for (let index = 308; index <= 332; index += 1) {
17187
+ checksum = checksum - (bytes[index] ?? 0) - 1 & 255;
17188
+ }
17189
+ return checksum === bytes[333];
17190
+ }
17132
17191
  function sniffSafeTextMime(bytes) {
17133
17192
  let text;
17134
17193
  try {
@@ -17161,6 +17220,7 @@ function createPajaBlossomEventResolver(options) {
17161
17220
  const now2 = options.now ?? Date.now;
17162
17221
  const ttl = Math.max(0, options.serverListTtlMs ?? DEFAULT_SERVER_LIST_TTL_MS);
17163
17222
  const windows = /* @__PURE__ */ new Map();
17223
+ const windowServers = /* @__PURE__ */ new Map();
17164
17224
  const serverLists = /* @__PURE__ */ new Map();
17165
17225
  const pendingServerLists = /* @__PURE__ */ new Map();
17166
17226
  function observe(windowId, results) {
@@ -17238,9 +17298,20 @@ function createPajaBlossomEventResolver(options) {
17238
17298
  );
17239
17299
  for (const list of lists) appendUnique(servers, list);
17240
17300
  }
17301
+ if (windowId) appendUnique(servers, windowServers.get(windowId) ?? []);
17241
17302
  appendUnique(servers, options.getConfiguredServers());
17242
17303
  return servers.slice(0, PAJA_RESOURCE_MAX_SERVERS);
17243
17304
  }
17305
+ function setWindowServers(windowId, servers) {
17306
+ if (!windowId) return;
17307
+ const normalized = [];
17308
+ for (const value of servers) {
17309
+ const server = normalizePublicBlossomServer(value);
17310
+ if (server) appendUnique(normalized, [server]);
17311
+ }
17312
+ if (normalized.length > 0) windowServers.set(windowId, normalized);
17313
+ else windowServers.delete(windowId);
17314
+ }
17244
17315
  async function discoverServerList(pubkey2) {
17245
17316
  const cached = serverLists.get(pubkey2);
17246
17317
  if (cached && cached.expiresAt > now2()) return [...cached.servers];
@@ -17285,7 +17356,11 @@ function createPajaBlossomEventResolver(options) {
17285
17356
  observe,
17286
17357
  decorate,
17287
17358
  getServers,
17288
- clearWindow: (windowId) => windows.delete(windowId)
17359
+ setWindowServers,
17360
+ clearWindow(windowId) {
17361
+ windows.delete(windowId);
17362
+ windowServers.delete(windowId);
17363
+ }
17289
17364
  };
17290
17365
  }
17291
17366
  function extractEventResourceLocations(event) {
@@ -20181,7 +20256,11 @@ function createDevServices(backend, getSimulation, relayConfig, onThemeService,
20181
20256
  return wasWebrtcEnabled !== webrtcEnabled || dmChanged;
20182
20257
  }
20183
20258
  refreshAvailability();
20184
- return { services, refreshAvailability };
20259
+ return {
20260
+ services,
20261
+ refreshAvailability,
20262
+ setWindowBlossomServers: blossomEventResolver.setWindowServers
20263
+ };
20185
20264
  }
20186
20265
  function createPajaAdapter(config, getSimulation, onThemeService, onThemeBroadcast, confirmRequest, signerProvider, getIdentity, onEnvironmentChanged, intentHost, userActivation, notifyOptions, configOptions) {
20187
20266
  const resolveIdentity = (windowId) => getIdentity?.(windowId) ?? {
@@ -20249,6 +20328,7 @@ function createPajaAdapter(config, getSimulation, onThemeService, onThemeBroadca
20249
20328
  });
20250
20329
  return {
20251
20330
  ready,
20331
+ setWindowBlossomServers: serviceBundle.setWindowBlossomServers,
20252
20332
  relayPool: createPajaRelayHooks(relayBackend, getSimulation, relayConfig),
20253
20333
  relayConfig,
20254
20334
  windowManager: { createWindow: () => null },
@@ -20341,7 +20421,7 @@ async function resolvePajaPointer(value, options = {}) {
20341
20421
  const ownsPool = options.pool === void 0;
20342
20422
  try {
20343
20423
  const event = await resolvePointerEvent(pointer, pool, relays, normalizeMaxWaitMs(options.maxWaitMs));
20344
- const blossomServers = [...options.blossomServers ?? []];
20424
+ const blossomServers = resolvePointerBlossomServers(event, options.blossomServers ?? []);
20345
20425
  const resolved = await resolveNapplet({
20346
20426
  event,
20347
20427
  cache: await openNappletArtifactCache({ requireStorageEstimate: false }),
@@ -20368,6 +20448,20 @@ async function resolvePajaPointer(value, options = {}) {
20368
20448
  }
20369
20449
  }
20370
20450
  }
20451
+ function resolvePointerBlossomServers(event, configured) {
20452
+ const servers = [];
20453
+ for (const value of event.tags.flatMap(
20454
+ (tag) => tag[0] === "server" && tag[1] ? [tag[1]] : []
20455
+ )) {
20456
+ const normalized = normalizePublicBlossomServer(value);
20457
+ if (normalized && !servers.includes(normalized)) servers.push(normalized);
20458
+ }
20459
+ for (const value of configured) {
20460
+ const server = normalizePublicBlossomServer(value) ?? value;
20461
+ if (!servers.includes(server)) servers.push(server);
20462
+ }
20463
+ return servers;
20464
+ }
20371
20465
  function injectPajaRuntimeCsp(html, origins) {
20372
20466
  const grantedOrigins = [...new Set(origins)].sort();
20373
20467
  const connectSrc = grantedOrigins.length > 0 ? `connect-src ${grantedOrigins.join(" ")}` : "connect-src 'none'";
@@ -21793,6 +21887,9 @@ function resolvedTargetKey(target) {
21793
21887
  function runtimeTabGenerationId(tab) {
21794
21888
  return `${tab.id}:${tab.generation}`;
21795
21889
  }
21890
+ function bindRuntimeTabBlossomServers(adapter, windowId, resolvedTarget) {
21891
+ adapter.setWindowBlossomServers(windowId, resolvedTarget.blossomServers);
21892
+ }
21796
21893
  function getActiveTab(state) {
21797
21894
  return state.tabs.find((tab) => tab.id === state.activeTabId) ?? null;
21798
21895
  }
@@ -22070,23 +22167,25 @@ function startRuntimeTabNavigation(tab, state, context) {
22070
22167
  tab.status = "booting";
22071
22168
  context.setStatus(state, "booting");
22072
22169
  renderRuntimeTabs(state);
22170
+ const windowId = runtimeTabWindowId(context.config, tab);
22171
+ bindRuntimeTabBlossomServers(context.adapter, windowId, tab.resolvedTarget);
22073
22172
  void context.navigateFrame(
22074
22173
  tab.frame,
22075
22174
  context.config,
22076
22175
  generation,
22077
22176
  context.adapter,
22078
22177
  tab.resolvedTarget,
22079
- runtimeTabWindowId(context.config, tab),
22178
+ windowId,
22080
22179
  () => tab.generation === generation,
22081
- (windowId) => {
22180
+ (windowId2) => {
22082
22181
  if (tab.generation !== generation) return;
22083
- tab.windowId = windowId;
22084
- if (state.activeTabId === tab.id) context.runtime.currentWindowId = windowId;
22182
+ tab.windowId = windowId2;
22183
+ if (state.activeTabId === tab.id) context.runtime.currentWindowId = windowId2;
22085
22184
  }
22086
- ).then((windowId) => {
22185
+ ).then((windowId2) => {
22087
22186
  if (tab.generation !== generation) return;
22088
- tab.windowId = windowId;
22089
- if (state.activeTabId === tab.id) context.runtime.currentWindowId = windowId;
22187
+ tab.windowId = windowId2;
22188
+ if (state.activeTabId === tab.id) context.runtime.currentWindowId = windowId2;
22090
22189
  }).catch((error) => {
22091
22190
  if (tab.generation !== generation) return;
22092
22191
  tab.frame.removeAttribute("src");