@vnejs/desktop 0.0.36 → 0.0.38
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 +9 -7
- package/src/cli.js +4 -1
- package/src/index.js +2 -1
- package/src/server.js +177 -63
- package/src/web.js +8 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vnejs/desktop",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.38",
|
|
4
4
|
"description": "Tools to run desktop app of @vnejs",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -15,13 +15,15 @@
|
|
|
15
15
|
"author": "",
|
|
16
16
|
"license": "ISC",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
18
|
+
"@fastify/cors": "~11.2.0",
|
|
19
|
+
"@fastify/static": "~9.1.3",
|
|
20
|
+
"commander": "~15.0.0",
|
|
21
|
+
"fastify": "~5.8.5",
|
|
22
|
+
"get-port": "~7.2.0",
|
|
23
|
+
"open": "~11.0.0"
|
|
22
24
|
},
|
|
23
25
|
"devDependencies": {
|
|
24
|
-
"@electron/packager": "
|
|
25
|
-
"electron": "~
|
|
26
|
+
"@electron/packager": "~20.0.1",
|
|
27
|
+
"electron": "~42.3.3"
|
|
26
28
|
}
|
|
27
29
|
}
|
package/src/cli.js
CHANGED
|
@@ -10,6 +10,9 @@ const program = new Command();
|
|
|
10
10
|
|
|
11
11
|
program.name(packageJson.name).description(packageJson.description).version(packageJson.version);
|
|
12
12
|
|
|
13
|
-
program
|
|
13
|
+
program
|
|
14
|
+
.command("web")
|
|
15
|
+
.option("--port-preferred <port>", "preferred port number (overrides package.json vnejs.portPreferred)")
|
|
16
|
+
.action(webAction);
|
|
14
17
|
|
|
15
18
|
program.parse();
|
package/src/index.js
CHANGED
package/src/server.js
CHANGED
|
@@ -1,85 +1,199 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const fastify = require("fastify");
|
|
4
|
+
const fastifyCors = require("@fastify/cors");
|
|
5
|
+
const fastifyStatic = require("@fastify/static");
|
|
6
|
+
const { readFile, readdir, writeFile, appendFile, mkdir, unlink, access } = require("fs/promises");
|
|
7
|
+
const { default: getPort } = require("get-port");
|
|
6
8
|
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
+
const getPrefix = (key) => {
|
|
10
|
+
const dot = key.indexOf(".");
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
return dot === -1 ? key : key.slice(0, dot);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const splitStateByPrefix = (state) =>
|
|
16
|
+
Object.entries(state).reduce((groups, [key, value]) => {
|
|
17
|
+
const prefix = getPrefix(key);
|
|
18
|
+
|
|
19
|
+
groups[prefix] ??= {};
|
|
20
|
+
groups[prefix][key] = value;
|
|
21
|
+
|
|
22
|
+
return groups;
|
|
23
|
+
}, {});
|
|
24
|
+
|
|
25
|
+
const isIPv4 = (family) => family === "IPv4" || family === 4;
|
|
26
|
+
|
|
27
|
+
const getServerUrls = (port) => {
|
|
28
|
+
const local = [`http://localhost:${port}/`, `http://127.0.0.1:${port}/`];
|
|
29
|
+
const network = new Set();
|
|
30
|
+
|
|
31
|
+
for (const interfaces of Object.values(os.networkInterfaces())) {
|
|
32
|
+
for (const iface of interfaces) {
|
|
33
|
+
if (isIPv4(iface.family) && !iface.internal) network.add(`http://${iface.address}:${port}/`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { local, network: [...network] };
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const logServerAddresses = (port) => {
|
|
41
|
+
const { local, network } = getServerUrls(port);
|
|
42
|
+
|
|
43
|
+
console.log("server started:");
|
|
44
|
+
console.log(" locally:");
|
|
45
|
+
local.forEach((url) => console.log(` ${url}`));
|
|
46
|
+
|
|
47
|
+
if (network.length) {
|
|
48
|
+
console.log(" on local network (wifi):");
|
|
49
|
+
network.forEach((url) => console.log(` ${url}`));
|
|
50
|
+
} else {
|
|
51
|
+
console.log(" on local network (wifi): not available");
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const pathExists = async (target) => {
|
|
56
|
+
try {
|
|
57
|
+
await access(target);
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const readPortPreferred = async (rootDir) => {
|
|
66
|
+
const pkg = JSON.parse(await readFile(path.join(rootDir, "package.json"), "utf8"));
|
|
67
|
+
const port = Number(pkg.vnejs?.portPreferred);
|
|
68
|
+
|
|
69
|
+
return Number.isInteger(port) && port > 0 ? port : undefined;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const runServer = async ({ rootDir, stateDir, oldApp, onAchievementLock, onAchievementUnlock } = {}) => {
|
|
73
|
+
const port = await getPort({ port: await readPortPreferred(rootDir) });
|
|
74
|
+
|
|
75
|
+
const resolvedStateDir = stateDir || path.join(rootDir, "state");
|
|
11
76
|
const gamePath = path.join(rootDir, "game");
|
|
12
77
|
|
|
13
|
-
const
|
|
78
|
+
const ensureStateDir = () => mkdir(resolvedStateDir, { recursive: true });
|
|
79
|
+
|
|
80
|
+
const getStateFiles = async () => {
|
|
81
|
+
try {
|
|
82
|
+
const files = await readdir(resolvedStateDir);
|
|
83
|
+
|
|
84
|
+
return files.filter((file) => file.endsWith(".json"));
|
|
85
|
+
} catch {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const onSaveState = async (req, reply) => {
|
|
91
|
+
try {
|
|
92
|
+
await ensureStateDir();
|
|
14
93
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
);
|
|
94
|
+
const groups = splitStateByPrefix(req.body);
|
|
95
|
+
const activePrefixes = new Set(Object.keys(groups));
|
|
96
|
+
const stalePrefixes = (await getStateFiles()).map((file) => file.slice(0, -".json".length)).filter((prefix) => !activePrefixes.has(prefix));
|
|
19
97
|
|
|
20
|
-
|
|
21
|
-
|
|
98
|
+
await Promise.all([
|
|
99
|
+
...Object.entries(groups).map(([prefix, state]) => writeFile(path.join(resolvedStateDir, `${prefix}.json`), JSON.stringify(state))),
|
|
100
|
+
...stalePrefixes.map((prefix) => unlink(path.join(resolvedStateDir, `${prefix}.json`))),
|
|
101
|
+
]);
|
|
22
102
|
|
|
23
|
-
|
|
103
|
+
return reply.send({ status: "ok" });
|
|
104
|
+
} catch {
|
|
105
|
+
return reply.send({ status: "error" });
|
|
106
|
+
}
|
|
24
107
|
};
|
|
25
|
-
const onAchievementUnlock = (req, res) => {
|
|
26
|
-
typeof achievementUnlock === "function" && achievementUnlock(req.body?.id, req.body?.data);
|
|
27
108
|
|
|
28
|
-
|
|
109
|
+
const onGetState = async (req, reply) => {
|
|
110
|
+
const stateFiles = await getStateFiles();
|
|
111
|
+
|
|
112
|
+
if (!stateFiles.length) return reply.send({ status: "ok", state: {} });
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const chunks = await Promise.all(stateFiles.map((file) => readFile(path.join(resolvedStateDir, file), "utf8").then(JSON.parse)));
|
|
116
|
+
|
|
117
|
+
return reply.send({ status: "ok", state: Object.assign({}, ...chunks) });
|
|
118
|
+
} catch {
|
|
119
|
+
return reply.send({ status: "error" });
|
|
120
|
+
}
|
|
29
121
|
};
|
|
30
122
|
|
|
31
|
-
const
|
|
32
|
-
|
|
123
|
+
const onAchievementLockMiddleware = (req, reply) => {
|
|
124
|
+
typeof onAchievementLock === "function" && onAchievementLock(req.body?.id, req.body?.data);
|
|
125
|
+
|
|
126
|
+
return reply.send({ status: "ok" });
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const onAchievementUnlockMiddleware = (req, reply) => {
|
|
130
|
+
typeof onAchievementUnlock === "function" && onAchievementUnlock(req.body?.id, req.body?.data);
|
|
131
|
+
|
|
132
|
+
return reply.send({ status: "ok" });
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const onLog = (req, reply) => {
|
|
136
|
+
reply.send("ok");
|
|
33
137
|
|
|
34
138
|
appendFile(path.join(rootDir, "log.txt"), `${req.body.log}\n`);
|
|
35
139
|
};
|
|
36
140
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
res.header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS");
|
|
45
|
-
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
46
|
-
|
|
47
|
-
next();
|
|
48
|
-
})
|
|
49
|
-
.use(bodyParser.json({ limit: "50mb" }))
|
|
50
|
-
.use(express.static(path.join(rootDir, "dist")))
|
|
51
|
-
.use("/state/save", onSaveState)
|
|
52
|
-
.use("/state/get", onGetState)
|
|
53
|
-
.use("/achievement/lock", onAchievementLock)
|
|
54
|
-
.use("/achievement/unlock", onAchievementUnlock)
|
|
55
|
-
.use("/log", onLog);
|
|
56
|
-
|
|
57
|
-
readdirSync(gamePath).forEach((modDir) => {
|
|
58
|
-
const mediaDir = path.join(gamePath, modDir, "media");
|
|
59
|
-
const publicDir = path.join(gamePath, modDir, "public");
|
|
60
|
-
|
|
61
|
-
existsSync(mediaDir) && app.use("/media", express.static(mediaDir));
|
|
62
|
-
existsSync(publicDir) && app.use(express.static(publicDir));
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
return new Promise(async (resolve) => {
|
|
66
|
-
const server = app.listen(port);
|
|
67
|
-
|
|
68
|
-
server.on("listening", () => {
|
|
69
|
-
console.log("server started on port: ", port);
|
|
70
|
-
|
|
71
|
-
resolve(port);
|
|
141
|
+
const createApp = async () => {
|
|
142
|
+
const app = fastify({ bodyLimit: 50 * 1024 * 1024 });
|
|
143
|
+
|
|
144
|
+
await app.register(fastifyCors, {
|
|
145
|
+
origin: "*",
|
|
146
|
+
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
147
|
+
allowedHeaders: ["Content-Type", "Authorization"],
|
|
72
148
|
});
|
|
73
|
-
server.on("error", async (error) => {
|
|
74
|
-
if (error.code === "EADDRINUSE") {
|
|
75
|
-
console.log("server not started on port: ", port);
|
|
76
149
|
|
|
77
|
-
|
|
150
|
+
app.post("/state/save", onSaveState);
|
|
151
|
+
app.get("/state/get", onGetState);
|
|
152
|
+
app.post("/achievement/lock", onAchievementLockMiddleware);
|
|
153
|
+
app.post("/achievement/unlock", onAchievementUnlockMiddleware);
|
|
154
|
+
app.post("/log", onLog);
|
|
78
155
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
156
|
+
const publicDirs = [path.join(rootDir, "dist")];
|
|
157
|
+
const mediaDirs = [];
|
|
158
|
+
|
|
159
|
+
let modDirs = [];
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
modDirs = await readdir(gamePath);
|
|
163
|
+
} catch {}
|
|
164
|
+
|
|
165
|
+
for (const modDir of modDirs) {
|
|
166
|
+
const mediaDir = path.join(gamePath, modDir, "media");
|
|
167
|
+
const publicDir = path.join(gamePath, modDir, "public");
|
|
168
|
+
|
|
169
|
+
if (await pathExists(mediaDir)) mediaDirs.push(mediaDir);
|
|
170
|
+
if (await pathExists(publicDir)) publicDirs.push(publicDir);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
await app.register(fastifyStatic, { root: publicDirs });
|
|
174
|
+
|
|
175
|
+
if (mediaDirs.length) await app.register(fastifyStatic, { root: mediaDirs, prefix: "/media/", decorateReply: false });
|
|
176
|
+
|
|
177
|
+
return app;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const app = oldApp || (await createApp());
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
await app.listen({ port, host: "0.0.0.0" });
|
|
184
|
+
|
|
185
|
+
logServerAddresses(port);
|
|
186
|
+
|
|
187
|
+
return port;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (error.code === "EADDRINUSE") {
|
|
190
|
+
console.log("server not started on port: ", port);
|
|
191
|
+
|
|
192
|
+
return runServer({ rootDir, stateDir, oldApp: app, onAchievementLock, onAchievementUnlock });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
83
197
|
};
|
|
84
198
|
|
|
85
199
|
module.exports = { runServer };
|
package/src/web.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
const open = require("open");
|
|
1
|
+
const open = require("open").default;
|
|
2
2
|
const { runServer } = require("./server");
|
|
3
3
|
|
|
4
4
|
const getRootDir = () => process.env.PWD || process.env.INIT_CWD;
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const runWeb = async ({ rootDir = getRootDir() } = {}) => {
|
|
7
|
+
const port = await runServer({ rootDir });
|
|
8
|
+
|
|
9
|
+
return open(`http://localhost:${port}/?port=${port}`);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
module.exports = runWeb;
|