docpouch-client 0.8.4 → 0.8.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/src/index.ts CHANGED
@@ -6,342 +6,392 @@ import type {
6
6
  I_UserDisplay,
7
7
  I_DocumentEntry,
8
8
  I_DataStructure,
9
- I_LoginResponse, I_DocumentQuery, I_StructureCreation, I_WsMessage, I_EventString, I_DocumentType
9
+ I_LoginResponse, I_DocumentQuery, I_StructureCreation, I_WsMessage, I_EventString, I_DocumentType
10
10
  } from "./types.js";
11
11
  import {io, Socket} from "socket.io-client";
12
12
  import packetJson from '../package.json'
13
13
 
14
- export default class dbPouchClient {
15
- baseUrl: string;
16
- private authToken: string | null = null;
17
- socket: Socket;
18
- callbackFunction;
19
- realTimeSync: boolean = false;
20
- private connectionInProgress = false;
21
-
22
-
23
- constructor(host: string, port: number = 80, callback?: (event: I_EventString, data: I_WsMessage) => void) {
24
- this.baseUrl = host;
25
-
26
- const socketUrl = host.includes('://') ? host : `http://${host}`;
27
-
28
- // Don't append port twice if it's already in the URL
29
- const socketUrlWithPort = socketUrl.includes(':') && !socketUrl.endsWith(':')
30
- ? socketUrl
31
- : `${socketUrl}:${port}`;
32
-
33
-
34
- console.log(`Initializing Socket.IO with URL: ${socketUrlWithPort}, path: /socket.io`);
35
-
36
- this.socket = io(`${socketUrlWithPort}`, {
37
- autoConnect: false,
38
- transports: ['websocket'], // Try websocket only first
39
- reconnection: true,
40
- reconnectionAttempts: 5,
41
- reconnectionDelay: 1000,
42
- forceNew: true, // Force a new connection
43
- auth: {
44
- token: null // Will be set later
45
- },
46
- path: '/socket.io'
47
- });
48
-
49
- this.callbackFunction = callback;
50
-
51
- this.setupPermanentSocketListeners();
14
+ /**
15
+ * Client for interacting with docPouch API.
16
+ */
17
+ export default class docPouchClient {
18
+ /**
19
+ * The base URL of the server.
20
+ *
21
+ * @type {string}
22
+ */
23
+ baseUrl: string;
24
+ /**
25
+ * Socket.IO socket instance for real-time communication with the server.
26
+ *
27
+ * @type {Socket}
28
+ */
29
+ socket: Socket;
30
+ /**
31
+ * Callback function to handle socket events.
32
+ *
33
+ * @type {(event: I_EventString, data: I_WsMessage) => void}
34
+ */
35
+ callbackFunction;
36
+ /**
37
+ * Flag indicating whether real-time synchronization is enabled.
38
+ *
39
+ * @type {boolean}
40
+ */
41
+ realTimeSync: boolean = false;
42
+ /**
43
+ * Authentication token used to authorize requests.
44
+ *
45
+ * @private
46
+ * @type {string | null}
47
+ */
48
+ private authToken: string | null = null;
49
+ /**
50
+ * Flag indicating whether a connection attempt is in progress.
51
+ *
52
+ * @private
53
+ * @type {boolean}
54
+ */
55
+ private connectionInProgress = false;
56
+
57
+ /**
58
+ * Creates an instance of docPouchClient.
59
+ *
60
+ * @param {string} host - The base URL for the server.
61
+ * @param {number} [port=80] - The port number to connect to (default is 80).
62
+ * @param {(event: I_EventString, data: I_WsMessage) => void} [callback] - Optional callback function for socket events.
63
+ */
64
+ constructor(host: string, port: number = 80, callback?: (event: I_EventString, data: I_WsMessage) => void) {
65
+ this.baseUrl = host;
66
+ const socketUrl = host.includes('://') ? host : `https://${host}`;
67
+ const socketUrlWithPort = socketUrl.includes(':') && !socketUrl.endsWith(':')
68
+ ? socketUrl
69
+ : `${socketUrl}:${port}`;
70
+
71
+ console.log(`Initializing Socket.IO with URL: ${socketUrlWithPort}, path: /socket.io`);
72
+
73
+ this.socket = io(`${socketUrlWithPort}`, {
74
+ autoConnect: false,
75
+ transports: ['websocket'], // Try websocket only first
76
+ reconnection: true,
77
+ reconnectionAttempts: 5,
78
+ reconnectionDelay: 1000,
79
+ forceNew: true, // Force a new connection
80
+ auth: {
81
+ token: null // Will be set later
82
+ },
83
+ path: '/socket.io'
84
+ });
85
+
86
+ this.callbackFunction = callback;
87
+
88
+ this.setupPermanentSocketListeners();
89
+ }
90
+
91
+ /**
92
+ * Sets the real-time synchronization status.
93
+ *
94
+ * @param {boolean} newRealTimeSync - The new real-time sync setting (true/false).
95
+ */
96
+ setRealTimeSync(newRealTimeSync: boolean) {
97
+ console.log(`Setting realtime sync to: ${newRealTimeSync}. Current setting: ${this.realTimeSync}`);
98
+
99
+ // Skip if the setting isn't changing
100
+ if (newRealTimeSync === this.realTimeSync) {
101
+ console.log("Realtime sync setting unchanged, skipping");
102
+ return;
52
103
  }
53
104
 
54
- private setupPermanentSocketListeners() {
55
- // These are permanent listeners that won't be removed
56
- this.socket.on('connect_error', (error) => {
57
- console.error('Socket connection error:', error.message);
58
- this.connectionInProgress = false;
59
- });
60
-
61
- this.socket.on('connect', () => {
62
- console.log('Socket connected successfully with ID:', this.socket.id);
63
- this.connectionInProgress = false;
64
- });
65
-
66
- this.socket.on('disconnect', (reason) => {
67
- console.log('Socket disconnected. Reason:', reason);
68
- this.connectionInProgress = false;
69
- });
70
-
71
- this.socket.on('error', (error) => {
72
- console.error('Socket error:', error);
73
- this.connectionInProgress = false;
74
- });
105
+ this.realTimeSync = newRealTimeSync;
106
+
107
+ if (newRealTimeSync && this.authToken) {
108
+ console.log("Activating realtime updates");
109
+
110
+ // Ensure we're not in the middle of another connection attempt
111
+ if (this.connectionInProgress) {
112
+ console.log("Connection already in progress, waiting before initializing");
113
+ setTimeout(() => this.initWebSocket(), 500);
114
+ } else {
115
+ this.initWebSocket();
116
+ }
117
+ } else if (!newRealTimeSync) {
118
+ console.log("Deactivating realtime updates");
119
+ if (this.socket.connected) {
120
+ console.log("Disconnecting socket");
121
+ this.socket.disconnect();
122
+ }
75
123
  }
124
+ }
76
125
 
77
- setRealTimeSync = (newRealTimeSync: boolean) => {
78
- console.log(`Setting realtime sync to: ${newRealTimeSync}. Current setting: ${this.realTimeSync}`);
126
+ // User Administration Endpoints
127
+ async login(credentials: I_UserLogin): Promise<I_LoginResponse | null> {
128
+ const response = await this.request<I_LoginResponse>('/users/login', 'POST', credentials, false);
129
+ if (response.token) {
130
+ this.authToken = response.token;
79
131
 
80
- // Skip if the setting isn't changing
81
- if (newRealTimeSync === this.realTimeSync) {
82
- console.log("Realtime sync setting unchanged, skipping");
83
- return;
84
- }
132
+ // Reconnect websocket with new token if realtime sync is enabled
133
+ if (this.realTimeSync) {
134
+ this.initWebSocket();
135
+ }
85
136
 
86
- this.realTimeSync = newRealTimeSync;
87
-
88
- if (newRealTimeSync && this.authToken) {
89
- console.log("Activating realtime updates");
90
-
91
- // Ensure we're not in the middle of another connection attempt
92
- if (this.connectionInProgress) {
93
- console.log("Connection already in progress, waiting before initializing");
94
- setTimeout(() => this.initWebSocket(), 500);
95
- } else {
96
- this.initWebSocket();
97
- }
98
- } else if (!newRealTimeSync) {
99
- console.log("Deactivating realtime updates");
100
- if (this.socket.connected) {
101
- console.log("Disconnecting socket");
102
- this.socket.disconnect();
103
- }
104
- }
137
+ return {token: response.token, isAdmin: response.isAdmin, userName: response.userName};
105
138
  }
139
+ return null;
140
+ }
106
141
 
107
- private initWebSocket() {
108
- console.log("initWebSocket called. Auth token present:", !!this.authToken,
109
- "Connection in progress:", this.connectionInProgress,
110
- "Socket connected:", this.socket.connected);
142
+ async listUsers(): Promise<I_UserEntry[]> {
143
+ return await this.request<I_UserEntry[]>('/users/list', 'GET');
144
+ }
111
145
 
112
- if (!this.authToken) {
113
- console.log("Skipping WebSocket initialization: No auth token");
114
- return;
115
- }
146
+ async updateUser(userID: string, userData: I_UserUpdate): Promise<void> {
147
+ await this.request<void>(`/users/update/${userID}`, 'PATCH', userData);
148
+ }
116
149
 
117
- if (this.connectionInProgress) {
118
- console.log("Connection already in progress, skipping initialization");
119
- return;
120
- }
150
+ async createUser(userData: I_UserCreation): Promise<I_UserDisplay> {
151
+ return await this.request<I_UserDisplay>('/users/create', 'POST', userData);
152
+ }
121
153
 
122
- if (this.socket.connected) {
123
- console.log("Socket already connected with ID:", this.socket.id);
124
- return;
125
- }
154
+ async removeUser(userID: string): Promise<void> {
155
+ await this.request<void>(`/users/remove/${userID}`, 'DELETE');
156
+ }
126
157
 
127
- this.connectionInProgress = true;
158
+ // Document Management Endpoints
159
+ async createDocument(document: I_DocumentEntry): Promise<I_DocumentEntry> {
160
+ return await this.request<I_DocumentEntry>('/docs/create', 'POST', document);
161
+ }
128
162
 
129
- try {
130
- console.log("Setting up WebSocket connection with token");
163
+ async listDocuments(): Promise<I_DocumentEntry[]> {
164
+ return await this.request<I_DocumentEntry[]>('/docs/list', 'GET');
165
+ }
131
166
 
132
- // Update the auth token
133
- this.socket.auth = { token: this.authToken };
167
+ async fetchDocument(queryObject: I_DocumentQuery): Promise<I_DocumentEntry[]> {
168
+ return await this.request<I_DocumentEntry[]>(`/docs/fetch/`, 'POST', queryObject);
169
+ }
134
170
 
135
- // Remove any dynamic event listeners that might have been added
136
- this.socket.offAny();
171
+ async updateDocument(documentID: string, documentData: I_DocumentEntry): Promise<void> {
172
+ await this.request<void>(`/docs/update/${documentID}`, 'PATCH', documentData);
173
+ }
137
174
 
138
- // Set up event handler for application events
139
- this.socket.onAny((event: I_EventString, data: I_WsMessage) => {
140
- if (event === "heartbeatPing") {
141
- console.log("Ping event received:", data);
142
- this.socket.emit("heartbeatPong", Date.now());
143
- }
144
- else if (this.callbackFunction){
145
- this.callbackFunction(event, data);
146
- }
147
- });
175
+ async removeDocument(documentID: string): Promise<void> {
176
+ await this.request<void>(`/docs/remove/${documentID}`, 'DELETE');
177
+ }
148
178
 
149
- // Connect to the server
150
- console.log("Connecting socket with auth token");
151
- this.socket.connect();
179
+ // Data Structure Endpoints
180
+ async createStructure(structure: I_StructureCreation): Promise<I_DataStructure> {
181
+ return await this.request<I_DataStructure>('/structures/create', 'POST', structure);
182
+ }
152
183
 
153
- // Add a timeout to detect if connection is taking too long
154
- setTimeout(() => {
155
- if (this.connectionInProgress) {
156
- console.warn("Socket connection attempt timed out after 5 seconds");
157
- this.connectionInProgress = false;
158
-
159
- // If we're still not connected after the timeout, try again with polling
160
- if (!this.socket.connected) {
161
- console.log("Retrying connection with polling transport");
162
- this.socket.io.opts.transports = ['polling', 'websocket'];
163
- this.socket.connect();
164
- }
165
- }
166
- }, 5000);
167
- } catch (error) {
168
- console.error('Error in initWebSocket:', error);
169
- this.connectionInProgress = false;
170
- }
171
- }
184
+ async getStructures(): Promise<I_DataStructure[]> {
185
+ return await this.request<I_DataStructure[]>('/structures/list', 'GET');
186
+ }
172
187
 
173
- private async request<T>(endpoint: string, method: string, body?: any, requiresAuth: boolean = true): Promise<T> {
174
- const headers: HeadersInit = {
175
- 'Content-Type': 'application/json',
176
- };
177
-
178
- if (requiresAuth && this.authToken)
179
- headers['Authorization'] = `Bearer ${this.authToken}`;
180
- if (this.socket.id)
181
- headers['X-Socket-ID'] = this.socket.id;
182
-
183
- const options: RequestInit = {
184
- method,
185
- headers,
186
- body: body ? JSON.stringify(body) : undefined
187
- };
188
-
189
- // Ensure endpoint starts with /
190
- const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
191
- // Ensure baseUrl doesn't end with /
192
- const normalizedBaseUrl = this.baseUrl.endsWith('/') ? this.baseUrl.slice(0, -1) : this.baseUrl;
193
-
194
- const url = `${normalizedBaseUrl}${normalizedEndpoint}`;
195
- console.log(`Making ${method} request to: ${url}`);
196
-
197
- const response = await fetch(url, options);
198
-
199
- if (!response.ok) {
200
- if (response.status === 401 || response.status === 403) {
201
- this.authToken = null;
202
- }
203
- throw new Error(`API error: ${response.status} ${response.statusText}`);
204
- }
188
+ async updateStructure(structureID: string, structureData: I_DataStructure): Promise<void> {
189
+ await this.request<void>(`/structures/update/${structureID}`, 'PATCH', structureData);
190
+ }
205
191
 
206
- return await response.json() as T;
207
- }
192
+ async removeStructure(structureID: string): Promise<void> {
193
+ await this.request<void>(`/structures/remove/${structureID}`, 'DELETE');
194
+ }
208
195
 
209
- // User Administration Endpoints
210
- async login(credentials: I_UserLogin): Promise<I_LoginResponse | null> {
211
- const response = await this.request<I_LoginResponse>('/users/login', 'POST', credentials, false);
212
- if (response.token) {
213
- this.authToken = response.token;
196
+ // Data Type Endpoints
197
+ async createType(type: I_DocumentType): Promise<I_DocumentType> {
198
+ return await this.request<I_DocumentType>('/types/write', 'POST', type);
199
+ }
214
200
 
215
- // Reconnect websocket with new token if realtime sync is enabled
216
- if (this.realTimeSync) {
217
- this.initWebSocket();
218
- }
201
+ async removeType(typeID: string) {
202
+ return await this.request<void>(`/types/remove/${typeID}`, 'DELETE');
203
+ }
219
204
 
220
- return {token: response.token, isAdmin: response.isAdmin};
221
- }
222
- return null;
223
- }
205
+ async getTypes(): Promise<I_DocumentType[]> {
206
+ return await this.request<I_DocumentType[]>('/types/list', 'GET');
207
+ }
224
208
 
225
- async listUsers(): Promise<I_UserEntry[]> {
226
- return await this.request<I_UserEntry[]>('/users/list', 'GET');
227
- }
209
+ async updateType(updatedType: I_DocumentType): Promise<void> {
210
+ await this.request<void>(`/types/write`, 'POST', updatedType);
211
+ }
228
212
 
229
- async updateUser(userID: string, userData: I_UserUpdate): Promise<void> {
230
- await this.request<void>(`/users/update/${userID}`, 'PATCH', userData);
231
- }
232
-
233
- async createUser(userData: I_UserCreation): Promise<I_UserDisplay> {
234
- return await this.request<I_UserDisplay>('/users/create', 'POST', userData);
235
- }
213
+ setToken(token: string | null): void {
214
+ console.log("Setting token to:", token ? "***token***" : "null");
236
215
 
237
- async removeUser(userID: string): Promise<void> {
238
- await this.request<void>(`/users/remove/${userID}`, 'DELETE');
239
- }
240
-
241
- // Document Management Endpoints
242
- async createDocument(document: I_DocumentEntry): Promise<I_DocumentEntry> {
243
- return await this.request<I_DocumentEntry>('/docs/create', 'POST', document);
244
- }
216
+ const tokenChanged = this.authToken !== token;
217
+ this.authToken = token;
245
218
 
246
- async listDocuments(): Promise<I_DocumentEntry[]> {
247
- return await this.request<I_DocumentEntry[]>('/docs/list', 'GET');
219
+ if (!tokenChanged) {
220
+ console.log("Token unchanged, no need to reconnect");
221
+ return;
248
222
  }
249
223
 
250
- async fetchDocument(queryObject: I_DocumentQuery): Promise<I_DocumentEntry[]> {
251
- return await this.request<I_DocumentEntry[]>(`/docs/fetch/`, 'POST', queryObject);
224
+ // If we have a new token and realtime sync is enabled
225
+ if (token && this.realTimeSync) {
226
+ console.log("New token set, will initialize WebSocket");
227
+
228
+ // Ensure any existing connection is closed first
229
+ if (this.socket.connected) {
230
+ console.log("Disconnecting existing socket before reconnecting with new token");
231
+ this.socket.disconnect();
232
+ }
233
+
234
+ // Wait a moment for the disconnect to complete
235
+ setTimeout(() => {
236
+ console.log("Initializing WebSocket with new token");
237
+ this.initWebSocket();
238
+ }, 300);
252
239
  }
253
-
254
- async updateDocument(documentID: string, documentData: I_DocumentEntry): Promise<void> {
255
- await this.request<void>(`/docs/update/${documentID}`, 'PATCH', documentData);
256
- }
257
-
258
- async removeDocument(documentID: string): Promise<void> {
259
- await this.request<void>(`/docs/remove/${documentID}`, 'DELETE');
260
- }
261
-
262
- // Data Structure Endpoints
263
- async createStructure(structure: I_StructureCreation): Promise<I_DataStructure> {
264
- return await this.request<I_DataStructure>('/structures/create', 'POST', structure);
240
+ // If token was cleared or realtime sync is disabled
241
+ else if (this.socket.connected) {
242
+ console.log("Token cleared or realtime sync disabled, disconnecting");
243
+ this.socket.disconnect();
265
244
  }
266
-
267
- async getStructures(): Promise<I_DataStructure[]> {
268
- return await this.request<I_DataStructure[]>('/structures/list', 'GET');
245
+ }
246
+
247
+ getVersion() {
248
+ return packetJson.version;
249
+ }
250
+
251
+ debugSocketConnection(): void {
252
+ console.log("Socket connection debug info:");
253
+ console.log("- Connected:", this.socket.connected);
254
+ console.log("- Socket ID:", this.socket.id);
255
+ console.log("- Auth token present:", !!this.authToken);
256
+ console.log("- Connection in progress:", this.connectionInProgress);
257
+ console.log("- Realtime sync enabled:", this.realTimeSync);
258
+ console.log("- Socket options:", this.socket.io.opts);
259
+
260
+ // Try to force reconnection
261
+ if (!this.socket.connected && this.authToken && this.realTimeSync) {
262
+ console.log("Attempting to force reconnection...");
263
+ this.socket.auth = {token: this.authToken};
264
+ this.socket.connect();
269
265
  }
270
-
271
- async updateStructure(structureID: string, structureData: I_DataStructure): Promise<void> {
272
- await this.request<void>(`/structures/update/${structureID}`, 'PATCH', structureData);
266
+ }
267
+
268
+ /**
269
+ * Sets up permanent socket listeners for the client.
270
+ *
271
+ * @private
272
+ */
273
+ private setupPermanentSocketListeners() {
274
+ // These are permanent listeners that won't be removed
275
+ this.socket.on('connect_error', (error) => {
276
+ console.error('Socket connection error:', error.message);
277
+ this.connectionInProgress = false;
278
+ });
279
+
280
+ this.socket.on('connect', () => {
281
+ console.log('Socket connected successfully with ID:', this.socket.id);
282
+ this.connectionInProgress = false;
283
+ });
284
+
285
+ this.socket.on('disconnect', (reason) => {
286
+ console.log('Socket disconnected. Reason:', reason);
287
+ this.connectionInProgress = false;
288
+ });
289
+
290
+ this.socket.on('error', (error) => {
291
+ console.error('Socket error:', error);
292
+ this.connectionInProgress = false;
293
+ });
294
+ }
295
+
296
+ /**
297
+ * Initializes the WebSocket connection with the server.
298
+ *
299
+ * @private
300
+ */
301
+ private initWebSocket() {
302
+ console.log("initWebSocket called. Auth token present:", !!this.authToken,
303
+ "Connection in progress:", this.connectionInProgress,
304
+ "Socket connected:", this.socket.connected);
305
+
306
+ if (!this.authToken) {
307
+ console.log("Skipping WebSocket initialization: No auth token");
308
+ return;
273
309
  }
274
310
 
275
- async removeStructure(structureID: string): Promise<void> {
276
- await this.request<void>(`/structures/remove/${structureID}`, 'DELETE');
311
+ if (this.connectionInProgress) {
312
+ console.log("Connection already in progress, skipping initialization");
313
+ return;
277
314
  }
278
315
 
279
- // Data Type Endpoints
280
- async createType(type: I_DocumentType): Promise<I_DocumentType> {
281
- return await this.request<I_DocumentType>('/types/write', 'POST', type);
316
+ if (this.socket.connected) {
317
+ console.log("Socket already connected with ID:", this.socket.id);
318
+ return;
282
319
  }
283
320
 
284
- async removeType(typeID: string) {
285
- return await this.request<void>(`/types/remove/${typeID}`, 'DELETE');
286
- }
321
+ this.connectionInProgress = true;
287
322
 
288
- async getTypes(): Promise<I_DocumentType[]> {
289
- return await this.request<I_DocumentType[]>('/types/list', 'GET');
290
- }
323
+ try {
324
+ console.log("Setting up WebSocket connection with token");
291
325
 
292
- setToken(token: string | null): void {
293
- console.log("Setting token to:", token ? "***token***" : "null");
326
+ // Update the auth token
327
+ this.socket.auth = {token: this.authToken};
294
328
 
295
- const tokenChanged = this.authToken !== token;
296
- this.authToken = token;
329
+ // Remove any dynamic event listeners that might have been added
330
+ this.socket.offAny();
297
331
 
298
- if (!tokenChanged) {
299
- console.log("Token unchanged, no need to reconnect");
300
- return;
332
+ // Set up event handler for application events
333
+ this.socket.onAny((event: I_EventString, data: I_WsMessage) => {
334
+ if (event === "heartbeatPing") {
335
+ console.log("Ping event received:", data);
336
+ this.socket.emit("heartbeatPong", Date.now());
337
+ } else if (this.callbackFunction) {
338
+ this.callbackFunction(event, data);
301
339
  }
340
+ });
302
341
 
303
- // If we have a new token and realtime sync is enabled
304
- if (token && this.realTimeSync) {
305
- console.log("New token set, will initialize WebSocket");
306
-
307
- // Ensure any existing connection is closed first
308
- if (this.socket.connected) {
309
- console.log("Disconnecting existing socket before reconnecting with new token");
310
- this.socket.disconnect();
311
- }
312
-
313
- // Wait a moment for the disconnect to complete
314
- setTimeout(() => {
315
- console.log("Initializing WebSocket with new token");
316
- this.initWebSocket();
317
- }, 300);
318
- }
319
- // If token was cleared or realtime sync is disabled
320
- else if (this.socket.connected) {
321
- console.log("Token cleared or realtime sync disabled, disconnecting");
322
- this.socket.disconnect();
323
- }
324
- }
342
+ // Connect to the server
343
+ console.log("Connecting socket with auth token");
344
+ this.socket.connect();
325
345
 
326
- getVersion() {
327
- return packetJson.version;
328
- }
346
+ // Add a timeout to detect if connection is taking too long
347
+ setTimeout(() => {
348
+ if (this.connectionInProgress) {
349
+ console.warn("Socket connection attempt timed out after 5 seconds");
350
+ this.connectionInProgress = false;
329
351
 
330
- debugSocketConnection(): void {
331
- console.log("Socket connection debug info:");
332
- console.log("- Connected:", this.socket.connected);
333
- console.log("- Socket ID:", this.socket.id);
334
- console.log("- Auth token present:", !!this.authToken);
335
- console.log("- Connection in progress:", this.connectionInProgress);
336
- console.log("- Realtime sync enabled:", this.realTimeSync);
337
- console.log("- Socket options:", this.socket.io.opts);
338
-
339
- // Try to force reconnection
340
- if (!this.socket.connected && this.authToken && this.realTimeSync) {
341
- console.log("Attempting to force reconnection...");
342
- this.socket.auth = { token: this.authToken };
352
+ // If we're still not connected after the timeout, try again with polling
353
+ if (!this.socket.connected) {
354
+ console.log("Retrying connection with polling transport");
355
+ this.socket.io.opts.transports = ['polling', 'websocket'];
343
356
  this.socket.connect();
357
+ }
344
358
  }
359
+ }, 5000);
360
+ } catch (error) {
361
+ console.error('Error in initWebSocket:', error);
362
+ this.connectionInProgress = false;
345
363
  }
364
+ }
365
+
366
+ private async request<T>(endpoint: string, method: string, body?: any, requiresAuth: boolean = true): Promise<T> {
367
+ const headers: HeadersInit = {
368
+ 'Content-Type': 'application/json',
369
+ };
370
+
371
+ if (requiresAuth && this.authToken)
372
+ headers['Authorization'] = `Bearer ${this.authToken}`;
373
+ if (this.socket.id)
374
+ headers['X-Socket-ID'] = this.socket.id;
375
+
376
+ const options: RequestInit = {
377
+ method,
378
+ headers,
379
+ body: body ? JSON.stringify(body) : undefined
380
+ };
381
+
382
+ const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
383
+ const normalizedBaseUrl = this.baseUrl.endsWith('/') ? this.baseUrl.slice(0, -1) : this.baseUrl;
384
+ const url = `${normalizedBaseUrl}${normalizedEndpoint}`;
385
+ const response = await fetch(url, options);
386
+
387
+ if (!response.ok) {
388
+ if (response.status === 401 || response.status === 403) {
389
+ this.authToken = null;
390
+ }
391
+ throw new Error(`API error: ${response.status} ${response.statusText}`);
392
+ }
393
+
394
+ return await response.json() as T;
395
+ }
346
396
 
347
397
  }