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.
Files changed (53) hide show
  1. package/build/editor.bundle.js +10 -2
  2. package/build/esm/editor/components/editor.js +8 -0
  3. package/build/esm/editor/components/file-editor.js +1 -1
  4. package/build/esm/editor/components/highlight-text-area.js +2 -1
  5. package/build/esm/src/common/arguments.d.ts +7 -0
  6. package/build/esm/src/common/arguments.js +103 -2
  7. package/build/esm/src/common/help.d.ts +1 -1
  8. package/build/esm/src/common/help.js +4 -0
  9. package/build/esm/src/common/jwt.d.ts +49 -0
  10. package/build/esm/src/common/jwt.js +76 -0
  11. package/build/esm/src/inflate/inflate-sea.js +9 -8
  12. package/build/esm/src/inflate/setup-http.d.ts +1 -0
  13. package/build/esm/src/inflate/setup-http.js +8 -0
  14. package/build/esm/src/lib.d.ts +1 -1
  15. package/build/esm/src/lib.js +2 -2
  16. package/build/esm/src/main.js +4 -1
  17. package/build/esm/src/services/app.d.ts +5 -0
  18. package/build/esm/src/services/app.js +33 -7
  19. package/build/esm/src/services/globals.js +45 -1
  20. package/build/esm/src/services/utils/cluster-ws.d.ts +4 -2
  21. package/build/esm/src/services/utils/cluster-ws.js +10 -1
  22. package/build/esm/src/services/utils/server-interface.d.ts +4 -30
  23. package/build/esm/src/services/utils/server-interface.js +53 -64
  24. package/build/esm/src/services/utils/websocketmanager.d.ts +3 -0
  25. package/build/esm/src/services/utils/websocketmanager.js +8 -2
  26. package/build/esm/src/types.d.ts +85 -5
  27. package/build/lib.cjs +3026 -367
  28. package/editor/components/editor.tsx +8 -0
  29. package/editor/components/file-editor.tsx +1 -1
  30. package/editor/components/highlight-text-area.tsx +2 -1
  31. package/package.json +5 -4
  32. package/sea/install-nodejs.sh +1 -1
  33. package/sea/node.version.tag +1 -1
  34. package/sea/types.json +1 -1
  35. package/src/common/arguments.ts +118 -2
  36. package/src/common/help.ts +4 -0
  37. package/src/common/jwt.ts +85 -0
  38. package/src/inflate/inflate-sea.ts +9 -8
  39. package/src/inflate/setup-http.ts +9 -0
  40. package/src/lib.ts +2 -2
  41. package/src/main.ts +4 -1
  42. package/src/services/app.ts +38 -7
  43. package/src/services/globals.ts +45 -2
  44. package/src/services/utils/cluster-ws.ts +6 -1
  45. package/src/services/utils/server-interface.ts +53 -140
  46. package/src/services/utils/websocketmanager.ts +10 -2
  47. package/src/types/browser.globals.d.ts +0 -8
  48. package/src/types/cookie.d.ts +2 -0
  49. package/src/types/jose.d.ts +2 -0
  50. package/src/types/jsx.globals.d.ts +1 -0
  51. package/src/types/miqro.d.ts +101 -2
  52. package/src/types/server.globals.d.ts +4 -29
  53. package/src/types.ts +87 -5
@@ -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
  }
@@ -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 { ServerInterfaceImpl, ServerRequestHandler, WebSocketManager, initGlobals, DBManager, App, LoggerHandler, LogProvider, LocalCache, ClusterCache } = require("./lib.cjs");
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 logProvider = new LogProvider();
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 = new ServerInterfaceImpl({
79
+ const serverInterface = createServerInterface({
80
80
  cache,
81
81
  localCache,
82
- logProvider,
82
+ loggerProvider,
83
83
  wsManager: webSocketManager,
84
- logger: logProvider.getLogger("server"),
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 = r.inflatePath;
136
+ const rPath = join(relative(cwd(), dirname(data.filePath)), basename(data.filePath));
137
137
  if (rPath) {
138
- const apiInflatedPath = join("..", "..", service, "http", rPath + ".api.js");
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 { ServerInterfaceImpl, ServerInterfaceImplOptions } from "./services/utils/server-interface.js";
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) {
@@ -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 { ServerInterfaceImpl } from "./utils/server-interface.js";
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 = new ServerInterfaceImpl({
159
+ this.serverInterface = createServerInterface({
154
160
  cache: this.cache,
155
- localCache: this.localCache,
156
161
  dbManager: this.dbManager,
157
- wsManager: this.webSocketManager,
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;
@@ -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
  }