miqro 7.0.1 → 7.0.3
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/build/esm/editor/auth.d.ts +6 -0
- package/build/esm/editor/auth.js +41 -0
- package/build/esm/editor/common/admin-interface.d.ts +36 -0
- package/build/esm/editor/common/admin-interface.js +44 -0
- package/build/esm/editor/common/constants.d.ts +4 -0
- package/build/esm/editor/common/constants.js +20 -0
- package/build/esm/editor/common/constants.server.d.ts +2 -0
- package/build/esm/editor/common/constants.server.js +4 -0
- package/build/esm/editor/common/editor-index.d.ts +2 -0
- package/build/esm/editor/common/editor-index.js +14 -0
- package/build/esm/editor/common/html-encode.d.ts +1 -0
- package/build/esm/editor/common/html-encode.js +14 -0
- package/build/esm/editor/common/log-socket.d.ts +15 -0
- package/build/esm/editor/common/log-socket.js +71 -0
- package/build/esm/editor/common/templates.d.ts +11 -0
- package/build/esm/editor/common/templates.js +477 -0
- package/build/esm/editor/components/api-preview.d.ts +11 -0
- package/build/esm/editor/components/api-preview.js +92 -0
- package/build/esm/editor/components/editor.d.ts +16 -0
- package/build/esm/editor/components/editor.js +367 -0
- package/build/esm/editor/components/file-browser.d.ts +37 -0
- package/build/esm/editor/components/file-browser.js +127 -0
- package/build/esm/editor/components/file-editor-toolbar.d.ts +22 -0
- package/build/esm/editor/components/file-editor-toolbar.js +95 -0
- package/build/esm/editor/components/file-editor.d.ts +32 -0
- package/build/esm/editor/components/file-editor.js +61 -0
- package/build/esm/editor/components/filter-query.d.ts +1 -0
- package/build/esm/editor/components/filter-query.js +23 -0
- package/build/esm/editor/components/highlight-text-area.d.ts +11 -0
- package/build/esm/editor/components/highlight-text-area.js +127 -0
- package/build/esm/editor/components/log-viewer.d.ts +6 -0
- package/build/esm/editor/components/log-viewer.js +71 -0
- package/build/esm/editor/components/new-file.d.ts +10 -0
- package/build/esm/editor/components/new-file.js +119 -0
- package/build/esm/editor/components/scroll-query.d.ts +7 -0
- package/build/esm/editor/components/scroll-query.js +22 -0
- package/build/esm/editor/components/start-page.d.ts +13 -0
- package/build/esm/editor/components/start-page.js +32 -0
- package/build/esm/editor/http/admin/editor/api/fs/delete.api.d.ts +3 -0
- package/build/esm/editor/http/admin/editor/api/fs/delete.api.js +30 -0
- package/build/esm/editor/http/admin/editor/api/fs/read.api.d.ts +5 -0
- package/build/esm/editor/http/admin/editor/api/fs/read.api.js +50 -0
- package/build/esm/editor/http/admin/editor/api/fs/rename.api.d.ts +4 -0
- package/build/esm/editor/http/admin/editor/api/fs/rename.api.js +40 -0
- package/build/esm/editor/http/admin/editor/api/fs/scan.api.d.ts +26 -0
- package/build/esm/editor/http/admin/editor/api/fs/scan.api.js +150 -0
- package/build/esm/editor/http/admin/editor/api/fs/write.api.d.ts +3 -0
- package/build/esm/editor/http/admin/editor/api/fs/write.api.js +39 -0
- package/build/esm/editor/http/admin/editor/api/server/reload.api.d.ts +10 -0
- package/build/esm/editor/http/admin/editor/api/server/reload.api.js +46 -0
- package/build/esm/editor/http/admin/editor/api/server/restart.api.d.ts +10 -0
- package/build/esm/editor/http/admin/editor/api/server/restart.api.js +46 -0
- package/build/esm/editor/http/admin/editor/editor.d.ts +1 -0
- package/build/esm/editor/http/admin/editor/editor.js +8 -0
- package/build/esm/editor/http/admin/editor/index.api.d.ts +3 -0
- package/build/esm/editor/http/admin/editor/index.api.js +22 -0
- package/build/esm/editor/server.d.ts +3 -0
- package/build/esm/editor/server.js +49 -0
- package/build/esm/editor/ws.d.ts +3 -0
- package/build/esm/editor/ws.js +11 -0
- package/build/esm/src/inflate/inflate-sea.js +8 -1
- package/build/esm/src/services/app.js +7 -2
- package/build/lib.cjs +14 -3
- package/editor/auth.ts +51 -0
- package/editor/common/admin-interface.ts +84 -0
- package/editor/common/constants.server.ts +5 -0
- package/editor/common/constants.ts +21 -0
- package/editor/common/editor-index.tsx +17 -0
- package/editor/common/html-encode.ts +14 -0
- package/editor/common/log-socket.tsx +87 -0
- package/editor/common/templates.ts +481 -0
- package/editor/components/api-preview.tsx +118 -0
- package/editor/components/editor.tsx +496 -0
- package/editor/components/file-browser.tsx +311 -0
- package/editor/components/file-editor-toolbar.tsx +194 -0
- package/editor/components/file-editor.tsx +125 -0
- package/editor/components/filter-query.tsx +26 -0
- package/editor/components/highlight-text-area.tsx +148 -0
- package/editor/components/log-viewer.tsx +113 -0
- package/editor/components/new-file.tsx +172 -0
- package/editor/components/scroll-query.tsx +25 -0
- package/editor/components/start-page.tsx +52 -0
- package/editor/http/admin/editor/api/fs/delete.api.tsx +32 -0
- package/editor/http/admin/editor/api/fs/read.api.tsx +55 -0
- package/editor/http/admin/editor/api/fs/rename.api.tsx +41 -0
- package/editor/http/admin/editor/api/fs/scan.api.tsx +181 -0
- package/editor/http/admin/editor/api/fs/write.api.tsx +41 -0
- package/editor/http/admin/editor/api/server/reload.api.ts +53 -0
- package/editor/http/admin/editor/api/server/restart.api.tsx +52 -0
- package/editor/http/admin/editor/editor.tsx +10 -0
- package/editor/http/admin/editor/index.api.tsx +42 -0
- package/editor/server.ts +57 -0
- package/editor/ws.ts +15 -0
- package/package.json +2 -2
- package/src/bin/compile.ts +35 -0
- package/src/bin/doc-md.ts +210 -0
- package/src/bin/generate-doc.ts +64 -0
- package/src/bin/test.ts +92 -0
- package/src/bin/types.ts +34 -0
- package/src/cluster.ts +27 -0
- package/src/common/arguments.ts +762 -0
- package/src/common/assets.ts +148 -0
- package/src/common/checksum.ts +58 -0
- package/src/common/constants.ts +18 -0
- package/src/common/content-type.ts +84 -0
- package/src/common/esbuild.ts +102 -0
- package/src/common/exit.ts +91 -0
- package/src/common/fs.ts +82 -0
- package/src/common/help.ts +60 -0
- package/src/common/jsx.ts +562 -0
- package/src/common/jwt.ts +85 -0
- package/src/common/paths.ts +107 -0
- package/src/common/watch.ts +88 -0
- package/src/inflate/inflate-sea.ts +244 -0
- package/src/inflate/inflate.ts +101 -0
- package/src/inflate/md.ts +25 -0
- package/src/inflate/setup-auth.ts +41 -0
- package/src/inflate/setup-cors.ts +41 -0
- package/src/inflate/setup-db.ts +117 -0
- package/src/inflate/setup-error.ts +44 -0
- package/src/inflate/setup-http.ts +704 -0
- package/src/inflate/setup-log.ts +45 -0
- package/src/inflate/setup-middleware.ts +47 -0
- package/src/inflate/setup-server-config.ts +48 -0
- package/src/inflate/setup-test.ts +23 -0
- package/src/inflate/setup-ws.ts +50 -0
- package/src/inflate/setup.doc.ts +92 -0
- package/src/inflate/utils/sea-utils.ts +14 -0
- package/src/lib.ts +34 -0
- package/src/main.ts +101 -0
- package/src/services/app.ts +703 -0
- package/src/services/editor.tsx +101 -0
- package/src/services/globals.ts.ignore +186 -0
- package/src/services/hot-reload.ts +51 -0
- package/src/services/migrations.ts +68 -0
- package/src/services/utils/admin-interface.ts +37 -0
- package/src/services/utils/cache.ts +88 -0
- package/src/services/utils/cluster-cache.ts +230 -0
- package/src/services/utils/cluster-ws.ts +202 -0
- package/src/services/utils/db-manager.ts +92 -0
- package/src/services/utils/get-route.ts +70 -0
- package/src/services/utils/jwt.ts +25 -0
- package/src/services/utils/log-transport.ts +81 -0
- package/src/services/utils/log.ts +92 -0
- package/src/services/utils/middleware.ts +10 -0
- package/src/services/utils/server-interface.ts +122 -0
- package/src/services/utils/websocketmanager.ts +157 -0
- package/src/types.ts +307 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { Logger } from "@miqro/core";
|
|
2
|
+
import { CacheInterface } from "../../types.js";
|
|
3
|
+
|
|
4
|
+
const ClusterCacheType = "$$$$$$$$$$$ClusterCacheType$$$$$$$$$$$";
|
|
5
|
+
|
|
6
|
+
interface ClusterCacheMessage {
|
|
7
|
+
type: typeof ClusterCacheType;
|
|
8
|
+
target: string;
|
|
9
|
+
action: "set" | "unset" | "set_add" | "set_delete" | "array_push";
|
|
10
|
+
fromPID: number;
|
|
11
|
+
key: string;
|
|
12
|
+
value?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class ClusterCache implements CacheInterface {
|
|
16
|
+
private localCache = new Map<string, unknown>();
|
|
17
|
+
//private logger: Logger;
|
|
18
|
+
listener: (data: any) => Promise<void>;
|
|
19
|
+
|
|
20
|
+
constructor(public name: string, public logger?: Logger) {
|
|
21
|
+
this.listener = async (data) => {
|
|
22
|
+
try {
|
|
23
|
+
const msg = (data as ClusterCacheMessage);
|
|
24
|
+
|
|
25
|
+
//console.dir(msg);
|
|
26
|
+
if (
|
|
27
|
+
msg &&
|
|
28
|
+
msg.key &&
|
|
29
|
+
msg.action &&
|
|
30
|
+
msg.type === ClusterCacheType &&
|
|
31
|
+
msg.fromPID !== process.pid,
|
|
32
|
+
(msg.action === "set_add" || msg.action === "set" || msg.action === "unset" || msg.action === "set_delete" || msg.action === "array_push") &&
|
|
33
|
+
msg.target === this.name) {
|
|
34
|
+
this.logger?.debug("remote cluster cache message from [%s] [%s] [%s] [%s]", msg.fromPID, msg.target, msg.action, msg.key);
|
|
35
|
+
switch (msg.action) {
|
|
36
|
+
case "unset":
|
|
37
|
+
this.localCache.delete(msg.key);
|
|
38
|
+
break;
|
|
39
|
+
case "set":
|
|
40
|
+
this.localCache.set(msg.key, msg.value);
|
|
41
|
+
break;
|
|
42
|
+
case "set_add": {
|
|
43
|
+
//this.localCache.set(msg.key, msg.value);
|
|
44
|
+
const list = this.localCache.has(msg.key) ? this.localCache.get(msg.key) : new Set<string>();
|
|
45
|
+
if (!(list instanceof Set)) {
|
|
46
|
+
throw new Error("cannot apply push on non array");
|
|
47
|
+
}
|
|
48
|
+
if (!list.has(msg.value)) {
|
|
49
|
+
list.add(msg.value);
|
|
50
|
+
}
|
|
51
|
+
this.localCache.set(msg.key, list);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case "set_delete": {
|
|
55
|
+
//this.localCache.set(msg.key, msg.value);
|
|
56
|
+
const list = this.localCache.has(msg.key) ? this.localCache.get(msg.key) : new Set<string>();
|
|
57
|
+
if (!(list instanceof Set)) {
|
|
58
|
+
throw new Error("cannot apply push on non array");
|
|
59
|
+
}
|
|
60
|
+
if (list.has(msg.value)) {
|
|
61
|
+
list.delete(msg.value);
|
|
62
|
+
}
|
|
63
|
+
this.localCache.set(msg.key, list);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "array_push": {
|
|
67
|
+
//this.localCache.set(msg.key, msg.value);
|
|
68
|
+
const list = this.localCache.has(msg.key) ? this.localCache.get(msg.key) : [];
|
|
69
|
+
if (!(list instanceof Array)) {
|
|
70
|
+
throw new Error("cannot apply push on non array");
|
|
71
|
+
}
|
|
72
|
+
list.push(msg.value);
|
|
73
|
+
this.localCache.set(msg.key, list);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
this.logger?.error(e);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
this.connect();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
connect() {
|
|
86
|
+
if (process.send) {
|
|
87
|
+
process.removeListener("message", this.listener);
|
|
88
|
+
process.on("message", this.listener);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
disconnect() {
|
|
93
|
+
process.removeListener("message", this.listener);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get<T = any>(key: string): T | undefined {
|
|
97
|
+
this.logger?.trace("get(%s)", key);
|
|
98
|
+
return this.localCache.get(key) as T;
|
|
99
|
+
}
|
|
100
|
+
set(key: string, value: unknown): void {
|
|
101
|
+
this.localCache.set(key, value);
|
|
102
|
+
this.logger?.trace("set(%s, ...)", key);
|
|
103
|
+
if (process.send) {
|
|
104
|
+
setTimeout(() => {
|
|
105
|
+
process.send({
|
|
106
|
+
type: ClusterCacheType,
|
|
107
|
+
action: "set",
|
|
108
|
+
target: this.name,
|
|
109
|
+
fromPID: process.pid,
|
|
110
|
+
key,
|
|
111
|
+
value
|
|
112
|
+
} as ClusterCacheMessage);
|
|
113
|
+
}, 10);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
unset(key: string): void {
|
|
117
|
+
this.logger?.trace("unset(%s)", key);
|
|
118
|
+
this.localCache.delete(key);
|
|
119
|
+
if (process.send) {
|
|
120
|
+
setTimeout(() => {
|
|
121
|
+
process.send({
|
|
122
|
+
type: ClusterCacheType,
|
|
123
|
+
target: this.name,
|
|
124
|
+
action: "unset",
|
|
125
|
+
fromPID: process.pid,
|
|
126
|
+
key
|
|
127
|
+
} as ClusterCacheMessage);
|
|
128
|
+
}, 10);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
has(key: string): boolean {
|
|
132
|
+
this.logger?.trace("has(%s)", key);
|
|
133
|
+
return this.localCache.has(key);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
set_add(key: string, value: unknown): void {
|
|
137
|
+
this.logger?.trace("push(%s)", key);
|
|
138
|
+
const list = this.localCache.has(key) ? this.localCache.get(key) : new Set<string>();
|
|
139
|
+
if (!(list instanceof Set)) {
|
|
140
|
+
throw new Error("cannot apply on non Set");
|
|
141
|
+
}
|
|
142
|
+
if (!list.has(value)) {
|
|
143
|
+
list.add(value);
|
|
144
|
+
}
|
|
145
|
+
this.localCache.set(key, list);
|
|
146
|
+
|
|
147
|
+
if (process.send) {
|
|
148
|
+
setTimeout(() => {
|
|
149
|
+
process.send({
|
|
150
|
+
type: ClusterCacheType,
|
|
151
|
+
target: this.name,
|
|
152
|
+
action: "set_add",
|
|
153
|
+
fromPID: process.pid,
|
|
154
|
+
key,
|
|
155
|
+
value
|
|
156
|
+
} as ClusterCacheMessage);
|
|
157
|
+
}, 10);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
set_delete(key: string, value: unknown): void {
|
|
161
|
+
this.logger?.trace("delete(%s)", key);
|
|
162
|
+
const list = this.localCache.has(key) ? this.localCache.get(key) : new Set<string>();
|
|
163
|
+
if (!(list instanceof Set)) {
|
|
164
|
+
throw new Error("cannot apply on non Set");
|
|
165
|
+
}
|
|
166
|
+
if (list.has(value)) {
|
|
167
|
+
list.delete(value);
|
|
168
|
+
}
|
|
169
|
+
this.localCache.set(key, list);
|
|
170
|
+
if (process.send) {
|
|
171
|
+
setTimeout(() => {
|
|
172
|
+
process.send({
|
|
173
|
+
type: ClusterCacheType,
|
|
174
|
+
target: this.name,
|
|
175
|
+
action: "set_delete",
|
|
176
|
+
fromPID: process.pid,
|
|
177
|
+
key,
|
|
178
|
+
value
|
|
179
|
+
} as ClusterCacheMessage);
|
|
180
|
+
}, 10);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
set_has(key: string, value: unknown): boolean {
|
|
184
|
+
this.logger?.trace("set_has(%s)", key);
|
|
185
|
+
const list = this.localCache.has(key) ? this.localCache.get(key) : new Set<string>();
|
|
186
|
+
if (!(list instanceof Set)) {
|
|
187
|
+
throw new Error("cannot apply on non Set");
|
|
188
|
+
}
|
|
189
|
+
const ret = list.has(value);
|
|
190
|
+
this.localCache.set(key, list);
|
|
191
|
+
return ret;
|
|
192
|
+
}
|
|
193
|
+
set_clear(key: string): void {
|
|
194
|
+
this.logger?.trace("set_clear(%s)", key);
|
|
195
|
+
const list = this.localCache.has(key) ? this.localCache.get(key) : new Set<string>();
|
|
196
|
+
if (!(list instanceof Set)) {
|
|
197
|
+
throw new Error("cannot apply on non Set");
|
|
198
|
+
}
|
|
199
|
+
list.clear();
|
|
200
|
+
this.localCache.set(key, list);
|
|
201
|
+
}
|
|
202
|
+
array_push(key: string, value: unknown): void {
|
|
203
|
+
this.logger?.trace("array_push(%s)", key);
|
|
204
|
+
const list = this.localCache.has(key) ? this.localCache.get(key) : [];
|
|
205
|
+
if (!(list instanceof Array)) {
|
|
206
|
+
throw new Error("cannot apply on non Array");
|
|
207
|
+
}
|
|
208
|
+
list.push(value);
|
|
209
|
+
this.localCache.set(key, list);
|
|
210
|
+
if (process.send) {
|
|
211
|
+
setTimeout(() => {
|
|
212
|
+
process.send({
|
|
213
|
+
type: ClusterCacheType,
|
|
214
|
+
target: this.name,
|
|
215
|
+
action: "array_push",
|
|
216
|
+
fromPID: process.pid,
|
|
217
|
+
key,
|
|
218
|
+
value
|
|
219
|
+
} as ClusterCacheMessage);
|
|
220
|
+
}, 10);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
array_clear(key: string): void {
|
|
224
|
+
this.logger?.trace("array_clear(%s)", key);
|
|
225
|
+
if (this.localCache.has(key) && !(this.localCache.get(key) instanceof Array)) {
|
|
226
|
+
throw new Error("cannot apply on non Array");
|
|
227
|
+
}
|
|
228
|
+
this.localCache.set(key, []);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { Logger, WebSocketServer, WebSocketServerOptions } from "@miqro/core";
|
|
2
|
+
import { Serializable } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
const ClusterWebSocketServer2MessageType = "$$$$ClusterWebSocketServer2Message$$$$$";
|
|
5
|
+
|
|
6
|
+
export interface ClusterWebSocketServer2Message {
|
|
7
|
+
type: typeof ClusterWebSocketServer2MessageType;
|
|
8
|
+
target: string;
|
|
9
|
+
action: "connection" | "disconnection" | "sendMessage" | "error" | "sync";
|
|
10
|
+
clientUUID?: string;
|
|
11
|
+
errorMessage?: string;
|
|
12
|
+
fromUUID?: string;
|
|
13
|
+
fromPID: string,
|
|
14
|
+
payload?: Serializable;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class ClusterWebSocketServer2 extends WebSocketServer {
|
|
18
|
+
public remoteClients: Set<string> = new Set();
|
|
19
|
+
listener: (data: any) => Promise<void>;
|
|
20
|
+
public constructor(protected name: string, public path: string, public logger: Logger | Console, options: WebSocketServerOptions) {
|
|
21
|
+
super({
|
|
22
|
+
...options,
|
|
23
|
+
validate: (req) => {
|
|
24
|
+
if (
|
|
25
|
+
this.options.maxConnections !== undefined &&
|
|
26
|
+
this.clients.size + this.remoteClients.size >= this.options.maxConnections
|
|
27
|
+
) {
|
|
28
|
+
this.logger?.warn("[%s] max web socket connection reached! connection refused from [%s]", req.uuid, req.socket.remoteAddress);
|
|
29
|
+
return false;
|
|
30
|
+
} else {
|
|
31
|
+
return options.validate ? options.validate(req) : true;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
onError: (req, error) => {
|
|
35
|
+
if (process.send) {
|
|
36
|
+
process.send({
|
|
37
|
+
type: ClusterWebSocketServer2MessageType,
|
|
38
|
+
action: "error",
|
|
39
|
+
target: this.name,
|
|
40
|
+
clientUUID: req.uuid,
|
|
41
|
+
fromPID: String(process.pid),
|
|
42
|
+
errorMessage: error.message
|
|
43
|
+
} as ClusterWebSocketServer2Message);
|
|
44
|
+
}
|
|
45
|
+
this.logger?.error("[%s] error from (%s) error [%s]", req.uuid, req.req.socket.remoteAddress, error);
|
|
46
|
+
this.logger?.error(error);
|
|
47
|
+
if (options.onError) {
|
|
48
|
+
options.onError(req, error);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
onConnection: (req) => {
|
|
52
|
+
if (process.send) {
|
|
53
|
+
process.send({
|
|
54
|
+
type: ClusterWebSocketServer2MessageType,
|
|
55
|
+
action: "connection",
|
|
56
|
+
target: this.name,
|
|
57
|
+
fromPID: String(process.pid),
|
|
58
|
+
clientUUID: req.uuid
|
|
59
|
+
} as ClusterWebSocketServer2Message);
|
|
60
|
+
}
|
|
61
|
+
this.logger?.log("[%s] new web socket connection from (%s)", req.uuid, req.req.socket.remoteAddress);
|
|
62
|
+
if (options.onConnection) {
|
|
63
|
+
options.onConnection(req);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
onDisconnect: (req) => {
|
|
67
|
+
if (process.send) {
|
|
68
|
+
process.send({
|
|
69
|
+
type: ClusterWebSocketServer2MessageType,
|
|
70
|
+
action: "disconnection",
|
|
71
|
+
target: this.name,
|
|
72
|
+
fromPID: String(process.pid),
|
|
73
|
+
clientUUID: req.uuid
|
|
74
|
+
} as ClusterWebSocketServer2Message);
|
|
75
|
+
}
|
|
76
|
+
this.logger?.log("[%s] [%s] web socket disconnection from (%s)", req.uuid, this.path, req.req.socket.remoteAddress);
|
|
77
|
+
if (options.onDisconnect) {
|
|
78
|
+
options.onDisconnect(req);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
this.listener = async (data) => {
|
|
83
|
+
try {
|
|
84
|
+
const msg = (data as ClusterWebSocketServer2Message);
|
|
85
|
+
if (
|
|
86
|
+
msg &&
|
|
87
|
+
msg.type === ClusterWebSocketServer2MessageType &&
|
|
88
|
+
msg.target === this.name &&
|
|
89
|
+
msg.action) {
|
|
90
|
+
// receive message from cluster workers
|
|
91
|
+
//console.dir(data);
|
|
92
|
+
//this.logger?.debug("remote web socket server message from [%s] [%s] [%s]", msg.fromPID, msg.target, msg.action);
|
|
93
|
+
switch (msg.action) {
|
|
94
|
+
case "sync":
|
|
95
|
+
if (process.send) {
|
|
96
|
+
for (const clientUUID of this.clients.keys()) {
|
|
97
|
+
process.send({
|
|
98
|
+
type: ClusterWebSocketServer2MessageType,
|
|
99
|
+
action: "connection",
|
|
100
|
+
target: this.name,
|
|
101
|
+
fromPID: String(process.pid),
|
|
102
|
+
clientUUID
|
|
103
|
+
} as ClusterWebSocketServer2Message);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
case "connection":
|
|
108
|
+
if (!msg.clientUUID) {
|
|
109
|
+
throw new Error(`action [${msg.action}] without clientUUID`);
|
|
110
|
+
}
|
|
111
|
+
if (!this.remoteClients.has(msg.clientUUID)) {
|
|
112
|
+
this.remoteClients.add(msg.clientUUID);
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
case "disconnection":
|
|
116
|
+
if (!msg.clientUUID) {
|
|
117
|
+
throw new Error(`action [${msg.action}] without clientUUID`);
|
|
118
|
+
}
|
|
119
|
+
if (this.remoteClients.has(msg.clientUUID)) {
|
|
120
|
+
this.remoteClients.delete(msg.clientUUID);
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
case "sendMessage": {
|
|
124
|
+
const payload = String(msg.payload);
|
|
125
|
+
if (!msg.clientUUID) {
|
|
126
|
+
// broadcast to local clients
|
|
127
|
+
await super.broadcast(payload, msg.fromUUID);
|
|
128
|
+
} else if (this.isConnected(msg.clientUUID)) {
|
|
129
|
+
// write if local client
|
|
130
|
+
await super.writeTo(msg.clientUUID, payload);
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
default:
|
|
135
|
+
throw new Error(`action [${msg.action}] not supported`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch (e) {
|
|
139
|
+
console.error(e);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
this.connect();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public connect() {
|
|
147
|
+
if (process.send) {
|
|
148
|
+
process.removeListener("message", this.listener);
|
|
149
|
+
process.on("message", this.listener);
|
|
150
|
+
process.send({
|
|
151
|
+
type: ClusterWebSocketServer2MessageType,
|
|
152
|
+
action: "sync",
|
|
153
|
+
fromPID: String(process.pid),
|
|
154
|
+
target: this.name
|
|
155
|
+
} as ClusterWebSocketServer2Message);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public dispose() {
|
|
160
|
+
process.removeListener("message", this.listener);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public async broadcast(payload: string, fromUUID?: string): Promise<void> {
|
|
164
|
+
if (process.send) {
|
|
165
|
+
process.send({
|
|
166
|
+
type: ClusterWebSocketServer2MessageType,
|
|
167
|
+
action: "sendMessage",
|
|
168
|
+
payload,
|
|
169
|
+
target: this.name,
|
|
170
|
+
fromPID: String(process.pid),
|
|
171
|
+
fromUUID
|
|
172
|
+
} as ClusterWebSocketServer2Message);
|
|
173
|
+
}
|
|
174
|
+
return super.broadcast(payload, fromUUID);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public async writeTo(clientUUID: string, payload: string): Promise<void> {
|
|
178
|
+
if (this.remoteClients.has(clientUUID)) {
|
|
179
|
+
// write to remote client via IPC
|
|
180
|
+
if (process.send) {
|
|
181
|
+
process.send({
|
|
182
|
+
type: ClusterWebSocketServer2MessageType,
|
|
183
|
+
action: "sendMessage",
|
|
184
|
+
clientUUID,
|
|
185
|
+
target: this.name,
|
|
186
|
+
fromPID: String(process.pid),
|
|
187
|
+
payload
|
|
188
|
+
} as ClusterWebSocketServer2Message);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// due to the implementation of isUUIDValid depends on this.remoteClients to be sync there can be two clients registered with the same UUID in the cluster
|
|
193
|
+
if (this.clients.has(clientUUID)) {
|
|
194
|
+
// write to local client
|
|
195
|
+
return super.writeTo(clientUUID, payload);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public isUUIDValid(uuid: string) {
|
|
200
|
+
return !this.clients.has(uuid) && !this.remoteClients.has(uuid);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import cluster from "node:cluster";
|
|
2
|
+
import { Database } from "@miqro/query";
|
|
3
|
+
import { DBConfig } from "../../types.js";
|
|
4
|
+
import { LogProvider } from "./log.js";
|
|
5
|
+
import { Logger } from "@miqro/core";
|
|
6
|
+
|
|
7
|
+
export class DBManager {
|
|
8
|
+
private map = new Map<string, Database>();
|
|
9
|
+
public options: {
|
|
10
|
+
loggerProvider?: LogProvider;
|
|
11
|
+
logger?: Logger;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
constructor(options?: {
|
|
15
|
+
loggerProvider?: LogProvider;
|
|
16
|
+
logger?: Logger;
|
|
17
|
+
}) {
|
|
18
|
+
this.options = options ? options : {};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async setupDB(config: DBConfig) {
|
|
22
|
+
if (this.getDB(config.name)) {
|
|
23
|
+
throw new Error("cannot override db");
|
|
24
|
+
}
|
|
25
|
+
if (!config.disabled) {
|
|
26
|
+
const DB_IDENTIFIER = cluster.isPrimary ? "DATABASE" : process.env["CLUSTER_NODE_NUMBER"] ? `WORKER_${process.env["CLUSTER_NODE_NUMBER"]}_DATABASE` : "WORKER_DATABASE";
|
|
27
|
+
this.options?.logger?.debug("setting up db connection [%s]", config.name);
|
|
28
|
+
this.options?.logger?.trace("creating db connection [%s]", config.name);
|
|
29
|
+
const db = new Database({
|
|
30
|
+
dialect: config.dialect ? config.dialect as any : "node:sqlite",
|
|
31
|
+
storage: config.storage ? config.storage : "./db.sqlite3",
|
|
32
|
+
connectionString: config.url,
|
|
33
|
+
logger: this.options?.loggerProvider?.getLogger(`${DB_IDENTIFIER}_${config.name}`)
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
this.options?.logger?.trace("connecting db connection [%s]", config.name);
|
|
37
|
+
await db.connect();
|
|
38
|
+
this.options?.logger?.trace("db connection [%s] connected", config.name);
|
|
39
|
+
|
|
40
|
+
/*if (config.name && config.name !== name) {
|
|
41
|
+
this.setDB(config.name, db);
|
|
42
|
+
}*/
|
|
43
|
+
|
|
44
|
+
this.setDB(config.name, db);
|
|
45
|
+
|
|
46
|
+
return db;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async connectAll() {
|
|
51
|
+
this.options?.logger?.debug("connection all db connections");
|
|
52
|
+
const tR = [];
|
|
53
|
+
for (const name of this.map.keys()) {
|
|
54
|
+
const db = this.map.get(name);
|
|
55
|
+
if (db && db.status === "disconnected") {
|
|
56
|
+
this.options?.logger?.debug("connecting db connection [%s]", name);
|
|
57
|
+
tR.push(db.connect());
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
await Promise.all(tR);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async closeAll() {
|
|
64
|
+
this.options?.logger?.debug("closing all db connections");
|
|
65
|
+
for (const name of this.map.keys()) {
|
|
66
|
+
const db = this.map.get(name);
|
|
67
|
+
if (db && db.status === "connected") {
|
|
68
|
+
this.options?.logger?.debug("disconnecting db connection [%s]", name);
|
|
69
|
+
await db.disconnect();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async deleteAll() {
|
|
75
|
+
await this.closeAll();
|
|
76
|
+
this.options?.logger?.debug("clear all db connections");
|
|
77
|
+
this.map.clear();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getDB(name: string): Database | null {
|
|
81
|
+
const db = this.map.get(name);
|
|
82
|
+
return db ? db : null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
setDB(name: string, db: Database): void {
|
|
86
|
+
if (this.getDB(name)) {
|
|
87
|
+
throw new Error("cannot override db connection [" + name + "]");
|
|
88
|
+
}
|
|
89
|
+
this.map.set(name, db);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { APIRoute, normalizePath, RouterHandlerOptions } from "@miqro/core";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface RouterUseCall {
|
|
5
|
+
path?: string;
|
|
6
|
+
method?: string;
|
|
7
|
+
inflatePath: string;
|
|
8
|
+
defaultInflatePath: string;
|
|
9
|
+
options?: RouterHandlerOptions;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getRoutes(prePath: string, defaultPath: string, apiOptions?: Partial<APIRoute>): RouterUseCall[] {
|
|
13
|
+
const ret: RouterUseCall[] = [];
|
|
14
|
+
const path = apiOptions && apiOptions.path ? apiOptions.path : defaultPath;
|
|
15
|
+
const defaultInflatePath = join(prePath, defaultPath);
|
|
16
|
+
const method = apiOptions && apiOptions.method ? apiOptions.method : "GET";
|
|
17
|
+
|
|
18
|
+
const methods = method instanceof Array ? method : [method];
|
|
19
|
+
const paths = path instanceof Array ? path : [path];
|
|
20
|
+
|
|
21
|
+
if (apiOptions && apiOptions.path === null) {
|
|
22
|
+
for (const m of methods) {
|
|
23
|
+
ret.push({
|
|
24
|
+
options: {
|
|
25
|
+
apiName: apiOptions?.apiName,
|
|
26
|
+
description: apiOptions?.description,
|
|
27
|
+
identifier: apiOptions?.identifier,
|
|
28
|
+
middleware: apiOptions?.middleware,
|
|
29
|
+
name: apiOptions?.name,
|
|
30
|
+
parser: apiOptions?.parser,
|
|
31
|
+
policy: apiOptions?.policy,
|
|
32
|
+
request: apiOptions?.request,
|
|
33
|
+
response: apiOptions?.response,
|
|
34
|
+
session: apiOptions?.session
|
|
35
|
+
},
|
|
36
|
+
inflatePath: defaultInflatePath,
|
|
37
|
+
defaultInflatePath,
|
|
38
|
+
method: m.toLocaleLowerCase() !== "use" ? m.toUpperCase() : undefined,
|
|
39
|
+
path: undefined
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
for (const p of paths) {
|
|
44
|
+
for (const m of methods) {
|
|
45
|
+
ret.push({
|
|
46
|
+
options: {
|
|
47
|
+
apiName: apiOptions?.apiName,
|
|
48
|
+
description: apiOptions?.description,
|
|
49
|
+
identifier: apiOptions?.identifier,
|
|
50
|
+
middleware: apiOptions?.middleware,
|
|
51
|
+
name: apiOptions?.name,
|
|
52
|
+
parser: apiOptions?.parser,
|
|
53
|
+
policy: apiOptions?.policy,
|
|
54
|
+
request: apiOptions?.request,
|
|
55
|
+
response: apiOptions?.response,
|
|
56
|
+
session: apiOptions?.session
|
|
57
|
+
},
|
|
58
|
+
inflatePath: p !== "/" ? join(prePath, p) : defaultInflatePath,
|
|
59
|
+
defaultInflatePath,
|
|
60
|
+
method: m.toLocaleLowerCase() !== "use" ? m.toUpperCase() : undefined,
|
|
61
|
+
path: normalizePath(join(prePath, p))
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
return ret;
|
|
70
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { decodeJWT, decodeProtectedHeaderJWT, decryptJWT, encryptJWT, signJWT, verifyJWT } from "../../common/jwt.js";
|
|
2
|
+
import { JWTInterface } from "../../types.js";
|
|
3
|
+
import { createSecretKey } from "node:crypto";
|
|
4
|
+
|
|
5
|
+
export const jwt: JWTInterface = {
|
|
6
|
+
createSecretKey,
|
|
7
|
+
decode(jwt) {
|
|
8
|
+
return decodeJWT(jwt);
|
|
9
|
+
},
|
|
10
|
+
decodeProtectedHeader(token) {
|
|
11
|
+
return decodeProtectedHeaderJWT(token);
|
|
12
|
+
},
|
|
13
|
+
decrypt(jwt, secret, options) {
|
|
14
|
+
return decryptJWT(jwt, secret, options);
|
|
15
|
+
},
|
|
16
|
+
encrypt(payload, secret, options) {
|
|
17
|
+
return encryptJWT(payload, secret, options);
|
|
18
|
+
},
|
|
19
|
+
sign(payload, secret, options) {
|
|
20
|
+
return signJWT(payload, secret, options);
|
|
21
|
+
},
|
|
22
|
+
verify(jwt, secret, options) {
|
|
23
|
+
return verifyJWT(jwt, secret, options);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ConsoleTransport, FileTransport, LoggerTransport, LoggerTransportWriteArgs, LogLevel, WriteArgs } from "@miqro/core";
|
|
2
|
+
import { format } from "node:util";
|
|
3
|
+
import { LOG_SOCKET_PATH, LOG_WRITE_EVENT } from "../../../editor/common/constants.js";
|
|
4
|
+
import { Miqro } from "../app.js";
|
|
5
|
+
|
|
6
|
+
export function createLogProviderOptions(app: Miqro) {
|
|
7
|
+
const defaultConsole = ConsoleTransport();
|
|
8
|
+
//console.log("app.options.logFile [%s]", app.options.logFile);
|
|
9
|
+
const defaultFile: LoggerTransport | undefined =
|
|
10
|
+
app.options.logFile !== true && app.options.logFile !== false && String(app.options.logFile).toUpperCase() !== "TRUE" && String(app.options.logFile).toUpperCase() !== "FALSE" &&
|
|
11
|
+
app.options.logFile ? FileTransport(app.options.logFile) :
|
|
12
|
+
String(app.options.logFile).toUpperCase() === "TRUE" || app.options.logFile === true || app.options.logFile === undefined ?
|
|
13
|
+
FileTransport() :
|
|
14
|
+
undefined;
|
|
15
|
+
const defaultWrite = async (args: LoggerTransportWriteArgs, level?: LogLevel) => {
|
|
16
|
+
try {
|
|
17
|
+
const serviceNamesWithLogConfigReplaceConsole = level === undefined && app.inflated ?
|
|
18
|
+
Object.keys(app.inflated.logConfigMap).filter(serviceName => app.inflated.logConfigMap[serviceName].replaceConsoleTransport) : [];
|
|
19
|
+
const serviceNamesWithLogConfigReplaceFile = level === undefined && app.inflated ?
|
|
20
|
+
Object.keys(app.inflated.logConfigMap).filter(serviceName => app.inflated.logConfigMap[serviceName].replaceFileTransport) : [];
|
|
21
|
+
await Promise.allSettled((level === undefined ?
|
|
22
|
+
[
|
|
23
|
+
level === undefined && serviceNamesWithLogConfigReplaceConsole.length === 0 && defaultConsole ?
|
|
24
|
+
defaultConsole.write(args) : Promise.resolve(),
|
|
25
|
+
level === undefined && serviceNamesWithLogConfigReplaceFile.length === 0 && defaultFile ?
|
|
26
|
+
defaultFile.write(args) : Promise.resolve()
|
|
27
|
+
] : []).concat(app.inflated ?
|
|
28
|
+
Object.keys(app.inflated.logConfigMap).map(serviceName => app.inflated.logConfigMap[serviceName]).filter(c => c.level === level).map(c => c.write(args)) : []
|
|
29
|
+
));
|
|
30
|
+
} catch (e) {
|
|
31
|
+
console.error(e);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
name: app.options.name,
|
|
36
|
+
formatter: (args: WriteArgs) => {
|
|
37
|
+
const params: string[] = args.optionalParams;
|
|
38
|
+
return format(
|
|
39
|
+
`${new Date().toISOString()} PID[${process.pid}] ${args.identifier ? `[${args.identifier}] ` : ""}${args.level !== "info" ? (args.level === "error" || args.level === "warn" ? `[${args.level.toUpperCase()}] ` : `[${args.level}] `) : ""}${args.message}`,
|
|
40
|
+
...params)
|
|
41
|
+
},
|
|
42
|
+
transports: [
|
|
43
|
+
...(([undefined, "error", "warn", "info", "debug", "trace"] as LogLevel[]).map(level => {
|
|
44
|
+
return level ? {
|
|
45
|
+
level,
|
|
46
|
+
write: async (args) => {
|
|
47
|
+
await defaultWrite(args, level);
|
|
48
|
+
}
|
|
49
|
+
} : {
|
|
50
|
+
write: async (args) => {
|
|
51
|
+
await defaultWrite(args, undefined);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
})), {
|
|
55
|
+
level: "trace" as LogLevel,
|
|
56
|
+
write: async (args) => {
|
|
57
|
+
try {
|
|
58
|
+
if (app.options.editor) {
|
|
59
|
+
try {
|
|
60
|
+
const ws = app.webSocketManager.getWS(LOG_SOCKET_PATH);
|
|
61
|
+
if (ws) {
|
|
62
|
+
//console.log("\n\n" + process.pid + " broadcasting " + LOG_SOCKET_PATH + "\n\n\n")
|
|
63
|
+
await ws.broadcast(JSON.stringify({
|
|
64
|
+
type: LOG_WRITE_EVENT,
|
|
65
|
+
level: args.level,
|
|
66
|
+
identifier: args.identifier,
|
|
67
|
+
out: args.out
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
console.error(e);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.error(e);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}],
|
|
79
|
+
...(app.options.logProviderOptions ? app.options.logProviderOptions : {})
|
|
80
|
+
};
|
|
81
|
+
}
|