@rc-ex/ws 0.16.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/exceptions/ClosedException.d.ts +4 -0
- package/lib/exceptions/ClosedException.js +9 -0
- package/lib/exceptions/ClosedException.js.map +1 -0
- package/lib/exceptions/ConnectionException.d.ts +7 -0
- package/lib/exceptions/ConnectionException.js +16 -0
- package/lib/exceptions/ConnectionException.js.map +1 -0
- package/lib/exceptions/TimeoutException.d.ts +4 -0
- package/lib/exceptions/TimeoutException.js +9 -0
- package/lib/exceptions/TimeoutException.js.map +1 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +25 -22
- package/lib/index.js.map +1 -1
- package/lib/rest.d.ts +3 -3
- package/lib/rest.js +18 -20
- package/lib/rest.js.map +1 -1
- package/lib/subscription.d.ts +6 -4
- package/lib/subscription.js +15 -9
- package/lib/subscription.js.map +1 -1
- package/lib/types.d.ts +14 -0
- package/lib/types.js.map +1 -1
- package/lib/utils.d.ts +1 -1
- package/lib/utils.js +14 -12
- package/lib/utils.js.map +1 -1
- package/package.json +11 -5
- package/src/exceptions/ClosedException.ts +7 -0
- package/src/exceptions/ConnectionException.ts +17 -0
- package/src/exceptions/TimeoutException.ts +7 -0
- package/{index.ts → src/index.ts} +50 -36
- package/{rest.ts → src/rest.ts} +26 -26
- package/{subscription.ts → src/subscription.ts} +41 -32
- package/{types.ts → src/types.ts} +27 -5
- package/{utils.ts → src/utils.ts} +15 -14
- package/tsconfig.json +3 -2
- package/exceptions.ts +0 -25
- package/lib/exceptions.d.ts +0 -12
- package/lib/exceptions.js +0 -29
- package/lib/exceptions.js.map +0 -1
- package/lib/package.json +0 -31
|
@@ -1,28 +1,30 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
1
2
|
import RingCentral from '@rc-ex/core';
|
|
2
3
|
import {
|
|
4
|
+
RestMethod,
|
|
3
5
|
RestRequestConfig,
|
|
4
6
|
RestResponse,
|
|
5
|
-
|
|
6
|
-
} from '@rc-ex/core/lib/Rest';
|
|
7
|
+
} from '@rc-ex/core/lib/types';
|
|
7
8
|
import SdkExtension from '@rc-ex/core/lib/SdkExtension';
|
|
8
|
-
import WS, {OPEN, CONNECTING, MessageEvent} from 'isomorphic-ws';
|
|
9
|
+
import WS, { OPEN, CONNECTING, MessageEvent } from 'isomorphic-ws';
|
|
9
10
|
import hyperid from 'hyperid';
|
|
10
|
-
import {EventEmitter} from 'events';
|
|
11
|
+
import { EventEmitter } from 'events';
|
|
11
12
|
import waitFor from 'wait-for-async';
|
|
12
13
|
import RestException from '@rc-ex/core/lib/RestException';
|
|
13
|
-
import
|
|
14
|
+
import SubscriptionInfo from '@rc-ex/core/lib/definitions/SubscriptionInfo';
|
|
14
15
|
import debounce from 'lodash/debounce';
|
|
15
16
|
|
|
16
|
-
import {request} from './rest';
|
|
17
|
+
import { request } from './rest';
|
|
17
18
|
import {
|
|
18
19
|
WsToken,
|
|
19
20
|
ConnectionDetails,
|
|
20
21
|
WebSocketOptions,
|
|
21
22
|
WsgEvent,
|
|
22
23
|
Wsc,
|
|
24
|
+
WebSocketExtensionInterface,
|
|
23
25
|
} from './types';
|
|
24
26
|
import Subscription from './subscription';
|
|
25
|
-
import
|
|
27
|
+
import ConnectionException from './exceptions/ConnectionException';
|
|
26
28
|
import Utils from './utils';
|
|
27
29
|
|
|
28
30
|
const uuid = hyperid();
|
|
@@ -39,19 +41,30 @@ class WebSocketExtension extends SdkExtension {
|
|
|
39
41
|
eventEmitter = new EventEmitter();
|
|
40
42
|
|
|
41
43
|
options: WebSocketOptions;
|
|
44
|
+
|
|
42
45
|
rc!: RingCentral;
|
|
46
|
+
|
|
43
47
|
wsToken?: WsToken;
|
|
48
|
+
|
|
44
49
|
wsTokenExpiresAt = 0;
|
|
50
|
+
|
|
45
51
|
ws!: WS;
|
|
52
|
+
|
|
46
53
|
connectionDetails!: ConnectionDetails;
|
|
54
|
+
|
|
47
55
|
wsc?: Wsc;
|
|
56
|
+
|
|
48
57
|
subscriptions: Subscription[] = [];
|
|
58
|
+
|
|
49
59
|
recover: Function;
|
|
60
|
+
|
|
50
61
|
connect: Function;
|
|
51
62
|
|
|
52
63
|
// for auto recover
|
|
53
64
|
intervalHandle?: NodeJS.Timeout;
|
|
65
|
+
|
|
54
66
|
recoverTimestamp?: number;
|
|
67
|
+
|
|
55
68
|
pingServerHandle?: NodeJS.Timeout;
|
|
56
69
|
|
|
57
70
|
request = request; // request method was moved to another file to keep this file short
|
|
@@ -64,7 +77,7 @@ class WebSocketExtension extends SdkExtension {
|
|
|
64
77
|
this.options.autoRecover ??= {
|
|
65
78
|
enabled: true,
|
|
66
79
|
};
|
|
67
|
-
this.options.autoRecover.checkInterval ??= retriesAttempted => {
|
|
80
|
+
this.options.autoRecover.checkInterval ??= (retriesAttempted) => {
|
|
68
81
|
const interval = 2000 + 2000 * retriesAttempted;
|
|
69
82
|
return Math.min(8000, interval);
|
|
70
83
|
};
|
|
@@ -81,9 +94,9 @@ class WebSocketExtension extends SdkExtension {
|
|
|
81
94
|
|
|
82
95
|
disable() {
|
|
83
96
|
super.disable();
|
|
84
|
-
|
|
97
|
+
(this.subscriptions ?? []).forEach((subscription) => {
|
|
85
98
|
subscription.enabled = false;
|
|
86
|
-
}
|
|
99
|
+
});
|
|
87
100
|
}
|
|
88
101
|
|
|
89
102
|
async install(rc: RingCentral) {
|
|
@@ -95,7 +108,7 @@ class WebSocketExtension extends SdkExtension {
|
|
|
95
108
|
endpoint: string,
|
|
96
109
|
content?: {},
|
|
97
110
|
queryParams?: {},
|
|
98
|
-
config?: RestRequestConfig
|
|
111
|
+
config?: RestRequestConfig,
|
|
99
112
|
): Promise<RestResponse<T>> => {
|
|
100
113
|
if (!this.enabled || !this.options.restOverWebSocket) {
|
|
101
114
|
return request<T>(method, endpoint, content, queryParams, config);
|
|
@@ -103,10 +116,10 @@ class WebSocketExtension extends SdkExtension {
|
|
|
103
116
|
if (
|
|
104
117
|
// the following cannot be done with WebSocket
|
|
105
118
|
(config?.headers?.['Content-Type'] as string | undefined)?.includes(
|
|
106
|
-
'multipart/form-data'
|
|
107
|
-
)
|
|
108
|
-
config?.responseType === 'arraybuffer'
|
|
109
|
-
endpoint.startsWith('/restapi/oauth/') // token, revoke, wstoken
|
|
119
|
+
'multipart/form-data',
|
|
120
|
+
)
|
|
121
|
+
|| config?.responseType === 'arraybuffer'
|
|
122
|
+
|| endpoint.startsWith('/restapi/oauth/') // token, revoke, wstoken
|
|
110
123
|
) {
|
|
111
124
|
return request<T>(method, endpoint, content, queryParams, config);
|
|
112
125
|
}
|
|
@@ -155,14 +168,14 @@ class WebSocketExtension extends SdkExtension {
|
|
|
155
168
|
retriesAttempted = 0;
|
|
156
169
|
if (this.options.debugMode) {
|
|
157
170
|
console.debug(
|
|
158
|
-
`Auto recover done, recoveryState: ${this.connectionDetails.recoveryState}
|
|
171
|
+
`Auto recover done, recoveryState: ${this.connectionDetails.recoveryState}`,
|
|
159
172
|
);
|
|
160
173
|
}
|
|
161
174
|
this.eventEmitter.emit(
|
|
162
175
|
this.connectionDetails.recoveryState === 'Successful'
|
|
163
176
|
? Events.autoRecoverSuccess
|
|
164
177
|
: Events.autoRecoverFailed,
|
|
165
|
-
this.ws
|
|
178
|
+
this.ws,
|
|
166
179
|
);
|
|
167
180
|
} catch (e) {
|
|
168
181
|
if (e instanceof RestException) {
|
|
@@ -176,13 +189,13 @@ class WebSocketExtension extends SdkExtension {
|
|
|
176
189
|
}
|
|
177
190
|
this.intervalHandle = setInterval(
|
|
178
191
|
check,
|
|
179
|
-
this.options.autoRecover!.checkInterval!(retriesAttempted)
|
|
192
|
+
this.options.autoRecover!.checkInterval!(retriesAttempted),
|
|
180
193
|
);
|
|
181
194
|
}
|
|
182
195
|
};
|
|
183
196
|
this.intervalHandle = setInterval(
|
|
184
197
|
check,
|
|
185
|
-
this.options.autoRecover!.checkInterval!(retriesAttempted)
|
|
198
|
+
this.options.autoRecover!.checkInterval!(retriesAttempted),
|
|
186
199
|
);
|
|
187
200
|
|
|
188
201
|
// browser only code start
|
|
@@ -212,9 +225,9 @@ class WebSocketExtension extends SdkExtension {
|
|
|
212
225
|
this.recoverTimestamp = Date.now();
|
|
213
226
|
}
|
|
214
227
|
if (
|
|
215
|
-
this.connectionDetails !== undefined
|
|
216
|
-
Date.now() - this.recoverTimestamp
|
|
217
|
-
|
|
228
|
+
this.connectionDetails !== undefined
|
|
229
|
+
&& Date.now() - this.recoverTimestamp
|
|
230
|
+
> this.connectionDetails.recoveryTimeout * 1000
|
|
218
231
|
) {
|
|
219
232
|
if (this.options.debugMode) {
|
|
220
233
|
console.debug('connect to WSG but do not recover');
|
|
@@ -246,7 +259,7 @@ class WebSocketExtension extends SdkExtension {
|
|
|
246
259
|
type: 'Heartbeat',
|
|
247
260
|
messageId: uuid(),
|
|
248
261
|
},
|
|
249
|
-
])
|
|
262
|
+
]),
|
|
250
263
|
);
|
|
251
264
|
}
|
|
252
265
|
} catch (e) {
|
|
@@ -258,8 +271,7 @@ class WebSocketExtension extends SdkExtension {
|
|
|
258
271
|
if (Date.now() > this.wsTokenExpiresAt) {
|
|
259
272
|
const r = await this.rc.post('/restapi/oauth/wstoken');
|
|
260
273
|
this.wsToken = r.data as WsToken;
|
|
261
|
-
this.wsTokenExpiresAt =
|
|
262
|
-
Date.now() + (this.wsToken.expires_in - 10) * 1000;
|
|
274
|
+
this.wsTokenExpiresAt = Date.now() + (this.wsToken.expires_in - 10) * 1000;
|
|
263
275
|
}
|
|
264
276
|
let wsUri = '';
|
|
265
277
|
if (this.wsToken) {
|
|
@@ -290,7 +302,7 @@ class WebSocketExtension extends SdkExtension {
|
|
|
290
302
|
}
|
|
291
303
|
this.pingServerHandle = setTimeout(
|
|
292
304
|
() => this.pingServer(),
|
|
293
|
-
this.options.autoRecover!.pingServerInterval
|
|
305
|
+
this.options.autoRecover!.pingServerInterval,
|
|
294
306
|
);
|
|
295
307
|
});
|
|
296
308
|
}
|
|
@@ -305,10 +317,10 @@ class WebSocketExtension extends SdkExtension {
|
|
|
305
317
|
const event = mEvent as WsgEvent;
|
|
306
318
|
const [meta, body] = Utils.splitWsgData(event.data);
|
|
307
319
|
if (
|
|
308
|
-
meta.wsc
|
|
309
|
-
(!this.wsc
|
|
310
|
-
(meta.type === 'ConnectionDetails' && body.recoveryState)
|
|
311
|
-
this.wsc.sequence < meta.wsc.sequence)
|
|
320
|
+
meta.wsc
|
|
321
|
+
&& (!this.wsc
|
|
322
|
+
|| (meta.type === 'ConnectionDetails' && body.recoveryState)
|
|
323
|
+
|| this.wsc.sequence < meta.wsc.sequence)
|
|
312
324
|
) {
|
|
313
325
|
this.wsc = meta.wsc;
|
|
314
326
|
this.eventEmitter.emit(Events.newWsc, this.wsc);
|
|
@@ -318,7 +330,7 @@ class WebSocketExtension extends SdkExtension {
|
|
|
318
330
|
// get initial ConnectionDetails data
|
|
319
331
|
const [meta, body, event] = await Utils.waitForWebSocketMessage(
|
|
320
332
|
this.ws,
|
|
321
|
-
meta => meta.type === 'ConnectionDetails' || meta.type === 'Error'
|
|
333
|
+
(meta) => meta.type === 'ConnectionDetails' || meta.type === 'Error',
|
|
322
334
|
);
|
|
323
335
|
if (meta.type === 'Error') {
|
|
324
336
|
throw new ConnectionException(event);
|
|
@@ -326,12 +338,14 @@ class WebSocketExtension extends SdkExtension {
|
|
|
326
338
|
this.connectionDetails = body;
|
|
327
339
|
|
|
328
340
|
// recover all subscriptions, if there are any
|
|
329
|
-
for (const subscription of this.subscriptions.filter(
|
|
341
|
+
for (const subscription of this.subscriptions.filter(
|
|
342
|
+
(sub) => sub.enabled,
|
|
343
|
+
)) {
|
|
330
344
|
// because we have a new ws object
|
|
331
345
|
subscription.setupWsEventListener();
|
|
332
346
|
if (
|
|
333
|
-
!recoverSession
|
|
334
|
-
this.connectionDetails.recoveryState === 'Failed'
|
|
347
|
+
!recoverSession
|
|
348
|
+
|| this.connectionDetails.recoveryState === 'Failed'
|
|
335
349
|
) {
|
|
336
350
|
// create new subscription if don't recover existing one
|
|
337
351
|
await subscription.subscribe();
|
|
@@ -361,9 +375,9 @@ class WebSocketExtension extends SdkExtension {
|
|
|
361
375
|
async subscribe(
|
|
362
376
|
eventFilters: string[],
|
|
363
377
|
callback: (event: {}) => void,
|
|
364
|
-
cache: SubscriptionInfo | undefined | null = undefined
|
|
378
|
+
cache: SubscriptionInfo | undefined | null = undefined,
|
|
365
379
|
) {
|
|
366
|
-
const subscription = new Subscription(this, eventFilters, callback);
|
|
380
|
+
const subscription = new Subscription(this as WebSocketExtensionInterface, eventFilters, callback);
|
|
367
381
|
if (cache === undefined || cache === null) {
|
|
368
382
|
await subscription.subscribe();
|
|
369
383
|
} else {
|
package/{rest.ts → src/rest.ts}
RENAMED
|
@@ -2,72 +2,72 @@ import {
|
|
|
2
2
|
RestMethod,
|
|
3
3
|
RestRequestConfig,
|
|
4
4
|
RestResponse,
|
|
5
|
-
} from '@rc-ex/core/lib/
|
|
5
|
+
} from '@rc-ex/core/lib/types';
|
|
6
6
|
import RestException from '@rc-ex/core/lib/RestException';
|
|
7
7
|
import hyperid from 'hyperid';
|
|
8
|
-
import {getReasonPhrase} from 'http-status-codes';
|
|
8
|
+
import { getReasonPhrase } from 'http-status-codes';
|
|
9
9
|
|
|
10
|
-
import WebSocketExtension from './index';
|
|
11
|
-
import {version} from './package.json';
|
|
12
10
|
import Utils from './utils';
|
|
11
|
+
import {
|
|
12
|
+
WebSocketExtensionInterface,
|
|
13
|
+
} from './types';
|
|
14
|
+
|
|
15
|
+
const version = '0.16';
|
|
13
16
|
|
|
14
17
|
const uuid = hyperid();
|
|
15
18
|
|
|
16
19
|
export async function request<T>(
|
|
17
|
-
this:
|
|
20
|
+
this: WebSocketExtensionInterface,
|
|
18
21
|
method: RestMethod,
|
|
19
22
|
endpoint: string,
|
|
20
23
|
content?: {},
|
|
21
24
|
queryParams?: {},
|
|
22
|
-
config?: RestRequestConfig
|
|
25
|
+
config?: RestRequestConfig,
|
|
23
26
|
): Promise<RestResponse<T>> {
|
|
24
|
-
const
|
|
25
|
-
method
|
|
27
|
+
const newConfig: RestRequestConfig = {
|
|
28
|
+
method,
|
|
26
29
|
baseURL: this.wsToken?.uri,
|
|
27
30
|
url: endpoint,
|
|
28
31
|
data: content,
|
|
29
32
|
params: queryParams,
|
|
30
33
|
...config,
|
|
31
34
|
};
|
|
32
|
-
|
|
33
|
-
...
|
|
34
|
-
'X-User-Agent': `${this.rc.rest!.appName}/${
|
|
35
|
-
this.rc.rest!.appVersion
|
|
36
|
-
} ringcentral-extensible/ws/${version}`,
|
|
35
|
+
newConfig.headers = {
|
|
36
|
+
...newConfig.headers,
|
|
37
|
+
'X-User-Agent': `${this.rc.rest!.appName}/${this.rc.rest!.appVersion} ringcentral-extensible/ws/${version}`,
|
|
37
38
|
};
|
|
38
39
|
const messageId = uuid();
|
|
39
40
|
const requestBody = [
|
|
40
41
|
{
|
|
41
42
|
type: 'ClientRequest',
|
|
42
43
|
messageId,
|
|
43
|
-
method:
|
|
44
|
-
path:
|
|
45
|
-
headers:
|
|
46
|
-
query:
|
|
44
|
+
method: newConfig.method,
|
|
45
|
+
path: newConfig.url,
|
|
46
|
+
headers: newConfig.headers,
|
|
47
|
+
query: newConfig.params,
|
|
47
48
|
},
|
|
48
49
|
];
|
|
49
|
-
if (
|
|
50
|
-
requestBody.push(
|
|
50
|
+
if (newConfig.data) {
|
|
51
|
+
requestBody.push(newConfig.data);
|
|
51
52
|
}
|
|
52
53
|
await this.ws.send(JSON.stringify(requestBody));
|
|
53
54
|
const [meta, body] = await Utils.waitForWebSocketMessage(
|
|
54
55
|
this.ws,
|
|
55
|
-
|
|
56
|
+
(_meta) => _meta.messageId === messageId,
|
|
56
57
|
);
|
|
57
58
|
const response: RestResponse = {
|
|
58
59
|
data: body as T,
|
|
59
60
|
status: meta.status,
|
|
60
61
|
statusText: getReasonPhrase(meta.status),
|
|
61
62
|
headers: meta.headers,
|
|
62
|
-
config:
|
|
63
|
+
config: newConfig,
|
|
63
64
|
};
|
|
64
65
|
if (
|
|
65
|
-
meta.type === 'ClientRequest'
|
|
66
|
-
meta.status >= 200
|
|
67
|
-
meta.status < 300
|
|
66
|
+
meta.type === 'ClientRequest'
|
|
67
|
+
&& meta.status >= 200
|
|
68
|
+
&& meta.status < 300
|
|
68
69
|
) {
|
|
69
70
|
return response;
|
|
70
|
-
} else {
|
|
71
|
-
throw new RestException(response);
|
|
72
71
|
}
|
|
72
|
+
throw new RestException(response);
|
|
73
73
|
}
|
|
@@ -1,36 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from '@rc-ex/core/lib/
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import WebSocketExtension from './index';
|
|
9
|
-
import {WsgEvent, WsgMeta} from './types';
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import CreateSubscriptionRequest from '@rc-ex/core/lib/definitions/CreateSubscriptionRequest';
|
|
3
|
+
import SubscriptionInfo from '@rc-ex/core/lib/definitions/SubscriptionInfo';
|
|
4
|
+
import { RestResponse } from '@rc-ex/core/lib/types';
|
|
5
|
+
import { MessageEvent } from 'ws';
|
|
6
|
+
|
|
7
|
+
import { WsgEvent, WsgMeta, WebSocketExtensionInterface } from './types';
|
|
10
8
|
import Utils from './utils';
|
|
11
9
|
|
|
12
10
|
class Subscription {
|
|
13
|
-
wse:
|
|
11
|
+
wse: WebSocketExtensionInterface;
|
|
12
|
+
|
|
14
13
|
eventFilters: string[];
|
|
14
|
+
|
|
15
15
|
eventListener: (event: MessageEvent) => void;
|
|
16
|
+
|
|
16
17
|
timeout?: NodeJS.Timeout;
|
|
18
|
+
|
|
17
19
|
enabled = true;
|
|
18
20
|
|
|
19
21
|
constructor(
|
|
20
|
-
wse:
|
|
22
|
+
wse: WebSocketExtensionInterface,
|
|
21
23
|
eventFilters: string[],
|
|
22
|
-
callback: (event: {}) => void
|
|
24
|
+
callback: (event: {}) => void,
|
|
23
25
|
) {
|
|
24
26
|
this.wse = wse;
|
|
25
27
|
this.eventFilters = eventFilters;
|
|
26
28
|
this.eventListener = (mEvent: MessageEvent) => {
|
|
27
29
|
const event = mEvent as WsgEvent;
|
|
28
|
-
const [meta, body]: [WsgMeta, {subscriptionId: string}] =
|
|
29
|
-
Utils.splitWsgData(event.data);
|
|
30
|
+
const [meta, body]: [WsgMeta, { subscriptionId: string }] = Utils.splitWsgData(event.data);
|
|
30
31
|
if (
|
|
31
|
-
this.enabled
|
|
32
|
-
meta.type === 'ServerNotification'
|
|
33
|
-
body.subscriptionId === this.subscriptionInfo!.id
|
|
32
|
+
this.enabled
|
|
33
|
+
&& meta.type === 'ServerNotification'
|
|
34
|
+
&& body.subscriptionId === this.subscriptionInfo!.id
|
|
34
35
|
) {
|
|
35
36
|
callback(body);
|
|
36
37
|
}
|
|
@@ -44,15 +45,17 @@ class Subscription {
|
|
|
44
45
|
|
|
45
46
|
get requestBody(): CreateSubscriptionRequest {
|
|
46
47
|
return {
|
|
47
|
-
deliveryMode: {transportType: 'WebSocket'},
|
|
48
|
+
deliveryMode: { transportType: 'WebSocket' },
|
|
48
49
|
eventFilters: this.eventFilters,
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
_subscriptionInfo?: SubscriptionInfo;
|
|
54
|
+
|
|
53
55
|
get subscriptionInfo(): SubscriptionInfo | undefined {
|
|
54
56
|
return this._subscriptionInfo;
|
|
55
57
|
}
|
|
58
|
+
|
|
56
59
|
set subscriptionInfo(_subscription) {
|
|
57
60
|
this._subscriptionInfo = _subscription;
|
|
58
61
|
if (this.timeout) {
|
|
@@ -71,7 +74,7 @@ class Subscription {
|
|
|
71
74
|
await this.wse.request<SubscriptionInfo>(
|
|
72
75
|
'POST',
|
|
73
76
|
'/restapi/v1.0/subscription',
|
|
74
|
-
this.requestBody
|
|
77
|
+
this.requestBody,
|
|
75
78
|
)
|
|
76
79
|
).data;
|
|
77
80
|
}
|
|
@@ -85,11 +88,11 @@ class Subscription {
|
|
|
85
88
|
await this.wse.request<SubscriptionInfo>(
|
|
86
89
|
'PUT',
|
|
87
90
|
`/restapi/v1.0/subscription/${this.subscriptionInfo!.id}`,
|
|
88
|
-
this.requestBody
|
|
91
|
+
this.requestBody,
|
|
89
92
|
)
|
|
90
93
|
).data;
|
|
91
94
|
} catch (e) {
|
|
92
|
-
const re = e as {response: RestResponse};
|
|
95
|
+
const re = e as { response: RestResponse };
|
|
93
96
|
if (re.response && re.response.status === 404) {
|
|
94
97
|
// subscription expired
|
|
95
98
|
await this.subscribe();
|
|
@@ -101,24 +104,19 @@ class Subscription {
|
|
|
101
104
|
if (!this.subscriptionInfo) {
|
|
102
105
|
return;
|
|
103
106
|
}
|
|
104
|
-
if (this.timeout) {
|
|
105
|
-
global.clearTimeout(this.timeout);
|
|
106
|
-
this.timeout = undefined;
|
|
107
|
-
}
|
|
108
107
|
try {
|
|
109
108
|
await this.wse.request<SubscriptionInfo>(
|
|
110
109
|
'DELETE',
|
|
111
|
-
`/restapi/v1.0/subscription/${this.subscriptionInfo!.id}
|
|
110
|
+
`/restapi/v1.0/subscription/${this.subscriptionInfo!.id}`,
|
|
112
111
|
);
|
|
113
112
|
} catch (e) {
|
|
114
|
-
const re = e as {response: RestResponse};
|
|
113
|
+
const re = e as { response: RestResponse };
|
|
115
114
|
if (re.response && re.response.status === 404) {
|
|
116
115
|
// ignore
|
|
117
116
|
if (this.wse.options.debugMode) {
|
|
118
117
|
console.debug(
|
|
119
|
-
`Subscription ${
|
|
120
|
-
|
|
121
|
-
} doesn't exist on server side`
|
|
118
|
+
`Subscription ${this.subscriptionInfo!.id
|
|
119
|
+
} doesn't exist on server side`,
|
|
122
120
|
);
|
|
123
121
|
}
|
|
124
122
|
} else if (re.response && re.response.status === 401) {
|
|
@@ -130,9 +128,20 @@ class Subscription {
|
|
|
130
128
|
throw e;
|
|
131
129
|
}
|
|
132
130
|
}
|
|
133
|
-
this.
|
|
131
|
+
this.remove();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
remove() {
|
|
135
|
+
if (this.timeout) {
|
|
136
|
+
global.clearTimeout(this.timeout);
|
|
137
|
+
this.timeout = undefined;
|
|
138
|
+
}
|
|
134
139
|
this.enabled = false;
|
|
135
|
-
this.
|
|
140
|
+
this.subscriptionInfo = undefined;
|
|
141
|
+
if (this.wse.ws) {
|
|
142
|
+
this.wse.ws.removeEventListener('message', this.eventListener);
|
|
143
|
+
}
|
|
144
|
+
this.wse.subscriptions = this.wse.subscriptions.filter((x) => x !== this);
|
|
136
145
|
}
|
|
137
146
|
}
|
|
138
147
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import RingCentral from '@rc-ex/core';
|
|
2
|
+
import { RestMethod, RestRequestConfig, RestResponse } from '@rc-ex/core/lib/types';
|
|
3
|
+
import WS from 'isomorphic-ws';
|
|
4
|
+
|
|
1
5
|
export type WsToken = {
|
|
2
6
|
uri: string;
|
|
3
7
|
ws_access_token: string;
|
|
@@ -27,11 +31,11 @@ export type Wsc = {
|
|
|
27
31
|
|
|
28
32
|
export type WsgMeta = {
|
|
29
33
|
type:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
| 'ClientRequest'
|
|
35
|
+
| 'ServerNotification'
|
|
36
|
+
| 'Error'
|
|
37
|
+
| 'ConnectionDetails'
|
|
38
|
+
| 'Heartbeat';
|
|
35
39
|
messageId: string;
|
|
36
40
|
status: number;
|
|
37
41
|
headers: {
|
|
@@ -56,3 +60,21 @@ export type ConnectionDetails = {
|
|
|
56
60
|
recoveryState?: 'Successful' | 'Failed';
|
|
57
61
|
recoveryErrorCode?: string;
|
|
58
62
|
};
|
|
63
|
+
|
|
64
|
+
export interface WebSocketExtensionInterface {
|
|
65
|
+
options: WebSocketOptions;
|
|
66
|
+
subscriptions: SubscriptionInterface[];
|
|
67
|
+
ws: WS;
|
|
68
|
+
wsToken?: WsToken;
|
|
69
|
+
rc: RingCentral;
|
|
70
|
+
request<T>(
|
|
71
|
+
method: RestMethod,
|
|
72
|
+
endpoint: string,
|
|
73
|
+
content?: {},
|
|
74
|
+
queryParams?: {},
|
|
75
|
+
config?: RestRequestConfig,
|
|
76
|
+
): Promise<RestResponse<T>>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface SubscriptionInterface {
|
|
80
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import WS, { MessageEvent } from 'isomorphic-ws';
|
|
2
3
|
|
|
3
|
-
import {WsgMeta, WsgEvent} from './types';
|
|
4
|
-
import
|
|
4
|
+
import { WsgMeta, WsgEvent } from './types';
|
|
5
|
+
import ClosedException from './exceptions/ClosedException';
|
|
6
|
+
import TimeoutException from './exceptions/TimeoutException';
|
|
5
7
|
|
|
6
8
|
class Utils {
|
|
7
9
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -12,19 +14,19 @@ class Utils {
|
|
|
12
14
|
JSON.parse(wsgData.substring(1, index)),
|
|
13
15
|
wsgData.substring(index + 1, wsgData.length - 1),
|
|
14
16
|
];
|
|
15
|
-
} else {
|
|
16
|
-
return JSON.parse(wsgData);
|
|
17
17
|
}
|
|
18
|
+
return JSON.parse(wsgData);
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
static debugWebSocket(
|
|
21
|
+
static debugWebSocket(_ws: WS) {
|
|
22
|
+
const ws = _ws;
|
|
21
23
|
const send = ws.send.bind(ws);
|
|
22
24
|
ws.send = async (str: string) => {
|
|
23
25
|
await send(str);
|
|
24
26
|
console.debug(
|
|
25
27
|
`*** WebSocket outgoing message: ***
|
|
26
28
|
${JSON.stringify(JSON.parse(str), null, 2)}
|
|
27
|
-
|
|
29
|
+
******`,
|
|
28
30
|
);
|
|
29
31
|
};
|
|
30
32
|
ws.addEventListener('message', (mEvent: MessageEvent) => {
|
|
@@ -32,16 +34,16 @@ ${JSON.stringify(JSON.parse(str), null, 2)}
|
|
|
32
34
|
console.debug(
|
|
33
35
|
`*** WebSocket incoming message: ***
|
|
34
36
|
${JSON.stringify(JSON.parse(event.data), null, 2)}
|
|
35
|
-
|
|
37
|
+
******`,
|
|
36
38
|
);
|
|
37
39
|
});
|
|
38
|
-
ws.addEventListener('open', event => {
|
|
40
|
+
ws.addEventListener('open', (event) => {
|
|
39
41
|
console.debug('WebSocket open event:', event);
|
|
40
42
|
});
|
|
41
|
-
ws.addEventListener('error', event => {
|
|
43
|
+
ws.addEventListener('error', (event) => {
|
|
42
44
|
console.debug('WebSocket error event:', event);
|
|
43
45
|
});
|
|
44
|
-
ws.addEventListener('close', event => {
|
|
46
|
+
ws.addEventListener('close', (event) => {
|
|
45
47
|
console.debug('WebSocket close event:', event);
|
|
46
48
|
});
|
|
47
49
|
}
|
|
@@ -49,7 +51,7 @@ ${JSON.stringify(JSON.parse(event.data), null, 2)}
|
|
|
49
51
|
static waitForWebSocketMessage(
|
|
50
52
|
ws: WS,
|
|
51
53
|
matchCondition: (meta: WsgMeta) => boolean,
|
|
52
|
-
timeout = 60000
|
|
54
|
+
timeout = 60000,
|
|
53
55
|
) {
|
|
54
56
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
57
|
return new Promise<[WsgMeta, any, WsgEvent]>((resolve, reject) => {
|
|
@@ -60,10 +62,10 @@ ${JSON.stringify(JSON.parse(event.data), null, 2)}
|
|
|
60
62
|
}
|
|
61
63
|
}, 1000);
|
|
62
64
|
const timeoutHandle = setTimeout(() => {
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
63
66
|
ws.removeEventListener('message', handler);
|
|
64
67
|
clearInterval(checkHandle);
|
|
65
68
|
reject(new TimeoutException());
|
|
66
|
-
return;
|
|
67
69
|
}, timeout);
|
|
68
70
|
const handler = (mEvent: MessageEvent) => {
|
|
69
71
|
const event = mEvent as WsgEvent;
|
|
@@ -73,7 +75,6 @@ ${JSON.stringify(JSON.parse(event.data), null, 2)}
|
|
|
73
75
|
clearInterval(checkHandle);
|
|
74
76
|
clearTimeout(timeoutHandle);
|
|
75
77
|
resolve([meta, body, event]);
|
|
76
|
-
return;
|
|
77
78
|
}
|
|
78
79
|
};
|
|
79
80
|
ws.addEventListener('message', handler);
|
package/tsconfig.json
CHANGED
package/exceptions.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import {WsgEvent, WsgError, WsgMeta} from './types';
|
|
2
|
-
import Utils from './utils';
|
|
3
|
-
|
|
4
|
-
export class ConnectionException extends Error {
|
|
5
|
-
wsgEvent: WsgEvent;
|
|
6
|
-
wsgError: WsgError;
|
|
7
|
-
constructor(wsgEvent: WsgEvent) {
|
|
8
|
-
const [, wsgError]: [WsgMeta, WsgError] = Utils.splitWsgData(wsgEvent.data);
|
|
9
|
-
super(JSON.stringify(wsgError, null, 2));
|
|
10
|
-
this.wsgEvent = wsgEvent;
|
|
11
|
-
this.wsgError = wsgError;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class TimeoutException extends Error {
|
|
16
|
-
constructor(message?: string) {
|
|
17
|
-
super(message ?? 'Failed to receive expected WebSocket message in time.');
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class ClosedException extends Error {
|
|
22
|
-
constructor(message?: string) {
|
|
23
|
-
super(message ?? 'WebSocket has been closed');
|
|
24
|
-
}
|
|
25
|
-
}
|
package/lib/exceptions.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { WsgEvent, WsgError } from './types';
|
|
2
|
-
export declare class ConnectionException extends Error {
|
|
3
|
-
wsgEvent: WsgEvent;
|
|
4
|
-
wsgError: WsgError;
|
|
5
|
-
constructor(wsgEvent: WsgEvent);
|
|
6
|
-
}
|
|
7
|
-
export declare class TimeoutException extends Error {
|
|
8
|
-
constructor(message?: string);
|
|
9
|
-
}
|
|
10
|
-
export declare class ClosedException extends Error {
|
|
11
|
-
constructor(message?: string);
|
|
12
|
-
}
|