@kehto/paja 0.16.2 → 0.16.4

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
 
@@ -324,8 +325,8 @@ without prefetching bytes. Resolution tries request and
324
325
  event-local server hints first, then lazily queries hinted authors' and the
325
326
  event publisher's newest BUD-03 kind `10063` lists through the verified
326
327
  NIP-65-aware OUTBOX router, then queries the active shell user's BUD-03 list
327
- through that same router, then uses host-configured runtime-pointer and
328
- 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
329
330
  lookup works independently of upload mode; an upload runtime may reuse the same
330
331
  servers when present. ROM-specific event and publisher locations retain
331
332
  priority over the user/runtime fallbacks. The combined list is capped at eight
@@ -333,8 +334,14 @@ candidates. The only accepted identifier is `blossom:sha256:<hex>`;
333
334
  Paja refuses redirects, verifies the requested SHA-256, and permits plain-HTTP
334
335
  transport only for configured loopback development defaults. Browser-only Paja
335
336
  cannot pin DNS results, so production runtimes must add the draft's DNS-time
336
- private-address checks. This behavior targets draft
337
- [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
338
345
  [`napplet/web#206@19e0029b`](https://github.com/napplet/web/pull/206) released as
339
346
  `@napplet/core`/`@napplet/nap` 0.32.0, `@napplet/shim` 0.30.0, and
340
347
  `@napplet/sdk` 0.28.0. Publisher discovery follows
@@ -702,7 +702,7 @@ function createNapEnforceGate(config) {
702
702
  function formatDenialReason(capability) {
703
703
  return `denied: ${capability}`;
704
704
  }
705
- function createSessionRegistry(notifier) {
705
+ function createSessionRegistry(notifier, onRegister) {
706
706
  const byWindowId2 = /* @__PURE__ */ new Map();
707
707
  const byPubkey2 = /* @__PURE__ */ new Map();
708
708
  const byWindowIdEntry = /* @__PURE__ */ new Map();
@@ -712,6 +712,7 @@ function createSessionRegistry(notifier) {
712
712
  byWindowId2.set(windowId, entry.pubkey);
713
713
  byPubkey2.set(entry.pubkey, entry);
714
714
  byWindowIdEntry.set(windowId, entry);
715
+ onRegister?.(windowId);
715
716
  },
716
717
  unregister(windowId) {
717
718
  const pubkey2 = byWindowId2.get(windowId);
@@ -931,6 +932,12 @@ function createFirewallState(persistence) {
931
932
  counters = result2.newState;
932
933
  return result2;
933
934
  },
935
+ resetInitBudget(initKey) {
936
+ if (!Object.hasOwn(counters.bursts, initKey)) return;
937
+ const bursts = { ...counters.bursts };
938
+ delete bursts[initKey];
939
+ counters = { ...counters, bursts };
940
+ },
934
941
  setPolicy(napplet, policy) {
935
942
  config = setPolicy(config, napplet, policy);
936
943
  },
@@ -2416,8 +2423,8 @@ function buildObservation(envelope, windowId, senderCap, sessionRegistry2, getFo
2416
2423
  size,
2417
2424
  initElapsedMs,
2418
2425
  // `windowId` comes from the host's source-bound session lookup, never the
2419
- // napplet message envelope. It scopes startup burst accounting to this
2420
- // registered realm while rate limits remain dTag-wide.
2426
+ // napplet message envelope. Trusted register() calls retire this key's
2427
+ // previous startup budget; ordinary messages cannot reset it.
2421
2428
  initKey: windowId,
2422
2429
  focused: focus.focused,
2423
2430
  msSinceFocusGain: focus.msSinceFocusGain,
@@ -2664,7 +2671,10 @@ function createRuntime(hooks) {
2664
2671
  const subscriptions = /* @__PURE__ */ new Map();
2665
2672
  const serviceRegistry = { ...hooks.services };
2666
2673
  const registeredServices = createRegisteredServices(serviceRegistry);
2667
- const sessionRegistry2 = createSessionRegistry(hooks.onPendingUpdate);
2674
+ const firewallState = createFirewallState(hooks.firewallPersistence);
2675
+ const sessionRegistry2 = createSessionRegistry(hooks.onPendingUpdate, (windowId) => {
2676
+ firewallState.resetInitBudget(windowId);
2677
+ });
2668
2678
  let incRuntime = null;
2669
2679
  const aclState = createAclState(hooks.aclPersistence, "permissive", (mutation) => {
2670
2680
  if (mutation.type === "revoke" && mutation.capability !== "relay:read") return;
@@ -2674,7 +2684,6 @@ function createRuntime(hooks) {
2674
2684
  }
2675
2685
  }
2676
2686
  });
2677
- const firewallState = createFirewallState(hooks.firewallPersistence);
2678
2687
  const manifestCache2 = createManifestCache(hooks.manifestPersistence);
2679
2688
  const replayDetector = createReplayDetector(
2680
2689
  hooks.getConfigOverrides ? () => hooks.getConfigOverrides().replayWindowSeconds : void 0
@@ -4033,16 +4042,29 @@ function nappletNamespacePrelude(domains) {
4033
4042
  function request(message, resultType, map, options = {}) {
4034
4043
  const requestId = message.id ?? id();
4035
4044
  const outbound = { ...message, id: requestId };
4045
+ const timeoutMs = options.timeoutMs === void 0 ? requestTimeoutMs : options.timeoutMs;
4046
+ if (options.signal?.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
4036
4047
  return new Promise((resolve, reject2) => {
4037
- const off = listen((event) => {
4048
+ let timer;
4049
+ let off;
4050
+ const cleanup = () => {
4051
+ off?.();
4052
+ if (timer !== void 0) clearTimeout(timer);
4053
+ options.signal?.removeEventListener("abort", abort);
4054
+ };
4055
+ const abort = () => {
4056
+ cleanup();
4057
+ if (options.cancelOnAbort) post({ type: "resource.cancel", id: requestId });
4058
+ reject2(new DOMException("Aborted", "AbortError"));
4059
+ };
4060
+ off = listen((event) => {
4038
4061
  if (!isParentMessage(event)) return;
4039
4062
  const incoming = event.data;
4040
4063
  if (typeof incoming !== "object" || incoming === null) return;
4041
4064
  const msg = incoming;
4042
4065
  if (msg.type !== resultType && msg.type !== `${message.type}.error`) return;
4043
4066
  if (msg.id !== requestId) return;
4044
- off();
4045
- clearTimeout(timer);
4067
+ cleanup();
4046
4068
  const error = typeof msg.error === "string" ? msg.error : "";
4047
4069
  if (error && options.rejectOnError !== false) {
4048
4070
  reject2(new Error(error));
@@ -4054,10 +4076,13 @@ function nappletNamespacePrelude(domains) {
4054
4076
  reject2(err2);
4055
4077
  }
4056
4078
  });
4057
- const timer = setTimeout(() => {
4058
- off();
4059
- reject2(new Error(`${message.type} timed out`));
4060
- }, requestTimeoutMs);
4079
+ if (timeoutMs !== null) {
4080
+ timer = setTimeout(() => {
4081
+ cleanup();
4082
+ reject2(new Error(`${message.type} timed out`));
4083
+ }, timeoutMs);
4084
+ }
4085
+ options.signal?.addEventListener("abort", abort, { once: true });
4061
4086
  post(outbound);
4062
4087
  });
4063
4088
  }
@@ -4806,17 +4831,25 @@ function nappletNamespacePrelude(domains) {
4806
4831
  type: "resource.bytes",
4807
4832
  url,
4808
4833
  ...Array.isArray(options?.servers) ? { servers: Array.from(options.servers) } : {}
4809
- }, "resource.bytes.result", (msg) => msg.blob);
4834
+ }, "resource.bytes.result", (msg) => msg.blob, {
4835
+ timeoutMs: null,
4836
+ signal: options?.signal,
4837
+ cancelOnAbort: true
4838
+ });
4810
4839
  return {
4811
4840
  info: () => request({ type: "resource.info" }, "resource.info.result", (msg) => msg.info),
4812
4841
  bytes,
4813
- bytesMany: (requests) => request({
4842
+ bytesMany: (requests, options) => request({
4814
4843
  type: "resource.bytesMany",
4815
4844
  requests: Array.from(requests || [], (entry) => ({
4816
4845
  url: entry.url,
4817
4846
  ...Array.isArray(entry.servers) ? { servers: Array.from(entry.servers) } : {}
4818
4847
  }))
4819
- }, "resource.bytesMany.result", (msg) => Array.isArray(msg.items) ? msg.items : []),
4848
+ }, "resource.bytesMany.result", (msg) => Array.isArray(msg.items) ? msg.items : [], {
4849
+ timeoutMs: null,
4850
+ signal: options?.signal,
4851
+ cancelOnAbort: true
4852
+ }),
4820
4853
  bytesAsObjectURL(url) {
4821
4854
  let objectUrl = "";
4822
4855
  const handle = {
@@ -9572,6 +9605,9 @@ function isValidFilterArray2(value) {
9572
9605
  function isPlainObject2(value) {
9573
9606
  return typeof value === "object" && value !== null && !Array.isArray(value);
9574
9607
  }
9608
+ function actionRegistryKey(windowId, actionId) {
9609
+ return JSON.stringify([windowId, actionId]);
9610
+ }
9575
9611
  var MODIFIER_ALIASES = {
9576
9612
  ctrl: "ctrl",
9577
9613
  control: "ctrl",
@@ -9640,7 +9676,7 @@ function bindingsForWindow(windowId, registry2, windowIndex) {
9640
9676
  if (!actionIds) return [];
9641
9677
  const bindings = [];
9642
9678
  for (const actionId of actionIds) {
9643
- const entry = registry2.get(actionId);
9679
+ const entry = registry2.get(actionRegistryKey(windowId, actionId));
9644
9680
  if (!entry || entry.windowId !== windowId) continue;
9645
9681
  bindings.push({ actionId, key: entry.chordString });
9646
9682
  }
@@ -9653,10 +9689,9 @@ function pushBindings(windowId, registry2, windowIndex, send) {
9653
9689
  };
9654
9690
  send(payload);
9655
9691
  }
9656
- function removeActionFromWindowIndex(actionId, windowIndex) {
9657
- for (const [wid, set] of windowIndex.entries()) {
9658
- if (set.delete(actionId) && set.size === 0) windowIndex.delete(wid);
9659
- }
9692
+ function removeActionFromWindowIndex(windowId, actionId, windowIndex) {
9693
+ const actions = windowIndex.get(windowId);
9694
+ if (actions?.delete(actionId) && actions.size === 0) windowIndex.delete(windowId);
9660
9695
  }
9661
9696
  var KEYS_SERVICE_VERSION = "1.3.0";
9662
9697
  function rememberActionForWindow(windowIndex, windowId, actionId) {
@@ -9664,7 +9699,7 @@ function rememberActionForWindow(windowIndex, windowId, actionId) {
9664
9699
  windowIndex.get(windowId).add(actionId);
9665
9700
  }
9666
9701
  function bindActionEntry(registry2, windowIndex, actionId, windowId, chord, chordString) {
9667
- registry2.set(actionId, { chord, chordString, windowId });
9702
+ registry2.set(actionRegistryKey(windowId, actionId), { actionId, chord, chordString, windowId });
9668
9703
  rememberActionForWindow(windowIndex, windowId, actionId);
9669
9704
  }
9670
9705
  function bindActionAndMarkChanged(registry2, windowIndex, changedWindowIds, actionId, windowId, chord, chordString) {
@@ -9737,6 +9772,7 @@ function createKeysService(options = {}) {
9737
9772
  const bridgeSendHandles = /* @__PURE__ */ new Map();
9738
9773
  const unsubscribeHandles = /* @__PURE__ */ new Map();
9739
9774
  const handleBridgeRegisterAction = (windowId, m, send) => {
9775
+ const registryKey = actionRegistryKey(windowId, m.action.id);
9740
9776
  bridgeSendHandles.set(windowId, send);
9741
9777
  let binding;
9742
9778
  const changedWindowIds = /* @__PURE__ */ new Set();
@@ -9744,8 +9780,8 @@ function createKeysService(options = {}) {
9744
9780
  try {
9745
9781
  const chord = parseChord(m.action.defaultKey);
9746
9782
  const normalizedChord = formatChord(chord);
9747
- const existingEntry = bridgeActionRegistry.get(m.action.id);
9748
- const existing = unsubscribeHandles.get(m.action.id);
9783
+ const existingEntry = bridgeActionRegistry.get(registryKey);
9784
+ const existing = unsubscribeHandles.get(registryKey);
9749
9785
  let nextUnsubscribe;
9750
9786
  if (!isUnavailableBinding(chord)) {
9751
9787
  nextUnsubscribe = bridge.subscribe(normalizedChord, (ev) => {
@@ -9771,13 +9807,13 @@ function createKeysService(options = {}) {
9771
9807
  existing();
9772
9808
  } catch {
9773
9809
  }
9774
- unsubscribeHandles.delete(m.action.id);
9810
+ unsubscribeHandles.delete(registryKey);
9775
9811
  }
9776
9812
  if (existingEntry) changedWindowIds.add(existingEntry.windowId);
9777
- removeActionFromWindowIndex(m.action.id, bridgeWindowActions);
9778
- bridgeActionRegistry.delete(m.action.id);
9813
+ removeActionFromWindowIndex(windowId, m.action.id, bridgeWindowActions);
9814
+ bridgeActionRegistry.delete(registryKey);
9779
9815
  if (nextUnsubscribe) {
9780
- unsubscribeHandles.set(m.action.id, nextUnsubscribe);
9816
+ unsubscribeHandles.set(registryKey, nextUnsubscribe);
9781
9817
  binding = bindActionAndMarkChanged(
9782
9818
  bridgeActionRegistry,
9783
9819
  bridgeWindowActions,
@@ -9807,18 +9843,19 @@ function createKeysService(options = {}) {
9807
9843
  send
9808
9844
  );
9809
9845
  };
9810
- const handleBridgeUnregisterAction = (_windowId, m, send) => {
9846
+ const handleBridgeUnregisterAction = (windowId, m, send) => {
9811
9847
  if (!m.actionId) return;
9812
- const unsubscribe = unsubscribeHandles.get(m.actionId);
9813
- const entry = bridgeActionRegistry.get(m.actionId);
9848
+ const registryKey = actionRegistryKey(windowId, m.actionId);
9849
+ const unsubscribe = unsubscribeHandles.get(registryKey);
9850
+ const entry = bridgeActionRegistry.get(registryKey);
9814
9851
  if (!unsubscribe) return;
9815
9852
  try {
9816
9853
  unsubscribe();
9817
9854
  } catch {
9818
9855
  }
9819
- unsubscribeHandles.delete(m.actionId);
9820
- bridgeActionRegistry.delete(m.actionId);
9821
- removeActionFromWindowIndex(m.actionId, bridgeWindowActions);
9856
+ unsubscribeHandles.delete(registryKey);
9857
+ bridgeActionRegistry.delete(registryKey);
9858
+ removeActionFromWindowIndex(windowId, m.actionId, bridgeWindowActions);
9822
9859
  if (entry) {
9823
9860
  const ownerSend = bridgeSendHandles.get(entry.windowId) ?? send;
9824
9861
  pushBindings(entry.windowId, bridgeActionRegistry, bridgeWindowActions, ownerSend);
@@ -9838,15 +9875,16 @@ function createKeysService(options = {}) {
9838
9875
  const actions = bridgeWindowActions.get(windowId);
9839
9876
  if (!actions) return;
9840
9877
  for (const actionId of actions) {
9841
- const unsubscribe = unsubscribeHandles.get(actionId);
9878
+ const registryKey = actionRegistryKey(windowId, actionId);
9879
+ const unsubscribe = unsubscribeHandles.get(registryKey);
9842
9880
  if (unsubscribe) {
9843
9881
  try {
9844
9882
  unsubscribe();
9845
9883
  } catch {
9846
9884
  }
9847
9885
  }
9848
- unsubscribeHandles.delete(actionId);
9849
- bridgeActionRegistry.delete(actionId);
9886
+ unsubscribeHandles.delete(registryKey);
9887
+ bridgeActionRegistry.delete(registryKey);
9850
9888
  }
9851
9889
  bridgeWindowActions.delete(windowId);
9852
9890
  bridgeSendHandles.delete(windowId);
@@ -9899,13 +9937,13 @@ function createKeysService(options = {}) {
9899
9937
  });
9900
9938
  }
9901
9939
  if (isReserved) return;
9902
- for (const [actionId, entry] of actionRegistry.entries()) {
9940
+ for (const entry of actionRegistry.values()) {
9903
9941
  if (chordMatches(entry.chord, ev)) {
9904
9942
  const send = sendHandles.get(entry.windowId);
9905
9943
  if (send) {
9906
9944
  const payload = {
9907
9945
  type: "keys.action",
9908
- actionId
9946
+ actionId: entry.actionId
9909
9947
  };
9910
9948
  send(payload);
9911
9949
  }
@@ -9914,6 +9952,7 @@ function createKeysService(options = {}) {
9914
9952
  };
9915
9953
  target.addEventListener("keydown", listener);
9916
9954
  const handleDocumentRegisterAction = (windowId, m, send) => {
9955
+ const registryKey = actionRegistryKey(windowId, m.action.id);
9917
9956
  sendHandles.set(windowId, send);
9918
9957
  let binding;
9919
9958
  const changedWindowIds = /* @__PURE__ */ new Set();
@@ -9921,11 +9960,11 @@ function createKeysService(options = {}) {
9921
9960
  try {
9922
9961
  const chord = parseChord(m.action.defaultKey);
9923
9962
  const normalizedChord = formatChord(chord);
9924
- const existing = actionRegistry.get(m.action.id);
9963
+ const existing = actionRegistry.get(registryKey);
9925
9964
  if (existing) {
9926
9965
  changedWindowIds.add(existing.windowId);
9927
- removeActionFromWindowIndex(m.action.id, windowActions);
9928
- actionRegistry.delete(m.action.id);
9966
+ removeActionFromWindowIndex(windowId, m.action.id, windowActions);
9967
+ actionRegistry.delete(registryKey);
9929
9968
  }
9930
9969
  if (!isUnavailableBinding(chord)) {
9931
9970
  binding = bindActionAndMarkChanged(actionRegistry, windowActions, changedWindowIds, m.action.id, windowId, chord, normalizedChord);
@@ -9943,10 +9982,12 @@ function createKeysService(options = {}) {
9943
9982
  sendRegisterActionResult2(send, m, binding);
9944
9983
  pushChangedBindings(changedWindowIds, actionRegistry, windowActions, sendHandles, send);
9945
9984
  };
9946
- const handleDocumentUnregisterAction = (_windowId, m, send) => {
9947
- if (!m.actionId || !actionRegistry.has(m.actionId)) return;
9948
- const entry = actionRegistry.get(m.actionId);
9949
- actionRegistry.delete(m.actionId);
9985
+ const handleDocumentUnregisterAction = (windowId, m, send) => {
9986
+ if (!m.actionId) return;
9987
+ const registryKey = actionRegistryKey(windowId, m.actionId);
9988
+ const entry = actionRegistry.get(registryKey);
9989
+ if (!entry) return;
9990
+ actionRegistry.delete(registryKey);
9950
9991
  const set = windowActions.get(entry.windowId);
9951
9992
  if (set) {
9952
9993
  set.delete(m.actionId);
@@ -9966,7 +10007,7 @@ function createKeysService(options = {}) {
9966
10007
  onWindowDestroyed(windowId) {
9967
10008
  const actions = windowActions.get(windowId);
9968
10009
  if (actions) {
9969
- for (const actionId of actions) actionRegistry.delete(actionId);
10010
+ for (const actionId of actions) actionRegistry.delete(actionRegistryKey(windowId, actionId));
9970
10011
  windowActions.delete(windowId);
9971
10012
  }
9972
10013
  sendHandles.delete(windowId);
@@ -17220,6 +17261,7 @@ function createPajaBlossomEventResolver(options) {
17220
17261
  const now2 = options.now ?? Date.now;
17221
17262
  const ttl = Math.max(0, options.serverListTtlMs ?? DEFAULT_SERVER_LIST_TTL_MS);
17222
17263
  const windows = /* @__PURE__ */ new Map();
17264
+ const windowServers = /* @__PURE__ */ new Map();
17223
17265
  const serverLists = /* @__PURE__ */ new Map();
17224
17266
  const pendingServerLists = /* @__PURE__ */ new Map();
17225
17267
  function observe(windowId, results) {
@@ -17297,9 +17339,20 @@ function createPajaBlossomEventResolver(options) {
17297
17339
  );
17298
17340
  for (const list of lists) appendUnique(servers, list);
17299
17341
  }
17342
+ if (windowId) appendUnique(servers, windowServers.get(windowId) ?? []);
17300
17343
  appendUnique(servers, options.getConfiguredServers());
17301
17344
  return servers.slice(0, PAJA_RESOURCE_MAX_SERVERS);
17302
17345
  }
17346
+ function setWindowServers(windowId, servers) {
17347
+ if (!windowId) return;
17348
+ const normalized = [];
17349
+ for (const value of servers) {
17350
+ const server = normalizePublicBlossomServer(value);
17351
+ if (server) appendUnique(normalized, [server]);
17352
+ }
17353
+ if (normalized.length > 0) windowServers.set(windowId, normalized);
17354
+ else windowServers.delete(windowId);
17355
+ }
17303
17356
  async function discoverServerList(pubkey2) {
17304
17357
  const cached = serverLists.get(pubkey2);
17305
17358
  if (cached && cached.expiresAt > now2()) return [...cached.servers];
@@ -17344,7 +17397,11 @@ function createPajaBlossomEventResolver(options) {
17344
17397
  observe,
17345
17398
  decorate,
17346
17399
  getServers,
17347
- clearWindow: (windowId) => windows.delete(windowId)
17400
+ setWindowServers,
17401
+ clearWindow(windowId) {
17402
+ windows.delete(windowId);
17403
+ windowServers.delete(windowId);
17404
+ }
17348
17405
  };
17349
17406
  }
17350
17407
  function extractEventResourceLocations(event) {
@@ -20240,7 +20297,11 @@ function createDevServices(backend, getSimulation, relayConfig, onThemeService,
20240
20297
  return wasWebrtcEnabled !== webrtcEnabled || dmChanged;
20241
20298
  }
20242
20299
  refreshAvailability();
20243
- return { services, refreshAvailability };
20300
+ return {
20301
+ services,
20302
+ refreshAvailability,
20303
+ setWindowBlossomServers: blossomEventResolver.setWindowServers
20304
+ };
20244
20305
  }
20245
20306
  function createPajaAdapter(config, getSimulation, onThemeService, onThemeBroadcast, confirmRequest, signerProvider, getIdentity, onEnvironmentChanged, intentHost, userActivation, notifyOptions, configOptions) {
20246
20307
  const resolveIdentity = (windowId) => getIdentity?.(windowId) ?? {
@@ -20308,6 +20369,7 @@ function createPajaAdapter(config, getSimulation, onThemeService, onThemeBroadca
20308
20369
  });
20309
20370
  return {
20310
20371
  ready,
20372
+ setWindowBlossomServers: serviceBundle.setWindowBlossomServers,
20311
20373
  relayPool: createPajaRelayHooks(relayBackend, getSimulation, relayConfig),
20312
20374
  relayConfig,
20313
20375
  windowManager: { createWindow: () => null },
@@ -20400,7 +20462,7 @@ async function resolvePajaPointer(value, options = {}) {
20400
20462
  const ownsPool = options.pool === void 0;
20401
20463
  try {
20402
20464
  const event = await resolvePointerEvent(pointer, pool, relays, normalizeMaxWaitMs(options.maxWaitMs));
20403
- const blossomServers = [...options.blossomServers ?? []];
20465
+ const blossomServers = resolvePointerBlossomServers(event, options.blossomServers ?? []);
20404
20466
  const resolved = await resolveNapplet({
20405
20467
  event,
20406
20468
  cache: await openNappletArtifactCache({ requireStorageEstimate: false }),
@@ -20427,6 +20489,20 @@ async function resolvePajaPointer(value, options = {}) {
20427
20489
  }
20428
20490
  }
20429
20491
  }
20492
+ function resolvePointerBlossomServers(event, configured) {
20493
+ const servers = [];
20494
+ for (const value of event.tags.flatMap(
20495
+ (tag) => tag[0] === "server" && tag[1] ? [tag[1]] : []
20496
+ )) {
20497
+ const normalized = normalizePublicBlossomServer(value);
20498
+ if (normalized && !servers.includes(normalized)) servers.push(normalized);
20499
+ }
20500
+ for (const value of configured) {
20501
+ const server = normalizePublicBlossomServer(value) ?? value;
20502
+ if (!servers.includes(server)) servers.push(server);
20503
+ }
20504
+ return servers;
20505
+ }
20430
20506
  function injectPajaRuntimeCsp(html, origins) {
20431
20507
  const grantedOrigins = [...new Set(origins)].sort();
20432
20508
  const connectSrc = grantedOrigins.length > 0 ? `connect-src ${grantedOrigins.join(" ")}` : "connect-src 'none'";
@@ -21852,6 +21928,9 @@ function resolvedTargetKey(target) {
21852
21928
  function runtimeTabGenerationId(tab) {
21853
21929
  return `${tab.id}:${tab.generation}`;
21854
21930
  }
21931
+ function bindRuntimeTabBlossomServers(adapter, windowId, resolvedTarget) {
21932
+ adapter.setWindowBlossomServers(windowId, resolvedTarget.blossomServers);
21933
+ }
21855
21934
  function getActiveTab(state) {
21856
21935
  return state.tabs.find((tab) => tab.id === state.activeTabId) ?? null;
21857
21936
  }
@@ -22129,23 +22208,25 @@ function startRuntimeTabNavigation(tab, state, context) {
22129
22208
  tab.status = "booting";
22130
22209
  context.setStatus(state, "booting");
22131
22210
  renderRuntimeTabs(state);
22211
+ const windowId = runtimeTabWindowId(context.config, tab);
22212
+ bindRuntimeTabBlossomServers(context.adapter, windowId, tab.resolvedTarget);
22132
22213
  void context.navigateFrame(
22133
22214
  tab.frame,
22134
22215
  context.config,
22135
22216
  generation,
22136
22217
  context.adapter,
22137
22218
  tab.resolvedTarget,
22138
- runtimeTabWindowId(context.config, tab),
22219
+ windowId,
22139
22220
  () => tab.generation === generation,
22140
- (windowId) => {
22221
+ (windowId2) => {
22141
22222
  if (tab.generation !== generation) return;
22142
- tab.windowId = windowId;
22143
- if (state.activeTabId === tab.id) context.runtime.currentWindowId = windowId;
22223
+ tab.windowId = windowId2;
22224
+ if (state.activeTabId === tab.id) context.runtime.currentWindowId = windowId2;
22144
22225
  }
22145
- ).then((windowId) => {
22226
+ ).then((windowId2) => {
22146
22227
  if (tab.generation !== generation) return;
22147
- tab.windowId = windowId;
22148
- if (state.activeTabId === tab.id) context.runtime.currentWindowId = windowId;
22228
+ tab.windowId = windowId2;
22229
+ if (state.activeTabId === tab.id) context.runtime.currentWindowId = windowId2;
22149
22230
  }).catch((error) => {
22150
22231
  if (tab.generation !== generation) return;
22151
22232
  tab.frame.removeAttribute("src");