@djangocfg/centrifugo 1.0.1 → 1.0.2

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.
Files changed (34) hide show
  1. package/README.md +345 -34
  2. package/package.json +6 -4
  3. package/src/config.ts +1 -1
  4. package/src/core/client/CentrifugoRPCClient.ts +281 -0
  5. package/src/core/client/index.ts +5 -0
  6. package/src/core/index.ts +15 -0
  7. package/src/core/logger/LogsStore.ts +101 -0
  8. package/src/core/logger/createLogger.ts +79 -0
  9. package/src/core/logger/index.ts +9 -0
  10. package/src/core/types/index.ts +68 -0
  11. package/src/debug/ConnectionTab/ConnectionTab.tsx +160 -0
  12. package/src/debug/ConnectionTab/index.ts +5 -0
  13. package/src/debug/DebugPanel/DebugPanel.tsx +102 -0
  14. package/src/debug/DebugPanel/index.ts +5 -0
  15. package/src/debug/LogsTab/LogsTab.tsx +236 -0
  16. package/src/debug/LogsTab/index.ts +5 -0
  17. package/src/debug/SubscriptionsTab/SubscriptionsTab.tsx +135 -0
  18. package/src/debug/SubscriptionsTab/index.ts +5 -0
  19. package/src/debug/index.ts +11 -0
  20. package/src/hooks/index.ts +2 -5
  21. package/src/hooks/useSubscription.ts +66 -65
  22. package/src/index.ts +94 -13
  23. package/src/providers/CentrifugoProvider/CentrifugoProvider.tsx +381 -0
  24. package/src/providers/CentrifugoProvider/index.ts +6 -0
  25. package/src/providers/LogsProvider/LogsProvider.tsx +107 -0
  26. package/src/providers/LogsProvider/index.ts +6 -0
  27. package/src/providers/index.ts +9 -0
  28. package/API_GENERATOR.md +0 -253
  29. package/src/components/CentrifugoDebug.tsx +0 -182
  30. package/src/components/index.ts +0 -5
  31. package/src/context/CentrifugoProvider.tsx +0 -228
  32. package/src/context/index.ts +0 -5
  33. package/src/hooks/useLogger.ts +0 -69
  34. package/src/types/index.ts +0 -45
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Connection Tab
3
+ *
4
+ * Shows WebSocket connection status, uptime, and controls.
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import { Wifi, WifiOff, Clock, RefreshCw } from 'lucide-react';
10
+ import {
11
+ Card,
12
+ CardContent,
13
+ CardHeader,
14
+ CardTitle,
15
+ Badge,
16
+ Button,
17
+ Separator,
18
+ } from '@djangocfg/ui';
19
+ import { useCentrifugo } from '../../providers/CentrifugoProvider';
20
+
21
+ // ─────────────────────────────────────────────────────────────────────────
22
+ // Helper: Format uptime
23
+ // ─────────────────────────────────────────────────────────────────────────
24
+
25
+ function formatUptime(seconds: number): string {
26
+ if (seconds === 0) return '0s';
27
+
28
+ const hours = Math.floor(seconds / 3600);
29
+ const minutes = Math.floor((seconds % 3600) / 60);
30
+ const secs = seconds % 60;
31
+
32
+ if (hours > 0) {
33
+ return `${hours}h ${minutes}m ${secs}s`;
34
+ } else if (minutes > 0) {
35
+ return `${minutes}m ${secs}s`;
36
+ } else {
37
+ return `${secs}s`;
38
+ }
39
+ }
40
+
41
+ // ─────────────────────────────────────────────────────────────────────────
42
+ // Component
43
+ // ─────────────────────────────────────────────────────────────────────────
44
+
45
+ export function ConnectionTab() {
46
+ const {
47
+ isConnected,
48
+ isConnecting,
49
+ connectionState,
50
+ error,
51
+ uptime,
52
+ connect,
53
+ disconnect,
54
+ reconnect,
55
+ } = useCentrifugo();
56
+
57
+ const statusIcon = isConnected ? (
58
+ <Wifi className="h-5 w-5 text-green-600" />
59
+ ) : (
60
+ <WifiOff className="h-5 w-5 text-red-600" />
61
+ );
62
+
63
+ const statusBadge = isConnected ? (
64
+ <Badge variant="default" className="flex items-center gap-1">
65
+ <span className="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
66
+ Connected
67
+ </Badge>
68
+ ) : isConnecting ? (
69
+ <Badge variant="secondary" className="flex items-center gap-1">
70
+ <RefreshCw className="h-3 w-3 animate-spin" />
71
+ Connecting...
72
+ </Badge>
73
+ ) : (
74
+ <Badge variant="destructive">Disconnected</Badge>
75
+ );
76
+
77
+ return (
78
+ <div className="space-y-4">
79
+ {/* Status Card */}
80
+ <Card>
81
+ <CardHeader>
82
+ <CardTitle className="flex items-center justify-between">
83
+ <span className="flex items-center gap-2">
84
+ {statusIcon}
85
+ Connection Status
86
+ </span>
87
+ {statusBadge}
88
+ </CardTitle>
89
+ </CardHeader>
90
+ <CardContent className="space-y-4">
91
+ {/* State */}
92
+ <div className="flex justify-between text-sm">
93
+ <span className="text-muted-foreground">State:</span>
94
+ <span className="font-mono font-medium capitalize">{connectionState}</span>
95
+ </div>
96
+
97
+ {/* Uptime */}
98
+ {isConnected && (
99
+ <div className="flex justify-between text-sm">
100
+ <span className="text-muted-foreground">Uptime:</span>
101
+ <span className="font-mono font-medium flex items-center gap-1">
102
+ <Clock className="h-3 w-3" />
103
+ {formatUptime(uptime)}
104
+ </span>
105
+ </div>
106
+ )}
107
+
108
+ {/* Error */}
109
+ {error && (
110
+ <>
111
+ <Separator />
112
+ <div className="space-y-2">
113
+ <span className="text-sm text-muted-foreground">Error:</span>
114
+ <div className="text-xs text-red-600 bg-red-50 dark:bg-red-950/20 p-2 rounded">
115
+ {error.message}
116
+ </div>
117
+ </div>
118
+ </>
119
+ )}
120
+
121
+ {/* Controls */}
122
+ <Separator />
123
+ <div className="flex gap-2">
124
+ {isConnected ? (
125
+ <>
126
+ <Button
127
+ variant="destructive"
128
+ size="sm"
129
+ onClick={disconnect}
130
+ className="flex-1"
131
+ >
132
+ Disconnect
133
+ </Button>
134
+ <Button
135
+ variant="outline"
136
+ size="sm"
137
+ onClick={reconnect}
138
+ className="flex-1"
139
+ >
140
+ <RefreshCw className="h-3 w-3 mr-1" />
141
+ Reconnect
142
+ </Button>
143
+ </>
144
+ ) : (
145
+ <Button
146
+ variant="default"
147
+ size="sm"
148
+ onClick={connect}
149
+ disabled={isConnecting}
150
+ className="flex-1"
151
+ >
152
+ {isConnecting ? 'Connecting...' : 'Connect'}
153
+ </Button>
154
+ )}
155
+ </div>
156
+ </CardContent>
157
+ </Card>
158
+ </div>
159
+ );
160
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Connection Tab
3
+ */
4
+
5
+ export { ConnectionTab } from './ConnectionTab';
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Debug Panel
3
+ *
4
+ * Main debug UI with FAB button + Sheet modal + Tabs.
5
+ * Only visible in development mode.
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import { useState } from 'react';
11
+ import { Bug } from 'lucide-react';
12
+ import {
13
+ Sheet,
14
+ SheetContent,
15
+ SheetHeader,
16
+ SheetTitle,
17
+ SheetDescription,
18
+ Tabs,
19
+ TabsList,
20
+ TabsTrigger,
21
+ TabsContent,
22
+ } from '@djangocfg/ui';
23
+ import { ConnectionTab } from '../ConnectionTab';
24
+ import { LogsTab } from '../LogsTab';
25
+ import { SubscriptionsTab } from '../SubscriptionsTab';
26
+
27
+ // ─────────────────────────────────────────────────────────────────────────
28
+ // Config
29
+ // ─────────────────────────────────────────────────────────────────────────
30
+
31
+ const isDevelopment = process.env.NODE_ENV === 'development';
32
+ const isStaticBuild = process.env.NEXT_PHASE === 'phase-production-build';
33
+
34
+ const showDebugPanel = isDevelopment && !isStaticBuild;
35
+
36
+ // ─────────────────────────────────────────────────────────────────────────
37
+ // Component
38
+ // ─────────────────────────────────────────────────────────────────────────
39
+
40
+ export function DebugPanel() {
41
+ const [isOpen, setIsOpen] = useState(false);
42
+ const [activeTab, setActiveTab] = useState('connection');
43
+
44
+ // Don't render in production
45
+ if (!showDebugPanel) {
46
+ return null;
47
+ }
48
+
49
+ return (
50
+ <>
51
+ {/* FAB Button (fixed bottom-left) */}
52
+ <button
53
+ onClick={() => setIsOpen(true)}
54
+ className="rounded-full bg-primary text-primary-foreground shadow-lg hover:bg-primary/90 transition-all duration-200 flex items-center justify-center"
55
+ style={{
56
+ position: 'fixed',
57
+ bottom: '1rem',
58
+ left: '1rem',
59
+ width: '56px',
60
+ height: '56px',
61
+ zIndex: 9999,
62
+ }}
63
+ aria-label="Open Centrifugo Debug Panel"
64
+ >
65
+ <Bug className="h-6 w-6" />
66
+ </button>
67
+
68
+ {/* Sheet Modal */}
69
+ <Sheet open={isOpen} onOpenChange={setIsOpen}>
70
+ <SheetContent side="right" className="w-full sm:max-w-2xl">
71
+ <SheetHeader>
72
+ <SheetTitle>Centrifugo Debug</SheetTitle>
73
+ <SheetDescription>
74
+ WebSocket connection status, logs, and subscriptions
75
+ </SheetDescription>
76
+ </SheetHeader>
77
+
78
+ {/* Tabs */}
79
+ <Tabs value={activeTab} onValueChange={setActiveTab} className="mt-6">
80
+ <TabsList className="grid w-full grid-cols-3">
81
+ <TabsTrigger value="connection">Connection</TabsTrigger>
82
+ <TabsTrigger value="logs">Logs</TabsTrigger>
83
+ <TabsTrigger value="subscriptions">Subscriptions</TabsTrigger>
84
+ </TabsList>
85
+
86
+ <TabsContent value="connection" className="mt-4">
87
+ <ConnectionTab />
88
+ </TabsContent>
89
+
90
+ <TabsContent value="logs" className="mt-4">
91
+ <LogsTab />
92
+ </TabsContent>
93
+
94
+ <TabsContent value="subscriptions" className="mt-4">
95
+ <SubscriptionsTab />
96
+ </TabsContent>
97
+ </Tabs>
98
+ </SheetContent>
99
+ </Sheet>
100
+ </>
101
+ );
102
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Debug Panel
3
+ */
4
+
5
+ export { DebugPanel } from './DebugPanel';
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Logs Tab
3
+ *
4
+ * Bash-like logs viewer with filters, search, and auto-scroll.
5
+ * Uses PrettyCode for syntax highlighting.
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import { useRef, useEffect, useState } from 'react';
11
+ import { Trash2, Search, Filter } from 'lucide-react';
12
+ import moment from 'moment';
13
+ import {
14
+ Card,
15
+ CardContent,
16
+ CardHeader,
17
+ CardTitle,
18
+ Button,
19
+ Input,
20
+ Select,
21
+ SelectContent,
22
+ SelectItem,
23
+ SelectTrigger,
24
+ SelectValue,
25
+ ScrollArea,
26
+ Badge,
27
+ PrettyCode,
28
+ } from '@djangocfg/ui';
29
+ import { useLogs } from '../../providers/LogsProvider';
30
+ import type { LogLevel, LogEntry } from '../../core/types';
31
+
32
+ // ─────────────────────────────────────────────────────────────────────────
33
+ // Helpers
34
+ // ─────────────────────────────────────────────────────────────────────────
35
+
36
+ function formatTimestamp(date: Date): string {
37
+ return moment(date).format('HH:mm:ss.SSS');
38
+ }
39
+
40
+ function getLevelColor(level: LogLevel): string {
41
+ switch (level) {
42
+ case 'debug':
43
+ return 'text-blue-600 dark:text-blue-400';
44
+ case 'info':
45
+ return 'text-gray-600 dark:text-gray-400';
46
+ case 'success':
47
+ return 'text-green-600 dark:text-green-400';
48
+ case 'warning':
49
+ return 'text-yellow-600 dark:text-yellow-400';
50
+ case 'error':
51
+ return 'text-red-600 dark:text-red-400';
52
+ }
53
+ }
54
+
55
+ function getLevelBadgeVariant(level: LogLevel): 'default' | 'secondary' | 'destructive' | 'outline' {
56
+ switch (level) {
57
+ case 'error':
58
+ return 'destructive';
59
+ case 'warning':
60
+ return 'outline';
61
+ case 'success':
62
+ return 'default';
63
+ default:
64
+ return 'secondary';
65
+ }
66
+ }
67
+
68
+ // ─────────────────────────────────────────────────────────────────────────
69
+ // Component
70
+ // ─────────────────────────────────────────────────────────────────────────
71
+
72
+ export function LogsTab() {
73
+ const { filteredLogs, filter, setFilter, clearLogs, count } = useLogs();
74
+ const [search, setSearch] = useState('');
75
+ const [autoScroll, setAutoScroll] = useState(true);
76
+ const scrollRef = useRef<HTMLDivElement>(null);
77
+
78
+ // Auto-scroll to bottom when new logs arrive
79
+ useEffect(() => {
80
+ if (autoScroll && scrollRef.current) {
81
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
82
+ }
83
+ }, [filteredLogs, autoScroll]);
84
+
85
+ // Handle search with debounce
86
+ useEffect(() => {
87
+ const timeout = setTimeout(() => {
88
+ setFilter({ search: search || undefined });
89
+ }, 300);
90
+
91
+ return () => clearTimeout(timeout);
92
+ }, [search, setFilter]);
93
+
94
+ return (
95
+ <div className="space-y-4">
96
+ {/* Controls */}
97
+ <Card>
98
+ <CardHeader>
99
+ <CardTitle className="flex items-center justify-between">
100
+ <span className="flex items-center gap-2">
101
+ Logs
102
+ <Badge variant="secondary">{count}</Badge>
103
+ </span>
104
+ <div className="flex gap-2">
105
+ <Button
106
+ variant="outline"
107
+ size="sm"
108
+ onClick={() => setAutoScroll(!autoScroll)}
109
+ >
110
+ Auto-scroll: {autoScroll ? 'ON' : 'OFF'}
111
+ </Button>
112
+ <Button
113
+ variant="destructive"
114
+ size="sm"
115
+ onClick={clearLogs}
116
+ >
117
+ <Trash2 className="h-3 w-3 mr-1" />
118
+ Clear
119
+ </Button>
120
+ </div>
121
+ </CardTitle>
122
+ </CardHeader>
123
+ <CardContent className="space-y-3">
124
+ {/* Search */}
125
+ <div className="flex gap-2">
126
+ <div className="relative flex-1">
127
+ <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
128
+ <Input
129
+ placeholder="Search logs..."
130
+ value={search}
131
+ onChange={(e) => setSearch(e.target.value)}
132
+ className="pl-8"
133
+ />
134
+ </div>
135
+ </div>
136
+
137
+ {/* Filters */}
138
+ <div className="flex gap-2">
139
+ <Select
140
+ value={filter.level || 'all'}
141
+ onValueChange={(value) =>
142
+ setFilter({ level: value === 'all' ? undefined : (value as LogLevel) })
143
+ }
144
+ >
145
+ <SelectTrigger className="w-[140px]">
146
+ <Filter className="h-3 w-3 mr-1" />
147
+ <SelectValue placeholder="Level" />
148
+ </SelectTrigger>
149
+ <SelectContent>
150
+ <SelectItem value="all">All Levels</SelectItem>
151
+ <SelectItem value="debug">Debug</SelectItem>
152
+ <SelectItem value="info">Info</SelectItem>
153
+ <SelectItem value="success">Success</SelectItem>
154
+ <SelectItem value="warning">Warning</SelectItem>
155
+ <SelectItem value="error">Error</SelectItem>
156
+ </SelectContent>
157
+ </Select>
158
+
159
+ <Select
160
+ value={filter.source || 'all'}
161
+ onValueChange={(value) =>
162
+ setFilter({ source: value === 'all' ? undefined : (value as LogEntry['source']) })
163
+ }
164
+ >
165
+ <SelectTrigger className="w-[140px]">
166
+ <Filter className="h-3 w-3 mr-1" />
167
+ <SelectValue placeholder="Source" />
168
+ </SelectTrigger>
169
+ <SelectContent>
170
+ <SelectItem value="all">All Sources</SelectItem>
171
+ <SelectItem value="client">Client</SelectItem>
172
+ <SelectItem value="provider">Provider</SelectItem>
173
+ <SelectItem value="subscription">Subscription</SelectItem>
174
+ <SelectItem value="system">System</SelectItem>
175
+ </SelectContent>
176
+ </Select>
177
+ </div>
178
+ </CardContent>
179
+ </Card>
180
+
181
+ {/* Logs Viewer (Bash-like) */}
182
+ <Card>
183
+ <CardContent className="p-0">
184
+ <ScrollArea ref={scrollRef} className="h-[400px] w-full">
185
+ <div className="p-4 font-mono text-xs space-y-1 bg-slate-950 text-slate-50">
186
+ {filteredLogs.length === 0 ? (
187
+ <div className="text-slate-500 text-center py-8">
188
+ No logs to display
189
+ </div>
190
+ ) : (
191
+ filteredLogs.map((log) => (
192
+ <div key={log.id} className="flex gap-2 hover:bg-slate-900 px-1 py-0.5 rounded">
193
+ {/* Timestamp */}
194
+ <span className="text-slate-500 shrink-0">
195
+ [{formatTimestamp(log.timestamp)}]
196
+ </span>
197
+
198
+ {/* Level */}
199
+ <span className={`${getLevelColor(log.level)} font-bold uppercase shrink-0 w-16`}>
200
+ {log.level}
201
+ </span>
202
+
203
+ {/* Source */}
204
+ <span className="text-blue-400 shrink-0 w-24">
205
+ [{log.source}]
206
+ </span>
207
+
208
+ {/* Message */}
209
+ <span className="text-slate-200">{log.message}</span>
210
+
211
+ {/* Data (if present) */}
212
+ {log.data && (
213
+ <details className="text-slate-400 cursor-pointer">
214
+ <summary className="inline">
215
+ <Badge variant="outline" className="text-xs ml-2">
216
+ data
217
+ </Badge>
218
+ </summary>
219
+ <div className="mt-2 ml-4">
220
+ <PrettyCode
221
+ data={typeof log.data === 'object' ? log.data : { value: log.data }}
222
+ language="json"
223
+ />
224
+ </div>
225
+ </details>
226
+ )}
227
+ </div>
228
+ ))
229
+ )}
230
+ </div>
231
+ </ScrollArea>
232
+ </CardContent>
233
+ </Card>
234
+ </div>
235
+ );
236
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Logs Tab
3
+ */
4
+
5
+ export { LogsTab } from './LogsTab';
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Subscriptions Tab
3
+ *
4
+ * Shows active channel subscriptions with controls.
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import { Fragment } from 'react';
10
+ import { Radio, Trash2 } from 'lucide-react';
11
+ import {
12
+ Card,
13
+ CardContent,
14
+ CardHeader,
15
+ CardTitle,
16
+ Badge,
17
+ Button,
18
+ ScrollArea,
19
+ Separator,
20
+ } from '@djangocfg/ui';
21
+ import { useCentrifugo } from '../../providers/CentrifugoProvider';
22
+
23
+ // ─────────────────────────────────────────────────────────────────────────
24
+ // Helper: Format subscription time
25
+ // ─────────────────────────────────────────────────────────────────────────
26
+
27
+ function formatSubscribedTime(timestamp: number): string {
28
+ const now = Date.now();
29
+ const diff = Math.floor((now - timestamp) / 1000);
30
+
31
+ if (diff < 60) {
32
+ return `${diff}s ago`;
33
+ } else if (diff < 3600) {
34
+ return `${Math.floor(diff / 60)}m ago`;
35
+ } else if (diff < 86400) {
36
+ return `${Math.floor(diff / 3600)}h ago`;
37
+ } else {
38
+ return `${Math.floor(diff / 86400)}d ago`;
39
+ }
40
+ }
41
+
42
+ // ─────────────────────────────────────────────────────────────────────────
43
+ // Component
44
+ // ─────────────────────────────────────────────────────────────────────────
45
+
46
+ export function SubscriptionsTab() {
47
+ const { activeSubscriptions, unsubscribe } = useCentrifugo();
48
+
49
+ const handleUnsubscribe = (channel: string) => {
50
+ unsubscribe(channel);
51
+ };
52
+
53
+ return (
54
+ <div className="space-y-4">
55
+ {/* Header Card */}
56
+ <Card>
57
+ <CardHeader>
58
+ <CardTitle className="flex items-center justify-between">
59
+ <span className="flex items-center gap-2">
60
+ <Radio className="h-5 w-5" />
61
+ Active Subscriptions
62
+ </span>
63
+ <Badge variant="secondary">{activeSubscriptions.length}</Badge>
64
+ </CardTitle>
65
+ </CardHeader>
66
+ </Card>
67
+
68
+ {/* Subscriptions List */}
69
+ <Card>
70
+ <CardContent className="p-0">
71
+ {activeSubscriptions.length === 0 ? (
72
+ <div className="p-8 text-center text-muted-foreground">
73
+ No active subscriptions
74
+ </div>
75
+ ) : (
76
+ <ScrollArea className="h-[450px]">
77
+ <div className="p-4 space-y-3">
78
+ {activeSubscriptions.map((sub, index) => (
79
+ <Fragment key={sub.channel}>
80
+ {index > 0 && <Separator />}
81
+ <div className="flex items-start justify-between gap-4 p-3 rounded-lg hover:bg-muted/50 transition-colors">
82
+ <div className="flex-1 space-y-2">
83
+ {/* Channel Name */}
84
+ <div className="flex items-center gap-2">
85
+ <Radio className="h-4 w-4 text-green-600 animate-pulse" />
86
+ <span className="font-mono text-sm font-medium">
87
+ {sub.channel}
88
+ </span>
89
+ </div>
90
+
91
+ {/* Metadata */}
92
+ <div className="flex gap-3 text-xs text-muted-foreground">
93
+ <span className="flex items-center gap-1">
94
+ <Badge variant="outline" className="text-xs">
95
+ {sub.type}
96
+ </Badge>
97
+ </span>
98
+ <span>
99
+ Subscribed: {formatSubscribedTime(sub.subscribedAt)}
100
+ </span>
101
+ </div>
102
+
103
+ {/* Additional Info */}
104
+ {sub.data && (
105
+ <div className="text-xs text-muted-foreground">
106
+ <details className="cursor-pointer">
107
+ <summary className="inline">View data</summary>
108
+ <pre className="mt-2 p-2 bg-muted rounded text-xs overflow-auto">
109
+ {JSON.stringify(sub.data, null, 2)}
110
+ </pre>
111
+ </details>
112
+ </div>
113
+ )}
114
+ </div>
115
+
116
+ {/* Unsubscribe Button */}
117
+ <Button
118
+ variant="ghost"
119
+ size="sm"
120
+ onClick={() => handleUnsubscribe(sub.channel)}
121
+ className="shrink-0"
122
+ >
123
+ <Trash2 className="h-3 w-3 text-red-600" />
124
+ </Button>
125
+ </div>
126
+ </Fragment>
127
+ ))}
128
+ </div>
129
+ </ScrollArea>
130
+ )}
131
+ </CardContent>
132
+ </Card>
133
+ </div>
134
+ );
135
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Subscriptions Tab
3
+ */
4
+
5
+ export { SubscriptionsTab } from './SubscriptionsTab';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Debug Components
3
+ *
4
+ * Development-only debug UI for monitoring Centrifugo connections,
5
+ * logs, and subscriptions.
6
+ */
7
+
8
+ export { DebugPanel } from './DebugPanel';
9
+ export { ConnectionTab } from './ConnectionTab';
10
+ export { LogsTab } from './LogsTab';
11
+ export { SubscriptionsTab } from './SubscriptionsTab';
@@ -1,9 +1,6 @@
1
1
  /**
2
- * Centrifugo Hooks
2
+ * Hooks Module
3
3
  */
4
4
 
5
- export { useCentrifugoLogger, useRPCLogger } from './useLogger';
6
- export type { CentrifugoLogger, RPCLogger } from './useLogger';
7
-
8
5
  export { useSubscription } from './useSubscription';
9
- export type { useSubscriptionOptions } from './useSubscription';
6
+ export type { UseSubscriptionOptions, UseSubscriptionResult } from './useSubscription';