miqro 6.1.4 → 6.2.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/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/arguments.d.ts +7 -0
- package/build/esm/src/common/arguments.js +103 -2
- package/build/esm/src/common/help.d.ts +1 -1
- package/build/esm/src/common/help.js +4 -0
- 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/main.js +4 -1
- package/build/esm/src/services/app.d.ts +5 -0
- package/build/esm/src/services/app.js +33 -7
- 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 +53 -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 +85 -5
- package/build/lib.cjs +3026 -367
- 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 +5 -4
- package/sea/install-nodejs.sh +1 -1
- package/sea/node.version.tag +1 -1
- package/sea/types.json +1 -1
- package/src/common/arguments.ts +118 -2
- package/src/common/help.ts +4 -0
- 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/main.ts +4 -1
- package/src/services/app.ts +38 -7
- package/src/services/globals.ts +45 -2
- package/src/services/utils/cluster-ws.ts +6 -1
- package/src/services/utils/server-interface.ts +53 -140
- package/src/services/utils/websocketmanager.ts +10 -2
- package/src/types/browser.globals.d.ts +0 -8
- package/src/types/cookie.d.ts +2 -0
- package/src/types/jose.d.ts +2 -0
- package/src/types/jsx.globals.d.ts +1 -0
- package/src/types/miqro.d.ts +101 -2
- package/src/types/server.globals.d.ts +4 -29
- package/src/types.ts +87 -5
package/src/common/arguments.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { isSea } from "node:sea";
|
|
|
12
12
|
import cluster from "node:cluster";
|
|
13
13
|
import { __package_dirname, getVersion } from "./assets.js";
|
|
14
14
|
import { Parser, Schema } from "@miqro/parser";
|
|
15
|
+
import { ServerOptions } from "node:https";
|
|
15
16
|
|
|
16
17
|
const parser = new Parser();
|
|
17
18
|
|
|
@@ -23,6 +24,9 @@ interface MiqroJSON {
|
|
|
23
24
|
browser?: string | boolean;
|
|
24
25
|
logFile?: string | boolean;
|
|
25
26
|
editor?: boolean;
|
|
27
|
+
https?: boolean;
|
|
28
|
+
serverOptions?: ServerOptions;
|
|
29
|
+
httpsRedirect?: number;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
const MiqroJSONSchema: Schema<MiqroJSON> = {
|
|
@@ -34,7 +38,10 @@ const MiqroJSONSchema: Schema<MiqroJSON> = {
|
|
|
34
38
|
inflateDir: "string?",
|
|
35
39
|
browser: "boolean?|string?",
|
|
36
40
|
logFile: "boolean?|string?",
|
|
37
|
-
editor: "boolean?"
|
|
41
|
+
editor: "boolean?",
|
|
42
|
+
https: "boolean?",
|
|
43
|
+
serverOptions: "any?",
|
|
44
|
+
httpsRedirect: "number?"
|
|
38
45
|
}
|
|
39
46
|
}
|
|
40
47
|
|
|
@@ -76,6 +83,9 @@ export interface Arguments {
|
|
|
76
83
|
services: string[];
|
|
77
84
|
editor: boolean;
|
|
78
85
|
hotreload: boolean;
|
|
86
|
+
https: boolean;
|
|
87
|
+
serverOptions: ServerOptions;
|
|
88
|
+
httpsRedirect?: number;
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
/**
|
|
@@ -87,6 +97,9 @@ export function parseArguments(): Arguments {
|
|
|
87
97
|
|
|
88
98
|
const args = cluster.isPrimary ? process.argv.slice(2, process.argv.length) : process.argv.slice(3, process.argv.length);
|
|
89
99
|
const flags: {
|
|
100
|
+
httpsRedirect: number | null;
|
|
101
|
+
https: boolean | null;
|
|
102
|
+
serverOptions: ServerOptions;
|
|
90
103
|
logFile: string | boolean | null;
|
|
91
104
|
browser: string | boolean | null;
|
|
92
105
|
name: string | null;
|
|
@@ -110,6 +123,9 @@ export function parseArguments(): Arguments {
|
|
|
110
123
|
inflateDir?: string | null;
|
|
111
124
|
hotreload?: boolean | null;
|
|
112
125
|
} = {
|
|
126
|
+
httpsRedirect: null,
|
|
127
|
+
https: null,
|
|
128
|
+
serverOptions: {},
|
|
113
129
|
name: null,
|
|
114
130
|
browser: null,
|
|
115
131
|
logFile: null,
|
|
@@ -196,6 +212,59 @@ export function parseArguments(): Arguments {
|
|
|
196
212
|
}
|
|
197
213
|
flags.hotreload = true;
|
|
198
214
|
continue;
|
|
215
|
+
case "--https":
|
|
216
|
+
if (flags.https !== null) {
|
|
217
|
+
console.error("bad arguments.");
|
|
218
|
+
console.error(usage);
|
|
219
|
+
process.exit(EXIT_CODES.BAD_ARGUMENTS);
|
|
220
|
+
}
|
|
221
|
+
flags.https = true;
|
|
222
|
+
continue;
|
|
223
|
+
case "--https-redirect":
|
|
224
|
+
if (flags.httpsRedirect !== null) {
|
|
225
|
+
console.error("bad arguments.");
|
|
226
|
+
console.error(usage);
|
|
227
|
+
process.exit(EXIT_CODES.BAD_ARGUMENTS);
|
|
228
|
+
}
|
|
229
|
+
const httpsRedirectPort = parseInt(String(args[i + 1]) as any, 10);
|
|
230
|
+
if (isNaN(httpsRedirectPort)) {
|
|
231
|
+
console.error("bad arguments. --https-redirect must be a number.");
|
|
232
|
+
console.error(usage);
|
|
233
|
+
process.exit(EXIT_CODES.BAD_ARGUMENTS);
|
|
234
|
+
}
|
|
235
|
+
flags.httpsRedirect = httpsRedirectPort;
|
|
236
|
+
i++;
|
|
237
|
+
continue;
|
|
238
|
+
case "--https-cert":
|
|
239
|
+
if (flags.serverOptions.cert) {
|
|
240
|
+
console.error("bad arguments.");
|
|
241
|
+
console.error(usage);
|
|
242
|
+
process.exit(EXIT_CODES.BAD_ARGUMENTS);
|
|
243
|
+
}
|
|
244
|
+
const httpsCertPath = args[i + 1] as any;
|
|
245
|
+
if (typeof httpsCertPath !== "string") {
|
|
246
|
+
console.error("bad arguments. --https-cert must be a string.");
|
|
247
|
+
console.error(usage);
|
|
248
|
+
process.exit(EXIT_CODES.BAD_ARGUMENTS);
|
|
249
|
+
}
|
|
250
|
+
flags.serverOptions.cert = readFileSync(httpsCertPath);
|
|
251
|
+
i++;
|
|
252
|
+
continue;
|
|
253
|
+
case "--https-key":
|
|
254
|
+
if (flags.serverOptions.key) {
|
|
255
|
+
console.error("bad arguments.");
|
|
256
|
+
console.error(usage);
|
|
257
|
+
process.exit(EXIT_CODES.BAD_ARGUMENTS);
|
|
258
|
+
}
|
|
259
|
+
const httpsKeyPath = args[i + 1] as any;
|
|
260
|
+
if (typeof httpsKeyPath !== "string") {
|
|
261
|
+
console.error("bad arguments. --https-key must be a string.");
|
|
262
|
+
console.error(usage);
|
|
263
|
+
process.exit(EXIT_CODES.BAD_ARGUMENTS);
|
|
264
|
+
}
|
|
265
|
+
flags.serverOptions.key = readFileSync(httpsKeyPath);
|
|
266
|
+
i++;
|
|
267
|
+
continue;
|
|
199
268
|
case "--port":
|
|
200
269
|
if (flags.port !== null) {
|
|
201
270
|
console.error("bad arguments.");
|
|
@@ -426,6 +495,8 @@ export function parseArguments(): Arguments {
|
|
|
426
495
|
const miqroJSONPath = !flags.disableMiqroJSON ? flags.miqroJSONPath ? resolve(flags.miqroJSONPath) : getMiqroJSONPath() : false;
|
|
427
496
|
const miqroRC = miqroJSONPath ? importMiqroJSON(miqroJSONPath) : {};
|
|
428
497
|
|
|
498
|
+
//console.dir(miqroRC.serverOptions);
|
|
499
|
+
|
|
429
500
|
// try to load .miqrorc
|
|
430
501
|
if (!flags.disableMiqroJSON && miqroJSONPath) {
|
|
431
502
|
if (services.length === 0) {
|
|
@@ -435,6 +506,33 @@ export function parseArguments(): Arguments {
|
|
|
435
506
|
}
|
|
436
507
|
}
|
|
437
508
|
}
|
|
509
|
+
if (flags.https === null) {
|
|
510
|
+
if (miqroRC.https) {
|
|
511
|
+
flags.https = miqroRC.https;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (flags.httpsRedirect === null) {
|
|
515
|
+
if (miqroRC.httpsRedirect && flags.https) {
|
|
516
|
+
/*console.log("reading key from " + String(miqroRC.serverOptions?.key));
|
|
517
|
+
flags.serverOptions.key = readFileSync(String(miqroRC.serverOptions?.key));*/
|
|
518
|
+
flags.httpsRedirect = miqroRC.httpsRedirect;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (!flags.serverOptions.key) {
|
|
522
|
+
if (miqroRC.serverOptions?.key) {
|
|
523
|
+
/*console.log("reading key from " + String(miqroRC.serverOptions?.key));
|
|
524
|
+
flags.serverOptions.key = readFileSync(String(miqroRC.serverOptions?.key));*/
|
|
525
|
+
flags.serverOptions.key = miqroRC.serverOptions?.key;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (!flags.serverOptions.cert) {
|
|
530
|
+
if (miqroRC.serverOptions?.cert) {
|
|
531
|
+
/*console.log("reading cert from " + String(miqroRC.serverOptions?.cert));
|
|
532
|
+
flags.serverOptions.cert = readFileSync(String(miqroRC.serverOptions?.cert));*/
|
|
533
|
+
flags.serverOptions.cert = miqroRC.serverOptions?.cert;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
438
536
|
if (flags.port === null) {
|
|
439
537
|
if (miqroRC.port) {
|
|
440
538
|
flags.port = String(miqroRC.port);
|
|
@@ -548,6 +646,21 @@ export function parseArguments(): Arguments {
|
|
|
548
646
|
|
|
549
647
|
const generateDocType = flags.generateDocType ? flags.generateDocType as any : "MD";
|
|
550
648
|
|
|
649
|
+
if (flags.https && (!flags.serverOptions.key)) {
|
|
650
|
+
console.error("bad arguments. cannot use --https without --https-key. or set values in miqro.json file.");
|
|
651
|
+
process.exit(EXIT_CODES.BAD_ARGUMENTS);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (flags.https && (!flags.serverOptions.cert)) {
|
|
655
|
+
console.error("bad arguments. cannot use --https without --https-cert. or set values in miqro.json file.");
|
|
656
|
+
process.exit(EXIT_CODES.BAD_ARGUMENTS);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (flags.httpsRedirect && !flags.https) {
|
|
660
|
+
console.error("bad arguments. cannot use --https-redirect without --https. or set values in miqro.json file.");
|
|
661
|
+
process.exit(EXIT_CODES.BAD_ARGUMENTS);
|
|
662
|
+
}
|
|
663
|
+
|
|
551
664
|
return {
|
|
552
665
|
name: flags.name ? flags.name : undefined,
|
|
553
666
|
browser: flags.browser !== null ? flags.browser : undefined,
|
|
@@ -571,6 +684,9 @@ export function parseArguments(): Arguments {
|
|
|
571
684
|
generateDocOut: flags.generateDocOut ? resolve(process.cwd(), flags.generateDocOut) : generateDocType === "MD" ? resolve(process.cwd(), "API.md") : resolve(process.cwd(), "API.json"),
|
|
572
685
|
generateDocType,
|
|
573
686
|
services,
|
|
574
|
-
editor: flags.editor ? true : false
|
|
687
|
+
editor: flags.editor ? true : false,
|
|
688
|
+
https: flags.https ? true : false,
|
|
689
|
+
httpsRedirect: flags.httpsRedirect ? flags.httpsRedirect : undefined,
|
|
690
|
+
serverOptions: flags.serverOptions
|
|
575
691
|
}
|
|
576
692
|
}
|
package/src/common/help.ts
CHANGED
|
@@ -40,6 +40,10 @@ export const help = `
|
|
|
40
40
|
--config\n\toverrides the default miqro.json path.
|
|
41
41
|
--port\n\toverrides the default port from PORT.
|
|
42
42
|
--name\n\toverrides the default name of the server.
|
|
43
|
+
--https\n\tserves the server in https instead of http
|
|
44
|
+
--https-key\n\tpoint to a server.key file for https.
|
|
45
|
+
--https-cert\n\tpoint to a server.cert file for https.
|
|
46
|
+
--https-redirect\n\tserves an aditional http server that redirects to https. it needs a port number.
|
|
43
47
|
|
|
44
48
|
==environment variables==
|
|
45
49
|
|
|
@@ -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/main.ts
CHANGED
|
@@ -22,7 +22,10 @@ async function main(args: Arguments) {
|
|
|
22
22
|
services: args.services,
|
|
23
23
|
browser: args.browser,
|
|
24
24
|
logFile: args.logFile,
|
|
25
|
-
hotreload: args.test ? false : args.hotreload
|
|
25
|
+
hotreload: args.test ? false : args.hotreload,
|
|
26
|
+
https: args.test ? false : args.https,
|
|
27
|
+
serverOptions: args.serverOptions,
|
|
28
|
+
httpRedirect: args.test ? undefined : args.httpsRedirect
|
|
26
29
|
});
|
|
27
30
|
// check arguments
|
|
28
31
|
if (args.generateDoc) {
|
package/src/services/app.ts
CHANGED
|
@@ -25,12 +25,13 @@ 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";
|
|
32
32
|
import { dirname, join, relative, resolve } from "node:path";
|
|
33
33
|
import { cwd } from "node:process";
|
|
34
|
+
import { ServerOptions } from "node:https";
|
|
34
35
|
|
|
35
36
|
export interface MiqroOptions {
|
|
36
37
|
name: string;
|
|
@@ -42,6 +43,9 @@ export interface MiqroOptions {
|
|
|
42
43
|
browser?: string | boolean;
|
|
43
44
|
logFile?: string | boolean;
|
|
44
45
|
hotreload?: boolean;
|
|
46
|
+
serverOptions?: ServerOptions<any, any>;
|
|
47
|
+
https?: boolean;
|
|
48
|
+
httpRedirect?: number;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
export interface InflateOptions {
|
|
@@ -76,6 +80,7 @@ export class Miqro {
|
|
|
76
80
|
public status: "stopped" | "starting" | "stopping" | "reloading" | "started" = "stopped";
|
|
77
81
|
public options: MiqroOptions;
|
|
78
82
|
public server?: App | null = null;
|
|
83
|
+
public httpsRedirectServer?: App | null = null;
|
|
79
84
|
public cache: ClusterCache;
|
|
80
85
|
public localCache: LocalCache;
|
|
81
86
|
public webSocketManager: WebSocketManager;
|
|
@@ -139,6 +144,7 @@ export class Miqro {
|
|
|
139
144
|
this.localCache = new LocalCache(`MiqroApplicationLocalCache[${this.options.name}]`, this.logger);
|
|
140
145
|
this.webSocketManager = new WebSocketManager({
|
|
141
146
|
logger: this.logger,
|
|
147
|
+
loggerProvider: this.loggerProvider,
|
|
142
148
|
name: `MiqroApplicationWebsocketManager[${this.options.name}]`,
|
|
143
149
|
avoidLogSocket: this.options.editor
|
|
144
150
|
});
|
|
@@ -150,16 +156,17 @@ export class Miqro {
|
|
|
150
156
|
logger: this.logger
|
|
151
157
|
});
|
|
152
158
|
|
|
153
|
-
this.serverInterface =
|
|
159
|
+
this.serverInterface = createServerInterface({
|
|
154
160
|
cache: this.cache,
|
|
155
|
-
localCache: this.localCache,
|
|
156
161
|
dbManager: this.dbManager,
|
|
157
|
-
|
|
162
|
+
localCache: this.localCache,
|
|
163
|
+
webSocketManager: this.webSocketManager,
|
|
158
164
|
app: this,
|
|
159
165
|
logger: this.logger,
|
|
160
166
|
loggerProvider: this.loggerProvider,
|
|
161
167
|
port: this.options.port
|
|
162
168
|
});
|
|
169
|
+
|
|
163
170
|
this.serverRequestHandler = ServerRequestHandler(this.serverInterface);
|
|
164
171
|
|
|
165
172
|
this.adminInterface = createAdminInterface(this);
|
|
@@ -422,6 +429,7 @@ export class Miqro {
|
|
|
422
429
|
//this.disconnect();
|
|
423
430
|
this.status = "starting";
|
|
424
431
|
this.server = undefined;
|
|
432
|
+
this.httpsRedirectServer = undefined;
|
|
425
433
|
this.connect();
|
|
426
434
|
await this.dbManager.connectAll();
|
|
427
435
|
this.server = new App({
|
|
@@ -429,8 +437,17 @@ export class Miqro {
|
|
|
429
437
|
req.server = this.serverInterface;
|
|
430
438
|
return this.webSocketManager.onUpgrade(req, socket, head);
|
|
431
439
|
},
|
|
432
|
-
loggerFactory: this.loggerProvider.requestLoggerFactory
|
|
440
|
+
loggerFactory: this.loggerProvider.requestLoggerFactory,
|
|
441
|
+
serverOptions: this.options?.serverOptions,
|
|
442
|
+
https: this.options?.https
|
|
433
443
|
});
|
|
444
|
+
if (this.options?.httpRedirect) {
|
|
445
|
+
this.httpsRedirectServer = new App();
|
|
446
|
+
this.httpsRedirectServer.use(async (req: ServerRequest, res) => {
|
|
447
|
+
const hostname = req.headers.host.split(":").length > 1 ? req.headers.host.split(":")[0] : req.headers.host;
|
|
448
|
+
return await res.redirect('https://' + hostname + ":" + this.options.port + req.url);
|
|
449
|
+
});
|
|
450
|
+
}
|
|
434
451
|
|
|
435
452
|
this.webSocketManager.replaceALLWS(this.inflated.wsConfigList);
|
|
436
453
|
|
|
@@ -453,11 +470,20 @@ export class Miqro {
|
|
|
453
470
|
this.logger?.trace("calling listen on [%s]", this.options.port);
|
|
454
471
|
|
|
455
472
|
await this.server.listen(this.options.port);
|
|
473
|
+
if (this.options?.httpRedirect) {
|
|
474
|
+
await this.httpsRedirectServer.listen(this.options.httpRedirect);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (this.options?.httpRedirect && this.logger && (cluster.isPrimary || process.env["CLUSTER_NODE_NUMBER"] === "0")) {
|
|
478
|
+
this.logger?.log("\t\t==listening on [http][%s] for [https][%s] redirection==", this.options?.httpRedirect, this.options.port);
|
|
479
|
+
} else if (this.options?.httpRedirect) {
|
|
480
|
+
this.logger?.debug("\t\t==listening on [http][%s] for [https][%s] redirection==", this.options?.httpRedirect, this.options.port);
|
|
481
|
+
}
|
|
456
482
|
|
|
457
483
|
if (this.logger && (cluster.isPrimary || process.env["CLUSTER_NODE_NUMBER"] === "0")) {
|
|
458
|
-
this.logger?.log("\t\t==listening on [%s]==", this.options.port);
|
|
484
|
+
this.logger?.log("\t\t==listening on [%s][%s]==", this.options.https ? "https" : "http", this.options.port);
|
|
459
485
|
} else {
|
|
460
|
-
this.logger?.debug("\t\t==listening on [%s]==", this.options.port);
|
|
486
|
+
this.logger?.debug("\t\t==listening on [%s][%s]==", this.options.https ? "https" : "http", this.options.port);
|
|
461
487
|
}
|
|
462
488
|
|
|
463
489
|
await notifiyServerConfig(this.logger, this.serverInterface, this.adminInterface, this.inflated.serverConfigMap, "start");
|
|
@@ -482,7 +508,9 @@ export class Miqro {
|
|
|
482
508
|
this.watcher = null;
|
|
483
509
|
}
|
|
484
510
|
const server = this.server;
|
|
511
|
+
const httpsRedirectServer = this.httpsRedirectServer;
|
|
485
512
|
this.server = null;
|
|
513
|
+
this.httpsRedirectServer = null;
|
|
486
514
|
this.disconnect();
|
|
487
515
|
this.logger?.debug("\t\t==stop==");
|
|
488
516
|
this.logger?.debug("clear running server routes");
|
|
@@ -493,6 +521,9 @@ export class Miqro {
|
|
|
493
521
|
notifiyServerConfigSync(this, "unload");
|
|
494
522
|
//server.ws.disconnectAll();
|
|
495
523
|
this.logger?.debug("stopping");
|
|
524
|
+
if (httpsRedirectServer) {
|
|
525
|
+
await httpsRedirectServer.close();
|
|
526
|
+
}
|
|
496
527
|
const p = server.close();
|
|
497
528
|
await p;
|
|
498
529
|
await pD;
|
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
|
}
|