@eleven-am/pondsocket-client 0.0.34 → 0.0.35
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/browser/client.js +4 -174
- package/browser/client.test.js +0 -4
- package/browser/sseClient.js +40 -115
- package/core/baseClient.js +116 -0
- package/core/channel.js +22 -12
- package/core/channel.test.js +26 -0
- package/core/webSocketClient.js +101 -0
- package/dist.d.ts +1 -1
- package/node/node.js +11 -79
- package/node/node.test.js +30 -0
- package/package.json +28 -10
package/browser/client.js
CHANGED
|
@@ -1,28 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
-
};
|
|
8
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
-
};
|
|
13
|
-
var _PondClient_instances, _PondClient_channels, _PondClient_clearTimeouts, _PondClient_createPublisher, _PondClient_handleAcknowledge, _PondClient_handleUnauthorized, _PondClient_init;
|
|
14
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
3
|
exports.PondClient = void 0;
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const DEFAULT_CONNECTION_TIMEOUT = 10000;
|
|
20
|
-
const DEFAULT_MAX_RECONNECT_DELAY = 30000;
|
|
21
|
-
class PondClient {
|
|
22
|
-
constructor(endpoint, params = {}, options = {}) {
|
|
23
|
-
var _a, _b;
|
|
24
|
-
_PondClient_instances.add(this);
|
|
25
|
-
_PondClient_channels.set(this, void 0);
|
|
4
|
+
const webSocketClient_1 = require("../core/webSocketClient");
|
|
5
|
+
class PondClient extends webSocketClient_1.WebSocketClient {
|
|
6
|
+
_resolveAddress(endpoint, params) {
|
|
26
7
|
let address;
|
|
27
8
|
try {
|
|
28
9
|
address = new URL(endpoint);
|
|
@@ -32,164 +13,13 @@ class PondClient {
|
|
|
32
13
|
address.pathname = endpoint;
|
|
33
14
|
address.hash = '';
|
|
34
15
|
}
|
|
35
|
-
this._disconnecting = false;
|
|
36
|
-
this._reconnectAttempts = 0;
|
|
37
16
|
const query = new URLSearchParams(params);
|
|
38
17
|
address.search = query.toString();
|
|
39
18
|
const protocol = address.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
40
19
|
if (address.protocol !== 'wss:' && address.protocol !== 'ws:') {
|
|
41
20
|
address.protocol = protocol;
|
|
42
21
|
}
|
|
43
|
-
|
|
44
|
-
this._options = {
|
|
45
|
-
connectionTimeout: (_a = options.connectionTimeout) !== null && _a !== void 0 ? _a : DEFAULT_CONNECTION_TIMEOUT,
|
|
46
|
-
maxReconnectDelay: (_b = options.maxReconnectDelay) !== null && _b !== void 0 ? _b : DEFAULT_MAX_RECONNECT_DELAY,
|
|
47
|
-
pingInterval: options.pingInterval,
|
|
48
|
-
};
|
|
49
|
-
__classPrivateFieldSet(this, _PondClient_channels, new Map(), "f");
|
|
50
|
-
this._broadcaster = new pondsocket_common_1.Subject();
|
|
51
|
-
this._connectionState = new pondsocket_common_1.BehaviorSubject(types_1.ConnectionState.DISCONNECTED);
|
|
52
|
-
this._errorSubject = new pondsocket_common_1.Subject();
|
|
53
|
-
__classPrivateFieldGet(this, _PondClient_instances, "m", _PondClient_init).call(this);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* @desc Connects to the server and returns the socket.
|
|
57
|
-
*/
|
|
58
|
-
connect() {
|
|
59
|
-
this._disconnecting = false;
|
|
60
|
-
this._connectionState.publish(types_1.ConnectionState.CONNECTING);
|
|
61
|
-
const socket = new WebSocket(this._address.toString());
|
|
62
|
-
this._connectionTimeoutId = setTimeout(() => {
|
|
63
|
-
if (socket.readyState === WebSocket.CONNECTING) {
|
|
64
|
-
const error = new Error('Connection timeout');
|
|
65
|
-
this._errorSubject.publish(error);
|
|
66
|
-
socket.close();
|
|
67
|
-
}
|
|
68
|
-
}, this._options.connectionTimeout);
|
|
69
|
-
socket.onopen = () => {
|
|
70
|
-
__classPrivateFieldGet(this, _PondClient_instances, "m", _PondClient_clearTimeouts).call(this);
|
|
71
|
-
this._reconnectAttempts = 0;
|
|
72
|
-
if (this._options.pingInterval) {
|
|
73
|
-
this._pingIntervalId = setInterval(() => {
|
|
74
|
-
if (socket.readyState === WebSocket.OPEN) {
|
|
75
|
-
socket.send(JSON.stringify({ action: 'ping' }));
|
|
76
|
-
}
|
|
77
|
-
}, this._options.pingInterval);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
socket.onmessage = (message) => {
|
|
81
|
-
const lines = message.data.trim().split('\n');
|
|
82
|
-
for (const line of lines) {
|
|
83
|
-
if (line.trim()) {
|
|
84
|
-
const data = JSON.parse(line);
|
|
85
|
-
const event = pondsocket_common_1.channelEventSchema.parse(data);
|
|
86
|
-
this._broadcaster.publish(event);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
socket.onerror = (event) => {
|
|
91
|
-
const error = new Error('WebSocket error');
|
|
92
|
-
this._errorSubject.publish(error);
|
|
93
|
-
socket.close();
|
|
94
|
-
};
|
|
95
|
-
socket.onclose = () => {
|
|
96
|
-
__classPrivateFieldGet(this, _PondClient_instances, "m", _PondClient_clearTimeouts).call(this);
|
|
97
|
-
this._connectionState.publish(types_1.ConnectionState.DISCONNECTED);
|
|
98
|
-
if (this._disconnecting) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const delay = Math.min(1000 * Math.pow(2, this._reconnectAttempts), this._options.maxReconnectDelay);
|
|
102
|
-
this._reconnectAttempts++;
|
|
103
|
-
setTimeout(() => {
|
|
104
|
-
this.connect();
|
|
105
|
-
}, delay);
|
|
106
|
-
};
|
|
107
|
-
this._socket = socket;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* @desc Returns the current state of the socket.
|
|
111
|
-
*/
|
|
112
|
-
getState() {
|
|
113
|
-
return this._connectionState.value;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* @desc Disconnects the socket.
|
|
117
|
-
*/
|
|
118
|
-
disconnect() {
|
|
119
|
-
var _a;
|
|
120
|
-
__classPrivateFieldGet(this, _PondClient_instances, "m", _PondClient_clearTimeouts).call(this);
|
|
121
|
-
this._disconnecting = true;
|
|
122
|
-
this._connectionState.publish(types_1.ConnectionState.DISCONNECTED);
|
|
123
|
-
(_a = this._socket) === null || _a === void 0 ? void 0 : _a.close();
|
|
124
|
-
__classPrivateFieldGet(this, _PondClient_channels, "f").clear();
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* @desc Creates a channel with the given name and params.
|
|
128
|
-
* @param name - The name of the channel.
|
|
129
|
-
* @param params - The params to send to the server.
|
|
130
|
-
*/
|
|
131
|
-
createChannel(name, params) {
|
|
132
|
-
const channel = __classPrivateFieldGet(this, _PondClient_channels, "f").get(name);
|
|
133
|
-
if (channel && channel.channelState !== pondsocket_common_1.ChannelState.CLOSED) {
|
|
134
|
-
return channel;
|
|
135
|
-
}
|
|
136
|
-
const publisher = __classPrivateFieldGet(this, _PondClient_instances, "m", _PondClient_createPublisher).call(this);
|
|
137
|
-
const newChannel = new channel_1.Channel(publisher, this._connectionState, name, params || {});
|
|
138
|
-
__classPrivateFieldGet(this, _PondClient_channels, "f").set(name, newChannel);
|
|
139
|
-
return newChannel;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* @desc Subscribes to the connection state.
|
|
143
|
-
* @param callback - The callback to call when the state changes.
|
|
144
|
-
*/
|
|
145
|
-
onConnectionChange(callback) {
|
|
146
|
-
return this._connectionState.subscribe(callback);
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* @desc Subscribes to connection errors.
|
|
150
|
-
* @param callback - The callback to call when an error occurs.
|
|
151
|
-
*/
|
|
152
|
-
onError(callback) {
|
|
153
|
-
return this._errorSubject.subscribe(callback);
|
|
22
|
+
return address;
|
|
154
23
|
}
|
|
155
24
|
}
|
|
156
25
|
exports.PondClient = PondClient;
|
|
157
|
-
_PondClient_channels = new WeakMap(), _PondClient_instances = new WeakSet(), _PondClient_clearTimeouts = function _PondClient_clearTimeouts() {
|
|
158
|
-
if (this._connectionTimeoutId) {
|
|
159
|
-
clearTimeout(this._connectionTimeoutId);
|
|
160
|
-
this._connectionTimeoutId = undefined;
|
|
161
|
-
}
|
|
162
|
-
if (this._pingIntervalId) {
|
|
163
|
-
clearInterval(this._pingIntervalId);
|
|
164
|
-
this._pingIntervalId = undefined;
|
|
165
|
-
}
|
|
166
|
-
}, _PondClient_createPublisher = function _PondClient_createPublisher() {
|
|
167
|
-
return (message) => {
|
|
168
|
-
if (this._connectionState.value === types_1.ConnectionState.CONNECTED) {
|
|
169
|
-
this._socket.send(JSON.stringify(message));
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
}, _PondClient_handleAcknowledge = function _PondClient_handleAcknowledge(message) {
|
|
173
|
-
var _a;
|
|
174
|
-
const channel = (_a = __classPrivateFieldGet(this, _PondClient_channels, "f").get(message.channelName)) !== null && _a !== void 0 ? _a : new channel_1.Channel(__classPrivateFieldGet(this, _PondClient_instances, "m", _PondClient_createPublisher).call(this), this._connectionState, message.channelName, {});
|
|
175
|
-
__classPrivateFieldGet(this, _PondClient_channels, "f").set(message.channelName, channel);
|
|
176
|
-
channel.acknowledge(this._broadcaster);
|
|
177
|
-
}, _PondClient_handleUnauthorized = function _PondClient_handleUnauthorized(message) {
|
|
178
|
-
const channel = __classPrivateFieldGet(this, _PondClient_channels, "f").get(message.channelName);
|
|
179
|
-
if (channel) {
|
|
180
|
-
const payload = message.payload;
|
|
181
|
-
channel.decline(payload);
|
|
182
|
-
}
|
|
183
|
-
}, _PondClient_init = function _PondClient_init() {
|
|
184
|
-
this._broadcaster.subscribe((message) => {
|
|
185
|
-
if (message.event === pondsocket_common_1.Events.ACKNOWLEDGE) {
|
|
186
|
-
__classPrivateFieldGet(this, _PondClient_instances, "m", _PondClient_handleAcknowledge).call(this, message);
|
|
187
|
-
}
|
|
188
|
-
else if (message.event === pondsocket_common_1.Events.UNAUTHORIZED) {
|
|
189
|
-
__classPrivateFieldGet(this, _PondClient_instances, "m", _PondClient_handleUnauthorized).call(this, message);
|
|
190
|
-
}
|
|
191
|
-
else if (message.event === pondsocket_common_1.Events.CONNECTION && message.action === pondsocket_common_1.ServerActions.CONNECT) {
|
|
192
|
-
this._connectionState.publish(types_1.ConnectionState.CONNECTED);
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
};
|
package/browser/client.test.js
CHANGED
|
@@ -13,7 +13,6 @@ const pondsocket_common_1 = require("@eleven-am/pondsocket-common");
|
|
|
13
13
|
const client_1 = require("./client");
|
|
14
14
|
const types_1 = require("../types");
|
|
15
15
|
class MockWebSocket {
|
|
16
|
-
// eslint-disable-next-line no-useless-constructor
|
|
17
16
|
constructor(url) {
|
|
18
17
|
this.url = url;
|
|
19
18
|
this.send = jest.fn();
|
|
@@ -23,7 +22,6 @@ class MockWebSocket {
|
|
|
23
22
|
}
|
|
24
23
|
MockWebSocket.CONNECTING = 0;
|
|
25
24
|
MockWebSocket.OPEN = 1;
|
|
26
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
27
25
|
// @ts-expect-error
|
|
28
26
|
global.WebSocket = MockWebSocket;
|
|
29
27
|
describe('PondClient', () => {
|
|
@@ -92,7 +90,6 @@ describe('PondClient', () => {
|
|
|
92
90
|
test('createChannel method should create a new channel or return an existing one', () => {
|
|
93
91
|
const mockChannel = pondClient.createChannel('exampleChannel');
|
|
94
92
|
const mockExistingChannel = pondClient.createChannel('exampleChannel');
|
|
95
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
96
93
|
expect(mockChannel).toBeInstanceOf(require('../core/channel').Channel);
|
|
97
94
|
expect(mockExistingChannel).toBe(mockChannel);
|
|
98
95
|
});
|
|
@@ -104,7 +101,6 @@ describe('PondClient', () => {
|
|
|
104
101
|
expect(mockCallback).toHaveBeenCalledWith(types_1.ConnectionState.CONNECTED);
|
|
105
102
|
unsubscribe();
|
|
106
103
|
pondClient['_connectionState'].publish(types_1.ConnectionState.DISCONNECTED);
|
|
107
|
-
// Should not be called again after unsubscribe
|
|
108
104
|
expect(mockCallback).toHaveBeenCalledTimes(1);
|
|
109
105
|
});
|
|
110
106
|
test('getState method should return the current state of the socket', () => {
|
package/browser/sseClient.js
CHANGED
|
@@ -1,28 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
-
};
|
|
8
2
|
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
3
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
4
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
5
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
6
|
};
|
|
13
|
-
var _SSEClient_instances,
|
|
7
|
+
var _SSEClient_instances, _SSEClient_handleMessage;
|
|
14
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
9
|
exports.SSEClient = void 0;
|
|
16
10
|
const pondsocket_common_1 = require("@eleven-am/pondsocket-common");
|
|
17
|
-
const
|
|
11
|
+
const baseClient_1 = require("../core/baseClient");
|
|
18
12
|
const types_1 = require("../types");
|
|
19
|
-
|
|
20
|
-
const DEFAULT_MAX_RECONNECT_DELAY = 30000;
|
|
21
|
-
class SSEClient {
|
|
13
|
+
class SSEClient extends baseClient_1.BaseClient {
|
|
22
14
|
constructor(endpoint, params = {}, options = {}) {
|
|
23
|
-
|
|
15
|
+
super(endpoint, params, options);
|
|
24
16
|
_SSEClient_instances.add(this);
|
|
25
|
-
|
|
17
|
+
this._postAddress = new URL(this._address.toString());
|
|
18
|
+
this._withCredentials = options.withCredentials;
|
|
19
|
+
}
|
|
20
|
+
_resolveAddress(endpoint, params) {
|
|
26
21
|
let address;
|
|
27
22
|
try {
|
|
28
23
|
address = new URL(endpoint);
|
|
@@ -31,33 +26,20 @@ class SSEClient {
|
|
|
31
26
|
address = new URL(window.location.toString());
|
|
32
27
|
address.pathname = endpoint;
|
|
33
28
|
}
|
|
34
|
-
this._disconnecting = false;
|
|
35
|
-
this._reconnectAttempts = 0;
|
|
36
29
|
const query = new URLSearchParams(params);
|
|
37
30
|
address.search = query.toString();
|
|
38
31
|
if (address.protocol !== 'https:' && address.protocol !== 'http:') {
|
|
39
32
|
address.protocol = window.location.protocol;
|
|
40
33
|
}
|
|
41
|
-
|
|
42
|
-
this._postAddress = new URL(address.toString());
|
|
43
|
-
this._options = {
|
|
44
|
-
connectionTimeout: (_a = options.connectionTimeout) !== null && _a !== void 0 ? _a : DEFAULT_CONNECTION_TIMEOUT,
|
|
45
|
-
maxReconnectDelay: (_b = options.maxReconnectDelay) !== null && _b !== void 0 ? _b : DEFAULT_MAX_RECONNECT_DELAY,
|
|
46
|
-
pingInterval: options.pingInterval,
|
|
47
|
-
withCredentials: options.withCredentials,
|
|
48
|
-
};
|
|
49
|
-
__classPrivateFieldSet(this, _SSEClient_channels, new Map(), "f");
|
|
50
|
-
this._broadcaster = new pondsocket_common_1.Subject();
|
|
51
|
-
this._connectionState = new pondsocket_common_1.BehaviorSubject(types_1.ConnectionState.DISCONNECTED);
|
|
52
|
-
this._errorSubject = new pondsocket_common_1.Subject();
|
|
53
|
-
__classPrivateFieldGet(this, _SSEClient_instances, "m", _SSEClient_init).call(this);
|
|
34
|
+
return address;
|
|
54
35
|
}
|
|
55
36
|
connect() {
|
|
56
37
|
var _a;
|
|
57
38
|
this._disconnecting = false;
|
|
39
|
+
this._connectionId = undefined;
|
|
58
40
|
this._connectionState.publish(types_1.ConnectionState.CONNECTING);
|
|
59
41
|
const eventSource = new EventSource(this._address.toString(), {
|
|
60
|
-
withCredentials: (_a = this.
|
|
42
|
+
withCredentials: (_a = this._withCredentials) !== null && _a !== void 0 ? _a : false,
|
|
61
43
|
});
|
|
62
44
|
this._connectionTimeoutId = setTimeout(() => {
|
|
63
45
|
if (eventSource.readyState === EventSource.CONNECTING) {
|
|
@@ -67,7 +49,7 @@ class SSEClient {
|
|
|
67
49
|
}
|
|
68
50
|
}, this._options.connectionTimeout);
|
|
69
51
|
eventSource.onopen = () => {
|
|
70
|
-
|
|
52
|
+
this._clearConnectionTimeout();
|
|
71
53
|
this._reconnectAttempts = 0;
|
|
72
54
|
};
|
|
73
55
|
eventSource.onmessage = (event) => {
|
|
@@ -77,28 +59,18 @@ class SSEClient {
|
|
|
77
59
|
const error = new Error('SSE connection error');
|
|
78
60
|
this._errorSubject.publish(error);
|
|
79
61
|
eventSource.close();
|
|
80
|
-
|
|
62
|
+
this._clearConnectionTimeout();
|
|
81
63
|
this._connectionState.publish(types_1.ConnectionState.DISCONNECTED);
|
|
82
|
-
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const delay = Math.min(1000 * Math.pow(2, this._reconnectAttempts), this._options.maxReconnectDelay);
|
|
86
|
-
this._reconnectAttempts++;
|
|
87
|
-
setTimeout(() => {
|
|
88
|
-
this.connect();
|
|
89
|
-
}, delay);
|
|
64
|
+
this._scheduleReconnect();
|
|
90
65
|
};
|
|
91
66
|
this._eventSource = eventSource;
|
|
92
67
|
}
|
|
93
|
-
getState() {
|
|
94
|
-
return this._connectionState.value;
|
|
95
|
-
}
|
|
96
68
|
getConnectionId() {
|
|
97
69
|
return this._connectionId;
|
|
98
70
|
}
|
|
99
71
|
disconnect() {
|
|
100
72
|
var _a;
|
|
101
|
-
|
|
73
|
+
this._clearConnectionTimeout();
|
|
102
74
|
this._disconnecting = true;
|
|
103
75
|
this._connectionState.publish(types_1.ConnectionState.DISCONNECTED);
|
|
104
76
|
if (this._connectionId) {
|
|
@@ -107,93 +79,46 @@ class SSEClient {
|
|
|
107
79
|
headers: {
|
|
108
80
|
'X-Connection-ID': this._connectionId,
|
|
109
81
|
},
|
|
110
|
-
credentials: this.
|
|
82
|
+
credentials: this._withCredentials ? 'include' : 'same-origin',
|
|
111
83
|
}).catch(() => {
|
|
112
84
|
});
|
|
113
85
|
}
|
|
114
86
|
(_a = this._eventSource) === null || _a === void 0 ? void 0 : _a.close();
|
|
115
87
|
this._connectionId = undefined;
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
createChannel(name, params) {
|
|
119
|
-
const channel = __classPrivateFieldGet(this, _SSEClient_channels, "f").get(name);
|
|
120
|
-
if (channel && channel.channelState !== pondsocket_common_1.ChannelState.CLOSED) {
|
|
121
|
-
return channel;
|
|
122
|
-
}
|
|
123
|
-
const publisher = __classPrivateFieldGet(this, _SSEClient_instances, "m", _SSEClient_createPublisher).call(this);
|
|
124
|
-
const newChannel = new channel_1.Channel(publisher, this._connectionState, name, params || {});
|
|
125
|
-
__classPrivateFieldGet(this, _SSEClient_channels, "f").set(name, newChannel);
|
|
126
|
-
return newChannel;
|
|
88
|
+
this._clearChannels();
|
|
127
89
|
}
|
|
128
|
-
|
|
129
|
-
return
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
90
|
+
_createPublisher() {
|
|
91
|
+
return (message) => {
|
|
92
|
+
if (this._connectionState.value === types_1.ConnectionState.CONNECTED && this._connectionId) {
|
|
93
|
+
fetch(this._postAddress.toString(), {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Content-Type': 'application/json',
|
|
97
|
+
'X-Connection-ID': this._connectionId,
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify(message),
|
|
100
|
+
credentials: this._withCredentials ? 'include' : 'same-origin',
|
|
101
|
+
}).catch((err) => {
|
|
102
|
+
this._errorSubject.publish(err instanceof Error ? err : new Error('Failed to send message'));
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
};
|
|
133
106
|
}
|
|
134
107
|
}
|
|
135
108
|
exports.SSEClient = SSEClient;
|
|
136
|
-
|
|
109
|
+
_SSEClient_instances = new WeakSet(), _SSEClient_handleMessage = function _SSEClient_handleMessage(data) {
|
|
137
110
|
try {
|
|
138
|
-
const
|
|
139
|
-
for (const
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (event.event === pondsocket_common_1.Events.CONNECTION && event.action === pondsocket_common_1.ServerActions.CONNECT) {
|
|
144
|
-
if (event.payload && typeof event.payload === 'object' && 'connectionId' in event.payload) {
|
|
145
|
-
this._connectionId = event.payload.connectionId;
|
|
146
|
-
}
|
|
111
|
+
const events = this._parseMessages(data);
|
|
112
|
+
for (const event of events) {
|
|
113
|
+
if (event.event === pondsocket_common_1.Events.CONNECTION && event.action === pondsocket_common_1.ServerActions.CONNECT) {
|
|
114
|
+
if (event.payload && typeof event.payload === 'object' && 'connectionId' in event.payload) {
|
|
115
|
+
this._connectionId = event.payload.connectionId;
|
|
147
116
|
}
|
|
148
|
-
this._broadcaster.publish(event);
|
|
149
117
|
}
|
|
118
|
+
this._broadcaster.publish(event);
|
|
150
119
|
}
|
|
151
120
|
}
|
|
152
121
|
catch (e) {
|
|
153
122
|
this._errorSubject.publish(e instanceof Error ? e : new Error('Failed to parse SSE message'));
|
|
154
123
|
}
|
|
155
|
-
}, _SSEClient_clearTimeout = function _SSEClient_clearTimeout() {
|
|
156
|
-
if (this._connectionTimeoutId) {
|
|
157
|
-
clearTimeout(this._connectionTimeoutId);
|
|
158
|
-
this._connectionTimeoutId = undefined;
|
|
159
|
-
}
|
|
160
|
-
}, _SSEClient_createPublisher = function _SSEClient_createPublisher() {
|
|
161
|
-
return (message) => {
|
|
162
|
-
if (this._connectionState.value === types_1.ConnectionState.CONNECTED && this._connectionId) {
|
|
163
|
-
fetch(this._postAddress.toString(), {
|
|
164
|
-
method: 'POST',
|
|
165
|
-
headers: {
|
|
166
|
-
'Content-Type': 'application/json',
|
|
167
|
-
'X-Connection-ID': this._connectionId,
|
|
168
|
-
},
|
|
169
|
-
body: JSON.stringify(message),
|
|
170
|
-
credentials: this._options.withCredentials ? 'include' : 'same-origin',
|
|
171
|
-
}).catch((err) => {
|
|
172
|
-
this._errorSubject.publish(err instanceof Error ? err : new Error('Failed to send message'));
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
}, _SSEClient_handleAcknowledge = function _SSEClient_handleAcknowledge(message) {
|
|
177
|
-
var _a;
|
|
178
|
-
const channel = (_a = __classPrivateFieldGet(this, _SSEClient_channels, "f").get(message.channelName)) !== null && _a !== void 0 ? _a : new channel_1.Channel(__classPrivateFieldGet(this, _SSEClient_instances, "m", _SSEClient_createPublisher).call(this), this._connectionState, message.channelName, {});
|
|
179
|
-
__classPrivateFieldGet(this, _SSEClient_channels, "f").set(message.channelName, channel);
|
|
180
|
-
channel.acknowledge(this._broadcaster);
|
|
181
|
-
}, _SSEClient_handleUnauthorized = function _SSEClient_handleUnauthorized(message) {
|
|
182
|
-
const channel = __classPrivateFieldGet(this, _SSEClient_channels, "f").get(message.channelName);
|
|
183
|
-
if (channel) {
|
|
184
|
-
const payload = message.payload;
|
|
185
|
-
channel.decline(payload);
|
|
186
|
-
}
|
|
187
|
-
}, _SSEClient_init = function _SSEClient_init() {
|
|
188
|
-
this._broadcaster.subscribe((message) => {
|
|
189
|
-
if (message.event === pondsocket_common_1.Events.ACKNOWLEDGE) {
|
|
190
|
-
__classPrivateFieldGet(this, _SSEClient_instances, "m", _SSEClient_handleAcknowledge).call(this, message);
|
|
191
|
-
}
|
|
192
|
-
else if (message.event === pondsocket_common_1.Events.UNAUTHORIZED) {
|
|
193
|
-
__classPrivateFieldGet(this, _SSEClient_instances, "m", _SSEClient_handleUnauthorized).call(this, message);
|
|
194
|
-
}
|
|
195
|
-
else if (message.event === pondsocket_common_1.Events.CONNECTION && message.action === pondsocket_common_1.ServerActions.CONNECT) {
|
|
196
|
-
this._connectionState.publish(types_1.ConnectionState.CONNECTED);
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
124
|
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var _BaseClient_instances, _BaseClient_channels, _BaseClient_handleAcknowledge, _BaseClient_handleUnauthorized, _BaseClient_init;
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.BaseClient = void 0;
|
|
16
|
+
const pondsocket_common_1 = require("@eleven-am/pondsocket-common");
|
|
17
|
+
const channel_1 = require("./channel");
|
|
18
|
+
const types_1 = require("../types");
|
|
19
|
+
const DEFAULT_CONNECTION_TIMEOUT = 10000;
|
|
20
|
+
const DEFAULT_MAX_RECONNECT_DELAY = 30000;
|
|
21
|
+
class BaseClient {
|
|
22
|
+
constructor(endpoint, params = {}, options = {}) {
|
|
23
|
+
var _a, _b;
|
|
24
|
+
_BaseClient_instances.add(this);
|
|
25
|
+
_BaseClient_channels.set(this, void 0);
|
|
26
|
+
this._address = this._resolveAddress(endpoint, params);
|
|
27
|
+
this._disconnecting = false;
|
|
28
|
+
this._reconnectAttempts = 0;
|
|
29
|
+
this._options = {
|
|
30
|
+
connectionTimeout: (_a = options.connectionTimeout) !== null && _a !== void 0 ? _a : DEFAULT_CONNECTION_TIMEOUT,
|
|
31
|
+
maxReconnectDelay: (_b = options.maxReconnectDelay) !== null && _b !== void 0 ? _b : DEFAULT_MAX_RECONNECT_DELAY,
|
|
32
|
+
pingInterval: options.pingInterval,
|
|
33
|
+
};
|
|
34
|
+
__classPrivateFieldSet(this, _BaseClient_channels, new Map(), "f");
|
|
35
|
+
this._broadcaster = new pondsocket_common_1.Subject();
|
|
36
|
+
this._connectionState = new pondsocket_common_1.BehaviorSubject(types_1.ConnectionState.DISCONNECTED);
|
|
37
|
+
this._errorSubject = new pondsocket_common_1.Subject();
|
|
38
|
+
__classPrivateFieldGet(this, _BaseClient_instances, "m", _BaseClient_init).call(this);
|
|
39
|
+
}
|
|
40
|
+
_clearConnectionTimeout() {
|
|
41
|
+
if (this._connectionTimeoutId) {
|
|
42
|
+
clearTimeout(this._connectionTimeoutId);
|
|
43
|
+
this._connectionTimeoutId = undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
_scheduleReconnect() {
|
|
47
|
+
if (this._disconnecting) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const delay = Math.min(1000 * Math.pow(2, this._reconnectAttempts), this._options.maxReconnectDelay);
|
|
51
|
+
this._reconnectAttempts++;
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
this.connect();
|
|
54
|
+
}, delay);
|
|
55
|
+
}
|
|
56
|
+
getState() {
|
|
57
|
+
return this._connectionState.value;
|
|
58
|
+
}
|
|
59
|
+
createChannel(name, params) {
|
|
60
|
+
const channel = __classPrivateFieldGet(this, _BaseClient_channels, "f").get(name);
|
|
61
|
+
if (channel && channel.channelState !== pondsocket_common_1.ChannelState.CLOSED) {
|
|
62
|
+
return channel;
|
|
63
|
+
}
|
|
64
|
+
const publisher = this._createPublisher();
|
|
65
|
+
const newChannel = new channel_1.Channel(publisher, this._connectionState, name, params || {});
|
|
66
|
+
__classPrivateFieldGet(this, _BaseClient_channels, "f").set(name, newChannel);
|
|
67
|
+
return newChannel;
|
|
68
|
+
}
|
|
69
|
+
onConnectionChange(callback) {
|
|
70
|
+
return this._connectionState.subscribe(callback);
|
|
71
|
+
}
|
|
72
|
+
onError(callback) {
|
|
73
|
+
return this._errorSubject.subscribe(callback);
|
|
74
|
+
}
|
|
75
|
+
_parseMessages(data) {
|
|
76
|
+
const events = [];
|
|
77
|
+
const lines = data.trim().split('\n');
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
if (line.trim()) {
|
|
80
|
+
const parsed = JSON.parse(line);
|
|
81
|
+
const event = pondsocket_common_1.channelEventSchema.parse(parsed);
|
|
82
|
+
events.push(event);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return events;
|
|
86
|
+
}
|
|
87
|
+
_clearChannels() {
|
|
88
|
+
__classPrivateFieldGet(this, _BaseClient_channels, "f").clear();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.BaseClient = BaseClient;
|
|
92
|
+
_BaseClient_channels = new WeakMap(), _BaseClient_instances = new WeakSet(), _BaseClient_handleAcknowledge = function _BaseClient_handleAcknowledge(message) {
|
|
93
|
+
const channel = __classPrivateFieldGet(this, _BaseClient_channels, "f").get(message.channelName);
|
|
94
|
+
if (!channel) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
channel.acknowledge(this._broadcaster);
|
|
98
|
+
}, _BaseClient_handleUnauthorized = function _BaseClient_handleUnauthorized(message) {
|
|
99
|
+
const channel = __classPrivateFieldGet(this, _BaseClient_channels, "f").get(message.channelName);
|
|
100
|
+
if (channel) {
|
|
101
|
+
const payload = message.payload;
|
|
102
|
+
channel.decline(payload);
|
|
103
|
+
}
|
|
104
|
+
}, _BaseClient_init = function _BaseClient_init() {
|
|
105
|
+
this._broadcaster.subscribe((message) => {
|
|
106
|
+
if (message.event === pondsocket_common_1.Events.ACKNOWLEDGE) {
|
|
107
|
+
__classPrivateFieldGet(this, _BaseClient_instances, "m", _BaseClient_handleAcknowledge).call(this, message);
|
|
108
|
+
}
|
|
109
|
+
else if (message.event === pondsocket_common_1.Events.UNAUTHORIZED) {
|
|
110
|
+
__classPrivateFieldGet(this, _BaseClient_instances, "m", _BaseClient_handleUnauthorized).call(this, message);
|
|
111
|
+
}
|
|
112
|
+
else if (message.event === pondsocket_common_1.Events.CONNECTION && message.action === pondsocket_common_1.ServerActions.CONNECT) {
|
|
113
|
+
this._connectionState.publish(types_1.ConnectionState.CONNECTED);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
};
|
package/core/channel.js
CHANGED
|
@@ -10,13 +10,13 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
11
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
12
|
};
|
|
13
|
-
var _Channel_instances, _Channel_name, _Channel_queue, _Channel_presence, _Channel_presenceSub, _Channel_publisher, _Channel_joinParams, _Channel_receiver, _Channel_clientState, _Channel_joinState, _Channel_emptyQueue, _Channel_init, _Channel_onMessage, _Channel_publish, _Channel_subscribeToPresence;
|
|
13
|
+
var _Channel_instances, _Channel_name, _Channel_queue, _Channel_presence, _Channel_presenceSub, _Channel_publisher, _Channel_joinParams, _Channel_receiver, _Channel_clientState, _Channel_joinState, _Channel_maxQueueSize, _Channel_emptyQueue, _Channel_init, _Channel_onMessage, _Channel_publish, _Channel_subscribeToPresence;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.Channel = void 0;
|
|
16
16
|
const pondsocket_common_1 = require("@eleven-am/pondsocket-common");
|
|
17
17
|
const types_1 = require("../types");
|
|
18
18
|
class Channel {
|
|
19
|
-
constructor(publisher, clientState, name, params) {
|
|
19
|
+
constructor(publisher, clientState, name, params, maxQueueSize = 100) {
|
|
20
20
|
_Channel_instances.add(this);
|
|
21
21
|
_Channel_name.set(this, void 0);
|
|
22
22
|
_Channel_queue.set(this, void 0);
|
|
@@ -27,11 +27,13 @@ class Channel {
|
|
|
27
27
|
_Channel_receiver.set(this, void 0);
|
|
28
28
|
_Channel_clientState.set(this, void 0);
|
|
29
29
|
_Channel_joinState.set(this, void 0);
|
|
30
|
+
_Channel_maxQueueSize.set(this, void 0);
|
|
30
31
|
__classPrivateFieldSet(this, _Channel_name, name, "f");
|
|
31
32
|
__classPrivateFieldSet(this, _Channel_queue, [], "f");
|
|
32
33
|
__classPrivateFieldSet(this, _Channel_presence, [], "f");
|
|
33
34
|
__classPrivateFieldSet(this, _Channel_joinParams, params, "f");
|
|
34
35
|
__classPrivateFieldSet(this, _Channel_publisher, publisher, "f");
|
|
36
|
+
__classPrivateFieldSet(this, _Channel_maxQueueSize, maxQueueSize, "f");
|
|
35
37
|
__classPrivateFieldSet(this, _Channel_clientState, clientState, "f");
|
|
36
38
|
__classPrivateFieldSet(this, _Channel_receiver, new pondsocket_common_1.Subject(), "f");
|
|
37
39
|
__classPrivateFieldSet(this, _Channel_joinState, new pondsocket_common_1.BehaviorSubject(pondsocket_common_1.ChannelState.IDLE), "f");
|
|
@@ -56,6 +58,9 @@ class Channel {
|
|
|
56
58
|
* @param receiver - The receiver to subscribe to.
|
|
57
59
|
*/
|
|
58
60
|
acknowledge(receiver) {
|
|
61
|
+
if (__classPrivateFieldGet(this, _Channel_joinState, "f").value === pondsocket_common_1.ChannelState.JOINED) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
59
64
|
__classPrivateFieldGet(this, _Channel_joinState, "f").publish(pondsocket_common_1.ChannelState.JOINED);
|
|
60
65
|
__classPrivateFieldGet(this, _Channel_instances, "m", _Channel_init).call(this, receiver);
|
|
61
66
|
__classPrivateFieldGet(this, _Channel_instances, "m", _Channel_emptyQueue).call(this);
|
|
@@ -198,20 +203,22 @@ class Channel {
|
|
|
198
203
|
};
|
|
199
204
|
__classPrivateFieldGet(this, _Channel_instances, "m", _Channel_publish).call(this, message);
|
|
200
205
|
}
|
|
201
|
-
|
|
202
|
-
* @desc Sends a message to the server and waits for a response.
|
|
203
|
-
* @param sentEvent - The event to send.
|
|
204
|
-
* @param payload - The message to send.
|
|
205
|
-
*/
|
|
206
|
-
sendForResponse(sentEvent, payload) {
|
|
206
|
+
sendForResponse(sentEvent, payload, timeoutMs = 30000) {
|
|
207
207
|
const requestId = (0, pondsocket_common_1.uuid)();
|
|
208
|
-
return new Promise((resolve) => {
|
|
209
|
-
const
|
|
208
|
+
return new Promise((resolve, reject) => {
|
|
209
|
+
const cleanup = { timer: undefined,
|
|
210
|
+
unsub: () => { } };
|
|
211
|
+
cleanup.unsub = __classPrivateFieldGet(this, _Channel_instances, "m", _Channel_onMessage).call(this, (_, message, responseId) => {
|
|
210
212
|
if (requestId === responseId) {
|
|
213
|
+
clearTimeout(cleanup.timer);
|
|
211
214
|
resolve(message);
|
|
212
|
-
unsub();
|
|
215
|
+
cleanup.unsub();
|
|
213
216
|
}
|
|
214
217
|
});
|
|
218
|
+
cleanup.timer = setTimeout(() => {
|
|
219
|
+
cleanup.unsub();
|
|
220
|
+
reject(new Error(`sendForResponse timed out after ${timeoutMs}ms for event "${String(sentEvent)}"`));
|
|
221
|
+
}, timeoutMs);
|
|
215
222
|
const message = {
|
|
216
223
|
action: pondsocket_common_1.ClientActions.BROADCAST,
|
|
217
224
|
channelName: __classPrivateFieldGet(this, _Channel_name, "f"),
|
|
@@ -224,7 +231,7 @@ class Channel {
|
|
|
224
231
|
}
|
|
225
232
|
}
|
|
226
233
|
exports.Channel = Channel;
|
|
227
|
-
_Channel_name = new WeakMap(), _Channel_queue = new WeakMap(), _Channel_presence = new WeakMap(), _Channel_presenceSub = new WeakMap(), _Channel_publisher = new WeakMap(), _Channel_joinParams = new WeakMap(), _Channel_receiver = new WeakMap(), _Channel_clientState = new WeakMap(), _Channel_joinState = new WeakMap(), _Channel_instances = new WeakSet(), _Channel_emptyQueue = function _Channel_emptyQueue() {
|
|
234
|
+
_Channel_name = new WeakMap(), _Channel_queue = new WeakMap(), _Channel_presence = new WeakMap(), _Channel_presenceSub = new WeakMap(), _Channel_publisher = new WeakMap(), _Channel_joinParams = new WeakMap(), _Channel_receiver = new WeakMap(), _Channel_clientState = new WeakMap(), _Channel_joinState = new WeakMap(), _Channel_maxQueueSize = new WeakMap(), _Channel_instances = new WeakSet(), _Channel_emptyQueue = function _Channel_emptyQueue() {
|
|
228
235
|
__classPrivateFieldGet(this, _Channel_queue, "f")
|
|
229
236
|
.forEach((message) => __classPrivateFieldGet(this, _Channel_publisher, "f").call(this, message));
|
|
230
237
|
__classPrivateFieldSet(this, _Channel_queue, [], "f");
|
|
@@ -268,6 +275,9 @@ _Channel_name = new WeakMap(), _Channel_queue = new WeakMap(), _Channel_presence
|
|
|
268
275
|
if (__classPrivateFieldGet(this, _Channel_joinState, "f").value === pondsocket_common_1.ChannelState.JOINED) {
|
|
269
276
|
return __classPrivateFieldGet(this, _Channel_publisher, "f").call(this, data);
|
|
270
277
|
}
|
|
278
|
+
if (__classPrivateFieldGet(this, _Channel_queue, "f").length >= __classPrivateFieldGet(this, _Channel_maxQueueSize, "f")) {
|
|
279
|
+
__classPrivateFieldGet(this, _Channel_queue, "f").shift();
|
|
280
|
+
}
|
|
271
281
|
__classPrivateFieldGet(this, _Channel_queue, "f").push(data);
|
|
272
282
|
}, _Channel_subscribeToPresence = function _Channel_subscribeToPresence(callback) {
|
|
273
283
|
return __classPrivateFieldGet(this, _Channel_receiver, "f").subscribe((data) => {
|
package/core/channel.test.js
CHANGED
|
@@ -685,6 +685,32 @@ describe('Channel', () => {
|
|
|
685
685
|
});
|
|
686
686
|
expect(specificMessageListener).not.toHaveBeenCalled();
|
|
687
687
|
});
|
|
688
|
+
it('should handle decline by setting state to DECLINED and clearing queue', () => {
|
|
689
|
+
const { channel } = createChannel();
|
|
690
|
+
channel.sendMessage('queued', { data: 'test' });
|
|
691
|
+
channel.decline({ message: 'Not authorized', statusCode: 403 });
|
|
692
|
+
expect(channel.channelState).toBe(pondsocket_common_1.ChannelState.DECLINED);
|
|
693
|
+
});
|
|
694
|
+
it('should not re-join when already in JOINED state', () => {
|
|
695
|
+
const { channel, publisher, receiver } = createChannel();
|
|
696
|
+
channel.join();
|
|
697
|
+
channel.acknowledge(receiver);
|
|
698
|
+
expect(channel.channelState).toBe(pondsocket_common_1.ChannelState.JOINED);
|
|
699
|
+
publisher.mockClear();
|
|
700
|
+
channel.join();
|
|
701
|
+
expect(publisher).not.toHaveBeenCalled();
|
|
702
|
+
});
|
|
703
|
+
it('sendForResponse should reject with timeout error', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
704
|
+
const publisher = jest.fn();
|
|
705
|
+
const state = new pondsocket_common_1.BehaviorSubject(types_1.ConnectionState.CONNECTED);
|
|
706
|
+
const receiver = new pondsocket_common_1.Subject();
|
|
707
|
+
const channel = new channel_1.Channel(publisher, state, 'test', {});
|
|
708
|
+
channel.join();
|
|
709
|
+
channel.acknowledge(receiver);
|
|
710
|
+
// @ts-expect-error - testing timeout behavior
|
|
711
|
+
const promise = channel.sendForResponse('timeout-event', { data: 'test' }, 100);
|
|
712
|
+
yield expect(promise).rejects.toThrow(/sendForResponse timed out after 100ms/);
|
|
713
|
+
}));
|
|
688
714
|
it('should be able to wait for a message', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
689
715
|
const state = new pondsocket_common_1.BehaviorSubject(types_1.ConnectionState.CONNECTED);
|
|
690
716
|
const receiver = new pondsocket_common_1.Subject();
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebSocketClient = void 0;
|
|
4
|
+
const baseClient_1 = require("./baseClient");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
class WebSocketClient extends baseClient_1.BaseClient {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
this._lastMessageTime = 0;
|
|
10
|
+
}
|
|
11
|
+
connect() {
|
|
12
|
+
this._disconnecting = false;
|
|
13
|
+
this._connectionState.publish(types_1.ConnectionState.CONNECTING);
|
|
14
|
+
const socket = new WebSocket(this._address.toString());
|
|
15
|
+
this._connectionTimeoutId = setTimeout(() => {
|
|
16
|
+
if (socket.readyState === WebSocket.CONNECTING) {
|
|
17
|
+
const error = new Error('Connection timeout');
|
|
18
|
+
this._errorSubject.publish(error);
|
|
19
|
+
socket.close();
|
|
20
|
+
}
|
|
21
|
+
}, this._options.connectionTimeout);
|
|
22
|
+
socket.onopen = () => {
|
|
23
|
+
this._clearConnectionTimeout();
|
|
24
|
+
this._clearPingInterval();
|
|
25
|
+
this._clearPongTimeout();
|
|
26
|
+
this._reconnectAttempts = 0;
|
|
27
|
+
this._lastMessageTime = Date.now();
|
|
28
|
+
if (this._options.pingInterval) {
|
|
29
|
+
this._pingIntervalId = setInterval(() => {
|
|
30
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
31
|
+
socket.send(JSON.stringify({ action: 'ping' }));
|
|
32
|
+
}
|
|
33
|
+
}, this._options.pingInterval);
|
|
34
|
+
this._schedulePongTimeout(socket);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
socket.onmessage = (message) => {
|
|
38
|
+
this._lastMessageTime = Date.now();
|
|
39
|
+
const events = this._parseMessages(message.data);
|
|
40
|
+
for (const event of events) {
|
|
41
|
+
this._broadcaster.publish(event);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
socket.onerror = () => {
|
|
45
|
+
const error = new Error('WebSocket error');
|
|
46
|
+
this._errorSubject.publish(error);
|
|
47
|
+
socket.close();
|
|
48
|
+
};
|
|
49
|
+
socket.onclose = () => {
|
|
50
|
+
this._clearConnectionTimeout();
|
|
51
|
+
this._clearPingInterval();
|
|
52
|
+
this._clearPongTimeout();
|
|
53
|
+
this._connectionState.publish(types_1.ConnectionState.DISCONNECTED);
|
|
54
|
+
this._scheduleReconnect();
|
|
55
|
+
};
|
|
56
|
+
this._socket = socket;
|
|
57
|
+
}
|
|
58
|
+
disconnect() {
|
|
59
|
+
var _a;
|
|
60
|
+
this._clearConnectionTimeout();
|
|
61
|
+
this._clearPingInterval();
|
|
62
|
+
this._clearPongTimeout();
|
|
63
|
+
this._disconnecting = true;
|
|
64
|
+
this._connectionState.publish(types_1.ConnectionState.DISCONNECTED);
|
|
65
|
+
(_a = this._socket) === null || _a === void 0 ? void 0 : _a.close();
|
|
66
|
+
this._clearChannels();
|
|
67
|
+
}
|
|
68
|
+
_createPublisher() {
|
|
69
|
+
return (message) => {
|
|
70
|
+
var _a;
|
|
71
|
+
if (this._connectionState.value === types_1.ConnectionState.CONNECTED) {
|
|
72
|
+
(_a = this._socket) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify(message));
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
_clearPingInterval() {
|
|
77
|
+
if (this._pingIntervalId) {
|
|
78
|
+
clearInterval(this._pingIntervalId);
|
|
79
|
+
this._pingIntervalId = undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
_clearPongTimeout() {
|
|
83
|
+
if (this._pongTimeoutId) {
|
|
84
|
+
clearTimeout(this._pongTimeoutId);
|
|
85
|
+
this._pongTimeoutId = undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
_schedulePongTimeout(socket) {
|
|
89
|
+
this._clearPongTimeout();
|
|
90
|
+
const timeout = this._options.pingInterval * 2;
|
|
91
|
+
this._pongTimeoutId = setTimeout(() => {
|
|
92
|
+
if (Date.now() - this._lastMessageTime >= timeout) {
|
|
93
|
+
this._errorSubject.publish(new Error('Connection lost: no response from server'));
|
|
94
|
+
socket.close();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this._schedulePongTimeout(socket);
|
|
98
|
+
}, timeout);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.WebSocketClient = WebSocketClient;
|
package/dist.d.ts
CHANGED
|
@@ -102,7 +102,7 @@ declare class Channel<EventMap extends PondEventMap = PondEventMap, Presence ext
|
|
|
102
102
|
* @param sentEvent - The event to send.
|
|
103
103
|
* @param payload - The message to send.
|
|
104
104
|
*/
|
|
105
|
-
sendForResponse<Event extends EventWithResponse<EventMap>> (sentEvent: Event, payload: PayloadForResponse<EventMap, Event
|
|
105
|
+
sendForResponse<Event extends EventWithResponse<EventMap>> (sentEvent: Event, payload: PayloadForResponse<EventMap, Event>, timeoutMs?: number): Promise<ResponseForEvent<EventMap, Event>>;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
declare class PondClient {
|
package/node/node.js
CHANGED
|
@@ -1,85 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
3
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
4
|
-
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
5
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
6
|
-
};
|
|
7
|
-
var _PondClient_instances, _PondClient_clearTimeouts;
|
|
8
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
3
|
exports.PondClient = void 0;
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* @desc Connects to the server and returns the socket.
|
|
22
|
-
*/
|
|
23
|
-
connect() {
|
|
24
|
-
this._disconnecting = false;
|
|
25
|
-
this._connectionState.publish(types_1.ConnectionState.CONNECTING);
|
|
26
|
-
const socket = new WebSocket(this._address.toString());
|
|
27
|
-
this._connectionTimeoutId = setTimeout(() => {
|
|
28
|
-
if (socket.readyState === WebSocket.CONNECTING) {
|
|
29
|
-
const error = new Error('Connection timeout');
|
|
30
|
-
this._errorSubject.publish(error);
|
|
31
|
-
socket.close();
|
|
32
|
-
}
|
|
33
|
-
}, this._options.connectionTimeout);
|
|
34
|
-
socket.onopen = () => {
|
|
35
|
-
__classPrivateFieldGet(this, _PondClient_instances, "m", _PondClient_clearTimeouts).call(this);
|
|
36
|
-
this._reconnectAttempts = 0;
|
|
37
|
-
if (this._options.pingInterval) {
|
|
38
|
-
this._pingIntervalId = setInterval(() => {
|
|
39
|
-
if (socket.readyState === WebSocket.OPEN) {
|
|
40
|
-
socket.send(JSON.stringify({ action: 'ping' }));
|
|
41
|
-
}
|
|
42
|
-
}, this._options.pingInterval);
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
socket.onmessage = (message) => {
|
|
46
|
-
const lines = message.data.trim().split('\n');
|
|
47
|
-
for (const line of lines) {
|
|
48
|
-
if (line.trim()) {
|
|
49
|
-
const data = JSON.parse(line);
|
|
50
|
-
const event = pondsocket_common_1.channelEventSchema.parse(data);
|
|
51
|
-
this._broadcaster.publish(event);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
socket.onerror = () => {
|
|
56
|
-
const error = new Error('WebSocket error');
|
|
57
|
-
this._errorSubject.publish(error);
|
|
58
|
-
socket.close();
|
|
59
|
-
};
|
|
60
|
-
socket.onclose = () => {
|
|
61
|
-
__classPrivateFieldGet(this, _PondClient_instances, "m", _PondClient_clearTimeouts).call(this);
|
|
62
|
-
this._connectionState.publish(types_1.ConnectionState.DISCONNECTED);
|
|
63
|
-
if (this._disconnecting) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const delay = Math.min(1000 * Math.pow(2, this._reconnectAttempts), this._options.maxReconnectDelay);
|
|
67
|
-
this._reconnectAttempts++;
|
|
68
|
-
setTimeout(() => {
|
|
69
|
-
this.connect();
|
|
70
|
-
}, delay);
|
|
71
|
-
};
|
|
72
|
-
this._socket = socket;
|
|
4
|
+
const webSocketClient_1 = require("../core/webSocketClient");
|
|
5
|
+
class PondClient extends webSocketClient_1.WebSocketClient {
|
|
6
|
+
_resolveAddress(endpoint, params) {
|
|
7
|
+
const address = new URL(endpoint);
|
|
8
|
+
const query = new URLSearchParams(params);
|
|
9
|
+
address.search = query.toString();
|
|
10
|
+
const protocol = address.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
11
|
+
if (address.protocol !== 'wss:' && address.protocol !== 'ws:') {
|
|
12
|
+
address.protocol = protocol;
|
|
13
|
+
}
|
|
14
|
+
return address;
|
|
73
15
|
}
|
|
74
16
|
}
|
|
75
17
|
exports.PondClient = PondClient;
|
|
76
|
-
_PondClient_instances = new WeakSet(), _PondClient_clearTimeouts = function _PondClient_clearTimeouts() {
|
|
77
|
-
if (this._connectionTimeoutId) {
|
|
78
|
-
clearTimeout(this._connectionTimeoutId);
|
|
79
|
-
this._connectionTimeoutId = undefined;
|
|
80
|
-
}
|
|
81
|
-
if (this._pingIntervalId) {
|
|
82
|
-
clearInterval(this._pingIntervalId);
|
|
83
|
-
this._pingIntervalId = undefined;
|
|
84
|
-
}
|
|
85
|
-
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const node_1 = require("./node");
|
|
4
|
+
describe('PondClient (Node)', () => {
|
|
5
|
+
it('should resolve http endpoint to ws protocol', () => {
|
|
6
|
+
const client = new node_1.PondClient('http://localhost:3000/ws', { token: 'abc' });
|
|
7
|
+
const address = client._address;
|
|
8
|
+
expect(address.protocol).toBe('ws:');
|
|
9
|
+
expect(address.hostname).toBe('localhost');
|
|
10
|
+
expect(address.port).toBe('3000');
|
|
11
|
+
expect(address.pathname).toBe('/ws');
|
|
12
|
+
expect(address.searchParams.get('token')).toBe('abc');
|
|
13
|
+
});
|
|
14
|
+
it('should resolve https endpoint to wss protocol', () => {
|
|
15
|
+
const client = new node_1.PondClient('https://example.com/ws', {});
|
|
16
|
+
const address = client._address;
|
|
17
|
+
expect(address.protocol).toBe('wss:');
|
|
18
|
+
expect(address.hostname).toBe('example.com');
|
|
19
|
+
});
|
|
20
|
+
it('should keep ws protocol if already specified', () => {
|
|
21
|
+
const client = new node_1.PondClient('ws://localhost:3000/ws', {});
|
|
22
|
+
const address = client._address;
|
|
23
|
+
expect(address.protocol).toBe('ws:');
|
|
24
|
+
});
|
|
25
|
+
it('should keep wss protocol if already specified', () => {
|
|
26
|
+
const client = new node_1.PondClient('wss://example.com/ws', {});
|
|
27
|
+
const address = client._address;
|
|
28
|
+
expect(address.protocol).toBe('wss:');
|
|
29
|
+
});
|
|
30
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eleven-am/pondsocket-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.35",
|
|
4
4
|
"description": "PondSocket is a fast simple socket server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"socket",
|
|
@@ -30,18 +30,23 @@
|
|
|
30
30
|
"pipeline": "npm run test && npm run build && npm run push"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@eleven-am/pondsocket-common": "^0.0.
|
|
34
|
-
"websocket": "^1.0.35"
|
|
33
|
+
"@eleven-am/pondsocket-common": "^0.0.37"
|
|
35
34
|
},
|
|
36
35
|
"devDependencies": {
|
|
36
|
+
"@eslint/compat": "^2.0.2",
|
|
37
|
+
"@eslint/eslintrc": "^3.3.4",
|
|
38
|
+
"@eslint/js": "^9.39.3",
|
|
39
|
+
"@stylistic/eslint-plugin-ts": "^4.4.1",
|
|
37
40
|
"@types/jest": "^30.0.0",
|
|
38
|
-
"@
|
|
39
|
-
"@typescript-eslint/
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
42
|
+
"@typescript-eslint/parser": "^8.56.1",
|
|
43
|
+
"eslint": "^9.39.3",
|
|
40
44
|
"eslint-plugin-file-progress": "^3.0.2",
|
|
41
45
|
"eslint-plugin-import": "^2.32.0",
|
|
46
|
+
"globals": "^17.4.0",
|
|
42
47
|
"jest": "^30.2.0",
|
|
43
|
-
"prettier": "^3.
|
|
44
|
-
"supertest": "^7.
|
|
48
|
+
"prettier": "^3.8.1",
|
|
49
|
+
"supertest": "^7.2.2",
|
|
45
50
|
"ts-jest": "^29.4.6",
|
|
46
51
|
"ts-loader": "^9.5.4",
|
|
47
52
|
"ts-node": "^10.9.2",
|
|
@@ -56,12 +61,25 @@
|
|
|
56
61
|
"rootDir": "src",
|
|
57
62
|
"testRegex": ".*\\.test\\.ts$",
|
|
58
63
|
"transform": {
|
|
59
|
-
"^.+\\.(t|j)s$":
|
|
64
|
+
"^.+\\.(t|j)s$": [
|
|
65
|
+
"ts-jest",
|
|
66
|
+
{
|
|
67
|
+
"diagnostics": false
|
|
68
|
+
}
|
|
69
|
+
]
|
|
60
70
|
},
|
|
61
71
|
"collectCoverageFrom": [
|
|
62
|
-
"**/*.(t|j)s"
|
|
72
|
+
"**/*.(t|j)s",
|
|
73
|
+
"!**/*.test.ts",
|
|
74
|
+
"!**/*.d.ts",
|
|
75
|
+
"!**/index.ts",
|
|
76
|
+
"!**/index.d.ts",
|
|
77
|
+
"!**/dist.d.ts"
|
|
63
78
|
],
|
|
64
79
|
"coverageDirectory": "../coverage",
|
|
65
|
-
"testEnvironment": "node"
|
|
80
|
+
"testEnvironment": "node",
|
|
81
|
+
"moduleNameMapper": {
|
|
82
|
+
"^@eleven-am/pondsocket-common$": "<rootDir>/../../comnon/dist"
|
|
83
|
+
}
|
|
66
84
|
}
|
|
67
85
|
}
|