miqro 6.1.4 → 6.2.0
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/editor.bundle.js +10 -2
- package/build/esm/editor/components/editor.js +8 -0
- package/build/esm/editor/components/file-editor.js +1 -1
- package/build/esm/editor/components/highlight-text-area.js +2 -1
- package/build/esm/src/common/jwt.d.ts +49 -0
- package/build/esm/src/common/jwt.js +76 -0
- package/build/esm/src/inflate/inflate-sea.js +9 -8
- package/build/esm/src/inflate/setup-http.d.ts +1 -0
- package/build/esm/src/inflate/setup-http.js +8 -0
- package/build/esm/src/lib.d.ts +1 -1
- package/build/esm/src/lib.js +2 -2
- package/build/esm/src/services/app.js +5 -4
- package/build/esm/src/services/globals.js +45 -1
- package/build/esm/src/services/utils/cluster-ws.d.ts +4 -2
- package/build/esm/src/services/utils/cluster-ws.js +10 -1
- package/build/esm/src/services/utils/server-interface.d.ts +4 -30
- package/build/esm/src/services/utils/server-interface.js +44 -64
- package/build/esm/src/services/utils/websocketmanager.d.ts +3 -0
- package/build/esm/src/services/utils/websocketmanager.js +8 -2
- package/build/esm/src/types.d.ts +76 -5
- package/build/lib.cjs +2983 -362
- package/editor/components/editor.tsx +8 -0
- package/editor/components/file-editor.tsx +1 -1
- package/editor/components/highlight-text-area.tsx +2 -1
- package/package.json +4 -3
- package/sea/install-nodejs.sh +1 -1
- package/sea/node.version.tag +1 -1
- package/sea/types.json +1 -1
- package/src/common/jwt.ts +85 -0
- package/src/inflate/inflate-sea.ts +9 -8
- package/src/inflate/setup-http.ts +9 -0
- package/src/lib.ts +2 -2
- package/src/services/app.ts +6 -4
- package/src/services/globals.ts +45 -2
- package/src/services/utils/cluster-ws.ts +6 -1
- package/src/services/utils/server-interface.ts +44 -140
- package/src/services/utils/websocketmanager.ts +10 -2
- package/src/types/cookie.d.ts +2 -0
- package/src/types/jose.d.ts +2 -0
- package/src/types/miqro.d.ts +92 -2
- package/src/types/server.globals.d.ts +4 -29
- package/src/types.ts +78 -5
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { EncryptJWT, EncryptOptions, JWTDecryptOptions, JWTDecryptResult, JWTPayload, JWTVerifyOptions, JWTVerifyResult, ProtectedHeaderParameters, SignJWT, SignOptions, decodeJwt, decodeProtectedHeader, jwtDecrypt as joseJWTDecrypt, jwtVerify } from "jose";
|
|
2
|
+
import { KeyObject } from "node:crypto";
|
|
3
|
+
import { EncryptJWTOptions, JWTSignOptions } from "../types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* creates a JWT encrypted token with jose
|
|
7
|
+
*
|
|
8
|
+
* @param payload the payload to encrypt
|
|
9
|
+
* @param secret the secret example. const secret = createSecretKey(process.env.JWT_SECRET, 'utf-8');
|
|
10
|
+
* @param options options like expiratation date, issuer and audience
|
|
11
|
+
* @returns
|
|
12
|
+
*/
|
|
13
|
+
export async function encryptJWT(payload: JWTPayload, secret: KeyObject, options?: Partial<EncryptJWTOptions>): Promise<string> {
|
|
14
|
+
const en = new EncryptJWT(payload)
|
|
15
|
+
.setProtectedHeader({
|
|
16
|
+
alg: options?.alg ? options?.alg : 'dir',
|
|
17
|
+
enc: options?.enc ? options?.enc : 'A128CBC-HS256'
|
|
18
|
+
})
|
|
19
|
+
.setIssuedAt(options?.iat)
|
|
20
|
+
.setIssuer(options?.iss ? options?.iss : 'urn:example:issuer')
|
|
21
|
+
.setAudience(options?.aud ? options?.aud : 'urn:example:audience')
|
|
22
|
+
.setExpirationTime(options?.exp ? options?.exp : '2h');
|
|
23
|
+
|
|
24
|
+
return await en.encrypt(secret, options?.options);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* decrypts a JWT token with jose
|
|
29
|
+
* @param jwt the JWT token
|
|
30
|
+
* @param secret the secret example. const secret = createSecretKey(process.env.JWT_SECRET, 'utf-8');
|
|
31
|
+
* @param options options like issuer and audience
|
|
32
|
+
* @returns
|
|
33
|
+
*/
|
|
34
|
+
export async function decryptJWT<PayloadType = JWTPayload>(jwt: string, secret: KeyObject, options?: Partial<JWTDecryptOptions>): Promise<JWTDecryptResult<PayloadType>> {
|
|
35
|
+
return await joseJWTDecrypt(jwt, secret, options)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* verify a JWT token with jose
|
|
40
|
+
* @param jwt the JWT token
|
|
41
|
+
* @param secret the secret example. const secret = createSecretKey(process.env.JWT_SECRET, 'utf-8');
|
|
42
|
+
* @param options options like issuer and audience
|
|
43
|
+
* @returns
|
|
44
|
+
*/
|
|
45
|
+
export async function verifyJWT<PayloadType = JWTPayload>(jwt: string, secret: KeyObject, options?: Partial<JWTVerifyOptions>): Promise<JWTVerifyResult<PayloadType>> {
|
|
46
|
+
return await jwtVerify(jwt, secret, options)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* creates a signed JWT with jose
|
|
51
|
+
*
|
|
52
|
+
* @param payload the payload to encrypt
|
|
53
|
+
* @param secret the secret example. const secret = createSecretKey(process.env.JWT_SECRET, 'utf-8');
|
|
54
|
+
* @param options options like expiratation date, issuer and audience
|
|
55
|
+
* @returns
|
|
56
|
+
*/
|
|
57
|
+
export async function signJWT(payload: JWTPayload, secret: KeyObject, options?: Partial<JWTSignOptions>): Promise<string> {
|
|
58
|
+
const sign = new SignJWT(payload)
|
|
59
|
+
.setProtectedHeader({
|
|
60
|
+
alg: options?.alg ? options?.alg : 'HS256',
|
|
61
|
+
})
|
|
62
|
+
.setIssuedAt(options?.iat)
|
|
63
|
+
.setIssuer(options?.iss ? options?.iss : 'urn:example:issuer')
|
|
64
|
+
.setAudience(options?.aud ? options?.aud : 'urn:example:audience')
|
|
65
|
+
.setExpirationTime(options?.exp ? options?.exp : '2h');
|
|
66
|
+
return sign.sign(secret, options?.options);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* decodes a protected header with jose
|
|
71
|
+
* @param token
|
|
72
|
+
* @returns
|
|
73
|
+
*/
|
|
74
|
+
export function decodeProtectedHeaderJWT(token: string | object): ProtectedHeaderParameters {
|
|
75
|
+
return decodeProtectedHeader(token);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* decodes a jwt token
|
|
80
|
+
* @param jwt
|
|
81
|
+
* @returns
|
|
82
|
+
*/
|
|
83
|
+
export function decodeJWT<PayloadType = JWTPayload>(jwt: string): PayloadType & JWTPayload {
|
|
84
|
+
return decodeJwt<PayloadType>(jwt)
|
|
85
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Logger } from "@miqro/core";
|
|
2
2
|
import { chmodSync, constants, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { dirname, extname, join, relative, resolve } from "node:path";
|
|
3
|
+
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
4
4
|
import { cwd, platform } from "node:process";
|
|
5
5
|
|
|
6
6
|
import { RouteFileMap, StaticFileMap } from "./setup-http.js";
|
|
@@ -66,22 +66,22 @@ export async function inflateAppForSea(logger: Logger, inflateDir: string, servi
|
|
|
66
66
|
|
|
67
67
|
writeFile(logger, join(inflateDir, "sea", "package.json"), `{ "type": "module", "private": true }`);
|
|
68
68
|
|
|
69
|
-
writeFile(logger, join(inflateDir, "sea", "app.cjs"), `const {
|
|
69
|
+
writeFile(logger, join(inflateDir, "sea", "app.cjs"), `const { createServerInterface, ServerRequestHandler, WebSocketManager, initGlobals, DBManager, App, LoggerHandler, LogProvider, LocalCache, ClusterCache } = require("./lib.cjs");
|
|
70
70
|
|
|
71
71
|
async function main() {
|
|
72
72
|
const PORT = process.env["PORT"] ? process.env["PORT"] : 8080;
|
|
73
|
-
const
|
|
73
|
+
const loggerProvider = new LogProvider();
|
|
74
74
|
const localCache = new LocalCache();
|
|
75
75
|
const cache = new ClusterCache();
|
|
76
76
|
const webSocketManager = new WebSocketManager();
|
|
77
77
|
const dbManager = new DBManager();
|
|
78
78
|
await initGlobals();
|
|
79
|
-
const serverInterface =
|
|
79
|
+
const serverInterface = createServerInterface({
|
|
80
80
|
cache,
|
|
81
81
|
localCache,
|
|
82
|
-
|
|
82
|
+
loggerProvider,
|
|
83
83
|
wsManager: webSocketManager,
|
|
84
|
-
logger:
|
|
84
|
+
logger: loggerProvider.getLogger("server"),
|
|
85
85
|
dbManager,
|
|
86
86
|
port: PORT
|
|
87
87
|
});
|
|
@@ -133,9 +133,10 @@ ${Object.keys(serviceRouteFileMap)
|
|
|
133
133
|
.map(filePath => serviceRouteFileMap[filePath])
|
|
134
134
|
.filter(data => data.previewMethod === "api")
|
|
135
135
|
.map(data => data.routes.map(r => {
|
|
136
|
-
const rPath =
|
|
136
|
+
const rPath = join(relative(cwd(), dirname(data.filePath)), basename(data.filePath));
|
|
137
137
|
if (rPath) {
|
|
138
|
-
const
|
|
138
|
+
const rPathExt = extname(rPath);
|
|
139
|
+
const apiInflatedPath = join("..", "..", rPath.substring(0, rPath.length - rPathExt.length) + ".js");
|
|
139
140
|
return ` await appendAPIModule(router, "../../${service}/http", "./${apiInflatedPath}", (await import("./${apiInflatedPath}")).default);`;
|
|
140
141
|
} else {
|
|
141
142
|
return "";
|
|
@@ -27,6 +27,7 @@ export interface RouteFileMap {
|
|
|
27
27
|
options?: RouterHandlerOptions;
|
|
28
28
|
inflatePath?: string;
|
|
29
29
|
}[];
|
|
30
|
+
filePath: string;
|
|
30
31
|
service: string;
|
|
31
32
|
previewMethod: "api" | "html" | null;
|
|
32
33
|
}
|
|
@@ -93,6 +94,7 @@ function createStaticRoute(service: string, logger: Logger, router: Router, dir:
|
|
|
93
94
|
path: normalizePath(path)
|
|
94
95
|
}],
|
|
95
96
|
service,
|
|
97
|
+
filePath: file.filePath,
|
|
96
98
|
previewMethod: "html"
|
|
97
99
|
};
|
|
98
100
|
|
|
@@ -185,6 +187,7 @@ async function createRouterFromDirectory(server: ServerInterface, hotreload: boo
|
|
|
185
187
|
routeFileMap[file.filePath] = {
|
|
186
188
|
routes,
|
|
187
189
|
service,
|
|
190
|
+
filePath: file.filePath,
|
|
188
191
|
previewMethod: "api"
|
|
189
192
|
};
|
|
190
193
|
|
|
@@ -230,6 +233,7 @@ async function createRouterFromDirectory(server: ServerInterface, hotreload: boo
|
|
|
230
233
|
routeFileMap[file.filePath] = {
|
|
231
234
|
routes,
|
|
232
235
|
service,
|
|
236
|
+
filePath: file.filePath,
|
|
233
237
|
previewMethod: "html"
|
|
234
238
|
};
|
|
235
239
|
|
|
@@ -297,6 +301,7 @@ async function createRouterFromDirectory(server: ServerInterface, hotreload: boo
|
|
|
297
301
|
|
|
298
302
|
routeFileMap[file.filePath] = {
|
|
299
303
|
routes,
|
|
304
|
+
filePath: file.filePath,
|
|
300
305
|
service,
|
|
301
306
|
previewMethod: "html"
|
|
302
307
|
};
|
|
@@ -375,6 +380,7 @@ async function createRouterFromDirectory(server: ServerInterface, hotreload: boo
|
|
|
375
380
|
method: "GET",
|
|
376
381
|
path
|
|
377
382
|
}],
|
|
383
|
+
filePath: file.filePath,
|
|
378
384
|
service,
|
|
379
385
|
previewMethod: "html"
|
|
380
386
|
};
|
|
@@ -428,6 +434,7 @@ async function createRouterFromDirectory(server: ServerInterface, hotreload: boo
|
|
|
428
434
|
path
|
|
429
435
|
}],
|
|
430
436
|
service,
|
|
437
|
+
filePath: file.filePath,
|
|
431
438
|
previewMethod: "html"
|
|
432
439
|
};
|
|
433
440
|
|
|
@@ -487,6 +494,7 @@ async function createRouterFromDirectory(server: ServerInterface, hotreload: boo
|
|
|
487
494
|
method: "GET",
|
|
488
495
|
path
|
|
489
496
|
}],
|
|
497
|
+
filePath: file.filePath,
|
|
490
498
|
service,
|
|
491
499
|
previewMethod: "html"
|
|
492
500
|
};
|
|
@@ -545,6 +553,7 @@ async function createRouterFromDirectory(server: ServerInterface, hotreload: boo
|
|
|
545
553
|
path
|
|
546
554
|
}],
|
|
547
555
|
service,
|
|
556
|
+
filePath: file.filePath,
|
|
548
557
|
previewMethod: "html"
|
|
549
558
|
};
|
|
550
559
|
|
package/src/lib.ts
CHANGED
|
@@ -10,10 +10,10 @@ export { DBManager } from "./services/utils/db-manager.js";
|
|
|
10
10
|
export { LogProvider, LogProviderOptions } from "./services/utils/log.js";
|
|
11
11
|
export { Miqro, MiqroOptions, ServerRequestHandler } from "./services/app.js";
|
|
12
12
|
export { initGlobals, assertGlobalTampered } from "./services/globals.js";
|
|
13
|
-
export { appendAPIModule } from "./inflate/utils/sea-utils.js";
|
|
14
13
|
|
|
15
14
|
//exported for --inflate-sea
|
|
16
|
-
export {
|
|
15
|
+
export { appendAPIModule } from "./inflate/utils/sea-utils.js";
|
|
16
|
+
export { createServerInterface, ServerInterfaceImplOptions } from "./services/utils/server-interface.js";
|
|
17
17
|
export { App, LoggerHandler, Router } from "@miqro/core";
|
|
18
18
|
export { migration } from "@miqro/query";
|
|
19
19
|
|
package/src/services/app.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { setupExitHandlers } from "../common/exit.js";
|
|
|
25
25
|
import { inflateDBConfig, inflateDBMigrations, MigrationModule } from "../inflate/setup-db.js";
|
|
26
26
|
import { getServicePath } from "../common/paths.js";
|
|
27
27
|
import { LogConfigMap } from "../inflate/setup-log.js";
|
|
28
|
-
import {
|
|
28
|
+
import { createServerInterface } from "./utils/server-interface.js";
|
|
29
29
|
import { getPORT, importMiqroJSON } from "../common/arguments.js";
|
|
30
30
|
import { createLogProviderOptions } from "./utils/log-transport.js";
|
|
31
31
|
import { createAdminInterface } from "./utils/admin-interface.js";
|
|
@@ -139,6 +139,7 @@ export class Miqro {
|
|
|
139
139
|
this.localCache = new LocalCache(`MiqroApplicationLocalCache[${this.options.name}]`, this.logger);
|
|
140
140
|
this.webSocketManager = new WebSocketManager({
|
|
141
141
|
logger: this.logger,
|
|
142
|
+
loggerProvider: this.loggerProvider,
|
|
142
143
|
name: `MiqroApplicationWebsocketManager[${this.options.name}]`,
|
|
143
144
|
avoidLogSocket: this.options.editor
|
|
144
145
|
});
|
|
@@ -150,16 +151,17 @@ export class Miqro {
|
|
|
150
151
|
logger: this.logger
|
|
151
152
|
});
|
|
152
153
|
|
|
153
|
-
this.serverInterface =
|
|
154
|
+
this.serverInterface = createServerInterface({
|
|
154
155
|
cache: this.cache,
|
|
155
|
-
localCache: this.localCache,
|
|
156
156
|
dbManager: this.dbManager,
|
|
157
|
-
|
|
157
|
+
localCache: this.localCache,
|
|
158
|
+
webSocketManager: this.webSocketManager,
|
|
158
159
|
app: this,
|
|
159
160
|
logger: this.logger,
|
|
160
161
|
loggerProvider: this.loggerProvider,
|
|
161
162
|
port: this.options.port
|
|
162
163
|
});
|
|
164
|
+
|
|
163
165
|
this.serverRequestHandler = ServerRequestHandler(this.serverInterface);
|
|
164
166
|
|
|
165
167
|
this.adminInterface = createAdminInterface(this);
|
package/src/services/globals.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
//@ts-ignore
|
|
2
2
|
import { ReadBuffer, URLEncodedParser, JSONParser, TextParser, CORS, SessionHandler } from "@miqro/core";
|
|
3
|
+
import cluster from "node:cluster";
|
|
3
4
|
import { strictEqual } from "node:assert";
|
|
4
5
|
import { HTMLEncode } from "@miqro/jsx-node";
|
|
5
6
|
import { createElement as realCreateElement, enableDebugLog, useRuntime, Link, Router, usePathname, Fragment, useEffect, useRef, useState, useQuery, useRefresh, useElement, createContext, useContext, Component, Props } from "@miqro/jsx";
|
|
@@ -7,7 +8,10 @@ import { createElement as realCreateElement, enableDebugLog, useRuntime, Link, R
|
|
|
7
8
|
import { jsx2HTML } from "../common/jsx.js";
|
|
8
9
|
import { inflateMD2HTML } from "../inflate/md.js";
|
|
9
10
|
import { EXIT_CODES } from "../common/constants.js";
|
|
10
|
-
import { ServerGlobal } from "../lib.js";
|
|
11
|
+
import { ClusterCache, LocalCache, ServerGlobal } from "../lib.js";
|
|
12
|
+
import { decodeJWT, decodeProtectedHeaderJWT, decryptJWT, encryptJWT, signJWT, verifyJWT } from "../common/jwt.js";
|
|
13
|
+
import { createSecretKey } from "node:crypto";
|
|
14
|
+
import { Parser } from "@miqro/parser";
|
|
11
15
|
|
|
12
16
|
/*const globaljsx: any = Object.freeze({
|
|
13
17
|
useContext,
|
|
@@ -83,7 +87,46 @@ const globalServer: ServerGlobal = Object.freeze<ServerGlobal>({
|
|
|
83
87
|
session: SessionHandler
|
|
84
88
|
}),
|
|
85
89
|
encodeHTML: HTMLEncode,
|
|
86
|
-
inflateMDtoHTML: inflateMD2HTML
|
|
90
|
+
inflateMDtoHTML: inflateMD2HTML,
|
|
91
|
+
createSecretKey,
|
|
92
|
+
newParser() {
|
|
93
|
+
return new Parser();
|
|
94
|
+
},
|
|
95
|
+
newClusterCache(name, logger) {
|
|
96
|
+
return new ClusterCache(name, logger);
|
|
97
|
+
},
|
|
98
|
+
newLocalCache(name, logger) {
|
|
99
|
+
return new LocalCache(name, logger);
|
|
100
|
+
},
|
|
101
|
+
getWorkerNumber(): number {
|
|
102
|
+
return cluster.isPrimary || process.env["CLUSTER_NODE_NUMBER"] === undefined ? 0 : parseInt(process.env["CLUSTER_NODE_NUMBER"], 10);
|
|
103
|
+
},
|
|
104
|
+
getWorkerCount(): number {
|
|
105
|
+
return cluster.isPrimary || process.env["CLUSTER_NODE_NUMBER"] === undefined || process.env["CLUSTER_COUNT"] === undefined ? 1 : parseInt(process.env["CLUSTER_COUNT"], 10);
|
|
106
|
+
},
|
|
107
|
+
isPrimaryWorker(): boolean {
|
|
108
|
+
return cluster.isPrimary || process.env["CLUSTER_NODE_NUMBER"] === "0";
|
|
109
|
+
},
|
|
110
|
+
jwt: {
|
|
111
|
+
decode(jwt) {
|
|
112
|
+
return decodeJWT(jwt);
|
|
113
|
+
},
|
|
114
|
+
decodeProtectedHeader(token) {
|
|
115
|
+
return decodeProtectedHeaderJWT(token);
|
|
116
|
+
},
|
|
117
|
+
decrypt(jwt, secret, options) {
|
|
118
|
+
return decryptJWT(jwt, secret, options);
|
|
119
|
+
},
|
|
120
|
+
encrypt(payload, secret, options) {
|
|
121
|
+
return encryptJWT(payload, secret, options);
|
|
122
|
+
},
|
|
123
|
+
sign(payload, secret, options) {
|
|
124
|
+
return signJWT(payload, secret, options);
|
|
125
|
+
},
|
|
126
|
+
verify(jwt, secret, options) {
|
|
127
|
+
return verifyJWT(jwt, secret, options);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
87
130
|
}) as ServerGlobal;
|
|
88
131
|
|
|
89
132
|
export function browserJSXGlobals(inFile: string, jsxPath: string | false = false, useExport = true): string {
|
|
@@ -17,7 +17,7 @@ export interface ClusterWebSocketServer2Message {
|
|
|
17
17
|
export class ClusterWebSocketServer2 extends WebSocketServer {
|
|
18
18
|
public remoteClients: Set<string> = new Set();
|
|
19
19
|
listener: (data: any) => Promise<void>;
|
|
20
|
-
public constructor(protected name: string, options: WebSocketServerOptions) {
|
|
20
|
+
public constructor(protected name: string, public path: string, public logger: Logger | Console, options: WebSocketServerOptions) {
|
|
21
21
|
super({
|
|
22
22
|
...options,
|
|
23
23
|
validate: (req) => {
|
|
@@ -25,6 +25,7 @@ export class ClusterWebSocketServer2 extends WebSocketServer {
|
|
|
25
25
|
this.options.maxConnections !== undefined &&
|
|
26
26
|
this.clients.size + this.remoteClients.size >= this.options.maxConnections
|
|
27
27
|
) {
|
|
28
|
+
this.logger?.warn("[%s] max web socket connection reached! connection refused from [%s]", req.uuid, req.socket.remoteAddress);
|
|
28
29
|
return false;
|
|
29
30
|
} else {
|
|
30
31
|
return options.validate ? options.validate(req) : true;
|
|
@@ -41,6 +42,8 @@ export class ClusterWebSocketServer2 extends WebSocketServer {
|
|
|
41
42
|
errorMessage: error.message
|
|
42
43
|
} as ClusterWebSocketServer2Message);
|
|
43
44
|
}
|
|
45
|
+
this.logger?.error("[%s] error from (%s) error [%s]", req.uuid, req.req.socket.remoteAddress, error);
|
|
46
|
+
this.logger?.error(error);
|
|
44
47
|
if (options.onError) {
|
|
45
48
|
options.onError(req, error);
|
|
46
49
|
}
|
|
@@ -55,6 +58,7 @@ export class ClusterWebSocketServer2 extends WebSocketServer {
|
|
|
55
58
|
clientUUID: req.uuid
|
|
56
59
|
} as ClusterWebSocketServer2Message);
|
|
57
60
|
}
|
|
61
|
+
this.logger?.log("[%s] new web socket connection from (%s)", req.uuid, req.req.socket.remoteAddress);
|
|
58
62
|
if (options.onConnection) {
|
|
59
63
|
options.onConnection(req);
|
|
60
64
|
}
|
|
@@ -69,6 +73,7 @@ export class ClusterWebSocketServer2 extends WebSocketServer {
|
|
|
69
73
|
clientUUID: req.uuid
|
|
70
74
|
} as ClusterWebSocketServer2Message);
|
|
71
75
|
}
|
|
76
|
+
this.logger?.log("[%s] [%s] web socket disconnection from (%s)", req.uuid, this.path, req.req.socket.remoteAddress);
|
|
72
77
|
if (options.onDisconnect) {
|
|
73
78
|
options.onDisconnect(req);
|
|
74
79
|
}
|
|
@@ -7,137 +7,33 @@ import { Miqro } from "../app.js";
|
|
|
7
7
|
import { WebSocketManager } from "./websocketmanager.js";
|
|
8
8
|
import { execSync } from "node:child_process";
|
|
9
9
|
import { LogProvider } from "./log.js";
|
|
10
|
-
|
|
11
|
-
/*
|
|
12
|
-
{
|
|
13
|
-
cache: this.cache,
|
|
14
|
-
localCache: this.localCache,
|
|
15
|
-
db: {
|
|
16
|
-
get: (name: string) => {
|
|
17
|
-
return this.dbManager.getDB(name);
|
|
18
|
-
},
|
|
19
|
-
getMigrations: () => {
|
|
20
|
-
if (this.inflated) {
|
|
21
|
-
const ret: NamedMigration[] = [];
|
|
22
|
-
for (const d of this.inflated.dbList) {
|
|
23
|
-
ret.push(...(d.migrations.map(m => {
|
|
24
|
-
return {
|
|
25
|
-
name: m.name,
|
|
26
|
-
service: m.service,
|
|
27
|
-
dbName: m.dbName
|
|
28
|
-
}
|
|
29
|
-
})));
|
|
30
|
-
}
|
|
31
|
-
return ret;
|
|
32
|
-
}
|
|
33
|
-
return [];
|
|
34
|
-
},
|
|
35
|
-
migrate: (options) => {
|
|
36
|
-
return this.migrate(options);
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
ws: {
|
|
40
|
-
get: (path: string) => {
|
|
41
|
-
if (path === LOG_SOCKET_PATH) {
|
|
42
|
-
throw new Error("cannot use this path");
|
|
43
|
-
}
|
|
44
|
-
return this.webSocketManager.getWS(path);
|
|
45
|
-
},
|
|
46
|
-
disconnectAll: (path: string) => {
|
|
47
|
-
if (path === LOG_SOCKET_PATH) {
|
|
48
|
-
throw new Error("cannot use this path");
|
|
49
|
-
}
|
|
50
|
-
this.webSocketManager.disconnectAllFrom(path);
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
isPrimaryWorker: () => {
|
|
54
|
-
return cluster.isPrimary || process.env["CLUSTER_NODE_NUMBER"] === "0";
|
|
55
|
-
},
|
|
56
|
-
openBrowser: (path: string) => {
|
|
57
|
-
const PORT = this.options.port;
|
|
58
|
-
const URL = `http://localhost:${PORT}${path}`;
|
|
59
|
-
const DEFAULT_OPEN = process.platform === "win32" ? "explorer" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
60
|
-
const OPEN = process.env["BROWSER"] ? process.env["BROWSER"] === "none" ? false : process.env["BROWSER"] : DEFAULT_OPEN;
|
|
61
|
-
if (OPEN) {
|
|
62
|
-
const openCMD = `${OPEN} "${URL}"`;
|
|
63
|
-
this.logger?.info("opening browser with [%s]", openCMD);
|
|
64
|
-
execSync(openCMD);
|
|
65
|
-
} else {
|
|
66
|
-
this.logger?.warn("ignoring browser [%s]", process.env["BROWSER"]);
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
logger: this.logger,
|
|
70
|
-
getLogger: (identifier: string, options?: { level?: any; transports?: any[]; formatter?: any; }) => {
|
|
71
|
-
return this.loggerProvider.getLogger(identifier, options);
|
|
72
|
-
}
|
|
73
|
-
}*/
|
|
10
|
+
import { initGlobals } from "../globals.js";
|
|
74
11
|
|
|
75
12
|
export interface ServerInterfaceImplOptions {
|
|
76
13
|
cache: CacheInterface;
|
|
77
14
|
localCache: CacheInterface;
|
|
78
15
|
dbManager: DBManager;
|
|
79
|
-
|
|
16
|
+
webSocketManager: WebSocketManager;
|
|
80
17
|
logger?: Logger;
|
|
81
18
|
app?: Miqro;
|
|
82
19
|
port?: string;
|
|
83
20
|
loggerProvider?: LogProvider;
|
|
84
21
|
}
|
|
85
22
|
|
|
86
|
-
export
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
migrate(options: MigrateOptions): Promise<void>;
|
|
96
|
-
};
|
|
97
|
-
public ws: {
|
|
98
|
-
get(path: string): WebSocketServer | undefined;
|
|
99
|
-
disconnectAll(path: string): void;
|
|
100
|
-
};
|
|
101
|
-
public loggerProvider?: LogProvider;
|
|
102
|
-
openBrowser: (path: string) => void;
|
|
103
|
-
|
|
104
|
-
constructor(options: ServerInterfaceImplOptions) {
|
|
105
|
-
this.cache = options.cache;
|
|
106
|
-
this.localCache = options.localCache;
|
|
107
|
-
this.logger = options.logger ? options.logger : options.loggerProvider ? options.loggerProvider.getLogger("server") : undefined;
|
|
108
|
-
this.port = options.port;
|
|
109
|
-
|
|
110
|
-
const dbManager = options.dbManager;
|
|
111
|
-
const wsManager = options.wsManager;
|
|
112
|
-
this.loggerProvider = options.loggerProvider;
|
|
113
|
-
const app = options.app;
|
|
114
|
-
|
|
115
|
-
this.openBrowser = (path: string) => {
|
|
116
|
-
const PORT = this.port;
|
|
117
|
-
const URL = `http://localhost${PORT ? `:${PORT}` : ""}${path}`;
|
|
118
|
-
const DEFAULT_OPEN = process.platform === "win32" ? "explorer" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
119
|
-
const OPEN = app.options.browser !== undefined && String(app.options.browser).toUpperCase() !== "TRUE" && String(app.options.browser).toUpperCase() !== "1" ?
|
|
120
|
-
String(app.options.browser).toUpperCase() !== "0" && String(app.options.browser).toUpperCase() !== "FALSE" && String(app.options.browser).toUpperCase() !== "NONE" && app.options.browser ?
|
|
121
|
-
app.options.browser : false :
|
|
122
|
-
process.env["BROWSER"] ?
|
|
123
|
-
process.env["BROWSER"] === "none" ? false : process.env["BROWSER"] : DEFAULT_OPEN;
|
|
124
|
-
if (OPEN) {
|
|
125
|
-
const openCMD = `${OPEN} "${URL}"`;
|
|
126
|
-
this.logger?.info("opening browser with [%s]", openCMD);
|
|
127
|
-
execSync(openCMD);
|
|
128
|
-
} else {
|
|
129
|
-
this.logger?.warn("ignoring browser [%s]", OPEN);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
this.db = Object.freeze({
|
|
134
|
-
get: (name: string) => {
|
|
135
|
-
return dbManager.getDB(name);
|
|
23
|
+
export function createServerInterface(options: ServerInterfaceImplOptions): ServerInterface {
|
|
24
|
+
initGlobals();
|
|
25
|
+
return Object.freeze<ServerInterface>({
|
|
26
|
+
cache: options.cache,
|
|
27
|
+
localCache: options.localCache,
|
|
28
|
+
logger: options.logger,
|
|
29
|
+
db: {
|
|
30
|
+
get(name) {
|
|
31
|
+
return options.dbManager.getDB(name);
|
|
136
32
|
},
|
|
137
|
-
getMigrations
|
|
138
|
-
if (app?.inflated) {
|
|
33
|
+
getMigrations() {
|
|
34
|
+
if (options.app?.inflated) {
|
|
139
35
|
const ret: NamedMigration[] = [];
|
|
140
|
-
for (const d of app?.inflated.dbList) {
|
|
36
|
+
for (const d of options.app?.inflated.dbList) {
|
|
141
37
|
ret.push(...(d.migrations.map(m => {
|
|
142
38
|
return {
|
|
143
39
|
name: m.name,
|
|
@@ -150,30 +46,38 @@ export class ServerInterfaceImpl implements ServerInterface {
|
|
|
150
46
|
}
|
|
151
47
|
return [];
|
|
152
48
|
},
|
|
153
|
-
migrate
|
|
154
|
-
return app?.migrate(
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
49
|
+
migrate(migrateOptions) {
|
|
50
|
+
return options?.app?.migrate(migrateOptions);
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
ws: {
|
|
158
54
|
get: (name: string) => {
|
|
159
|
-
return
|
|
55
|
+
return options?.webSocketManager?.getWS(name);
|
|
160
56
|
},
|
|
161
57
|
disconnectAll: (path: string) => {
|
|
162
|
-
return
|
|
58
|
+
return options?.webSocketManager?.disconnectAllButLOGSocket();
|
|
163
59
|
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
60
|
+
},
|
|
61
|
+
openBrowser(path) {
|
|
62
|
+
const PORT = options.port;
|
|
63
|
+
const URL = `http://localhost${PORT ? `:${PORT}` : ""}${path}`;
|
|
64
|
+
const DEFAULT_OPEN = process.platform === "win32" ? "explorer" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
65
|
+
const OPEN = options?.app?.options.browser !== undefined && String(options?.app?.options.browser).toUpperCase() !== "TRUE" && String(options?.app?.options.browser).toUpperCase() !== "1" ?
|
|
66
|
+
String(options?.app.options.browser).toUpperCase() !== "0" && String(options?.app?.options.browser).toUpperCase() !== "FALSE" && String(options?.app?.options.browser).toUpperCase() !== "NONE" && options?.app?.options.browser ?
|
|
67
|
+
options?.app.options.browser : false :
|
|
68
|
+
process.env["BROWSER"] ?
|
|
69
|
+
process.env["BROWSER"] === "none" ? false : process.env["BROWSER"] : DEFAULT_OPEN;
|
|
70
|
+
if (OPEN) {
|
|
71
|
+
const openCMD = `${OPEN} "${URL}"`;
|
|
72
|
+
options?.logger?.info("opening browser with [%s]", openCMD);
|
|
73
|
+
execSync(openCMD);
|
|
74
|
+
} else {
|
|
75
|
+
options?.logger?.warn("ignoring browser [%s]", OPEN);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
getLogger(identifier, loggerOptions) {
|
|
79
|
+
return options?.loggerProvider?.getLogger(identifier, loggerOptions);
|
|
80
|
+
},
|
|
81
|
+
...server
|
|
82
|
+
});
|
|
179
83
|
}
|
|
@@ -2,9 +2,11 @@ import { Logger } from "@miqro/core";
|
|
|
2
2
|
import { LOG_SOCKET_PATH } from "../../../editor/common/constants.js";
|
|
3
3
|
import { ClusterWebSocketServer2 } from "./cluster-ws.js";
|
|
4
4
|
import { WSConfig } from "../../types.js";
|
|
5
|
+
import { LogProvider } from "./log.js";
|
|
5
6
|
|
|
6
7
|
export interface WebSocketManagerOptions {
|
|
7
8
|
logger?: Logger | Console;
|
|
9
|
+
loggerProvider?: LogProvider;
|
|
8
10
|
name?: string;
|
|
9
11
|
avoidLogSocket?: boolean;
|
|
10
12
|
}
|
|
@@ -14,10 +16,12 @@ export class WebSocketManager {
|
|
|
14
16
|
public logger?: Logger | Console | null = null;
|
|
15
17
|
public name: string;
|
|
16
18
|
public avoidLogSocket: boolean;
|
|
19
|
+
public loggerProvider: LogProvider;
|
|
17
20
|
constructor(options?: WebSocketManagerOptions) {
|
|
18
21
|
this.onUpgrade = this.onUpgrade.bind(this);
|
|
19
22
|
this.logger = options && options.logger ? options.logger : null;
|
|
20
23
|
this.name = options && options.name ? options.name : "WebSocketManager";
|
|
24
|
+
this.loggerProvider = options.loggerProvider;
|
|
21
25
|
this.avoidLogSocket = options && options.avoidLogSocket ? options.avoidLogSocket : false;
|
|
22
26
|
}
|
|
23
27
|
|
|
@@ -46,7 +50,9 @@ export class WebSocketManager {
|
|
|
46
50
|
throw new Error(`ws on path ${wsConfig.path} already setup!`);
|
|
47
51
|
}
|
|
48
52
|
this.logger?.debug("setting up websocket on [%s]", wsConfig.path);
|
|
49
|
-
const
|
|
53
|
+
const identifier = wsConfig.path.replaceAll("/", "_").toUpperCase();
|
|
54
|
+
const logger = this.loggerProvider && identifier.length >= 0 ? this.loggerProvider.getLogger(identifier.substring(identifier.charAt(0) === "_" ? 1 : 0)) : this.logger;
|
|
55
|
+
const server = new ClusterWebSocketServer2(this.name + wsConfig.path, wsConfig.path, logger, wsConfig);
|
|
50
56
|
this.runningGlobalWSMap.set(wsConfig.path, server);
|
|
51
57
|
}
|
|
52
58
|
}
|
|
@@ -65,7 +71,9 @@ export class WebSocketManager {
|
|
|
65
71
|
throw new Error(`ws on path ${wsConfig.path} already setup!`);
|
|
66
72
|
}
|
|
67
73
|
this.logger?.debug("setting up websocket on [%s]", wsConfig.path);
|
|
68
|
-
const
|
|
74
|
+
const identifier = wsConfig.path.replaceAll("/", "_").toUpperCase();
|
|
75
|
+
const logger = this.loggerProvider && identifier.length >= 0 ? this.loggerProvider.getLogger(identifier.substring(identifier.charAt(0) === "_" ? 1 : 0)) : this.logger;
|
|
76
|
+
const server = new ClusterWebSocketServer2(this.name + wsConfig.path, wsConfig.path, logger, wsConfig);
|
|
69
77
|
this.runningGlobalWSMap.set(wsConfig.path, server);
|
|
70
78
|
}
|
|
71
79
|
}
|