@djangocfg/centrifugo 2.1.101 → 2.1.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,871 @@
1
+ 'use strict';
2
+
3
+ var lucideReact = require('lucide-react');
4
+ var moment = require('moment');
5
+ var react = require('react');
6
+ var uiNextjs = require('@djangocfg/ui-nextjs');
7
+ var consola = require('consola');
8
+ require('@djangocfg/api/auth');
9
+ var hooks = require('@djangocfg/ui-core/hooks');
10
+ var jsxRuntime = require('react/jsx-runtime');
11
+ require('centrifuge');
12
+
13
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
+
15
+ var moment__default = /*#__PURE__*/_interopDefault(moment);
16
+
17
+ var __defProp = Object.defineProperty;
18
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
19
+ var consolaLogger = consola.createConsola({
20
+ level: 4 ,
21
+ formatOptions: {
22
+ colors: true,
23
+ date: false,
24
+ compact: false
25
+ }
26
+ }).withTag("[Centrifugo]");
27
+ function getConsolaLogger(tag) {
28
+ const consola = consolaLogger.withTag(`[${tag}]`);
29
+ return {
30
+ debug: /* @__PURE__ */ __name((message, data) => consola.debug(message, data || ""), "debug"),
31
+ info: /* @__PURE__ */ __name((message, data) => consola.info(message, data || ""), "info"),
32
+ success: /* @__PURE__ */ __name((message, data) => consola.success(message, data || ""), "success"),
33
+ warning: /* @__PURE__ */ __name((message, data) => consola.warn(message, data || ""), "warning"),
34
+ error: /* @__PURE__ */ __name((message, error) => consola.error(message, error || ""), "error")
35
+ };
36
+ }
37
+ __name(getConsolaLogger, "getConsolaLogger");
38
+ var CENTRIFUGO_MONITOR_EVENTS = {
39
+ OPEN_MONITOR_DIALOG: "CENTRIFUGO_OPEN_MONITOR_DIALOG",
40
+ CLOSE_MONITOR_DIALOG: "CENTRIFUGO_CLOSE_MONITOR_DIALOG"
41
+ };
42
+ var emitOpenMonitorDialog = /* @__PURE__ */ __name((payload) => {
43
+ hooks.events.publish({
44
+ type: CENTRIFUGO_MONITOR_EVENTS.OPEN_MONITOR_DIALOG,
45
+ payload: payload || {}
46
+ });
47
+ }, "emitOpenMonitorDialog");
48
+ function MessageFilters({
49
+ filters,
50
+ onFiltersChange,
51
+ autoScroll,
52
+ onAutoScrollChange
53
+ }) {
54
+ const hasActiveFilters = filters.channels && filters.channels.length > 0 || filters.types && filters.types.length > 0 || filters.levels && filters.levels.length > 0 || filters.searchQuery;
55
+ const handleClearFilters = /* @__PURE__ */ __name(() => {
56
+ onFiltersChange({});
57
+ }, "handleClearFilters");
58
+ const handleSearchChange = /* @__PURE__ */ __name((e) => {
59
+ onFiltersChange({ ...filters, searchQuery: e.target.value });
60
+ }, "handleSearchChange");
61
+ const handleToggleLevel = /* @__PURE__ */ __name((level) => {
62
+ const currentLevels = filters.levels || [];
63
+ const newLevels = currentLevels.includes(level) ? currentLevels.filter((l) => l !== level) : [...currentLevels, level];
64
+ onFiltersChange({
65
+ ...filters,
66
+ levels: newLevels.length > 0 ? newLevels : void 0
67
+ });
68
+ }, "handleToggleLevel");
69
+ const handleToggleType = /* @__PURE__ */ __name((type) => {
70
+ const currentTypes = filters.types || [];
71
+ const newTypes = currentTypes.includes(type) ? currentTypes.filter((t) => t !== type) : [...currentTypes, type];
72
+ onFiltersChange({
73
+ ...filters,
74
+ types: newTypes.length > 0 ? newTypes : void 0
75
+ });
76
+ }, "handleToggleType");
77
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 p-3 border rounded-lg bg-muted/30", children: [
78
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
79
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
80
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Filter, { className: "h-4 w-4 text-muted-foreground" }),
81
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Filters" }),
82
+ hasActiveFilters && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "secondary", className: "text-xs", children: "Active" })
83
+ ] }),
84
+ hasActiveFilters && /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Button, { size: "sm", variant: "ghost", onClick: handleClearFilters, children: [
85
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3 mr-1" }),
86
+ "Clear"
87
+ ] })
88
+ ] }),
89
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
90
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "h-4 w-4 text-muted-foreground" }),
91
+ /* @__PURE__ */ jsxRuntime.jsx(
92
+ uiNextjs.Input,
93
+ {
94
+ type: "text",
95
+ placeholder: "Search messages...",
96
+ value: filters.searchQuery || "",
97
+ onChange: handleSearchChange,
98
+ className: "flex-1"
99
+ }
100
+ )
101
+ ] }),
102
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
103
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Level:" }),
104
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: ["info", "success", "warning", "error"].map((level) => {
105
+ const isActive = filters.levels?.includes(level);
106
+ return /* @__PURE__ */ jsxRuntime.jsx(
107
+ uiNextjs.Badge,
108
+ {
109
+ variant: isActive ? "default" : "outline",
110
+ className: "cursor-pointer",
111
+ onClick: () => handleToggleLevel(level),
112
+ children: level
113
+ },
114
+ level
115
+ );
116
+ }) })
117
+ ] }),
118
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
119
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Type:" }),
120
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: ["connection", "subscription", "publication", "unsubscription", "error", "system"].map((type) => {
121
+ const isActive = filters.types?.includes(type);
122
+ return /* @__PURE__ */ jsxRuntime.jsx(
123
+ uiNextjs.Badge,
124
+ {
125
+ variant: isActive ? "default" : "outline",
126
+ className: "cursor-pointer",
127
+ onClick: () => handleToggleType(type),
128
+ children: type
129
+ },
130
+ type
131
+ );
132
+ }) })
133
+ ] }),
134
+ 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: [
135
+ /* @__PURE__ */ jsxRuntime.jsx(
136
+ "input",
137
+ {
138
+ type: "checkbox",
139
+ checked: autoScroll,
140
+ onChange: (e) => onAutoScrollChange(e.target.checked),
141
+ className: "h-4 w-4 rounded border-gray-300"
142
+ }
143
+ ),
144
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Auto-scroll to latest" })
145
+ ] }) })
146
+ ] });
147
+ }
148
+ __name(MessageFilters, "MessageFilters");
149
+ function MessagesFeed({
150
+ maxMessages = 100,
151
+ showFilters = true,
152
+ showControls = true,
153
+ channels = [],
154
+ autoScroll: initialAutoScroll = true,
155
+ onMessageClick,
156
+ className = ""
157
+ }) {
158
+ const { isConnected, client } = useCentrifugo();
159
+ const [messages, setMessages] = react.useState([]);
160
+ const [isPaused, setIsPaused] = react.useState(false);
161
+ const [autoScroll, setAutoScroll] = react.useState(initialAutoScroll);
162
+ const [filters, setFilters] = react.useState({
163
+ channels: channels.length > 0 ? channels : void 0
164
+ });
165
+ const scrollRef = react.useRef(null);
166
+ const addMessage = react.useCallback(
167
+ (message) => {
168
+ if (isPaused) return;
169
+ setMessages((prev) => {
170
+ const newMessages = [message, ...prev];
171
+ return newMessages.slice(0, maxMessages);
172
+ });
173
+ },
174
+ [isPaused, maxMessages]
175
+ );
176
+ react.useEffect(() => {
177
+ if (!client) return;
178
+ const centrifuge = client.getCentrifuge();
179
+ const handleConnected = /* @__PURE__ */ __name(() => {
180
+ const now = moment__default.default.utc().valueOf();
181
+ addMessage({
182
+ id: `conn-${now}`,
183
+ timestamp: now,
184
+ type: "connection",
185
+ level: "success",
186
+ message: "Connected to Centrifugo"
187
+ });
188
+ }, "handleConnected");
189
+ const handleDisconnected = /* @__PURE__ */ __name(() => {
190
+ const now = moment__default.default.utc().valueOf();
191
+ addMessage({
192
+ id: `disconn-${now}`,
193
+ timestamp: now,
194
+ type: "connection",
195
+ level: "error",
196
+ message: "Disconnected from Centrifugo"
197
+ });
198
+ }, "handleDisconnected");
199
+ const handleError = /* @__PURE__ */ __name((ctx) => {
200
+ const now = moment__default.default.utc().valueOf();
201
+ addMessage({
202
+ id: `error-${now}`,
203
+ timestamp: now,
204
+ type: "error",
205
+ level: "error",
206
+ message: ctx.error?.message || "Connection error",
207
+ data: ctx
208
+ });
209
+ }, "handleError");
210
+ centrifuge.on("connected", handleConnected);
211
+ centrifuge.on("disconnected", handleDisconnected);
212
+ centrifuge.on("error", handleError);
213
+ return () => {
214
+ centrifuge.off("connected", handleConnected);
215
+ centrifuge.off("disconnected", handleDisconnected);
216
+ centrifuge.off("error", handleError);
217
+ };
218
+ }, [client, addMessage]);
219
+ react.useEffect(() => {
220
+ if (!client) return;
221
+ const centrifuge = client.getCentrifuge();
222
+ const handleSubscribed = /* @__PURE__ */ __name((ctx) => {
223
+ const now = moment__default.default.utc().valueOf();
224
+ addMessage({
225
+ id: `sub-${now}`,
226
+ timestamp: now,
227
+ type: "subscription",
228
+ level: "info",
229
+ channel: ctx.channel,
230
+ message: `Subscribed to ${ctx.channel}`,
231
+ data: ctx
232
+ });
233
+ }, "handleSubscribed");
234
+ const handleUnsubscribed = /* @__PURE__ */ __name((ctx) => {
235
+ const now = moment__default.default.utc().valueOf();
236
+ addMessage({
237
+ id: `unsub-${now}`,
238
+ timestamp: now,
239
+ type: "unsubscription",
240
+ level: "info",
241
+ channel: ctx.channel,
242
+ message: `Unsubscribed from ${ctx.channel}`,
243
+ data: ctx
244
+ });
245
+ }, "handleUnsubscribed");
246
+ const handlePublication = /* @__PURE__ */ __name((ctx) => {
247
+ const now = moment__default.default.utc().valueOf();
248
+ addMessage({
249
+ id: `pub-${now}-${Math.random()}`,
250
+ timestamp: now,
251
+ type: "publication",
252
+ level: "success",
253
+ channel: ctx.channel,
254
+ message: `Message from ${ctx.channel}`,
255
+ data: ctx.data
256
+ });
257
+ }, "handlePublication");
258
+ centrifuge.on("subscribed", handleSubscribed);
259
+ centrifuge.on("unsubscribed", handleUnsubscribed);
260
+ centrifuge.on("publication", handlePublication);
261
+ return () => {
262
+ centrifuge.off("subscribed", handleSubscribed);
263
+ centrifuge.off("unsubscribed", handleUnsubscribed);
264
+ centrifuge.off("publication", handlePublication);
265
+ };
266
+ }, [client, addMessage]);
267
+ react.useEffect(() => {
268
+ if (autoScroll && scrollRef.current) {
269
+ scrollRef.current.scrollTop = 0;
270
+ }
271
+ }, [messages, autoScroll]);
272
+ const filteredMessages = react.useMemo(() => {
273
+ return messages.filter((msg) => {
274
+ if (filters.channels && filters.channels.length > 0) {
275
+ if (!msg.channel || !filters.channels.includes(msg.channel)) {
276
+ return false;
277
+ }
278
+ }
279
+ if (filters.types && filters.types.length > 0) {
280
+ if (!filters.types.includes(msg.type)) {
281
+ return false;
282
+ }
283
+ }
284
+ if (filters.levels && filters.levels.length > 0) {
285
+ if (!filters.levels.includes(msg.level)) {
286
+ return false;
287
+ }
288
+ }
289
+ if (filters.searchQuery) {
290
+ const query = filters.searchQuery.toLowerCase();
291
+ const searchableText = [
292
+ msg.message,
293
+ msg.channel,
294
+ JSON.stringify(msg.data)
295
+ ].filter(Boolean).join(" ").toLowerCase();
296
+ if (!searchableText.includes(query)) {
297
+ return false;
298
+ }
299
+ }
300
+ return true;
301
+ }).map((msg) => ({
302
+ ...msg,
303
+ formattedTime: moment__default.default.utc(msg.timestamp).format("HH:mm:ss"),
304
+ formattedData: msg.data ? JSON.stringify(msg.data, null, 2) : null
305
+ }));
306
+ }, [messages, filters]);
307
+ const handleClear = /* @__PURE__ */ __name(() => {
308
+ setMessages([]);
309
+ }, "handleClear");
310
+ const handleDownload = /* @__PURE__ */ __name(() => {
311
+ const json = JSON.stringify(filteredMessages, null, 2);
312
+ const blob = new Blob([json], { type: "application/json" });
313
+ const url = URL.createObjectURL(blob);
314
+ const a = document.createElement("a");
315
+ a.href = url;
316
+ a.download = `centrifugo-messages-${moment__default.default.utc().format("YYYY-MM-DD-HHmmss")}.json`;
317
+ document.body.appendChild(a);
318
+ a.click();
319
+ document.body.removeChild(a);
320
+ URL.revokeObjectURL(url);
321
+ }, "handleDownload");
322
+ const getLevelIcon = /* @__PURE__ */ __name((level) => {
323
+ switch (level) {
324
+ case "error":
325
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-4 w-4 text-red-500" });
326
+ case "warning":
327
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-4 w-4 text-yellow-500" });
328
+ case "success":
329
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { className: "h-4 w-4 text-green-500" });
330
+ case "info":
331
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Info, { className: "h-4 w-4 text-blue-500" });
332
+ default:
333
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Circle, { className: "h-4 w-4 text-gray-500" });
334
+ }
335
+ }, "getLevelIcon");
336
+ const getLevelVariant = /* @__PURE__ */ __name((level) => {
337
+ switch (level) {
338
+ case "error":
339
+ return "destructive";
340
+ case "warning":
341
+ return "secondary";
342
+ case "success":
343
+ return "outline";
344
+ default:
345
+ return "default";
346
+ }
347
+ }, "getLevelVariant");
348
+ return /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Card, { className, children: [
349
+ /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
350
+ /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardTitle, { className: "flex items-center gap-2", children: [
351
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-5 w-5" }),
352
+ "Messages Feed",
353
+ /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "outline", children: filteredMessages.length })
354
+ ] }),
355
+ showControls && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
356
+ /* @__PURE__ */ jsxRuntime.jsx(
357
+ uiNextjs.Button,
358
+ {
359
+ size: "sm",
360
+ variant: "outline",
361
+ onClick: () => setIsPaused(!isPaused),
362
+ children: isPaused ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pause, { className: "h-4 w-4" })
363
+ }
364
+ ),
365
+ /* @__PURE__ */ jsxRuntime.jsx(
366
+ uiNextjs.Button,
367
+ {
368
+ size: "sm",
369
+ variant: "outline",
370
+ onClick: handleClear,
371
+ disabled: messages.length === 0,
372
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" })
373
+ }
374
+ ),
375
+ /* @__PURE__ */ jsxRuntime.jsx(
376
+ uiNextjs.Button,
377
+ {
378
+ size: "sm",
379
+ variant: "outline",
380
+ onClick: handleDownload,
381
+ disabled: filteredMessages.length === 0,
382
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-4 w-4" })
383
+ }
384
+ )
385
+ ] })
386
+ ] }) }),
387
+ /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardContent, { className: "space-y-4", children: [
388
+ showFilters && /* @__PURE__ */ jsxRuntime.jsx(
389
+ MessageFilters,
390
+ {
391
+ filters,
392
+ onFiltersChange: setFilters,
393
+ autoScroll,
394
+ onAutoScrollChange: setAutoScroll
395
+ }
396
+ ),
397
+ /* @__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: [
398
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-12 w-12 text-muted-foreground mb-4" }),
399
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: isPaused ? "Paused - Click play to resume" : "No messages yet" })
400
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: filteredMessages.map((msg) => /* @__PURE__ */ jsxRuntime.jsx(
401
+ "div",
402
+ {
403
+ className: "p-3 rounded border hover:bg-muted/50 transition-colors cursor-pointer",
404
+ onClick: () => onMessageClick?.(msg),
405
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
406
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 mt-0.5", children: getLevelIcon(msg.level) }),
407
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 space-y-1", children: [
408
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
409
+ /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: getLevelVariant(msg.level), className: "text-xs", children: msg.type }),
410
+ msg.channel && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "outline", className: "text-xs", children: msg.channel }),
411
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: msg.formattedTime })
412
+ ] }),
413
+ msg.message && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm break-words", children: msg.message }),
414
+ msg.formattedData && /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "text-xs", children: [
415
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { className: "cursor-pointer text-muted-foreground hover:text-foreground", children: "View data" }),
416
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "mt-2 p-2 bg-muted rounded overflow-x-auto", children: msg.formattedData })
417
+ ] })
418
+ ] })
419
+ ] })
420
+ },
421
+ msg.id
422
+ )) }) })
423
+ ] })
424
+ ] });
425
+ }
426
+ __name(MessagesFeed, "MessagesFeed");
427
+ var logger = getConsolaLogger("SubscriptionsList");
428
+ function SubscriptionsList({
429
+ showControls = true,
430
+ onSubscriptionClick,
431
+ className = ""
432
+ }) {
433
+ const { isConnected, client } = useCentrifugo();
434
+ const [subscriptions, setSubscriptions] = react.useState([]);
435
+ const updateSubscriptions = /* @__PURE__ */ __name(() => {
436
+ if (!client || !isConnected) {
437
+ setSubscriptions([]);
438
+ return;
439
+ }
440
+ try {
441
+ const centrifuge = client.getCentrifuge();
442
+ const subs = centrifuge.subscriptions();
443
+ const subscriptionsList = [];
444
+ for (const [channel, sub] of Object.entries(subs)) {
445
+ subscriptionsList.push({
446
+ channel,
447
+ state: sub.state
448
+ });
449
+ }
450
+ setSubscriptions(subscriptionsList);
451
+ } catch (error) {
452
+ logger.error("Failed to get subscriptions", error);
453
+ setSubscriptions([]);
454
+ }
455
+ }, "updateSubscriptions");
456
+ react.useEffect(() => {
457
+ updateSubscriptions();
458
+ if (!client) return;
459
+ const centrifuge = client.getCentrifuge();
460
+ const handleSubscribed = /* @__PURE__ */ __name(() => updateSubscriptions(), "handleSubscribed");
461
+ const handleUnsubscribed = /* @__PURE__ */ __name(() => updateSubscriptions(), "handleUnsubscribed");
462
+ centrifuge.on("subscribed", handleSubscribed);
463
+ centrifuge.on("unsubscribed", handleUnsubscribed);
464
+ const interval = setInterval(updateSubscriptions, 3e3);
465
+ return () => {
466
+ centrifuge.off("subscribed", handleSubscribed);
467
+ centrifuge.off("unsubscribed", handleUnsubscribed);
468
+ clearInterval(interval);
469
+ };
470
+ }, [client, isConnected]);
471
+ const handleUnsubscribe = /* @__PURE__ */ __name(async (channel) => {
472
+ if (!client) return;
473
+ try {
474
+ await client.unsubscribe(channel);
475
+ updateSubscriptions();
476
+ } catch (error) {
477
+ logger.error("Failed to unsubscribe", error);
478
+ }
479
+ }, "handleUnsubscribe");
480
+ return /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Card, { className, children: [
481
+ /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
482
+ /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardTitle, { className: "flex items-center gap-2", children: [
483
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Radio, { className: "h-5 w-5" }),
484
+ "Active Subscriptions",
485
+ /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "outline", children: subscriptions.length })
486
+ ] }),
487
+ showControls && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Button, { size: "sm", variant: "outline", onClick: updateSubscriptions, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-4 w-4" }) })
488
+ ] }) }),
489
+ /* @__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(
490
+ "div",
491
+ {
492
+ className: "flex items-center justify-between p-3 rounded border hover:bg-muted/50 transition-colors",
493
+ children: [
494
+ /* @__PURE__ */ jsxRuntime.jsx(
495
+ "div",
496
+ {
497
+ className: "flex-1 min-w-0 cursor-pointer",
498
+ onClick: () => onSubscriptionClick?.(sub.channel),
499
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
500
+ /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Badge, { variant: "outline", className: "text-xs", children: sub.state }),
501
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-mono truncate", children: sub.channel })
502
+ ] })
503
+ }
504
+ ),
505
+ showControls && /* @__PURE__ */ jsxRuntime.jsx(
506
+ uiNextjs.Button,
507
+ {
508
+ size: "sm",
509
+ variant: "ghost",
510
+ onClick: () => handleUnsubscribe(sub.channel),
511
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4 text-destructive" })
512
+ }
513
+ )
514
+ ]
515
+ },
516
+ sub.channel
517
+ )) }) }) })
518
+ ] });
519
+ }
520
+ __name(SubscriptionsList, "SubscriptionsList");
521
+ function CentrifugoMonitor({
522
+ variant = "full",
523
+ showConnectionStatus = true,
524
+ showMessagesFeed = true,
525
+ showSubscriptions = true,
526
+ showFilters = true,
527
+ showControls = true,
528
+ maxMessages = 100,
529
+ channels = [],
530
+ autoScroll = true,
531
+ onMessageClick,
532
+ onSubscriptionClick,
533
+ className = ""
534
+ }) {
535
+ if (variant === "minimal") {
536
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: /* @__PURE__ */ jsxRuntime.jsx(ConnectionStatus, { variant: "detailed", showUptime: true, showSubscriptions: true }) });
537
+ }
538
+ if (variant === "compact") {
539
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `space-y-4 ${className}`, children: [
540
+ showConnectionStatus && /* @__PURE__ */ jsxRuntime.jsx(ConnectionStatus, { variant: "detailed", showUptime: true, showSubscriptions: true }),
541
+ showMessagesFeed && /* @__PURE__ */ jsxRuntime.jsx(
542
+ MessagesFeed,
543
+ {
544
+ maxMessages,
545
+ showFilters: false,
546
+ showControls,
547
+ channels,
548
+ autoScroll,
549
+ onMessageClick
550
+ }
551
+ )
552
+ ] });
553
+ }
554
+ const tabsToShow = [
555
+ showConnectionStatus && { value: "connection", label: "Connection" },
556
+ showMessagesFeed && { value: "messages", label: "Messages" },
557
+ showSubscriptions && { value: "subscriptions", label: "Subscriptions" }
558
+ ].filter(Boolean);
559
+ if (tabsToShow.length === 0) {
560
+ return null;
561
+ }
562
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Tabs, { defaultValue: tabsToShow[0].value, children: [
563
+ /* @__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)) }),
564
+ showConnectionStatus && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.TabsContent, { value: "connection", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(ConnectionStatus, { variant: "detailed", showUptime: true, showSubscriptions: true }) }),
565
+ showMessagesFeed && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.TabsContent, { value: "messages", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
566
+ MessagesFeed,
567
+ {
568
+ maxMessages,
569
+ showFilters,
570
+ showControls,
571
+ channels,
572
+ autoScroll,
573
+ onMessageClick
574
+ }
575
+ ) }),
576
+ showSubscriptions && /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.TabsContent, { value: "subscriptions", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
577
+ SubscriptionsList,
578
+ {
579
+ showControls,
580
+ onSubscriptionClick
581
+ }
582
+ ) })
583
+ ] }) });
584
+ }
585
+ __name(CentrifugoMonitor, "CentrifugoMonitor");
586
+ function CentrifugoMonitorDialog() {
587
+ const [open, setOpen] = react.useState(false);
588
+ const [variant, setVariant] = react.useState("full");
589
+ hooks.useEventListener(
590
+ CENTRIFUGO_MONITOR_EVENTS.OPEN_MONITOR_DIALOG,
591
+ (payload) => {
592
+ if (payload?.variant) {
593
+ setVariant(payload.variant);
594
+ }
595
+ setOpen(true);
596
+ }
597
+ );
598
+ hooks.useEventListener(
599
+ CENTRIFUGO_MONITOR_EVENTS.CLOSE_MONITOR_DIALOG,
600
+ () => {
601
+ setOpen(false);
602
+ }
603
+ );
604
+ const handleClose = /* @__PURE__ */ __name(() => {
605
+ setOpen(false);
606
+ }, "handleClose");
607
+ return /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.Sheet, { open, onOpenChange: (isOpen) => !isOpen && handleClose(), children: /* @__PURE__ */ jsxRuntime.jsxs(
608
+ uiNextjs.SheetContent,
609
+ {
610
+ side: "right",
611
+ className: "max-w-2xl w-[90vw] sm:w-[672px]",
612
+ children: [
613
+ /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.SheetHeader, { children: [
614
+ /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.SheetTitle, { className: "flex items-center gap-2", children: [
615
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-5 w-5" }),
616
+ "Centrifugo Monitor"
617
+ ] }),
618
+ /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.SheetDescription, { children: "Real-time WebSocket monitoring and debugging" })
619
+ ] }),
620
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(CentrifugoMonitor, { variant }) })
621
+ ]
622
+ }
623
+ ) });
624
+ }
625
+ __name(CentrifugoMonitorDialog, "CentrifugoMonitorDialog");
626
+
627
+ // src/config.ts
628
+ process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
629
+ react.createContext(void 0);
630
+ var CentrifugoContext = react.createContext(void 0);
631
+ function useCentrifugo() {
632
+ const context = react.useContext(CentrifugoContext);
633
+ if (context === void 0) {
634
+ throw new Error("useCentrifugo must be used within a CentrifugoProvider");
635
+ }
636
+ return context;
637
+ }
638
+ __name(useCentrifugo, "useCentrifugo");
639
+ var logger2 = getConsolaLogger("ConnectionStatus");
640
+ function ConnectionStatus({
641
+ variant = "badge",
642
+ showUptime = false,
643
+ showSubscriptions = false,
644
+ className = ""
645
+ }) {
646
+ const { isConnected, client } = useCentrifugo();
647
+ const [connectionTime, setConnectionTime] = react.useState(null);
648
+ const [uptime, setUptime] = react.useState("");
649
+ const [activeSubscriptions, setActiveSubscriptions] = react.useState(0);
650
+ react.useEffect(() => {
651
+ if (isConnected && !connectionTime) {
652
+ setConnectionTime(moment__default.default.utc());
653
+ } else if (!isConnected) {
654
+ setConnectionTime(null);
655
+ }
656
+ }, [isConnected, connectionTime]);
657
+ react.useEffect(() => {
658
+ if (!isConnected || !connectionTime) {
659
+ setUptime("");
660
+ return;
661
+ }
662
+ const updateUptime = /* @__PURE__ */ __name(() => {
663
+ const now = moment__default.default.utc();
664
+ const duration = moment__default.default.duration(now.diff(connectionTime));
665
+ const hours = Math.floor(duration.asHours());
666
+ const minutes = duration.minutes();
667
+ const seconds = duration.seconds();
668
+ if (hours > 0) {
669
+ setUptime(`${hours}h ${minutes}m`);
670
+ } else if (minutes > 0) {
671
+ setUptime(`${minutes}m ${seconds}s`);
672
+ } else {
673
+ setUptime(`${seconds}s`);
674
+ }
675
+ }, "updateUptime");
676
+ updateUptime();
677
+ const interval = setInterval(updateUptime, 1e3);
678
+ return () => clearInterval(interval);
679
+ }, [isConnected, connectionTime]);
680
+ react.useEffect(() => {
681
+ if (!client || !isConnected) {
682
+ setActiveSubscriptions(0);
683
+ return;
684
+ }
685
+ const updateCount = /* @__PURE__ */ __name(() => {
686
+ try {
687
+ const centrifuge = client.getCentrifuge();
688
+ const subs = centrifuge.subscriptions();
689
+ setActiveSubscriptions(Object.keys(subs).length);
690
+ } catch (error) {
691
+ logger2.error("Failed to get active subscriptions", error);
692
+ }
693
+ }, "updateCount");
694
+ updateCount();
695
+ const interval = setInterval(updateCount, 2e3);
696
+ return () => clearInterval(interval);
697
+ }, [client, isConnected]);
698
+ if (variant === "badge") {
699
+ return /* @__PURE__ */ jsxRuntime.jsxs(
700
+ uiNextjs.Badge,
701
+ {
702
+ variant: isConnected ? "default" : "destructive",
703
+ className: `flex items-center gap-1 ${isConnected ? "animate-pulse" : ""} ${className}`,
704
+ children: [
705
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `h-2 w-2 rounded-full ${isConnected ? "bg-green-500" : "bg-red-500"}` }),
706
+ isConnected ? "Connected" : "Disconnected"
707
+ ]
708
+ }
709
+ );
710
+ }
711
+ if (variant === "inline") {
712
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex items-center gap-2 ${className}`, children: [
713
+ 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" }),
714
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: isConnected ? "Connected" : "Disconnected" }),
715
+ showUptime && uptime && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
716
+ "(",
717
+ uptime,
718
+ ")"
719
+ ] }),
720
+ showSubscriptions && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground flex items-center gap-1", children: [
721
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Radio, { className: "h-3 w-3" }),
722
+ activeSubscriptions
723
+ ] })
724
+ ] });
725
+ }
726
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `space-y-3 ${className}`, children: [
727
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
728
+ uiNextjs.Badge,
729
+ {
730
+ variant: isConnected ? "default" : "destructive",
731
+ className: `flex items-center gap-1 ${isConnected ? "animate-pulse" : ""}`,
732
+ children: [
733
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `h-2 w-2 rounded-full ${isConnected ? "bg-green-500" : "bg-red-500"}` }),
734
+ isConnected ? "Connected" : "Disconnected"
735
+ ]
736
+ }
737
+ ) }),
738
+ isConnected ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
739
+ showUptime && uptime && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs", children: [
740
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground flex items-center gap-1", children: [
741
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3 w-3" }),
742
+ "Uptime:"
743
+ ] }),
744
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono font-medium", children: uptime })
745
+ ] }),
746
+ showSubscriptions && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs", children: [
747
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground flex items-center gap-1", children: [
748
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Radio, { className: "h-3 w-3" }),
749
+ "Subscriptions:"
750
+ ] }),
751
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono font-medium", children: activeSubscriptions })
752
+ ] })
753
+ ] }) : /* @__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" })
754
+ ] });
755
+ }
756
+ __name(ConnectionStatus, "ConnectionStatus");
757
+ function ConnectionStatusCard({
758
+ showUptime = true,
759
+ showSubscriptions = true,
760
+ className = ""
761
+ }) {
762
+ const { isConnected } = useCentrifugo();
763
+ const statusColor = isConnected ? "border-green-500" : "border-red-500";
764
+ const handleClick = /* @__PURE__ */ __name(() => {
765
+ emitOpenMonitorDialog({ variant: "full" });
766
+ }, "handleClick");
767
+ return /* @__PURE__ */ jsxRuntime.jsxs(
768
+ uiNextjs.Card,
769
+ {
770
+ className: `${statusColor} ${className} cursor-pointer hover:shadow-lg transition-shadow`,
771
+ style: { borderLeftWidth: "4px" },
772
+ onClick: handleClick,
773
+ children: [
774
+ /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
775
+ /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardTitle, { className: "text-sm font-medium", children: "WebSocket" }),
776
+ /* @__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" }) })
777
+ ] }),
778
+ /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardContent, { children: /* @__PURE__ */ jsxRuntime.jsx(
779
+ ConnectionStatus,
780
+ {
781
+ variant: "detailed",
782
+ showUptime,
783
+ showSubscriptions
784
+ }
785
+ ) })
786
+ ]
787
+ }
788
+ );
789
+ }
790
+ __name(ConnectionStatusCard, "ConnectionStatusCard");
791
+ function CentrifugoMonitorFAB({
792
+ position = "bottom-left",
793
+ size = "md",
794
+ variant = "full"
795
+ }) {
796
+ const positionStyles = {
797
+ "bottom-left": { bottom: "1rem", left: "1rem" },
798
+ "bottom-right": { bottom: "1rem", right: "1rem" },
799
+ "top-left": { top: "1rem", left: "1rem" },
800
+ "top-right": { top: "1rem", right: "1rem" }
801
+ };
802
+ const sizeStyles = {
803
+ sm: { width: "48px", height: "48px" },
804
+ md: { width: "56px", height: "56px" },
805
+ lg: { width: "64px", height: "64px" }
806
+ };
807
+ const iconSizes = {
808
+ sm: "h-5 w-5",
809
+ md: "h-6 w-6",
810
+ lg: "h-7 w-7"
811
+ };
812
+ const handleClick = /* @__PURE__ */ __name(() => {
813
+ emitOpenMonitorDialog({ variant });
814
+ }, "handleClick");
815
+ return /* @__PURE__ */ jsxRuntime.jsx(
816
+ "button",
817
+ {
818
+ onClick: handleClick,
819
+ 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",
820
+ style: {
821
+ position: "fixed",
822
+ ...positionStyles[position],
823
+ ...sizeStyles[size],
824
+ zIndex: 9999
825
+ },
826
+ "aria-label": "Open Centrifugo Monitor",
827
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: iconSizes[size] })
828
+ }
829
+ );
830
+ }
831
+ __name(CentrifugoMonitorFAB, "CentrifugoMonitorFAB");
832
+ function CentrifugoMonitorWidget({
833
+ title = "WebSocket Monitor",
834
+ showExpandButton = true,
835
+ className = ""
836
+ }) {
837
+ const handleExpand = /* @__PURE__ */ __name(() => {
838
+ emitOpenMonitorDialog({ variant: "full" });
839
+ }, "handleExpand");
840
+ return /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.Card, { className, children: [
841
+ /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
842
+ /* @__PURE__ */ jsxRuntime.jsxs(uiNextjs.CardTitle, { className: "text-sm font-medium flex items-center gap-2", children: [
843
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-4 w-4" }),
844
+ title
845
+ ] }),
846
+ showExpandButton && /* @__PURE__ */ jsxRuntime.jsx(
847
+ uiNextjs.Button,
848
+ {
849
+ size: "sm",
850
+ variant: "ghost",
851
+ onClick: handleExpand,
852
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-4 w-4" })
853
+ }
854
+ )
855
+ ] }),
856
+ /* @__PURE__ */ jsxRuntime.jsx(uiNextjs.CardContent, { children: /* @__PURE__ */ jsxRuntime.jsx(ConnectionStatus, { variant: "detailed", showUptime: true, showSubscriptions: true }) })
857
+ ] });
858
+ }
859
+ __name(CentrifugoMonitorWidget, "CentrifugoMonitorWidget");
860
+
861
+ exports.CentrifugoMonitor = CentrifugoMonitor;
862
+ exports.CentrifugoMonitorDialog = CentrifugoMonitorDialog;
863
+ exports.CentrifugoMonitorFAB = CentrifugoMonitorFAB;
864
+ exports.CentrifugoMonitorWidget = CentrifugoMonitorWidget;
865
+ exports.ConnectionStatus = ConnectionStatus;
866
+ exports.ConnectionStatusCard = ConnectionStatusCard;
867
+ exports.MessageFilters = MessageFilters;
868
+ exports.MessagesFeed = MessagesFeed;
869
+ exports.SubscriptionsList = SubscriptionsList;
870
+ //# sourceMappingURL=components.cjs.map
871
+ //# sourceMappingURL=components.cjs.map