@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
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscription Management Module
|
|
3
|
+
*
|
|
4
|
+
* Handles Centrifugo channel subscriptions (pub/sub pattern).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Centrifuge, Subscription } from 'centrifuge';
|
|
8
|
+
import type { Logger } from '../logger';
|
|
9
|
+
|
|
10
|
+
export interface SubscriptionManager {
|
|
11
|
+
channelSubscriptions: Map<string, Subscription>;
|
|
12
|
+
centrifuge: Centrifuge;
|
|
13
|
+
logger: Logger;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Subscribe to a Centrifugo channel for real-time events.
|
|
18
|
+
*
|
|
19
|
+
* @param manager - Subscription manager state
|
|
20
|
+
* @param channel - Channel name (e.g., 'bot#bot-123#heartbeat')
|
|
21
|
+
* @param callback - Callback for received messages
|
|
22
|
+
* @returns Unsubscribe function
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* const unsubscribe = subscribe(manager, 'bot#bot-123#heartbeat', (data) => {
|
|
26
|
+
* console.log('Heartbeat:', data);
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* // Later: unsubscribe when done
|
|
30
|
+
* unsubscribe();
|
|
31
|
+
*/
|
|
32
|
+
export function subscribe(
|
|
33
|
+
manager: SubscriptionManager,
|
|
34
|
+
channel: string,
|
|
35
|
+
callback: (data: any) => void
|
|
36
|
+
): () => void {
|
|
37
|
+
const { channelSubscriptions, centrifuge, logger } = manager;
|
|
38
|
+
|
|
39
|
+
// Check if already subscribed - reuse existing subscription
|
|
40
|
+
const existingSub = channelSubscriptions.get(channel);
|
|
41
|
+
if (existingSub) {
|
|
42
|
+
logger.warning(`Already subscribed to ${channel}, reusing existing subscription`);
|
|
43
|
+
|
|
44
|
+
// Add new callback handler to existing subscription
|
|
45
|
+
const handler = (ctx: any) => {
|
|
46
|
+
try {
|
|
47
|
+
callback(ctx.data);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
logger.error(`Error in subscription callback for ${channel}`, error);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
existingSub.on('publication', handler);
|
|
54
|
+
|
|
55
|
+
// Return unsubscribe that only removes this specific handler
|
|
56
|
+
return () => {
|
|
57
|
+
existingSub.off('publication', handler);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Create new subscription using Centrifuge's getSubscription or newSubscription
|
|
62
|
+
let sub = centrifuge.getSubscription(channel);
|
|
63
|
+
if (!sub) {
|
|
64
|
+
sub = centrifuge.newSubscription(channel);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Handle publications with error handling
|
|
68
|
+
const publicationHandler = (ctx: any) => {
|
|
69
|
+
try {
|
|
70
|
+
callback(ctx.data);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
logger.error(`Error in subscription callback for ${channel}`, error);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
sub.on('publication', publicationHandler);
|
|
77
|
+
|
|
78
|
+
// Handle subscription lifecycle
|
|
79
|
+
sub.on('subscribed', () => {
|
|
80
|
+
logger.success(`Subscribed to ${channel}`);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
sub.on('error', (ctx: any) => {
|
|
84
|
+
logger.error(`Subscription error for ${channel}`, ctx.error);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Start subscription
|
|
88
|
+
sub.subscribe();
|
|
89
|
+
|
|
90
|
+
// Store subscription
|
|
91
|
+
channelSubscriptions.set(channel, sub);
|
|
92
|
+
|
|
93
|
+
// Return unsubscribe function
|
|
94
|
+
return () => unsubscribe(manager, channel);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Unsubscribe from a channel.
|
|
99
|
+
* Properly removes subscription from both internal map and Centrifuge client.
|
|
100
|
+
*/
|
|
101
|
+
export function unsubscribe(manager: SubscriptionManager, channel: string): void {
|
|
102
|
+
const { channelSubscriptions, centrifuge, logger } = manager;
|
|
103
|
+
const sub = channelSubscriptions.get(channel);
|
|
104
|
+
|
|
105
|
+
if (!sub) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
sub.unsubscribe();
|
|
111
|
+
centrifuge.removeSubscription(sub);
|
|
112
|
+
channelSubscriptions.delete(channel);
|
|
113
|
+
logger.info(`Unsubscribed from ${channel}`);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
logger.error(`Error unsubscribing from ${channel}`, error);
|
|
116
|
+
channelSubscriptions.delete(channel);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Unsubscribe from all channels.
|
|
122
|
+
*/
|
|
123
|
+
export function unsubscribeAll(manager: SubscriptionManager): void {
|
|
124
|
+
const { channelSubscriptions, centrifuge, logger } = manager;
|
|
125
|
+
|
|
126
|
+
if (channelSubscriptions.size === 0) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
channelSubscriptions.forEach((sub, channel) => {
|
|
131
|
+
try {
|
|
132
|
+
sub.unsubscribe();
|
|
133
|
+
centrifuge.removeSubscription(sub);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
logger.error(`Error unsubscribing from ${channel}`, error);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
channelSubscriptions.clear();
|
|
140
|
+
logger.info('Unsubscribed from all channels');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get list of active client-side subscriptions.
|
|
145
|
+
*/
|
|
146
|
+
export function getActiveSubscriptions(manager: SubscriptionManager): string[] {
|
|
147
|
+
return Array.from(manager.channelSubscriptions.keys());
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get list of server-side subscriptions (from JWT token).
|
|
152
|
+
*
|
|
153
|
+
* These are channels automatically subscribed by Centrifugo server
|
|
154
|
+
* based on the 'channels' claim in the JWT token.
|
|
155
|
+
*/
|
|
156
|
+
export function getServerSideSubscriptions(manager: SubscriptionManager): string[] {
|
|
157
|
+
try {
|
|
158
|
+
// Access Centrifuge.js internal state for server-side subs
|
|
159
|
+
// @ts-ignore - accessing internal property
|
|
160
|
+
const serverSubs = manager.centrifuge._serverSubs || {};
|
|
161
|
+
return Object.keys(serverSubs);
|
|
162
|
+
} catch {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get all active subscriptions (both client-side and server-side).
|
|
169
|
+
*/
|
|
170
|
+
export function getAllSubscriptions(manager: SubscriptionManager): string[] {
|
|
171
|
+
const clientSubs = getActiveSubscriptions(manager);
|
|
172
|
+
const serverSubs = getServerSideSubscriptions(manager);
|
|
173
|
+
|
|
174
|
+
// Combine and deduplicate
|
|
175
|
+
return Array.from(new Set([...clientSubs, ...serverSubs]));
|
|
176
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centrifugo Client Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Logger } from '../logger';
|
|
6
|
+
|
|
7
|
+
export interface CentrifugoClientOptions {
|
|
8
|
+
url: string;
|
|
9
|
+
token: string;
|
|
10
|
+
userId: string;
|
|
11
|
+
timeout?: number;
|
|
12
|
+
logger?: Logger;
|
|
13
|
+
/**
|
|
14
|
+
* Callback to get fresh token when current token expires.
|
|
15
|
+
* Centrifuge-js calls this automatically on token expiration.
|
|
16
|
+
*/
|
|
17
|
+
getToken?: () => Promise<string>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RPCOptions {
|
|
21
|
+
timeout?: number;
|
|
22
|
+
replyChannel?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RetryOptions {
|
|
26
|
+
/** Max retry attempts (default: 3) */
|
|
27
|
+
maxRetries?: number;
|
|
28
|
+
/** Base delay in ms for exponential backoff (default: 100) */
|
|
29
|
+
baseDelayMs?: number;
|
|
30
|
+
/** Max delay cap in ms (default: 2000) */
|
|
31
|
+
maxDelayMs?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface VersionCheckResult {
|
|
35
|
+
compatible: boolean;
|
|
36
|
+
clientVersion: string;
|
|
37
|
+
serverVersion: string;
|
|
38
|
+
message: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface PendingRequest {
|
|
42
|
+
resolve: (result: any) => void;
|
|
43
|
+
reject: (error: Error) => void;
|
|
44
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Version Checking Module
|
|
3
|
+
*
|
|
4
|
+
* Validates that client and server API versions are compatible.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { dispatchVersionMismatch } from '../../events';
|
|
8
|
+
|
|
9
|
+
import type { Logger } from '../logger';
|
|
10
|
+
import type { VersionCheckResult } from './types';
|
|
11
|
+
|
|
12
|
+
export interface VersionChecker {
|
|
13
|
+
namedRPC: <TRequest, TResponse>(method: string, data: TRequest) => Promise<TResponse>;
|
|
14
|
+
logger: Logger;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface CheckVersionResponse {
|
|
18
|
+
compatible: boolean;
|
|
19
|
+
client_version: string;
|
|
20
|
+
server_version: string;
|
|
21
|
+
message: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if client API version matches server version.
|
|
26
|
+
*
|
|
27
|
+
* Calls system.check_version RPC and dispatches a CustomEvent
|
|
28
|
+
* if versions don't match. Listen to 'centrifugo' event to handle globally.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { API_VERSION } from '@/_ws';
|
|
33
|
+
*
|
|
34
|
+
* // Check version after connect
|
|
35
|
+
* const result = await checkApiVersion(checker, API_VERSION);
|
|
36
|
+
* if (!result.compatible) {
|
|
37
|
+
* // Event already dispatched, but you can handle here too
|
|
38
|
+
* console.warn('Please refresh the page');
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* // Listen to event globally
|
|
42
|
+
* window.addEventListener('centrifugo', (e) => {
|
|
43
|
+
* if (e.detail.type === 'version_mismatch') {
|
|
44
|
+
* toast.warning('New version available. Please refresh.');
|
|
45
|
+
* }
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export async function checkApiVersion(
|
|
50
|
+
checker: VersionChecker,
|
|
51
|
+
clientVersion: string
|
|
52
|
+
): Promise<VersionCheckResult> {
|
|
53
|
+
const { namedRPC, logger } = checker;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const result = await namedRPC<{ client_version: string }, CheckVersionResponse>(
|
|
57
|
+
'system.check_version',
|
|
58
|
+
{ client_version: clientVersion }
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (!result.compatible) {
|
|
62
|
+
logger.warning(
|
|
63
|
+
`API version mismatch: client=${clientVersion}, server=${result.server_version}`
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Dispatch event for global handling
|
|
67
|
+
dispatchVersionMismatch({
|
|
68
|
+
clientVersion,
|
|
69
|
+
serverVersion: result.server_version,
|
|
70
|
+
message: result.message || 'API version mismatch. Please refresh the page.',
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
logger.success(`API version check passed: ${clientVersion}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
compatible: result.compatible,
|
|
78
|
+
clientVersion,
|
|
79
|
+
serverVersion: result.server_version,
|
|
80
|
+
message: result.message,
|
|
81
|
+
};
|
|
82
|
+
} catch (error) {
|
|
83
|
+
// If endpoint doesn't exist (old server), assume compatible
|
|
84
|
+
logger.warning('API version check endpoint not available');
|
|
85
|
+
return {
|
|
86
|
+
compatible: true,
|
|
87
|
+
clientVersion,
|
|
88
|
+
serverVersion: 'unknown',
|
|
89
|
+
message: 'Version check endpoint not available',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/events.ts
CHANGED
|
@@ -1,40 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centrifugo Package Events
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Unified event system for Centrifugo client.
|
|
5
|
+
* All events use single 'centrifugo' CustomEvent with type discriminator.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { events } from '@djangocfg/ui-nextjs';
|
|
9
9
|
|
|
10
10
|
// ─────────────────────────────────────────────────────────────────────────
|
|
11
|
-
// Event
|
|
11
|
+
// Event Constants
|
|
12
12
|
// ─────────────────────────────────────────────────────────────────────────
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Single event name for all Centrifugo events.
|
|
16
|
+
* Use `type` field in payload to distinguish event types.
|
|
17
|
+
*/
|
|
18
|
+
export const CENTRIFUGO_EVENT = 'centrifugo' as const;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Internal events for UI components (via events.publish)
|
|
22
|
+
*/
|
|
14
23
|
export const CENTRIFUGO_MONITOR_EVENTS = {
|
|
15
24
|
OPEN_MONITOR_DIALOG: 'CENTRIFUGO_OPEN_MONITOR_DIALOG',
|
|
16
25
|
CLOSE_MONITOR_DIALOG: 'CENTRIFUGO_CLOSE_MONITOR_DIALOG',
|
|
17
26
|
} as const;
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export const CENTRIFUGO_ERROR_EVENT = 'centrifugo-error' as const;
|
|
28
|
+
// Legacy exports for backwards compatibility
|
|
29
|
+
export const CENTRIFUGO_ERROR_EVENT = CENTRIFUGO_EVENT;
|
|
30
|
+
export const CENTRIFUGO_VERSION_MISMATCH_EVENT = CENTRIFUGO_EVENT;
|
|
23
31
|
|
|
24
32
|
// ─────────────────────────────────────────────────────────────────────────
|
|
25
|
-
// Event
|
|
33
|
+
// Event Types
|
|
26
34
|
// ─────────────────────────────────────────────────────────────────────────
|
|
27
35
|
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
export type CentrifugoEventType =
|
|
37
|
+
| 'error'
|
|
38
|
+
| 'version_mismatch'
|
|
39
|
+
| 'connected'
|
|
40
|
+
| 'disconnected'
|
|
41
|
+
| 'reconnecting';
|
|
32
42
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
export interface
|
|
43
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
44
|
+
// Event Payloads
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
export interface CentrifugoErrorData {
|
|
38
48
|
/** RPC method that failed */
|
|
39
49
|
method: string;
|
|
40
50
|
/** Error message */
|
|
@@ -45,60 +55,163 @@ export interface CentrifugoErrorPayload {
|
|
|
45
55
|
data?: any;
|
|
46
56
|
}
|
|
47
57
|
|
|
58
|
+
export interface CentrifugoVersionMismatchData {
|
|
59
|
+
/** Client API version hash */
|
|
60
|
+
clientVersion: string;
|
|
61
|
+
/** Server API version hash */
|
|
62
|
+
serverVersion: string;
|
|
63
|
+
/** Human-readable message */
|
|
64
|
+
message: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface CentrifugoConnectionData {
|
|
68
|
+
/** User ID */
|
|
69
|
+
userId?: string;
|
|
70
|
+
/** Reconnect attempt number (for reconnecting) */
|
|
71
|
+
attempt?: number;
|
|
72
|
+
/** Reason for disconnect */
|
|
73
|
+
reason?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Unified Centrifugo event payload
|
|
78
|
+
*/
|
|
79
|
+
export type CentrifugoEventPayload =
|
|
80
|
+
| { type: 'error'; data: CentrifugoErrorData }
|
|
81
|
+
| { type: 'version_mismatch'; data: CentrifugoVersionMismatchData }
|
|
82
|
+
| { type: 'connected'; data: CentrifugoConnectionData }
|
|
83
|
+
| { type: 'disconnected'; data: CentrifugoConnectionData }
|
|
84
|
+
| { type: 'reconnecting'; data: CentrifugoConnectionData };
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Full event detail (includes timestamp)
|
|
88
|
+
*/
|
|
89
|
+
export type CentrifugoEventDetail = CentrifugoEventPayload & {
|
|
90
|
+
timestamp: Date;
|
|
91
|
+
};
|
|
92
|
+
|
|
48
93
|
// ─────────────────────────────────────────────────────────────────────────
|
|
49
|
-
//
|
|
94
|
+
// Monitor Dialog Payloads (internal events)
|
|
50
95
|
// ─────────────────────────────────────────────────────────────────────────
|
|
51
96
|
|
|
52
|
-
export
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
});
|
|
57
|
-
};
|
|
97
|
+
export interface OpenMonitorDialogPayload {
|
|
98
|
+
variant?: 'compact' | 'full' | 'minimal';
|
|
99
|
+
defaultTab?: 'connection' | 'messages' | 'subscriptions';
|
|
100
|
+
}
|
|
58
101
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
102
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
103
|
+
// Legacy Types (for backwards compatibility)
|
|
104
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
/** @deprecated Use CentrifugoErrorData */
|
|
107
|
+
export type CentrifugoErrorPayload = CentrifugoErrorData;
|
|
108
|
+
|
|
109
|
+
/** @deprecated Use CentrifugoVersionMismatchData */
|
|
110
|
+
export type VersionMismatchPayload = CentrifugoVersionMismatchData;
|
|
65
111
|
|
|
66
112
|
// ─────────────────────────────────────────────────────────────────────────
|
|
67
|
-
//
|
|
113
|
+
// Event Dispatcher
|
|
68
114
|
// ─────────────────────────────────────────────────────────────────────────
|
|
69
115
|
|
|
70
116
|
/**
|
|
71
|
-
* Dispatch Centrifugo
|
|
72
|
-
*
|
|
73
|
-
* Uses window.dispatchEvent with CustomEvent to integrate with
|
|
74
|
-
* ErrorsTracker from @djangocfg/layouts package.
|
|
117
|
+
* Dispatch unified Centrifugo event
|
|
75
118
|
*
|
|
76
119
|
* @example
|
|
77
120
|
* ```ts
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
121
|
+
* // Dispatch error
|
|
122
|
+
* dispatchCentrifugoEvent({
|
|
123
|
+
* type: 'error',
|
|
124
|
+
* data: { method: 'terminal.input', error: 'timeout' }
|
|
125
|
+
* });
|
|
126
|
+
*
|
|
127
|
+
* // Listen to all Centrifugo events
|
|
128
|
+
* window.addEventListener('centrifugo', (e) => {
|
|
129
|
+
* const { type, data, timestamp } = e.detail;
|
|
130
|
+
* switch (type) {
|
|
131
|
+
* case 'error':
|
|
132
|
+
* console.error('RPC error:', data.method, data.error);
|
|
133
|
+
* break;
|
|
134
|
+
* case 'version_mismatch':
|
|
135
|
+
* toast.warning('Please refresh the page');
|
|
136
|
+
* break;
|
|
137
|
+
* }
|
|
83
138
|
* });
|
|
84
139
|
* ```
|
|
85
140
|
*/
|
|
86
|
-
export const
|
|
141
|
+
export const dispatchCentrifugoEvent = (payload: CentrifugoEventPayload): void => {
|
|
87
142
|
if (typeof window === 'undefined') {
|
|
88
143
|
return;
|
|
89
144
|
}
|
|
90
145
|
|
|
91
146
|
try {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
147
|
+
const detail: CentrifugoEventDetail = {
|
|
148
|
+
...payload,
|
|
149
|
+
timestamp: new Date(),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
window.dispatchEvent(new CustomEvent(CENTRIFUGO_EVENT, {
|
|
153
|
+
detail,
|
|
97
154
|
bubbles: true,
|
|
98
155
|
cancelable: false,
|
|
99
156
|
}));
|
|
100
|
-
} catch (
|
|
157
|
+
} catch (error) {
|
|
101
158
|
// Silently fail - event dispatch should never crash the app
|
|
102
159
|
}
|
|
103
160
|
};
|
|
104
161
|
|
|
162
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
163
|
+
// Convenience Dispatchers
|
|
164
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Dispatch error event
|
|
168
|
+
*/
|
|
169
|
+
export const dispatchCentrifugoError = (data: CentrifugoErrorData): void => {
|
|
170
|
+
dispatchCentrifugoEvent({ type: 'error', data });
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Dispatch version mismatch event
|
|
175
|
+
*/
|
|
176
|
+
export const dispatchVersionMismatch = (data: CentrifugoVersionMismatchData): void => {
|
|
177
|
+
dispatchCentrifugoEvent({ type: 'version_mismatch', data });
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Dispatch connected event
|
|
182
|
+
*/
|
|
183
|
+
export const dispatchConnected = (data: CentrifugoConnectionData = {}): void => {
|
|
184
|
+
dispatchCentrifugoEvent({ type: 'connected', data });
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Dispatch disconnected event
|
|
189
|
+
*/
|
|
190
|
+
export const dispatchDisconnected = (data: CentrifugoConnectionData = {}): void => {
|
|
191
|
+
dispatchCentrifugoEvent({ type: 'disconnected', data });
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Dispatch reconnecting event
|
|
196
|
+
*/
|
|
197
|
+
export const dispatchReconnecting = (data: CentrifugoConnectionData = {}): void => {
|
|
198
|
+
dispatchCentrifugoEvent({ type: 'reconnecting', data });
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
202
|
+
// Monitor Dialog Emitters (internal, uses events.publish)
|
|
203
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
export const emitOpenMonitorDialog = (payload?: OpenMonitorDialogPayload) => {
|
|
206
|
+
events.publish({
|
|
207
|
+
type: CENTRIFUGO_MONITOR_EVENTS.OPEN_MONITOR_DIALOG,
|
|
208
|
+
payload: payload || {},
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const emitCloseMonitorDialog = () => {
|
|
213
|
+
events.publish({
|
|
214
|
+
type: CENTRIFUGO_MONITOR_EVENTS.CLOSE_MONITOR_DIALOG,
|
|
215
|
+
payload: {},
|
|
216
|
+
});
|
|
217
|
+
};
|