@djangocfg/centrifugo 2.1.102 → 2.1.104
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/package.json +22 -27
- package/src/components/CentrifugoMonitor/CentrifugoMonitor.tsx +1 -1
- package/src/components/CentrifugoMonitor/CentrifugoMonitorDialog.tsx +2 -2
- package/src/components/CentrifugoMonitor/CentrifugoMonitorWidget.tsx +1 -1
- package/src/components/ConnectionStatus/ConnectionStatus.tsx +1 -1
- package/src/components/ConnectionStatus/ConnectionStatusCard.tsx +1 -1
- package/src/components/MessagesFeed/MessageFilters.tsx +1 -1
- package/src/components/MessagesFeed/MessagesFeed.tsx +1 -1
- package/src/components/SubscriptionsList/SubscriptionsList.tsx +1 -1
- package/src/components/index.ts +2 -0
- package/src/debug/ConnectionTab/ConnectionTab.tsx +1 -1
- package/src/debug/DebugPanel/DebugPanel.tsx +1 -1
- package/src/debug/LogsTab/LogsTab.tsx +1 -1
- package/src/debug/SubscriptionsTab/SubscriptionsTab.tsx +1 -1
- package/src/hooks/index.ts +2 -0
- package/src/index.ts +2 -0
- package/dist/components.cjs +0 -871
- package/dist/components.cjs.map +0 -1
- package/dist/components.d.mts +0 -129
- package/dist/components.d.ts +0 -129
- package/dist/components.mjs +0 -857
- package/dist/components.mjs.map +0 -1
- package/dist/hooks.cjs +0 -379
- package/dist/hooks.cjs.map +0 -1
- package/dist/hooks.d.mts +0 -128
- package/dist/hooks.d.ts +0 -128
- package/dist/hooks.mjs +0 -374
- package/dist/hooks.mjs.map +0 -1
- package/dist/index.cjs +0 -3007
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.mts +0 -838
- package/dist/index.d.ts +0 -838
- package/dist/index.mjs +0 -2948
- package/dist/index.mjs.map +0 -1
package/dist/index.cjs
DELETED
|
@@ -1,3007 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var centrifuge = require('centrifuge');
|
|
4
|
-
var hooks = require('@djangocfg/ui-core/hooks');
|
|
5
|
-
var consola = require('consola');
|
|
6
|
-
var react = require('react');
|
|
7
|
-
var auth = require('@djangocfg/api/auth');
|
|
8
|
-
var lucideReact = require('lucide-react');
|
|
9
|
-
var uiNextjs = require('@djangocfg/ui-nextjs');
|
|
10
|
-
var moment2 = require('moment');
|
|
11
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
12
|
-
var uiTools = require('@djangocfg/ui-tools');
|
|
13
|
-
|
|
14
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
|
-
|
|
16
|
-
var moment2__default = /*#__PURE__*/_interopDefault(moment2);
|
|
17
|
-
|
|
18
|
-
var __defProp = Object.defineProperty;
|
|
19
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
20
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
21
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
22
|
-
var CENTRIFUGO_EVENT = "centrifugo";
|
|
23
|
-
var CENTRIFUGO_MONITOR_EVENTS = {
|
|
24
|
-
OPEN_MONITOR_DIALOG: "CENTRIFUGO_OPEN_MONITOR_DIALOG",
|
|
25
|
-
CLOSE_MONITOR_DIALOG: "CENTRIFUGO_CLOSE_MONITOR_DIALOG"
|
|
26
|
-
};
|
|
27
|
-
var CENTRIFUGO_ERROR_EVENT = CENTRIFUGO_EVENT;
|
|
28
|
-
var CENTRIFUGO_VERSION_MISMATCH_EVENT = CENTRIFUGO_EVENT;
|
|
29
|
-
var dispatchCentrifugoEvent = /* @__PURE__ */ __name((payload) => {
|
|
30
|
-
if (typeof window === "undefined") {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
try {
|
|
34
|
-
const detail = {
|
|
35
|
-
...payload,
|
|
36
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
37
|
-
};
|
|
38
|
-
window.dispatchEvent(new CustomEvent(CENTRIFUGO_EVENT, {
|
|
39
|
-
detail,
|
|
40
|
-
bubbles: true,
|
|
41
|
-
cancelable: false
|
|
42
|
-
}));
|
|
43
|
-
} catch (error) {
|
|
44
|
-
}
|
|
45
|
-
}, "dispatchCentrifugoEvent");
|
|
46
|
-
var dispatchCentrifugoError = /* @__PURE__ */ __name((data) => {
|
|
47
|
-
dispatchCentrifugoEvent({ type: "error", data });
|
|
48
|
-
}, "dispatchCentrifugoError");
|
|
49
|
-
var dispatchVersionMismatch = /* @__PURE__ */ __name((data) => {
|
|
50
|
-
dispatchCentrifugoEvent({ type: "version_mismatch", data });
|
|
51
|
-
}, "dispatchVersionMismatch");
|
|
52
|
-
var dispatchConnected = /* @__PURE__ */ __name((data = {}) => {
|
|
53
|
-
dispatchCentrifugoEvent({ type: "connected", data });
|
|
54
|
-
}, "dispatchConnected");
|
|
55
|
-
var dispatchDisconnected = /* @__PURE__ */ __name((data = {}) => {
|
|
56
|
-
dispatchCentrifugoEvent({ type: "disconnected", data });
|
|
57
|
-
}, "dispatchDisconnected");
|
|
58
|
-
var dispatchReconnecting = /* @__PURE__ */ __name((data = {}) => {
|
|
59
|
-
dispatchCentrifugoEvent({ type: "reconnecting", data });
|
|
60
|
-
}, "dispatchReconnecting");
|
|
61
|
-
var emitOpenMonitorDialog = /* @__PURE__ */ __name((payload) => {
|
|
62
|
-
hooks.events.publish({
|
|
63
|
-
type: CENTRIFUGO_MONITOR_EVENTS.OPEN_MONITOR_DIALOG,
|
|
64
|
-
payload: payload || {}
|
|
65
|
-
});
|
|
66
|
-
}, "emitOpenMonitorDialog");
|
|
67
|
-
var emitCloseMonitorDialog = /* @__PURE__ */ __name(() => {
|
|
68
|
-
hooks.events.publish({
|
|
69
|
-
type: CENTRIFUGO_MONITOR_EVENTS.CLOSE_MONITOR_DIALOG,
|
|
70
|
-
payload: {}
|
|
71
|
-
});
|
|
72
|
-
}, "emitCloseMonitorDialog");
|
|
73
|
-
var consolaLogger = consola.createConsola({
|
|
74
|
-
level: 4 ,
|
|
75
|
-
formatOptions: {
|
|
76
|
-
colors: true,
|
|
77
|
-
date: false,
|
|
78
|
-
compact: false
|
|
79
|
-
}
|
|
80
|
-
}).withTag("[Centrifugo]");
|
|
81
|
-
function getConsolaLogger(tag) {
|
|
82
|
-
const consola = consolaLogger.withTag(`[${tag}]`);
|
|
83
|
-
return {
|
|
84
|
-
debug: /* @__PURE__ */ __name((message, data) => consola.debug(message, data || ""), "debug"),
|
|
85
|
-
info: /* @__PURE__ */ __name((message, data) => consola.info(message, data || ""), "info"),
|
|
86
|
-
success: /* @__PURE__ */ __name((message, data) => consola.success(message, data || ""), "success"),
|
|
87
|
-
warning: /* @__PURE__ */ __name((message, data) => consola.warn(message, data || ""), "warning"),
|
|
88
|
-
error: /* @__PURE__ */ __name((message, error) => consola.error(message, error || ""), "error")
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
__name(getConsolaLogger, "getConsolaLogger");
|
|
92
|
-
|
|
93
|
-
// src/core/client/connection.ts
|
|
94
|
-
function createConnectionState(urlOrOptions, token, userId, timeout = 3e4, logger3) {
|
|
95
|
-
let url;
|
|
96
|
-
let actualToken;
|
|
97
|
-
let actualUserId;
|
|
98
|
-
let actualTimeout;
|
|
99
|
-
let actualLogger;
|
|
100
|
-
let getToken;
|
|
101
|
-
if (typeof urlOrOptions === "object") {
|
|
102
|
-
url = urlOrOptions.url;
|
|
103
|
-
actualToken = urlOrOptions.token;
|
|
104
|
-
actualUserId = urlOrOptions.userId;
|
|
105
|
-
actualTimeout = urlOrOptions.timeout ?? 3e4;
|
|
106
|
-
actualLogger = urlOrOptions.logger;
|
|
107
|
-
getToken = urlOrOptions.getToken;
|
|
108
|
-
} else {
|
|
109
|
-
url = urlOrOptions;
|
|
110
|
-
actualToken = token;
|
|
111
|
-
actualUserId = userId;
|
|
112
|
-
actualTimeout = timeout;
|
|
113
|
-
actualLogger = logger3;
|
|
114
|
-
}
|
|
115
|
-
const log = actualLogger || getConsolaLogger("client");
|
|
116
|
-
const replyChannel = `user#${actualUserId}`;
|
|
117
|
-
const centrifugeOptions = {
|
|
118
|
-
token: actualToken
|
|
119
|
-
};
|
|
120
|
-
if (getToken) {
|
|
121
|
-
centrifugeOptions.getToken = async () => {
|
|
122
|
-
log.info("Token expired, refreshing...");
|
|
123
|
-
try {
|
|
124
|
-
const newToken = await getToken();
|
|
125
|
-
log.success("Token refreshed successfully");
|
|
126
|
-
return newToken;
|
|
127
|
-
} catch (error) {
|
|
128
|
-
log.error("Failed to refresh token", error);
|
|
129
|
-
throw error;
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
const centrifuge$1 = new centrifuge.Centrifuge(url, centrifugeOptions);
|
|
134
|
-
return {
|
|
135
|
-
centrifuge: centrifuge$1,
|
|
136
|
-
replySubscription: null,
|
|
137
|
-
pendingRequests: /* @__PURE__ */ new Map(),
|
|
138
|
-
channelSubscriptions: /* @__PURE__ */ new Map(),
|
|
139
|
-
userId: actualUserId,
|
|
140
|
-
replyChannel,
|
|
141
|
-
timeout: actualTimeout,
|
|
142
|
-
logger: log
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
__name(createConnectionState, "createConnectionState");
|
|
146
|
-
function setupConnectionHandlers(state, onResponse) {
|
|
147
|
-
const { centrifuge, pendingRequests, logger: logger3, userId } = state;
|
|
148
|
-
centrifuge.on("disconnected", (ctx) => {
|
|
149
|
-
pendingRequests.forEach(({ reject }) => {
|
|
150
|
-
reject(new Error("Disconnected from Centrifugo"));
|
|
151
|
-
});
|
|
152
|
-
pendingRequests.clear();
|
|
153
|
-
dispatchDisconnected({
|
|
154
|
-
userId,
|
|
155
|
-
reason: ctx.reason
|
|
156
|
-
});
|
|
157
|
-
logger3.info("Disconnected from Centrifugo", { reason: ctx.reason });
|
|
158
|
-
});
|
|
159
|
-
centrifuge.on("connected", () => {
|
|
160
|
-
dispatchConnected({ userId });
|
|
161
|
-
logger3.success("Connected to Centrifugo");
|
|
162
|
-
});
|
|
163
|
-
centrifuge.on("connecting", (ctx) => {
|
|
164
|
-
if (ctx.reason === "transport closed") {
|
|
165
|
-
dispatchReconnecting({
|
|
166
|
-
userId,
|
|
167
|
-
attempt: 1,
|
|
168
|
-
reason: ctx.reason
|
|
169
|
-
});
|
|
170
|
-
logger3.info("Reconnecting to Centrifugo...", { reason: ctx.reason });
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
__name(setupConnectionHandlers, "setupConnectionHandlers");
|
|
175
|
-
async function connect(state, onResponse) {
|
|
176
|
-
const { centrifuge, replyChannel, logger: logger3 } = state;
|
|
177
|
-
return new Promise((resolve, reject) => {
|
|
178
|
-
let resolved = false;
|
|
179
|
-
const onConnected = /* @__PURE__ */ __name(() => {
|
|
180
|
-
if (!resolved) {
|
|
181
|
-
resolved = true;
|
|
182
|
-
resolve();
|
|
183
|
-
}
|
|
184
|
-
}, "onConnected");
|
|
185
|
-
const onError = /* @__PURE__ */ __name((ctx) => {
|
|
186
|
-
if (!resolved) {
|
|
187
|
-
resolved = true;
|
|
188
|
-
reject(new Error(ctx.message || "Connection error"));
|
|
189
|
-
}
|
|
190
|
-
}, "onError");
|
|
191
|
-
centrifuge.on("connected", onConnected);
|
|
192
|
-
centrifuge.on("error", onError);
|
|
193
|
-
centrifuge.connect();
|
|
194
|
-
const subscription = centrifuge.newSubscription(replyChannel);
|
|
195
|
-
state.replySubscription = subscription;
|
|
196
|
-
subscription.on("publication", (ctx) => {
|
|
197
|
-
onResponse(ctx.data);
|
|
198
|
-
});
|
|
199
|
-
subscription.on("subscribed", () => {
|
|
200
|
-
logger3.success(`Subscribed to reply channel: ${replyChannel}`);
|
|
201
|
-
});
|
|
202
|
-
subscription.on("error", (ctx) => {
|
|
203
|
-
if (ctx.error?.code === 105) ; else {
|
|
204
|
-
logger3.error(`Subscription error for ${replyChannel}:`, ctx.error);
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
subscription.subscribe();
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
__name(connect, "connect");
|
|
211
|
-
function disconnect(state) {
|
|
212
|
-
const { centrifuge, replySubscription, channelSubscriptions, logger: logger3 } = state;
|
|
213
|
-
channelSubscriptions.forEach((sub, channel) => {
|
|
214
|
-
try {
|
|
215
|
-
sub.unsubscribe();
|
|
216
|
-
centrifuge.removeSubscription(sub);
|
|
217
|
-
} catch (error) {
|
|
218
|
-
logger3.error(`Error unsubscribing from ${channel}`, error);
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
channelSubscriptions.clear();
|
|
222
|
-
if (replySubscription) {
|
|
223
|
-
replySubscription.unsubscribe();
|
|
224
|
-
state.replySubscription = null;
|
|
225
|
-
}
|
|
226
|
-
centrifuge.disconnect();
|
|
227
|
-
logger3.info("Disconnected from Centrifugo");
|
|
228
|
-
}
|
|
229
|
-
__name(disconnect, "disconnect");
|
|
230
|
-
function generateCorrelationId() {
|
|
231
|
-
return `rpc-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
232
|
-
}
|
|
233
|
-
__name(generateCorrelationId, "generateCorrelationId");
|
|
234
|
-
|
|
235
|
-
// src/core/client/subscriptions.ts
|
|
236
|
-
function subscribe(manager, channel, callback) {
|
|
237
|
-
const { channelSubscriptions, centrifuge, logger: logger3 } = manager;
|
|
238
|
-
const existingSub = channelSubscriptions.get(channel);
|
|
239
|
-
if (existingSub) {
|
|
240
|
-
logger3.warning(`Already subscribed to ${channel}, reusing existing subscription`);
|
|
241
|
-
const handler = /* @__PURE__ */ __name((ctx) => {
|
|
242
|
-
try {
|
|
243
|
-
callback(ctx.data);
|
|
244
|
-
} catch (error) {
|
|
245
|
-
logger3.error(`Error in subscription callback for ${channel}`, error);
|
|
246
|
-
}
|
|
247
|
-
}, "handler");
|
|
248
|
-
existingSub.on("publication", handler);
|
|
249
|
-
return () => {
|
|
250
|
-
existingSub.off("publication", handler);
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
let sub = centrifuge.getSubscription(channel);
|
|
254
|
-
if (!sub) {
|
|
255
|
-
sub = centrifuge.newSubscription(channel);
|
|
256
|
-
}
|
|
257
|
-
const publicationHandler = /* @__PURE__ */ __name((ctx) => {
|
|
258
|
-
try {
|
|
259
|
-
callback(ctx.data);
|
|
260
|
-
} catch (error) {
|
|
261
|
-
logger3.error(`Error in subscription callback for ${channel}`, error);
|
|
262
|
-
}
|
|
263
|
-
}, "publicationHandler");
|
|
264
|
-
sub.on("publication", publicationHandler);
|
|
265
|
-
sub.on("subscribed", () => {
|
|
266
|
-
logger3.success(`Subscribed to ${channel}`);
|
|
267
|
-
});
|
|
268
|
-
sub.on("error", (ctx) => {
|
|
269
|
-
logger3.error(`Subscription error for ${channel}`, ctx.error);
|
|
270
|
-
});
|
|
271
|
-
sub.subscribe();
|
|
272
|
-
channelSubscriptions.set(channel, sub);
|
|
273
|
-
return () => unsubscribe(manager, channel);
|
|
274
|
-
}
|
|
275
|
-
__name(subscribe, "subscribe");
|
|
276
|
-
function unsubscribe(manager, channel) {
|
|
277
|
-
const { channelSubscriptions, centrifuge, logger: logger3 } = manager;
|
|
278
|
-
const sub = channelSubscriptions.get(channel);
|
|
279
|
-
if (!sub) {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
try {
|
|
283
|
-
sub.unsubscribe();
|
|
284
|
-
centrifuge.removeSubscription(sub);
|
|
285
|
-
channelSubscriptions.delete(channel);
|
|
286
|
-
logger3.info(`Unsubscribed from ${channel}`);
|
|
287
|
-
} catch (error) {
|
|
288
|
-
logger3.error(`Error unsubscribing from ${channel}`, error);
|
|
289
|
-
channelSubscriptions.delete(channel);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
__name(unsubscribe, "unsubscribe");
|
|
293
|
-
function unsubscribeAll(manager) {
|
|
294
|
-
const { channelSubscriptions, centrifuge, logger: logger3 } = manager;
|
|
295
|
-
if (channelSubscriptions.size === 0) {
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
channelSubscriptions.forEach((sub, channel) => {
|
|
299
|
-
try {
|
|
300
|
-
sub.unsubscribe();
|
|
301
|
-
centrifuge.removeSubscription(sub);
|
|
302
|
-
} catch (error) {
|
|
303
|
-
logger3.error(`Error unsubscribing from ${channel}`, error);
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
channelSubscriptions.clear();
|
|
307
|
-
logger3.info("Unsubscribed from all channels");
|
|
308
|
-
}
|
|
309
|
-
__name(unsubscribeAll, "unsubscribeAll");
|
|
310
|
-
function getActiveSubscriptions(manager) {
|
|
311
|
-
return Array.from(manager.channelSubscriptions.keys());
|
|
312
|
-
}
|
|
313
|
-
__name(getActiveSubscriptions, "getActiveSubscriptions");
|
|
314
|
-
function getServerSideSubscriptions(manager) {
|
|
315
|
-
try {
|
|
316
|
-
const serverSubs = manager.centrifuge._serverSubs || {};
|
|
317
|
-
return Object.keys(serverSubs);
|
|
318
|
-
} catch {
|
|
319
|
-
return [];
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
__name(getServerSideSubscriptions, "getServerSideSubscriptions");
|
|
323
|
-
function getAllSubscriptions(manager) {
|
|
324
|
-
const clientSubs = getActiveSubscriptions(manager);
|
|
325
|
-
const serverSubs = getServerSideSubscriptions(manager);
|
|
326
|
-
return Array.from(/* @__PURE__ */ new Set([...clientSubs, ...serverSubs]));
|
|
327
|
-
}
|
|
328
|
-
__name(getAllSubscriptions, "getAllSubscriptions");
|
|
329
|
-
|
|
330
|
-
// src/core/errors/RPCError.ts
|
|
331
|
-
var _RPCError = class _RPCError extends Error {
|
|
332
|
-
constructor(code, message, options) {
|
|
333
|
-
super(message);
|
|
334
|
-
__publicField(this, "code");
|
|
335
|
-
__publicField(this, "serverCode");
|
|
336
|
-
__publicField(this, "method");
|
|
337
|
-
__publicField(this, "isRetryable");
|
|
338
|
-
__publicField(this, "suggestedRetryDelay");
|
|
339
|
-
__publicField(this, "userMessage");
|
|
340
|
-
this.name = "RPCError";
|
|
341
|
-
this.code = code;
|
|
342
|
-
this.serverCode = options?.serverCode;
|
|
343
|
-
this.method = options?.method;
|
|
344
|
-
this.isRetryable = this.determineRetryable();
|
|
345
|
-
this.suggestedRetryDelay = this.determineSuggestedDelay();
|
|
346
|
-
this.userMessage = this.determineUserMessage();
|
|
347
|
-
if (options?.cause) {
|
|
348
|
-
this.cause = options.cause;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* Determine if this error should trigger a retry.
|
|
353
|
-
* Transient errors (timeout, network) are retryable.
|
|
354
|
-
* Permanent errors (encoding, 4xx) are not.
|
|
355
|
-
*/
|
|
356
|
-
determineRetryable() {
|
|
357
|
-
switch (this.code) {
|
|
358
|
-
// Transient errors - retry
|
|
359
|
-
case "timeout":
|
|
360
|
-
case "websocket_error":
|
|
361
|
-
case "connection_failed":
|
|
362
|
-
case "network_error":
|
|
363
|
-
return true;
|
|
364
|
-
// Server errors - retry only 5xx
|
|
365
|
-
case "server_error":
|
|
366
|
-
return this.serverCode ? this.serverCode >= 500 : false;
|
|
367
|
-
// Permanent errors - don't retry
|
|
368
|
-
case "not_connected":
|
|
369
|
-
case "encoding_error":
|
|
370
|
-
case "decoding_error":
|
|
371
|
-
case "cancelled":
|
|
372
|
-
case "unknown":
|
|
373
|
-
return false;
|
|
374
|
-
default:
|
|
375
|
-
return false;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Suggested delay before retry based on error type.
|
|
380
|
-
*/
|
|
381
|
-
determineSuggestedDelay() {
|
|
382
|
-
switch (this.code) {
|
|
383
|
-
case "timeout":
|
|
384
|
-
return 1e3;
|
|
385
|
-
case "websocket_error":
|
|
386
|
-
return 2e3;
|
|
387
|
-
case "server_error":
|
|
388
|
-
return 3e3;
|
|
389
|
-
case "connection_failed":
|
|
390
|
-
return 2e3;
|
|
391
|
-
case "network_error":
|
|
392
|
-
return 1500;
|
|
393
|
-
default:
|
|
394
|
-
return 1e3;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* User-friendly message for UI display.
|
|
399
|
-
*/
|
|
400
|
-
determineUserMessage() {
|
|
401
|
-
switch (this.code) {
|
|
402
|
-
case "not_connected":
|
|
403
|
-
return "Not connected. Please check your internet connection.";
|
|
404
|
-
case "timeout":
|
|
405
|
-
return "Request timed out. Please try again.";
|
|
406
|
-
case "server_error":
|
|
407
|
-
return this.message || "Server error. Please try again later.";
|
|
408
|
-
case "websocket_error":
|
|
409
|
-
return "Connection error. Please try again.";
|
|
410
|
-
case "connection_failed":
|
|
411
|
-
return "Unable to connect. Please check your internet connection.";
|
|
412
|
-
case "encoding_error":
|
|
413
|
-
case "decoding_error":
|
|
414
|
-
return "Data error. Please try again or contact support.";
|
|
415
|
-
case "cancelled":
|
|
416
|
-
return "Request cancelled.";
|
|
417
|
-
case "network_error":
|
|
418
|
-
return "Network error. Please check your connection.";
|
|
419
|
-
default:
|
|
420
|
-
return "An unexpected error occurred. Please try again.";
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
/**
|
|
424
|
-
* Create RPCError from Centrifugo/unknown error.
|
|
425
|
-
*/
|
|
426
|
-
static fromError(error, method) {
|
|
427
|
-
if (error instanceof _RPCError) {
|
|
428
|
-
return error;
|
|
429
|
-
}
|
|
430
|
-
if (typeof error === "object" && error !== null) {
|
|
431
|
-
const err = error;
|
|
432
|
-
const code = err.code;
|
|
433
|
-
const message = err.message || err.error || "Unknown error";
|
|
434
|
-
if (message.includes("timeout") || message.includes("Timeout")) {
|
|
435
|
-
return new _RPCError("timeout", message, { method });
|
|
436
|
-
}
|
|
437
|
-
if (message.includes("disconnect") || message.includes("connection") || message.includes("not connected")) {
|
|
438
|
-
return new _RPCError("connection_failed", message, { method });
|
|
439
|
-
}
|
|
440
|
-
if (code !== void 0) {
|
|
441
|
-
if (code >= 500) {
|
|
442
|
-
return new _RPCError("server_error", message, {
|
|
443
|
-
serverCode: code,
|
|
444
|
-
method
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
if (code >= 400) {
|
|
448
|
-
return new _RPCError("server_error", message, {
|
|
449
|
-
serverCode: code,
|
|
450
|
-
method
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
return new _RPCError("unknown", message, { method, cause: error });
|
|
455
|
-
}
|
|
456
|
-
if (error instanceof Error) {
|
|
457
|
-
if (error.name === "AbortError") {
|
|
458
|
-
return new _RPCError("cancelled", "Request cancelled", { method });
|
|
459
|
-
}
|
|
460
|
-
return new _RPCError("unknown", error.message, { method, cause: error });
|
|
461
|
-
}
|
|
462
|
-
return new _RPCError("unknown", String(error), { method });
|
|
463
|
-
}
|
|
464
|
-
};
|
|
465
|
-
__name(_RPCError, "RPCError");
|
|
466
|
-
var RPCError = _RPCError;
|
|
467
|
-
|
|
468
|
-
// src/core/client/rpc.ts
|
|
469
|
-
var DEFAULT_RPC_TIMEOUT = 3e4;
|
|
470
|
-
function handleResponse(pendingRequests, data) {
|
|
471
|
-
const correlationId = data.correlation_id;
|
|
472
|
-
if (!correlationId) {
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
const pending = pendingRequests.get(correlationId);
|
|
476
|
-
if (!pending) {
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
pendingRequests.delete(correlationId);
|
|
480
|
-
if (data.error) {
|
|
481
|
-
pending.reject(new Error(data.error.message || "RPC error"));
|
|
482
|
-
} else {
|
|
483
|
-
pending.resolve(data.result);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
__name(handleResponse, "handleResponse");
|
|
487
|
-
async function call(manager, method, params, replyChannel) {
|
|
488
|
-
const { centrifuge, pendingRequests, timeout, logger: logger3 } = manager;
|
|
489
|
-
const correlationId = generateCorrelationId();
|
|
490
|
-
const message = {
|
|
491
|
-
method,
|
|
492
|
-
params,
|
|
493
|
-
correlation_id: correlationId,
|
|
494
|
-
reply_to: replyChannel
|
|
495
|
-
};
|
|
496
|
-
const promise = new Promise((resolve, reject) => {
|
|
497
|
-
const timeoutId = setTimeout(() => {
|
|
498
|
-
pendingRequests.delete(correlationId);
|
|
499
|
-
reject(new Error(`RPC timeout: ${method}`));
|
|
500
|
-
}, timeout);
|
|
501
|
-
pendingRequests.set(correlationId, {
|
|
502
|
-
resolve: /* @__PURE__ */ __name((result) => {
|
|
503
|
-
clearTimeout(timeoutId);
|
|
504
|
-
resolve(result);
|
|
505
|
-
}, "resolve"),
|
|
506
|
-
reject: /* @__PURE__ */ __name((error) => {
|
|
507
|
-
clearTimeout(timeoutId);
|
|
508
|
-
reject(error);
|
|
509
|
-
}, "reject")
|
|
510
|
-
});
|
|
511
|
-
});
|
|
512
|
-
await centrifuge.publish("rpc.requests", message);
|
|
513
|
-
return promise;
|
|
514
|
-
}
|
|
515
|
-
__name(call, "call");
|
|
516
|
-
async function rpc(manager, method, params, options = {}) {
|
|
517
|
-
const { centrifuge, logger: logger3, subscribe: subscribe2, userId } = manager;
|
|
518
|
-
const { timeout = 1e4, replyChannel = `user#${userId}` } = options;
|
|
519
|
-
const correlationId = generateCorrelationId();
|
|
520
|
-
logger3.info(`RPC request: ${method}`, { correlationId, params });
|
|
521
|
-
return new Promise((resolve, reject) => {
|
|
522
|
-
let timeoutId = null;
|
|
523
|
-
let unsubscribe2 = null;
|
|
524
|
-
const cleanup = /* @__PURE__ */ __name(() => {
|
|
525
|
-
if (timeoutId) {
|
|
526
|
-
clearTimeout(timeoutId);
|
|
527
|
-
timeoutId = null;
|
|
528
|
-
}
|
|
529
|
-
if (unsubscribe2) {
|
|
530
|
-
unsubscribe2();
|
|
531
|
-
unsubscribe2 = null;
|
|
532
|
-
}
|
|
533
|
-
}, "cleanup");
|
|
534
|
-
timeoutId = setTimeout(() => {
|
|
535
|
-
cleanup();
|
|
536
|
-
const error = new Error(`RPC timeout: ${method} (${timeout}ms)`);
|
|
537
|
-
logger3.error(`RPC timeout: ${method}`, { correlationId, timeout });
|
|
538
|
-
reject(error);
|
|
539
|
-
}, timeout);
|
|
540
|
-
try {
|
|
541
|
-
unsubscribe2 = subscribe2(replyChannel, (data) => {
|
|
542
|
-
try {
|
|
543
|
-
if (data.correlation_id === correlationId) {
|
|
544
|
-
cleanup();
|
|
545
|
-
if (data.error) {
|
|
546
|
-
logger3.error(`RPC error: ${method}`, {
|
|
547
|
-
correlationId,
|
|
548
|
-
error: data.error
|
|
549
|
-
});
|
|
550
|
-
reject(new Error(data.error.message || "RPC error"));
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
logger3.success(`RPC response: ${method}`, {
|
|
554
|
-
correlationId,
|
|
555
|
-
hasResult: !!data.result
|
|
556
|
-
});
|
|
557
|
-
resolve(data.result);
|
|
558
|
-
}
|
|
559
|
-
} catch (error) {
|
|
560
|
-
cleanup();
|
|
561
|
-
logger3.error(`Error processing RPC response: ${method}`, error);
|
|
562
|
-
reject(error);
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
const rpcChannel = `rpc#${method}`;
|
|
566
|
-
const sub = centrifuge.getSubscription(rpcChannel);
|
|
567
|
-
if (sub) {
|
|
568
|
-
sub.publish({
|
|
569
|
-
correlation_id: correlationId,
|
|
570
|
-
method,
|
|
571
|
-
params,
|
|
572
|
-
reply_to: replyChannel,
|
|
573
|
-
timestamp: Date.now()
|
|
574
|
-
}).catch((error) => {
|
|
575
|
-
cleanup();
|
|
576
|
-
logger3.error(`Failed to publish RPC request: ${method}`, error);
|
|
577
|
-
reject(error);
|
|
578
|
-
});
|
|
579
|
-
} else {
|
|
580
|
-
cleanup();
|
|
581
|
-
reject(
|
|
582
|
-
new Error(
|
|
583
|
-
`Cannot publish RPC request: no subscription to ${rpcChannel}. Backend should provide a publish endpoint or use server-side subscriptions.`
|
|
584
|
-
)
|
|
585
|
-
);
|
|
586
|
-
}
|
|
587
|
-
} catch (error) {
|
|
588
|
-
cleanup();
|
|
589
|
-
logger3.error(`Failed to setup RPC: ${method}`, error);
|
|
590
|
-
reject(error);
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
__name(rpc, "rpc");
|
|
595
|
-
function createTimeoutPromise(timeoutMs, method) {
|
|
596
|
-
return new Promise((_, reject) => {
|
|
597
|
-
setTimeout(() => {
|
|
598
|
-
reject(new RPCError("timeout", `RPC timeout after ${timeoutMs}ms: ${method}`, { method }));
|
|
599
|
-
}, timeoutMs);
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
__name(createTimeoutPromise, "createTimeoutPromise");
|
|
603
|
-
async function namedRPC(manager, method, data, options) {
|
|
604
|
-
const { centrifuge, logger: logger3 } = manager;
|
|
605
|
-
const timeoutMs = options?.timeout ?? DEFAULT_RPC_TIMEOUT;
|
|
606
|
-
logger3.info(`Native RPC: ${method}`, { data, timeout: timeoutMs });
|
|
607
|
-
try {
|
|
608
|
-
const result = await Promise.race([
|
|
609
|
-
centrifuge.rpc(method, data),
|
|
610
|
-
createTimeoutPromise(timeoutMs, method)
|
|
611
|
-
]);
|
|
612
|
-
logger3.success(`Native RPC success: ${method}`, {
|
|
613
|
-
hasData: !!result.data
|
|
614
|
-
});
|
|
615
|
-
return result.data;
|
|
616
|
-
} catch (error) {
|
|
617
|
-
const rpcError = RPCError.fromError(error, method);
|
|
618
|
-
logger3.error(`Native RPC failed: ${method}`, {
|
|
619
|
-
code: rpcError.code,
|
|
620
|
-
isRetryable: rpcError.isRetryable,
|
|
621
|
-
message: rpcError.message
|
|
622
|
-
});
|
|
623
|
-
dispatchCentrifugoError({
|
|
624
|
-
method,
|
|
625
|
-
error: rpcError.message,
|
|
626
|
-
code: rpcError.serverCode,
|
|
627
|
-
data
|
|
628
|
-
});
|
|
629
|
-
throw rpcError;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
__name(namedRPC, "namedRPC");
|
|
633
|
-
function namedRPCNoWait(manager, method, data, options) {
|
|
634
|
-
const { centrifuge, logger: logger3 } = manager;
|
|
635
|
-
const maxRetries = options?.maxRetries ?? 3;
|
|
636
|
-
const baseDelayMs = options?.baseDelayMs ?? 100;
|
|
637
|
-
const maxDelayMs = options?.maxDelayMs ?? 2e3;
|
|
638
|
-
const attemptSend = /* @__PURE__ */ __name((attempt) => {
|
|
639
|
-
centrifuge.rpc(method, data).catch((error) => {
|
|
640
|
-
const rpcError = RPCError.fromError(error, method);
|
|
641
|
-
if (attempt < maxRetries && rpcError.isRetryable) {
|
|
642
|
-
const baseDelay = rpcError.suggestedRetryDelay || baseDelayMs;
|
|
643
|
-
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelayMs);
|
|
644
|
-
const jitter = delay * 0.2 * (Math.random() * 2 - 1);
|
|
645
|
-
const finalDelay = Math.max(0, Math.round(delay + jitter));
|
|
646
|
-
logger3.warning(
|
|
647
|
-
`Fire-and-forget RPC failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${finalDelay}ms: ${method}`,
|
|
648
|
-
{ code: rpcError.code, isRetryable: rpcError.isRetryable }
|
|
649
|
-
);
|
|
650
|
-
setTimeout(() => attemptSend(attempt + 1), finalDelay);
|
|
651
|
-
} else {
|
|
652
|
-
logger3.error(
|
|
653
|
-
`Fire-and-forget RPC failed after ${attempt + 1} attempts: ${method}`,
|
|
654
|
-
{ code: rpcError.code, message: rpcError.message }
|
|
655
|
-
);
|
|
656
|
-
}
|
|
657
|
-
});
|
|
658
|
-
}, "attemptSend");
|
|
659
|
-
attemptSend(0);
|
|
660
|
-
}
|
|
661
|
-
__name(namedRPCNoWait, "namedRPCNoWait");
|
|
662
|
-
async function namedRPCWithRetry(manager, method, data, options) {
|
|
663
|
-
const { logger: logger3 } = manager;
|
|
664
|
-
const maxRetries = options?.maxRetries ?? 3;
|
|
665
|
-
const baseDelayMs = options?.baseDelayMs ?? 1e3;
|
|
666
|
-
const maxDelayMs = options?.maxDelayMs ?? 1e4;
|
|
667
|
-
let lastError = null;
|
|
668
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
669
|
-
try {
|
|
670
|
-
return await namedRPC(manager, method, data, {
|
|
671
|
-
timeout: options?.timeout
|
|
672
|
-
});
|
|
673
|
-
} catch (error) {
|
|
674
|
-
lastError = error instanceof RPCError ? error : RPCError.fromError(error, method);
|
|
675
|
-
if (!lastError.isRetryable) {
|
|
676
|
-
throw lastError;
|
|
677
|
-
}
|
|
678
|
-
if (attempt >= maxRetries) {
|
|
679
|
-
throw lastError;
|
|
680
|
-
}
|
|
681
|
-
const suggestedDelay = lastError.suggestedRetryDelay || baseDelayMs;
|
|
682
|
-
const exponentialDelay = suggestedDelay * Math.pow(2, attempt);
|
|
683
|
-
const cappedDelay = Math.min(exponentialDelay, maxDelayMs);
|
|
684
|
-
const jitter = cappedDelay * 0.2 * (Math.random() * 2 - 1);
|
|
685
|
-
const delayMs = Math.max(0, Math.round(cappedDelay + jitter));
|
|
686
|
-
logger3.warning(
|
|
687
|
-
`RPC retry (${attempt + 1}/${maxRetries}): ${method} in ${delayMs}ms`,
|
|
688
|
-
{ code: lastError.code, message: lastError.message }
|
|
689
|
-
);
|
|
690
|
-
if (options?.onRetry) {
|
|
691
|
-
options.onRetry(attempt + 1, lastError, delayMs);
|
|
692
|
-
}
|
|
693
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
throw lastError ?? new RPCError("unknown", "Retry failed", { method });
|
|
697
|
-
}
|
|
698
|
-
__name(namedRPCWithRetry, "namedRPCWithRetry");
|
|
699
|
-
|
|
700
|
-
// src/core/client/version.ts
|
|
701
|
-
async function checkApiVersion(checker, clientVersion) {
|
|
702
|
-
const { namedRPC: namedRPC2, logger: logger3 } = checker;
|
|
703
|
-
try {
|
|
704
|
-
const result = await namedRPC2(
|
|
705
|
-
"system.check_version",
|
|
706
|
-
{ client_version: clientVersion }
|
|
707
|
-
);
|
|
708
|
-
if (!result.compatible) {
|
|
709
|
-
logger3.warning(
|
|
710
|
-
`API version mismatch: client=${clientVersion}, server=${result.server_version}`
|
|
711
|
-
);
|
|
712
|
-
dispatchVersionMismatch({
|
|
713
|
-
clientVersion,
|
|
714
|
-
serverVersion: result.server_version,
|
|
715
|
-
message: result.message || "API version mismatch. Please refresh the page."
|
|
716
|
-
});
|
|
717
|
-
} else {
|
|
718
|
-
logger3.success(`API version check passed: ${clientVersion}`);
|
|
719
|
-
}
|
|
720
|
-
return {
|
|
721
|
-
compatible: result.compatible,
|
|
722
|
-
clientVersion,
|
|
723
|
-
serverVersion: result.server_version,
|
|
724
|
-
message: result.message
|
|
725
|
-
};
|
|
726
|
-
} catch (error) {
|
|
727
|
-
logger3.warning("API version check endpoint not available");
|
|
728
|
-
return {
|
|
729
|
-
compatible: true,
|
|
730
|
-
clientVersion,
|
|
731
|
-
serverVersion: "unknown",
|
|
732
|
-
message: "Version check endpoint not available"
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
__name(checkApiVersion, "checkApiVersion");
|
|
737
|
-
|
|
738
|
-
// src/core/client/CentrifugoRPCClient.ts
|
|
739
|
-
var _CentrifugoRPCClient = class _CentrifugoRPCClient {
|
|
740
|
-
constructor(urlOrOptions, token, userId, timeout = 3e4, logger3) {
|
|
741
|
-
__publicField(this, "state");
|
|
742
|
-
this.state = createConnectionState(urlOrOptions, token, userId, timeout, logger3);
|
|
743
|
-
setupConnectionHandlers(this.state);
|
|
744
|
-
}
|
|
745
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
746
|
-
// Connection API
|
|
747
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
748
|
-
async connect() {
|
|
749
|
-
return connect(this.state, (data) => this.handleResponse(data));
|
|
750
|
-
}
|
|
751
|
-
async disconnect() {
|
|
752
|
-
disconnect(this.state);
|
|
753
|
-
}
|
|
754
|
-
getCentrifuge() {
|
|
755
|
-
return this.state.centrifuge;
|
|
756
|
-
}
|
|
757
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
758
|
-
// Subscription API
|
|
759
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
760
|
-
subscribe(channel, callback) {
|
|
761
|
-
return subscribe(this.subscriptionManager, channel, callback);
|
|
762
|
-
}
|
|
763
|
-
unsubscribe(channel) {
|
|
764
|
-
unsubscribe(this.subscriptionManager, channel);
|
|
765
|
-
}
|
|
766
|
-
unsubscribeAll() {
|
|
767
|
-
unsubscribeAll(this.subscriptionManager);
|
|
768
|
-
}
|
|
769
|
-
getActiveSubscriptions() {
|
|
770
|
-
return getActiveSubscriptions(this.subscriptionManager);
|
|
771
|
-
}
|
|
772
|
-
getServerSideSubscriptions() {
|
|
773
|
-
return getServerSideSubscriptions(this.subscriptionManager);
|
|
774
|
-
}
|
|
775
|
-
getAllSubscriptions() {
|
|
776
|
-
return getAllSubscriptions(this.subscriptionManager);
|
|
777
|
-
}
|
|
778
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
779
|
-
// RPC Pattern API (Legacy - Correlation ID)
|
|
780
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
781
|
-
async call(method, params) {
|
|
782
|
-
return call(this.rpcManager, method, params, this.state.replyChannel);
|
|
783
|
-
}
|
|
784
|
-
async rpc(method, params, options = {}) {
|
|
785
|
-
return rpc(this.rpcManager, method, params, options);
|
|
786
|
-
}
|
|
787
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
788
|
-
// Native Centrifugo RPC (via RPC Proxy)
|
|
789
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
790
|
-
async namedRPC(method, data, options) {
|
|
791
|
-
return namedRPC(this.rpcManager, method, data, options);
|
|
792
|
-
}
|
|
793
|
-
namedRPCNoWait(method, data, options) {
|
|
794
|
-
namedRPCNoWait(this.rpcManager, method, data, options);
|
|
795
|
-
}
|
|
796
|
-
async namedRPCWithRetry(method, data, options) {
|
|
797
|
-
return namedRPCWithRetry(this.rpcManager, method, data, options);
|
|
798
|
-
}
|
|
799
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
800
|
-
// API Version Checking
|
|
801
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
802
|
-
async checkApiVersion(clientVersion) {
|
|
803
|
-
return checkApiVersion(
|
|
804
|
-
{
|
|
805
|
-
namedRPC: /* @__PURE__ */ __name((method, data) => this.namedRPC(method, data), "namedRPC"),
|
|
806
|
-
logger: this.state.logger
|
|
807
|
-
},
|
|
808
|
-
clientVersion
|
|
809
|
-
);
|
|
810
|
-
}
|
|
811
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
812
|
-
// Internal Helpers
|
|
813
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
814
|
-
handleResponse(data) {
|
|
815
|
-
handleResponse(this.state.pendingRequests, data);
|
|
816
|
-
}
|
|
817
|
-
get subscriptionManager() {
|
|
818
|
-
return {
|
|
819
|
-
channelSubscriptions: this.state.channelSubscriptions,
|
|
820
|
-
centrifuge: this.state.centrifuge,
|
|
821
|
-
logger: this.state.logger
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
get rpcManager() {
|
|
825
|
-
return {
|
|
826
|
-
centrifuge: this.state.centrifuge,
|
|
827
|
-
pendingRequests: this.state.pendingRequests,
|
|
828
|
-
channelSubscriptions: this.state.channelSubscriptions,
|
|
829
|
-
userId: this.state.userId,
|
|
830
|
-
timeout: this.state.timeout,
|
|
831
|
-
logger: this.state.logger,
|
|
832
|
-
subscribe: /* @__PURE__ */ __name((channel, callback) => this.subscribe(channel, callback), "subscribe")
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
836
|
-
// Getters for Testing/Debugging
|
|
837
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
838
|
-
/** @internal */
|
|
839
|
-
get logger() {
|
|
840
|
-
return this.state.logger;
|
|
841
|
-
}
|
|
842
|
-
/** @internal */
|
|
843
|
-
get userId() {
|
|
844
|
-
return this.state.userId;
|
|
845
|
-
}
|
|
846
|
-
/** @internal */
|
|
847
|
-
get replyChannel() {
|
|
848
|
-
return this.state.replyChannel;
|
|
849
|
-
}
|
|
850
|
-
};
|
|
851
|
-
__name(_CentrifugoRPCClient, "CentrifugoRPCClient");
|
|
852
|
-
var CentrifugoRPCClient = _CentrifugoRPCClient;
|
|
853
|
-
|
|
854
|
-
// src/core/logger/LogsStore.ts
|
|
855
|
-
var _LogsStore = class _LogsStore {
|
|
856
|
-
constructor(maxLogs = 500) {
|
|
857
|
-
__publicField(this, "logs", []);
|
|
858
|
-
__publicField(this, "listeners", /* @__PURE__ */ new Set());
|
|
859
|
-
__publicField(this, "maxLogs");
|
|
860
|
-
this.maxLogs = maxLogs;
|
|
861
|
-
}
|
|
862
|
-
/**
|
|
863
|
-
* Add log entry
|
|
864
|
-
*/
|
|
865
|
-
add(entry) {
|
|
866
|
-
const logEntry = {
|
|
867
|
-
...entry,
|
|
868
|
-
id: `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
869
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
870
|
-
};
|
|
871
|
-
this.logs.push(logEntry);
|
|
872
|
-
if (this.logs.length > this.maxLogs) {
|
|
873
|
-
this.logs = this.logs.slice(-this.maxLogs);
|
|
874
|
-
}
|
|
875
|
-
this.notify();
|
|
876
|
-
}
|
|
877
|
-
/**
|
|
878
|
-
* Get all logs
|
|
879
|
-
*/
|
|
880
|
-
getAll() {
|
|
881
|
-
return [...this.logs];
|
|
882
|
-
}
|
|
883
|
-
/**
|
|
884
|
-
* Get logs by level
|
|
885
|
-
*/
|
|
886
|
-
getByLevel(level) {
|
|
887
|
-
return this.logs.filter((log) => log.level === level);
|
|
888
|
-
}
|
|
889
|
-
/**
|
|
890
|
-
* Get logs by source
|
|
891
|
-
*/
|
|
892
|
-
getBySource(source) {
|
|
893
|
-
return this.logs.filter((log) => log.source === source);
|
|
894
|
-
}
|
|
895
|
-
/**
|
|
896
|
-
* Clear all logs
|
|
897
|
-
*/
|
|
898
|
-
clear() {
|
|
899
|
-
this.logs = [];
|
|
900
|
-
this.notify();
|
|
901
|
-
}
|
|
902
|
-
/**
|
|
903
|
-
* Subscribe to log changes
|
|
904
|
-
*/
|
|
905
|
-
subscribe(listener) {
|
|
906
|
-
this.listeners.add(listener);
|
|
907
|
-
return () => this.listeners.delete(listener);
|
|
908
|
-
}
|
|
909
|
-
/**
|
|
910
|
-
* Notify all listeners
|
|
911
|
-
*/
|
|
912
|
-
notify() {
|
|
913
|
-
const logs = this.getAll();
|
|
914
|
-
this.listeners.forEach((listener) => listener(logs));
|
|
915
|
-
}
|
|
916
|
-
/**
|
|
917
|
-
* Get logs count
|
|
918
|
-
*/
|
|
919
|
-
get count() {
|
|
920
|
-
return this.logs.length;
|
|
921
|
-
}
|
|
922
|
-
};
|
|
923
|
-
__name(_LogsStore, "LogsStore");
|
|
924
|
-
var LogsStore = _LogsStore;
|
|
925
|
-
var globalStore = null;
|
|
926
|
-
function getGlobalLogsStore() {
|
|
927
|
-
if (!globalStore) {
|
|
928
|
-
globalStore = new LogsStore();
|
|
929
|
-
}
|
|
930
|
-
return globalStore;
|
|
931
|
-
}
|
|
932
|
-
__name(getGlobalLogsStore, "getGlobalLogsStore");
|
|
933
|
-
|
|
934
|
-
// src/core/logger/createLogger.ts
|
|
935
|
-
function createLogger(configOrPrefix) {
|
|
936
|
-
const config = typeof configOrPrefix === "string" ? { source: "client", tag: configOrPrefix } : configOrPrefix;
|
|
937
|
-
const { source, isDevelopment: isDevelopment4 = true, tag = "Centrifugo" } = config;
|
|
938
|
-
const logsStore = getGlobalLogsStore();
|
|
939
|
-
const consola$1 = consola.createConsola({
|
|
940
|
-
level: isDevelopment4 ? 4 : 3,
|
|
941
|
-
formatOptions: {
|
|
942
|
-
colors: true,
|
|
943
|
-
date: false,
|
|
944
|
-
compact: !isDevelopment4
|
|
945
|
-
}
|
|
946
|
-
}).withTag(`[${tag}]`);
|
|
947
|
-
const log = /* @__PURE__ */ __name((level, message, data) => {
|
|
948
|
-
logsStore.add({
|
|
949
|
-
level,
|
|
950
|
-
source,
|
|
951
|
-
message,
|
|
952
|
-
data
|
|
953
|
-
});
|
|
954
|
-
if (!isDevelopment4) return;
|
|
955
|
-
switch (level) {
|
|
956
|
-
case "debug":
|
|
957
|
-
consola$1.debug(message, data || "");
|
|
958
|
-
break;
|
|
959
|
-
case "info":
|
|
960
|
-
consola$1.info(message, data || "");
|
|
961
|
-
break;
|
|
962
|
-
case "success":
|
|
963
|
-
consola$1.success(message, data || "");
|
|
964
|
-
break;
|
|
965
|
-
case "warning":
|
|
966
|
-
consola$1.warn(message, data || "");
|
|
967
|
-
break;
|
|
968
|
-
case "error":
|
|
969
|
-
consola$1.error(message, data || "");
|
|
970
|
-
break;
|
|
971
|
-
}
|
|
972
|
-
}, "log");
|
|
973
|
-
return {
|
|
974
|
-
debug: /* @__PURE__ */ __name((message, data) => log("debug", message, data), "debug"),
|
|
975
|
-
info: /* @__PURE__ */ __name((message, data) => log("info", message, data), "info"),
|
|
976
|
-
success: /* @__PURE__ */ __name((message, data) => log("success", message, data), "success"),
|
|
977
|
-
warning: /* @__PURE__ */ __name((message, data) => log("warning", message, data), "warning"),
|
|
978
|
-
error: /* @__PURE__ */ __name((message, error) => log("error", message, error), "error")
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
__name(createLogger, "createLogger");
|
|
982
|
-
var logger = getConsolaLogger("ConnectionStatus");
|
|
983
|
-
function ConnectionStatus({
|
|
984
|
-
variant = "badge",
|
|
985
|
-
showUptime = false,
|
|
986
|
-
showSubscriptions = false,
|
|
987
|
-
className = ""
|
|
988
|
-
}) {
|
|
989
|
-
const { isConnected, client } = useCentrifugo();
|
|
990
|
-
const [connectionTime, setConnectionTime] = react.useState(null);
|
|
991
|
-
const [uptime, setUptime] = react.useState("");
|
|
992
|
-
const [activeSubscriptions, setActiveSubscriptions] = react.useState(0);
|
|
993
|
-
react.useEffect(() => {
|
|
994
|
-
if (isConnected && !connectionTime) {
|
|
995
|
-
setConnectionTime(moment2__default.default.utc());
|
|
996
|
-
} else if (!isConnected) {
|
|
997
|
-
setConnectionTime(null);
|
|
998
|
-
}
|
|
999
|
-
}, [isConnected, connectionTime]);
|
|
1000
|
-
react.useEffect(() => {
|
|
1001
|
-
if (!isConnected || !connectionTime) {
|
|
1002
|
-
setUptime("");
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
const updateUptime = /* @__PURE__ */ __name(() => {
|
|
1006
|
-
const now = moment2__default.default.utc();
|
|
1007
|
-
const duration = moment2__default.default.duration(now.diff(connectionTime));
|
|
1008
|
-
const hours = Math.floor(duration.asHours());
|
|
1009
|
-
const minutes = duration.minutes();
|
|
1010
|
-
const seconds = duration.seconds();
|
|
1011
|
-
if (hours > 0) {
|
|
1012
|
-
setUptime(`${hours}h ${minutes}m`);
|
|
1013
|
-
} else if (minutes > 0) {
|
|
1014
|
-
setUptime(`${minutes}m ${seconds}s`);
|
|
1015
|
-
} else {
|
|
1016
|
-
setUptime(`${seconds}s`);
|
|
1017
|
-
}
|
|
1018
|
-
}, "updateUptime");
|
|
1019
|
-
updateUptime();
|
|
1020
|
-
const interval = setInterval(updateUptime, 1e3);
|
|
1021
|
-
return () => clearInterval(interval);
|
|
1022
|
-
}, [isConnected, connectionTime]);
|
|
1023
|
-
react.useEffect(() => {
|
|
1024
|
-
if (!client || !isConnected) {
|
|
1025
|
-
setActiveSubscriptions(0);
|
|
1026
|
-
return;
|
|
1027
|
-
}
|
|
1028
|
-
const updateCount = /* @__PURE__ */ __name(() => {
|
|
1029
|
-
try {
|
|
1030
|
-
const centrifuge = client.getCentrifuge();
|
|
1031
|
-
const subs = centrifuge.subscriptions();
|
|
1032
|
-
setActiveSubscriptions(Object.keys(subs).length);
|
|
1033
|
-
} catch (error) {
|
|
1034
|
-
logger.error("Failed to get active subscriptions", error);
|
|
1035
|
-
}
|
|
1036
|
-
}, "updateCount");
|
|
1037
|
-
updateCount();
|
|
1038
|
-
const interval = setInterval(updateCount, 2e3);
|
|
1039
|
-
return () => clearInterval(interval);
|
|
1040
|
-
}, [client, isConnected]);
|
|
1041
|
-
if (variant === "badge") {
|
|
1042
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1043
|
-
uiNextjs.Badge,
|
|
1044
|
-
{
|
|
1045
|
-
variant: isConnected ? "default" : "destructive",
|
|
1046
|
-
className: `flex items-center gap-1 ${isConnected ? "animate-pulse" : ""} ${className}`,
|
|
1047
|
-
children: [
|
|
1048
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `h-2 w-2 rounded-full ${isConnected ? "bg-green-500" : "bg-red-500"}` }),
|
|
1049
|
-
isConnected ? "Connected" : "Disconnected"
|
|
1050
|
-
]
|
|
1051
|
-
}
|
|
1052
|
-
);
|
|
1053
|
-
}
|
|
1054
|
-
if (variant === "inline") {
|
|
1055
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex items-center gap-2 ${className}`, children: [
|
|
1056
|
-
isConnected ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Wifi, { className: "h-4 w-4 text-green-600" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.WifiOff, { className: "h-4 w-4 text-red-600" }),
|
|
1057
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: isConnected ? "Connected" : "Disconnected" }),
|
|
1058
|
-
showUptime && uptime && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
1059
|
-
"(",
|
|
1060
|
-
uptime,
|
|
1061
|
-
")"
|
|
1062
|
-
] }),
|
|
1063
|
-
showSubscriptions && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground flex items-center gap-1", children: [
|
|
1064
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Radio, { className: "h-3 w-3" }),
|
|
1065
|
-
activeSubscriptions
|
|
1066
|
-
] })
|
|
1067
|
-
] });
|
|
1068
|
-
}
|
|
1069
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `space-y-3 ${className}`, children: [
|
|
1070
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1071
|
-
uiNextjs.Badge,
|
|
1072
|
-
{
|
|
1073
|
-
variant: isConnected ? "default" : "destructive",
|
|
1074
|
-
className: `flex items-center gap-1 ${isConnected ? "animate-pulse" : ""}`,
|
|
1075
|
-
children: [
|
|
1076
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `h-2 w-2 rounded-full ${isConnected ? "bg-green-500" : "bg-red-500"}` }),
|
|
1077
|
-
isConnected ? "Connected" : "Disconnected"
|
|
1078
|
-
]
|
|
1079
|
-
}
|
|
1080
|
-
) }),
|
|
1081
|
-
isConnected ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
1082
|
-
showUptime && uptime && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs", children: [
|
|
1083
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground flex items-center gap-1", children: [
|
|
1084
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3 w-3" }),
|
|
1085
|
-
"Uptime:"
|
|
1086
|
-
] }),
|
|
1087
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono font-medium", children: uptime })
|
|
1088
|
-
] }),
|
|
1089
|
-
showSubscriptions && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs", children: [
|
|
1090
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground flex items-center gap-1", children: [
|
|
1091
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Radio, { className: "h-3 w-3" }),
|
|
1092
|
-
"Subscriptions:"
|
|
1093
|
-
] }),
|
|
1094
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono font-medium", children: activeSubscriptions })
|
|
1095
|
-
] })
|
|
1096
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground p-2 rounded bg-red-50 dark:bg-red-950/20", children: "Real-time features unavailable" })
|
|
1097
|
-
] });
|
|
1098
|
-
}
|
|
1099
|
-
__name(ConnectionStatus, "ConnectionStatus");
|
|
1100
|
-
function ConnectionStatusCard({
|
|
1101
|
-
showUptime = true,
|
|
1102
|
-
showSubscriptions = true,
|
|
1103
|
-
className = ""
|
|
1104
|
-
}) {
|
|
1105
|
-
const { isConnected } = useCentrifugo();
|
|
1106
|
-
const statusColor = isConnected ? "border-green-500" : "border-red-500";
|
|
1107
|
-
const handleClick = /* @__PURE__ */ __name(() => {
|
|
1108
|
-
emitOpenMonitorDialog({ variant: "full" });
|
|
1109
|
-
}, "handleClick");
|
|
1110
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1111
|
-
uiNextjs.Card,
|
|
1112
|
-
{
|
|
1113
|
-
className: `${statusColor} ${className} cursor-pointer hover:shadow-lg transition-shadow`,
|
|
1114
|
-
style: { borderLeftWidth: "4px" },
|
|
1115
|
-
onClick: handleClick,
|
|
1116
|
-
children: [
|
|
1117
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
1118
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardTitle, { className: "text-sm font-medium", children: "WebSocket" }),
|
|
1119
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: isConnected ? "text-green-600" : "text-red-600", children: isConnected ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Wifi, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.WifiOff, { className: "h-4 w-4" }) })
|
|
1120
|
-
] }),
|
|
1121
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardContent, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1122
|
-
ConnectionStatus,
|
|
1123
|
-
{
|
|
1124
|
-
variant: "detailed",
|
|
1125
|
-
showUptime,
|
|
1126
|
-
showSubscriptions
|
|
1127
|
-
}
|
|
1128
|
-
) })
|
|
1129
|
-
]
|
|
1130
|
-
}
|
|
1131
|
-
);
|
|
1132
|
-
}
|
|
1133
|
-
__name(ConnectionStatusCard, "ConnectionStatusCard");
|
|
1134
|
-
function MessageFilters({
|
|
1135
|
-
filters,
|
|
1136
|
-
onFiltersChange,
|
|
1137
|
-
autoScroll,
|
|
1138
|
-
onAutoScrollChange
|
|
1139
|
-
}) {
|
|
1140
|
-
const hasActiveFilters = filters.channels && filters.channels.length > 0 || filters.types && filters.types.length > 0 || filters.levels && filters.levels.length > 0 || filters.searchQuery;
|
|
1141
|
-
const handleClearFilters = /* @__PURE__ */ __name(() => {
|
|
1142
|
-
onFiltersChange({});
|
|
1143
|
-
}, "handleClearFilters");
|
|
1144
|
-
const handleSearchChange = /* @__PURE__ */ __name((e) => {
|
|
1145
|
-
onFiltersChange({ ...filters, searchQuery: e.target.value });
|
|
1146
|
-
}, "handleSearchChange");
|
|
1147
|
-
const handleToggleLevel = /* @__PURE__ */ __name((level) => {
|
|
1148
|
-
const currentLevels = filters.levels || [];
|
|
1149
|
-
const newLevels = currentLevels.includes(level) ? currentLevels.filter((l) => l !== level) : [...currentLevels, level];
|
|
1150
|
-
onFiltersChange({
|
|
1151
|
-
...filters,
|
|
1152
|
-
levels: newLevels.length > 0 ? newLevels : void 0
|
|
1153
|
-
});
|
|
1154
|
-
}, "handleToggleLevel");
|
|
1155
|
-
const handleToggleType = /* @__PURE__ */ __name((type) => {
|
|
1156
|
-
const currentTypes = filters.types || [];
|
|
1157
|
-
const newTypes = currentTypes.includes(type) ? currentTypes.filter((t) => t !== type) : [...currentTypes, type];
|
|
1158
|
-
onFiltersChange({
|
|
1159
|
-
...filters,
|
|
1160
|
-
types: newTypes.length > 0 ? newTypes : void 0
|
|
1161
|
-
});
|
|
1162
|
-
}, "handleToggleType");
|
|
1163
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 p-3 border rounded-lg bg-muted/30", children: [
|
|
1164
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1165
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1166
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Filter, { className: "h-4 w-4 text-muted-foreground" }),
|
|
1167
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Filters" }),
|
|
1168
|
-
hasActiveFilters && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "secondary", className: "text-xs", children: "Active" })
|
|
1169
|
-
] }),
|
|
1170
|
-
hasActiveFilters && /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Button, { size: "sm", variant: "ghost", onClick: handleClearFilters, children: [
|
|
1171
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3 mr-1" }),
|
|
1172
|
-
"Clear"
|
|
1173
|
-
] })
|
|
1174
|
-
] }),
|
|
1175
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1176
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "h-4 w-4 text-muted-foreground" }),
|
|
1177
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1178
|
-
uiNextjs.Input,
|
|
1179
|
-
{
|
|
1180
|
-
type: "text",
|
|
1181
|
-
placeholder: "Search messages...",
|
|
1182
|
-
value: filters.searchQuery || "",
|
|
1183
|
-
onChange: handleSearchChange,
|
|
1184
|
-
className: "flex-1"
|
|
1185
|
-
}
|
|
1186
|
-
)
|
|
1187
|
-
] }),
|
|
1188
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
1189
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Level:" }),
|
|
1190
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: ["info", "success", "warning", "error"].map((level) => {
|
|
1191
|
-
const isActive = filters.levels?.includes(level);
|
|
1192
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1193
|
-
uiNextjs.Badge,
|
|
1194
|
-
{
|
|
1195
|
-
variant: isActive ? "default" : "outline",
|
|
1196
|
-
className: "cursor-pointer",
|
|
1197
|
-
onClick: () => handleToggleLevel(level),
|
|
1198
|
-
children: level
|
|
1199
|
-
},
|
|
1200
|
-
level
|
|
1201
|
-
);
|
|
1202
|
-
}) })
|
|
1203
|
-
] }),
|
|
1204
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
1205
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Type:" }),
|
|
1206
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: ["connection", "subscription", "publication", "unsubscription", "error", "system"].map((type) => {
|
|
1207
|
-
const isActive = filters.types?.includes(type);
|
|
1208
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1209
|
-
uiNextjs.Badge,
|
|
1210
|
-
{
|
|
1211
|
-
variant: isActive ? "default" : "outline",
|
|
1212
|
-
className: "cursor-pointer",
|
|
1213
|
-
onClick: () => handleToggleType(type),
|
|
1214
|
-
children: type
|
|
1215
|
-
},
|
|
1216
|
-
type
|
|
1217
|
-
);
|
|
1218
|
-
}) })
|
|
1219
|
-
] }),
|
|
1220
|
-
onAutoScrollChange && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-2 border-t", children: /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 text-sm cursor-pointer", children: [
|
|
1221
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1222
|
-
"input",
|
|
1223
|
-
{
|
|
1224
|
-
type: "checkbox",
|
|
1225
|
-
checked: autoScroll,
|
|
1226
|
-
onChange: (e) => onAutoScrollChange(e.target.checked),
|
|
1227
|
-
className: "h-4 w-4 rounded border-gray-300"
|
|
1228
|
-
}
|
|
1229
|
-
),
|
|
1230
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Auto-scroll to latest" })
|
|
1231
|
-
] }) })
|
|
1232
|
-
] });
|
|
1233
|
-
}
|
|
1234
|
-
__name(MessageFilters, "MessageFilters");
|
|
1235
|
-
function MessagesFeed({
|
|
1236
|
-
maxMessages = 100,
|
|
1237
|
-
showFilters = true,
|
|
1238
|
-
showControls = true,
|
|
1239
|
-
channels = [],
|
|
1240
|
-
autoScroll: initialAutoScroll = true,
|
|
1241
|
-
onMessageClick,
|
|
1242
|
-
className = ""
|
|
1243
|
-
}) {
|
|
1244
|
-
const { isConnected, client } = useCentrifugo();
|
|
1245
|
-
const [messages, setMessages] = react.useState([]);
|
|
1246
|
-
const [isPaused, setIsPaused] = react.useState(false);
|
|
1247
|
-
const [autoScroll, setAutoScroll] = react.useState(initialAutoScroll);
|
|
1248
|
-
const [filters, setFilters] = react.useState({
|
|
1249
|
-
channels: channels.length > 0 ? channels : void 0
|
|
1250
|
-
});
|
|
1251
|
-
const scrollRef = react.useRef(null);
|
|
1252
|
-
const addMessage = react.useCallback(
|
|
1253
|
-
(message) => {
|
|
1254
|
-
if (isPaused) return;
|
|
1255
|
-
setMessages((prev) => {
|
|
1256
|
-
const newMessages = [message, ...prev];
|
|
1257
|
-
return newMessages.slice(0, maxMessages);
|
|
1258
|
-
});
|
|
1259
|
-
},
|
|
1260
|
-
[isPaused, maxMessages]
|
|
1261
|
-
);
|
|
1262
|
-
react.useEffect(() => {
|
|
1263
|
-
if (!client) return;
|
|
1264
|
-
const centrifuge = client.getCentrifuge();
|
|
1265
|
-
const handleConnected = /* @__PURE__ */ __name(() => {
|
|
1266
|
-
const now = moment2__default.default.utc().valueOf();
|
|
1267
|
-
addMessage({
|
|
1268
|
-
id: `conn-${now}`,
|
|
1269
|
-
timestamp: now,
|
|
1270
|
-
type: "connection",
|
|
1271
|
-
level: "success",
|
|
1272
|
-
message: "Connected to Centrifugo"
|
|
1273
|
-
});
|
|
1274
|
-
}, "handleConnected");
|
|
1275
|
-
const handleDisconnected = /* @__PURE__ */ __name(() => {
|
|
1276
|
-
const now = moment2__default.default.utc().valueOf();
|
|
1277
|
-
addMessage({
|
|
1278
|
-
id: `disconn-${now}`,
|
|
1279
|
-
timestamp: now,
|
|
1280
|
-
type: "connection",
|
|
1281
|
-
level: "error",
|
|
1282
|
-
message: "Disconnected from Centrifugo"
|
|
1283
|
-
});
|
|
1284
|
-
}, "handleDisconnected");
|
|
1285
|
-
const handleError = /* @__PURE__ */ __name((ctx) => {
|
|
1286
|
-
const now = moment2__default.default.utc().valueOf();
|
|
1287
|
-
addMessage({
|
|
1288
|
-
id: `error-${now}`,
|
|
1289
|
-
timestamp: now,
|
|
1290
|
-
type: "error",
|
|
1291
|
-
level: "error",
|
|
1292
|
-
message: ctx.error?.message || "Connection error",
|
|
1293
|
-
data: ctx
|
|
1294
|
-
});
|
|
1295
|
-
}, "handleError");
|
|
1296
|
-
centrifuge.on("connected", handleConnected);
|
|
1297
|
-
centrifuge.on("disconnected", handleDisconnected);
|
|
1298
|
-
centrifuge.on("error", handleError);
|
|
1299
|
-
return () => {
|
|
1300
|
-
centrifuge.off("connected", handleConnected);
|
|
1301
|
-
centrifuge.off("disconnected", handleDisconnected);
|
|
1302
|
-
centrifuge.off("error", handleError);
|
|
1303
|
-
};
|
|
1304
|
-
}, [client, addMessage]);
|
|
1305
|
-
react.useEffect(() => {
|
|
1306
|
-
if (!client) return;
|
|
1307
|
-
const centrifuge = client.getCentrifuge();
|
|
1308
|
-
const handleSubscribed = /* @__PURE__ */ __name((ctx) => {
|
|
1309
|
-
const now = moment2__default.default.utc().valueOf();
|
|
1310
|
-
addMessage({
|
|
1311
|
-
id: `sub-${now}`,
|
|
1312
|
-
timestamp: now,
|
|
1313
|
-
type: "subscription",
|
|
1314
|
-
level: "info",
|
|
1315
|
-
channel: ctx.channel,
|
|
1316
|
-
message: `Subscribed to ${ctx.channel}`,
|
|
1317
|
-
data: ctx
|
|
1318
|
-
});
|
|
1319
|
-
}, "handleSubscribed");
|
|
1320
|
-
const handleUnsubscribed = /* @__PURE__ */ __name((ctx) => {
|
|
1321
|
-
const now = moment2__default.default.utc().valueOf();
|
|
1322
|
-
addMessage({
|
|
1323
|
-
id: `unsub-${now}`,
|
|
1324
|
-
timestamp: now,
|
|
1325
|
-
type: "unsubscription",
|
|
1326
|
-
level: "info",
|
|
1327
|
-
channel: ctx.channel,
|
|
1328
|
-
message: `Unsubscribed from ${ctx.channel}`,
|
|
1329
|
-
data: ctx
|
|
1330
|
-
});
|
|
1331
|
-
}, "handleUnsubscribed");
|
|
1332
|
-
const handlePublication = /* @__PURE__ */ __name((ctx) => {
|
|
1333
|
-
const now = moment2__default.default.utc().valueOf();
|
|
1334
|
-
addMessage({
|
|
1335
|
-
id: `pub-${now}-${Math.random()}`,
|
|
1336
|
-
timestamp: now,
|
|
1337
|
-
type: "publication",
|
|
1338
|
-
level: "success",
|
|
1339
|
-
channel: ctx.channel,
|
|
1340
|
-
message: `Message from ${ctx.channel}`,
|
|
1341
|
-
data: ctx.data
|
|
1342
|
-
});
|
|
1343
|
-
}, "handlePublication");
|
|
1344
|
-
centrifuge.on("subscribed", handleSubscribed);
|
|
1345
|
-
centrifuge.on("unsubscribed", handleUnsubscribed);
|
|
1346
|
-
centrifuge.on("publication", handlePublication);
|
|
1347
|
-
return () => {
|
|
1348
|
-
centrifuge.off("subscribed", handleSubscribed);
|
|
1349
|
-
centrifuge.off("unsubscribed", handleUnsubscribed);
|
|
1350
|
-
centrifuge.off("publication", handlePublication);
|
|
1351
|
-
};
|
|
1352
|
-
}, [client, addMessage]);
|
|
1353
|
-
react.useEffect(() => {
|
|
1354
|
-
if (autoScroll && scrollRef.current) {
|
|
1355
|
-
scrollRef.current.scrollTop = 0;
|
|
1356
|
-
}
|
|
1357
|
-
}, [messages, autoScroll]);
|
|
1358
|
-
const filteredMessages = react.useMemo(() => {
|
|
1359
|
-
return messages.filter((msg) => {
|
|
1360
|
-
if (filters.channels && filters.channels.length > 0) {
|
|
1361
|
-
if (!msg.channel || !filters.channels.includes(msg.channel)) {
|
|
1362
|
-
return false;
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
if (filters.types && filters.types.length > 0) {
|
|
1366
|
-
if (!filters.types.includes(msg.type)) {
|
|
1367
|
-
return false;
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
if (filters.levels && filters.levels.length > 0) {
|
|
1371
|
-
if (!filters.levels.includes(msg.level)) {
|
|
1372
|
-
return false;
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
if (filters.searchQuery) {
|
|
1376
|
-
const query = filters.searchQuery.toLowerCase();
|
|
1377
|
-
const searchableText = [
|
|
1378
|
-
msg.message,
|
|
1379
|
-
msg.channel,
|
|
1380
|
-
JSON.stringify(msg.data)
|
|
1381
|
-
].filter(Boolean).join(" ").toLowerCase();
|
|
1382
|
-
if (!searchableText.includes(query)) {
|
|
1383
|
-
return false;
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
return true;
|
|
1387
|
-
}).map((msg) => ({
|
|
1388
|
-
...msg,
|
|
1389
|
-
formattedTime: moment2__default.default.utc(msg.timestamp).format("HH:mm:ss"),
|
|
1390
|
-
formattedData: msg.data ? JSON.stringify(msg.data, null, 2) : null
|
|
1391
|
-
}));
|
|
1392
|
-
}, [messages, filters]);
|
|
1393
|
-
const handleClear = /* @__PURE__ */ __name(() => {
|
|
1394
|
-
setMessages([]);
|
|
1395
|
-
}, "handleClear");
|
|
1396
|
-
const handleDownload = /* @__PURE__ */ __name(() => {
|
|
1397
|
-
const json = JSON.stringify(filteredMessages, null, 2);
|
|
1398
|
-
const blob = new Blob([json], { type: "application/json" });
|
|
1399
|
-
const url = URL.createObjectURL(blob);
|
|
1400
|
-
const a = document.createElement("a");
|
|
1401
|
-
a.href = url;
|
|
1402
|
-
a.download = `centrifugo-messages-${moment2__default.default.utc().format("YYYY-MM-DD-HHmmss")}.json`;
|
|
1403
|
-
document.body.appendChild(a);
|
|
1404
|
-
a.click();
|
|
1405
|
-
document.body.removeChild(a);
|
|
1406
|
-
URL.revokeObjectURL(url);
|
|
1407
|
-
}, "handleDownload");
|
|
1408
|
-
const getLevelIcon = /* @__PURE__ */ __name((level) => {
|
|
1409
|
-
switch (level) {
|
|
1410
|
-
case "error":
|
|
1411
|
-
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-4 w-4 text-red-500" });
|
|
1412
|
-
case "warning":
|
|
1413
|
-
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-4 w-4 text-yellow-500" });
|
|
1414
|
-
case "success":
|
|
1415
|
-
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { className: "h-4 w-4 text-green-500" });
|
|
1416
|
-
case "info":
|
|
1417
|
-
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Info, { className: "h-4 w-4 text-blue-500" });
|
|
1418
|
-
default:
|
|
1419
|
-
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Circle, { className: "h-4 w-4 text-gray-500" });
|
|
1420
|
-
}
|
|
1421
|
-
}, "getLevelIcon");
|
|
1422
|
-
const getLevelVariant = /* @__PURE__ */ __name((level) => {
|
|
1423
|
-
switch (level) {
|
|
1424
|
-
case "error":
|
|
1425
|
-
return "destructive";
|
|
1426
|
-
case "warning":
|
|
1427
|
-
return "secondary";
|
|
1428
|
-
case "success":
|
|
1429
|
-
return "outline";
|
|
1430
|
-
default:
|
|
1431
|
-
return "default";
|
|
1432
|
-
}
|
|
1433
|
-
}, "getLevelVariant");
|
|
1434
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Card, { className, children: [
|
|
1435
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1436
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardTitle, { className: "flex items-center gap-2", children: [
|
|
1437
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-5 w-5" }),
|
|
1438
|
-
"Messages Feed",
|
|
1439
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "outline", children: filteredMessages.length })
|
|
1440
|
-
] }),
|
|
1441
|
-
showControls && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1442
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1443
|
-
uiNextjs.Button,
|
|
1444
|
-
{
|
|
1445
|
-
size: "sm",
|
|
1446
|
-
variant: "outline",
|
|
1447
|
-
onClick: () => setIsPaused(!isPaused),
|
|
1448
|
-
children: isPaused ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pause, { className: "h-4 w-4" })
|
|
1449
|
-
}
|
|
1450
|
-
),
|
|
1451
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1452
|
-
uiNextjs.Button,
|
|
1453
|
-
{
|
|
1454
|
-
size: "sm",
|
|
1455
|
-
variant: "outline",
|
|
1456
|
-
onClick: handleClear,
|
|
1457
|
-
disabled: messages.length === 0,
|
|
1458
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" })
|
|
1459
|
-
}
|
|
1460
|
-
),
|
|
1461
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1462
|
-
uiNextjs.Button,
|
|
1463
|
-
{
|
|
1464
|
-
size: "sm",
|
|
1465
|
-
variant: "outline",
|
|
1466
|
-
onClick: handleDownload,
|
|
1467
|
-
disabled: filteredMessages.length === 0,
|
|
1468
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-4 w-4" })
|
|
1469
|
-
}
|
|
1470
|
-
)
|
|
1471
|
-
] })
|
|
1472
|
-
] }) }),
|
|
1473
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardContent, { className: "space-y-4", children: [
|
|
1474
|
-
showFilters && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1475
|
-
MessageFilters,
|
|
1476
|
-
{
|
|
1477
|
-
filters,
|
|
1478
|
-
onFiltersChange: setFilters,
|
|
1479
|
-
autoScroll,
|
|
1480
|
-
onAutoScrollChange: setAutoScroll
|
|
1481
|
-
}
|
|
1482
|
-
),
|
|
1483
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.ScrollArea, { className: "h-[400px]", viewportRef: scrollRef, children: filteredMessages.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [
|
|
1484
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-12 w-12 text-muted-foreground mb-4" }),
|
|
1485
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: isPaused ? "Paused - Click play to resume" : "No messages yet" })
|
|
1486
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: filteredMessages.map((msg) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1487
|
-
"div",
|
|
1488
|
-
{
|
|
1489
|
-
className: "p-3 rounded border hover:bg-muted/50 transition-colors cursor-pointer",
|
|
1490
|
-
onClick: () => onMessageClick?.(msg),
|
|
1491
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
|
|
1492
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 mt-0.5", children: getLevelIcon(msg.level) }),
|
|
1493
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 space-y-1", children: [
|
|
1494
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
1495
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: getLevelVariant(msg.level), className: "text-xs", children: msg.type }),
|
|
1496
|
-
msg.channel && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "outline", className: "text-xs", children: msg.channel }),
|
|
1497
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: msg.formattedTime })
|
|
1498
|
-
] }),
|
|
1499
|
-
msg.message && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm break-words", children: msg.message }),
|
|
1500
|
-
msg.formattedData && /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "text-xs", children: [
|
|
1501
|
-
/* @__PURE__ */ jsxRuntime.jsx("summary", { className: "cursor-pointer text-muted-foreground hover:text-foreground", children: "View data" }),
|
|
1502
|
-
/* @__PURE__ */ jsxRuntime.jsx("pre", { className: "mt-2 p-2 bg-muted rounded overflow-x-auto", children: msg.formattedData })
|
|
1503
|
-
] })
|
|
1504
|
-
] })
|
|
1505
|
-
] })
|
|
1506
|
-
},
|
|
1507
|
-
msg.id
|
|
1508
|
-
)) }) })
|
|
1509
|
-
] })
|
|
1510
|
-
] });
|
|
1511
|
-
}
|
|
1512
|
-
__name(MessagesFeed, "MessagesFeed");
|
|
1513
|
-
var logger2 = getConsolaLogger("SubscriptionsList");
|
|
1514
|
-
function SubscriptionsList({
|
|
1515
|
-
showControls = true,
|
|
1516
|
-
onSubscriptionClick,
|
|
1517
|
-
className = ""
|
|
1518
|
-
}) {
|
|
1519
|
-
const { isConnected, client } = useCentrifugo();
|
|
1520
|
-
const [subscriptions, setSubscriptions] = react.useState([]);
|
|
1521
|
-
const updateSubscriptions = /* @__PURE__ */ __name(() => {
|
|
1522
|
-
if (!client || !isConnected) {
|
|
1523
|
-
setSubscriptions([]);
|
|
1524
|
-
return;
|
|
1525
|
-
}
|
|
1526
|
-
try {
|
|
1527
|
-
const centrifuge = client.getCentrifuge();
|
|
1528
|
-
const subs = centrifuge.subscriptions();
|
|
1529
|
-
const subscriptionsList = [];
|
|
1530
|
-
for (const [channel, sub] of Object.entries(subs)) {
|
|
1531
|
-
subscriptionsList.push({
|
|
1532
|
-
channel,
|
|
1533
|
-
state: sub.state
|
|
1534
|
-
});
|
|
1535
|
-
}
|
|
1536
|
-
setSubscriptions(subscriptionsList);
|
|
1537
|
-
} catch (error) {
|
|
1538
|
-
logger2.error("Failed to get subscriptions", error);
|
|
1539
|
-
setSubscriptions([]);
|
|
1540
|
-
}
|
|
1541
|
-
}, "updateSubscriptions");
|
|
1542
|
-
react.useEffect(() => {
|
|
1543
|
-
updateSubscriptions();
|
|
1544
|
-
if (!client) return;
|
|
1545
|
-
const centrifuge = client.getCentrifuge();
|
|
1546
|
-
const handleSubscribed = /* @__PURE__ */ __name(() => updateSubscriptions(), "handleSubscribed");
|
|
1547
|
-
const handleUnsubscribed = /* @__PURE__ */ __name(() => updateSubscriptions(), "handleUnsubscribed");
|
|
1548
|
-
centrifuge.on("subscribed", handleSubscribed);
|
|
1549
|
-
centrifuge.on("unsubscribed", handleUnsubscribed);
|
|
1550
|
-
const interval = setInterval(updateSubscriptions, 3e3);
|
|
1551
|
-
return () => {
|
|
1552
|
-
centrifuge.off("subscribed", handleSubscribed);
|
|
1553
|
-
centrifuge.off("unsubscribed", handleUnsubscribed);
|
|
1554
|
-
clearInterval(interval);
|
|
1555
|
-
};
|
|
1556
|
-
}, [client, isConnected]);
|
|
1557
|
-
const handleUnsubscribe = /* @__PURE__ */ __name(async (channel) => {
|
|
1558
|
-
if (!client) return;
|
|
1559
|
-
try {
|
|
1560
|
-
await client.unsubscribe(channel);
|
|
1561
|
-
updateSubscriptions();
|
|
1562
|
-
} catch (error) {
|
|
1563
|
-
logger2.error("Failed to unsubscribe", error);
|
|
1564
|
-
}
|
|
1565
|
-
}, "handleUnsubscribe");
|
|
1566
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Card, { className, children: [
|
|
1567
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1568
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardTitle, { className: "flex items-center gap-2", children: [
|
|
1569
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Radio, { className: "h-5 w-5" }),
|
|
1570
|
-
"Active Subscriptions",
|
|
1571
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "outline", children: subscriptions.length })
|
|
1572
|
-
] }),
|
|
1573
|
-
showControls && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Button, { size: "sm", variant: "outline", onClick: updateSubscriptions, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-4 w-4" }) })
|
|
1574
|
-
] }) }),
|
|
1575
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardContent, { children: !isConnected ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-8 text-sm text-muted-foreground", children: "Not connected to Centrifugo" }) : subscriptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-8 text-sm text-muted-foreground", children: "No active subscriptions" }) : /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.ScrollArea, { className: "h-[300px]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: subscriptions.map((sub) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1576
|
-
"div",
|
|
1577
|
-
{
|
|
1578
|
-
className: "flex items-center justify-between p-3 rounded border hover:bg-muted/50 transition-colors",
|
|
1579
|
-
children: [
|
|
1580
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1581
|
-
"div",
|
|
1582
|
-
{
|
|
1583
|
-
className: "flex-1 min-w-0 cursor-pointer",
|
|
1584
|
-
onClick: () => onSubscriptionClick?.(sub.channel),
|
|
1585
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1586
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "outline", className: "text-xs", children: sub.state }),
|
|
1587
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-mono truncate", children: sub.channel })
|
|
1588
|
-
] })
|
|
1589
|
-
}
|
|
1590
|
-
),
|
|
1591
|
-
showControls && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1592
|
-
uiNextjs.Button,
|
|
1593
|
-
{
|
|
1594
|
-
size: "sm",
|
|
1595
|
-
variant: "ghost",
|
|
1596
|
-
onClick: () => handleUnsubscribe(sub.channel),
|
|
1597
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4 text-destructive" })
|
|
1598
|
-
}
|
|
1599
|
-
)
|
|
1600
|
-
]
|
|
1601
|
-
},
|
|
1602
|
-
sub.channel
|
|
1603
|
-
)) }) }) })
|
|
1604
|
-
] });
|
|
1605
|
-
}
|
|
1606
|
-
__name(SubscriptionsList, "SubscriptionsList");
|
|
1607
|
-
function CentrifugoMonitor({
|
|
1608
|
-
variant = "full",
|
|
1609
|
-
showConnectionStatus = true,
|
|
1610
|
-
showMessagesFeed = true,
|
|
1611
|
-
showSubscriptions = true,
|
|
1612
|
-
showFilters = true,
|
|
1613
|
-
showControls = true,
|
|
1614
|
-
maxMessages = 100,
|
|
1615
|
-
channels = [],
|
|
1616
|
-
autoScroll = true,
|
|
1617
|
-
onMessageClick,
|
|
1618
|
-
onSubscriptionClick,
|
|
1619
|
-
className = ""
|
|
1620
|
-
}) {
|
|
1621
|
-
if (variant === "minimal") {
|
|
1622
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: /* @__PURE__ */ jsxRuntime.jsx(ConnectionStatus, { variant: "detailed", showUptime: true, showSubscriptions: true }) });
|
|
1623
|
-
}
|
|
1624
|
-
if (variant === "compact") {
|
|
1625
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `space-y-4 ${className}`, children: [
|
|
1626
|
-
showConnectionStatus && /* @__PURE__ */ jsxRuntime.jsx(ConnectionStatus, { variant: "detailed", showUptime: true, showSubscriptions: true }),
|
|
1627
|
-
showMessagesFeed && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1628
|
-
MessagesFeed,
|
|
1629
|
-
{
|
|
1630
|
-
maxMessages,
|
|
1631
|
-
showFilters: false,
|
|
1632
|
-
showControls,
|
|
1633
|
-
channels,
|
|
1634
|
-
autoScroll,
|
|
1635
|
-
onMessageClick
|
|
1636
|
-
}
|
|
1637
|
-
)
|
|
1638
|
-
] });
|
|
1639
|
-
}
|
|
1640
|
-
const tabsToShow = [
|
|
1641
|
-
showConnectionStatus && { value: "connection", label: "Connection" },
|
|
1642
|
-
showMessagesFeed && { value: "messages", label: "Messages" },
|
|
1643
|
-
showSubscriptions && { value: "subscriptions", label: "Subscriptions" }
|
|
1644
|
-
].filter(Boolean);
|
|
1645
|
-
if (tabsToShow.length === 0) {
|
|
1646
|
-
return null;
|
|
1647
|
-
}
|
|
1648
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Tabs, { defaultValue: tabsToShow[0].value, children: [
|
|
1649
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.TabsList, { className: `grid w-full grid-cols-${tabsToShow.length}`, children: tabsToShow.map((tab) => /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.TabsTrigger, { value: tab.value, children: tab.label }, tab.value)) }),
|
|
1650
|
-
showConnectionStatus && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.TabsContent, { value: "connection", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(ConnectionStatus, { variant: "detailed", showUptime: true, showSubscriptions: true }) }),
|
|
1651
|
-
showMessagesFeed && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.TabsContent, { value: "messages", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1652
|
-
MessagesFeed,
|
|
1653
|
-
{
|
|
1654
|
-
maxMessages,
|
|
1655
|
-
showFilters,
|
|
1656
|
-
showControls,
|
|
1657
|
-
channels,
|
|
1658
|
-
autoScroll,
|
|
1659
|
-
onMessageClick
|
|
1660
|
-
}
|
|
1661
|
-
) }),
|
|
1662
|
-
showSubscriptions && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.TabsContent, { value: "subscriptions", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1663
|
-
SubscriptionsList,
|
|
1664
|
-
{
|
|
1665
|
-
showControls,
|
|
1666
|
-
onSubscriptionClick
|
|
1667
|
-
}
|
|
1668
|
-
) })
|
|
1669
|
-
] }) });
|
|
1670
|
-
}
|
|
1671
|
-
__name(CentrifugoMonitor, "CentrifugoMonitor");
|
|
1672
|
-
function CentrifugoMonitorDialog() {
|
|
1673
|
-
const [open, setOpen] = react.useState(false);
|
|
1674
|
-
const [variant, setVariant] = react.useState("full");
|
|
1675
|
-
hooks.useEventListener(
|
|
1676
|
-
CENTRIFUGO_MONITOR_EVENTS.OPEN_MONITOR_DIALOG,
|
|
1677
|
-
(payload) => {
|
|
1678
|
-
if (payload?.variant) {
|
|
1679
|
-
setVariant(payload.variant);
|
|
1680
|
-
}
|
|
1681
|
-
setOpen(true);
|
|
1682
|
-
}
|
|
1683
|
-
);
|
|
1684
|
-
hooks.useEventListener(
|
|
1685
|
-
CENTRIFUGO_MONITOR_EVENTS.CLOSE_MONITOR_DIALOG,
|
|
1686
|
-
() => {
|
|
1687
|
-
setOpen(false);
|
|
1688
|
-
}
|
|
1689
|
-
);
|
|
1690
|
-
const handleClose = /* @__PURE__ */ __name(() => {
|
|
1691
|
-
setOpen(false);
|
|
1692
|
-
}, "handleClose");
|
|
1693
|
-
return /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Sheet, { open, onOpenChange: (isOpen) => !isOpen && handleClose(), children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1694
|
-
uiNextjs.SheetContent,
|
|
1695
|
-
{
|
|
1696
|
-
side: "right",
|
|
1697
|
-
className: "max-w-2xl w-[90vw] sm:w-[672px]",
|
|
1698
|
-
children: [
|
|
1699
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.SheetHeader, { children: [
|
|
1700
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.SheetTitle, { className: "flex items-center gap-2", children: [
|
|
1701
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-5 w-5" }),
|
|
1702
|
-
"Centrifugo Monitor"
|
|
1703
|
-
] }),
|
|
1704
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SheetDescription, { children: "Real-time WebSocket monitoring and debugging" })
|
|
1705
|
-
] }),
|
|
1706
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(CentrifugoMonitor, { variant }) })
|
|
1707
|
-
]
|
|
1708
|
-
}
|
|
1709
|
-
) });
|
|
1710
|
-
}
|
|
1711
|
-
__name(CentrifugoMonitorDialog, "CentrifugoMonitorDialog");
|
|
1712
|
-
|
|
1713
|
-
// src/config.ts
|
|
1714
|
-
var isDevelopment2 = true;
|
|
1715
|
-
var isProduction = !isDevelopment2;
|
|
1716
|
-
var isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
|
|
1717
|
-
var showDebugPanel = !isStaticBuild;
|
|
1718
|
-
var reconnectConfig = {
|
|
1719
|
-
// Initial delay before first reconnect attempt (ms)
|
|
1720
|
-
initialDelay: 2e3 ,
|
|
1721
|
-
// Maximum delay between reconnect attempts (ms)
|
|
1722
|
-
maxDelay: 3e4 ,
|
|
1723
|
-
// Multiplier for exponential backoff
|
|
1724
|
-
multiplier: 1.5,
|
|
1725
|
-
// Maximum number of reconnect attempts
|
|
1726
|
-
// Dev: 3 attempts then stop (server probably not running)
|
|
1727
|
-
// Prod: 10 attempts then stop (avoid infinite reconnection spam)
|
|
1728
|
-
maxAttempts: 3 ,
|
|
1729
|
-
// Jitter factor to randomize delays (0-1)
|
|
1730
|
-
jitter: 0.1
|
|
1731
|
-
};
|
|
1732
|
-
var centrifugoConfig = {
|
|
1733
|
-
// Show debug panel only in development and not in static builds
|
|
1734
|
-
showDebugPanel,
|
|
1735
|
-
// Reconnect settings
|
|
1736
|
-
reconnect: reconnectConfig
|
|
1737
|
-
};
|
|
1738
|
-
var tipShown = false;
|
|
1739
|
-
function useCodegenTip() {
|
|
1740
|
-
const mountedRef = react.useRef(false);
|
|
1741
|
-
react.useEffect(() => {
|
|
1742
|
-
if (tipShown) return;
|
|
1743
|
-
if (mountedRef.current) return;
|
|
1744
|
-
mountedRef.current = true;
|
|
1745
|
-
tipShown = true;
|
|
1746
|
-
consolaLogger.info(
|
|
1747
|
-
"\u{1F4A1} Generate type-safe clients from @websocket_rpc handlers:\n\npython manage.py generate_centrifugo_clients --typescript\n\nLearn more: https://djangocfg.com/docs/features/integrations/centrifugo/client-generation/"
|
|
1748
|
-
);
|
|
1749
|
-
}, []);
|
|
1750
|
-
}
|
|
1751
|
-
__name(useCodegenTip, "useCodegenTip");
|
|
1752
|
-
function getVisibilityState() {
|
|
1753
|
-
if (typeof document === "undefined") return true;
|
|
1754
|
-
return document.visibilityState === "visible";
|
|
1755
|
-
}
|
|
1756
|
-
__name(getVisibilityState, "getVisibilityState");
|
|
1757
|
-
function usePageVisibility(options = {}) {
|
|
1758
|
-
const { onVisible, onHidden, onChange } = options;
|
|
1759
|
-
const [state, setState] = react.useState(() => ({
|
|
1760
|
-
isVisible: getVisibilityState(),
|
|
1761
|
-
wasHidden: false,
|
|
1762
|
-
visibleSince: getVisibilityState() ? Date.now() : null,
|
|
1763
|
-
hiddenDuration: 0
|
|
1764
|
-
}));
|
|
1765
|
-
const onVisibleRef = react.useRef(onVisible);
|
|
1766
|
-
const onHiddenRef = react.useRef(onHidden);
|
|
1767
|
-
const onChangeRef = react.useRef(onChange);
|
|
1768
|
-
const hiddenAtRef = react.useRef(null);
|
|
1769
|
-
onVisibleRef.current = onVisible;
|
|
1770
|
-
onHiddenRef.current = onHidden;
|
|
1771
|
-
onChangeRef.current = onChange;
|
|
1772
|
-
const checkVisibility = react.useCallback(() => {
|
|
1773
|
-
return getVisibilityState();
|
|
1774
|
-
}, []);
|
|
1775
|
-
react.useEffect(() => {
|
|
1776
|
-
if (typeof document === "undefined") return;
|
|
1777
|
-
const handleVisibilityChange = /* @__PURE__ */ __name(() => {
|
|
1778
|
-
const isNowVisible = getVisibilityState();
|
|
1779
|
-
setState((prev) => {
|
|
1780
|
-
let hiddenDuration = prev.hiddenDuration;
|
|
1781
|
-
if (isNowVisible && hiddenAtRef.current !== null) {
|
|
1782
|
-
hiddenDuration = Date.now() - hiddenAtRef.current;
|
|
1783
|
-
hiddenAtRef.current = null;
|
|
1784
|
-
}
|
|
1785
|
-
if (!isNowVisible) {
|
|
1786
|
-
hiddenAtRef.current = Date.now();
|
|
1787
|
-
}
|
|
1788
|
-
return {
|
|
1789
|
-
isVisible: isNowVisible,
|
|
1790
|
-
wasHidden: prev.wasHidden || !isNowVisible,
|
|
1791
|
-
visibleSince: isNowVisible ? prev.visibleSince ?? Date.now() : null,
|
|
1792
|
-
hiddenDuration
|
|
1793
|
-
};
|
|
1794
|
-
});
|
|
1795
|
-
onChangeRef.current?.(isNowVisible);
|
|
1796
|
-
if (isNowVisible) {
|
|
1797
|
-
onVisibleRef.current?.();
|
|
1798
|
-
} else {
|
|
1799
|
-
onHiddenRef.current?.();
|
|
1800
|
-
}
|
|
1801
|
-
}, "handleVisibilityChange");
|
|
1802
|
-
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
1803
|
-
return () => {
|
|
1804
|
-
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
1805
|
-
};
|
|
1806
|
-
}, []);
|
|
1807
|
-
return {
|
|
1808
|
-
...state,
|
|
1809
|
-
checkVisibility
|
|
1810
|
-
};
|
|
1811
|
-
}
|
|
1812
|
-
__name(usePageVisibility, "usePageVisibility");
|
|
1813
|
-
var LogsContext = react.createContext(void 0);
|
|
1814
|
-
function LogsProvider({ children }) {
|
|
1815
|
-
const [logs, setLogs] = react.useState([]);
|
|
1816
|
-
const [filter, setFilterState] = react.useState({});
|
|
1817
|
-
const logsStore = getGlobalLogsStore();
|
|
1818
|
-
react.useEffect(() => {
|
|
1819
|
-
setLogs(logsStore.getAll());
|
|
1820
|
-
const unsubscribe2 = logsStore.subscribe((updatedLogs) => {
|
|
1821
|
-
setLogs(updatedLogs);
|
|
1822
|
-
});
|
|
1823
|
-
return unsubscribe2;
|
|
1824
|
-
}, [logsStore]);
|
|
1825
|
-
const filteredLogs = logs.filter((log) => {
|
|
1826
|
-
if (filter.level && log.level !== filter.level) return false;
|
|
1827
|
-
if (filter.source && log.source !== filter.source) return false;
|
|
1828
|
-
if (filter.search) {
|
|
1829
|
-
const searchLower = filter.search.toLowerCase();
|
|
1830
|
-
return log.message.toLowerCase().includes(searchLower);
|
|
1831
|
-
}
|
|
1832
|
-
return true;
|
|
1833
|
-
});
|
|
1834
|
-
const setFilter = react.useCallback((partialFilter) => {
|
|
1835
|
-
setFilterState((prev) => ({ ...prev, ...partialFilter }));
|
|
1836
|
-
}, []);
|
|
1837
|
-
const clearLogs = react.useCallback(() => {
|
|
1838
|
-
logsStore.clear();
|
|
1839
|
-
}, [logsStore]);
|
|
1840
|
-
const value = {
|
|
1841
|
-
logs,
|
|
1842
|
-
filteredLogs,
|
|
1843
|
-
filter,
|
|
1844
|
-
setFilter,
|
|
1845
|
-
clearLogs,
|
|
1846
|
-
count: logs.length
|
|
1847
|
-
};
|
|
1848
|
-
return /* @__PURE__ */ jsxRuntime.jsx(LogsContext.Provider, { value, children });
|
|
1849
|
-
}
|
|
1850
|
-
__name(LogsProvider, "LogsProvider");
|
|
1851
|
-
function useLogs() {
|
|
1852
|
-
const context = react.useContext(LogsContext);
|
|
1853
|
-
if (context === void 0) {
|
|
1854
|
-
throw new Error("useLogs must be used within a LogsProvider");
|
|
1855
|
-
}
|
|
1856
|
-
return context;
|
|
1857
|
-
}
|
|
1858
|
-
__name(useLogs, "useLogs");
|
|
1859
|
-
var CentrifugoContext = react.createContext(void 0);
|
|
1860
|
-
function CentrifugoProviderInner({
|
|
1861
|
-
children,
|
|
1862
|
-
enabled = false,
|
|
1863
|
-
url,
|
|
1864
|
-
autoConnect: autoConnectProp = true,
|
|
1865
|
-
onTokenRefresh
|
|
1866
|
-
}) {
|
|
1867
|
-
const { isAuthenticated, isLoading, user } = auth.useAuth();
|
|
1868
|
-
const [client, setClient] = react.useState(null);
|
|
1869
|
-
const [isConnected, setIsConnected] = react.useState(false);
|
|
1870
|
-
const [isConnecting, setIsConnecting] = react.useState(false);
|
|
1871
|
-
const [error, setError] = react.useState(null);
|
|
1872
|
-
const [connectionTime, setConnectionTime] = react.useState(null);
|
|
1873
|
-
const [uptime, setUptime] = react.useState(0);
|
|
1874
|
-
const [subscriptions, setSubscriptions] = react.useState([]);
|
|
1875
|
-
const [activeSubscriptions, setActiveSubscriptions] = react.useState([]);
|
|
1876
|
-
const logger3 = react.useMemo(() => getConsolaLogger("provider"), []);
|
|
1877
|
-
useCodegenTip();
|
|
1878
|
-
const reconnectTimeoutRef = react.useRef(null);
|
|
1879
|
-
const hasConnectedRef = react.useRef(false);
|
|
1880
|
-
const isConnectingRef = react.useRef(false);
|
|
1881
|
-
const isMountedRef = react.useRef(true);
|
|
1882
|
-
const reconnectAttemptRef = react.useRef(0);
|
|
1883
|
-
const reconnectStoppedRef = react.useRef(false);
|
|
1884
|
-
const devWarningShownRef = react.useRef(false);
|
|
1885
|
-
const connectRef = react.useRef(null);
|
|
1886
|
-
const disconnectRef = react.useRef(null);
|
|
1887
|
-
const wasConnectedBeforeHiddenRef = react.useRef(false);
|
|
1888
|
-
const clientInstanceRef = react.useRef(null);
|
|
1889
|
-
const centrifugoToken = user?.centrifugo;
|
|
1890
|
-
const hasCentrifugoToken = !!centrifugoToken?.token;
|
|
1891
|
-
const getReconnectDelay = react.useCallback((attempt) => {
|
|
1892
|
-
const { initialDelay, maxDelay, multiplier, jitter } = reconnectConfig;
|
|
1893
|
-
let delay = initialDelay * Math.pow(multiplier, attempt);
|
|
1894
|
-
delay = Math.min(delay, maxDelay);
|
|
1895
|
-
const jitterAmount = delay * jitter * (Math.random() * 2 - 1);
|
|
1896
|
-
delay = Math.round(delay + jitterAmount);
|
|
1897
|
-
return delay;
|
|
1898
|
-
}, []);
|
|
1899
|
-
const wsUrl = react.useMemo(() => {
|
|
1900
|
-
if (url) return url;
|
|
1901
|
-
if (centrifugoToken?.centrifugo_url) return centrifugoToken.centrifugo_url;
|
|
1902
|
-
return "";
|
|
1903
|
-
}, [url, centrifugoToken?.centrifugo_url]);
|
|
1904
|
-
const autoConnect = autoConnectProp && (isAuthenticated && !isLoading) && enabled && hasCentrifugoToken;
|
|
1905
|
-
react.useEffect(() => {
|
|
1906
|
-
if (!isLoading) {
|
|
1907
|
-
logger3.info(`Auto-connect: ${autoConnect ? "YES" : "NO"}`, {
|
|
1908
|
-
authenticated: isAuthenticated,
|
|
1909
|
-
loading: isLoading,
|
|
1910
|
-
enabled,
|
|
1911
|
-
hasToken: hasCentrifugoToken,
|
|
1912
|
-
url: wsUrl
|
|
1913
|
-
});
|
|
1914
|
-
}
|
|
1915
|
-
}, [autoConnect, isAuthenticated, isLoading, enabled, hasCentrifugoToken, logger3, wsUrl]);
|
|
1916
|
-
react.useEffect(() => {
|
|
1917
|
-
if (!isConnected || !connectionTime) {
|
|
1918
|
-
setUptime(0);
|
|
1919
|
-
return;
|
|
1920
|
-
}
|
|
1921
|
-
const updateUptime = /* @__PURE__ */ __name(() => {
|
|
1922
|
-
const now = /* @__PURE__ */ new Date();
|
|
1923
|
-
const diff = Math.floor((now.getTime() - connectionTime.getTime()) / 1e3);
|
|
1924
|
-
setUptime(diff);
|
|
1925
|
-
}, "updateUptime");
|
|
1926
|
-
updateUptime();
|
|
1927
|
-
const interval = setInterval(updateUptime, 1e3);
|
|
1928
|
-
return () => clearInterval(interval);
|
|
1929
|
-
}, [isConnected, connectionTime]);
|
|
1930
|
-
react.useEffect(() => {
|
|
1931
|
-
if (!client || !isConnected) {
|
|
1932
|
-
setSubscriptions([]);
|
|
1933
|
-
setActiveSubscriptions([]);
|
|
1934
|
-
return;
|
|
1935
|
-
}
|
|
1936
|
-
const updateSubs = /* @__PURE__ */ __name(() => {
|
|
1937
|
-
try {
|
|
1938
|
-
const subs = client.getAllSubscriptions?.() || [];
|
|
1939
|
-
setSubscriptions(subs);
|
|
1940
|
-
const activeSubs = subs.map((channel) => ({
|
|
1941
|
-
channel,
|
|
1942
|
-
type: "client",
|
|
1943
|
-
subscribedAt: Date.now()
|
|
1944
|
-
}));
|
|
1945
|
-
setActiveSubscriptions(activeSubs);
|
|
1946
|
-
} catch (error2) {
|
|
1947
|
-
logger3.error("Failed to get subscriptions", error2);
|
|
1948
|
-
}
|
|
1949
|
-
}, "updateSubs");
|
|
1950
|
-
updateSubs();
|
|
1951
|
-
const interval = setInterval(updateSubs, 2e3);
|
|
1952
|
-
return () => clearInterval(interval);
|
|
1953
|
-
}, [client, isConnected, logger3]);
|
|
1954
|
-
const connect2 = react.useCallback(async () => {
|
|
1955
|
-
if (reconnectStoppedRef.current) return;
|
|
1956
|
-
if (hasConnectedRef.current || isConnectingRef.current) return;
|
|
1957
|
-
if (isConnecting || isConnected) return;
|
|
1958
|
-
isConnectingRef.current = true;
|
|
1959
|
-
setIsConnecting(true);
|
|
1960
|
-
setError(null);
|
|
1961
|
-
try {
|
|
1962
|
-
if (clientInstanceRef.current) {
|
|
1963
|
-
logger3.info("Reconnecting to WebSocket server (reusing client)...");
|
|
1964
|
-
await clientInstanceRef.current.connect();
|
|
1965
|
-
if (!isMountedRef.current) {
|
|
1966
|
-
isConnectingRef.current = false;
|
|
1967
|
-
return;
|
|
1968
|
-
}
|
|
1969
|
-
hasConnectedRef.current = true;
|
|
1970
|
-
isConnectingRef.current = false;
|
|
1971
|
-
if (reconnectTimeoutRef.current) {
|
|
1972
|
-
clearTimeout(reconnectTimeoutRef.current);
|
|
1973
|
-
reconnectTimeoutRef.current = null;
|
|
1974
|
-
}
|
|
1975
|
-
setIsConnected(true);
|
|
1976
|
-
setConnectionTime(/* @__PURE__ */ new Date());
|
|
1977
|
-
setError(null);
|
|
1978
|
-
logger3.success("WebSocket reconnected");
|
|
1979
|
-
reconnectAttemptRef.current = 0;
|
|
1980
|
-
devWarningShownRef.current = false;
|
|
1981
|
-
reconnectStoppedRef.current = false;
|
|
1982
|
-
return;
|
|
1983
|
-
}
|
|
1984
|
-
logger3.info("Connecting to WebSocket server...");
|
|
1985
|
-
if (!centrifugoToken?.token) {
|
|
1986
|
-
throw new Error("No Centrifugo token available");
|
|
1987
|
-
}
|
|
1988
|
-
const token = centrifugoToken.token;
|
|
1989
|
-
let userId = user?.id?.toString() || "1";
|
|
1990
|
-
if (!user?.id) {
|
|
1991
|
-
try {
|
|
1992
|
-
const tokenPayload = JSON.parse(atob(token.split(".")[1]));
|
|
1993
|
-
userId = tokenPayload.user_id?.toString() || tokenPayload.sub?.toString() || "1";
|
|
1994
|
-
} catch (err) {
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
const rpcClient = new CentrifugoRPCClient({
|
|
1998
|
-
url: wsUrl,
|
|
1999
|
-
token,
|
|
2000
|
-
userId,
|
|
2001
|
-
timeout: 3e4,
|
|
2002
|
-
logger: logger3,
|
|
2003
|
-
getToken: onTokenRefresh
|
|
2004
|
-
});
|
|
2005
|
-
await rpcClient.connect();
|
|
2006
|
-
if (!isMountedRef.current) {
|
|
2007
|
-
rpcClient.disconnect();
|
|
2008
|
-
isConnectingRef.current = false;
|
|
2009
|
-
return;
|
|
2010
|
-
}
|
|
2011
|
-
hasConnectedRef.current = true;
|
|
2012
|
-
isConnectingRef.current = false;
|
|
2013
|
-
if (reconnectTimeoutRef.current) {
|
|
2014
|
-
clearTimeout(reconnectTimeoutRef.current);
|
|
2015
|
-
reconnectTimeoutRef.current = null;
|
|
2016
|
-
}
|
|
2017
|
-
clientInstanceRef.current = rpcClient;
|
|
2018
|
-
setClient(rpcClient);
|
|
2019
|
-
setIsConnected(true);
|
|
2020
|
-
setConnectionTime(/* @__PURE__ */ new Date());
|
|
2021
|
-
setError(null);
|
|
2022
|
-
logger3.success("WebSocket connected");
|
|
2023
|
-
reconnectAttemptRef.current = 0;
|
|
2024
|
-
devWarningShownRef.current = false;
|
|
2025
|
-
reconnectStoppedRef.current = false;
|
|
2026
|
-
} catch (err) {
|
|
2027
|
-
const error2 = err instanceof Error ? err : new Error("Connection failed");
|
|
2028
|
-
setError(error2);
|
|
2029
|
-
setClient(null);
|
|
2030
|
-
setIsConnected(false);
|
|
2031
|
-
setConnectionTime(null);
|
|
2032
|
-
hasConnectedRef.current = false;
|
|
2033
|
-
isConnectingRef.current = false;
|
|
2034
|
-
const isAuthError = error2.message.includes("token") || error2.message.includes("auth") || error2.message.includes("expired");
|
|
2035
|
-
if (isAuthError) {
|
|
2036
|
-
logger3.error("Authentication failed", error2);
|
|
2037
|
-
} else {
|
|
2038
|
-
const { maxAttempts } = reconnectConfig;
|
|
2039
|
-
const currentAttempt = reconnectAttemptRef.current;
|
|
2040
|
-
{
|
|
2041
|
-
if (!devWarningShownRef.current) {
|
|
2042
|
-
devWarningShownRef.current = true;
|
|
2043
|
-
logger3.warning(
|
|
2044
|
-
"\u{1F50C} Centrifugo server is not running. Start it with: docker compose -f docker-compose-local-services.yml up centrifugo"
|
|
2045
|
-
);
|
|
2046
|
-
}
|
|
2047
|
-
if (maxAttempts > 0 && currentAttempt >= maxAttempts) {
|
|
2048
|
-
reconnectStoppedRef.current = true;
|
|
2049
|
-
logger3.info(`Stopped reconnecting after ${maxAttempts} attempts (dev mode)`);
|
|
2050
|
-
return;
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
if (currentAttempt < maxAttempts) {
|
|
2054
|
-
const delay = getReconnectDelay(currentAttempt);
|
|
2055
|
-
reconnectAttemptRef.current = currentAttempt + 1;
|
|
2056
|
-
if (currentAttempt < 2) {
|
|
2057
|
-
logger3.info(`Reconnecting in ${Math.round(delay / 1e3)}s (attempt ${currentAttempt + 1}/${maxAttempts})...`);
|
|
2058
|
-
}
|
|
2059
|
-
reconnectTimeoutRef.current = setTimeout(() => {
|
|
2060
|
-
connectRef.current?.();
|
|
2061
|
-
}, delay);
|
|
2062
|
-
} else {
|
|
2063
|
-
reconnectStoppedRef.current = true;
|
|
2064
|
-
logger3.warning(`Stopped reconnecting after ${maxAttempts} attempts. WebSocket server may be unavailable.`);
|
|
2065
|
-
}
|
|
2066
|
-
}
|
|
2067
|
-
} finally {
|
|
2068
|
-
setIsConnecting(false);
|
|
2069
|
-
}
|
|
2070
|
-
}, [wsUrl, centrifugoToken, user, logger3, isConnecting, isConnected, getReconnectDelay, onTokenRefresh]);
|
|
2071
|
-
const disconnect2 = react.useCallback(() => {
|
|
2072
|
-
if (isConnectingRef.current) return;
|
|
2073
|
-
if (reconnectTimeoutRef.current) {
|
|
2074
|
-
clearTimeout(reconnectTimeoutRef.current);
|
|
2075
|
-
reconnectTimeoutRef.current = null;
|
|
2076
|
-
}
|
|
2077
|
-
if (client) {
|
|
2078
|
-
logger3.info("Disconnecting from WebSocket server...");
|
|
2079
|
-
client.disconnect();
|
|
2080
|
-
setClient(null);
|
|
2081
|
-
setIsConnected(false);
|
|
2082
|
-
setConnectionTime(null);
|
|
2083
|
-
setError(null);
|
|
2084
|
-
setSubscriptions([]);
|
|
2085
|
-
}
|
|
2086
|
-
hasConnectedRef.current = false;
|
|
2087
|
-
isConnectingRef.current = false;
|
|
2088
|
-
reconnectAttemptRef.current = 0;
|
|
2089
|
-
devWarningShownRef.current = false;
|
|
2090
|
-
reconnectStoppedRef.current = false;
|
|
2091
|
-
}, [client, logger3]);
|
|
2092
|
-
const reconnect = react.useCallback(async () => {
|
|
2093
|
-
disconnect2();
|
|
2094
|
-
await connect2();
|
|
2095
|
-
}, [connect2, disconnect2]);
|
|
2096
|
-
const unsubscribe2 = react.useCallback((channel) => {
|
|
2097
|
-
if (!client) {
|
|
2098
|
-
logger3.warning("Cannot unsubscribe: client not connected");
|
|
2099
|
-
return;
|
|
2100
|
-
}
|
|
2101
|
-
try {
|
|
2102
|
-
client.unsubscribe?.(channel);
|
|
2103
|
-
logger3.info(`Unsubscribed from channel: ${channel}`);
|
|
2104
|
-
setSubscriptions((prev) => prev.filter((ch) => ch !== channel));
|
|
2105
|
-
setActiveSubscriptions((prev) => prev.filter((sub) => sub.channel !== channel));
|
|
2106
|
-
} catch (error2) {
|
|
2107
|
-
logger3.error(`Failed to unsubscribe from ${channel}`, error2);
|
|
2108
|
-
}
|
|
2109
|
-
}, [client, logger3]);
|
|
2110
|
-
connectRef.current = connect2;
|
|
2111
|
-
disconnectRef.current = disconnect2;
|
|
2112
|
-
react.useEffect(() => {
|
|
2113
|
-
isMountedRef.current = true;
|
|
2114
|
-
if (autoConnect && !hasConnectedRef.current && !reconnectStoppedRef.current) {
|
|
2115
|
-
connectRef.current?.();
|
|
2116
|
-
}
|
|
2117
|
-
return () => {
|
|
2118
|
-
if (isConnectingRef.current && !hasConnectedRef.current) {
|
|
2119
|
-
return;
|
|
2120
|
-
}
|
|
2121
|
-
if (!hasConnectedRef.current) {
|
|
2122
|
-
return;
|
|
2123
|
-
}
|
|
2124
|
-
isMountedRef.current = false;
|
|
2125
|
-
disconnectRef.current?.();
|
|
2126
|
-
};
|
|
2127
|
-
}, [autoConnect]);
|
|
2128
|
-
usePageVisibility({
|
|
2129
|
-
onHidden: /* @__PURE__ */ __name(() => {
|
|
2130
|
-
wasConnectedBeforeHiddenRef.current = isConnected;
|
|
2131
|
-
if (reconnectTimeoutRef.current) {
|
|
2132
|
-
clearTimeout(reconnectTimeoutRef.current);
|
|
2133
|
-
reconnectTimeoutRef.current = null;
|
|
2134
|
-
logger3.debug("Paused reconnect attempts (tab hidden)");
|
|
2135
|
-
}
|
|
2136
|
-
}, "onHidden"),
|
|
2137
|
-
onVisible: /* @__PURE__ */ __name(() => {
|
|
2138
|
-
const shouldReconnect = autoConnect && !isConnected && !isConnectingRef.current && !reconnectStoppedRef.current && (wasConnectedBeforeHiddenRef.current || !hasConnectedRef.current);
|
|
2139
|
-
if (shouldReconnect) {
|
|
2140
|
-
reconnectAttemptRef.current = 0;
|
|
2141
|
-
devWarningShownRef.current = false;
|
|
2142
|
-
logger3.info("Tab visible - attempting reconnect...");
|
|
2143
|
-
connectRef.current?.();
|
|
2144
|
-
} else if (isConnected) {
|
|
2145
|
-
logger3.debug("Tab visible - connection still active");
|
|
2146
|
-
}
|
|
2147
|
-
}, "onVisible")
|
|
2148
|
-
});
|
|
2149
|
-
const connectionState = isConnected ? "connected" : isConnecting ? "connecting" : error ? "error" : "disconnected";
|
|
2150
|
-
const value = {
|
|
2151
|
-
client,
|
|
2152
|
-
isConnected,
|
|
2153
|
-
isConnecting,
|
|
2154
|
-
error,
|
|
2155
|
-
connectionState,
|
|
2156
|
-
uptime,
|
|
2157
|
-
subscriptions,
|
|
2158
|
-
activeSubscriptions,
|
|
2159
|
-
connect: connect2,
|
|
2160
|
-
disconnect: disconnect2,
|
|
2161
|
-
reconnect,
|
|
2162
|
-
unsubscribe: unsubscribe2,
|
|
2163
|
-
enabled
|
|
2164
|
-
};
|
|
2165
|
-
return /* @__PURE__ */ jsxRuntime.jsx(CentrifugoContext.Provider, { value, children });
|
|
2166
|
-
}
|
|
2167
|
-
__name(CentrifugoProviderInner, "CentrifugoProviderInner");
|
|
2168
|
-
function CentrifugoProvider(props) {
|
|
2169
|
-
return /* @__PURE__ */ jsxRuntime.jsx(LogsProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(CentrifugoProviderInner, { ...props, children: [
|
|
2170
|
-
props.children,
|
|
2171
|
-
/* @__PURE__ */ jsxRuntime.jsx(CentrifugoMonitorDialog, {})
|
|
2172
|
-
] }) });
|
|
2173
|
-
}
|
|
2174
|
-
__name(CentrifugoProvider, "CentrifugoProvider");
|
|
2175
|
-
function useCentrifugo() {
|
|
2176
|
-
const context = react.useContext(CentrifugoContext);
|
|
2177
|
-
if (context === void 0) {
|
|
2178
|
-
throw new Error("useCentrifugo must be used within a CentrifugoProvider");
|
|
2179
|
-
}
|
|
2180
|
-
return context;
|
|
2181
|
-
}
|
|
2182
|
-
__name(useCentrifugo, "useCentrifugo");
|
|
2183
|
-
|
|
2184
|
-
// src/core/utils/channelValidator.ts
|
|
2185
|
-
function validateChannelName(channel) {
|
|
2186
|
-
const hashCount = (channel.match(/#/g) || []).length;
|
|
2187
|
-
if (hashCount >= 2) {
|
|
2188
|
-
const parts = channel.split("#");
|
|
2189
|
-
const [namespace, possibleUserId, ...rest] = parts;
|
|
2190
|
-
const isNumericUserId = /^\d+$/.test(possibleUserId);
|
|
2191
|
-
if (!isNumericUserId && possibleUserId) {
|
|
2192
|
-
const suggestion = channel.replace(/#/g, ":");
|
|
2193
|
-
return {
|
|
2194
|
-
valid: false,
|
|
2195
|
-
warning: `Channel "${channel}" uses '#' separator which Centrifugo interprets as user-limited channel boundary. The part "${possibleUserId}" will be treated as user_id, which may cause permission errors if not in JWT token.`,
|
|
2196
|
-
suggestion: `Use ':' for namespace separation: "${suggestion}"`
|
|
2197
|
-
};
|
|
2198
|
-
}
|
|
2199
|
-
if (isNumericUserId) {
|
|
2200
|
-
return {
|
|
2201
|
-
valid: true,
|
|
2202
|
-
warning: `Channel "${channel}" appears to be a user-limited channel (user_id: ${possibleUserId}). Make sure your JWT token's "sub" field matches "${possibleUserId}".`
|
|
2203
|
-
};
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
if (hashCount === 1) {
|
|
2207
|
-
const [namespace, userId] = channel.split("#");
|
|
2208
|
-
if (userId && !/^\d+$/.test(userId) && userId !== "*") {
|
|
2209
|
-
const suggestion = channel.replace("#", ":");
|
|
2210
|
-
return {
|
|
2211
|
-
valid: false,
|
|
2212
|
-
warning: `Channel "${channel}" uses '#' but "${userId}" doesn't look like a user_id. This might cause permission issues.`,
|
|
2213
|
-
suggestion: `Consider using ':' instead: "${suggestion}"`
|
|
2214
|
-
};
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
return { valid: true };
|
|
2218
|
-
}
|
|
2219
|
-
__name(validateChannelName, "validateChannelName");
|
|
2220
|
-
function logChannelWarnings(channel, logger3) {
|
|
2221
|
-
const result = validateChannelName(channel);
|
|
2222
|
-
if (!result.valid && result.warning) {
|
|
2223
|
-
const message = `[Centrifugo Channel Warning]
|
|
2224
|
-
${result.warning}${result.suggestion ? `
|
|
2225
|
-
\u{1F4A1} Suggestion: ${result.suggestion}` : ""}`;
|
|
2226
|
-
if (logger3?.warning) {
|
|
2227
|
-
logger3.warning(message);
|
|
2228
|
-
} else {
|
|
2229
|
-
console.warn(message);
|
|
2230
|
-
}
|
|
2231
|
-
} else if (result.warning) {
|
|
2232
|
-
if (logger3?.warning) {
|
|
2233
|
-
logger3.warning(`[Centrifugo] ${result.warning}`);
|
|
2234
|
-
}
|
|
2235
|
-
}
|
|
2236
|
-
}
|
|
2237
|
-
__name(logChannelWarnings, "logChannelWarnings");
|
|
2238
|
-
|
|
2239
|
-
// src/hooks/useSubscription.ts
|
|
2240
|
-
function useSubscription(options) {
|
|
2241
|
-
const { client, isConnected } = useCentrifugo();
|
|
2242
|
-
const { channel, enabled = true, onPublication, onError } = options;
|
|
2243
|
-
const [data, setData] = react.useState(null);
|
|
2244
|
-
const [error, setError] = react.useState(null);
|
|
2245
|
-
const [isSubscribed, setIsSubscribed] = react.useState(false);
|
|
2246
|
-
const unsubscribeRef = react.useRef(null);
|
|
2247
|
-
const logger3 = react.useRef(getConsolaLogger("useSubscription")).current;
|
|
2248
|
-
const onPublicationRef = react.useRef(onPublication);
|
|
2249
|
-
const onErrorRef = react.useRef(onError);
|
|
2250
|
-
react.useEffect(() => {
|
|
2251
|
-
onPublicationRef.current = onPublication;
|
|
2252
|
-
onErrorRef.current = onError;
|
|
2253
|
-
}, [onPublication, onError]);
|
|
2254
|
-
const unsubscribe2 = react.useCallback(() => {
|
|
2255
|
-
if (unsubscribeRef.current) {
|
|
2256
|
-
try {
|
|
2257
|
-
unsubscribeRef.current();
|
|
2258
|
-
unsubscribeRef.current = null;
|
|
2259
|
-
setIsSubscribed(false);
|
|
2260
|
-
logger3.info(`Unsubscribed from channel: ${channel}`);
|
|
2261
|
-
} catch (err) {
|
|
2262
|
-
logger3.error(`Error during unsubscribe from ${channel}`, err);
|
|
2263
|
-
}
|
|
2264
|
-
}
|
|
2265
|
-
}, [channel, logger3]);
|
|
2266
|
-
react.useEffect(() => {
|
|
2267
|
-
if (!client || !isConnected || !enabled) {
|
|
2268
|
-
if (!isConnected && isSubscribed) {
|
|
2269
|
-
setIsSubscribed(false);
|
|
2270
|
-
}
|
|
2271
|
-
return;
|
|
2272
|
-
}
|
|
2273
|
-
logChannelWarnings(channel, logger3);
|
|
2274
|
-
logger3.info(`Subscribing to channel: ${channel}`);
|
|
2275
|
-
try {
|
|
2276
|
-
const unsub = client.subscribe(channel, (receivedData) => {
|
|
2277
|
-
try {
|
|
2278
|
-
setData(receivedData);
|
|
2279
|
-
setError(null);
|
|
2280
|
-
onPublicationRef.current?.(receivedData);
|
|
2281
|
-
} catch (callbackError) {
|
|
2282
|
-
logger3.error(`Error in onPublication callback for ${channel}`, callbackError);
|
|
2283
|
-
}
|
|
2284
|
-
});
|
|
2285
|
-
unsubscribeRef.current = unsub;
|
|
2286
|
-
setIsSubscribed(true);
|
|
2287
|
-
logger3.success(`Subscribed to channel: ${channel}`);
|
|
2288
|
-
} catch (err) {
|
|
2289
|
-
const subscriptionError = err instanceof Error ? err : new Error("Subscription failed");
|
|
2290
|
-
setError(subscriptionError);
|
|
2291
|
-
try {
|
|
2292
|
-
onErrorRef.current?.(subscriptionError);
|
|
2293
|
-
} catch (callbackError) {
|
|
2294
|
-
logger3.error(`Error in onError callback for ${channel}`, callbackError);
|
|
2295
|
-
}
|
|
2296
|
-
logger3.error(`Subscription failed: ${channel}`, subscriptionError);
|
|
2297
|
-
}
|
|
2298
|
-
return () => {
|
|
2299
|
-
unsubscribe2();
|
|
2300
|
-
};
|
|
2301
|
-
}, [client, isConnected, enabled, channel, unsubscribe2, logger3, isSubscribed]);
|
|
2302
|
-
return {
|
|
2303
|
-
data,
|
|
2304
|
-
error,
|
|
2305
|
-
isSubscribed,
|
|
2306
|
-
unsubscribe: unsubscribe2
|
|
2307
|
-
};
|
|
2308
|
-
}
|
|
2309
|
-
__name(useSubscription, "useSubscription");
|
|
2310
|
-
function useRPC(defaultOptions = {}) {
|
|
2311
|
-
const { client, isConnected } = useCentrifugo();
|
|
2312
|
-
const [isLoading, setIsLoading] = react.useState(false);
|
|
2313
|
-
const [error, setError] = react.useState(null);
|
|
2314
|
-
const logger3 = react.useRef(getConsolaLogger("useRPC")).current;
|
|
2315
|
-
const abortControllerRef = react.useRef(null);
|
|
2316
|
-
const reset = react.useCallback(() => {
|
|
2317
|
-
setIsLoading(false);
|
|
2318
|
-
setError(null);
|
|
2319
|
-
if (abortControllerRef.current) {
|
|
2320
|
-
abortControllerRef.current.abort();
|
|
2321
|
-
abortControllerRef.current = null;
|
|
2322
|
-
}
|
|
2323
|
-
}, []);
|
|
2324
|
-
const call2 = react.useCallback(
|
|
2325
|
-
async (method, params, options = {}) => {
|
|
2326
|
-
if (!client) {
|
|
2327
|
-
const error2 = new Error("Centrifugo client not available");
|
|
2328
|
-
setError(error2);
|
|
2329
|
-
throw error2;
|
|
2330
|
-
}
|
|
2331
|
-
if (!isConnected) {
|
|
2332
|
-
const error2 = new Error("Not connected to Centrifugo");
|
|
2333
|
-
setError(error2);
|
|
2334
|
-
throw error2;
|
|
2335
|
-
}
|
|
2336
|
-
setError(null);
|
|
2337
|
-
setIsLoading(true);
|
|
2338
|
-
const abortController = new AbortController();
|
|
2339
|
-
abortControllerRef.current = abortController;
|
|
2340
|
-
try {
|
|
2341
|
-
const mergedOptions = {
|
|
2342
|
-
...defaultOptions,
|
|
2343
|
-
...options
|
|
2344
|
-
};
|
|
2345
|
-
logger3.info(`RPC call: ${method}`, { params });
|
|
2346
|
-
const result = await client.rpc(
|
|
2347
|
-
method,
|
|
2348
|
-
params,
|
|
2349
|
-
{
|
|
2350
|
-
timeout: mergedOptions.timeout,
|
|
2351
|
-
replyChannel: mergedOptions.replyChannel
|
|
2352
|
-
}
|
|
2353
|
-
);
|
|
2354
|
-
if (abortController.signal.aborted) {
|
|
2355
|
-
throw new Error("RPC call aborted");
|
|
2356
|
-
}
|
|
2357
|
-
logger3.success(`RPC success: ${method}`);
|
|
2358
|
-
setIsLoading(false);
|
|
2359
|
-
return result;
|
|
2360
|
-
} catch (err) {
|
|
2361
|
-
const rpcError = err instanceof Error ? err : new Error("RPC call failed");
|
|
2362
|
-
if (!abortController.signal.aborted) {
|
|
2363
|
-
setError(rpcError);
|
|
2364
|
-
logger3.error(`RPC failed: ${method}`, rpcError);
|
|
2365
|
-
const onError = options.onError || defaultOptions.onError;
|
|
2366
|
-
if (onError) {
|
|
2367
|
-
try {
|
|
2368
|
-
onError(rpcError);
|
|
2369
|
-
} catch (callbackError) {
|
|
2370
|
-
logger3.error("Error in onError callback", callbackError);
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
setIsLoading(false);
|
|
2375
|
-
throw rpcError;
|
|
2376
|
-
} finally {
|
|
2377
|
-
if (abortControllerRef.current === abortController) {
|
|
2378
|
-
abortControllerRef.current = null;
|
|
2379
|
-
}
|
|
2380
|
-
}
|
|
2381
|
-
},
|
|
2382
|
-
[client, isConnected, defaultOptions, logger3]
|
|
2383
|
-
);
|
|
2384
|
-
return {
|
|
2385
|
-
call: call2,
|
|
2386
|
-
isLoading,
|
|
2387
|
-
error,
|
|
2388
|
-
reset
|
|
2389
|
-
};
|
|
2390
|
-
}
|
|
2391
|
-
__name(useRPC, "useRPC");
|
|
2392
|
-
function useNamedRPC(defaultOptions = {}) {
|
|
2393
|
-
const { client, isConnected } = useCentrifugo();
|
|
2394
|
-
const [isLoading, setIsLoading] = react.useState(false);
|
|
2395
|
-
const [error, setError] = react.useState(null);
|
|
2396
|
-
const logger3 = react.useRef(getConsolaLogger("useNamedRPC")).current;
|
|
2397
|
-
const reset = react.useCallback(() => {
|
|
2398
|
-
setIsLoading(false);
|
|
2399
|
-
setError(null);
|
|
2400
|
-
}, []);
|
|
2401
|
-
const call2 = react.useCallback(
|
|
2402
|
-
async (method, data) => {
|
|
2403
|
-
if (!client) {
|
|
2404
|
-
const error2 = new Error("Centrifugo client not available");
|
|
2405
|
-
setError(error2);
|
|
2406
|
-
throw error2;
|
|
2407
|
-
}
|
|
2408
|
-
if (!isConnected) {
|
|
2409
|
-
const error2 = new Error("Not connected to Centrifugo");
|
|
2410
|
-
setError(error2);
|
|
2411
|
-
throw error2;
|
|
2412
|
-
}
|
|
2413
|
-
setError(null);
|
|
2414
|
-
setIsLoading(true);
|
|
2415
|
-
try {
|
|
2416
|
-
logger3.info(`Native RPC call: ${method}`, { data });
|
|
2417
|
-
const result = await client.namedRPC(method, data);
|
|
2418
|
-
logger3.success(`Native RPC success: ${method}`);
|
|
2419
|
-
setIsLoading(false);
|
|
2420
|
-
return result;
|
|
2421
|
-
} catch (err) {
|
|
2422
|
-
const rpcError = err instanceof Error ? err : new Error("Native RPC call failed");
|
|
2423
|
-
setError(rpcError);
|
|
2424
|
-
logger3.error(`Native RPC failed: ${method}`, rpcError);
|
|
2425
|
-
if (defaultOptions.onError) {
|
|
2426
|
-
try {
|
|
2427
|
-
defaultOptions.onError(rpcError);
|
|
2428
|
-
} catch (callbackError) {
|
|
2429
|
-
logger3.error("Error in onError callback", callbackError);
|
|
2430
|
-
}
|
|
2431
|
-
}
|
|
2432
|
-
setIsLoading(false);
|
|
2433
|
-
throw rpcError;
|
|
2434
|
-
}
|
|
2435
|
-
},
|
|
2436
|
-
[client, isConnected, defaultOptions, logger3]
|
|
2437
|
-
);
|
|
2438
|
-
return {
|
|
2439
|
-
call: call2,
|
|
2440
|
-
isLoading,
|
|
2441
|
-
error,
|
|
2442
|
-
reset
|
|
2443
|
-
};
|
|
2444
|
-
}
|
|
2445
|
-
__name(useNamedRPC, "useNamedRPC");
|
|
2446
|
-
|
|
2447
|
-
// src/core/errors/RPCRetryHandler.ts
|
|
2448
|
-
var DEFAULT_RETRY_CONFIG = {
|
|
2449
|
-
maxRetries: 3,
|
|
2450
|
-
baseDelayMs: 1e3,
|
|
2451
|
-
maxDelayMs: 1e4,
|
|
2452
|
-
jitterFactor: 0.2
|
|
2453
|
-
};
|
|
2454
|
-
function calculateDelay(attempt, config, error) {
|
|
2455
|
-
const baseDelay = error?.suggestedRetryDelay ?? config.baseDelayMs;
|
|
2456
|
-
const exponentialDelay = baseDelay * Math.pow(2, attempt);
|
|
2457
|
-
const cappedDelay = Math.min(exponentialDelay, config.maxDelayMs);
|
|
2458
|
-
const jitter = cappedDelay * config.jitterFactor * (Math.random() * 2 - 1);
|
|
2459
|
-
return Math.max(0, Math.round(cappedDelay + jitter));
|
|
2460
|
-
}
|
|
2461
|
-
__name(calculateDelay, "calculateDelay");
|
|
2462
|
-
function sleep(ms) {
|
|
2463
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2464
|
-
}
|
|
2465
|
-
__name(sleep, "sleep");
|
|
2466
|
-
async function withRetry(operation, config = {}, onRetry) {
|
|
2467
|
-
const fullConfig = { ...DEFAULT_RETRY_CONFIG, ...config };
|
|
2468
|
-
const state = {
|
|
2469
|
-
attempt: 0,
|
|
2470
|
-
lastError: null,
|
|
2471
|
-
totalDelayMs: 0
|
|
2472
|
-
};
|
|
2473
|
-
while (state.attempt <= fullConfig.maxRetries) {
|
|
2474
|
-
try {
|
|
2475
|
-
return await operation();
|
|
2476
|
-
} catch (error) {
|
|
2477
|
-
const rpcError = RPCError.fromError(error);
|
|
2478
|
-
state.lastError = rpcError;
|
|
2479
|
-
if (!rpcError.isRetryable) {
|
|
2480
|
-
throw rpcError;
|
|
2481
|
-
}
|
|
2482
|
-
if (state.attempt >= fullConfig.maxRetries) {
|
|
2483
|
-
throw rpcError;
|
|
2484
|
-
}
|
|
2485
|
-
const delayMs = calculateDelay(state.attempt, fullConfig, rpcError);
|
|
2486
|
-
state.totalDelayMs += delayMs;
|
|
2487
|
-
if (onRetry) {
|
|
2488
|
-
onRetry(state, delayMs);
|
|
2489
|
-
}
|
|
2490
|
-
await sleep(delayMs);
|
|
2491
|
-
state.attempt++;
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
|
-
throw state.lastError ?? new RPCError("unknown", "Retry failed");
|
|
2495
|
-
}
|
|
2496
|
-
__name(withRetry, "withRetry");
|
|
2497
|
-
function createRetryHandler(config = {}) {
|
|
2498
|
-
const fullConfig = { ...DEFAULT_RETRY_CONFIG, ...config };
|
|
2499
|
-
return {
|
|
2500
|
-
config: fullConfig,
|
|
2501
|
-
/**
|
|
2502
|
-
* Execute with retry.
|
|
2503
|
-
*/
|
|
2504
|
-
execute: /* @__PURE__ */ __name((operation, onRetry) => withRetry(operation, fullConfig, onRetry), "execute"),
|
|
2505
|
-
/**
|
|
2506
|
-
* Check if error should be retried.
|
|
2507
|
-
*/
|
|
2508
|
-
shouldRetry: /* @__PURE__ */ __name((error, attempt) => {
|
|
2509
|
-
if (attempt >= fullConfig.maxRetries) {
|
|
2510
|
-
return false;
|
|
2511
|
-
}
|
|
2512
|
-
const rpcError = RPCError.fromError(error);
|
|
2513
|
-
return rpcError.isRetryable;
|
|
2514
|
-
}, "shouldRetry"),
|
|
2515
|
-
/**
|
|
2516
|
-
* Get delay for next retry.
|
|
2517
|
-
*/
|
|
2518
|
-
getDelay: /* @__PURE__ */ __name((attempt, error) => {
|
|
2519
|
-
const rpcError = error ? RPCError.fromError(error) : void 0;
|
|
2520
|
-
return calculateDelay(attempt, fullConfig, rpcError);
|
|
2521
|
-
}, "getDelay")
|
|
2522
|
-
};
|
|
2523
|
-
}
|
|
2524
|
-
__name(createRetryHandler, "createRetryHandler");
|
|
2525
|
-
function CentrifugoMonitorFAB({
|
|
2526
|
-
position = "bottom-left",
|
|
2527
|
-
size = "md",
|
|
2528
|
-
variant = "full"
|
|
2529
|
-
}) {
|
|
2530
|
-
const positionStyles = {
|
|
2531
|
-
"bottom-left": { bottom: "1rem", left: "1rem" },
|
|
2532
|
-
"bottom-right": { bottom: "1rem", right: "1rem" },
|
|
2533
|
-
"top-left": { top: "1rem", left: "1rem" },
|
|
2534
|
-
"top-right": { top: "1rem", right: "1rem" }
|
|
2535
|
-
};
|
|
2536
|
-
const sizeStyles = {
|
|
2537
|
-
sm: { width: "48px", height: "48px" },
|
|
2538
|
-
md: { width: "56px", height: "56px" },
|
|
2539
|
-
lg: { width: "64px", height: "64px" }
|
|
2540
|
-
};
|
|
2541
|
-
const iconSizes = {
|
|
2542
|
-
sm: "h-5 w-5",
|
|
2543
|
-
md: "h-6 w-6",
|
|
2544
|
-
lg: "h-7 w-7"
|
|
2545
|
-
};
|
|
2546
|
-
const handleClick = /* @__PURE__ */ __name(() => {
|
|
2547
|
-
emitOpenMonitorDialog({ variant });
|
|
2548
|
-
}, "handleClick");
|
|
2549
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2550
|
-
"button",
|
|
2551
|
-
{
|
|
2552
|
-
onClick: handleClick,
|
|
2553
|
-
className: "rounded-full bg-primary text-primary-foreground shadow-lg hover:bg-primary/90 transition-all duration-200 flex items-center justify-center hover:scale-110",
|
|
2554
|
-
style: {
|
|
2555
|
-
position: "fixed",
|
|
2556
|
-
...positionStyles[position],
|
|
2557
|
-
...sizeStyles[size],
|
|
2558
|
-
zIndex: 9999
|
|
2559
|
-
},
|
|
2560
|
-
"aria-label": "Open Centrifugo Monitor",
|
|
2561
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: iconSizes[size] })
|
|
2562
|
-
}
|
|
2563
|
-
);
|
|
2564
|
-
}
|
|
2565
|
-
__name(CentrifugoMonitorFAB, "CentrifugoMonitorFAB");
|
|
2566
|
-
function CentrifugoMonitorWidget({
|
|
2567
|
-
title = "WebSocket Monitor",
|
|
2568
|
-
showExpandButton = true,
|
|
2569
|
-
className = ""
|
|
2570
|
-
}) {
|
|
2571
|
-
const handleExpand = /* @__PURE__ */ __name(() => {
|
|
2572
|
-
emitOpenMonitorDialog({ variant: "full" });
|
|
2573
|
-
}, "handleExpand");
|
|
2574
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Card, { className, children: [
|
|
2575
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
2576
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardTitle, { className: "text-sm font-medium flex items-center gap-2", children: [
|
|
2577
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-4 w-4" }),
|
|
2578
|
-
title
|
|
2579
|
-
] }),
|
|
2580
|
-
showExpandButton && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2581
|
-
uiNextjs.Button,
|
|
2582
|
-
{
|
|
2583
|
-
size: "sm",
|
|
2584
|
-
variant: "ghost",
|
|
2585
|
-
onClick: handleExpand,
|
|
2586
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-4 w-4" })
|
|
2587
|
-
}
|
|
2588
|
-
)
|
|
2589
|
-
] }),
|
|
2590
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardContent, { children: /* @__PURE__ */ jsxRuntime.jsx(ConnectionStatus, { variant: "detailed", showUptime: true, showSubscriptions: true }) })
|
|
2591
|
-
] });
|
|
2592
|
-
}
|
|
2593
|
-
__name(CentrifugoMonitorWidget, "CentrifugoMonitorWidget");
|
|
2594
|
-
function formatUptime(seconds) {
|
|
2595
|
-
if (seconds === 0) return "0s";
|
|
2596
|
-
const hours = Math.floor(seconds / 3600);
|
|
2597
|
-
const minutes = Math.floor(seconds % 3600 / 60);
|
|
2598
|
-
const secs = seconds % 60;
|
|
2599
|
-
if (hours > 0) {
|
|
2600
|
-
return `${hours}h ${minutes}m ${secs}s`;
|
|
2601
|
-
} else if (minutes > 0) {
|
|
2602
|
-
return `${minutes}m ${secs}s`;
|
|
2603
|
-
} else {
|
|
2604
|
-
return `${secs}s`;
|
|
2605
|
-
}
|
|
2606
|
-
}
|
|
2607
|
-
__name(formatUptime, "formatUptime");
|
|
2608
|
-
function ConnectionTab() {
|
|
2609
|
-
const {
|
|
2610
|
-
isConnected,
|
|
2611
|
-
isConnecting,
|
|
2612
|
-
connectionState,
|
|
2613
|
-
error,
|
|
2614
|
-
uptime,
|
|
2615
|
-
connect: connect2,
|
|
2616
|
-
disconnect: disconnect2,
|
|
2617
|
-
reconnect
|
|
2618
|
-
} = useCentrifugo();
|
|
2619
|
-
const statusIcon = isConnected ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Wifi, { className: "h-5 w-5 text-green-600" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.WifiOff, { className: "h-5 w-5 text-red-600" });
|
|
2620
|
-
const statusBadge = isConnected ? /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Badge, { variant: "default", className: "flex items-center gap-1", children: [
|
|
2621
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse" }),
|
|
2622
|
-
"Connected"
|
|
2623
|
-
] }) : isConnecting ? /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Badge, { variant: "secondary", className: "flex items-center gap-1", children: [
|
|
2624
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-3 w-3 animate-spin" }),
|
|
2625
|
-
"Connecting..."
|
|
2626
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "destructive", children: "Disconnected" });
|
|
2627
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Card, { children: [
|
|
2628
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardTitle, { className: "flex items-center justify-between", children: [
|
|
2629
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
2630
|
-
statusIcon,
|
|
2631
|
-
"Connection Status"
|
|
2632
|
-
] }),
|
|
2633
|
-
statusBadge
|
|
2634
|
-
] }) }),
|
|
2635
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardContent, { className: "space-y-4", children: [
|
|
2636
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between text-sm", children: [
|
|
2637
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "State:" }),
|
|
2638
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono font-medium capitalize", children: connectionState })
|
|
2639
|
-
] }),
|
|
2640
|
-
isConnected && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between text-sm", children: [
|
|
2641
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "Uptime:" }),
|
|
2642
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono font-medium flex items-center gap-1", children: [
|
|
2643
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3 w-3" }),
|
|
2644
|
-
formatUptime(uptime)
|
|
2645
|
-
] })
|
|
2646
|
-
] }),
|
|
2647
|
-
error && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2648
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Separator, {}),
|
|
2649
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2650
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: "Error:" }),
|
|
2651
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-red-600 bg-red-50 dark:bg-red-950/20 p-2 rounded", children: error.message })
|
|
2652
|
-
] })
|
|
2653
|
-
] }),
|
|
2654
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Separator, {}),
|
|
2655
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2", children: isConnected ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2656
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2657
|
-
uiNextjs.Button,
|
|
2658
|
-
{
|
|
2659
|
-
variant: "destructive",
|
|
2660
|
-
size: "sm",
|
|
2661
|
-
onClick: disconnect2,
|
|
2662
|
-
className: "flex-1",
|
|
2663
|
-
children: "Disconnect"
|
|
2664
|
-
}
|
|
2665
|
-
),
|
|
2666
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2667
|
-
uiNextjs.Button,
|
|
2668
|
-
{
|
|
2669
|
-
variant: "outline",
|
|
2670
|
-
size: "sm",
|
|
2671
|
-
onClick: reconnect,
|
|
2672
|
-
className: "flex-1",
|
|
2673
|
-
children: [
|
|
2674
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-3 w-3 mr-1" }),
|
|
2675
|
-
"Reconnect"
|
|
2676
|
-
]
|
|
2677
|
-
}
|
|
2678
|
-
)
|
|
2679
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2680
|
-
uiNextjs.Button,
|
|
2681
|
-
{
|
|
2682
|
-
variant: "default",
|
|
2683
|
-
size: "sm",
|
|
2684
|
-
onClick: connect2,
|
|
2685
|
-
disabled: isConnecting,
|
|
2686
|
-
className: "flex-1",
|
|
2687
|
-
children: isConnecting ? "Connecting..." : "Connect"
|
|
2688
|
-
}
|
|
2689
|
-
) })
|
|
2690
|
-
] })
|
|
2691
|
-
] }) });
|
|
2692
|
-
}
|
|
2693
|
-
__name(ConnectionTab, "ConnectionTab");
|
|
2694
|
-
function formatTimestamp(date) {
|
|
2695
|
-
return moment2__default.default(date).format("HH:mm:ss.SSS");
|
|
2696
|
-
}
|
|
2697
|
-
__name(formatTimestamp, "formatTimestamp");
|
|
2698
|
-
function getLevelColor(level) {
|
|
2699
|
-
switch (level) {
|
|
2700
|
-
case "debug":
|
|
2701
|
-
return "text-blue-600 dark:text-blue-400";
|
|
2702
|
-
case "info":
|
|
2703
|
-
return "text-gray-600 dark:text-gray-400";
|
|
2704
|
-
case "success":
|
|
2705
|
-
return "text-green-600 dark:text-green-400";
|
|
2706
|
-
case "warning":
|
|
2707
|
-
return "text-yellow-600 dark:text-yellow-400";
|
|
2708
|
-
case "error":
|
|
2709
|
-
return "text-red-600 dark:text-red-400";
|
|
2710
|
-
}
|
|
2711
|
-
}
|
|
2712
|
-
__name(getLevelColor, "getLevelColor");
|
|
2713
|
-
function LogsTab() {
|
|
2714
|
-
const { filteredLogs, filter, setFilter, clearLogs, count } = useLogs();
|
|
2715
|
-
const [search, setSearch] = react.useState("");
|
|
2716
|
-
const [autoScroll, setAutoScroll] = react.useState(true);
|
|
2717
|
-
const scrollRef = react.useRef(null);
|
|
2718
|
-
react.useEffect(() => {
|
|
2719
|
-
if (autoScroll && scrollRef.current) {
|
|
2720
|
-
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
2721
|
-
}
|
|
2722
|
-
}, [filteredLogs, autoScroll]);
|
|
2723
|
-
react.useEffect(() => {
|
|
2724
|
-
const timeout = setTimeout(() => {
|
|
2725
|
-
setFilter({ search: search || void 0 });
|
|
2726
|
-
}, 300);
|
|
2727
|
-
return () => clearTimeout(timeout);
|
|
2728
|
-
}, [search, setFilter]);
|
|
2729
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
2730
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Card, { children: [
|
|
2731
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardTitle, { className: "flex items-center justify-between", children: [
|
|
2732
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
2733
|
-
"Logs",
|
|
2734
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "secondary", children: count })
|
|
2735
|
-
] }),
|
|
2736
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
2737
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2738
|
-
uiNextjs.Button,
|
|
2739
|
-
{
|
|
2740
|
-
variant: "outline",
|
|
2741
|
-
size: "sm",
|
|
2742
|
-
onClick: () => setAutoScroll(!autoScroll),
|
|
2743
|
-
children: [
|
|
2744
|
-
"Auto-scroll: ",
|
|
2745
|
-
autoScroll ? "ON" : "OFF"
|
|
2746
|
-
]
|
|
2747
|
-
}
|
|
2748
|
-
),
|
|
2749
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2750
|
-
uiNextjs.Button,
|
|
2751
|
-
{
|
|
2752
|
-
variant: "destructive",
|
|
2753
|
-
size: "sm",
|
|
2754
|
-
onClick: clearLogs,
|
|
2755
|
-
children: [
|
|
2756
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3 w-3 mr-1" }),
|
|
2757
|
-
"Clear"
|
|
2758
|
-
]
|
|
2759
|
-
}
|
|
2760
|
-
)
|
|
2761
|
-
] })
|
|
2762
|
-
] }) }),
|
|
2763
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardContent, { className: "space-y-3", children: [
|
|
2764
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex-1", children: [
|
|
2765
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" }),
|
|
2766
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2767
|
-
uiNextjs.Input,
|
|
2768
|
-
{
|
|
2769
|
-
placeholder: "Search logs...",
|
|
2770
|
-
value: search,
|
|
2771
|
-
onChange: (e) => setSearch(e.target.value),
|
|
2772
|
-
className: "pl-8"
|
|
2773
|
-
}
|
|
2774
|
-
)
|
|
2775
|
-
] }) }),
|
|
2776
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
2777
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2778
|
-
uiNextjs.Select,
|
|
2779
|
-
{
|
|
2780
|
-
value: filter.level || "all",
|
|
2781
|
-
onValueChange: (value) => setFilter({ level: value === "all" ? void 0 : value }),
|
|
2782
|
-
children: [
|
|
2783
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.SelectTrigger, { className: "w-[140px]", children: [
|
|
2784
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Filter, { className: "h-3 w-3 mr-1" }),
|
|
2785
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SelectValue, { placeholder: "Level" })
|
|
2786
|
-
] }),
|
|
2787
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.SelectContent, { children: [
|
|
2788
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SelectItem, { value: "all", children: "All Levels" }),
|
|
2789
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SelectItem, { value: "debug", children: "Debug" }),
|
|
2790
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SelectItem, { value: "info", children: "Info" }),
|
|
2791
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SelectItem, { value: "success", children: "Success" }),
|
|
2792
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SelectItem, { value: "warning", children: "Warning" }),
|
|
2793
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SelectItem, { value: "error", children: "Error" })
|
|
2794
|
-
] })
|
|
2795
|
-
]
|
|
2796
|
-
}
|
|
2797
|
-
),
|
|
2798
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2799
|
-
uiNextjs.Select,
|
|
2800
|
-
{
|
|
2801
|
-
value: filter.source || "all",
|
|
2802
|
-
onValueChange: (value) => setFilter({ source: value === "all" ? void 0 : value }),
|
|
2803
|
-
children: [
|
|
2804
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.SelectTrigger, { className: "w-[140px]", children: [
|
|
2805
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Filter, { className: "h-3 w-3 mr-1" }),
|
|
2806
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SelectValue, { placeholder: "Source" })
|
|
2807
|
-
] }),
|
|
2808
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.SelectContent, { children: [
|
|
2809
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SelectItem, { value: "all", children: "All Sources" }),
|
|
2810
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SelectItem, { value: "client", children: "Client" }),
|
|
2811
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SelectItem, { value: "provider", children: "Provider" }),
|
|
2812
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SelectItem, { value: "subscription", children: "Subscription" }),
|
|
2813
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SelectItem, { value: "system", children: "System" })
|
|
2814
|
-
] })
|
|
2815
|
-
]
|
|
2816
|
-
}
|
|
2817
|
-
)
|
|
2818
|
-
] })
|
|
2819
|
-
] })
|
|
2820
|
-
] }),
|
|
2821
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Card, { children: /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardContent, { className: "p-0", children: /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.ScrollArea, { viewportRef: scrollRef, className: "h-[400px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 font-mono text-xs space-y-1 bg-slate-950 text-slate-50", children: filteredLogs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-slate-500 text-center py-8", children: "No logs to display" }) : filteredLogs.map((log) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 hover:bg-slate-900 px-1 py-0.5 rounded", children: [
|
|
2822
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-slate-500 shrink-0", children: [
|
|
2823
|
-
"[",
|
|
2824
|
-
formatTimestamp(log.timestamp),
|
|
2825
|
-
"]"
|
|
2826
|
-
] }),
|
|
2827
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `${getLevelColor(log.level)} font-bold uppercase shrink-0 w-16`, children: log.level }),
|
|
2828
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-blue-400 shrink-0 w-24", children: [
|
|
2829
|
-
"[",
|
|
2830
|
-
log.source,
|
|
2831
|
-
"]"
|
|
2832
|
-
] }),
|
|
2833
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-slate-200", children: log.message }),
|
|
2834
|
-
log.data && /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "text-slate-400 cursor-pointer", children: [
|
|
2835
|
-
/* @__PURE__ */ jsxRuntime.jsx("summary", { className: "inline", children: /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "outline", className: "text-xs ml-2", children: "data" }) }),
|
|
2836
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 ml-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2837
|
-
uiTools.PrettyCode,
|
|
2838
|
-
{
|
|
2839
|
-
data: typeof log.data === "object" ? log.data : { value: log.data },
|
|
2840
|
-
language: "json"
|
|
2841
|
-
}
|
|
2842
|
-
) })
|
|
2843
|
-
] })
|
|
2844
|
-
] }, log.id)) }) }) }) })
|
|
2845
|
-
] });
|
|
2846
|
-
}
|
|
2847
|
-
__name(LogsTab, "LogsTab");
|
|
2848
|
-
function formatSubscribedTime(timestamp) {
|
|
2849
|
-
const now = Date.now();
|
|
2850
|
-
const diff = Math.floor((now - timestamp) / 1e3);
|
|
2851
|
-
if (diff < 60) {
|
|
2852
|
-
return `${diff}s ago`;
|
|
2853
|
-
} else if (diff < 3600) {
|
|
2854
|
-
return `${Math.floor(diff / 60)}m ago`;
|
|
2855
|
-
} else if (diff < 86400) {
|
|
2856
|
-
return `${Math.floor(diff / 3600)}h ago`;
|
|
2857
|
-
} else {
|
|
2858
|
-
return `${Math.floor(diff / 86400)}d ago`;
|
|
2859
|
-
}
|
|
2860
|
-
}
|
|
2861
|
-
__name(formatSubscribedTime, "formatSubscribedTime");
|
|
2862
|
-
function SubscriptionsTab() {
|
|
2863
|
-
const { activeSubscriptions, unsubscribe: unsubscribe2 } = useCentrifugo();
|
|
2864
|
-
const handleUnsubscribe = /* @__PURE__ */ __name((channel) => {
|
|
2865
|
-
unsubscribe2(channel);
|
|
2866
|
-
}, "handleUnsubscribe");
|
|
2867
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
2868
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Card, { children: /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardTitle, { className: "flex items-center justify-between", children: [
|
|
2869
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-2", children: [
|
|
2870
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Radio, { className: "h-5 w-5" }),
|
|
2871
|
-
"Active Subscriptions"
|
|
2872
|
-
] }),
|
|
2873
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "secondary", children: activeSubscriptions.length })
|
|
2874
|
-
] }) }) }),
|
|
2875
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Card, { children: /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardContent, { className: "p-0", children: activeSubscriptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-8 text-center text-muted-foreground", children: "No active subscriptions" }) : /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.ScrollArea, { className: "h-[450px]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 space-y-3", children: activeSubscriptions.map((sub, index) => /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
2876
|
-
index > 0 && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Separator, {}),
|
|
2877
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-4 p-3 rounded-lg hover:bg-muted/50 transition-colors", children: [
|
|
2878
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-2", children: [
|
|
2879
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2880
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Radio, { className: "h-4 w-4 text-green-600 animate-pulse" }),
|
|
2881
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-sm font-medium", children: sub.channel })
|
|
2882
|
-
] }),
|
|
2883
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3 text-xs text-muted-foreground", children: [
|
|
2884
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "outline", className: "text-xs", children: sub.type }) }),
|
|
2885
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
2886
|
-
"Subscribed: ",
|
|
2887
|
-
formatSubscribedTime(sub.subscribedAt)
|
|
2888
|
-
] })
|
|
2889
|
-
] }),
|
|
2890
|
-
sub.data && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "cursor-pointer", children: [
|
|
2891
|
-
/* @__PURE__ */ jsxRuntime.jsx("summary", { className: "inline", children: "View data" }),
|
|
2892
|
-
/* @__PURE__ */ jsxRuntime.jsx("pre", { className: "mt-2 p-2 bg-muted rounded text-xs overflow-auto", children: JSON.stringify(sub.data, null, 2) })
|
|
2893
|
-
] }) })
|
|
2894
|
-
] }),
|
|
2895
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2896
|
-
uiNextjs.Button,
|
|
2897
|
-
{
|
|
2898
|
-
variant: "ghost",
|
|
2899
|
-
size: "sm",
|
|
2900
|
-
onClick: () => handleUnsubscribe(sub.channel),
|
|
2901
|
-
className: "shrink-0",
|
|
2902
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3 w-3 text-red-600" })
|
|
2903
|
-
}
|
|
2904
|
-
)
|
|
2905
|
-
] })
|
|
2906
|
-
] }, sub.channel)) }) }) }) })
|
|
2907
|
-
] });
|
|
2908
|
-
}
|
|
2909
|
-
__name(SubscriptionsTab, "SubscriptionsTab");
|
|
2910
|
-
function DebugPanel() {
|
|
2911
|
-
const [isOpen, setIsOpen] = react.useState(false);
|
|
2912
|
-
const [activeTab, setActiveTab] = react.useState("connection");
|
|
2913
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2914
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2915
|
-
"button",
|
|
2916
|
-
{
|
|
2917
|
-
onClick: () => setIsOpen(true),
|
|
2918
|
-
className: "rounded-full bg-primary text-primary-foreground shadow-lg hover:bg-primary/90 transition-all duration-200 flex items-center justify-center",
|
|
2919
|
-
style: {
|
|
2920
|
-
position: "fixed",
|
|
2921
|
-
bottom: "1rem",
|
|
2922
|
-
left: "1rem",
|
|
2923
|
-
width: "56px",
|
|
2924
|
-
height: "56px",
|
|
2925
|
-
zIndex: 9999
|
|
2926
|
-
},
|
|
2927
|
-
"aria-label": "Open Centrifugo Debug Panel",
|
|
2928
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Bug, { className: "h-6 w-6" })
|
|
2929
|
-
}
|
|
2930
|
-
),
|
|
2931
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Sheet, { open: isOpen, onOpenChange: setIsOpen, children: /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.SheetContent, { side: "right", className: "w-full sm:max-w-2xl", children: [
|
|
2932
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.SheetHeader, { children: [
|
|
2933
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SheetTitle, { children: "Centrifugo Debug" }),
|
|
2934
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SheetDescription, { children: "WebSocket connection status, logs, and subscriptions" })
|
|
2935
|
-
] }),
|
|
2936
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Tabs, { value: activeTab, onValueChange: setActiveTab, className: "mt-6", children: [
|
|
2937
|
-
/* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.TabsList, { className: "grid w-full grid-cols-3", children: [
|
|
2938
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.TabsTrigger, { value: "connection", children: "Connection" }),
|
|
2939
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.TabsTrigger, { value: "logs", children: "Logs" }),
|
|
2940
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.TabsTrigger, { value: "subscriptions", children: "Subscriptions" })
|
|
2941
|
-
] }),
|
|
2942
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.TabsContent, { value: "connection", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(ConnectionTab, {}) }),
|
|
2943
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.TabsContent, { value: "logs", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(LogsTab, {}) }),
|
|
2944
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiNextjs.TabsContent, { value: "subscriptions", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(SubscriptionsTab, {}) })
|
|
2945
|
-
] })
|
|
2946
|
-
] }) })
|
|
2947
|
-
] });
|
|
2948
|
-
}
|
|
2949
|
-
__name(DebugPanel, "DebugPanel");
|
|
2950
|
-
|
|
2951
|
-
exports.CENTRIFUGO_ERROR_EVENT = CENTRIFUGO_ERROR_EVENT;
|
|
2952
|
-
exports.CENTRIFUGO_EVENT = CENTRIFUGO_EVENT;
|
|
2953
|
-
exports.CENTRIFUGO_MONITOR_EVENTS = CENTRIFUGO_MONITOR_EVENTS;
|
|
2954
|
-
exports.CENTRIFUGO_VERSION_MISMATCH_EVENT = CENTRIFUGO_VERSION_MISMATCH_EVENT;
|
|
2955
|
-
exports.CentrifugoMonitor = CentrifugoMonitor;
|
|
2956
|
-
exports.CentrifugoMonitorDialog = CentrifugoMonitorDialog;
|
|
2957
|
-
exports.CentrifugoMonitorFAB = CentrifugoMonitorFAB;
|
|
2958
|
-
exports.CentrifugoMonitorWidget = CentrifugoMonitorWidget;
|
|
2959
|
-
exports.CentrifugoProvider = CentrifugoProvider;
|
|
2960
|
-
exports.CentrifugoRPCClient = CentrifugoRPCClient;
|
|
2961
|
-
exports.ConnectionStatus = ConnectionStatus;
|
|
2962
|
-
exports.ConnectionStatusCard = ConnectionStatusCard;
|
|
2963
|
-
exports.ConnectionTab = ConnectionTab;
|
|
2964
|
-
exports.DEFAULT_RETRY_CONFIG = DEFAULT_RETRY_CONFIG;
|
|
2965
|
-
exports.DebugPanel = DebugPanel;
|
|
2966
|
-
exports.LogsProvider = LogsProvider;
|
|
2967
|
-
exports.LogsTab = LogsTab;
|
|
2968
|
-
exports.MessageFilters = MessageFilters;
|
|
2969
|
-
exports.MessagesFeed = MessagesFeed;
|
|
2970
|
-
exports.RPCError = RPCError;
|
|
2971
|
-
exports.SubscriptionsList = SubscriptionsList;
|
|
2972
|
-
exports.SubscriptionsTab = SubscriptionsTab;
|
|
2973
|
-
exports.calculateDelay = calculateDelay;
|
|
2974
|
-
exports.centrifugoConfig = centrifugoConfig;
|
|
2975
|
-
exports.consolaLogger = consolaLogger;
|
|
2976
|
-
exports.createLogger = createLogger;
|
|
2977
|
-
exports.createRetryHandler = createRetryHandler;
|
|
2978
|
-
exports.dispatchCentrifugoError = dispatchCentrifugoError;
|
|
2979
|
-
exports.dispatchCentrifugoEvent = dispatchCentrifugoEvent;
|
|
2980
|
-
exports.dispatchConnected = dispatchConnected;
|
|
2981
|
-
exports.dispatchDisconnected = dispatchDisconnected;
|
|
2982
|
-
exports.dispatchReconnecting = dispatchReconnecting;
|
|
2983
|
-
exports.dispatchVersionMismatch = dispatchVersionMismatch;
|
|
2984
|
-
exports.emitCloseMonitorDialog = emitCloseMonitorDialog;
|
|
2985
|
-
exports.emitOpenMonitorDialog = emitOpenMonitorDialog;
|
|
2986
|
-
exports.getConsolaLogger = getConsolaLogger;
|
|
2987
|
-
exports.getGlobalLogsStore = getGlobalLogsStore;
|
|
2988
|
-
exports.isDevelopment = isDevelopment2;
|
|
2989
|
-
exports.isProduction = isProduction;
|
|
2990
|
-
exports.isStaticBuild = isStaticBuild;
|
|
2991
|
-
exports.sleep = sleep;
|
|
2992
|
-
exports.useCentrifugo = useCentrifugo;
|
|
2993
|
-
exports.useCodegenTip = useCodegenTip;
|
|
2994
|
-
exports.useLogs = useLogs;
|
|
2995
|
-
exports.useNamedRPC = useNamedRPC;
|
|
2996
|
-
exports.usePageVisibility = usePageVisibility;
|
|
2997
|
-
exports.useRPC = useRPC;
|
|
2998
|
-
exports.useSubscription = useSubscription;
|
|
2999
|
-
exports.withRetry = withRetry;
|
|
3000
|
-
Object.keys(centrifuge).forEach(function (k) {
|
|
3001
|
-
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
3002
|
-
enumerable: true,
|
|
3003
|
-
get: function () { return centrifuge[k]; }
|
|
3004
|
-
});
|
|
3005
|
-
});
|
|
3006
|
-
//# sourceMappingURL=index.cjs.map
|
|
3007
|
-
//# sourceMappingURL=index.cjs.map
|