@webiny/app-websockets 6.4.5 → 6.6.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/WebsocketsContextProvider.js +42 -12
- package/WebsocketsContextProvider.js.map +1 -1
- 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/index.d.ts +4 -1
- package/index.js +2 -0
- package/index.js.map +1 -1
- package/package.json +7 -6
- package/utils/getUrl.d.ts +0 -1
- package/utils/getUrl.js +0 -7
- package/utils/getUrl.js.map +0 -1
|
@@ -1,13 +1,24 @@
|
|
|
1
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
|
-
|
|
9
|
+
const noopSubscription = ()=>({
|
|
10
|
+
cb: ()=>void 0,
|
|
11
|
+
id: "",
|
|
12
|
+
off: ()=>void 0
|
|
13
|
+
});
|
|
7
14
|
const WebsocketsContext = /*#__PURE__*/ react.createContext(void 0);
|
|
8
15
|
const WebsocketsContextProvider = (props)=>{
|
|
9
16
|
const { tenant } = useTenantContext();
|
|
10
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;
|
|
11
22
|
const socketsRef = useRef(null);
|
|
12
23
|
const [current, setCurrent] = useState({});
|
|
13
24
|
const getToken = useCallback(async ()=>await authenticationContext.getIdToken(), [
|
|
@@ -58,11 +69,8 @@ const WebsocketsContextProvider = (props)=>{
|
|
|
58
69
|
if (!token || !tenant) return;
|
|
59
70
|
if (current.tenant === tenant) return;
|
|
60
71
|
if (socketsRef.current) await socketsRef.current.close(WebsocketsCloseCode.NORMAL, "Changing tenant.");
|
|
61
|
-
const url =
|
|
62
|
-
if (!url) return void console.
|
|
63
|
-
tenant,
|
|
64
|
-
token
|
|
65
|
-
});
|
|
72
|
+
const url = wsUrl;
|
|
73
|
+
if (!url) return void console.warn("WebSocket URL not configured; real-time updates are disabled.");
|
|
66
74
|
socketsRef.current = createWebsocketsManager(createWebsocketsConnection({
|
|
67
75
|
subscriptionManager,
|
|
68
76
|
url,
|
|
@@ -80,7 +88,20 @@ const WebsocketsContextProvider = (props)=>{
|
|
|
80
88
|
}, [
|
|
81
89
|
tenant,
|
|
82
90
|
subscriptionManager,
|
|
83
|
-
getToken
|
|
91
|
+
getToken,
|
|
92
|
+
wsUrl
|
|
93
|
+
]);
|
|
94
|
+
useEffect(()=>{
|
|
95
|
+
if (!socketsRef.current) return;
|
|
96
|
+
const subscription = socketsRef.current.onMessage(async (event)=>{
|
|
97
|
+
await eventPublisher.publish(new WebsocketEvent(event.data));
|
|
98
|
+
});
|
|
99
|
+
return ()=>{
|
|
100
|
+
subscription.off();
|
|
101
|
+
};
|
|
102
|
+
}, [
|
|
103
|
+
current.tenant,
|
|
104
|
+
eventPublisher
|
|
84
105
|
]);
|
|
85
106
|
const websocketActions = useMemo(()=>createWebsocketsActions({
|
|
86
107
|
manager: socketsRef.current,
|
|
@@ -101,19 +122,28 @@ const WebsocketsContextProvider = (props)=>{
|
|
|
101
122
|
const createAction = useCallback((name)=>createWebsocketsAction(websocketActions, name), [
|
|
102
123
|
websocketActions
|
|
103
124
|
]);
|
|
104
|
-
const onMessage = useCallback((action, cb)=>
|
|
125
|
+
const onMessage = useCallback((action, cb)=>{
|
|
126
|
+
if (!socketsRef.current) return noopSubscription();
|
|
127
|
+
return socketsRef.current.onMessage(async (event)=>{
|
|
105
128
|
if (event.data.action !== action) return;
|
|
106
129
|
cb(event.data);
|
|
107
|
-
})
|
|
130
|
+
});
|
|
131
|
+
}, [
|
|
108
132
|
socketsRef.current
|
|
109
133
|
]);
|
|
110
|
-
const onError = useCallback((cb)=>
|
|
134
|
+
const onError = useCallback((cb)=>{
|
|
135
|
+
if (!socketsRef.current) return noopSubscription();
|
|
136
|
+
return socketsRef.current.onError((data)=>cb(data));
|
|
137
|
+
}, [
|
|
111
138
|
socketsRef.current
|
|
112
139
|
]);
|
|
113
|
-
const onClose = useCallback((cb)=>
|
|
140
|
+
const onClose = useCallback((cb)=>{
|
|
141
|
+
if (!socketsRef.current) return noopSubscription();
|
|
142
|
+
return socketsRef.current.onClose((data)=>cb(data));
|
|
143
|
+
}, [
|
|
114
144
|
socketsRef.current
|
|
115
145
|
]);
|
|
116
|
-
if (!socketsRef.current) return props.loader || null;
|
|
146
|
+
if (wsUrl && !socketsRef.current) return props.loader || null;
|
|
117
147
|
const value = {
|
|
118
148
|
send,
|
|
119
149
|
createAction,
|
|
@@ -1 +1 @@
|
|
|
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 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"],"names":["WebsocketsContext","React","undefined","WebsocketsContextProvider","props","tenant","useTenantContext","authenticationContext","useFeature","AuthenticationContextFeature","socketsRef","useRef","current","setCurrent","useState","getToken","useCallback","subscriptionManager","useMemo","manager","createWebsocketsSubscriptionManager","currentIteration","event","WebsocketsCloseCode","setTimeout","console","useEffect","abortController","AbortController","window","CloseEvent","token","url","getUrl","createWebsocketsManager","createWebsocketsConnection","websocketActions","createWebsocketsActions","send","action","data","timeout","createAction","name","createWebsocketsAction","onMessage","cb","onError","onClose","value"],"mappings":"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"}
|
|
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 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 = wsUrl;\n\n if (!url) {\n // No WS URL configured (e.g. self-hosted server 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 self-hosted server 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","manager","createWebsocketsSubscriptionManager","currentIteration","event","WebsocketsCloseCode","setTimeout","console","useEffect","abortController","AbortController","window","CloseEvent","token","url","createWebsocketsManager","createWebsocketsConnection","subscription","WebsocketEvent","websocketActions","createWebsocketsActions","send","action","data","timeout","createAction","name","createWebsocketsAction","onMessage","cb","onError","onClose","value"],"mappings":";;;;;;;;AAyBA,MAAMA,mBAAmB,IAAqC;QAC1D,IAAI,IAAMC;QACV,IAAI;QACJ,KAAK,IAAMA;IACf;AAOO,MAAMC,oBAAoB,WAAHA,GAAGC,MAAAA,aAAmB,CAChDF;AAOG,MAAMG,4BAA4B,CAACC;IACtC,MAAM,EAAEC,MAAM,EAAE,GAAGC;IACnB,MAAM,EAAEC,qBAAqB,EAAE,GAAGC,WAAWC;IAC7C,MAAM,EAAEC,cAAc,EAAE,GAAGF,WAAWG;IAItC,MAAMC,YAAYJ,WAAWK;IAC7B,MAAMC,kBAAkBF,UAAU,GAAG,CAAC;IACtC,MAAMG,QAAQD,kBAAkBA,kBAAkBd;IAElD,MAAMgB,aAAaC,OAAkC;IAErD,MAAM,CAACC,SAASC,WAAW,GAAGC,SAAuB,CAAC;IAEtD,MAAMC,WAAWC,YAAY,UAClB,MAAMf,sBAAsB,UAAU,IAC9C;QAACA;KAAsB;IAE1B,MAAMgB,sBAAsBC,QAAQ;QAChC,MAAMC,UAAUC;QAEhB,IAAIC,mBAAmB;QACvBF,QAAQ,OAAO,CAACG,CAAAA;YACZ,IAAID,mBAAmB,KAAKC,MAAM,IAAI,KAAKC,oBAAoB,UAAU,EACrE;YAEJF;YACAG,WAAW;gBACP,IAAI,CAACd,WAAW,OAAO,EACnB;gBACG,IAAIA,WAAW,OAAO,CAAC,QAAQ,IAAI;oBACtCe,QAAQ,GAAG,CAAC;oBAEZf,WAAW,OAAO,CAAC,OAAO;gBAC9B;YACJ,GAAG;QACP;QAEA,OAAOS;IACX,GAAG,EAAE;IAQLO,UAAU;QAIN,MAAMC,kBAAkB,IAAIC;QAE5BC,OAAO,gBAAgB,CACnB,SACA;YACI,IAAI,CAACnB,WAAW,OAAO,EACnB;YACG,IAAIA,WAAW,OAAO,CAAC,QAAQ,IAAI;gBACtCe,QAAQ,GAAG,CAAC;gBACZf,WAAW,OAAO,CAAC,OAAO;YAC9B;QACJ,GACA;YAAE,QAAQiB,gBAAgB,MAAM;QAAC;QAErCE,OAAO,gBAAgB,CACnB,SACA;YACIZ,oBAAoB,cAAc,CAC9B,IAAIa,WAAW,eAAe;gBAC1B,MAAMP,oBAAoB,UAAU;gBACpC,QAAQ;YACZ;QAER,GACA;YAAE,QAAQI,gBAAgB,MAAM;QAAC;QAGrC,OAAO;YACHA,gBAAgB,KAAK;QACzB;IACJ,GAAG,EAAE;IAELD,UAAU;QACL;YACG,MAAMK,QAAQ,MAAMhB;YACpB,IAAI,CAACgB,SAAS,CAAChC,QACX;YACG,IAAIa,QAAQ,MAAM,KAAKb,QAC1B;YACG,IAAIW,WAAW,OAAO,EACzB,MAAMA,WAAW,OAAO,CAAC,KAAK,CAACa,oBAAoB,MAAM,EAAE;YAE/D,MAAMS,MAAMvB;YAEZ,IAAI,CAACuB,KAAK,YAENP,QAAQ,IAAI,CAAC;YAIjBf,WAAW,OAAO,GAAGuB,wBACjBC,2BAA2B;gBACvBjB;gBACAe;gBACAjC;gBACAgB;gBACA,UAAU;oBAAC;iBAAe;YAC9B;YAEJ,MAAML,WAAW,OAAO,CAAC,OAAO;YAEhCG,WAAW;gBAAEd;YAAO;QACxB;IACJ,GAAG;QAACA;QAAQkB;QAAqBF;QAAUN;KAAM;IAOjDiB,UAAU;QACN,IAAI,CAAChB,WAAW,OAAO,EACnB;QAEJ,MAAMyB,eAAezB,WAAW,OAAO,CAAC,SAAS,CAAsB,OAAMY;YACzE,MAAMlB,eAAe,OAAO,CAAC,IAAIgC,eAAed,MAAM,IAAI;QAC9D;QACA,OAAO;YACHa,aAAa,GAAG;QACpB;IACJ,GAAG;QAACvB,QAAQ,MAAM;QAAER;KAAe;IAEnC,MAAMiC,mBAAmBnB,QAAQ,IACtBoB,wBAAwB;YAC3B,SAAS5B,WAAW,OAAO;YAC3BX;YACAgB;QACJ,IACD;QAACL,WAAW,OAAO;QAAEX;QAAQgB;KAAS;IAEzC,MAAMwB,OAAOvB,YACT,OAAOwB,QAAQC,MAAMC,UACVL,iBAAiB,GAAG,CAAC;YACxBG;YACAC;YACAC;QACJ,IAEJ;QAACL;KAAiB;IAGtB,MAAMM,eAAe3B,YACjB,CACI4B,OAEOC,uBAA6BR,kBAAkBO,OAE1D;QAACP;KAAiB;IAGtB,MAAMS,YAAY9B,YACd,CACIwB,QACAO;QAGA,IAAI,CAACrC,WAAW,OAAO,EACnB,OAAOjB;QAEX,OAAOiB,WAAW,OAAO,CAAC,SAAS,CAAI,OAAMY;YACzC,IAAIA,MAAM,IAAI,CAAC,MAAM,KAAKkB,QACtB;YAEJO,GAAGzB,MAAM,IAAI;QACjB;IACJ,GACA;QAACZ,WAAW,OAAO;KAAC;IAGxB,MAAMsC,UAAUhC,YACZ,CAAC+B;QACG,IAAI,CAACrC,WAAW,OAAO,EACnB,OAAOjB;QAEX,OAAOiB,WAAW,OAAO,CAAC,OAAO,CAAC+B,CAAAA,OACvBM,GAAGN;IAElB,GACA;QAAC/B,WAAW,OAAO;KAAC;IAGxB,MAAMuC,UAAUjC,YACZ,CAAC+B;QACG,IAAI,CAACrC,WAAW,OAAO,EACnB,OAAOjB;QAEX,OAAOiB,WAAW,OAAO,CAAC,OAAO,CAAC+B,CAAAA,OACvBM,GAAGN;IAElB,GACA;QAAC/B,WAAW,OAAO;KAAC;IAKxB,IAAID,SAAS,CAACC,WAAW,OAAO,EAC5B,OAAOZ,MAAM,MAAM,IAAI;IAG3B,MAAMoD,QAA4B;QAC9BX;QACAI;QACAG;QACAE;QACAC;IACJ;IACA,OAAO,WAAP,GAAO,oBAACtD,kBAAkB,QAAQ;QAAC,OAAOuD;QAAQ,GAAGpD,KAAK;;AAC9D"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseEvent } from "@webiny/app/features/eventPublisher/index.js";
|
|
2
|
+
import type { IncomingGenericData } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Published for every incoming websocket message. Carries the raw message data
|
|
5
|
+
* (which always includes an `action`), and routes to `WebsocketEventHandler` handlers.
|
|
6
|
+
*/
|
|
7
|
+
export declare class WebsocketEvent extends BaseEvent<IncomingGenericData> {
|
|
8
|
+
readonly eventType: "Websockets/MessageReceived";
|
|
9
|
+
getHandlerAbstraction(): import("@webiny/di").Abstraction<import("@webiny/app/features/eventPublisher/abstractions.js").IEventHandler<WebsocketEvent>>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseEvent } from "@webiny/app/features/eventPublisher/index.js";
|
|
2
|
+
import { WebsocketEventHandler } from "./abstractions.js";
|
|
3
|
+
class WebsocketEvent extends BaseEvent {
|
|
4
|
+
getHandlerAbstraction() {
|
|
5
|
+
return WebsocketEventHandler;
|
|
6
|
+
}
|
|
7
|
+
constructor(...args){
|
|
8
|
+
super(...args), this.eventType = "Websockets/MessageReceived";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export { WebsocketEvent };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=WebsocketEvent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events/WebsocketEvent.js","sources":["../../src/events/WebsocketEvent.ts"],"sourcesContent":["import { BaseEvent } from \"@webiny/app/features/eventPublisher/index.js\";\nimport { WebsocketEventHandler } from \"./abstractions.js\";\nimport type { IncomingGenericData } from \"~/types.js\";\n\n/**\n * Published for every incoming websocket message. Carries the raw message data\n * (which always includes an `action`), and routes to `WebsocketEventHandler` handlers.\n */\nexport class WebsocketEvent extends BaseEvent<IncomingGenericData> {\n readonly eventType = \"Websockets/MessageReceived\" as const;\n\n getHandlerAbstraction() {\n return WebsocketEventHandler;\n }\n}\n"],"names":["WebsocketEvent","BaseEvent","WebsocketEventHandler"],"mappings":";;AAQO,MAAMA,uBAAuBC;IAGhC,wBAAwB;QACpB,OAAOC;IACX;;QALG,qBACM,SAAS,GAAG;;AAKzB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IEventHandler } from "@webiny/app/features/eventPublisher/index.js";
|
|
2
|
+
import type { WebsocketEvent } from "./WebsocketEvent.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handlers registered against this abstraction receive EVERY incoming websocket message
|
|
5
|
+
* (published as a `WebsocketEvent` by the websockets→EventPublisher bridge). Each handler
|
|
6
|
+
* is responsible for filtering by `event.payload.action` and reacting to the ones it cares
|
|
7
|
+
* about, instead of subscribing to the websocket service directly.
|
|
8
|
+
*/
|
|
9
|
+
export declare const WebsocketEventHandler: import("@webiny/di").Abstraction<IEventHandler<WebsocketEvent>>;
|
|
10
|
+
export declare namespace WebsocketEventHandler {
|
|
11
|
+
type Interface = IEventHandler<WebsocketEvent>;
|
|
12
|
+
type Event = WebsocketEvent;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events/abstractions.js","sources":["../../src/events/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/admin\";\nimport type { IEventHandler } from \"@webiny/app/features/eventPublisher/index.js\";\nimport type { WebsocketEvent } from \"./WebsocketEvent.js\";\n\n/**\n * Handlers registered against this abstraction receive EVERY incoming websocket message\n * (published as a `WebsocketEvent` by the websockets→EventPublisher bridge). Each handler\n * is responsible for filtering by `event.payload.action` and reacting to the ones it cares\n * about, instead of subscribing to the websocket service directly.\n */\nexport const WebsocketEventHandler = createAbstraction<IEventHandler<WebsocketEvent>>(\n \"App/WebsocketEventHandler\"\n);\n\nexport namespace WebsocketEventHandler {\n export type Interface = IEventHandler<WebsocketEvent>;\n export type Event = WebsocketEvent;\n}\n"],"names":["WebsocketEventHandler","createAbstraction"],"mappings":";AAUO,MAAMA,wBAAwBC,kBACjC"}
|
package/index.d.ts
CHANGED
|
@@ -2,6 +2,9 @@ import React from "react";
|
|
|
2
2
|
export interface WebsocketsProviderProps {
|
|
3
3
|
children: React.ReactNode;
|
|
4
4
|
}
|
|
5
|
-
|
|
5
|
+
declare const WebsocketsExtension: () => React.JSX.Element;
|
|
6
|
+
export declare const Websockets: React.MemoExoticComponent<typeof WebsocketsExtension>;
|
|
6
7
|
export * from "./types.js";
|
|
7
8
|
export * from "./hooks/index.js";
|
|
9
|
+
export * from "./events/WebsocketEvent.js";
|
|
10
|
+
export * from "./events/abstractions.js";
|
package/index.js
CHANGED
|
@@ -3,6 +3,8 @@ import { Provider } from "@webiny/app";
|
|
|
3
3
|
import { WebsocketsContextProvider } from "./WebsocketsContextProvider.js";
|
|
4
4
|
export * from "./types.js";
|
|
5
5
|
export * from "./hooks/index.js";
|
|
6
|
+
export * from "./events/WebsocketEvent.js";
|
|
7
|
+
export * from "./events/abstractions.js";
|
|
6
8
|
const WebsocketsHoc = (Component)=>function(props) {
|
|
7
9
|
return /*#__PURE__*/ react.createElement(WebsocketsContextProvider, null, /*#__PURE__*/ react.createElement(Component, props));
|
|
8
10
|
};
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.tsx"],"sourcesContent":["import React from \"react\";\nimport { Provider } from \"@webiny/app\";\nimport { WebsocketsContextProvider } from \"~/WebsocketsContextProvider.js\";\n\nexport interface WebsocketsProviderProps {\n children: React.ReactNode;\n}\n\nconst WebsocketsHoc = (Component: React.ComponentType<React.PropsWithChildren>) => {\n return function WebsocketsProvider(props: WebsocketsProviderProps) {\n return (\n <WebsocketsContextProvider>\n <Component {...props} />\n </WebsocketsContextProvider>\n );\n };\n};\n\nconst WebsocketsExtension = () => {\n return (\n <>\n <Provider hoc={WebsocketsHoc} />\n </>\n );\n};\n\nexport const Websockets = React.memo(WebsocketsExtension);\n\nexport * from \"./types.js\";\nexport * from \"./hooks/index.js\";\n"],"names":["WebsocketsHoc","Component","props","WebsocketsContextProvider","WebsocketsExtension","Provider","Websockets","React"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.tsx"],"sourcesContent":["import React from \"react\";\nimport { Provider } from \"@webiny/app\";\nimport { WebsocketsContextProvider } from \"~/WebsocketsContextProvider.js\";\n\nexport interface WebsocketsProviderProps {\n children: React.ReactNode;\n}\n\nconst WebsocketsHoc = (Component: React.ComponentType<React.PropsWithChildren>) => {\n return function WebsocketsProvider(props: WebsocketsProviderProps) {\n return (\n <WebsocketsContextProvider>\n <Component {...props} />\n </WebsocketsContextProvider>\n );\n };\n};\n\nconst WebsocketsExtension = () => {\n return (\n <>\n <Provider hoc={WebsocketsHoc} />\n </>\n );\n};\n\nexport const Websockets = React.memo(WebsocketsExtension);\n\nexport * from \"./types.js\";\nexport * from \"./hooks/index.js\";\nexport * from \"./events/WebsocketEvent.js\";\nexport * from \"./events/abstractions.js\";\n"],"names":["WebsocketsHoc","Component","props","WebsocketsContextProvider","WebsocketsExtension","Provider","Websockets","React"],"mappings":";;;;;;;AAQA,MAAMA,gBAAgB,CAACC,YACZ,SAA4BC,KAA8B;QAC7D,OAAO,WAAP,GACI,oBAACC,2BAAyBA,MAAAA,WAAAA,GACtB,oBAACF,WAAcC;IAG3B;AAGJ,MAAME,sBAAsB,IACjB,WAAP,GACI,wDACI,oBAACC,UAAQA;QAAC,KAAKL;;AAKpB,MAAMM,aAAa,WAAHA,GAAGC,MAAAA,IAAU,CAACH"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webiny/app-websockets",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.6.0-alpha.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./index.js",
|
|
@@ -17,16 +17,17 @@
|
|
|
17
17
|
],
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@webiny/app": "6.
|
|
21
|
-
"@webiny/app-admin": "6.
|
|
22
|
-
"@webiny/
|
|
20
|
+
"@webiny/app": "6.6.0-alpha.0",
|
|
21
|
+
"@webiny/app-admin": "6.6.0-alpha.0",
|
|
22
|
+
"@webiny/feature": "6.6.0-alpha.0",
|
|
23
|
+
"@webiny/utils": "6.6.0-alpha.0",
|
|
23
24
|
"react": "18.3.1",
|
|
24
25
|
"react-dom": "18.3.1"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
|
-
"@webiny/build-tools": "6.
|
|
28
|
+
"@webiny/build-tools": "6.6.0-alpha.0",
|
|
28
29
|
"rimraf": "6.1.3",
|
|
29
|
-
"typescript": "
|
|
30
|
+
"typescript": "7.0.2"
|
|
30
31
|
},
|
|
31
32
|
"publishConfig": {
|
|
32
33
|
"access": "public"
|
package/utils/getUrl.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const getUrl: () => string | undefined;
|
package/utils/getUrl.js
DELETED
package/utils/getUrl.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils/getUrl.js","sources":["../../src/utils/getUrl.ts"],"sourcesContent":["export const getUrl = (): string | undefined => {\n const websocketApiUrl = process.env.REACT_APP_WEBSOCKET_URL;\n\n return !websocketApiUrl || websocketApiUrl === \"undefined\" ? undefined : websocketApiUrl;\n};\n"],"names":["getUrl","websocketApiUrl","process","undefined"],"mappings":"AAAO,MAAMA,SAAS;IAClB,MAAMC,kBAAkBC,QAAQ,GAAG,CAAC,uBAAuB;IAE3D,OAAO,AAACD,mBAAmBA,AAAoB,gBAApBA,kBAA8CA,kBAAZE;AACjE"}
|