@syncular/console 0.0.6-84 → 0.0.6-86
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/App.d.ts +2 -0
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +2 -2
- package/dist/App.js.map +1 -1
- package/dist/hooks/ConnectionContext.d.ts +3 -1
- package/dist/hooks/ConnectionContext.d.ts.map +1 -1
- package/dist/hooks/ConnectionContext.js +76 -13
- package/dist/hooks/ConnectionContext.js.map +1 -1
- package/dist/hooks/useLiveEvents.d.ts.map +1 -1
- package/dist/hooks/useLiveEvents.js +22 -3
- package/dist/hooks/useLiveEvents.js.map +1 -1
- package/dist/pages/Config.js +14 -5
- package/dist/pages/Config.js.map +1 -1
- package/package.json +4 -4
- package/src/App.tsx +6 -1
- package/src/hooks/ConnectionContext.tsx +88 -14
- package/src/hooks/useLiveEvents.ts +27 -3
- package/src/pages/Config.tsx +14 -5
- package/web-dist/assets/{index-BQBP4Ple.js → index-BJsNmMGm.js} +26 -26
- package/web-dist/index.html +1 -1
|
@@ -18,7 +18,58 @@ import {
|
|
|
18
18
|
createConsoleClient,
|
|
19
19
|
testConnection,
|
|
20
20
|
} from '../lib/api';
|
|
21
|
-
|
|
21
|
+
|
|
22
|
+
export type ConnectionStorageMode = 'memory' | 'session' | 'local';
|
|
23
|
+
|
|
24
|
+
const CONNECTION_STORAGE_KEY = 'sync-console-connection';
|
|
25
|
+
|
|
26
|
+
function normalizeConfig(
|
|
27
|
+
config: ConnectionConfig | null | undefined
|
|
28
|
+
): ConnectionConfig | null {
|
|
29
|
+
if (!config) return null;
|
|
30
|
+
const serverUrl = config.serverUrl?.trim() ?? '';
|
|
31
|
+
const token = config.token?.trim() ?? '';
|
|
32
|
+
if (!serverUrl || !token) return null;
|
|
33
|
+
return { serverUrl, token };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getStorageForMode(mode: ConnectionStorageMode): Storage | null {
|
|
37
|
+
if (typeof window === 'undefined') return null;
|
|
38
|
+
if (mode === 'local') return window.localStorage;
|
|
39
|
+
if (mode === 'session') return window.sessionStorage;
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readStoredConfig(
|
|
44
|
+
mode: ConnectionStorageMode
|
|
45
|
+
): ConnectionConfig | null {
|
|
46
|
+
const storage = getStorageForMode(mode);
|
|
47
|
+
if (!storage) return null;
|
|
48
|
+
try {
|
|
49
|
+
const raw = storage.getItem(CONNECTION_STORAGE_KEY);
|
|
50
|
+
if (!raw) return null;
|
|
51
|
+
return normalizeConfig(JSON.parse(raw) as ConnectionConfig);
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function writeStoredConfig(
|
|
58
|
+
mode: ConnectionStorageMode,
|
|
59
|
+
config: ConnectionConfig | null
|
|
60
|
+
): void {
|
|
61
|
+
const storage = getStorageForMode(mode);
|
|
62
|
+
if (!storage) return;
|
|
63
|
+
try {
|
|
64
|
+
if (!config) {
|
|
65
|
+
storage.removeItem(CONNECTION_STORAGE_KEY);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
storage.setItem(CONNECTION_STORAGE_KEY, JSON.stringify(config));
|
|
69
|
+
} catch {
|
|
70
|
+
// Ignore storage write errors.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
22
73
|
|
|
23
74
|
interface ConnectionState {
|
|
24
75
|
isConnected: boolean;
|
|
@@ -46,6 +97,7 @@ interface ConnectionProviderProps {
|
|
|
46
97
|
children: ReactNode;
|
|
47
98
|
defaultConfig?: ConnectionConfig | null;
|
|
48
99
|
autoConnect?: boolean;
|
|
100
|
+
storageMode?: ConnectionStorageMode;
|
|
49
101
|
}
|
|
50
102
|
|
|
51
103
|
const ConnectionContext = createContext<ConnectionContextValue | null>(null);
|
|
@@ -54,10 +106,34 @@ export function ConnectionProvider({
|
|
|
54
106
|
children,
|
|
55
107
|
defaultConfig = null,
|
|
56
108
|
autoConnect = false,
|
|
109
|
+
storageMode = 'session',
|
|
57
110
|
}: ConnectionProviderProps) {
|
|
58
|
-
const [config,
|
|
59
|
-
|
|
60
|
-
|
|
111
|
+
const [config, setConfigState] = useState<ConnectionConfig | null>(() =>
|
|
112
|
+
readStoredConfig(storageMode)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
const storedConfig = readStoredConfig(storageMode);
|
|
117
|
+
setConfigState(storedConfig);
|
|
118
|
+
}, [storageMode]);
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (typeof window === 'undefined') return;
|
|
122
|
+
if (storageMode !== 'local') {
|
|
123
|
+
window.localStorage.removeItem(CONNECTION_STORAGE_KEY);
|
|
124
|
+
}
|
|
125
|
+
if (storageMode === 'memory') {
|
|
126
|
+
window.sessionStorage.removeItem(CONNECTION_STORAGE_KEY);
|
|
127
|
+
}
|
|
128
|
+
}, [storageMode]);
|
|
129
|
+
|
|
130
|
+
const setConfigStorage = useCallback(
|
|
131
|
+
(nextConfig: ConnectionConfig | null) => {
|
|
132
|
+
const normalized = normalizeConfig(nextConfig);
|
|
133
|
+
setConfigState(normalized);
|
|
134
|
+
writeStoredConfig(storageMode, normalized);
|
|
135
|
+
},
|
|
136
|
+
[storageMode]
|
|
61
137
|
);
|
|
62
138
|
|
|
63
139
|
const [state, setState] = useState<ConnectionState>({
|
|
@@ -94,18 +170,16 @@ export function ConnectionProvider({
|
|
|
94
170
|
return false;
|
|
95
171
|
}
|
|
96
172
|
|
|
97
|
-
const normalizedConfig
|
|
98
|
-
serverUrl: effectiveConfig.serverUrl?.trim() ?? '',
|
|
99
|
-
token: effectiveConfig.token?.trim() ?? '',
|
|
100
|
-
};
|
|
173
|
+
const normalizedConfig = normalizeConfig(effectiveConfig);
|
|
101
174
|
|
|
102
175
|
// Validate config has required fields
|
|
103
|
-
if (!normalizedConfig
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
176
|
+
if (!normalizedConfig) {
|
|
177
|
+
const hasServerUrl =
|
|
178
|
+
(effectiveConfig.serverUrl?.trim() ?? '').length > 0;
|
|
179
|
+
setState((s) => ({
|
|
180
|
+
...s,
|
|
181
|
+
error: hasServerUrl ? 'Token is required' : 'Server URL is required',
|
|
182
|
+
}));
|
|
109
183
|
return false;
|
|
110
184
|
}
|
|
111
185
|
|
|
@@ -243,7 +243,6 @@ export function useLiveEvents(
|
|
|
243
243
|
: baseUrl.pathname;
|
|
244
244
|
baseUrl.pathname = `${normalizedPath}/console/events/live`;
|
|
245
245
|
baseUrl.search = '';
|
|
246
|
-
baseUrl.searchParams.set('token', config.token);
|
|
247
246
|
if (lastEventTimestampRef.current) {
|
|
248
247
|
baseUrl.searchParams.set('since', lastEventTimestampRef.current);
|
|
249
248
|
}
|
|
@@ -266,8 +265,21 @@ export function useLiveEvents(
|
|
|
266
265
|
return;
|
|
267
266
|
}
|
|
268
267
|
reconnectAttemptsRef.current = 0;
|
|
269
|
-
markActivity();
|
|
270
268
|
setError(null);
|
|
269
|
+
setConnectionState('connecting');
|
|
270
|
+
setIsConnected(false);
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
ws.send(
|
|
274
|
+
JSON.stringify({
|
|
275
|
+
type: 'auth',
|
|
276
|
+
token: config.token,
|
|
277
|
+
})
|
|
278
|
+
);
|
|
279
|
+
} catch {
|
|
280
|
+
ws.close();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
271
283
|
|
|
272
284
|
clearStaleInterval();
|
|
273
285
|
staleCheckIntervalRef.current = setInterval(() => {
|
|
@@ -304,7 +316,19 @@ export function useLiveEvents(
|
|
|
304
316
|
markActivity();
|
|
305
317
|
|
|
306
318
|
// Skip control events
|
|
307
|
-
if (
|
|
319
|
+
if (
|
|
320
|
+
eventType === 'connected' ||
|
|
321
|
+
eventType === 'heartbeat' ||
|
|
322
|
+
eventType === 'auth_required'
|
|
323
|
+
) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (eventType === 'error') {
|
|
327
|
+
const message =
|
|
328
|
+
typeof data.message === 'string'
|
|
329
|
+
? data.message
|
|
330
|
+
: 'Live events authentication failed';
|
|
331
|
+
setError(new Error(message));
|
|
308
332
|
return;
|
|
309
333
|
}
|
|
310
334
|
|
package/src/pages/Config.tsx
CHANGED
|
@@ -80,15 +80,24 @@ function ConnectionTab() {
|
|
|
80
80
|
|
|
81
81
|
useEffect(() => {
|
|
82
82
|
const params = new URLSearchParams(window.location.search);
|
|
83
|
-
const urlToken = params.get('token');
|
|
84
83
|
const urlServer = params.get('server');
|
|
84
|
+
let shouldReplaceUrl = false;
|
|
85
85
|
|
|
86
|
-
if (urlToken) {
|
|
87
|
-
setToken(urlToken);
|
|
88
|
-
window.history.replaceState({}, '', window.location.pathname);
|
|
89
|
-
}
|
|
90
86
|
if (urlServer) {
|
|
91
87
|
setServerUrl(urlServer);
|
|
88
|
+
params.delete('server');
|
|
89
|
+
shouldReplaceUrl = true;
|
|
90
|
+
}
|
|
91
|
+
if (params.has('token')) {
|
|
92
|
+
params.delete('token');
|
|
93
|
+
shouldReplaceUrl = true;
|
|
94
|
+
}
|
|
95
|
+
if (shouldReplaceUrl) {
|
|
96
|
+
const nextQuery = params.toString();
|
|
97
|
+
const nextUrl = nextQuery
|
|
98
|
+
? `${window.location.pathname}?${nextQuery}`
|
|
99
|
+
: window.location.pathname;
|
|
100
|
+
window.history.replaceState({}, '', nextUrl);
|
|
92
101
|
}
|
|
93
102
|
}, []);
|
|
94
103
|
|