lz-nframe 1.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/lib/NApp.d.ts +13 -0
- package/lib/NApp.js +23 -0
- package/lib/NConfigsMgr.d.ts +50 -0
- package/lib/NConfigsMgr.js +162 -0
- package/lib/NHttpClient.d.ts +71 -0
- package/lib/NHttpClient.js +196 -0
- package/lib/NSimpleHttpServer.d.ts +75 -0
- package/lib/NSimpleHttpServer.js +222 -0
- package/lib/NType.d.ts +64 -0
- package/lib/NType.js +8 -0
- package/lib/NWSClient.d.ts +44 -0
- package/lib/NWSClient.js +134 -0
- package/lib/NWSServer.d.ts +46 -0
- package/lib/NWSServer.js +133 -0
- package/lib/NWSServiceHub.d.ts +301 -0
- package/lib/NWSServiceHub.js +807 -0
- package/lib/NWSSocket.d.ts +23 -0
- package/lib/NWSSocket.js +30 -0
- package/lib/NWSUserClientJ.d.ts +76 -0
- package/lib/NWSUserClientJ.js +232 -0
- package/lib/NWSUserMgrJ.d.ts +82 -0
- package/lib/NWSUserMgrJ.js +208 -0
- package/package.json +28 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
文件: NSimpleHttpServer.ts
|
|
3
|
+
创建: 2022年02月17日
|
|
4
|
+
作者: 老张
|
|
5
|
+
描述:
|
|
6
|
+
基于NodeJS内置http模块实现http(s)服务器,不需要额外的依赖包
|
|
7
|
+
|
|
8
|
+
文档:
|
|
9
|
+
http模块api参考:http://nodejs.cn/api/http.html
|
|
10
|
+
|
|
11
|
+
参考:
|
|
12
|
+
https://www.cnblogs.com/chuanzi/p/10507919.html
|
|
13
|
+
|
|
14
|
+
示例:
|
|
15
|
+
new NSimpleHttpServer()
|
|
16
|
+
.addPage('/', (req, res) => {
|
|
17
|
+
res.write('HelloWorld\n');
|
|
18
|
+
console.log(req.socket.remoteAddress);
|
|
19
|
+
res.write('远程地址:' + req.socket.remoteAddress + '\n');
|
|
20
|
+
res.write('本地地址:' + req.socket.localAddress + '\n');
|
|
21
|
+
}, {contentType: ContentType.TextPlain})
|
|
22
|
+
.start(3000);
|
|
23
|
+
*******************************************************************************/
|
|
24
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
25
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
26
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
27
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
28
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
29
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
30
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
import * as http from 'http';
|
|
34
|
+
export default class NSimpleHttpServer {
|
|
35
|
+
constructor(params) {
|
|
36
|
+
var _a;
|
|
37
|
+
this.server = null;
|
|
38
|
+
this.pages = {};
|
|
39
|
+
this.maxBodyBytes = (_a = params === null || params === void 0 ? void 0 : params.maxBodyBytes) !== null && _a !== void 0 ? _a : 8192;
|
|
40
|
+
this.server = http.createServer(this.onRequest.bind(this));
|
|
41
|
+
this.server.on('error', (err) => {
|
|
42
|
+
console.error(err.message);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** 启动服务 host如果为null那么req.socket.remoteAddress格式如::ffff:127.0.0.1 */
|
|
46
|
+
start(port, host = '0.0.0.0', onListening) {
|
|
47
|
+
var _a;
|
|
48
|
+
(_a = this.server) === null || _a === void 0 ? void 0 : _a.listen(port, host, () => {
|
|
49
|
+
console.log(`[NSimpleHttpServer] 已启动 ${host}:${port}`);
|
|
50
|
+
onListening === null || onListening === void 0 ? void 0 : onListening();
|
|
51
|
+
});
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
close() {
|
|
55
|
+
var _a;
|
|
56
|
+
(_a = this.server) === null || _a === void 0 ? void 0 : _a.close();
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 注册页面/路由
|
|
61
|
+
* @param url 路径,会做规范化(去掉尾斜杠,根路径'/'除外)
|
|
62
|
+
* @param requestDealer 请求处理函数;内部可主动调用 res.writeHead/res.end 自定义状态码
|
|
63
|
+
* @param options.contentType 响应头的 Content-Type;兼容旧 enum 整行写法与纯 MIME 写法
|
|
64
|
+
* @param options.allowCORS 开启简单 CORS,并自动应答 OPTIONS 预检
|
|
65
|
+
* @param options.method 仅允许指定 HTTP 方法(如 'POST' 或 ['GET','POST']),不传则放行所有方法
|
|
66
|
+
*/
|
|
67
|
+
addPage(url, requestDealer, options) {
|
|
68
|
+
const normalizedUrl = this.normalizePath(url);
|
|
69
|
+
this.pages[normalizedUrl] = {
|
|
70
|
+
url: normalizedUrl,
|
|
71
|
+
contentType: (options === null || options === void 0 ? void 0 : options.contentType) || ContentType.TextHtml,
|
|
72
|
+
allowCORS: options === null || options === void 0 ? void 0 : options.allowCORS,
|
|
73
|
+
method: options === null || options === void 0 ? void 0 : options.method,
|
|
74
|
+
requestDealer
|
|
75
|
+
};
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
onRequest(req, res) {
|
|
79
|
+
// 用 new URL 替代已弃用的 url.parse;req.url 异常时返回 400
|
|
80
|
+
let parsed;
|
|
81
|
+
try {
|
|
82
|
+
parsed = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
|
83
|
+
}
|
|
84
|
+
catch (_a) {
|
|
85
|
+
res.writeHead(400, http.STATUS_CODES[400]);
|
|
86
|
+
res.end();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// 路径规范化后再查表,使 /api 与 /api/ 行为一致
|
|
90
|
+
const pathname = this.normalizePath(parsed.pathname);
|
|
91
|
+
let page = this.pages[pathname];
|
|
92
|
+
if (!page) {
|
|
93
|
+
res.writeHead(404, http.STATUS_CODES[404]);
|
|
94
|
+
res.end();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
let heads = {};
|
|
98
|
+
if (page.contentType) {
|
|
99
|
+
const ct = this.parseContentType(page.contentType);
|
|
100
|
+
if (ct) {
|
|
101
|
+
heads[ct.key] = ct.value;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (page.allowCORS) {
|
|
105
|
+
heads['Access-Control-Allow-Origin'] = '*';
|
|
106
|
+
heads['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS';
|
|
107
|
+
heads['Access-Control-Allow-Headers'] = 'Content-Type, Authorization';
|
|
108
|
+
}
|
|
109
|
+
// CORS 预检请求:直接 204 返回,不再读 body 也不调 dealer
|
|
110
|
+
if (page.allowCORS && req.method === 'OPTIONS') {
|
|
111
|
+
res.writeHead(204, heads);
|
|
112
|
+
res.end();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// method 校验:注册了 method 限制时,其他方法返回 405 + Allow
|
|
116
|
+
if (page.method) {
|
|
117
|
+
const allowed = (Array.isArray(page.method) ? page.method : [page.method]).map(m => m.toUpperCase());
|
|
118
|
+
if (!allowed.includes((req.method || '').toUpperCase())) {
|
|
119
|
+
res.writeHead(405, http.STATUS_CODES[405], { 'Allow': allowed.join(', ') });
|
|
120
|
+
res.end();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const chunks = [];
|
|
125
|
+
let received = 0;
|
|
126
|
+
let aborted = false;
|
|
127
|
+
// 收集body:用 Buffer 数组累积,避免字符串拼接破坏二进制或跨包多字节字符
|
|
128
|
+
req.on('data', (chunk) => {
|
|
129
|
+
if (aborted)
|
|
130
|
+
return;
|
|
131
|
+
received += chunk.length;
|
|
132
|
+
if (received > this.maxBodyBytes) {
|
|
133
|
+
aborted = true;
|
|
134
|
+
if (!res.headersSent) {
|
|
135
|
+
res.writeHead(413, http.STATUS_CODES[413]);
|
|
136
|
+
}
|
|
137
|
+
if (!res.writableEnded)
|
|
138
|
+
res.end();
|
|
139
|
+
req.destroy();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
chunks.push(chunk);
|
|
143
|
+
});
|
|
144
|
+
req.on('end', () => __awaiter(this, void 0, void 0, function* () {
|
|
145
|
+
// 已被中止或响应已结束则不再处理,防止二次写入
|
|
146
|
+
if (aborted || res.writableEnded)
|
|
147
|
+
return;
|
|
148
|
+
// 整理query,url中多次为同名参数设置值可传递数组,对于数组参数直接保留第一个成员
|
|
149
|
+
let query = {};
|
|
150
|
+
for (const [key, val] of parsed.searchParams.entries()) {
|
|
151
|
+
if (!(key in query)) {
|
|
152
|
+
query[key] = val;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// 注意:@types/node 在新版本中将 Buffer 的 ArrayBufferLike 与 Buffer.concat 期望的类型不完全兼容,需要做断言
|
|
156
|
+
const body = Buffer.concat(chunks).toString('utf-8');
|
|
157
|
+
try {
|
|
158
|
+
// 先设置默认响应头,但不提前写状态码,避免覆盖 dealer 的自定义 writeHead
|
|
159
|
+
if (!res.headersSent) {
|
|
160
|
+
const headerEntries = Object.entries(heads);
|
|
161
|
+
for (const [key, value] of headerEntries) {
|
|
162
|
+
res.setHeader(key, value);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (page.requestDealer) {
|
|
166
|
+
yield page.requestDealer(req, res, query, body);
|
|
167
|
+
}
|
|
168
|
+
// 若 dealer 未写入状态码/响应头,则默认返回 200
|
|
169
|
+
if (!res.headersSent) {
|
|
170
|
+
res.writeHead(200, http.STATUS_CODES[200]);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
// dealer 内部异常兜底,避免 unhandledRejection 拖崩进程
|
|
175
|
+
console.error('[NSimpleHttpServer] dealer 异常:', e);
|
|
176
|
+
if (!res.headersSent) {
|
|
177
|
+
res.writeHead(500, http.STATUS_CODES[500]);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
if (!res.writableEnded)
|
|
182
|
+
res.end();
|
|
183
|
+
}
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
/** 路径规范化:去掉尾部多余斜杠,但保留根路径'/' */
|
|
187
|
+
normalizePath(p) {
|
|
188
|
+
if (!p)
|
|
189
|
+
return '/';
|
|
190
|
+
if (p.length > 1 && p.endsWith('/')) {
|
|
191
|
+
const trimmed = p.replace(/\/+$/, '');
|
|
192
|
+
return trimmed || '/';
|
|
193
|
+
}
|
|
194
|
+
return p;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* 解析 contentType 配置,同时兼容两种写法:
|
|
198
|
+
* 1) 旧 enum 整行写法(含 ':'):'Content-Type: text/html; charset=utf-8;'
|
|
199
|
+
* 2) 纯 MIME 写法:'application/json'
|
|
200
|
+
* 修正了原实现 split(':').length===2 才生效的限制(含多冒号会被丢弃)以及尾分号问题
|
|
201
|
+
*/
|
|
202
|
+
parseContentType(contentType) {
|
|
203
|
+
if (!contentType)
|
|
204
|
+
return null;
|
|
205
|
+
const idx = contentType.indexOf(':');
|
|
206
|
+
if (idx > 0) {
|
|
207
|
+
// 仅按第一个冒号切,避免值里有冒号被截断
|
|
208
|
+
const key = contentType.slice(0, idx).trim();
|
|
209
|
+
const value = contentType.slice(idx + 1).trim().replace(/;\s*$/, '');
|
|
210
|
+
if (key && value)
|
|
211
|
+
return { key, value };
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
return { key: 'Content-Type', value: contentType.trim() };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
export var ContentType;
|
|
218
|
+
(function (ContentType) {
|
|
219
|
+
ContentType["TextHtml"] = "Content-Type: text/html; charset=utf-8;";
|
|
220
|
+
ContentType["TextPlain"] = "Content-Type: text/plain; charset=utf-8;";
|
|
221
|
+
ContentType["ApplicationJson"] = "Content-Type: application/json; charset=utf-8;";
|
|
222
|
+
})(ContentType || (ContentType = {}));
|
package/lib/NType.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
文件: NType.ts
|
|
3
|
+
创建: 2026年05月06日
|
|
4
|
+
作者: 老张
|
|
5
|
+
描述:
|
|
6
|
+
类型定义
|
|
7
|
+
*******************************************************************************/
|
|
8
|
+
/** 普通类型值(不含 Date/Function/RegExp 等复杂对象) */
|
|
9
|
+
export type PlainType = string | number | boolean | null | undefined;
|
|
10
|
+
/** 递归定义允许的配置值类型:原始类型、普通对象、数组 */
|
|
11
|
+
export type PlainValue = PlainType | {
|
|
12
|
+
[key: string]: PlainValue;
|
|
13
|
+
} | PlainValue[];
|
|
14
|
+
/**
|
|
15
|
+
* 递归提取对象所有属性的点号路径(仅支持字符串 key)
|
|
16
|
+
* 同时包含中间对象路径与叶子路径,方便监听整个子对象或单个字段的变化。
|
|
17
|
+
* 数组类型视为叶子(不展开 length / 数组方法等无意义路径)。
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* interface Config {
|
|
21
|
+
* port: number;
|
|
22
|
+
* db: { host: string; port: number };
|
|
23
|
+
* tags: string[];
|
|
24
|
+
* }
|
|
25
|
+
* type Keys = NestedKeyOf<Config>;
|
|
26
|
+
* // → 'port' | 'db' | 'db.host' | 'db.port' | 'tags'
|
|
27
|
+
*
|
|
28
|
+
* 推演过程:
|
|
29
|
+
* NestedKeyOf<Config, ''>
|
|
30
|
+
* ├── 'port': 非对象 → 'port'
|
|
31
|
+
* ├── 'db': 是对象(非数组)→ 'db' | NestedKeyOf<{host, port}, 'db.'>
|
|
32
|
+
* │ ├── 'host' → 'db.host'
|
|
33
|
+
* │ └── 'port' → 'db.port'
|
|
34
|
+
* └── 'tags': 是数组 → 'tags'(按叶子处理)
|
|
35
|
+
*
|
|
36
|
+
* @param T 目标对象类型
|
|
37
|
+
* @param Prefix 路径前缀,内部递归使用,外部调用无需传入
|
|
38
|
+
*/
|
|
39
|
+
export type NestedKeyOf<T, Prefix extends string = ''> = T extends object ? {
|
|
40
|
+
[K in keyof T & string]: T[K] extends readonly any[] ? `${Prefix}${K}` : T[K] extends object ? `${Prefix}${K}` | NestedKeyOf<T[K], `${Prefix}${K}.`> : `${Prefix}${K}`;
|
|
41
|
+
}[keyof T & string] : never;
|
|
42
|
+
/**
|
|
43
|
+
* 根据点号分隔的路径字符串,从对象类型中提取对应位置值的类型
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* interface Config {
|
|
47
|
+
* port: number;
|
|
48
|
+
* db: { host: string; port: number };
|
|
49
|
+
* }
|
|
50
|
+
* type T1 = ValueAtPath<Config, 'db'>; // → { host: string; port: number }
|
|
51
|
+
* type T2 = ValueAtPath<Config, 'db.host'>; // → string
|
|
52
|
+
* type T3 = ValueAtPath<Config, 'db.port'>; // → number
|
|
53
|
+
* type T4 = ValueAtPath<Config, 'foo'>; // → never(路径不存在)
|
|
54
|
+
*
|
|
55
|
+
* 推演过程:
|
|
56
|
+
* ValueAtPath<Config, 'db.host'>
|
|
57
|
+
* → 匹配 `${infer K}.${infer Rest}` → K='db', Rest='host'
|
|
58
|
+
* → 'db' extends keyof Config → ValueAtPath<Config['db'], 'host'>
|
|
59
|
+
* → 不再含 '.' → 匹配 P extends keyof T → {host, port}['host'] → string
|
|
60
|
+
*
|
|
61
|
+
* @param T 目标对象类型
|
|
62
|
+
* @param P 点号分隔的路径字符串,如 'db.host'
|
|
63
|
+
*/
|
|
64
|
+
export type ValueAtPath<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? ValueAtPath<T[K], Rest> : never : P extends keyof T ? T[P] : never;
|
package/lib/NType.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
文件: NWSClient.ts
|
|
3
|
+
创建: 2024年1月30日
|
|
4
|
+
作者: 老张
|
|
5
|
+
描述:
|
|
6
|
+
websocket客户端模块,基于ws模块,另外nodejs还有socket.io模块可以实现ws
|
|
7
|
+
|
|
8
|
+
websocket事件:
|
|
9
|
+
✦onopen 网络连接建立时触发该事件
|
|
10
|
+
✦onerror 网络发生错误时触发该事件
|
|
11
|
+
✦onclose websocket关闭时触发该事件
|
|
12
|
+
✦onmessage 接收到消息
|
|
13
|
+
*******************************************************************************/
|
|
14
|
+
import { EventEmitter } from 'events';
|
|
15
|
+
export default class NWSClient extends EventEmitter {
|
|
16
|
+
static EventError: string;
|
|
17
|
+
static EventConnected: string;
|
|
18
|
+
static EventDisconnected: string;
|
|
19
|
+
static EventMessage: string;
|
|
20
|
+
static EventServiceStarted: string;
|
|
21
|
+
private log;
|
|
22
|
+
private get connected();
|
|
23
|
+
private url;
|
|
24
|
+
private socket;
|
|
25
|
+
private _connected;
|
|
26
|
+
/** 上次发起连接的时间 */
|
|
27
|
+
private lastConnectStartTime;
|
|
28
|
+
constructor(params?: {
|
|
29
|
+
log: boolean;
|
|
30
|
+
});
|
|
31
|
+
connect(_url?: string): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* 发送消息到服务器
|
|
34
|
+
* @param msg 文本或二进制消息
|
|
35
|
+
*/
|
|
36
|
+
send(msg: Buffer | string): boolean;
|
|
37
|
+
/** 主动断开与服务器的连接 */
|
|
38
|
+
disconnect(): void;
|
|
39
|
+
private onConnected;
|
|
40
|
+
/** 收到服务器数据 */
|
|
41
|
+
private onData;
|
|
42
|
+
private onClose;
|
|
43
|
+
private onError;
|
|
44
|
+
}
|
package/lib/NWSClient.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
文件: NWSClient.ts
|
|
3
|
+
创建: 2024年1月30日
|
|
4
|
+
作者: 老张
|
|
5
|
+
描述:
|
|
6
|
+
websocket客户端模块,基于ws模块,另外nodejs还有socket.io模块可以实现ws
|
|
7
|
+
|
|
8
|
+
websocket事件:
|
|
9
|
+
✦onopen 网络连接建立时触发该事件
|
|
10
|
+
✦onerror 网络发生错误时触发该事件
|
|
11
|
+
✦onclose websocket关闭时触发该事件
|
|
12
|
+
✦onmessage 接收到消息
|
|
13
|
+
*******************************************************************************/
|
|
14
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
15
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
16
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
17
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
18
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
19
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
20
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
import { EventEmitter } from 'events';
|
|
24
|
+
import WebSocket from 'ws';
|
|
25
|
+
import { NWSSocket } from './NWSSocket';
|
|
26
|
+
class NWSClient extends EventEmitter {
|
|
27
|
+
get connected() {
|
|
28
|
+
return this._connected;
|
|
29
|
+
}
|
|
30
|
+
constructor(params) {
|
|
31
|
+
super();
|
|
32
|
+
this.log = false;
|
|
33
|
+
this.socket = null;
|
|
34
|
+
this._connected = false;
|
|
35
|
+
/** 上次发起连接的时间 */
|
|
36
|
+
this.lastConnectStartTime = 0;
|
|
37
|
+
this.log = (params === null || params === void 0 ? void 0 : params.log) || false;
|
|
38
|
+
}
|
|
39
|
+
connect(_url) {
|
|
40
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
41
|
+
if (this._connected) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
this._connected = false;
|
|
45
|
+
let url = _url || this.url || '';
|
|
46
|
+
url = url.replace(new RegExp('^http(s){0,1}://'), 'ws$1://');
|
|
47
|
+
if (!url) {
|
|
48
|
+
console.error('URL异常');
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
this.url = url;
|
|
52
|
+
if (this.socket) {
|
|
53
|
+
// 重连前闭没必要让onclose触发事件
|
|
54
|
+
this.socket.onopen = null;
|
|
55
|
+
this.socket.onmessage = null;
|
|
56
|
+
this.socket.onclose = null;
|
|
57
|
+
this.socket.onerror = null;
|
|
58
|
+
this.socket.close();
|
|
59
|
+
this.socket = null;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
this.socket = new NWSSocket(url);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
console.error('URL异常:', err);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
this.lastConnectStartTime = Date.now();
|
|
69
|
+
this.socket.onopen = this.onConnected.bind(this);
|
|
70
|
+
this.socket.onmessage = this.onData.bind(this);
|
|
71
|
+
this.socket.onclose = this.onClose.bind(this);
|
|
72
|
+
this.socket.onerror = this.onError.bind(this);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 发送消息到服务器
|
|
77
|
+
* @param msg 文本或二进制消息
|
|
78
|
+
*/
|
|
79
|
+
send(msg) {
|
|
80
|
+
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
this.socket.send(msg);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
/** 主动断开与服务器的连接 */
|
|
87
|
+
disconnect() {
|
|
88
|
+
if (!this.socket) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const socket = this.socket;
|
|
92
|
+
this.socket = null;
|
|
93
|
+
this._connected = false;
|
|
94
|
+
// 清除事件监听,避免触发事件
|
|
95
|
+
socket.onopen = null;
|
|
96
|
+
socket.onmessage = null;
|
|
97
|
+
socket.onclose = null;
|
|
98
|
+
socket.onerror = null;
|
|
99
|
+
// 使用 terminate 强制立即断开,避免 close 异步报错
|
|
100
|
+
socket.terminate();
|
|
101
|
+
}
|
|
102
|
+
onConnected(event) {
|
|
103
|
+
console.log('onConnected');
|
|
104
|
+
this._connected = true;
|
|
105
|
+
this.emit(NWSClient.EventConnected);
|
|
106
|
+
}
|
|
107
|
+
/** 收到服务器数据 */
|
|
108
|
+
onData(event) {
|
|
109
|
+
var _a, _b;
|
|
110
|
+
console.log('onData');
|
|
111
|
+
let str = typeof event.data === 'string' ?
|
|
112
|
+
event.data :
|
|
113
|
+
((_b = (_a = event.data) === null || _a === void 0 ? void 0 : _a.toString) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
114
|
+
if (typeof str !== 'string') {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this.emit(NWSClient.EventMessage, str);
|
|
118
|
+
}
|
|
119
|
+
onClose(event) {
|
|
120
|
+
console.log('onClose');
|
|
121
|
+
this._connected = false;
|
|
122
|
+
this.emit(NWSClient.EventDisconnected, event.reason || '连接关闭');
|
|
123
|
+
}
|
|
124
|
+
onError(err) {
|
|
125
|
+
console.log('onError');
|
|
126
|
+
this.emit(NWSClient.EventError, err);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
NWSClient.EventError = 'error';
|
|
130
|
+
NWSClient.EventConnected = 'connected';
|
|
131
|
+
NWSClient.EventDisconnected = 'disconnected';
|
|
132
|
+
NWSClient.EventMessage = 'message';
|
|
133
|
+
NWSClient.EventServiceStarted = 'started';
|
|
134
|
+
export default NWSClient;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
文件: NWSServer.ts
|
|
3
|
+
创建: 2022年06月21日
|
|
4
|
+
作者: 老张
|
|
5
|
+
描述:
|
|
6
|
+
websocket服务模块,基于ws模块,另外nodejs还有socket.io模块可以实现ws
|
|
7
|
+
|
|
8
|
+
websocket事件:
|
|
9
|
+
✦onopen 网络连接建立时触发该事件
|
|
10
|
+
✦onerror 网络发生错误时触发该事件
|
|
11
|
+
✦onclose websocket关闭时触发该事件
|
|
12
|
+
✦onmessage 接收到消息
|
|
13
|
+
*******************************************************************************/
|
|
14
|
+
import { EventEmitter } from 'events';
|
|
15
|
+
import { NWSSocket } from './NWSSocket';
|
|
16
|
+
export default class NWSServer extends EventEmitter {
|
|
17
|
+
static EventError: string;
|
|
18
|
+
static EventServiceStarted: string;
|
|
19
|
+
/** 客户端连入(或重连)事件 */
|
|
20
|
+
static EventClientConnected: string;
|
|
21
|
+
/** 客户端断开 */
|
|
22
|
+
static EventClientDisconnected: string;
|
|
23
|
+
/** 客户端发上来的消息,参数:linkId,data,isBinary */
|
|
24
|
+
static EventClientMessage: string;
|
|
25
|
+
private log;
|
|
26
|
+
private wsServer;
|
|
27
|
+
constructor(params?: {
|
|
28
|
+
log: boolean;
|
|
29
|
+
});
|
|
30
|
+
/** 启动服务 host如果为null那么req.socket.remoteAddress格式如::ffff:127.0.0.1 */
|
|
31
|
+
start(port: number, host?: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* 发送消息给客户端
|
|
34
|
+
* @param linkId 连接id
|
|
35
|
+
* @param msg 要发送的消息
|
|
36
|
+
*/
|
|
37
|
+
sendToClient(linkId: string, msg: Buffer | string): boolean;
|
|
38
|
+
getSocket(linkId: string): NWSSocket | null;
|
|
39
|
+
/** 主动断开某个客户端的连接 */
|
|
40
|
+
disconnectClient(linkId: string): void;
|
|
41
|
+
/** 停止服务 */
|
|
42
|
+
stop(): void;
|
|
43
|
+
private onClientConnect;
|
|
44
|
+
private onClientMessage;
|
|
45
|
+
private onClientDisconnect;
|
|
46
|
+
}
|
package/lib/NWSServer.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
文件: NWSServer.ts
|
|
3
|
+
创建: 2022年06月21日
|
|
4
|
+
作者: 老张
|
|
5
|
+
描述:
|
|
6
|
+
websocket服务模块,基于ws模块,另外nodejs还有socket.io模块可以实现ws
|
|
7
|
+
|
|
8
|
+
websocket事件:
|
|
9
|
+
✦onopen 网络连接建立时触发该事件
|
|
10
|
+
✦onerror 网络发生错误时触发该事件
|
|
11
|
+
✦onclose websocket关闭时触发该事件
|
|
12
|
+
✦onmessage 接收到消息
|
|
13
|
+
*******************************************************************************/
|
|
14
|
+
import { EventEmitter } from 'events';
|
|
15
|
+
import WebSocket from 'ws'; // 1. 替换导入
|
|
16
|
+
import { NWSSocket } from './NWSSocket';
|
|
17
|
+
import NHttpClient from './NHttpClient';
|
|
18
|
+
class NWSServer extends EventEmitter {
|
|
19
|
+
constructor(params) {
|
|
20
|
+
super();
|
|
21
|
+
this.log = false;
|
|
22
|
+
this.wsServer = null;
|
|
23
|
+
this.log = (params === null || params === void 0 ? void 0 : params.log) || false;
|
|
24
|
+
}
|
|
25
|
+
/** 启动服务 host如果为null那么req.socket.remoteAddress格式如::ffff:127.0.0.1 */
|
|
26
|
+
start(port, host = '0.0.0.0') {
|
|
27
|
+
if (this.wsServer) {
|
|
28
|
+
this.wsServer.close();
|
|
29
|
+
this.wsServer = null;
|
|
30
|
+
}
|
|
31
|
+
this.wsServer = new WebSocket.Server({
|
|
32
|
+
port,
|
|
33
|
+
host,
|
|
34
|
+
WebSocket: NWSSocket,
|
|
35
|
+
});
|
|
36
|
+
this.wsServer.on('error', (err) => {
|
|
37
|
+
if (this.log)
|
|
38
|
+
console.log('NWSServer错误:' + err.message);
|
|
39
|
+
this.emit(NWSServer.EventError, err);
|
|
40
|
+
});
|
|
41
|
+
/** 监听启动 */
|
|
42
|
+
this.wsServer.on('listening', () => {
|
|
43
|
+
// 发出服务启动消息,外部可监听
|
|
44
|
+
this.emit(NWSServer.EventServiceStarted);
|
|
45
|
+
});
|
|
46
|
+
/** 监听客户端连接 */
|
|
47
|
+
this.wsServer.on('connection', this.onClientConnect.bind(this));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 发送消息给客户端
|
|
51
|
+
* @param linkId 连接id
|
|
52
|
+
* @param msg 要发送的消息
|
|
53
|
+
*/
|
|
54
|
+
sendToClient(linkId, msg) {
|
|
55
|
+
let client = this.getSocket(linkId);
|
|
56
|
+
if (!client || client.readyState !== WebSocket.OPEN) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
client.send(msg);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
getSocket(linkId) {
|
|
63
|
+
var _a;
|
|
64
|
+
let clients = (_a = this.wsServer) === null || _a === void 0 ? void 0 : _a.clients;
|
|
65
|
+
if (!linkId || !clients)
|
|
66
|
+
return null;
|
|
67
|
+
for (let client of clients) {
|
|
68
|
+
if (client.linkId === linkId) {
|
|
69
|
+
return client;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/** 主动断开某个客户端的连接 */
|
|
75
|
+
disconnectClient(linkId) {
|
|
76
|
+
let client = this.getSocket(linkId);
|
|
77
|
+
if (!client) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
client.close();
|
|
81
|
+
}
|
|
82
|
+
/** 停止服务 */
|
|
83
|
+
stop() {
|
|
84
|
+
if (!this.wsServer) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.wsServer.close();
|
|
88
|
+
this.wsServer = null;
|
|
89
|
+
}
|
|
90
|
+
onClientConnect(socket, req) {
|
|
91
|
+
if (!socket)
|
|
92
|
+
return;
|
|
93
|
+
if (!socket.linkId) {
|
|
94
|
+
socket.linkId = NWSSocket.genUniqueId();
|
|
95
|
+
}
|
|
96
|
+
if (this.log)
|
|
97
|
+
console.log(`新用户连入(ip:${NHttpClient.getClientIp(req)}),分配连接id:${socket.linkId}`);
|
|
98
|
+
// 下方的事件都是ws模块封装后的事件,而不是websocket的协议事件
|
|
99
|
+
socket.removeAllListeners('message');
|
|
100
|
+
socket.on('message', this.onClientMessage.bind(this, socket));
|
|
101
|
+
socket.on('close', this.onClientDisconnect.bind(this, socket));
|
|
102
|
+
socket.on('error', () => { });
|
|
103
|
+
// 抛出连接事件,外部调用本模块可以监听连接事件获取linkId
|
|
104
|
+
this.emit(NWSServer.EventClientConnected, socket.linkId);
|
|
105
|
+
}
|
|
106
|
+
onClientMessage(client, data, isBinary) {
|
|
107
|
+
if (!client)
|
|
108
|
+
return;
|
|
109
|
+
client.actTime = Date.now();
|
|
110
|
+
if (this.log)
|
|
111
|
+
console.log(`用户 ${client.linkId} 消息:${data}`);
|
|
112
|
+
this.emit(NWSServer.EventClientMessage, client.linkId, data, isBinary);
|
|
113
|
+
}
|
|
114
|
+
onClientDisconnect(client) {
|
|
115
|
+
if (!client)
|
|
116
|
+
return;
|
|
117
|
+
if (this.log)
|
|
118
|
+
console.log(`用户 ${client.linkId} 断开连接`);
|
|
119
|
+
client.removeAllListeners('message');
|
|
120
|
+
client.removeAllListeners('close');
|
|
121
|
+
client.removeAllListeners('error');
|
|
122
|
+
this.emit(NWSServer.EventClientDisconnected, client.linkId);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
NWSServer.EventError = 'error';
|
|
126
|
+
NWSServer.EventServiceStarted = 'started';
|
|
127
|
+
/** 客户端连入(或重连)事件 */
|
|
128
|
+
NWSServer.EventClientConnected = 'connected';
|
|
129
|
+
/** 客户端断开 */
|
|
130
|
+
NWSServer.EventClientDisconnected = 'disconnected';
|
|
131
|
+
/** 客户端发上来的消息,参数:linkId,data,isBinary */
|
|
132
|
+
NWSServer.EventClientMessage = 'message';
|
|
133
|
+
export default NWSServer;
|