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/dist/index.js CHANGED
@@ -1,40 +1,39 @@
1
1
  import { io } from "socket.io-client";
2
2
  import packetJson from '../package.json';
3
- export default class dbPouchClient {
3
+ /**
4
+ * Client for interacting with docPouch API.
5
+ */
6
+ export default class docPouchClient {
7
+ /**
8
+ * Creates an instance of docPouchClient.
9
+ *
10
+ * @param {string} host - The base URL for the server.
11
+ * @param {number} [port=80] - The port number to connect to (default is 80).
12
+ * @param {(event: I_EventString, data: I_WsMessage) => void} [callback] - Optional callback function for socket events.
13
+ */
4
14
  constructor(host, port = 80, callback) {
5
- this.authToken = null;
15
+ /**
16
+ * Flag indicating whether real-time synchronization is enabled.
17
+ *
18
+ * @type {boolean}
19
+ */
6
20
  this.realTimeSync = false;
21
+ /**
22
+ * Authentication token used to authorize requests.
23
+ *
24
+ * @private
25
+ * @type {string | null}
26
+ */
27
+ this.authToken = null;
28
+ /**
29
+ * Flag indicating whether a connection attempt is in progress.
30
+ *
31
+ * @private
32
+ * @type {boolean}
33
+ */
7
34
  this.connectionInProgress = false;
8
- this.setRealTimeSync = (newRealTimeSync) => {
9
- console.log(`Setting realtime sync to: ${newRealTimeSync}. Current setting: ${this.realTimeSync}`);
10
- // Skip if the setting isn't changing
11
- if (newRealTimeSync === this.realTimeSync) {
12
- console.log("Realtime sync setting unchanged, skipping");
13
- return;
14
- }
15
- this.realTimeSync = newRealTimeSync;
16
- if (newRealTimeSync && this.authToken) {
17
- console.log("Activating realtime updates");
18
- // Ensure we're not in the middle of another connection attempt
19
- if (this.connectionInProgress) {
20
- console.log("Connection already in progress, waiting before initializing");
21
- setTimeout(() => this.initWebSocket(), 500);
22
- }
23
- else {
24
- this.initWebSocket();
25
- }
26
- }
27
- else if (!newRealTimeSync) {
28
- console.log("Deactivating realtime updates");
29
- if (this.socket.connected) {
30
- console.log("Disconnecting socket");
31
- this.socket.disconnect();
32
- }
33
- }
34
- };
35
35
  this.baseUrl = host;
36
- const socketUrl = host.includes('://') ? host : `http://${host}`;
37
- // Don't append port twice if it's already in the URL
36
+ const socketUrl = host.includes('://') ? host : `https://${host}`;
38
37
  const socketUrlWithPort = socketUrl.includes(':') && !socketUrl.endsWith(':')
39
38
  ? socketUrl
40
39
  : `${socketUrl}:${port}`;
@@ -54,105 +53,37 @@ export default class dbPouchClient {
54
53
  this.callbackFunction = callback;
55
54
  this.setupPermanentSocketListeners();
56
55
  }
57
- setupPermanentSocketListeners() {
58
- // These are permanent listeners that won't be removed
59
- this.socket.on('connect_error', (error) => {
60
- console.error('Socket connection error:', error.message);
61
- this.connectionInProgress = false;
62
- });
63
- this.socket.on('connect', () => {
64
- console.log('Socket connected successfully with ID:', this.socket.id);
65
- this.connectionInProgress = false;
66
- });
67
- this.socket.on('disconnect', (reason) => {
68
- console.log('Socket disconnected. Reason:', reason);
69
- this.connectionInProgress = false;
70
- });
71
- this.socket.on('error', (error) => {
72
- console.error('Socket error:', error);
73
- this.connectionInProgress = false;
74
- });
75
- }
76
- initWebSocket() {
77
- console.log("initWebSocket called. Auth token present:", !!this.authToken, "Connection in progress:", this.connectionInProgress, "Socket connected:", this.socket.connected);
78
- if (!this.authToken) {
79
- console.log("Skipping WebSocket initialization: No auth token");
56
+ /**
57
+ * Sets the real-time synchronization status.
58
+ *
59
+ * @param {boolean} newRealTimeSync - The new real-time sync setting (true/false).
60
+ */
61
+ setRealTimeSync(newRealTimeSync) {
62
+ console.log(`Setting realtime sync to: ${newRealTimeSync}. Current setting: ${this.realTimeSync}`);
63
+ // Skip if the setting isn't changing
64
+ if (newRealTimeSync === this.realTimeSync) {
65
+ console.log("Realtime sync setting unchanged, skipping");
80
66
  return;
81
67
  }
82
- if (this.connectionInProgress) {
83
- console.log("Connection already in progress, skipping initialization");
84
- return;
85
- }
86
- if (this.socket.connected) {
87
- console.log("Socket already connected with ID:", this.socket.id);
88
- return;
89
- }
90
- this.connectionInProgress = true;
91
- try {
92
- console.log("Setting up WebSocket connection with token");
93
- // Update the auth token
94
- this.socket.auth = { token: this.authToken };
95
- // Remove any dynamic event listeners that might have been added
96
- this.socket.offAny();
97
- // Set up event handler for application events
98
- this.socket.onAny((event, data) => {
99
- if (event === "heartbeatPing") {
100
- console.log("Ping event received:", data);
101
- this.socket.emit("heartbeatPong", Date.now());
102
- }
103
- else if (this.callbackFunction) {
104
- this.callbackFunction(event, data);
105
- }
106
- });
107
- // Connect to the server
108
- console.log("Connecting socket with auth token");
109
- this.socket.connect();
110
- // Add a timeout to detect if connection is taking too long
111
- setTimeout(() => {
112
- if (this.connectionInProgress) {
113
- console.warn("Socket connection attempt timed out after 5 seconds");
114
- this.connectionInProgress = false;
115
- // If we're still not connected after the timeout, try again with polling
116
- if (!this.socket.connected) {
117
- console.log("Retrying connection with polling transport");
118
- this.socket.io.opts.transports = ['polling', 'websocket'];
119
- this.socket.connect();
120
- }
121
- }
122
- }, 5000);
123
- }
124
- catch (error) {
125
- console.error('Error in initWebSocket:', error);
126
- this.connectionInProgress = false;
68
+ this.realTimeSync = newRealTimeSync;
69
+ if (newRealTimeSync && this.authToken) {
70
+ console.log("Activating realtime updates");
71
+ // Ensure we're not in the middle of another connection attempt
72
+ if (this.connectionInProgress) {
73
+ console.log("Connection already in progress, waiting before initializing");
74
+ setTimeout(() => this.initWebSocket(), 500);
75
+ }
76
+ else {
77
+ this.initWebSocket();
78
+ }
127
79
  }
128
- }
129
- async request(endpoint, method, body, requiresAuth = true) {
130
- const headers = {
131
- 'Content-Type': 'application/json',
132
- };
133
- if (requiresAuth && this.authToken)
134
- headers['Authorization'] = `Bearer ${this.authToken}`;
135
- if (this.socket.id)
136
- headers['X-Socket-ID'] = this.socket.id;
137
- const options = {
138
- method,
139
- headers,
140
- body: body ? JSON.stringify(body) : undefined
141
- };
142
- // Ensure endpoint starts with /
143
- const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
144
- // Ensure baseUrl doesn't end with /
145
- const normalizedBaseUrl = this.baseUrl.endsWith('/') ? this.baseUrl.slice(0, -1) : this.baseUrl;
146
- const url = `${normalizedBaseUrl}${normalizedEndpoint}`;
147
- console.log(`Making ${method} request to: ${url}`);
148
- const response = await fetch(url, options);
149
- if (!response.ok) {
150
- if (response.status === 401 || response.status === 403) {
151
- this.authToken = null;
80
+ else if (!newRealTimeSync) {
81
+ console.log("Deactivating realtime updates");
82
+ if (this.socket.connected) {
83
+ console.log("Disconnecting socket");
84
+ this.socket.disconnect();
152
85
  }
153
- throw new Error(`API error: ${response.status} ${response.statusText}`);
154
86
  }
155
- return await response.json();
156
87
  }
157
88
  // User Administration Endpoints
158
89
  async login(credentials) {
@@ -163,7 +94,7 @@ export default class dbPouchClient {
163
94
  if (this.realTimeSync) {
164
95
  this.initWebSocket();
165
96
  }
166
- return { token: response.token, isAdmin: response.isAdmin };
97
+ return { token: response.token, isAdmin: response.isAdmin, userName: response.userName };
167
98
  }
168
99
  return null;
169
100
  }
@@ -210,7 +141,7 @@ export default class dbPouchClient {
210
141
  }
211
142
  // Data Type Endpoints
212
143
  async createType(type) {
213
- return await this.request('/types/write', 'POST', type);
144
+ return await this.request('/types/write', 'PATCH', type);
214
145
  }
215
146
  async removeType(typeID) {
216
147
  return await this.request(`/types/remove/${typeID}`, 'DELETE');
@@ -218,6 +149,9 @@ export default class dbPouchClient {
218
149
  async getTypes() {
219
150
  return await this.request('/types/list', 'GET');
220
151
  }
152
+ async updateType(updatedType) {
153
+ await this.request(`/types/write`, 'PATCH', updatedType);
154
+ }
221
155
  setToken(token) {
222
156
  console.log("Setting token to:", token ? "***token***" : "null");
223
157
  const tokenChanged = this.authToken !== token;
@@ -264,4 +198,111 @@ export default class dbPouchClient {
264
198
  this.socket.connect();
265
199
  }
266
200
  }
201
+ /**
202
+ * Sets up permanent socket listeners for the client.
203
+ *
204
+ * @private
205
+ */
206
+ setupPermanentSocketListeners() {
207
+ // These are permanent listeners that won't be removed
208
+ this.socket.on('connect_error', (error) => {
209
+ console.error('Socket connection error:', error.message);
210
+ this.connectionInProgress = false;
211
+ });
212
+ this.socket.on('connect', () => {
213
+ console.log('Socket connected successfully with ID:', this.socket.id);
214
+ this.connectionInProgress = false;
215
+ });
216
+ this.socket.on('disconnect', (reason) => {
217
+ console.log('Socket disconnected. Reason:', reason);
218
+ this.connectionInProgress = false;
219
+ });
220
+ this.socket.on('error', (error) => {
221
+ console.error('Socket error:', error);
222
+ this.connectionInProgress = false;
223
+ });
224
+ }
225
+ /**
226
+ * Initializes the WebSocket connection with the server.
227
+ *
228
+ * @private
229
+ */
230
+ initWebSocket() {
231
+ console.log("initWebSocket called. Auth token present:", !!this.authToken, "Connection in progress:", this.connectionInProgress, "Socket connected:", this.socket.connected);
232
+ if (!this.authToken) {
233
+ console.log("Skipping WebSocket initialization: No auth token");
234
+ return;
235
+ }
236
+ if (this.connectionInProgress) {
237
+ console.log("Connection already in progress, skipping initialization");
238
+ return;
239
+ }
240
+ if (this.socket.connected) {
241
+ console.log("Socket already connected with ID:", this.socket.id);
242
+ return;
243
+ }
244
+ this.connectionInProgress = true;
245
+ try {
246
+ console.log("Setting up WebSocket connection with token");
247
+ // Update the auth token
248
+ this.socket.auth = { token: this.authToken };
249
+ // Remove any dynamic event listeners that might have been added
250
+ this.socket.offAny();
251
+ // Set up event handler for application events
252
+ this.socket.onAny((event, data) => {
253
+ if (event === "heartbeatPing") {
254
+ console.log("Ping event received:", data);
255
+ this.socket.emit("heartbeatPong", Date.now());
256
+ }
257
+ else if (this.callbackFunction) {
258
+ this.callbackFunction(event, data);
259
+ }
260
+ });
261
+ // Connect to the server
262
+ console.log("Connecting socket with auth token");
263
+ this.socket.connect();
264
+ // Add a timeout to detect if connection is taking too long
265
+ setTimeout(() => {
266
+ if (this.connectionInProgress) {
267
+ console.warn("Socket connection attempt timed out after 5 seconds");
268
+ this.connectionInProgress = false;
269
+ // If we're still not connected after the timeout, try again with polling
270
+ if (!this.socket.connected) {
271
+ console.log("Retrying connection with polling transport");
272
+ this.socket.io.opts.transports = ['polling', 'websocket'];
273
+ this.socket.connect();
274
+ }
275
+ }
276
+ }, 5000);
277
+ }
278
+ catch (error) {
279
+ console.error('Error in initWebSocket:', error);
280
+ this.connectionInProgress = false;
281
+ }
282
+ }
283
+ async request(endpoint, method, body, requiresAuth = true) {
284
+ const headers = {
285
+ 'Content-Type': 'application/json',
286
+ };
287
+ if (requiresAuth && this.authToken)
288
+ headers['Authorization'] = `Bearer ${this.authToken}`;
289
+ if (this.socket.id)
290
+ headers['X-Socket-ID'] = this.socket.id;
291
+ const options = {
292
+ method,
293
+ headers,
294
+ body: body ? JSON.stringify(body) : undefined
295
+ };
296
+ const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
297
+ const normalizedBaseUrl = this.baseUrl.endsWith('/') ? this.baseUrl.slice(0, -1) : this.baseUrl;
298
+ const url = `${normalizedBaseUrl}${normalizedEndpoint}`;
299
+ const response = await fetch(url, options);
300
+ if (!response.ok) {
301
+ if (response.status === 401 || response.status === 403) {
302
+ this.authToken = null;
303
+ }
304
+ throw new Error(`API error: ${response.status} ${response.statusText}`);
305
+ }
306
+ return await response.json();
307
+ }
267
308
  }
package/dist/types.d.ts CHANGED
@@ -14,6 +14,7 @@ export interface I_UserCreation {
14
14
  isAdmin: boolean;
15
15
  }
16
16
  export interface I_UserUpdate {
17
+ _id: string;
17
18
  name?: string;
18
19
  password?: string;
19
20
  email?: string;
@@ -31,6 +32,7 @@ export interface I_UserDisplay {
31
32
  export interface I_LoginResponse {
32
33
  token: string;
33
34
  isAdmin: boolean;
35
+ userName: string;
34
36
  }
35
37
  export interface I_DocumentEntry extends I_DocumentCreationOwned {
36
38
  _id: string;
@@ -41,6 +43,8 @@ export interface I_DocumentCreation {
41
43
  type: number;
42
44
  subType: number;
43
45
  content: any;
46
+ shareWithGroup: boolean;
47
+ shareWithDepartment: boolean;
44
48
  }
45
49
  export interface I_DocumentCreationOwned extends I_DocumentCreation {
46
50
  owner: string;
@@ -56,33 +60,36 @@ export interface I_DocumentQuery {
56
60
  title?: string;
57
61
  type?: number;
58
62
  subType?: number;
63
+ shareWithGroup?: boolean;
64
+ shareWithDepartment?: boolean;
59
65
  }
60
66
  export interface I_DataStructure {
61
67
  _id?: string | undefined;
62
68
  name: string;
63
69
  description: string;
64
- reference?: any;
65
- fields: any[];
70
+ fields: I_StructureField[];
71
+ }
72
+ export interface I_StructureField {
73
+ name: string;
74
+ type: string;
75
+ items?: string;
66
76
  }
67
77
  export interface I_StructureEntry {
68
78
  _id?: string;
69
79
  name: string;
70
80
  description: string;
71
- reference?: any;
72
- fields: any[];
81
+ fields: I_StructureField[];
73
82
  }
74
83
  export interface I_StructureCreation {
75
84
  name: string;
76
85
  description?: string;
77
- reference?: any;
78
- fields: any[];
86
+ fields: I_StructureField[];
79
87
  }
80
88
  export interface I_StructureUpdate {
81
89
  _id: string;
82
90
  name?: string;
83
91
  description?: string;
84
- reference?: any;
85
- fields?: any[];
92
+ fields?: I_StructureField[];
86
93
  }
87
94
  export interface I_DocumentType {
88
95
  _id?: string;
@@ -92,7 +99,7 @@ export interface I_DocumentType {
92
99
  description?: string;
93
100
  defaultStructureID?: string;
94
101
  }
95
- export type I_EventString = 'subscribe' | 'unsubscribe' | 'heartbeatPong' | "heartbeatPing" | "newDocument" | "newStructure" | "newUser" | "removedID" | "changedDocument" | "changedStructure" | "changedUser" | "confirmSubscription" | "confirmUnsubscription" | "removedUser" | "removedStructure" | "removedDocument";
102
+ export type I_EventString = 'heartbeatPong' | "heartbeatPing" | "newDocument" | "newStructure" | "newUser" | "newType" | "removedID" | "changedDocument" | "changedStructure" | "changedUser" | "changedType" | "removedUser" | "removedStructure" | "removedDocument" | "removedType";
96
103
  export interface I_WsMessage {
97
104
  newDocument?: I_DocumentEntry;
98
105
  newStructure?: I_StructureEntry;
@@ -105,4 +112,5 @@ export interface I_WsMessage {
105
112
  confirmUnsubscription?: boolean;
106
113
  heartbeatPing?: number;
107
114
  heartbeatPong?: number;
115
+ newType?: I_DocumentType;
108
116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docpouch-client",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "exports": {
@@ -22,9 +22,9 @@
22
22
  "license": "MIT",
23
23
  "description": "A small class to more easily access the docPouch API",
24
24
  "devDependencies": {
25
- "@types/jest": "^29.5.14",
26
- "@types/node": "^24.0.1",
27
- "jest": "^30.0.0",
25
+ "@types/jest": "^30.0.0",
26
+ "@types/node": "^24.0.7",
27
+ "jest": "^30.0.3",
28
28
  "ts-jest": "^29.4.0"
29
29
  },
30
30
  "dependencies": {
@@ -1,4 +1,4 @@
1
- import dbPouchClient from '../index.js';
1
+ import docPouchClient from '../index.js';
2
2
  import packetJson from '../../package.json';
3
3
  import {
4
4
  I_UserLogin,
@@ -28,7 +28,7 @@ global.fetch = jest.fn();
28
28
  const mockFetch = global.fetch as jest.Mock;
29
29
 
30
30
  describe('Index Class', () => {
31
- let index: dbPouchClient;
31
+ let index: docPouchClient;
32
32
  const baseUrl = 'http://example.com';
33
33
 
34
34
  beforeEach(() => {
@@ -37,7 +37,7 @@ describe('Index Class', () => {
37
37
  mockFetch.mockClear();
38
38
 
39
39
  // Create a new instance of Index for each test
40
- index = new dbPouchClient(baseUrl);
40
+ index = new docPouchClient(baseUrl);
41
41
 
42
42
  // Mock successful fetch response
43
43
  mockFetch.mockImplementation(() =>
@@ -62,7 +62,7 @@ describe('Index Class', () => {
62
62
 
63
63
  it('should connect socket and set up event handler when callback is provided', () => {
64
64
  const mockCallback = jest.fn();
65
- const indexWithCallback = new dbPouchClient(baseUrl, mockCallback);
65
+ const indexWithCallback = new docPouchClient(baseUrl, mockCallback);
66
66
 
67
67
  expect(indexWithCallback.socket.connect).toHaveBeenCalled();
68
68
  expect(indexWithCallback.socket.onAny).toHaveBeenCalled();
@@ -86,7 +86,7 @@ describe('Index Class', () => {
86
86
  jest.mock('../../package.json', () => ({ version: packetJson.version }), { virtual: true });
87
87
 
88
88
  // We need to re-create the index instance to pick up the mocked package.json
89
- const newIndex = new dbPouchClient(baseUrl);
89
+ const newIndex = new docPouchClient(baseUrl);
90
90
  expect(newIndex.getVersion()).toBe(packetJson.version);
91
91
  });
92
92
  });
@@ -472,7 +472,7 @@ describe('Index Class', () => {
472
472
  describe('WebSocket functionality', () => {
473
473
  it('should connect socket when callback is provided', () => {
474
474
  const mockCallback = jest.fn();
475
- const indexWithCallback = new dbPouchClient(baseUrl, mockCallback);
475
+ const indexWithCallback = new docPouchClient(baseUrl, mockCallback);
476
476
 
477
477
  // Verify that connect was called
478
478
  expect(indexWithCallback.socket.connect).toHaveBeenCalled();
@@ -480,7 +480,7 @@ describe('Index Class', () => {
480
480
 
481
481
  it('should set up event handler when callback is provided', () => {
482
482
  const mockCallback = jest.fn();
483
- const indexWithCallback = new dbPouchClient(baseUrl, mockCallback);
483
+ const indexWithCallback = new docPouchClient(baseUrl, mockCallback);
484
484
 
485
485
  // Verify that onAny was called
486
486
  expect(indexWithCallback.socket.onAny).toHaveBeenCalled();