arcadible 0.1.47 → 0.1.49
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 +1 -0
- package/bin/arc-default.js +1 -0
- package/bin/arc-init.js +1 -1
- package/bin/arc-serve.js +213 -0
- package/package.json +5 -1
- package/src/action/serve/tool.js +166 -0
- package/src/cli/index.js +2 -0
- package/src/cli/serve.js +15 -0
- package/src/config/code.js +1 -0
- package/src/config/template.js +7 -7
- package/src/config/uri.js +5 -0
- package/src/config/url.js +2 -0
- package/src/phrase.js +110 -1
- package/src/serve/files.js +61 -0
- package/src/serve/game.js +185 -0
- package/src/serve/page.js +176 -0
- package/src/serve/reload.js +63 -0
- package/src/serve/remote.js +119 -0
- package/src/serve/server.js +115 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// arc serve's server: a local play page and the game it frames, on one port
|
|
3
|
+
// and two origins, as on Arcadible, where the play page and each build have
|
|
4
|
+
// origins of their own. http://localhost:<port> is the page, and
|
|
5
|
+
// http://127.0.0.1:<port> the game. It listens on loopback only, and needs
|
|
6
|
+
// nothing from the CLI, so another app can run a game this way too.
|
|
7
|
+
|
|
8
|
+
// [import]
|
|
9
|
+
|
|
10
|
+
const http = require("node:http");
|
|
11
|
+
|
|
12
|
+
// [import]
|
|
13
|
+
|
|
14
|
+
const game = require("./game");
|
|
15
|
+
const page = require("./page");
|
|
16
|
+
const reload = require("./reload");
|
|
17
|
+
|
|
18
|
+
// [use]
|
|
19
|
+
|
|
20
|
+
const gameHandler = game.gameHandler;
|
|
21
|
+
const pageHandler = page.pageHandler;
|
|
22
|
+
const reloader = reload.reloader;
|
|
23
|
+
|
|
24
|
+
// [declare]
|
|
25
|
+
|
|
26
|
+
// the hostnames a browser may use for the page, on IPv4 and IPv6. `localhost`
|
|
27
|
+
// is IPv6's ::1 on some systems, so both are listened on.
|
|
28
|
+
const LOOPBACK = ["127.0.0.1", "::1"];
|
|
29
|
+
|
|
30
|
+
// [function]
|
|
31
|
+
|
|
32
|
+
// listens on `port` at `address`, resolving with the server, or rejecting with
|
|
33
|
+
// the listen error, such as EADDRINUSE.
|
|
34
|
+
const listen = function (handler, port, address) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const server = http.createServer(handler);
|
|
37
|
+
server.once("error", reject);
|
|
38
|
+
server.listen(port, address, () => {
|
|
39
|
+
server.off("error", reject);
|
|
40
|
+
resolve(server);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// [function]
|
|
46
|
+
|
|
47
|
+
// starts serving `files` for `info`, `{ id, channel, version, title }`, on
|
|
48
|
+
// `port`, with Arcadible's own files from `remote`. With `src`, another
|
|
49
|
+
// server's page, it frames that instead, and serves no files. Resolves with
|
|
50
|
+
// `{ page, game, reload(), close() }`: the page's origin, the game's URL,
|
|
51
|
+
// and the controls.
|
|
52
|
+
const serve = async function ({ files, info, onRemote, port, remote, src }) {
|
|
53
|
+
const origins = {
|
|
54
|
+
game: `http://127.0.0.1:${port}`,
|
|
55
|
+
page: `http://localhost:${port}`,
|
|
56
|
+
};
|
|
57
|
+
const framed = src ?? `${origins.game}/`;
|
|
58
|
+
const reloads = reloader();
|
|
59
|
+
const forGame = files
|
|
60
|
+
? gameHandler({ files, onRemote, page: origins.page, remote })
|
|
61
|
+
: null;
|
|
62
|
+
const forPage = pageHandler({
|
|
63
|
+
game: { ...info, src: framed },
|
|
64
|
+
onRemote,
|
|
65
|
+
reloads,
|
|
66
|
+
remote,
|
|
67
|
+
});
|
|
68
|
+
// the Host header picks the origin; anything else is refused, so a page
|
|
69
|
+
// elsewhere can't reach the game's files by another name (DNS rebinding).
|
|
70
|
+
const handler = function (req, res) {
|
|
71
|
+
const host = req.headers.host;
|
|
72
|
+
const pick = {
|
|
73
|
+
[`127.0.0.1:${port}`]: forGame,
|
|
74
|
+
[`localhost:${port}`]: forPage,
|
|
75
|
+
}[host];
|
|
76
|
+
if (!pick) {
|
|
77
|
+
res.writeHead(421, { "content-type": "text/plain; charset=utf-8" });
|
|
78
|
+
res.end("Misdirected Request\n");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
pick(req, res).catch(() => {
|
|
82
|
+
if (!res.headersSent) {
|
|
83
|
+
res.writeHead(500);
|
|
84
|
+
}
|
|
85
|
+
res.end();
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
const ipv4 = await listen(handler, port, LOOPBACK[0]);
|
|
89
|
+
// the IPv6 loopback is a bonus: a system without it still has 127.0.0.1.
|
|
90
|
+
let ipv6 = null;
|
|
91
|
+
try {
|
|
92
|
+
ipv6 = await listen(handler, port, LOOPBACK[1]);
|
|
93
|
+
} catch (ex) {
|
|
94
|
+
if (ex.code === "EADDRINUSE") {
|
|
95
|
+
ipv4.close();
|
|
96
|
+
throw ex;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
close() {
|
|
101
|
+
reloads.close();
|
|
102
|
+
for (const server of [ipv4, ipv6].filter(Boolean)) {
|
|
103
|
+
server.closeAllConnections();
|
|
104
|
+
server.close();
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
game: framed,
|
|
108
|
+
page: origins.page,
|
|
109
|
+
reload: reloads.reload,
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// [export]
|
|
114
|
+
|
|
115
|
+
module.exports = { serve };
|