@lemoncloud/chatic-sockets-lib 0.1.0 → 0.2.1
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/client-socket-v2/connection-rotation-controller.d.ts +4 -0
- package/dist/client-socket-v2/connection-rotation-controller.js +9 -3
- package/dist/client-socket-v2/create-client-socket-v2.js +155 -27
- package/dist/client-socket-v2/gateways/auth-gateway.d.ts +6 -0
- package/dist/client-socket-v2/gateways/auth-gateway.js +11 -0
- package/dist/client-socket-v2/gateways/channel-gateway.d.ts +38 -0
- package/dist/client-socket-v2/gateways/channel-gateway.js +24 -0
- package/dist/client-socket-v2/gateways/chat-gateway.d.ts +22 -0
- package/dist/client-socket-v2/gateways/chat-gateway.js +16 -0
- package/dist/client-socket-v2/gateways/cloud-gateway.d.ts +12 -0
- package/dist/client-socket-v2/gateways/cloud-gateway.js +11 -0
- package/dist/client-socket-v2/gateways/user-gateway.d.ts +26 -0
- package/dist/client-socket-v2/gateways/user-gateway.js +18 -0
- package/dist/client-socket-v2/index.d.ts +10 -0
- package/dist/client-socket-v2/index.js +9 -0
- package/dist/client-socket-v2/keep-alive-loop.d.ts +5 -7
- package/dist/client-socket-v2/keep-alive-loop.js +32 -9
- package/dist/client-socket-v2/message-router.d.ts +1 -0
- package/dist/client-socket-v2/message-router.js +4 -0
- package/dist/client-socket-v2/reconnect-controller.d.ts +23 -7
- package/dist/client-socket-v2/reconnect-controller.js +72 -9
- package/dist/client-socket-v2/socket-runtime.js +14 -4
- package/dist/client-socket-v2/socket-transport.d.ts +22 -5
- package/dist/client-socket-v2/socket-transport.js +79 -55
- package/dist/client-socket-v2/types.d.ts +45 -8
- package/dist/lib/auth/types.d.ts +36 -0
- package/dist/lib/auth/types.js +2 -0
- package/dist/lib/channel/types.d.ts +175 -0
- package/dist/lib/channel/types.js +2 -0
- package/dist/lib/chat/types.d.ts +79 -0
- package/dist/lib/chat/types.js +2 -0
- package/dist/lib/cloud/types.d.ts +18 -0
- package/dist/lib/cloud/types.js +2 -0
- package/dist/lib/device/types.d.ts +1 -0
- package/dist/lib/socket-actions.d.ts +239 -0
- package/dist/lib/socket-actions.js +167 -0
- package/dist/lib/socket-inputs.d.ts +13 -0
- package/dist/lib/socket-inputs.js +8 -0
- package/dist/lib/sockets/types.d.ts +27 -0
- package/dist/lib/sockets/types.js +17 -0
- package/dist/lib/types.d.ts +2 -0
- package/dist/lib/user/types.d.ts +102 -0
- package/dist/lib/user/types.js +2 -0
- package/dist/modules/chat/types.d.ts +65 -0
- package/dist/modules/chat/types.js +55 -0
- package/package.json +1 -1
|
@@ -10,12 +10,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.KeepAliveLoop = void 0;
|
|
13
|
+
const DEFAULT_PING_TIMEOUT_MS = 10000;
|
|
14
|
+
const DEFAULT_MAX_MISSED_PONGS = 2;
|
|
13
15
|
class KeepAliveLoop {
|
|
14
16
|
constructor(options) {
|
|
15
|
-
var _a, _b, _c;
|
|
17
|
+
var _a, _b, _c, _d, _e;
|
|
16
18
|
this.options = options;
|
|
17
19
|
this.unsubs = [];
|
|
18
20
|
this.running = false;
|
|
21
|
+
this.missedPongs = 0;
|
|
19
22
|
this.start = () => {
|
|
20
23
|
this.running = true;
|
|
21
24
|
if (this.options.client.state === 'connected')
|
|
@@ -55,12 +58,12 @@ class KeepAliveLoop {
|
|
|
55
58
|
this.timer = undefined;
|
|
56
59
|
};
|
|
57
60
|
this.tick = () => __awaiter(this, void 0, void 0, function* () {
|
|
58
|
-
var
|
|
61
|
+
var _f, _g, _h;
|
|
59
62
|
if (!this.running || this.options.client.state !== 'connected')
|
|
60
63
|
return;
|
|
61
64
|
if (this.inFlight)
|
|
62
65
|
return;
|
|
63
|
-
const payload = (
|
|
66
|
+
const payload = (_h = (_g = (_f = this.options).buildPayload) === null || _g === void 0 ? void 0 : _g.call(_f)) !== null && _h !== void 0 ? _h : null;
|
|
64
67
|
this.inFlight = Promise.resolve()
|
|
65
68
|
.then(() => __awaiter(this, void 0, void 0, function* () {
|
|
66
69
|
if (this.mode === 'send') {
|
|
@@ -68,10 +71,24 @@ class KeepAliveLoop {
|
|
|
68
71
|
return;
|
|
69
72
|
}
|
|
70
73
|
yield this.options.client.request('system.ping', payload, {
|
|
71
|
-
timeoutMs: this.
|
|
74
|
+
timeoutMs: this.pingTimeoutMs,
|
|
72
75
|
});
|
|
76
|
+
this.missedPongs = 0;
|
|
77
|
+
}))
|
|
78
|
+
.catch(() => __awaiter(this, void 0, void 0, function* () {
|
|
79
|
+
if (this.mode !== 'request')
|
|
80
|
+
return;
|
|
81
|
+
this.missedPongs += 1;
|
|
82
|
+
if (this.missedPongs < this.maxMissedPongs)
|
|
83
|
+
return;
|
|
84
|
+
this.missedPongs = 0;
|
|
85
|
+
if (this.options.onPongTimeout) {
|
|
86
|
+
yield Promise.resolve(this.options.onPongTimeout()).catch(() => undefined);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
yield this.options.client.disconnect(1001, 'pong-timeout').catch(() => undefined);
|
|
90
|
+
}
|
|
73
91
|
}))
|
|
74
|
-
.catch(() => undefined)
|
|
75
92
|
.then(() => {
|
|
76
93
|
this.inFlight = undefined;
|
|
77
94
|
this.scheduleNext();
|
|
@@ -79,14 +96,20 @@ class KeepAliveLoop {
|
|
|
79
96
|
yield this.inFlight;
|
|
80
97
|
});
|
|
81
98
|
this.intervalMs = (_a = options.intervalMs) !== null && _a !== void 0 ? _a : 30000;
|
|
82
|
-
this.
|
|
99
|
+
this.pingTimeoutMs = (_b = options.timeoutMs) !== null && _b !== void 0 ? _b : DEFAULT_PING_TIMEOUT_MS;
|
|
100
|
+
this.maxMissedPongs = Math.max(1, (_c = options.maxMissedPongs) !== null && _c !== void 0 ? _c : DEFAULT_MAX_MISSED_PONGS);
|
|
101
|
+
this.mode = (_d = options.mode) !== null && _d !== void 0 ? _d : 'request';
|
|
83
102
|
this.timerScheduler = options.timerScheduler;
|
|
84
|
-
this.timerKey = (
|
|
103
|
+
this.timerKey = (_e = options.timerKey) !== null && _e !== void 0 ? _e : 'keepalive';
|
|
85
104
|
this.unsubs.push(options.client.onState(event => {
|
|
86
|
-
if (event.next === 'connected' && this.running)
|
|
105
|
+
if (event.next === 'connected' && this.running) {
|
|
106
|
+
this.missedPongs = 0;
|
|
87
107
|
this.scheduleNow();
|
|
88
|
-
|
|
108
|
+
}
|
|
109
|
+
if (event.next === 'closing' || event.next === 'closed') {
|
|
110
|
+
this.missedPongs = 0;
|
|
89
111
|
this.clearTimer();
|
|
112
|
+
}
|
|
90
113
|
}));
|
|
91
114
|
}
|
|
92
115
|
}
|
|
@@ -5,5 +5,6 @@ export declare class MessageRouter {
|
|
|
5
5
|
private readonly typeListeners;
|
|
6
6
|
onAny: (listener: MessageListener) => (() => void);
|
|
7
7
|
onType: <T = any>(type: string, listener: MessageListener<T>) => (() => void);
|
|
8
|
+
clear: () => void;
|
|
8
9
|
route: (message: SocketMessage<any>) => number;
|
|
9
10
|
}
|
|
@@ -23,6 +23,10 @@ class MessageRouter {
|
|
|
23
23
|
this.typeListeners.delete(key);
|
|
24
24
|
};
|
|
25
25
|
};
|
|
26
|
+
this.clear = () => {
|
|
27
|
+
this.anyListeners.clear();
|
|
28
|
+
this.typeListeners.clear();
|
|
29
|
+
};
|
|
26
30
|
this.route = (message) => {
|
|
27
31
|
var _a;
|
|
28
32
|
const key = `${(_a = message === null || message === void 0 ? void 0 : message.type) !== null && _a !== void 0 ? _a : ''}`.trim();
|
|
@@ -1,32 +1,48 @@
|
|
|
1
|
-
import type { ClientSocketV2, ReconnectController, SharedTimerScheduler } from './types';
|
|
2
|
-
export interface
|
|
1
|
+
import type { AutoReconnectOptionsPartial, ClientSocketV2, ReconnectController, SharedTimerScheduler } from './types';
|
|
2
|
+
export interface AutoReconnectGiveUpEvent {
|
|
3
|
+
attempts: number;
|
|
4
|
+
}
|
|
5
|
+
export interface AutoReconnectConnectFailedEvent {
|
|
6
|
+
attempt: number;
|
|
7
|
+
error: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface AutoReconnectControllerOptions extends AutoReconnectOptionsPartial {
|
|
3
10
|
client: ClientSocketV2;
|
|
4
|
-
minDelayMs?: number;
|
|
5
|
-
maxDelayMs?: number;
|
|
6
|
-
factor?: number;
|
|
7
11
|
timerScheduler?: SharedTimerScheduler;
|
|
8
|
-
timerKey?: string;
|
|
9
12
|
}
|
|
10
13
|
export declare class AutoReconnectController implements ReconnectController {
|
|
11
14
|
private readonly options;
|
|
12
15
|
private readonly minDelayMs;
|
|
13
16
|
private readonly maxDelayMs;
|
|
14
17
|
private readonly factor;
|
|
18
|
+
private readonly jitterRatio;
|
|
19
|
+
private readonly jitterMode;
|
|
20
|
+
private readonly maxAttempts;
|
|
21
|
+
private readonly minStableMs;
|
|
15
22
|
private readonly timerScheduler?;
|
|
16
23
|
private readonly timerKey;
|
|
24
|
+
private readonly now;
|
|
17
25
|
private readonly unsubs;
|
|
26
|
+
private readonly giveUpListeners;
|
|
27
|
+
private readonly connectFailedListeners;
|
|
18
28
|
private timer?;
|
|
19
29
|
private active;
|
|
20
30
|
private manualStop;
|
|
21
31
|
private restarting;
|
|
22
32
|
private attempt;
|
|
23
33
|
private connecting?;
|
|
34
|
+
private stableSince?;
|
|
24
35
|
constructor(options: AutoReconnectControllerOptions);
|
|
25
36
|
start: () => Promise<void>;
|
|
26
|
-
stop: () => Promise<void>;
|
|
37
|
+
stop: (code?: number, reason?: string) => Promise<void>;
|
|
27
38
|
restart: () => Promise<void>;
|
|
39
|
+
onGiveUp: (listener: (event: AutoReconnectGiveUpEvent) => void) => (() => void);
|
|
40
|
+
onConnectFailed: (listener: (event: AutoReconnectConnectFailedEvent) => void) => (() => void);
|
|
28
41
|
destroy: () => void;
|
|
42
|
+
private applyStableReset;
|
|
43
|
+
private giveUp;
|
|
29
44
|
private scheduleReconnect;
|
|
30
45
|
private tryConnect;
|
|
31
46
|
private clearTimer;
|
|
47
|
+
private computeBackoffDelay;
|
|
32
48
|
}
|
|
@@ -10,11 +10,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.AutoReconnectController = void 0;
|
|
13
|
+
const DEFAULT_MIN_STABLE_MS = 5000;
|
|
13
14
|
class AutoReconnectController {
|
|
14
15
|
constructor(options) {
|
|
15
|
-
var _a, _b, _c, _d;
|
|
16
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
16
17
|
this.options = options;
|
|
17
18
|
this.unsubs = [];
|
|
19
|
+
this.giveUpListeners = new Set();
|
|
20
|
+
this.connectFailedListeners = new Set();
|
|
18
21
|
this.active = false;
|
|
19
22
|
this.manualStop = false;
|
|
20
23
|
this.restarting = false;
|
|
@@ -23,17 +26,20 @@ class AutoReconnectController {
|
|
|
23
26
|
this.active = true;
|
|
24
27
|
this.manualStop = false;
|
|
25
28
|
this.clearTimer();
|
|
29
|
+
this.attempt = 0;
|
|
30
|
+
this.stableSince = this.options.client.state === 'connected' ? this.now() : undefined;
|
|
26
31
|
if (this.options.client.state !== 'connected') {
|
|
27
32
|
yield this.tryConnect();
|
|
28
33
|
}
|
|
29
34
|
});
|
|
30
|
-
this.stop = () => __awaiter(this, void 0, void 0, function* () {
|
|
35
|
+
this.stop = (code, reason) => __awaiter(this, void 0, void 0, function* () {
|
|
31
36
|
this.active = false;
|
|
32
37
|
this.manualStop = true;
|
|
33
38
|
this.restarting = false;
|
|
34
39
|
this.clearTimer();
|
|
35
40
|
this.attempt = 0;
|
|
36
|
-
|
|
41
|
+
this.stableSince = undefined;
|
|
42
|
+
yield this.options.client.disconnect(code, reason);
|
|
37
43
|
});
|
|
38
44
|
this.restart = () => __awaiter(this, void 0, void 0, function* () {
|
|
39
45
|
if (!this.active) {
|
|
@@ -42,6 +48,7 @@ class AutoReconnectController {
|
|
|
42
48
|
}
|
|
43
49
|
this.clearTimer();
|
|
44
50
|
this.attempt = 0;
|
|
51
|
+
this.stableSince = undefined;
|
|
45
52
|
this.restarting = true;
|
|
46
53
|
try {
|
|
47
54
|
if (this.options.client.state === 'connected' || this.options.client.state === 'connecting') {
|
|
@@ -53,16 +60,48 @@ class AutoReconnectController {
|
|
|
53
60
|
this.restarting = false;
|
|
54
61
|
}
|
|
55
62
|
});
|
|
63
|
+
this.onGiveUp = (listener) => {
|
|
64
|
+
this.giveUpListeners.add(listener);
|
|
65
|
+
return () => {
|
|
66
|
+
this.giveUpListeners.delete(listener);
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
this.onConnectFailed = (listener) => {
|
|
70
|
+
this.connectFailedListeners.add(listener);
|
|
71
|
+
return () => {
|
|
72
|
+
this.connectFailedListeners.delete(listener);
|
|
73
|
+
};
|
|
74
|
+
};
|
|
56
75
|
this.destroy = () => {
|
|
57
76
|
this.clearTimer();
|
|
77
|
+
this.giveUpListeners.clear();
|
|
78
|
+
this.connectFailedListeners.clear();
|
|
58
79
|
this.unsubs.splice(0).forEach(unsub => unsub());
|
|
59
80
|
};
|
|
81
|
+
this.applyStableReset = () => {
|
|
82
|
+
if (this.stableSince === undefined)
|
|
83
|
+
return;
|
|
84
|
+
const stableMs = this.now() - this.stableSince;
|
|
85
|
+
this.stableSince = undefined;
|
|
86
|
+
if (stableMs >= this.minStableMs)
|
|
87
|
+
this.attempt = 0;
|
|
88
|
+
};
|
|
89
|
+
this.giveUp = () => {
|
|
90
|
+
const attempts = this.attempt;
|
|
91
|
+
this.active = false;
|
|
92
|
+
this.clearTimer();
|
|
93
|
+
this.giveUpListeners.forEach(listener => listener({ attempts }));
|
|
94
|
+
};
|
|
60
95
|
this.scheduleReconnect = () => {
|
|
61
96
|
if (!this.active || this.manualStop)
|
|
62
97
|
return;
|
|
63
98
|
if (this.timer || this.connecting)
|
|
64
99
|
return;
|
|
65
|
-
|
|
100
|
+
if (this.maxAttempts > 0 && this.attempt >= this.maxAttempts) {
|
|
101
|
+
this.giveUp();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const delayMs = this.computeBackoffDelay(this.attempt);
|
|
66
105
|
if (this.timerScheduler) {
|
|
67
106
|
this.timerScheduler.schedule(this.timerKey, delayMs, () => {
|
|
68
107
|
void this.tryConnect();
|
|
@@ -81,13 +120,21 @@ class AutoReconnectController {
|
|
|
81
120
|
return;
|
|
82
121
|
if (this.connecting)
|
|
83
122
|
return this.connecting;
|
|
123
|
+
const attempt = this.attempt;
|
|
124
|
+
let failed = false;
|
|
125
|
+
let failedError;
|
|
84
126
|
this.connecting = this.options.client
|
|
85
127
|
.connect()
|
|
86
|
-
.catch(
|
|
87
|
-
|
|
128
|
+
.catch(error => {
|
|
129
|
+
failed = true;
|
|
130
|
+
failedError = error;
|
|
88
131
|
})
|
|
89
132
|
.then(() => {
|
|
90
133
|
this.connecting = undefined;
|
|
134
|
+
if (failed) {
|
|
135
|
+
this.connectFailedListeners.forEach(listener => listener({ attempt, error: failedError }));
|
|
136
|
+
this.scheduleReconnect();
|
|
137
|
+
}
|
|
91
138
|
});
|
|
92
139
|
return this.connecting;
|
|
93
140
|
});
|
|
@@ -98,20 +145,36 @@ class AutoReconnectController {
|
|
|
98
145
|
clearTimeout(this.timer);
|
|
99
146
|
this.timer = undefined;
|
|
100
147
|
};
|
|
148
|
+
this.computeBackoffDelay = (attempt) => {
|
|
149
|
+
const expDelay = Math.min(this.minDelayMs * Math.pow(this.factor, attempt), this.maxDelayMs);
|
|
150
|
+
if (this.jitterMode === 'full') {
|
|
151
|
+
/** AWS full jitter: random in [0, expDelay). minDelay floor 적용. */
|
|
152
|
+
const delay = Math.random() * expDelay;
|
|
153
|
+
return Math.max(this.minDelayMs, Math.min(this.maxDelayMs, delay));
|
|
154
|
+
}
|
|
155
|
+
const jitter = this.jitterRatio > 0 ? expDelay * (Math.random() * 2 - 1) * this.jitterRatio : 0;
|
|
156
|
+
return Math.max(this.minDelayMs, Math.min(this.maxDelayMs, expDelay + jitter));
|
|
157
|
+
};
|
|
101
158
|
this.minDelayMs = (_a = options.minDelayMs) !== null && _a !== void 0 ? _a : 500;
|
|
102
|
-
this.maxDelayMs = (_b = options.maxDelayMs) !== null && _b !== void 0 ? _b :
|
|
159
|
+
this.maxDelayMs = (_b = options.maxDelayMs) !== null && _b !== void 0 ? _b : 30000;
|
|
103
160
|
this.factor = (_c = options.factor) !== null && _c !== void 0 ? _c : 2;
|
|
161
|
+
this.jitterRatio = Math.max(0, Math.min(1, (_d = options.jitterRatio) !== null && _d !== void 0 ? _d : 0.3));
|
|
162
|
+
this.jitterMode = (_e = options.jitterMode) !== null && _e !== void 0 ? _e : 'equal';
|
|
163
|
+
this.maxAttempts = Math.max(0, (_f = options.maxAttempts) !== null && _f !== void 0 ? _f : 0);
|
|
164
|
+
this.minStableMs = Math.max(0, (_g = options.minStableMs) !== null && _g !== void 0 ? _g : DEFAULT_MIN_STABLE_MS);
|
|
104
165
|
this.timerScheduler = options.timerScheduler;
|
|
105
|
-
this.timerKey = (
|
|
166
|
+
this.timerKey = (_h = options.timerKey) !== null && _h !== void 0 ? _h : 'reconnect';
|
|
167
|
+
this.now = (_j = options.now) !== null && _j !== void 0 ? _j : (() => Date.now());
|
|
106
168
|
this.unsubs.push(options.client.onState(event => {
|
|
107
169
|
if (!this.active)
|
|
108
170
|
return;
|
|
109
171
|
if (event.next === 'connected') {
|
|
110
|
-
this.
|
|
172
|
+
this.stableSince = this.now();
|
|
111
173
|
this.clearTimer();
|
|
112
174
|
this.connecting = undefined;
|
|
113
175
|
}
|
|
114
176
|
if (event.next === 'closed' && !this.manualStop && !this.restarting) {
|
|
177
|
+
this.applyStableReset();
|
|
115
178
|
this.scheduleReconnect();
|
|
116
179
|
}
|
|
117
180
|
}));
|
|
@@ -17,7 +17,7 @@ const shared_timer_scheduler_1 = require("./shared-timer-scheduler");
|
|
|
17
17
|
const sync_scheduler_1 = require("./sync-scheduler");
|
|
18
18
|
class SocketRuntime {
|
|
19
19
|
constructor(options) {
|
|
20
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
20
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
21
21
|
this.options = options;
|
|
22
22
|
this.start = () => __awaiter(this, void 0, void 0, function* () {
|
|
23
23
|
this.keepAlive.start();
|
|
@@ -42,11 +42,21 @@ class SocketRuntime {
|
|
|
42
42
|
timerScheduler: this.timerScheduler,
|
|
43
43
|
});
|
|
44
44
|
this.keepAlive =
|
|
45
|
-
(_e = options.keepAlive) !== null && _e !== void 0 ? _e :
|
|
45
|
+
(_e = options.keepAlive) !== null && _e !== void 0 ? _e : (options.keepAliveOptions
|
|
46
|
+
? new keep_alive_loop_1.KeepAliveLoop(Object.assign({ client: options.client, timerScheduler: this.timerScheduler }, options.keepAliveOptions))
|
|
47
|
+
: (_f = options.client.keepAlive) !== null && _f !== void 0 ? _f : new keep_alive_loop_1.KeepAliveLoop({
|
|
48
|
+
client: options.client,
|
|
49
|
+
timerScheduler: this.timerScheduler,
|
|
50
|
+
}));
|
|
46
51
|
this.reconnect =
|
|
47
|
-
(
|
|
52
|
+
(_g = options.reconnect) !== null && _g !== void 0 ? _g : (options.reconnectOptions
|
|
53
|
+
? new reconnect_controller_1.AutoReconnectController(Object.assign({ client: options.client, timerScheduler: this.timerScheduler }, options.reconnectOptions))
|
|
54
|
+
: (_h = options.client.reconnect) !== null && _h !== void 0 ? _h : new reconnect_controller_1.AutoReconnectController({
|
|
55
|
+
client: options.client,
|
|
56
|
+
timerScheduler: this.timerScheduler,
|
|
57
|
+
}));
|
|
48
58
|
this.rotation =
|
|
49
|
-
(
|
|
59
|
+
(_j = options.rotation) !== null && _j !== void 0 ? _j : new connection_rotation_controller_1.ConnectionRotationController(Object.assign({ client: options.client, reconnect: this.reconnect, timerScheduler: this.timerScheduler }, (options.rotationOptions || {})));
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
62
|
exports.SocketRuntime = SocketRuntime;
|
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
import type { ClientSocketState, SocketFactoryContext, SocketLike, SocketTransport, SocketTransportEventMap } from './types';
|
|
2
|
+
export interface WebSocketTransportOptions {
|
|
3
|
+
/** connect() open 이벤트 대기 timeout. 만료 시 socket close + Promise reject. 0이면 무한 대기. 기본 10000 */
|
|
4
|
+
connectTimeoutMs?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* `SocketTransport` adapter over lemon-model's owned WebSocket network.
|
|
8
|
+
*
|
|
9
|
+
* Raw network concerns (socket creation, OPEN send guard, actual close, raw event mapping) are owned by
|
|
10
|
+
* `OwnedWebSocketNetwork`. This adapter only translates that single-shot network into chatic's reconnectable,
|
|
11
|
+
* code-carrying, 4-event / 5-state lifecycle. The connect timeout stays here because it belongs to the
|
|
12
|
+
* reconnect/backoff policy, not the raw transport.
|
|
13
|
+
*/
|
|
2
14
|
export declare class WebSocketTransport implements SocketTransport {
|
|
3
15
|
readonly url: string;
|
|
4
16
|
readonly protocols?: string | string[];
|
|
5
|
-
private readonly socketFactory
|
|
17
|
+
private readonly socketFactory?;
|
|
6
18
|
private _state;
|
|
7
|
-
private
|
|
8
|
-
private
|
|
19
|
+
private network?;
|
|
20
|
+
private networkUnsubs;
|
|
9
21
|
private readonly listeners;
|
|
10
|
-
|
|
22
|
+
private readonly connectTimeoutMs;
|
|
23
|
+
private connecting?;
|
|
24
|
+
private cancelConnecting?;
|
|
25
|
+
constructor(url: string, protocols?: string | string[], socketFactory?: (context: SocketFactoryContext) => SocketLike, options?: WebSocketTransportOptions);
|
|
11
26
|
get state(): ClientSocketState;
|
|
12
27
|
on: <TType extends keyof SocketTransportEventMap>(type: TType, listener: (event: SocketTransportEventMap[TType]) => void) => (() => void);
|
|
13
28
|
connect: () => Promise<void>;
|
|
14
29
|
disconnect: (code?: number, reason?: string) => Promise<void>;
|
|
15
30
|
send: (raw: string) => void;
|
|
16
31
|
private emit;
|
|
17
|
-
|
|
32
|
+
/** bridge chatic's `SocketLike` factory to the owned network's `WebSocketClosable` factory */
|
|
33
|
+
private buildSocketFactory;
|
|
34
|
+
private cleanupNetwork;
|
|
18
35
|
}
|
|
@@ -10,28 +10,24 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.WebSocketTransport = void 0;
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return () => {
|
|
24
|
-
if (socket[key] === listener)
|
|
25
|
-
socket[key] = prev !== null && prev !== void 0 ? prev : null;
|
|
26
|
-
};
|
|
27
|
-
};
|
|
13
|
+
const lemon_model_1 = require("lemon-model");
|
|
14
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 10000;
|
|
15
|
+
/**
|
|
16
|
+
* `SocketTransport` adapter over lemon-model's owned WebSocket network.
|
|
17
|
+
*
|
|
18
|
+
* Raw network concerns (socket creation, OPEN send guard, actual close, raw event mapping) are owned by
|
|
19
|
+
* `OwnedWebSocketNetwork`. This adapter only translates that single-shot network into chatic's reconnectable,
|
|
20
|
+
* code-carrying, 4-event / 5-state lifecycle. The connect timeout stays here because it belongs to the
|
|
21
|
+
* reconnect/backoff policy, not the raw transport.
|
|
22
|
+
*/
|
|
28
23
|
class WebSocketTransport {
|
|
29
|
-
constructor(url, protocols, socketFactory
|
|
24
|
+
constructor(url, protocols, socketFactory, options) {
|
|
25
|
+
var _a;
|
|
30
26
|
this.url = url;
|
|
31
27
|
this.protocols = protocols;
|
|
32
28
|
this.socketFactory = socketFactory;
|
|
33
29
|
this._state = 'idle';
|
|
34
|
-
this.
|
|
30
|
+
this.networkUnsubs = [];
|
|
35
31
|
this.listeners = new Map();
|
|
36
32
|
this.on = (type, listener) => {
|
|
37
33
|
var _a;
|
|
@@ -47,75 +43,103 @@ class WebSocketTransport {
|
|
|
47
43
|
this.connect = () => __awaiter(this, void 0, void 0, function* () {
|
|
48
44
|
if (this._state === 'connected')
|
|
49
45
|
return;
|
|
50
|
-
if (this.
|
|
51
|
-
return;
|
|
52
|
-
this.
|
|
46
|
+
if (this.connecting)
|
|
47
|
+
return this.connecting;
|
|
48
|
+
this.cleanupNetwork();
|
|
53
49
|
this._state = 'connecting';
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
const network = (0, lemon_model_1.createOwnedWebSocketNetwork)({
|
|
51
|
+
url: this.url,
|
|
52
|
+
protocols: this.protocols,
|
|
53
|
+
socketFactory: this.buildSocketFactory(),
|
|
54
|
+
connectTimeoutMs: 0, // connect timeout is owned here (reconnect policy), not by the network
|
|
55
|
+
});
|
|
56
|
+
this.network = network;
|
|
57
|
+
this.connecting = new Promise((resolve, reject) => {
|
|
58
|
+
var _a;
|
|
59
|
+
let settled = false;
|
|
60
|
+
let timeoutTimer;
|
|
58
61
|
const settleConnect = (handler) => {
|
|
59
|
-
if (
|
|
62
|
+
if (settled)
|
|
60
63
|
return;
|
|
61
|
-
|
|
64
|
+
settled = true;
|
|
65
|
+
if (timeoutTimer)
|
|
66
|
+
clearTimeout(timeoutTimer);
|
|
67
|
+
this.connecting = undefined;
|
|
68
|
+
this.cancelConnecting = undefined;
|
|
62
69
|
handler();
|
|
63
70
|
};
|
|
64
|
-
this.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
settleConnect(() =>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}),
|
|
79
|
-
|
|
71
|
+
this.cancelConnecting = error => settleConnect(() => reject(error));
|
|
72
|
+
if (this.connectTimeoutMs > 0) {
|
|
73
|
+
timeoutTimer = setTimeout(() => {
|
|
74
|
+
const error = new Error(`408 CONNECT TIMEOUT - WebSocketTransport.connect() after ${this.connectTimeoutMs}ms`);
|
|
75
|
+
settleConnect(() => reject(error));
|
|
76
|
+
network.close(1000, 'connect-timeout');
|
|
77
|
+
}, this.connectTimeoutMs);
|
|
78
|
+
}
|
|
79
|
+
const unsubOpen = (_a = network.onOpen) === null || _a === void 0 ? void 0 : _a.call(network, () => {
|
|
80
|
+
this._state = 'connected';
|
|
81
|
+
this.emit('open', undefined);
|
|
82
|
+
settleConnect(() => resolve());
|
|
83
|
+
});
|
|
84
|
+
this.networkUnsubs = [
|
|
85
|
+
network.onMessage(raw => this.emit('message', { data: raw })),
|
|
86
|
+
network.onError((event, context) => {
|
|
80
87
|
var _a;
|
|
81
|
-
|
|
88
|
+
if (context.scope === 'ownedWebSocket.close') {
|
|
89
|
+
const close = event;
|
|
90
|
+
this._state = 'closed';
|
|
91
|
+
this.emit('close', { code: close === null || close === void 0 ? void 0 : close.code, reason: close === null || close === void 0 ? void 0 : close.reason, wasClean: close === null || close === void 0 ? void 0 : close.wasClean });
|
|
92
|
+
settleConnect(() => resolve()); // close-before-open resolves connect (reconnect drives off state)
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const error = (_a = event === null || event === void 0 ? void 0 : event.error) !== null && _a !== void 0 ? _a : (event instanceof Error ? event : new Error(`socket transport error`));
|
|
82
96
|
this.emit('error', { error });
|
|
83
97
|
if (this._state !== 'connected')
|
|
84
98
|
this._state = 'closed';
|
|
85
99
|
settleConnect(() => reject(error));
|
|
86
100
|
}),
|
|
87
|
-
bindSocketListener(socket, 'message', event => {
|
|
88
|
-
this.emit('message', { data: (0, common_1.asString)(event === null || event === void 0 ? void 0 : event.data, '') });
|
|
89
|
-
}),
|
|
90
101
|
];
|
|
102
|
+
if (unsubOpen)
|
|
103
|
+
this.networkUnsubs.push(unsubOpen);
|
|
91
104
|
});
|
|
105
|
+
return this.connecting;
|
|
92
106
|
});
|
|
93
107
|
this.disconnect = (code, reason) => __awaiter(this, void 0, void 0, function* () {
|
|
94
|
-
|
|
108
|
+
var _b;
|
|
109
|
+
if (!this.network) {
|
|
95
110
|
this._state = 'closed';
|
|
96
111
|
return;
|
|
97
112
|
}
|
|
98
113
|
this._state = 'closing';
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
this.
|
|
114
|
+
const network = this.network;
|
|
115
|
+
network.close(code, reason);
|
|
116
|
+
(_b = this.cancelConnecting) === null || _b === void 0 ? void 0 : _b.call(this, new Error(`499 CLIENT CLOSED REQUEST - disconnect during connecting`));
|
|
117
|
+
this.cleanupNetwork();
|
|
102
118
|
this._state = 'closed';
|
|
103
119
|
this.emit('close', { code, reason, wasClean: true });
|
|
104
120
|
});
|
|
105
121
|
this.send = (raw) => {
|
|
106
|
-
if (!this.
|
|
122
|
+
if (!this.network || this.network.readyState !== 'open') {
|
|
107
123
|
throw new Error(`503 SOCKET NOT CONNECTED - WebSocketTransport.send()`);
|
|
108
124
|
}
|
|
109
|
-
this.
|
|
125
|
+
this.network.send(raw);
|
|
110
126
|
};
|
|
111
127
|
this.emit = (type, event) => {
|
|
112
128
|
var _a;
|
|
113
129
|
(_a = this.listeners.get(type)) === null || _a === void 0 ? void 0 : _a.forEach(listener => listener(event));
|
|
114
130
|
};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
131
|
+
/** bridge chatic's `SocketLike` factory to the owned network's `WebSocketClosable` factory */
|
|
132
|
+
this.buildSocketFactory = () => {
|
|
133
|
+
const factory = this.socketFactory;
|
|
134
|
+
if (!factory)
|
|
135
|
+
return undefined;
|
|
136
|
+
return context => factory({ url: context.url, protocols: context.protocols });
|
|
137
|
+
};
|
|
138
|
+
this.cleanupNetwork = () => {
|
|
139
|
+
this.networkUnsubs.splice(0).forEach(unsub => unsub());
|
|
140
|
+
this.network = undefined;
|
|
118
141
|
};
|
|
142
|
+
this.connectTimeoutMs = (_a = options === null || options === void 0 ? void 0 : options.connectTimeoutMs) !== null && _a !== void 0 ? _a : DEFAULT_CONNECT_TIMEOUT_MS;
|
|
119
143
|
}
|
|
120
144
|
get state() {
|
|
121
145
|
return this._state;
|