beam.com 0.1.0-rc.1
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/LICENSE +202 -0
- package/NOTICE +97 -0
- package/README.md +369 -0
- package/js/beam.com.mjs +33 -0
- package/js/download.mjs +78 -0
- package/js/node.mjs +75 -0
- package/package.json +36 -0
- package/runtime/app-com.js +203 -0
- package/runtime/beam.mjs +2 -0
- package/runtime/beam.wasm +0 -0
- package/runtime/browser/beam-wasm.js +2 -0
- package/runtime/browser/net.js +7 -0
- package/runtime/browser/none.js +4 -0
- package/runtime/browser.js +161 -0
- package/runtime/cloudflare/durable.js +361 -0
- package/runtime/cloudflare/index.js +76 -0
- package/runtime/cloudflare/release.js +42 -0
- package/runtime/cloudflare/snapshot.js +3 -0
- package/runtime/cloudflare/worker.js +1891 -0
- package/runtime/deno/app.js +8 -0
- package/runtime/deno/beam-wasm.js +3 -0
- package/runtime/deno/none.js +3 -0
- package/runtime/deno/release-bin.js +46 -0
- package/runtime/deno/snapshot-bin.js +11 -0
- package/runtime/deno/worker.js +1891 -0
- package/runtime/deno.js +346 -0
- package/runtime/deno.json +5 -0
- package/runtime/durable-global.js +40 -0
- package/runtime/durable.js +361 -0
- package/runtime/global.js +26 -0
- package/runtime/licenses/LICENSE +202 -0
- package/runtime/licenses/NOTICE +97 -0
- package/runtime/licenses/bcrypt_elixir.txt +87 -0
- package/runtime/licenses/cosmopolitan.txt +16 -0
- package/runtime/licenses/elixir/Apache-2.0.txt +73 -0
- package/runtime/licenses/elixir/LicenseRef-elixir-trademark-policy.txt +98 -0
- package/runtime/licenses/elixir/LicenseRef-scancode-unicode.txt +58 -0
- package/runtime/licenses/emscripten.txt +102 -0
- package/runtime/licenses/exqlite.txt +21 -0
- package/runtime/licenses/llvm.txt +311 -0
- package/runtime/licenses/musl.txt +193 -0
- package/runtime/licenses/otp/BSD-2-Clause.txt +22 -0
- package/runtime/licenses/otp/BSD-3-Clause.txt +25 -0
- package/runtime/licenses/otp/LicenseRef-IETF-MIB.txt +9 -0
- package/runtime/licenses/otp/LicenseRef-RSA-PKCS3.txt +23 -0
- package/runtime/licenses/otp/LicenseRef-RSA-PKCS5v2-0.txt +17 -0
- package/runtime/licenses/otp/LicenseRef-scancode-ietf-trust.txt +296 -0
- package/runtime/licenses/otp/LicenseRef-scancode-ietf.txt +23 -0
- package/runtime/licenses/otp/LicenseRef-scancode-w3c-docs-19990405.txt +26 -0
- package/runtime/licenses/otp/LicenseRef-scancode-wxwindows-free-doc-3.txt +57 -0
- package/runtime/licenses/otp/MIT.txt +17 -0
- package/runtime/licenses/otp/SGI-B-2.0.txt +25 -0
- package/runtime/licenses/otp/W3C-19980720.txt +55 -0
- package/runtime/licenses/picosat.txt +19 -0
- package/runtime/licenses/wamr.txt +219 -0
- package/runtime/nifs.js +2 -0
- package/runtime/package.json +1 -0
- package/runtime/page/404.html +49 -0
- package/runtime/page/app-com.js +203 -0
- package/runtime/page/beam.mjs +2 -0
- package/runtime/page/browser/beam-wasm.js +2 -0
- package/runtime/page/browser/net.js +7 -0
- package/runtime/page/browser/none.js +4 -0
- package/runtime/page/browser.js +161 -0
- package/runtime/page/index.html +50 -0
- package/runtime/page/licenses/LICENSE +202 -0
- package/runtime/page/licenses/NOTICE +97 -0
- package/runtime/page/licenses/bcrypt_elixir.txt +87 -0
- package/runtime/page/licenses/cosmopolitan.txt +16 -0
- package/runtime/page/licenses/elixir/Apache-2.0.txt +73 -0
- package/runtime/page/licenses/elixir/LicenseRef-elixir-trademark-policy.txt +98 -0
- package/runtime/page/licenses/elixir/LicenseRef-scancode-unicode.txt +58 -0
- package/runtime/page/licenses/emscripten.txt +102 -0
- package/runtime/page/licenses/exqlite.txt +21 -0
- package/runtime/page/licenses/llvm.txt +311 -0
- package/runtime/page/licenses/musl.txt +193 -0
- package/runtime/page/licenses/otp/BSD-2-Clause.txt +22 -0
- package/runtime/page/licenses/otp/BSD-3-Clause.txt +25 -0
- package/runtime/page/licenses/otp/LicenseRef-IETF-MIB.txt +9 -0
- package/runtime/page/licenses/otp/LicenseRef-RSA-PKCS3.txt +23 -0
- package/runtime/page/licenses/otp/LicenseRef-RSA-PKCS5v2-0.txt +17 -0
- package/runtime/page/licenses/otp/LicenseRef-scancode-ietf-trust.txt +296 -0
- package/runtime/page/licenses/otp/LicenseRef-scancode-ietf.txt +23 -0
- package/runtime/page/licenses/otp/LicenseRef-scancode-w3c-docs-19990405.txt +26 -0
- package/runtime/page/licenses/otp/LicenseRef-scancode-wxwindows-free-doc-3.txt +57 -0
- package/runtime/page/licenses/otp/MIT.txt +17 -0
- package/runtime/page/licenses/otp/SGI-B-2.0.txt +25 -0
- package/runtime/page/licenses/otp/W3C-19980720.txt +55 -0
- package/runtime/page/licenses/picosat.txt +19 -0
- package/runtime/page/licenses/wamr.txt +219 -0
- package/runtime/page/main.js +176 -0
- package/runtime/page/runtime-id.js +3 -0
- package/runtime/page/scope.js +17 -0
- package/runtime/page/sw.js +111 -0
- package/runtime/page/vm.js +148 -0
- package/runtime/page/worker.js +1891 -0
- package/runtime/page/ws-shim.js +143 -0
- package/runtime/release/app.js +17 -0
- package/runtime/release.json +1 -0
- package/runtime/runtime-id.js +3 -0
- package/runtime/tcp-proxy.mjs +30 -0
- package/runtime/worker.js +1891 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// The VM of the page, in a module SharedWorker: one VM for all the tabs of
|
|
2
|
+
// the site. Without SharedWorker, it is a module Web Worker of one tab
|
|
3
|
+
// (index.html). So its work does not stop the page. A tab sends it:
|
|
4
|
+
// - {type: 'start', base, env, app}: boot the release (release.bin, or the
|
|
5
|
+
// app.com of the URL app) with the variables env (env.json, or those of
|
|
6
|
+
// a page), and the base path of the frame in BEAM_BASE_PATH. The
|
|
7
|
+
// first start boots the VM. A later start (another tab) gets the reply
|
|
8
|
+
// of the same boot;
|
|
9
|
+
// - {type: 'fetch', req} with a port: an HTTP request of a frame (sw.js);
|
|
10
|
+
// - {type: 'ws', path, protocols} with a port: a WebSocket of a frame
|
|
11
|
+
// (ws-shim.js).
|
|
12
|
+
// The cookies of the VM stay here (a service worker cannot set them in
|
|
13
|
+
// the browser), one jar for all the tabs: a login in one tab is a login in
|
|
14
|
+
// all the tabs. Each request and each upgrade gets them. The listeners are
|
|
15
|
+
// here before any import, so the first message does not come before them.
|
|
16
|
+
// The VM stops 30 s after the last tab of the site leaves (see LINGER).
|
|
17
|
+
|
|
18
|
+
let beam = null;
|
|
19
|
+
let booting = null; // the boot: a promise of the first start
|
|
20
|
+
let base = ''; // the path of the frame on the site: the VM gets the rest
|
|
21
|
+
const jar = new Map();
|
|
22
|
+
const pending = [];
|
|
23
|
+
|
|
24
|
+
function cookie() {
|
|
25
|
+
return [...jar].map(([k, v]) => `${k}=${v}`).join('; ');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function keep(response) {
|
|
29
|
+
for (const line of response.setCookies ?? []) {
|
|
30
|
+
const [pair, ...attrs] = line.split(';');
|
|
31
|
+
const at = pair.indexOf('=');
|
|
32
|
+
if (at < 1) continue;
|
|
33
|
+
const name = pair.slice(0, at).trim(), value = pair.slice(at + 1).trim();
|
|
34
|
+
const gone = attrs.some((a) => /^\s*max-age\s*=\s*(0|-)/i.test(a))
|
|
35
|
+
|| attrs.some((a) => /^\s*expires\s*=/i.test(a) && Date.parse(a.split('=').slice(1).join('=')) < Date.now());
|
|
36
|
+
if (gone) jar.delete(name); else jar.set(name, value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// The host of each request is localhost (PHX_HOST of env.json), so that the
|
|
41
|
+
// app takes its WebSocket (check_origin). x-forwarded-proto is https, as
|
|
42
|
+
// behind a proxy: an app with force_ssl and rewrite_on: [:x_forwarded_proto]
|
|
43
|
+
// then does not redirect to https://localhost/.
|
|
44
|
+
async function fetchVm({ path, method, headers, body }, port) {
|
|
45
|
+
try {
|
|
46
|
+
// No accept-encoding: a body that the app compresses would reach the frame as it is.
|
|
47
|
+
const h = Object.fromEntries(headers.filter(([k]) => !['cookie', 'host', 'accept-encoding'].includes(k)));
|
|
48
|
+
h.cookie = cookie();
|
|
49
|
+
h.host = 'localhost';
|
|
50
|
+
h['x-forwarded-proto'] = 'https';
|
|
51
|
+
const r = await beam.fetch(path, { method, headers: h, body });
|
|
52
|
+
keep(r);
|
|
53
|
+
const out = [...r.headers].filter(([k]) => k !== 'set-cookie');
|
|
54
|
+
const data = method === 'HEAD' ? null : await r.arrayBuffer();
|
|
55
|
+
port.postMessage({ status: r.status, statusText: r.statusText, headers: out,
|
|
56
|
+
location: r.headers.get('location'), body: data }, data ? [data] : []);
|
|
57
|
+
} catch (e) {
|
|
58
|
+
port.postMessage({ error: String(e?.message ?? e) });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function socketVm({ path, protocols }, port) {
|
|
63
|
+
let socket;
|
|
64
|
+
try {
|
|
65
|
+
const headers = { cookie: cookie(), host: 'localhost', origin: 'http://localhost', 'x-forwarded-proto': 'https' };
|
|
66
|
+
if (protocols.length) headers['sec-websocket-protocol'] = protocols.join(', ');
|
|
67
|
+
socket = await beam.socket(path.startsWith(`${base}/`) ? path.slice(base.length) : path, { headers });
|
|
68
|
+
} catch (e) {
|
|
69
|
+
port.postMessage({ close: true, code: 1006, error: String(e?.message ?? e) });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
socket.addEventListener('message', (e) => {
|
|
73
|
+
const data = e.data instanceof Uint8Array ? e.data.slice().buffer : e.data;
|
|
74
|
+
port.postMessage({ message: data });
|
|
75
|
+
});
|
|
76
|
+
socket.addEventListener('close', (e) => port.postMessage({ close: true, code: e.code, reason: e.reason }));
|
|
77
|
+
port.onmessage = (e) => {
|
|
78
|
+
if (e.data.close) socket.close(e.data.code, e.data.reason);
|
|
79
|
+
else socket.send(e.data.message);
|
|
80
|
+
};
|
|
81
|
+
port.postMessage({ open: true, protocol: protocols[0] ?? '' });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function handle(e) {
|
|
85
|
+
const m = e.data;
|
|
86
|
+
if (m.type === 'fetch') fetchVm(m.req, e.ports[0]);
|
|
87
|
+
else if (m.type === 'ws') socketVm(m, e.ports[0]);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// app: the URL of a native app.com, in place of release.bin of the site.
|
|
91
|
+
function boot({ base: path, env, app }) {
|
|
92
|
+
booting ??= (async () => {
|
|
93
|
+
base = path;
|
|
94
|
+
const { start } = await import('./browser.js');
|
|
95
|
+
beam = await start({ release: './release.bin', app, env: { ...env, BEAM_BASE_PATH: base } });
|
|
96
|
+
for (const p of pending.splice(0)) handle(p);
|
|
97
|
+
return { type: 'ready', restored: !!beam.vm.restored, name: beam.name };
|
|
98
|
+
})().catch((err) => {
|
|
99
|
+
// The next start boots again: a SharedWorker lives while a tab of the
|
|
100
|
+
// site is open, so an error at the first visit must not stay.
|
|
101
|
+
booting = null;
|
|
102
|
+
return { type: 'error', message: `The VM did not start: ${err?.message ?? err}` };
|
|
103
|
+
});
|
|
104
|
+
return booting;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// A message of a tab. reply: the answer to its start.
|
|
108
|
+
function receive(e, reply) {
|
|
109
|
+
if (e.data.type === 'start') boot(e.data).then(reply);
|
|
110
|
+
else if (beam) handle(e);
|
|
111
|
+
else pending.push(e);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// The lifetime of the SharedWorker: the ports of the open tabs. A tab says
|
|
115
|
+
// "bye" when it leaves (pagehide), and "hello" when it comes back from the
|
|
116
|
+
// back/forward cache. With no open tab, the VM stops after LINGER ms, so a
|
|
117
|
+
// reload keeps the VM and its data. There is no heartbeat: a browser slows
|
|
118
|
+
// the timers of a hidden tab, and a heartbeat would stop the VM of an open
|
|
119
|
+
// tab. A tab that crashes says no "bye": then the VM lives while the
|
|
120
|
+
// browser keeps the SharedWorker.
|
|
121
|
+
const LINGER = 30000;
|
|
122
|
+
const open = new Set();
|
|
123
|
+
let linger = null;
|
|
124
|
+
|
|
125
|
+
function present(port) {
|
|
126
|
+
open.add(port);
|
|
127
|
+
clearTimeout(linger);
|
|
128
|
+
linger = null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function gone(port) {
|
|
132
|
+
open.delete(port);
|
|
133
|
+
if (open.size === 0 && !linger) linger = setTimeout(() => self.close(), LINGER);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if ('onconnect' in self) {
|
|
137
|
+
self.addEventListener('connect', (c) => {
|
|
138
|
+
const port = c.ports[0];
|
|
139
|
+
present(port);
|
|
140
|
+
port.onmessage = (e) => {
|
|
141
|
+
if (e.data.type === 'bye') gone(port);
|
|
142
|
+
else if (e.data.type === 'hello') present(port);
|
|
143
|
+
else receive(e, (m) => port.postMessage(m));
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
} else {
|
|
147
|
+
self.addEventListener('message', (e) => receive(e, (m) => self.postMessage(m)));
|
|
148
|
+
}
|