@djangocfg/centrifugo 1.0.1 → 1.0.3
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 +345 -34
- package/package.json +6 -4
- package/src/config.ts +1 -1
- package/src/core/client/CentrifugoRPCClient.ts +281 -0
- package/src/core/client/index.ts +5 -0
- package/src/core/index.ts +15 -0
- package/src/core/logger/LogsStore.ts +101 -0
- package/src/core/logger/createLogger.ts +79 -0
- package/src/core/logger/index.ts +9 -0
- package/src/core/types/index.ts +68 -0
- package/src/debug/ConnectionTab/ConnectionTab.tsx +160 -0
- package/src/debug/ConnectionTab/index.ts +5 -0
- package/src/debug/DebugPanel/DebugPanel.tsx +88 -0
- package/src/debug/DebugPanel/index.ts +5 -0
- package/src/debug/LogsTab/LogsTab.tsx +236 -0
- package/src/debug/LogsTab/index.ts +5 -0
- package/src/debug/SubscriptionsTab/SubscriptionsTab.tsx +135 -0
- package/src/debug/SubscriptionsTab/index.ts +5 -0
- package/src/debug/index.ts +11 -0
- package/src/hooks/index.ts +2 -5
- package/src/hooks/useSubscription.ts +66 -65
- package/src/index.ts +94 -13
- package/src/providers/CentrifugoProvider/CentrifugoProvider.tsx +380 -0
- package/src/providers/CentrifugoProvider/index.ts +6 -0
- package/src/providers/LogsProvider/LogsProvider.tsx +107 -0
- package/src/providers/LogsProvider/index.ts +6 -0
- package/src/providers/index.ts +9 -0
- package/API_GENERATOR.md +0 -253
- package/src/components/CentrifugoDebug.tsx +0 -182
- package/src/components/index.ts +0 -5
- package/src/context/CentrifugoProvider.tsx +0 -228
- package/src/context/index.ts +0 -5
- package/src/hooks/useLogger.ts +0 -69
- package/src/types/index.ts +0 -45
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base Centrifugo Provider
|
|
3
|
-
*
|
|
4
|
-
* Generic WebSocket RPC provider that can be used in any app.
|
|
5
|
-
* Automatically accesses auth context from @djangocfg/layouts.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use client';
|
|
9
|
-
|
|
10
|
-
import { createContext, useContext, useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
11
|
-
import { useAuth } from '@djangocfg/layouts';
|
|
12
|
-
import type { CentrifugoProviderProps, CentrifugoContextValue } from '../types';
|
|
13
|
-
import { useCentrifugoLogger } from '../hooks';
|
|
14
|
-
import { CentrifugoDebug } from '../components';
|
|
15
|
-
|
|
16
|
-
const CentrifugoContext = createContext<CentrifugoContextValue | undefined>(undefined);
|
|
17
|
-
|
|
18
|
-
export function CentrifugoProvider({
|
|
19
|
-
children,
|
|
20
|
-
enabled = false,
|
|
21
|
-
RPCClientClass,
|
|
22
|
-
APIClientClass,
|
|
23
|
-
url,
|
|
24
|
-
autoConnect: autoConnectProp = true,
|
|
25
|
-
}: CentrifugoProviderProps) {
|
|
26
|
-
const { isAuthenticated, isLoading, user } = useAuth();
|
|
27
|
-
|
|
28
|
-
const [client, setClient] = useState<any>(null);
|
|
29
|
-
const [baseClient, setBaseClient] = useState<any>(null);
|
|
30
|
-
const [isConnected, setIsConnected] = useState(false);
|
|
31
|
-
const [isConnecting, setIsConnecting] = useState(false);
|
|
32
|
-
const [error, setError] = useState<Error | null>(null);
|
|
33
|
-
|
|
34
|
-
const logger = useCentrifugoLogger();
|
|
35
|
-
|
|
36
|
-
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
37
|
-
const hasConnectedRef = useRef(false);
|
|
38
|
-
const isConnectingRef = useRef(false);
|
|
39
|
-
const isMountedRef = useRef(true);
|
|
40
|
-
|
|
41
|
-
const centrifugoToken = user?.centrifugo;
|
|
42
|
-
const hasCentrifugoToken = !!centrifugoToken?.token;
|
|
43
|
-
|
|
44
|
-
const wsUrl = useMemo(() => {
|
|
45
|
-
if (url) return url;
|
|
46
|
-
if (centrifugoToken?.centrifugo_url) return centrifugoToken.centrifugo_url;
|
|
47
|
-
return '';
|
|
48
|
-
}, [url, centrifugoToken?.centrifugo_url]);
|
|
49
|
-
|
|
50
|
-
const autoConnect = autoConnectProp &&
|
|
51
|
-
(isAuthenticated && !isLoading) &&
|
|
52
|
-
enabled &&
|
|
53
|
-
hasCentrifugoToken;
|
|
54
|
-
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
if (!isLoading) {
|
|
57
|
-
logger.info(`[RPC] Auto-connect decision: ${autoConnect ? 'YES' : 'NO'}`);
|
|
58
|
-
logger.info(`[RPC] - Authenticated: ${isAuthenticated}`);
|
|
59
|
-
logger.info(`[RPC] - Auth loading: ${isLoading}`);
|
|
60
|
-
logger.info(`[RPC] - Centrifugo enabled: ${enabled}`);
|
|
61
|
-
logger.info(`[RPC] - Centrifugo token from profile: ${hasCentrifugoToken}`);
|
|
62
|
-
logger.info(`[RPC] - WebSocket URL: ${wsUrl}`);
|
|
63
|
-
|
|
64
|
-
if (hasCentrifugoToken && centrifugoToken) {
|
|
65
|
-
logger.info(`[RPC] - Channels: ${centrifugoToken.channels?.join(', ')}`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}, [autoConnect, isAuthenticated, isLoading, enabled, hasCentrifugoToken, centrifugoToken, logger, wsUrl]);
|
|
69
|
-
|
|
70
|
-
const connect = useCallback(async () => {
|
|
71
|
-
if (hasConnectedRef.current || isConnectingRef.current) return;
|
|
72
|
-
if (isConnecting || isConnected) return;
|
|
73
|
-
|
|
74
|
-
isConnectingRef.current = true;
|
|
75
|
-
setIsConnecting(true);
|
|
76
|
-
setError(null);
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
logger.info('Connecting to WebSocket RPC server...');
|
|
80
|
-
|
|
81
|
-
if (!centrifugoToken?.token) {
|
|
82
|
-
throw new Error('No Centrifugo token available in user profile. Please refresh the page.');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const token = centrifugoToken.token;
|
|
86
|
-
let userId = user?.id?.toString() || '1';
|
|
87
|
-
|
|
88
|
-
if (!user?.id) {
|
|
89
|
-
try {
|
|
90
|
-
const tokenPayload = JSON.parse(atob(token.split('.')[1]));
|
|
91
|
-
userId = tokenPayload.user_id?.toString() || tokenPayload.sub?.toString() || '1';
|
|
92
|
-
} catch (err) {
|
|
93
|
-
// Silently fallback
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const baseRPC = new RPCClientClass(wsUrl, token, userId);
|
|
98
|
-
await baseRPC.connect();
|
|
99
|
-
|
|
100
|
-
if (!isMountedRef.current) {
|
|
101
|
-
baseRPC.disconnect();
|
|
102
|
-
isConnectingRef.current = false;
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const rpcClient = new APIClientClass(baseRPC);
|
|
107
|
-
|
|
108
|
-
hasConnectedRef.current = true;
|
|
109
|
-
isConnectingRef.current = false;
|
|
110
|
-
|
|
111
|
-
setBaseClient(baseRPC);
|
|
112
|
-
setClient(rpcClient);
|
|
113
|
-
setIsConnected(true);
|
|
114
|
-
setError(null);
|
|
115
|
-
|
|
116
|
-
logger.success('WebSocket RPC connected successfully');
|
|
117
|
-
} catch (err) {
|
|
118
|
-
const error = err instanceof Error ? err : new Error('Connection failed');
|
|
119
|
-
setError(error);
|
|
120
|
-
setBaseClient(null);
|
|
121
|
-
setClient(null);
|
|
122
|
-
setIsConnected(false);
|
|
123
|
-
hasConnectedRef.current = false;
|
|
124
|
-
isConnectingRef.current = false;
|
|
125
|
-
|
|
126
|
-
const isAuthError = error.message.includes('token') ||
|
|
127
|
-
error.message.includes('auth') ||
|
|
128
|
-
error.message.includes('expired');
|
|
129
|
-
|
|
130
|
-
if (isAuthError) {
|
|
131
|
-
logger.error('WebSocket RPC authentication failed', error);
|
|
132
|
-
} else {
|
|
133
|
-
logger.error('WebSocket RPC connection failed', error);
|
|
134
|
-
reconnectTimeoutRef.current = setTimeout(() => {
|
|
135
|
-
logger.info('Attempting to reconnect...');
|
|
136
|
-
connect();
|
|
137
|
-
}, 5000);
|
|
138
|
-
}
|
|
139
|
-
} finally {
|
|
140
|
-
setIsConnecting(false);
|
|
141
|
-
}
|
|
142
|
-
}, [wsUrl, centrifugoToken, user, logger, RPCClientClass, APIClientClass, isConnecting, isConnected]);
|
|
143
|
-
|
|
144
|
-
const disconnect = useCallback(() => {
|
|
145
|
-
if (isConnectingRef.current) return;
|
|
146
|
-
|
|
147
|
-
if (reconnectTimeoutRef.current) {
|
|
148
|
-
clearTimeout(reconnectTimeoutRef.current);
|
|
149
|
-
reconnectTimeoutRef.current = null;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (baseClient) {
|
|
153
|
-
logger.info('Disconnecting from WebSocket RPC server...');
|
|
154
|
-
baseClient.disconnect();
|
|
155
|
-
setBaseClient(null);
|
|
156
|
-
setClient(null);
|
|
157
|
-
setIsConnected(false);
|
|
158
|
-
setError(null);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
hasConnectedRef.current = false;
|
|
162
|
-
isConnectingRef.current = false;
|
|
163
|
-
}, [baseClient, logger]);
|
|
164
|
-
|
|
165
|
-
const reconnect = useCallback(async () => {
|
|
166
|
-
disconnect();
|
|
167
|
-
await connect();
|
|
168
|
-
}, [connect, disconnect]);
|
|
169
|
-
|
|
170
|
-
useEffect(() => {
|
|
171
|
-
isMountedRef.current = true;
|
|
172
|
-
|
|
173
|
-
if (autoConnect && !hasConnectedRef.current) {
|
|
174
|
-
connect();
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return () => {
|
|
178
|
-
if (isConnectingRef.current && !hasConnectedRef.current) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (!hasConnectedRef.current) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
isMountedRef.current = false;
|
|
187
|
-
disconnect();
|
|
188
|
-
};
|
|
189
|
-
}, [autoConnect, connect, disconnect]);
|
|
190
|
-
|
|
191
|
-
const connectionState = isConnected
|
|
192
|
-
? 'connected'
|
|
193
|
-
: isConnecting
|
|
194
|
-
? 'connecting'
|
|
195
|
-
: error
|
|
196
|
-
? 'error'
|
|
197
|
-
: 'disconnected';
|
|
198
|
-
|
|
199
|
-
const value: CentrifugoContextValue = {
|
|
200
|
-
client,
|
|
201
|
-
baseClient,
|
|
202
|
-
isConnected,
|
|
203
|
-
isConnecting,
|
|
204
|
-
error,
|
|
205
|
-
connectionState,
|
|
206
|
-
enabled,
|
|
207
|
-
connect,
|
|
208
|
-
disconnect,
|
|
209
|
-
reconnect,
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
return (
|
|
213
|
-
<CentrifugoContext.Provider value={value}>
|
|
214
|
-
{children}
|
|
215
|
-
<CentrifugoDebug />
|
|
216
|
-
</CentrifugoContext.Provider>
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export function useCentrifugo(): CentrifugoContextValue {
|
|
221
|
-
const context = useContext(CentrifugoContext);
|
|
222
|
-
|
|
223
|
-
if (context === undefined) {
|
|
224
|
-
throw new Error('useCentrifugo must be used within a CentrifugoProvider');
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return context;
|
|
228
|
-
}
|
package/src/context/index.ts
DELETED
package/src/hooks/useLogger.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centrifugo Logger Hook
|
|
3
|
-
*
|
|
4
|
-
* React hook for Centrifugo logging using consola library.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
'use client';
|
|
8
|
-
|
|
9
|
-
import { useMemo } from 'react';
|
|
10
|
-
import { createConsola, type ConsolaInstance } from 'consola';
|
|
11
|
-
|
|
12
|
-
export interface CentrifugoLogger {
|
|
13
|
-
info: (message: string) => void;
|
|
14
|
-
debug: (message: string) => void;
|
|
15
|
-
warning: (message: string) => void;
|
|
16
|
-
error: (message: string, error?: Error) => void;
|
|
17
|
-
success: (message: string) => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* React hook for Centrifugo logger with consola
|
|
22
|
-
*
|
|
23
|
-
* @param isDevelopment - Enable debug logging (optional)
|
|
24
|
-
* @returns Centrifugo logger instance
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```tsx
|
|
28
|
-
* function MyComponent() {
|
|
29
|
-
* const logger = useCentrifugoLogger();
|
|
30
|
-
* logger.info('Connected to Centrifugo server');
|
|
31
|
-
* }
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
export function useCentrifugoLogger(isDevelopment = process.env.NODE_ENV === 'development'): CentrifugoLogger {
|
|
35
|
-
const logger = useMemo(() => {
|
|
36
|
-
const consola = createConsola({
|
|
37
|
-
level: isDevelopment ? 4 : 3, // debug in dev, info in prod
|
|
38
|
-
formatOptions: {
|
|
39
|
-
colors: true,
|
|
40
|
-
date: false,
|
|
41
|
-
compact: !isDevelopment,
|
|
42
|
-
},
|
|
43
|
-
}).withTag('Centrifugo');
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
info: (message: string) => {
|
|
47
|
-
if (isDevelopment) consola.info(message);
|
|
48
|
-
},
|
|
49
|
-
debug: (message: string) => {
|
|
50
|
-
if (isDevelopment) consola.debug(message);
|
|
51
|
-
},
|
|
52
|
-
warning: (message: string) => {
|
|
53
|
-
if (isDevelopment) consola.warn(message);
|
|
54
|
-
},
|
|
55
|
-
error: (message: string, error?: Error) => {
|
|
56
|
-
consola.error(message, error || '');
|
|
57
|
-
},
|
|
58
|
-
success: (message: string) => {
|
|
59
|
-
if (isDevelopment) consola.success(message);
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
}, [isDevelopment]);
|
|
63
|
-
|
|
64
|
-
return logger;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Alias for backward compatibility
|
|
68
|
-
export const useRPCLogger = useCentrifugoLogger;
|
|
69
|
-
export type RPCLogger = CentrifugoLogger;
|
package/src/types/index.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centrifugo Package Types
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface CentrifugoToken {
|
|
6
|
-
token: string;
|
|
7
|
-
centrifugo_url: string;
|
|
8
|
-
channels: string[];
|
|
9
|
-
expires_at: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface User {
|
|
13
|
-
id: number;
|
|
14
|
-
centrifugo?: CentrifugoToken;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface CentrifugoProviderProps {
|
|
18
|
-
children: React.ReactNode;
|
|
19
|
-
/** Is Centrifugo enabled */
|
|
20
|
-
enabled?: boolean;
|
|
21
|
-
/** Base RPC client class */
|
|
22
|
-
RPCClientClass: any;
|
|
23
|
-
/** API client wrapper class */
|
|
24
|
-
APIClientClass: any;
|
|
25
|
-
/** Custom WebSocket URL (defaults to user profile URL) */
|
|
26
|
-
url?: string;
|
|
27
|
-
/** Auto-connect on mount (default: true) */
|
|
28
|
-
autoConnect?: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface CentrifugoContextValue {
|
|
32
|
-
// Connection State
|
|
33
|
-
client: any;
|
|
34
|
-
baseClient: any;
|
|
35
|
-
isConnected: boolean;
|
|
36
|
-
isConnecting: boolean;
|
|
37
|
-
error: Error | null;
|
|
38
|
-
connectionState: string;
|
|
39
|
-
enabled: boolean;
|
|
40
|
-
|
|
41
|
-
// Connection Methods
|
|
42
|
-
connect: () => Promise<void>;
|
|
43
|
-
disconnect: () => void;
|
|
44
|
-
reconnect: () => Promise<void>;
|
|
45
|
-
}
|