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.
- package/.github/workflows/ci.yml +61 -0
- package/.husky/pre-commit +6 -0
- package/.prettierignore +3 -0
- package/.prettierrc +5 -0
- package/.releaserc.json +22 -0
- package/LICENSE +21 -0
- package/README.md +495 -0
- package/assets/react-broadcast-sync-logo.png +0 -0
- package/commitlint.config.js +3 -0
- package/dist/BroadcastProvider.d.ts +14 -0
- package/dist/hooks/useBroadcastChannel.d.ts +3 -0
- package/dist/index.cjs.js +2 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/types/types.d.ts +66 -0
- package/dist/useBroadcastChannel.d.ts +3 -0
- package/dist/utils/debug.d.ts +18 -0
- package/dist/utils/messageUtils.d.ts +22 -0
- package/eslint.config.js +29 -0
- package/jest.config.cjs +10 -0
- package/jest.setup.ts +1 -0
- package/package.json +109 -0
- package/rollup.config.mjs +92 -0
- package/src/BroadcastProvider.tsx +80 -0
- package/src/hooks/useBroadcastChannel.ts +232 -0
- package/src/index.ts +9 -0
- package/src/tests/BroadcastProvider.test.tsx +163 -0
- package/src/tests/debug.test.ts +199 -0
- package/src/tests/messageUtils.test.ts +247 -0
- package/src/tests/useBroadcastChannel.test.tsx +504 -0
- package/src/types/types.ts +92 -0
- package/src/utils/debug.ts +47 -0
- package/src/utils/messageUtils.ts +128 -0
- package/tsconfig.json +36 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const INTERNAL_PREFIX = '__INTERNAL__';
|
|
2
|
+
const SECRET = 'react-broadcast-sync';
|
|
3
|
+
|
|
4
|
+
// Generate a random 9-character alphanumeric string
|
|
5
|
+
export const generateRandomPart = () => Math.random().toString(36).substr(2, 9);
|
|
6
|
+
|
|
7
|
+
// Generate a unique source name for a tab
|
|
8
|
+
export const generateSourceName = () => `tab-${generateRandomPart()}`;
|
|
9
|
+
|
|
10
|
+
// Generate a unique message ID
|
|
11
|
+
export const generateMessageId = (source: string, timestamp: number) => {
|
|
12
|
+
const raw = `${generateRandomPart()}-${source}-${timestamp}`;
|
|
13
|
+
return btoa(raw).replace(/=+$/, '');
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Check if a message is valid
|
|
17
|
+
export const isValidMessage = (message: any): boolean => {
|
|
18
|
+
return Boolean(message && typeof message === 'object' && 'id' in message);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Check if a message has expired
|
|
22
|
+
export const isMessageExpired = (message: { expirationDate?: number }): boolean => {
|
|
23
|
+
return message.expirationDate ? message.expirationDate < Date.now() : false;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Create a new message object
|
|
27
|
+
export const createMessage = (
|
|
28
|
+
type: string,
|
|
29
|
+
content: any,
|
|
30
|
+
source: string,
|
|
31
|
+
options: { expirationDate?: number; expirationDuration?: number } = {}
|
|
32
|
+
): any => {
|
|
33
|
+
const timestamp = Date.now();
|
|
34
|
+
return {
|
|
35
|
+
id: generateMessageId(source, timestamp),
|
|
36
|
+
type,
|
|
37
|
+
message: content,
|
|
38
|
+
timestamp,
|
|
39
|
+
source,
|
|
40
|
+
expirationDate: options.expirationDate ??
|
|
41
|
+
(options.expirationDuration ? timestamp + options.expirationDuration : undefined),
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
export const getInternalMessageType = (
|
|
47
|
+
baseType: 'CLEAR_MESSAGE' | 'CLEAR_ALL_MESSAGES',
|
|
48
|
+
channelName: string,
|
|
49
|
+
namespace = ''
|
|
50
|
+
): string => {
|
|
51
|
+
const fullChannel = `${channelName}-${namespace}`;
|
|
52
|
+
const input = `${SECRET}:${baseType}:${fullChannel}`;
|
|
53
|
+
const hash = btoa(input); // optional: use sha256 if added
|
|
54
|
+
return `${INTERNAL_PREFIX}:${baseType}:${hash}`;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const isInternalType = (type: string): boolean => {
|
|
58
|
+
return type.startsWith(`${INTERNAL_PREFIX}:`);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const isValidInternalClearMessage = (message: any): message is { id: string, type: string, source: string } => {
|
|
62
|
+
return (
|
|
63
|
+
message &&
|
|
64
|
+
typeof message === 'object' &&
|
|
65
|
+
typeof message.id === 'string' &&
|
|
66
|
+
typeof message.source === 'string' &&
|
|
67
|
+
typeof message.type === 'string' &&
|
|
68
|
+
isInternalType(message.type)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const debounce = <T extends (...args: any[]) => any>(
|
|
73
|
+
fn: T,
|
|
74
|
+
wait: number
|
|
75
|
+
): ((...args: Parameters<T>) => void) & {
|
|
76
|
+
cancel: () => void;
|
|
77
|
+
flush: () => ReturnType<T> | undefined;
|
|
78
|
+
} => {
|
|
79
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
80
|
+
let lastArgs: Parameters<T> | null = null;
|
|
81
|
+
let lastThis: any = null;
|
|
82
|
+
let result: ReturnType<T> | undefined;
|
|
83
|
+
|
|
84
|
+
const debounced = function(this: any, ...args: Parameters<T>) {
|
|
85
|
+
lastArgs = args;
|
|
86
|
+
lastThis = this;
|
|
87
|
+
|
|
88
|
+
if (timeoutId !== null) {
|
|
89
|
+
clearTimeout(timeoutId);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
timeoutId = setTimeout(() => {
|
|
93
|
+
timeoutId = null;
|
|
94
|
+
if (lastArgs !== null) {
|
|
95
|
+
result = fn.apply(lastThis, lastArgs);
|
|
96
|
+
lastArgs = null;
|
|
97
|
+
lastThis = null;
|
|
98
|
+
}
|
|
99
|
+
}, wait);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
debounced.cancel = () => {
|
|
103
|
+
if (timeoutId !== null) {
|
|
104
|
+
clearTimeout(timeoutId);
|
|
105
|
+
timeoutId = null;
|
|
106
|
+
lastArgs = null;
|
|
107
|
+
lastThis = null;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
debounced.flush = () => {
|
|
112
|
+
if (timeoutId !== null) {
|
|
113
|
+
clearTimeout(timeoutId);
|
|
114
|
+
timeoutId = null;
|
|
115
|
+
if (lastArgs !== null) {
|
|
116
|
+
result = fn.apply(lastThis, lastArgs);
|
|
117
|
+
lastArgs = null;
|
|
118
|
+
lastThis = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return debounced as typeof debounced & {
|
|
125
|
+
cancel: () => void;
|
|
126
|
+
flush: () => ReturnType<T> | undefined;
|
|
127
|
+
};
|
|
128
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES6",
|
|
4
|
+
"lib": ["DOM", "ES2017"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"jsx": "react-jsx",
|
|
12
|
+
"outDir": "./dist",
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationDir": "./dist",
|
|
15
|
+
"emitDeclarationOnly": false,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"resolveJsonModule": true,
|
|
18
|
+
"allowSyntheticDefaultImports": true,
|
|
19
|
+
"noImplicitAny": true,
|
|
20
|
+
"strictNullChecks": true,
|
|
21
|
+
"strictFunctionTypes": true,
|
|
22
|
+
"strictBindCallApply": true,
|
|
23
|
+
"strictPropertyInitialization": true,
|
|
24
|
+
"noImplicitThis": true,
|
|
25
|
+
"alwaysStrict": true,
|
|
26
|
+
"noUnusedLocals": true,
|
|
27
|
+
"noUnusedParameters": true,
|
|
28
|
+
"noImplicitReturns": true,
|
|
29
|
+
"noFallthroughCasesInSwitch": true,
|
|
30
|
+
"allowJs": true,
|
|
31
|
+
"checkJs": false,
|
|
32
|
+
"types": ["jest", "node", "react"]
|
|
33
|
+
},
|
|
34
|
+
"include": ["src/**/*"],
|
|
35
|
+
"exclude": ["node_modules", "dist"]
|
|
36
|
+
}
|