@volter/world-runtime 3.0.161 → 3.0.163
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/served-world.js +12 -7
- package/dist/src/service-exit.d.ts +3 -2
- package/dist/src/service-exit.js +7 -5
- package/package.json +4 -4
- package/src/served-world.ts +11 -7
- package/src/service-exit.ts +5 -3
package/dist/src/served-world.js
CHANGED
|
@@ -1883,20 +1883,23 @@ export class WorldDoors {
|
|
|
1883
1883
|
// a browser session is opened by either token: the read token's is how a view-only link opens a vendor's screens
|
|
1884
1884
|
// the session gate comes first: a page that holds only the cookie (a sibling World's, the same site) can neither
|
|
1885
1885
|
// re-issue nor end the session through this door
|
|
1886
|
-
// A HOSTED SITE OPENED (`
|
|
1887
|
-
//
|
|
1888
|
-
//
|
|
1889
|
-
if (kind === 'sites' && name === 'go' && request.method === 'GET') {
|
|
1886
|
+
// A HOSTED SITE OPENED (`sites/go?host=<hostname>&land=<path>`): GET hands a navigation to the site's own address;
|
|
1887
|
+
// POST gives an authenticated console click that same opening URL, with the same scoped one-time pages pass.
|
|
1888
|
+
// A GET navigation from a sibling site may open a read; a POST handoff comes only from the console's own origin.
|
|
1889
|
+
if (kind === 'sites' && name === 'go' && (request.method === 'GET' || request.method === 'POST')) {
|
|
1890
1890
|
const fetchSite = request.headers.get('sec-fetch-site');
|
|
1891
1891
|
const dest = request.headers.get('sec-fetch-dest');
|
|
1892
|
+
const handoff = request.method === 'POST';
|
|
1893
|
+
if (handoff && fetchSite !== 'same-origin')
|
|
1894
|
+
return Response.json({ error: "a site opening URL is requested only from the World's own page (Sec-Fetch-Site: same-origin)" }, { status: 403 });
|
|
1892
1895
|
const browser = url.searchParams.get('browser') ?? undefined;
|
|
1893
1896
|
if (browser && fetchSite !== 'same-origin' && fetchSite !== 'none')
|
|
1894
1897
|
return Response.json({ error: 'a browser site opens only from its World or a typed address (Sec-Fetch-Site: same-origin or none)' }, { status: 403 });
|
|
1895
1898
|
// a person's navigation (from here, from the site's own page linking back, or typed in), or a frame of this World's
|
|
1896
1899
|
// own console (its board, its comparison)
|
|
1897
|
-
const navigated =
|
|
1900
|
+
const navigated = request.method === 'GET' && dest === 'document' && ['same-origin', 'same-site', 'none'].includes(fetchSite ?? '');
|
|
1898
1901
|
const framed = fetchSite === 'same-origin' && dest === 'iframe';
|
|
1899
|
-
if (via === 'session' && !navigated && !framed)
|
|
1902
|
+
if (via === 'session' && !handoff && !navigated && !framed)
|
|
1900
1903
|
return Response.json({ error: "a hosted site opens on a person's navigation" }, { status: 403 });
|
|
1901
1904
|
const template = this.host.sitesTemplate?.() ?? null;
|
|
1902
1905
|
const hostname = (url.searchParams.get('host') ?? '').toLowerCase();
|
|
@@ -1924,7 +1927,9 @@ export class WorldDoors {
|
|
|
1924
1927
|
const passed = this.mintPagesPass(browser ? scope : 'read', { site: hostname, ...(browser ? { browser } : {}), ...(asked.session ? { session: asked.session } : {}), ...(asked.who ? { who: asked.who } : {}), ...(this.host.browserOrigin?.() ? { framer: this.host.browserOrigin() } : {}) });
|
|
1925
1928
|
if (!passed)
|
|
1926
1929
|
return Response.json({ error: 'too many sites opened at once: try again in a minute' }, { status: 429 });
|
|
1927
|
-
|
|
1930
|
+
const to = `${at}/-/${this.served}/pages/open#${new URLSearchParams({ pass: passed, land: land.startsWith('/') ? land : '/' }).toString()}`;
|
|
1931
|
+
const headers = { 'cache-control': 'no-store', 'referrer-policy': 'no-referrer' };
|
|
1932
|
+
return handoff ? Response.json({ url: to }, { headers }) : new Response(null, { status: 303, headers: { ...headers, location: to } });
|
|
1928
1933
|
}
|
|
1929
1934
|
const foreign = this.crossOriginSession(request, via);
|
|
1930
1935
|
if (foreign)
|
|
@@ -29,8 +29,9 @@ export type ServiceEnd = {
|
|
|
29
29
|
* which has no runs to tell apart. A run is bounded by its start record or, for a spawn that failed before starting
|
|
30
30
|
* or a log from before start records, by the previous run's exit. */
|
|
31
31
|
export declare function readServiceEnd(log: string): ServiceEnd | undefined;
|
|
32
|
-
/** One line for a service that is not live: how it ended, who stopped it, when, and after how long.
|
|
33
|
-
|
|
32
|
+
/** One line for a service that is not live: how it ended, who stopped it, when, and after how long.
|
|
33
|
+
* A caller with only an exit event cannot attribute its stop without the run's stop history. */
|
|
34
|
+
export declare function describeServiceEnd(end: ServiceEnd, stopContext?: 'known' | 'unknown'): string;
|
|
34
35
|
export type WorldEvent = {
|
|
35
36
|
event: string;
|
|
36
37
|
service?: string;
|
package/dist/src/service-exit.js
CHANGED
|
@@ -139,8 +139,9 @@ const duration = (ms) => {
|
|
|
139
139
|
const s = Math.round(ms / 1000);
|
|
140
140
|
return s < 60 ? `${s}s` : s < 3600 ? `${Math.floor(s / 60)}m${String(s % 60).padStart(2, '0')}s` : `${Math.floor(s / 3600)}h${String(Math.floor((s % 3600) / 60)).padStart(2, '0')}m`;
|
|
141
141
|
};
|
|
142
|
-
/** One line for a service that is not live: how it ended, who stopped it, when, and after how long.
|
|
143
|
-
|
|
142
|
+
/** One line for a service that is not live: how it ended, who stopped it, when, and after how long.
|
|
143
|
+
* A caller with only an exit event cannot attribute its stop without the run's stop history. */
|
|
144
|
+
export function describeServiceEnd(end, stopContext = 'known') {
|
|
144
145
|
const exit = end.exited;
|
|
145
146
|
// A log from before exit records split code from signal: a status past 128 is 128 + the signal's number.
|
|
146
147
|
if (exit && exit.code === undefined && exit.signal === undefined && exit.status > 128)
|
|
@@ -157,8 +158,9 @@ export function describeServiceEnd(end) {
|
|
|
157
158
|
const ended = exit.signal ? `ended by ${exit.signal}` : `exit code ${exit.code ?? exit.status}`;
|
|
158
159
|
const how = exit.error ? `failed to spawn (${exit.error})`
|
|
159
160
|
: exit.stopRequested && end.stopped ? `was stopped by the World (${end.stopped.by}; ${ended})`
|
|
160
|
-
: exit.stopRequested ? `
|
|
161
|
-
: exit.
|
|
161
|
+
: exit.stopRequested && stopContext === 'unknown' ? `stopped after a ${exit.stopRequested} request (${ended})`
|
|
162
|
+
: exit.stopRequested ? `was stopped by a ${exit.stopRequested} from outside the World (no World stop was recorded; ${ended})`
|
|
163
|
+
: exit.signal ? `was killed by ${exit.signal}` : `exited with code ${exit.code ?? exit.status}`;
|
|
162
164
|
const ran = exit.uptimeMs !== undefined ? ` after running ${duration(exit.uptimeMs)}` : '';
|
|
163
165
|
return `${how}${ran}, at ${exit.at}`;
|
|
164
166
|
}
|
|
@@ -181,7 +183,7 @@ export function describeWorldEvent(event) {
|
|
|
181
183
|
if (event.event === 'world.service.started')
|
|
182
184
|
return `${event.at} ${who} started (pid ${event.pid})`;
|
|
183
185
|
if (event.event === 'world.service.exited')
|
|
184
|
-
return `${event.at} ${who} ${describeServiceEnd({ exited: event, lastOutput: [] }).replace(/, at [^,]*$/, '')}`;
|
|
186
|
+
return `${event.at} ${who} ${describeServiceEnd({ exited: event, lastOutput: [] }, 'unknown').replace(/, at [^,]*$/, '')}`;
|
|
185
187
|
if (event.event === 'world.service.stop')
|
|
186
188
|
return `${event.at} ${who} stopped by the World (${event.by}, ${event.signal})`;
|
|
187
189
|
if (event.event === 'world.service.killed')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-runtime",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.163",
|
|
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",
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"node": ">=22.3"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@volter/world-core": "3.0.
|
|
74
|
-
"@volter/world-console": "3.0.
|
|
73
|
+
"@volter/world-core": "3.0.155",
|
|
74
|
+
"@volter/world-console": "3.0.151"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
77
|
"@electric-sql/pglite": "0.5.8",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"@volter/browser-runtime": "0.1.104",
|
|
81
81
|
"@volter/browser-sandboxes": "0.1.84",
|
|
82
82
|
"@volter/world-access": "3.0.1",
|
|
83
|
-
"@volter/world-core": "3.0.
|
|
83
|
+
"@volter/world-core": "3.0.155",
|
|
84
84
|
"modern-screenshot": "4.7.0",
|
|
85
85
|
"mongodb": "~6.20.0",
|
|
86
86
|
"pg-gateway": "0.3.0-beta.4",
|
package/src/served-world.ts
CHANGED
|
@@ -1630,19 +1630,21 @@ export class WorldDoors {
|
|
|
1630
1630
|
// a browser session is opened by either token: the read token's is how a view-only link opens a vendor's screens
|
|
1631
1631
|
// the session gate comes first: a page that holds only the cookie (a sibling World's, the same site) can neither
|
|
1632
1632
|
// re-issue nor end the session through this door
|
|
1633
|
-
// A HOSTED SITE OPENED (`
|
|
1634
|
-
//
|
|
1635
|
-
//
|
|
1636
|
-
if (kind === 'sites' && name === 'go' && request.method === 'GET') {
|
|
1633
|
+
// A HOSTED SITE OPENED (`sites/go?host=<hostname>&land=<path>`): GET hands a navigation to the site's own address;
|
|
1634
|
+
// POST gives an authenticated console click that same opening URL, with the same scoped one-time pages pass.
|
|
1635
|
+
// A GET navigation from a sibling site may open a read; a POST handoff comes only from the console's own origin.
|
|
1636
|
+
if (kind === 'sites' && name === 'go' && (request.method === 'GET' || request.method === 'POST')) {
|
|
1637
1637
|
const fetchSite = request.headers.get('sec-fetch-site');
|
|
1638
1638
|
const dest = request.headers.get('sec-fetch-dest');
|
|
1639
|
+
const handoff = request.method === 'POST';
|
|
1640
|
+
if (handoff && fetchSite !== 'same-origin') return Response.json({ error: "a site opening URL is requested only from the World's own page (Sec-Fetch-Site: same-origin)" }, { status: 403 });
|
|
1639
1641
|
const browser = url.searchParams.get('browser') ?? undefined;
|
|
1640
1642
|
if (browser && fetchSite !== 'same-origin' && fetchSite !== 'none') return Response.json({ error: 'a browser site opens only from its World or a typed address (Sec-Fetch-Site: same-origin or none)' }, { status: 403 });
|
|
1641
1643
|
// a person's navigation (from here, from the site's own page linking back, or typed in), or a frame of this World's
|
|
1642
1644
|
// own console (its board, its comparison)
|
|
1643
|
-
const navigated =
|
|
1645
|
+
const navigated = request.method === 'GET' && dest === 'document' && ['same-origin', 'same-site', 'none'].includes(fetchSite ?? '');
|
|
1644
1646
|
const framed = fetchSite === 'same-origin' && dest === 'iframe';
|
|
1645
|
-
if (via === 'session' && !navigated && !framed) return Response.json({ error: "a hosted site opens on a person's navigation" }, { status: 403 });
|
|
1647
|
+
if (via === 'session' && !handoff && !navigated && !framed) return Response.json({ error: "a hosted site opens on a person's navigation" }, { status: 403 });
|
|
1646
1648
|
const template = this.host.sitesTemplate?.() ?? null;
|
|
1647
1649
|
const hostname = (url.searchParams.get('host') ?? '').toLowerCase();
|
|
1648
1650
|
if (browser) {
|
|
@@ -1660,7 +1662,9 @@ export class WorldDoors {
|
|
|
1660
1662
|
const land = url.searchParams.get('land') ?? '/';
|
|
1661
1663
|
const passed = this.mintPagesPass(browser ? scope : 'read', { site: hostname, ...(browser ? { browser } : {}), ...(asked.session ? { session: asked.session } : {}), ...(asked.who ? { who: asked.who } : {}), ...(this.host.browserOrigin?.() ? { framer: this.host.browserOrigin!()! } : {}) });
|
|
1662
1664
|
if (!passed) return Response.json({ error: 'too many sites opened at once: try again in a minute' }, { status: 429 });
|
|
1663
|
-
|
|
1665
|
+
const to = `${at}/-/${this.served}/pages/open#${new URLSearchParams({ pass: passed, land: land.startsWith('/') ? land : '/' }).toString()}`;
|
|
1666
|
+
const headers = { 'cache-control': 'no-store', 'referrer-policy': 'no-referrer' };
|
|
1667
|
+
return handoff ? Response.json({ url: to }, { headers }) : new Response(null, { status: 303, headers: { ...headers, location: to } });
|
|
1664
1668
|
}
|
|
1665
1669
|
const foreign = this.crossOriginSession(request, via);
|
|
1666
1670
|
if (foreign) return foreign;
|
package/src/service-exit.ts
CHANGED
|
@@ -129,8 +129,9 @@ const duration = (ms: number): string => {
|
|
|
129
129
|
return s < 60 ? `${s}s` : s < 3600 ? `${Math.floor(s / 60)}m${String(s % 60).padStart(2, '0')}s` : `${Math.floor(s / 3600)}h${String(Math.floor((s % 3600) / 60)).padStart(2, '0')}m`;
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
-
/** One line for a service that is not live: how it ended, who stopped it, when, and after how long.
|
|
133
|
-
|
|
132
|
+
/** One line for a service that is not live: how it ended, who stopped it, when, and after how long.
|
|
133
|
+
* A caller with only an exit event cannot attribute its stop without the run's stop history. */
|
|
134
|
+
export function describeServiceEnd(end: ServiceEnd, stopContext: 'known' | 'unknown' = 'known'): string {
|
|
134
135
|
const exit = end.exited;
|
|
135
136
|
// A log from before exit records split code from signal: a status past 128 is 128 + the signal's number.
|
|
136
137
|
if (exit && exit.code === undefined && exit.signal === undefined && exit.status > 128) exit.signal = `signal ${exit.status - 128}`;
|
|
@@ -144,6 +145,7 @@ export function describeServiceEnd(end: ServiceEnd): string {
|
|
|
144
145
|
const ended = exit.signal ? `ended by ${exit.signal}` : `exit code ${exit.code ?? exit.status}`;
|
|
145
146
|
const how = exit.error ? `failed to spawn (${exit.error})`
|
|
146
147
|
: exit.stopRequested && end.stopped ? `was stopped by the World (${end.stopped.by}; ${ended})`
|
|
148
|
+
: exit.stopRequested && stopContext === 'unknown' ? `stopped after a ${exit.stopRequested} request (${ended})`
|
|
147
149
|
: exit.stopRequested ? `was stopped by a ${exit.stopRequested} from outside the World (no World stop was recorded; ${ended})`
|
|
148
150
|
: exit.signal ? `was killed by ${exit.signal}` : `exited with code ${exit.code ?? exit.status}`;
|
|
149
151
|
const ran = exit.uptimeMs !== undefined ? ` after running ${duration(exit.uptimeMs)}` : '';
|
|
@@ -162,7 +164,7 @@ export function readWorldEvents(path: string): WorldEvent[] {
|
|
|
162
164
|
export function describeWorldEvent(event: WorldEvent): string {
|
|
163
165
|
const who = event.service ?? '(world)';
|
|
164
166
|
if (event.event === 'world.service.started') return `${event.at} ${who} started (pid ${event.pid})`;
|
|
165
|
-
if (event.event === 'world.service.exited') return `${event.at} ${who} ${describeServiceEnd({ exited: event as unknown as ServiceExitRecord, lastOutput: [] }).replace(/, at [^,]*$/, '')}`;
|
|
167
|
+
if (event.event === 'world.service.exited') return `${event.at} ${who} ${describeServiceEnd({ exited: event as unknown as ServiceExitRecord, lastOutput: [] }, 'unknown').replace(/, at [^,]*$/, '')}`;
|
|
166
168
|
if (event.event === 'world.service.stop') return `${event.at} ${who} stopped by the World (${event.by}, ${event.signal})`;
|
|
167
169
|
if (event.event === 'world.service.killed') return `${event.at} ${who} killed by the World (${event.by}, SIGKILL after the grace)`;
|
|
168
170
|
return `${event.at} ${who} ${event.event}`;
|