@rc-ex/ws 1.2.0 → 1.2.2
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/ClosedException.js.map +1 -1
- package/lib/exceptions/ClosedException.ts +7 -0
- package/lib/exceptions/ConnectionException.d.ts +1 -1
- package/lib/exceptions/ConnectionException.js.map +1 -1
- package/lib/exceptions/ConnectionException.ts +17 -0
- package/lib/exceptions/TimeoutException.js +1 -1
- package/lib/exceptions/TimeoutException.js.map +1 -1
- package/lib/exceptions/TimeoutException.ts +7 -0
- package/lib/index.d.ts +8 -8
- package/lib/index.js +23 -23
- package/lib/index.js.map +1 -1
- package/lib/index.ts +418 -0
- package/lib/rest.d.ts +2 -2
- package/lib/rest.js +5 -6
- package/lib/rest.js.map +1 -1
- package/lib/rest.ts +71 -0
- package/lib/subscription.d.ts +4 -4
- package/lib/subscription.js +11 -9
- package/lib/subscription.js.map +1 -1
- package/lib/subscription.ts +131 -0
- package/lib/types.d.ts +5 -5
- package/lib/types.js.map +1 -1
- package/lib/types.ts +85 -0
- package/lib/utils.d.ts +2 -2
- package/lib/utils.js +16 -16
- package/lib/utils.js.map +1 -1
- package/lib/utils.ts +82 -0
- package/package.json +6 -6
- package/src/exceptions/ClosedException.d.ts +4 -0
- package/src/exceptions/ClosedException.js +9 -0
- package/src/exceptions/ClosedException.js.map +1 -0
- package/src/exceptions/ClosedException.ts +1 -1
- package/src/exceptions/ConnectionException.d.ts +7 -0
- package/src/exceptions/ConnectionException.js +16 -0
- package/src/exceptions/ConnectionException.js.map +1 -0
- package/src/exceptions/ConnectionException.ts +2 -2
- package/src/exceptions/TimeoutException.d.ts +4 -0
- package/src/exceptions/TimeoutException.js +9 -0
- package/src/exceptions/TimeoutException.js.map +1 -0
- package/src/exceptions/TimeoutException.ts +1 -1
- package/src/index.d.ts +44 -0
- package/src/index.js +329 -0
- package/src/index.js.map +1 -0
- package/src/index.ts +81 -51
- package/src/rest.d.ts +3 -0
- package/src/rest.js +55 -0
- package/src/rest.js.map +1 -0
- package/src/rest.ts +22 -12
- package/src/subscription.d.ts +20 -0
- package/src/subscription.js +91 -0
- package/src/subscription.js.map +1 -0
- package/src/subscription.ts +36 -18
- package/src/types.d.ts +61 -0
- package/src/types.js +3 -0
- package/src/types.js.map +1 -0
- package/src/types.ts +14 -5
- package/src/utils.d.ts +8 -0
- package/src/utils.js +72 -0
- package/src/utils.js.map +1 -0
- package/src/utils.ts +26 -23
- package/tsconfig.json +0 -3
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type CreateSubscriptionRequest from "@rc-ex/core/src/definitions/CreateSubscriptionRequest";
|
|
2
|
+
import type SubscriptionInfo from "@rc-ex/core/src/definitions/SubscriptionInfo";
|
|
3
|
+
import type { RestResponse } from "@rc-ex/core/src/types";
|
|
4
|
+
import type { MessageEvent } from "ws";
|
|
5
|
+
|
|
6
|
+
import type { WebSocketExtensionInterface, WsgEvent, WsgMeta } from "./types";
|
|
7
|
+
import Utils from "./utils";
|
|
8
|
+
|
|
9
|
+
class Subscription {
|
|
10
|
+
public subscriptionInfo?: SubscriptionInfo;
|
|
11
|
+
|
|
12
|
+
public wse: WebSocketExtensionInterface;
|
|
13
|
+
|
|
14
|
+
public eventFilters: string[];
|
|
15
|
+
|
|
16
|
+
public eventListener: (event: MessageEvent) => void;
|
|
17
|
+
|
|
18
|
+
public timeout?: NodeJS.Timeout;
|
|
19
|
+
|
|
20
|
+
public enabled = true;
|
|
21
|
+
|
|
22
|
+
public constructor(
|
|
23
|
+
wse: WebSocketExtensionInterface,
|
|
24
|
+
eventFilters: string[],
|
|
25
|
+
callback: (event: {}) => void,
|
|
26
|
+
) {
|
|
27
|
+
this.wse = wse;
|
|
28
|
+
this.eventFilters = eventFilters;
|
|
29
|
+
this.eventListener = (mEvent: MessageEvent) => {
|
|
30
|
+
const event = mEvent as WsgEvent;
|
|
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
|
+
) {
|
|
37
|
+
callback(body);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
this.setupWsEventListener();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public setupWsEventListener() {
|
|
44
|
+
this.wse.ws.addEventListener("message", this.eventListener);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public get requestBody(): CreateSubscriptionRequest {
|
|
48
|
+
return {
|
|
49
|
+
deliveryMode: { transportType: "WebSocket" as any }, // because WebSocket is not in spec
|
|
50
|
+
eventFilters: this.eventFilters,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async subscribe() {
|
|
55
|
+
this.subscriptionInfo = (
|
|
56
|
+
await this.wse.request<SubscriptionInfo>(
|
|
57
|
+
"POST",
|
|
58
|
+
"/restapi/v1.0/subscription",
|
|
59
|
+
this.requestBody,
|
|
60
|
+
)
|
|
61
|
+
).data;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public async refresh() {
|
|
65
|
+
if (!this.subscriptionInfo) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
this.subscriptionInfo = (
|
|
70
|
+
await this.wse.request<SubscriptionInfo>(
|
|
71
|
+
"PUT",
|
|
72
|
+
`/restapi/v1.0/subscription/${this.subscriptionInfo!.id}`,
|
|
73
|
+
this.requestBody,
|
|
74
|
+
)
|
|
75
|
+
).data;
|
|
76
|
+
} catch (e) {
|
|
77
|
+
const re = e as { response: RestResponse };
|
|
78
|
+
if (re.response && re.response.status === 404) {
|
|
79
|
+
// subscription expired
|
|
80
|
+
await this.subscribe();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public async revoke() {
|
|
86
|
+
if (!this.subscriptionInfo) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
await this.wse.request<SubscriptionInfo>(
|
|
91
|
+
"DELETE",
|
|
92
|
+
`/restapi/v1.0/subscription/${this.subscriptionInfo!.id}`,
|
|
93
|
+
);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
const re = e as { response: RestResponse };
|
|
96
|
+
if (re.response && re.response.status === 404) {
|
|
97
|
+
// ignore
|
|
98
|
+
if (this.wse.options.debugMode) {
|
|
99
|
+
console.debug(
|
|
100
|
+
`Subscription ${
|
|
101
|
+
this.subscriptionInfo!.id
|
|
102
|
+
} doesn't exist on server side`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
} else if (re.response && re.response.status === 401) {
|
|
106
|
+
// ignore
|
|
107
|
+
if (this.wse.options.debugMode) {
|
|
108
|
+
console.debug("Token invalid when trying to revoke subscription");
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
throw e;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
this.remove();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public remove() {
|
|
118
|
+
if (this.timeout) {
|
|
119
|
+
global.clearTimeout(this.timeout);
|
|
120
|
+
this.timeout = undefined;
|
|
121
|
+
}
|
|
122
|
+
this.enabled = false;
|
|
123
|
+
this.subscriptionInfo = undefined;
|
|
124
|
+
if (this.wse.ws) {
|
|
125
|
+
this.wse.ws.removeEventListener("message", this.eventListener);
|
|
126
|
+
}
|
|
127
|
+
this.wse.subscription = undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export default Subscription;
|
package/lib/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type RingCentral from
|
|
2
|
-
import type { RestMethod, RestRequestConfig, RestResponse } from
|
|
3
|
-
import type WS from
|
|
1
|
+
import type RingCentral from "@rc-ex/core";
|
|
2
|
+
import type { RestMethod, RestRequestConfig, RestResponse } from "@rc-ex/core/src/types";
|
|
3
|
+
import type WS from "isomorphic-ws";
|
|
4
4
|
export interface WsToken {
|
|
5
5
|
uri: string;
|
|
6
6
|
ws_access_token: string;
|
|
@@ -25,7 +25,7 @@ export interface Wsc {
|
|
|
25
25
|
sequence: number;
|
|
26
26
|
}
|
|
27
27
|
export interface WsgMeta {
|
|
28
|
-
type:
|
|
28
|
+
type: "ClientRequest" | "ServerNotification" | "Error" | "ConnectionDetails" | "Heartbeat";
|
|
29
29
|
messageId: string;
|
|
30
30
|
status: number;
|
|
31
31
|
headers: {
|
|
@@ -45,7 +45,7 @@ export interface ConnectionDetails {
|
|
|
45
45
|
idleTimeout: number;
|
|
46
46
|
absoluteTimeout: number;
|
|
47
47
|
maxActiveRequests: number;
|
|
48
|
-
recoveryState?:
|
|
48
|
+
recoveryState?: "Successful" | "Failed";
|
|
49
49
|
recoveryErrorCode?: string;
|
|
50
50
|
}
|
|
51
51
|
export interface WebSocketExtensionInterface {
|
package/lib/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":""}
|
package/lib/types.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type RingCentral from "@rc-ex/core";
|
|
2
|
+
import type {
|
|
3
|
+
RestMethod,
|
|
4
|
+
RestRequestConfig,
|
|
5
|
+
RestResponse,
|
|
6
|
+
} from "@rc-ex/core/src/types";
|
|
7
|
+
import type WS from "isomorphic-ws";
|
|
8
|
+
|
|
9
|
+
export interface WsToken {
|
|
10
|
+
uri: string;
|
|
11
|
+
ws_access_token: string;
|
|
12
|
+
expires_in: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type CheckInterval = (retriesAttempted: number) => number;
|
|
16
|
+
export interface WebSocketOptions {
|
|
17
|
+
restOverWebSocket?: boolean;
|
|
18
|
+
debugMode?: boolean;
|
|
19
|
+
autoRecover?: {
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
checkInterval?: CheckInterval;
|
|
22
|
+
pingServerInterval?: number;
|
|
23
|
+
};
|
|
24
|
+
wscToken?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface WsgEvent {
|
|
28
|
+
data: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Wsc {
|
|
32
|
+
token: string;
|
|
33
|
+
sequence: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface WsgMeta {
|
|
37
|
+
type:
|
|
38
|
+
| "ClientRequest"
|
|
39
|
+
| "ServerNotification"
|
|
40
|
+
| "Error"
|
|
41
|
+
| "ConnectionDetails"
|
|
42
|
+
| "Heartbeat";
|
|
43
|
+
messageId: string;
|
|
44
|
+
status: number;
|
|
45
|
+
headers: {
|
|
46
|
+
[key: string]: string;
|
|
47
|
+
};
|
|
48
|
+
wsc?: Wsc;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface WsgError {
|
|
52
|
+
errorCode: string;
|
|
53
|
+
message: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ConnectionDetails {
|
|
57
|
+
creationTime: string;
|
|
58
|
+
maxConnectionsPerSession: number;
|
|
59
|
+
recoveryBufferSize: number;
|
|
60
|
+
recoveryTimeout: number;
|
|
61
|
+
idleTimeout: number;
|
|
62
|
+
absoluteTimeout: number;
|
|
63
|
+
maxActiveRequests: number;
|
|
64
|
+
recoveryState?: "Successful" | "Failed";
|
|
65
|
+
recoveryErrorCode?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface WebSocketExtensionInterface {
|
|
69
|
+
options: WebSocketOptions;
|
|
70
|
+
subscription?: SubscriptionInterface;
|
|
71
|
+
ws: WS;
|
|
72
|
+
wsToken?: WsToken;
|
|
73
|
+
rc: RingCentral;
|
|
74
|
+
request: <T>(
|
|
75
|
+
method: RestMethod,
|
|
76
|
+
endpoint: string,
|
|
77
|
+
content?: {},
|
|
78
|
+
queryParams?: {},
|
|
79
|
+
config?: RestRequestConfig,
|
|
80
|
+
) => Promise<RestResponse<T>>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface SubscriptionInterface {
|
|
84
|
+
eventFilters: string[];
|
|
85
|
+
}
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type WS from
|
|
2
|
-
import type {
|
|
1
|
+
import type WS from "isomorphic-ws";
|
|
2
|
+
import type { WsgEvent, WsgMeta } from "./types";
|
|
3
3
|
declare class Utils {
|
|
4
4
|
static splitWsgData(wsgData: string): [WsgMeta, any];
|
|
5
5
|
static debugWebSocket(_ws: WS): void;
|
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":["
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["utils.ts"],"names":[],"mappings":"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"}
|
package/lib/utils.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { MessageEvent } from "isomorphic-ws";
|
|
2
|
+
import type WS from "isomorphic-ws";
|
|
3
|
+
|
|
4
|
+
import type { WsgEvent, WsgMeta } from "./types";
|
|
5
|
+
import ClosedException from "./exceptions/ClosedException";
|
|
6
|
+
import TimeoutException from "./exceptions/TimeoutException";
|
|
7
|
+
|
|
8
|
+
class Utils {
|
|
9
|
+
public static splitWsgData(wsgData: string): [WsgMeta, any] {
|
|
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
|
+
];
|
|
16
|
+
}
|
|
17
|
+
return JSON.parse(wsgData);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public static debugWebSocket(_ws: WS) {
|
|
21
|
+
const ws = _ws;
|
|
22
|
+
const send = ws.send.bind(ws);
|
|
23
|
+
ws.send = async (str: string) => {
|
|
24
|
+
await send(str);
|
|
25
|
+
console.debug(
|
|
26
|
+
`*** WebSocket outgoing message: ***
|
|
27
|
+
${JSON.stringify(JSON.parse(str), null, 2)}
|
|
28
|
+
******`,
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
ws.addEventListener("message", (mEvent: MessageEvent) => {
|
|
32
|
+
const event = mEvent as WsgEvent;
|
|
33
|
+
console.debug(
|
|
34
|
+
`*** WebSocket incoming message: ***
|
|
35
|
+
${JSON.stringify(JSON.parse(event.data), null, 2)}
|
|
36
|
+
******`,
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
ws.addEventListener("open", (event) => {
|
|
40
|
+
console.debug("WebSocket open event:", event);
|
|
41
|
+
});
|
|
42
|
+
ws.addEventListener("error", (event) => {
|
|
43
|
+
console.debug("WebSocket error event:", event);
|
|
44
|
+
});
|
|
45
|
+
ws.addEventListener("close", (event) => {
|
|
46
|
+
console.debug("WebSocket close event:", event);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public static waitForWebSocketMessage(
|
|
51
|
+
ws: WS,
|
|
52
|
+
matchCondition: (meta: WsgMeta) => boolean,
|
|
53
|
+
timeout = 60000,
|
|
54
|
+
) {
|
|
55
|
+
return new Promise<[WsgMeta, any, WsgEvent]>((resolve, reject) => {
|
|
56
|
+
const checkHandle = setInterval(() => {
|
|
57
|
+
if (ws.readyState === ws.CLOSED) {
|
|
58
|
+
clearInterval(checkHandle);
|
|
59
|
+
reject(new ClosedException());
|
|
60
|
+
}
|
|
61
|
+
}, 1000);
|
|
62
|
+
const timeoutHandle = setTimeout(() => {
|
|
63
|
+
ws.removeEventListener("message", handler);
|
|
64
|
+
clearInterval(checkHandle);
|
|
65
|
+
reject(new TimeoutException());
|
|
66
|
+
}, timeout);
|
|
67
|
+
const handler = (mEvent: MessageEvent) => {
|
|
68
|
+
const event = mEvent as WsgEvent;
|
|
69
|
+
const [meta, body] = Utils.splitWsgData(event.data);
|
|
70
|
+
if (matchCondition(meta)) {
|
|
71
|
+
ws.removeEventListener("message", handler);
|
|
72
|
+
clearInterval(checkHandle);
|
|
73
|
+
clearTimeout(timeoutHandle);
|
|
74
|
+
resolve([meta, body, event]);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
ws.addEventListener("message", handler);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export default Utils;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rc-ex/ws",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
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",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"main": "
|
|
9
|
-
"types": "
|
|
8
|
+
"main": "src/index.js",
|
|
9
|
+
"types": "src/index.d.ts",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "git+https://github.com/ringcentral/ringcentral-extensible.git"
|
|
@@ -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.2"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "a0e465fd1f5d4b0758e6c3641635d5d2ee6c9a3a"
|
|
32
32
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class ClosedException extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message !== null && message !== void 0 ? message : "WebSocket has been closed");
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
exports.default = ClosedException;
|
|
9
|
+
//# sourceMappingURL=ClosedException.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClosedException.js","sourceRoot":"","sources":["ClosedException.ts"],"names":[],"mappings":";;AAAA,MAAM,eAAgB,SAAQ,KAAK;IACjC,YAAmB,OAAgB;QACjC,KAAK,CAAC,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,2BAA2B,CAAC,CAAC;IAChD,CAAC;CACF;AAED,kBAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const utils_1 = __importDefault(require("../utils"));
|
|
7
|
+
class ConnectionException extends Error {
|
|
8
|
+
constructor(wsgEvent) {
|
|
9
|
+
const [, wsgError] = utils_1.default.splitWsgData(wsgEvent.data);
|
|
10
|
+
super(JSON.stringify(wsgError, null, 2));
|
|
11
|
+
this.wsgEvent = wsgEvent;
|
|
12
|
+
this.wsgError = wsgError;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.default = ConnectionException;
|
|
16
|
+
//# sourceMappingURL=ConnectionException.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConnectionException.js","sourceRoot":"","sources":["ConnectionException.ts"],"names":[],"mappings":";;;;;AACA,qDAA6B;AAE7B,MAAM,mBAAoB,SAAQ,KAAK;IAKrC,YAAmB,QAAkB;QACnC,MAAM,CAAC,EAAE,QAAQ,CAAC,GAAwB,eAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC5E,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACzC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED,kBAAe,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class TimeoutException extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message !== null && message !== void 0 ? message : "Failed to receive expected WebSocket message in time.");
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
exports.default = TimeoutException;
|
|
9
|
+
//# sourceMappingURL=TimeoutException.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimeoutException.js","sourceRoot":"","sources":["TimeoutException.ts"],"names":[],"mappings":";;AAAA,MAAM,gBAAiB,SAAQ,KAAK;IAClC,YAAmB,OAAgB;QACjC,KAAK,CAAC,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,uDAAuD,CAAC,CAAC;IAC5E,CAAC;CACF;AAED,kBAAe,gBAAgB,CAAC"}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type RingCentral from "@rc-ex/core";
|
|
2
|
+
import SdkExtension from "@rc-ex/core/src/SdkExtension";
|
|
3
|
+
import WS from "isomorphic-ws";
|
|
4
|
+
import { EventEmitter } from "events";
|
|
5
|
+
import type SubscriptionInfo from "@rc-ex/core/src/definitions/SubscriptionInfo";
|
|
6
|
+
import { request } from "./rest";
|
|
7
|
+
import type { ConnectionDetails, WebSocketOptions, Wsc, WsToken } from "./types";
|
|
8
|
+
import Subscription from "./subscription";
|
|
9
|
+
export declare enum Events {
|
|
10
|
+
autoRecoverSuccess = "autoRecoverSuccess",
|
|
11
|
+
autoRecoverFailed = "autoRecoverFailed",
|
|
12
|
+
autoRecoverError = "autoRecoverError",
|
|
13
|
+
newWebSocketObject = "newWebSocketObject",
|
|
14
|
+
newWsc = "newWsc",
|
|
15
|
+
connectionReady = "connectionReady"
|
|
16
|
+
}
|
|
17
|
+
declare class WebSocketExtension extends SdkExtension {
|
|
18
|
+
eventEmitter: EventEmitter<[never]>;
|
|
19
|
+
options: WebSocketOptions;
|
|
20
|
+
rc: RingCentral;
|
|
21
|
+
wsToken?: WsToken;
|
|
22
|
+
wsTokenExpiresAt: number;
|
|
23
|
+
ws: WS;
|
|
24
|
+
connectionDetails: ConnectionDetails;
|
|
25
|
+
wsc?: Wsc;
|
|
26
|
+
subscription?: Subscription;
|
|
27
|
+
intervalHandle?: NodeJS.Timeout;
|
|
28
|
+
recoverTimestamp?: number;
|
|
29
|
+
pingServerHandle?: NodeJS.Timeout;
|
|
30
|
+
_recoverPromise?: Promise<void>;
|
|
31
|
+
_connectPromise?: Promise<void>;
|
|
32
|
+
request: typeof request;
|
|
33
|
+
constructor(options?: WebSocketOptions);
|
|
34
|
+
disable(): void;
|
|
35
|
+
install(rc: RingCentral): Promise<void>;
|
|
36
|
+
recover(): Promise<void>;
|
|
37
|
+
_recover(): Promise<void>;
|
|
38
|
+
pingServer(): Promise<void>;
|
|
39
|
+
connect(recoverSession?: boolean): Promise<void>;
|
|
40
|
+
_connect(recoverSession?: boolean): Promise<void>;
|
|
41
|
+
revoke(keepInterval?: boolean): Promise<void>;
|
|
42
|
+
subscribe(eventFilters: string[], callback: (event: {}) => void, cache?: SubscriptionInfo | undefined | null): Promise<Subscription>;
|
|
43
|
+
}
|
|
44
|
+
export default WebSocketExtension;
|