@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.
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 +88 -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 +380 -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
@@ -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
- }
@@ -1,5 +0,0 @@
1
- /**
2
- * Centrifugo Providers
3
- */
4
-
5
- export { CentrifugoProvider, useCentrifugo } from './CentrifugoProvider';
@@ -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;
@@ -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
- }