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/README.md +305 -0
- package/dist/index.d.ts +64 -6
- package/dist/index.js +168 -127
- package/dist/types.d.ts +17 -9
- package/package.json +4 -4
- package/src/__tests__/index.test.ts +7 -7
- package/src/index.ts +334 -284
- package/src/types.ts +23 -11
package/src/index.ts
CHANGED
|
@@ -6,342 +6,392 @@ import type {
|
|
|
6
6
|
I_UserDisplay,
|
|
7
7
|
I_DocumentEntry,
|
|
8
8
|
I_DataStructure,
|
|
9
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
132
|
+
// Reconnect websocket with new token if realtime sync is enabled
|
|
133
|
+
if (this.realTimeSync) {
|
|
134
|
+
this.initWebSocket();
|
|
135
|
+
}
|
|
85
136
|
|
|
86
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
154
|
+
async removeUser(userID: string): Promise<void> {
|
|
155
|
+
await this.request<void>(`/users/remove/${userID}`, 'DELETE');
|
|
156
|
+
}
|
|
126
157
|
|
|
127
|
-
|
|
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
|
-
|
|
130
|
-
|
|
163
|
+
async listDocuments(): Promise<I_DocumentEntry[]> {
|
|
164
|
+
return await this.request<I_DocumentEntry[]>('/docs/list', 'GET');
|
|
165
|
+
}
|
|
131
166
|
|
|
132
|
-
|
|
133
|
-
|
|
167
|
+
async fetchDocument(queryObject: I_DocumentQuery): Promise<I_DocumentEntry[]> {
|
|
168
|
+
return await this.request<I_DocumentEntry[]>(`/docs/fetch/`, 'POST', queryObject);
|
|
169
|
+
}
|
|
134
170
|
|
|
135
|
-
|
|
136
|
-
|
|
171
|
+
async updateDocument(documentID: string, documentData: I_DocumentEntry): Promise<void> {
|
|
172
|
+
await this.request<void>(`/docs/update/${documentID}`, 'PATCH', documentData);
|
|
173
|
+
}
|
|
137
174
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
207
|
-
}
|
|
192
|
+
async removeStructure(structureID: string): Promise<void> {
|
|
193
|
+
await this.request<void>(`/structures/remove/${structureID}`, 'DELETE');
|
|
194
|
+
}
|
|
208
195
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
201
|
+
async removeType(typeID: string) {
|
|
202
|
+
return await this.request<void>(`/types/remove/${typeID}`, 'DELETE');
|
|
203
|
+
}
|
|
219
204
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
205
|
+
async getTypes(): Promise<I_DocumentType[]> {
|
|
206
|
+
return await this.request<I_DocumentType[]>('/types/list', 'GET');
|
|
207
|
+
}
|
|
224
208
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
209
|
+
async updateType(updatedType: I_DocumentType): Promise<void> {
|
|
210
|
+
await this.request<void>(`/types/write`, 'POST', updatedType);
|
|
211
|
+
}
|
|
228
212
|
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
238
|
-
|
|
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
|
-
|
|
247
|
-
|
|
219
|
+
if (!tokenChanged) {
|
|
220
|
+
console.log("Token unchanged, no need to reconnect");
|
|
221
|
+
return;
|
|
248
222
|
}
|
|
249
223
|
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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
|
-
|
|
268
|
-
|
|
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
|
-
|
|
272
|
-
|
|
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
|
-
|
|
276
|
-
|
|
311
|
+
if (this.connectionInProgress) {
|
|
312
|
+
console.log("Connection already in progress, skipping initialization");
|
|
313
|
+
return;
|
|
277
314
|
}
|
|
278
315
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
316
|
+
if (this.socket.connected) {
|
|
317
|
+
console.log("Socket already connected with ID:", this.socket.id);
|
|
318
|
+
return;
|
|
282
319
|
}
|
|
283
320
|
|
|
284
|
-
|
|
285
|
-
return await this.request<void>(`/types/remove/${typeID}`, 'DELETE');
|
|
286
|
-
}
|
|
321
|
+
this.connectionInProgress = true;
|
|
287
322
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
323
|
+
try {
|
|
324
|
+
console.log("Setting up WebSocket connection with token");
|
|
291
325
|
|
|
292
|
-
|
|
293
|
-
|
|
326
|
+
// Update the auth token
|
|
327
|
+
this.socket.auth = {token: this.authToken};
|
|
294
328
|
|
|
295
|
-
|
|
296
|
-
|
|
329
|
+
// Remove any dynamic event listeners that might have been added
|
|
330
|
+
this.socket.offAny();
|
|
297
331
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
327
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
}
|