@volter/world-runtime 2.0.35 → 2.0.37
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/dist/src/world-view.d.ts +3 -1
- package/dist/src/world-view.js +19 -3
- package/package.json +5 -5
- package/src/world-view.ts +19 -4
package/dist/src/world-view.d.ts
CHANGED
|
@@ -21,5 +21,7 @@ export declare function serveWorldView(name: string, opts?: {
|
|
|
21
21
|
host?: string;
|
|
22
22
|
console?: ViewConsole;
|
|
23
23
|
announce?: (line: string) => void; /** each World a browser steps into at an origin of its own (on by default on loopback) */
|
|
24
|
-
origins?: boolean;
|
|
24
|
+
origins?: boolean; /** the World's own origin as a browser reaches it through a proxy on this machine (a desktop serving it as an app):
|
|
25
|
+
* a request the proxy forwards from that host (X-Forwarded-Host) is answered as at that origin, its local session included */
|
|
26
|
+
origin?: string;
|
|
25
27
|
}): Promise<WorldView>;
|
package/dist/src/world-view.js
CHANGED
|
@@ -39,7 +39,15 @@ export async function serveWorldView(name, opts = {}) {
|
|
|
39
39
|
// a non-loopback --host turns origins off by default: say so (the World and its branches then share one origin)
|
|
40
40
|
if (opts.origins === undefined && !origins)
|
|
41
41
|
opts.announce?.(`origins off (bound to ${host}): the World and its branches share one origin, where a browser session is only path-scoped; bind loopback for an origin per World`);
|
|
42
|
-
const
|
|
42
|
+
const proxied = opts.origin ? new URL(opts.origin) : null;
|
|
43
|
+
if (proxied && (proxied.origin !== opts.origin || !/^https?:$/.test(proxied.protocol)))
|
|
44
|
+
throw new Error(`--origin is an origin (http(s)://host[:port]), got ${opts.origin}`);
|
|
45
|
+
if (proxied && !(loopbackBind && origins))
|
|
46
|
+
throw new Error('--origin is for a World served on loopback with origins on, behind a proxy on this machine');
|
|
47
|
+
// the World itself at the proxy's origin; its branches keep their own *.localhost origins
|
|
48
|
+
// (the first World mounted is the World; every later one is a branch of it)
|
|
49
|
+
let base = null;
|
|
50
|
+
const browserOrigin = origins ? (served) => (proxied && (base === null || served === base) ? proxied.origin : port ? localWorldOrigin(served, port) : null) : undefined;
|
|
43
51
|
const mountAt = (at) => mountWorld(loadWorldConfig(join(at, stateDirName(), 'world.json'), at).config.id, { root: at, branches: (served) => branches.doorsFor(served), ...(browserOrigin ? { browserOrigin } : {}), ...(local ? { localTrust: true } : {}) });
|
|
44
52
|
const branches = new LocalBranches({ dir: join(root, stateDirName(), 'branches'), origin: () => loopback, worlds, mount: mountAt, ...(opts.announce ? { announce: opts.announce } : {}) });
|
|
45
53
|
// THE WORLD'S OWN BRANCHES, one served at a time: serving another stops this one's twins and starts that one's under the
|
|
@@ -84,17 +92,25 @@ export async function serveWorldView(name, opts = {}) {
|
|
|
84
92
|
};
|
|
85
93
|
const mountOpts = { root, branches: (served) => branches.doorsFor(served), ...(browserOrigin ? { browserOrigin } : {}), ...(local ? { localTrust: true } : {}), serveBranch };
|
|
86
94
|
let world = await mountWorld(name, mountOpts);
|
|
95
|
+
base = world.served;
|
|
87
96
|
worlds.set(world.served, world);
|
|
88
97
|
const server = await serveHttp({
|
|
89
98
|
// a pushed blob (a release video) is one PUT: bodies up to 1 GiB, as `serve` takes them
|
|
90
99
|
hostname: host, port: opts.port ?? 0, maxRequestBodySize: 1024 * 1024 * 1024,
|
|
91
100
|
// this front serves a World and its branches; each World's own twins journal their requests
|
|
92
101
|
twinRequestJournal: false,
|
|
93
|
-
async fetch(
|
|
102
|
+
async fetch(incoming) {
|
|
103
|
+
// forwarded from the proxy's host: answered as at the World's own origin there (only that exact host; the proxy is
|
|
104
|
+
// on this machine, as everything that reaches a loopback front is)
|
|
105
|
+
const addressed = new URL(incoming.url).hostname.replace(/^\[|\]$/g, '');
|
|
106
|
+
const fromProxy = proxied && incoming.headers.get('x-forwarded-host') === proxied.host && ['localhost', '127.0.0.1', '::1'].includes(addressed);
|
|
107
|
+
const request = fromProxy
|
|
108
|
+
? new Request(`${proxied.origin}${new URL(incoming.url).pathname}${new URL(incoming.url).search}`, incoming)
|
|
109
|
+
: incoming;
|
|
94
110
|
const at = new URL(request.url);
|
|
95
111
|
const path = at.pathname;
|
|
96
112
|
// a World's own origin serves that World (and the ping and the console) and no other
|
|
97
|
-
const own = origins ? worldOfHost(at.hostname, worlds.keys()) : undefined;
|
|
113
|
+
const own = origins ? (proxied && at.host === proxied.host ? base : worldOfHost(at.hostname, worlds.keys())) : undefined;
|
|
98
114
|
if (own === null)
|
|
99
115
|
return Response.json({ error: `no world at ${at.host}` }, { status: 404 });
|
|
100
116
|
// the bare address, typed into a browser, is the World's page: at its own origin when it has one
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-runtime",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.37",
|
|
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",
|
|
@@ -67,14 +67,14 @@
|
|
|
67
67
|
"node": ">=22.3"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@volter/world-core": "2.0.
|
|
71
|
-
"@volter/world-console": "2.0.
|
|
70
|
+
"@volter/world-core": "2.0.21",
|
|
71
|
+
"@volter/world-console": "2.0.13"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"@electric-sql/pglite": "0.5.8",
|
|
75
75
|
"@electric-sql/pglite-pgvector": "0.0.9",
|
|
76
76
|
"@volter/world-access": "2.0.2",
|
|
77
|
-
"@volter/world-core": "2.0.
|
|
77
|
+
"@volter/world-core": "2.0.21",
|
|
78
78
|
"pg-gateway": "0.3.0-beta.4",
|
|
79
79
|
"smol-toml": "^1.8.0"
|
|
80
80
|
},
|
|
@@ -84,6 +84,6 @@
|
|
|
84
84
|
}
|
|
85
85
|
},
|
|
86
86
|
"optionalDependencies": {
|
|
87
|
-
"@volter/twin-mongodb": "0.1.
|
|
87
|
+
"@volter/twin-mongodb": "0.1.21"
|
|
88
88
|
}
|
|
89
89
|
}
|
package/src/world-view.ts
CHANGED
|
@@ -28,7 +28,8 @@ function branchRoots(dir: string): string[] {
|
|
|
28
28
|
return out.sort();
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export async function serveWorldView(name: string, opts: { root?: string; port?: number; host?: string; console?: ViewConsole; announce?: (line: string) => void; /** each World a browser steps into at an origin of its own (on by default on loopback) */ origins?: boolean
|
|
31
|
+
export async function serveWorldView(name: string, opts: { root?: string; port?: number; host?: string; console?: ViewConsole; announce?: (line: string) => void; /** each World a browser steps into at an origin of its own (on by default on loopback) */ origins?: boolean; /** the World's own origin as a browser reaches it through a proxy on this machine (a desktop serving it as an app):
|
|
32
|
+
* a request the proxy forwards from that host (X-Forwarded-Host) is answered as at that origin, its local session included */ origin?: string } = {}): Promise<WorldView> {
|
|
32
33
|
const root = resolve(opts.root ?? process.cwd());
|
|
33
34
|
const host = opts.host ?? '127.0.0.1';
|
|
34
35
|
const worlds = new Map<string, MountedWorld>();
|
|
@@ -41,7 +42,13 @@ export async function serveWorldView(name: string, opts: { root?: string; port?:
|
|
|
41
42
|
const local = loopbackBind && origins;
|
|
42
43
|
// a non-loopback --host turns origins off by default: say so (the World and its branches then share one origin)
|
|
43
44
|
if (opts.origins === undefined && !origins) opts.announce?.(`origins off (bound to ${host}): the World and its branches share one origin, where a browser session is only path-scoped; bind loopback for an origin per World`);
|
|
44
|
-
const
|
|
45
|
+
const proxied = opts.origin ? new URL(opts.origin) : null;
|
|
46
|
+
if (proxied && (proxied.origin !== opts.origin || !/^https?:$/.test(proxied.protocol))) throw new Error(`--origin is an origin (http(s)://host[:port]), got ${opts.origin}`);
|
|
47
|
+
if (proxied && !(loopbackBind && origins)) throw new Error('--origin is for a World served on loopback with origins on, behind a proxy on this machine');
|
|
48
|
+
// the World itself at the proxy's origin; its branches keep their own *.localhost origins
|
|
49
|
+
// (the first World mounted is the World; every later one is a branch of it)
|
|
50
|
+
let base: string | null = null;
|
|
51
|
+
const browserOrigin = origins ? (served: string) => (proxied && (base === null || served === base) ? proxied.origin : port ? localWorldOrigin(served, port) : null) : undefined;
|
|
45
52
|
const mountAt = (at: string): Promise<MountedWorld> => mountWorld(loadWorldConfig(join(at, stateDirName(), 'world.json'), at).config.id, { root: at, branches: (served) => branches.doorsFor(served), ...(browserOrigin ? { browserOrigin } : {}), ...(local ? { localTrust: true } : {}) });
|
|
46
53
|
const branches: LocalBranches = new LocalBranches({ dir: join(root, stateDirName(), 'branches'), origin: () => loopback, worlds, mount: mountAt, ...(opts.announce ? { announce: opts.announce } : {}) });
|
|
47
54
|
// THE WORLD'S OWN BRANCHES, one served at a time: serving another stops this one's twins and starts that one's under the
|
|
@@ -75,16 +82,24 @@ export async function serveWorldView(name: string, opts: { root?: string; port?:
|
|
|
75
82
|
};
|
|
76
83
|
const mountOpts = { root, branches: (served: string) => branches.doorsFor(served), ...(browserOrigin ? { browserOrigin } : {}), ...(local ? { localTrust: true } : {}), serveBranch };
|
|
77
84
|
let world = await mountWorld(name, mountOpts);
|
|
85
|
+
base = world.served;
|
|
78
86
|
worlds.set(world.served, world);
|
|
79
87
|
const server = await serveHttp({
|
|
80
88
|
// a pushed blob (a release video) is one PUT: bodies up to 1 GiB, as `serve` takes them
|
|
81
89
|
hostname: host, port: opts.port ?? 0, maxRequestBodySize: 1024 * 1024 * 1024,
|
|
82
90
|
// this front serves a World and its branches; each World's own twins journal their requests
|
|
83
91
|
twinRequestJournal: false,
|
|
84
|
-
async fetch(
|
|
92
|
+
async fetch(incoming) {
|
|
93
|
+
// forwarded from the proxy's host: answered as at the World's own origin there (only that exact host; the proxy is
|
|
94
|
+
// on this machine, as everything that reaches a loopback front is)
|
|
95
|
+
const addressed = new URL(incoming.url).hostname.replace(/^\[|\]$/g, '');
|
|
96
|
+
const fromProxy = proxied && incoming.headers.get('x-forwarded-host') === proxied.host && ['localhost', '127.0.0.1', '::1'].includes(addressed);
|
|
97
|
+
const request = fromProxy
|
|
98
|
+
? new Request(`${proxied.origin}${new URL(incoming.url).pathname}${new URL(incoming.url).search}`, incoming)
|
|
99
|
+
: incoming;
|
|
85
100
|
const at = new URL(request.url); const path = at.pathname;
|
|
86
101
|
// a World's own origin serves that World (and the ping and the console) and no other
|
|
87
|
-
const own = origins ? worldOfHost(at.hostname, worlds.keys()) : undefined;
|
|
102
|
+
const own = origins ? (proxied && at.host === proxied.host ? base! : worldOfHost(at.hostname, worlds.keys())) : undefined;
|
|
88
103
|
if (own === null) return Response.json({ error: `no world at ${at.host}` }, { status: 404 });
|
|
89
104
|
// the bare address, typed into a browser, is the World's page: at its own origin when it has one
|
|
90
105
|
if (opts.console && path === '/' && request.method === 'GET' && (request.headers.get('accept') ?? '').includes('text/html')) {
|