node-karin 1.15.5 → 1.16.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/CHANGELOG.md +31 -0
- package/dist/adapter-BqlH3u3X.mjs +218 -0
- package/dist/app-DdMQbBEY.mjs +4109 -0
- package/dist/cache-CPcPeo6N.mjs +163 -0
- package/dist/chunk-NzVPYdc1.mjs +21 -0
- package/dist/cli/index.cjs +10900 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.mjs +10770 -10224
- package/dist/file-ZGuqNDd-.mjs +15987 -0
- package/dist/file-dGy9of8-.mjs +268 -0
- package/dist/fsSync-Cf5MWILk.mjs +65 -0
- package/dist/index.d.ts +12235 -12738
- package/dist/index.mjs +2054 -25247
- package/dist/internal-DupfycKE.mjs +597 -0
- package/dist/kv-DZp4UIxg.mjs +192 -0
- package/dist/module/art-template.d.ts +2 -13
- package/dist/module/art-template.mjs +3 -1
- package/dist/module/axios.d.ts +3 -2
- package/dist/module/axios.mjs +5 -2
- package/dist/module/chalk.d.ts +3 -2
- package/dist/module/chalk.mjs +5 -2
- package/dist/module/chokidar.d.ts +3 -2
- package/dist/module/chokidar.mjs +5 -2
- package/dist/module/express.d.ts +2 -1
- package/dist/module/express.mjs +3 -1
- package/dist/module/lodash.d.ts +2 -1
- package/dist/module/lodash.mjs +3 -1
- package/dist/module/log4js.d.ts +3 -2
- package/dist/module/log4js.mjs +5 -2
- package/dist/module/moment.d.ts +2 -1
- package/dist/module/moment.mjs +3 -1
- package/dist/module/node-schedule.d.ts +3 -2
- package/dist/module/node-schedule.mjs +5 -2
- package/dist/module/redis.d.ts +3 -2
- package/dist/module/redis.mjs +5 -2
- package/dist/module/sqlite3.d.ts +3 -2
- package/dist/module/sqlite3.mjs +5 -2
- package/dist/module/ws.d.ts +3 -2
- package/dist/module/ws.mjs +5 -2
- package/dist/module/yaml.d.ts +3 -2
- package/dist/module/yaml.mjs +5 -2
- package/dist/queue-CnKedaZA.mjs +70 -0
- package/dist/redis-aLJ7wbJH.mjs +1556 -0
- package/dist/render-DPqueDZr.mjs +170 -0
- package/dist/root.d.ts +46 -46
- package/dist/root.mjs +136 -93
- package/dist/router-zPSN9-tY.mjs +124 -0
- package/dist/server-DT64D-m-.mjs +38 -0
- package/dist/snapka-BTlnZOyI.mjs +450 -0
- package/dist/sqlite-Dcj9jlW9.mjs +307 -0
- package/dist/start/app.d.ts +1 -1
- package/dist/start/app.mjs +14 -7
- package/dist/start/index.d.ts +1 -1
- package/dist/start/index.mjs +325 -656
- package/dist/template-Djk6y0uC.mjs +133 -0
- package/dist/terminalManager-Lxa8Sm06.mjs +783 -0
- package/dist/uptime-C121X_rq.mjs +210 -0
- package/dist/web/{CompressaPRO-GX.woff2.br → CompressaPRO-GX.woff2} +0 -0
- package/dist/web/assets/css/style-CBB8wM_W.css +14880 -0
- package/dist/web/assets/js/entry-Blf4Trpx.js +258540 -0
- package/dist/web/{googleapis.woff2.br → googleapis.woff2} +0 -0
- package/dist/web/index.html +2 -15
- package/dist/web/karin.png +0 -0
- package/dist/web/sha256.min.js +9 -0
- package/dist/ws-BLDoC2gV.mjs +80 -0
- package/dist/ws-CcoWd3Ar.mjs +106 -0
- package/package.json +7 -7
- package/dist/global.d.d.ts +0 -68
- package/dist/types-hAhbXJDZ.d.ts +0 -109
- package/dist/web/assets/css/components-ep7vm38G.css +0 -1
- package/dist/web/assets/css/index-Dadvd9mn.css.br +0 -0
- package/dist/web/assets/css/vendor-editor-CFbL2ovg.css.br +0 -0
- package/dist/web/assets/css/vendor-others-ZgkIHsf0.css +0 -1
- package/dist/web/assets/js/components-CU2xw4lY.js.br +0 -0
- package/dist/web/assets/js/entry-Dvb7eYLE.js.br +0 -0
- package/dist/web/assets/js/hooks-CRfhs4ON.js.br +0 -0
- package/dist/web/assets/js/page-404.tsx-DYMd_RI_.js +0 -1
- package/dist/web/assets/js/page-dashboard-CG60V_Z-.js.br +0 -0
- package/dist/web/assets/js/page-loading.tsx-wY8a9me3.js.br +0 -0
- package/dist/web/assets/js/page-login.tsx-B54ZOEZB.js.br +0 -0
- package/dist/web/assets/js/utils-C9nWTSuo.js +0 -2
- package/dist/web/assets/js/vendor-editor-BmqYP7lh.js.br +0 -0
- package/dist/web/assets/js/vendor-heroui-ClBCy2zk.js.br +0 -0
- package/dist/web/assets/js/vendor-others-6GiMrjd4.js.br +0 -0
- package/dist/web/assets/js/vendor-react-Dc9jdQiK.js.br +0 -0
- package/dist/web/assets/js/vendor-ui-utils-D0xkboLL.js.br +0 -0
- package/dist/web/assets/js/vendor-visual-saF8KLH_.js.br +0 -0
- package/dist/web/karin.png.br +0 -0
- package/dist/web/sha256.min.js.br +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { g as WS_CONNECTION_PUPPETEER, r as listeners } from "./internal-DupfycKE.mjs";
|
|
2
|
+
import { t as WebSocketRender } from "./ws-CcoWd3Ar.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/adapter/render/connect/server.ts
|
|
5
|
+
/**
|
|
6
|
+
* @description WebSocket服务端渲染
|
|
7
|
+
* @class WebSocketServerRenderer
|
|
8
|
+
*/
|
|
9
|
+
var WebSocketServerRenderer = class extends WebSocketRender {
|
|
10
|
+
/** 请求实例 */
|
|
11
|
+
request;
|
|
12
|
+
constructor(socket, request) {
|
|
13
|
+
super(socket);
|
|
14
|
+
this.request = request;
|
|
15
|
+
}
|
|
16
|
+
connection() {
|
|
17
|
+
const url = `ws://${this.request.headers.host}${this.request.url}`;
|
|
18
|
+
if (process.env.WS_SERVER_AUTH_KEY) {
|
|
19
|
+
const token = this.request.headers["authorization"];
|
|
20
|
+
if (!token || !this.auth(process.env.WS_SERVER_AUTH_KEY, token)) {
|
|
21
|
+
logger.error(`[WebSocket] 鉴权失败: authorization: ${token} url: ${url}`);
|
|
22
|
+
this.socket.close();
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
logger.info(`[WebSocket] 连接成功: url: ${url}`);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
listeners.on(WS_CONNECTION_PUPPETEER, (socket, request, call) => {
|
|
31
|
+
call();
|
|
32
|
+
const server = new WebSocketServerRenderer(socket, request);
|
|
33
|
+
if (!server.connection()) return;
|
|
34
|
+
server.init();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { };
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import { i as getRenderCfg } from "./file-ZGuqNDd-.mjs";
|
|
2
|
+
import { r as listeners, x as isPublic, y as WS_SNAPKA } from "./internal-DupfycKE.mjs";
|
|
3
|
+
import { o as registerRender, u as unregisterRender } from "./cache-CPcPeo6N.mjs";
|
|
4
|
+
import { t as renderTemplate } from "./template-Djk6y0uC.mjs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import { URL, fileURLToPath } from "node:url";
|
|
8
|
+
import axios from "axios";
|
|
9
|
+
import lodash from "lodash";
|
|
10
|
+
import crypto from "node:crypto";
|
|
11
|
+
import { WebSocket } from "ws";
|
|
12
|
+
|
|
13
|
+
//#region src/adapter/snapka/key.ts
|
|
14
|
+
/**
|
|
15
|
+
* 创建ws响应key 用于收到响应后发布事件
|
|
16
|
+
* @param echo 请求唯一标识符
|
|
17
|
+
*/
|
|
18
|
+
const createWsResponseKey = (echo) => {
|
|
19
|
+
return `_response:${echo}`;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/adapter/snapka/request.ts
|
|
24
|
+
/** 索引 */
|
|
25
|
+
let index = 0;
|
|
26
|
+
/**
|
|
27
|
+
* 创建请求错误
|
|
28
|
+
* @param options 请求选项
|
|
29
|
+
* @param errorType 错误类型
|
|
30
|
+
* @param cause 错误原因
|
|
31
|
+
* @returns Error实例
|
|
32
|
+
*/
|
|
33
|
+
const createRequestError = (options, errorType, cause) => {
|
|
34
|
+
return new Error(`[sendRequest] 请求错误:
|
|
35
|
+
options: ${options}\n error: ${errorType}`, { cause });
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* 发送ws请求
|
|
39
|
+
* @description 开启监听响应 你需要自己往`listeners` `emit` 响应事件
|
|
40
|
+
* @param socket WebSocket
|
|
41
|
+
* @param data 发送的结构体
|
|
42
|
+
* @param options 请求参数
|
|
43
|
+
* @returns 响应数据
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { listeners } from '@/core/internal'
|
|
46
|
+
* import { createWsResponseKey } from './key'
|
|
47
|
+
*
|
|
48
|
+
* const { echo, data } = result
|
|
49
|
+
* const key = createWsResponseKey(echo)
|
|
50
|
+
* listeners.emit(key, data)
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
const sendWsRequest = (socket, data, options = {
|
|
54
|
+
timeout: 60 * 1e3,
|
|
55
|
+
onRequest: true
|
|
56
|
+
}) => {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
/** 超时时间 */
|
|
59
|
+
const timeout = options?.timeout ?? 60 * 1e3;
|
|
60
|
+
/** 检查WebSocket连接状态 */
|
|
61
|
+
if (socket.readyState !== socket.OPEN) return reject(createRequestError(JSON.stringify(data), `WebSocket未连接,当前状态: ${socket.readyState}`));
|
|
62
|
+
if (options.onRequest) {
|
|
63
|
+
/** 递增并检查索引是否需要重置 */
|
|
64
|
+
if (index >= Number.MAX_SAFE_INTEGER) index = 0;
|
|
65
|
+
const echo = (++index).toString();
|
|
66
|
+
const key = createWsResponseKey(echo);
|
|
67
|
+
const str = JSON.stringify({
|
|
68
|
+
...data,
|
|
69
|
+
echo
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* 监听响应函数
|
|
73
|
+
* @param data 响应数据
|
|
74
|
+
*/
|
|
75
|
+
const result = (data) => {
|
|
76
|
+
clearTimeout(timer);
|
|
77
|
+
if (data?.status === "ok") return resolve(data.data);
|
|
78
|
+
reject(createRequestError(str, "请求失败", data));
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* 超时函数
|
|
82
|
+
*/
|
|
83
|
+
const timer = setTimeout(() => {
|
|
84
|
+
listeners.off(key, result);
|
|
85
|
+
reject(createRequestError(str, `请求超时 ${timeout}ms`));
|
|
86
|
+
}, timeout);
|
|
87
|
+
listeners.once(key, result);
|
|
88
|
+
try {
|
|
89
|
+
socket.send(str);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
listeners.off(key, result);
|
|
93
|
+
reject(createRequestError(str, "发送失败", error));
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
socket.send(JSON.stringify(data));
|
|
99
|
+
resolve(void 0);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
reject(createRequestError(JSON.stringify(data), "发送失败", error));
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* puppeteer-server 发送截图请求
|
|
107
|
+
* @param socket WebSocket
|
|
108
|
+
* @param params 发送的结构体
|
|
109
|
+
* @param timeout 请求超时时间
|
|
110
|
+
* @returns 响应数据
|
|
111
|
+
*/
|
|
112
|
+
const sendWsScreenshotRequest = (socket, params, timeout = 60 * 1e3) => {
|
|
113
|
+
return sendWsRequest(socket, {
|
|
114
|
+
params,
|
|
115
|
+
type: "request",
|
|
116
|
+
action: params.data ? "render" : "screenshot"
|
|
117
|
+
}, {
|
|
118
|
+
onRequest: true,
|
|
119
|
+
timeout
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/adapter/snapka/client.ts
|
|
125
|
+
/** 前缀 */
|
|
126
|
+
const PREFIX = "[snapka-ws-clinet] ";
|
|
127
|
+
/** 客户端缓存 */
|
|
128
|
+
const snapkaMap$1 = /* @__PURE__ */ new Map();
|
|
129
|
+
/**
|
|
130
|
+
* 创建snapka客户端
|
|
131
|
+
* @param clientOptions 客户端配置
|
|
132
|
+
* @returns 客户端
|
|
133
|
+
*/
|
|
134
|
+
const createSnapkaClient = (clientOptions) => {
|
|
135
|
+
let index = -1;
|
|
136
|
+
let isReconnect = true;
|
|
137
|
+
let reconnectTimer;
|
|
138
|
+
/** 客户端配置 */
|
|
139
|
+
const { enable, url, token, reconnectTime = 5e3, heartbeatTime = 3e4, isSnapka = false } = clientOptions;
|
|
140
|
+
/** 如果未启用或不是snapka模式则直接返回 */
|
|
141
|
+
if (!enable || !isSnapka) return;
|
|
142
|
+
/** 生成鉴权 */
|
|
143
|
+
const authorization = token ? `Bearer ${crypto.createHash("sha256").update(token).digest("hex")}` : void 0;
|
|
144
|
+
/**
|
|
145
|
+
* 关闭客户端
|
|
146
|
+
*/
|
|
147
|
+
const close = () => {
|
|
148
|
+
isReconnect = false;
|
|
149
|
+
if (reconnectTimer) {
|
|
150
|
+
clearTimeout(reconnectTimer);
|
|
151
|
+
reconnectTimer = void 0;
|
|
152
|
+
}
|
|
153
|
+
client?.close();
|
|
154
|
+
snapkaMap$1.delete(url);
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* 创建客户端
|
|
158
|
+
*/
|
|
159
|
+
const client = token ? new WebSocket(url, { headers: { authorization: `Bearer ${crypto.createHash("sha256").update(token).digest("hex")}` } }) : new WebSocket(url);
|
|
160
|
+
/**
|
|
161
|
+
* 关闭客户端
|
|
162
|
+
*/
|
|
163
|
+
const fnc = (isPrint = true) => {
|
|
164
|
+
client.removeAllListeners();
|
|
165
|
+
client?.close();
|
|
166
|
+
index > 0 && unregisterRender(index);
|
|
167
|
+
if (!isReconnect) {
|
|
168
|
+
isPrint && logger.error(`${PREFIX}连接关闭: ${url}`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
isPrint && logger.error(`${PREFIX}连接关闭: ${url} ${reconnectTime / 1e3}s 后重连...`);
|
|
172
|
+
reconnectTimer = setTimeout(() => createSnapkaClient(clientOptions), reconnectTime);
|
|
173
|
+
};
|
|
174
|
+
client.once("open", () => {
|
|
175
|
+
const timer = setInterval(() => client.ping(), heartbeatTime);
|
|
176
|
+
if (snapkaMap$1.has(url)) snapkaMap$1.get(url)?.close();
|
|
177
|
+
snapkaMap$1.set(url, {
|
|
178
|
+
client,
|
|
179
|
+
close
|
|
180
|
+
});
|
|
181
|
+
client.once("close", () => {
|
|
182
|
+
clearInterval(timer);
|
|
183
|
+
fnc();
|
|
184
|
+
});
|
|
185
|
+
setTimeout(() => {
|
|
186
|
+
if (client.readyState !== WebSocket.OPEN) return;
|
|
187
|
+
logger.info(`${PREFIX}连接成功: ${url}`);
|
|
188
|
+
index = registerRender("snapka", render);
|
|
189
|
+
client.on("message", async (event) => onMessage(client, url, event, authorization));
|
|
190
|
+
}, 3e3);
|
|
191
|
+
});
|
|
192
|
+
client.on("error", (error) => {
|
|
193
|
+
logger.error(`${PREFIX}连接错误: ${error}`);
|
|
194
|
+
fnc(false);
|
|
195
|
+
});
|
|
196
|
+
/**
|
|
197
|
+
* 截图函数
|
|
198
|
+
*/
|
|
199
|
+
const render = (options) => {
|
|
200
|
+
options = renderTemplate(options);
|
|
201
|
+
return sendWsScreenshotRequest(client, options);
|
|
202
|
+
};
|
|
203
|
+
return {
|
|
204
|
+
render,
|
|
205
|
+
close
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* 收到消息事件
|
|
210
|
+
* @param client 客户端
|
|
211
|
+
* @param url 地址
|
|
212
|
+
* @param event 事件
|
|
213
|
+
* @param authorization 授权
|
|
214
|
+
*/
|
|
215
|
+
const onMessage = async (client, url, event, authorization) => {
|
|
216
|
+
const raw = event.toString();
|
|
217
|
+
const options = JSON.parse(raw);
|
|
218
|
+
logger.debug(`${PREFIX}收到消息: ${lodash.truncate(raw, { length: 300 })}`);
|
|
219
|
+
if (options.type === "response") {
|
|
220
|
+
const key = createWsResponseKey(options.echo);
|
|
221
|
+
return listeners.emit(key, options);
|
|
222
|
+
}
|
|
223
|
+
if (options.type !== "request") return logger.error(`${PREFIX}收到未知消息: ${raw}`);
|
|
224
|
+
if (options.action === "uploadFile") {
|
|
225
|
+
const file = fileURLToPath(options.params.path);
|
|
226
|
+
logger.debug(`${PREFIX}收到上传文件请求: ${options.params.path}`);
|
|
227
|
+
if (!isPublic(file)) {
|
|
228
|
+
logger.error(`${PREFIX}上传文件失败: 非法的路径,${file} 没有处于允许静态资源目录下`);
|
|
229
|
+
/** 失败了需要发一个响应 不然snapka会一直等待 */
|
|
230
|
+
client.send(JSON.stringify({
|
|
231
|
+
type: "response",
|
|
232
|
+
action: "uploadFile",
|
|
233
|
+
echo: options.echo,
|
|
234
|
+
status: "failed",
|
|
235
|
+
data: "非法的路径,没有处于允许静态资源目录下"
|
|
236
|
+
}));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const target = new URL(url);
|
|
240
|
+
target.protocol = url.startsWith("wss") ? "https:" : "http:";
|
|
241
|
+
target.pathname = options.params.uploadPath;
|
|
242
|
+
try {
|
|
243
|
+
await axios.post(target.toString(), {
|
|
244
|
+
echo: options.echo,
|
|
245
|
+
file: `base64://${fs.readFileSync(file, "base64")}`
|
|
246
|
+
}, { headers: {
|
|
247
|
+
"Content-Type": "application/json",
|
|
248
|
+
Authorization: authorization
|
|
249
|
+
} });
|
|
250
|
+
} catch (error) {
|
|
251
|
+
logger.error(new Error(`${PREFIX}上传文件失败: ${error}`, { cause: error }));
|
|
252
|
+
/** 失败了需要发一个响应 不然snapka会一直等待 */
|
|
253
|
+
client.send(JSON.stringify({
|
|
254
|
+
type: "response",
|
|
255
|
+
action: "uploadFile",
|
|
256
|
+
echo: options.echo,
|
|
257
|
+
status: "failed",
|
|
258
|
+
data: error.message
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* 初始化snapka客户端
|
|
265
|
+
*/
|
|
266
|
+
const initSnapkaClient = async () => {
|
|
267
|
+
const { getRenderCfg } = await import("./file-ZGuqNDd-.mjs").then((n) => n.Zn);
|
|
268
|
+
getRenderCfg().ws_client.forEach((item) => {
|
|
269
|
+
createSnapkaClient(item);
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* 断开snapka客户端
|
|
274
|
+
* @param url 地址
|
|
275
|
+
*/
|
|
276
|
+
const disconnectSnapkaClient = (url) => {
|
|
277
|
+
const cache = snapkaMap$1.get(url);
|
|
278
|
+
if (!cache) return;
|
|
279
|
+
cache.close();
|
|
280
|
+
snapkaMap$1.delete(url);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
//#endregion
|
|
284
|
+
//#region src/adapter/snapka/auth.ts
|
|
285
|
+
/**
|
|
286
|
+
* 鉴权
|
|
287
|
+
* 1. sha256 加密
|
|
288
|
+
* 2. 明文对比
|
|
289
|
+
* 3. 以上都需要带`Bearer`前缀
|
|
290
|
+
* @param token 令牌
|
|
291
|
+
*/
|
|
292
|
+
const auth = (token) => {
|
|
293
|
+
if (!process.env.WS_SERVER_AUTH_KEY) return true;
|
|
294
|
+
if (!token) return false;
|
|
295
|
+
/** 先对比明文 */
|
|
296
|
+
if (token === `Bearer ${process.env.WS_SERVER_AUTH_KEY}`) return true;
|
|
297
|
+
if (token === `Bearer ${crypto.createHash("sha256").update(process.env.WS_SERVER_AUTH_KEY).digest("hex")}`) return true;
|
|
298
|
+
return false;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region src/adapter/snapka/server.ts
|
|
303
|
+
/**
|
|
304
|
+
* @description 2.0 puppeteer WebSocket服务端
|
|
305
|
+
* @param socket WebSocket
|
|
306
|
+
* @param request IncomingMessage
|
|
307
|
+
*/
|
|
308
|
+
const WebSocketPuppeteerServer = async (socket, request) => {
|
|
309
|
+
let index = -1;
|
|
310
|
+
/** 鉴权 */
|
|
311
|
+
const authorization = request.headers["authorization"];
|
|
312
|
+
if (!auth(authorization)) {
|
|
313
|
+
socket.close();
|
|
314
|
+
logger.error(`[WebSocket] 鉴权失败: authorization: ${authorization} url: ${request.url}`);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* 截图函数
|
|
319
|
+
*/
|
|
320
|
+
const render = (options) => {
|
|
321
|
+
options = renderTemplate(options);
|
|
322
|
+
return sendWsScreenshotRequest(socket, options);
|
|
323
|
+
};
|
|
324
|
+
socket.on("close", () => {
|
|
325
|
+
index > 0 && unregisterRender(index);
|
|
326
|
+
socket.removeAllListeners();
|
|
327
|
+
socket.close();
|
|
328
|
+
});
|
|
329
|
+
socket.on("message", (event) => {
|
|
330
|
+
const raw = event.toString();
|
|
331
|
+
const { type, status, echo, data } = JSON.parse(raw) || {};
|
|
332
|
+
logger.debug(`[WebSocket] ${echo} ${type} ${status}`);
|
|
333
|
+
logger.trace(`[WebSocket] ${echo} ${raw}`);
|
|
334
|
+
if (type !== "response") {
|
|
335
|
+
logger.error(`[WebSocket] 未知的请求: ${raw}`);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const key = createWsResponseKey(echo);
|
|
339
|
+
listeners.emit(key, {
|
|
340
|
+
status,
|
|
341
|
+
data
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
index = registerRender(request.headers["x-client-name"] || "snapka", render);
|
|
345
|
+
};
|
|
346
|
+
/**
|
|
347
|
+
* @description 初始化WebSocketPuppeteerServer
|
|
348
|
+
*/
|
|
349
|
+
const initWebSocketPuppeteerServer = () => {
|
|
350
|
+
listeners.on(WS_SNAPKA, (socket, request, call) => {
|
|
351
|
+
call();
|
|
352
|
+
WebSocketPuppeteerServer(socket, request);
|
|
353
|
+
});
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
//#endregion
|
|
357
|
+
//#region src/adapter/snapka/http.ts
|
|
358
|
+
const snapkaMap = /* @__PURE__ */ new Map();
|
|
359
|
+
/**
|
|
360
|
+
* 创建snapka http
|
|
361
|
+
* @param options 配置
|
|
362
|
+
*/
|
|
363
|
+
const createSnapkaHttp = async (options) => {
|
|
364
|
+
if (!options.isSnapka || !options.enable) return;
|
|
365
|
+
const authorization = `Bearer ${crypto.createHash("sha256").update(options.token).digest("hex")}`;
|
|
366
|
+
/** 测试是否可以连接 */
|
|
367
|
+
const url = path.dirname(options.url) + `/ping?token=${authorization}`;
|
|
368
|
+
const test = async () => {
|
|
369
|
+
try {
|
|
370
|
+
return (await axios.get(url)).data;
|
|
371
|
+
} catch (error) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
if (!await test()) logger.error(`无法连接到 Snapka-http 服务: ${options.url},将在后台继续尝试连接`);
|
|
376
|
+
/**
|
|
377
|
+
* 截图函数
|
|
378
|
+
*/
|
|
379
|
+
const render = async (data) => {
|
|
380
|
+
data = renderTemplate(data);
|
|
381
|
+
try {
|
|
382
|
+
const result = await axios.post(options.url, data, { headers: { Authorization: authorization } });
|
|
383
|
+
if (result.status === 200 && result.data?.code === 200) return result.data.data;
|
|
384
|
+
throw new Error("请求失败:", { cause: result.data });
|
|
385
|
+
} catch (error) {
|
|
386
|
+
logger.error(`Snapka-http 服务请求异常: ${options.url}`, error);
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
/** ping定时器 */
|
|
391
|
+
let pingTimer;
|
|
392
|
+
/** 连续失败次数 */
|
|
393
|
+
let consecutiveFailures = 0;
|
|
394
|
+
/** 注册render */
|
|
395
|
+
const index = registerRender("snapka-http", render);
|
|
396
|
+
/**
|
|
397
|
+
* 关闭snapka http
|
|
398
|
+
*/
|
|
399
|
+
const close = () => {
|
|
400
|
+
if (pingTimer) {
|
|
401
|
+
clearInterval(pingTimer);
|
|
402
|
+
pingTimer = void 0;
|
|
403
|
+
}
|
|
404
|
+
snapkaMap.delete(options.url);
|
|
405
|
+
unregisterRender(index);
|
|
406
|
+
};
|
|
407
|
+
/**
|
|
408
|
+
* 启动ping检测
|
|
409
|
+
*/
|
|
410
|
+
const startPingInterval = () => {
|
|
411
|
+
pingTimer = setInterval(async () => {
|
|
412
|
+
if (await test()) {
|
|
413
|
+
if (consecutiveFailures > 0) {
|
|
414
|
+
logger.info(`Snapka-http 服务 ${options.url} 恢复连接`);
|
|
415
|
+
consecutiveFailures = 0;
|
|
416
|
+
}
|
|
417
|
+
} else {
|
|
418
|
+
consecutiveFailures++;
|
|
419
|
+
logger.error(`Snapka-http 服务连接异常 (${consecutiveFailures}次): ${options.url},将继续尝试重连`);
|
|
420
|
+
}
|
|
421
|
+
}, 1e4);
|
|
422
|
+
};
|
|
423
|
+
startPingInterval();
|
|
424
|
+
snapkaMap.set(options.url, {
|
|
425
|
+
close,
|
|
426
|
+
pingTimer
|
|
427
|
+
});
|
|
428
|
+
return {
|
|
429
|
+
render,
|
|
430
|
+
close
|
|
431
|
+
};
|
|
432
|
+
};
|
|
433
|
+
/**
|
|
434
|
+
* 初始化snapka http
|
|
435
|
+
*/
|
|
436
|
+
const initSnapkaHttp = async () => {
|
|
437
|
+
const cfg = getRenderCfg();
|
|
438
|
+
for (const options of cfg.http_server) await createSnapkaHttp(options);
|
|
439
|
+
};
|
|
440
|
+
/**
|
|
441
|
+
* 断开snapka http
|
|
442
|
+
* @param url 地址
|
|
443
|
+
*/
|
|
444
|
+
const disconnectSnapkaHttp = (url) => {
|
|
445
|
+
const handler = snapkaMap.get(url);
|
|
446
|
+
if (handler) handler.close();
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
//#endregion
|
|
450
|
+
export { createSnapkaClient, createSnapkaHttp, disconnectSnapkaClient, disconnectSnapkaHttp, initSnapkaClient, initSnapkaHttp, initWebSocketPuppeteerServer };
|