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.
Files changed (89) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/adapter-BqlH3u3X.mjs +218 -0
  3. package/dist/app-DdMQbBEY.mjs +4109 -0
  4. package/dist/cache-CPcPeo6N.mjs +163 -0
  5. package/dist/chunk-NzVPYdc1.mjs +21 -0
  6. package/dist/cli/index.cjs +10900 -1
  7. package/dist/cli/index.d.ts +1 -1
  8. package/dist/cli/index.mjs +10770 -10224
  9. package/dist/file-ZGuqNDd-.mjs +15987 -0
  10. package/dist/file-dGy9of8-.mjs +268 -0
  11. package/dist/fsSync-Cf5MWILk.mjs +65 -0
  12. package/dist/index.d.ts +12235 -12738
  13. package/dist/index.mjs +2054 -25247
  14. package/dist/internal-DupfycKE.mjs +597 -0
  15. package/dist/kv-DZp4UIxg.mjs +192 -0
  16. package/dist/module/art-template.d.ts +2 -13
  17. package/dist/module/art-template.mjs +3 -1
  18. package/dist/module/axios.d.ts +3 -2
  19. package/dist/module/axios.mjs +5 -2
  20. package/dist/module/chalk.d.ts +3 -2
  21. package/dist/module/chalk.mjs +5 -2
  22. package/dist/module/chokidar.d.ts +3 -2
  23. package/dist/module/chokidar.mjs +5 -2
  24. package/dist/module/express.d.ts +2 -1
  25. package/dist/module/express.mjs +3 -1
  26. package/dist/module/lodash.d.ts +2 -1
  27. package/dist/module/lodash.mjs +3 -1
  28. package/dist/module/log4js.d.ts +3 -2
  29. package/dist/module/log4js.mjs +5 -2
  30. package/dist/module/moment.d.ts +2 -1
  31. package/dist/module/moment.mjs +3 -1
  32. package/dist/module/node-schedule.d.ts +3 -2
  33. package/dist/module/node-schedule.mjs +5 -2
  34. package/dist/module/redis.d.ts +3 -2
  35. package/dist/module/redis.mjs +5 -2
  36. package/dist/module/sqlite3.d.ts +3 -2
  37. package/dist/module/sqlite3.mjs +5 -2
  38. package/dist/module/ws.d.ts +3 -2
  39. package/dist/module/ws.mjs +5 -2
  40. package/dist/module/yaml.d.ts +3 -2
  41. package/dist/module/yaml.mjs +5 -2
  42. package/dist/queue-CnKedaZA.mjs +70 -0
  43. package/dist/redis-aLJ7wbJH.mjs +1556 -0
  44. package/dist/render-DPqueDZr.mjs +170 -0
  45. package/dist/root.d.ts +46 -46
  46. package/dist/root.mjs +136 -93
  47. package/dist/router-zPSN9-tY.mjs +124 -0
  48. package/dist/server-DT64D-m-.mjs +38 -0
  49. package/dist/snapka-BTlnZOyI.mjs +450 -0
  50. package/dist/sqlite-Dcj9jlW9.mjs +307 -0
  51. package/dist/start/app.d.ts +1 -1
  52. package/dist/start/app.mjs +14 -7
  53. package/dist/start/index.d.ts +1 -1
  54. package/dist/start/index.mjs +325 -656
  55. package/dist/template-Djk6y0uC.mjs +133 -0
  56. package/dist/terminalManager-Lxa8Sm06.mjs +783 -0
  57. package/dist/uptime-C121X_rq.mjs +210 -0
  58. package/dist/web/{CompressaPRO-GX.woff2.br → CompressaPRO-GX.woff2} +0 -0
  59. package/dist/web/assets/css/style-CBB8wM_W.css +14880 -0
  60. package/dist/web/assets/js/entry-Blf4Trpx.js +258540 -0
  61. package/dist/web/{googleapis.woff2.br → googleapis.woff2} +0 -0
  62. package/dist/web/index.html +2 -15
  63. package/dist/web/karin.png +0 -0
  64. package/dist/web/sha256.min.js +9 -0
  65. package/dist/ws-BLDoC2gV.mjs +80 -0
  66. package/dist/ws-CcoWd3Ar.mjs +106 -0
  67. package/package.json +7 -7
  68. package/dist/global.d.d.ts +0 -68
  69. package/dist/types-hAhbXJDZ.d.ts +0 -109
  70. package/dist/web/assets/css/components-ep7vm38G.css +0 -1
  71. package/dist/web/assets/css/index-Dadvd9mn.css.br +0 -0
  72. package/dist/web/assets/css/vendor-editor-CFbL2ovg.css.br +0 -0
  73. package/dist/web/assets/css/vendor-others-ZgkIHsf0.css +0 -1
  74. package/dist/web/assets/js/components-CU2xw4lY.js.br +0 -0
  75. package/dist/web/assets/js/entry-Dvb7eYLE.js.br +0 -0
  76. package/dist/web/assets/js/hooks-CRfhs4ON.js.br +0 -0
  77. package/dist/web/assets/js/page-404.tsx-DYMd_RI_.js +0 -1
  78. package/dist/web/assets/js/page-dashboard-CG60V_Z-.js.br +0 -0
  79. package/dist/web/assets/js/page-loading.tsx-wY8a9me3.js.br +0 -0
  80. package/dist/web/assets/js/page-login.tsx-B54ZOEZB.js.br +0 -0
  81. package/dist/web/assets/js/utils-C9nWTSuo.js +0 -2
  82. package/dist/web/assets/js/vendor-editor-BmqYP7lh.js.br +0 -0
  83. package/dist/web/assets/js/vendor-heroui-ClBCy2zk.js.br +0 -0
  84. package/dist/web/assets/js/vendor-others-6GiMrjd4.js.br +0 -0
  85. package/dist/web/assets/js/vendor-react-Dc9jdQiK.js.br +0 -0
  86. package/dist/web/assets/js/vendor-ui-utils-D0xkboLL.js.br +0 -0
  87. package/dist/web/assets/js/vendor-visual-saF8KLH_.js.br +0 -0
  88. package/dist/web/karin.png.br +0 -0
  89. 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 };