docpouch-client 0.8.9 → 0.8.11
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/index.d.ts +0 -3
- package/package.json +5 -5
- package/src/index.ts +409 -411
package/dist/index.d.ts
CHANGED
|
@@ -106,7 +106,6 @@ export interface I_UserCreation {
|
|
|
106
106
|
isAdmin: boolean;
|
|
107
107
|
}
|
|
108
108
|
export interface I_UserUpdate {
|
|
109
|
-
_id: string;
|
|
110
109
|
name?: string;
|
|
111
110
|
password?: string;
|
|
112
111
|
email?: string;
|
|
@@ -142,7 +141,6 @@ export interface I_DocumentCreationOwned extends I_DocumentCreation {
|
|
|
142
141
|
owner: string;
|
|
143
142
|
}
|
|
144
143
|
export interface I_DocumentUpdate extends I_DocumentQuery {
|
|
145
|
-
_id: string;
|
|
146
144
|
content?: any;
|
|
147
145
|
description?: string;
|
|
148
146
|
}
|
|
@@ -178,7 +176,6 @@ export interface I_StructureCreation {
|
|
|
178
176
|
fields: I_StructureField[];
|
|
179
177
|
}
|
|
180
178
|
export interface I_StructureUpdate {
|
|
181
|
-
_id?: string;
|
|
182
179
|
name?: string;
|
|
183
180
|
description?: string;
|
|
184
181
|
fields?: I_StructureField[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docpouch-client",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.11",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/jest": "^30.0.0",
|
|
30
|
-
"@types/node": "^24.0
|
|
31
|
-
"jest": "^30.0.
|
|
32
|
-
"ts-jest": "^29.4.
|
|
33
|
-
"typescript": "^5.
|
|
30
|
+
"@types/node": "^24.3.0",
|
|
31
|
+
"jest": "^30.0.5",
|
|
32
|
+
"ts-jest": "^29.4.1",
|
|
33
|
+
"typescript": "^5.9.2"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"socket.io-client": "^4.8.1"
|
package/src/index.ts
CHANGED
|
@@ -5,511 +5,509 @@ import packetJson from '../package.json'
|
|
|
5
5
|
* Client for interacting with docPouch API.
|
|
6
6
|
*/
|
|
7
7
|
export default class docPouchClient {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Sets the real-time synchronization status.
|
|
83
|
-
*
|
|
84
|
-
* @param {boolean} newRealTimeSync - The new real-time sync setting (true/false).
|
|
85
|
-
*/
|
|
86
|
-
setRealTimeSync(newRealTimeSync: boolean) {
|
|
87
|
-
console.log(`Setting realtime sync to: ${newRealTimeSync}. Current setting: ${this.realTimeSync}`);
|
|
88
|
-
|
|
89
|
-
// Skip if the setting isn't changing
|
|
90
|
-
if (newRealTimeSync === this.realTimeSync) {
|
|
91
|
-
console.log("Realtime sync setting unchanged, skipping");
|
|
92
|
-
return;
|
|
8
|
+
/**
|
|
9
|
+
* The base URL of the server.
|
|
10
|
+
*
|
|
11
|
+
* @type {string}
|
|
12
|
+
*/
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
/**
|
|
15
|
+
* Socket.IO socket instance for real-time communication with the server.
|
|
16
|
+
*
|
|
17
|
+
* @type {Socket}
|
|
18
|
+
*/
|
|
19
|
+
socket: Socket;
|
|
20
|
+
/**
|
|
21
|
+
* Callback function to handle socket events.
|
|
22
|
+
*
|
|
23
|
+
* @type {(event: I_EventString, data: I_WsMessage) => void}
|
|
24
|
+
*/
|
|
25
|
+
callbackFunction;
|
|
26
|
+
/**
|
|
27
|
+
* Flag indicating whether real-time synchronization is enabled.
|
|
28
|
+
*
|
|
29
|
+
* @type {boolean}
|
|
30
|
+
*/
|
|
31
|
+
realTimeSync: boolean = false;
|
|
32
|
+
/**
|
|
33
|
+
* Authentication token used to authorize requests.
|
|
34
|
+
*
|
|
35
|
+
* @private
|
|
36
|
+
* @type {string | null}
|
|
37
|
+
*/
|
|
38
|
+
private authToken: string | null = null;
|
|
39
|
+
/**
|
|
40
|
+
* Flag indicating whether a connection attempt is in progress.
|
|
41
|
+
*
|
|
42
|
+
* @private
|
|
43
|
+
* @type {boolean}
|
|
44
|
+
*/
|
|
45
|
+
private connectionInProgress = false;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates an instance of docPouchClient.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} host - The base URL for the server.
|
|
51
|
+
* @param {number} [port=80] - The port number to connect to (default is 80).
|
|
52
|
+
* @param {(event: I_EventString, data: I_WsMessage) => void} [callback] - Optional callback function for socket events.
|
|
53
|
+
*/
|
|
54
|
+
constructor(host: string, port: number = 80, callback?: (event: I_EventString, data: I_WsMessage) => void) {
|
|
55
|
+
this.baseUrl = host;
|
|
56
|
+
const socketUrl = host.includes('://') ? host : `https://${host}`;
|
|
57
|
+
const socketUrlWithPort = socketUrl.includes(':') && !socketUrl.endsWith(':')
|
|
58
|
+
? socketUrl
|
|
59
|
+
: `${socketUrl}:${port}`;
|
|
60
|
+
|
|
61
|
+
console.log(`Initializing Socket.IO with URL: ${socketUrlWithPort}, path: /socket.io`);
|
|
62
|
+
|
|
63
|
+
this.socket = io(`${socketUrlWithPort}`, {
|
|
64
|
+
autoConnect: false,
|
|
65
|
+
transports: ['websocket'], // Try websocket only first
|
|
66
|
+
reconnection: true,
|
|
67
|
+
reconnectionAttempts: 5,
|
|
68
|
+
reconnectionDelay: 1000,
|
|
69
|
+
forceNew: true, // Force a new connection
|
|
70
|
+
auth: {
|
|
71
|
+
token: null // Will be set later
|
|
72
|
+
},
|
|
73
|
+
path: '/socket.io'
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.callbackFunction = callback;
|
|
77
|
+
|
|
78
|
+
this.setupPermanentSocketListeners();
|
|
93
79
|
}
|
|
94
80
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
this.
|
|
112
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Sets the real-time synchronization status.
|
|
83
|
+
*
|
|
84
|
+
* @param {boolean} newRealTimeSync - The new real-time sync setting (true/false).
|
|
85
|
+
*/
|
|
86
|
+
setRealTimeSync(newRealTimeSync: boolean) {
|
|
87
|
+
console.log(`Setting realtime sync to: ${newRealTimeSync}. Current setting: ${this.realTimeSync}`);
|
|
88
|
+
|
|
89
|
+
// Skip if the setting isn't changing
|
|
90
|
+
if (newRealTimeSync === this.realTimeSync) {
|
|
91
|
+
console.log("Realtime sync setting unchanged, skipping");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.realTimeSync = newRealTimeSync;
|
|
96
|
+
|
|
97
|
+
if (newRealTimeSync && this.authToken) {
|
|
98
|
+
console.log("Activating realtime updates");
|
|
99
|
+
|
|
100
|
+
// Ensure we're not in the middle of another connection attempt
|
|
101
|
+
if (this.connectionInProgress) {
|
|
102
|
+
console.log("Connection already in progress, waiting before initializing");
|
|
103
|
+
setTimeout(() => this.initWebSocket(), 500);
|
|
104
|
+
} else {
|
|
105
|
+
this.initWebSocket();
|
|
106
|
+
}
|
|
107
|
+
} else if (!newRealTimeSync) {
|
|
108
|
+
console.log("Deactivating realtime updates");
|
|
109
|
+
if (this.socket.connected) {
|
|
110
|
+
console.log("Disconnecting socket");
|
|
111
|
+
this.socket.disconnect();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
113
114
|
}
|
|
114
|
-
}
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
// User Administration Endpoints
|
|
117
|
+
async login(credentials: I_UserLogin): Promise<I_LoginResponse | null> {
|
|
118
|
+
const response = await this.request<I_LoginResponse>('/users/login', 'POST', credentials, false);
|
|
119
|
+
if (response.token) {
|
|
120
|
+
this.authToken = response.token;
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
// Reconnect websocket with new token if realtime sync is enabled
|
|
123
|
+
if (this.realTimeSync) {
|
|
124
|
+
this.initWebSocket();
|
|
125
|
+
}
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
return {token: response.token, isAdmin: response.isAdmin, userName: response.userName};
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
128
130
|
}
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
async listUsers(): Promise<I_UserEntry[]> {
|
|
133
|
+
return await this.request<I_UserEntry[]>('/users/list', 'GET');
|
|
134
|
+
}
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
async updateUser(userID: string, userData: I_UserUpdate): Promise<void> {
|
|
137
|
+
await this.request<void>(`/users/update/${userID}`, 'PATCH', userData);
|
|
138
|
+
}
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
async createUser(userData: I_UserCreation): Promise<I_UserDisplay> {
|
|
141
|
+
return await this.request<I_UserDisplay>('/users/create', 'POST', userData);
|
|
142
|
+
}
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
async removeUser(userID: string): Promise<void> {
|
|
145
|
+
await this.request<void>(`/users/remove/${userID}`, 'DELETE');
|
|
146
|
+
}
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
// Document Management Endpoints
|
|
149
|
+
async createDocument(document: I_DocumentEntry): Promise<I_DocumentEntry> {
|
|
150
|
+
return await this.request<I_DocumentEntry>('/docs/create', 'POST', document);
|
|
151
|
+
}
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
async listDocuments(): Promise<I_DocumentEntry[]> {
|
|
154
|
+
return await this.request<I_DocumentEntry[]>('/docs/list', 'GET');
|
|
155
|
+
}
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
async fetchDocument(queryObject: I_DocumentQuery): Promise<I_DocumentEntry[]> {
|
|
158
|
+
return await this.request<I_DocumentEntry[]>(`/docs/fetch/`, 'POST', queryObject);
|
|
159
|
+
}
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
async updateDocument(documentID: string, documentData: I_DocumentEntry): Promise<void> {
|
|
162
|
+
await this.request<void>(`/docs/update/${documentID}`, 'PATCH', documentData);
|
|
163
|
+
}
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
async removeDocument(documentID: string): Promise<void> {
|
|
166
|
+
await this.request<void>(`/docs/remove/${documentID}`, 'DELETE');
|
|
167
|
+
}
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
169
|
+
// Data Structure Endpoints
|
|
170
|
+
async createStructure(structure: I_StructureCreation): Promise<I_DataStructure> {
|
|
171
|
+
return await this.request<I_DataStructure>('/structures/create', 'POST', structure);
|
|
172
|
+
}
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
async getStructures(): Promise<I_DataStructure[]> {
|
|
175
|
+
return await this.request<I_DataStructure[]>('/structures/list', 'GET');
|
|
176
|
+
}
|
|
177
177
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
async updateStructure(structureID: string, structureData: I_DataStructure): Promise<void> {
|
|
179
|
+
await this.request<void>(`/structures/update/${structureID}`, 'PATCH', structureData);
|
|
180
|
+
}
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
async removeStructure(structureID: string): Promise<void> {
|
|
183
|
+
await this.request<void>(`/structures/remove/${structureID}`, 'DELETE');
|
|
184
|
+
}
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
186
|
+
// Data Type Endpoints
|
|
187
|
+
async createType(type: I_DocumentType): Promise<I_DocumentType> {
|
|
188
|
+
return await this.request<I_DocumentType>('/types/write', 'POST', type);
|
|
189
|
+
}
|
|
190
190
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
191
|
+
async removeType(typeID: string) {
|
|
192
|
+
return await this.request<void>(`/types/remove/${typeID}`, 'DELETE');
|
|
193
|
+
}
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
195
|
+
async getTypes(): Promise<I_DocumentType[]> {
|
|
196
|
+
return await this.request<I_DocumentType[]>('/types/list', 'GET');
|
|
197
|
+
}
|
|
198
198
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
async updateType(updatedType: I_DocumentType): Promise<void> {
|
|
200
|
+
await this.request<void>(`/types/write`, 'POST', updatedType);
|
|
201
|
+
}
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
setToken(token: string | null): void {
|
|
204
|
+
console.log("Setting token to:", token ? "***token***" : "null");
|
|
205
205
|
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
const tokenChanged = this.authToken !== token;
|
|
207
|
+
this.authToken = token;
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
if (!tokenChanged) {
|
|
210
|
+
console.log("Token unchanged, no need to reconnect");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
213
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
getVersion() {
|
|
238
|
-
return packetJson.version;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
debugSocketConnection(): void {
|
|
242
|
-
console.log("Socket connection debug info:");
|
|
243
|
-
console.log("- Connected:", this.socket.connected);
|
|
244
|
-
console.log("- Socket ID:", this.socket.id);
|
|
245
|
-
console.log("- Auth token present:", !!this.authToken);
|
|
246
|
-
console.log("- Connection in progress:", this.connectionInProgress);
|
|
247
|
-
console.log("- Realtime sync enabled:", this.realTimeSync);
|
|
248
|
-
console.log("- Socket options:", this.socket.io.opts);
|
|
249
|
-
|
|
250
|
-
// Try to force reconnection
|
|
251
|
-
if (!this.socket.connected && this.authToken && this.realTimeSync) {
|
|
252
|
-
console.log("Attempting to force reconnection...");
|
|
253
|
-
this.socket.auth = {token: this.authToken};
|
|
254
|
-
this.socket.connect();
|
|
214
|
+
// If we have a new token and realtime sync is enabled
|
|
215
|
+
if (token && this.realTimeSync) {
|
|
216
|
+
console.log("New token set, will initialize WebSocket");
|
|
217
|
+
|
|
218
|
+
// Ensure any existing connection is closed first
|
|
219
|
+
if (this.socket.connected) {
|
|
220
|
+
console.log("Disconnecting existing socket before reconnecting with new token");
|
|
221
|
+
this.socket.disconnect();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Wait a moment for the disconnect to complete
|
|
225
|
+
setTimeout(() => {
|
|
226
|
+
console.log("Initializing WebSocket with new token");
|
|
227
|
+
this.initWebSocket();
|
|
228
|
+
}, 300);
|
|
229
|
+
}
|
|
230
|
+
// If token was cleared or realtime sync is disabled
|
|
231
|
+
else if (this.socket.connected) {
|
|
232
|
+
console.log("Token cleared or realtime sync disabled, disconnecting");
|
|
233
|
+
this.socket.disconnect();
|
|
234
|
+
}
|
|
255
235
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
* Sets up permanent socket listeners for the client.
|
|
260
|
-
*
|
|
261
|
-
* @private
|
|
262
|
-
*/
|
|
263
|
-
private setupPermanentSocketListeners() {
|
|
264
|
-
// These are permanent listeners that won't be removed
|
|
265
|
-
this.socket.on('connect_error', (error) => {
|
|
266
|
-
console.error('Socket connection error:', error.message);
|
|
267
|
-
this.connectionInProgress = false;
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
this.socket.on('connect', () => {
|
|
271
|
-
console.log('Socket connected successfully with ID:', this.socket.id);
|
|
272
|
-
this.connectionInProgress = false;
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
this.socket.on('disconnect', (reason) => {
|
|
276
|
-
console.log('Socket disconnected. Reason:', reason);
|
|
277
|
-
this.connectionInProgress = false;
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
this.socket.on('error', (error) => {
|
|
281
|
-
console.error('Socket error:', error);
|
|
282
|
-
this.connectionInProgress = false;
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Initializes the WebSocket connection with the server.
|
|
288
|
-
*
|
|
289
|
-
* @private
|
|
290
|
-
*/
|
|
291
|
-
private initWebSocket() {
|
|
292
|
-
console.log("initWebSocket called. Auth token present:", !!this.authToken,
|
|
293
|
-
"Connection in progress:", this.connectionInProgress,
|
|
294
|
-
"Socket connected:", this.socket.connected);
|
|
295
|
-
|
|
296
|
-
if (!this.authToken) {
|
|
297
|
-
console.log("Skipping WebSocket initialization: No auth token");
|
|
298
|
-
return;
|
|
236
|
+
|
|
237
|
+
getVersion() {
|
|
238
|
+
return packetJson.version;
|
|
299
239
|
}
|
|
300
240
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
241
|
+
debugSocketConnection(): void {
|
|
242
|
+
console.log("Socket connection debug info:");
|
|
243
|
+
console.log("- Connected:", this.socket.connected);
|
|
244
|
+
console.log("- Socket ID:", this.socket.id);
|
|
245
|
+
console.log("- Auth token present:", !!this.authToken);
|
|
246
|
+
console.log("- Connection in progress:", this.connectionInProgress);
|
|
247
|
+
console.log("- Realtime sync enabled:", this.realTimeSync);
|
|
248
|
+
console.log("- Socket options:", this.socket.io.opts);
|
|
249
|
+
|
|
250
|
+
// Try to force reconnection
|
|
251
|
+
if (!this.socket.connected && this.authToken && this.realTimeSync) {
|
|
252
|
+
console.log("Attempting to force reconnection...");
|
|
253
|
+
this.socket.auth = {token: this.authToken};
|
|
254
|
+
this.socket.connect();
|
|
255
|
+
}
|
|
304
256
|
}
|
|
305
257
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Sets up permanent socket listeners for the client.
|
|
260
|
+
*
|
|
261
|
+
* @private
|
|
262
|
+
*/
|
|
263
|
+
private setupPermanentSocketListeners() {
|
|
264
|
+
// These are permanent listeners that won't be removed
|
|
265
|
+
this.socket.on('connect_error', (error) => {
|
|
266
|
+
console.error('Socket connection error:', error.message);
|
|
267
|
+
this.connectionInProgress = false;
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
this.socket.on('connect', () => {
|
|
271
|
+
console.log('Socket connected successfully with ID:', this.socket.id);
|
|
272
|
+
this.connectionInProgress = false;
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
this.socket.on('disconnect', (reason) => {
|
|
276
|
+
console.log('Socket disconnected. Reason:', reason);
|
|
277
|
+
this.connectionInProgress = false;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
this.socket.on('error', (error) => {
|
|
281
|
+
console.error('Socket error:', error);
|
|
282
|
+
this.connectionInProgress = false;
|
|
283
|
+
});
|
|
309
284
|
}
|
|
310
285
|
|
|
311
|
-
|
|
286
|
+
/**
|
|
287
|
+
* Initializes the WebSocket connection with the server.
|
|
288
|
+
*
|
|
289
|
+
* @private
|
|
290
|
+
*/
|
|
291
|
+
private initWebSocket() {
|
|
292
|
+
console.log("initWebSocket called. Auth token present:", !!this.authToken,
|
|
293
|
+
"Connection in progress:", this.connectionInProgress,
|
|
294
|
+
"Socket connected:", this.socket.connected);
|
|
295
|
+
|
|
296
|
+
if (!this.authToken) {
|
|
297
|
+
console.log("Skipping WebSocket initialization: No auth token");
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
312
300
|
|
|
313
|
-
|
|
314
|
-
|
|
301
|
+
if (this.connectionInProgress) {
|
|
302
|
+
console.log("Connection already in progress, skipping initialization");
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
315
305
|
|
|
316
|
-
|
|
317
|
-
|
|
306
|
+
if (this.socket.connected) {
|
|
307
|
+
console.log("Socket already connected with ID:", this.socket.id);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
318
310
|
|
|
319
|
-
|
|
320
|
-
this.socket.offAny();
|
|
311
|
+
this.connectionInProgress = true;
|
|
321
312
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (event === "heartbeatPing") {
|
|
325
|
-
console.log("Ping event received:", data);
|
|
326
|
-
this.socket.emit("heartbeatPong", Date.now());
|
|
327
|
-
} else if (this.callbackFunction) {
|
|
328
|
-
this.callbackFunction(event, data);
|
|
329
|
-
}
|
|
330
|
-
});
|
|
313
|
+
try {
|
|
314
|
+
console.log("Setting up WebSocket connection with token");
|
|
331
315
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
this.socket.connect();
|
|
316
|
+
// Update the auth token
|
|
317
|
+
this.socket.auth = {token: this.authToken};
|
|
335
318
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
319
|
+
// Remove any dynamic event listeners that might have been added
|
|
320
|
+
this.socket.offAny();
|
|
321
|
+
|
|
322
|
+
// Set up event handler for application events
|
|
323
|
+
this.socket.onAny((event: I_EventString, data: I_WsMessage) => {
|
|
324
|
+
if (event === "heartbeatPing") {
|
|
325
|
+
console.log("Ping event received:", data);
|
|
326
|
+
this.socket.emit("heartbeatPong", Date.now());
|
|
327
|
+
} else if (this.callbackFunction) {
|
|
328
|
+
this.callbackFunction(event, data);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
341
331
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
console.log("Retrying connection with polling transport");
|
|
345
|
-
this.socket.io.opts.transports = ['polling', 'websocket'];
|
|
332
|
+
// Connect to the server
|
|
333
|
+
console.log("Connecting socket with auth token");
|
|
346
334
|
this.socket.connect();
|
|
347
|
-
|
|
335
|
+
|
|
336
|
+
// Add a timeout to detect if connection is taking too long
|
|
337
|
+
setTimeout(() => {
|
|
338
|
+
if (this.connectionInProgress) {
|
|
339
|
+
console.warn("Socket connection attempt timed out after 5 seconds");
|
|
340
|
+
this.connectionInProgress = false;
|
|
341
|
+
|
|
342
|
+
// If we're still not connected after the timeout, try again with polling
|
|
343
|
+
if (!this.socket.connected) {
|
|
344
|
+
console.log("Retrying connection with polling transport");
|
|
345
|
+
this.socket.io.opts.transports = ['polling', 'websocket'];
|
|
346
|
+
this.socket.connect();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}, 5000);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error('Error in initWebSocket:', error);
|
|
352
|
+
this.connectionInProgress = false;
|
|
348
353
|
}
|
|
349
|
-
}, 5000);
|
|
350
|
-
} catch (error) {
|
|
351
|
-
console.error('Error in initWebSocket:', error);
|
|
352
|
-
this.connectionInProgress = false;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
private async request<T>(endpoint: string, method: string, body?: any, requiresAuth: boolean = true): Promise<T> {
|
|
357
|
-
const headers: HeadersInit = {
|
|
358
|
-
'Content-Type': 'application/json',
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
if (requiresAuth && this.authToken)
|
|
362
|
-
headers['Authorization'] = `Bearer ${this.authToken}`;
|
|
363
|
-
if (this.socket.id)
|
|
364
|
-
headers['X-Socket-ID'] = this.socket.id;
|
|
365
|
-
|
|
366
|
-
const options: RequestInit = {
|
|
367
|
-
method,
|
|
368
|
-
headers,
|
|
369
|
-
body: body ? JSON.stringify(body) : undefined
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
|
373
|
-
const normalizedBaseUrl = this.baseUrl.endsWith('/') ? this.baseUrl.slice(0, -1) : this.baseUrl;
|
|
374
|
-
const url = `${normalizedBaseUrl}${normalizedEndpoint}`;
|
|
375
|
-
const response = await fetch(url, options);
|
|
376
|
-
|
|
377
|
-
if (!response.ok) {
|
|
378
|
-
if (response.status === 401 || response.status === 403) {
|
|
379
|
-
this.authToken = null;
|
|
380
|
-
}
|
|
381
|
-
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
382
354
|
}
|
|
383
355
|
|
|
384
|
-
|
|
385
|
-
|
|
356
|
+
private async request<T>(endpoint: string, method: string, body?: any, requiresAuth: boolean = true): Promise<T> {
|
|
357
|
+
const headers: HeadersInit = {
|
|
358
|
+
'Content-Type': 'application/json',
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
if (requiresAuth && this.authToken)
|
|
362
|
+
headers['Authorization'] = `Bearer ${this.authToken}`;
|
|
363
|
+
if (this.socket.id)
|
|
364
|
+
headers['X-Socket-ID'] = this.socket.id;
|
|
365
|
+
|
|
366
|
+
const options: RequestInit = {
|
|
367
|
+
method,
|
|
368
|
+
headers,
|
|
369
|
+
body: body ? JSON.stringify(body) : undefined
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
|
373
|
+
const normalizedBaseUrl = this.baseUrl.endsWith('/') ? this.baseUrl.slice(0, -1) : this.baseUrl;
|
|
374
|
+
const url = `${normalizedBaseUrl}${normalizedEndpoint}`;
|
|
375
|
+
const response = await fetch(url, options);
|
|
376
|
+
|
|
377
|
+
if (!response.ok) {
|
|
378
|
+
if (response.status === 401 || response.status === 403) {
|
|
379
|
+
this.authToken = null;
|
|
380
|
+
}
|
|
381
|
+
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return await response.json() as T;
|
|
385
|
+
}
|
|
386
386
|
}
|
|
387
387
|
|
|
388
388
|
// Common type definitions for both frontend and backend
|
|
389
389
|
|
|
390
390
|
// User related types
|
|
391
391
|
export interface I_UserEntry extends I_UserCreation {
|
|
392
|
-
|
|
392
|
+
_id: string;
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
export interface I_UserLogin {
|
|
396
|
-
|
|
397
|
-
|
|
396
|
+
name: string;
|
|
397
|
+
password: string;
|
|
398
398
|
}
|
|
399
399
|
|
|
400
400
|
export interface I_UserCreation {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
401
|
+
name: string;
|
|
402
|
+
password: string;
|
|
403
|
+
email?: string;
|
|
404
|
+
department: string;
|
|
405
|
+
group: string;
|
|
406
|
+
isAdmin: boolean;
|
|
407
407
|
}
|
|
408
408
|
|
|
409
409
|
export interface I_UserUpdate {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
isAdmin?: boolean;
|
|
410
|
+
name?: string;
|
|
411
|
+
password?: string;
|
|
412
|
+
email?: string;
|
|
413
|
+
department?: string;
|
|
414
|
+
group?: string;
|
|
415
|
+
isAdmin?: boolean;
|
|
417
416
|
}
|
|
418
417
|
|
|
419
418
|
export interface I_UserDisplay {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
419
|
+
_id: string;
|
|
420
|
+
username: string;
|
|
421
|
+
department: string;
|
|
422
|
+
group: string;
|
|
423
|
+
email?: string;
|
|
425
424
|
}
|
|
426
425
|
|
|
427
426
|
export interface I_LoginResponse {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
427
|
+
token: string;
|
|
428
|
+
isAdmin: boolean;
|
|
429
|
+
userName: string;
|
|
431
430
|
}
|
|
432
431
|
|
|
433
432
|
// Document related types
|
|
434
433
|
export interface I_DocumentEntry extends I_DocumentCreationOwned {
|
|
435
|
-
|
|
434
|
+
_id: string;
|
|
436
435
|
}
|
|
437
436
|
|
|
438
437
|
export interface I_DocumentCreation {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
438
|
+
title: string;
|
|
439
|
+
description?: string;
|
|
440
|
+
type: number;
|
|
441
|
+
subType: number;
|
|
442
|
+
content: any;
|
|
443
|
+
shareWithGroup: boolean;
|
|
444
|
+
shareWithDepartment: boolean;
|
|
446
445
|
}
|
|
447
446
|
|
|
448
447
|
|
|
449
448
|
export interface I_DocumentCreationOwned extends I_DocumentCreation {
|
|
450
|
-
|
|
449
|
+
owner: string;
|
|
451
450
|
}
|
|
452
451
|
|
|
453
452
|
export interface I_DocumentUpdate extends I_DocumentQuery {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
description?: string;
|
|
453
|
+
content?: any;
|
|
454
|
+
description?: string;
|
|
457
455
|
}
|
|
458
456
|
|
|
459
457
|
export interface I_DocumentQuery {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
458
|
+
_id?: string;
|
|
459
|
+
owner?: string;
|
|
460
|
+
title?: string;
|
|
461
|
+
type?: number;
|
|
462
|
+
subType?: number;
|
|
463
|
+
shareWithGroup?: boolean;
|
|
464
|
+
shareWithDepartment?: boolean;
|
|
467
465
|
}
|
|
468
466
|
|
|
469
467
|
|
|
470
468
|
// Structure related types
|
|
471
469
|
export interface I_DataStructure {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
470
|
+
_id?: string | undefined;
|
|
471
|
+
name: string;
|
|
472
|
+
description: string;
|
|
473
|
+
fields: I_StructureField[];
|
|
476
474
|
}
|
|
477
475
|
|
|
478
476
|
export interface I_StructureField {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
477
|
+
name: string;
|
|
478
|
+
type: string;
|
|
479
|
+
items?: string;
|
|
482
480
|
}
|
|
483
481
|
|
|
484
482
|
export interface I_StructureEntry {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
483
|
+
_id?: string;
|
|
484
|
+
name: string;
|
|
485
|
+
description: string;
|
|
486
|
+
fields: I_StructureField[];
|
|
489
487
|
}
|
|
490
488
|
|
|
491
489
|
|
|
492
490
|
export interface I_StructureCreation {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
491
|
+
name: string;
|
|
492
|
+
description?: string;
|
|
493
|
+
fields: I_StructureField[];
|
|
496
494
|
}
|
|
497
495
|
|
|
498
496
|
export interface I_StructureUpdate {
|
|
499
|
-
_id?: string
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
497
|
+
_id?: string
|
|
498
|
+
name?: string;
|
|
499
|
+
description?: string;
|
|
500
|
+
fields?: I_StructureField[];
|
|
503
501
|
}
|
|
504
502
|
|
|
505
503
|
// Document type related types
|
|
506
504
|
export interface I_DocumentType {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
505
|
+
_id?: string;
|
|
506
|
+
type: number;
|
|
507
|
+
subType: number;
|
|
508
|
+
name: string;
|
|
509
|
+
description?: string;
|
|
510
|
+
defaultStructureID?: string;
|
|
513
511
|
}
|
|
514
512
|
|
|
515
513
|
// WebSocket-related types
|
|
@@ -518,16 +516,16 @@ export type I_EventString = 'heartbeatPong' | "heartbeatPing" | "newDocument" |
|
|
|
518
516
|
"removedUser" | "removedStructure" | "removedDocument" | "removedType";
|
|
519
517
|
|
|
520
518
|
export interface I_WsMessage {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
519
|
+
newDocument?: I_DocumentEntry;
|
|
520
|
+
newStructure?: I_StructureEntry;
|
|
521
|
+
newUser?: I_UserEntry;
|
|
522
|
+
removedID?: string;
|
|
523
|
+
changedDocument?: I_DocumentUpdate;
|
|
524
|
+
changedStructure?: I_StructureUpdate;
|
|
525
|
+
changedUser?: I_UserUpdate;
|
|
526
|
+
confirmSubscription?: boolean;
|
|
527
|
+
confirmUnsubscription?: boolean;
|
|
528
|
+
heartbeatPing?: number;
|
|
529
|
+
heartbeatPong?: number;
|
|
530
|
+
newType?: I_DocumentType;
|
|
533
531
|
}
|