@djangocfg/centrifugo 1.0.5 → 1.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/centrifugo",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Production-ready Centrifugo WebSocket client with React integration, RPC pattern, composable UI components, and comprehensive monitoring tools",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -19,8 +19,8 @@
19
19
  "centrifuge": "^5.2.2"
20
20
  },
21
21
  "peerDependencies": {
22
- "@djangocfg/ui": "^1.2.30",
23
- "@djangocfg/layouts": "^1.2.30",
22
+ "@djangocfg/ui": "^1.2.31",
23
+ "@djangocfg/layouts": "^1.2.31",
24
24
  "consola": "^3.4.2",
25
25
  "lucide-react": "^0.468.0",
26
26
  "moment": "^2.30.1",
@@ -28,7 +28,7 @@
28
28
  "react-dom": "^19.0.0"
29
29
  },
30
30
  "devDependencies": {
31
- "@djangocfg/typescript-config": "^1.2.30",
31
+ "@djangocfg/typescript-config": "^1.2.31",
32
32
  "@types/react": "^19.0.6",
33
33
  "@types/react-dom": "^19.0.2",
34
34
  "moment": "^2.30.1",
@@ -2,63 +2,73 @@
2
2
  * Centrifugo Monitor Dialog Component
3
3
  *
4
4
  * Sheet/Dialog wrapper for CentrifugoMonitor
5
+ * Uses event-driven approach - listens to OPEN_MONITOR_DIALOG event
5
6
  */
6
7
 
7
8
  'use client';
8
9
 
9
- import React from 'react';
10
+ import React, { useState } from 'react';
10
11
  import {
11
12
  Sheet,
12
13
  SheetContent,
13
14
  SheetHeader,
14
15
  SheetTitle,
15
16
  SheetDescription,
17
+ useEventListener,
16
18
  } from '@djangocfg/ui';
17
19
  import { Activity } from 'lucide-react';
18
- import { CentrifugoMonitor, type CentrifugoMonitorProps } from './CentrifugoMonitor';
20
+ import { CentrifugoMonitor } from './CentrifugoMonitor';
21
+ import { CENTRIFUGO_MONITOR_EVENTS, type OpenMonitorDialogPayload } from '../../events';
19
22
 
20
23
  // ─────────────────────────────────────────────────────────────────────────
21
- // Types
24
+ // Component
22
25
  // ─────────────────────────────────────────────────────────────────────────
23
26
 
24
- export interface CentrifugoMonitorDialogProps extends Omit<CentrifugoMonitorProps, 'className'> {
25
- open: boolean;
26
- onOpenChange: (open: boolean) => void;
27
- title?: string;
28
- description?: string;
29
- side?: 'left' | 'right' | 'top' | 'bottom';
30
- className?: string;
31
- }
27
+ export function CentrifugoMonitorDialog() {
28
+ const [open, setOpen] = useState(false);
29
+ const [variant, setVariant] = useState<'compact' | 'full' | 'minimal'>('full');
32
30
 
33
- // ─────────────────────────────────────────────────────────────────────────
34
- // Component
35
- // ─────────────────────────────────────────────────────────────────────────
31
+ // Listen for dialog open event
32
+ useEventListener<typeof CENTRIFUGO_MONITOR_EVENTS.OPEN_MONITOR_DIALOG, OpenMonitorDialogPayload>(
33
+ CENTRIFUGO_MONITOR_EVENTS.OPEN_MONITOR_DIALOG,
34
+ (payload) => {
35
+ if (payload?.variant) {
36
+ setVariant(payload.variant);
37
+ }
38
+ setOpen(true);
39
+ }
40
+ );
41
+
42
+ // Listen for dialog close event
43
+ useEventListener(
44
+ CENTRIFUGO_MONITOR_EVENTS.CLOSE_MONITOR_DIALOG,
45
+ () => {
46
+ setOpen(false);
47
+ }
48
+ );
49
+
50
+ const handleClose = () => {
51
+ setOpen(false);
52
+ };
36
53
 
37
- export function CentrifugoMonitorDialog({
38
- open,
39
- onOpenChange,
40
- title = 'Centrifugo Monitor',
41
- description = 'Real-time WebSocket monitoring and debugging',
42
- side = 'right',
43
- className = '',
44
- ...monitorProps
45
- }: CentrifugoMonitorDialogProps) {
46
54
  return (
47
- <Sheet open={open} onOpenChange={onOpenChange}>
55
+ <Sheet open={open} onOpenChange={(isOpen) => !isOpen && handleClose()}>
48
56
  <SheetContent
49
- side={side}
50
- className={`max-w-2xl w-[90vw] sm:w-[672px] ${className}`}
57
+ side="right"
58
+ className="max-w-2xl w-[90vw] sm:w-[672px]"
51
59
  >
52
60
  <SheetHeader>
53
61
  <SheetTitle className="flex items-center gap-2">
54
62
  <Activity className="h-5 w-5" />
55
- {title}
63
+ Centrifugo Monitor
56
64
  </SheetTitle>
57
- {description && <SheetDescription>{description}</SheetDescription>}
65
+ <SheetDescription>
66
+ Real-time WebSocket monitoring and debugging
67
+ </SheetDescription>
58
68
  </SheetHeader>
59
69
 
60
70
  <div className="mt-6">
61
- <CentrifugoMonitor {...monitorProps} />
71
+ <CentrifugoMonitor variant={variant} />
62
72
  </div>
63
73
  </SheetContent>
64
74
  </Sheet>
@@ -1,23 +1,24 @@
1
1
  /**
2
2
  * Centrifugo Monitor FAB Component
3
3
  *
4
- * Floating Action Button that opens CentrifugoMonitorDialog
4
+ * Floating Action Button that opens CentrifugoMonitorDialog via events
5
5
  * Replaces the old DebugPanel FAB
6
6
  */
7
7
 
8
8
  'use client';
9
9
 
10
- import React, { useState } from 'react';
10
+ import React from 'react';
11
11
  import { Activity } from 'lucide-react';
12
- import { CentrifugoMonitorDialog, type CentrifugoMonitorDialogProps } from './CentrifugoMonitorDialog';
12
+ import { emitOpenMonitorDialog, type OpenMonitorDialogPayload } from '../../events';
13
13
 
14
14
  // ─────────────────────────────────────────────────────────────────────────
15
15
  // Types
16
16
  // ─────────────────────────────────────────────────────────────────────────
17
17
 
18
- export interface CentrifugoMonitorFABProps extends Omit<CentrifugoMonitorDialogProps, 'open' | 'onOpenChange'> {
18
+ export interface CentrifugoMonitorFABProps {
19
19
  position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
20
20
  size?: 'sm' | 'md' | 'lg';
21
+ variant?: OpenMonitorDialogPayload['variant'];
21
22
  }
22
23
 
23
24
  // ─────────────────────────────────────────────────────────────────────────
@@ -27,9 +28,8 @@ export interface CentrifugoMonitorFABProps extends Omit<CentrifugoMonitorDialogP
27
28
  export function CentrifugoMonitorFAB({
28
29
  position = 'bottom-left',
29
30
  size = 'md',
30
- ...dialogProps
31
+ variant = 'full',
31
32
  }: CentrifugoMonitorFABProps) {
32
- const [isOpen, setIsOpen] = useState(false);
33
33
 
34
34
  // Position styles
35
35
  const positionStyles = {
@@ -52,30 +52,24 @@ export function CentrifugoMonitorFAB({
52
52
  lg: 'h-7 w-7',
53
53
  };
54
54
 
55
- return (
56
- <>
57
- {/* FAB Button */}
58
- <button
59
- onClick={() => setIsOpen(true)}
60
- className="rounded-full bg-primary text-primary-foreground shadow-lg hover:bg-primary/90 transition-all duration-200 flex items-center justify-center hover:scale-110"
61
- style={{
62
- position: 'fixed',
63
- ...positionStyles[position],
64
- ...sizeStyles[size],
65
- zIndex: 9999,
66
- }}
67
- aria-label="Open Centrifugo Monitor"
68
- >
69
- <Activity className={iconSizes[size]} />
70
- </button>
55
+ const handleClick = () => {
56
+ emitOpenMonitorDialog({ variant });
57
+ };
71
58
 
72
- {/* Dialog */}
73
- <CentrifugoMonitorDialog
74
- open={isOpen}
75
- onOpenChange={setIsOpen}
76
- {...dialogProps}
77
- />
78
- </>
59
+ return (
60
+ <button
61
+ onClick={handleClick}
62
+ className="rounded-full bg-primary text-primary-foreground shadow-lg hover:bg-primary/90 transition-all duration-200 flex items-center justify-center hover:scale-110"
63
+ style={{
64
+ position: 'fixed',
65
+ ...positionStyles[position],
66
+ ...sizeStyles[size],
67
+ zIndex: 9999,
68
+ }}
69
+ aria-label="Open Centrifugo Monitor"
70
+ >
71
+ <Activity className={iconSizes[size]} />
72
+ </button>
79
73
  );
80
74
  }
81
75
 
@@ -2,23 +2,22 @@
2
2
  * Centrifugo Monitor Widget Component
3
3
  *
4
4
  * Card-based widget for dashboards
5
- * Combines ConnectionStatusCard with compact monitor view
5
+ * Opens monitor dialog via events
6
6
  */
7
7
 
8
8
  'use client';
9
9
 
10
- import React, { useState } from 'react';
10
+ import React from 'react';
11
11
  import { Card, CardContent, CardHeader, CardTitle, Button } from '@djangocfg/ui';
12
12
  import { Activity, Maximize2 } from 'lucide-react';
13
13
  import { ConnectionStatus } from '../ConnectionStatus';
14
- import { CentrifugoMonitorDialog } from './CentrifugoMonitorDialog';
15
- import type { CentrifugoMonitorProps } from './CentrifugoMonitor';
14
+ import { emitOpenMonitorDialog } from '../../events';
16
15
 
17
16
  // ─────────────────────────────────────────────────────────────────────────
18
17
  // Types
19
18
  // ─────────────────────────────────────────────────────────────────────────
20
19
 
21
- export interface CentrifugoMonitorWidgetProps extends Omit<CentrifugoMonitorProps, 'variant' | 'className'> {
20
+ export interface CentrifugoMonitorWidgetProps {
22
21
  title?: string;
23
22
  showExpandButton?: boolean;
24
23
  className?: string;
@@ -32,43 +31,32 @@ export function CentrifugoMonitorWidget({
32
31
  title = 'WebSocket Monitor',
33
32
  showExpandButton = true,
34
33
  className = '',
35
- ...monitorProps
36
34
  }: CentrifugoMonitorWidgetProps) {
37
- const [isDialogOpen, setIsDialogOpen] = useState(false);
35
+ const handleExpand = () => {
36
+ emitOpenMonitorDialog({ variant: 'full' });
37
+ };
38
38
 
39
39
  return (
40
- <>
41
- <Card className={className}>
42
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
43
- <CardTitle className="text-sm font-medium flex items-center gap-2">
44
- <Activity className="h-4 w-4" />
45
- {title}
46
- </CardTitle>
47
- {showExpandButton && (
48
- <Button
49
- size="sm"
50
- variant="ghost"
51
- onClick={() => setIsDialogOpen(true)}
52
- >
53
- <Maximize2 className="h-4 w-4" />
54
- </Button>
55
- )}
56
- </CardHeader>
57
- <CardContent>
58
- <ConnectionStatus variant="detailed" showUptime showSubscriptions />
59
- </CardContent>
60
- </Card>
61
-
62
- {/* Full monitor dialog */}
63
- {showExpandButton && (
64
- <CentrifugoMonitorDialog
65
- open={isDialogOpen}
66
- onOpenChange={setIsDialogOpen}
67
- variant="full"
68
- {...monitorProps}
69
- />
70
- )}
71
- </>
40
+ <Card className={className}>
41
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
42
+ <CardTitle className="text-sm font-medium flex items-center gap-2">
43
+ <Activity className="h-4 w-4" />
44
+ {title}
45
+ </CardTitle>
46
+ {showExpandButton && (
47
+ <Button
48
+ size="sm"
49
+ variant="ghost"
50
+ onClick={handleExpand}
51
+ >
52
+ <Maximize2 className="h-4 w-4" />
53
+ </Button>
54
+ )}
55
+ </CardHeader>
56
+ <CardContent>
57
+ <ConnectionStatus variant="detailed" showUptime showSubscriptions />
58
+ </CardContent>
59
+ </Card>
72
60
  );
73
61
  }
74
62
 
@@ -8,7 +8,6 @@ export { CentrifugoMonitorFAB } from './CentrifugoMonitorFAB';
8
8
  export { CentrifugoMonitorWidget } from './CentrifugoMonitorWidget';
9
9
 
10
10
  export type { CentrifugoMonitorProps } from './CentrifugoMonitor';
11
- export type { CentrifugoMonitorDialogProps } from './CentrifugoMonitorDialog';
12
11
  export type { CentrifugoMonitorFABProps } from './CentrifugoMonitorFAB';
13
12
  export type { CentrifugoMonitorWidgetProps } from './CentrifugoMonitorWidget';
14
13
 
@@ -2,7 +2,7 @@
2
2
  * Connection Status Card Component
3
3
  *
4
4
  * Card wrapper for ConnectionStatus - ready for dashboard widgets
5
- * Clickable - opens CentrifugoMonitorDialog on click
5
+ * Clickable - opens CentrifugoMonitorDialog on click via events
6
6
  */
7
7
 
8
8
  'use client';
@@ -12,7 +12,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@djangocfg/ui';
12
12
  import { Wifi, WifiOff } from 'lucide-react';
13
13
  import { useCentrifugo } from '../../providers/CentrifugoProvider';
14
14
  import { ConnectionStatus } from './ConnectionStatus';
15
- import { CentrifugoMonitorDialog } from '../CentrifugoMonitor/CentrifugoMonitorDialog';
15
+ import { emitOpenMonitorDialog } from '../../events';
16
16
 
17
17
  // ─────────────────────────────────────────────────────────────────────────
18
18
  // Types
@@ -34,37 +34,33 @@ export function ConnectionStatusCard({
34
34
  className = '',
35
35
  }: ConnectionStatusCardProps) {
36
36
  const { isConnected } = useCentrifugo();
37
- const [dialogOpen, setDialogOpen] = React.useState(false);
38
37
 
39
38
  const statusColor = isConnected ? 'border-green-500' : 'border-red-500';
40
39
 
41
- return (
42
- <>
43
- <Card
44
- className={`${statusColor} ${className} cursor-pointer hover:shadow-lg transition-shadow`}
45
- style={{ borderLeftWidth: '4px' }}
46
- onClick={() => setDialogOpen(true)}
47
- >
48
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
49
- <CardTitle className="text-sm font-medium">WebSocket</CardTitle>
50
- <div className={isConnected ? 'text-green-600' : 'text-red-600'}>
51
- {isConnected ? <Wifi className="h-4 w-4" /> : <WifiOff className="h-4 w-4" />}
52
- </div>
53
- </CardHeader>
54
- <CardContent>
55
- <ConnectionStatus
56
- variant="detailed"
57
- showUptime={showUptime}
58
- showSubscriptions={showSubscriptions}
59
- />
60
- </CardContent>
61
- </Card>
40
+ const handleClick = () => {
41
+ emitOpenMonitorDialog({ variant: 'full' });
42
+ };
62
43
 
63
- <CentrifugoMonitorDialog
64
- open={dialogOpen}
65
- onOpenChange={setDialogOpen}
66
- />
67
- </>
44
+ return (
45
+ <Card
46
+ className={`${statusColor} ${className} cursor-pointer hover:shadow-lg transition-shadow`}
47
+ style={{ borderLeftWidth: '4px' }}
48
+ onClick={handleClick}
49
+ >
50
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
51
+ <CardTitle className="text-sm font-medium">WebSocket</CardTitle>
52
+ <div className={isConnected ? 'text-green-600' : 'text-red-600'}>
53
+ {isConnected ? <Wifi className="h-4 w-4" /> : <WifiOff className="h-4 w-4" />}
54
+ </div>
55
+ </CardHeader>
56
+ <CardContent>
57
+ <ConnectionStatus
58
+ variant="detailed"
59
+ showUptime={showUptime}
60
+ showSubscriptions={showSubscriptions}
61
+ />
62
+ </CardContent>
63
+ </Card>
68
64
  );
69
65
  }
70
66
 
package/src/events.ts ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Centrifugo Package Events
3
+ *
4
+ * Event-driven communication for Centrifugo monitor dialog
5
+ */
6
+
7
+ import { events } from '@djangocfg/ui';
8
+
9
+ // ─────────────────────────────────────────────────────────────────────────
10
+ // Event Types
11
+ // ─────────────────────────────────────────────────────────────────────────
12
+
13
+ export const CENTRIFUGO_MONITOR_EVENTS = {
14
+ OPEN_MONITOR_DIALOG: 'CENTRIFUGO_OPEN_MONITOR_DIALOG',
15
+ CLOSE_MONITOR_DIALOG: 'CENTRIFUGO_CLOSE_MONITOR_DIALOG',
16
+ } as const;
17
+
18
+ // ─────────────────────────────────────────────────────────────────────────
19
+ // Event Payload Types
20
+ // ─────────────────────────────────────────────────────────────────────────
21
+
22
+ export interface OpenMonitorDialogPayload {
23
+ variant?: 'compact' | 'full' | 'minimal';
24
+ defaultTab?: 'connection' | 'messages' | 'subscriptions';
25
+ }
26
+
27
+ // ─────────────────────────────────────────────────────────────────────────
28
+ // Event Emitters
29
+ // ─────────────────────────────────────────────────────────────────────────
30
+
31
+ export const emitOpenMonitorDialog = (payload?: OpenMonitorDialogPayload) => {
32
+ events.publish({
33
+ type: CENTRIFUGO_MONITOR_EVENTS.OPEN_MONITOR_DIALOG,
34
+ payload: payload || {},
35
+ });
36
+ };
37
+
38
+ export const emitCloseMonitorDialog = () => {
39
+ events.publish({
40
+ type: CENTRIFUGO_MONITOR_EVENTS.CLOSE_MONITOR_DIALOG,
41
+ payload: {},
42
+ });
43
+ };
44
+
package/src/index.ts CHANGED
@@ -99,6 +99,12 @@ export type {
99
99
  export { centrifugoConfig, isDevelopment, isProduction, isStaticBuild } from './config';
100
100
  export type { CentrifugoConfig } from './config';
101
101
 
102
+ // ─────────────────────────────────────────────────────────────────────────
103
+ // Events
104
+ // ─────────────────────────────────────────────────────────────────────────
105
+
106
+ export * from './events';
107
+
102
108
  // ─────────────────────────────────────────────────────────────────────────
103
109
  // Components (Universal, composable monitoring components)
104
110
  // ─────────────────────────────────────────────────────────────────────────
@@ -11,9 +11,10 @@ import { createContext, useContext, useState, useEffect, useCallback, useRef, us
11
11
  import { useAuth } from '@djangocfg/layouts';
12
12
  import { CentrifugoRPCClient } from '../../core/client';
13
13
  import { createLogger } from '../../core/logger';
14
- import type { ConnectionState, CentrifugoToken } from '../../core/types';
14
+ import type { ConnectionState, CentrifugoToken, ActiveSubscription } from '../../core/types';
15
15
  import { LogsProvider } from '../LogsProvider';
16
16
  import { isStaticBuild } from '../../config';
17
+ import { CentrifugoMonitorDialog } from '../../components/CentrifugoMonitor/CentrifugoMonitorDialog';
17
18
 
18
19
  // ─────────────────────────────────────────────────────────────────────────
19
20
  // Context
@@ -32,7 +33,7 @@ export interface CentrifugoContextValue {
32
33
  // Connection Info
33
34
  uptime: number; // seconds
34
35
  subscriptions: string[];
35
- activeSubscriptions: import('../../core/types').ActiveSubscription[];
36
+ activeSubscriptions: ActiveSubscription[];
36
37
 
37
38
  // Controls
38
39
  connect: () => Promise<void>;
@@ -76,7 +77,7 @@ function CentrifugoProviderInner({
76
77
  const [connectionTime, setConnectionTime] = useState<Date | null>(null);
77
78
  const [uptime, setUptime] = useState<number>(0);
78
79
  const [subscriptions, setSubscriptions] = useState<string[]>([]);
79
- const [activeSubscriptions, setActiveSubscriptions] = useState<import('../../core/types').ActiveSubscription[]>([]);
80
+ const [activeSubscriptions, setActiveSubscriptions] = useState<ActiveSubscription[]>([]);
80
81
 
81
82
  const logger = useMemo(() => createLogger({ source: 'provider' }), []);
82
83
 
@@ -145,7 +146,7 @@ function CentrifugoProviderInner({
145
146
  setSubscriptions(subs);
146
147
 
147
148
  // Convert to ActiveSubscription format
148
- const activeSubs: import('../../core/types').ActiveSubscription[] = subs.map((channel) => ({
149
+ const activeSubs: ActiveSubscription[] = subs.map((channel) => ({
149
150
  channel,
150
151
  type: 'client' as const,
151
152
  subscribedAt: Date.now(),
@@ -337,13 +338,16 @@ function CentrifugoProviderInner({
337
338
  }
338
339
 
339
340
  // ─────────────────────────────────────────────────────────────────────────
340
- // Main Provider (wraps LogsProvider)
341
+ // Main Provider (wraps LogsProvider and includes Dialog)
341
342
  // ─────────────────────────────────────────────────────────────────────────
342
343
 
343
344
  export function CentrifugoProvider(props: CentrifugoProviderProps) {
344
345
  return (
345
346
  <LogsProvider>
346
- <CentrifugoProviderInner {...props} />
347
+ <CentrifugoProviderInner {...props}>
348
+ {props.children}
349
+ <CentrifugoMonitorDialog />
350
+ </CentrifugoProviderInner>
347
351
  </LogsProvider>
348
352
  );
349
353
  }