@djangocfg/centrifugo 1.0.1 → 1.0.2

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 +102 -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 +381 -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
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Logs Provider
3
+ *
4
+ * Provides access to accumulated logs via React Context.
5
+ * Wraps LogsStore and exposes logs + controls.
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react';
11
+ import type { LogEntry, LogLevel } from '../../core/types';
12
+ import { getGlobalLogsStore } from '../../core/logger';
13
+
14
+ // ─────────────────────────────────────────────────────────────────────────
15
+ // Context
16
+ // ─────────────────────────────────────────────────────────────────────────
17
+
18
+ export interface LogsContextValue {
19
+ logs: LogEntry[];
20
+ filteredLogs: LogEntry[];
21
+ filter: LogsFilter;
22
+ setFilter: (filter: Partial<LogsFilter>) => void;
23
+ clearLogs: () => void;
24
+ count: number;
25
+ }
26
+
27
+ export interface LogsFilter {
28
+ level?: LogLevel;
29
+ source?: LogEntry['source'];
30
+ search?: string;
31
+ }
32
+
33
+ const LogsContext = createContext<LogsContextValue | undefined>(undefined);
34
+
35
+ // ─────────────────────────────────────────────────────────────────────────
36
+ // Provider
37
+ // ─────────────────────────────────────────────────────────────────────────
38
+
39
+ export interface LogsProviderProps {
40
+ children: ReactNode;
41
+ }
42
+
43
+ export function LogsProvider({ children }: LogsProviderProps) {
44
+ const [logs, setLogs] = useState<LogEntry[]>([]);
45
+ const [filter, setFilterState] = useState<LogsFilter>({});
46
+
47
+ const logsStore = getGlobalLogsStore();
48
+
49
+ // Subscribe to log changes
50
+ useEffect(() => {
51
+ // Initial load
52
+ setLogs(logsStore.getAll());
53
+
54
+ // Subscribe to updates
55
+ const unsubscribe = logsStore.subscribe((updatedLogs) => {
56
+ setLogs(updatedLogs);
57
+ });
58
+
59
+ return unsubscribe;
60
+ }, [logsStore]);
61
+
62
+ // Filter logs
63
+ const filteredLogs = logs.filter((log) => {
64
+ if (filter.level && log.level !== filter.level) return false;
65
+ if (filter.source && log.source !== filter.source) return false;
66
+ if (filter.search) {
67
+ const searchLower = filter.search.toLowerCase();
68
+ return log.message.toLowerCase().includes(searchLower);
69
+ }
70
+ return true;
71
+ });
72
+
73
+ // Set filter (merge with existing)
74
+ const setFilter = useCallback((partialFilter: Partial<LogsFilter>) => {
75
+ setFilterState((prev) => ({ ...prev, ...partialFilter }));
76
+ }, []);
77
+
78
+ // Clear all logs
79
+ const clearLogs = useCallback(() => {
80
+ logsStore.clear();
81
+ }, [logsStore]);
82
+
83
+ const value: LogsContextValue = {
84
+ logs,
85
+ filteredLogs,
86
+ filter,
87
+ setFilter,
88
+ clearLogs,
89
+ count: logs.length,
90
+ };
91
+
92
+ return <LogsContext.Provider value={value}>{children}</LogsContext.Provider>;
93
+ }
94
+
95
+ // ─────────────────────────────────────────────────────────────────────────
96
+ // Hook
97
+ // ─────────────────────────────────────────────────────────────────────────
98
+
99
+ export function useLogs(): LogsContextValue {
100
+ const context = useContext(LogsContext);
101
+
102
+ if (context === undefined) {
103
+ throw new Error('useLogs must be used within a LogsProvider');
104
+ }
105
+
106
+ return context;
107
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Logs Provider
3
+ */
4
+
5
+ export { LogsProvider, useLogs } from './LogsProvider';
6
+ export type { LogsContextValue, LogsFilter, LogsProviderProps } from './LogsProvider';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Providers Module
3
+ */
4
+
5
+ export { CentrifugoProvider, useCentrifugo } from './CentrifugoProvider';
6
+ export type { CentrifugoContextValue, CentrifugoProviderProps } from './CentrifugoProvider';
7
+
8
+ export { LogsProvider, useLogs } from './LogsProvider';
9
+ export type { LogsContextValue, LogsFilter, LogsProviderProps } from './LogsProvider';
package/API_GENERATOR.md DELETED
@@ -1,253 +0,0 @@
1
- # Centrifugo Client Generator
2
-
3
- Documentation for generating Centrifugo WebSocket RPC clients from gRPC services and Django configuration.
4
-
5
- ## Overview
6
-
7
- The Centrifugo client generator creates typed WebSocket RPC clients from:
8
- - **gRPC Services**: Protocol buffer definitions and service implementations
9
- - **Django Config**: Configuration schema from `config.py`
10
-
11
- This enables full-stack type safety and auto-completion for WebSocket RPC communication.
12
-
13
- ## Paths Reference
14
-
15
- ```
16
- $PACKAGES = ../packages (monorepo packages root)
17
- $ADMIN = ../apps/admin (admin app root)
18
- $DJANGO = ../../../../solution/projects/django (django project root)
19
- $DJANGO_CFG = ../../../../solution/projects/django_cfg (django_cfg package)
20
- ```
21
-
22
- ## Quick Start
23
-
24
- ```bash
25
- # From admin app root
26
- cd $ADMIN && make centrifugo
27
- ```
28
-
29
- This will:
30
- 1. Generate clients (TypeScript, Python, Go) in `$DJANGO/openapi/centrifugo/`
31
- 2. Copy TypeScript client to `$ADMIN/src/centrifugo/generated/`
32
-
33
- ## Generator Architecture
34
-
35
- ### 1. Management Command
36
-
37
- **Location**: `$DJANGO/core/management/commands/generate_centrifugo.py`
38
-
39
- This is the main entry point that orchestrates the generation process.
40
-
41
- ```python
42
- # Usage
43
- python manage.py generate_centrifugo
44
- ```
45
-
46
- ### 2. Codegen Logic
47
-
48
- **Location**: `$DJANGO_CFG/apps/integrations/centrifugo/codegen/`
49
-
50
- The generator has modular architecture:
51
-
52
- ```
53
- codegen/
54
- ├── management/commands/
55
- │ └── generate_centrifugo_clients.py # Core generator command
56
- ├── generators/
57
- │ ├── typescript_thin/ # TypeScript client generator
58
- │ │ ├── generator.py
59
- │ │ └── templates/ # Jinja2 templates
60
- │ ├── python/ # Python client generator
61
- │ └── go/ # Go client generator
62
- └── utils/ # Shared utilities
63
- ```
64
-
65
- ### 3. Templates
66
-
67
- Client generation uses Jinja2 templates located in:
68
- - `$DJANGO_CFG/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/`
69
-
70
- Key templates:
71
- - `client.ts.j2` - RPC method wrappers
72
- - `rpc-client.ts.j2` - Base Centrifugo client with connection logic
73
- - `types.ts.j2` - TypeScript type definitions
74
-
75
- ## Source Data
76
-
77
- ### gRPC Services
78
-
79
- **Location**: `$DJANGO/apps/*/grpc_services`
80
-
81
- The generator scans all Django apps for gRPC service definitions:
82
- - `.proto` files
83
- - Service implementations
84
- - Method signatures
85
-
86
- ### Django Config
87
-
88
- **Location**: `$DJANGO/api/config.py`
89
-
90
- Configuration schema is extracted from:
91
- ```python
92
- class ConfigView(APIView):
93
- @action(detail=False, methods=['get'])
94
- def config(self, request):
95
- return Response({
96
- 'centrifugo': {
97
- 'enabled': settings.CENTRIFUGO_ENABLED,
98
- 'url': settings.CENTRIFUGO_URL,
99
- # ...
100
- }
101
- })
102
- ```
103
-
104
- ## Generated Outputs
105
-
106
- ### TypeScript Client
107
-
108
- **Output**: `$DJANGO/openapi/centrifugo/typescript/`
109
-
110
- Generated files:
111
- - `client.ts` - High-level RPC method wrappers
112
- - `rpc-client.ts` - Low-level Centrifugo client
113
- - `types.ts` - TypeScript interfaces
114
-
115
- **Copied to**: `$ADMIN/src/centrifugo/generated/`
116
-
117
- ### Python Client
118
-
119
- **Output**: `$DJANGO/openapi/centrifugo/python/`
120
-
121
- ### Go Client
122
-
123
- **Output**: `$DJANGO/openapi/centrifugo/go/`
124
-
125
- ## Modifying the Generator
126
-
127
- The codegen logic is in `$DJANGO_CFG` which is auto-linked to Django. Changes are immediately available:
128
-
129
- 1. Edit generator code or templates in `$DJANGO_CFG/apps/integrations/centrifugo/codegen/`
130
- 2. Re-run generation: `cd $ADMIN && make centrifugo`
131
- 3. Verify generated output
132
-
133
- ### Example: Modify TypeScript Template
134
-
135
- ```bash
136
- # Edit base client template
137
- vim $DJANGO_CFG/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2
138
-
139
- # Regenerate
140
- cd $ADMIN && make centrifugo
141
-
142
- # Check output
143
- cat $ADMIN/src/centrifugo/generated/rpc-client.ts
144
- ```
145
-
146
- ## Integration with Admin App
147
-
148
- The admin app wraps the generated client with app-specific providers:
149
-
150
- ```
151
- $ADMIN/src/centrifugo/
152
- ├── generated/ # Auto-generated (DO NOT EDIT)
153
- │ ├── client.ts
154
- │ ├── rpc-client.ts
155
- │ └── types.ts
156
- ├── AppCentrifugoProvider.tsx # App-specific wrapper
157
- └── index.ts # Re-exports from @djangocfg/centrifugo
158
- ```
159
-
160
- Universal components (context, hooks, debug) are in `@djangocfg/centrifugo` package:
161
-
162
- ```
163
- $PACKAGES/centrifugo/src/
164
- ├── context/ # React context provider
165
- ├── hooks/ # Custom hooks
166
- ├── components/ # Debug panel, etc.
167
- ├── config.ts # Environment config
168
- └── types.ts # Shared types
169
- ```
170
-
171
- ## Development Workflow
172
-
173
- ### 1. Add New gRPC Service
174
-
175
- ```bash
176
- # 1. Define .proto file
177
- vim $DJANGO/apps/myapp/grpc_services/myservice.proto
178
-
179
- # 2. Implement service
180
- vim $DJANGO/apps/myapp/grpc_services/myservice.py
181
-
182
- # 3. Regenerate client
183
- cd $ADMIN && make centrifugo
184
- ```
185
-
186
- ### 2. Use in Frontend
187
-
188
- ```tsx
189
- import { useCentrifugo } from '@/centrifugo';
190
-
191
- function MyComponent() {
192
- const { client, isConnected } = useCentrifugo();
193
-
194
- const handleCall = async () => {
195
- // Auto-generated typed method
196
- const result = await client?.myMethod({ param: 'value' });
197
- };
198
- }
199
- ```
200
-
201
- ## Troubleshooting
202
-
203
- ### Client Generation Fails
204
-
205
- 1. Check Django apps are properly configured
206
- 2. Verify gRPC services are valid
207
- 3. Check template syntax in `$DJANGO_CFG/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/`
208
-
209
- ### TypeScript Errors After Generation
210
-
211
- 1. Check generated types in `src/centrifugo/generated/types.ts`
212
- 2. Verify imports match package structure
213
- 3. Run type check: `pnpm tsc --noEmit`
214
-
215
- ### Connection Issues
216
-
217
- Use the debug panel (FAB button in bottom-left corner) to inspect:
218
- - Connection state
219
- - Authentication status
220
- - WebSocket URL
221
- - Channel subscriptions
222
- - Error messages
223
-
224
- ## Advanced Configuration
225
-
226
- ### Custom Templates
227
-
228
- You can customize the generated output by modifying templates:
229
-
230
- ```bash
231
- # Location
232
- $DJANGO_CFG/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/
233
-
234
- # Files
235
- client.ts.j2 # Method wrappers
236
- rpc-client.ts.j2 # Base client
237
- types.ts.j2 # Type definitions
238
- ```
239
-
240
- ### Generator Options
241
-
242
- The `generate_centrifugo_clients` command supports options:
243
-
244
- ```bash
245
- python manage.py generate_centrifugo_clients --help
246
- ```
247
-
248
- ## References
249
-
250
- - **Centrifugo Docs**: https://centrifugal.dev/
251
- - **Centrifuge-JS**: https://github.com/centrifugal/centrifuge-js
252
- - **gRPC**: https://grpc.io/
253
- - **Jinja2**: https://jinja.palletsprojects.com/
@@ -1,182 +0,0 @@
1
- /**
2
- * Centrifugo Debug Component
3
- *
4
- * Displays detailed Centrifugo connection status and diagnostics
5
- */
6
-
7
- 'use client';
8
-
9
- import React from 'react';
10
- import { useCentrifugo } from '../context';
11
- import { useAuth } from '@djangocfg/layouts';
12
- import { Card, CardHeader, CardTitle, CardContent, Badge, Separator, Button, useCopy } from '@djangocfg/ui';
13
- import { PrettyCode } from '@djangocfg/ui/tools';
14
- import { Wifi, WifiOff, Radio, X, Copy, RefreshCw } from 'lucide-react';
15
- import { centrifugoConfig } from '../config';
16
-
17
- export function CentrifugoDebug() {
18
- const { isConnected, isConnecting, error, connectionState, baseClient, enabled, reconnect } = useCentrifugo();
19
- const { isAuthenticated, isLoading: authLoading, user } = useAuth();
20
- const [isVisible, setIsVisible] = React.useState(true);
21
- const [isReconnecting, setIsReconnecting] = React.useState(false);
22
- const { copyToClipboard } = useCopy();
23
-
24
- const centrifugoToken = user?.centrifugo;
25
- const hasCentrifugoToken = !!centrifugoToken?.token;
26
-
27
- const shouldConnect = isAuthenticated && !authLoading && enabled && hasCentrifugoToken;
28
-
29
- const debugInfo = React.useMemo(() => ({
30
- connection: {
31
- status: connectionState,
32
- isConnected,
33
- isConnecting,
34
- error: error?.message || null,
35
- },
36
- auth: {
37
- isAuthenticated,
38
- authLoading,
39
- userId: user?.id,
40
- },
41
- config: {
42
- centrifugoEnabled: enabled,
43
- hasCentrifugoToken,
44
- },
45
- websocket: {
46
- url: centrifugoToken?.centrifugo_url || null,
47
- channels: centrifugoToken?.channels || [],
48
- activeSubscriptions: baseClient?.getAllSubscriptions?.() || [],
49
- },
50
- autoConnect: {
51
- shouldConnect,
52
- reasons: {
53
- authenticated: isAuthenticated,
54
- authLoading: !authLoading,
55
- centrifugoEnabled: enabled,
56
- hasToken: hasCentrifugoToken,
57
- },
58
- },
59
- }), [connectionState, isConnected, isConnecting, error, isAuthenticated, authLoading, user?.id, enabled, hasCentrifugoToken, centrifugoToken, baseClient, shouldConnect]);
60
-
61
- const copyDebugInfo = () => {
62
- copyToClipboard(JSON.stringify(debugInfo, null, 2), 'Debug info copied to clipboard');
63
- };
64
-
65
- const handleReconnect = async () => {
66
- setIsReconnecting(true);
67
- try {
68
- await reconnect();
69
- } finally {
70
- setTimeout(() => setIsReconnecting(false), 1000);
71
- }
72
- };
73
-
74
- // Only show in development mode and not in static builds
75
- if (!centrifugoConfig.showDebugPanel) {
76
- return null;
77
- }
78
-
79
- return (
80
- <>
81
- {/* FAB Button (always visible) */}
82
- <button
83
- onClick={() => setIsVisible(!isVisible)}
84
- className="rounded-full bg-primary text-primary-foreground shadow-lg hover:shadow-xl transition-all duration-200 flex items-center justify-center"
85
- style={{ position: 'fixed', bottom: '1rem', left: '1rem', width: '56px', height: '56px', zIndex: 9999 }}
86
- title="Toggle Centrifugo Debug Panel"
87
- >
88
- {isConnected ? (
89
- <Wifi style={{ width: '24px', height: '24px' }} />
90
- ) : (
91
- <WifiOff style={{ width: '24px', height: '24px' }} />
92
- )}
93
- </button>
94
-
95
- {/* Debug Panel */}
96
- {isVisible && (
97
- <div style={{ position: 'fixed', bottom: '5rem', left: '1rem', width: '420px', zIndex: 9999 }}>
98
- <Card className="shadow-lg">
99
- <CardHeader className="pb-3">
100
- <div className="flex items-center justify-between">
101
- <div className="flex items-center gap-2">
102
- {isConnected ? (
103
- <Wifi className="text-green-600 dark:text-green-500" style={{ width: '16px', height: '16px' }} />
104
- ) : (
105
- <WifiOff className="text-red-600 dark:text-red-500" style={{ width: '16px', height: '16px' }} />
106
- )}
107
- <CardTitle className="text-base">Centrifugo Debug</CardTitle>
108
- </div>
109
- <div className="flex items-center gap-1">
110
- <Button
111
- variant="ghost"
112
- size="sm"
113
- onClick={handleReconnect}
114
- disabled={isReconnecting || isConnecting}
115
- className="px-2 py-1"
116
- title="Reconnect"
117
- >
118
- <RefreshCw
119
- style={{ width: '14px', height: '14px' }}
120
- className={isReconnecting ? 'animate-spin' : ''}
121
- />
122
- </Button>
123
- <Button
124
- variant="ghost"
125
- size="sm"
126
- onClick={copyDebugInfo}
127
- className="px-2 py-1"
128
- title="Copy debug info"
129
- >
130
- <Copy style={{ width: '14px', height: '14px' }} />
131
- </Button>
132
- <Button
133
- variant="ghost"
134
- size="sm"
135
- onClick={() => setIsVisible(false)}
136
- className="px-2 py-1"
137
- >
138
- <X style={{ width: '14px', height: '14px' }} />
139
- </Button>
140
- </div>
141
- </div>
142
- </CardHeader>
143
-
144
- <CardContent className="space-y-3">
145
- {/* Connection Status Badge */}
146
- <div className="flex items-center justify-between">
147
- <span className="text-sm font-medium">Status:</span>
148
- <Badge variant={isConnected ? 'default' : isConnecting ? 'secondary' : 'destructive'}>
149
- {connectionState.toUpperCase()}
150
- </Badge>
151
- </div>
152
-
153
- {/* Auto-Connect Decision */}
154
- <div className="flex items-center justify-between">
155
- <span className="text-sm font-medium">Should Connect:</span>
156
- <Badge variant={shouldConnect ? 'default' : 'outline'}>
157
- {shouldConnect ? 'YES' : 'NO'}
158
- </Badge>
159
- </div>
160
-
161
- <Separator />
162
-
163
- {/* JSON Debug Output */}
164
- <div className="space-y-2">
165
- <div className="flex items-center gap-2">
166
- <Radio className="text-muted-foreground" style={{ width: '14px', height: '14px' }} />
167
- <span className="text-sm font-medium">Debug State:</span>
168
- </div>
169
- <div className="max-h-[300px] overflow-y-auto">
170
- <PrettyCode data={debugInfo} language="json" />
171
- </div>
172
- </div>
173
- </CardContent>
174
- </Card>
175
- </div>
176
- )}
177
- </>
178
- );
179
- }
180
-
181
- // Alias for backward compatibility
182
- export const WSRPCDebug = CentrifugoDebug;
@@ -1,5 +0,0 @@
1
- /**
2
- * Centrifugo Components
3
- */
4
-
5
- export { CentrifugoDebug, WSRPCDebug } from './CentrifugoDebug';