@vrtmrz/livesync-commonlib 0.1.42 → 0.1.44

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.
@@ -0,0 +1,13 @@
1
+ class DiagnosticReadError extends Error {
2
+ constructor(reason, missing = false) {
3
+ super(reason);
4
+ this.reason = reason;
5
+ this.missing = missing;
6
+ this.name = "DiagnosticReadError";
7
+ }
8
+ reason;
9
+ missing;
10
+ }
11
+ export {
12
+ DiagnosticReadError
13
+ };
@@ -0,0 +1,55 @@
1
+ export interface DatabaseUsageGetOptions {
2
+ revs?: boolean;
3
+ revs_info?: boolean;
4
+ conflicts?: boolean;
5
+ rev?: string;
6
+ }
7
+ export interface DatabaseUsageSource {
8
+ info(): Promise<{
9
+ update_seq?: string | number;
10
+ }>;
11
+ enumerateIds(): AsyncIterable<string>;
12
+ get(id: string, options?: DatabaseUsageGetOptions): Promise<object | null | undefined>;
13
+ }
14
+ export interface DatabaseUsageProgress {
15
+ phase: "reading" | "accounting";
16
+ idsProcessed: number;
17
+ revisionsProcessed: number;
18
+ processed: number;
19
+ total: number;
20
+ totalIsFinal: boolean;
21
+ }
22
+ export type DatabaseUsageStatus = "complete" | "partial" | "failed" | "cancelled";
23
+ export type DatabaseUsageErrorCode = "start_info_read_failed" | "start_update_seq_unavailable" | "end_info_read_failed" | "end_update_seq_unavailable" | "configuration_changed" | "id_enumeration_failed" | "revision_list_read_failed" | "revision_not_found" | "revision_read_failed" | "revision_document_invalid";
24
+ export interface DatabaseUsageError {
25
+ code: DatabaseUsageErrorCode;
26
+ id?: string;
27
+ revision?: string;
28
+ }
29
+ export interface DatabaseUsageRow {
30
+ title: string;
31
+ id: string;
32
+ path: string;
33
+ revision: string;
34
+ rev: string;
35
+ revHash: string;
36
+ uniqueChunkCount: number;
37
+ sharedChunkCount: number;
38
+ uniqueChunkSize: number;
39
+ sharedChunkSize: number;
40
+ }
41
+ export interface DatabaseUsageResult {
42
+ version: 1;
43
+ kind: "usage";
44
+ status: DatabaseUsageStatus;
45
+ startUpdateSeq?: string | number;
46
+ endUpdateSeq?: string | number;
47
+ changedDuringRun?: boolean;
48
+ rows: DatabaseUsageRow[];
49
+ errors: DatabaseUsageError[];
50
+ }
51
+ export declare function analyseDatabaseUsage(options: {
52
+ source: DatabaseUsageSource;
53
+ signal?: AbortSignal;
54
+ onProgress?: (progress: DatabaseUsageProgress) => void;
55
+ }): Promise<DatabaseUsageResult>;
@@ -0,0 +1,345 @@
1
+ const USAGE_ACCOUNTING_YIELD_EVERY = 64;
2
+ function asRecord(value) {
3
+ return value;
4
+ }
5
+ function getRevisionNumber(revision) {
6
+ const generation = revision.split("-")[0];
7
+ return `${generation ? Number.parseInt(generation) : 0}`;
8
+ }
9
+ function getRevisionHash(revision) {
10
+ const hash = revision.split("-")[1];
11
+ if (hash === void 0) throw new Error("invalid revision");
12
+ return hash.substring(0, 6);
13
+ }
14
+ function getRevisionInfos(value) {
15
+ if (!Array.isArray(value)) return [];
16
+ return value;
17
+ }
18
+ function getChildren(value) {
19
+ const children = value ?? [];
20
+ if (!Array.isArray(children) || !children.every((child) => typeof child === "string")) {
21
+ throw new Error("invalid chunk references");
22
+ }
23
+ return new Set(children);
24
+ }
25
+ function getStringField(value) {
26
+ if (typeof value !== "string") throw new Error("invalid document field");
27
+ return value;
28
+ }
29
+ async function analyseDatabaseUsage(options) {
30
+ const { source, signal, onProgress } = options;
31
+ const errors = [];
32
+ const documentMap = /* @__PURE__ */ new Map();
33
+ const chunkMap = /* @__PURE__ */ new Map();
34
+ const progress = {
35
+ phase: "reading",
36
+ idsProcessed: 0,
37
+ revisionsProcessed: 0,
38
+ processed: 0,
39
+ total: 0,
40
+ totalIsFinal: false
41
+ };
42
+ let startUpdateSeq;
43
+ let endUpdateSeq;
44
+ let enumerationFailed = false;
45
+ const reportProgress = () => {
46
+ try {
47
+ onProgress?.({ ...progress });
48
+ } catch {
49
+ }
50
+ };
51
+ const isCancelled = () => signal?.aborted === true;
52
+ let cancelled = isCancelled();
53
+ let documentRevisionCount = 0;
54
+ let referenceCount = 0;
55
+ reportProgress();
56
+ if (isCancelled()) cancelled = true;
57
+ if (!cancelled) {
58
+ try {
59
+ const info = await source.info();
60
+ startUpdateSeq = info.update_seq;
61
+ if (startUpdateSeq === void 0) errors.push({ code: "start_update_seq_unavailable" });
62
+ } catch {
63
+ errors.push({ code: "start_info_read_failed" });
64
+ }
65
+ if (isCancelled()) cancelled = true;
66
+ }
67
+ let enumerationFinished = false;
68
+ try {
69
+ if (!cancelled && !isCancelled()) {
70
+ for await (const id of source.enumerateIds()) {
71
+ if (isCancelled()) {
72
+ cancelled = true;
73
+ break;
74
+ }
75
+ let revisionInfos = [];
76
+ let revisionList;
77
+ let revisionListReadFailed = false;
78
+ try {
79
+ revisionList = await source.get(id, { revs: true, revs_info: true, conflicts: true });
80
+ } catch {
81
+ if (!isCancelled()) {
82
+ errors.push({ code: "revision_list_read_failed", id });
83
+ revisionListReadFailed = true;
84
+ }
85
+ }
86
+ if (isCancelled()) {
87
+ cancelled = true;
88
+ break;
89
+ }
90
+ if (revisionList) revisionInfos = getRevisionInfos(asRecord(revisionList)._revs_info);
91
+ else if (!revisionListReadFailed) {
92
+ errors.push({ code: "revision_list_read_failed", id });
93
+ }
94
+ progress.idsProcessed++;
95
+ for (const revisionInfo of revisionInfos) {
96
+ if (revisionInfo.status === "available" && typeof revisionInfo.rev === "string") {
97
+ progress.total++;
98
+ }
99
+ }
100
+ reportProgress();
101
+ if (isCancelled()) {
102
+ cancelled = true;
103
+ break;
104
+ }
105
+ for (const revisionInfo of revisionInfos) {
106
+ if (isCancelled()) {
107
+ cancelled = true;
108
+ break;
109
+ }
110
+ if (revisionInfo.status !== "available") continue;
111
+ const revision = typeof revisionInfo.rev === "string" ? revisionInfo.rev : void 0;
112
+ if (revision === void 0) {
113
+ errors.push({ code: "revision_document_invalid", id });
114
+ continue;
115
+ }
116
+ let rawDocument;
117
+ try {
118
+ rawDocument = await source.get(id, { rev: revision });
119
+ } catch {
120
+ if (isCancelled()) {
121
+ cancelled = true;
122
+ break;
123
+ }
124
+ errors.push({ code: "revision_read_failed", id, revision });
125
+ progress.revisionsProcessed++;
126
+ progress.processed++;
127
+ reportProgress();
128
+ continue;
129
+ }
130
+ if (isCancelled()) {
131
+ cancelled = true;
132
+ break;
133
+ }
134
+ if (!rawDocument) {
135
+ errors.push({ code: "revision_not_found", id, revision });
136
+ progress.revisionsProcessed++;
137
+ progress.processed++;
138
+ reportProgress();
139
+ continue;
140
+ }
141
+ try {
142
+ const document = asRecord(rawDocument);
143
+ if ("children" in document) {
144
+ const documentId = getStringField(document._id);
145
+ const documentRevision = getStringField(document._rev);
146
+ const path = getStringField(document.path);
147
+ const chunks = getChildren(document.children);
148
+ const revisions = documentMap.get(documentId) ?? [];
149
+ revisions.push({
150
+ id: documentId,
151
+ revision: documentRevision,
152
+ path,
153
+ chunks,
154
+ uniqueChunks: /* @__PURE__ */ new Set(),
155
+ sharedChunks: /* @__PURE__ */ new Set(),
156
+ uniqueChunkSize: 0,
157
+ sharedChunkSize: 0
158
+ });
159
+ documentMap.set(documentId, revisions);
160
+ documentRevisionCount++;
161
+ referenceCount += chunks.size;
162
+ } else if (document.type === "leaf") {
163
+ const chunkId = getStringField(document._id);
164
+ if (!chunkMap.has(chunkId) && !document._deleted) {
165
+ if (typeof document.data !== "string") throw new Error("invalid chunk data");
166
+ chunkMap.set(chunkId, { length: document.data.length, refCount: 0 });
167
+ }
168
+ }
169
+ } catch {
170
+ errors.push({ code: "revision_document_invalid", id, revision });
171
+ }
172
+ progress.revisionsProcessed++;
173
+ progress.processed++;
174
+ reportProgress();
175
+ }
176
+ if (isCancelled()) cancelled = true;
177
+ if (cancelled) break;
178
+ }
179
+ if (!cancelled && !isCancelled()) enumerationFinished = true;
180
+ } else if (isCancelled()) {
181
+ cancelled = true;
182
+ }
183
+ } catch {
184
+ if (isCancelled()) cancelled = true;
185
+ else {
186
+ enumerationFailed = true;
187
+ errors.push({ code: "id_enumeration_failed" });
188
+ }
189
+ }
190
+ if (isCancelled()) cancelled = true;
191
+ if (enumerationFinished) {
192
+ progress.totalIsFinal = true;
193
+ reportProgress();
194
+ if (isCancelled()) cancelled = true;
195
+ }
196
+ const readingWasCancelled = cancelled;
197
+ progress.phase = "accounting";
198
+ progress.processed = 0;
199
+ progress.total = referenceCount + documentRevisionCount + chunkMap.size;
200
+ progress.totalIsFinal = true;
201
+ reportProgress();
202
+ let accountingCancelled = !readingWasCancelled && isCancelled();
203
+ const rows = [];
204
+ const accountingErrors = [];
205
+ const recordAccountingWork = () => {
206
+ progress.processed++;
207
+ return progress.processed % USAGE_ACCOUNTING_YIELD_EVERY === 0;
208
+ };
209
+ const yieldAfterAccountingBatch = async () => {
210
+ reportProgress();
211
+ await new Promise((resolve) => setTimeout(resolve, 0));
212
+ return readingWasCancelled || !isCancelled();
213
+ };
214
+ if (!readingWasCancelled && isCancelled()) accountingCancelled = true;
215
+ if (!accountingCancelled) {
216
+ for (const revisions of documentMap.values()) {
217
+ for (const document of revisions) {
218
+ for (const chunkId of document.chunks) {
219
+ const chunk = chunkMap.get(chunkId);
220
+ if (chunk) {
221
+ if (chunk.refCount === 0) {
222
+ document.uniqueChunks.add(chunkId);
223
+ document.uniqueChunkSize += chunk.length;
224
+ } else {
225
+ document.sharedChunks.add(chunkId);
226
+ document.sharedChunkSize += chunk.length;
227
+ }
228
+ chunk.refCount++;
229
+ }
230
+ if (recordAccountingWork() && !await yieldAfterAccountingBatch()) {
231
+ accountingCancelled = true;
232
+ break;
233
+ }
234
+ }
235
+ if (accountingCancelled) break;
236
+ }
237
+ if (accountingCancelled) break;
238
+ }
239
+ }
240
+ if (!accountingCancelled) {
241
+ for (const revisions of documentMap.values()) {
242
+ for (const document of revisions) {
243
+ const revisionNumber = getRevisionNumber(document.revision);
244
+ let revisionHash;
245
+ try {
246
+ revisionHash = getRevisionHash(document.revision);
247
+ } catch {
248
+ accountingErrors.push({
249
+ code: "revision_document_invalid",
250
+ id: document.id,
251
+ revision: document.revision
252
+ });
253
+ if (recordAccountingWork() && !await yieldAfterAccountingBatch()) {
254
+ accountingCancelled = true;
255
+ break;
256
+ }
257
+ continue;
258
+ }
259
+ rows.push({
260
+ title: `${document.path} (${document.revision})`,
261
+ id: document.id,
262
+ path: document.path,
263
+ revision: document.revision,
264
+ rev: revisionNumber,
265
+ revHash: revisionHash,
266
+ uniqueChunkCount: document.uniqueChunks.size,
267
+ sharedChunkCount: document.sharedChunks.size,
268
+ uniqueChunkSize: document.uniqueChunkSize,
269
+ sharedChunkSize: document.sharedChunkSize
270
+ });
271
+ if (recordAccountingWork() && !await yieldAfterAccountingBatch()) {
272
+ accountingCancelled = true;
273
+ break;
274
+ }
275
+ }
276
+ if (accountingCancelled) break;
277
+ }
278
+ }
279
+ let orphanChunkCount = 0;
280
+ let orphanChunkSize = 0;
281
+ if (!accountingCancelled) {
282
+ for (const chunk of chunkMap.values()) {
283
+ if (chunk.refCount === 0) {
284
+ orphanChunkCount++;
285
+ orphanChunkSize += chunk.length;
286
+ }
287
+ if (recordAccountingWork() && !await yieldAfterAccountingBatch()) {
288
+ accountingCancelled = true;
289
+ break;
290
+ }
291
+ }
292
+ }
293
+ if (accountingCancelled) {
294
+ cancelled = true;
295
+ rows.length = 0;
296
+ } else {
297
+ errors.push(...accountingErrors);
298
+ rows.push({
299
+ title: "__orphan",
300
+ id: "__orphan",
301
+ path: "__orphan",
302
+ revision: "1-xxxxx",
303
+ rev: "1",
304
+ revHash: "xxxxx",
305
+ uniqueChunkCount: orphanChunkCount,
306
+ sharedChunkCount: 0,
307
+ uniqueChunkSize: orphanChunkSize,
308
+ sharedChunkSize: 0
309
+ });
310
+ }
311
+ if (!cancelled && !isCancelled()) {
312
+ try {
313
+ const info = await source.info();
314
+ endUpdateSeq = info.update_seq;
315
+ if (endUpdateSeq === void 0) errors.push({ code: "end_update_seq_unavailable" });
316
+ } catch {
317
+ errors.push({ code: "end_info_read_failed" });
318
+ }
319
+ }
320
+ if (isCancelled()) cancelled = true;
321
+ reportProgress();
322
+ if (isCancelled()) cancelled = true;
323
+ const changedDuringRun = startUpdateSeq === void 0 || endUpdateSeq === void 0 ? void 0 : startUpdateSeq !== endUpdateSeq;
324
+ const snapshotUncertain = startUpdateSeq === void 0 || endUpdateSeq === void 0;
325
+ const hasDocumentRows = rows.length > 1;
326
+ let status;
327
+ if (cancelled) status = "cancelled";
328
+ else if (changedDuringRun === true || snapshotUncertain) status = "partial";
329
+ else if (enumerationFailed && !hasDocumentRows) status = "failed";
330
+ else if (errors.length > 0) status = "partial";
331
+ else status = "complete";
332
+ return {
333
+ version: 1,
334
+ kind: "usage",
335
+ status,
336
+ ...startUpdateSeq === void 0 ? {} : { startUpdateSeq },
337
+ ...endUpdateSeq === void 0 ? {} : { endUpdateSeq },
338
+ ...changedDuringRun === void 0 ? {} : { changedDuringRun },
339
+ rows,
340
+ errors
341
+ };
342
+ }
343
+ export {
344
+ analyseDatabaseUsage
345
+ };
@@ -1,12 +1,14 @@
1
+ import { Logger } from "../common/logger.js";
1
2
  import { type EntryDoc, type AnyEntry, type EntryLeaf, type DocumentID, type E2EEAlgorithm } from "../common/types.js";
2
3
  import { encryptWorker, decryptWorker, encryptHKDFWorker, decryptHKDFWorker } from "#worker";
3
4
  export declare const encrypt: typeof encryptWorker;
4
5
  export declare const decrypt: typeof decryptWorker;
5
6
  export declare const encryptHKDF: typeof encryptHKDFWorker;
6
7
  export declare const decryptHKDF: typeof decryptHKDFWorker;
8
+ export declare function isEncryptedMeta<T extends AnyEntry>(doc: T): boolean;
7
9
  export declare let preprocessOutgoing: (doc: AnyEntry | EntryLeaf) => Promise<AnyEntry | EntryLeaf>;
8
10
  export declare let preprocessIncoming: (doc: EntryDoc) => Promise<EntryDoc>;
9
- export declare function getConfiguredFunctionsForEncryption(passphrase: string, useDynamicIterationCount: boolean, migrationDecrypt: boolean, getPBKDF2Salt: () => Promise<Uint8Array<ArrayBuffer>>, algorithm: E2EEAlgorithm, encryptInternalMetadata?: boolean): {
11
+ export declare function getConfiguredFunctionsForEncryption(passphrase: string, useDynamicIterationCount: boolean, migrationDecrypt: boolean, getPBKDF2Salt: () => Promise<Uint8Array<ArrayBuffer>>, algorithm: E2EEAlgorithm, encryptInternalMetadata?: boolean, log?: typeof Logger): {
10
12
  incoming: (doc: AnyEntry | EntryLeaf) => Promise<AnyEntry | EntryLeaf>;
11
13
  outgoing: (doc: EntryDoc) => Promise<AnyEntry | EntryLeaf>;
12
14
  };
@@ -32,20 +32,20 @@ function getEncryptionVersion(data) {
32
32
  }
33
33
  return EncryptionVersions.UNENCRYPTED;
34
34
  }
35
- async function tryDecryptV1AsFallback(encryptedData, passphrase, useDynamicIterationCount) {
35
+ async function tryDecryptV1AsFallback(encryptedData, passphrase, useDynamicIterationCount, log = Logger) {
36
36
  try {
37
37
  return await decrypt(encryptedData, passphrase, useDynamicIterationCount);
38
38
  } catch (ex) {
39
39
  try {
40
- Logger(
40
+ log(
41
41
  "Failed to decrypt with V1 method. Fallback to disable useDynamicIterationCount.",
42
42
  LOG_LEVEL_VERBOSE
43
43
  );
44
- Logger(ex, LOG_LEVEL_VERBOSE);
44
+ log(ex, LOG_LEVEL_VERBOSE);
45
45
  return await decrypt(encryptedData, passphrase, false);
46
46
  } catch (ex2) {
47
- Logger("Completely failed to decrypt with V1 method.", LOG_LEVEL_VERBOSE);
48
- Logger(ex2, LOG_LEVEL_VERBOSE);
47
+ log("Completely failed to decrypt with V1 method.", LOG_LEVEL_VERBOSE);
48
+ log(ex2, LOG_LEVEL_VERBOSE);
49
49
  return false;
50
50
  }
51
51
  }
@@ -153,7 +153,7 @@ async function incomingEncryptHKDF(doc, passphrase, useDynamicIterationCount, ge
153
153
  }
154
154
  return saveDoc;
155
155
  }
156
- async function outgoingDecryptHKDF(doc, migrationDecrypt, decrypted, passphrase, useDynamicIterationCount, getPBKDF2Salt) {
156
+ async function outgoingDecryptHKDF(doc, migrationDecrypt, decrypted, passphrase, useDynamicIterationCount, getPBKDF2Salt, log) {
157
157
  const loadDoc = {
158
158
  ...doc
159
159
  };
@@ -165,21 +165,26 @@ async function outgoingDecryptHKDF(doc, migrationDecrypt, decrypted, passphrase,
165
165
  loadDoc.data = await decryptHKDF(loadDoc.data, passphrase, pbkdf2salt);
166
166
  delete loadDoc.e_;
167
167
  } else if (encryptionVersion === EncryptionVersions.ENCRYPTED) {
168
- const decryptedData = await tryDecryptV1AsFallback(loadDoc.data, passphrase, useDynamicIterationCount);
168
+ const decryptedData = await tryDecryptV1AsFallback(
169
+ loadDoc.data,
170
+ passphrase,
171
+ useDynamicIterationCount,
172
+ log
173
+ );
169
174
  if (decryptedData === false) {
170
- Logger(MESSAGE_FALLBACK_DECRYPT_FAILED, LOG_LEVEL_NOTICE);
175
+ log(MESSAGE_FALLBACK_DECRYPT_FAILED, LOG_LEVEL_NOTICE);
171
176
  throw new Error(MESSAGE_FALLBACK_DECRYPT_FAILED);
172
177
  }
173
178
  loadDoc.data = decryptedData;
174
179
  delete loadDoc.e_;
175
180
  } else if (encryptionVersion === EncryptionVersions.UNENCRYPTED) {
176
181
  } else {
177
- Logger("Unknown encryption version. Cannot decrypt.", LOG_LEVEL_NOTICE);
182
+ log("Unknown encryption version. Cannot decrypt.", LOG_LEVEL_NOTICE);
178
183
  throw new Error("Unknown encryption version. Cannot decrypt.");
179
184
  }
180
185
  } catch (ex) {
181
- Logger(DECRYPTION_HKDF_FAILED, LOG_LEVEL_NOTICE);
182
- Logger(ex);
186
+ log(DECRYPTION_HKDF_FAILED, LOG_LEVEL_NOTICE);
187
+ log(ex);
183
188
  throw ex;
184
189
  }
185
190
  }
@@ -192,16 +197,16 @@ async function outgoingDecryptHKDF(doc, migrationDecrypt, decrypted, passphrase,
192
197
  loadDoc[key] = metadata[key];
193
198
  }
194
199
  } catch (ex) {
195
- Logger(`${DECRYPTION_HKDF_FAILED} on Path`, LOG_LEVEL_NOTICE);
196
- Logger(ex);
200
+ log(`${DECRYPTION_HKDF_FAILED} on Path`, LOG_LEVEL_NOTICE);
201
+ log(ex);
197
202
  throw ex;
198
203
  }
199
204
  } else {
200
205
  const path = getPath(loadDoc);
201
206
  if (isPathProbablyObfuscated(path)) {
202
- const decryptedPath = await tryDecryptV1AsFallback(path, passphrase, useDynamicIterationCount);
207
+ const decryptedPath = await tryDecryptV1AsFallback(path, passphrase, useDynamicIterationCount, log);
203
208
  if (decryptedPath === false) {
204
- Logger(`${MESSAGE_FALLBACK_DECRYPT_FAILED} on Path`, LOG_LEVEL_NOTICE);
209
+ log(`${MESSAGE_FALLBACK_DECRYPT_FAILED} on Path`, LOG_LEVEL_NOTICE);
205
210
  throw new Error(MESSAGE_FALLBACK_DECRYPT_FAILED);
206
211
  }
207
212
  loadDoc.path = decryptedPath;
@@ -215,7 +220,8 @@ async function outgoingDecryptHKDF(doc, migrationDecrypt, decrypted, passphrase,
215
220
  const decryptedEden = await tryDecryptV1AsFallback(
216
221
  loadDoc.eden[EDEN_ENCRYPTED_KEY].data,
217
222
  passphrase,
218
- useDynamicIterationCount
223
+ useDynamicIterationCount,
224
+ log
219
225
  );
220
226
  if (decryptedEden === false) throw new Error(MESSAGE_FALLBACK_DECRYPT_FAILED);
221
227
  readEden = {
@@ -224,8 +230,8 @@ async function outgoingDecryptHKDF(doc, migrationDecrypt, decrypted, passphrase,
224
230
  };
225
231
  edenDecrypted = true;
226
232
  } catch (ex) {
227
- Logger(`${DECRYPTION_FALLBACK_FAILED} on Eden`, LOG_LEVEL_NOTICE);
228
- Logger(ex);
233
+ log(`${DECRYPTION_FALLBACK_FAILED} on Eden`, LOG_LEVEL_NOTICE);
234
+ log(ex);
229
235
  throw ex;
230
236
  }
231
237
  }
@@ -243,8 +249,8 @@ async function outgoingDecryptHKDF(doc, migrationDecrypt, decrypted, passphrase,
243
249
  };
244
250
  edenDecrypted = true;
245
251
  } catch (ex) {
246
- Logger(`${DECRYPTION_HKDF_FAILED} on Eden`, LOG_LEVEL_NOTICE);
247
- Logger(ex);
252
+ log(`${DECRYPTION_HKDF_FAILED} on Eden`, LOG_LEVEL_NOTICE);
253
+ log(ex);
248
254
  throw ex;
249
255
  }
250
256
  }
@@ -296,7 +302,7 @@ async function incomingEncryptV1(doc, passphrase, useDynamicIterationCount) {
296
302
  }
297
303
  return saveDoc;
298
304
  }
299
- async function outgoingDecryptV1(doc, migrationDecrypt, decrypted, passphrase, useDynamicIterationCount) {
305
+ async function outgoingDecryptV1(doc, migrationDecrypt, decrypted, passphrase, useDynamicIterationCount, log) {
300
306
  const loadDoc = {
301
307
  ...doc
302
308
  };
@@ -361,15 +367,15 @@ async function outgoingDecryptV1(doc, migrationDecrypt, decrypted, passphrase, u
361
367
  if (migrationDecrypt && ex2 instanceof SyntaxError) {
362
368
  return loadDoc;
363
369
  }
364
- Logger("Decryption failed.", LOG_LEVEL_NOTICE);
365
- Logger(ex2, LOG_LEVEL_VERBOSE);
366
- Logger(`id:${loadDoc._id}-${loadDoc._rev?.substring(0, 10)}`, LOG_LEVEL_VERBOSE);
370
+ log("Decryption failed.", LOG_LEVEL_NOTICE);
371
+ log(ex2, LOG_LEVEL_VERBOSE);
372
+ log(`id:${loadDoc._id}-${loadDoc._rev?.substring(0, 10)}`, LOG_LEVEL_VERBOSE);
367
373
  throw ex2;
368
374
  }
369
375
  } else {
370
- Logger("Decryption failed.", LOG_LEVEL_NOTICE);
371
- Logger(ex, LOG_LEVEL_VERBOSE);
372
- Logger(`id:${loadDoc._id}-${loadDoc._rev?.substring(0, 10)}`, LOG_LEVEL_VERBOSE);
376
+ log("Decryption failed.", LOG_LEVEL_NOTICE);
377
+ log(ex, LOG_LEVEL_VERBOSE);
378
+ log(`id:${loadDoc._id}-${loadDoc._rev?.substring(0, 10)}`, LOG_LEVEL_VERBOSE);
373
379
  throw ex;
374
380
  }
375
381
  }
@@ -382,7 +388,7 @@ let preprocessOutgoing = async (doc) => {
382
388
  let preprocessIncoming = async (doc) => {
383
389
  return await Promise.resolve(doc);
384
390
  };
385
- function getConfiguredFunctionsForEncryption(passphrase, useDynamicIterationCount, migrationDecrypt, getPBKDF2Salt, algorithm, encryptInternalMetadata = false) {
391
+ function getConfiguredFunctionsForEncryption(passphrase, useDynamicIterationCount, migrationDecrypt, getPBKDF2Salt, algorithm, encryptInternalMetadata = false, log = Logger) {
386
392
  const decryptedCache = /* @__PURE__ */ new Map();
387
393
  const incoming = (doc) => algorithm === E2EEAlgorithms.V2 ? incomingEncryptHKDF(doc, passphrase, useDynamicIterationCount, getPBKDF2Salt, encryptInternalMetadata) : incomingEncryptV1(doc, passphrase, useDynamicIterationCount);
388
394
  const outgoing = (doc) => algorithm !== E2EEAlgorithms.ForceV1 ? outgoingDecryptHKDF(
@@ -391,8 +397,9 @@ function getConfiguredFunctionsForEncryption(passphrase, useDynamicIterationCoun
391
397
  decryptedCache,
392
398
  passphrase,
393
399
  useDynamicIterationCount,
394
- getPBKDF2Salt
395
- ) : outgoingDecryptV1(doc, migrationDecrypt, decryptedCache, passphrase, useDynamicIterationCount);
400
+ getPBKDF2Salt,
401
+ log
402
+ ) : outgoingDecryptV1(doc, migrationDecrypt, decryptedCache, passphrase, useDynamicIterationCount, log);
396
403
  return {
397
404
  incoming,
398
405
  outgoing
@@ -459,6 +466,7 @@ export {
459
466
  encrypt,
460
467
  encryptHKDF,
461
468
  getConfiguredFunctionsForEncryption,
469
+ isEncryptedMeta,
462
470
  preprocessIncoming,
463
471
  preprocessOutgoing,
464
472
  shouldDecryptEden,
@@ -1,6 +1,7 @@
1
1
  import { type DocumentID, type FilePathWithPrefix, type FilePathWithPrefixLC, type MetaEntry, type UXFileInfoStub, type ObsidianLiveSyncSettings, type LOG_LEVEL } from "../common/types.js";
2
2
  import { type LogFunction } from "../services/lib/logUtils.js";
3
3
  import type { NecessaryServices } from "../interfaces/ServiceModule.js";
4
+ import type { IPathService } from "../services/base/IService.js";
4
5
  import { UnresolvedErrorManager } from "../services/base/UnresolvedErrorManager.js";
5
6
  import { VaultScanResults, type VaultScanResult } from "../services/base/VaultScanResult.js";
6
7
  export { VaultScanResults };
@@ -105,7 +106,11 @@ export declare function getMetadataDocumentNamespace(value: string): MetadataDoc
105
106
  * separate consistent document for the same logical path may still proceed
106
107
  * through ordinary path-based handling.
107
108
  */
108
- export declare function inspectMetadataDocumentIdentity(host: NecessaryServices<"path", never>, doc: MetaEntry): Promise<MetadataDocumentIdentityInspection>;
109
+ export declare function inspectMetadataDocumentIdentity(host: {
110
+ services: {
111
+ path: Pick<IPathService, "getPath" | "path2id">;
112
+ };
113
+ }, doc: MetaEntry): Promise<MetadataDocumentIdentityInspection>;
109
114
  /**
110
115
  * Enumerate Metadata from its actual local document IDs and report only the
111
116
  * entries which ordinary path-based inspection cannot address.
@@ -139,7 +144,11 @@ export declare function collectDeletedFiles(host: NecessaryServices<"setting" |
139
144
  * @param doc Meta entry document
140
145
  * @returns Path string
141
146
  */
142
- export declare function getPathFromEntry(host: NecessaryServices<"path", never>, doc: MetaEntry): FilePathWithPrefix;
147
+ export declare function getPathFromEntry(host: {
148
+ services: {
149
+ path: Pick<IPathService, "getPath">;
150
+ };
151
+ }, doc: MetaEntry): FilePathWithPrefix;
143
152
  /**
144
153
  * Synchronise a single file between database and storage based on freshness comparison.
145
154
  * @param host Services container
@@ -183,7 +183,7 @@ Please enable them from the settings screen after setup is complete.`,
183
183
  await assertUniqueStoragePathIdentities(selected, this.path);
184
184
  if (databaseReady) {
185
185
  await assertMetadataPathIdentities(
186
- this.database.localDatabase.findAllDocs(),
186
+ this.database.localDatabase.findAllDocs({ conflicts: true }),
187
187
  this.path,
188
188
  requireMatchingIds
189
189
  );