@xrystal/core 3.26.3 → 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
46
|
params: params || {},
|
|
46
|
-
|
|
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,45 +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
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
else if (typeof bodySource === 'string') {
|
|
123
|
-
text = bodySource;
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
return bodySource;
|
|
127
|
-
}
|
|
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) => {
|
|
128
128
|
if (!text)
|
|
129
129
|
return {};
|
|
130
|
-
if (contentType.includes(
|
|
130
|
+
if (contentType.includes("application/json")) {
|
|
131
131
|
try {
|
|
132
132
|
return JSON.parse(text);
|
|
133
133
|
}
|
|
@@ -135,77 +135,72 @@ export default class Controller extends BaseController {
|
|
|
135
135
|
return text;
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
-
else if (contentType.includes(
|
|
138
|
+
else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
139
139
|
return qs.parse(text);
|
|
140
140
|
}
|
|
141
|
-
|
|
141
|
+
try {
|
|
142
|
+
return JSON.parse(text);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
142
145
|
try {
|
|
143
|
-
return
|
|
146
|
+
return qs.parse(text);
|
|
144
147
|
}
|
|
145
148
|
catch {
|
|
146
|
-
|
|
147
|
-
return qs.parse(text);
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
return text;
|
|
151
|
-
}
|
|
149
|
+
return text;
|
|
152
150
|
}
|
|
153
151
|
}
|
|
154
152
|
};
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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());
|
|
161
162
|
}
|
|
162
|
-
|
|
163
|
-
if (typeof
|
|
164
|
-
|
|
165
|
-
const cloned = rawReq.clone ? rawReq.clone() : rawReq;
|
|
166
|
-
parsedBody = await consumeBody(await cloned.text());
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
parsedBody = {};
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
else if (typeof rawReq.on === 'function') {
|
|
173
|
-
parsedBody = await new Promise((resolve) => {
|
|
174
|
-
let bodyStr = '';
|
|
175
|
-
rawReq.on('data', (chunk) => { bodyStr += chunk; });
|
|
176
|
-
rawReq.on('end', async () => resolve(await consumeBody(bodyStr)));
|
|
177
|
-
rawReq.on('error', () => resolve({}));
|
|
178
|
-
});
|
|
179
|
-
}
|
|
163
|
+
catch {
|
|
164
|
+
if (typeof parsedBody === "string")
|
|
165
|
+
parsedBody = parseText(parsedBody);
|
|
180
166
|
}
|
|
181
167
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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);
|
|
186
179
|
}
|
|
187
|
-
catch { }
|
|
188
180
|
}
|
|
189
|
-
if (parsedBody && typeof parsedBody ===
|
|
181
|
+
if (parsedBody && typeof parsedBody === "object" && parsedBody.constructor?.name === "FormData") {
|
|
190
182
|
parsedBody = Object.fromEntries(parsedBody.entries());
|
|
191
183
|
}
|
|
192
184
|
if (store) {
|
|
193
185
|
if (!store.metadata)
|
|
194
186
|
store.metadata = { locals: {} };
|
|
195
187
|
store.metadata._parsedBody = parsedBody || {};
|
|
196
|
-
store.metadata._parsedQuery =
|
|
188
|
+
store.metadata._parsedQuery = currentReqBase.query;
|
|
197
189
|
}
|
|
198
190
|
const currentReq = this.req;
|
|
199
191
|
const p = {
|
|
200
192
|
req: currentReq,
|
|
201
193
|
res: currentRes,
|
|
202
194
|
body: currentReq.body,
|
|
195
|
+
query: currentReq.query,
|
|
203
196
|
queries: currentReq.query,
|
|
204
197
|
params: currentReq.params,
|
|
198
|
+
cookies: currentReq.cookies,
|
|
199
|
+
locals: currentRes.locals,
|
|
205
200
|
t: currentReq.t,
|
|
206
201
|
};
|
|
207
202
|
const extractMeta = (obj) => {
|
|
208
|
-
if (!obj || typeof obj !==
|
|
203
|
+
if (!obj || typeof obj !== "object")
|
|
209
204
|
return;
|
|
210
205
|
if (obj.code !== undefined && store?.metadata)
|
|
211
206
|
store.metadata._code = obj.code;
|
|
@@ -229,9 +224,9 @@ export default class Controller extends BaseController {
|
|
|
229
224
|
}
|
|
230
225
|
let logicResult = logic ? await logic(p) : {};
|
|
231
226
|
extractMeta(logicResult);
|
|
232
|
-
if (logicResult instanceof Response || logicResult?.constructor?.name ===
|
|
227
|
+
if (logicResult instanceof Response || logicResult?.constructor?.name === "Response")
|
|
233
228
|
return logicResult;
|
|
234
|
-
if (logicResult && typeof logicResult ===
|
|
229
|
+
if (logicResult && typeof logicResult === "object" && typeof logicResult.response === "function") {
|
|
235
230
|
logicResult = await logicResult.response();
|
|
236
231
|
extractMeta(logicResult);
|
|
237
232
|
}
|
|
@@ -245,17 +240,17 @@ export default class Controller extends BaseController {
|
|
|
245
240
|
return currentRes.status(200).send(resResult);
|
|
246
241
|
let finalData = undefined;
|
|
247
242
|
let finalExtraData = undefined;
|
|
248
|
-
let messageSource =
|
|
243
|
+
let messageSource = "success_process";
|
|
249
244
|
const extractData = (obj) => {
|
|
250
|
-
if (!obj || typeof obj !==
|
|
245
|
+
if (!obj || typeof obj !== "object")
|
|
251
246
|
return;
|
|
252
|
-
if (obj.payload && typeof obj.payload ===
|
|
247
|
+
if (obj.payload && typeof obj.payload === "object") {
|
|
253
248
|
finalData = obj.payload.data !== undefined ? obj.payload.data : obj.payload;
|
|
254
249
|
finalExtraData = obj.payload.extraData;
|
|
255
250
|
}
|
|
256
251
|
else if (obj.data !== undefined || obj.extraData !== undefined) {
|
|
257
252
|
finalData = obj.data;
|
|
258
|
-
finalExtraData = obj.
|
|
253
|
+
finalExtraData = obj.extraAddr;
|
|
259
254
|
}
|
|
260
255
|
else {
|
|
261
256
|
const hasMeta = obj.status !== undefined || obj.message !== undefined || obj.code !== undefined || obj.statusCode !== undefined;
|
|
@@ -265,7 +260,7 @@ export default class Controller extends BaseController {
|
|
|
265
260
|
if (obj.message)
|
|
266
261
|
messageSource = obj.message;
|
|
267
262
|
};
|
|
268
|
-
if (typeof resResult ===
|
|
263
|
+
if (typeof resResult === "string") {
|
|
269
264
|
messageSource = resResult;
|
|
270
265
|
extractData(logicResult);
|
|
271
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
|
};
|