@syncvault/sdk 1.2.0 → 1.3.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncvault/sdk",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "SyncVault SDK - Zero-knowledge encrypted sync for Node.js and browsers",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.d.ts CHANGED
@@ -35,6 +35,20 @@ export interface QuotaInfo {
35
35
  unlimited: boolean;
36
36
  }
37
37
 
38
+ export interface SharedVault {
39
+ id: string;
40
+ name: string;
41
+ ownerId: string;
42
+ ownerUsername: string;
43
+ memberCount: number;
44
+ isOwner: boolean;
45
+ createdAt: string;
46
+ }
47
+
48
+ export interface PutOptions {
49
+ updatedAt?: number | Date;
50
+ }
51
+
38
52
  export declare class SyncVault {
39
53
  constructor(options: SyncVaultOptions);
40
54
 
@@ -48,7 +62,7 @@ export declare class SyncVault {
48
62
  register(username: string, password: string): Promise<User>;
49
63
 
50
64
  // Data operations
51
- put<T = unknown>(path: string, data: T): Promise<PutResponse>;
65
+ put<T = unknown>(path: string, data: T, options?: PutOptions): Promise<PutResponse>;
52
66
  get<T = unknown>(path: string): Promise<T>;
53
67
  list(): Promise<FileInfo[]>;
54
68
  delete(path: string): Promise<DeleteResponse>;
@@ -64,6 +78,13 @@ export declare class SyncVault {
64
78
  // Quota info
65
79
  getQuota(): Promise<QuotaInfo>;
66
80
 
81
+ // Shared vaults
82
+ getSharedVaults(): Promise<SharedVault[]>;
83
+ listShared(vaultId: string): Promise<FileInfo[]>;
84
+ putShared<T = unknown>(vaultId: string, path: string, data: T, sharedPassword?: string): Promise<PutResponse>;
85
+ getShared<T = unknown>(vaultId: string, path: string, sharedPassword?: string): Promise<T>;
86
+ deleteShared(vaultId: string, path: string): Promise<DeleteResponse>;
87
+
67
88
  // State
68
89
  isAuthenticated(): boolean;
69
90
  logout(): void;
@@ -79,6 +100,7 @@ export interface PendingOperation {
79
100
  type: 'put' | 'delete';
80
101
  path: string;
81
102
  data?: string;
103
+ updatedAt?: number;
82
104
  createdAt: number;
83
105
  retries: number;
84
106
  }
package/src/index.js CHANGED
@@ -107,15 +107,24 @@ export class SyncVault {
107
107
 
108
108
  /**
109
109
  * Store encrypted data
110
+ * @param {string} path - File path
111
+ * @param {any} data - Data to encrypt and store
112
+ * @param {Object} options - Optional settings
113
+ * @param {number} options.updatedAt - Timestamp for LWW conflict resolution
110
114
  */
111
- async put(path, data) {
115
+ async put(path, data, options = {}) {
112
116
  this._checkAuth();
113
117
 
114
118
  const encrypted = await encrypt(data, this.password);
119
+ const body = { path, data: encrypted };
120
+
121
+ if (options.updatedAt) {
122
+ body.updatedAt = new Date(options.updatedAt).toISOString();
123
+ }
115
124
 
116
125
  const response = await this._request('/api/sync/put', {
117
126
  method: 'POST',
118
- body: JSON.stringify({ path, data: encrypted })
127
+ body: JSON.stringify(body)
119
128
  });
120
129
 
121
130
  return response;
@@ -215,6 +224,63 @@ export class SyncVault {
215
224
  return this._request('/api/sync/quota');
216
225
  }
217
226
 
227
+ // --- Shared Vaults ---
228
+
229
+ /**
230
+ * Get all shared vaults the user has access to in this app
231
+ */
232
+ async getSharedVaults() {
233
+ this._checkAuth();
234
+
235
+ return this._request('/api/sync/shared/vaults');
236
+ }
237
+
238
+ /**
239
+ * List files in a shared vault
240
+ */
241
+ async listShared(vaultId) {
242
+ this._checkAuth();
243
+
244
+ const response = await this._request(`/api/sync/shared/${vaultId}/list`);
245
+ return response.files;
246
+ }
247
+
248
+ /**
249
+ * Store encrypted data in a shared vault
250
+ */
251
+ async putShared(vaultId, path, data, sharedPassword) {
252
+ this._checkAuth();
253
+
254
+ const encrypted = await encrypt(data, sharedPassword || this.password);
255
+
256
+ return this._request(`/api/sync/shared/${vaultId}/put`, {
257
+ method: 'POST',
258
+ body: JSON.stringify({ path, data: encrypted })
259
+ });
260
+ }
261
+
262
+ /**
263
+ * Retrieve and decrypt data from a shared vault
264
+ */
265
+ async getShared(vaultId, path, sharedPassword) {
266
+ this._checkAuth();
267
+
268
+ const response = await this._request(`/api/sync/shared/${vaultId}/get?path=${encodeURIComponent(path)}`);
269
+ return decrypt(response.data, sharedPassword || this.password);
270
+ }
271
+
272
+ /**
273
+ * Delete a file from a shared vault
274
+ */
275
+ async deleteShared(vaultId, path) {
276
+ this._checkAuth();
277
+
278
+ return this._request(`/api/sync/shared/${vaultId}/delete`, {
279
+ method: 'POST',
280
+ body: JSON.stringify({ path })
281
+ });
282
+ }
283
+
218
284
  /**
219
285
  * Check if user is authenticated
220
286
  */
package/src/offline.js CHANGED
@@ -219,15 +219,16 @@ export class OfflineSyncVault {
219
219
  }
220
220
 
221
221
  /**
222
- * Put with offline support
222
+ * Put with offline support (LWW enabled)
223
223
  */
224
224
  async put(path, data) {
225
225
  await this.init();
226
226
 
227
+ const timestamp = Date.now();
227
228
  const encrypted = await encrypt(data, this.client.password);
228
229
 
229
230
  try {
230
- const result = await this.client.put(path, data);
231
+ const result = await this.client.put(path, data, { updatedAt: timestamp });
231
232
  await this.store.setCache(path, encrypted);
232
233
  return result;
233
234
  } catch (error) {
@@ -236,7 +237,8 @@ export class OfflineSyncVault {
236
237
  await this.store.queueOperation({
237
238
  type: 'put',
238
239
  path,
239
- data: encrypted
240
+ data: encrypted,
241
+ updatedAt: timestamp
240
242
  });
241
243
  return { queued: true, path };
242
244
  }
@@ -391,10 +393,31 @@ export class OfflineSyncVault {
391
393
  }
392
394
 
393
395
  async _syncPut(op) {
394
- await this.client._request('/api/sync/put', {
395
- method: 'POST',
396
- body: JSON.stringify({ path: op.path, data: op.data })
397
- });
396
+ const body = { path: op.path, data: op.data };
397
+ if (op.updatedAt) {
398
+ body.updatedAt = new Date(op.updatedAt).toISOString();
399
+ }
400
+
401
+ try {
402
+ await this.client._request('/api/sync/put', {
403
+ method: 'POST',
404
+ body: JSON.stringify(body)
405
+ });
406
+ } catch (error) {
407
+ // LWW conflict - server has newer data, discard local change
408
+ if (error.statusCode === 409 && error.data?.code === 'CONFLICT_STALE') {
409
+ // Fetch fresh data from server to update cache
410
+ try {
411
+ const result = await this.client.get(op.path);
412
+ const encrypted = await encrypt(result, this.client.password);
413
+ await this.store.setCache(op.path, encrypted);
414
+ } catch (cacheErr) {
415
+ console.warn('Failed to update cache after LWW conflict:', cacheErr.message);
416
+ }
417
+ return; // Consider this "synced" - we accepted server's version
418
+ }
419
+ throw error;
420
+ }
398
421
  }
399
422
 
400
423
  async _syncDelete(op) {