openart-realtime-sdk 1.0.2 → 1.0.3
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/dist/client/index.cjs +93 -73
- package/dist/client/index.d.cts +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +93 -73
- package/dist/server/index.cjs +125 -52
- package/dist/server/index.js +125 -52
- package/package.json +1 -1
package/dist/client/index.cjs
CHANGED
|
@@ -86,77 +86,87 @@ function RealtimeProvider({
|
|
|
86
86
|
connect();
|
|
87
87
|
}, PING_TIMEOUT_MS);
|
|
88
88
|
}, []);
|
|
89
|
-
const connect = react.useCallback(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
eventSourceRef.current
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
const payload = JSON.parse(evt.data);
|
|
89
|
+
const connect = react.useCallback(
|
|
90
|
+
(opts) => {
|
|
91
|
+
const { replayEventsSince } = opts ?? { replayEventsSince: Date.now() };
|
|
92
|
+
const channels = Array.from(getAllNeededChannels());
|
|
93
|
+
if (channels.length === 0) return;
|
|
94
|
+
if (reconnectAttemptsRef.current >= maxReconnectAttempts) {
|
|
95
|
+
console.log("Max reconnection attempts reached.");
|
|
96
|
+
setStatus("error");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (visibilityTimeoutRef.current) {
|
|
100
|
+
clearTimeout(visibilityTimeoutRef.current);
|
|
101
|
+
visibilityTimeoutRef.current = null;
|
|
102
|
+
}
|
|
103
|
+
if (eventSourceRef.current) {
|
|
104
|
+
eventSourceRef.current.close();
|
|
105
|
+
}
|
|
106
|
+
setStatus("connecting");
|
|
107
|
+
try {
|
|
108
|
+
const channelsParam = channels.map((ch) => `channel=${encodeURIComponent(ch)}`).join("&");
|
|
109
|
+
const lastAckParam = channels.map((c) => {
|
|
110
|
+
const lastAck = lastAckRef.current.get(c) ?? String(replayEventsSince);
|
|
111
|
+
return `last_ack_${encodeURIComponent(c)}=${encodeURIComponent(
|
|
112
|
+
lastAck
|
|
113
|
+
)}`;
|
|
114
|
+
}).join("&");
|
|
115
|
+
const url = api.url + "?" + channelsParam + "&" + lastAckParam;
|
|
116
|
+
const eventSource = new EventSource(url, {
|
|
117
|
+
withCredentials: api.withCredentials ?? false
|
|
118
|
+
});
|
|
119
|
+
eventSourceRef.current = eventSource;
|
|
120
|
+
eventSource.onopen = () => {
|
|
121
|
+
reconnectAttemptsRef.current = 0;
|
|
122
|
+
setStatus("connected");
|
|
125
123
|
resetPingTimeout();
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
124
|
+
};
|
|
125
|
+
eventSource.onmessage = (evt) => {
|
|
126
|
+
try {
|
|
127
|
+
const payload = JSON.parse(evt.data);
|
|
128
|
+
resetPingTimeout();
|
|
129
|
+
handleMessage(payload);
|
|
130
|
+
const systemResult = systemEvent.safeParse(payload);
|
|
131
|
+
if (systemResult.success) {
|
|
132
|
+
if (systemResult.data.type === "reconnect") {
|
|
133
|
+
connect({ replayEventsSince: systemResult.data.timestamp });
|
|
134
|
+
}
|
|
131
135
|
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.warn("Error parsing message:", error);
|
|
132
138
|
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
139
|
+
};
|
|
140
|
+
eventSource.onerror = () => {
|
|
141
|
+
if (eventSource !== eventSourceRef.current) return;
|
|
142
|
+
const readyState = eventSourceRef.current?.readyState;
|
|
143
|
+
if (readyState === EventSource.CONNECTING) return;
|
|
144
|
+
if (readyState === EventSource.CLOSED) {
|
|
145
|
+
console.log("Connection closed, reconnecting...");
|
|
146
|
+
}
|
|
147
|
+
setStatus("disconnected");
|
|
148
|
+
if (reconnectAttemptsRef.current < maxReconnectAttempts) {
|
|
149
|
+
reconnectAttemptsRef.current++;
|
|
150
|
+
const timeoutMs = Math.min(
|
|
151
|
+
1e3 * Math.pow(2, reconnectAttemptsRef.current),
|
|
152
|
+
3e4
|
|
153
|
+
);
|
|
154
|
+
console.log(
|
|
155
|
+
`Reconnecting in ${timeoutMs}ms... (Attempt ${reconnectAttemptsRef.current})`
|
|
156
|
+
);
|
|
157
|
+
reconnectTimeoutRef.current = setTimeout(() => {
|
|
158
|
+
connect();
|
|
159
|
+
}, timeoutMs);
|
|
160
|
+
} else {
|
|
161
|
+
setStatus("error");
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
} catch (error) {
|
|
165
|
+
setStatus("error");
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
[getAllNeededChannels, maxReconnectAttempts, api.url, api.withCredentials]
|
|
169
|
+
);
|
|
160
170
|
const debouncedConnect = react.useCallback(() => {
|
|
161
171
|
if (debounceTimeoutRef.current) {
|
|
162
172
|
clearTimeout(debounceTimeoutRef.current);
|
|
@@ -173,7 +183,8 @@ function RealtimeProvider({
|
|
|
173
183
|
if (!disconnectOnWindowHidden || typeof document === "undefined") return;
|
|
174
184
|
const handleVisibilityChange = () => {
|
|
175
185
|
if (document.hidden) {
|
|
176
|
-
if (visibilityTimeoutRef.current)
|
|
186
|
+
if (visibilityTimeoutRef.current)
|
|
187
|
+
clearTimeout(visibilityTimeoutRef.current);
|
|
177
188
|
visibilityTimeoutRef.current = setTimeout(() => {
|
|
178
189
|
console.log("Window hidden for too long, disconnecting...");
|
|
179
190
|
cleanup();
|
|
@@ -192,7 +203,8 @@ function RealtimeProvider({
|
|
|
192
203
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
193
204
|
return () => {
|
|
194
205
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
195
|
-
if (visibilityTimeoutRef.current)
|
|
206
|
+
if (visibilityTimeoutRef.current)
|
|
207
|
+
clearTimeout(visibilityTimeoutRef.current);
|
|
196
208
|
};
|
|
197
209
|
}, [disconnectOnWindowHidden, disconnectDelay, connect, status]);
|
|
198
210
|
const handleMessage = (payload) => {
|
|
@@ -262,7 +274,9 @@ function RealtimeProvider({
|
|
|
262
274
|
function useRealtimeContext() {
|
|
263
275
|
const context = react.useContext(RealtimeContext);
|
|
264
276
|
if (!context) {
|
|
265
|
-
throw new Error(
|
|
277
|
+
throw new Error(
|
|
278
|
+
"useRealtimeContext must be used within a RealtimeProvider"
|
|
279
|
+
);
|
|
266
280
|
}
|
|
267
281
|
return context;
|
|
268
282
|
}
|
|
@@ -279,7 +293,9 @@ function useRealtime(opts) {
|
|
|
279
293
|
"useRealtime: No RealtimeProvider found. Wrap your app in <RealtimeProvider> to use Realtime."
|
|
280
294
|
);
|
|
281
295
|
}
|
|
282
|
-
const registrationId = react.useRef(
|
|
296
|
+
const registrationId = react.useRef(
|
|
297
|
+
Math.random().toString(36).substring(2)
|
|
298
|
+
).current;
|
|
283
299
|
const onDataRef = react.useRef(onData);
|
|
284
300
|
onDataRef.current = onData;
|
|
285
301
|
react.useEffect(() => {
|
|
@@ -296,7 +312,11 @@ function useRealtime(opts) {
|
|
|
296
312
|
if (events && events.length > 0 && !events.includes(event)) {
|
|
297
313
|
return;
|
|
298
314
|
}
|
|
299
|
-
const payload = {
|
|
315
|
+
const payload = {
|
|
316
|
+
event,
|
|
317
|
+
data,
|
|
318
|
+
channel
|
|
319
|
+
};
|
|
300
320
|
onDataRef.current?.(payload);
|
|
301
321
|
}
|
|
302
322
|
});
|
package/dist/client/index.d.cts
CHANGED
|
@@ -39,7 +39,7 @@ interface RealtimeProviderProps {
|
|
|
39
39
|
*/
|
|
40
40
|
disconnectDelay?: number;
|
|
41
41
|
}
|
|
42
|
-
declare function RealtimeProvider({ children, api, maxReconnectAttempts, disconnectOnWindowHidden, disconnectDelay }: RealtimeProviderProps): react_jsx_runtime.JSX.Element;
|
|
42
|
+
declare function RealtimeProvider({ children, api, maxReconnectAttempts, disconnectOnWindowHidden, disconnectDelay, }: RealtimeProviderProps): react_jsx_runtime.JSX.Element;
|
|
43
43
|
declare function useRealtimeContext(): RealtimeContextValue;
|
|
44
44
|
declare const createRealtime: <T extends Record<string, unknown>>() => {
|
|
45
45
|
useRealtime: <const E extends EventPaths<T>>(opts: UseRealtimeOpts<T, E>) => {
|
package/dist/client/index.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ interface RealtimeProviderProps {
|
|
|
39
39
|
*/
|
|
40
40
|
disconnectDelay?: number;
|
|
41
41
|
}
|
|
42
|
-
declare function RealtimeProvider({ children, api, maxReconnectAttempts, disconnectOnWindowHidden, disconnectDelay }: RealtimeProviderProps): react_jsx_runtime.JSX.Element;
|
|
42
|
+
declare function RealtimeProvider({ children, api, maxReconnectAttempts, disconnectOnWindowHidden, disconnectDelay, }: RealtimeProviderProps): react_jsx_runtime.JSX.Element;
|
|
43
43
|
declare function useRealtimeContext(): RealtimeContextValue;
|
|
44
44
|
declare const createRealtime: <T extends Record<string, unknown>>() => {
|
|
45
45
|
useRealtime: <const E extends EventPaths<T>>(opts: UseRealtimeOpts<T, E>) => {
|
package/dist/client/index.js
CHANGED
|
@@ -80,77 +80,87 @@ function RealtimeProvider({
|
|
|
80
80
|
connect();
|
|
81
81
|
}, PING_TIMEOUT_MS);
|
|
82
82
|
}, []);
|
|
83
|
-
const connect = useCallback(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
eventSourceRef.current
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
const payload = JSON.parse(evt.data);
|
|
83
|
+
const connect = useCallback(
|
|
84
|
+
(opts) => {
|
|
85
|
+
const { replayEventsSince } = opts ?? { replayEventsSince: Date.now() };
|
|
86
|
+
const channels = Array.from(getAllNeededChannels());
|
|
87
|
+
if (channels.length === 0) return;
|
|
88
|
+
if (reconnectAttemptsRef.current >= maxReconnectAttempts) {
|
|
89
|
+
console.log("Max reconnection attempts reached.");
|
|
90
|
+
setStatus("error");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (visibilityTimeoutRef.current) {
|
|
94
|
+
clearTimeout(visibilityTimeoutRef.current);
|
|
95
|
+
visibilityTimeoutRef.current = null;
|
|
96
|
+
}
|
|
97
|
+
if (eventSourceRef.current) {
|
|
98
|
+
eventSourceRef.current.close();
|
|
99
|
+
}
|
|
100
|
+
setStatus("connecting");
|
|
101
|
+
try {
|
|
102
|
+
const channelsParam = channels.map((ch) => `channel=${encodeURIComponent(ch)}`).join("&");
|
|
103
|
+
const lastAckParam = channels.map((c) => {
|
|
104
|
+
const lastAck = lastAckRef.current.get(c) ?? String(replayEventsSince);
|
|
105
|
+
return `last_ack_${encodeURIComponent(c)}=${encodeURIComponent(
|
|
106
|
+
lastAck
|
|
107
|
+
)}`;
|
|
108
|
+
}).join("&");
|
|
109
|
+
const url = api.url + "?" + channelsParam + "&" + lastAckParam;
|
|
110
|
+
const eventSource = new EventSource(url, {
|
|
111
|
+
withCredentials: api.withCredentials ?? false
|
|
112
|
+
});
|
|
113
|
+
eventSourceRef.current = eventSource;
|
|
114
|
+
eventSource.onopen = () => {
|
|
115
|
+
reconnectAttemptsRef.current = 0;
|
|
116
|
+
setStatus("connected");
|
|
119
117
|
resetPingTimeout();
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
};
|
|
119
|
+
eventSource.onmessage = (evt) => {
|
|
120
|
+
try {
|
|
121
|
+
const payload = JSON.parse(evt.data);
|
|
122
|
+
resetPingTimeout();
|
|
123
|
+
handleMessage(payload);
|
|
124
|
+
const systemResult = systemEvent.safeParse(payload);
|
|
125
|
+
if (systemResult.success) {
|
|
126
|
+
if (systemResult.data.type === "reconnect") {
|
|
127
|
+
connect({ replayEventsSince: systemResult.data.timestamp });
|
|
128
|
+
}
|
|
125
129
|
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.warn("Error parsing message:", error);
|
|
126
132
|
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
133
|
+
};
|
|
134
|
+
eventSource.onerror = () => {
|
|
135
|
+
if (eventSource !== eventSourceRef.current) return;
|
|
136
|
+
const readyState = eventSourceRef.current?.readyState;
|
|
137
|
+
if (readyState === EventSource.CONNECTING) return;
|
|
138
|
+
if (readyState === EventSource.CLOSED) {
|
|
139
|
+
console.log("Connection closed, reconnecting...");
|
|
140
|
+
}
|
|
141
|
+
setStatus("disconnected");
|
|
142
|
+
if (reconnectAttemptsRef.current < maxReconnectAttempts) {
|
|
143
|
+
reconnectAttemptsRef.current++;
|
|
144
|
+
const timeoutMs = Math.min(
|
|
145
|
+
1e3 * Math.pow(2, reconnectAttemptsRef.current),
|
|
146
|
+
3e4
|
|
147
|
+
);
|
|
148
|
+
console.log(
|
|
149
|
+
`Reconnecting in ${timeoutMs}ms... (Attempt ${reconnectAttemptsRef.current})`
|
|
150
|
+
);
|
|
151
|
+
reconnectTimeoutRef.current = setTimeout(() => {
|
|
152
|
+
connect();
|
|
153
|
+
}, timeoutMs);
|
|
154
|
+
} else {
|
|
155
|
+
setStatus("error");
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
} catch (error) {
|
|
159
|
+
setStatus("error");
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
[getAllNeededChannels, maxReconnectAttempts, api.url, api.withCredentials]
|
|
163
|
+
);
|
|
154
164
|
const debouncedConnect = useCallback(() => {
|
|
155
165
|
if (debounceTimeoutRef.current) {
|
|
156
166
|
clearTimeout(debounceTimeoutRef.current);
|
|
@@ -167,7 +177,8 @@ function RealtimeProvider({
|
|
|
167
177
|
if (!disconnectOnWindowHidden || typeof document === "undefined") return;
|
|
168
178
|
const handleVisibilityChange = () => {
|
|
169
179
|
if (document.hidden) {
|
|
170
|
-
if (visibilityTimeoutRef.current)
|
|
180
|
+
if (visibilityTimeoutRef.current)
|
|
181
|
+
clearTimeout(visibilityTimeoutRef.current);
|
|
171
182
|
visibilityTimeoutRef.current = setTimeout(() => {
|
|
172
183
|
console.log("Window hidden for too long, disconnecting...");
|
|
173
184
|
cleanup();
|
|
@@ -186,7 +197,8 @@ function RealtimeProvider({
|
|
|
186
197
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
187
198
|
return () => {
|
|
188
199
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
189
|
-
if (visibilityTimeoutRef.current)
|
|
200
|
+
if (visibilityTimeoutRef.current)
|
|
201
|
+
clearTimeout(visibilityTimeoutRef.current);
|
|
190
202
|
};
|
|
191
203
|
}, [disconnectOnWindowHidden, disconnectDelay, connect, status]);
|
|
192
204
|
const handleMessage = (payload) => {
|
|
@@ -256,7 +268,9 @@ function RealtimeProvider({
|
|
|
256
268
|
function useRealtimeContext() {
|
|
257
269
|
const context = useContext(RealtimeContext);
|
|
258
270
|
if (!context) {
|
|
259
|
-
throw new Error(
|
|
271
|
+
throw new Error(
|
|
272
|
+
"useRealtimeContext must be used within a RealtimeProvider"
|
|
273
|
+
);
|
|
260
274
|
}
|
|
261
275
|
return context;
|
|
262
276
|
}
|
|
@@ -273,7 +287,9 @@ function useRealtime(opts) {
|
|
|
273
287
|
"useRealtime: No RealtimeProvider found. Wrap your app in <RealtimeProvider> to use Realtime."
|
|
274
288
|
);
|
|
275
289
|
}
|
|
276
|
-
const registrationId = useRef(
|
|
290
|
+
const registrationId = useRef(
|
|
291
|
+
Math.random().toString(36).substring(2)
|
|
292
|
+
).current;
|
|
277
293
|
const onDataRef = useRef(onData);
|
|
278
294
|
onDataRef.current = onData;
|
|
279
295
|
useEffect(() => {
|
|
@@ -290,7 +306,11 @@ function useRealtime(opts) {
|
|
|
290
306
|
if (events && events.length > 0 && !events.includes(event)) {
|
|
291
307
|
return;
|
|
292
308
|
}
|
|
293
|
-
const payload = {
|
|
309
|
+
const payload = {
|
|
310
|
+
event,
|
|
311
|
+
data,
|
|
312
|
+
channel
|
|
313
|
+
};
|
|
294
314
|
onDataRef.current?.(payload);
|
|
295
315
|
}
|
|
296
316
|
});
|
package/dist/server/index.cjs
CHANGED
|
@@ -132,6 +132,10 @@ function handle(config) {
|
|
|
132
132
|
request.signal.addEventListener("abort", handleAbort);
|
|
133
133
|
const safeEnqueue = (data) => {
|
|
134
134
|
if (isClosed) return;
|
|
135
|
+
if (controller.desiredSize && controller.desiredSize <= 0) {
|
|
136
|
+
logger.warn?.("\u26A0\uFE0F Client too slow, dropping message to prevent OOM.");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
135
139
|
try {
|
|
136
140
|
controller.enqueue(data);
|
|
137
141
|
} catch (err) {
|
|
@@ -152,15 +156,15 @@ function handle(config) {
|
|
|
152
156
|
let buffer = [];
|
|
153
157
|
let isHistoryReplayed = false;
|
|
154
158
|
const lastHistoryIds = /* @__PURE__ */ new Map();
|
|
155
|
-
const onManagerMessage = (message) => {
|
|
159
|
+
const onManagerMessage = (message, encodedMessage) => {
|
|
156
160
|
logger.debug?.("\u2B07\uFE0F Received event:", message);
|
|
157
161
|
if (!isHistoryReplayed) {
|
|
158
162
|
buffer.push(message);
|
|
159
163
|
} else {
|
|
160
|
-
safeEnqueue(
|
|
164
|
+
safeEnqueue(encodedMessage);
|
|
161
165
|
}
|
|
162
166
|
};
|
|
163
|
-
const
|
|
167
|
+
const executeHistoryPipeline = async () => {
|
|
164
168
|
const pipeline = redis.pipeline();
|
|
165
169
|
const channelAcks = /* @__PURE__ */ new Map();
|
|
166
170
|
for (const channel of channels) {
|
|
@@ -171,36 +175,53 @@ function handle(config) {
|
|
|
171
175
|
safeEnqueue(json(connectedEvent));
|
|
172
176
|
const lastAck = searchParams.get(`last_ack_${channel}`) ?? String(Date.now());
|
|
173
177
|
channelAcks.set(channel, lastAck);
|
|
174
|
-
pipeline.xrange(
|
|
178
|
+
pipeline.xrange(
|
|
179
|
+
channel,
|
|
180
|
+
`(${lastAck}`,
|
|
181
|
+
"+",
|
|
182
|
+
"COUNT",
|
|
183
|
+
maxRecoveryLimit
|
|
184
|
+
);
|
|
175
185
|
}
|
|
176
186
|
try {
|
|
177
|
-
|
|
178
|
-
if (results) {
|
|
179
|
-
results.forEach((result, index) => {
|
|
180
|
-
const [err, rawMissing] = result;
|
|
181
|
-
const channel = channels[index];
|
|
182
|
-
if (!channel) return;
|
|
183
|
-
if (err) {
|
|
184
|
-
logger.error(`Error fetching history for channel ${channel}:`, err);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
const missingMessages = parseStreamResponse(rawMissing);
|
|
188
|
-
if (missingMessages.length > 0) {
|
|
189
|
-
missingMessages.forEach((value) => {
|
|
190
|
-
const eventWithId = value;
|
|
191
|
-
const event = userEvent.safeParse(eventWithId);
|
|
192
|
-
if (event.success) safeEnqueue(json(event.data));
|
|
193
|
-
});
|
|
194
|
-
lastHistoryIds.set(channel, missingMessages[missingMessages.length - 1]?.id ?? "");
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
}
|
|
187
|
+
return await pipeline.exec();
|
|
198
188
|
} catch (error) {
|
|
199
189
|
logger.error("Error executing history pipeline:", error);
|
|
190
|
+
return null;
|
|
200
191
|
}
|
|
192
|
+
};
|
|
193
|
+
const processHistoryResults = (results) => {
|
|
194
|
+
if (!results) return;
|
|
195
|
+
results.forEach((result, index) => {
|
|
196
|
+
const [err, rawMissing] = result;
|
|
197
|
+
const channel = channels[index];
|
|
198
|
+
if (!channel) return;
|
|
199
|
+
if (err) {
|
|
200
|
+
logger.error(
|
|
201
|
+
`Error fetching history for channel ${channel}:`,
|
|
202
|
+
err
|
|
203
|
+
);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const missingMessages = parseStreamResponse(rawMissing);
|
|
207
|
+
if (missingMessages.length > 0) {
|
|
208
|
+
missingMessages.forEach((value) => {
|
|
209
|
+
const eventWithId = value;
|
|
210
|
+
const event = userEvent.safeParse(eventWithId);
|
|
211
|
+
if (event.success) safeEnqueue(json(event.data));
|
|
212
|
+
});
|
|
213
|
+
lastHistoryIds.set(
|
|
214
|
+
channel,
|
|
215
|
+
missingMessages[missingMessages.length - 1]?.id ?? ""
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
};
|
|
220
|
+
const flushBuffer = () => {
|
|
201
221
|
for (const msg of buffer) {
|
|
202
222
|
const channelLastId = lastHistoryIds.get(msg.channel);
|
|
203
|
-
if (channelLastId && compareStreamIds(msg.id, channelLastId) <= 0)
|
|
223
|
+
if (channelLastId && compareStreamIds(msg.id, channelLastId) <= 0)
|
|
224
|
+
continue;
|
|
204
225
|
safeEnqueue(json(msg));
|
|
205
226
|
}
|
|
206
227
|
buffer = [];
|
|
@@ -208,11 +229,20 @@ function handle(config) {
|
|
|
208
229
|
logger.info("\u2705 Subscription established:", { channels });
|
|
209
230
|
};
|
|
210
231
|
try {
|
|
211
|
-
await Promise.all(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
232
|
+
const [_, historyResults] = await Promise.all([
|
|
233
|
+
Promise.all(
|
|
234
|
+
channels.map(async (channel) => {
|
|
235
|
+
const unsub = await subscriptionManager.subscribe(
|
|
236
|
+
channel,
|
|
237
|
+
onManagerMessage
|
|
238
|
+
);
|
|
239
|
+
unsubs.push(unsub);
|
|
240
|
+
})
|
|
241
|
+
),
|
|
242
|
+
executeHistoryPipeline()
|
|
243
|
+
]);
|
|
244
|
+
processHistoryResults(historyResults);
|
|
245
|
+
flushBuffer();
|
|
216
246
|
} catch (err) {
|
|
217
247
|
const errorMessage = err instanceof Error ? err.message : "Unknown error";
|
|
218
248
|
logger.error("\u26A0\uFE0F Redis subscriber error:", errorMessage);
|
|
@@ -238,8 +268,9 @@ function handle(config) {
|
|
|
238
268
|
return new StreamingResponse(stream);
|
|
239
269
|
};
|
|
240
270
|
}
|
|
271
|
+
var encoder = new TextEncoder();
|
|
241
272
|
function json(data) {
|
|
242
|
-
return
|
|
273
|
+
return encoder.encode(`data: ${JSON.stringify(data)}
|
|
243
274
|
|
|
244
275
|
`);
|
|
245
276
|
}
|
|
@@ -262,17 +293,18 @@ var StreamingResponse = class extends Response {
|
|
|
262
293
|
};
|
|
263
294
|
|
|
264
295
|
// src/server/subscription-manager.ts
|
|
296
|
+
var encoder2 = new TextEncoder();
|
|
265
297
|
var SubscriptionManager = class {
|
|
266
298
|
redis;
|
|
267
299
|
subRedis;
|
|
268
300
|
// Map<ChannelName, Set<Listener>>
|
|
269
301
|
listeners = /* @__PURE__ */ new Map();
|
|
270
302
|
unsubscribeTimers = /* @__PURE__ */ new Map();
|
|
271
|
-
|
|
272
|
-
constructor(redis,
|
|
303
|
+
logger;
|
|
304
|
+
constructor(redis, logger) {
|
|
273
305
|
this.redis = redis;
|
|
274
306
|
this.subRedis = redis.duplicate();
|
|
275
|
-
this.
|
|
307
|
+
this.logger = logger;
|
|
276
308
|
this.setupMessageListener();
|
|
277
309
|
}
|
|
278
310
|
setupMessageListener() {
|
|
@@ -292,34 +324,49 @@ var SubscriptionManager = class {
|
|
|
292
324
|
}
|
|
293
325
|
const result = userEvent.safeParse(payload);
|
|
294
326
|
if (result.success) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
327
|
+
const encodedMessage = encoder2.encode(
|
|
328
|
+
`data: ${JSON.stringify(result.data)}
|
|
329
|
+
|
|
330
|
+
`
|
|
331
|
+
);
|
|
332
|
+
this.logger.debug?.(
|
|
333
|
+
`[SubscriptionManager] Dispatching message to ${handlers.size} listeners on ${channel}`
|
|
334
|
+
);
|
|
298
335
|
handlers.forEach((listener) => {
|
|
299
336
|
try {
|
|
300
|
-
listener(result.data);
|
|
337
|
+
listener(result.data, encodedMessage);
|
|
301
338
|
} catch (listenerErr) {
|
|
302
|
-
|
|
339
|
+
this.logger.error(
|
|
340
|
+
`[SubscriptionManager] Error in listener for ${channel}:`,
|
|
341
|
+
listenerErr
|
|
342
|
+
);
|
|
303
343
|
}
|
|
304
344
|
});
|
|
305
345
|
}
|
|
306
346
|
} catch (err) {
|
|
307
|
-
|
|
347
|
+
this.logger.error(
|
|
348
|
+
`[SubscriptionManager] Error processing message on ${channel}:`,
|
|
349
|
+
err
|
|
350
|
+
);
|
|
308
351
|
}
|
|
309
352
|
});
|
|
310
353
|
this.subRedis.on("error", (err) => {
|
|
311
|
-
|
|
354
|
+
this.logger.error("[SubscriptionManager] Redis subscription error:", err);
|
|
312
355
|
});
|
|
313
356
|
}
|
|
314
357
|
async subscribe(channel, listener) {
|
|
315
358
|
if (this.unsubscribeTimers.has(channel)) {
|
|
316
359
|
clearTimeout(this.unsubscribeTimers.get(channel));
|
|
317
360
|
this.unsubscribeTimers.delete(channel);
|
|
318
|
-
|
|
361
|
+
this.logger.debug?.(
|
|
362
|
+
`[SubscriptionManager] Cancelled pending unsubscribe for: ${channel}`
|
|
363
|
+
);
|
|
319
364
|
}
|
|
320
365
|
if (!this.listeners.has(channel)) {
|
|
321
366
|
this.listeners.set(channel, /* @__PURE__ */ new Set());
|
|
322
|
-
|
|
367
|
+
this.logger.debug?.(
|
|
368
|
+
`[SubscriptionManager] Subscribing to Redis channel: ${channel}`
|
|
369
|
+
);
|
|
323
370
|
await this.subRedis.subscribe(channel);
|
|
324
371
|
}
|
|
325
372
|
const channelListeners = this.listeners.get(channel);
|
|
@@ -335,9 +382,14 @@ var SubscriptionManager = class {
|
|
|
335
382
|
const timer = setTimeout(() => {
|
|
336
383
|
this.listeners.delete(channel);
|
|
337
384
|
this.unsubscribeTimers.delete(channel);
|
|
338
|
-
|
|
385
|
+
this.logger.debug?.(
|
|
386
|
+
`[SubscriptionManager] Unsubscribing from Redis channel: ${channel}`
|
|
387
|
+
);
|
|
339
388
|
this.subRedis.unsubscribe(channel).catch((err) => {
|
|
340
|
-
|
|
389
|
+
this.logger.error(
|
|
390
|
+
`[SubscriptionManager] Error unsubscribing from ${channel}:`,
|
|
391
|
+
err
|
|
392
|
+
);
|
|
341
393
|
});
|
|
342
394
|
}, 2e3);
|
|
343
395
|
this.unsubscribeTimers.set(channel, timer);
|
|
@@ -387,7 +439,10 @@ var RealtimeBase = class {
|
|
|
387
439
|
}
|
|
388
440
|
};
|
|
389
441
|
if (this._redis) {
|
|
390
|
-
this._subscriptionManager = new SubscriptionManager(
|
|
442
|
+
this._subscriptionManager = new SubscriptionManager(
|
|
443
|
+
this._redis,
|
|
444
|
+
this._logger
|
|
445
|
+
);
|
|
391
446
|
}
|
|
392
447
|
Object.assign(this, this.createEventHandlers("default"));
|
|
393
448
|
}
|
|
@@ -400,7 +455,10 @@ var RealtimeBase = class {
|
|
|
400
455
|
let pingInterval = void 0;
|
|
401
456
|
const startPingInterval = () => {
|
|
402
457
|
pingInterval = setInterval(() => {
|
|
403
|
-
this._redis?.publish(
|
|
458
|
+
this._redis?.publish(
|
|
459
|
+
channel,
|
|
460
|
+
JSON.stringify({ type: "ping", timestamp: Date.now() })
|
|
461
|
+
);
|
|
404
462
|
}, 6e4);
|
|
405
463
|
};
|
|
406
464
|
const stopPingInterval = () => {
|
|
@@ -412,7 +470,13 @@ var RealtimeBase = class {
|
|
|
412
470
|
const start = args?.start ? String(args.start) : "-";
|
|
413
471
|
const end = args?.end ? String(args.end) : "+";
|
|
414
472
|
const limit = args?.limit ?? 1e3;
|
|
415
|
-
const rawHistory = await redis.xrange(
|
|
473
|
+
const rawHistory = await redis.xrange(
|
|
474
|
+
channel,
|
|
475
|
+
start,
|
|
476
|
+
end,
|
|
477
|
+
"COUNT",
|
|
478
|
+
limit
|
|
479
|
+
);
|
|
416
480
|
const historyMessages = parseStreamResponse(rawHistory);
|
|
417
481
|
return historyMessages.map((value) => {
|
|
418
482
|
if (typeof value === "object" && value !== null) {
|
|
@@ -456,17 +520,26 @@ var RealtimeBase = class {
|
|
|
456
520
|
const limit = typeof history === "object" ? history.limit : void 0;
|
|
457
521
|
let rawMessages = [];
|
|
458
522
|
if (limit) {
|
|
459
|
-
rawMessages = await redis.xrange(
|
|
523
|
+
rawMessages = await redis.xrange(
|
|
524
|
+
channel,
|
|
525
|
+
start,
|
|
526
|
+
end,
|
|
527
|
+
"COUNT",
|
|
528
|
+
limit
|
|
529
|
+
);
|
|
460
530
|
} else {
|
|
461
531
|
rawMessages = await redis.xrange(channel, start, end);
|
|
462
532
|
}
|
|
463
533
|
const messages = parseStreamResponse(rawMessages);
|
|
464
534
|
for (const message of messages) {
|
|
465
535
|
const typedMessage = message;
|
|
466
|
-
if (!typedMessage.event || events && !events.includes(typedMessage.event))
|
|
536
|
+
if (!typedMessage.event || events && !events.includes(typedMessage.event))
|
|
537
|
+
continue;
|
|
467
538
|
const result = userEvent.safeParse(message);
|
|
468
539
|
if (result.success) {
|
|
469
|
-
onData(
|
|
540
|
+
onData(
|
|
541
|
+
result.data
|
|
542
|
+
);
|
|
470
543
|
}
|
|
471
544
|
}
|
|
472
545
|
if (messages.length > 0) {
|
package/dist/server/index.js
CHANGED
|
@@ -107,6 +107,10 @@ function handle(config) {
|
|
|
107
107
|
request.signal.addEventListener("abort", handleAbort);
|
|
108
108
|
const safeEnqueue = (data) => {
|
|
109
109
|
if (isClosed) return;
|
|
110
|
+
if (controller.desiredSize && controller.desiredSize <= 0) {
|
|
111
|
+
logger.warn?.("\u26A0\uFE0F Client too slow, dropping message to prevent OOM.");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
110
114
|
try {
|
|
111
115
|
controller.enqueue(data);
|
|
112
116
|
} catch (err) {
|
|
@@ -127,15 +131,15 @@ function handle(config) {
|
|
|
127
131
|
let buffer = [];
|
|
128
132
|
let isHistoryReplayed = false;
|
|
129
133
|
const lastHistoryIds = /* @__PURE__ */ new Map();
|
|
130
|
-
const onManagerMessage = (message) => {
|
|
134
|
+
const onManagerMessage = (message, encodedMessage) => {
|
|
131
135
|
logger.debug?.("\u2B07\uFE0F Received event:", message);
|
|
132
136
|
if (!isHistoryReplayed) {
|
|
133
137
|
buffer.push(message);
|
|
134
138
|
} else {
|
|
135
|
-
safeEnqueue(
|
|
139
|
+
safeEnqueue(encodedMessage);
|
|
136
140
|
}
|
|
137
141
|
};
|
|
138
|
-
const
|
|
142
|
+
const executeHistoryPipeline = async () => {
|
|
139
143
|
const pipeline = redis.pipeline();
|
|
140
144
|
const channelAcks = /* @__PURE__ */ new Map();
|
|
141
145
|
for (const channel of channels) {
|
|
@@ -146,36 +150,53 @@ function handle(config) {
|
|
|
146
150
|
safeEnqueue(json(connectedEvent));
|
|
147
151
|
const lastAck = searchParams.get(`last_ack_${channel}`) ?? String(Date.now());
|
|
148
152
|
channelAcks.set(channel, lastAck);
|
|
149
|
-
pipeline.xrange(
|
|
153
|
+
pipeline.xrange(
|
|
154
|
+
channel,
|
|
155
|
+
`(${lastAck}`,
|
|
156
|
+
"+",
|
|
157
|
+
"COUNT",
|
|
158
|
+
maxRecoveryLimit
|
|
159
|
+
);
|
|
150
160
|
}
|
|
151
161
|
try {
|
|
152
|
-
|
|
153
|
-
if (results) {
|
|
154
|
-
results.forEach((result, index) => {
|
|
155
|
-
const [err, rawMissing] = result;
|
|
156
|
-
const channel = channels[index];
|
|
157
|
-
if (!channel) return;
|
|
158
|
-
if (err) {
|
|
159
|
-
logger.error(`Error fetching history for channel ${channel}:`, err);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
const missingMessages = parseStreamResponse(rawMissing);
|
|
163
|
-
if (missingMessages.length > 0) {
|
|
164
|
-
missingMessages.forEach((value) => {
|
|
165
|
-
const eventWithId = value;
|
|
166
|
-
const event = userEvent.safeParse(eventWithId);
|
|
167
|
-
if (event.success) safeEnqueue(json(event.data));
|
|
168
|
-
});
|
|
169
|
-
lastHistoryIds.set(channel, missingMessages[missingMessages.length - 1]?.id ?? "");
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
}
|
|
162
|
+
return await pipeline.exec();
|
|
173
163
|
} catch (error) {
|
|
174
164
|
logger.error("Error executing history pipeline:", error);
|
|
165
|
+
return null;
|
|
175
166
|
}
|
|
167
|
+
};
|
|
168
|
+
const processHistoryResults = (results) => {
|
|
169
|
+
if (!results) return;
|
|
170
|
+
results.forEach((result, index) => {
|
|
171
|
+
const [err, rawMissing] = result;
|
|
172
|
+
const channel = channels[index];
|
|
173
|
+
if (!channel) return;
|
|
174
|
+
if (err) {
|
|
175
|
+
logger.error(
|
|
176
|
+
`Error fetching history for channel ${channel}:`,
|
|
177
|
+
err
|
|
178
|
+
);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const missingMessages = parseStreamResponse(rawMissing);
|
|
182
|
+
if (missingMessages.length > 0) {
|
|
183
|
+
missingMessages.forEach((value) => {
|
|
184
|
+
const eventWithId = value;
|
|
185
|
+
const event = userEvent.safeParse(eventWithId);
|
|
186
|
+
if (event.success) safeEnqueue(json(event.data));
|
|
187
|
+
});
|
|
188
|
+
lastHistoryIds.set(
|
|
189
|
+
channel,
|
|
190
|
+
missingMessages[missingMessages.length - 1]?.id ?? ""
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
const flushBuffer = () => {
|
|
176
196
|
for (const msg of buffer) {
|
|
177
197
|
const channelLastId = lastHistoryIds.get(msg.channel);
|
|
178
|
-
if (channelLastId && compareStreamIds(msg.id, channelLastId) <= 0)
|
|
198
|
+
if (channelLastId && compareStreamIds(msg.id, channelLastId) <= 0)
|
|
199
|
+
continue;
|
|
179
200
|
safeEnqueue(json(msg));
|
|
180
201
|
}
|
|
181
202
|
buffer = [];
|
|
@@ -183,11 +204,20 @@ function handle(config) {
|
|
|
183
204
|
logger.info("\u2705 Subscription established:", { channels });
|
|
184
205
|
};
|
|
185
206
|
try {
|
|
186
|
-
await Promise.all(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
207
|
+
const [_, historyResults] = await Promise.all([
|
|
208
|
+
Promise.all(
|
|
209
|
+
channels.map(async (channel) => {
|
|
210
|
+
const unsub = await subscriptionManager.subscribe(
|
|
211
|
+
channel,
|
|
212
|
+
onManagerMessage
|
|
213
|
+
);
|
|
214
|
+
unsubs.push(unsub);
|
|
215
|
+
})
|
|
216
|
+
),
|
|
217
|
+
executeHistoryPipeline()
|
|
218
|
+
]);
|
|
219
|
+
processHistoryResults(historyResults);
|
|
220
|
+
flushBuffer();
|
|
191
221
|
} catch (err) {
|
|
192
222
|
const errorMessage = err instanceof Error ? err.message : "Unknown error";
|
|
193
223
|
logger.error("\u26A0\uFE0F Redis subscriber error:", errorMessage);
|
|
@@ -213,8 +243,9 @@ function handle(config) {
|
|
|
213
243
|
return new StreamingResponse(stream);
|
|
214
244
|
};
|
|
215
245
|
}
|
|
246
|
+
var encoder = new TextEncoder();
|
|
216
247
|
function json(data) {
|
|
217
|
-
return
|
|
248
|
+
return encoder.encode(`data: ${JSON.stringify(data)}
|
|
218
249
|
|
|
219
250
|
`);
|
|
220
251
|
}
|
|
@@ -237,17 +268,18 @@ var StreamingResponse = class extends Response {
|
|
|
237
268
|
};
|
|
238
269
|
|
|
239
270
|
// src/server/subscription-manager.ts
|
|
271
|
+
var encoder2 = new TextEncoder();
|
|
240
272
|
var SubscriptionManager = class {
|
|
241
273
|
redis;
|
|
242
274
|
subRedis;
|
|
243
275
|
// Map<ChannelName, Set<Listener>>
|
|
244
276
|
listeners = /* @__PURE__ */ new Map();
|
|
245
277
|
unsubscribeTimers = /* @__PURE__ */ new Map();
|
|
246
|
-
|
|
247
|
-
constructor(redis,
|
|
278
|
+
logger;
|
|
279
|
+
constructor(redis, logger) {
|
|
248
280
|
this.redis = redis;
|
|
249
281
|
this.subRedis = redis.duplicate();
|
|
250
|
-
this.
|
|
282
|
+
this.logger = logger;
|
|
251
283
|
this.setupMessageListener();
|
|
252
284
|
}
|
|
253
285
|
setupMessageListener() {
|
|
@@ -267,34 +299,49 @@ var SubscriptionManager = class {
|
|
|
267
299
|
}
|
|
268
300
|
const result = userEvent.safeParse(payload);
|
|
269
301
|
if (result.success) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
302
|
+
const encodedMessage = encoder2.encode(
|
|
303
|
+
`data: ${JSON.stringify(result.data)}
|
|
304
|
+
|
|
305
|
+
`
|
|
306
|
+
);
|
|
307
|
+
this.logger.debug?.(
|
|
308
|
+
`[SubscriptionManager] Dispatching message to ${handlers.size} listeners on ${channel}`
|
|
309
|
+
);
|
|
273
310
|
handlers.forEach((listener) => {
|
|
274
311
|
try {
|
|
275
|
-
listener(result.data);
|
|
312
|
+
listener(result.data, encodedMessage);
|
|
276
313
|
} catch (listenerErr) {
|
|
277
|
-
|
|
314
|
+
this.logger.error(
|
|
315
|
+
`[SubscriptionManager] Error in listener for ${channel}:`,
|
|
316
|
+
listenerErr
|
|
317
|
+
);
|
|
278
318
|
}
|
|
279
319
|
});
|
|
280
320
|
}
|
|
281
321
|
} catch (err) {
|
|
282
|
-
|
|
322
|
+
this.logger.error(
|
|
323
|
+
`[SubscriptionManager] Error processing message on ${channel}:`,
|
|
324
|
+
err
|
|
325
|
+
);
|
|
283
326
|
}
|
|
284
327
|
});
|
|
285
328
|
this.subRedis.on("error", (err) => {
|
|
286
|
-
|
|
329
|
+
this.logger.error("[SubscriptionManager] Redis subscription error:", err);
|
|
287
330
|
});
|
|
288
331
|
}
|
|
289
332
|
async subscribe(channel, listener) {
|
|
290
333
|
if (this.unsubscribeTimers.has(channel)) {
|
|
291
334
|
clearTimeout(this.unsubscribeTimers.get(channel));
|
|
292
335
|
this.unsubscribeTimers.delete(channel);
|
|
293
|
-
|
|
336
|
+
this.logger.debug?.(
|
|
337
|
+
`[SubscriptionManager] Cancelled pending unsubscribe for: ${channel}`
|
|
338
|
+
);
|
|
294
339
|
}
|
|
295
340
|
if (!this.listeners.has(channel)) {
|
|
296
341
|
this.listeners.set(channel, /* @__PURE__ */ new Set());
|
|
297
|
-
|
|
342
|
+
this.logger.debug?.(
|
|
343
|
+
`[SubscriptionManager] Subscribing to Redis channel: ${channel}`
|
|
344
|
+
);
|
|
298
345
|
await this.subRedis.subscribe(channel);
|
|
299
346
|
}
|
|
300
347
|
const channelListeners = this.listeners.get(channel);
|
|
@@ -310,9 +357,14 @@ var SubscriptionManager = class {
|
|
|
310
357
|
const timer = setTimeout(() => {
|
|
311
358
|
this.listeners.delete(channel);
|
|
312
359
|
this.unsubscribeTimers.delete(channel);
|
|
313
|
-
|
|
360
|
+
this.logger.debug?.(
|
|
361
|
+
`[SubscriptionManager] Unsubscribing from Redis channel: ${channel}`
|
|
362
|
+
);
|
|
314
363
|
this.subRedis.unsubscribe(channel).catch((err) => {
|
|
315
|
-
|
|
364
|
+
this.logger.error(
|
|
365
|
+
`[SubscriptionManager] Error unsubscribing from ${channel}:`,
|
|
366
|
+
err
|
|
367
|
+
);
|
|
316
368
|
});
|
|
317
369
|
}, 2e3);
|
|
318
370
|
this.unsubscribeTimers.set(channel, timer);
|
|
@@ -362,7 +414,10 @@ var RealtimeBase = class {
|
|
|
362
414
|
}
|
|
363
415
|
};
|
|
364
416
|
if (this._redis) {
|
|
365
|
-
this._subscriptionManager = new SubscriptionManager(
|
|
417
|
+
this._subscriptionManager = new SubscriptionManager(
|
|
418
|
+
this._redis,
|
|
419
|
+
this._logger
|
|
420
|
+
);
|
|
366
421
|
}
|
|
367
422
|
Object.assign(this, this.createEventHandlers("default"));
|
|
368
423
|
}
|
|
@@ -375,7 +430,10 @@ var RealtimeBase = class {
|
|
|
375
430
|
let pingInterval = void 0;
|
|
376
431
|
const startPingInterval = () => {
|
|
377
432
|
pingInterval = setInterval(() => {
|
|
378
|
-
this._redis?.publish(
|
|
433
|
+
this._redis?.publish(
|
|
434
|
+
channel,
|
|
435
|
+
JSON.stringify({ type: "ping", timestamp: Date.now() })
|
|
436
|
+
);
|
|
379
437
|
}, 6e4);
|
|
380
438
|
};
|
|
381
439
|
const stopPingInterval = () => {
|
|
@@ -387,7 +445,13 @@ var RealtimeBase = class {
|
|
|
387
445
|
const start = args?.start ? String(args.start) : "-";
|
|
388
446
|
const end = args?.end ? String(args.end) : "+";
|
|
389
447
|
const limit = args?.limit ?? 1e3;
|
|
390
|
-
const rawHistory = await redis.xrange(
|
|
448
|
+
const rawHistory = await redis.xrange(
|
|
449
|
+
channel,
|
|
450
|
+
start,
|
|
451
|
+
end,
|
|
452
|
+
"COUNT",
|
|
453
|
+
limit
|
|
454
|
+
);
|
|
391
455
|
const historyMessages = parseStreamResponse(rawHistory);
|
|
392
456
|
return historyMessages.map((value) => {
|
|
393
457
|
if (typeof value === "object" && value !== null) {
|
|
@@ -431,17 +495,26 @@ var RealtimeBase = class {
|
|
|
431
495
|
const limit = typeof history === "object" ? history.limit : void 0;
|
|
432
496
|
let rawMessages = [];
|
|
433
497
|
if (limit) {
|
|
434
|
-
rawMessages = await redis.xrange(
|
|
498
|
+
rawMessages = await redis.xrange(
|
|
499
|
+
channel,
|
|
500
|
+
start,
|
|
501
|
+
end,
|
|
502
|
+
"COUNT",
|
|
503
|
+
limit
|
|
504
|
+
);
|
|
435
505
|
} else {
|
|
436
506
|
rawMessages = await redis.xrange(channel, start, end);
|
|
437
507
|
}
|
|
438
508
|
const messages = parseStreamResponse(rawMessages);
|
|
439
509
|
for (const message of messages) {
|
|
440
510
|
const typedMessage = message;
|
|
441
|
-
if (!typedMessage.event || events && !events.includes(typedMessage.event))
|
|
511
|
+
if (!typedMessage.event || events && !events.includes(typedMessage.event))
|
|
512
|
+
continue;
|
|
442
513
|
const result = userEvent.safeParse(message);
|
|
443
514
|
if (result.success) {
|
|
444
|
-
onData(
|
|
515
|
+
onData(
|
|
516
|
+
result.data
|
|
517
|
+
);
|
|
445
518
|
}
|
|
446
519
|
}
|
|
447
520
|
if (messages.length > 0) {
|