@rc-ex/ws 1.2.0 → 1.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/README.md +49 -22
- package/lib/exceptions/ClosedException.js +1 -1
- package/lib/exceptions/ConnectionException.d.ts +1 -1
- package/lib/exceptions/TimeoutException.js +1 -1
- package/lib/index.d.ts +8 -8
- package/lib/index.js +21 -21
- package/lib/index.js.map +1 -1
- package/lib/rest.d.ts +2 -2
- package/lib/rest.js +4 -5
- package/lib/rest.js.map +1 -1
- package/lib/subscription.d.ts +4 -4
- package/lib/subscription.js +11 -9
- package/lib/subscription.js.map +1 -1
- package/lib/types.d.ts +5 -5
- package/lib/utils.d.ts +2 -2
- package/lib/utils.js +16 -16
- package/lib/utils.js.map +1 -1
- package/package.json +4 -4
- package/src/exceptions/ClosedException.ts +1 -1
- package/src/exceptions/ConnectionException.ts +2 -2
- package/src/exceptions/TimeoutException.ts +1 -1
- package/src/index.ts +81 -51
- package/src/rest.ts +22 -12
- package/src/subscription.ts +36 -18
- package/src/types.ts +14 -5
- package/src/utils.ts +26 -23
package/lib/utils.js
CHANGED
|
@@ -6,11 +6,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const ClosedException_1 = __importDefault(require("./exceptions/ClosedException"));
|
|
7
7
|
const TimeoutException_1 = __importDefault(require("./exceptions/TimeoutException"));
|
|
8
8
|
class Utils {
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
9
|
static splitWsgData(wsgData) {
|
|
11
|
-
if (wsgData.includes(
|
|
12
|
-
const index = wsgData.indexOf(
|
|
13
|
-
return [
|
|
10
|
+
if (wsgData.includes(",--Boundary")) {
|
|
11
|
+
const index = wsgData.indexOf(",--Boundary");
|
|
12
|
+
return [
|
|
13
|
+
JSON.parse(wsgData.substring(1, index)),
|
|
14
|
+
wsgData.substring(index + 1, wsgData.length - 1),
|
|
15
|
+
];
|
|
14
16
|
}
|
|
15
17
|
return JSON.parse(wsgData);
|
|
16
18
|
}
|
|
@@ -23,24 +25,23 @@ class Utils {
|
|
|
23
25
|
${JSON.stringify(JSON.parse(str), null, 2)}
|
|
24
26
|
******`);
|
|
25
27
|
};
|
|
26
|
-
ws.addEventListener(
|
|
28
|
+
ws.addEventListener("message", (mEvent) => {
|
|
27
29
|
const event = mEvent;
|
|
28
30
|
console.debug(`*** WebSocket incoming message: ***
|
|
29
31
|
${JSON.stringify(JSON.parse(event.data), null, 2)}
|
|
30
32
|
******`);
|
|
31
33
|
});
|
|
32
|
-
ws.addEventListener(
|
|
33
|
-
console.debug(
|
|
34
|
+
ws.addEventListener("open", (event) => {
|
|
35
|
+
console.debug("WebSocket open event:", event);
|
|
34
36
|
});
|
|
35
|
-
ws.addEventListener(
|
|
36
|
-
console.debug(
|
|
37
|
+
ws.addEventListener("error", (event) => {
|
|
38
|
+
console.debug("WebSocket error event:", event);
|
|
37
39
|
});
|
|
38
|
-
ws.addEventListener(
|
|
39
|
-
console.debug(
|
|
40
|
+
ws.addEventListener("close", (event) => {
|
|
41
|
+
console.debug("WebSocket close event:", event);
|
|
40
42
|
});
|
|
41
43
|
}
|
|
42
44
|
static waitForWebSocketMessage(ws, matchCondition, timeout = 60000) {
|
|
43
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
45
|
return new Promise((resolve, reject) => {
|
|
45
46
|
const checkHandle = setInterval(() => {
|
|
46
47
|
if (ws.readyState === ws.CLOSED) {
|
|
@@ -49,8 +50,7 @@ ${JSON.stringify(JSON.parse(event.data), null, 2)}
|
|
|
49
50
|
}
|
|
50
51
|
}, 1000);
|
|
51
52
|
const timeoutHandle = setTimeout(() => {
|
|
52
|
-
|
|
53
|
-
ws.removeEventListener('message', handler);
|
|
53
|
+
ws.removeEventListener("message", handler);
|
|
54
54
|
clearInterval(checkHandle);
|
|
55
55
|
reject(new TimeoutException_1.default());
|
|
56
56
|
}, timeout);
|
|
@@ -58,13 +58,13 @@ ${JSON.stringify(JSON.parse(event.data), null, 2)}
|
|
|
58
58
|
const event = mEvent;
|
|
59
59
|
const [meta, body] = Utils.splitWsgData(event.data);
|
|
60
60
|
if (matchCondition(meta)) {
|
|
61
|
-
ws.removeEventListener(
|
|
61
|
+
ws.removeEventListener("message", handler);
|
|
62
62
|
clearInterval(checkHandle);
|
|
63
63
|
clearTimeout(timeoutHandle);
|
|
64
64
|
resolve([meta, body, event]);
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
|
-
ws.addEventListener(
|
|
67
|
+
ws.addEventListener("message", handler);
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
}
|
package/lib/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rc-ex/ws",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "WebSocket extension for ringcentral-extensible project",
|
|
5
5
|
"author": "Tyler Liu <tyler.liu@ringcentral.com>",
|
|
6
6
|
"homepage": "https://github.com/ringcentral/ringcentral-extensible/tree/master/packages/extensions/ws",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"access": "public"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@types/ws": "^8.5.
|
|
21
|
+
"@types/ws": "^8.5.14",
|
|
22
22
|
"http-status-codes": "^2.3.0",
|
|
23
23
|
"hyperid": "^3.3.0",
|
|
24
24
|
"isomorphic-ws": "^5.0.0",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"ws": "^8.18.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@rc-ex/core": "^1.5.
|
|
29
|
+
"@rc-ex/core": "^1.5.1"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "1a8a5becc832b92af73f1ad1f58b87f38fee3650"
|
|
32
32
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
import type
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
import
|
|
14
|
-
import type
|
|
15
|
-
|
|
16
|
-
import
|
|
17
|
-
import
|
|
1
|
+
import type RingCentral from "@rc-ex/core";
|
|
2
|
+
import type {
|
|
3
|
+
RestMethod,
|
|
4
|
+
RestRequestConfig,
|
|
5
|
+
RestResponse,
|
|
6
|
+
} from "@rc-ex/core/lib/types";
|
|
7
|
+
import SdkExtension from "@rc-ex/core/lib/SdkExtension";
|
|
8
|
+
import type { MessageEvent } from "isomorphic-ws";
|
|
9
|
+
import WS from "isomorphic-ws";
|
|
10
|
+
import hyperid from "hyperid";
|
|
11
|
+
import { EventEmitter } from "events";
|
|
12
|
+
import waitFor from "wait-for-async";
|
|
13
|
+
import RestException from "@rc-ex/core/lib/RestException";
|
|
14
|
+
import type SubscriptionInfo from "@rc-ex/core/lib/definitions/SubscriptionInfo";
|
|
15
|
+
|
|
16
|
+
import { request } from "./rest";
|
|
17
|
+
import type {
|
|
18
|
+
ConnectionDetails,
|
|
19
|
+
WebSocketExtensionInterface,
|
|
20
|
+
WebSocketOptions,
|
|
21
|
+
Wsc,
|
|
22
|
+
WsgEvent,
|
|
23
|
+
WsToken,
|
|
24
|
+
} from "./types";
|
|
25
|
+
import Subscription from "./subscription";
|
|
26
|
+
import ConnectionException from "./exceptions/ConnectionException";
|
|
27
|
+
import Utils from "./utils";
|
|
18
28
|
|
|
19
29
|
const CONNECTING = 0;
|
|
20
30
|
const OPEN = 1;
|
|
@@ -22,12 +32,12 @@ const OPEN = 1;
|
|
|
22
32
|
const uuid = hyperid();
|
|
23
33
|
|
|
24
34
|
export enum Events {
|
|
25
|
-
autoRecoverSuccess =
|
|
26
|
-
autoRecoverFailed =
|
|
27
|
-
autoRecoverError =
|
|
28
|
-
newWebSocketObject =
|
|
29
|
-
newWsc =
|
|
30
|
-
connectionReady =
|
|
35
|
+
autoRecoverSuccess = "autoRecoverSuccess",
|
|
36
|
+
autoRecoverFailed = "autoRecoverFailed",
|
|
37
|
+
autoRecoverError = "autoRecoverError",
|
|
38
|
+
newWebSocketObject = "newWebSocketObject",
|
|
39
|
+
newWsc = "newWsc",
|
|
40
|
+
connectionReady = "connectionReady",
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
class WebSocketExtension extends SdkExtension {
|
|
@@ -94,16 +104,17 @@ class WebSocketExtension extends SdkExtension {
|
|
|
94
104
|
content?: {},
|
|
95
105
|
queryParams?: {},
|
|
96
106
|
config?: RestRequestConfig,
|
|
97
|
-
// eslint-disable-next-line max-params
|
|
98
107
|
): Promise<RestResponse<T>> => {
|
|
99
108
|
if (!this.enabled || !this.options.restOverWebSocket) {
|
|
100
109
|
return request(method, endpoint, content, queryParams, config);
|
|
101
110
|
}
|
|
102
111
|
if (
|
|
103
112
|
// the following cannot be done with WebSocket
|
|
104
|
-
config?.headers?.getContentType?.toString()?.includes(
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
config?.headers?.getContentType?.toString()?.includes(
|
|
114
|
+
"multipart/form-data",
|
|
115
|
+
) ||
|
|
116
|
+
config?.responseType === "arraybuffer" ||
|
|
117
|
+
endpoint.startsWith("/restapi/oauth/") // token, revoke, wstoken
|
|
107
118
|
) {
|
|
108
119
|
return request(method, endpoint, content, queryParams, config);
|
|
109
120
|
}
|
|
@@ -134,7 +145,7 @@ class WebSocketExtension extends SdkExtension {
|
|
|
134
145
|
throw e; // such as InsufficientPermissions
|
|
135
146
|
}
|
|
136
147
|
if (this.options.debugMode) {
|
|
137
|
-
console.debug(
|
|
148
|
+
console.debug("Initial connect failed:", e);
|
|
138
149
|
}
|
|
139
150
|
}
|
|
140
151
|
let retriesAttempted = 0;
|
|
@@ -156,10 +167,12 @@ class WebSocketExtension extends SdkExtension {
|
|
|
156
167
|
await this.recover();
|
|
157
168
|
retriesAttempted = 0;
|
|
158
169
|
if (this.options.debugMode) {
|
|
159
|
-
console.debug(
|
|
170
|
+
console.debug(
|
|
171
|
+
`Auto recover done, recoveryState: ${this.connectionDetails.recoveryState}`,
|
|
172
|
+
);
|
|
160
173
|
}
|
|
161
174
|
this.eventEmitter.emit(
|
|
162
|
-
this.connectionDetails.recoveryState ===
|
|
175
|
+
this.connectionDetails.recoveryState === "Successful"
|
|
163
176
|
? Events.autoRecoverSuccess
|
|
164
177
|
: Events.autoRecoverFailed,
|
|
165
178
|
this.ws,
|
|
@@ -170,25 +183,31 @@ class WebSocketExtension extends SdkExtension {
|
|
|
170
183
|
}
|
|
171
184
|
retriesAttempted += 1;
|
|
172
185
|
if (this.options.debugMode) {
|
|
173
|
-
console.debug(
|
|
186
|
+
console.debug("Auto recover error:", e);
|
|
174
187
|
}
|
|
175
188
|
this.eventEmitter.emit(Events.autoRecoverError, e);
|
|
176
189
|
}
|
|
177
|
-
this.intervalHandle = setInterval(
|
|
190
|
+
this.intervalHandle = setInterval(
|
|
191
|
+
check,
|
|
192
|
+
this.options.autoRecover!.checkInterval!(retriesAttempted),
|
|
193
|
+
);
|
|
178
194
|
}
|
|
179
195
|
checking = false;
|
|
180
196
|
};
|
|
181
|
-
this.intervalHandle = setInterval(
|
|
197
|
+
this.intervalHandle = setInterval(
|
|
198
|
+
check,
|
|
199
|
+
this.options.autoRecover!.checkInterval!(retriesAttempted),
|
|
200
|
+
);
|
|
182
201
|
|
|
183
202
|
// browser only code start
|
|
184
|
-
if (typeof window !==
|
|
185
|
-
window.addEventListener(
|
|
203
|
+
if (typeof window !== "undefined" && window.addEventListener) {
|
|
204
|
+
window.addEventListener("offline", () => {
|
|
186
205
|
if (this.pingServerHandle) {
|
|
187
206
|
clearTimeout(this.pingServerHandle);
|
|
188
207
|
}
|
|
189
208
|
this.ws?.close();
|
|
190
209
|
});
|
|
191
|
-
window.addEventListener(
|
|
210
|
+
window.addEventListener("online", () => {
|
|
192
211
|
check();
|
|
193
212
|
});
|
|
194
213
|
}
|
|
@@ -221,15 +240,16 @@ class WebSocketExtension extends SdkExtension {
|
|
|
221
240
|
}
|
|
222
241
|
if (
|
|
223
242
|
this.connectionDetails !== undefined &&
|
|
224
|
-
Date.now() - this.recoverTimestamp >
|
|
243
|
+
Date.now() - this.recoverTimestamp >
|
|
244
|
+
this.connectionDetails.recoveryTimeout * 1000
|
|
225
245
|
) {
|
|
226
246
|
if (this.options.debugMode) {
|
|
227
|
-
console.debug(
|
|
247
|
+
console.debug("connect to WSG but do not recover");
|
|
228
248
|
}
|
|
229
249
|
await this.connect(false); // connect to WSG but do not recover
|
|
230
250
|
} else {
|
|
231
251
|
if (this.options.debugMode) {
|
|
232
|
-
console.debug(
|
|
252
|
+
console.debug("connect to WSG and recover");
|
|
233
253
|
}
|
|
234
254
|
await this.connect(true); // connect to WSG and recover
|
|
235
255
|
}
|
|
@@ -248,12 +268,11 @@ class WebSocketExtension extends SdkExtension {
|
|
|
248
268
|
await this.ws.send(
|
|
249
269
|
JSON.stringify([
|
|
250
270
|
{
|
|
251
|
-
type:
|
|
271
|
+
type: "Heartbeat",
|
|
252
272
|
messageId: uuid(),
|
|
253
273
|
},
|
|
254
274
|
]),
|
|
255
275
|
);
|
|
256
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
257
276
|
} catch (e) {
|
|
258
277
|
this.ws.close(); // Explicitly mark WS as closed
|
|
259
278
|
}
|
|
@@ -274,12 +293,14 @@ class WebSocketExtension extends SdkExtension {
|
|
|
274
293
|
|
|
275
294
|
public async _connect(recoverSession = false) {
|
|
276
295
|
if (!this.wsToken || Date.now() > this.wsTokenExpiresAt) {
|
|
277
|
-
const r = await this.rc.post(
|
|
296
|
+
const r = await this.rc.post("/restapi/oauth/wstoken");
|
|
278
297
|
this.wsToken = r.data as WsToken;
|
|
279
298
|
// `expires_in` default value is 600 seconds. That's why we `* 0.8`
|
|
280
299
|
this.wsTokenExpiresAt = Date.now() + this.wsToken.expires_in * 0.8 * 1000;
|
|
281
300
|
}
|
|
282
|
-
let wsUri = `${this.wsToken!.uri}?access_token=${
|
|
301
|
+
let wsUri = `${this.wsToken!.uri}?access_token=${
|
|
302
|
+
this.wsToken!.ws_access_token
|
|
303
|
+
}`;
|
|
283
304
|
if (recoverSession && this.wsc) {
|
|
284
305
|
wsUri += `&wsc=${this.wsc.token}`;
|
|
285
306
|
}
|
|
@@ -299,11 +320,14 @@ class WebSocketExtension extends SdkExtension {
|
|
|
299
320
|
};
|
|
300
321
|
|
|
301
322
|
if (this.options.autoRecover?.enabled) {
|
|
302
|
-
this.ws.addEventListener(
|
|
323
|
+
this.ws.addEventListener("message", () => {
|
|
303
324
|
if (this.pingServerHandle) {
|
|
304
325
|
clearTimeout(this.pingServerHandle);
|
|
305
326
|
}
|
|
306
|
-
this.pingServerHandle = setTimeout(
|
|
327
|
+
this.pingServerHandle = setTimeout(
|
|
328
|
+
() => this.pingServer(),
|
|
329
|
+
this.options.autoRecover!.pingServerInterval,
|
|
330
|
+
);
|
|
307
331
|
});
|
|
308
332
|
}
|
|
309
333
|
|
|
@@ -313,13 +337,13 @@ class WebSocketExtension extends SdkExtension {
|
|
|
313
337
|
}
|
|
314
338
|
|
|
315
339
|
// listen for new wsc data
|
|
316
|
-
this.ws.addEventListener(
|
|
340
|
+
this.ws.addEventListener("message", (mEvent: MessageEvent) => {
|
|
317
341
|
const event = mEvent as WsgEvent;
|
|
318
342
|
const [meta, body] = Utils.splitWsgData(event.data);
|
|
319
343
|
if (
|
|
320
344
|
meta.wsc &&
|
|
321
345
|
(!this.wsc ||
|
|
322
|
-
(meta.type ===
|
|
346
|
+
(meta.type === "ConnectionDetails" && body.recoveryState) ||
|
|
323
347
|
this.wsc.sequence < meta.wsc.sequence)
|
|
324
348
|
) {
|
|
325
349
|
this.wsc = meta.wsc;
|
|
@@ -330,9 +354,9 @@ class WebSocketExtension extends SdkExtension {
|
|
|
330
354
|
// get initial ConnectionDetails data
|
|
331
355
|
const [meta, body, event] = await Utils.waitForWebSocketMessage(
|
|
332
356
|
this.ws,
|
|
333
|
-
(meta) => meta.type ===
|
|
357
|
+
(meta) => meta.type === "ConnectionDetails" || meta.type === "Error",
|
|
334
358
|
);
|
|
335
|
-
if (meta.type ===
|
|
359
|
+
if (meta.type === "Error") {
|
|
336
360
|
throw new ConnectionException(event);
|
|
337
361
|
}
|
|
338
362
|
this.connectionDetails = body;
|
|
@@ -344,7 +368,9 @@ class WebSocketExtension extends SdkExtension {
|
|
|
344
368
|
if (this.subscription && this.subscription.enabled) {
|
|
345
369
|
// because we have a new ws object
|
|
346
370
|
this.subscription.setupWsEventListener();
|
|
347
|
-
if (
|
|
371
|
+
if (
|
|
372
|
+
!recoverSession || this.connectionDetails.recoveryState === "Failed"
|
|
373
|
+
) {
|
|
348
374
|
// create new subscription if don't recover existing one
|
|
349
375
|
await this.subscription.subscribe();
|
|
350
376
|
}
|
|
@@ -373,7 +399,11 @@ class WebSocketExtension extends SdkExtension {
|
|
|
373
399
|
callback: (event: {}) => void,
|
|
374
400
|
cache: SubscriptionInfo | undefined | null = undefined,
|
|
375
401
|
) {
|
|
376
|
-
const subscription = new Subscription(
|
|
402
|
+
const subscription = new Subscription(
|
|
403
|
+
this as WebSocketExtensionInterface,
|
|
404
|
+
eventFilters,
|
|
405
|
+
callback,
|
|
406
|
+
);
|
|
377
407
|
if (cache === undefined || cache === null) {
|
|
378
408
|
await subscription.subscribe();
|
|
379
409
|
} else {
|
package/src/rest.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type {
|
|
2
|
+
RestMethod,
|
|
3
|
+
RestRequestConfig,
|
|
4
|
+
RestResponse,
|
|
5
|
+
} from "@rc-ex/core/lib/types";
|
|
6
|
+
import RestException from "@rc-ex/core/lib/RestException";
|
|
7
|
+
import hyperid from "hyperid";
|
|
8
|
+
import { getReasonPhrase } from "http-status-codes";
|
|
5
9
|
|
|
6
|
-
import Utils from
|
|
7
|
-
import type { WebSocketExtensionInterface } from
|
|
10
|
+
import Utils from "./utils";
|
|
11
|
+
import type { WebSocketExtensionInterface } from "./types";
|
|
8
12
|
|
|
9
|
-
const version =
|
|
13
|
+
const version = "0.16";
|
|
10
14
|
|
|
11
15
|
const uuid = hyperid();
|
|
12
16
|
|
|
13
|
-
// eslint-disable-next-line max-params
|
|
14
17
|
export async function request<T>(
|
|
15
18
|
this: WebSocketExtensionInterface,
|
|
16
19
|
method: RestMethod,
|
|
@@ -29,12 +32,14 @@ export async function request<T>(
|
|
|
29
32
|
};
|
|
30
33
|
newConfig.headers = {
|
|
31
34
|
...newConfig.headers,
|
|
32
|
-
|
|
35
|
+
"X-User-Agent": `${this.rc.rest!.appName}/${
|
|
36
|
+
this.rc.rest!.appVersion
|
|
37
|
+
} ringcentral-extensible/ws/${version}`,
|
|
33
38
|
} as any;
|
|
34
39
|
const messageId = uuid();
|
|
35
40
|
const requestBody = [
|
|
36
41
|
{
|
|
37
|
-
type:
|
|
42
|
+
type: "ClientRequest",
|
|
38
43
|
messageId,
|
|
39
44
|
method: newConfig.method,
|
|
40
45
|
path: newConfig.url,
|
|
@@ -46,7 +51,10 @@ export async function request<T>(
|
|
|
46
51
|
requestBody.push(newConfig.data);
|
|
47
52
|
}
|
|
48
53
|
await this.ws.send(JSON.stringify(requestBody));
|
|
49
|
-
const [meta, body] = await Utils.waitForWebSocketMessage(
|
|
54
|
+
const [meta, body] = await Utils.waitForWebSocketMessage(
|
|
55
|
+
this.ws,
|
|
56
|
+
(_meta) => _meta.messageId === messageId,
|
|
57
|
+
);
|
|
50
58
|
const response: RestResponse = {
|
|
51
59
|
data: body as T,
|
|
52
60
|
status: meta.status,
|
|
@@ -54,7 +62,9 @@ export async function request<T>(
|
|
|
54
62
|
headers: meta.headers,
|
|
55
63
|
config: newConfig as any,
|
|
56
64
|
};
|
|
57
|
-
if (
|
|
65
|
+
if (
|
|
66
|
+
meta.type === "ClientRequest" && meta.status >= 200 && meta.status < 300
|
|
67
|
+
) {
|
|
58
68
|
return response;
|
|
59
69
|
}
|
|
60
70
|
throw new RestException(response);
|
package/src/subscription.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
import type
|
|
3
|
-
import type
|
|
4
|
-
import type {
|
|
5
|
-
import type { MessageEvent } from 'ws';
|
|
1
|
+
import type CreateSubscriptionRequest from "@rc-ex/core/lib/definitions/CreateSubscriptionRequest";
|
|
2
|
+
import type SubscriptionInfo from "@rc-ex/core/lib/definitions/SubscriptionInfo";
|
|
3
|
+
import type { RestResponse } from "@rc-ex/core/lib/types";
|
|
4
|
+
import type { MessageEvent } from "ws";
|
|
6
5
|
|
|
7
|
-
import type { WsgEvent, WsgMeta
|
|
8
|
-
import Utils from
|
|
6
|
+
import type { WebSocketExtensionInterface, WsgEvent, WsgMeta } from "./types";
|
|
7
|
+
import Utils from "./utils";
|
|
9
8
|
|
|
10
9
|
class Subscription {
|
|
11
10
|
public subscriptionInfo?: SubscriptionInfo;
|
|
@@ -20,13 +19,21 @@ class Subscription {
|
|
|
20
19
|
|
|
21
20
|
public enabled = true;
|
|
22
21
|
|
|
23
|
-
public constructor(
|
|
22
|
+
public constructor(
|
|
23
|
+
wse: WebSocketExtensionInterface,
|
|
24
|
+
eventFilters: string[],
|
|
25
|
+
callback: (event: {}) => void,
|
|
26
|
+
) {
|
|
24
27
|
this.wse = wse;
|
|
25
28
|
this.eventFilters = eventFilters;
|
|
26
29
|
this.eventListener = (mEvent: MessageEvent) => {
|
|
27
30
|
const event = mEvent as WsgEvent;
|
|
28
|
-
const [meta, body]: [WsgMeta, { subscriptionId: string }] = Utils
|
|
29
|
-
|
|
31
|
+
const [meta, body]: [WsgMeta, { subscriptionId: string }] = Utils
|
|
32
|
+
.splitWsgData(event.data);
|
|
33
|
+
if (
|
|
34
|
+
this.enabled && meta.type === "ServerNotification" &&
|
|
35
|
+
body.subscriptionId === this.subscriptionInfo!.id
|
|
36
|
+
) {
|
|
30
37
|
callback(body);
|
|
31
38
|
}
|
|
32
39
|
};
|
|
@@ -34,19 +41,23 @@ class Subscription {
|
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
public setupWsEventListener() {
|
|
37
|
-
this.wse.ws.addEventListener(
|
|
44
|
+
this.wse.ws.addEventListener("message", this.eventListener);
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
public get requestBody(): CreateSubscriptionRequest {
|
|
41
48
|
return {
|
|
42
|
-
deliveryMode: { transportType:
|
|
49
|
+
deliveryMode: { transportType: "WebSocket" as any }, // because WebSocket is not in spec
|
|
43
50
|
eventFilters: this.eventFilters,
|
|
44
51
|
};
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
public async subscribe() {
|
|
48
55
|
this.subscriptionInfo = (
|
|
49
|
-
await this.wse.request<SubscriptionInfo>(
|
|
56
|
+
await this.wse.request<SubscriptionInfo>(
|
|
57
|
+
"POST",
|
|
58
|
+
"/restapi/v1.0/subscription",
|
|
59
|
+
this.requestBody,
|
|
60
|
+
)
|
|
50
61
|
).data;
|
|
51
62
|
}
|
|
52
63
|
|
|
@@ -57,7 +68,7 @@ class Subscription {
|
|
|
57
68
|
try {
|
|
58
69
|
this.subscriptionInfo = (
|
|
59
70
|
await this.wse.request<SubscriptionInfo>(
|
|
60
|
-
|
|
71
|
+
"PUT",
|
|
61
72
|
`/restapi/v1.0/subscription/${this.subscriptionInfo!.id}`,
|
|
62
73
|
this.requestBody,
|
|
63
74
|
)
|
|
@@ -76,18 +87,25 @@ class Subscription {
|
|
|
76
87
|
return;
|
|
77
88
|
}
|
|
78
89
|
try {
|
|
79
|
-
await this.wse.request<SubscriptionInfo>(
|
|
90
|
+
await this.wse.request<SubscriptionInfo>(
|
|
91
|
+
"DELETE",
|
|
92
|
+
`/restapi/v1.0/subscription/${this.subscriptionInfo!.id}`,
|
|
93
|
+
);
|
|
80
94
|
} catch (e) {
|
|
81
95
|
const re = e as { response: RestResponse };
|
|
82
96
|
if (re.response && re.response.status === 404) {
|
|
83
97
|
// ignore
|
|
84
98
|
if (this.wse.options.debugMode) {
|
|
85
|
-
console.debug(
|
|
99
|
+
console.debug(
|
|
100
|
+
`Subscription ${
|
|
101
|
+
this.subscriptionInfo!.id
|
|
102
|
+
} doesn't exist on server side`,
|
|
103
|
+
);
|
|
86
104
|
}
|
|
87
105
|
} else if (re.response && re.response.status === 401) {
|
|
88
106
|
// ignore
|
|
89
107
|
if (this.wse.options.debugMode) {
|
|
90
|
-
console.debug(
|
|
108
|
+
console.debug("Token invalid when trying to revoke subscription");
|
|
91
109
|
}
|
|
92
110
|
} else {
|
|
93
111
|
throw e;
|
|
@@ -104,7 +122,7 @@ class Subscription {
|
|
|
104
122
|
this.enabled = false;
|
|
105
123
|
this.subscriptionInfo = undefined;
|
|
106
124
|
if (this.wse.ws) {
|
|
107
|
-
this.wse.ws.removeEventListener(
|
|
125
|
+
this.wse.ws.removeEventListener("message", this.eventListener);
|
|
108
126
|
}
|
|
109
127
|
this.wse.subscription = undefined;
|
|
110
128
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import type RingCentral from
|
|
2
|
-
import type {
|
|
3
|
-
|
|
1
|
+
import type RingCentral from "@rc-ex/core";
|
|
2
|
+
import type {
|
|
3
|
+
RestMethod,
|
|
4
|
+
RestRequestConfig,
|
|
5
|
+
RestResponse,
|
|
6
|
+
} from "@rc-ex/core/lib/types";
|
|
7
|
+
import type WS from "isomorphic-ws";
|
|
4
8
|
|
|
5
9
|
export interface WsToken {
|
|
6
10
|
uri: string;
|
|
@@ -30,7 +34,12 @@ export interface Wsc {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export interface WsgMeta {
|
|
33
|
-
type:
|
|
37
|
+
type:
|
|
38
|
+
| "ClientRequest"
|
|
39
|
+
| "ServerNotification"
|
|
40
|
+
| "Error"
|
|
41
|
+
| "ConnectionDetails"
|
|
42
|
+
| "Heartbeat";
|
|
34
43
|
messageId: string;
|
|
35
44
|
status: number;
|
|
36
45
|
headers: {
|
|
@@ -52,7 +61,7 @@ export interface ConnectionDetails {
|
|
|
52
61
|
idleTimeout: number;
|
|
53
62
|
absoluteTimeout: number;
|
|
54
63
|
maxActiveRequests: number;
|
|
55
|
-
recoveryState?:
|
|
64
|
+
recoveryState?: "Successful" | "Failed";
|
|
56
65
|
recoveryErrorCode?: string;
|
|
57
66
|
}
|
|
58
67
|
|