@djangocfg/centrifugo 2.1.231 → 2.1.233

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 (2) hide show
  1. package/README.md +46 -1387
  2. package/package.json +10 -10
package/README.md CHANGED
@@ -1,3 +1,9 @@
1
+ <div align="center">
2
+
3
+ ![@djangocfg/centrifugo](https://raw.githubusercontent.com/markolofsen/assets/main/libs/djangocfg/centrifugo.webp)
4
+
5
+ </div>
6
+
1
7
  # @djangocfg/centrifugo
2
8
 
3
9
  > Production-ready Centrifugo WebSocket client for React with real-time subscriptions, RPC patterns, and connection state management
@@ -5,73 +11,9 @@
5
11
  [![npm version](https://img.shields.io/npm/v/@djangocfg/centrifugo.svg)](https://www.npmjs.com/package/@djangocfg/centrifugo)
6
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
13
 
8
- **Part of [DjangoCFG](https://djangocfg.com)** — a modern Django framework for building production-ready SaaS applications. All `@djangocfg/*` packages are designed to work together, providing type-safe configuration, real-time features, and beautiful admin interfaces out of the box.
9
-
10
- ---
11
-
12
- Professional Centrifugo WebSocket client with React integration, composable UI components, and comprehensive monitoring tools.
13
-
14
- ## Features
15
-
16
- - 🔌 **Robust WebSocket Connection** - Auto-reconnect, error handling, and connection state management
17
- - 👁️ **Page Visibility Handling** - Auto-reconnect when tab becomes active, pause reconnects when hidden (saves battery)
18
- - 🔄 **RPC Pattern Support** - Request-response via correlation ID for synchronous-like communication
19
- - 📊 **Advanced Logging System** - Circular buffer with dual output (consola + in-memory accumulation)
20
- - 🧩 **Composable UI Components** - Flexible, reusable components for any use case
21
- - 🐛 **Monitoring Tools** - Real-time connection status, message feed, and subscriptions list
22
- - ⚛️ **React Integration** - Context providers and hooks for seamless integration
23
- - 🎯 **Type-Safe** - Full TypeScript support with proper Centrifuge types
24
- - 🏗️ **Platform-Agnostic Core** - Core modules can be used without React
25
- - 🎨 **Beautiful UI** - Pre-built components using shadcn/ui
26
- - 📅 **Moment.js Integration** - Consistent UTC time handling throughout
14
+ **Part of [DjangoCFG](https://djangocfg.com)** — a modern Django framework for building production-ready SaaS applications.
27
15
 
28
- ## Architecture
29
-
30
- ```
31
- src/
32
- ├── core/ # Platform-agnostic (no React dependencies)
33
- │ ├── client/ # CentrifugoRPCClient - WebSocket client
34
- │ │ ├── CentrifugoRPCClient.ts # Main facade (~165 lines)
35
- │ │ ├── connection.ts # Connection lifecycle
36
- │ │ ├── subscriptions.ts # Channel subscriptions
37
- │ │ ├── rpc.ts # RPC methods (namedRPC, namedRPCWithRetry, namedRPCNoWait)
38
- │ │ ├── version.ts # API version checking
39
- │ │ ├── types.ts # Type definitions
40
- │ │ └── index.ts # Exports
41
- │ ├── errors/ # Error handling with retry logic
42
- │ │ ├── RPCError.ts # Typed RPC errors (isRetryable, userMessage)
43
- │ │ └── RPCRetryHandler.ts # Exponential backoff retry
44
- │ ├── logger/ # Logging system with circular buffer
45
- │ │ ├── createLogger.ts # Logger factory (supports string prefix)
46
- │ │ └── LogsStore.ts # In-memory logs accumulation
47
- │ └── types/ # TypeScript type definitions
48
- ├── events.ts # Unified event system (single 'centrifugo' event)
49
- ├── providers/ # React Context providers
50
- │ ├── CentrifugoProvider/ # Main connection provider (no auto-FAB)
51
- │ └── LogsProvider/ # Logs accumulation provider
52
- ├── hooks/ # React hooks
53
- │ ├── useSubscription.ts # Channel subscription hook
54
- │ ├── useRPC.ts # RPC request-response hook
55
- │ ├── useNamedRPC.ts # Native Centrifugo RPC hook
56
- │ └── usePageVisibility.ts # Browser tab visibility tracking
57
- └── components/ # Composable UI components
58
- ├── ConnectionStatus/ # Connection status display
59
- │ ├── ConnectionStatus.tsx # Badge/inline/detailed variants
60
- │ └── ConnectionStatusCard.tsx # Card wrapper for dashboards
61
- ├── MessagesFeed/ # Real-time message feed
62
- │ ├── MessagesFeed.tsx # Main feed component
63
- │ ├── MessageFilters.tsx # Filtering controls
64
- │ └── types.ts # Message types
65
- ├── SubscriptionsList/ # Active subscriptions list
66
- │ └── SubscriptionsList.tsx # List with controls
67
- └── CentrifugoMonitor/ # Main monitoring component
68
- ├── CentrifugoMonitor.tsx # Composable monitor
69
- ├── CentrifugoMonitorDialog.tsx # Modal variant
70
- ├── CentrifugoMonitorFAB.tsx # FAB variant (manual)
71
- └── CentrifugoMonitorWidget.tsx # Dashboard widget
72
- ```
73
-
74
- ## Installation
16
+ ## Install
75
17
 
76
18
  ```bash
77
19
  pnpm add @djangocfg/centrifugo moment
@@ -84,49 +26,16 @@ pnpm add @djangocfg/centrifugo moment
84
26
  ```tsx
85
27
  import { CentrifugoProvider } from '@djangocfg/centrifugo';
86
28
 
87
- function App() {
88
- return (
89
- <CentrifugoProvider
90
- enabled={true}
91
- autoConnect={true}
92
- >
93
- <YourApp />
94
- </CentrifugoProvider>
95
- );
96
- }
97
- ```
98
-
99
- ### 2. Add Monitoring FAB (Optional)
100
-
101
- The FAB is **not** automatically included. You control when and where to show it:
102
-
103
- ```tsx
104
- import { CentrifugoProvider, CentrifugoMonitorFAB } from '@djangocfg/centrifugo';
105
- import { useAuth } from '@djangocfg/layouts';
106
-
107
- function CentrifugoMonitor() {
108
- const { isAdminUser } = useAuth();
109
- const isDevelopment = process.env.NODE_ENV === 'development';
110
-
111
- // Show FAB only for admins or in development
112
- if (!isDevelopment && !isAdminUser) {
113
- return null;
114
- }
115
-
116
- return <CentrifugoMonitorFAB variant="full" />;
117
- }
118
-
119
29
  function App() {
120
30
  return (
121
31
  <CentrifugoProvider enabled={true} autoConnect={true}>
122
32
  <YourApp />
123
- <CentrifugoMonitor />
124
33
  </CentrifugoProvider>
125
34
  );
126
35
  }
127
36
  ```
128
37
 
129
- ### 3. Use the connection in your components
38
+ ### 2. Use the connection
130
39
 
131
40
  ```tsx
132
41
  import { useCentrifugo } from '@djangocfg/centrifugo';
@@ -134,1333 +43,83 @@ import { useCentrifugo } from '@djangocfg/centrifugo';
134
43
  function YourComponent() {
135
44
  const { isConnected, connectionState, uptime } = useCentrifugo();
136
45
 
137
- return (
138
- <div>
139
- <p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
140
- <p>State: {connectionState}</p>
141
- <p>Uptime: {uptime}s</p>
142
- </div>
143
- );
46
+ return <p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>;
144
47
  }
145
48
  ```
146
49
 
147
- ### 4. Subscribe to channels
50
+ ### 3. Subscribe to channels
148
51
 
149
52
  ```tsx
150
53
  import { useSubscription } from '@djangocfg/centrifugo';
151
54
 
152
- function NotificationsComponent() {
55
+ function Notifications() {
153
56
  const { data, isSubscribed } = useSubscription({
154
57
  channel: 'notifications',
155
58
  enabled: true,
156
- onPublication: (data) => {
157
- console.log('New notification:', data);
158
- },
159
- onError: (error) => {
160
- console.error('Subscription error:', error);
161
- },
59
+ onPublication: (data) => console.log('New:', data),
162
60
  });
163
61
 
164
- return (
165
- <div>
166
- <p>Subscribed: {isSubscribed ? 'Yes' : 'No'}</p>
167
- {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
168
- </div>
169
- );
62
+ return <p>Subscribed: {isSubscribed ? 'Yes' : 'No'}</p>;
170
63
  }
171
64
  ```
172
65
 
173
- ## Core APIs
174
-
175
- ### CentrifugoProvider
176
-
177
- Main provider that manages the WebSocket connection.
178
-
179
- **Props:**
180
- - `enabled?: boolean` - Enable/disable the connection (default: `false`)
181
- - `url?: string` - WebSocket URL (falls back to user.centrifugo.centrifugo_url)
182
- - `autoConnect?: boolean` - Auto-connect when authenticated (default: `true`)
183
-
184
- **Context Value:**
185
- ```typescript
186
- interface CentrifugoContextValue {
187
- // Client
188
- client: CentrifugoRPCClient | null;
189
-
190
- // Connection State
191
- isConnected: boolean;
192
- isConnecting: boolean;
193
- error: Error | null;
194
- connectionState: 'disconnected' | 'connecting' | 'connected' | 'error';
195
-
196
- // Connection Info
197
- uptime: number; // seconds
198
- subscriptions: string[];
199
- activeSubscriptions: ActiveSubscription[];
200
-
201
- // Controls
202
- connect: () => Promise<void>;
203
- disconnect: () => void;
204
- reconnect: () => Promise<void>;
205
- unsubscribe: (channel: string) => void;
206
-
207
- // Config
208
- enabled: boolean;
209
- }
210
- ```
211
-
212
- ### useCentrifugo()
213
-
214
- Hook to access the Centrifugo connection context.
215
-
216
- ```tsx
217
- const {
218
- client,
219
- isConnected,
220
- connectionState,
221
- connect,
222
- disconnect,
223
- reconnect,
224
- } = useCentrifugo();
225
- ```
226
-
227
- ### useSubscription()
228
-
229
- Hook to subscribe to a channel with auto-cleanup.
230
-
231
- ```tsx
232
- const { data, isSubscribed, error } = useSubscription({
233
- channel: 'my-channel',
234
- enabled: true, // optional
235
- onPublication: (data) => {
236
- // Handle new data
237
- },
238
- onError: (error) => {
239
- // Handle error
240
- },
241
- });
242
- ```
243
-
244
- **Options:**
245
- ```typescript
246
- interface UseSubscriptionOptions<T> {
247
- channel: string;
248
- enabled?: boolean;
249
- onPublication?: (data: T) => void;
250
- onError?: (error: Error) => void;
251
- }
252
- ```
253
-
254
- ### useRPC()
255
-
256
- Hook for making RPC calls using correlation ID pattern.
257
-
258
- **What is RPC Pattern?**
259
-
260
- Centrifugo is pub/sub (fire-and-forget) and doesn't support RPC natively. We implement request-response pattern using **correlation ID** to match requests with responses:
261
-
262
- ```
263
- 1. Client generates unique correlation_id
264
- 2. Client subscribes to personal reply channel (user#{userId})
265
- 3. Client publishes request with correlation_id to rpc#{method}
266
- 4. Backend processes and publishes response with same correlation_id
267
- 5. Client matches response by correlation_id and resolves Promise
268
- ```
269
-
270
- **Basic Usage:**
271
-
272
- ```tsx
273
- import { useRPC } from '@djangocfg/centrifugo';
274
-
275
- function MyComponent() {
276
- const { call, isLoading, error } = useRPC();
277
-
278
- const handleGetStats = async () => {
279
- try {
280
- const result = await call('tasks.get_stats', { bot_id: '123' });
281
- console.log('Stats:', result);
282
- } catch (error) {
283
- console.error('RPC failed:', error);
284
- }
285
- };
286
-
287
- return (
288
- <button onClick={handleGetStats} disabled={isLoading}>
289
- {isLoading ? 'Loading...' : 'Get Stats'}
290
- </button>
291
- );
292
- }
293
- ```
294
-
295
- **With Type Safety:**
296
-
297
- ```tsx
298
- interface GetStatsRequest {
299
- bot_id: string;
300
- }
301
-
302
- interface GetStatsResponse {
303
- total_tasks: number;
304
- completed: number;
305
- failed: number;
306
- }
307
-
308
- const { call } = useRPC();
309
-
310
- const result = await call<GetStatsRequest, GetStatsResponse>(
311
- 'tasks.get_stats',
312
- { bot_id: '123' }
313
- );
314
-
315
- console.log(result.total_tasks); // Type-safe!
316
- ```
317
-
318
- **With Custom Options:**
319
-
320
- ```tsx
321
- const { call, isLoading, error, reset } = useRPC({
322
- timeout: 30000, // 30 seconds
323
- onError: (error) => {
324
- toast.error(`RPC failed: ${error.message}`);
325
- },
326
- });
327
-
328
- // Reset state
329
- reset();
330
- ```
331
-
332
- **Options:**
333
- ```typescript
334
- interface UseRPCOptions {
335
- timeout?: number; // Default: 10000ms
336
- replyChannel?: string; // Default: user#{userId}
337
- onError?: (error: Error) => void;
338
- }
339
-
340
- interface UseRPCResult {
341
- call: <TRequest, TResponse>(
342
- method: string,
343
- params: TRequest,
344
- options?: UseRPCOptions
345
- ) => Promise<TResponse>;
346
- isLoading: boolean;
347
- error: Error | null;
348
- reset: () => void;
349
- }
350
- ```
351
-
352
- ### useNamedRPC() - Native Centrifugo RPC
353
-
354
- Hook for making **native Centrifugo RPC calls** via RPC proxy.
355
-
356
- **This is the recommended approach** for request-response patterns. It uses Centrifugo's built-in RPC mechanism which proxies requests to Django.
357
-
358
- **Flow:**
359
- ```
360
- 1. Client calls namedRPC('terminal.input', data)
361
- 2. Centrifuge.js sends RPC over WebSocket
362
- 3. Centrifugo proxies to Django: POST /centrifugo/rpc/
363
- 4. Django routes to @websocket_rpc handler
364
- 5. Response returned to client
365
- ```
366
-
367
- **Basic Usage:**
66
+ ### 4. Make RPC calls
368
67
 
369
68
  ```tsx
370
69
  import { useNamedRPC } from '@djangocfg/centrifugo';
371
70
 
372
- function TerminalInput() {
373
- const { call, isLoading, error } = useNamedRPC();
374
-
375
- const handleSendInput = async (input: string) => {
376
- try {
377
- const result = await call('terminal.input', {
378
- session_id: 'abc-123',
379
- data: btoa(input) // Base64 encode
380
- });
381
- console.log('Result:', result);
382
- } catch (error) {
383
- console.error('RPC failed:', error);
384
- }
385
- };
386
-
387
- return (
388
- <button onClick={() => handleSendInput('ls -la')} disabled={isLoading}>
389
- {isLoading ? 'Sending...' : 'Send Command'}
390
- </button>
391
- );
392
- }
393
- ```
394
-
395
- **With Type Safety:**
396
-
397
- ```tsx
398
- interface TerminalInputRequest {
399
- session_id: string;
400
- data: string; // Base64 encoded
401
- }
402
-
403
- interface TerminalInputResponse {
404
- success: boolean;
405
- message?: string;
406
- }
407
-
408
- const { call } = useNamedRPC();
409
-
410
- const result = await call<TerminalInputRequest, TerminalInputResponse>(
411
- 'terminal.input',
412
- { session_id: 'abc-123', data: btoa('ls -la') }
413
- );
414
-
415
- console.log(result.success); // Type-safe!
416
- ```
417
-
418
- **Backend Handler:**
419
-
420
- ```python
421
- # Django backend with @websocket_rpc decorator
422
- from pydantic import BaseModel, Field
423
- from django_cfg.apps.integrations.centrifugo.decorators import websocket_rpc
71
+ function Terminal() {
72
+ const { call, isLoading } = useNamedRPC();
424
73
 
425
- class TerminalInputParams(BaseModel):
426
- session_id: str = Field(..., description="Session UUID")
427
- data: str = Field(..., description="Base64 encoded input")
428
-
429
- class SuccessResult(BaseModel):
430
- success: bool
431
- message: str = ""
432
-
433
- @websocket_rpc("terminal.input")
434
- async def terminal_input(conn, params: TerminalInputParams) -> SuccessResult:
435
- """Handle terminal input from browser."""
436
- # Forward to gRPC/Electron terminal
437
- await forward_to_terminal(params.session_id, base64.b64decode(params.data))
438
- return SuccessResult(success=True, message="Input sent")
439
- ```
440
-
441
- **Options:**
442
- ```typescript
443
- interface UseNamedRPCOptions {
444
- onError?: (error: Error) => void;
445
- }
446
-
447
- interface UseNamedRPCResult {
448
- call: <TRequest, TResponse>(
449
- method: string,
450
- data: TRequest
451
- ) => Promise<TResponse>;
452
- isLoading: boolean;
453
- error: Error | null;
454
- reset: () => void;
455
- }
456
- ```
457
-
458
- > **Note:** `useNamedRPC` uses native Centrifugo RPC which requires RPC proxy to be configured in Centrifugo server. See the [Setup Guide](https://djangocfg.com/docs/features/integrations/centrifugo/client-generation/) for configuration details.
459
-
460
- ### usePageVisibility()
461
-
462
- Hook for tracking browser tab visibility. Built into `CentrifugoProvider` for automatic reconnection handling.
463
-
464
- **Built-in Behavior (CentrifugoProvider):**
465
-
466
- The provider automatically handles visibility changes:
467
- - **Tab hidden**: Pauses reconnect attempts (saves battery)
468
- - **Tab visible**: Triggers reconnect if connection was lost
469
-
470
- **Standalone Usage:**
471
-
472
- ```tsx
473
- import { usePageVisibility } from '@djangocfg/centrifugo';
474
-
475
- function MyComponent() {
476
- const { isVisible, wasHidden, hiddenDuration } = usePageVisibility({
477
- onVisible: () => {
478
- console.log('Tab is now visible');
479
- // Refresh data, resume animations, etc.
480
- },
481
- onHidden: () => {
482
- console.log('Tab is now hidden');
483
- // Pause expensive operations
484
- },
485
- onChange: (isVisible) => {
486
- console.log('Visibility changed:', isVisible);
487
- },
488
- });
489
-
490
- return (
491
- <div>
492
- <p>Tab visible: {isVisible ? 'Yes' : 'No'}</p>
493
- <p>Was hidden: {wasHidden ? 'Yes' : 'No'}</p>
494
- {hiddenDuration > 0 && (
495
- <p>Was hidden for: {Math.round(hiddenDuration / 1000)}s</p>
496
- )}
497
- </div>
498
- );
499
- }
500
- ```
501
-
502
- **Return Value:**
503
- ```typescript
504
- interface UsePageVisibilityResult {
505
- /** Whether the page is currently visible */
506
- isVisible: boolean;
507
- /** Whether the page was ever hidden during this session */
508
- wasHidden: boolean;
509
- /** Timestamp when page became visible */
510
- visibleSince: number | null;
511
- /** How long the page was hidden (ms) */
512
- hiddenDuration: number;
513
- /** Force check visibility state */
514
- checkVisibility: () => boolean;
515
- }
516
- ```
517
-
518
- **Options:**
519
- ```typescript
520
- interface UsePageVisibilityOptions {
521
- /** Callback when page becomes visible */
522
- onVisible?: () => void;
523
- /** Callback when page becomes hidden */
524
- onHidden?: () => void;
525
- /** Callback with visibility state change */
526
- onChange?: (isVisible: boolean) => void;
527
- }
528
- ```
529
-
530
- **Use Cases:**
531
- - Pause/resume WebSocket reconnection attempts
532
- - Refresh stale data when tab becomes active
533
- - Pause expensive animations or timers
534
- - Track user engagement metrics
535
-
536
- ### namedRPCNoWait() - Fire-and-Forget RPC
537
-
538
- For latency-sensitive operations (like terminal input), use the fire-and-forget variant that returns immediately without waiting for a response.
539
-
540
- **When to use:**
541
- - Terminal input (user typing)
542
- - Real-time cursor position updates
543
- - Any operation where you don't need the response
544
-
545
- **Direct Client Usage:**
546
-
547
- ```tsx
548
- import { useCentrifugo } from '@djangocfg/centrifugo';
549
-
550
- function TerminalInput() {
551
- const { client } = useCentrifugo();
552
-
553
- const handleKeyPress = (key: string) => {
554
- // Fire-and-forget: returns immediately, doesn't wait for response
555
- client?.namedRPCNoWait('terminal.input', {
74
+ const send = async (input: string) => {
75
+ const result = await call('terminal.input', {
556
76
  session_id: 'abc-123',
557
- data: btoa(key)
77
+ data: btoa(input),
558
78
  });
559
79
  };
560
-
561
- return <input onKeyPress={(e) => handleKeyPress(e.key)} />;
562
- }
563
- ```
564
-
565
- **Backend Handler (Django):**
566
-
567
- ```python
568
- @websocket_rpc("terminal.input", no_wait=True)
569
- async def terminal_input(conn, params: TerminalInputParams) -> SuccessResult:
570
- """Handle terminal input (fire-and-forget)."""
571
- import asyncio
572
-
573
- # Spawn background task, return immediately
574
- asyncio.create_task(_send_input_to_agent(params.session_id, params.data))
575
-
576
- return SuccessResult(success=True, message="Input queued")
577
- ```
578
-
579
- **Retry Logic with Exponential Backoff:**
580
-
581
- `namedRPCNoWait` includes automatic retry with exponential backoff:
582
-
583
- ```tsx
584
- // Default: 3 retries, 100ms base delay, 2000ms max delay
585
- client?.namedRPCNoWait('terminal.input', { session_id, data });
586
-
587
- // Custom retry options
588
- client?.namedRPCNoWait('terminal.input', { session_id, data }, {
589
- maxRetries: 5, // Max retry attempts (default: 3)
590
- baseDelayMs: 100, // Base delay for exponential backoff (default: 100)
591
- maxDelayMs: 3000, // Max delay cap (default: 2000)
592
- });
593
- ```
594
-
595
- **Retry sequence:** 100ms → 200ms → 400ms → 800ms → 1600ms (capped at maxDelayMs)
596
-
597
- **Performance comparison:**
598
-
599
- | Method | Latency | Use Case |
600
- |--------|---------|----------|
601
- | `namedRPC()` | ~800-1800ms | Commands that need response |
602
- | `namedRPCNoWait()` | ~10-30ms | Real-time input, fire-and-forget |
603
-
604
- ### namedRPCWithRetry() - RPC with Timeout and Retry
605
-
606
- For operations that need both timeout protection and automatic retry on transient failures.
607
-
608
- ```tsx
609
- import { useCentrifugo } from '@djangocfg/centrifugo';
610
-
611
- function FileList() {
612
- const { client } = useCentrifugo();
613
-
614
- const loadFiles = async () => {
615
- // Automatically retries on timeout/network errors
616
- const files = await client?.namedRPCWithRetry('files.list',
617
- { path: '/home' },
618
- {
619
- timeout: 5000, // 5 second timeout per attempt
620
- maxRetries: 3, // Up to 3 retries
621
- baseDelayMs: 1000, // Start with 1s delay
622
- maxDelayMs: 10000, // Cap at 10s
623
- onRetry: (attempt, error, delay) => {
624
- console.log(`Retry ${attempt}: ${error.userMessage}, waiting ${delay}ms`);
625
- }
626
- }
627
- );
628
- return files;
629
- };
630
- }
631
- ```
632
-
633
- ### RPCError - Typed Error Handling
634
-
635
- All RPC methods now throw `RPCError` with classification for better error handling:
636
-
637
- ```tsx
638
- import { RPCError } from '@djangocfg/centrifugo';
639
-
640
- try {
641
- await client.namedRPC('files.list', { path: '/' });
642
- } catch (error) {
643
- if (error instanceof RPCError) {
644
- // Error classification
645
- console.log(error.code); // 'timeout' | 'network_error' | 'server_error' | ...
646
- console.log(error.isRetryable); // true for transient errors
647
- console.log(error.userMessage); // User-friendly message
648
- console.log(error.suggestedRetryDelay); // Recommended delay in ms
649
-
650
- // Show user-friendly message
651
- toast.error(error.userMessage);
652
-
653
- // Decide if retry makes sense
654
- if (error.isRetryable) {
655
- // Schedule retry
656
- }
657
- }
658
- }
659
- ```
660
-
661
- **Error Codes:**
662
-
663
- | Code | Retryable | Description |
664
- |------|-----------|-------------|
665
- | `timeout` | ✅ | Request timed out |
666
- | `network_error` | ✅ | Network connectivity issue |
667
- | `connection_failed` | ✅ | WebSocket connection failed |
668
- | `websocket_error` | ✅ | WebSocket protocol error |
669
- | `server_error` | ✅ (5xx only) | Server returned error |
670
- | `not_connected` | ❌ | Client not connected |
671
- | `encoding_error` | ❌ | Failed to encode request |
672
- | `decoding_error` | ❌ | Failed to decode response |
673
- | `cancelled` | ❌ | Request was cancelled |
674
-
675
- ### withRetry() - Generic Retry Utility
676
-
677
- For custom retry logic outside of RPC:
678
-
679
- ```tsx
680
- import { withRetry, RPCError } from '@djangocfg/centrifugo';
681
-
682
- const result = await withRetry(
683
- () => fetchSomething(),
684
- {
685
- maxRetries: 3,
686
- baseDelayMs: 1000,
687
- maxDelayMs: 10000,
688
- jitterFactor: 0.2, // ±20% randomization
689
- },
690
- (state, delay) => {
691
- console.log(`Retry ${state.attempt}, waiting ${delay}ms`);
692
- }
693
- );
694
- ```
695
-
696
- ### checkApiVersion() - API Contract Validation
697
-
698
- Validates that the client API version matches the server. Useful for detecting when the frontend needs to refresh after a backend deployment.
699
-
700
- ```tsx
701
- import { API_VERSION } from '@/_ws'; // Generated client exports version hash
702
-
703
- // Check version after connect
704
- const result = await client.checkApiVersion(API_VERSION);
705
-
706
- if (!result.compatible) {
707
- // Versions don't match - show refresh prompt
708
- toast.warning('New version available. Please refresh the page.');
709
80
  }
710
81
  ```
711
82
 
712
- **Unified Event Handling:**
713
-
714
- Version mismatch automatically dispatches a `'centrifugo'` event:
715
-
716
- ```tsx
717
- // Listen globally for version mismatch
718
- window.addEventListener('centrifugo', (e: CustomEvent) => {
719
- if (e.detail.type === 'version_mismatch') {
720
- const { clientVersion, serverVersion, message } = e.detail.data;
721
- toast.warning(message);
722
- }
723
- });
724
- ```
725
-
726
- **How Version Hash Works:**
727
-
728
- The version hash is computed from:
729
- - All `@websocket_rpc` method signatures (name, params, return type)
730
- - All Pydantic model schemas used in handlers
731
-
732
- When any handler or model changes, the hash changes, triggering a version mismatch.
733
-
734
- ## Auto-Generated Type-Safe Clients
735
-
736
- Django-CFG can auto-generate TypeScript clients from your `@websocket_rpc` handlers, providing full type safety and eliminating manual RPC calls.
737
-
738
- ### Generate Clients
739
-
740
- ```bash
741
- # Generate TypeScript client from @websocket_rpc handlers
742
- python manage.py generate_centrifugo_clients --typescript --output ./clients
743
-
744
- # Generate and auto-copy to Next.js app (recommended)
745
- python manage.py generate_centrifugo_clients --typescript
746
- # Outputs to: openapi/centrifuge/typescript/
747
- ```
748
-
749
- This generates type-safe clients with:
750
-
751
- ```
752
- typescript/
753
- ├── client.ts # Type-safe API methods
754
- ├── types.ts # Pydantic models → TypeScript interfaces
755
- ├── index.ts # Exports
756
- └── rpc-client.ts # Low-level RPC client (optional)
757
- ```
758
-
759
- **Copy to your app:**
760
-
761
- ```bash
762
- # Manual copy
763
- cp -r openapi/centrifuge/typescript/* apps/web/app/_ws/
764
-
765
- # Or create a custom command for your project
766
- # See: core/management/commands/generate_centrifuge_web.py
767
- ```
768
-
769
- ### Using Generated Clients
770
-
771
- **Before (manual RPC):**
772
- ```tsx
773
- // ❌ No type safety, easy to make mistakes
774
- const result = await client.namedRPC('ai_chat.get_messages', {
775
- session_id: sessionId,
776
- limit: 50,
777
- before_id: beforeId, // Could typo: beforeID
778
- });
779
- ```
83
+ ### 5. Add monitoring (optional)
780
84
 
781
- **After (generated client):**
782
- ```tsx
783
- import { APIClient, type MessageListResult } from '@/_ws';
784
-
785
- const apiClient = useMemo(() =>
786
- client ? new APIClient(client) : null,
787
- [client]
788
- );
789
-
790
- // ✅ Full type safety with autocomplete
791
- const result: MessageListResult = await apiClient.aiChatGetMessages({
792
- session_id: sessionId, // ✓ Autocomplete
793
- limit: 50, // ✓ Type checking
794
- before_id: beforeId, // ✓ Typo protection
795
- });
796
- ```
797
-
798
- ### How It Works
799
-
800
- 1. **Backend** - Define RPC handlers with Pydantic models:
801
-
802
- ```python
803
- from pydantic import BaseModel
804
- from django_cfg.apps.integrations.centrifugo.decorators import websocket_rpc
805
-
806
- class GetMessagesParams(BaseModel):
807
- session_id: str
808
- limit: int = 50
809
- before_id: str | None = None
810
-
811
- class MessageListResult(BaseModel):
812
- messages: list[MessageData]
813
- total: int
814
- has_more: bool
815
-
816
- @websocket_rpc("ai_chat.get_messages")
817
- async def ai_chat_get_messages(conn, params: GetMessagesParams) -> MessageListResult:
818
- # Implementation
819
- return MessageListResult(...)
820
- ```
821
-
822
- 2. **Generate** - Run generation command:
823
-
824
- ```bash
825
- python manage.py generate_centrifuge_web
826
- # Discovers all @websocket_rpc handlers
827
- # Converts Pydantic models to TypeScript
828
- # Generates type-safe APIClient
829
- ```
830
-
831
- 3. **Frontend** - Use generated client:
832
-
833
- ```tsx
834
- const apiClient = new APIClient(client);
835
- const result = await apiClient.aiChatGetMessages({
836
- session_id: "uuid",
837
- limit: 50,
838
- });
839
- // TypeScript knows result is MessageListResult!
840
- ```
841
-
842
- ### Benefits
843
-
844
- - ✅ **Type Safety** - Compile-time errors for invalid parameters
845
- - ✅ **Autocomplete** - IDE suggests available methods and parameters
846
- - ✅ **Sync with Backend** - Regenerate when backend changes
847
- - ✅ **No Manual Updates** - Types automatically match Pydantic models
848
- - ✅ **Documentation** - Docstrings from backend appear in IDE
849
-
850
- ### Available Methods
851
-
852
- All `@websocket_rpc` handlers are auto-generated:
853
-
854
- ```tsx
855
- // Terminal RPC
856
- await apiClient.terminalInput({ session_id, data });
857
- await apiClient.terminalResize({ session_id, cols, rows });
858
- await apiClient.terminalClose({ session_id });
859
-
860
- // AI Chat RPC
861
- await apiClient.aiChatCreateSession({ workspace_id, title });
862
- await apiClient.aiChatSendMessage({ session_id, message, stream });
863
- await apiClient.aiChatGetMessages({ session_id, limit, before_id });
864
- ```
865
-
866
- > **Tip:** Run `python manage.py generate_centrifugo_clients --typescript` after adding new RPC handlers to update types.
867
-
868
- ## UI Components
869
-
870
- All components are composable and can be used independently or together.
871
-
872
- ### ConnectionStatus
873
-
874
- Display connection status in various formats.
875
-
876
- **Variants:**
877
- - `badge` - Compact badge with status indicator
878
- - `inline` - Inline text with icon
879
- - `detailed` - Detailed view with uptime and subscriptions
880
- - `dot` - Minimal dot indicator with popover on click (ideal for toolbars)
881
-
882
- ```tsx
883
- import { ConnectionStatus } from '@djangocfg/centrifugo';
884
-
885
- // Badge variant
886
- <ConnectionStatus variant="badge" />
887
-
888
- // Dot variant - minimal indicator with popover (best for headers/toolbars)
889
- <ConnectionStatus variant="dot" dotSize="md" />
890
-
891
- // Detailed variant with uptime and subscriptions
892
- <ConnectionStatus
893
- variant="detailed"
894
- showUptime={true}
895
- showSubscriptions={true}
896
- />
897
- ```
898
-
899
- **ConnectionStatusCard** - Card wrapper for dashboards:
900
-
901
- ```tsx
902
- import { ConnectionStatusCard } from '@djangocfg/centrifugo';
903
-
904
- <ConnectionStatusCard />
905
- ```
906
-
907
- ### MessagesFeed
908
-
909
- Real-time feed of Centrifugo messages with filtering and export.
910
-
911
- ```tsx
912
- import { MessagesFeed } from '@djangocfg/centrifugo';
913
-
914
- <MessagesFeed
915
- maxMessages={100}
916
- showFilters={true}
917
- showControls={true}
918
- autoScroll={true}
919
- onMessageClick={(msg) => console.log(msg)}
920
- />
921
- ```
922
-
923
- **Features:**
924
- - Filter by type, level, channel
925
- - Search functionality
926
- - Pause/play auto-scroll
927
- - Download messages as JSON
928
- - Clear messages
929
-
930
- ### SubscriptionsList
931
-
932
- List of active subscriptions with management controls.
933
-
934
- ```tsx
935
- import { SubscriptionsList } from '@djangocfg/centrifugo';
936
-
937
- <SubscriptionsList
938
- showControls={true}
939
- onSubscriptionClick={(channel) => console.log(channel)}
940
- />
941
- ```
942
-
943
- **Features:**
944
- - Real-time subscription updates
945
- - Unsubscribe from channels
946
- - Click to view channel details
947
-
948
- ### CentrifugoMonitor
949
-
950
- Main monitoring component with multiple variants.
951
-
952
- **Basic Usage:**
953
-
954
- ```tsx
955
- import { CentrifugoMonitor } from '@djangocfg/centrifugo';
956
-
957
- // Embedded in a page
958
- <CentrifugoMonitor defaultTab="messages" />
959
- ```
960
-
961
- **Dialog Variant:**
962
-
963
- ```tsx
964
- import { CentrifugoMonitorDialog } from '@djangocfg/centrifugo';
965
-
966
- function MyComponent() {
967
- const [open, setOpen] = useState(false);
968
-
969
- return (
970
- <>
971
- <button onClick={() => setOpen(true)}>Open Monitor</button>
972
- <CentrifugoMonitorDialog
973
- open={open}
974
- onOpenChange={setOpen}
975
- defaultTab="status"
976
- />
977
- </>
978
- );
979
- }
980
- ```
981
-
982
- **FAB Variant:**
983
-
984
- ```tsx
985
- import { CentrifugoMonitorFAB } from '@djangocfg/centrifugo';
986
-
987
- // Full variant with all tabs
988
- <CentrifugoMonitorFAB variant="full" />
989
-
990
- // Compact variant (status only)
991
- <CentrifugoMonitorFAB variant="compact" />
992
- ```
993
-
994
- **Widget Variant (for Dashboards):**
995
-
996
- ```tsx
997
- import { CentrifugoMonitorWidget } from '@djangocfg/centrifugo';
998
-
999
- <CentrifugoMonitorWidget defaultTab="subscriptions" />
1000
- ```
1001
-
1002
- **Props:**
1003
- ```typescript
1004
- interface CentrifugoMonitorProps {
1005
- defaultTab?: 'status' | 'messages' | 'subscriptions';
1006
- className?: string;
1007
- }
1008
- ```
1009
-
1010
- ## Logging System
1011
-
1012
- The package includes a sophisticated logging system with:
1013
-
1014
- - **Circular Buffer** - Stores up to 500 logs (configurable)
1015
- - **Dual Output** - Consola (dev only) + in-memory store (always)
1016
- - **Structured Logs** - Timestamp, level, source, message, and optional data
1017
- - **Real-time Updates** - Subscribe to log changes for React updates
1018
- - **Prefixed Output** - Logger name appears as `[YourComponent]` in console
1019
-
1020
- **Creating a Logger:**
1021
-
1022
- ```typescript
1023
- import { createLogger } from '@djangocfg/centrifugo';
1024
-
1025
- // Simple usage with string prefix
1026
- const logger = createLogger('MyComponent');
1027
-
1028
- // Advanced usage with full config
1029
- const logger = createLogger({
1030
- source: 'client', // 'client' | 'provider' | 'subscription' | 'system'
1031
- tag: 'MyComponent',
1032
- isDevelopment: true,
1033
- });
1034
-
1035
- logger.debug('Debug message', { extra: 'data' });
1036
- logger.info('Info message');
1037
- logger.success('Success message');
1038
- logger.warning('Warning message');
1039
- logger.error('Error message', error);
1040
- ```
1041
-
1042
- **Accessing Logs:**
1043
-
1044
- ```typescript
1045
- import { getGlobalLogsStore } from '@djangocfg/centrifugo';
1046
-
1047
- const logsStore = getGlobalLogsStore();
1048
-
1049
- // Get all logs
1050
- const logs = logsStore.getAll();
1051
-
1052
- // Subscribe to changes
1053
- const unsubscribe = logsStore.subscribe((logs) => {
1054
- console.log('Logs updated:', logs);
1055
- });
1056
-
1057
- // Clear logs
1058
- logsStore.clear();
1059
- ```
1060
-
1061
- **Using LogsProvider:**
1062
-
1063
- ```tsx
1064
- import { useLogs } from '@djangocfg/centrifugo';
1065
-
1066
- function CustomLogsView() {
1067
- const { logs, setFilter, clearLogs } = useLogs();
1068
-
1069
- return (
1070
- <div>
1071
- <button onClick={() => setFilter({ level: 'error' })}>
1072
- Show Errors Only
1073
- </button>
1074
- <button onClick={() => setFilter({ source: 'client' })}>
1075
- Show Client Logs
1076
- </button>
1077
- <button onClick={() => setFilter({ search: 'WebSocket' })}>
1078
- Search "WebSocket"
1079
- </button>
1080
- <button onClick={clearLogs}>
1081
- Clear All
1082
- </button>
1083
-
1084
- <ul>
1085
- {logs.map(log => (
1086
- <li key={log.id}>{log.message}</li>
1087
- ))}
1088
- </ul>
1089
- </div>
1090
- );
1091
- }
1092
- ```
1093
-
1094
- ## Advanced Usage
1095
-
1096
- ### Using the Core Client (without React)
1097
-
1098
- ```typescript
1099
- import { CentrifugoRPCClient, createLogger } from '@djangocfg/centrifugo';
1100
-
1101
- const logger = createLogger('MyApp');
1102
-
1103
- // Recommended: Options-based constructor
1104
- const client = new CentrifugoRPCClient({
1105
- url: 'ws://localhost:8000/ws',
1106
- token: 'your-token',
1107
- userId: 'user-id',
1108
- timeout: 30000,
1109
- logger,
1110
- // Auto-refresh token on expiration
1111
- getToken: async () => {
1112
- const response = await fetch('/api/auth/refresh-token');
1113
- const { token } = await response.json();
1114
- return token;
1115
- },
1116
- });
1117
-
1118
- await client.connect();
1119
-
1120
- // Check API version after connect
1121
- import { API_VERSION } from '@/_ws';
1122
- await client.checkApiVersion(API_VERSION);
1123
-
1124
- // Subscribe to channel
1125
- const unsubscribe = client.subscribe('channel-name', (data) => {
1126
- console.log('Message:', data);
1127
- });
1128
-
1129
- // Get native Centrifuge client for low-level access
1130
- const centrifuge = client.getCentrifuge();
1131
- centrifuge.on('connected', () => console.log('Connected'));
1132
- ```
1133
-
1134
- ### Custom Dashboard Integration
1135
-
1136
- ```tsx
1137
- import {
1138
- ConnectionStatusCard,
1139
- MessagesFeed,
1140
- SubscriptionsList,
1141
- } from '@djangocfg/centrifugo';
1142
-
1143
- function Dashboard() {
1144
- return (
1145
- <div className="grid grid-cols-3 gap-4">
1146
- <ConnectionStatusCard />
1147
-
1148
- <div className="col-span-2">
1149
- <MessagesFeed
1150
- maxMessages={50}
1151
- showFilters={false}
1152
- />
1153
- </div>
1154
-
1155
- <div className="col-span-3">
1156
- <SubscriptionsList showControls={true} />
1157
- </div>
1158
- </div>
1159
- );
1160
- }
1161
- ```
1162
-
1163
- ### Embedded Monitor in Page
1164
-
1165
- ```tsx
1166
- import { CentrifugoMonitor } from '@djangocfg/centrifugo';
1167
-
1168
- function MonitoringPage() {
1169
- return (
1170
- <div className="container mx-auto p-6">
1171
- <h1>WebSocket Monitoring</h1>
1172
- <CentrifugoMonitor defaultTab="messages" />
1173
- </div>
1174
- );
1175
- }
1176
- ```
1177
-
1178
- ## TypeScript Support
1179
-
1180
- The package is fully typed with comprehensive TypeScript definitions:
1181
-
1182
- ```typescript
1183
- import type {
1184
- // Client Options
1185
- CentrifugoClientOptions,
1186
- RPCOptions,
1187
- RetryOptions,
1188
- VersionCheckResult,
1189
- NamedRPCWithRetryOptions,
1190
-
1191
- // Errors
1192
- RPCErrorCode,
1193
- RetryConfig,
1194
- RetryState,
1195
-
1196
- // Connection
1197
- ConnectionState,
1198
- CentrifugoToken,
1199
- User,
1200
-
1201
- // Logs
1202
- LogLevel,
1203
- LogEntry,
1204
-
1205
- // Subscriptions
1206
- ActiveSubscription,
1207
-
1208
- // Client
1209
- CentrifugoClientConfig,
1210
- CentrifugoClientState,
1211
-
1212
- // Provider
1213
- CentrifugoProviderProps,
1214
- CentrifugoContextValue,
1215
-
1216
- // Hooks
1217
- UseSubscriptionOptions,
1218
- UseSubscriptionResult,
1219
- UsePageVisibilityOptions,
1220
- UsePageVisibilityResult,
1221
- PageVisibilityState,
1222
-
1223
- // Components
1224
- ConnectionStatusProps,
1225
- MessagesFeedProps,
1226
- SubscriptionsListProps,
1227
- CentrifugoMonitorProps,
1228
- } from '@djangocfg/centrifugo';
1229
-
1230
- // Error classes
1231
- import { RPCError, withRetry, createRetryHandler } from '@djangocfg/centrifugo';
1232
- ```
1233
-
1234
- ## Unified Event System
1235
-
1236
- All Centrifugo events use a single `'centrifugo'` CustomEvent with a type discriminator. This simplifies event handling and reduces the number of event listeners needed.
1237
-
1238
- **Event Types:**
1239
-
1240
- | Type | Description | Data |
1241
- |------|-------------|------|
1242
- | `error` | RPC call failed | `{ method, error, code, data }` |
1243
- | `version_mismatch` | API version mismatch | `{ clientVersion, serverVersion, message }` |
1244
- | `connected` | Successfully connected | `{ userId }` |
1245
- | `disconnected` | Connection lost | `{ userId, reason }` |
1246
- | `reconnecting` | Attempting to reconnect | `{ userId, attempt, reason }` |
1247
-
1248
- **Listening to Events:**
1249
-
1250
- ```tsx
1251
- // Listen to all Centrifugo events
1252
- window.addEventListener('centrifugo', (e: CustomEvent) => {
1253
- const { type, data, timestamp } = e.detail;
1254
-
1255
- switch (type) {
1256
- case 'error':
1257
- console.error('RPC error:', data.method, data.error);
1258
- break;
1259
- case 'version_mismatch':
1260
- toast.warning('Please refresh the page');
1261
- break;
1262
- case 'connected':
1263
- console.log('Connected as', data.userId);
1264
- break;
1265
- case 'disconnected':
1266
- console.log('Disconnected:', data.reason);
1267
- break;
1268
- case 'reconnecting':
1269
- console.log('Reconnecting, attempt', data.attempt);
1270
- break;
1271
- }
1272
- });
1273
- ```
1274
-
1275
- **Dispatching Events (for custom integrations):**
1276
-
1277
- ```tsx
1278
- import {
1279
- dispatchCentrifugoError,
1280
- dispatchVersionMismatch,
1281
- dispatchConnected,
1282
- dispatchDisconnected,
1283
- dispatchReconnecting,
1284
- } from '@djangocfg/centrifugo';
1285
-
1286
- // Dispatch error
1287
- dispatchCentrifugoError({
1288
- method: 'terminal.input',
1289
- error: 'Connection timeout',
1290
- code: 408,
1291
- });
1292
-
1293
- // Dispatch version mismatch
1294
- dispatchVersionMismatch({
1295
- clientVersion: 'abc123',
1296
- serverVersion: 'def456',
1297
- message: 'API version mismatch',
1298
- });
1299
- ```
1300
-
1301
- **Integration with ErrorsTracker:**
1302
-
1303
- The `@djangocfg/layouts` package's `ErrorTrackingProvider` automatically listens to `'centrifugo'` events with `type: 'error'` and displays toast notifications.
1304
-
1305
- ```tsx
1306
- import { ErrorTrackingProvider } from '@djangocfg/layouts';
1307
-
1308
- <ErrorTrackingProvider centrifugo={{ enabled: true, showToast: true }}>
1309
- <App />
1310
- </ErrorTrackingProvider>
1311
- ```
1312
-
1313
- **Proper Centrifuge Types:**
1314
-
1315
- The package now uses proper types from the `centrifuge` library:
1316
-
1317
- ```typescript
1318
- import type { Subscription, SubscriptionState } from 'centrifuge';
1319
-
1320
- // No more 'as any' - fully type-safe!
1321
- const centrifuge = client.getCentrifuge();
1322
- const subs = centrifuge.subscriptions();
1323
-
1324
- for (const [channel, sub] of Object.entries(subs)) {
1325
- console.log(channel, sub.state); // SubscriptionState type
1326
- }
1327
- ```
1328
-
1329
- ## Best Practices
1330
-
1331
- ### 1. Logger Usage
1332
-
1333
- Always use the logger instead of `console`:
1334
-
1335
- ```typescript
1336
- // ❌ Bad
1337
- console.log('Message');
1338
-
1339
- // ✅ Good
1340
- const logger = createLogger('MyComponent');
1341
- logger.info('Message');
1342
- ```
1343
-
1344
- ### 2. Date Handling
1345
-
1346
- Use `moment` with UTC for all date operations:
1347
-
1348
- ```typescript
1349
- import moment from 'moment';
1350
-
1351
- // ❌ Bad
1352
- const now = new Date();
1353
- const timestamp = Date.now();
1354
-
1355
- // ✅ Good
1356
- const now = moment.utc();
1357
- const timestamp = moment.utc().valueOf();
1358
- const formatted = moment.utc().format('YYYY-MM-DD HH:mm:ss');
1359
- ```
1360
-
1361
- ### 3. FAB Control
1362
-
1363
- Don't rely on auto-showing FAB. Control it explicitly:
1364
-
1365
- ```typescript
1366
- // ✅ Good - Developer has full control
1367
- function App() {
1368
- return (
1369
- <CentrifugoProvider enabled={true}>
1370
- <YourApp />
1371
- {shouldShowMonitor && <CentrifugoMonitorFAB />}
1372
- </CentrifugoProvider>
1373
- );
1374
- }
1375
- ```
1376
-
1377
- ### 4. Component Composition
1378
-
1379
- Use individual components for custom layouts:
1380
-
1381
- ```typescript
1382
- // ✅ Good - Compose as needed
1383
- <div className="monitoring-panel">
1384
- <ConnectionStatus variant="detailed" showUptime />
1385
- <MessagesFeed maxMessages={50} />
1386
- <SubscriptionsList />
1387
- </div>
1388
- ```
1389
-
1390
- ## Migration Guide
1391
-
1392
- ### From Old DebugPanel
1393
-
1394
- **Before:**
1395
- ```tsx
1396
- import { DebugPanel } from '@djangocfg/centrifugo';
1397
-
1398
- // Auto-shown in development
1399
- <CentrifugoProvider>
1400
- <App />
1401
- </CentrifugoProvider>
1402
- ```
1403
-
1404
- **After:**
1405
85
  ```tsx
1406
86
  import { CentrifugoMonitorFAB } from '@djangocfg/centrifugo';
1407
87
 
1408
- // Manual control
1409
- <CentrifugoProvider>
1410
- <App />
1411
- <CentrifugoMonitorFAB variant="full" />
1412
- </CentrifugoProvider>
88
+ // Show only for admins or in dev
89
+ {shouldShowMonitor && <CentrifugoMonitorFAB variant="full" />}
1413
90
  ```
1414
91
 
1415
- ### From date-fns to moment
1416
-
1417
- **Before:**
1418
- ```typescript
1419
- import { format } from 'date-fns';
92
+ ## Documentation
1420
93
 
1421
- const formatted = format(new Date(), 'yyyy-MM-dd HH:mm:ss');
1422
- ```
94
+ | Document | Description |
95
+ |----------|-------------|
96
+ | [@docs/overview.md](@docs/overview.md) | Features, architecture, how it connects |
97
+ | [@docs/api-reference.md](@docs/api-reference.md) | Providers, hooks, core client, TypeScript types |
98
+ | [@docs/rpc.md](@docs/rpc.md) | useNamedRPC, useRPC, namedRPCNoWait, RPCError, retry |
99
+ | [@docs/components.md](@docs/components.md) | ConnectionStatus, MessagesFeed, Monitor, dashboard examples |
100
+ | [@docs/codegen.md](@docs/codegen.md) | Auto-generated type-safe clients from @websocket_rpc |
101
+ | [@docs/events.md](@docs/events.md) | Unified event system, ErrorsTracker integration |
102
+ | [@docs/logging.md](@docs/logging.md) | Logger, LogsProvider, in-memory store |
103
+ | [@docs/migration.md](@docs/migration.md) | Migration from DebugPanel, date-fns to moment |
1423
104
 
1424
- **After:**
1425
- ```typescript
1426
- import moment from 'moment';
105
+ ## Requirements
1427
106
 
1428
- const formatted = moment.utc().format('YYYY-MM-DD HH:mm:ss');
1429
- ```
107
+ - React 18+
108
+ - `@djangocfg/ui-nextjs` — UI components (shadcn/ui)
109
+ - `@djangocfg/layouts` — layout components
110
+ - `centrifuge` — WebSocket client library
111
+ - `moment` — date manipulation
112
+ - `consola` — console logging
1430
113
 
1431
114
  ## Development
1432
115
 
1433
116
  ```bash
1434
- # Install dependencies
1435
- pnpm install
1436
-
1437
- # Build the package
1438
- pnpm build
1439
-
1440
- # Type check
1441
- pnpm check
1442
-
1443
- # Run in development mode
1444
- pnpm dev
117
+ pnpm install # Install dependencies
118
+ pnpm build # Build
119
+ pnpm check # Type check
120
+ pnpm dev # Watch mode
1445
121
  ```
1446
122
 
1447
- ## Requirements
1448
-
1449
- - React 18+
1450
- - `@djangocfg/ui-nextjs` - UI components (shadcn/ui based)
1451
- - `@djangocfg/layouts` - Layout components
1452
- - `centrifuge` - WebSocket client library
1453
- - `moment` - Date manipulation library
1454
- - `consola` - Beautiful console logging
1455
-
1456
- ## Documentation
1457
-
1458
- Full documentation available at [djangocfg.com](https://djangocfg.com)
1459
-
1460
- ## Contributing
1461
-
1462
- Issues and pull requests are welcome at [GitHub](https://github.com/markolofsen/django-cfg)
1463
-
1464
123
  ## License
1465
124
 
1466
- MIT - see [LICENSE](./LICENSE) for details
125
+ MIT