react-broadcast-sync 1.1.4 → 1.2.1
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/CHANGELOG.md +14 -0
- package/README.md +54 -46
- package/demo/react-broadcast-sync-demo/package.json +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/types/types.d.ts +15 -6
- package/dist/utils/debug.d.ts +4 -2
- package/dist/utils/messageUtils.d.ts +3 -6
- package/package.json +1 -1
- package/src/hooks/useBroadcastChannel.ts +75 -55
- package/src/tests/messageUtils.test.ts +6 -6
- package/src/tests/useBroadcastChannel.same-tab.test.tsx +981 -0
- package/src/tests/useBroadcastChannel.test.tsx +14 -14
- package/src/types/types.ts +18 -7
- package/src/utils/debug.ts +4 -2
- package/src/utils/messageUtils.ts +6 -7
|
@@ -3,6 +3,9 @@ import {
|
|
|
3
3
|
BroadcastActions,
|
|
4
4
|
BroadcastMessage,
|
|
5
5
|
BroadcastOptions,
|
|
6
|
+
ClearMessage,
|
|
7
|
+
ClearReceivedMessagesOptions,
|
|
8
|
+
ClearSentMessagesOptions,
|
|
6
9
|
SendMessageOptions,
|
|
7
10
|
} from '../types/types';
|
|
8
11
|
import {
|
|
@@ -17,9 +20,8 @@ import {
|
|
|
17
20
|
} from '../utils/messageUtils';
|
|
18
21
|
import { debug } from '../utils/debug';
|
|
19
22
|
|
|
20
|
-
const INTERNAL_MESSAGE_TYPES = {
|
|
21
|
-
|
|
22
|
-
CLEAR_ALL_MESSAGES: 'CLEAR_ALL_MESSAGES',
|
|
23
|
+
const INTERNAL_MESSAGE_TYPES: Record<string, ClearMessage> = {
|
|
24
|
+
CLEAR_SENT_MESSAGES: 'CLEAR_SENT_MESSAGES',
|
|
23
25
|
} as const;
|
|
24
26
|
|
|
25
27
|
export const useBroadcastChannel = (
|
|
@@ -46,13 +48,8 @@ export const useBroadcastChannel = (
|
|
|
46
48
|
const receivedMessageIds = useRef(new Map<string, number>());
|
|
47
49
|
const source = useRef(sourceName || generateSourceName()).current;
|
|
48
50
|
const internalTypes = useRef({
|
|
49
|
-
|
|
50
|
-
INTERNAL_MESSAGE_TYPES.
|
|
51
|
-
channelName,
|
|
52
|
-
namespace
|
|
53
|
-
),
|
|
54
|
-
CLEAR_ALL_MESSAGES: getInternalMessageType(
|
|
55
|
-
INTERNAL_MESSAGE_TYPES.CLEAR_ALL_MESSAGES,
|
|
51
|
+
CLEAR_SENT_MESSAGES: getInternalMessageType(
|
|
52
|
+
INTERNAL_MESSAGE_TYPES.CLEAR_SENT_MESSAGES,
|
|
56
53
|
channelName,
|
|
57
54
|
namespace
|
|
58
55
|
),
|
|
@@ -80,42 +77,6 @@ export const useBroadcastChannel = (
|
|
|
80
77
|
setTimeout(() => setError(null), 3000);
|
|
81
78
|
}, []);
|
|
82
79
|
|
|
83
|
-
const clearMessage = useCallback(
|
|
84
|
-
(id: string) => {
|
|
85
|
-
setMessages(prev => prev.filter(msg => msg.id !== id));
|
|
86
|
-
setSentMessages(prev => prev.filter(msg => msg.id !== id));
|
|
87
|
-
channel.current?.postMessage({
|
|
88
|
-
id,
|
|
89
|
-
type: internalTypes.CLEAR_MESSAGE,
|
|
90
|
-
source,
|
|
91
|
-
});
|
|
92
|
-
debug.message.cleared(id);
|
|
93
|
-
},
|
|
94
|
-
[source, internalTypes.CLEAR_MESSAGE]
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
const clearAllMessages = useCallback(() => {
|
|
98
|
-
setMessages([]);
|
|
99
|
-
setSentMessages([]);
|
|
100
|
-
channel.current?.postMessage({
|
|
101
|
-
id: internalTypes.CLEAR_ALL_MESSAGES,
|
|
102
|
-
type: internalTypes.CLEAR_ALL_MESSAGES,
|
|
103
|
-
source,
|
|
104
|
-
});
|
|
105
|
-
}, [source, internalTypes.CLEAR_ALL_MESSAGES]);
|
|
106
|
-
|
|
107
|
-
const clearSentMessage = useCallback(
|
|
108
|
-
(id: string) => {
|
|
109
|
-
setSentMessages(prev => prev.filter(msg => msg.id !== id));
|
|
110
|
-
channel.current?.postMessage({
|
|
111
|
-
id,
|
|
112
|
-
type: internalTypes.CLEAR_MESSAGE,
|
|
113
|
-
source,
|
|
114
|
-
});
|
|
115
|
-
},
|
|
116
|
-
[source, internalTypes.CLEAR_MESSAGE]
|
|
117
|
-
);
|
|
118
|
-
|
|
119
80
|
const postMessage = useCallback(
|
|
120
81
|
(messageType: string, messageContent: any, options: SendMessageOptions = {}) => {
|
|
121
82
|
const channelCurrent = channel.current;
|
|
@@ -140,6 +101,55 @@ export const useBroadcastChannel = (
|
|
|
140
101
|
[source, setErrorMessage]
|
|
141
102
|
);
|
|
142
103
|
|
|
104
|
+
const clearReceivedMessages = useCallback((options: ClearReceivedMessagesOptions = {}) => {
|
|
105
|
+
const hasFilters = Boolean(
|
|
106
|
+
(options.ids && options.ids.length) ||
|
|
107
|
+
(options.types && options.types.length) ||
|
|
108
|
+
(options.sources && options.sources.length)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
setMessages(prev =>
|
|
112
|
+
hasFilters
|
|
113
|
+
? prev.filter(
|
|
114
|
+
msg =>
|
|
115
|
+
!(options.ids && options.ids.includes(msg.id)) &&
|
|
116
|
+
!(options.types && options.types.includes(msg.type)) &&
|
|
117
|
+
!(options.sources && options.sources.includes(msg.source))
|
|
118
|
+
)
|
|
119
|
+
: []
|
|
120
|
+
);
|
|
121
|
+
}, []);
|
|
122
|
+
|
|
123
|
+
const clearSentMessages = useCallback((options: ClearSentMessagesOptions = {}) => {
|
|
124
|
+
const { ids = [], types = [], sync = false } = options ?? {};
|
|
125
|
+
setSentMessages(prev =>
|
|
126
|
+
ids.length > 0 || types.length > 0
|
|
127
|
+
? prev.filter(msg => {
|
|
128
|
+
// Only consider messages from the same sender
|
|
129
|
+
if (msg.source !== source) return true;
|
|
130
|
+
|
|
131
|
+
// Decide whether this message should be cleared:
|
|
132
|
+
// - If the ids array is empty, treat as wildcard (match all ids)
|
|
133
|
+
// - If the types array is empty, treat as wildcard (match all types)
|
|
134
|
+
const idMatches = ids.length === 0 || ids.includes(msg.id);
|
|
135
|
+
const typeMatches = types.length === 0 || types.includes(msg.type);
|
|
136
|
+
|
|
137
|
+
// Remove when BOTH criteria match (wildcards included)
|
|
138
|
+
return !(idMatches && typeMatches);
|
|
139
|
+
})
|
|
140
|
+
: []
|
|
141
|
+
);
|
|
142
|
+
if (sync) {
|
|
143
|
+
channel.current?.postMessage(
|
|
144
|
+
createMessage(
|
|
145
|
+
internalTypes.CLEAR_SENT_MESSAGES,
|
|
146
|
+
{ ids: options.ids ?? [], types: options.types ?? [] },
|
|
147
|
+
source
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}, []);
|
|
152
|
+
|
|
143
153
|
const handleMessage = useCallback(
|
|
144
154
|
(event: MessageEvent) => {
|
|
145
155
|
try {
|
|
@@ -160,15 +170,26 @@ export const useBroadcastChannel = (
|
|
|
160
170
|
}
|
|
161
171
|
|
|
162
172
|
if (isValidInternalClearMessage(message)) {
|
|
163
|
-
if (message.type === internalTypes.
|
|
173
|
+
if (message.type === internalTypes.CLEAR_SENT_MESSAGES) {
|
|
174
|
+
const { ids = [], types = [] } = (message as any).message || {};
|
|
175
|
+
|
|
164
176
|
setMessages(prev =>
|
|
165
|
-
prev.filter(msg =>
|
|
177
|
+
prev.filter(msg => {
|
|
178
|
+
// Only consider messages from the same sender
|
|
179
|
+
if (msg.source !== message.source) return true;
|
|
180
|
+
|
|
181
|
+
// Decide whether this message should be cleared:
|
|
182
|
+
// - If the ids array is empty, treat as wildcard (match all ids)
|
|
183
|
+
// - If the types array is empty, treat as wildcard (match all types)
|
|
184
|
+
const idMatches = ids.length === 0 || ids.includes(msg.id);
|
|
185
|
+
const typeMatches = types.length === 0 || types.includes(msg.type);
|
|
186
|
+
|
|
187
|
+
// Remove when BOTH criteria match (wildcards included)
|
|
188
|
+
return !(idMatches && typeMatches);
|
|
189
|
+
})
|
|
166
190
|
);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
191
|
|
|
170
|
-
|
|
171
|
-
setMessages(prev => prev.filter(msg => msg.source !== message.source));
|
|
192
|
+
// After handling we don't want to process this internal control packet further
|
|
172
193
|
return;
|
|
173
194
|
}
|
|
174
195
|
}
|
|
@@ -249,9 +270,8 @@ export const useBroadcastChannel = (
|
|
|
249
270
|
messages,
|
|
250
271
|
sentMessages,
|
|
251
272
|
postMessage,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
clearSentMessage,
|
|
273
|
+
clearReceivedMessages,
|
|
274
|
+
clearSentMessages,
|
|
255
275
|
error,
|
|
256
276
|
};
|
|
257
277
|
};
|
|
@@ -65,19 +65,19 @@ describe('messageUtils', () => {
|
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
it('getInternalMessageType produces unique hashes for different channels', () => {
|
|
68
|
-
const one = getInternalMessageType('
|
|
69
|
-
const two = getInternalMessageType('
|
|
68
|
+
const one = getInternalMessageType('CLEAR_SENT_MESSAGES', 'chanA');
|
|
69
|
+
const two = getInternalMessageType('CLEAR_SENT_MESSAGES', 'chanB');
|
|
70
70
|
expect(one).not.toBe(two);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
it('getInternalMessageType generates consistent hash string', () => {
|
|
74
|
-
const type = getInternalMessageType('
|
|
74
|
+
const type = getInternalMessageType('CLEAR_SENT_MESSAGES', 'my-channel', 'ns');
|
|
75
75
|
expect(type).toContain('__INTERNAL__');
|
|
76
|
-
expect(type).toContain('
|
|
76
|
+
expect(type).toContain('CLEAR_SENT_MESSAGES');
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
it('isInternalType detects internal message types', () => {
|
|
80
|
-
const type = getInternalMessageType('
|
|
80
|
+
const type = getInternalMessageType('CLEAR_SENT_MESSAGES', 'chan');
|
|
81
81
|
expect(isInternalType(type)).toBe(true);
|
|
82
82
|
expect(isInternalType('normal-type')).toBe(false);
|
|
83
83
|
});
|
|
@@ -85,7 +85,7 @@ describe('messageUtils', () => {
|
|
|
85
85
|
it('isValidInternalClearMessage detects valid structure', () => {
|
|
86
86
|
const valid = {
|
|
87
87
|
id: 'abc',
|
|
88
|
-
type: getInternalMessageType('
|
|
88
|
+
type: getInternalMessageType('CLEAR_SENT_MESSAGES', 'ch'),
|
|
89
89
|
source: 'tab-1',
|
|
90
90
|
};
|
|
91
91
|
const invalid = { id: 123, type: 'bad', source: 'tab-1' };
|