@nocobase/server 2.2.23 → 2.2.24
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/lib/app-supervisor/main-only-adapter.js +8 -2
- package/lib/gateway/errors.d.ts +5 -0
- package/lib/gateway/errors.js +16 -0
- package/lib/gateway/index.d.ts +1 -0
- package/lib/gateway/index.js +47 -20
- package/lib/gateway/ws-server.d.ts +5 -0
- package/lib/gateway/ws-server.js +31 -5
- package/package.json +17 -17
|
@@ -50,7 +50,10 @@ const _MainOnlyAdapter = class _MainOnlyAdapter {
|
|
|
50
50
|
return this.apps[appName];
|
|
51
51
|
}
|
|
52
52
|
async bootstrapApp(appName) {
|
|
53
|
-
if (appName !== "main"
|
|
53
|
+
if (appName !== "main") {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!this.apps[appName]) {
|
|
54
57
|
this.setAppStatus(appName, "not_found");
|
|
55
58
|
return;
|
|
56
59
|
}
|
|
@@ -138,13 +141,16 @@ const _MainOnlyAdapter = class _MainOnlyAdapter {
|
|
|
138
141
|
return this.removeApp("main");
|
|
139
142
|
}
|
|
140
143
|
setAppStatus(appName, status, options = {}) {
|
|
141
|
-
if (this.status === status) {
|
|
144
|
+
if (appName !== "main" || this.status === status) {
|
|
142
145
|
return;
|
|
143
146
|
}
|
|
144
147
|
this.status = status;
|
|
145
148
|
this.supervisor.emit("appStatusChanged", { appName, status, options });
|
|
146
149
|
}
|
|
147
150
|
getAppStatus(appName, defaultStatus) {
|
|
151
|
+
if (appName !== "main") {
|
|
152
|
+
return "not_found";
|
|
153
|
+
}
|
|
148
154
|
return this.status ?? defaultStatus ?? null;
|
|
149
155
|
}
|
|
150
156
|
hasAppError(appName) {
|
package/lib/gateway/errors.d.ts
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
|
+
/** Raised when a request resolves to an app name that could never name a real application. */
|
|
10
|
+
export declare class InvalidAppNameError extends Error {
|
|
11
|
+
readonly appName: unknown;
|
|
12
|
+
constructor(appName: unknown);
|
|
13
|
+
}
|
|
9
14
|
interface AppError {
|
|
10
15
|
status: number;
|
|
11
16
|
message: any;
|
package/lib/gateway/errors.js
CHANGED
|
@@ -37,6 +37,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
37
37
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
38
38
|
var errors_exports = {};
|
|
39
39
|
__export(errors_exports, {
|
|
40
|
+
InvalidAppNameError: () => InvalidAppNameError,
|
|
40
41
|
applyErrorWithArgs: () => applyErrorWithArgs,
|
|
41
42
|
errors: () => errors,
|
|
42
43
|
getErrorWithCode: () => getErrorWithCode
|
|
@@ -44,6 +45,15 @@ __export(errors_exports, {
|
|
|
44
45
|
module.exports = __toCommonJS(errors_exports);
|
|
45
46
|
var import_app_supervisor = require("../app-supervisor");
|
|
46
47
|
var import_lodash = __toESM(require("lodash"));
|
|
48
|
+
const _InvalidAppNameError = class _InvalidAppNameError extends Error {
|
|
49
|
+
constructor(appName) {
|
|
50
|
+
super("invalid application name");
|
|
51
|
+
this.appName = appName;
|
|
52
|
+
this.name = "InvalidAppNameError";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
__name(_InvalidAppNameError, "InvalidAppNameError");
|
|
56
|
+
let InvalidAppNameError = _InvalidAppNameError;
|
|
47
57
|
function getMaintaining(app) {
|
|
48
58
|
var _a;
|
|
49
59
|
return (_a = app == null ? void 0 : app.getMaintaining) == null ? void 0 : _a.call(app);
|
|
@@ -54,6 +64,11 @@ function getAppName(app) {
|
|
|
54
64
|
}
|
|
55
65
|
__name(getAppName, "getAppName");
|
|
56
66
|
const errors = {
|
|
67
|
+
INVALID_APP_NAME: {
|
|
68
|
+
status: 400,
|
|
69
|
+
message: "invalid application name",
|
|
70
|
+
maintaining: false
|
|
71
|
+
},
|
|
57
72
|
APP_ENVIRONMENT_UNAVAILABLE: {
|
|
58
73
|
status: 503,
|
|
59
74
|
message: /* @__PURE__ */ __name(({ appName }) => `deployment environment for application ${appName} is unavailable`, "message"),
|
|
@@ -191,6 +206,7 @@ function applyErrorWithArgs(error, options) {
|
|
|
191
206
|
__name(applyErrorWithArgs, "applyErrorWithArgs");
|
|
192
207
|
// Annotate the CommonJS export names for ESM import in node:
|
|
193
208
|
0 && (module.exports = {
|
|
209
|
+
InvalidAppNameError,
|
|
194
210
|
applyErrorWithArgs,
|
|
195
211
|
errors,
|
|
196
212
|
getErrorWithCode
|
package/lib/gateway/index.d.ts
CHANGED
|
@@ -90,6 +90,7 @@ export declare class Gateway extends EventEmitter {
|
|
|
90
90
|
requestHandler(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
91
91
|
getAppSelectorMiddlewares(): Toposort<AppSelectorMiddleware>;
|
|
92
92
|
getRequestHandleAppName(req: IncomingMessage | IncomingRequest): Promise<string>;
|
|
93
|
+
private handleUpgrade;
|
|
93
94
|
getCallback(): any;
|
|
94
95
|
watch(): Promise<void>;
|
|
95
96
|
run(options: RunOptions): Promise<void>;
|
package/lib/gateway/index.js
CHANGED
|
@@ -72,6 +72,15 @@ var import_node_worker_threads = require("node:worker_threads");
|
|
|
72
72
|
var import_node_process = __toESM(require("node:process"));
|
|
73
73
|
var import_utils4 = require("./utils");
|
|
74
74
|
const compress = (0, import_node_util.promisify)((0, import_compression.default)());
|
|
75
|
+
const APP_NAME_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9_-]*$/;
|
|
76
|
+
const MAX_APP_NAME_LENGTH = 255;
|
|
77
|
+
function isValidAppName(name) {
|
|
78
|
+
if (typeof name !== "string" || name.length > MAX_APP_NAME_LENGTH || !APP_NAME_PATTERN.test(name)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return !(name in Object.prototype);
|
|
82
|
+
}
|
|
83
|
+
__name(isValidAppName, "isValidAppName");
|
|
75
84
|
function normalizeBasePath(path = "") {
|
|
76
85
|
const normalized = path.replace(/\/+/g, "/").replace(/\/$/, "");
|
|
77
86
|
return normalized || "/";
|
|
@@ -261,6 +270,9 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
261
270
|
this.emit("appSelectorChanged");
|
|
262
271
|
}
|
|
263
272
|
getLogger(appName, res) {
|
|
273
|
+
if (!isValidAppName(appName)) {
|
|
274
|
+
throw new Error("Invalid app name");
|
|
275
|
+
}
|
|
264
276
|
const reqId = (0, import_crypto.randomUUID)();
|
|
265
277
|
res.setHeader("X-Request-Id", reqId);
|
|
266
278
|
let logger = this.loggers.get(appName);
|
|
@@ -284,7 +296,9 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
284
296
|
res.end(JSON.stringify({ error }));
|
|
285
297
|
}
|
|
286
298
|
responseErrorWithCode(code, res, options) {
|
|
287
|
-
const
|
|
299
|
+
const appName = options == null ? void 0 : options.appName;
|
|
300
|
+
const loggerName = isValidAppName(appName) && import_app_supervisor.AppSupervisor.getInstance().hasApp(appName) ? appName : "main";
|
|
301
|
+
const log = this.getLogger(loggerName, res);
|
|
288
302
|
const error = (0, import_errors.applyErrorWithArgs)((0, import_errors.getErrorWithCode)(code), options);
|
|
289
303
|
log.error(error.message, {
|
|
290
304
|
method: "responseErrorWithCode",
|
|
@@ -387,6 +401,10 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
387
401
|
try {
|
|
388
402
|
handleApp = await this.getRequestHandleAppName(req);
|
|
389
403
|
} catch (error) {
|
|
404
|
+
if (error instanceof import_errors.InvalidAppNameError) {
|
|
405
|
+
this.responseErrorWithCode("INVALID_APP_NAME", res, {});
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
390
408
|
this.getLogger("main", res).error("Failed to get handle app name", { error });
|
|
391
409
|
this.responseErrorWithCode("APP_INITIALIZING", res, { appName: handleApp });
|
|
392
410
|
return;
|
|
@@ -538,8 +556,32 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
538
556
|
if (!ctx.resolvedAppName) {
|
|
539
557
|
ctx.resolvedAppName = "main";
|
|
540
558
|
}
|
|
559
|
+
if (!isValidAppName(ctx.resolvedAppName)) {
|
|
560
|
+
throw new import_errors.InvalidAppNameError(ctx.resolvedAppName);
|
|
561
|
+
}
|
|
541
562
|
return ctx.resolvedAppName;
|
|
542
563
|
}
|
|
564
|
+
async handleUpgrade(request, socket, head) {
|
|
565
|
+
const isProxy = await import_app_supervisor.AppSupervisor.getInstance().proxyWs(request, socket, head);
|
|
566
|
+
if (isProxy) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
const appInstance = await import_app_supervisor.AppSupervisor.getInstance().getApp("main");
|
|
570
|
+
for (const handle of _Gateway.wsServers) {
|
|
571
|
+
const result = await handle(request, socket, head, appInstance);
|
|
572
|
+
if (result !== false) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const { pathname } = (0, import_url.parse)(request.url);
|
|
577
|
+
if (pathname === import_node_process.default.env.WS_PATH) {
|
|
578
|
+
this.wsServer.wss.handleUpgrade(request, socket, head, (ws) => {
|
|
579
|
+
this.wsServer.wss.emit("connection", ws, request);
|
|
580
|
+
});
|
|
581
|
+
} else {
|
|
582
|
+
socket.destroy();
|
|
583
|
+
}
|
|
584
|
+
}
|
|
543
585
|
getCallback() {
|
|
544
586
|
return this.requestHandler.bind(this);
|
|
545
587
|
}
|
|
@@ -656,26 +698,11 @@ const _Gateway = class _Gateway extends import_events.EventEmitter {
|
|
|
656
698
|
this.getCallback()(req, res);
|
|
657
699
|
});
|
|
658
700
|
this.wsServer = new import_ws_server.WSServer();
|
|
659
|
-
this.server.on("upgrade",
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
const appInstance = await import_app_supervisor.AppSupervisor.getInstance().getApp("main");
|
|
665
|
-
for (const handle of _Gateway.wsServers) {
|
|
666
|
-
const result = await handle(request, socket, head, appInstance);
|
|
667
|
-
if (result !== false) {
|
|
668
|
-
return;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
const { pathname } = (0, import_url.parse)(request.url);
|
|
672
|
-
if (pathname === import_node_process.default.env.WS_PATH) {
|
|
673
|
-
this.wsServer.wss.handleUpgrade(request, socket, head, (ws) => {
|
|
674
|
-
this.wsServer.wss.emit("connection", ws, request);
|
|
675
|
-
});
|
|
676
|
-
} else {
|
|
701
|
+
this.server.on("upgrade", (request, socket, head) => {
|
|
702
|
+
this.handleUpgrade(request, socket, head).catch((error) => {
|
|
703
|
+
import_app_supervisor.AppSupervisor.getInstance().logger.error("Failed to handle websocket upgrade", { error });
|
|
677
704
|
socket.destroy();
|
|
678
|
-
}
|
|
705
|
+
});
|
|
679
706
|
});
|
|
680
707
|
this.server.listen(this.port, this.host, () => {
|
|
681
708
|
console.log(`Gateway HTTP Server running at http://${this.host}:${this.port}/`);
|
|
@@ -32,6 +32,11 @@ export declare class WSServer extends EventEmitter {
|
|
|
32
32
|
addNewConnection(ws: WebSocketWithId, request: IncomingMessage): WebSocketClient;
|
|
33
33
|
setClientTag(clientId: string, tagKey: string, tagValue: string): void;
|
|
34
34
|
removeClientTag(clientId: string, tagKey: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Drop a connection whose url or headers resolve to an app name that can never exist. Closing beats tagging it with
|
|
37
|
+
* the raw value: the tag would end up as a map key and would bootstrap an app on every selector change.
|
|
38
|
+
*/
|
|
39
|
+
private rejectInvalidAppName;
|
|
35
40
|
setClientApp(client: WebSocketClient): Promise<void>;
|
|
36
41
|
removeConnection(id: string): void;
|
|
37
42
|
sendMessageToConnection(client: WebSocketClient, sendMessage: object): void;
|
package/lib/gateway/ws-server.js
CHANGED
|
@@ -80,10 +80,19 @@ const _WSServer = class _WSServer extends import_events.default {
|
|
|
80
80
|
});
|
|
81
81
|
import_gateway.Gateway.getInstance().on("appSelectorChanged", () => {
|
|
82
82
|
this.loopThroughConnections(async (client) => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
let handleAppName;
|
|
84
|
+
try {
|
|
85
|
+
handleAppName = await import_gateway.Gateway.getInstance().getRequestHandleAppName({
|
|
86
|
+
url: client.url,
|
|
87
|
+
headers: client.headers
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (!(error instanceof import_errors.InvalidAppNameError)) {
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
this.rejectInvalidAppName(client);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
87
96
|
for (const tag of client.tags) {
|
|
88
97
|
if (tag.startsWith("app#")) {
|
|
89
98
|
client.tags.delete(tag);
|
|
@@ -238,12 +247,29 @@ const _WSServer = class _WSServer extends import_events.default {
|
|
|
238
247
|
}
|
|
239
248
|
});
|
|
240
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Drop a connection whose url or headers resolve to an app name that can never exist. Closing beats tagging it with
|
|
252
|
+
* the raw value: the tag would end up as a map key and would bootstrap an app on every selector change.
|
|
253
|
+
*/
|
|
254
|
+
rejectInvalidAppName(client) {
|
|
255
|
+
client.ws.close(1008, "INVALID_APP_NAME");
|
|
256
|
+
this.removeConnection(client.id);
|
|
257
|
+
}
|
|
241
258
|
async setClientApp(client) {
|
|
242
259
|
const req = {
|
|
243
260
|
url: client.url,
|
|
244
261
|
headers: client.headers
|
|
245
262
|
};
|
|
246
|
-
|
|
263
|
+
let handleAppName;
|
|
264
|
+
try {
|
|
265
|
+
handleAppName = await import_gateway.Gateway.getInstance().getRequestHandleAppName(req);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (!(error instanceof import_errors.InvalidAppNameError)) {
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
this.rejectInvalidAppName(client);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
247
273
|
client.app = handleAppName;
|
|
248
274
|
console.log(`client tags: app#${handleAppName}`);
|
|
249
275
|
client.tags.add(`app#${handleAppName}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/server",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.24",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"types": "./lib/index.d.ts",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -10,21 +10,21 @@
|
|
|
10
10
|
"@koa/cors": "^5.0.0",
|
|
11
11
|
"@koa/multer": "^3.1.0",
|
|
12
12
|
"@koa/router": "^13.1.0",
|
|
13
|
-
"@nocobase/acl": "2.2.
|
|
14
|
-
"@nocobase/actions": "2.2.
|
|
15
|
-
"@nocobase/ai": "2.2.
|
|
16
|
-
"@nocobase/auth": "2.2.
|
|
17
|
-
"@nocobase/cache": "2.2.
|
|
18
|
-
"@nocobase/data-source-manager": "2.2.
|
|
19
|
-
"@nocobase/database": "2.2.
|
|
20
|
-
"@nocobase/evaluators": "2.2.
|
|
21
|
-
"@nocobase/lock-manager": "2.2.
|
|
22
|
-
"@nocobase/logger": "2.2.
|
|
23
|
-
"@nocobase/resourcer": "2.2.
|
|
24
|
-
"@nocobase/sdk": "2.2.
|
|
25
|
-
"@nocobase/snowflake-id": "2.2.
|
|
26
|
-
"@nocobase/telemetry": "2.2.
|
|
27
|
-
"@nocobase/utils": "2.2.
|
|
13
|
+
"@nocobase/acl": "2.2.24",
|
|
14
|
+
"@nocobase/actions": "2.2.24",
|
|
15
|
+
"@nocobase/ai": "2.2.24",
|
|
16
|
+
"@nocobase/auth": "2.2.24",
|
|
17
|
+
"@nocobase/cache": "2.2.24",
|
|
18
|
+
"@nocobase/data-source-manager": "2.2.24",
|
|
19
|
+
"@nocobase/database": "2.2.24",
|
|
20
|
+
"@nocobase/evaluators": "2.2.24",
|
|
21
|
+
"@nocobase/lock-manager": "2.2.24",
|
|
22
|
+
"@nocobase/logger": "2.2.24",
|
|
23
|
+
"@nocobase/resourcer": "2.2.24",
|
|
24
|
+
"@nocobase/sdk": "2.2.24",
|
|
25
|
+
"@nocobase/snowflake-id": "2.2.24",
|
|
26
|
+
"@nocobase/telemetry": "2.2.24",
|
|
27
|
+
"@nocobase/utils": "2.2.24",
|
|
28
28
|
"@types/ini": "^1.3.31",
|
|
29
29
|
"@types/koa-send": "^4.1.3",
|
|
30
30
|
"@types/multer": "^1.4.12",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"@types/serve-handler": "^6.1.1",
|
|
61
61
|
"@types/ws": "^8.5.5"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "b067d4c18834112e59bb72b2084e872c59d64f9c"
|
|
64
64
|
}
|