@volter/world-runtime 3.0.36 → 3.0.38

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.
@@ -8,7 +8,7 @@ export type WorldBrowser = {
8
8
  export declare const browsersDir: (data: string) => string;
9
9
  export declare function worldBrowser(data: string, name: string): WorldBrowser | null;
10
10
  export declare function worldBrowsers(data: string): WorldBrowser[];
11
- export declare function putWorldBrowser(data: string, name: string, state: BrowserState): WorldBrowser;
11
+ export declare function putWorldBrowser(data: string, name: string, state: unknown): WorldBrowser;
12
12
  export declare function deleteWorldBrowser(data: string, name: string): void;
13
13
  export declare function inheritWorldBrowsers(fromData: string, toData: string): void;
14
14
  /** Derived files under the World state directory, written only by its current branch. */
@@ -26,7 +26,7 @@ export function worldBrowser(data, name) {
26
26
  held.text = text;
27
27
  return held.record;
28
28
  }
29
- validateBrowserState(record.state);
29
+ record.state = validateBrowserState(record.state);
30
30
  const freeze = (value) => {
31
31
  if (!value || typeof value !== 'object')
32
32
  return;
package/dist/src/init.js CHANGED
@@ -1159,15 +1159,15 @@ export async function seed(): Promise<void> {
1159
1159
  if (!door || !token) throw new Error('browser defaults run in a World seed with its browsers door and credential');
1160
1160
  const held = await fetch(\`\${door}/\${encodeURIComponent(call.browser)}\`, { headers: { 'x-volter-token': token } });
1161
1161
  if (!held.ok && held.status !== 404) throw new Error(\`browser \${call.browser}: \${held.status} \${await held.text()}\`);
1162
- snapshots.set(call.browser, held.ok ? await held.json() as Awaited<ReturnType<SeedBrowser['state']>> : { cookies: [], origins: [] });
1162
+ snapshots.set(call.browser, held.ok ? await held.json() as Awaited<ReturnType<SeedBrowser['state']>> : { storageState: { cookies: [], origins: [] }, context: {} });
1163
1163
  }
1164
1164
  browserHelper ??= await import('@volter/world-core/browser').catch(error => {
1165
- throw new Error('Install @volter/world-core 3.0.31 or later in this project to seed browsers.', { cause: error });
1165
+ throw new Error('Install @volter/world-core 3.0.37 or later in this project to seed browsers.', { cause: error });
1166
1166
  });
1167
1167
  const initial = snapshots.get(call.browser)!;
1168
1168
  const hostname = new URL(call.path).hostname;
1169
- if (initial.cookies.some(cookie => browserHelper!.cookieMatchesHost(cookie, hostname))
1170
- || initial.origins.some(origin => new URL(origin.origin).hostname === hostname)) continue;
1169
+ if (initial.storageState.cookies.some(cookie => browserHelper!.cookieMatchesHost(cookie, hostname))
1170
+ || initial.storageState.origins.some(origin => new URL(origin.origin).hostname === hostname)) continue;
1171
1171
  }
1172
1172
  const headers = { ...(call.body !== undefined ? { 'content-type': 'application/json' } : {}), ...(call.headers ?? {}) };
1173
1173
  const form = /^application\\/x-www-form-urlencoded\\b/i.test(new Headers(headers).get('content-type') ?? '');
@@ -32,7 +32,7 @@ import { boardFrameKey, getActiveWorldStore, withHistoryLocks, captureHistory, h
32
32
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
33
33
  import { CALLER_HEADER, callerForHeader, newTraceparent, parseTraceparent, traceparentForDelivery, readTwinRequestJournalFrom, assertSafeChangesetName, blobDigest, destinationOf, getActiveBlobStore, journalTwinRequest, loadCheck, twinRequestJournalFailures, openSealedAt, readTwinRequestJournal, serveHttp, twinRequestCredentials, TWIN_PREFIX_HEADER, TWIN_SITES_HEADER, TWIN_SITE_HEADER, siteUrlOf, ORIGINAL_PATH_HEADER, validateRemoteOrigin } from '@volter/world-core/runtime';
34
34
  import { resolveTwinHost } from '@volter/world-core/host-routing';
35
- import { assertBrowserName, validateBrowserState, cookieMatchesHost } from '@volter/world-core';
35
+ import { assertBrowserName, validateBrowserState, browserAcceptLanguage, cookieMatchesHost } from '@volter/world-core';
36
36
  import { worldBrowser, worldBrowsers, putWorldBrowser, deleteWorldBrowser, materializeBrowserFiles, browserLoad, putBrowserLoad, browserLoadId } from "./browsers.js";
37
37
  import { APP_READ_ENDPOINT_ENV } from "./init.js";
38
38
  import { packFacts, packHostRules } from "./pack-facts.js";
@@ -1129,8 +1129,8 @@ export class WorldDoors {
1129
1129
  return null;
1130
1130
  if (request.method !== 'GET' || !['document', 'iframe'].includes(request.headers.get('sec-fetch-dest') ?? ''))
1131
1131
  return null;
1132
- const cookies = browser.state.cookies.filter(c => cookieMatchesHost(c, site));
1133
- const storage = browser.state.origins.find(o => new URL(o.origin).hostname === site)?.localStorage ?? [];
1132
+ const cookies = browser.state.storageState.cookies.filter(c => cookieMatchesHost(c, site));
1133
+ const storage = browser.state.storageState.origins.find(o => new URL(o.origin).hostname === site)?.localStorage ?? [];
1134
1134
  const headers = new Headers({ 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'referrer-policy': 'no-referrer' });
1135
1135
  const protectedCookie = (key) => /^(?:__Host-)?volter_(?:world|pages|browser)$/.test(key);
1136
1136
  const expired = new Map();
@@ -1240,6 +1240,10 @@ export class WorldDoors {
1240
1240
  headers.set(name, v);
1241
1241
  }
1242
1242
  if (browser) {
1243
+ const profile = worldBrowser(this.up().dirs.data, browser)?.state;
1244
+ const language = profile ? browserAcceptLanguage(profile.context) : undefined;
1245
+ if (language !== undefined)
1246
+ headers.set('accept-language', language);
1243
1247
  const cookies = (request.headers.get('cookie') ?? '').split(/;\s*/).filter(c => !/^(?:__Host-)?volter_(?:world|pages|browser)=/.test(c));
1244
1248
  if (cookies.length)
1245
1249
  headers.set('cookie', cookies.join('; '));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-runtime",
3
- "version": "3.0.36",
3
+ "version": "3.0.38",
4
4
  "description": "World configs for twins: boot named local runtimes, allocate ports, generate world.env/instance.json, and run apps against fake-key twin worlds.",
5
5
  "keywords": [
6
6
  "twin",
@@ -68,14 +68,14 @@
68
68
  "node": ">=22.3"
69
69
  },
70
70
  "peerDependencies": {
71
- "@volter/world-core": "3.0.36",
72
- "@volter/world-console": "3.0.31"
71
+ "@volter/world-core": "3.0.38",
72
+ "@volter/world-console": "3.0.33"
73
73
  },
74
74
  "dependencies": {
75
75
  "@electric-sql/pglite": "0.5.8",
76
76
  "@electric-sql/pglite-pgvector": "0.0.9",
77
77
  "@volter/world-access": "3.0.1",
78
- "@volter/world-core": "3.0.36",
78
+ "@volter/world-core": "3.0.38",
79
79
  "pg-gateway": "0.3.0-beta.4",
80
80
  "smol-toml": "^1.8.0",
81
81
  "yaml": "2.9.0"
package/src/browsers.ts CHANGED
@@ -20,7 +20,7 @@ export function worldBrowser(data: string, name: string): WorldBrowser | null {
20
20
  const record = JSON.parse(text) as WorldBrowser;
21
21
  if (record.name !== name || typeof record.version !== 'string') throw new Error(`invalid browser record: ${name}`);
22
22
  if (held?.version === record.version) { held.text = text; return held.record; }
23
- validateBrowserState(record.state);
23
+ record.state = validateBrowserState(record.state);
24
24
  const freeze = (value: unknown): void => {
25
25
  if (!value || typeof value !== 'object') return;
26
26
  for (const item of Object.values(value)) freeze(item);
@@ -33,7 +33,7 @@ export function worldBrowser(data: string, name: string): WorldBrowser | null {
33
33
  export function worldBrowsers(data: string): WorldBrowser[] {
34
34
  return getActiveWorldStore().list(browsersDir(data)).filter(f => f.endsWith('.json')).sort().map(f => worldBrowser(data, f.slice(0, -5))!);
35
35
  }
36
- export function putWorldBrowser(data: string, name: string, state: BrowserState): WorldBrowser {
36
+ export function putWorldBrowser(data: string, name: string, state: unknown): WorldBrowser {
37
37
  const record = { name, state: validateBrowserState(state), version: randomUUID() };
38
38
  getActiveWorldStore().writeAtomic(path(data, name), `${JSON.stringify(record)}\n`, { secret: true });
39
39
  retireBrowserLoads(data, name, record);
package/src/init.ts CHANGED
@@ -1346,15 +1346,15 @@ export async function seed(): Promise<void> {
1346
1346
  if (!door || !token) throw new Error('browser defaults run in a World seed with its browsers door and credential');
1347
1347
  const held = await fetch(\`\${door}/\${encodeURIComponent(call.browser)}\`, { headers: { 'x-volter-token': token } });
1348
1348
  if (!held.ok && held.status !== 404) throw new Error(\`browser \${call.browser}: \${held.status} \${await held.text()}\`);
1349
- snapshots.set(call.browser, held.ok ? await held.json() as Awaited<ReturnType<SeedBrowser['state']>> : { cookies: [], origins: [] });
1349
+ snapshots.set(call.browser, held.ok ? await held.json() as Awaited<ReturnType<SeedBrowser['state']>> : { storageState: { cookies: [], origins: [] }, context: {} });
1350
1350
  }
1351
1351
  browserHelper ??= await import('@volter/world-core/browser').catch(error => {
1352
- throw new Error('Install @volter/world-core 3.0.31 or later in this project to seed browsers.', { cause: error });
1352
+ throw new Error('Install @volter/world-core 3.0.37 or later in this project to seed browsers.', { cause: error });
1353
1353
  });
1354
1354
  const initial = snapshots.get(call.browser)!;
1355
1355
  const hostname = new URL(call.path).hostname;
1356
- if (initial.cookies.some(cookie => browserHelper!.cookieMatchesHost(cookie, hostname))
1357
- || initial.origins.some(origin => new URL(origin.origin).hostname === hostname)) continue;
1356
+ if (initial.storageState.cookies.some(cookie => browserHelper!.cookieMatchesHost(cookie, hostname))
1357
+ || initial.storageState.origins.some(origin => new URL(origin.origin).hostname === hostname)) continue;
1358
1358
  }
1359
1359
  const headers = { ...(call.body !== undefined ? { 'content-type': 'application/json' } : {}), ...(call.headers ?? {}) };
1360
1360
  const form = /^application\\/x-www-form-urlencoded\\b/i.test(new Headers(headers).get('content-type') ?? '');
@@ -32,7 +32,7 @@ import { boardFrameKey, getActiveWorldStore, withHistoryLocks, captureHistory, h
32
32
  import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync , rmSync} from 'node:fs';
33
33
  import { CALLER_HEADER, callerForHeader, newTraceparent, parseTraceparent, traceparentForDelivery, readTwinRequestJournalFrom, type TwinRequestJournalCursor, assertSafeChangesetName, blobDigest, destinationOf, getActiveBlobStore, journalTwinRequest, loadCheck, twinRequestJournalFailures, openSealedAt, readTwinRequestJournal, serveHttp, twinRequestCredentials, TWIN_PREFIX_HEADER, TWIN_SITES_HEADER, TWIN_SITE_HEADER, siteLabel, siteUrlOf, ORIGINAL_PATH_HEADER, validateRemoteOrigin, type SealedCredential, type TwinStreamConnection, type TwinStreamSink, type BoardFrame } from '@volter/world-core/runtime';
34
34
  import { resolveTwinHost, type HostPredicate } from '@volter/world-core/host-routing';
35
- import { assertBrowserName, validateBrowserState, cookieMatchesHost } from '@volter/world-core';
35
+ import { assertBrowserName, validateBrowserState, browserAcceptLanguage, cookieMatchesHost } from '@volter/world-core';
36
36
  import { worldBrowser, worldBrowsers, putWorldBrowser, deleteWorldBrowser, materializeBrowserFiles, browserLoad, putBrowserLoad, browserLoadId } from './browsers.ts';
37
37
  import { APP_READ_ENDPOINT_ENV } from './init.ts';
38
38
  import { packFacts, packHostRules } from './pack-facts.ts';
@@ -1024,8 +1024,8 @@ export class WorldDoors {
1024
1024
  const carried = [current, previous].filter(load => load?.name === name && load.site === site);
1025
1025
  if (id === loadId && current?.name === name && current.site === site && current.version === browser.version) return null;
1026
1026
  if (request.method !== 'GET' || !['document', 'iframe'].includes(request.headers.get('sec-fetch-dest') ?? '')) return null;
1027
- const cookies = browser.state.cookies.filter(c => cookieMatchesHost(c, site));
1028
- const storage = browser.state.origins.find(o => new URL(o.origin).hostname === site)?.localStorage ?? [];
1027
+ const cookies = browser.state.storageState.cookies.filter(c => cookieMatchesHost(c, site));
1028
+ const storage = browser.state.storageState.origins.find(o => new URL(o.origin).hostname === site)?.localStorage ?? [];
1029
1029
  const headers = new Headers({ 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'referrer-policy': 'no-referrer' });
1030
1030
  const protectedCookie = (key: string): boolean => /^(?:__Host-)?volter_(?:world|pages|browser)$/.test(key);
1031
1031
  const expired = new Map<string, { name: string; path: string }>();
@@ -1111,6 +1111,9 @@ export class WorldDoors {
1111
1111
  for (const key of [TOKEN_HEADER, TWINS_KEY_HEADER, CALLER_HEADER, ORIGINAL_PATH_HEADER, READ_ONLY_HEADER, 'host', TWIN_PREFIX_HEADER, TWIN_SITES_HEADER, TWIN_SITE_HEADER]) headers.delete(key);
1112
1112
  for (const name of ['accept', 'accept-language', 'if-none-match', 'range', 'user-agent']) { const v = request.headers.get(name); if (v !== null) headers.set(name, v); }
1113
1113
  if (browser) {
1114
+ const profile = worldBrowser(this.up().dirs.data, browser)?.state;
1115
+ const language = profile ? browserAcceptLanguage(profile.context) : undefined;
1116
+ if (language !== undefined) headers.set('accept-language', language);
1114
1117
  const cookies = (request.headers.get('cookie') ?? '').split(/;\s*/).filter(c => !/^(?:__Host-)?volter_(?:world|pages|browser)=/.test(c));
1115
1118
  if (cookies.length) headers.set('cookie', cookies.join('; '));
1116
1119
  headers.set('x-volter-world-browser', browser);