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.
@@ -0,0 +1,23 @@
1
+ /*******************************************************************************
2
+ 文件: NWSSocket.ts
3
+ 创建: 2022年06月21日
4
+ 作者: 老张
5
+ 描述:
6
+ 基于ws模块,另外nodejs还有socket.io模块可以实现ws
7
+
8
+ websocket事件:
9
+ ✦onopen 网络连接建立时触发该事件
10
+ ✦onerror 网络发生错误时触发该事件
11
+ ✦onclose websocket关闭时触发该事件
12
+ ✦onmessage 接收到消息
13
+ *******************************************************************************/
14
+ import WebSocket from 'ws';
15
+ export declare class NWSSocket extends WebSocket {
16
+ /** 连接id,当前时间线程内全局唯一,若线程重启,生成的会与之前重复 */
17
+ linkId: string;
18
+ /** 最近活动时间 */
19
+ actTime: number;
20
+ /** 最近心跳检测时间 */
21
+ heartbeatCheckTime: number;
22
+ static genUniqueId(): string;
23
+ }
@@ -0,0 +1,30 @@
1
+ /*******************************************************************************
2
+ 文件: NWSSocket.ts
3
+ 创建: 2022年06月21日
4
+ 作者: 老张
5
+ 描述:
6
+ 基于ws模块,另外nodejs还有socket.io模块可以实现ws
7
+
8
+ websocket事件:
9
+ ✦onopen 网络连接建立时触发该事件
10
+ ✦onerror 网络发生错误时触发该事件
11
+ ✦onclose websocket关闭时触发该事件
12
+ ✦onmessage 接收到消息
13
+ *******************************************************************************/
14
+ import WebSocket from 'ws';
15
+ let autoIncId = 0;
16
+ export class NWSSocket extends WebSocket {
17
+ constructor() {
18
+ super(...arguments);
19
+ /** 连接id,当前时间线程内全局唯一,若线程重启,生成的会与之前重复 */
20
+ this.linkId = '';
21
+ /** 最近活动时间 */
22
+ this.actTime = 0;
23
+ /** 最近心跳检测时间 */
24
+ this.heartbeatCheckTime = 0;
25
+ }
26
+ static genUniqueId() {
27
+ autoIncId++;
28
+ return 'ws' + autoIncId;
29
+ }
30
+ }
@@ -0,0 +1,76 @@
1
+ /*******************************************************************************
2
+ 文件: NWSUserClientJ.ts
3
+ 创建: 2026年05月08日
4
+ 作者: 老张
5
+ 描述:
6
+ J含义为JSON.
7
+ 面向 Node.js 的 WebSocket 客户端,与 NWSUserMgrJ 配套使用。
8
+ 基于 JSON 帧通讯:{ "c": 命令, "d": 参数 },内置 _si_(登录)、_hb_(心跳)。
9
+
10
+ 参考:Cocos Creator 下的 XWebSocket;此处去掉 cc.EventTarget / xfire 依赖,
11
+ 使用 EventEmitter;传输层统一委托 NWSClient(内部使用 ws / NWSSocket)。
12
+
13
+ 内置命令(与 NWSUserMgrJ 一致):
14
+ ✦_si_ 服务端下发 resign 时须回传登录载荷;服务端下发 ok 表示登录完成
15
+ ✦_hb_ 心跳,收到后原样应答
16
+ *******************************************************************************/
17
+ import { EventEmitter } from 'events';
18
+ /** 与 NWSUserMgrJ 配套的 JSON WebSocket 客户端 */
19
+ export default class NWSUserClientJ extends EventEmitter {
20
+ static EventError: string;
21
+ static EventConnected: string;
22
+ static EventDisconnected: string;
23
+ static EventMessage: string;
24
+ /** 收到服务端 _si_ ok,登录验证完成 */
25
+ static EventSigned: string;
26
+ /** 是否已与服务器建立 WebSocket(不代表已完成 _si_ 验证) */
27
+ get connected(): boolean;
28
+ /** 是否已完成登录验证(收到 _si_ ok) */
29
+ get signed(): boolean;
30
+ private url;
31
+ /** WebSocket 传输层,由本类包装 JSON 协议 */
32
+ private client;
33
+ private _connected;
34
+ private _signed;
35
+ /** 服务端要求 resign 时用于生成登录参数的回调,须与 NWSUserMgrJ.signInChecker 约定一致 */
36
+ private getSignInData;
37
+ /** >0 开启掉线重连,单位为秒,轮询周期约为间隔的 1/10 */
38
+ private autoReconnectInterval;
39
+ private reconnectTimer;
40
+ /** 上次发起 connect 的时间戳(毫秒) */
41
+ private lastConnectStartTime;
42
+ /** 为 true 时表示用户主动 disconnect,自动重连不会触发 */
43
+ private suppressReconnect;
44
+ private connectTimeoutMs;
45
+ private signInTimeoutMs;
46
+ private log;
47
+ /** 等待本次 connect() 完成的回调(仅用于首次握手阶段) */
48
+ private connectFinish;
49
+ constructor(params: {
50
+ url?: string;
51
+ /** 自动重连间隔(秒),0 表示不重连 */
52
+ autoReconnectInterval?: number;
53
+ /** 响应 _si_ resign 时提交的登录数据 */
54
+ getSignInData: () => unknown;
55
+ connectTimeoutMs?: number;
56
+ signInTimeoutMs?: number;
57
+ log?: boolean;
58
+ });
59
+ /**
60
+ * 建立连接(仅 TCP/WebSocket 层),返回是否在超时时间内进入 OPEN。
61
+ */
62
+ connect(_url?: string): Promise<boolean>;
63
+ /**
64
+ * 连接并等待登录完成(收到 _si_ ok)。
65
+ */
66
+ signIn(_url?: string): Promise<boolean>;
67
+ /** 主动断开;之后不会自动重连,除非再次调用 connect / signIn */
68
+ disconnect(): void;
69
+ /** 发送业务或内置命令 */
70
+ send<T>(cmd: string, data: T): void;
71
+ private onClientConnected;
72
+ /** NWSClient 上报的原始字符串,解析为 JSON 帧 */
73
+ private onClientRawMessage;
74
+ private onClientDisconnected;
75
+ private onClientError;
76
+ }
@@ -0,0 +1,232 @@
1
+ /*******************************************************************************
2
+ 文件: NWSUserClientJ.ts
3
+ 创建: 2026年05月08日
4
+ 作者: 老张
5
+ 描述:
6
+ J含义为JSON.
7
+ 面向 Node.js 的 WebSocket 客户端,与 NWSUserMgrJ 配套使用。
8
+ 基于 JSON 帧通讯:{ "c": 命令, "d": 参数 },内置 _si_(登录)、_hb_(心跳)。
9
+
10
+ 参考:Cocos Creator 下的 XWebSocket;此处去掉 cc.EventTarget / xfire 依赖,
11
+ 使用 EventEmitter;传输层统一委托 NWSClient(内部使用 ws / NWSSocket)。
12
+
13
+ 内置命令(与 NWSUserMgrJ 一致):
14
+ ✦_si_ 服务端下发 resign 时须回传登录载荷;服务端下发 ok 表示登录完成
15
+ ✦_hb_ 心跳,收到后原样应答
16
+ *******************************************************************************/
17
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
18
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
19
+ return new (P || (P = Promise))(function (resolve, reject) {
20
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
21
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
22
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
23
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
24
+ });
25
+ };
26
+ import { EventEmitter } from 'events';
27
+ import NWSClient from './NWSClient';
28
+ /** 与 NWSUserMgrJ 配套的 JSON WebSocket 客户端 */
29
+ class NWSUserClientJ extends EventEmitter {
30
+ /** 是否已与服务器建立 WebSocket(不代表已完成 _si_ 验证) */
31
+ get connected() {
32
+ return this._connected;
33
+ }
34
+ /** 是否已完成登录验证(收到 _si_ ok) */
35
+ get signed() {
36
+ return this._signed;
37
+ }
38
+ constructor(params) {
39
+ var _a, _b;
40
+ super();
41
+ this._connected = false;
42
+ this._signed = false;
43
+ /** >0 开启掉线重连,单位为秒,轮询周期约为间隔的 1/10 */
44
+ this.autoReconnectInterval = 0;
45
+ this.reconnectTimer = null;
46
+ /** 上次发起 connect 的时间戳(毫秒) */
47
+ this.lastConnectStartTime = 0;
48
+ /** 为 true 时表示用户主动 disconnect,自动重连不会触发 */
49
+ this.suppressReconnect = false;
50
+ this.connectTimeoutMs = 5000;
51
+ this.signInTimeoutMs = 5000;
52
+ this.log = false;
53
+ /** 等待本次 connect() 完成的回调(仅用于首次握手阶段) */
54
+ this.connectFinish = null;
55
+ this.url = params.url || '';
56
+ this.getSignInData = params.getSignInData;
57
+ this.autoReconnectInterval = params.autoReconnectInterval || 0;
58
+ this.connectTimeoutMs = (_a = params.connectTimeoutMs) !== null && _a !== void 0 ? _a : 5000;
59
+ this.signInTimeoutMs = (_b = params.signInTimeoutMs) !== null && _b !== void 0 ? _b : 5000;
60
+ this.log = params.log || false;
61
+ this.client = new NWSClient({ log: this.log });
62
+ this.client.on(NWSClient.EventConnected, this.onClientConnected.bind(this));
63
+ this.client.on(NWSClient.EventMessage, this.onClientRawMessage.bind(this));
64
+ this.client.on(NWSClient.EventDisconnected, this.onClientDisconnected.bind(this));
65
+ this.client.on(NWSClient.EventError, this.onClientError.bind(this));
66
+ if (this.autoReconnectInterval > 0) {
67
+ const tickMs = Math.max(100, this.autoReconnectInterval * 100);
68
+ this.reconnectTimer = setInterval(() => {
69
+ if (this.suppressReconnect || !this.url) {
70
+ return;
71
+ }
72
+ if (this.client && !this._connected) {
73
+ const off = Date.now() - this.lastConnectStartTime;
74
+ if (off / 1000 >= this.autoReconnectInterval) {
75
+ void this.connect();
76
+ }
77
+ }
78
+ }, tickMs);
79
+ }
80
+ }
81
+ /**
82
+ * 建立连接(仅 TCP/WebSocket 层),返回是否在超时时间内进入 OPEN。
83
+ */
84
+ connect(_url) {
85
+ return __awaiter(this, void 0, void 0, function* () {
86
+ if (this._connected) {
87
+ return true;
88
+ }
89
+ this.suppressReconnect = false;
90
+ this._connected = false;
91
+ this._signed = false;
92
+ let url = _url || this.url || '';
93
+ url = url.replace(new RegExp('^http(s){0,1}://'), 'ws$1://');
94
+ if (!url) {
95
+ if (this.log) {
96
+ console.error('【NWSUserClientJ】URL 异常');
97
+ }
98
+ return false;
99
+ }
100
+ this.url = url;
101
+ let settled = false;
102
+ let innerResolve;
103
+ const connectPromise = new Promise((resolve) => {
104
+ innerResolve = resolve;
105
+ });
106
+ let timer;
107
+ const finishConnect = (ok) => {
108
+ if (settled) {
109
+ return;
110
+ }
111
+ settled = true;
112
+ this.connectFinish = null;
113
+ clearTimeout(timer);
114
+ innerResolve(ok);
115
+ };
116
+ timer = setTimeout(() => {
117
+ finishConnect(this._connected);
118
+ }, this.connectTimeoutMs);
119
+ this.lastConnectStartTime = Date.now();
120
+ this.connectFinish = finishConnect;
121
+ const started = yield this.client.connect(this.url);
122
+ if (started === false) {
123
+ finishConnect(false);
124
+ return connectPromise;
125
+ }
126
+ return connectPromise;
127
+ });
128
+ }
129
+ /**
130
+ * 连接并等待登录完成(收到 _si_ ok)。
131
+ */
132
+ signIn(_url) {
133
+ return __awaiter(this, void 0, void 0, function* () {
134
+ const ok = yield this.connect(_url);
135
+ if (!ok) {
136
+ if (this.log) {
137
+ console.error('【NWSUserClientJ】signIn:连接失败');
138
+ }
139
+ return false;
140
+ }
141
+ const deadline = Date.now() + this.signInTimeoutMs;
142
+ while (!this._signed && Date.now() < deadline) {
143
+ yield new Promise((r) => setImmediate(r));
144
+ }
145
+ if (!this._signed && this.log) {
146
+ console.error('【NWSUserClientJ】signIn:等待登录确认超时');
147
+ }
148
+ return this._signed;
149
+ });
150
+ }
151
+ /** 主动断开;之后不会自动重连,除非再次调用 connect / signIn */
152
+ disconnect() {
153
+ this.suppressReconnect = true;
154
+ this._connected = false;
155
+ this._signed = false;
156
+ this.client.disconnect();
157
+ if (this.connectFinish) {
158
+ this.connectFinish(false);
159
+ }
160
+ }
161
+ /** 发送业务或内置命令 */
162
+ send(cmd, data) {
163
+ const ok = this.client.send(JSON.stringify({ c: cmd, d: data }));
164
+ if (!ok) {
165
+ if (this._connected) {
166
+ this._connected = false;
167
+ this.emit(NWSUserClientJ.EventDisconnected, 'ws 连接未开启');
168
+ }
169
+ }
170
+ }
171
+ onClientConnected() {
172
+ this._connected = true;
173
+ this.emit(NWSUserClientJ.EventConnected);
174
+ if (this.connectFinish) {
175
+ this.connectFinish(true);
176
+ }
177
+ }
178
+ /** NWSClient 上报的原始字符串,解析为 JSON 帧 */
179
+ onClientRawMessage(str) {
180
+ if (typeof str !== 'string') {
181
+ return;
182
+ }
183
+ try {
184
+ let json = JSON.parse(str);
185
+ let cmd = json === null || json === void 0 ? void 0 : json.c;
186
+ let data = json === null || json === void 0 ? void 0 : json.d;
187
+ switch (cmd) {
188
+ case '_hb_':
189
+ this.send('_hb_', undefined);
190
+ break;
191
+ case '_si_': {
192
+ if (data === 'resign') {
193
+ this._signed = false;
194
+ this.send('_si_', this.getSignInData());
195
+ }
196
+ else if (data === 'ok') {
197
+ this._signed = true;
198
+ this.emit(NWSUserClientJ.EventSigned);
199
+ }
200
+ break;
201
+ }
202
+ default:
203
+ this.emit(NWSUserClientJ.EventMessage, cmd, data);
204
+ break;
205
+ }
206
+ }
207
+ catch (error) {
208
+ return;
209
+ }
210
+ }
211
+ onClientDisconnected(reason) {
212
+ this._connected = false;
213
+ this._signed = false;
214
+ if (this.connectFinish) {
215
+ this.connectFinish(false);
216
+ }
217
+ this.emit(NWSUserClientJ.EventDisconnected, reason || '连接关闭');
218
+ }
219
+ onClientError(err) {
220
+ this.emit(NWSUserClientJ.EventError, err);
221
+ if (this.connectFinish) {
222
+ this.connectFinish(false);
223
+ }
224
+ }
225
+ }
226
+ NWSUserClientJ.EventError = 'error';
227
+ NWSUserClientJ.EventConnected = 'connected';
228
+ NWSUserClientJ.EventDisconnected = 'disconnected';
229
+ NWSUserClientJ.EventMessage = 'message';
230
+ /** 收到服务端 _si_ ok,登录验证完成 */
231
+ NWSUserClientJ.EventSigned = 'signed';
232
+ export default NWSUserClientJ;
@@ -0,0 +1,82 @@
1
+ /*******************************************************************************
2
+ 文件: NWSUserMgrJ.ts
3
+ 创建: 2022年08月05日
4
+ 作者: 老张
5
+ 描述:
6
+ J含义为JSON.
7
+ 基于NWSServer的实时联网用户管理,内部通信使用NWSServer的接口,信息传输格式使用json。
8
+ ✦用户与WSSocket通过登录命令_si_(signIn缩写)动态绑定,以便实现重连功能(更换WSSocket)
9
+ ✦支持重连,用户短时间断开websocket可以重连
10
+ ✦支持心跳机制,可以主动检测用户是否掉线
11
+
12
+ 信息传输格式如:
13
+ c为命令,d为命令参数,下方为登录命令示例,实际登录命令参数需自定义
14
+ {"c": "_si_", "d": {"uid": "123456"}}
15
+
16
+ 内置命令说明(前后下划线是防止与用户自定义命令冲突):
17
+ ✦_si_
18
+ 此命令客户端、服务端同名,客户端收到需要响应相同命令
19
+ 登录信息,用来确定是哪个用户
20
+ 参数格式:自定义,服务端下发不需要参数
21
+ 需要实现校验函数:checkSignIn,用来返回uid,【必须,以防范冒名入侵】
22
+ ✦_hb_
23
+ 此命令客户端、服务端同名,客户端收到需要响应相同命令
24
+ 心跳
25
+ 参数格式:不需要
26
+
27
+ TODO 复用http服务端口,https://blog.csdn.net/qq_44856695/article/details/120250286
28
+ *******************************************************************************/
29
+ import { EventEmitter } from 'events';
30
+ import NWSServer from './NWSServer';
31
+ /** 基于NWSServer的实时联网用户管理,通信格式使用json */
32
+ export default class NWSUserMgrJ extends EventEmitter {
33
+ /** 用户掉线,已考虑超时问题,如果设置超时非0,则socket掉线会等候用户重连,重连成功不认定掉线 */
34
+ static EventUserDisconnected: string;
35
+ server: NWSServer;
36
+ /** 连接id <--> 用户id */
37
+ linkToUIds: {
38
+ [linkId: string]: string;
39
+ };
40
+ uidToLinks: {
41
+ [uid: string]: {
42
+ linkId: string;
43
+ closeTime?: number;
44
+ };
45
+ };
46
+ /** 未绑定用户的连接,超时未绑定直接断开 */
47
+ private unboundLinks;
48
+ /** 登录信息校验接口 */
49
+ private signInChecker;
50
+ /** 心跳检测间隔,单位:秒,0表不进行心跳检测 */
51
+ private heartbeatInterval;
52
+ /** 多久未响应就认定掉线,单位:秒,0表不检测超时,非0时最低5 */
53
+ private timeoutSeconds;
54
+ /** 是否输出日志 */
55
+ private log;
56
+ constructor(params: {
57
+ port: number;
58
+ host?: string;
59
+ /** 心跳检测间隔,空或0不开启心跳检测,单位:秒 */
60
+ heartbeatInterval?: number;
61
+ /** 多久未响应就认定掉线,单位:秒 */
62
+ timeoutSeconds?: number;
63
+ /** 登录信息校验,返回用户id,data为登录命令的自定义参数 */
64
+ signInChecker: (data: any) => string;
65
+ /** 是否进行日志输出 */
66
+ log?: boolean;
67
+ });
68
+ sendToUser<T>(uid: string, cmd: string, data: T): void;
69
+ /** 有用户连入NWSServer(也可能是老用户重连),此时是不能确认是哪个用户的 */
70
+ private onClientConnected;
71
+ private onClientDisconnected;
72
+ /** 处理来自用户的消息 */
73
+ private onClientMessage;
74
+ private sendToClient;
75
+ /**
76
+ * 心跳定时检测
77
+ * 像一定时间未活动的连接发送心跳命令,客户端需要响应
78
+ */
79
+ private heartbeatEngine;
80
+ /** 超时检测 */
81
+ private checkTimeout;
82
+ }
@@ -0,0 +1,208 @@
1
+ /*******************************************************************************
2
+ 文件: NWSUserMgrJ.ts
3
+ 创建: 2022年08月05日
4
+ 作者: 老张
5
+ 描述:
6
+ J含义为JSON.
7
+ 基于NWSServer的实时联网用户管理,内部通信使用NWSServer的接口,信息传输格式使用json。
8
+ ✦用户与WSSocket通过登录命令_si_(signIn缩写)动态绑定,以便实现重连功能(更换WSSocket)
9
+ ✦支持重连,用户短时间断开websocket可以重连
10
+ ✦支持心跳机制,可以主动检测用户是否掉线
11
+
12
+ 信息传输格式如:
13
+ c为命令,d为命令参数,下方为登录命令示例,实际登录命令参数需自定义
14
+ {"c": "_si_", "d": {"uid": "123456"}}
15
+
16
+ 内置命令说明(前后下划线是防止与用户自定义命令冲突):
17
+ ✦_si_
18
+ 此命令客户端、服务端同名,客户端收到需要响应相同命令
19
+ 登录信息,用来确定是哪个用户
20
+ 参数格式:自定义,服务端下发不需要参数
21
+ 需要实现校验函数:checkSignIn,用来返回uid,【必须,以防范冒名入侵】
22
+ ✦_hb_
23
+ 此命令客户端、服务端同名,客户端收到需要响应相同命令
24
+ 心跳
25
+ 参数格式:不需要
26
+
27
+ TODO 复用http服务端口,https://blog.csdn.net/qq_44856695/article/details/120250286
28
+ *******************************************************************************/
29
+ import { EventEmitter } from 'events';
30
+ import NWSServer from './NWSServer';
31
+ import NUtils from 'lz-nutils';
32
+ /** 基于NWSServer的实时联网用户管理,通信格式使用json */
33
+ class NWSUserMgrJ extends EventEmitter {
34
+ constructor(params) {
35
+ super();
36
+ /** 连接id <--> 用户id */
37
+ this.linkToUIds = {};
38
+ this.uidToLinks = {};
39
+ /** 未绑定用户的连接,超时未绑定直接断开 */
40
+ this.unboundLinks = {};
41
+ /** 心跳检测间隔,单位:秒,0表不进行心跳检测 */
42
+ this.heartbeatInterval = 0;
43
+ /** 多久未响应就认定掉线,单位:秒,0表不检测超时,非0时最低5 */
44
+ this.timeoutSeconds = 0;
45
+ /** 是否输出日志 */
46
+ this.log = false;
47
+ this.signInChecker = params.signInChecker;
48
+ this.server = new NWSServer({ log: false });
49
+ this.server.start(params.port, params.host);
50
+ this.server.on(NWSServer.EventError, (error) => {
51
+ console.error('NWSUserMgrJ ws异常:' + error.message);
52
+ });
53
+ this.server.on(NWSServer.EventServiceStarted, () => {
54
+ console.log('NWSUserMgrJ 服务启动');
55
+ });
56
+ if (params.heartbeatInterval && params.heartbeatInterval > 0) {
57
+ this.heartbeatInterval = params.heartbeatInterval;
58
+ setInterval(this.heartbeatEngine.bind(this), params.heartbeatInterval / 10);
59
+ }
60
+ if (params.timeoutSeconds && params.timeoutSeconds > 0) {
61
+ this.timeoutSeconds = Math.max(5, params.timeoutSeconds);
62
+ setInterval(this.checkTimeout.bind(this), params.timeoutSeconds / 10);
63
+ }
64
+ this.log = params.log || false;
65
+ // 连接监听
66
+ this.server.on(NWSServer.EventClientConnected, this.onClientConnected.bind(this));
67
+ // 连接断开监听
68
+ this.server.on(NWSServer.EventClientDisconnected, this.onClientDisconnected.bind(this));
69
+ // 消息监听
70
+ this.server.on(NWSServer.EventClientMessage, this.onClientMessage.bind(this));
71
+ }
72
+ sendToUser(uid, cmd, data) {
73
+ let link = this.uidToLinks[uid];
74
+ if (!link)
75
+ return;
76
+ this.sendToClient(link.linkId, cmd, data);
77
+ }
78
+ /** 有用户连入NWSServer(也可能是老用户重连),此时是不能确认是哪个用户的 */
79
+ onClientConnected(linkId) {
80
+ if (this.log) {
81
+ console.log('【NWSUserMgrJ】用户连入,分配linkId:' + linkId);
82
+ }
83
+ this.sendToClient(linkId, '_si_', 'resign');
84
+ this.unboundLinks[linkId] = true;
85
+ }
86
+ onClientDisconnected(linkId) {
87
+ delete this.unboundLinks[linkId];
88
+ let uid = this.linkToUIds[linkId];
89
+ delete this.linkToUIds[linkId];
90
+ // 如果没有开启超时检测,则ws掉线立即认定掉线
91
+ if (uid && !this.timeoutSeconds) {
92
+ delete this.uidToLinks[uid];
93
+ this.emit(NWSUserMgrJ.EventUserDisconnected, uid);
94
+ if (this.log) {
95
+ console.log(`【NWSUserMgrJ】用户断开连接,linkId:${linkId},uid:${uid}`);
96
+ }
97
+ }
98
+ else {
99
+ if (this.uidToLinks[uid]) {
100
+ this.uidToLinks[uid].closeTime = NUtils.currentTimeMillis;
101
+ }
102
+ }
103
+ }
104
+ /** 处理来自用户的消息 */
105
+ onClientMessage(linkId, msg, isBinary) {
106
+ var _a;
107
+ let json;
108
+ try {
109
+ json = JSON.parse(msg.toString());
110
+ }
111
+ catch (e) {
112
+ }
113
+ if (!json || typeof json.c !== 'string' || !json.c)
114
+ return;
115
+ let cmd = json.c;
116
+ let data = json.d;
117
+ switch (cmd) {
118
+ case '_si_': {
119
+ let uid = (_a = this.signInChecker) === null || _a === void 0 ? void 0 : _a.call(this, data);
120
+ if (!uid)
121
+ return;
122
+ // 解绑老连接
123
+ {
124
+ let oldLink = this.uidToLinks[uid];
125
+ if (oldLink) {
126
+ let ws = this.server.getSocket(oldLink.linkId);
127
+ ws === null || ws === void 0 ? void 0 : ws.removeAllListeners();
128
+ ws === null || ws === void 0 ? void 0 : ws.close();
129
+ delete this.uidToLinks[uid];
130
+ delete this.linkToUIds[oldLink.linkId];
131
+ }
132
+ }
133
+ // 绑定用户id与连接id
134
+ this.linkToUIds[linkId] = uid;
135
+ this.uidToLinks[uid] = { linkId };
136
+ if (this.log) {
137
+ console.log(`【NWSUserMgrJ】用户登录成功,linkId:${linkId} uid:${uid}`);
138
+ }
139
+ // 下发确认命令
140
+ this.sendToClient(linkId, '_si_', 'ok');
141
+ break;
142
+ }
143
+ case '_hb_': {
144
+ // 这里并不需要做啥特殊处理,活动时间已经在WSSocket中收到客户端消息时自动刷新
145
+ break;
146
+ }
147
+ default: {
148
+ // 先校验用户是否登录
149
+ let uid = this.linkToUIds[linkId];
150
+ if (!uid) {
151
+ // 下发登录命令
152
+ this.sendToClient(linkId, '_si_', 'resign');
153
+ return;
154
+ }
155
+ // 抛出命令、用户id、参数供监听
156
+ this.emit(cmd, uid, data);
157
+ break;
158
+ }
159
+ }
160
+ }
161
+ sendToClient(linkId, cmd, data) {
162
+ this.server.sendToClient(linkId, JSON.stringify({ c: cmd, d: data }));
163
+ }
164
+ /**
165
+ * 心跳定时检测
166
+ * 像一定时间未活动的连接发送心跳命令,客户端需要响应
167
+ */
168
+ heartbeatEngine() {
169
+ if (!this.server || !this.heartbeatInterval)
170
+ return;
171
+ for (let uid in this.uidToLinks) {
172
+ let link = this.uidToLinks[uid];
173
+ let socket = this.server.getSocket(link.linkId);
174
+ if (!socket)
175
+ continue;
176
+ if ((NUtils.currentTimeMillis - socket.actTime) / 1000 >= this.heartbeatInterval &&
177
+ (NUtils.currentTimeMillis - (socket.heartbeatCheckTime || 0)) / 1000 >= this.heartbeatInterval) {
178
+ this.sendToClient(link.linkId, '_hb_', undefined);
179
+ socket.heartbeatCheckTime = NUtils.currentTimeMillis;
180
+ }
181
+ }
182
+ }
183
+ /** 超时检测 */
184
+ checkTimeout() {
185
+ if (!this.server || !this.timeoutSeconds)
186
+ return;
187
+ for (let uid in this.uidToLinks) {
188
+ let link = this.uidToLinks[uid];
189
+ let socket = this.server.getSocket(link.linkId);
190
+ let actTime = (socket === null || socket === void 0 ? void 0 : socket.actTime) != null ? socket.actTime : link.closeTime;
191
+ if (actTime == null)
192
+ continue;
193
+ if ((NUtils.currentTimeMillis - actTime) / 1000 >= this.timeoutSeconds) {
194
+ delete this.unboundLinks[link.linkId];
195
+ delete this.linkToUIds[link.linkId];
196
+ delete this.uidToLinks[uid];
197
+ this.server.disconnectClient(link.linkId);
198
+ this.emit(NWSUserMgrJ.EventUserDisconnected, uid);
199
+ if (this.log) {
200
+ console.log(`【NWSUserMgrJ】用户超时断开连接,linkId:${link.linkId},uid:${uid}`);
201
+ }
202
+ }
203
+ }
204
+ }
205
+ }
206
+ /** 用户掉线,已考虑超时问题,如果设置超时非0,则socket掉线会等候用户重连,重连成功不认定掉线 */
207
+ NWSUserMgrJ.EventUserDisconnected = 'disconnected';
208
+ export default NWSUserMgrJ;
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "lz-nframe",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "type": "module",
6
+ "main": "./lib/NApp.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "node --import tsx --test test/NConfigsMgr.test.ts test/NHttpClient.test.ts test/NWSClient.test.ts test/NWSUserClientJ.test.ts test/NWSServiceHub.test.ts",
10
+ "test:watch": "node --import tsx --test --watch test/NConfigsMgr.test.ts test/NHttpClient.test.ts test/NWSClient.test.ts test/NWSUserClientJ.test.ts test/NWSServiceHub.test.ts"
11
+ },
12
+ "author": "",
13
+ "license": "ISC",
14
+ "files": [
15
+ "lib/",
16
+ "package.json"
17
+ ],
18
+ "devDependencies": {
19
+ "@types/node": "^20.12.2",
20
+ "@types/ws": "^8.18.1",
21
+ "tsx": "^4.21.0",
22
+ "typescript": "^5.6.3"
23
+ },
24
+ "dependencies": {
25
+ "lz-nutils": "^1.2.0",
26
+ "ws": "^8.18.3"
27
+ }
28
+ }