@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 +218 -7
- package/package.json +5 -5
- package/src/core/client/CentrifugoRPCClient.ts +146 -532
- package/src/core/client/connection.ts +229 -0
- package/src/core/client/index.ts +47 -2
- package/src/core/client/rpc.ts +290 -0
- package/src/core/client/subscriptions.ts +176 -0
- package/src/core/client/types.ts +44 -0
- package/src/core/client/version.ts +92 -0
- package/src/events.ts +160 -47
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
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
'
|
|
814
|
-
|
|
815
|
-
|
|
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.
|
|
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.
|
|
55
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
56
|
-
"@djangocfg/layouts": "^2.1.
|
|
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.
|
|
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",
|