@vnejs/desktop 0.0.36 → 0.0.37
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/server.js +172 -59
- package/src/web.js +6 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vnejs/desktop",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.37",
|
|
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/server.js
CHANGED
|
@@ -1,85 +1,198 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
-
const
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const fastify = require("fastify");
|
|
3
4
|
const getPort = require("get-port").default;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
5
|
+
const fastifyCors = require("@fastify/cors");
|
|
6
|
+
const fastifyStatic = require("@fastify/static");
|
|
7
|
+
const { readFile, readdir, writeFile, appendFile, mkdir, unlink, access } = require("fs/promises");
|
|
6
8
|
|
|
7
|
-
const
|
|
9
|
+
const getPrefix = (key) => {
|
|
10
|
+
const dot = key.indexOf(".");
|
|
11
|
+
|
|
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 runServer = async ({ rootDir, stateDir, oldApp, achievementLock, achievementUnlock } = {}) => {
|
|
8
66
|
const port = await getPort();
|
|
9
67
|
|
|
10
|
-
const
|
|
68
|
+
const resolvedStateDir = stateDir || path.join(rootDir, "state");
|
|
11
69
|
const gamePath = path.join(rootDir, "game");
|
|
12
70
|
|
|
13
|
-
const
|
|
71
|
+
const ensureStateDir = () => mkdir(resolvedStateDir, { recursive: true });
|
|
14
72
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
);
|
|
73
|
+
const getStateFiles = async () => {
|
|
74
|
+
try {
|
|
75
|
+
const files = await readdir(resolvedStateDir);
|
|
19
76
|
|
|
20
|
-
|
|
77
|
+
return files.filter((file) => file.endsWith(".json"));
|
|
78
|
+
} catch {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const onSaveState = async (req, reply) => {
|
|
84
|
+
try {
|
|
85
|
+
await ensureStateDir();
|
|
86
|
+
|
|
87
|
+
const groups = splitStateByPrefix(req.body);
|
|
88
|
+
const activePrefixes = new Set(Object.keys(groups));
|
|
89
|
+
const stalePrefixes = (await getStateFiles()).map((file) => file.slice(0, -".json".length)).filter((prefix) => !activePrefixes.has(prefix));
|
|
90
|
+
|
|
91
|
+
await Promise.all([
|
|
92
|
+
...Object.entries(groups).map(([prefix, state]) => writeFile(path.join(resolvedStateDir, `${prefix}.json`), JSON.stringify(state))),
|
|
93
|
+
...stalePrefixes.map((prefix) => unlink(path.join(resolvedStateDir, `${prefix}.json`))),
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
return reply.send({ status: "ok" });
|
|
97
|
+
} catch {
|
|
98
|
+
return reply.send({ status: "error" });
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const onGetState = async (req, reply) => {
|
|
103
|
+
const stateFiles = await getStateFiles();
|
|
104
|
+
|
|
105
|
+
if (!stateFiles.length) return reply.send({ status: "ok", state: {} });
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const chunks = await Promise.all(stateFiles.map((file) => readFile(path.join(resolvedStateDir, file), "utf8").then(JSON.parse)));
|
|
109
|
+
|
|
110
|
+
return reply.send({ status: "ok", state: Object.assign({}, ...chunks) });
|
|
111
|
+
} catch {
|
|
112
|
+
return reply.send({ status: "error" });
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const onAchievementLock = (req, reply) => {
|
|
21
117
|
typeof achievementLock === "function" && achievementLock(req.body?.id, req.body?.data);
|
|
22
118
|
|
|
23
|
-
return
|
|
119
|
+
return reply.send({ status: "ok" });
|
|
24
120
|
};
|
|
25
|
-
|
|
121
|
+
|
|
122
|
+
const onAchievementUnlock = (req, reply) => {
|
|
26
123
|
typeof achievementUnlock === "function" && achievementUnlock(req.body?.id, req.body?.data);
|
|
27
124
|
|
|
28
|
-
return
|
|
125
|
+
return reply.send({ status: "ok" });
|
|
29
126
|
};
|
|
30
127
|
|
|
31
|
-
const onLog = (req,
|
|
32
|
-
|
|
128
|
+
const onLog = (req, reply) => {
|
|
129
|
+
reply.send("ok");
|
|
33
130
|
|
|
34
131
|
appendFile(path.join(rootDir, "log.txt"), `${req.body.log}\n`);
|
|
35
132
|
};
|
|
36
133
|
|
|
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);
|
|
134
|
+
const createApp = async () => {
|
|
135
|
+
const app = fastify({ bodyLimit: 50 * 1024 * 1024 });
|
|
136
|
+
|
|
137
|
+
await app.register(fastifyCors, {
|
|
138
|
+
origin: "*",
|
|
139
|
+
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
140
|
+
allowedHeaders: ["Content-Type", "Authorization"],
|
|
72
141
|
});
|
|
73
|
-
server.on("error", async (error) => {
|
|
74
|
-
if (error.code === "EADDRINUSE") {
|
|
75
|
-
console.log("server not started on port: ", port);
|
|
76
142
|
|
|
77
|
-
|
|
143
|
+
app.post("/state/save", onSaveState);
|
|
144
|
+
app.get("/state/get", onGetState);
|
|
145
|
+
app.post("/achievement/lock", onAchievementLock);
|
|
146
|
+
app.post("/achievement/unlock", onAchievementUnlock);
|
|
147
|
+
app.post("/log", onLog);
|
|
78
148
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
149
|
+
const publicDirs = [path.join(rootDir, "dist")];
|
|
150
|
+
const mediaDirs = [];
|
|
151
|
+
|
|
152
|
+
let modDirs = [];
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
modDirs = await readdir(gamePath);
|
|
156
|
+
} catch {}
|
|
157
|
+
|
|
158
|
+
for (const modDir of modDirs) {
|
|
159
|
+
const mediaDir = path.join(gamePath, modDir, "media");
|
|
160
|
+
const publicDir = path.join(gamePath, modDir, "public");
|
|
161
|
+
|
|
162
|
+
if (await pathExists(mediaDir)) mediaDirs.push(mediaDir);
|
|
163
|
+
if (await pathExists(publicDir)) publicDirs.push(publicDir);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await app.register(fastifyStatic, { root: publicDirs });
|
|
167
|
+
|
|
168
|
+
if (mediaDirs.length) {
|
|
169
|
+
await app.register(fastifyStatic, {
|
|
170
|
+
root: mediaDirs,
|
|
171
|
+
prefix: "/media/",
|
|
172
|
+
decorateReply: false,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return app;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const app = oldApp || (await createApp());
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
await app.listen({ port, host: "0.0.0.0" });
|
|
183
|
+
|
|
184
|
+
logServerAddresses(port);
|
|
185
|
+
|
|
186
|
+
return port;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (error.code === "EADDRINUSE") {
|
|
189
|
+
console.log("server not started on port: ", port);
|
|
190
|
+
|
|
191
|
+
return runServer({ rootDir, stateDir, oldApp: app, achievementLock, achievementUnlock });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
83
196
|
};
|
|
84
197
|
|
|
85
198
|
module.exports = { runServer };
|
package/src/web.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
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
|
-
module.exports =
|
|
6
|
+
module.exports = async () => {
|
|
7
|
+
const port = await runServer({ rootDir: getRootDir() });
|
|
8
|
+
|
|
9
|
+
return open(`http://localhost:${port}`);
|
|
10
|
+
};
|