@yumerijs/core 1.3.3 → 2.0.0

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/dist/route.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Route = void 0;
4
+ const ws_1 = require("ws");
4
5
  function parsePatternToSegments(pattern) {
5
6
  // Normalize: remove leading/trailing slashes for consistent splitting
6
7
  const norm = pattern.replace(/^\/+|\/+$/g, '');
@@ -39,6 +40,7 @@ class Route {
39
40
  handler = null;
40
41
  middlewares = [];
41
42
  allowedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'];
43
+ ws = null;
42
44
  /**
43
45
  * 创建路由
44
46
  * @param path 路由路径
@@ -73,129 +75,135 @@ class Route {
73
75
  * @returns 匹配结果
74
76
  */
75
77
  match(pathname) {
76
- // normalize pathname: remove leading/trailing slashes
77
- const normPath = pathname.replace(/^\/+|\/+$/g, '');
78
- const parts = normPath === '' ? [] : normPath.split('/');
79
- const params = {};
80
- const pathParams = [];
81
- let i = 0; // index into segments (pattern)
82
- let j = 0; // index into parts (path)
83
- while (i < this.segments.length) {
84
- const seg = this.segments[i];
85
- // static must match exactly
86
- if (seg.type === 'static') {
87
- if (j >= parts.length)
88
- return null;
89
- if (parts[j] !== seg.value)
90
- return null;
91
- i++;
92
- j++;
93
- continue;
94
- }
95
- // seg.type === 'param'
96
- const nextSegIsStatic = (() => {
97
- const next = this.segments[i + 1];
98
- return !!next && next.type === 'static';
99
- })();
100
- switch (seg.modifier) {
101
- case '': { // required single segment
78
+ if (pathname.startsWith('/')) {
79
+ // normalize pathname: remove leading/trailing slashes
80
+ const normPath = pathname.replace(/^\/+|\/+$/g, '');
81
+ const parts = normPath === '' ? [] : normPath.split('/');
82
+ const params = {};
83
+ const pathParams = [];
84
+ let i = 0; // index into segments (pattern)
85
+ let j = 0; // index into parts (path)
86
+ while (i < this.segments.length) {
87
+ const seg = this.segments[i];
88
+ // static must match exactly
89
+ if (seg.type === 'static') {
102
90
  if (j >= parts.length)
103
91
  return null;
104
- params[seg.name] = parts[j];
105
- pathParams.push(parts[j]);
92
+ if (parts[j] !== seg.value)
93
+ return null;
106
94
  i++;
107
95
  j++;
108
- break;
96
+ continue;
109
97
  }
110
- case '?': { // optional single segment
111
- // If next segment is static, we should not consume the part when it equals that static
112
- if (j < parts.length) {
113
- if (nextSegIsStatic) {
114
- // peek: if current part equals next static, then this optional param is absent
115
- const nextStatic = this.segments[i + 1].value;
116
- if (parts[j] === nextStatic) {
117
- params[seg.name] = undefined;
118
- pathParams.push('');
119
- i++;
120
- break;
121
- }
122
- }
123
- // otherwise consume one segment as the optional param
98
+ // seg.type === 'param'
99
+ const nextSegIsStatic = (() => {
100
+ const next = this.segments[i + 1];
101
+ return !!next && next.type === 'static';
102
+ })();
103
+ switch (seg.modifier) {
104
+ case '': { // required single segment
105
+ if (j >= parts.length)
106
+ return null;
124
107
  params[seg.name] = parts[j];
125
108
  pathParams.push(parts[j]);
109
+ i++;
126
110
  j++;
111
+ break;
127
112
  }
128
- else {
129
- params[seg.name] = undefined;
130
- pathParams.push('');
131
- }
132
- i++;
133
- break;
134
- }
135
- case '+': { // required multi (one or more segments)
136
- if (j >= parts.length)
137
- return null; // must have at least one
138
- // find boundary where next static occurs (if any)
139
- let end = parts.length;
140
- if (nextSegIsStatic) {
141
- const nextStatic = this.segments[i + 1].value;
142
- let found = -1;
143
- for (let k = j; k < parts.length; k++) {
144
- if (parts[k] === nextStatic) {
145
- found = k;
146
- break;
113
+ case '?': { // optional single segment
114
+ // If next segment is static, we should not consume the part when it equals that static
115
+ if (j < parts.length) {
116
+ if (nextSegIsStatic) {
117
+ // peek: if current part equals next static, then this optional param is absent
118
+ const nextStatic = this.segments[i + 1].value;
119
+ if (parts[j] === nextStatic) {
120
+ params[seg.name] = undefined;
121
+ pathParams.push('');
122
+ i++;
123
+ break;
124
+ }
147
125
  }
126
+ // otherwise consume one segment as the optional param
127
+ params[seg.name] = parts[j];
128
+ pathParams.push(parts[j]);
129
+ j++;
130
+ }
131
+ else {
132
+ params[seg.name] = undefined;
133
+ pathParams.push('');
148
134
  }
149
- if (found === -1)
150
- return null; // next static not found -> cannot satisfy pattern
151
- end = found;
152
- if (end === j)
153
- return null; // need at least one segment
135
+ i++;
136
+ break;
154
137
  }
155
- // consume parts[j..end-1]
156
- const value = parts.slice(j, end).join('/');
157
- params[seg.name] = value;
158
- pathParams.push(value);
159
- j = end;
160
- i++;
161
- break;
162
- }
163
- case '*': { // optional multi (zero or more)
164
- // If next static exists, consume until that static; else consume all remainder (may be zero)
165
- let end = parts.length;
166
- if (nextSegIsStatic) {
167
- const nextStatic = this.segments[i + 1].value;
168
- let found = -1;
169
- for (let k = j; k < parts.length; k++) {
170
- if (parts[k] === nextStatic) {
171
- found = k;
172
- break;
138
+ case '+': { // required multi (one or more segments)
139
+ if (j >= parts.length)
140
+ return null; // must have at least one
141
+ // find boundary where next static occurs (if any)
142
+ let end = parts.length;
143
+ if (nextSegIsStatic) {
144
+ const nextStatic = this.segments[i + 1].value;
145
+ let found = -1;
146
+ for (let k = j; k < parts.length; k++) {
147
+ if (parts[k] === nextStatic) {
148
+ found = k;
149
+ break;
150
+ }
173
151
  }
174
- }
175
- if (found === -1) {
176
- // next static not found => we cannot match the remainder to the next pattern,
177
- // but '*' can consume all remainder (and then next static will fail later)
178
- end = parts.length;
179
- }
180
- else {
152
+ if (found === -1)
153
+ return null; // next static not found -> cannot satisfy pattern
181
154
  end = found;
155
+ if (end === j)
156
+ return null; // need at least one segment
182
157
  }
158
+ // consume parts[j..end-1]
159
+ const value = parts.slice(j, end).join('/');
160
+ params[seg.name] = value;
161
+ pathParams.push(value);
162
+ j = end;
163
+ i++;
164
+ break;
183
165
  }
184
- const value = parts.slice(j, end).join('/');
185
- params[seg.name] = value === '' ? undefined : value;
186
- pathParams.push(value === '' ? '' : value);
187
- j = end;
188
- i++;
189
- break;
166
+ case '*': { // optional multi (zero or more)
167
+ // If next static exists, consume until that static; else consume all remainder (may be zero)
168
+ let end = parts.length;
169
+ if (nextSegIsStatic) {
170
+ const nextStatic = this.segments[i + 1].value;
171
+ let found = -1;
172
+ for (let k = j; k < parts.length; k++) {
173
+ if (parts[k] === nextStatic) {
174
+ found = k;
175
+ break;
176
+ }
177
+ }
178
+ if (found === -1) {
179
+ // next static not found => we cannot match the remainder to the next pattern,
180
+ // but '*' can consume all remainder (and then next static will fail later)
181
+ end = parts.length;
182
+ }
183
+ else {
184
+ end = found;
185
+ }
186
+ }
187
+ const value = parts.slice(j, end).join('/');
188
+ params[seg.name] = value === '' ? undefined : value;
189
+ pathParams.push(value === '' ? '' : value);
190
+ j = end;
191
+ i++;
192
+ break;
193
+ }
194
+ default:
195
+ return null;
190
196
  }
191
- default:
192
- return null;
193
197
  }
198
+ // after finishing pattern, path must be fully consumed (no extra segments)
199
+ if (j !== parts.length)
200
+ return null;
201
+ return { params, pathParams };
194
202
  }
195
- // after finishing pattern, path must be fully consumed (no extra segments)
196
- if (j !== parts.length)
197
- return null;
198
- return { params, pathParams };
203
+ else if (pathname.startsWith('root') && this.path == pathname) {
204
+ return { params: { pathname }, pathParams: [pathname] };
205
+ }
206
+ return null;
199
207
  }
200
208
  /**
201
209
  * 执行路由处理器
@@ -217,5 +225,15 @@ class Route {
217
225
  this.allowedMethods = methods;
218
226
  return this;
219
227
  }
228
+ /**
229
+ * 注册Ws处理器
230
+ * @param handler 处理器
231
+ */
232
+ wsOn(event, handler) {
233
+ if (!this.ws)
234
+ this.ws = new ws_1.WebSocketServer({ noServer: true });
235
+ this.ws.on(event, handler);
236
+ return this;
237
+ }
220
238
  }
221
239
  exports.Route = Route;
package/dist/server.d.ts CHANGED
@@ -7,19 +7,16 @@ export interface ServerConfig {
7
7
  enableWs: boolean;
8
8
  }
9
9
  export declare class Server {
10
- private core;
10
+ core: Core;
11
11
  private port;
12
12
  private host;
13
13
  private enableCors;
14
14
  private staticDir;
15
- private enableWs;
16
15
  private httpServer;
17
- private wsServer;
18
16
  constructor(core: Core, config?: Partial<ServerConfig>);
19
17
  private createSession;
20
18
  private parseCookies;
21
19
  private getClientIP;
22
- private parseRequestBody;
23
20
  private serveStaticFile;
24
21
  start(): Promise<void>;
25
22
  stop(): void;
package/dist/server.js CHANGED
@@ -43,8 +43,6 @@ const http_1 = __importDefault(require("http"));
43
43
  const fs = __importStar(require("fs"));
44
44
  const path = __importStar(require("path"));
45
45
  const url_1 = require("url");
46
- const ws_1 = __importDefault(require("ws"));
47
- const formidable = __importStar(require("formidable"));
48
46
  const mime = __importStar(require("mime-types"));
49
47
  const logger = new logger_1.Logger('server');
50
48
  class Server {
@@ -53,19 +51,16 @@ class Server {
53
51
  host;
54
52
  enableCors;
55
53
  staticDir;
56
- enableWs;
57
54
  httpServer = null;
58
- wsServer = null;
59
55
  constructor(core, config = {}) {
60
56
  this.core = core;
61
57
  this.port = config.port ?? 14510;
62
58
  this.host = config.host ?? '0.0.0.0';
63
59
  this.enableCors = config.enableCors ?? true;
64
60
  this.staticDir = config.staticDir ?? 'static';
65
- this.enableWs = config.enableWs ?? true;
66
61
  }
67
- createSession(ip, cookies, res, req, extra = {}) {
68
- const session = new session_1.Session(ip, cookies, this, req, res);
62
+ createSession(ip, cookies, res, req, pathname, extra = {}) {
63
+ const session = new session_1.Session(ip, cookies, this, req, res, pathname);
69
64
  Object.assign(session.properties, extra);
70
65
  session.protocol = extra.protocol ?? 'http';
71
66
  return session;
@@ -85,46 +80,6 @@ class Server {
85
80
  const xff = req.headers['x-forwarded-for'];
86
81
  return xff ? xff.split(',')[0].trim() : req.socket.remoteAddress || '127.0.0.1';
87
82
  }
88
- async parseRequestBody(req) {
89
- return new Promise((resolve, reject) => {
90
- const contentType = (req.headers['content-type'] || '').toLowerCase();
91
- if (contentType.includes('application/json') || contentType.includes('application/x-www-form-urlencoded')) {
92
- let body = '';
93
- req.on('data', chunk => body += chunk.toString());
94
- req.on('end', () => {
95
- try {
96
- if (contentType.includes('application/json'))
97
- resolve(JSON.parse(body));
98
- else {
99
- const params = new URLSearchParams(body);
100
- const parsed = {};
101
- params.forEach((v, k) => parsed[k] = v);
102
- resolve(parsed);
103
- }
104
- }
105
- catch (e) {
106
- reject(e);
107
- }
108
- });
109
- }
110
- else if (contentType.includes('multipart/form-data')) {
111
- const form = formidable.formidable({});
112
- form.parse(req, (err, fields) => {
113
- if (err)
114
- return reject(err);
115
- const parsed = {};
116
- Object.keys(fields).forEach(k => {
117
- const val = fields[k];
118
- parsed[k] = Array.isArray(val) && val.length === 1 ? val[0] : val;
119
- });
120
- resolve(parsed);
121
- });
122
- }
123
- else
124
- resolve({});
125
- req.on('error', reject);
126
- });
127
- }
128
83
  serveStaticFile(pathname, res) {
129
84
  const fullPath = path.join(process.cwd(), this.staticDir, pathname);
130
85
  fs.readFile(fullPath, (err, data) => {
@@ -154,25 +109,9 @@ class Server {
154
109
  const queryParams = url.searchParams;
155
110
  const ip = this.getClientIP(req);
156
111
  const cookies = this.parseCookies(req);
157
- const session = this.createSession(ip, cookies, res, req, { protocol: 'http', header: req.headers });
158
- if (req.method?.toLowerCase() === 'post') {
159
- try {
160
- const body = await this.parseRequestBody(req);
161
- Object.entries(body).forEach(([k, v]) => {
162
- if (typeof v === 'string')
163
- queryParams.set(k, v);
164
- else if (Array.isArray(v))
165
- v.forEach(val => queryParams.append(k, val));
166
- });
167
- }
168
- catch (err) {
169
- res.writeHead(400, { 'Content-Type': 'text/plain' });
170
- res.end('Invalid request body');
171
- logger.error('Request body parse error:', err);
172
- return;
173
- }
174
- }
112
+ const session = this.createSession(ip, cookies, res, req, pathname, { protocol: 'http', header: req.headers });
175
113
  const route = this.core.getRoute(pathname);
114
+ const rootroute = this.core.getRoute('root');
176
115
  if (route && route.allowedMethods.includes(req.method ?? 'GET')) {
177
116
  const matched = await this.core.executeRoute(pathname, session, queryParams);
178
117
  if (!matched) {
@@ -188,42 +127,59 @@ class Server {
188
127
  res.end(session.body);
189
128
  }
190
129
  }
130
+ else if (rootroute && rootroute.allowedMethods.includes(req.method ?? 'GET')) {
131
+ const matched = await this.core.executeRoute('root', session, queryParams);
132
+ if (!matched) {
133
+ this.serveStaticFile(pathname, res);
134
+ }
135
+ else {
136
+ let head = session.head;
137
+ head['Set-Cookie'] = Object.entries(session.newCookie).map(([k, v]) => `${k}=${v}`);
138
+ if (this.enableCors) {
139
+ head['Access-Control-Allow-Origin'] = '*';
140
+ }
141
+ res.writeHead(session.status ?? 200, head);
142
+ res.end(session.body);
143
+ }
144
+ }
191
145
  else {
192
146
  this.serveStaticFile(pathname, res);
193
147
  }
194
148
  });
195
- if (this.enableWs) {
196
- this.wsServer = new ws_1.default.Server({ server: this.httpServer });
197
- this.wsServer.on('connection', (ws, req) => {
198
- const url = new url_1.URL(req.url || '/', `http://${req.headers.host}`);
199
- const pathname = url.pathname;
200
- const queryParams = url.searchParams;
201
- const ip = this.getClientIP(req);
202
- const cookies = this.parseCookies(req);
203
- const session = this.createSession(ip, cookies, undefined, req, { ws, protocol: 'ws' });
204
- ws.on('message', async (msg) => {
205
- try {
206
- const data = JSON.parse(msg.toString());
207
- Object.entries(data).forEach(([k, v]) => queryParams.set(k, v));
208
- }
209
- catch {
210
- queryParams.set('message', msg.toString());
211
- }
212
- await this.core.executeRoute(pathname, session, queryParams);
213
- });
214
- this.core.executeRoute(pathname, session, queryParams).catch(err => {
215
- logger.error(`WS initial route error for ${pathname}:`, err);
216
- ws.close(1011, 'Internal error');
217
- });
218
- });
219
- }
149
+ this.httpServer.on('upgrade', async (req, socket, head) => {
150
+ const url = new url_1.URL(req.url || '/', `http://${req.headers.host}`);
151
+ const pathname = url.pathname;
152
+ const queryParams = url.searchParams;
153
+ const ip = this.getClientIP(req);
154
+ const cookies = this.parseCookies(req);
155
+ const session = this.createSession(ip, cookies, null, req, pathname, { protocol: 'http', header: req.headers });
156
+ const route = this.core.getRoute(pathname);
157
+ if (route && route.ws != null) {
158
+ const matched = await this.core.executeRoute(pathname, session, queryParams);
159
+ if (!matched) {
160
+ socket.write('HTTP/1.1 400 Bad Request\r\n' +
161
+ 'Content-Type: text/plain\r\n' +
162
+ 'Connection: close\r\n');
163
+ socket.destroy();
164
+ }
165
+ else {
166
+ route.ws.handleUpgrade(req, socket, head, (ws) => {
167
+ route.ws.emit('connection', ws, req, session);
168
+ });
169
+ }
170
+ }
171
+ else {
172
+ socket.write('HTTP/1.1 400 Bad Request\r\n' +
173
+ 'Content-Type: text/plain\r\n' +
174
+ 'Connection: close\r\n');
175
+ socket.destroy();
176
+ }
177
+ });
220
178
  this.httpServer.listen(this.port, this.host, () => {
221
179
  logger.info(`Server listening on ${this.host}:${this.port}`);
222
180
  });
223
181
  }
224
182
  stop() {
225
- if (this.wsServer)
226
- this.wsServer.close();
227
183
  if (this.httpServer)
228
184
  this.httpServer.close(() => logger.info('Server stopped'));
229
185
  }
package/dist/session.d.ts CHANGED
@@ -5,6 +5,12 @@
5
5
  **/
6
6
  import { Server } from './server';
7
7
  import { IncomingMessage, ServerResponse } from 'http';
8
+ type ParsedParams = Record<string, string | string[] | undefined>;
9
+ export interface Client {
10
+ req: IncomingMessage;
11
+ res: ServerResponse;
12
+ headers?: Record<string, string>;
13
+ }
8
14
  export declare class Session {
9
15
  ip: string;
10
16
  cookie: Record<string, string>;
@@ -16,12 +22,42 @@ export declare class Session {
16
22
  status: number;
17
23
  body: any;
18
24
  properties?: Record<string, any>;
19
- res: any;
20
- req: any;
25
+ client: Client;
21
26
  server: Server;
22
27
  protocol: string;
23
- constructor(ip: string, cookie: Record<string, string>, server: Server, req?: IncomingMessage, res?: ServerResponse, query?: Record<string, string>);
28
+ pathname: string;
29
+ languages: string[];
30
+ /**
31
+ * @constructor
32
+ * @param ip 用户IP
33
+ * @param cookie 会话cookie
34
+ * @param query 请求字符串
35
+ */
36
+ constructor(ip: string, cookie: Record<string, string>, server: Server, req?: IncomingMessage, res?: ServerResponse, pathname?: string, query?: Record<string, string>);
37
+ /**
38
+ * 解析 Accept-Language 字符串为排序后的语言数组
39
+ * @param header Accept-Language 头,比如 "zh-CN,zh-TW;q=0.8,en-US;q=0.6"
40
+ * @returns 按优先级排序的语言数组,自动补上父语言
41
+ */
42
+ parseAcceptLanguages(header: string): any[];
43
+ /**
44
+ * 生成 sessionid
45
+ * @param ip 用户IP
46
+ * @param option 选项
47
+ */
24
48
  private generateId;
49
+ /**
50
+ * 解析请求体
51
+ * @param client 客户端
52
+ * @returns Promise<ParsedParams>
53
+ */
54
+ parseRequestBody(client?: Client): Promise<ParsedParams>;
55
+ /**
56
+ * 获取请求体
57
+ * @param req 请求对象
58
+ * @returns Promise<string>
59
+ */
60
+ getReqBody(req: IncomingMessage): Promise<string>;
25
61
  private md5;
26
62
  setData(key: string, value: any): void;
27
63
  deleteData(key: string): void;
@@ -30,4 +66,10 @@ export declare class Session {
30
66
  setMime(mimeType: 'png' | 'jpg' | 'jpeg' | 'pdf' | 'plain' | 'html' | 'json' | 'xml' | string): void;
31
67
  send(data: any): any;
32
68
  endsession(message: any): any;
69
+ /**
70
+ * 获取I18n文本
71
+ * @param name 文本点名称
72
+ */
73
+ text(name: string): any;
33
74
  }
75
+ export {};