@webiny/app-websockets 0.0.0-unstable.eb196ccd2f → 0.0.0-unstable.eb81deee7e
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 +8 -9
- package/WebsocketsContextProvider.d.ts +1 -1
- package/WebsocketsContextProvider.js +137 -154
- package/WebsocketsContextProvider.js.map +1 -1
- package/domain/WebsocketsAction.d.ts +1 -1
- package/domain/WebsocketsAction.js +17 -24
- package/domain/WebsocketsAction.js.map +1 -1
- package/domain/WebsocketsActions.d.ts +1 -3
- package/domain/WebsocketsActions.js +52 -78
- package/domain/WebsocketsActions.js.map +1 -1
- package/domain/WebsocketsConnection.d.ts +31 -4
- package/domain/WebsocketsConnection.js +162 -107
- package/domain/WebsocketsConnection.js.map +1 -1
- package/domain/WebsocketsManager.d.ts +1 -1
- package/domain/WebsocketsManager.js +33 -34
- package/domain/WebsocketsManager.js.map +1 -1
- package/domain/WebsocketsSubscriptionManager.d.ts +2 -2
- package/domain/WebsocketsSubscriptionManager.js +58 -65
- package/domain/WebsocketsSubscriptionManager.js.map +1 -1
- package/domain/abstractions/IWebsocketsAction.d.ts +1 -1
- package/domain/abstractions/IWebsocketsAction.js +0 -3
- package/domain/abstractions/IWebsocketsActions.d.ts +2 -2
- package/domain/abstractions/IWebsocketsActions.js +0 -3
- package/domain/abstractions/IWebsocketsConnection.d.ts +2 -2
- package/domain/abstractions/IWebsocketsConnection.js +7 -6
- package/domain/abstractions/IWebsocketsConnection.js.map +1 -1
- package/domain/abstractions/IWebsocketsManager.d.ts +2 -6
- package/domain/abstractions/IWebsocketsManager.js +0 -3
- package/domain/abstractions/IWebsocketsSubscriptionManager.d.ts +2 -2
- package/domain/abstractions/IWebsocketsSubscriptionManager.js +0 -3
- package/domain/abstractions/types.d.ts +1 -1
- package/domain/abstractions/types.js +19 -18
- package/domain/abstractions/types.js.map +1 -1
- package/domain/index.d.ts +5 -5
- package/domain/index.js +5 -7
- package/domain/types.d.ts +6 -6
- package/domain/types.js +2 -8
- package/events/WebsocketEvent.d.ts +10 -0
- package/events/WebsocketEvent.js +13 -0
- package/events/WebsocketEvent.js.map +1 -0
- package/events/abstractions.d.ts +13 -0
- package/events/abstractions.js +5 -0
- package/events/abstractions.js.map +1 -0
- package/exports/admin/websockets.d.ts +1 -0
- package/exports/admin/websockets.js +1 -0
- package/hooks/index.d.ts +1 -1
- package/hooks/index.js +1 -3
- package/hooks/useWebsockets.d.ts +1 -1
- package/hooks/useWebsockets.js +6 -7
- package/hooks/useWebsockets.js.map +1 -1
- package/index.d.ts +6 -3
- package/index.js +14 -15
- package/index.js.map +1 -1
- package/package.json +21 -19
- package/types.d.ts +2 -2
- package/types.js +1 -4
- package/domain/abstractions/IWebsocketsAction.js.map +0 -1
- package/domain/abstractions/IWebsocketsActions.js.map +0 -1
- package/domain/abstractions/IWebsocketsManager.js.map +0 -1
- package/domain/abstractions/IWebsocketsSubscriptionManager.js.map +0 -1
- package/domain/index.js.map +0 -1
- package/domain/types.js.map +0 -1
- package/hooks/index.js.map +0 -1
- package/types.js.map +0 -1
- package/utils/getUrl.d.ts +0 -1
- package/utils/getUrl.js +0 -6
- package/utils/getUrl.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"domain/WebsocketsActions.js","sources":["../../src/domain/WebsocketsActions.ts"],"sourcesContent":["import type {\n IGenericData,\n IWebsocketsActions,\n IWebsocketsActionsRunParams,\n IWebsocketsManager,\n IWebsocketManagerSendData\n} from \"./types.js\";\n\nexport interface IWebsocketActionsParams {\n manager: IWebsocketsManager;\n tenant: string | null;\n getToken: () => Promise<string | undefined>;\n}\n\nexport class WebsocketsActions implements IWebsocketsActions {\n public readonly manager: IWebsocketsManager;\n\n private readonly getToken: () => Promise<string | undefined>;\n private readonly tenant: string | null;\n\n public constructor(params: IWebsocketActionsParams) {\n this.manager = params.manager;\n this.tenant = params.tenant;\n this.getToken = params.getToken;\n }\n\n public async run<T extends IGenericData = IGenericData, R extends IGenericData = IGenericData>(\n params: IWebsocketsActionsRunParams<T>\n ): Promise<R | null> {\n const { action, timeout, data } = params;\n const token = await this.getToken();\n if (!token) {\n console.error(\"Token is not set - cannot send a websocket message.\");\n return null;\n } else if (!this.tenant) {\n console.error(\"Tenant is not set - cannot send a websocket message.\");\n return null;\n }\n\n /**\n * If no timeout was sent, we will just send the message and return null.\n * No waiting for the response.\n */\n if (!timeout || timeout < 0) {\n this.manager.send<IWebsocketManagerSendData<T>>({\n /**\n * It is ok to cast as we are checking the values a few lines above.\n */\n token,\n tenant: this.tenant as string,\n action,\n data: data || ({} as T)\n });\n return null;\n }\n /**\n * In case of a timeout, we will send the message and wait for the response.\n */\n return await new Promise<R>((resolve, reject) => {\n let promiseTimeout: NodeJS.Timeout | null = null;\n const subscription = this.manager.onMessage<R>(async event => {\n if (event.data.messageId !== subscription.id) {\n return;\n }\n resolve(event.data);\n subscription.off();\n if (!promiseTimeout) {\n return;\n }\n clearTimeout(promiseTimeout);\n });\n\n promiseTimeout = setTimeout(() => {\n const message = `Websocket action \"${action}\" timeout.`;\n subscription.off();\n reject(new Error(message));\n }, timeout);\n\n this.manager.send<IWebsocketManagerSendData<T>>({\n /**\n * It is ok to cast as we are checking the values a few lines above.\n */\n token,\n tenant: this.tenant as string,\n messageId: subscription.id,\n action,\n data: data || ({} as T)\n });\n }).catch(ex => {\n console.error(\"Error while sending websocket message.\", ex);\n return null;\n });\n }\n}\n\nexport const createWebsocketsActions = (params: IWebsocketActionsParams): IWebsocketsActions => {\n return new WebsocketsActions(params);\n};\n"],"names":["WebsocketsActions","params","action","timeout","data","token","console","Promise","resolve","reject","promiseTimeout","subscription","event","clearTimeout","setTimeout","message","Error","ex","createWebsocketsActions"],"mappings":"AAcO,MAAMA;IAMT,YAAmBC,MAA+B,CAAE;QAChD,IAAI,CAAC,OAAO,GAAGA,OAAO,OAAO;QAC7B,IAAI,CAAC,MAAM,GAAGA,OAAO,MAAM;QAC3B,IAAI,CAAC,QAAQ,GAAGA,OAAO,QAAQ;IACnC;IAEA,MAAa,IACTA,MAAsC,EACrB;QACjB,MAAM,EAAEC,MAAM,EAAEC,OAAO,EAAEC,IAAI,EAAE,GAAGH;QAClC,MAAMI,QAAQ,MAAM,IAAI,CAAC,QAAQ;QACjC,IAAKA,OAGE;YAAA,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;gBACrBC,QAAQ,KAAK,CAAC;gBACd,OAAO;YACX;QAAA,OANY;YACRA,QAAQ,KAAK,CAAC;YACd,OAAO;QACX;QASA,IAAI,CAACH,WAAWA,UAAU,GAAG;YACzB,IAAI,CAAC,OAAO,CAAC,IAAI,CAA+B;gBAI5CE;gBACA,QAAQ,IAAI,CAAC,MAAM;gBACnBH;gBACA,MAAME,QAAS,CAAC;YACpB;YACA,OAAO;QACX;QAIA,OAAO,MAAM,IAAIG,QAAW,CAACC,SAASC;YAClC,IAAIC,iBAAwC;YAC5C,MAAMC,eAAe,IAAI,CAAC,OAAO,CAAC,SAAS,CAAI,OAAMC;gBACjD,IAAIA,MAAM,IAAI,CAAC,SAAS,KAAKD,aAAa,EAAE,EACxC;gBAEJH,QAAQI,MAAM,IAAI;gBAClBD,aAAa,GAAG;gBAChB,IAAI,CAACD,gBACD;gBAEJG,aAAaH;YACjB;YAEAA,iBAAiBI,WAAW;gBACxB,MAAMC,UAAU,CAAC,kBAAkB,EAAEb,OAAO,UAAU,CAAC;gBACvDS,aAAa,GAAG;gBAChBF,OAAO,IAAIO,MAAMD;YACrB,GAAGZ;YAEH,IAAI,CAAC,OAAO,CAAC,IAAI,CAA+B;gBAI5CE;gBACA,QAAQ,IAAI,CAAC,MAAM;gBACnB,WAAWM,aAAa,EAAE;gBAC1BT;gBACA,MAAME,QAAS,CAAC;YACpB;QACJ,GAAG,KAAK,CAACa,CAAAA;YACLX,QAAQ,KAAK,CAAC,0CAA0CW;YACxD,OAAO;QACX;IACJ;AACJ;AAEO,MAAMC,0BAA0B,CAACjB,SAC7B,IAAID,kBAAkBC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IGenericData, IWebsocketsConnection, IWebsocketsConnectionFactory, IWebsocketsConnectProtocol, IWebsocketsSubscriptionManager, WebsocketsCloseCode } from "./types";
|
|
1
|
+
import type { IGenericData, IWebsocketsConnection, IWebsocketsConnectionFactory, IWebsocketsConnectProtocol, IWebsocketsSubscriptionManager, WebsocketsCloseCode } from "./types.js";
|
|
2
2
|
interface IConnection {
|
|
3
3
|
ws: WebSocket | null;
|
|
4
4
|
}
|
|
@@ -10,26 +10,43 @@ declare global {
|
|
|
10
10
|
WebinyWebsocketsConnectionCache: IConnection;
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* API Gateway closes a WebSocket after 10 minutes without traffic (and after 2 hours regardless), so
|
|
15
|
+
* we ping well inside that window. The ping has no route of its own and lands on `$default`.
|
|
16
|
+
*/
|
|
17
|
+
export declare const WEBSOCKETS_HEARTBEAT_INTERVAL: number;
|
|
18
|
+
export declare const WEBSOCKETS_HEARTBEAT_ACTION = "ping";
|
|
13
19
|
export interface IWebsocketsConnectionParams {
|
|
14
20
|
url: string;
|
|
15
21
|
tenant: string;
|
|
16
|
-
locale: string;
|
|
17
22
|
getToken(): Promise<string | undefined>;
|
|
18
23
|
subscriptionManager: IWebsocketsSubscriptionManager;
|
|
19
24
|
protocol?: IWebsocketsConnectProtocol;
|
|
20
25
|
factory?: IWebsocketsConnectionFactory;
|
|
26
|
+
heartbeatInterval?: number;
|
|
27
|
+
reconnectDelay?: (attempt: number) => number;
|
|
21
28
|
}
|
|
22
29
|
export declare class WebsocketsConnection implements IWebsocketsConnection {
|
|
23
30
|
private readonly url;
|
|
24
31
|
private readonly getToken;
|
|
25
32
|
private tenant;
|
|
26
|
-
private locale;
|
|
27
33
|
private readonly protocol;
|
|
28
34
|
readonly subscriptionManager: IWebsocketsSubscriptionManager;
|
|
29
35
|
private readonly factory;
|
|
36
|
+
private readonly heartbeatInterval;
|
|
37
|
+
private readonly reconnectDelay;
|
|
38
|
+
private reconnectAttempt;
|
|
39
|
+
private reconnectTimer;
|
|
40
|
+
private heartbeatTimer;
|
|
41
|
+
/**
|
|
42
|
+
* Opening a socket waits for a token first. `connecting` lets concurrent callers (a reconnect
|
|
43
|
+
* timer, the window-focus handler, `send()`) share that one attempt instead of each creating a
|
|
44
|
+
* socket, and `generation` lets `close()` invalidate an attempt that is already waiting.
|
|
45
|
+
*/
|
|
46
|
+
private connecting;
|
|
47
|
+
private generation;
|
|
30
48
|
constructor(params: IWebsocketsConnectionParams);
|
|
31
49
|
setTenant(tenant: string): void;
|
|
32
|
-
setLocale(locale: string): void;
|
|
33
50
|
connect(): Promise<void>;
|
|
34
51
|
close(code: WebsocketsCloseCode, reason: string): Promise<boolean>;
|
|
35
52
|
send<T extends IGenericData = IGenericData>(data: T): Promise<void>;
|
|
@@ -37,6 +54,16 @@ export declare class WebsocketsConnection implements IWebsocketsConnection {
|
|
|
37
54
|
isClosed(): boolean;
|
|
38
55
|
private createUrl;
|
|
39
56
|
private getConnection;
|
|
57
|
+
/**
|
|
58
|
+
* Resolves to `null` when `close()` ran while the token was being fetched: the attempt is
|
|
59
|
+
* abandoned rather than opening a socket nobody asked for any more.
|
|
60
|
+
*/
|
|
61
|
+
private openConnection;
|
|
62
|
+
private scheduleReconnect;
|
|
63
|
+
private cancelReconnect;
|
|
64
|
+
private startHeartbeat;
|
|
65
|
+
private stopHeartbeat;
|
|
66
|
+
private sendHeartbeat;
|
|
40
67
|
}
|
|
41
68
|
export declare const createWebsocketsConnection: (params: IWebsocketsConnectionParams) => IWebsocketsConnection;
|
|
42
69
|
export {};
|
|
@@ -1,118 +1,173 @@
|
|
|
1
|
-
import { WebsocketsReadyState } from "./types";
|
|
2
|
-
const defaultFactory = (url, protocol)
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* We need to attach the websockets cache to window object, or it will be reset on every hot reload.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
if (!window.WebinyWebsocketsConnectionCache) {
|
|
11
|
-
window.WebinyWebsocketsConnectionCache = {
|
|
1
|
+
import { WebsocketsReadyState } from "./types.js";
|
|
2
|
+
const defaultFactory = (url, protocol)=>new WebSocket(url, protocol);
|
|
3
|
+
if (!window.WebinyWebsocketsConnectionCache) window.WebinyWebsocketsConnectionCache = {
|
|
12
4
|
ws: null
|
|
13
|
-
|
|
14
|
-
}
|
|
5
|
+
};
|
|
15
6
|
const connectionCache = window.WebinyWebsocketsConnectionCache;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
7
|
+
const WEBSOCKETS_HEARTBEAT_INTERVAL = 300000;
|
|
8
|
+
const WEBSOCKETS_HEARTBEAT_ACTION = "ping";
|
|
9
|
+
const MAX_RECONNECT_DELAY = 30000;
|
|
10
|
+
const defaultReconnectDelay = (attempt)=>Math.min(1000 * 2 ** attempt, MAX_RECONNECT_DELAY);
|
|
11
|
+
class WebsocketsConnection {
|
|
12
|
+
constructor(params){
|
|
13
|
+
this.reconnectAttempt = 0;
|
|
14
|
+
this.reconnectTimer = null;
|
|
15
|
+
this.heartbeatTimer = null;
|
|
16
|
+
this.connecting = null;
|
|
17
|
+
this.generation = 0;
|
|
18
|
+
this.url = params.url;
|
|
19
|
+
this.tenant = params.tenant;
|
|
20
|
+
this.getToken = params.getToken;
|
|
21
|
+
this.protocol = params.protocol;
|
|
22
|
+
this.subscriptionManager = params.subscriptionManager;
|
|
23
|
+
this.factory = params.factory || defaultFactory;
|
|
24
|
+
this.heartbeatInterval = params.heartbeatInterval ?? WEBSOCKETS_HEARTBEAT_INTERVAL;
|
|
25
|
+
this.reconnectDelay = params.reconnectDelay ?? defaultReconnectDelay;
|
|
26
|
+
}
|
|
27
|
+
setTenant(tenant) {
|
|
28
|
+
this.tenant = tenant;
|
|
29
|
+
}
|
|
30
|
+
async connect() {
|
|
31
|
+
await this.getConnection();
|
|
32
|
+
}
|
|
33
|
+
async close(code, reason) {
|
|
34
|
+
this.generation++;
|
|
35
|
+
this.connecting = null;
|
|
36
|
+
this.stopHeartbeat();
|
|
37
|
+
this.cancelReconnect();
|
|
38
|
+
if (!connectionCache.ws || connectionCache.ws.readyState === WebsocketsReadyState.CLOSED || connectionCache.ws.readyState === WebsocketsReadyState.CLOSING) {
|
|
39
|
+
connectionCache.ws = void 0;
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
const ws = connectionCache.ws;
|
|
43
|
+
connectionCache.ws = void 0;
|
|
44
|
+
ws.close(code, reason);
|
|
45
|
+
return true;
|
|
39
46
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
async send(data) {
|
|
45
|
-
const connection = await this.getConnection();
|
|
46
|
-
if (connection.readyState !== WebsocketsReadyState.OPEN) {
|
|
47
|
-
console.info("Websocket connection is not open, cannot send any data.", data);
|
|
48
|
-
return;
|
|
47
|
+
async send(data) {
|
|
48
|
+
const connection = await this.getConnection();
|
|
49
|
+
if (!connection || connection.readyState !== WebsocketsReadyState.OPEN) return void console.info("Websocket connection is not open, cannot send any data.", data);
|
|
50
|
+
connection.send(JSON.stringify(data));
|
|
49
51
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
isConnected() {
|
|
53
|
-
return connectionCache.ws?.readyState === WebsocketsReadyState.OPEN;
|
|
54
|
-
}
|
|
55
|
-
isClosed() {
|
|
56
|
-
return connectionCache.ws?.readyState === WebsocketsReadyState.CLOSED;
|
|
57
|
-
}
|
|
58
|
-
async createUrl() {
|
|
59
|
-
const token = await this.getToken();
|
|
60
|
-
if (!token) {
|
|
61
|
-
console.error(`Missing token to connect to websockets.`);
|
|
62
|
-
return null;
|
|
52
|
+
isConnected() {
|
|
53
|
+
return connectionCache.ws?.readyState === WebsocketsReadyState.OPEN;
|
|
63
54
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
url: `${this.url}?token=${token}&tenant=${this.tenant}&locale=${this.locale}`
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
async getConnection() {
|
|
70
|
-
if (connectionCache.ws?.readyState === WebsocketsReadyState.OPEN) {
|
|
71
|
-
return connectionCache.ws;
|
|
72
|
-
} else if (connectionCache.ws?.readyState === WebsocketsReadyState.CONNECTING) {
|
|
73
|
-
return connectionCache.ws;
|
|
55
|
+
isClosed() {
|
|
56
|
+
return connectionCache.ws?.readyState === WebsocketsReadyState.CLOSED;
|
|
74
57
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
58
|
+
async createUrl() {
|
|
59
|
+
const token = await this.getToken();
|
|
60
|
+
if (!token) {
|
|
61
|
+
console.error("Missing token to connect to websockets.");
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
token,
|
|
66
|
+
url: `${this.url}?token=${token}&tenant=${this.tenant}`
|
|
67
|
+
};
|
|
78
68
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
});
|
|
90
|
-
connectionCache.ws.addEventListener("close", event => {
|
|
91
|
-
return this.subscriptionManager.triggerOnClose(event);
|
|
92
|
-
});
|
|
93
|
-
connectionCache.ws.addEventListener("error", event => {
|
|
94
|
-
console.info(`Error in the Websocket connection.`, event);
|
|
95
|
-
/**
|
|
96
|
-
* Let's close it if possible.
|
|
97
|
-
* It will reopen automatically.
|
|
98
|
-
*/
|
|
99
|
-
if (connectionCache.ws?.close) {
|
|
100
|
-
try {
|
|
101
|
-
connectionCache.ws.close();
|
|
102
|
-
} catch (ex) {
|
|
103
|
-
console.error(ex);
|
|
69
|
+
getConnection() {
|
|
70
|
+
const cached = connectionCache.ws;
|
|
71
|
+
if (cached?.readyState === WebsocketsReadyState.OPEN || cached?.readyState === WebsocketsReadyState.CONNECTING) return Promise.resolve(cached);
|
|
72
|
+
if (!this.connecting) {
|
|
73
|
+
const attempt = this.openConnection();
|
|
74
|
+
const clear = ()=>{
|
|
75
|
+
if (this.connecting === attempt) this.connecting = null;
|
|
76
|
+
};
|
|
77
|
+
attempt.then(clear, clear);
|
|
78
|
+
this.connecting = attempt;
|
|
104
79
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
80
|
+
return this.connecting;
|
|
81
|
+
}
|
|
82
|
+
async openConnection() {
|
|
83
|
+
const generation = this.generation;
|
|
84
|
+
const result = await this.createUrl();
|
|
85
|
+
if (generation !== this.generation) return null;
|
|
86
|
+
if (!result) throw new Error("Missing URL for WebSocket to connect to.");
|
|
87
|
+
const { url } = result;
|
|
88
|
+
const ws = this.factory(url, this.protocol);
|
|
89
|
+
connectionCache.ws = ws;
|
|
90
|
+
const start = new Date().getTime();
|
|
91
|
+
console.log(`Websockets connecting to ${this.url}...`);
|
|
92
|
+
ws.addEventListener("open", (event)=>{
|
|
93
|
+
const end = new Date().getTime();
|
|
94
|
+
console.log(`...connected in ${end - start}ms.`);
|
|
95
|
+
this.reconnectAttempt = 0;
|
|
96
|
+
this.startHeartbeat();
|
|
97
|
+
return this.subscriptionManager.triggerOnOpen(event);
|
|
98
|
+
});
|
|
99
|
+
ws.addEventListener("close", (event)=>{
|
|
100
|
+
console.warn("Websocket connection closed.", {
|
|
101
|
+
code: event.code,
|
|
102
|
+
reason: event.reason,
|
|
103
|
+
wasClean: event.wasClean,
|
|
104
|
+
at: new Date().toISOString()
|
|
105
|
+
});
|
|
106
|
+
if (connectionCache.ws === ws) {
|
|
107
|
+
this.stopHeartbeat();
|
|
108
|
+
this.scheduleReconnect();
|
|
109
|
+
}
|
|
110
|
+
return this.subscriptionManager.triggerOnClose(event);
|
|
111
|
+
});
|
|
112
|
+
ws.addEventListener("error", (event)=>{
|
|
113
|
+
console.info("Error in the Websocket connection.", event);
|
|
114
|
+
try {
|
|
115
|
+
ws.close();
|
|
116
|
+
} catch (ex) {
|
|
117
|
+
console.error(ex);
|
|
118
|
+
}
|
|
119
|
+
return this.subscriptionManager.triggerOnError(event);
|
|
120
|
+
});
|
|
121
|
+
ws.addEventListener("message", (event)=>this.subscriptionManager.triggerOnMessage(event));
|
|
122
|
+
return ws;
|
|
123
|
+
}
|
|
124
|
+
scheduleReconnect() {
|
|
125
|
+
if (this.reconnectTimer) return;
|
|
126
|
+
const delay = this.reconnectDelay(this.reconnectAttempt);
|
|
127
|
+
this.reconnectAttempt++;
|
|
128
|
+
console.log(`Websockets reconnecting in ${delay}ms (attempt ${this.reconnectAttempt}).`);
|
|
129
|
+
const generation = this.generation;
|
|
130
|
+
this.reconnectTimer = setTimeout(async ()=>{
|
|
131
|
+
this.reconnectTimer = null;
|
|
132
|
+
try {
|
|
133
|
+
await this.connect();
|
|
134
|
+
} catch (ex) {
|
|
135
|
+
if (generation !== this.generation) return;
|
|
136
|
+
console.error("Websockets reconnect failed.", ex);
|
|
137
|
+
this.scheduleReconnect();
|
|
138
|
+
}
|
|
139
|
+
}, delay);
|
|
140
|
+
}
|
|
141
|
+
cancelReconnect() {
|
|
142
|
+
if (!this.reconnectTimer) return;
|
|
143
|
+
clearTimeout(this.reconnectTimer);
|
|
144
|
+
this.reconnectTimer = null;
|
|
145
|
+
}
|
|
146
|
+
startHeartbeat() {
|
|
147
|
+
this.stopHeartbeat();
|
|
148
|
+
this.heartbeatTimer = setInterval(()=>{
|
|
149
|
+
this.sendHeartbeat().catch((ex)=>{
|
|
150
|
+
console.error("Websockets heartbeat failed.", ex);
|
|
151
|
+
});
|
|
152
|
+
}, this.heartbeatInterval);
|
|
153
|
+
}
|
|
154
|
+
stopHeartbeat() {
|
|
155
|
+
if (!this.heartbeatTimer) return;
|
|
156
|
+
clearInterval(this.heartbeatTimer);
|
|
157
|
+
this.heartbeatTimer = null;
|
|
158
|
+
}
|
|
159
|
+
async sendHeartbeat() {
|
|
160
|
+
if (!this.isConnected()) return;
|
|
161
|
+
const token = await this.getToken();
|
|
162
|
+
if (!token || !this.isConnected()) return;
|
|
163
|
+
connectionCache.ws.send(JSON.stringify({
|
|
164
|
+
action: WEBSOCKETS_HEARTBEAT_ACTION,
|
|
165
|
+
token,
|
|
166
|
+
tenant: this.tenant
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
113
169
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
};
|
|
170
|
+
const createWebsocketsConnection = (params)=>new WebsocketsConnection(params);
|
|
171
|
+
export { WEBSOCKETS_HEARTBEAT_ACTION, WEBSOCKETS_HEARTBEAT_INTERVAL, WebsocketsConnection, createWebsocketsConnection };
|
|
117
172
|
|
|
118
173
|
//# sourceMappingURL=WebsocketsConnection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["WebsocketsReadyState","defaultFactory","url","protocol","WebSocket","window","WebinyWebsocketsConnectionCache","ws","connectionCache","WebsocketsConnection","constructor","params","tenant","locale","getToken","subscriptionManager","factory","setTenant","setLocale","connect","getConnection","close","code","reason","readyState","CLOSED","CLOSING","undefined","send","data","connection","OPEN","console","info","JSON","stringify","isConnected","isClosed","createUrl","token","error","CONNECTING","result","Error","start","Date","getTime","log","addEventListener","event","end","triggerOnOpen","triggerOnClose","ex","triggerOnError","triggerOnMessage","createWebsocketsConnection"],"sources":["WebsocketsConnection.ts"],"sourcesContent":["import type {\n IGenericData,\n IWebsocketsConnection,\n IWebsocketsConnectionFactory,\n IWebsocketsConnectProtocol,\n IWebsocketsManagerMessageEvent,\n IWebsocketsSubscriptionManager,\n WebsocketsCloseCode\n} from \"./types\";\nimport { WebsocketsReadyState } from \"./types\";\n\ninterface ICreateUrlResult {\n token: string;\n url: string;\n}\n\nconst defaultFactory: IWebsocketsConnectionFactory = (url, protocol) => {\n return new WebSocket(url, protocol);\n};\n\ninterface IConnection {\n ws: WebSocket | null;\n}\n\n/**\n * We need to attach the websockets cache to window object, or it will be reset on every hot reload.\n */\ndeclare global {\n interface Window {\n WebinyWebsocketsConnectionCache: IConnection;\n }\n}\n\nif (!window.WebinyWebsocketsConnectionCache) {\n window.WebinyWebsocketsConnectionCache = {\n ws: null\n };\n}\n\nconst connectionCache = window.WebinyWebsocketsConnectionCache;\n\nexport interface IWebsocketsConnectionParams {\n url: string;\n tenant: string;\n locale: string;\n getToken(): Promise<string | undefined>;\n subscriptionManager: IWebsocketsSubscriptionManager;\n protocol?: IWebsocketsConnectProtocol;\n factory?: IWebsocketsConnectionFactory;\n}\n\nexport class WebsocketsConnection implements IWebsocketsConnection {\n private readonly url: string;\n private readonly getToken: () => Promise<string | undefined>;\n private tenant: string;\n private locale: string;\n private readonly protocol: IWebsocketsConnectProtocol;\n public readonly subscriptionManager: IWebsocketsSubscriptionManager;\n private readonly factory: IWebsocketsConnectionFactory;\n\n public constructor(params: IWebsocketsConnectionParams) {\n this.url = params.url;\n this.tenant = params.tenant;\n this.locale = params.locale;\n this.getToken = params.getToken;\n this.protocol = params.protocol;\n this.subscriptionManager = params.subscriptionManager;\n this.factory = params.factory || defaultFactory;\n }\n\n public setTenant(tenant: string): void {\n this.tenant = tenant;\n }\n\n public setLocale(locale: string): void {\n this.locale = locale;\n }\n\n public async connect(): Promise<void> {\n await this.getConnection();\n }\n\n public async close(code: WebsocketsCloseCode, reason: string): Promise<boolean> {\n if (\n !connectionCache.ws ||\n connectionCache.ws.readyState === WebsocketsReadyState.CLOSED ||\n connectionCache.ws.readyState === WebsocketsReadyState.CLOSING\n ) {\n connectionCache.ws = undefined as unknown as null;\n\n return true;\n }\n connectionCache.ws.close(code, reason);\n\n connectionCache.ws = undefined as unknown as null;\n return true;\n }\n\n public async send<T extends IGenericData = IGenericData>(data: T): Promise<void> {\n const connection = await this.getConnection();\n if (connection.readyState !== WebsocketsReadyState.OPEN) {\n console.info(\"Websocket connection is not open, cannot send any data.\", data);\n return;\n }\n connection.send(JSON.stringify(data));\n }\n\n public isConnected(): boolean {\n return connectionCache.ws?.readyState === WebsocketsReadyState.OPEN;\n }\n\n public isClosed(): boolean {\n return connectionCache.ws?.readyState === WebsocketsReadyState.CLOSED;\n }\n\n private async createUrl(): Promise<ICreateUrlResult | null> {\n const token = await this.getToken();\n if (!token) {\n console.error(`Missing token to connect to websockets.`);\n return null;\n }\n return {\n token,\n url: `${this.url}?token=${token}&tenant=${this.tenant}&locale=${this.locale}`\n };\n }\n\n private async getConnection(): Promise<WebSocket> {\n if (connectionCache.ws?.readyState === WebsocketsReadyState.OPEN) {\n return connectionCache.ws;\n } else if (connectionCache.ws?.readyState === WebsocketsReadyState.CONNECTING) {\n return connectionCache.ws;\n }\n\n const result = await this.createUrl();\n if (!result) {\n throw new Error(`Missing URL for WebSocket to connect to.`);\n }\n const { url } = result;\n\n connectionCache.ws = this.factory(url, this.protocol);\n\n const start = new Date().getTime();\n\n console.log(`Websockets connecting to ${this.url}...`);\n\n connectionCache.ws.addEventListener(\"open\", event => {\n const end = new Date().getTime();\n console.log(`...connected in ${end - start}ms.`);\n return this.subscriptionManager.triggerOnOpen(event);\n });\n connectionCache.ws.addEventListener(\"close\", event => {\n return this.subscriptionManager.triggerOnClose(event);\n });\n connectionCache.ws.addEventListener(\"error\", event => {\n console.info(`Error in the Websocket connection.`, event);\n /**\n * Let's close it if possible.\n * It will reopen automatically.\n */\n if (connectionCache.ws?.close) {\n try {\n connectionCache.ws.close();\n } catch (ex) {\n console.error(ex);\n }\n }\n return this.subscriptionManager.triggerOnError(event);\n });\n\n connectionCache.ws.addEventListener(\n \"message\",\n (event: IWebsocketsManagerMessageEvent<string>) => {\n return this.subscriptionManager.triggerOnMessage(event);\n }\n );\n\n return connectionCache.ws;\n }\n}\n\nexport const createWebsocketsConnection = (\n params: IWebsocketsConnectionParams\n): IWebsocketsConnection => {\n return new WebsocketsConnection(params);\n};\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"domain/WebsocketsConnection.js","sources":["../../src/domain/WebsocketsConnection.ts"],"sourcesContent":["import type {\n IGenericData,\n IWebsocketsConnection,\n IWebsocketsConnectionFactory,\n IWebsocketsConnectProtocol,\n IWebsocketsManagerMessageEvent,\n IWebsocketsSubscriptionManager,\n WebsocketsCloseCode\n} from \"./types.js\";\nimport { WebsocketsReadyState } from \"./types.js\";\n\ninterface ICreateUrlResult {\n token: string;\n url: string;\n}\n\nconst defaultFactory: IWebsocketsConnectionFactory = (url, protocol) => {\n return new WebSocket(url, protocol);\n};\n\ninterface IConnection {\n ws: WebSocket | null;\n}\n\n/**\n * We need to attach the websockets cache to window object, or it will be reset on every hot reload.\n */\ndeclare global {\n interface Window {\n WebinyWebsocketsConnectionCache: IConnection;\n }\n}\n\nif (!window.WebinyWebsocketsConnectionCache) {\n window.WebinyWebsocketsConnectionCache = {\n ws: null\n };\n}\n\nconst connectionCache = window.WebinyWebsocketsConnectionCache;\n\n/**\n * API Gateway closes a WebSocket after 10 minutes without traffic (and after 2 hours regardless), so\n * we ping well inside that window. The ping has no route of its own and lands on `$default`.\n */\nexport const WEBSOCKETS_HEARTBEAT_INTERVAL = 5 * 60 * 1000;\nexport const WEBSOCKETS_HEARTBEAT_ACTION = \"ping\";\n\nconst MAX_RECONNECT_DELAY = 30 * 1000;\n\nconst defaultReconnectDelay = (attempt: number): number => {\n return Math.min(1000 * 2 ** attempt, MAX_RECONNECT_DELAY);\n};\n\nexport interface IWebsocketsConnectionParams {\n url: string;\n tenant: string;\n getToken(): Promise<string | undefined>;\n subscriptionManager: IWebsocketsSubscriptionManager;\n protocol?: IWebsocketsConnectProtocol;\n factory?: IWebsocketsConnectionFactory;\n heartbeatInterval?: number;\n reconnectDelay?: (attempt: number) => number;\n}\n\nexport class WebsocketsConnection implements IWebsocketsConnection {\n private readonly url: string;\n private readonly getToken: () => Promise<string | undefined>;\n private tenant: string;\n private readonly protocol: IWebsocketsConnectProtocol;\n public readonly subscriptionManager: IWebsocketsSubscriptionManager;\n private readonly factory: IWebsocketsConnectionFactory;\n private readonly heartbeatInterval: number;\n private readonly reconnectDelay: (attempt: number) => number;\n private reconnectAttempt = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n /**\n * Opening a socket waits for a token first. `connecting` lets concurrent callers (a reconnect\n * timer, the window-focus handler, `send()`) share that one attempt instead of each creating a\n * socket, and `generation` lets `close()` invalidate an attempt that is already waiting.\n */\n private connecting: Promise<WebSocket | null> | null = null;\n private generation = 0;\n\n public constructor(params: IWebsocketsConnectionParams) {\n this.url = params.url;\n this.tenant = params.tenant;\n this.getToken = params.getToken;\n this.protocol = params.protocol;\n this.subscriptionManager = params.subscriptionManager;\n this.factory = params.factory || defaultFactory;\n this.heartbeatInterval = params.heartbeatInterval ?? WEBSOCKETS_HEARTBEAT_INTERVAL;\n this.reconnectDelay = params.reconnectDelay ?? defaultReconnectDelay;\n }\n\n public setTenant(tenant: string): void {\n this.tenant = tenant;\n }\n\n public async connect(): Promise<void> {\n await this.getConnection();\n }\n\n public async close(code: WebsocketsCloseCode, reason: string): Promise<boolean> {\n this.generation++;\n this.connecting = null;\n this.stopHeartbeat();\n this.cancelReconnect();\n if (\n !connectionCache.ws ||\n connectionCache.ws.readyState === WebsocketsReadyState.CLOSED ||\n connectionCache.ws.readyState === WebsocketsReadyState.CLOSING\n ) {\n connectionCache.ws = undefined as unknown as null;\n\n return true;\n }\n // Detach first: the close handler reconnects only while the closing socket is still the\n // cached one, and this close is intentional.\n const ws = connectionCache.ws;\n connectionCache.ws = undefined as unknown as null;\n ws.close(code, reason);\n\n return true;\n }\n\n public async send<T extends IGenericData = IGenericData>(data: T): Promise<void> {\n const connection = await this.getConnection();\n if (!connection || connection.readyState !== WebsocketsReadyState.OPEN) {\n console.info(\"Websocket connection is not open, cannot send any data.\", data);\n return;\n }\n connection.send(JSON.stringify(data));\n }\n\n public isConnected(): boolean {\n return connectionCache.ws?.readyState === WebsocketsReadyState.OPEN;\n }\n\n public isClosed(): boolean {\n return connectionCache.ws?.readyState === WebsocketsReadyState.CLOSED;\n }\n\n private async createUrl(): Promise<ICreateUrlResult | null> {\n const token = await this.getToken();\n if (!token) {\n console.error(`Missing token to connect to websockets.`);\n return null;\n }\n return {\n token,\n url: `${this.url}?token=${token}&tenant=${this.tenant}`\n };\n }\n\n private getConnection(): Promise<WebSocket | null> {\n const cached = connectionCache.ws;\n if (\n cached?.readyState === WebsocketsReadyState.OPEN ||\n cached?.readyState === WebsocketsReadyState.CONNECTING\n ) {\n return Promise.resolve(cached);\n }\n\n if (!this.connecting) {\n const attempt = this.openConnection();\n const clear = () => {\n if (this.connecting === attempt) {\n this.connecting = null;\n }\n };\n attempt.then(clear, clear);\n this.connecting = attempt;\n }\n return this.connecting;\n }\n\n /**\n * Resolves to `null` when `close()` ran while the token was being fetched: the attempt is\n * abandoned rather than opening a socket nobody asked for any more.\n */\n private async openConnection(): Promise<WebSocket | null> {\n const generation = this.generation;\n const result = await this.createUrl();\n if (generation !== this.generation) {\n return null;\n }\n if (!result) {\n throw new Error(`Missing URL for WebSocket to connect to.`);\n }\n const { url } = result;\n\n const ws = this.factory(url, this.protocol);\n connectionCache.ws = ws;\n\n const start = new Date().getTime();\n\n console.log(`Websockets connecting to ${this.url}...`);\n\n ws.addEventListener(\"open\", event => {\n const end = new Date().getTime();\n console.log(`...connected in ${end - start}ms.`);\n this.reconnectAttempt = 0;\n this.startHeartbeat();\n return this.subscriptionManager.triggerOnOpen(event);\n });\n ws.addEventListener(\"close\", event => {\n console.warn(\"Websocket connection closed.\", {\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean,\n at: new Date().toISOString()\n });\n /**\n * `close()` detaches the socket from the cache before closing it, so a socket that is no\n * longer the cached one was either closed on purpose or already replaced. Only the\n * current socket closing on its own warrants a reconnect: while it is down, the server\n * has no registered connection for this identity and every push to it is dropped.\n */\n if (connectionCache.ws === ws) {\n this.stopHeartbeat();\n this.scheduleReconnect();\n }\n return this.subscriptionManager.triggerOnClose(event);\n });\n ws.addEventListener(\"error\", event => {\n console.info(`Error in the Websocket connection.`, event);\n /**\n * Close this socket (not whatever is cached by now); the close handler reconnects if\n * it is still the current one.\n */\n try {\n ws.close();\n } catch (ex) {\n console.error(ex);\n }\n return this.subscriptionManager.triggerOnError(event);\n });\n\n ws.addEventListener(\"message\", (event: IWebsocketsManagerMessageEvent<string>) => {\n return this.subscriptionManager.triggerOnMessage(event);\n });\n\n return ws;\n }\n\n private scheduleReconnect(): void {\n if (this.reconnectTimer) {\n return;\n }\n const delay = this.reconnectDelay(this.reconnectAttempt);\n this.reconnectAttempt++;\n console.log(`Websockets reconnecting in ${delay}ms (attempt ${this.reconnectAttempt}).`);\n\n const generation = this.generation;\n this.reconnectTimer = setTimeout(async () => {\n this.reconnectTimer = null;\n try {\n await this.connect();\n } catch (ex) {\n if (generation !== this.generation) {\n // close() ran while this attempt was in flight: it was meant to stop here.\n return;\n }\n // No token yet (e.g. mid-refresh) or the socket could not be created: try again.\n console.error(\"Websockets reconnect failed.\", ex);\n this.scheduleReconnect();\n }\n }, delay);\n }\n\n private cancelReconnect(): void {\n if (!this.reconnectTimer) {\n return;\n }\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n this.sendHeartbeat().catch(ex => {\n console.error(\"Websockets heartbeat failed.\", ex);\n });\n }, this.heartbeatInterval);\n }\n\n private stopHeartbeat(): void {\n if (!this.heartbeatTimer) {\n return;\n }\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n\n private async sendHeartbeat(): Promise<void> {\n if (!this.isConnected()) {\n return;\n }\n const token = await this.getToken();\n if (!token || !this.isConnected()) {\n return;\n }\n // Same envelope as WebsocketsActions: the `$default` route authenticates every message.\n connectionCache.ws!.send(\n JSON.stringify({ action: WEBSOCKETS_HEARTBEAT_ACTION, token, tenant: this.tenant })\n );\n }\n}\n\nexport const createWebsocketsConnection = (\n params: IWebsocketsConnectionParams\n): IWebsocketsConnection => {\n return new WebsocketsConnection(params);\n};\n"],"names":["defaultFactory","url","protocol","WebSocket","window","connectionCache","WEBSOCKETS_HEARTBEAT_INTERVAL","WEBSOCKETS_HEARTBEAT_ACTION","MAX_RECONNECT_DELAY","defaultReconnectDelay","attempt","Math","WebsocketsConnection","params","tenant","code","reason","WebsocketsReadyState","undefined","ws","data","connection","console","JSON","token","cached","Promise","clear","generation","result","Error","start","Date","event","end","ex","delay","setTimeout","clearTimeout","setInterval","clearInterval","createWebsocketsConnection"],"mappings":";AAgBA,MAAMA,iBAA+C,CAACC,KAAKC,WAChD,IAAIC,UAAUF,KAAKC;AAgB9B,IAAI,CAACE,OAAO,+BAA+B,EACvCA,OAAO,+BAA+B,GAAG;IACrC,IAAI;AACR;AAGJ,MAAMC,kBAAkBD,OAAO,+BAA+B;AAMvD,MAAME,gCAAgC;AACtC,MAAMC,8BAA8B;AAE3C,MAAMC,sBAAsB;AAE5B,MAAMC,wBAAwB,CAACC,UACpBC,KAAK,GAAG,CAAC,OAAO,KAAKD,SAASF;AAclC,MAAMI;IAoBT,YAAmBC,MAAmC,CAAE;aAXhD,gBAAgB,GAAG;aACnB,cAAc,GAAyC;aACvD,cAAc,GAA0C;QAK/D,KACO,UAAU,GAAqC;aAC/C,UAAU,GAAG;QAGjB,IAAI,CAAC,GAAG,GAAGA,OAAO,GAAG;QACrB,IAAI,CAAC,MAAM,GAAGA,OAAO,MAAM;QAC3B,IAAI,CAAC,QAAQ,GAAGA,OAAO,QAAQ;QAC/B,IAAI,CAAC,QAAQ,GAAGA,OAAO,QAAQ;QAC/B,IAAI,CAAC,mBAAmB,GAAGA,OAAO,mBAAmB;QACrD,IAAI,CAAC,OAAO,GAAGA,OAAO,OAAO,IAAIb;QACjC,IAAI,CAAC,iBAAiB,GAAGa,OAAO,iBAAiB,IAAIP;QACrD,IAAI,CAAC,cAAc,GAAGO,OAAO,cAAc,IAAIJ;IACnD;IAEO,UAAUK,MAAc,EAAQ;QACnC,IAAI,CAAC,MAAM,GAAGA;IAClB;IAEA,MAAa,UAAyB;QAClC,MAAM,IAAI,CAAC,aAAa;IAC5B;IAEA,MAAa,MAAMC,IAAyB,EAAEC,MAAc,EAAoB;QAC5E,IAAI,CAAC,UAAU;QACf,IAAI,CAAC,UAAU,GAAG;QAClB,IAAI,CAAC,aAAa;QAClB,IAAI,CAAC,eAAe;QACpB,IACI,CAACX,gBAAgB,EAAE,IACnBA,gBAAgB,EAAE,CAAC,UAAU,KAAKY,qBAAqB,MAAM,IAC7DZ,gBAAgB,EAAE,CAAC,UAAU,KAAKY,qBAAqB,OAAO,EAChE;YACEZ,gBAAgB,EAAE,GAAGa;YAErB,OAAO;QACX;QAGA,MAAMC,KAAKd,gBAAgB,EAAE;QAC7BA,gBAAgB,EAAE,GAAGa;QACrBC,GAAG,KAAK,CAACJ,MAAMC;QAEf,OAAO;IACX;IAEA,MAAa,KAA4CI,IAAO,EAAiB;QAC7E,MAAMC,aAAa,MAAM,IAAI,CAAC,aAAa;QAC3C,IAAI,CAACA,cAAcA,WAAW,UAAU,KAAKJ,qBAAqB,IAAI,EAAE,YACpEK,QAAQ,IAAI,CAAC,2DAA2DF;QAG5EC,WAAW,IAAI,CAACE,KAAK,SAAS,CAACH;IACnC;IAEO,cAAuB;QAC1B,OAAOf,gBAAgB,EAAE,EAAE,eAAeY,qBAAqB,IAAI;IACvE;IAEO,WAAoB;QACvB,OAAOZ,gBAAgB,EAAE,EAAE,eAAeY,qBAAqB,MAAM;IACzE;IAEA,MAAc,YAA8C;QACxD,MAAMO,QAAQ,MAAM,IAAI,CAAC,QAAQ;QACjC,IAAI,CAACA,OAAO;YACRF,QAAQ,KAAK,CAAC;YACd,OAAO;QACX;QACA,OAAO;YACHE;YACA,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAEA,MAAM,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE;QAC3D;IACJ;IAEQ,gBAA2C;QAC/C,MAAMC,SAASpB,gBAAgB,EAAE;QACjC,IACIoB,QAAQ,eAAeR,qBAAqB,IAAI,IAChDQ,QAAQ,eAAeR,qBAAqB,UAAU,EAEtD,OAAOS,QAAQ,OAAO,CAACD;QAG3B,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YAClB,MAAMf,UAAU,IAAI,CAAC,cAAc;YACnC,MAAMiB,QAAQ;gBACV,IAAI,IAAI,CAAC,UAAU,KAAKjB,SACpB,IAAI,CAAC,UAAU,GAAG;YAE1B;YACAA,QAAQ,IAAI,CAACiB,OAAOA;YACpB,IAAI,CAAC,UAAU,GAAGjB;QACtB;QACA,OAAO,IAAI,CAAC,UAAU;IAC1B;IAMA,MAAc,iBAA4C;QACtD,MAAMkB,aAAa,IAAI,CAAC,UAAU;QAClC,MAAMC,SAAS,MAAM,IAAI,CAAC,SAAS;QACnC,IAAID,eAAe,IAAI,CAAC,UAAU,EAC9B,OAAO;QAEX,IAAI,CAACC,QACD,MAAM,IAAIC,MAAM;QAEpB,MAAM,EAAE7B,GAAG,EAAE,GAAG4B;QAEhB,MAAMV,KAAK,IAAI,CAAC,OAAO,CAAClB,KAAK,IAAI,CAAC,QAAQ;QAC1CI,gBAAgB,EAAE,GAAGc;QAErB,MAAMY,QAAQ,IAAIC,OAAO,OAAO;QAEhCV,QAAQ,GAAG,CAAC,CAAC,yBAAyB,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QAErDH,GAAG,gBAAgB,CAAC,QAAQc,CAAAA;YACxB,MAAMC,MAAM,IAAIF,OAAO,OAAO;YAC9BV,QAAQ,GAAG,CAAC,CAAC,gBAAgB,EAAEY,MAAMH,MAAM,GAAG,CAAC;YAC/C,IAAI,CAAC,gBAAgB,GAAG;YACxB,IAAI,CAAC,cAAc;YACnB,OAAO,IAAI,CAAC,mBAAmB,CAAC,aAAa,CAACE;QAClD;QACAd,GAAG,gBAAgB,CAAC,SAASc,CAAAA;YACzBX,QAAQ,IAAI,CAAC,gCAAgC;gBACzC,MAAMW,MAAM,IAAI;gBAChB,QAAQA,MAAM,MAAM;gBACpB,UAAUA,MAAM,QAAQ;gBACxB,IAAI,IAAID,OAAO,WAAW;YAC9B;YAOA,IAAI3B,gBAAgB,EAAE,KAAKc,IAAI;gBAC3B,IAAI,CAAC,aAAa;gBAClB,IAAI,CAAC,iBAAiB;YAC1B;YACA,OAAO,IAAI,CAAC,mBAAmB,CAAC,cAAc,CAACc;QACnD;QACAd,GAAG,gBAAgB,CAAC,SAASc,CAAAA;YACzBX,QAAQ,IAAI,CAAC,sCAAsCW;YAKnD,IAAI;gBACAd,GAAG,KAAK;YACZ,EAAE,OAAOgB,IAAI;gBACTb,QAAQ,KAAK,CAACa;YAClB;YACA,OAAO,IAAI,CAAC,mBAAmB,CAAC,cAAc,CAACF;QACnD;QAEAd,GAAG,gBAAgB,CAAC,WAAW,CAACc,QACrB,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAACA;QAGrD,OAAOd;IACX;IAEQ,oBAA0B;QAC9B,IAAI,IAAI,CAAC,cAAc,EACnB;QAEJ,MAAMiB,QAAQ,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,gBAAgB;QACvD,IAAI,CAAC,gBAAgB;QACrBd,QAAQ,GAAG,CAAC,CAAC,2BAA2B,EAAEc,MAAM,YAAY,EAAE,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAEvF,MAAMR,aAAa,IAAI,CAAC,UAAU;QAClC,IAAI,CAAC,cAAc,GAAGS,WAAW;YAC7B,IAAI,CAAC,cAAc,GAAG;YACtB,IAAI;gBACA,MAAM,IAAI,CAAC,OAAO;YACtB,EAAE,OAAOF,IAAI;gBACT,IAAIP,eAAe,IAAI,CAAC,UAAU,EAE9B;gBAGJN,QAAQ,KAAK,CAAC,gCAAgCa;gBAC9C,IAAI,CAAC,iBAAiB;YAC1B;QACJ,GAAGC;IACP;IAEQ,kBAAwB;QAC5B,IAAI,CAAC,IAAI,CAAC,cAAc,EACpB;QAEJE,aAAa,IAAI,CAAC,cAAc;QAChC,IAAI,CAAC,cAAc,GAAG;IAC1B;IAEQ,iBAAuB;QAC3B,IAAI,CAAC,aAAa;QAClB,IAAI,CAAC,cAAc,GAAGC,YAAY;YAC9B,IAAI,CAAC,aAAa,GAAG,KAAK,CAACJ,CAAAA;gBACvBb,QAAQ,KAAK,CAAC,gCAAgCa;YAClD;QACJ,GAAG,IAAI,CAAC,iBAAiB;IAC7B;IAEQ,gBAAsB;QAC1B,IAAI,CAAC,IAAI,CAAC,cAAc,EACpB;QAEJK,cAAc,IAAI,CAAC,cAAc;QACjC,IAAI,CAAC,cAAc,GAAG;IAC1B;IAEA,MAAc,gBAA+B;QACzC,IAAI,CAAC,IAAI,CAAC,WAAW,IACjB;QAEJ,MAAMhB,QAAQ,MAAM,IAAI,CAAC,QAAQ;QACjC,IAAI,CAACA,SAAS,CAAC,IAAI,CAAC,WAAW,IAC3B;QAGJnB,gBAAgB,EAAE,CAAE,IAAI,CACpBkB,KAAK,SAAS,CAAC;YAAE,QAAQhB;YAA6BiB;YAAO,QAAQ,IAAI,CAAC,MAAM;QAAC;IAEzF;AACJ;AAEO,MAAMiB,6BAA6B,CACtC5B,SAEO,IAAID,qBAAqBC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IGenericData, IWebsocketsConnection, IWebsocketsManager, IWebsocketsManagerCloseEvent, IWebsocketsManagerErrorEvent, IWebsocketsManagerMessageEvent, IWebsocketsManagerOpenEvent, IWebsocketManagerSendData, IWebsocketsSubscription, IWebsocketsSubscriptionCallback, WebsocketsCloseCode } from "./types";
|
|
1
|
+
import type { IGenericData, IWebsocketsConnection, IWebsocketsManager, IWebsocketsManagerCloseEvent, IWebsocketsManagerErrorEvent, IWebsocketsManagerMessageEvent, IWebsocketsManagerOpenEvent, IWebsocketManagerSendData, IWebsocketsSubscription, IWebsocketsSubscriptionCallback, WebsocketsCloseCode } from "./types.js";
|
|
2
2
|
export declare class WebsocketsManager implements IWebsocketsManager {
|
|
3
3
|
readonly connection: IWebsocketsConnection;
|
|
4
4
|
constructor(connection: IWebsocketsConnection);
|
|
@@ -1,37 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
1
|
+
class WebsocketsManager {
|
|
2
|
+
constructor(connection){
|
|
3
|
+
this.connection = connection;
|
|
4
|
+
}
|
|
5
|
+
onOpen(cb) {
|
|
6
|
+
return this.connection.subscriptionManager.onOpen(cb);
|
|
7
|
+
}
|
|
8
|
+
onClose(cb) {
|
|
9
|
+
return this.connection.subscriptionManager.onClose(cb);
|
|
10
|
+
}
|
|
11
|
+
onMessage(cb) {
|
|
12
|
+
return this.connection.subscriptionManager.onMessage(cb);
|
|
13
|
+
}
|
|
14
|
+
onError(cb) {
|
|
15
|
+
return this.connection.subscriptionManager.onError(cb);
|
|
16
|
+
}
|
|
17
|
+
async connect() {
|
|
18
|
+
return this.connection.connect();
|
|
19
|
+
}
|
|
20
|
+
async close(code, reason) {
|
|
21
|
+
return await this.connection.close(code, reason);
|
|
22
|
+
}
|
|
23
|
+
send(data) {
|
|
24
|
+
return this.connection.send(data);
|
|
25
|
+
}
|
|
26
|
+
isConnected() {
|
|
27
|
+
return this.connection.isConnected();
|
|
28
|
+
}
|
|
29
|
+
isClosed() {
|
|
30
|
+
return this.connection.isClosed();
|
|
31
|
+
}
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
};
|
|
33
|
+
const createWebsocketsManager = (connection)=>new WebsocketsManager(connection);
|
|
34
|
+
export { WebsocketsManager, createWebsocketsManager };
|
|
36
35
|
|
|
37
36
|
//# sourceMappingURL=WebsocketsManager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"domain/WebsocketsManager.js","sources":["../../src/domain/WebsocketsManager.ts"],"sourcesContent":["import type {\n IGenericData,\n IWebsocketsConnection,\n IWebsocketsManager,\n IWebsocketsManagerCloseEvent,\n IWebsocketsManagerErrorEvent,\n IWebsocketsManagerMessageEvent,\n IWebsocketsManagerOpenEvent,\n IWebsocketManagerSendData,\n IWebsocketsSubscription,\n IWebsocketsSubscriptionCallback,\n WebsocketsCloseCode\n} from \"./types.js\";\n\nexport class WebsocketsManager implements IWebsocketsManager {\n public readonly connection: IWebsocketsConnection;\n\n public constructor(connection: IWebsocketsConnection) {\n this.connection = connection;\n }\n\n public onOpen(\n cb: IWebsocketsSubscriptionCallback<IWebsocketsManagerOpenEvent>\n ): IWebsocketsSubscription<IWebsocketsManagerOpenEvent> {\n return this.connection.subscriptionManager.onOpen(cb);\n }\n\n public onClose(\n cb: IWebsocketsSubscriptionCallback<IWebsocketsManagerCloseEvent>\n ): IWebsocketsSubscription<IWebsocketsManagerCloseEvent> {\n return this.connection.subscriptionManager.onClose(cb);\n }\n\n public onMessage<T extends IGenericData = IGenericData>(\n cb: IWebsocketsSubscriptionCallback<IWebsocketsManagerMessageEvent<T>>\n ): IWebsocketsSubscription<IWebsocketsManagerMessageEvent<T>> {\n return this.connection.subscriptionManager.onMessage<T>(cb);\n }\n\n public onError(\n cb: IWebsocketsSubscriptionCallback<IWebsocketsManagerErrorEvent>\n ): IWebsocketsSubscription<IWebsocketsManagerErrorEvent> {\n return this.connection.subscriptionManager.onError(cb);\n }\n\n public async connect(): Promise<void> {\n return this.connection.connect();\n }\n\n public async close(code: WebsocketsCloseCode, reason: string): Promise<boolean> {\n return await this.connection.close(code, reason);\n }\n\n public send<T extends IWebsocketManagerSendData = IWebsocketManagerSendData>(data: T): void {\n return this.connection.send<T>(data);\n }\n\n public isConnected(): boolean {\n return this.connection.isConnected();\n }\n\n public isClosed(): boolean {\n return this.connection.isClosed();\n }\n}\n\nexport const createWebsocketsManager = (connection: IWebsocketsConnection): IWebsocketsManager => {\n return new WebsocketsManager(connection);\n};\n"],"names":["WebsocketsManager","connection","cb","code","reason","data","createWebsocketsManager"],"mappings":"AAcO,MAAMA;IAGT,YAAmBC,UAAiC,CAAE;QAClD,IAAI,CAAC,UAAU,GAAGA;IACtB;IAEO,OACHC,EAAgE,EACZ;QACpD,OAAO,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,MAAM,CAACA;IACtD;IAEO,QACHA,EAAiE,EACZ;QACrD,OAAO,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,OAAO,CAACA;IACvD;IAEO,UACHA,EAAsE,EACZ;QAC1D,OAAO,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,SAAS,CAAIA;IAC5D;IAEO,QACHA,EAAiE,EACZ;QACrD,OAAO,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,OAAO,CAACA;IACvD;IAEA,MAAa,UAAyB;QAClC,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO;IAClC;IAEA,MAAa,MAAMC,IAAyB,EAAEC,MAAc,EAAoB;QAC5E,OAAO,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAACD,MAAMC;IAC7C;IAEO,KAAsEC,IAAO,EAAQ;QACxF,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAIA;IACnC;IAEO,cAAuB;QAC1B,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW;IACtC;IAEO,WAAoB;QACvB,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ;IACnC;AACJ;AAEO,MAAMC,0BAA0B,CAACL,aAC7B,IAAID,kBAAkBC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { IGenericData, IWebsocketsManagerCloseEvent, IWebsocketsManagerErrorEvent, IWebsocketsManagerMessageEvent, IWebsocketsManagerOpenEvent } from "./types";
|
|
2
|
-
import type { IWebsocketsSubscription, IWebsocketsSubscriptionCallback, IWebsocketsSubscriptionManager } from "./abstractions/IWebsocketsSubscriptionManager";
|
|
1
|
+
import type { IGenericData, IWebsocketsManagerCloseEvent, IWebsocketsManagerErrorEvent, IWebsocketsManagerMessageEvent, IWebsocketsManagerOpenEvent } from "./types.js";
|
|
2
|
+
import type { IWebsocketsSubscription, IWebsocketsSubscriptionCallback, IWebsocketsSubscriptionManager } from "./abstractions/IWebsocketsSubscriptionManager.js";
|
|
3
3
|
export declare class WebsocketsSubscriptionManager implements IWebsocketsSubscriptionManager {
|
|
4
4
|
private subscriptions;
|
|
5
5
|
onOpen(cb: IWebsocketsSubscriptionCallback<IWebsocketsManagerOpenEvent>): IWebsocketsSubscription<IWebsocketsManagerOpenEvent>;
|