@xrystal/core 3.26.2 → 3.26.4
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/package.json
CHANGED
|
@@ -2,14 +2,15 @@ import { AsyncLocalStorage } from 'node:async_hooks';
|
|
|
2
2
|
import Logger from '../logger';
|
|
3
3
|
import System from '../system';
|
|
4
4
|
import { IProvide, ProtocolEnum } from '../../utils/index';
|
|
5
|
-
export interface CustomRequest {
|
|
6
|
-
accounts?: any;
|
|
7
|
-
url: string;
|
|
8
|
-
method: string;
|
|
5
|
+
export interface CustomRequest<B = any, Q = any, P = any> {
|
|
9
6
|
headers: Record<string, any>;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
method: string;
|
|
8
|
+
url: string;
|
|
9
|
+
body: B;
|
|
10
|
+
params: P;
|
|
11
|
+
query: Q;
|
|
12
|
+
cookies: Record<string, any>;
|
|
13
|
+
ip: string;
|
|
13
14
|
lang: string;
|
|
14
15
|
t: (k: string, args?: any) => string;
|
|
15
16
|
}
|
|
@@ -76,9 +77,40 @@ export default abstract class Controller extends BaseController implements IProv
|
|
|
76
77
|
constructor();
|
|
77
78
|
onInit(): Promise<void>;
|
|
78
79
|
protected parseMessage(msg: any, t: Function, isError?: boolean): string;
|
|
79
|
-
schema({ checks, logic, response }: {
|
|
80
|
-
checks?: (args:
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
schema<B = any, Q = any, P = any>({ checks, logic, response }: {
|
|
81
|
+
checks?: (args: {
|
|
82
|
+
req: CustomRequest<B, Q, P>;
|
|
83
|
+
res: CustomResponse;
|
|
84
|
+
body: B;
|
|
85
|
+
query: Q;
|
|
86
|
+
queries: Q;
|
|
87
|
+
params: P;
|
|
88
|
+
cookies: Record<string, any>;
|
|
89
|
+
locals: Record<string, any>;
|
|
90
|
+
t: Function;
|
|
91
|
+
}) => Promise<any>;
|
|
92
|
+
logic?: (args: {
|
|
93
|
+
req: CustomRequest<B, Q, P>;
|
|
94
|
+
res: CustomResponse;
|
|
95
|
+
body: B;
|
|
96
|
+
query: Q;
|
|
97
|
+
queries: Q;
|
|
98
|
+
params: P;
|
|
99
|
+
cookies: Record<string, any>;
|
|
100
|
+
locals: Record<string, any>;
|
|
101
|
+
t: Function;
|
|
102
|
+
}) => Promise<any>;
|
|
103
|
+
response?: (args: {
|
|
104
|
+
req: CustomRequest<B, Q, P>;
|
|
105
|
+
res: CustomResponse;
|
|
106
|
+
body: B;
|
|
107
|
+
query: Q;
|
|
108
|
+
queries: Q;
|
|
109
|
+
params: P;
|
|
110
|
+
cookies: Record<string, any>;
|
|
111
|
+
locals: Record<string, any>;
|
|
112
|
+
t: Function;
|
|
113
|
+
logicResult: any;
|
|
114
|
+
}) => Promise<any>;
|
|
83
115
|
}): Promise<any>;
|
|
84
116
|
}
|
|
@@ -2,7 +2,7 @@ import qs from 'qs';
|
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
3
|
import Logger from '../logger';
|
|
4
4
|
import System from '../system';
|
|
5
|
-
import { LoggerLayerEnum, ProtocolEnum, responseMessageHelper, ResponseSchema, x } from '../../utils/index';
|
|
5
|
+
import { getClientIp, LoggerLayerEnum, ProtocolEnum, responseMessageHelper, ResponseSchema, x } from '../../utils/index';
|
|
6
6
|
export const controllerContextStorage = new AsyncLocalStorage();
|
|
7
7
|
export const getControllerCtx = () => controllerContextStorage.getStore();
|
|
8
8
|
export class BaseController {
|
|
@@ -22,10 +22,10 @@ export class BaseController {
|
|
|
22
22
|
const identityT = (k) => k;
|
|
23
23
|
const body = store?.metadata?._parsedBody || ctx.body || req.body || ctx.request?.body || {};
|
|
24
24
|
let query = store?.metadata?._parsedQuery || ctx.query || req.query || ctx.request?.query || {};
|
|
25
|
-
const urlStr = ctx.request?.url || req.url || ctx.url ||
|
|
26
|
-
if ((!query || Object.keys(query).length === 0) && urlStr && typeof urlStr ===
|
|
27
|
-
if (urlStr.includes(
|
|
28
|
-
const queryString = urlStr.split(
|
|
25
|
+
const urlStr = ctx.request?.url || req.url || ctx.url || "";
|
|
26
|
+
if ((!query || Object.keys(query).length === 0) && urlStr && typeof urlStr === "string") {
|
|
27
|
+
if (urlStr.includes("?")) {
|
|
28
|
+
const queryString = urlStr.split("?")[1];
|
|
29
29
|
if (queryString)
|
|
30
30
|
query = qs.parse(queryString);
|
|
31
31
|
}
|
|
@@ -36,14 +36,17 @@ export class BaseController {
|
|
|
36
36
|
...(ctx.request?.params || {}),
|
|
37
37
|
...(ctx.request?.routeParams || {})
|
|
38
38
|
};
|
|
39
|
+
const cookies = ctx.cookie || req.cookies || ctx.request?.cookie || ctx.request?.cookies || {};
|
|
39
40
|
return {
|
|
40
41
|
url: urlStr,
|
|
41
|
-
method: ctx.method || req.method || ctx.request?.method ||
|
|
42
|
+
method: (ctx.method || req.method || ctx.request?.method || "").toUpperCase(),
|
|
42
43
|
headers: ctx.headers || req.headers || ctx.request?.headers || {},
|
|
43
44
|
body,
|
|
44
45
|
query: query || {},
|
|
45
|
-
params,
|
|
46
|
-
|
|
46
|
+
params: params || {},
|
|
47
|
+
cookies: cookies || {},
|
|
48
|
+
ip: getClientIp(req, ctx),
|
|
49
|
+
lang: ctx.lang || req.lang || "en",
|
|
47
50
|
t: ctx.t || req.t || identityT,
|
|
48
51
|
accounts: ctx.accounts || req.accounts || ctx.user
|
|
49
52
|
};
|
|
@@ -67,16 +70,16 @@ export class BaseController {
|
|
|
67
70
|
return data;
|
|
68
71
|
if (data instanceof Response)
|
|
69
72
|
return data;
|
|
70
|
-
const isBinary = data instanceof Blob || data instanceof Uint8Array || data instanceof ArrayBuffer || (typeof data?.pipe ===
|
|
73
|
+
const isBinary = data instanceof Blob || data instanceof Uint8Array || data instanceof ArrayBuffer || (typeof data?.pipe === "function");
|
|
71
74
|
const isRaw = store.metadata?._isRaw || isBinary;
|
|
72
|
-
let contentType = store.metadata?._contentType || (isRaw ?
|
|
75
|
+
let contentType = store.metadata?._contentType || (isRaw ? "application/octet-stream" : "application/json");
|
|
73
76
|
let bizCode = store.metadata._code || 200;
|
|
74
77
|
let httpStatus = store.metadata._statusCode;
|
|
75
78
|
if (!httpStatus) {
|
|
76
79
|
httpStatus = (bizCode >= 100 && bizCode <= 101) || (bizCode >= 200 && bizCode <= 599) ? bizCode : (bizCode === 0 || (bizCode >= 200 && bizCode <= 299) ? 200 : 400);
|
|
77
80
|
}
|
|
78
|
-
const body = isRaw || typeof data ===
|
|
79
|
-
return new Response(body, { status: httpStatus, headers: {
|
|
81
|
+
const body = isRaw || typeof data === "string" ? data : JSON.stringify(data?.getResponse ? data.getResponse : data);
|
|
82
|
+
return new Response(body, { status: httpStatus, headers: { "content-type": contentType } });
|
|
80
83
|
},
|
|
81
84
|
json(data) { return this.send(data); }
|
|
82
85
|
};
|
|
@@ -89,46 +92,42 @@ export default class Controller extends BaseController {
|
|
|
89
92
|
this.supportedProtocols = Array.isArray(protocols) ? protocols : [protocols || ProtocolEnum.HTTP];
|
|
90
93
|
}
|
|
91
94
|
parseMessage(msg, t, isError = false) {
|
|
92
|
-
const content = String(msg ||
|
|
95
|
+
const content = String(msg || "");
|
|
93
96
|
if (!content)
|
|
94
|
-
return isError ? responseMessageHelper.unsuccessful(
|
|
95
|
-
let method = isError ?
|
|
96
|
-
let payloadString =
|
|
97
|
-
if (content.startsWith(
|
|
98
|
-
const parts = content.split(
|
|
97
|
+
return isError ? responseMessageHelper.unsuccessful("", "", t) : responseMessageHelper.successfully("", "", t);
|
|
98
|
+
let method = isError ? "unsuccessful" : "successfully";
|
|
99
|
+
let payloadString = "";
|
|
100
|
+
if (content.startsWith("@")) {
|
|
101
|
+
const parts = content.split(" ");
|
|
99
102
|
method = parts[0].substring(1);
|
|
100
|
-
payloadString = parts.slice(1).join(
|
|
103
|
+
payloadString = parts.slice(1).join(" ");
|
|
101
104
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
else {
|
|
106
|
+
payloadString = content;
|
|
107
|
+
}
|
|
108
|
+
const params = payloadString ? payloadString.split("-").map(p => p.trim()).filter(p => p !== "") : [];
|
|
109
|
+
const p1 = params.length > 0 ? params[0] : (content.startsWith("@") ? undefined : "");
|
|
110
|
+
const p2 = params.length > 1 ? params.slice(1).join(" ") : (content.startsWith("@") ? undefined : "");
|
|
105
111
|
if (responseMessageHelper[method]) {
|
|
106
112
|
return responseMessageHelper[method](p1, p2, t);
|
|
107
113
|
}
|
|
108
|
-
return isError ? responseMessageHelper.unsuccessful(p1, p2, t) : responseMessageHelper.successfully(p1, p2, t);
|
|
114
|
+
return isError ? responseMessageHelper.unsuccessful(p1 || "", p2 || "", t) : responseMessageHelper.successfully(p1 || "", p2 || "", t);
|
|
109
115
|
}
|
|
110
116
|
async schema({ checks, logic, response }) {
|
|
111
117
|
const currentRes = this.res;
|
|
112
118
|
const store = this.currentStore;
|
|
113
119
|
try {
|
|
114
120
|
const rawReq = store?.req || store?.ctx?.request || store?.ctx?.req;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
else if (typeof bodySource === 'string') {
|
|
124
|
-
text = bodySource;
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
return bodySource;
|
|
128
|
-
}
|
|
121
|
+
const currentReqBase = this.req;
|
|
122
|
+
const method = currentReqBase.method;
|
|
123
|
+
const headers = currentReqBase.headers;
|
|
124
|
+
const contentType = (headers["content-type"] || "").toLowerCase();
|
|
125
|
+
const contentLength = parseInt(headers["content-length"] || "-1");
|
|
126
|
+
let parsedBody = currentReqBase.body;
|
|
127
|
+
const parseText = (text) => {
|
|
129
128
|
if (!text)
|
|
130
129
|
return {};
|
|
131
|
-
if (contentType.includes(
|
|
130
|
+
if (contentType.includes("application/json")) {
|
|
132
131
|
try {
|
|
133
132
|
return JSON.parse(text);
|
|
134
133
|
}
|
|
@@ -136,70 +135,72 @@ export default class Controller extends BaseController {
|
|
|
136
135
|
return text;
|
|
137
136
|
}
|
|
138
137
|
}
|
|
139
|
-
else if (contentType.includes(
|
|
138
|
+
else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
140
139
|
return qs.parse(text);
|
|
141
140
|
}
|
|
142
|
-
|
|
141
|
+
try {
|
|
142
|
+
return JSON.parse(text);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
143
145
|
try {
|
|
144
|
-
return
|
|
146
|
+
return qs.parse(text);
|
|
145
147
|
}
|
|
146
148
|
catch {
|
|
147
|
-
|
|
148
|
-
return qs.parse(text);
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
return text;
|
|
152
|
-
}
|
|
149
|
+
return text;
|
|
153
150
|
}
|
|
154
151
|
}
|
|
155
152
|
};
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
153
|
+
const isStream = (obj) => obj instanceof ReadableStream || (obj && typeof obj.getReader === "function");
|
|
154
|
+
const isProcessed = parsedBody && typeof parsedBody === "object" && !isStream(parsedBody) && Object.keys(parsedBody).length > 0;
|
|
155
|
+
if (this.protocol !== ProtocolEnum.WEBSOCKET && !["GET", "HEAD", "OPTIONS"].includes(method) && !isProcessed) {
|
|
156
|
+
const isModern = rawReq && typeof rawReq.json === "function" && typeof rawReq.text === "function";
|
|
157
|
+
const isNode = rawReq && typeof rawReq.on === "function";
|
|
158
|
+
if (isModern && (isStream(parsedBody) || contentLength > 0)) {
|
|
159
|
+
try {
|
|
160
|
+
const cloned = rawReq.clone();
|
|
161
|
+
parsedBody = parseText(await cloned.text());
|
|
162
162
|
}
|
|
163
|
-
|
|
164
|
-
if (typeof
|
|
165
|
-
|
|
166
|
-
const cloned = rawReq.clone ? rawReq.clone() : rawReq;
|
|
167
|
-
parsedBody = await consumeBody(await cloned.text());
|
|
168
|
-
}
|
|
169
|
-
catch {
|
|
170
|
-
parsedBody = {};
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
else if (typeof rawReq.on === 'function') {
|
|
174
|
-
parsedBody = await new Promise((resolve) => {
|
|
175
|
-
let bodyStr = '';
|
|
176
|
-
rawReq.on('data', (chunk) => { bodyStr += chunk; });
|
|
177
|
-
rawReq.on('end', async () => resolve(await consumeBody(bodyStr)));
|
|
178
|
-
rawReq.on('error', () => resolve({}));
|
|
179
|
-
});
|
|
180
|
-
}
|
|
163
|
+
catch {
|
|
164
|
+
if (typeof parsedBody === "string")
|
|
165
|
+
parsedBody = parseText(parsedBody);
|
|
181
166
|
}
|
|
182
167
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
168
|
+
else if (isNode && contentLength !== 0) {
|
|
169
|
+
parsedBody = await new Promise((resolve) => {
|
|
170
|
+
let bodyStr = "";
|
|
171
|
+
const timer = setTimeout(() => resolve({}), 200);
|
|
172
|
+
rawReq.on("data", (chunk) => { bodyStr += chunk; });
|
|
173
|
+
rawReq.on("end", () => { clearTimeout(timer); resolve(parseText(bodyStr)); });
|
|
174
|
+
rawReq.on("error", () => { clearTimeout(timer); resolve({}); });
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else if (typeof parsedBody === "string" && parsedBody.length > 0) {
|
|
178
|
+
parsedBody = parseText(parsedBody);
|
|
187
179
|
}
|
|
188
|
-
catch { }
|
|
189
180
|
}
|
|
190
|
-
if (parsedBody && typeof parsedBody ===
|
|
181
|
+
if (parsedBody && typeof parsedBody === "object" && parsedBody.constructor?.name === "FormData") {
|
|
191
182
|
parsedBody = Object.fromEntries(parsedBody.entries());
|
|
192
183
|
}
|
|
193
184
|
if (store) {
|
|
194
185
|
if (!store.metadata)
|
|
195
186
|
store.metadata = { locals: {} };
|
|
196
187
|
store.metadata._parsedBody = parsedBody || {};
|
|
197
|
-
store.metadata._parsedQuery =
|
|
188
|
+
store.metadata._parsedQuery = currentReqBase.query;
|
|
198
189
|
}
|
|
199
190
|
const currentReq = this.req;
|
|
200
|
-
const p = {
|
|
191
|
+
const p = {
|
|
192
|
+
req: currentReq,
|
|
193
|
+
res: currentRes,
|
|
194
|
+
body: currentReq.body,
|
|
195
|
+
query: currentReq.query,
|
|
196
|
+
queries: currentReq.query,
|
|
197
|
+
params: currentReq.params,
|
|
198
|
+
cookies: currentReq.cookies,
|
|
199
|
+
locals: currentRes.locals,
|
|
200
|
+
t: currentReq.t,
|
|
201
|
+
};
|
|
201
202
|
const extractMeta = (obj) => {
|
|
202
|
-
if (!obj || typeof obj !==
|
|
203
|
+
if (!obj || typeof obj !== "object")
|
|
203
204
|
return;
|
|
204
205
|
if (obj.code !== undefined && store?.metadata)
|
|
205
206
|
store.metadata._code = obj.code;
|
|
@@ -223,9 +224,9 @@ export default class Controller extends BaseController {
|
|
|
223
224
|
}
|
|
224
225
|
let logicResult = logic ? await logic(p) : {};
|
|
225
226
|
extractMeta(logicResult);
|
|
226
|
-
if (logicResult instanceof Response || logicResult?.constructor?.name ===
|
|
227
|
+
if (logicResult instanceof Response || logicResult?.constructor?.name === "Response")
|
|
227
228
|
return logicResult;
|
|
228
|
-
if (logicResult && typeof logicResult ===
|
|
229
|
+
if (logicResult && typeof logicResult === "object" && typeof logicResult.response === "function") {
|
|
229
230
|
logicResult = await logicResult.response();
|
|
230
231
|
extractMeta(logicResult);
|
|
231
232
|
}
|
|
@@ -239,17 +240,17 @@ export default class Controller extends BaseController {
|
|
|
239
240
|
return currentRes.status(200).send(resResult);
|
|
240
241
|
let finalData = undefined;
|
|
241
242
|
let finalExtraData = undefined;
|
|
242
|
-
let messageSource =
|
|
243
|
+
let messageSource = "success_process";
|
|
243
244
|
const extractData = (obj) => {
|
|
244
|
-
if (!obj || typeof obj !==
|
|
245
|
+
if (!obj || typeof obj !== "object")
|
|
245
246
|
return;
|
|
246
|
-
if (obj.payload && typeof obj.payload ===
|
|
247
|
+
if (obj.payload && typeof obj.payload === "object") {
|
|
247
248
|
finalData = obj.payload.data !== undefined ? obj.payload.data : obj.payload;
|
|
248
249
|
finalExtraData = obj.payload.extraData;
|
|
249
250
|
}
|
|
250
251
|
else if (obj.data !== undefined || obj.extraData !== undefined) {
|
|
251
252
|
finalData = obj.data;
|
|
252
|
-
finalExtraData = obj.
|
|
253
|
+
finalExtraData = obj.extraAddr;
|
|
253
254
|
}
|
|
254
255
|
else {
|
|
255
256
|
const hasMeta = obj.status !== undefined || obj.message !== undefined || obj.code !== undefined || obj.statusCode !== undefined;
|
|
@@ -259,7 +260,7 @@ export default class Controller extends BaseController {
|
|
|
259
260
|
if (obj.message)
|
|
260
261
|
messageSource = obj.message;
|
|
261
262
|
};
|
|
262
|
-
if (typeof resResult ===
|
|
263
|
+
if (typeof resResult === "string") {
|
|
263
264
|
messageSource = resResult;
|
|
264
265
|
extractData(logicResult);
|
|
265
266
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const getClientIp: (req: any) => any;
|
|
1
|
+
export declare const getClientIp: (req: any, ctx?: any) => any;
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
-
export const getClientIp = (req) => {
|
|
2
|
-
|
|
1
|
+
export const getClientIp = (req, ctx = {}) => {
|
|
2
|
+
try {
|
|
3
|
+
const forwarded = req?.headers?.["x-forwarded-for"] || ctx?.headers?.["x-forwarded-for"];
|
|
4
|
+
if (forwarded)
|
|
5
|
+
return forwarded.split(",")[0].trim().replace("::ffff:", "");
|
|
6
|
+
if (ctx?.ip)
|
|
7
|
+
return ctx.ip.replace("::ffff:", "");
|
|
8
|
+
if (req?.ip)
|
|
9
|
+
return req.ip.replace("::ffff:", "");
|
|
10
|
+
const remoteAddr = req?.socket?.remoteAddress ||
|
|
11
|
+
ctx?.req?.socket?.remoteAddress ||
|
|
12
|
+
req?.remoteAddress ||
|
|
13
|
+
ctx?.address;
|
|
14
|
+
if (remoteAddr)
|
|
15
|
+
return remoteAddr.replace("::ffff:", "");
|
|
16
|
+
return "127.0.0.1";
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
return "127.0.0.1";
|
|
20
|
+
}
|
|
3
21
|
};
|