react-broadcast-sync 1.0.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.
@@ -0,0 +1,3 @@
1
+ export { useBroadcastChannel } from './hooks/useBroadcastChannel';
2
+ export { BroadcastProvider, useBroadcastProvider } from './BroadcastProvider';
3
+ export type { BroadcastOptions, SendMessageOptions, BroadcastMessage, BroadcastActions } from './types/types';
@@ -0,0 +1,2 @@
1
+ import e,{useState as r,useRef as t,useMemo as n,useCallback as s,useEffect as o,createContext as a,useContext as c}from"react";import{jsx as i}from"react/jsx-runtime";const l="__INTERNAL__",u=()=>Math.random().toString(36).substr(2,9),d=(e,r)=>{const t=`${u()}-${e}-${r}`;return btoa(t).replace(/=+$/,"")},p=e=>!!e.expirationDate&&e.expirationDate<Date.now(),E=(e,r,t="")=>{const n=btoa(`react-broadcast-sync:${e}:${`${r}-${t}`}`);return`${l}:${e}:${n}`},f=e=>e.startsWith(`${l}:`),h=(e,r)=>{let t,n=null,s=null,o=null;const a=function(...a){s=a,o=this,null!==n&&clearTimeout(n),n=setTimeout((()=>{n=null,null!==s&&(t=e.apply(o,s),s=null,o=null)}),r)};return a.cancel=()=>{null!==n&&(clearTimeout(n),n=null,s=null,o=null)},a.flush=()=>(null!==n&&(clearTimeout(n),n=null,null!==s&&(t=e.apply(o,s),s=null,o=null)),t),a},v=(e,r,t)=>{if("undefined"==typeof process||!process.env||"true"!==process.env.REACT_APP_DEBUG_BROADCAST)return;const n=`[${(new Date).toISOString()}] [react-broadcast-sync]`;switch(e){case"info":console.log(n,r,t||"");break;case"warn":console.warn(n,r,t||"");break;case"error":console.error(n,r,t||"")}},g={created:e=>v("info","Channel created:",e),closed:e=>v("info","Channel closed:",e)},S={sent:e=>v("info","Message sent:",e),received:e=>v("info","Message received:",e),cleared:e=>v("info","Message cleared:",e),expired:e=>v("info","Message expired:",e),duplicate:e=>v("warn","Duplicate message ignored:",e)},A={started:()=>v("info","Cleanup started"),completed:e=>v("info","Cleanup completed:",{removedCount:e})},m=e=>v("error","Error:",e),C="CLEAR_MESSAGE",L="CLEAR_ALL_MESSAGES",M=(e,a={})=>{const{sourceName:c,cleaningInterval:i=1e3,keepLatestMessage:l=!1,registeredTypes:v=[],namespace:M="",deduplicationTTL:y=3e5,cleanupDebounceMs:_=0}=a,[b,w]=r([]),[D,R]=r([]),[$,G]=r(null),B=t(null),x=t(new Map),T=t(c||`tab-${u()}`).current,P=t({CLEAR_MESSAGE:E(C,e,M),CLEAR_ALL_MESSAGES:E(L,e,M)}).current,I=n((()=>`${e}-${M}`),[e,M]),k=s((()=>{w((e=>e.filter((e=>!p(e)))))}),[]),N=t(_>0?h(k,_):Object.assign(k,{cancel:()=>{}})).current,j=s((e=>{G(e),setTimeout((()=>G(null)),3e3)}),[]),O=s((e=>{var r;w((r=>r.filter((r=>r.id!==e)))),R((r=>r.filter((r=>r.id!==e)))),null===(r=B.current)||void 0===r||r.postMessage({id:e,type:P.CLEAR_MESSAGE,source:T}),S.cleared(e)}),[T,P.CLEAR_MESSAGE]),F=s((()=>{var e;w([]),R([]),null===(e=B.current)||void 0===e||e.postMessage({id:P.CLEAR_ALL_MESSAGES,type:P.CLEAR_ALL_MESSAGES,source:T})}),[T,P.CLEAR_ALL_MESSAGES]),U=s((e=>{var r;R((r=>r.filter((r=>r.id!==e)))),null===(r=B.current)||void 0===r||r.postMessage({id:e,type:P.CLEAR_MESSAGE,source:T})}),[T,P.CLEAR_MESSAGE]),W=s(((e,r,t={})=>{const n=B.current;if(!n){const e="Channel not available";return m(e),void j(e)}try{const s=((e,r,t,n={})=>{var s;const o=Date.now();return{id:d(t,o),type:e,message:r,timestamp:o,source:t,expirationDate:null!==(s=n.expirationDate)&&void 0!==s?s:n.expirationDuration?o+n.expirationDuration:void 0}})(e,r,T,t);n.postMessage(s),S.sent(s),R((e=>[...e,s]))}catch(e){const r="Failed to send message";m(r),j(r)}}),[T,j]),q=s((e=>{try{const r=e.data;if(S.received(r),!(e=>Boolean(e&&"object"==typeof e&&"id"in e))(r))return;if(r.source===T)return;if(!f(r.type)&&v.length>0&&!v.includes(r.type))return;if(p(r))return void S.expired(r.id);if((e=>e&&"object"==typeof e&&"string"==typeof e.id&&"string"==typeof e.source&&"string"==typeof e.type&&f(e.type))(r)){if(r.type===P.CLEAR_MESSAGE)return void w((e=>e.filter((e=>!(e.id===r.id&&e.source===r.source)))));if(r.type===P.CLEAR_ALL_MESSAGES)return void w((e=>e.filter((e=>e.source!==r.source))))}const t=Date.now(),n=x.current.get(r.id);if(n&&t-n<y)return void S.duplicate(r.id);x.current.set(r.id,t),w((e=>l?[r]:[...e,r]))}catch(e){const r="Error processing broadcast message";m(r),j(r)}}),[T,v,l,j,P,y]);return o((()=>{if("undefined"==typeof BroadcastChannel){const e="BroadcastChannel is not supported in this browser. Please check browser compatibility.";return m(e),void j(e)}const e=new BroadcastChannel(I);return B.current=e,g.created(I),e.addEventListener("message",q),()=>{e.removeEventListener("message",q),e.close(),g.closed(I)}}),[I,q,j]),o((()=>{if(i<=0)return;const e=setInterval((()=>{A.started();const e=b.length;N(),A.completed(e-b.length)}),i);return()=>{var r;clearInterval(e),_>0&&(null===(r=N.cancel)||void 0===r||r.call(N))}}),[i,N,_]),o((()=>{const e=setInterval((()=>{const e=Date.now();for(const[r,t]of x.current.entries())e-t>=y&&x.current.delete(r)}),6e4);return()=>clearInterval(e)}),[y]),{channelName:I,messages:b,sentMessages:D,postMessage:W,clearMessage:O,clearAllMessages:F,clearSentMessage:U,error:$}},y=a(void 0);class _ extends e.Component{constructor(){super(...arguments),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(e){console.error("BroadcastChannel error:",e)}render(){return this.state.hasError?i("div",{children:"Error occurred in BroadcastProvider"}):this.props.children}}const b=({children:r,channelName:s})=>{const{messages:a,error:c}=M(s),[l,u]=e.useState(null),d=t(null);o((()=>{if("undefined"==typeof BroadcastChannel)return void u("BroadcastChannel is not supported in this browser. Please check browser compatibility.");const e=new BroadcastChannel(s);return d.current=e,()=>{e.close()}}),[s]);const p=n((()=>({messages:a,error:l||c,channel:d.current})),[a,l,c]);return i(_,{children:i(y.Provider,{value:p,children:r})})},w=()=>{const e=c(y);if(!e)throw new Error("useBroadcastProvider must be used within a BroadcastProvider");return e};export{b as BroadcastProvider,M as useBroadcastChannel,w as useBroadcastProvider};
2
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":["../src/utils/messageUtils.ts","../src/utils/debug.ts","../src/hooks/useBroadcastChannel.ts","../src/BroadcastProvider.tsx"],"sourcesContent":[null,null,null,null],"names":["INTERNAL_PREFIX","generateRandomPart","Math","random","toString","substr","generateMessageId","source","timestamp","raw","btoa","replace","isMessageExpired","message","expirationDate","Date","now","getInternalMessageType","baseType","channelName","namespace","hash","isInternalType","type","startsWith","debounce","fn","wait","result","timeoutId","lastArgs","lastThis","debounced","args","this","clearTimeout","setTimeout","apply","cancel","flush","log","level","data","process","env","REACT_APP_DEBUG_BROADCAST","prefix","toISOString","console","warn","error","debug","created","closed","sent","received","cleared","messageId","expired","duplicate","started","completed","removedCount","INTERNAL_MESSAGE_TYPES","useBroadcastChannel","options","sourceName","cleaningInterval","keepLatestMessage","registeredTypes","deduplicationTTL","cleanupDebounceMs","messages","setMessages","useState","sentMessages","setSentMessages","setError","channel","useRef","receivedMessageIds","Map","current","internalTypes","CLEAR_MESSAGE","CLEAR_ALL_MESSAGES","resolvedChannelName","useMemo","performCleanup","useCallback","prev","filter","msg","debouncedCleanup","Object","assign","setErrorMessage","clearMessage","id","_a","postMessage","clearAllMessages","clearSentMessage","messageType","messageContent","channelCurrent","createMessage","content","expirationDuration","undefined","e","handleMessage","event","Boolean","isValidMessage","length","includes","isValidInternalClearMessage","receivedAt","get","set","useEffect","BroadcastChannel","bc","addEventListener","removeEventListener","close","interval","setInterval","before","clearInterval","call","entries","delete","BroadcastChannelContext","createContext","ErrorBoundary","React","Component","constructor","state","hasError","getDerivedStateFromError","componentDidCatch","render","_jsx","props","children","BroadcastProvider","hookError","value","Provider","useBroadcastProvider","context","useContext","Error"],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Options for configuring the broadcast channel
3
+ */
4
+ export interface BroadcastOptions {
5
+ /** Custom source name for this instance */
6
+ sourceName?: string;
7
+ /** Interval in milliseconds for cleaning expired messages (default: 1000) */
8
+ cleaningInterval?: number;
9
+ /** If true, only the latest message is kept */
10
+ keepLatestMessage?: boolean;
11
+ /** Array of allowed message types */
12
+ registeredTypes?: string[];
13
+ /** Channel namespace for isolation */
14
+ namespace?: string;
15
+ /** TTL for deduplication in milliseconds (default: 5 minutes) */
16
+ deduplicationTTL?: number;
17
+ /** Debounce time for messages cleanup in milliseconds (default: 1000) */
18
+ cleanupDebounceMs?: number;
19
+ }
20
+ /**
21
+ * Options for sending messages
22
+ */
23
+ export interface SendMessageOptions {
24
+ /** Message expiration duration in milliseconds */
25
+ expirationDuration?: number;
26
+ /** Specific expiration timestamp */
27
+ expirationDate?: number;
28
+ }
29
+ /**
30
+ * Structure of a broadcast message
31
+ */
32
+ export interface BroadcastMessage {
33
+ /** Unique message identifier */
34
+ id: string;
35
+ /** Message type for categorization */
36
+ type: string;
37
+ /** Message content */
38
+ message: any;
39
+ /** Creation timestamp */
40
+ timestamp: number;
41
+ /** Source identifier */
42
+ source: string;
43
+ /** Optional expiration timestamp */
44
+ expirationDate?: number;
45
+ }
46
+ /**
47
+ * Actions and state provided by the broadcast channel hook
48
+ */
49
+ export interface BroadcastActions {
50
+ /** The resolved channel name including namespace */
51
+ channelName: string;
52
+ /** Array of received messages */
53
+ messages: BroadcastMessage[];
54
+ /** Array of sent messages */
55
+ sentMessages: BroadcastMessage[];
56
+ /** Function to send a message */
57
+ postMessage: (messageType: string, messageContent: any, options?: SendMessageOptions) => void;
58
+ /** Function to clear a specific message */
59
+ clearMessage: (id: string) => void;
60
+ /** Function to clear all messages */
61
+ clearAllMessages: () => void;
62
+ /** Function to clear a specific sent message */
63
+ clearSentMessage: (id: string) => void;
64
+ /** Current error state */
65
+ error: string | null;
66
+ }
@@ -0,0 +1,3 @@
1
+ import { BroadcastActions, BroadcastOptions } from './types/types';
2
+ export declare const useBroadcastChannel: (channelName: string, options?: BroadcastOptions) => BroadcastActions;
3
+ export default useBroadcastChannel;
@@ -0,0 +1,18 @@
1
+ export declare const debug: {
2
+ channel: {
3
+ created: (channelName: string) => void;
4
+ closed: (channelName: string) => void;
5
+ };
6
+ message: {
7
+ sent: (message: any) => void;
8
+ received: (message: any) => void;
9
+ cleared: (messageId: string) => void;
10
+ expired: (messageId: string) => void;
11
+ duplicate: (messageId: string) => void;
12
+ };
13
+ cleanup: {
14
+ started: () => void;
15
+ completed: (removedCount: number) => void;
16
+ };
17
+ error: (error: string) => void;
18
+ };
@@ -0,0 +1,22 @@
1
+ export declare const generateRandomPart: () => string;
2
+ export declare const generateSourceName: () => string;
3
+ export declare const generateMessageId: (source: string, timestamp: number) => string;
4
+ export declare const isValidMessage: (message: any) => boolean;
5
+ export declare const isMessageExpired: (message: {
6
+ expirationDate?: number;
7
+ }) => boolean;
8
+ export declare const createMessage: (type: string, content: any, source: string, options?: {
9
+ expirationDate?: number;
10
+ expirationDuration?: number;
11
+ }) => any;
12
+ export declare const getInternalMessageType: (baseType: "CLEAR_MESSAGE" | "CLEAR_ALL_MESSAGES", channelName: string, namespace?: string) => string;
13
+ export declare const isInternalType: (type: string) => boolean;
14
+ export declare const isValidInternalClearMessage: (message: any) => message is {
15
+ id: string;
16
+ type: string;
17
+ source: string;
18
+ };
19
+ export declare const debounce: <T extends (...args: any[]) => any>(fn: T, wait: number) => ((...args: Parameters<T>) => void) & {
20
+ cancel: () => void;
21
+ flush: () => ReturnType<T> | undefined;
22
+ };
@@ -0,0 +1,29 @@
1
+ import eslint from '@eslint/js';
2
+ import tseslint from 'typescript-eslint';
3
+ import reactPlugin from 'eslint-plugin-react';
4
+ import reactHooksPlugin from 'eslint-plugin-react-hooks';
5
+ import prettierPlugin from 'eslint-plugin-prettier';
6
+
7
+ export default [
8
+ eslint.configs.recommended,
9
+ ...tseslint.configs.recommended,
10
+ {
11
+ files: ['src/**/*.{ts,tsx}'],
12
+ ignores: ['**/*', '!src/**/*.{ts,tsx}'],
13
+ plugins: {
14
+ react: reactPlugin,
15
+ 'react-hooks': reactHooksPlugin,
16
+ prettier: prettierPlugin,
17
+ },
18
+ rules: {
19
+ 'react/react-in-jsx-scope': 'off',
20
+ 'react/prop-types': 'off',
21
+ '@typescript-eslint/no-unused-vars': [
22
+ 'warn',
23
+ { argsIgnorePattern: '^_' },
24
+ ],
25
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
26
+ 'prettier/prettier': 'error',
27
+ },
28
+ },
29
+ ];
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'jsdom',
4
+ transform: {
5
+ '^.+\\.(ts|tsx)$': 'ts-jest',
6
+ },
7
+ testMatch: ['**/src/tests/**/*.test.(ts|tsx)'],
8
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
9
+ };
10
+
package/jest.setup.ts ADDED
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
package/package.json ADDED
@@ -0,0 +1,109 @@
1
+ {
2
+ "name": "react-broadcast-sync",
3
+ "version": "1.0.0",
4
+ "description": "A lightweight React hook and provider for syncing state across browser tabs using BroadcastChannel API.",
5
+ "main": "dist/index.cjs.js",
6
+ "module": "dist/index.esm.js",
7
+ "types": "dist/index.d.ts",
8
+ "sideEffects": false,
9
+ "type": "module",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.esm.js",
13
+ "require": "./dist/index.cjs.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "build": "rollup -c rollup.config.mjs",
19
+ "prepare": "npm run build && husky install",
20
+ "test": "jest",
21
+ "lint": "eslint . --ext .ts,.tsx",
22
+ "typecheck": "tsc --noEmit",
23
+ "format": "prettier --write .",
24
+ "format:check": "prettier --check .",
25
+ "release": "semantic-release",
26
+ "lint-staged": "lint-staged"
27
+ },
28
+ "lint-staged": {
29
+ "*.{ts,tsx}": [
30
+ "eslint --fix",
31
+ "prettier --write"
32
+ ]
33
+ },
34
+ "keywords": [
35
+ "react",
36
+ "hooks",
37
+ "broadcastchannel",
38
+ "sync",
39
+ "cross-tab",
40
+ "state-management"
41
+ ],
42
+ "author": "Idan Shalem <idanshalem25@gmail.com>",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/IdanShalem/react-broadcast-sync.git"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/IdanShalem/react-broadcast-sync/issues"
50
+ },
51
+ "homepage": "https://github.com/IdanShalem/react-broadcast-sync#readme",
52
+ "peerDependencies": {
53
+ "react": ">=16.8.0",
54
+ "react-dom": ">=16.8.0"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "react": {
58
+ "optional": false
59
+ },
60
+ "react-dom": {
61
+ "optional": false
62
+ }
63
+ },
64
+ "dependencies": {
65
+ "@babel/runtime": "^7.24.0",
66
+ "tslib": "^2.8.1"
67
+ },
68
+ "devDependencies": {
69
+ "@babel/core": "^7.24.0",
70
+ "@babel/plugin-transform-runtime": "^7.24.0",
71
+ "@babel/preset-react": "^7.26.3",
72
+ "@commitlint/cli": "^19.8.1",
73
+ "@commitlint/config-conventional": "^19.8.1",
74
+ "@rollup/plugin-babel": "^6.0.4",
75
+ "@rollup/plugin-commonjs": "^28.0.3",
76
+ "@rollup/plugin-node-resolve": "^16.0.1",
77
+ "@rollup/plugin-terser": "^0.4.4",
78
+ "@rollup/plugin-typescript": "^12.1.2",
79
+ "@semantic-release/changelog": "^6.0.3",
80
+ "@semantic-release/git": "^10.0.1",
81
+ "@semantic-release/github": "^11.0.3",
82
+ "@semantic-release/npm": "^12.0.1",
83
+ "@testing-library/dom": "^10.4.0",
84
+ "@testing-library/jest-dom": "^6.6.3",
85
+ "@testing-library/react": "^16.3.0",
86
+ "@testing-library/react-hooks": "^8.0.1",
87
+ "@types/jest": "^29.5.14",
88
+ "@types/node": "^20.0.0",
89
+ "@types/react": "^19.1.6",
90
+ "@vitejs/plugin-react": "^4.3.4",
91
+ "commitlint": "^19.8.1",
92
+ "eslint": "^9.27.0",
93
+ "eslint-plugin-prettier": "^5.4.0",
94
+ "eslint-plugin-react": "^7.37.5",
95
+ "eslint-plugin-react-hooks": "^5.2.0",
96
+ "husky": "^8.0.3",
97
+ "jest": "^29.7.0",
98
+ "jest-environment-jsdom": "^30.0.0-beta.3",
99
+ "lint-staged": "^16.1.0",
100
+ "react": "^19.1.0",
101
+ "react-dom": "^19.1.0",
102
+ "rollup": "^4.38.0",
103
+ "rollup-plugin-peer-deps-external": "^2.2.4",
104
+ "semantic-release": "^24.2.5",
105
+ "ts-jest": "^29.3.4",
106
+ "typescript": ">=4.0.0",
107
+ "typescript-eslint": "^8.33.0"
108
+ }
109
+ }
@@ -0,0 +1,92 @@
1
+ import babel from '@rollup/plugin-babel';
2
+ import resolve from '@rollup/plugin-node-resolve';
3
+ import commonjs from '@rollup/plugin-commonjs';
4
+ import typescript from '@rollup/plugin-typescript';
5
+ import peerDepsExternal from 'rollup-plugin-peer-deps-external';
6
+ import terser from '@rollup/plugin-terser';
7
+
8
+ const external = [
9
+ 'react',
10
+ 'react-dom',
11
+ 'react/jsx-runtime',
12
+ /^react\//,
13
+ /^react-dom\//,
14
+ 'typescript',
15
+ 'tslib',
16
+ '@babel/runtime'
17
+ ];
18
+
19
+ export default {
20
+ input: 'src/index.ts',
21
+ output: [
22
+ {
23
+ file: 'dist/index.esm.js',
24
+ format: 'es',
25
+ sourcemap: true,
26
+ globals: {
27
+ react: 'React',
28
+ 'react-dom': 'ReactDOM',
29
+ typescript: 'ts',
30
+ tslib: 'tslib',
31
+ '@babel/runtime': 'babelRuntime'
32
+ },
33
+ exports: 'named'
34
+ },
35
+ {
36
+ file: 'dist/index.cjs.js',
37
+ format: 'cjs',
38
+ sourcemap: true,
39
+ globals: {
40
+ react: 'React',
41
+ 'react-dom': 'ReactDOM',
42
+ typescript: 'ts',
43
+ tslib: 'tslib',
44
+ '@babel/runtime': 'babelRuntime'
45
+ },
46
+ exports: 'named'
47
+ },
48
+ ],
49
+ plugins: [
50
+ peerDepsExternal({
51
+ includeDependencies: false
52
+ }),
53
+ resolve({
54
+ mainFields: ['module', 'main'],
55
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
56
+ preferBuiltins: true,
57
+ dedupe: ['react', 'react-dom']
58
+ }),
59
+ commonjs({
60
+ include: /node_modules/,
61
+ transformMixedEsModules: true,
62
+ requireReturnsDefault: 'auto'
63
+ }),
64
+ typescript({
65
+ tsconfig: './tsconfig.json',
66
+ exclude: ['**/__tests__/**', '**/*.test.ts', '**/*.test.tsx'],
67
+ declaration: true,
68
+ declarationDir: './dist',
69
+ emitDeclarationOnly: false,
70
+ sourceMap: true
71
+ }),
72
+ babel({
73
+ babelHelpers: 'runtime',
74
+ presets: [
75
+ ['@babel/preset-react', {
76
+ runtime: 'automatic',
77
+ development: process.env.NODE_ENV === 'development'
78
+ }]
79
+ ],
80
+ plugins: [
81
+ ['@babel/plugin-transform-runtime', {
82
+ regenerator: true,
83
+ useESModules: true
84
+ }]
85
+ ],
86
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
87
+ exclude: 'node_modules/**'
88
+ }),
89
+ terser()
90
+ ],
91
+ external
92
+ };
@@ -0,0 +1,80 @@
1
+ import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react';
2
+ import { useBroadcastChannel } from './hooks/useBroadcastChannel';
3
+ import { BroadcastMessage } from './types/types';
4
+
5
+ interface BroadcastContextValue {
6
+ messages: BroadcastMessage[];
7
+ error: string | null;
8
+ channel: BroadcastChannel | null;
9
+ }
10
+
11
+ const BroadcastChannelContext = createContext<BroadcastContextValue | undefined>(undefined);
12
+
13
+ interface ErrorBoundaryProps {
14
+ children: React.ReactNode;
15
+ }
16
+
17
+ class ErrorBoundary extends React.Component<ErrorBoundaryProps, { hasError: boolean }> {
18
+ state = { hasError: false };
19
+
20
+ static getDerivedStateFromError() {
21
+ return { hasError: true };
22
+ }
23
+
24
+ componentDidCatch(error: Error) {
25
+ console.error('BroadcastChannel error:', error);
26
+ }
27
+
28
+ render() {
29
+ if (this.state.hasError) {
30
+ return <div>Error occurred in BroadcastProvider</div>;
31
+ }
32
+ return this.props.children;
33
+ }
34
+ }
35
+
36
+ interface BroadcastProviderProps {
37
+ channelName: string;
38
+ children: React.ReactNode;
39
+ }
40
+
41
+ export const BroadcastProvider: React.FC<BroadcastProviderProps> = ({ children, channelName }) => {
42
+ const { messages, error: hookError } = useBroadcastChannel(channelName);
43
+
44
+ const [error, setError] = React.useState<string | null>(null);
45
+ const channel = useRef<BroadcastChannel | null>(null);
46
+
47
+ useEffect(() => {
48
+ if (typeof BroadcastChannel === 'undefined') {
49
+ setError('BroadcastChannel is not supported in this browser. Please check browser compatibility.');
50
+ return;
51
+ }
52
+ const bc = new BroadcastChannel(channelName);
53
+ channel.current = bc;
54
+
55
+ return () => {
56
+ bc.close();
57
+ };
58
+ }, [channelName]);
59
+
60
+ const value = useMemo(
61
+ () => ({ messages, error: error || hookError, channel: channel.current }),
62
+ [messages, error, hookError]
63
+ );
64
+
65
+ return (
66
+ <ErrorBoundary>
67
+ <BroadcastChannelContext.Provider value={value}>
68
+ {children}
69
+ </BroadcastChannelContext.Provider>
70
+ </ErrorBoundary>
71
+ );
72
+ };
73
+
74
+ export const useBroadcastProvider = (): BroadcastContextValue => {
75
+ const context = useContext(BroadcastChannelContext);
76
+ if (!context) {
77
+ throw new Error('useBroadcastProvider must be used within a BroadcastProvider');
78
+ }
79
+ return context;
80
+ };