diff --git a/README.md b/README.md index 0b6fd33..354ae20 100644 --- a/README.md +++ b/README.md @@ -387,3 +387,22 @@ If used, an [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/Event - There is no 'CLOSING' readyState for `EventSource`, and as such, the CLOSED readyState is `2` for an `EventSource`, whereas it is `3` for a WebSocket. For purposes of internal consistency, the `readyState` returned by `useWebSocket` will follow the `WebSocket` enumeration and use `3` for the CLOSED event for both instance types. - `getEventSource` will return the underlying EventSource, even if `Options#share` is used -- as opposed to the `WebSocket` equivalent which returns a `Proxy`. - There is no concept of sending messages from the client, and as such `sendMessage` will not be provided. + +### Reset Global State +There are some cases when the global state of the library won't reset with the page. The main behavior relies on the fact that a single page application operates only in one window, but some scenarios allow us to make a new window via `window.open` and inject code there. In that case, child window will be closed, but the global state of the library remains the same in the main window. This happens because react does not finish components lifecycle on window close. + +To avoid troubles with the new initialization of components related to the same URL, you can reset the global state for a specific connection based on your own logic. + +```js +import React, { useEffect } from 'react'; +import { resetGlobalState } from 'react-use-websocket'; + +// insside second window opened via window.open +export const ChildWindow = () => { + useEffect(() => { + window.addEventListener("unload", () => { + resetGlobalState('wss://echo.websocket.org'); + }); + }, []); +} +``` diff --git a/src/index.ts b/src/index.ts index 32153b1..5c09663 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,3 +7,5 @@ export { useSocketIO } from './lib/use-socket-io'; export { ReadyState } from './lib/constants'; export { useEventSource } from './lib/use-event-source'; + +export { resetGlobalState } from './lib/util'; diff --git a/src/lib/globals.test.ts b/src/lib/globals.test.ts new file mode 100644 index 0000000..5fb44a3 --- /dev/null +++ b/src/lib/globals.test.ts @@ -0,0 +1,33 @@ +import {sharedWebSockets, resetWebSockets} from './globals'; +import {WebSocketLike} from './types'; + +const FIRST_URL = 'ws://localhost:1234'; +const SECOND_URL = 'ws://localhost:4321'; + +const websocket1 = {} as WebSocketLike; +const websocket2 = {} as WebSocketLike; + +beforeEach(() => { + resetWebSockets(); +}); + +test('resetWebsockets removes subscribers only for a specific URL', () => { + sharedWebSockets[FIRST_URL] = websocket1; + sharedWebSockets[SECOND_URL] = websocket2; + expect(Object.values(sharedWebSockets)).toHaveLength(2); + + resetWebSockets(FIRST_URL); + + expect(sharedWebSockets[FIRST_URL]).toBeUndefined(); + expect(sharedWebSockets[SECOND_URL]).not.toBeUndefined(); +}); + +test('resetWebsockets removes all subscribers when URL is not set', () => { + sharedWebSockets[FIRST_URL] = websocket1; + sharedWebSockets[SECOND_URL] = websocket2; + expect(Object.values(sharedWebSockets)).toHaveLength(2); + + resetWebSockets(); + + expect(Object.values(sharedWebSockets)).toHaveLength(0); +}); diff --git a/src/lib/globals.ts b/src/lib/globals.ts index 07416ff..6ed6c11 100644 --- a/src/lib/globals.ts +++ b/src/lib/globals.ts @@ -5,3 +5,15 @@ export interface SharedWebSockets { } export const sharedWebSockets: SharedWebSockets = {}; + +export const resetWebSockets = (url?: string): void => { + if (url && sharedWebSockets.hasOwnProperty(url)) { + delete sharedWebSockets[url]; + } else { + for (let url in sharedWebSockets){ + if (sharedWebSockets.hasOwnProperty(url)){ + delete sharedWebSockets[url]; + } + } + } +} diff --git a/src/lib/manage-subscribers.test.ts b/src/lib/manage-subscribers.test.ts index 6349cc0..0160835 100644 --- a/src/lib/manage-subscribers.test.ts +++ b/src/lib/manage-subscribers.test.ts @@ -3,10 +3,12 @@ import { hasSubscribers, addSubscriber, removeSubscriber, + resetSubscribers, } from './manage-subscribers'; import { Subscriber } from './types'; const URL = 'ws://localhost:1234'; +const SECOND_URL = 'ws://localhost:4321' const noop = () => {}; const subscriber1: Subscriber = { @@ -66,3 +68,33 @@ test('removeSubscriber removes a subscriber from a url subscription', () => { removeSubscriber(URL, subscriber2); expect(getSubscribers(URL)).toHaveLength(0); }); + +test('resetSubscribers removes subscribers only for a specific URL', () => { + addSubscriber(URL, subscriber1); + addSubscriber(URL, subscriber2); + expect(getSubscribers(URL)).toHaveLength(2); + + addSubscriber(SECOND_URL, subscriber1); + addSubscriber(SECOND_URL, subscriber2); + expect(getSubscribers(SECOND_URL)).toHaveLength(2); + + resetSubscribers(URL); + + expect(getSubscribers(URL)).toHaveLength(0); + expect(getSubscribers(SECOND_URL)).toHaveLength(2); +}); + +test('resetSubscribers removes all subscribers when URL is not set', () => { + addSubscriber(URL, subscriber1); + addSubscriber(URL, subscriber2); + expect(getSubscribers(URL)).toHaveLength(2); + + addSubscriber(SECOND_URL, subscriber1); + addSubscriber(SECOND_URL, subscriber2); + expect(getSubscribers(SECOND_URL)).toHaveLength(2); + + resetSubscribers(); + + expect(getSubscribers(URL)).toHaveLength(0); + expect(getSubscribers(SECOND_URL)).toHaveLength(0); +}); diff --git a/src/lib/manage-subscribers.ts b/src/lib/manage-subscribers.ts index d3a1ae3..4dca896 100644 --- a/src/lib/manage-subscribers.ts +++ b/src/lib/manage-subscribers.ts @@ -1,4 +1,5 @@ import { Subscriber } from './types'; +import {sharedWebSockets} from "@lib/globals"; export type Subscribers = { [url: string]: Set, @@ -26,3 +27,15 @@ export const addSubscriber = (url: string, subscriber: Subscriber): void => { export const removeSubscriber = (url: string, subscriber: Subscriber): void => { subscribers[url].delete(subscriber); }; + +export const resetSubscribers = (url?: string): void => { + if (url && subscribers.hasOwnProperty(url)) { + delete subscribers[url]; + } else { + for (let url in subscribers){ + if (subscribers.hasOwnProperty(url)){ + delete subscribers[url]; + } + } + } +} diff --git a/src/lib/util.ts b/src/lib/util.ts index 209235e..d53c76f 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -1,7 +1,15 @@ import { WebSocketLike } from './types'; +import {resetWebSockets} from './globals'; +import {resetSubscribers} from './manage-subscribers'; export function assertIsWebSocket ( webSocketInstance: WebSocketLike, ): asserts webSocketInstance is WebSocket { if (webSocketInstance instanceof WebSocket === false) throw new Error(''); }; + + +export function resetGlobalState (url?: string): void { + resetSubscribers(url); + resetWebSockets(url); +};