@webiny/app-websockets 0.0.0-unstable.e6f0dc8ca7 → 0.0.0-unstable.e9333b3f0b

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.
Files changed (50) hide show
  1. package/WebsocketsContextProvider.js +134 -142
  2. package/WebsocketsContextProvider.js.map +1 -1
  3. package/domain/WebsocketsAction.js +17 -24
  4. package/domain/WebsocketsAction.js.map +1 -1
  5. package/domain/WebsocketsActions.js +52 -73
  6. package/domain/WebsocketsActions.js.map +1 -1
  7. package/domain/WebsocketsConnection.d.ts +30 -0
  8. package/domain/WebsocketsConnection.js +161 -102
  9. package/domain/WebsocketsConnection.js.map +1 -1
  10. package/domain/WebsocketsManager.js +33 -34
  11. package/domain/WebsocketsManager.js.map +1 -1
  12. package/domain/WebsocketsSubscriptionManager.js +57 -64
  13. package/domain/WebsocketsSubscriptionManager.js.map +1 -1
  14. package/domain/abstractions/IWebsocketsAction.js +0 -3
  15. package/domain/abstractions/IWebsocketsActions.js +0 -3
  16. package/domain/abstractions/IWebsocketsConnection.js +7 -6
  17. package/domain/abstractions/IWebsocketsConnection.js.map +1 -1
  18. package/domain/abstractions/IWebsocketsManager.js +0 -3
  19. package/domain/abstractions/IWebsocketsSubscriptionManager.js +0 -3
  20. package/domain/abstractions/types.js +19 -18
  21. package/domain/abstractions/types.js.map +1 -1
  22. package/domain/index.js +0 -2
  23. package/domain/types.js +0 -2
  24. package/events/WebsocketEvent.d.ts +10 -0
  25. package/events/WebsocketEvent.js +13 -0
  26. package/events/WebsocketEvent.js.map +1 -0
  27. package/events/abstractions.d.ts +13 -0
  28. package/events/abstractions.js +5 -0
  29. package/events/abstractions.js.map +1 -0
  30. package/exports/admin/websockets.d.ts +1 -0
  31. package/exports/admin/websockets.js +1 -0
  32. package/hooks/index.js +0 -2
  33. package/hooks/useWebsockets.js +5 -6
  34. package/hooks/useWebsockets.js.map +1 -1
  35. package/index.d.ts +4 -1
  36. package/index.js +11 -12
  37. package/index.js.map +1 -1
  38. package/package.json +13 -9
  39. package/types.js +0 -3
  40. package/domain/abstractions/IWebsocketsAction.js.map +0 -1
  41. package/domain/abstractions/IWebsocketsActions.js.map +0 -1
  42. package/domain/abstractions/IWebsocketsManager.js.map +0 -1
  43. package/domain/abstractions/IWebsocketsSubscriptionManager.js.map +0 -1
  44. package/domain/index.js.map +0 -1
  45. package/domain/types.js.map +0 -1
  46. package/hooks/index.js.map +0 -1
  47. package/types.js.map +0 -1
  48. package/utils/getUrl.d.ts +0 -1
  49. package/utils/getUrl.js +0 -6
  50. package/utils/getUrl.js.map +0 -1
@@ -1,154 +1,146 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
1
+ import react, { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { useFeature, useTenantContext } from "@webiny/app-admin";
3
3
  import { AuthenticationContextFeature } from "@webiny/app-admin/features/security/AuthenticationContext/feature.js";
4
+ import { EventPublisherFeature } from "@webiny/app/features/eventPublisher/feature.js";
5
+ import { EnvConfigFeature } from "@webiny/app/features/envConfig/feature.js";
6
+ import { WebsocketEvent } from "./events/WebsocketEvent.js";
4
7
  import { WebsocketsCloseCode } from "./types.js";
5
8
  import { createWebsocketsAction, createWebsocketsActions, createWebsocketsConnection, createWebsocketsManager, createWebsocketsSubscriptionManager } from "./domain/index.js";
6
- import { getUrl } from "./utils/getUrl.js";
7
- export const WebsocketsContext = /*#__PURE__*/React.createContext(undefined);
8
- export const WebsocketsContextProvider = props => {
9
- const {
10
- tenant
11
- } = useTenantContext();
12
- const {
13
- authenticationContext
14
- } = useFeature(AuthenticationContextFeature);
15
- const socketsRef = useRef(null);
16
- const [current, setCurrent] = useState({});
17
- const getToken = useCallback(async () => {
18
- return await authenticationContext.getIdToken();
19
- }, [authenticationContext]);
20
- const subscriptionManager = useMemo(() => {
21
- const manager = createWebsocketsSubscriptionManager();
22
- let currentIteration = 0;
23
- manager.onClose(event => {
24
- if (currentIteration > 5 || event.code !== WebsocketsCloseCode.GOING_AWAY) {
25
- return;
26
- }
27
- currentIteration++;
28
- setTimeout(() => {
29
- if (!socketsRef.current) {
30
- return;
31
- } else if (socketsRef.current.isClosed()) {
32
- console.log("Running auto-reconnect.");
33
- socketsRef.current.connect();
34
- }
35
- }, 1000);
9
+ const noopSubscription = ()=>({
10
+ cb: ()=>void 0,
11
+ id: "",
12
+ off: ()=>void 0
36
13
  });
37
- return manager;
38
- }, []);
39
-
40
- /**
41
- * We need this useEffect to close the websocket connection and remove window focus event in case component is unmounted.
42
- * This will, probably, happen only during the development phase.
43
- *
44
- * If we did not disconnect on component unmount, we would have a memory leak - multiple connections would be opened.
45
- */
46
- useEffect(() => {
47
- /**
48
- * We want to add a window event listener which will check if the connection is closed, and if its - it will connect again.
49
- */
50
- const abortController = new AbortController();
51
- window.addEventListener("focus", () => {
52
- if (!socketsRef.current) {
53
- return;
54
- } else if (socketsRef.current.isClosed()) {
55
- console.log("Running auto-reconnect on focus.");
56
- socketsRef.current.connect();
57
- }
58
- }, {
59
- signal: abortController.signal
60
- });
61
- window.addEventListener("close", () => {
62
- subscriptionManager.triggerOnClose(new CloseEvent("windowClose", {
63
- code: WebsocketsCloseCode.GOING_AWAY,
64
- reason: "Closing Window or Tab."
65
- }));
66
- }, {
67
- signal: abortController.signal
68
- });
69
- return () => {
70
- abortController.abort();
71
- };
72
- }, []);
73
- useEffect(() => {
74
- (async () => {
75
- const token = await getToken();
76
- if (!token || !tenant) {
77
- return;
78
- } else if (current.tenant === tenant) {
79
- return;
80
- } else if (socketsRef.current) {
81
- await socketsRef.current.close(WebsocketsCloseCode.NORMAL, "Changing tenant.");
82
- }
83
- const url = getUrl();
84
- if (!url) {
85
- console.error("Not possible to connect to the websocket without a valid URL.", {
86
- tenant,
87
- token
14
+ const WebsocketsContext = /*#__PURE__*/ react.createContext(void 0);
15
+ const WebsocketsContextProvider = (props)=>{
16
+ const { tenant } = useTenantContext();
17
+ const { authenticationContext } = useFeature(AuthenticationContextFeature);
18
+ const { eventPublisher } = useFeature(EventPublisherFeature);
19
+ const envConfig = useFeature(EnvConfigFeature);
20
+ const configuredWsUrl = envConfig.get("websocketUrl");
21
+ const wsUrl = configuredWsUrl ? configuredWsUrl : void 0;
22
+ const socketsRef = useRef(null);
23
+ const [current, setCurrent] = useState({});
24
+ const getToken = useCallback(async ()=>await authenticationContext.getIdToken(), [
25
+ authenticationContext
26
+ ]);
27
+ const subscriptionManager = useMemo(()=>createWebsocketsSubscriptionManager(), []);
28
+ useEffect(()=>{
29
+ const abortController = new AbortController();
30
+ window.addEventListener("focus", ()=>{
31
+ if (!socketsRef.current) return;
32
+ if (socketsRef.current.isClosed()) {
33
+ console.log("Running auto-reconnect on focus.");
34
+ socketsRef.current.connect();
35
+ }
36
+ }, {
37
+ signal: abortController.signal
88
38
  });
89
- return;
90
- }
91
- socketsRef.current = createWebsocketsManager(createWebsocketsConnection({
92
- subscriptionManager,
93
- url,
39
+ window.addEventListener("close", ()=>{
40
+ subscriptionManager.triggerOnClose(new CloseEvent("windowClose", {
41
+ code: WebsocketsCloseCode.GOING_AWAY,
42
+ reason: "Closing Window or Tab."
43
+ }));
44
+ }, {
45
+ signal: abortController.signal
46
+ });
47
+ return ()=>{
48
+ abortController.abort();
49
+ };
50
+ }, []);
51
+ useEffect(()=>{
52
+ (async ()=>{
53
+ const token = await getToken();
54
+ if (!token || !tenant) return;
55
+ if (current.tenant === tenant) return;
56
+ if (socketsRef.current) await socketsRef.current.close(WebsocketsCloseCode.NORMAL, "Changing tenant.");
57
+ const url = wsUrl;
58
+ if (!url) return void console.warn("WebSocket URL not configured; real-time updates are disabled.");
59
+ socketsRef.current = createWebsocketsManager(createWebsocketsConnection({
60
+ subscriptionManager,
61
+ url,
62
+ tenant,
63
+ getToken,
64
+ protocol: [
65
+ "webiny-ws-v1"
66
+ ]
67
+ }));
68
+ await socketsRef.current.connect();
69
+ setCurrent({
70
+ tenant
71
+ });
72
+ })();
73
+ }, [
94
74
  tenant,
75
+ subscriptionManager,
95
76
  getToken,
96
- protocol: ["webiny-ws-v1"]
97
- }));
98
- await socketsRef.current.connect();
99
- setCurrent({
100
- tenant
101
- });
102
- })();
103
- }, [tenant, subscriptionManager, getToken]);
104
- const websocketActions = useMemo(() => {
105
- return createWebsocketsActions({
106
- manager: socketsRef.current,
107
- tenant,
108
- getToken
109
- });
110
- }, [socketsRef.current, tenant, getToken]);
111
- const send = useCallback(async (action, data, timeout) => {
112
- return websocketActions.run({
113
- action,
114
- data,
115
- timeout
116
- });
117
- }, [websocketActions]);
118
- const createAction = useCallback(name => {
119
- return createWebsocketsAction(websocketActions, name);
120
- }, [websocketActions]);
121
- const onMessage = useCallback((action, cb) => {
122
- return socketsRef.current.onMessage(async event => {
123
- if (event.data.action !== action) {
124
- return;
125
- }
126
- cb(event.data);
127
- });
128
- }, [socketsRef.current]);
129
- const onError = useCallback(cb => {
130
- return socketsRef.current.onError(data => {
131
- return cb(data);
132
- });
133
- }, [socketsRef.current]);
134
- const onClose = useCallback(cb => {
135
- return socketsRef.current.onClose(data => {
136
- return cb(data);
77
+ wsUrl
78
+ ]);
79
+ useEffect(()=>{
80
+ if (!socketsRef.current) return;
81
+ const subscription = socketsRef.current.onMessage(async (event)=>{
82
+ await eventPublisher.publish(new WebsocketEvent(event.data));
83
+ });
84
+ return ()=>{
85
+ subscription.off();
86
+ };
87
+ }, [
88
+ current.tenant,
89
+ eventPublisher
90
+ ]);
91
+ const websocketActions = useMemo(()=>createWebsocketsActions({
92
+ manager: socketsRef.current,
93
+ tenant,
94
+ getToken
95
+ }), [
96
+ socketsRef.current,
97
+ tenant,
98
+ getToken
99
+ ]);
100
+ const send = useCallback(async (action, data, timeout)=>websocketActions.run({
101
+ action,
102
+ data,
103
+ timeout
104
+ }), [
105
+ websocketActions
106
+ ]);
107
+ const createAction = useCallback((name)=>createWebsocketsAction(websocketActions, name), [
108
+ websocketActions
109
+ ]);
110
+ const onMessage = useCallback((action, cb)=>{
111
+ if (!socketsRef.current) return noopSubscription();
112
+ return socketsRef.current.onMessage(async (event)=>{
113
+ if (event.data.action !== action) return;
114
+ cb(event.data);
115
+ });
116
+ }, [
117
+ socketsRef.current
118
+ ]);
119
+ const onError = useCallback((cb)=>{
120
+ if (!socketsRef.current) return noopSubscription();
121
+ return socketsRef.current.onError((data)=>cb(data));
122
+ }, [
123
+ socketsRef.current
124
+ ]);
125
+ const onClose = useCallback((cb)=>{
126
+ if (!socketsRef.current) return noopSubscription();
127
+ return socketsRef.current.onClose((data)=>cb(data));
128
+ }, [
129
+ socketsRef.current
130
+ ]);
131
+ if (wsUrl && !socketsRef.current) return props.loader || null;
132
+ const value = {
133
+ send,
134
+ createAction,
135
+ onMessage,
136
+ onError,
137
+ onClose
138
+ };
139
+ return /*#__PURE__*/ react.createElement(WebsocketsContext.Provider, {
140
+ value: value,
141
+ ...props
137
142
  });
138
- }, [socketsRef.current]);
139
- if (!socketsRef.current) {
140
- return props.loader || null;
141
- }
142
- const value = {
143
- send,
144
- createAction,
145
- onMessage,
146
- onError,
147
- onClose
148
- };
149
- return /*#__PURE__*/React.createElement(WebsocketsContext.Provider, Object.assign({
150
- value: value
151
- }, props));
152
143
  };
144
+ export { WebsocketsContext, WebsocketsContextProvider };
153
145
 
154
146
  //# sourceMappingURL=WebsocketsContextProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["React","useCallback","useEffect","useMemo","useRef","useState","useFeature","useTenantContext","AuthenticationContextFeature","WebsocketsCloseCode","createWebsocketsAction","createWebsocketsActions","createWebsocketsConnection","createWebsocketsManager","createWebsocketsSubscriptionManager","getUrl","WebsocketsContext","createContext","undefined","WebsocketsContextProvider","props","tenant","authenticationContext","socketsRef","current","setCurrent","getToken","getIdToken","subscriptionManager","manager","currentIteration","onClose","event","code","GOING_AWAY","setTimeout","isClosed","console","log","connect","abortController","AbortController","window","addEventListener","signal","triggerOnClose","CloseEvent","reason","abort","token","close","NORMAL","url","error","protocol","websocketActions","send","action","data","timeout","run","createAction","name","onMessage","cb","onError","loader","value","createElement","Provider","Object","assign"],"sources":["WebsocketsContextProvider.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useFeature, useTenantContext } from \"@webiny/app-admin\";\nimport { AuthenticationContextFeature } from \"@webiny/app-admin/features/security/AuthenticationContext/feature.js\";\nimport type {\n IncomingGenericData,\n IWebsocketsContext,\n IWebsocketsContextSendCallable,\n IWebsocketsManagerCloseEvent,\n IWebsocketsManagerErrorEvent\n} from \"~/types.js\";\nimport { WebsocketsCloseCode } from \"~/types.js\";\nimport {\n createWebsocketsAction,\n createWebsocketsActions,\n createWebsocketsConnection,\n createWebsocketsManager,\n createWebsocketsSubscriptionManager\n} from \"./domain/index.js\";\nimport type { IGenericData, IWebsocketsManager } from \"./domain/types.js\";\nimport { getUrl } from \"./utils/getUrl.js\";\n\nexport interface IWebsocketsContextProviderProps {\n loader?: React.ReactElement;\n children: React.ReactNode;\n}\n\nexport const WebsocketsContext = React.createContext<IWebsocketsContext>(\n undefined as unknown as IWebsocketsContext\n);\n\ninterface ICurrentData {\n tenant?: string;\n}\n\nexport const WebsocketsContextProvider = (props: IWebsocketsContextProviderProps) => {\n const { tenant } = useTenantContext();\n const { authenticationContext } = useFeature(AuthenticationContextFeature);\n\n const socketsRef = useRef<IWebsocketsManager | null>(null);\n\n const [current, setCurrent] = useState<ICurrentData>({});\n\n const getToken = useCallback(async () => {\n return await authenticationContext.getIdToken();\n }, [authenticationContext]);\n\n const subscriptionManager = useMemo(() => {\n const manager = createWebsocketsSubscriptionManager();\n\n let currentIteration = 0;\n manager.onClose(event => {\n if (currentIteration > 5 || event.code !== WebsocketsCloseCode.GOING_AWAY) {\n return;\n }\n currentIteration++;\n setTimeout(() => {\n if (!socketsRef.current) {\n return;\n } else if (socketsRef.current.isClosed()) {\n console.log(\"Running auto-reconnect.\");\n\n socketsRef.current.connect();\n }\n }, 1000);\n });\n\n return manager;\n }, []);\n\n /**\n * We need this useEffect to close the websocket connection and remove window focus event in case component is unmounted.\n * This will, probably, happen only during the development phase.\n *\n * If we did not disconnect on component unmount, we would have a memory leak - multiple connections would be opened.\n */\n useEffect(() => {\n /**\n * We want to add a window event listener which will check if the connection is closed, and if its - it will connect again.\n */\n const abortController = new AbortController();\n\n window.addEventListener(\n \"focus\",\n () => {\n if (!socketsRef.current) {\n return;\n } else if (socketsRef.current.isClosed()) {\n console.log(\"Running auto-reconnect on focus.\");\n socketsRef.current.connect();\n }\n },\n { signal: abortController.signal }\n );\n window.addEventListener(\n \"close\",\n () => {\n subscriptionManager.triggerOnClose(\n new CloseEvent(\"windowClose\", {\n code: WebsocketsCloseCode.GOING_AWAY,\n reason: \"Closing Window or Tab.\"\n })\n );\n },\n { signal: abortController.signal }\n );\n\n return () => {\n abortController.abort();\n };\n }, []);\n\n useEffect(() => {\n (async () => {\n const token = await getToken();\n if (!token || !tenant) {\n return;\n } else if (current.tenant === tenant) {\n return;\n } else if (socketsRef.current) {\n await socketsRef.current.close(WebsocketsCloseCode.NORMAL, \"Changing tenant.\");\n }\n const url = getUrl();\n\n if (!url) {\n console.error(\"Not possible to connect to the websocket without a valid URL.\", {\n tenant,\n token\n });\n return;\n }\n\n socketsRef.current = createWebsocketsManager(\n createWebsocketsConnection({\n subscriptionManager,\n url,\n tenant,\n getToken,\n protocol: [\"webiny-ws-v1\"]\n })\n );\n await socketsRef.current.connect();\n\n setCurrent({ tenant });\n })();\n }, [tenant, subscriptionManager, getToken]);\n\n const websocketActions = useMemo(() => {\n return createWebsocketsActions({\n manager: socketsRef.current!,\n tenant,\n getToken\n });\n }, [socketsRef.current, tenant, getToken]);\n\n const send = useCallback<IWebsocketsContextSendCallable>(\n async (action, data, timeout) => {\n return websocketActions.run({\n action,\n data,\n timeout\n });\n },\n [websocketActions]\n );\n\n const createAction = useCallback(\n <T extends IGenericData = IGenericData, R extends IGenericData = IGenericData>(\n name: string\n ) => {\n return createWebsocketsAction<T, R>(websocketActions, name);\n },\n [websocketActions]\n );\n\n const onMessage = useCallback(\n <T extends IncomingGenericData = IncomingGenericData>(\n action: string,\n cb: (data: T) => void\n ) => {\n return socketsRef.current!.onMessage<T>(async event => {\n if (event.data.action !== action) {\n return;\n }\n cb(event.data);\n });\n },\n [socketsRef.current]\n );\n\n const onError = useCallback(\n (cb: (data: IWebsocketsManagerErrorEvent) => void) => {\n return socketsRef.current!.onError(data => {\n return cb(data);\n });\n },\n [socketsRef.current]\n );\n\n const onClose = useCallback(\n (cb: (data: IWebsocketsManagerCloseEvent) => void) => {\n return socketsRef.current!.onClose(data => {\n return cb(data);\n });\n },\n [socketsRef.current]\n );\n\n if (!socketsRef.current) {\n return props.loader || null;\n }\n\n const value: IWebsocketsContext = {\n send,\n createAction,\n onMessage,\n onError,\n onClose\n };\n return <WebsocketsContext.Provider value={value} {...props} />;\n};\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"WebsocketsContextProvider.js","sources":["../src/WebsocketsContextProvider.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useFeature, useTenantContext } from \"@webiny/app-admin\";\nimport { AuthenticationContextFeature } from \"@webiny/app-admin/features/security/AuthenticationContext/feature.js\";\nimport { EventPublisherFeature } from \"@webiny/app/features/eventPublisher/feature.js\";\nimport { EnvConfigFeature } from \"@webiny/app/features/envConfig/feature.js\";\nimport { WebsocketEvent } from \"./events/WebsocketEvent.js\";\nimport type {\n IncomingGenericData,\n IWebsocketsContext,\n IWebsocketsContextSendCallable,\n IWebsocketsManagerCloseEvent,\n IWebsocketsManagerErrorEvent\n} from \"~/types.js\";\nimport { WebsocketsCloseCode } from \"~/types.js\";\nimport {\n createWebsocketsAction,\n createWebsocketsActions,\n createWebsocketsConnection,\n createWebsocketsManager,\n createWebsocketsSubscriptionManager\n} from \"./domain/index.js\";\nimport type { IGenericData, IWebsocketsManager } from \"./domain/types.js\";\nimport type { IWebsocketsSubscription } from \"./domain/abstractions/IWebsocketsSubscriptionManager.js\";\n\n// No-op subscription returned when WS isn't connected (no URL configured).\nconst noopSubscription = (): IWebsocketsSubscription<any> => ({\n cb: () => undefined,\n id: \"\",\n off: () => undefined\n});\n\nexport interface IWebsocketsContextProviderProps {\n loader?: React.ReactElement;\n children: React.ReactNode;\n}\n\nexport const WebsocketsContext = React.createContext<IWebsocketsContext>(\n undefined as unknown as IWebsocketsContext\n);\n\ninterface ICurrentData {\n tenant?: string;\n}\n\nexport const WebsocketsContextProvider = (props: IWebsocketsContextProviderProps) => {\n const { tenant } = useTenantContext();\n const { authenticationContext } = useFeature(AuthenticationContextFeature);\n const { eventPublisher } = useFeature(EventPublisherFeature);\n\n // The WebSocket URL is resolved once, at the admin composition root, into EnvConfig (the only\n // place that reads process.env). Empty string = not configured → real-time updates are disabled.\n const envConfig = useFeature(EnvConfigFeature);\n const configuredWsUrl = envConfig.get(\"websocketUrl\");\n const wsUrl = configuredWsUrl ? configuredWsUrl : undefined;\n\n const socketsRef = useRef<IWebsocketsManager | null>(null);\n\n const [current, setCurrent] = useState<ICurrentData>({});\n\n const getToken = useCallback(async () => {\n return await authenticationContext.getIdToken();\n }, [authenticationContext]);\n\n // Reconnecting after an unexpected close (with backoff) and the keepalive heartbeat live in\n // WebsocketsConnection, next to the socket they manage.\n const subscriptionManager = useMemo(() => createWebsocketsSubscriptionManager(), []);\n\n /**\n * We need this useEffect to close the websocket connection and remove window focus event in case component is unmounted.\n * This will, probably, happen only during the development phase.\n *\n * If we did not disconnect on component unmount, we would have a memory leak - multiple connections would be opened.\n */\n useEffect(() => {\n /**\n * We want to add a window event listener which will check if the connection is closed, and if its - it will connect again.\n */\n const abortController = new AbortController();\n\n window.addEventListener(\n \"focus\",\n () => {\n if (!socketsRef.current) {\n return;\n } else if (socketsRef.current.isClosed()) {\n console.log(\"Running auto-reconnect on focus.\");\n socketsRef.current.connect();\n }\n },\n { signal: abortController.signal }\n );\n window.addEventListener(\n \"close\",\n () => {\n subscriptionManager.triggerOnClose(\n new CloseEvent(\"windowClose\", {\n code: WebsocketsCloseCode.GOING_AWAY,\n reason: \"Closing Window or Tab.\"\n })\n );\n },\n { signal: abortController.signal }\n );\n\n return () => {\n abortController.abort();\n };\n }, []);\n\n useEffect(() => {\n (async () => {\n const token = await getToken();\n if (!token || !tenant) {\n return;\n } else if (current.tenant === tenant) {\n return;\n } else if (socketsRef.current) {\n await socketsRef.current.close(WebsocketsCloseCode.NORMAL, \"Changing tenant.\");\n }\n const url = wsUrl;\n\n if (!url) {\n // No WS URL configured (e.g. the standalone hosting type) — skip WS, run app anyway.\n console.warn(\"WebSocket URL not configured; real-time updates are disabled.\");\n return;\n }\n\n socketsRef.current = createWebsocketsManager(\n createWebsocketsConnection({\n subscriptionManager,\n url,\n tenant,\n getToken,\n protocol: [\"webiny-ws-v1\"]\n })\n );\n await socketsRef.current.connect();\n\n setCurrent({ tenant });\n })();\n }, [tenant, subscriptionManager, getToken, wsUrl]);\n\n /**\n * Bridge: subscribe once to ALL incoming websocket messages and re-publish each one through\n * the EventPublisher as a `WebsocketEvent`. Feature code registers `WebsocketEventHandler`\n * handlers instead of subscribing to the websocket service directly.\n */\n useEffect(() => {\n if (!socketsRef.current) {\n return;\n }\n const subscription = socketsRef.current.onMessage<IncomingGenericData>(async event => {\n await eventPublisher.publish(new WebsocketEvent(event.data));\n });\n return () => {\n subscription.off();\n };\n }, [current.tenant, eventPublisher]);\n\n const websocketActions = useMemo(() => {\n return createWebsocketsActions({\n manager: socketsRef.current!,\n tenant,\n getToken\n });\n }, [socketsRef.current, tenant, getToken]);\n\n const send = useCallback<IWebsocketsContextSendCallable>(\n async (action, data, timeout) => {\n return websocketActions.run({\n action,\n data,\n timeout\n });\n },\n [websocketActions]\n );\n\n const createAction = useCallback(\n <T extends IGenericData = IGenericData, R extends IGenericData = IGenericData>(\n name: string\n ) => {\n return createWebsocketsAction<T, R>(websocketActions, name);\n },\n [websocketActions]\n );\n\n const onMessage = useCallback(\n <T extends IncomingGenericData = IncomingGenericData>(\n action: string,\n cb: (data: T) => void\n ) => {\n // No-op subscription when WS isn't connected (e.g. no URL configured).\n if (!socketsRef.current) {\n return noopSubscription();\n }\n return socketsRef.current.onMessage<T>(async event => {\n if (event.data.action !== action) {\n return;\n }\n cb(event.data);\n });\n },\n [socketsRef.current]\n );\n\n const onError = useCallback(\n (cb: (data: IWebsocketsManagerErrorEvent) => void) => {\n if (!socketsRef.current) {\n return noopSubscription();\n }\n return socketsRef.current.onError(data => {\n return cb(data);\n });\n },\n [socketsRef.current]\n );\n\n const onClose = useCallback(\n (cb: (data: IWebsocketsManagerCloseEvent) => void) => {\n if (!socketsRef.current) {\n return noopSubscription();\n }\n return socketsRef.current.onClose(data => {\n return cb(data);\n });\n },\n [socketsRef.current]\n );\n\n // Only block the app on the WS connection when WS is actually configured. Without a URL\n // (e.g. the standalone hosting type), skip WS and render the app anyway.\n if (wsUrl && !socketsRef.current) {\n return props.loader || null;\n }\n\n const value: IWebsocketsContext = {\n send,\n createAction,\n onMessage,\n onError,\n onClose\n };\n return <WebsocketsContext.Provider value={value} {...props} />;\n};\n"],"names":["noopSubscription","undefined","WebsocketsContext","React","WebsocketsContextProvider","props","tenant","useTenantContext","authenticationContext","useFeature","AuthenticationContextFeature","eventPublisher","EventPublisherFeature","envConfig","EnvConfigFeature","configuredWsUrl","wsUrl","socketsRef","useRef","current","setCurrent","useState","getToken","useCallback","subscriptionManager","useMemo","createWebsocketsSubscriptionManager","useEffect","abortController","AbortController","window","console","CloseEvent","WebsocketsCloseCode","token","url","createWebsocketsManager","createWebsocketsConnection","subscription","event","WebsocketEvent","websocketActions","createWebsocketsActions","send","action","data","timeout","createAction","name","createWebsocketsAction","onMessage","cb","onError","onClose","value"],"mappings":"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"}
@@ -1,28 +1,21 @@
1
- export class WebsocketsAction {
2
- constructor(actions, name) {
3
- this.name = name;
4
- this.actions = actions;
5
- }
6
- async trigger(params) {
7
- const {
8
- data,
9
- onResponse,
10
- timeout = 10000
11
- } = params || {};
12
- const promise = this.actions.run({
13
- action: this.name,
14
- data,
15
- timeout: onResponse && timeout > 0 ? timeout : undefined
16
- });
17
- if (!onResponse) {
18
- return null;
1
+ class WebsocketsAction {
2
+ constructor(actions, name){
3
+ this.name = name;
4
+ this.actions = actions;
5
+ }
6
+ async trigger(params) {
7
+ const { data, onResponse, timeout = 10000 } = params || {};
8
+ const promise = this.actions.run({
9
+ action: this.name,
10
+ data,
11
+ timeout: onResponse && timeout > 0 ? timeout : void 0
12
+ });
13
+ if (!onResponse) return null;
14
+ const result = await promise;
15
+ return onResponse(result);
19
16
  }
20
- const result = await promise;
21
- return onResponse(result);
22
- }
23
17
  }
24
- export const createWebsocketsAction = (actions, name) => {
25
- return new WebsocketsAction(actions, name);
26
- };
18
+ const createWebsocketsAction = (actions, name)=>new WebsocketsAction(actions, name);
19
+ export { WebsocketsAction, createWebsocketsAction };
27
20
 
28
21
  //# sourceMappingURL=WebsocketsAction.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["WebsocketsAction","constructor","actions","name","trigger","params","data","onResponse","timeout","promise","run","action","undefined","result","createWebsocketsAction"],"sources":["WebsocketsAction.ts"],"sourcesContent":["import type {\n IGenericData,\n IWebsocketsAction,\n IWebsocketsActions,\n IWebsocketsActionsTriggerParams\n} from \"./types.js\";\n\nexport class WebsocketsAction<\n T extends IGenericData = IGenericData,\n R extends IGenericData = IGenericData\n> implements IWebsocketsAction<T, R> {\n private readonly actions: IWebsocketsActions;\n private readonly name: string;\n\n public constructor(actions: IWebsocketsActions, name: string) {\n this.name = name;\n this.actions = actions;\n }\n\n public async trigger(params?: IWebsocketsActionsTriggerParams<T, R>): Promise<R | null> {\n const { data, onResponse, timeout = 10000 } = params || {};\n const promise = this.actions.run<T, R>({\n action: this.name,\n data,\n timeout: onResponse && timeout > 0 ? timeout : undefined\n });\n if (!onResponse) {\n return null;\n }\n const result = await promise;\n\n return onResponse(result);\n }\n}\n\nexport const createWebsocketsAction = <\n T extends IGenericData = IGenericData,\n R extends IGenericData = IGenericData\n>(\n actions: IWebsocketsActions,\n name: string\n): IWebsocketsAction<T, R> => {\n return new WebsocketsAction<T, R>(actions, name);\n};\n"],"mappings":"AAOA,OAAO,MAAMA,gBAAgB,CAGQ;EAI1BC,WAAWA,CAACC,OAA2B,EAAEC,IAAY,EAAE;IAC1D,IAAI,CAACA,IAAI,GAAGA,IAAI;IAChB,IAAI,CAACD,OAAO,GAAGA,OAAO;EAC1B;EAEA,MAAaE,OAAOA,CAACC,MAA8C,EAAqB;IACpF,MAAM;MAAEC,IAAI;MAAEC,UAAU;MAAEC,OAAO,GAAG;IAAM,CAAC,GAAGH,MAAM,IAAI,CAAC,CAAC;IAC1D,MAAMI,OAAO,GAAG,IAAI,CAACP,OAAO,CAACQ,GAAG,CAAO;MACnCC,MAAM,EAAE,IAAI,CAACR,IAAI;MACjBG,IAAI;MACJE,OAAO,EAAED,UAAU,IAAIC,OAAO,GAAG,CAAC,GAAGA,OAAO,GAAGI;IACnD,CAAC,CAAC;IACF,IAAI,CAACL,UAAU,EAAE;MACb,OAAO,IAAI;IACf;IACA,MAAMM,MAAM,GAAG,MAAMJ,OAAO;IAE5B,OAAOF,UAAU,CAACM,MAAM,CAAC;EAC7B;AACJ;AAEA,OAAO,MAAMC,sBAAsB,GAAGA,CAIlCZ,OAA2B,EAC3BC,IAAY,KACc;EAC1B,OAAO,IAAIH,gBAAgB,CAAOE,OAAO,EAAEC,IAAI,CAAC;AACpD,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"domain/WebsocketsAction.js","sources":["../../src/domain/WebsocketsAction.ts"],"sourcesContent":["import type {\n IGenericData,\n IWebsocketsAction,\n IWebsocketsActions,\n IWebsocketsActionsTriggerParams\n} from \"./types.js\";\n\nexport class WebsocketsAction<\n T extends IGenericData = IGenericData,\n R extends IGenericData = IGenericData\n> implements IWebsocketsAction<T, R> {\n private readonly actions: IWebsocketsActions;\n private readonly name: string;\n\n public constructor(actions: IWebsocketsActions, name: string) {\n this.name = name;\n this.actions = actions;\n }\n\n public async trigger(params?: IWebsocketsActionsTriggerParams<T, R>): Promise<R | null> {\n const { data, onResponse, timeout = 10000 } = params || {};\n const promise = this.actions.run<T, R>({\n action: this.name,\n data,\n timeout: onResponse && timeout > 0 ? timeout : undefined\n });\n if (!onResponse) {\n return null;\n }\n const result = await promise;\n\n return onResponse(result);\n }\n}\n\nexport const createWebsocketsAction = <\n T extends IGenericData = IGenericData,\n R extends IGenericData = IGenericData\n>(\n actions: IWebsocketsActions,\n name: string\n): IWebsocketsAction<T, R> => {\n return new WebsocketsAction<T, R>(actions, name);\n};\n"],"names":["WebsocketsAction","actions","name","params","data","onResponse","timeout","promise","undefined","result","createWebsocketsAction"],"mappings":"AAOO,MAAMA;IAOT,YAAmBC,OAA2B,EAAEC,IAAY,CAAE;QAC1D,IAAI,CAAC,IAAI,GAAGA;QACZ,IAAI,CAAC,OAAO,GAAGD;IACnB;IAEA,MAAa,QAAQE,MAA8C,EAAqB;QACpF,MAAM,EAAEC,IAAI,EAAEC,UAAU,EAAEC,UAAU,KAAK,EAAE,GAAGH,UAAU,CAAC;QACzD,MAAMI,UAAU,IAAI,CAAC,OAAO,CAAC,GAAG,CAAO;YACnC,QAAQ,IAAI,CAAC,IAAI;YACjBH;YACA,SAASC,cAAcC,UAAU,IAAIA,UAAUE;QACnD;QACA,IAAI,CAACH,YACD,OAAO;QAEX,MAAMI,SAAS,MAAMF;QAErB,OAAOF,WAAWI;IACtB;AACJ;AAEO,MAAMC,yBAAyB,CAIlCT,SACAC,OAEO,IAAIF,iBAAuBC,SAASC"}
@@ -1,79 +1,58 @@
1
- export class WebsocketsActions {
2
- constructor(params) {
3
- this.manager = params.manager;
4
- this.tenant = params.tenant;
5
- this.getToken = params.getToken;
6
- }
7
- async run(params) {
8
- const {
9
- action,
10
- timeout,
11
- data
12
- } = params;
13
- const token = await this.getToken();
14
- if (!token) {
15
- console.error("Token is not set - cannot send a websocket message.");
16
- return null;
17
- } else if (!this.tenant) {
18
- console.error("Tenant is not set - cannot send a websocket message.");
19
- return null;
1
+ class WebsocketsActions {
2
+ constructor(params){
3
+ this.manager = params.manager;
4
+ this.tenant = params.tenant;
5
+ this.getToken = params.getToken;
20
6
  }
21
-
22
- /**
23
- * If no timeout was sent, we will just send the message and return null.
24
- * No waiting for the response.
25
- */
26
- if (!timeout || timeout < 0) {
27
- this.manager.send({
28
- /**
29
- * It is ok to cast as we are checking the values a few lines above.
30
- */
31
- token,
32
- tenant: this.tenant,
33
- action,
34
- data: data || {}
35
- });
36
- return null;
37
- }
38
- /**
39
- * In case of a timeout, we will send the message and wait for the response.
40
- */
41
- return await new Promise((resolve, reject) => {
42
- let promiseTimeout = null;
43
- const subscription = this.manager.onMessage(async event => {
44
- if (event.data.messageId !== subscription.id) {
45
- return;
7
+ async run(params) {
8
+ const { action, timeout, data } = params;
9
+ const token = await this.getToken();
10
+ if (token) {
11
+ if (!this.tenant) {
12
+ console.error("Tenant is not set - cannot send a websocket message.");
13
+ return null;
14
+ }
15
+ } else {
16
+ console.error("Token is not set - cannot send a websocket message.");
17
+ return null;
46
18
  }
47
- resolve(event.data);
48
- subscription.off();
49
- if (!promiseTimeout) {
50
- return;
19
+ if (!timeout || timeout < 0) {
20
+ this.manager.send({
21
+ token,
22
+ tenant: this.tenant,
23
+ action,
24
+ data: data || {}
25
+ });
26
+ return null;
51
27
  }
52
- clearTimeout(promiseTimeout);
53
- });
54
- promiseTimeout = setTimeout(() => {
55
- const message = `Websocket action "${action}" timeout.`;
56
- subscription.off();
57
- reject(new Error(message));
58
- }, timeout);
59
- this.manager.send({
60
- /**
61
- * It is ok to cast as we are checking the values a few lines above.
62
- */
63
- token,
64
- tenant: this.tenant,
65
- messageId: subscription.id,
66
- action,
67
- data: data || {}
68
- });
69
- }).catch(ex => {
70
- console.error("Error while sending websocket message.", ex);
71
- return null;
72
- });
73
- }
28
+ return await new Promise((resolve, reject)=>{
29
+ let promiseTimeout = null;
30
+ const subscription = this.manager.onMessage(async (event)=>{
31
+ if (event.data.messageId !== subscription.id) return;
32
+ resolve(event.data);
33
+ subscription.off();
34
+ if (!promiseTimeout) return;
35
+ clearTimeout(promiseTimeout);
36
+ });
37
+ promiseTimeout = setTimeout(()=>{
38
+ const message = `Websocket action "${action}" timeout.`;
39
+ subscription.off();
40
+ reject(new Error(message));
41
+ }, timeout);
42
+ this.manager.send({
43
+ token,
44
+ tenant: this.tenant,
45
+ messageId: subscription.id,
46
+ action,
47
+ data: data || {}
48
+ });
49
+ }).catch((ex)=>{
50
+ console.error("Error while sending websocket message.", ex);
51
+ return null;
52
+ });
53
+ }
74
54
  }
75
- export const createWebsocketsActions = params => {
76
- return new WebsocketsActions(params);
77
- };
55
+ const createWebsocketsActions = (params)=>new WebsocketsActions(params);
56
+ export { WebsocketsActions, createWebsocketsActions };
78
57
 
79
58
  //# sourceMappingURL=WebsocketsActions.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["WebsocketsActions","constructor","params","manager","tenant","getToken","run","action","timeout","data","token","console","error","send","Promise","resolve","reject","promiseTimeout","subscription","onMessage","event","messageId","id","off","clearTimeout","setTimeout","message","Error","catch","ex","createWebsocketsActions"],"sources":["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"],"mappings":"AAcA,OAAO,MAAMA,iBAAiB,CAA+B;EAMlDC,WAAWA,CAACC,MAA+B,EAAE;IAChD,IAAI,CAACC,OAAO,GAAGD,MAAM,CAACC,OAAO;IAC7B,IAAI,CAACC,MAAM,GAAGF,MAAM,CAACE,MAAM;IAC3B,IAAI,CAACC,QAAQ,GAAGH,MAAM,CAACG,QAAQ;EACnC;EAEA,MAAaC,GAAGA,CACZJ,MAAsC,EACrB;IACjB,MAAM;MAAEK,MAAM;MAAEC,OAAO;MAAEC;IAAK,CAAC,GAAGP,MAAM;IACxC,MAAMQ,KAAK,GAAG,MAAM,IAAI,CAACL,QAAQ,CAAC,CAAC;IACnC,IAAI,CAACK,KAAK,EAAE;MACRC,OAAO,CAACC,KAAK,CAAC,qDAAqD,CAAC;MACpE,OAAO,IAAI;IACf,CAAC,MAAM,IAAI,CAAC,IAAI,CAACR,MAAM,EAAE;MACrBO,OAAO,CAACC,KAAK,CAAC,sDAAsD,CAAC;MACrE,OAAO,IAAI;IACf;;IAEA;AACR;AACA;AACA;IACQ,IAAI,CAACJ,OAAO,IAAIA,OAAO,GAAG,CAAC,EAAE;MACzB,IAAI,CAACL,OAAO,CAACU,IAAI,CAA+B;QAC5C;AAChB;AACA;QACgBH,KAAK;QACLN,MAAM,EAAE,IAAI,CAACA,MAAgB;QAC7BG,MAAM;QACNE,IAAI,EAAEA,IAAI,IAAK,CAAC;MACpB,CAAC,CAAC;MACF,OAAO,IAAI;IACf;IACA;AACR;AACA;IACQ,OAAO,MAAM,IAAIK,OAAO,CAAI,CAACC,OAAO,EAAEC,MAAM,KAAK;MAC7C,IAAIC,cAAqC,GAAG,IAAI;MAChD,MAAMC,YAAY,GAAG,IAAI,CAACf,OAAO,CAACgB,SAAS,CAAI,MAAMC,KAAK,IAAI;QAC1D,IAAIA,KAAK,CAACX,IAAI,CAACY,SAAS,KAAKH,YAAY,CAACI,EAAE,EAAE;UAC1C;QACJ;QACAP,OAAO,CAACK,KAAK,CAACX,IAAI,CAAC;QACnBS,YAAY,CAACK,GAAG,CAAC,CAAC;QAClB,IAAI,CAACN,cAAc,EAAE;UACjB;QACJ;QACAO,YAAY,CAACP,cAAc,CAAC;MAChC,CAAC,CAAC;MAEFA,cAAc,GAAGQ,UAAU,CAAC,MAAM;QAC9B,MAAMC,OAAO,GAAG,qBAAqBnB,MAAM,YAAY;QACvDW,YAAY,CAACK,GAAG,CAAC,CAAC;QAClBP,MAAM,CAAC,IAAIW,KAAK,CAACD,OAAO,CAAC,CAAC;MAC9B,CAAC,EAAElB,OAAO,CAAC;MAEX,IAAI,CAACL,OAAO,CAACU,IAAI,CAA+B;QAC5C;AAChB;AACA;QACgBH,KAAK;QACLN,MAAM,EAAE,IAAI,CAACA,MAAgB;QAC7BiB,SAAS,EAAEH,YAAY,CAACI,EAAE;QAC1Bf,MAAM;QACNE,IAAI,EAAEA,IAAI,IAAK,CAAC;MACpB,CAAC,CAAC;IACN,CAAC,CAAC,CAACmB,KAAK,CAACC,EAAE,IAAI;MACXlB,OAAO,CAACC,KAAK,CAAC,wCAAwC,EAAEiB,EAAE,CAAC;MAC3D,OAAO,IAAI;IACf,CAAC,CAAC;EACN;AACJ;AAEA,OAAO,MAAMC,uBAAuB,GAAI5B,MAA+B,IAAyB;EAC5F,OAAO,IAAIF,iBAAiB,CAACE,MAAM,CAAC;AACxC,CAAC","ignoreList":[]}
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"}
@@ -10,6 +10,12 @@ 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;
@@ -17,6 +23,8 @@ export interface IWebsocketsConnectionParams {
17
23
  subscriptionManager: IWebsocketsSubscriptionManager;
18
24
  protocol?: IWebsocketsConnectProtocol;
19
25
  factory?: IWebsocketsConnectionFactory;
26
+ heartbeatInterval?: number;
27
+ reconnectDelay?: (attempt: number) => number;
20
28
  }
21
29
  export declare class WebsocketsConnection implements IWebsocketsConnection {
22
30
  private readonly url;
@@ -25,6 +33,18 @@ export declare class WebsocketsConnection implements IWebsocketsConnection {
25
33
  private readonly protocol;
26
34
  readonly subscriptionManager: IWebsocketsSubscriptionManager;
27
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;
28
48
  constructor(params: IWebsocketsConnectionParams);
29
49
  setTenant(tenant: string): void;
30
50
  connect(): Promise<void>;
@@ -34,6 +54,16 @@ export declare class WebsocketsConnection implements IWebsocketsConnection {
34
54
  isClosed(): boolean;
35
55
  private createUrl;
36
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;
37
67
  }
38
68
  export declare const createWebsocketsConnection: (params: IWebsocketsConnectionParams) => IWebsocketsConnection;
39
69
  export {};