@rc-ex/ws 1.2.2 → 1.3.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/esm/exceptions/ClosedException.js +7 -0
- package/lib/esm/exceptions/ClosedException.js.map +1 -0
- package/{src → lib/esm}/exceptions/ConnectionException.d.ts +1 -1
- package/lib/esm/exceptions/ConnectionException.js +13 -0
- package/lib/esm/exceptions/ConnectionException.js.map +1 -0
- package/lib/esm/exceptions/TimeoutException.js +7 -0
- package/lib/esm/exceptions/TimeoutException.js.map +1 -0
- package/{src → lib/esm}/index.d.ts +5 -5
- package/lib/{index.js → esm/index.js} +62 -63
- package/lib/esm/index.js.map +1 -0
- package/{src → lib/esm}/rest.d.ts +2 -2
- package/lib/{rest.js → esm/rest.js} +10 -17
- package/lib/esm/rest.js.map +1 -0
- package/{src → lib/esm}/subscription.d.ts +3 -3
- package/{src → lib/esm}/subscription.js +9 -9
- package/lib/esm/subscription.js.map +1 -0
- package/{src → lib/esm}/types.d.ts +1 -1
- package/lib/esm/types.js +2 -0
- package/lib/esm/types.js.map +1 -0
- package/{src → lib/esm}/utils.d.ts +1 -1
- package/lib/{utils.js → esm/utils.js} +5 -10
- package/lib/esm/utils.js.map +1 -0
- package/lib/exceptions/ClosedException.cjs +26 -0
- package/lib/exceptions/ClosedException.js.map +1 -1
- package/lib/exceptions/ConnectionException.cjs +35 -0
- package/lib/exceptions/ConnectionException.d.ts +1 -1
- package/lib/exceptions/ConnectionException.js.map +1 -1
- package/lib/exceptions/TimeoutException.cjs +26 -0
- package/lib/exceptions/TimeoutException.js.map +1 -1
- package/lib/index.cjs +515 -0
- package/lib/index.d.ts +5 -5
- package/lib/index.js.map +1 -1
- package/lib/rest.cjs +104 -0
- package/lib/rest.d.ts +2 -2
- package/lib/rest.js.map +1 -1
- package/lib/subscription.cjs +178 -0
- package/lib/subscription.d.ts +3 -3
- package/lib/subscription.js.map +1 -1
- package/lib/types.d.ts +1 -1
- package/lib/types.js.map +1 -1
- package/lib/utils.cjs +116 -0
- package/lib/utils.d.ts +1 -1
- package/lib/utils.js.map +1 -1
- package/package.json +10 -5
- package/src/exceptions/ConnectionException.ts +2 -2
- package/src/index.ts +15 -12
- package/src/rest.ts +4 -4
- package/src/subscription.ts +9 -5
- package/src/types.ts +1 -1
- package/src/utils.ts +3 -3
- package/lib/exceptions/ClosedException.js +0 -9
- package/lib/exceptions/ClosedException.ts +0 -7
- package/lib/exceptions/ConnectionException.js +0 -16
- package/lib/exceptions/ConnectionException.ts +0 -17
- package/lib/exceptions/TimeoutException.js +0 -9
- package/lib/exceptions/TimeoutException.ts +0 -7
- package/lib/index.ts +0 -418
- package/lib/rest.ts +0 -71
- package/lib/subscription.js +0 -91
- package/lib/subscription.ts +0 -131
- package/lib/types.ts +0 -85
- package/lib/utils.ts +0 -82
- package/src/exceptions/ClosedException.js +0 -9
- package/src/exceptions/ClosedException.js.map +0 -1
- package/src/exceptions/ConnectionException.js +0 -16
- package/src/exceptions/ConnectionException.js.map +0 -1
- package/src/exceptions/TimeoutException.js +0 -9
- package/src/exceptions/TimeoutException.js.map +0 -1
- package/src/index.js +0 -329
- package/src/index.js.map +0 -1
- package/src/rest.js +0 -55
- package/src/rest.js.map +0 -1
- package/src/subscription.js.map +0 -1
- package/src/types.js +0 -3
- package/src/types.js.map +0 -1
- package/src/utils.js +0 -72
- package/src/utils.js.map +0 -1
- /package/{src → lib/esm}/exceptions/ClosedException.d.ts +0 -0
- /package/{src → lib/esm}/exceptions/TimeoutException.d.ts +0 -0
- /package/lib/{types.js → types.cjs} +0 -0
package/lib/subscription.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
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.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
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.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
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;
|
|
@@ -1,9 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,16 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,9 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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.js
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
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
|
-
exports.Events = void 0;
|
|
7
|
-
const SdkExtension_1 = __importDefault(require("@rc-ex/core/src/SdkExtension"));
|
|
8
|
-
const isomorphic_ws_1 = __importDefault(require("isomorphic-ws"));
|
|
9
|
-
const hyperid_1 = __importDefault(require("hyperid"));
|
|
10
|
-
const events_1 = require("events");
|
|
11
|
-
const wait_for_async_1 = __importDefault(require("wait-for-async"));
|
|
12
|
-
const RestException_1 = __importDefault(require("@rc-ex/core/src/RestException"));
|
|
13
|
-
const rest_1 = require("./rest");
|
|
14
|
-
const subscription_1 = __importDefault(require("./subscription"));
|
|
15
|
-
const ConnectionException_1 = __importDefault(require("./exceptions/ConnectionException"));
|
|
16
|
-
const utils_1 = __importDefault(require("./utils"));
|
|
17
|
-
const CONNECTING = 0;
|
|
18
|
-
const OPEN = 1;
|
|
19
|
-
const uuid = (0, hyperid_1.default)();
|
|
20
|
-
var Events;
|
|
21
|
-
(function (Events) {
|
|
22
|
-
Events["autoRecoverSuccess"] = "autoRecoverSuccess";
|
|
23
|
-
Events["autoRecoverFailed"] = "autoRecoverFailed";
|
|
24
|
-
Events["autoRecoverError"] = "autoRecoverError";
|
|
25
|
-
Events["newWebSocketObject"] = "newWebSocketObject";
|
|
26
|
-
Events["newWsc"] = "newWsc";
|
|
27
|
-
Events["connectionReady"] = "connectionReady";
|
|
28
|
-
})(Events || (exports.Events = Events = {}));
|
|
29
|
-
class WebSocketExtension extends SdkExtension_1.default {
|
|
30
|
-
constructor(options = {}) {
|
|
31
|
-
var _a, _b, _c, _d, _e;
|
|
32
|
-
var _f, _g, _h, _j, _k;
|
|
33
|
-
super();
|
|
34
|
-
this.eventEmitter = new events_1.EventEmitter();
|
|
35
|
-
this.wsTokenExpiresAt = 0;
|
|
36
|
-
this.request = rest_1.request; // request method was moved to another file to keep this file short
|
|
37
|
-
this.options = options;
|
|
38
|
-
(_a = (_f = this.options).restOverWebSocket) !== null && _a !== void 0 ? _a : (_f.restOverWebSocket = false);
|
|
39
|
-
(_b = (_g = this.options).debugMode) !== null && _b !== void 0 ? _b : (_g.debugMode = false);
|
|
40
|
-
(_c = (_h = this.options).autoRecover) !== null && _c !== void 0 ? _c : (_h.autoRecover = {
|
|
41
|
-
enabled: true,
|
|
42
|
-
});
|
|
43
|
-
(_d = (_j = this.options.autoRecover).checkInterval) !== null && _d !== void 0 ? _d : (_j.checkInterval = (retriesAttempted) => {
|
|
44
|
-
const interval = 2000 + 2000 * retriesAttempted;
|
|
45
|
-
return Math.min(8000, interval);
|
|
46
|
-
});
|
|
47
|
-
(_e = (_k = this.options.autoRecover).pingServerInterval) !== null && _e !== void 0 ? _e : (_k.pingServerInterval = 60000);
|
|
48
|
-
}
|
|
49
|
-
disable() {
|
|
50
|
-
super.disable();
|
|
51
|
-
if (this.subscription) {
|
|
52
|
-
this.subscription.enabled = false;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
async install(rc) {
|
|
56
|
-
this.rc = rc;
|
|
57
|
-
if (this.options.restOverWebSocket) {
|
|
58
|
-
const request = rc.request.bind(rc);
|
|
59
|
-
rc.request = async (method, endpoint, content, queryParams, config) => {
|
|
60
|
-
var _a, _b, _c;
|
|
61
|
-
if (!this.enabled || !this.options.restOverWebSocket) {
|
|
62
|
-
return request(method, endpoint, content, queryParams, config);
|
|
63
|
-
}
|
|
64
|
-
if (
|
|
65
|
-
// the following cannot be done with WebSocket
|
|
66
|
-
((_c = (_b = (_a = config === null || config === void 0 ? void 0 : config.headers) === null || _a === void 0 ? void 0 : _a.getContentType) === null || _b === void 0 ? void 0 : _b.toString()) === null || _c === void 0 ? void 0 : _c.includes("multipart/form-data")) ||
|
|
67
|
-
(config === null || config === void 0 ? void 0 : config.responseType) === "arraybuffer" ||
|
|
68
|
-
endpoint.startsWith("/restapi/oauth/") // token, revoke, wstoken
|
|
69
|
-
) {
|
|
70
|
-
return request(method, endpoint, content, queryParams, config);
|
|
71
|
-
}
|
|
72
|
-
return this.request(method, endpoint, content, queryParams, config);
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
// should recover if this.options.wscToken
|
|
76
|
-
let connectMethod = this.connect.bind(this);
|
|
77
|
-
if (this.options.wscToken) {
|
|
78
|
-
this.wsc = {
|
|
79
|
-
token: this.options.wscToken,
|
|
80
|
-
sequence: 0,
|
|
81
|
-
};
|
|
82
|
-
connectMethod = this.recover.bind(this);
|
|
83
|
-
}
|
|
84
|
-
if (!this.options.autoRecover.enabled) {
|
|
85
|
-
await connectMethod();
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
// code after is for auto recover
|
|
89
|
-
try {
|
|
90
|
-
await connectMethod();
|
|
91
|
-
}
|
|
92
|
-
catch (e) {
|
|
93
|
-
if (e instanceof RestException_1.default) {
|
|
94
|
-
throw e; // such as InsufficientPermissions
|
|
95
|
-
}
|
|
96
|
-
if (this.options.debugMode) {
|
|
97
|
-
console.debug("Initial connect failed:", e);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
let retriesAttempted = 0;
|
|
101
|
-
let checking = false;
|
|
102
|
-
const check = async () => {
|
|
103
|
-
var _a, _b, _c;
|
|
104
|
-
if (!this.enabled) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (((_a = this.options.autoRecover) === null || _a === void 0 ? void 0 : _a.enabled) !== true) {
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (checking) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
checking = true;
|
|
114
|
-
if (((_b = this.ws) === null || _b === void 0 ? void 0 : _b.readyState) !== OPEN && ((_c = this.ws) === null || _c === void 0 ? void 0 : _c.readyState) !== CONNECTING) {
|
|
115
|
-
clearInterval(this.intervalHandle);
|
|
116
|
-
try {
|
|
117
|
-
await this.recover();
|
|
118
|
-
retriesAttempted = 0;
|
|
119
|
-
if (this.options.debugMode) {
|
|
120
|
-
console.debug(`Auto recover done, recoveryState: ${this.connectionDetails.recoveryState}`);
|
|
121
|
-
}
|
|
122
|
-
this.eventEmitter.emit(this.connectionDetails.recoveryState === "Successful"
|
|
123
|
-
? Events.autoRecoverSuccess
|
|
124
|
-
: Events.autoRecoverFailed, this.ws);
|
|
125
|
-
}
|
|
126
|
-
catch (e) {
|
|
127
|
-
if (e instanceof RestException_1.default) {
|
|
128
|
-
throw e; // such as InsufficientPermissions
|
|
129
|
-
}
|
|
130
|
-
retriesAttempted += 1;
|
|
131
|
-
if (this.options.debugMode) {
|
|
132
|
-
console.debug("Auto recover error:", e);
|
|
133
|
-
}
|
|
134
|
-
this.eventEmitter.emit(Events.autoRecoverError, e);
|
|
135
|
-
}
|
|
136
|
-
this.intervalHandle = setInterval(check, this.options.autoRecover.checkInterval(retriesAttempted));
|
|
137
|
-
}
|
|
138
|
-
checking = false;
|
|
139
|
-
};
|
|
140
|
-
this.intervalHandle = setInterval(check, this.options.autoRecover.checkInterval(retriesAttempted));
|
|
141
|
-
// browser only code start
|
|
142
|
-
if (typeof window !== "undefined" && window.addEventListener) {
|
|
143
|
-
window.addEventListener("offline", () => {
|
|
144
|
-
var _a;
|
|
145
|
-
if (this.pingServerHandle) {
|
|
146
|
-
clearTimeout(this.pingServerHandle);
|
|
147
|
-
}
|
|
148
|
-
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.close();
|
|
149
|
-
});
|
|
150
|
-
window.addEventListener("online", () => {
|
|
151
|
-
check();
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
// browser only code end
|
|
155
|
-
}
|
|
156
|
-
async recover() {
|
|
157
|
-
if (this._recoverPromise) {
|
|
158
|
-
return this._recoverPromise;
|
|
159
|
-
}
|
|
160
|
-
this._recoverPromise = this._recover();
|
|
161
|
-
try {
|
|
162
|
-
await this._recoverPromise;
|
|
163
|
-
}
|
|
164
|
-
finally {
|
|
165
|
-
this._recoverPromise = undefined;
|
|
166
|
-
}
|
|
167
|
-
return undefined;
|
|
168
|
-
}
|
|
169
|
-
async _recover() {
|
|
170
|
-
var _a, _b;
|
|
171
|
-
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === OPEN || ((_b = this.ws) === null || _b === void 0 ? void 0 : _b.readyState) === CONNECTING) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (!this.wsc || !this.wsc.token) {
|
|
175
|
-
await this.connect(false); // connect to WSG but do not recover
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
if (this.recoverTimestamp === undefined) {
|
|
179
|
-
this.recoverTimestamp = Date.now();
|
|
180
|
-
}
|
|
181
|
-
if (this.connectionDetails !== undefined &&
|
|
182
|
-
Date.now() - this.recoverTimestamp >
|
|
183
|
-
this.connectionDetails.recoveryTimeout * 1000) {
|
|
184
|
-
if (this.options.debugMode) {
|
|
185
|
-
console.debug("connect to WSG but do not recover");
|
|
186
|
-
}
|
|
187
|
-
await this.connect(false); // connect to WSG but do not recover
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
if (this.options.debugMode) {
|
|
191
|
-
console.debug("connect to WSG and recover");
|
|
192
|
-
}
|
|
193
|
-
await this.connect(true); // connect to WSG and recover
|
|
194
|
-
}
|
|
195
|
-
this.recoverTimestamp = undefined;
|
|
196
|
-
this.enable();
|
|
197
|
-
}
|
|
198
|
-
async pingServer() {
|
|
199
|
-
var _a, _b;
|
|
200
|
-
if (((_a = this.options.autoRecover) === null || _a === void 0 ? void 0 : _a.enabled) !== true) {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (((_b = this.ws) === null || _b === void 0 ? void 0 : _b.readyState) !== OPEN) {
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
try {
|
|
207
|
-
await this.ws.send(JSON.stringify([
|
|
208
|
-
{
|
|
209
|
-
type: "Heartbeat",
|
|
210
|
-
messageId: uuid(),
|
|
211
|
-
},
|
|
212
|
-
]));
|
|
213
|
-
}
|
|
214
|
-
catch (e) {
|
|
215
|
-
this.ws.close(); // Explicitly mark WS as closed
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
async connect(recoverSession) {
|
|
219
|
-
if (this._connectPromise) {
|
|
220
|
-
return this._connectPromise;
|
|
221
|
-
}
|
|
222
|
-
this._connectPromise = this._connect(recoverSession);
|
|
223
|
-
try {
|
|
224
|
-
await this._connectPromise;
|
|
225
|
-
}
|
|
226
|
-
finally {
|
|
227
|
-
this._connectPromise = undefined;
|
|
228
|
-
}
|
|
229
|
-
return undefined;
|
|
230
|
-
}
|
|
231
|
-
async _connect(recoverSession = false) {
|
|
232
|
-
var _a;
|
|
233
|
-
if (!this.wsToken || Date.now() > this.wsTokenExpiresAt) {
|
|
234
|
-
const r = await this.rc.post("/restapi/oauth/wstoken");
|
|
235
|
-
this.wsToken = r.data;
|
|
236
|
-
// `expires_in` default value is 600 seconds. That's why we `* 0.8`
|
|
237
|
-
this.wsTokenExpiresAt = Date.now() + this.wsToken.expires_in * 0.8 * 1000;
|
|
238
|
-
}
|
|
239
|
-
let wsUri = `${this.wsToken.uri}?access_token=${this.wsToken.ws_access_token}`;
|
|
240
|
-
if (recoverSession && this.wsc) {
|
|
241
|
-
wsUri += `&wsc=${this.wsc.token}`;
|
|
242
|
-
}
|
|
243
|
-
this.ws = new isomorphic_ws_1.default(wsUri);
|
|
244
|
-
this.eventEmitter.emit(Events.newWebSocketObject, this.ws);
|
|
245
|
-
// override send method to wait for connecting
|
|
246
|
-
const send = this.ws.send.bind(this.ws);
|
|
247
|
-
this.ws.send = async (s) => {
|
|
248
|
-
if (this.ws.readyState === CONNECTING) {
|
|
249
|
-
await (0, wait_for_async_1.default)({
|
|
250
|
-
interval: 100,
|
|
251
|
-
condition: () => this.ws.readyState !== CONNECTING,
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
await send(s);
|
|
255
|
-
};
|
|
256
|
-
if ((_a = this.options.autoRecover) === null || _a === void 0 ? void 0 : _a.enabled) {
|
|
257
|
-
this.ws.addEventListener("message", () => {
|
|
258
|
-
if (this.pingServerHandle) {
|
|
259
|
-
clearTimeout(this.pingServerHandle);
|
|
260
|
-
}
|
|
261
|
-
this.pingServerHandle = setTimeout(() => this.pingServer(), this.options.autoRecover.pingServerInterval);
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
// debug mode to print all WebSocket traffic
|
|
265
|
-
if (this.options.debugMode) {
|
|
266
|
-
utils_1.default.debugWebSocket(this.ws);
|
|
267
|
-
}
|
|
268
|
-
// listen for new wsc data
|
|
269
|
-
this.ws.addEventListener("message", (mEvent) => {
|
|
270
|
-
const event = mEvent;
|
|
271
|
-
const [meta, body] = utils_1.default.splitWsgData(event.data);
|
|
272
|
-
if (meta.wsc &&
|
|
273
|
-
(!this.wsc ||
|
|
274
|
-
(meta.type === "ConnectionDetails" && body.recoveryState) ||
|
|
275
|
-
this.wsc.sequence < meta.wsc.sequence)) {
|
|
276
|
-
this.wsc = meta.wsc;
|
|
277
|
-
this.eventEmitter.emit(Events.newWsc, this.wsc);
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
// get initial ConnectionDetails data
|
|
281
|
-
const [meta, body, event] = await utils_1.default.waitForWebSocketMessage(this.ws, (meta) => meta.type === "ConnectionDetails" || meta.type === "Error");
|
|
282
|
-
if (meta.type === "Error") {
|
|
283
|
-
throw new ConnectionException_1.default(event);
|
|
284
|
-
}
|
|
285
|
-
this.connectionDetails = body;
|
|
286
|
-
// fired when ws connection is ready for creating subscription
|
|
287
|
-
this.eventEmitter.emit(Events.connectionReady, this.ws);
|
|
288
|
-
// recover the subscription, if it exists and enabled
|
|
289
|
-
if (this.subscription && this.subscription.enabled) {
|
|
290
|
-
// because we have a new ws object
|
|
291
|
-
this.subscription.setupWsEventListener();
|
|
292
|
-
if (!recoverSession || this.connectionDetails.recoveryState === "Failed") {
|
|
293
|
-
// create new subscription if don't recover existing one
|
|
294
|
-
await this.subscription.subscribe();
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
// keepInterval means we do not clear the interval
|
|
299
|
-
async revoke(keepInterval = false) {
|
|
300
|
-
var _a, _b;
|
|
301
|
-
await ((_a = this.subscription) === null || _a === void 0 ? void 0 : _a.revoke());
|
|
302
|
-
this.subscription = undefined;
|
|
303
|
-
if (!keepInterval && this.intervalHandle) {
|
|
304
|
-
clearInterval(this.intervalHandle);
|
|
305
|
-
}
|
|
306
|
-
if (this.pingServerHandle) {
|
|
307
|
-
clearTimeout(this.pingServerHandle);
|
|
308
|
-
}
|
|
309
|
-
(_b = this.ws) === null || _b === void 0 ? void 0 : _b.close();
|
|
310
|
-
this.wsc = undefined;
|
|
311
|
-
this.wsToken = undefined;
|
|
312
|
-
this.wsTokenExpiresAt = 0;
|
|
313
|
-
this.disable();
|
|
314
|
-
}
|
|
315
|
-
async subscribe(eventFilters, callback, cache = undefined) {
|
|
316
|
-
const subscription = new subscription_1.default(this, eventFilters, callback);
|
|
317
|
-
if (cache === undefined || cache === null) {
|
|
318
|
-
await subscription.subscribe();
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
subscription.subscriptionInfo = cache;
|
|
322
|
-
await subscription.refresh();
|
|
323
|
-
}
|
|
324
|
-
this.subscription = subscription;
|
|
325
|
-
return subscription;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
exports.default = WebSocketExtension;
|
|
329
|
-
//# sourceMappingURL=index.js.map
|