@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.
@@ -18,7 +18,58 @@ import {
18
18
  createConsoleClient,
19
19
  testConnection,
20
20
  } from '../lib/api';
21
- import { useLocalStorage } from './useLocalStorage';
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, setConfigStorage] = useLocalStorage<ConnectionConfig | null>(
59
- 'sync-console-connection',
60
- null
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: ConnectionConfig = {
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.serverUrl) {
104
- setState((s) => ({ ...s, error: 'Server URL is required' }));
105
- return false;
106
- }
107
- if (!normalizedConfig.token) {
108
- setState((s) => ({ ...s, error: 'Token is required' }));
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 (eventType === 'connected' || eventType === 'heartbeat') {
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
 
@@ -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