@djangocfg/centrifugo 2.1.53 → 2.1.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,10 +30,18 @@ Professional Centrifugo WebSocket client with React integration, composable UI c
30
30
  src/
31
31
  ├── core/ # Platform-agnostic (no React dependencies)
32
32
  │ ├── client/ # CentrifugoRPCClient - WebSocket client
33
+ │ │ ├── CentrifugoRPCClient.ts # Main facade (~165 lines)
34
+ │ │ ├── connection.ts # Connection lifecycle
35
+ │ │ ├── subscriptions.ts # Channel subscriptions
36
+ │ │ ├── rpc.ts # RPC methods (namedRPC, namedRPCNoWait)
37
+ │ │ ├── version.ts # API version checking
38
+ │ │ ├── types.ts # Type definitions
39
+ │ │ └── index.ts # Exports
33
40
  │ ├── logger/ # Logging system with circular buffer
34
41
  │ │ ├── createLogger.ts # Logger factory (supports string prefix)
35
42
  │ │ └── LogsStore.ts # In-memory logs accumulation
36
43
  │ └── types/ # TypeScript type definitions
44
+ ├── events.ts # Unified event system (single 'centrifugo' event)
37
45
  ├── providers/ # React Context providers
38
46
  │ ├── CentrifugoProvider/ # Main connection provider (no auto-FAB)
39
47
  │ └── LogsProvider/ # Logs accumulation provider
@@ -443,6 +451,112 @@ interface UseNamedRPCResult {
443
451
 
444
452
  > **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.
445
453
 
454
+ ### namedRPCNoWait() - Fire-and-Forget RPC
455
+
456
+ For latency-sensitive operations (like terminal input), use the fire-and-forget variant that returns immediately without waiting for a response.
457
+
458
+ **When to use:**
459
+ - Terminal input (user typing)
460
+ - Real-time cursor position updates
461
+ - Any operation where you don't need the response
462
+
463
+ **Direct Client Usage:**
464
+
465
+ ```tsx
466
+ import { useCentrifugo } from '@djangocfg/centrifugo';
467
+
468
+ function TerminalInput() {
469
+ const { client } = useCentrifugo();
470
+
471
+ const handleKeyPress = (key: string) => {
472
+ // Fire-and-forget: returns immediately, doesn't wait for response
473
+ client?.namedRPCNoWait('terminal.input', {
474
+ session_id: 'abc-123',
475
+ data: btoa(key)
476
+ });
477
+ };
478
+
479
+ return <input onKeyPress={(e) => handleKeyPress(e.key)} />;
480
+ }
481
+ ```
482
+
483
+ **Backend Handler (Django):**
484
+
485
+ ```python
486
+ @websocket_rpc("terminal.input", no_wait=True)
487
+ async def terminal_input(conn, params: TerminalInputParams) -> SuccessResult:
488
+ """Handle terminal input (fire-and-forget)."""
489
+ import asyncio
490
+
491
+ # Spawn background task, return immediately
492
+ asyncio.create_task(_send_input_to_agent(params.session_id, params.data))
493
+
494
+ return SuccessResult(success=True, message="Input queued")
495
+ ```
496
+
497
+ **Retry Logic with Exponential Backoff:**
498
+
499
+ `namedRPCNoWait` includes automatic retry with exponential backoff:
500
+
501
+ ```tsx
502
+ // Default: 3 retries, 100ms base delay, 2000ms max delay
503
+ client?.namedRPCNoWait('terminal.input', { session_id, data });
504
+
505
+ // Custom retry options
506
+ client?.namedRPCNoWait('terminal.input', { session_id, data }, {
507
+ maxRetries: 5, // Max retry attempts (default: 3)
508
+ baseDelayMs: 100, // Base delay for exponential backoff (default: 100)
509
+ maxDelayMs: 3000, // Max delay cap (default: 2000)
510
+ });
511
+ ```
512
+
513
+ **Retry sequence:** 100ms → 200ms → 400ms → 800ms → 1600ms (capped at maxDelayMs)
514
+
515
+ **Performance comparison:**
516
+
517
+ | Method | Latency | Use Case |
518
+ |--------|---------|----------|
519
+ | `namedRPC()` | ~800-1800ms | Commands that need response |
520
+ | `namedRPCNoWait()` | ~10-30ms | Real-time input, fire-and-forget |
521
+
522
+ ### checkApiVersion() - API Contract Validation
523
+
524
+ Validates that the client API version matches the server. Useful for detecting when the frontend needs to refresh after a backend deployment.
525
+
526
+ ```tsx
527
+ import { API_VERSION } from '@/_ws'; // Generated client exports version hash
528
+
529
+ // Check version after connect
530
+ const result = await client.checkApiVersion(API_VERSION);
531
+
532
+ if (!result.compatible) {
533
+ // Versions don't match - show refresh prompt
534
+ toast.warning('New version available. Please refresh the page.');
535
+ }
536
+ ```
537
+
538
+ **Unified Event Handling:**
539
+
540
+ Version mismatch automatically dispatches a `'centrifugo'` event:
541
+
542
+ ```tsx
543
+ // Listen globally for version mismatch
544
+ window.addEventListener('centrifugo', (e: CustomEvent) => {
545
+ if (e.detail.type === 'version_mismatch') {
546
+ const { clientVersion, serverVersion, message } = e.detail.data;
547
+ toast.warning(message);
548
+ }
549
+ });
550
+ ```
551
+
552
+ **How Version Hash Works:**
553
+
554
+ The version hash is computed from:
555
+ - All `@websocket_rpc` method signatures (name, params, return type)
556
+ - All Pydantic model schemas used in handlers
557
+
558
+ When any handler or model changes, the hash changes, triggering a version mismatch.
559
+
446
560
  ## Auto-Generated Type-Safe Clients
447
561
 
448
562
  Django-CFG can auto-generate TypeScript clients from your `@websocket_rpc` handlers, providing full type safety and eliminating manual RPC calls.
@@ -807,16 +921,28 @@ function CustomLogsView() {
807
921
  import { CentrifugoRPCClient, createLogger } from '@djangocfg/centrifugo';
808
922
 
809
923
  const logger = createLogger('MyApp');
810
- const client = new CentrifugoRPCClient(
811
- 'ws://localhost:8000/ws',
812
- 'your-token',
813
- 'user-id',
814
- 30000, // timeout
815
- (entry) => logger.info(entry.message, entry.data)
816
- );
924
+
925
+ // Recommended: Options-based constructor
926
+ const client = new CentrifugoRPCClient({
927
+ url: 'ws://localhost:8000/ws',
928
+ token: 'your-token',
929
+ userId: 'user-id',
930
+ timeout: 30000,
931
+ logger,
932
+ // Auto-refresh token on expiration
933
+ getToken: async () => {
934
+ const response = await fetch('/api/auth/refresh-token');
935
+ const { token } = await response.json();
936
+ return token;
937
+ },
938
+ });
817
939
 
818
940
  await client.connect();
819
941
 
942
+ // Check API version after connect
943
+ import { API_VERSION } from '@/_ws';
944
+ await client.checkApiVersion(API_VERSION);
945
+
820
946
  // Subscribe to channel
821
947
  const unsubscribe = client.subscribe('channel-name', (data) => {
822
948
  console.log('Message:', data);
@@ -877,6 +1003,12 @@ The package is fully typed with comprehensive TypeScript definitions:
877
1003
 
878
1004
  ```typescript
879
1005
  import type {
1006
+ // Client Options
1007
+ CentrifugoClientOptions,
1008
+ RPCOptions,
1009
+ RetryOptions,
1010
+ VersionCheckResult,
1011
+
880
1012
  // Connection
881
1013
  ConnectionState,
882
1014
  CentrifugoToken,
@@ -909,6 +1041,85 @@ import type {
909
1041
  } from '@djangocfg/centrifugo';
910
1042
  ```
911
1043
 
1044
+ ## Unified Event System
1045
+
1046
+ All Centrifugo events use a single `'centrifugo'` CustomEvent with a type discriminator. This simplifies event handling and reduces the number of event listeners needed.
1047
+
1048
+ **Event Types:**
1049
+
1050
+ | Type | Description | Data |
1051
+ |------|-------------|------|
1052
+ | `error` | RPC call failed | `{ method, error, code, data }` |
1053
+ | `version_mismatch` | API version mismatch | `{ clientVersion, serverVersion, message }` |
1054
+ | `connected` | Successfully connected | `{ userId }` |
1055
+ | `disconnected` | Connection lost | `{ userId, reason }` |
1056
+ | `reconnecting` | Attempting to reconnect | `{ userId, attempt, reason }` |
1057
+
1058
+ **Listening to Events:**
1059
+
1060
+ ```tsx
1061
+ // Listen to all Centrifugo events
1062
+ window.addEventListener('centrifugo', (e: CustomEvent) => {
1063
+ const { type, data, timestamp } = e.detail;
1064
+
1065
+ switch (type) {
1066
+ case 'error':
1067
+ console.error('RPC error:', data.method, data.error);
1068
+ break;
1069
+ case 'version_mismatch':
1070
+ toast.warning('Please refresh the page');
1071
+ break;
1072
+ case 'connected':
1073
+ console.log('Connected as', data.userId);
1074
+ break;
1075
+ case 'disconnected':
1076
+ console.log('Disconnected:', data.reason);
1077
+ break;
1078
+ case 'reconnecting':
1079
+ console.log('Reconnecting, attempt', data.attempt);
1080
+ break;
1081
+ }
1082
+ });
1083
+ ```
1084
+
1085
+ **Dispatching Events (for custom integrations):**
1086
+
1087
+ ```tsx
1088
+ import {
1089
+ dispatchCentrifugoError,
1090
+ dispatchVersionMismatch,
1091
+ dispatchConnected,
1092
+ dispatchDisconnected,
1093
+ dispatchReconnecting,
1094
+ } from '@djangocfg/centrifugo';
1095
+
1096
+ // Dispatch error
1097
+ dispatchCentrifugoError({
1098
+ method: 'terminal.input',
1099
+ error: 'Connection timeout',
1100
+ code: 408,
1101
+ });
1102
+
1103
+ // Dispatch version mismatch
1104
+ dispatchVersionMismatch({
1105
+ clientVersion: 'abc123',
1106
+ serverVersion: 'def456',
1107
+ message: 'API version mismatch',
1108
+ });
1109
+ ```
1110
+
1111
+ **Integration with ErrorsTracker:**
1112
+
1113
+ The `@djangocfg/layouts` package's `ErrorTrackingProvider` automatically listens to `'centrifugo'` events with `type: 'error'` and displays toast notifications.
1114
+
1115
+ ```tsx
1116
+ import { ErrorTrackingProvider } from '@djangocfg/layouts';
1117
+
1118
+ <ErrorTrackingProvider centrifugo={{ enabled: true, showToast: true }}>
1119
+ <App />
1120
+ </ErrorTrackingProvider>
1121
+ ```
1122
+
912
1123
  **Proper Centrifuge Types:**
913
1124
 
914
1125
  The package now uses proper types from the `centrifuge` library:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/centrifugo",
3
- "version": "2.1.53",
3
+ "version": "2.1.55",
4
4
  "description": "Production-ready Centrifugo WebSocket client for React with real-time subscriptions, RPC patterns, and connection state management",
5
5
  "keywords": [
6
6
  "centrifugo",
@@ -51,9 +51,9 @@
51
51
  "centrifuge": "^5.2.2"
52
52
  },
53
53
  "peerDependencies": {
54
- "@djangocfg/api": "^2.1.53",
55
- "@djangocfg/ui-nextjs": "^2.1.53",
56
- "@djangocfg/layouts": "^2.1.53",
54
+ "@djangocfg/api": "^2.1.55",
55
+ "@djangocfg/ui-nextjs": "^2.1.55",
56
+ "@djangocfg/layouts": "^2.1.55",
57
57
  "consola": "^3.4.2",
58
58
  "lucide-react": "^0.545.0",
59
59
  "moment": "^2.30.1",
@@ -61,7 +61,7 @@
61
61
  "react-dom": "^19.1.0"
62
62
  },
63
63
  "devDependencies": {
64
- "@djangocfg/typescript-config": "^2.1.53",
64
+ "@djangocfg/typescript-config": "^2.1.55",
65
65
  "@types/react": "^19.1.0",
66
66
  "@types/react-dom": "^19.1.0",
67
67
  "moment": "^2.30.1",