arcadible 1.0.8 → 1.0.10
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/package.json +1 -1
- package/src/account.js +1 -1
- package/src/action/serve/tool.js +14 -0
- package/src/serve/favicon.ico +0 -0
- package/src/serve/page.js +56 -3
- package/src/serve/server.js +12 -2
- package/src/util/os.js +4 -1
package/package.json
CHANGED
package/src/account.js
CHANGED
|
@@ -64,7 +64,7 @@ const expired = function (session) {
|
|
|
64
64
|
// the session is a credential, so only its owner can read it: the directory
|
|
65
65
|
// is 0700 and the file 0600. `mode` only applies when a file is created, so
|
|
66
66
|
// both are also set on every save, which fixes an older, readable file. The
|
|
67
|
-
// modes are ignored on Windows, where %
|
|
67
|
+
// modes are ignored on Windows, where %LOCALAPPDATA% is already private.
|
|
68
68
|
const save = function (current) {
|
|
69
69
|
return mkdir(dir, { mode: DIR_MODE, recursive: true })
|
|
70
70
|
.then(() => chmod(dir, DIR_MODE))
|
package/src/action/serve/tool.js
CHANGED
|
@@ -22,6 +22,7 @@ const commands = require("../../util/command");
|
|
|
22
22
|
const error = require("../../util/error");
|
|
23
23
|
const files = require("../../serve/files");
|
|
24
24
|
const given = require("../../parser/given");
|
|
25
|
+
const media = require("../../parser/media");
|
|
25
26
|
const os = require("../../util/os");
|
|
26
27
|
const phrase = require("../../phrase");
|
|
27
28
|
const reload = require("../../serve/reload");
|
|
@@ -36,6 +37,7 @@ const configDir = os.configDir;
|
|
|
36
37
|
const dirFiles = files.dirFiles;
|
|
37
38
|
const join = path.join;
|
|
38
39
|
const pathExists = fs.pathExists;
|
|
40
|
+
const readFile = fs.promises.readFile;
|
|
39
41
|
const relative = path.relative;
|
|
40
42
|
const remoteFiles = remote.remoteFiles;
|
|
41
43
|
const resolvePath = path.resolve;
|
|
@@ -92,6 +94,17 @@ const probe = async function (url) {
|
|
|
92
94
|
|
|
93
95
|
// [function]
|
|
94
96
|
|
|
97
|
+
// the project's store icon, .arcadible/icon.png, or null: found by its exact
|
|
98
|
+
// name, as `arc deploy` finds it, each time it's asked for.
|
|
99
|
+
const storeIcon = function (root) {
|
|
100
|
+
return async () => {
|
|
101
|
+
const { game } = await media(root);
|
|
102
|
+
return game.icon ? readFile(join(root, game.icon)) : null;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// [function]
|
|
107
|
+
|
|
95
108
|
// listens, turning a port in use into its code.
|
|
96
109
|
const listen = async function (options) {
|
|
97
110
|
try {
|
|
@@ -125,6 +138,7 @@ const main = async function (conf) {
|
|
|
125
138
|
const project = await given(conf);
|
|
126
139
|
const game = project.game;
|
|
127
140
|
const options = {
|
|
141
|
+
icon: storeIcon(root),
|
|
128
142
|
info: {
|
|
129
143
|
channel: CHANNEL,
|
|
130
144
|
id: game.id ?? UNNAMED,
|
|
Binary file
|
package/src/serve/page.js
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
// (site/src/javascripts/arcadible/play/app/page.coffee), with the same
|
|
4
4
|
// sandbox and permissions, and runs the play page's own host for the SDK,
|
|
5
5
|
// so saves are kept by the page, across builds. It reloads the
|
|
6
|
-
// game, and only the game, when told to.
|
|
6
|
+
// game, and only the game, when told to. Its tab shows the game's store
|
|
7
|
+
// icon, as the play page's does, or Arcadible's.
|
|
8
|
+
|
|
9
|
+
// [import]
|
|
10
|
+
|
|
11
|
+
const fs = require("node:fs");
|
|
12
|
+
const path = require("node:path");
|
|
7
13
|
|
|
8
14
|
// [import]
|
|
9
15
|
|
|
@@ -12,6 +18,8 @@ const serveGame = require("./game");
|
|
|
12
18
|
// [use]
|
|
13
19
|
|
|
14
20
|
const endWith = serveGame.endWith;
|
|
21
|
+
const join = path.join;
|
|
22
|
+
const readFile = fs.promises.readFile;
|
|
15
23
|
|
|
16
24
|
// [declare]
|
|
17
25
|
|
|
@@ -23,6 +31,10 @@ const SANDBOX =
|
|
|
23
31
|
// the page's own paths. Its origin isn't the game's, so they can't collide.
|
|
24
32
|
const HOST = "/_arcadible/host.js";
|
|
25
33
|
const RELOAD = "/_arcadible/reload";
|
|
34
|
+
const ICON = "/_arcadible/icon";
|
|
35
|
+
// the play page's own favicon.ico (app/src/web/arcadible-play/content), for a
|
|
36
|
+
// game without a store icon, and for /favicon.ico, which browsers ask for.
|
|
37
|
+
const FAVICON = join(__dirname, "favicon.ico");
|
|
26
38
|
|
|
27
39
|
// [declare]
|
|
28
40
|
|
|
@@ -44,6 +56,24 @@ const scriptJson = function (value) {
|
|
|
44
56
|
|
|
45
57
|
// [function]
|
|
46
58
|
|
|
59
|
+
// the play page's favicon.ico, read once.
|
|
60
|
+
let favicon = null;
|
|
61
|
+
const readFavicon = function () {
|
|
62
|
+
favicon ??= readFile(FAVICON);
|
|
63
|
+
return favicon;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// [function]
|
|
67
|
+
|
|
68
|
+
// `png`, the game's store icon, with the corners the play page's icons have
|
|
69
|
+
// (h.border_radius in app/lib/python/offst/util.py: 22% of the size).
|
|
70
|
+
const roundedIcon = function (png) {
|
|
71
|
+
const href = `data:image/png;base64,${png.toString("base64")}`;
|
|
72
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><clipPath id="c"><rect width="100" height="100" rx="22"/></clipPath><image href="${href}" width="100" height="100" preserveAspectRatio="xMidYMid slice" clip-path="url(#c)"/></svg>`;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// [function]
|
|
76
|
+
|
|
47
77
|
const escapeHtml = function (text) {
|
|
48
78
|
return text.replace(
|
|
49
79
|
/[&<>"]/gu,
|
|
@@ -70,6 +100,7 @@ const html = function (game) {
|
|
|
70
100
|
<meta charset="utf-8">
|
|
71
101
|
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
|
|
72
102
|
<title>${escapeHtml(game.title)}</title>
|
|
103
|
+
<link href="${ICON}" rel="icon">
|
|
73
104
|
<style>html,body{margin:0;height:100%;overflow:hidden;background:#000}iframe{display:block;width:100%;height:100%;border:0;visibility:hidden}</style>
|
|
74
105
|
</head>
|
|
75
106
|
<body>
|
|
@@ -117,9 +148,11 @@ const html = function (game) {
|
|
|
117
148
|
// [function]
|
|
118
149
|
|
|
119
150
|
// the request handler for the page's origin. `reloads` keeps the pages
|
|
120
|
-
// listening for a reload; `remote` has the host
|
|
151
|
+
// listening for a reload; `remote` has the host; `icon()` resolves with the
|
|
152
|
+
// game's store icon, or null.
|
|
121
153
|
const pageHandler = function ({
|
|
122
154
|
game,
|
|
155
|
+
icon = () => Promise.resolve(null),
|
|
123
156
|
onRemote = () => null,
|
|
124
157
|
reloads,
|
|
125
158
|
remote,
|
|
@@ -138,6 +171,26 @@ const pageHandler = function ({
|
|
|
138
171
|
endWith(req, res, html(game));
|
|
139
172
|
return Promise.resolve();
|
|
140
173
|
}
|
|
174
|
+
if (pathname === ICON) {
|
|
175
|
+
// read for each request, so an icon added while serving shows on the
|
|
176
|
+
// next load.
|
|
177
|
+
return icon()
|
|
178
|
+
.catch(() => null)
|
|
179
|
+
.then(async (png) => {
|
|
180
|
+
const body = png ? roundedIcon(png) : await readFavicon();
|
|
181
|
+
res.writeHead(200, {
|
|
182
|
+
...HEADERS,
|
|
183
|
+
"content-type": png ? "image/svg+xml" : "image/x-icon",
|
|
184
|
+
});
|
|
185
|
+
endWith(req, res, body);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (pathname === "/favicon.ico") {
|
|
189
|
+
return readFavicon().then((body) => {
|
|
190
|
+
res.writeHead(200, { ...HEADERS, "content-type": "image/x-icon" });
|
|
191
|
+
endWith(req, res, body);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
141
194
|
if (pathname === RELOAD) {
|
|
142
195
|
reloads.listen(res);
|
|
143
196
|
return Promise.resolve();
|
|
@@ -173,4 +226,4 @@ const pageHandler = function ({
|
|
|
173
226
|
|
|
174
227
|
// [export]
|
|
175
228
|
|
|
176
|
-
module.exports = { ALLOW, SANDBOX, html, pageHandler };
|
|
229
|
+
module.exports = { ALLOW, FAVICON, SANDBOX, html, pageHandler };
|
package/src/serve/server.js
CHANGED
|
@@ -45,11 +45,20 @@ const listen = function (handler, port, address) {
|
|
|
45
45
|
// [function]
|
|
46
46
|
|
|
47
47
|
// starts serving `files` for `info`, `{ id, channel, version, title }`, on
|
|
48
|
-
// `port`, with Arcadible's own files from `remote
|
|
48
|
+
// `port`, with Arcadible's own files from `remote`, and the tab's icon from
|
|
49
|
+
// `icon()`, which resolves with a PNG or null. With `src`, another
|
|
49
50
|
// server's page, it frames that instead, and serves no files. Resolves with
|
|
50
51
|
// `{ page, game, reload(), close() }`: the page's origin, the game's URL,
|
|
51
52
|
// and the controls.
|
|
52
|
-
const serve = async function ({
|
|
53
|
+
const serve = async function ({
|
|
54
|
+
files,
|
|
55
|
+
icon,
|
|
56
|
+
info,
|
|
57
|
+
onRemote,
|
|
58
|
+
port,
|
|
59
|
+
remote,
|
|
60
|
+
src,
|
|
61
|
+
}) {
|
|
53
62
|
const origins = {
|
|
54
63
|
game: `http://127.0.0.1:${port}`,
|
|
55
64
|
page: `http://localhost:${port}`,
|
|
@@ -61,6 +70,7 @@ const serve = async function ({ files, info, onRemote, port, remote, src }) {
|
|
|
61
70
|
: null;
|
|
62
71
|
const forPage = pageHandler({
|
|
63
72
|
game: { ...info, src: framed },
|
|
73
|
+
icon,
|
|
64
74
|
onRemote,
|
|
65
75
|
reloads,
|
|
66
76
|
remote,
|
package/src/util/os.js
CHANGED
|
@@ -12,9 +12,12 @@ const join = path.join;
|
|
|
12
12
|
|
|
13
13
|
// [function]
|
|
14
14
|
|
|
15
|
+
// on Windows, the machine's own folder (%LOCALAPPDATA%), not the roaming one:
|
|
16
|
+
// the session is a credential, and the SDK's copy a cache, neither of which
|
|
17
|
+
// should follow a roaming profile to other machines
|
|
15
18
|
const configDir = function () {
|
|
16
19
|
if (process.platform === "win32") {
|
|
17
|
-
return process.env.
|
|
20
|
+
return process.env.LOCALAPPDATA || join(homedir(), "AppData", "Local");
|
|
18
21
|
}
|
|
19
22
|
return join(homedir(), ".config");
|
|
20
23
|
};
|