@djangocfg/centrifugo 2.1.101 → 2.1.102
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/dist/components.cjs +871 -0
- package/dist/components.cjs.map +1 -0
- package/dist/components.d.mts +129 -0
- package/dist/components.d.ts +129 -0
- package/dist/components.mjs +857 -0
- package/dist/components.mjs.map +1 -0
- package/dist/hooks.cjs +379 -0
- package/dist/hooks.cjs.map +1 -0
- package/dist/hooks.d.mts +128 -0
- package/dist/hooks.d.ts +128 -0
- package/dist/hooks.mjs +374 -0
- package/dist/hooks.mjs.map +1 -0
- package/dist/index.cjs +3007 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +838 -0
- package/dist/index.d.ts +838 -0
- package/dist/index.mjs +2948 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +35 -13
- package/src/components/CentrifugoMonitor/CentrifugoMonitorDialog.tsx +2 -1
- package/src/events.ts +1 -1
package/dist/hooks.d.mts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSubscription Hook
|
|
3
|
+
*
|
|
4
|
+
* Subscribe to Centrifugo channel with auto-cleanup.
|
|
5
|
+
*
|
|
6
|
+
* Best practices:
|
|
7
|
+
* - Callbacks are stored in refs to avoid re-subscriptions
|
|
8
|
+
* - Proper error handling in callbacks (as recommended by centrifuge-js)
|
|
9
|
+
* - Cleanup on unmount
|
|
10
|
+
* - Stable unsubscribe function
|
|
11
|
+
*/
|
|
12
|
+
interface UseSubscriptionOptions<T = any> {
|
|
13
|
+
channel: string;
|
|
14
|
+
enabled?: boolean;
|
|
15
|
+
onPublication?: (data: T) => void;
|
|
16
|
+
onError?: (error: Error) => void;
|
|
17
|
+
}
|
|
18
|
+
interface UseSubscriptionResult<T = any> {
|
|
19
|
+
data: T | null;
|
|
20
|
+
error: Error | null;
|
|
21
|
+
isSubscribed: boolean;
|
|
22
|
+
unsubscribe: () => void;
|
|
23
|
+
}
|
|
24
|
+
declare function useSubscription<T = any>(options: UseSubscriptionOptions<T>): UseSubscriptionResult<T>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* useRPC Hook
|
|
28
|
+
*
|
|
29
|
+
* React hook for making RPC calls via Centrifugo using correlation ID pattern.
|
|
30
|
+
* Provides type-safe request-response communication over WebSocket.
|
|
31
|
+
*
|
|
32
|
+
* Pattern:
|
|
33
|
+
* 1. Client sends request with correlation_id to rpc#{method}
|
|
34
|
+
* 2. Backend processes and sends response to user#{userId} with same correlation_id
|
|
35
|
+
* 3. Client matches response by correlation_id and resolves Promise
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const { call, isLoading, error } = useRPC();
|
|
39
|
+
*
|
|
40
|
+
* const handleGetStats = async () => {
|
|
41
|
+
* const result = await call('tasks.get_stats', { bot_id: '123' });
|
|
42
|
+
* console.log('Stats:', result);
|
|
43
|
+
* };
|
|
44
|
+
*/
|
|
45
|
+
interface UseRPCOptions {
|
|
46
|
+
timeout?: number;
|
|
47
|
+
replyChannel?: string;
|
|
48
|
+
onError?: (error: Error) => void;
|
|
49
|
+
}
|
|
50
|
+
interface UseRPCResult {
|
|
51
|
+
call: <TRequest = any, TResponse = any>(method: string, params: TRequest, options?: UseRPCOptions) => Promise<TResponse>;
|
|
52
|
+
isLoading: boolean;
|
|
53
|
+
error: Error | null;
|
|
54
|
+
reset: () => void;
|
|
55
|
+
}
|
|
56
|
+
declare function useRPC(defaultOptions?: UseRPCOptions): UseRPCResult;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* useNamedRPC Hook
|
|
60
|
+
*
|
|
61
|
+
* React hook for making native Centrifugo RPC calls via RPC proxy.
|
|
62
|
+
* Uses Centrifugo's built-in RPC mechanism which proxies to Django.
|
|
63
|
+
*
|
|
64
|
+
* Flow:
|
|
65
|
+
* 1. Client calls namedRPC('terminal.input', data)
|
|
66
|
+
* 2. Centrifuge.js sends RPC over WebSocket
|
|
67
|
+
* 3. Centrifugo proxies to Django: POST /centrifugo/rpc/
|
|
68
|
+
* 4. Django routes to @websocket_rpc handler
|
|
69
|
+
* 5. Response returned to client
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* const { call, isLoading, error } = useNamedRPC();
|
|
73
|
+
*
|
|
74
|
+
* const handleSendInput = async () => {
|
|
75
|
+
* const result = await call('terminal.input', {
|
|
76
|
+
* session_id: 'abc-123',
|
|
77
|
+
* data: btoa('ls -la')
|
|
78
|
+
* });
|
|
79
|
+
* console.log('Result:', result);
|
|
80
|
+
* };
|
|
81
|
+
*/
|
|
82
|
+
interface UseNamedRPCOptions {
|
|
83
|
+
onError?: (error: Error) => void;
|
|
84
|
+
}
|
|
85
|
+
interface UseNamedRPCResult {
|
|
86
|
+
call: <TRequest = any, TResponse = any>(method: string, data: TRequest) => Promise<TResponse>;
|
|
87
|
+
isLoading: boolean;
|
|
88
|
+
error: Error | null;
|
|
89
|
+
reset: () => void;
|
|
90
|
+
}
|
|
91
|
+
declare function useNamedRPC(defaultOptions?: UseNamedRPCOptions): UseNamedRPCResult;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* usePageVisibility Hook
|
|
95
|
+
*
|
|
96
|
+
* Tracks browser tab visibility state using the Page Visibility API.
|
|
97
|
+
* Returns true when the page is visible, false when hidden.
|
|
98
|
+
*
|
|
99
|
+
* Use cases:
|
|
100
|
+
* - Pause/resume WebSocket connections
|
|
101
|
+
* - Pause expensive operations when tab is inactive
|
|
102
|
+
* - Trigger data sync when tab becomes visible
|
|
103
|
+
*/
|
|
104
|
+
interface PageVisibilityState {
|
|
105
|
+
/** Whether the page is currently visible */
|
|
106
|
+
isVisible: boolean;
|
|
107
|
+
/** Whether the page was ever hidden during this session */
|
|
108
|
+
wasHidden: boolean;
|
|
109
|
+
/** Timestamp when page became visible (for uptime tracking) */
|
|
110
|
+
visibleSince: number | null;
|
|
111
|
+
/** How long the page was hidden (ms), reset when visible */
|
|
112
|
+
hiddenDuration: number;
|
|
113
|
+
}
|
|
114
|
+
interface UsePageVisibilityOptions {
|
|
115
|
+
/** Callback when page becomes visible */
|
|
116
|
+
onVisible?: () => void;
|
|
117
|
+
/** Callback when page becomes hidden */
|
|
118
|
+
onHidden?: () => void;
|
|
119
|
+
/** Callback with visibility state change */
|
|
120
|
+
onChange?: (isVisible: boolean) => void;
|
|
121
|
+
}
|
|
122
|
+
interface UsePageVisibilityResult extends PageVisibilityState {
|
|
123
|
+
/** Force check visibility state */
|
|
124
|
+
checkVisibility: () => boolean;
|
|
125
|
+
}
|
|
126
|
+
declare function usePageVisibility(options?: UsePageVisibilityOptions): UsePageVisibilityResult;
|
|
127
|
+
|
|
128
|
+
export { type PageVisibilityState, type UseNamedRPCOptions, type UseNamedRPCResult, type UsePageVisibilityOptions, type UsePageVisibilityResult, type UseRPCOptions, type UseRPCResult, type UseSubscriptionOptions, type UseSubscriptionResult, useNamedRPC, usePageVisibility, useRPC, useSubscription };
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSubscription Hook
|
|
3
|
+
*
|
|
4
|
+
* Subscribe to Centrifugo channel with auto-cleanup.
|
|
5
|
+
*
|
|
6
|
+
* Best practices:
|
|
7
|
+
* - Callbacks are stored in refs to avoid re-subscriptions
|
|
8
|
+
* - Proper error handling in callbacks (as recommended by centrifuge-js)
|
|
9
|
+
* - Cleanup on unmount
|
|
10
|
+
* - Stable unsubscribe function
|
|
11
|
+
*/
|
|
12
|
+
interface UseSubscriptionOptions<T = any> {
|
|
13
|
+
channel: string;
|
|
14
|
+
enabled?: boolean;
|
|
15
|
+
onPublication?: (data: T) => void;
|
|
16
|
+
onError?: (error: Error) => void;
|
|
17
|
+
}
|
|
18
|
+
interface UseSubscriptionResult<T = any> {
|
|
19
|
+
data: T | null;
|
|
20
|
+
error: Error | null;
|
|
21
|
+
isSubscribed: boolean;
|
|
22
|
+
unsubscribe: () => void;
|
|
23
|
+
}
|
|
24
|
+
declare function useSubscription<T = any>(options: UseSubscriptionOptions<T>): UseSubscriptionResult<T>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* useRPC Hook
|
|
28
|
+
*
|
|
29
|
+
* React hook for making RPC calls via Centrifugo using correlation ID pattern.
|
|
30
|
+
* Provides type-safe request-response communication over WebSocket.
|
|
31
|
+
*
|
|
32
|
+
* Pattern:
|
|
33
|
+
* 1. Client sends request with correlation_id to rpc#{method}
|
|
34
|
+
* 2. Backend processes and sends response to user#{userId} with same correlation_id
|
|
35
|
+
* 3. Client matches response by correlation_id and resolves Promise
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const { call, isLoading, error } = useRPC();
|
|
39
|
+
*
|
|
40
|
+
* const handleGetStats = async () => {
|
|
41
|
+
* const result = await call('tasks.get_stats', { bot_id: '123' });
|
|
42
|
+
* console.log('Stats:', result);
|
|
43
|
+
* };
|
|
44
|
+
*/
|
|
45
|
+
interface UseRPCOptions {
|
|
46
|
+
timeout?: number;
|
|
47
|
+
replyChannel?: string;
|
|
48
|
+
onError?: (error: Error) => void;
|
|
49
|
+
}
|
|
50
|
+
interface UseRPCResult {
|
|
51
|
+
call: <TRequest = any, TResponse = any>(method: string, params: TRequest, options?: UseRPCOptions) => Promise<TResponse>;
|
|
52
|
+
isLoading: boolean;
|
|
53
|
+
error: Error | null;
|
|
54
|
+
reset: () => void;
|
|
55
|
+
}
|
|
56
|
+
declare function useRPC(defaultOptions?: UseRPCOptions): UseRPCResult;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* useNamedRPC Hook
|
|
60
|
+
*
|
|
61
|
+
* React hook for making native Centrifugo RPC calls via RPC proxy.
|
|
62
|
+
* Uses Centrifugo's built-in RPC mechanism which proxies to Django.
|
|
63
|
+
*
|
|
64
|
+
* Flow:
|
|
65
|
+
* 1. Client calls namedRPC('terminal.input', data)
|
|
66
|
+
* 2. Centrifuge.js sends RPC over WebSocket
|
|
67
|
+
* 3. Centrifugo proxies to Django: POST /centrifugo/rpc/
|
|
68
|
+
* 4. Django routes to @websocket_rpc handler
|
|
69
|
+
* 5. Response returned to client
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* const { call, isLoading, error } = useNamedRPC();
|
|
73
|
+
*
|
|
74
|
+
* const handleSendInput = async () => {
|
|
75
|
+
* const result = await call('terminal.input', {
|
|
76
|
+
* session_id: 'abc-123',
|
|
77
|
+
* data: btoa('ls -la')
|
|
78
|
+
* });
|
|
79
|
+
* console.log('Result:', result);
|
|
80
|
+
* };
|
|
81
|
+
*/
|
|
82
|
+
interface UseNamedRPCOptions {
|
|
83
|
+
onError?: (error: Error) => void;
|
|
84
|
+
}
|
|
85
|
+
interface UseNamedRPCResult {
|
|
86
|
+
call: <TRequest = any, TResponse = any>(method: string, data: TRequest) => Promise<TResponse>;
|
|
87
|
+
isLoading: boolean;
|
|
88
|
+
error: Error | null;
|
|
89
|
+
reset: () => void;
|
|
90
|
+
}
|
|
91
|
+
declare function useNamedRPC(defaultOptions?: UseNamedRPCOptions): UseNamedRPCResult;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* usePageVisibility Hook
|
|
95
|
+
*
|
|
96
|
+
* Tracks browser tab visibility state using the Page Visibility API.
|
|
97
|
+
* Returns true when the page is visible, false when hidden.
|
|
98
|
+
*
|
|
99
|
+
* Use cases:
|
|
100
|
+
* - Pause/resume WebSocket connections
|
|
101
|
+
* - Pause expensive operations when tab is inactive
|
|
102
|
+
* - Trigger data sync when tab becomes visible
|
|
103
|
+
*/
|
|
104
|
+
interface PageVisibilityState {
|
|
105
|
+
/** Whether the page is currently visible */
|
|
106
|
+
isVisible: boolean;
|
|
107
|
+
/** Whether the page was ever hidden during this session */
|
|
108
|
+
wasHidden: boolean;
|
|
109
|
+
/** Timestamp when page became visible (for uptime tracking) */
|
|
110
|
+
visibleSince: number | null;
|
|
111
|
+
/** How long the page was hidden (ms), reset when visible */
|
|
112
|
+
hiddenDuration: number;
|
|
113
|
+
}
|
|
114
|
+
interface UsePageVisibilityOptions {
|
|
115
|
+
/** Callback when page becomes visible */
|
|
116
|
+
onVisible?: () => void;
|
|
117
|
+
/** Callback when page becomes hidden */
|
|
118
|
+
onHidden?: () => void;
|
|
119
|
+
/** Callback with visibility state change */
|
|
120
|
+
onChange?: (isVisible: boolean) => void;
|
|
121
|
+
}
|
|
122
|
+
interface UsePageVisibilityResult extends PageVisibilityState {
|
|
123
|
+
/** Force check visibility state */
|
|
124
|
+
checkVisibility: () => boolean;
|
|
125
|
+
}
|
|
126
|
+
declare function usePageVisibility(options?: UsePageVisibilityOptions): UsePageVisibilityResult;
|
|
127
|
+
|
|
128
|
+
export { type PageVisibilityState, type UseNamedRPCOptions, type UseNamedRPCResult, type UsePageVisibilityOptions, type UsePageVisibilityResult, type UseRPCOptions, type UseRPCResult, type UseSubscriptionOptions, type UseSubscriptionResult, useNamedRPC, usePageVisibility, useRPC, useSubscription };
|
package/dist/hooks.mjs
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { createContext, useState, useRef, useCallback, useEffect, useContext } from 'react';
|
|
2
|
+
import { createConsola } from 'consola';
|
|
3
|
+
import '@djangocfg/api/auth';
|
|
4
|
+
import 'lucide-react';
|
|
5
|
+
import '@djangocfg/ui-core/hooks';
|
|
6
|
+
import '@djangocfg/ui-nextjs';
|
|
7
|
+
import 'moment';
|
|
8
|
+
import 'react/jsx-runtime';
|
|
9
|
+
import 'centrifuge';
|
|
10
|
+
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
13
|
+
var consolaLogger = createConsola({
|
|
14
|
+
level: 4 ,
|
|
15
|
+
formatOptions: {
|
|
16
|
+
colors: true,
|
|
17
|
+
date: false,
|
|
18
|
+
compact: false
|
|
19
|
+
}
|
|
20
|
+
}).withTag("[Centrifugo]");
|
|
21
|
+
function getConsolaLogger(tag) {
|
|
22
|
+
const consola = consolaLogger.withTag(`[${tag}]`);
|
|
23
|
+
return {
|
|
24
|
+
debug: /* @__PURE__ */ __name((message, data) => consola.debug(message, data || ""), "debug"),
|
|
25
|
+
info: /* @__PURE__ */ __name((message, data) => consola.info(message, data || ""), "info"),
|
|
26
|
+
success: /* @__PURE__ */ __name((message, data) => consola.success(message, data || ""), "success"),
|
|
27
|
+
warning: /* @__PURE__ */ __name((message, data) => consola.warn(message, data || ""), "warning"),
|
|
28
|
+
error: /* @__PURE__ */ __name((message, error) => consola.error(message, error || ""), "error")
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
__name(getConsolaLogger, "getConsolaLogger");
|
|
32
|
+
|
|
33
|
+
// src/core/utils/channelValidator.ts
|
|
34
|
+
function validateChannelName(channel) {
|
|
35
|
+
const hashCount = (channel.match(/#/g) || []).length;
|
|
36
|
+
if (hashCount >= 2) {
|
|
37
|
+
const parts = channel.split("#");
|
|
38
|
+
const [namespace, possibleUserId, ...rest] = parts;
|
|
39
|
+
const isNumericUserId = /^\d+$/.test(possibleUserId);
|
|
40
|
+
if (!isNumericUserId && possibleUserId) {
|
|
41
|
+
const suggestion = channel.replace(/#/g, ":");
|
|
42
|
+
return {
|
|
43
|
+
valid: false,
|
|
44
|
+
warning: `Channel "${channel}" uses '#' separator which Centrifugo interprets as user-limited channel boundary. The part "${possibleUserId}" will be treated as user_id, which may cause permission errors if not in JWT token.`,
|
|
45
|
+
suggestion: `Use ':' for namespace separation: "${suggestion}"`
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (isNumericUserId) {
|
|
49
|
+
return {
|
|
50
|
+
valid: true,
|
|
51
|
+
warning: `Channel "${channel}" appears to be a user-limited channel (user_id: ${possibleUserId}). Make sure your JWT token's "sub" field matches "${possibleUserId}".`
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (hashCount === 1) {
|
|
56
|
+
const [namespace, userId] = channel.split("#");
|
|
57
|
+
if (userId && !/^\d+$/.test(userId) && userId !== "*") {
|
|
58
|
+
const suggestion = channel.replace("#", ":");
|
|
59
|
+
return {
|
|
60
|
+
valid: false,
|
|
61
|
+
warning: `Channel "${channel}" uses '#' but "${userId}" doesn't look like a user_id. This might cause permission issues.`,
|
|
62
|
+
suggestion: `Consider using ':' instead: "${suggestion}"`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { valid: true };
|
|
67
|
+
}
|
|
68
|
+
__name(validateChannelName, "validateChannelName");
|
|
69
|
+
function logChannelWarnings(channel, logger3) {
|
|
70
|
+
const result = validateChannelName(channel);
|
|
71
|
+
if (!result.valid && result.warning) {
|
|
72
|
+
const message = `[Centrifugo Channel Warning]
|
|
73
|
+
${result.warning}${result.suggestion ? `
|
|
74
|
+
\u{1F4A1} Suggestion: ${result.suggestion}` : ""}`;
|
|
75
|
+
if (logger3?.warning) {
|
|
76
|
+
logger3.warning(message);
|
|
77
|
+
} else {
|
|
78
|
+
console.warn(message);
|
|
79
|
+
}
|
|
80
|
+
} else if (result.warning) {
|
|
81
|
+
if (logger3?.warning) {
|
|
82
|
+
logger3.warning(`[Centrifugo] ${result.warning}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
__name(logChannelWarnings, "logChannelWarnings");
|
|
87
|
+
getConsolaLogger("ConnectionStatus");
|
|
88
|
+
getConsolaLogger("SubscriptionsList");
|
|
89
|
+
|
|
90
|
+
// src/config.ts
|
|
91
|
+
process.env.NEXT_PUBLIC_STATIC_BUILD === "true";
|
|
92
|
+
function getVisibilityState() {
|
|
93
|
+
if (typeof document === "undefined") return true;
|
|
94
|
+
return document.visibilityState === "visible";
|
|
95
|
+
}
|
|
96
|
+
__name(getVisibilityState, "getVisibilityState");
|
|
97
|
+
function usePageVisibility(options = {}) {
|
|
98
|
+
const { onVisible, onHidden, onChange } = options;
|
|
99
|
+
const [state, setState] = useState(() => ({
|
|
100
|
+
isVisible: getVisibilityState(),
|
|
101
|
+
wasHidden: false,
|
|
102
|
+
visibleSince: getVisibilityState() ? Date.now() : null,
|
|
103
|
+
hiddenDuration: 0
|
|
104
|
+
}));
|
|
105
|
+
const onVisibleRef = useRef(onVisible);
|
|
106
|
+
const onHiddenRef = useRef(onHidden);
|
|
107
|
+
const onChangeRef = useRef(onChange);
|
|
108
|
+
const hiddenAtRef = useRef(null);
|
|
109
|
+
onVisibleRef.current = onVisible;
|
|
110
|
+
onHiddenRef.current = onHidden;
|
|
111
|
+
onChangeRef.current = onChange;
|
|
112
|
+
const checkVisibility = useCallback(() => {
|
|
113
|
+
return getVisibilityState();
|
|
114
|
+
}, []);
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (typeof document === "undefined") return;
|
|
117
|
+
const handleVisibilityChange = /* @__PURE__ */ __name(() => {
|
|
118
|
+
const isNowVisible = getVisibilityState();
|
|
119
|
+
setState((prev) => {
|
|
120
|
+
let hiddenDuration = prev.hiddenDuration;
|
|
121
|
+
if (isNowVisible && hiddenAtRef.current !== null) {
|
|
122
|
+
hiddenDuration = Date.now() - hiddenAtRef.current;
|
|
123
|
+
hiddenAtRef.current = null;
|
|
124
|
+
}
|
|
125
|
+
if (!isNowVisible) {
|
|
126
|
+
hiddenAtRef.current = Date.now();
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
isVisible: isNowVisible,
|
|
130
|
+
wasHidden: prev.wasHidden || !isNowVisible,
|
|
131
|
+
visibleSince: isNowVisible ? prev.visibleSince ?? Date.now() : null,
|
|
132
|
+
hiddenDuration
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
onChangeRef.current?.(isNowVisible);
|
|
136
|
+
if (isNowVisible) {
|
|
137
|
+
onVisibleRef.current?.();
|
|
138
|
+
} else {
|
|
139
|
+
onHiddenRef.current?.();
|
|
140
|
+
}
|
|
141
|
+
}, "handleVisibilityChange");
|
|
142
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
143
|
+
return () => {
|
|
144
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
145
|
+
};
|
|
146
|
+
}, []);
|
|
147
|
+
return {
|
|
148
|
+
...state,
|
|
149
|
+
checkVisibility
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
__name(usePageVisibility, "usePageVisibility");
|
|
153
|
+
createContext(void 0);
|
|
154
|
+
var CentrifugoContext = createContext(void 0);
|
|
155
|
+
function useCentrifugo() {
|
|
156
|
+
const context = useContext(CentrifugoContext);
|
|
157
|
+
if (context === void 0) {
|
|
158
|
+
throw new Error("useCentrifugo must be used within a CentrifugoProvider");
|
|
159
|
+
}
|
|
160
|
+
return context;
|
|
161
|
+
}
|
|
162
|
+
__name(useCentrifugo, "useCentrifugo");
|
|
163
|
+
|
|
164
|
+
// src/hooks/useSubscription.ts
|
|
165
|
+
function useSubscription(options) {
|
|
166
|
+
const { client, isConnected } = useCentrifugo();
|
|
167
|
+
const { channel, enabled = true, onPublication, onError } = options;
|
|
168
|
+
const [data, setData] = useState(null);
|
|
169
|
+
const [error, setError] = useState(null);
|
|
170
|
+
const [isSubscribed, setIsSubscribed] = useState(false);
|
|
171
|
+
const unsubscribeRef = useRef(null);
|
|
172
|
+
const logger3 = useRef(getConsolaLogger("useSubscription")).current;
|
|
173
|
+
const onPublicationRef = useRef(onPublication);
|
|
174
|
+
const onErrorRef = useRef(onError);
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
onPublicationRef.current = onPublication;
|
|
177
|
+
onErrorRef.current = onError;
|
|
178
|
+
}, [onPublication, onError]);
|
|
179
|
+
const unsubscribe2 = useCallback(() => {
|
|
180
|
+
if (unsubscribeRef.current) {
|
|
181
|
+
try {
|
|
182
|
+
unsubscribeRef.current();
|
|
183
|
+
unsubscribeRef.current = null;
|
|
184
|
+
setIsSubscribed(false);
|
|
185
|
+
logger3.info(`Unsubscribed from channel: ${channel}`);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
logger3.error(`Error during unsubscribe from ${channel}`, err);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}, [channel, logger3]);
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (!client || !isConnected || !enabled) {
|
|
193
|
+
if (!isConnected && isSubscribed) {
|
|
194
|
+
setIsSubscribed(false);
|
|
195
|
+
}
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
logChannelWarnings(channel, logger3);
|
|
199
|
+
logger3.info(`Subscribing to channel: ${channel}`);
|
|
200
|
+
try {
|
|
201
|
+
const unsub = client.subscribe(channel, (receivedData) => {
|
|
202
|
+
try {
|
|
203
|
+
setData(receivedData);
|
|
204
|
+
setError(null);
|
|
205
|
+
onPublicationRef.current?.(receivedData);
|
|
206
|
+
} catch (callbackError) {
|
|
207
|
+
logger3.error(`Error in onPublication callback for ${channel}`, callbackError);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
unsubscribeRef.current = unsub;
|
|
211
|
+
setIsSubscribed(true);
|
|
212
|
+
logger3.success(`Subscribed to channel: ${channel}`);
|
|
213
|
+
} catch (err) {
|
|
214
|
+
const subscriptionError = err instanceof Error ? err : new Error("Subscription failed");
|
|
215
|
+
setError(subscriptionError);
|
|
216
|
+
try {
|
|
217
|
+
onErrorRef.current?.(subscriptionError);
|
|
218
|
+
} catch (callbackError) {
|
|
219
|
+
logger3.error(`Error in onError callback for ${channel}`, callbackError);
|
|
220
|
+
}
|
|
221
|
+
logger3.error(`Subscription failed: ${channel}`, subscriptionError);
|
|
222
|
+
}
|
|
223
|
+
return () => {
|
|
224
|
+
unsubscribe2();
|
|
225
|
+
};
|
|
226
|
+
}, [client, isConnected, enabled, channel, unsubscribe2, logger3, isSubscribed]);
|
|
227
|
+
return {
|
|
228
|
+
data,
|
|
229
|
+
error,
|
|
230
|
+
isSubscribed,
|
|
231
|
+
unsubscribe: unsubscribe2
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
__name(useSubscription, "useSubscription");
|
|
235
|
+
function useRPC(defaultOptions = {}) {
|
|
236
|
+
const { client, isConnected } = useCentrifugo();
|
|
237
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
238
|
+
const [error, setError] = useState(null);
|
|
239
|
+
const logger3 = useRef(getConsolaLogger("useRPC")).current;
|
|
240
|
+
const abortControllerRef = useRef(null);
|
|
241
|
+
const reset = useCallback(() => {
|
|
242
|
+
setIsLoading(false);
|
|
243
|
+
setError(null);
|
|
244
|
+
if (abortControllerRef.current) {
|
|
245
|
+
abortControllerRef.current.abort();
|
|
246
|
+
abortControllerRef.current = null;
|
|
247
|
+
}
|
|
248
|
+
}, []);
|
|
249
|
+
const call2 = useCallback(
|
|
250
|
+
async (method, params, options = {}) => {
|
|
251
|
+
if (!client) {
|
|
252
|
+
const error2 = new Error("Centrifugo client not available");
|
|
253
|
+
setError(error2);
|
|
254
|
+
throw error2;
|
|
255
|
+
}
|
|
256
|
+
if (!isConnected) {
|
|
257
|
+
const error2 = new Error("Not connected to Centrifugo");
|
|
258
|
+
setError(error2);
|
|
259
|
+
throw error2;
|
|
260
|
+
}
|
|
261
|
+
setError(null);
|
|
262
|
+
setIsLoading(true);
|
|
263
|
+
const abortController = new AbortController();
|
|
264
|
+
abortControllerRef.current = abortController;
|
|
265
|
+
try {
|
|
266
|
+
const mergedOptions = {
|
|
267
|
+
...defaultOptions,
|
|
268
|
+
...options
|
|
269
|
+
};
|
|
270
|
+
logger3.info(`RPC call: ${method}`, { params });
|
|
271
|
+
const result = await client.rpc(
|
|
272
|
+
method,
|
|
273
|
+
params,
|
|
274
|
+
{
|
|
275
|
+
timeout: mergedOptions.timeout,
|
|
276
|
+
replyChannel: mergedOptions.replyChannel
|
|
277
|
+
}
|
|
278
|
+
);
|
|
279
|
+
if (abortController.signal.aborted) {
|
|
280
|
+
throw new Error("RPC call aborted");
|
|
281
|
+
}
|
|
282
|
+
logger3.success(`RPC success: ${method}`);
|
|
283
|
+
setIsLoading(false);
|
|
284
|
+
return result;
|
|
285
|
+
} catch (err) {
|
|
286
|
+
const rpcError = err instanceof Error ? err : new Error("RPC call failed");
|
|
287
|
+
if (!abortController.signal.aborted) {
|
|
288
|
+
setError(rpcError);
|
|
289
|
+
logger3.error(`RPC failed: ${method}`, rpcError);
|
|
290
|
+
const onError = options.onError || defaultOptions.onError;
|
|
291
|
+
if (onError) {
|
|
292
|
+
try {
|
|
293
|
+
onError(rpcError);
|
|
294
|
+
} catch (callbackError) {
|
|
295
|
+
logger3.error("Error in onError callback", callbackError);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
setIsLoading(false);
|
|
300
|
+
throw rpcError;
|
|
301
|
+
} finally {
|
|
302
|
+
if (abortControllerRef.current === abortController) {
|
|
303
|
+
abortControllerRef.current = null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
[client, isConnected, defaultOptions, logger3]
|
|
308
|
+
);
|
|
309
|
+
return {
|
|
310
|
+
call: call2,
|
|
311
|
+
isLoading,
|
|
312
|
+
error,
|
|
313
|
+
reset
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
__name(useRPC, "useRPC");
|
|
317
|
+
function useNamedRPC(defaultOptions = {}) {
|
|
318
|
+
const { client, isConnected } = useCentrifugo();
|
|
319
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
320
|
+
const [error, setError] = useState(null);
|
|
321
|
+
const logger3 = useRef(getConsolaLogger("useNamedRPC")).current;
|
|
322
|
+
const reset = useCallback(() => {
|
|
323
|
+
setIsLoading(false);
|
|
324
|
+
setError(null);
|
|
325
|
+
}, []);
|
|
326
|
+
const call2 = useCallback(
|
|
327
|
+
async (method, data) => {
|
|
328
|
+
if (!client) {
|
|
329
|
+
const error2 = new Error("Centrifugo client not available");
|
|
330
|
+
setError(error2);
|
|
331
|
+
throw error2;
|
|
332
|
+
}
|
|
333
|
+
if (!isConnected) {
|
|
334
|
+
const error2 = new Error("Not connected to Centrifugo");
|
|
335
|
+
setError(error2);
|
|
336
|
+
throw error2;
|
|
337
|
+
}
|
|
338
|
+
setError(null);
|
|
339
|
+
setIsLoading(true);
|
|
340
|
+
try {
|
|
341
|
+
logger3.info(`Native RPC call: ${method}`, { data });
|
|
342
|
+
const result = await client.namedRPC(method, data);
|
|
343
|
+
logger3.success(`Native RPC success: ${method}`);
|
|
344
|
+
setIsLoading(false);
|
|
345
|
+
return result;
|
|
346
|
+
} catch (err) {
|
|
347
|
+
const rpcError = err instanceof Error ? err : new Error("Native RPC call failed");
|
|
348
|
+
setError(rpcError);
|
|
349
|
+
logger3.error(`Native RPC failed: ${method}`, rpcError);
|
|
350
|
+
if (defaultOptions.onError) {
|
|
351
|
+
try {
|
|
352
|
+
defaultOptions.onError(rpcError);
|
|
353
|
+
} catch (callbackError) {
|
|
354
|
+
logger3.error("Error in onError callback", callbackError);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
setIsLoading(false);
|
|
358
|
+
throw rpcError;
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
[client, isConnected, defaultOptions, logger3]
|
|
362
|
+
);
|
|
363
|
+
return {
|
|
364
|
+
call: call2,
|
|
365
|
+
isLoading,
|
|
366
|
+
error,
|
|
367
|
+
reset
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
__name(useNamedRPC, "useNamedRPC");
|
|
371
|
+
|
|
372
|
+
export { useNamedRPC, usePageVisibility, useRPC, useSubscription };
|
|
373
|
+
//# sourceMappingURL=hooks.mjs.map
|
|
374
|
+
//# sourceMappingURL=hooks.mjs.map
|