@rapidmx/web-client 0.36.0 → 0.37.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/apps/shared/calendar/calendarReminders.ts +35 -0
- package/apps/shared/calendar/sealedEvents.ts +42 -9
- package/apps/shared/calendar/useCalendarReminders.ts +28 -9
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +4 -0
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +29 -1
- package/apps/shared/components/calendar/EventDetails.tsx +13 -2
- package/apps/shared/components/calendar/EventEditor.tsx +124 -17
- package/apps/shared/components/calendar/EventFormParts.tsx +1 -0
- package/apps/shared/components/calendar/EventModal.tsx +19 -2
- package/apps/shared/components/calendar/RequestChangeForm.tsx +4 -1
- package/apps/shared/components/calendar/eventForm.ts +2 -0
- package/apps/shared/components/contacts/ContactCard.tsx +13 -1
- package/apps/shared/components/contacts/ContactDetailPane.tsx +189 -9
- package/apps/shared/components/contacts/ContactForm.tsx +56 -7
- package/apps/shared/components/contacts/KeyChangeReview.tsx +5 -1
- package/apps/shared/components/contacts/contactCardData.ts +6 -3
- package/apps/shared/components/contacts/contactKeys.ts +3 -3
- package/apps/shared/components/layout/AppShell.tsx +34 -5
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +11 -4
- package/apps/shared/components/layout/SharedMailboxKeyManager.tsx +37 -6
- package/apps/shared/components/mail/InviteCard.tsx +11 -1
- package/apps/shared/components/mail/MessageDetailPane.tsx +25 -14
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +9 -2
- package/apps/shared/components/mail/compose/RecipientInput.tsx +2 -1
- package/apps/shared/components/mail/compose/protectedContactSuggestions.ts +121 -0
- package/apps/shared/components/mail/invite/inviteStore.ts +11 -1
- package/apps/shared/components/mail/pinnedSigners.ts +14 -0
- package/apps/shared/components/mail/reading/conversationPrint.ts +3 -3
- package/apps/shared/components/settings/KeyExportSetting.tsx +4 -1
- package/apps/shared/components/settings/OpenPgpKeySetting.tsx +240 -0
- package/apps/shared/components/settings/ZeroAccessSetting.tsx +13 -2
- package/apps/shared/components/settings/ZeroAccessTypes.tsx +175 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +9 -2
- package/apps/shared/mail/outbox/sendJob.ts +349 -50
- package/apps/shared/mail/outbox/sendOutcomes.ts +91 -20
- package/apps/shared/search/LocalIndexLifecycle.tsx +62 -33
- package/apps/shared/search/localIndexBuilder.ts +35 -24
- package/apps/shared/search/localIndexRpcClient.ts +91 -11
- package/apps/shared/search/localIndexSchema.ts +18 -2
- package/apps/shared/search/localIndexTransport.ts +6 -3
- package/apps/shared/search/localIndexWorker.ts +35 -18
- package/apps/shared/search/searchTier2.ts +5 -0
- package/apps/shared/search/zeroAccessIndexSync.ts +232 -40
- package/apps/shared/search/zeroAccessSyncState.ts +70 -0
- package/apps/shared/zeroAccess/ProtectedMark.tsx +19 -0
- package/apps/shared/zeroAccess/useKeysLocked.ts +25 -0
- package/apps/shared/zeroAccess/useOpenedRecord.ts +71 -0
- package/apps/www/_shell.tsx +2 -0
- package/apps/www/calendar/index.tsx +49 -5
- package/apps/www/contacts/index.tsx +67 -11
- package/apps/www/index.tsx +10 -1
- package/apps/www/settings/encryption/index.tsx +16 -1
- package/apps/www/settings/privacy/index.tsx +87 -38
- package/apps/www/tasks/index.tsx +24 -6
- package/dist/apps/shared/calendar/calendarReminders.d.ts +14 -0
- package/dist/apps/shared/calendar/calendarReminders.js +26 -0
- package/dist/apps/shared/calendar/sealedEvents.d.ts +3 -2
- package/dist/apps/shared/calendar/sealedEvents.js +40 -9
- package/dist/apps/shared/calendar/useCalendarReminders.d.ts +2 -1
- package/dist/apps/shared/calendar/useCalendarReminders.js +27 -9
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +3 -0
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +9 -2
- package/dist/apps/shared/components/calendar/EventDetails.js +13 -2
- package/dist/apps/shared/components/calendar/EventEditor.js +115 -18
- package/dist/apps/shared/components/calendar/EventFormParts.js +1 -1
- package/dist/apps/shared/components/calendar/EventModal.d.ts +1 -1
- package/dist/apps/shared/components/calendar/EventModal.js +15 -2
- package/dist/apps/shared/components/calendar/RequestChangeForm.d.ts +3 -0
- package/dist/apps/shared/components/calendar/RequestChangeForm.js +1 -1
- package/dist/apps/shared/components/calendar/eventForm.d.ts +2 -0
- package/dist/apps/shared/components/contacts/ContactCard.js +12 -1
- package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +2 -1
- package/dist/apps/shared/components/contacts/ContactDetailPane.js +103 -9
- package/dist/apps/shared/components/contacts/ContactForm.d.ts +4 -1
- package/dist/apps/shared/components/contacts/ContactForm.js +37 -7
- package/dist/apps/shared/components/contacts/KeyChangeReview.d.ts +3 -1
- package/dist/apps/shared/components/contacts/KeyChangeReview.js +3 -2
- package/dist/apps/shared/components/contacts/contactCardData.js +6 -3
- package/dist/apps/shared/components/contacts/contactKeys.d.ts +1 -1
- package/dist/apps/shared/components/contacts/contactKeys.js +3 -3
- package/dist/apps/shared/components/layout/AppShell.d.ts +18 -1
- package/dist/apps/shared/components/layout/AppShell.js +17 -6
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +10 -4
- package/dist/apps/shared/components/layout/SharedMailboxKeyManager.d.ts +3 -1
- package/dist/apps/shared/components/layout/SharedMailboxKeyManager.js +30 -4
- package/dist/apps/shared/components/mail/InviteCard.js +11 -1
- package/dist/apps/shared/components/mail/MessageDetailPane.js +15 -11
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +2 -2
- package/dist/apps/shared/components/mail/compose/RecipientInput.js +3 -2
- package/dist/apps/shared/components/mail/compose/protectedContactSuggestions.d.ts +14 -0
- package/dist/apps/shared/components/mail/compose/protectedContactSuggestions.js +96 -0
- package/dist/apps/shared/components/mail/invite/inviteStore.js +11 -1
- package/dist/apps/shared/components/mail/pinnedSigners.d.ts +6 -0
- package/dist/apps/shared/components/mail/pinnedSigners.js +10 -1
- package/dist/apps/shared/components/mail/reading/conversationPrint.d.ts +2 -2
- package/dist/apps/shared/components/mail/reading/conversationPrint.js +3 -3
- package/dist/apps/shared/components/settings/KeyExportSetting.js +3 -1
- package/dist/apps/shared/components/settings/OpenPgpKeySetting.d.ts +13 -0
- package/dist/apps/shared/components/settings/OpenPgpKeySetting.js +143 -0
- package/dist/apps/shared/components/settings/ZeroAccessSetting.d.ts +2 -0
- package/dist/apps/shared/components/settings/ZeroAccessSetting.js +12 -3
- package/dist/apps/shared/components/settings/ZeroAccessTypes.d.ts +29 -0
- package/dist/apps/shared/components/settings/ZeroAccessTypes.js +107 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +3 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +3 -2
- package/dist/apps/shared/mail/outbox/sendJob.d.ts +37 -4
- package/dist/apps/shared/mail/outbox/sendJob.js +287 -42
- package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +12 -0
- package/dist/apps/shared/mail/outbox/sendOutcomes.js +76 -20
- package/dist/apps/shared/search/LocalIndexLifecycle.js +55 -33
- package/dist/apps/shared/search/localIndexBuilder.d.ts +4 -1
- package/dist/apps/shared/search/localIndexBuilder.js +31 -20
- package/dist/apps/shared/search/localIndexRpcClient.d.ts +5 -39
- package/dist/apps/shared/search/localIndexRpcClient.js +126 -10
- package/dist/apps/shared/search/localIndexSchema.d.ts +14 -2
- package/dist/apps/shared/search/localIndexSchema.js +16 -2
- package/dist/apps/shared/search/localIndexTransport.d.ts +6 -3
- package/dist/apps/shared/search/localIndexWorker.d.ts +9 -0
- package/dist/apps/shared/search/localIndexWorker.js +22 -15
- package/dist/apps/shared/search/searchTier2.js +5 -0
- package/dist/apps/shared/search/zeroAccessIndexSync.d.ts +37 -6
- package/dist/apps/shared/search/zeroAccessIndexSync.js +199 -37
- package/dist/apps/shared/search/zeroAccessSyncState.d.ts +20 -0
- package/dist/apps/shared/search/zeroAccessSyncState.js +65 -0
- package/dist/apps/shared/zeroAccess/ProtectedMark.d.ts +7 -0
- package/dist/apps/shared/zeroAccess/ProtectedMark.js +10 -0
- package/dist/apps/shared/zeroAccess/useKeysLocked.d.ts +6 -0
- package/dist/apps/shared/zeroAccess/useKeysLocked.js +20 -0
- package/dist/apps/shared/zeroAccess/useOpenedRecord.d.ts +18 -0
- package/dist/apps/shared/zeroAccess/useOpenedRecord.js +66 -0
- package/dist/apps/www/_shell.d.ts +1 -1
- package/dist/apps/www/_shell.js +2 -2
- package/dist/apps/www/calendar/index.js +47 -5
- package/dist/apps/www/contacts/index.js +64 -11
- package/dist/apps/www/index.js +10 -1
- package/dist/apps/www/settings/encryption/index.js +14 -4
- package/dist/apps/www/settings/privacy/index.js +46 -5
- package/dist/apps/www/tasks/index.js +21 -7
- package/dist/lib/admin/domainsApi.d.ts +1 -1
- package/dist/lib/calendar/calendarApi.d.ts +46 -6
- package/dist/lib/calendar/calendarApi.js +110 -17
- package/dist/lib/calendar/calendarMutations.d.ts +20 -7
- package/dist/lib/calendar/calendarMutations.js +73 -14
- package/dist/lib/calendar/inviteApi.d.ts +17 -3
- package/dist/lib/calendar/inviteApi.js +61 -4
- package/dist/lib/calendar/protectedEvents.d.ts +31 -0
- package/dist/lib/calendar/protectedEvents.js +113 -0
- package/dist/lib/calendar/recurrence.d.ts +2 -0
- package/dist/lib/calendar/recurrence.js +16 -0
- package/dist/lib/calendar/sealedInvites.d.ts +6 -8
- package/dist/lib/calendar/sealedInvites.js +43 -6
- package/dist/lib/contacts/contactsApi.d.ts +29 -3
- package/dist/lib/contacts/contactsApi.js +23 -7
- package/dist/lib/contacts/useContactPhotoSrc.d.ts +5 -2
- package/dist/lib/contacts/useContactPhotoSrc.js +91 -11
- package/dist/lib/crypto/composeSecurity.d.ts +11 -3
- package/dist/lib/crypto/composeSecurity.js +13 -5
- package/dist/lib/crypto/keyExport.js +2 -2
- package/dist/lib/crypto/keyPairMatch.d.ts +9 -0
- package/dist/lib/crypto/keyPairMatch.js +65 -0
- package/dist/lib/crypto/keySession.d.ts +19 -2
- package/dist/lib/crypto/keySession.js +89 -13
- package/dist/lib/crypto/keyvaultApi.d.ts +41 -5
- package/dist/lib/crypto/keyvaultApi.js +13 -3
- package/dist/lib/crypto/meetingKeys.d.ts +25 -0
- package/dist/lib/crypto/meetingKeys.js +87 -0
- package/dist/lib/crypto/messageSecurity.d.ts +26 -2
- package/dist/lib/crypto/messageSecurity.js +122 -10
- package/dist/lib/crypto/openPgpKey.d.ts +39 -0
- package/dist/lib/crypto/openPgpKey.js +89 -0
- package/dist/lib/crypto/openpgp.d.ts +47 -0
- package/dist/lib/crypto/openpgp.js +88 -0
- package/dist/lib/crypto/pgpMessage.d.ts +51 -0
- package/dist/lib/crypto/pgpMessage.js +271 -0
- package/dist/lib/crypto/sharedMailboxKeys.d.ts +15 -3
- package/dist/lib/crypto/sharedMailboxKeys.js +102 -29
- package/dist/lib/crypto/smimeMessage.d.ts +21 -0
- package/dist/lib/crypto/smimeMessage.js +11 -7
- package/dist/lib/crypto/zeroAccessKey.js +5 -0
- package/dist/lib/mail/mailApi.d.ts +33 -0
- package/dist/lib/mail/mailboxImportApi.d.ts +10 -0
- package/dist/lib/mail/mailboxImportApi.js +15 -0
- package/dist/lib/search/zeroAccessIndexApi.d.ts +4 -0
- package/dist/lib/tasks/tasksApi.d.ts +11 -1
- package/dist/lib/tasks/tasksApi.js +10 -4
- package/dist/lib/util/clientPlatform.d.ts +14 -0
- package/dist/lib/util/clientPlatform.js +26 -0
- package/dist/lib/videoconf/protectedMeetings.d.ts +74 -0
- package/dist/lib/videoconf/protectedMeetings.js +181 -0
- package/dist/lib/videoconf/videoMeetingsApi.d.ts +25 -4
- package/dist/lib/videoconf/videoMeetingsApi.js +1 -1
- package/dist/lib/zeroAccess/sealedMoves.d.ts +41 -0
- package/dist/lib/zeroAccess/sealedMoves.js +63 -0
- package/dist/lib/zeroAccess/sealedRecords.d.ts +29 -0
- package/dist/lib/zeroAccess/sealedRecords.js +136 -0
- package/lib/admin/domainsApi.ts +1 -1
- package/lib/calendar/calendarApi.ts +140 -17
- package/lib/calendar/calendarMutations.ts +79 -12
- package/lib/calendar/inviteApi.ts +75 -5
- package/lib/calendar/protectedEvents.ts +143 -0
- package/lib/calendar/recurrence.ts +17 -0
- package/lib/calendar/sealedInvites.ts +47 -7
- package/lib/contacts/contactsApi.ts +43 -17
- package/lib/contacts/useContactPhotoSrc.ts +103 -13
- package/lib/crypto/composeSecurity.ts +20 -5
- package/lib/crypto/keyExport.ts +2 -2
- package/lib/crypto/keyPairMatch.ts +69 -0
- package/lib/crypto/keySession.ts +102 -12
- package/lib/crypto/keyvaultApi.ts +49 -7
- package/lib/crypto/meetingKeys.ts +118 -0
- package/lib/crypto/messageSecurity.ts +174 -13
- package/lib/crypto/openPgpKey.ts +118 -0
- package/lib/crypto/openpgp.ts +115 -0
- package/lib/crypto/pgpMessage.ts +336 -0
- package/lib/crypto/sharedMailboxKeys.ts +106 -28
- package/lib/crypto/smimeMessage.ts +11 -7
- package/lib/crypto/zeroAccessKey.ts +5 -0
- package/lib/mail/mailApi.ts +27 -0
- package/lib/mail/mailboxImportApi.ts +24 -0
- package/lib/search/zeroAccessIndexApi.ts +4 -0
- package/lib/tasks/tasksApi.ts +19 -5
- package/lib/util/clientPlatform.ts +50 -0
- package/lib/videoconf/protectedMeetings.ts +221 -0
- package/lib/videoconf/videoMeetingsApi.ts +15 -4
- package/lib/zeroAccess/sealedMoves.ts +79 -0
- package/lib/zeroAccess/sealedRecords.ts +153 -0
- package/package.json +12 -3
|
@@ -1,4 +1,51 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* Main-thread Promise-based RPC wrapper around `localIndexWorker.ts`. One Worker per tab (spawned
|
|
7
|
+
* lazily, on first use, not at module load - a page that never touches search shouldn't pay for booting
|
|
8
|
+
* the WASM module at all), shared across every mailbox `init()`-ed this session - the Worker itself keeps
|
|
9
|
+
* a per-mailbox connection map (see that file's own doc comment).
|
|
10
|
+
*
|
|
11
|
+
* `postMessage` has no native request/response pairing, so every call generates its own numeric `id` and
|
|
12
|
+
* this module correlates the eventual matching response via a pending-request map.
|
|
13
|
+
*
|
|
14
|
+
* **Generations**: `nextLocalIndexGeneration()` hands out one monotonic counter shared by build passes and
|
|
15
|
+
* destroys, so the Worker can reject a build call issued before the index was destroyed (see
|
|
16
|
+
* `GenerationParams`).
|
|
17
|
+
*
|
|
18
|
+
* **Sign-out reaches every tab**: `destroyAllLocalIndexes()` broadcasts on a `BroadcastChannel`; any other
|
|
19
|
+
* tab running a Worker closes its connections and refuses further `init`s.
|
|
20
|
+
*
|
|
21
|
+
* **Failed deletions are retried**: a destroy that couldn't remove a directory (another tab held it, the page
|
|
22
|
+
* navigated away first) is recorded in `localStorage` and retried before this tab's first `init`.
|
|
23
|
+
*
|
|
24
|
+
* **A crashed Worker never leaves a caller hanging**: the `error` listener registered in `getWorker()` rejects
|
|
25
|
+
* every pending request and drops the module-level `worker` reference so the next `call()` spawns a
|
|
26
|
+
* replacement. `searchTier2.ts` additionally races its own calls against a timeout, since a Worker that's
|
|
27
|
+
* merely stuck (not crashed) posts no `error` event at all.
|
|
28
|
+
*
|
|
29
|
+
* **A lock destroys the index**: `initLocalIndex()` subscribes once to the key session (`subscribeKeySession()`); when the keys of
|
|
30
|
+
* a mailbox whose index it opened are destroyed (an idle lock, a manual lock, a sign-out), that mailbox's build is cancelled and its
|
|
31
|
+
* index destroyed (spec section 11), whether or not a mail view (`LocalIndexLifecycle`) is still mounted to notice - after leaving Mail
|
|
32
|
+
* for Calendar, or in a host whose account switch unmounted the shell that opened it (`tauri-client`).
|
|
33
|
+
*
|
|
34
|
+
* **Swappable transport**: everything above is specific to this one implementation - the Worker/OPFS-backed
|
|
35
|
+
* default. Every function this module exports below (`initLocalIndex`, `searchLocal`, `destroyLocalIndex`, ...)
|
|
36
|
+
* is a thin delegator to whichever `LocalIndexTransport` (`localIndexTransport.ts`) is "current," which defaults
|
|
37
|
+
* to `workerLocalIndexTransport` - this file's own implementation, wrapped - and can be swapped once, at startup,
|
|
38
|
+
* via `setLocalIndexTransport()`. A host app with a different storage backend (no Worker, no OPFS, no WASM at
|
|
39
|
+
* all - `tauri-client`'s native SQLCipher/`rusqlite` index) installs its own transport there; every call site
|
|
40
|
+
* that already goes through this file's exports (`searchTier2.ts`, `LocalIndexLifecycle.tsx`,
|
|
41
|
+
* `localIndexBuilder.ts`, and the rest) then runs against that backend instead, unmodified. The three
|
|
42
|
+
* module-level constants below (`SIGN_OUT_CHANNEL`, `PENDING_DELETIONS_KEY`, `DESTROY_ALL_TIMEOUT_MS`) remain
|
|
43
|
+
* plain exports, not part of the swappable interface - they name this one transport's own cross-tab/localStorage
|
|
44
|
+
* mechanics, which a different transport has no reason to share.
|
|
45
|
+
*/
|
|
46
|
+
import { subscribeKeySession } from "../../../lib/crypto/keySession.js";
|
|
1
47
|
import { removeLocalIndexDirectories, removeLocalIndexDirectory } from "./localIndexStorage.js";
|
|
48
|
+
import { forgetAllZeroAccessSync, forgetZeroAccessSync, forgetZeroAccessSyncExcept } from "./zeroAccessSyncState.js";
|
|
2
49
|
let worker;
|
|
3
50
|
let nextRequestId = 1;
|
|
4
51
|
const pending = new Map();
|
|
@@ -26,6 +73,11 @@ function getWorker() {
|
|
|
26
73
|
const created = new Worker(new URL("./localIndexWorker.js", import.meta.url), { type: "module" });
|
|
27
74
|
worker = created;
|
|
28
75
|
created.addEventListener("message", (event) => {
|
|
76
|
+
if ("type" in event.data) {
|
|
77
|
+
// The Worker discarded a corrupted index: what sync passes remembered of it goes too, or a rebuilt one would skip it.
|
|
78
|
+
forgetZeroAccessSync(event.data.mailboxUid);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
29
81
|
const entry = pending.get(event.data.id);
|
|
30
82
|
if (!entry) {
|
|
31
83
|
return;
|
|
@@ -174,11 +226,17 @@ async function removeLocalEntityImpl(mailboxUid, entityUid) {
|
|
|
174
226
|
}
|
|
175
227
|
/** Re-points one indexed message at its new folder (archive, cancel-scheduled-send) so `folder:`-scoped
|
|
176
228
|
* local searches stay correct. Same no-op/never-rejects rules as `removeLocalEntityImpl()`. */
|
|
177
|
-
async function moveLocalEntityImpl(mailboxUid, entityUid, folderUid, entityVersion) {
|
|
229
|
+
async function moveLocalEntityImpl(mailboxUid, entityUid, folderUid, entityVersion, flags) {
|
|
178
230
|
if (!initializedMailboxes.has(mailboxUid)) {
|
|
179
231
|
return;
|
|
180
232
|
}
|
|
181
|
-
await call("moveEntity", {
|
|
233
|
+
await call("moveEntity", {
|
|
234
|
+
mailboxUid,
|
|
235
|
+
entityUid,
|
|
236
|
+
folderUid,
|
|
237
|
+
...(entityVersion !== undefined ? { entityVersion } : {}),
|
|
238
|
+
...(flags !== undefined ? { flags } : {}),
|
|
239
|
+
}).catch(() => undefined);
|
|
182
240
|
}
|
|
183
241
|
function getIndexedVersionsImpl(mailboxUid, entityUids) {
|
|
184
242
|
return call("indexedVersions", { mailboxUid, entityUids });
|
|
@@ -204,6 +262,9 @@ function setLocalIndexBuildingImpl(mailboxUid, building, completion) {
|
|
|
204
262
|
* when the index could not be removed, e.g. because another tab still has it open; the deletion is then
|
|
205
263
|
* retried on the next page load. */
|
|
206
264
|
async function destroyLocalIndexImpl(mailboxUid) {
|
|
265
|
+
// What zero-access sync passes remembered of it goes with it, here too, so a host wrapper destroying mailboxes one by one through this
|
|
266
|
+
// transport (`tauri-client`'s scoped sign-out) forgets theirs.
|
|
267
|
+
forgetZeroAccessSync(mailboxUid);
|
|
207
268
|
updatePendingDeletions((current) => current.add(mailboxUid));
|
|
208
269
|
try {
|
|
209
270
|
await call("destroy", { mailboxUid, generation: nextLocalIndexGenerationImpl() });
|
|
@@ -231,6 +292,9 @@ export const DESTROY_ALL_TIMEOUT_MS = 3000;
|
|
|
231
292
|
*/
|
|
232
293
|
async function destroyAllLocalIndexesImpl(timeoutMs) {
|
|
233
294
|
signedOut = true;
|
|
295
|
+
// Here, in the transport, as for prune: a host wrapper that destroys only some accounts' indexes (`tauri-client`, several accounts in
|
|
296
|
+
// one origin) keeps the other accounts' sync state too.
|
|
297
|
+
forgetAllZeroAccessSync();
|
|
234
298
|
writePendingDeletions([ALL_INDEXES]);
|
|
235
299
|
try {
|
|
236
300
|
getChannel()?.postMessage({ type: "sign-out" });
|
|
@@ -263,9 +327,13 @@ async function destroyAllLocalIndexesImpl(timeoutMs) {
|
|
|
263
327
|
return result;
|
|
264
328
|
}
|
|
265
329
|
/** Removes local indexes for mailboxes outside `accessibleMailboxUids` - e.g. left behind by a different
|
|
266
|
-
* user who signed in on this device without signing out
|
|
330
|
+
* user who signed in on this device without signing out - and what zero-access sync passes remembered of them, as on a destroy. Both
|
|
331
|
+
* here, in the transport, so a host's wrapper that keeps other indexes (`tauri-client`, several accounts in one origin) keeps their sync
|
|
332
|
+
* state too. Never rejects. */
|
|
267
333
|
async function pruneInaccessibleLocalIndexesImpl(accessibleMailboxUids) {
|
|
268
|
-
|
|
334
|
+
const accessible = new Set(accessibleMailboxUids);
|
|
335
|
+
forgetZeroAccessSyncExcept(accessible);
|
|
336
|
+
await removeLocalIndexDirectories(accessible).catch(() => undefined);
|
|
269
337
|
}
|
|
270
338
|
/**
|
|
271
339
|
* This file's own Worker/OPFS-backed implementation, wrapping the functions above - see this module's own doc
|
|
@@ -305,8 +373,46 @@ export function setLocalIndexTransport(transport) {
|
|
|
305
373
|
export function nextLocalIndexGeneration() {
|
|
306
374
|
return currentTransport.nextGeneration();
|
|
307
375
|
}
|
|
308
|
-
|
|
309
|
-
|
|
376
|
+
/** Every mailbox whose index `initLocalIndex()` opened, through whichever transport, and that wasn't destroyed since. */
|
|
377
|
+
const openedMailboxes = new Set();
|
|
378
|
+
/** How many times each mailbox's keys were destroyed this page load - an `initLocalIndex()` that a lock overtook destroys what it opened. */
|
|
379
|
+
const lockCounts = new Map();
|
|
380
|
+
let watchingLocks = false;
|
|
381
|
+
/** Cancels `mailboxUid`'s build and destroys its index, as `LocalIndexLifecycle` does on a lock. Never rejects. */
|
|
382
|
+
function destroyOnLock(mailboxUid) {
|
|
383
|
+
// Stop a build pass still running with the destroyed keys; the destroy also makes the Worker reject anything that pass still sends.
|
|
384
|
+
// Loaded lazily (it imports this module, and carries the message security code).
|
|
385
|
+
import("./localIndexBuilder.js")
|
|
386
|
+
.then(({ cancelLocalIndexBuild }) => cancelLocalIndexBuild(mailboxUid))
|
|
387
|
+
.catch(() => undefined);
|
|
388
|
+
void destroyLocalIndex(mailboxUid);
|
|
389
|
+
}
|
|
390
|
+
/** Subscribes, once, to key destruction: see this module's doc comment ("A lock destroys the index"). */
|
|
391
|
+
function watchKeyLocks() {
|
|
392
|
+
if (watchingLocks) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
watchingLocks = true;
|
|
396
|
+
subscribeKeySession(({ mailboxUid, state }) => {
|
|
397
|
+
if (state !== "locked") {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
lockCounts.set(mailboxUid, (lockCounts.get(mailboxUid) ?? 0) + 1);
|
|
401
|
+
if (openedMailboxes.has(mailboxUid)) {
|
|
402
|
+
destroyOnLock(mailboxUid);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
export async function initLocalIndex(params) {
|
|
407
|
+
watchKeyLocks();
|
|
408
|
+
const locks = lockCounts.get(params.mailboxUid) ?? 0;
|
|
409
|
+
await currentTransport.init(params);
|
|
410
|
+
if ((lockCounts.get(params.mailboxUid) ?? 0) !== locks) {
|
|
411
|
+
// Its keys were destroyed while it opened: it never counted as open, so the lock's own destroy didn't reach it.
|
|
412
|
+
destroyOnLock(params.mailboxUid);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
openedMailboxes.add(params.mailboxUid);
|
|
310
416
|
}
|
|
311
417
|
export function indexLocalEntities(mailboxUid, entities, generation) {
|
|
312
418
|
return currentTransport.indexEntities(mailboxUid, entities, generation);
|
|
@@ -317,8 +423,12 @@ export function removeLocalEntity(mailboxUid, entityUid) {
|
|
|
317
423
|
return currentTransport.removeEntity(mailboxUid, entityUid);
|
|
318
424
|
}
|
|
319
425
|
/** Re-points one indexed message at its new folder (archive, cancel-scheduled-send) so `folder:`-scoped
|
|
320
|
-
* local searches stay correct. Same no-op/never-rejects rules as `removeLocalEntity()`.
|
|
321
|
-
|
|
426
|
+
* local searches stay correct. Same no-op/never-rejects rules as `removeLocalEntity()`. `flags` (`,flag,flag,`) sets the row's flags
|
|
427
|
+
* too - a zero-access row's, which nothing else records. */
|
|
428
|
+
export function moveLocalEntity(mailboxUid, entityUid, folderUid, entityVersion, flags) {
|
|
429
|
+
if (flags !== undefined) {
|
|
430
|
+
return currentTransport.moveEntity(mailboxUid, entityUid, folderUid, entityVersion, flags);
|
|
431
|
+
}
|
|
322
432
|
return entityVersion !== undefined
|
|
323
433
|
? currentTransport.moveEntity(mailboxUid, entityUid, folderUid, entityVersion)
|
|
324
434
|
: currentTransport.moveEntity(mailboxUid, entityUid, folderUid);
|
|
@@ -346,17 +456,23 @@ export function setLocalIndexBuilding(mailboxUid, building, completion = { compl
|
|
|
346
456
|
* resolves `false` when the index could not be removed - see the current transport's own doc comment for what
|
|
347
457
|
* that means for it (the Worker transport also logs why, and retries on the next page load). */
|
|
348
458
|
export function destroyLocalIndex(mailboxUid) {
|
|
459
|
+
openedMailboxes.delete(mailboxUid);
|
|
460
|
+
// What zero-access sync passes remembered importing into it goes with it, whatever the transport.
|
|
461
|
+
forgetZeroAccessSync(mailboxUid);
|
|
349
462
|
return currentTransport.destroy(mailboxUid);
|
|
350
463
|
}
|
|
351
464
|
/** Destroys **every** local index this transport knows of - not just the mailboxes this session opened - e.g.
|
|
352
465
|
* on sign-out. Never rejects. Resolves `true` only if everything was removed within `timeoutMs`. */
|
|
353
466
|
export function destroyAllLocalIndexes(timeoutMs = DESTROY_ALL_TIMEOUT_MS) {
|
|
467
|
+
// `openedMailboxes` is left as it is: a host transport may keep other accounts' indexes open (`tauri-client`), and a lock of one
|
|
468
|
+
// already destroyed finds nothing to close.
|
|
354
469
|
return currentTransport.destroyAll(timeoutMs);
|
|
355
470
|
}
|
|
356
471
|
/** Removes local indexes for mailboxes outside `accessibleMailboxUids` - e.g. left behind by a different
|
|
357
|
-
* user who signed in on this device without signing out.
|
|
472
|
+
* user who signed in on this device without signing out. The transport decides what goes, zero-access sync state included (the
|
|
473
|
+
* Worker transport forgets it with the index; a host wrapper that skips the prune keeps both). Never rejects. */
|
|
358
474
|
export function pruneInaccessibleLocalIndexes(accessibleMailboxUids) {
|
|
359
|
-
return currentTransport.pruneInaccessible(accessibleMailboxUids);
|
|
475
|
+
return currentTransport.pruneInaccessible(new Set(accessibleMailboxUids));
|
|
360
476
|
}
|
|
361
477
|
/** Retries every deletion an earlier session couldn't finish. Never rejects. */
|
|
362
478
|
export function retryPendingLocalIndexDeletions() {
|
|
@@ -59,10 +59,22 @@ export interface LocalIndexEntity {
|
|
|
59
59
|
* folder) - lets a rebuild skip re-fetching/decrypting anything already indexed unchanged. */
|
|
60
60
|
entityVersion?: string;
|
|
61
61
|
}
|
|
62
|
+
/** The change marker a message's row stores (`LocalIndexEntity.entityVersion`): a new server `version` (flags, content) or a folder move
|
|
63
|
+
* both invalidate the indexed copy. */
|
|
64
|
+
export declare function messageRowVersion(message: {
|
|
65
|
+
version: number;
|
|
66
|
+
folderUid: string;
|
|
67
|
+
}): string;
|
|
68
|
+
/** A message's set flags as a row stores them. Leading/trailing comma so `flags LIKE '%,x,%'` (`buildSearchPredicates()`) matches
|
|
69
|
+
* correctly even for the first/last flag in the list. */
|
|
70
|
+
export declare function messageRowFlags(message: {
|
|
71
|
+
flags: object;
|
|
72
|
+
}): string;
|
|
62
73
|
/** `entity_uid` upsert - `ON CONFLICT` (SQLite's UPSERT syntax) rather than a separate delete-then-insert,
|
|
63
74
|
* so re-indexing an already-present message (a flag changed, a folder move) updates it in place and the
|
|
64
|
-
* `entities_au` trigger keeps `entities_fts` in sync automatically.
|
|
65
|
-
|
|
75
|
+
* `entities_au` trigger keeps `entities_fts` in sync automatically. A row written without a folder, flags or version (a zero-access
|
|
76
|
+
* document, which carries none) keeps the ones recorded: re-importing it must not make it look unrecorded again. */
|
|
77
|
+
export declare const UPSERT_ENTITY_SQL = "\nINSERT INTO entities (entity_type, entity_uid, mailbox_uid, folder_uid, date_for_sort, participants, flags, has_attachments, subject, body, attachment_text, byte_size, entity_version)\nVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\nON CONFLICT(entity_uid) DO UPDATE SET\n folder_uid = COALESCE(excluded.folder_uid, entities.folder_uid), date_for_sort = excluded.date_for_sort, participants = excluded.participants,\n flags = CASE WHEN excluded.flags = '' THEN entities.flags ELSE excluded.flags END, has_attachments = excluded.has_attachments, subject = excluded.subject,\n body = excluded.body, attachment_text = excluded.attachment_text, byte_size = excluded.byte_size,\n entity_version = COALESCE(excluded.entity_version, entities.entity_version)\n";
|
|
66
78
|
/** Bind values for `UPSERT_ENTITY_SQL`, in column order - kept alongside it so the two can never drift
|
|
67
79
|
* out of sync with each other. */
|
|
68
80
|
export declare function entityBindValues(entity: LocalIndexEntity): (string | number)[];
|
|
@@ -84,9 +84,23 @@ END;
|
|
|
84
84
|
* order - see this module's own doc comment on why the order is load-bearing. Centralized here so a
|
|
85
85
|
* future column reorder can't silently desync a query building its own literal weight list. */
|
|
86
86
|
export const BM25_WEIGHTS_SQL = "3.0, 2.0, 1.0, 1.0";
|
|
87
|
+
/** The change marker a message's row stores (`LocalIndexEntity.entityVersion`): a new server `version` (flags, content) or a folder move
|
|
88
|
+
* both invalidate the indexed copy. */
|
|
89
|
+
export function messageRowVersion(message) {
|
|
90
|
+
return `${message.version}:${message.folderUid}`;
|
|
91
|
+
}
|
|
92
|
+
/** A message's set flags as a row stores them. Leading/trailing comma so `flags LIKE '%,x,%'` (`buildSearchPredicates()`) matches
|
|
93
|
+
* correctly even for the first/last flag in the list. */
|
|
94
|
+
export function messageRowFlags(message) {
|
|
95
|
+
const set = Object.entries(message.flags)
|
|
96
|
+
.filter(([, value]) => value)
|
|
97
|
+
.map(([key]) => key);
|
|
98
|
+
return `,${set.join(",")},`;
|
|
99
|
+
}
|
|
87
100
|
/** `entity_uid` upsert - `ON CONFLICT` (SQLite's UPSERT syntax) rather than a separate delete-then-insert,
|
|
88
101
|
* so re-indexing an already-present message (a flag changed, a folder move) updates it in place and the
|
|
89
|
-
* `entities_au` trigger keeps `entities_fts` in sync automatically.
|
|
102
|
+
* `entities_au` trigger keeps `entities_fts` in sync automatically. A row written without a folder, flags or version (a zero-access
|
|
103
|
+
* document, which carries none) keeps the ones recorded: re-importing it must not make it look unrecorded again. */
|
|
90
104
|
export const UPSERT_ENTITY_SQL = `
|
|
91
105
|
INSERT INTO entities (entity_type, entity_uid, mailbox_uid, folder_uid, date_for_sort, participants, flags, has_attachments, subject, body, attachment_text, byte_size, entity_version)
|
|
92
106
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
@@ -94,7 +108,7 @@ ON CONFLICT(entity_uid) DO UPDATE SET
|
|
|
94
108
|
folder_uid = COALESCE(excluded.folder_uid, entities.folder_uid), date_for_sort = excluded.date_for_sort, participants = excluded.participants,
|
|
95
109
|
flags = CASE WHEN excluded.flags = '' THEN entities.flags ELSE excluded.flags END, has_attachments = excluded.has_attachments, subject = excluded.subject,
|
|
96
110
|
body = excluded.body, attachment_text = excluded.attachment_text, byte_size = excluded.byte_size,
|
|
97
|
-
entity_version = excluded.entity_version
|
|
111
|
+
entity_version = COALESCE(excluded.entity_version, entities.entity_version)
|
|
98
112
|
`;
|
|
99
113
|
/** Bind values for `UPSERT_ENTITY_SQL`, in column order - kept alongside it so the two can never drift
|
|
100
114
|
* out of sync with each other. */
|
|
@@ -52,8 +52,9 @@ export interface LocalIndexTransport {
|
|
|
52
52
|
/** Drops one indexed entity. */
|
|
53
53
|
removeEntity(mailboxUid: string, entityUid: string): Promise<void>;
|
|
54
54
|
/** Re-points one indexed entity at its new folder. */
|
|
55
|
-
/** `entityVersion`, when given, is recorded with the folder (a transport that can't record it leaves the row's version cleared)
|
|
56
|
-
|
|
55
|
+
/** `entityVersion`, when given, is recorded with the folder (a transport that can't record it leaves the row's version cleared), and
|
|
56
|
+
* `flags` replaces the row's flags (a transport that can't record them leaves them as they are). */
|
|
57
|
+
moveEntity(mailboxUid: string, entityUid: string, folderUid: string, entityVersion?: string, flags?: string): Promise<void>;
|
|
57
58
|
/** The indexed version marker (`Message.version` + folder) currently stored for each of the given entity uids. */
|
|
58
59
|
indexedVersions(mailboxUid: string, entityUids: string[]): Promise<Record<string, string>>;
|
|
59
60
|
/** Removes indexed rows outside `keepEntityUids` (and `since`/`options.folderUids`'s own scope). Returns how many rows were pruned. */
|
|
@@ -71,7 +72,9 @@ export interface LocalIndexTransport {
|
|
|
71
72
|
/** Destroys every index this transport knows of, e.g. on sign-out. Never rejects; resolves `true` only if
|
|
72
73
|
* everything was removed within `timeoutMs`. */
|
|
73
74
|
destroyAll(timeoutMs: number): Promise<boolean>;
|
|
74
|
-
/** Removes indexes for mailboxes outside `accessibleMailboxUids
|
|
75
|
+
/** Removes indexes for mailboxes outside `accessibleMailboxUids`, and what zero-access sync remembered of each one it removes
|
|
76
|
+
* (`forgetZeroAccessSyncExcept()`; `pruneInaccessibleLocalIndexes()` leaves that to the transport, so a host can keep both). Never
|
|
77
|
+
* rejects. */
|
|
75
78
|
pruneInaccessible(accessibleMailboxUids: Iterable<string>): Promise<void>;
|
|
76
79
|
/** Retries any deletion an earlier session couldn't finish. Never rejects. */
|
|
77
80
|
retryPendingDeletions(): Promise<void>;
|
|
@@ -17,6 +17,11 @@ export type LocalIndexResponse = {
|
|
|
17
17
|
ok: false;
|
|
18
18
|
error: string;
|
|
19
19
|
};
|
|
20
|
+
/** Posted unprompted when a corrupted index was discarded (`resetCorruptedConnection()`), so the main thread forgets what it remembered of it. */
|
|
21
|
+
export interface LocalIndexResetNotice {
|
|
22
|
+
type: "reset";
|
|
23
|
+
mailboxUid: string;
|
|
24
|
+
}
|
|
20
25
|
/**
|
|
21
26
|
* Carried by every call a build pass makes (`init`, `setWindow`, `setBuilding`, `indexEntities`,
|
|
22
27
|
* `pruneEntities`). Generations come from one monotonic counter on the main thread
|
|
@@ -64,6 +69,8 @@ export interface MoveEntityParams {
|
|
|
64
69
|
folderUid: string;
|
|
65
70
|
/** Recorded as the row's `entity_version`; cleared (the row then counts as changed) when absent. */
|
|
66
71
|
entityVersion?: string;
|
|
72
|
+
/** The row's flags (`,flag,flag,`), set too when given - a zero-access row, whose imported document carries none. */
|
|
73
|
+
flags?: string;
|
|
67
74
|
}
|
|
68
75
|
export interface IndexedVersionsParams {
|
|
69
76
|
mailboxUid: string;
|
|
@@ -143,5 +150,7 @@ export declare class StaleGenerationError extends Error {
|
|
|
143
150
|
export declare class LocalIndexBusyError extends Error {
|
|
144
151
|
constructor(mailboxUid: string);
|
|
145
152
|
}
|
|
153
|
+
/** How many uids one `indexedVersions()` statement binds. */
|
|
154
|
+
export declare const INDEXED_VERSIONS_BATCH = 500;
|
|
146
155
|
/** Routes one request to its handler, inside the owning mailbox's serial queue. Exported for tests. */
|
|
147
156
|
export declare function handleRequest(method: LocalIndexRequest["method"], params: unknown): Promise<unknown>;
|
|
@@ -324,6 +324,7 @@ async function withCurrentConnection(params, operation) {
|
|
|
324
324
|
async function resetCorruptedConnection(mailboxUid, connection) {
|
|
325
325
|
console.warn(`localIndexWorker: local search index for mailbox ${mailboxUid} is corrupted; discarding and rebuilding.`);
|
|
326
326
|
connections.delete(mailboxUid);
|
|
327
|
+
self.postMessage({ type: "reset", mailboxUid });
|
|
327
328
|
await closeRawConnection(connection).catch(() => undefined);
|
|
328
329
|
try {
|
|
329
330
|
await removeLocalIndexDirectory(mailboxUid);
|
|
@@ -428,27 +429,33 @@ async function removeEntity(connection, entityUid) {
|
|
|
428
429
|
}
|
|
429
430
|
}
|
|
430
431
|
/** Re-points an indexed message at a new folder in place (archive, cancel-scheduled-send). Clears
|
|
431
|
-
* `entity_version` so the next build pass re-validates the row against the server. */
|
|
432
|
-
async function moveEntity(connection, entityUid, folderUid, entityVersion) {
|
|
433
|
-
|
|
434
|
-
|
|
432
|
+
* `entity_version` so the next build pass re-validates the row against the server. Sets its flags too when `flags` is given. */
|
|
433
|
+
async function moveEntity(connection, entityUid, folderUid, entityVersion, flags) {
|
|
434
|
+
const [sql, values] = flags === undefined
|
|
435
|
+
? ["UPDATE entities SET folder_uid = ?, entity_version = ? WHERE entity_uid = ?", [folderUid, entityVersion ?? null, entityUid]]
|
|
436
|
+
: ["UPDATE entities SET folder_uid = ?, entity_version = ?, flags = ? WHERE entity_uid = ?", [folderUid, entityVersion ?? null, flags, entityUid]];
|
|
437
|
+
for await (const stmt of connection.sqlite3.statements(connection.db, sql)) {
|
|
438
|
+
connection.sqlite3.bind_collection(stmt, values);
|
|
435
439
|
await connection.sqlite3.step(stmt);
|
|
436
440
|
}
|
|
437
441
|
}
|
|
442
|
+
/** How many uids one `indexedVersions()` statement binds. */
|
|
443
|
+
export const INDEXED_VERSIONS_BATCH = 500;
|
|
438
444
|
/** `entity_version` for whichever of `entityUids` are already indexed - one batched query per builder page,
|
|
439
445
|
* so a rebuild can skip re-fetching/decrypting unchanged messages. */
|
|
440
446
|
async function indexedVersions(connection, entityUids) {
|
|
441
447
|
const versions = {};
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
448
|
+
// In statements of at most `INDEXED_VERSIONS_BATCH` uids: SQLite refuses one binding more than its variable limit (32766 here).
|
|
449
|
+
for (let i = 0; i < entityUids.length; i += INDEXED_VERSIONS_BATCH) {
|
|
450
|
+
const batch = entityUids.slice(i, i + INDEXED_VERSIONS_BATCH);
|
|
451
|
+
const placeholders = batch.map(() => "?").join(", ");
|
|
452
|
+
for await (const stmt of connection.sqlite3.statements(connection.db, `SELECT entity_uid, entity_version FROM entities WHERE entity_uid IN (${placeholders})`)) {
|
|
453
|
+
connection.sqlite3.bind_collection(stmt, batch);
|
|
454
|
+
while ((await connection.sqlite3.step(stmt)) === SQLite.SQLITE_ROW) {
|
|
455
|
+
const version = connection.sqlite3.column(stmt, 1);
|
|
456
|
+
if (version !== null) {
|
|
457
|
+
versions[connection.sqlite3.column(stmt, 0)] = version;
|
|
458
|
+
}
|
|
452
459
|
}
|
|
453
460
|
}
|
|
454
461
|
}
|
|
@@ -636,7 +643,7 @@ export async function handleRequest(method, params) {
|
|
|
636
643
|
}
|
|
637
644
|
case "moveEntity": {
|
|
638
645
|
const p = params;
|
|
639
|
-
return runExclusive(p.mailboxUid, () => withConnection(p.mailboxUid, (c) => moveEntity(c, p.entityUid, p.folderUid, p.entityVersion)));
|
|
646
|
+
return runExclusive(p.mailboxUid, () => withConnection(p.mailboxUid, (c) => moveEntity(c, p.entityUid, p.folderUid, p.entityVersion, p.flags)));
|
|
640
647
|
}
|
|
641
648
|
case "indexedVersions": {
|
|
642
649
|
const p = params;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { KeysLockedError } from "../../../lib/crypto/masterKey.js";
|
|
1
2
|
import { deriveLocalIndexKey } from "./localIndexKey.js";
|
|
2
3
|
import { getLocalCoverage, initLocalIndex, searchLocal } from "./localIndexRpcClient.js";
|
|
3
4
|
/** Ensures this mailbox's local index connection is open before it's queried - idempotent
|
|
@@ -6,6 +7,10 @@ import { getLocalCoverage, initLocalIndex, searchLocal } from "./localIndexRpcCl
|
|
|
6
7
|
* wins, and the other is a cheap no-op. */
|
|
7
8
|
async function ensureInitialized(mailboxUid, unlocked) {
|
|
8
9
|
const indexKey = await deriveLocalIndexKey(unlocked.masterKey, mailboxUid);
|
|
10
|
+
// Locked during the derive, which still succeeds: open nothing with the destroyed keys (the lock's own destroy found nothing open).
|
|
11
|
+
if (unlocked.destroyed) {
|
|
12
|
+
throw new KeysLockedError();
|
|
13
|
+
}
|
|
9
14
|
await initLocalIndex({ mailboxUid, indexKey });
|
|
10
15
|
}
|
|
11
16
|
/** How long a Tier 2 search may run before this function gives up on it and degrades to "no local results" -
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import type { UnlockedKeys } from "../../../lib/crypto/keySession.js";
|
|
2
|
-
import { type SegmentManifest, type ZeroAccessIndexDocument } from "../../../lib/search/zeroAccessIndexApi.js";
|
|
2
|
+
import { type IndexWeekEntries, type SegmentManifest, type ZeroAccessIndexDocument } from "../../../lib/search/zeroAccessIndexApi.js";
|
|
3
3
|
import { type ApiClient } from "../../../lib/util/api.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type ClientPlatform } from "../../../lib/util/clientPlatform.js";
|
|
5
|
+
import { type LocalIndexEntity } from "./localIndexSchema.js";
|
|
6
|
+
import { forgetZeroAccessSync } from "./zeroAccessSyncState.js";
|
|
7
|
+
export { forgetZeroAccessSync };
|
|
5
8
|
/** The segment format this client writes and reads. */
|
|
6
9
|
export declare const SEGMENT_SCHEMA_VERSION = 1;
|
|
7
10
|
/** How far before its last pass the live tail asks again, so an entry committed late is never skipped. */
|
|
8
11
|
export declare const TAIL_OVERLAP_MS: number;
|
|
9
12
|
/** At most this many weeks are compacted per pass, so a large backlog is worked through over several passes. */
|
|
10
13
|
export declare const MAX_COMPACTIONS_PER_PASS = 4;
|
|
14
|
+
/** How long after a week's `compactableAt` each platform takes it (restapi `specs/search.md` §8 *Who compacts*): a computer first,
|
|
15
|
+
* a phone only when nothing else has for a day. */
|
|
16
|
+
export declare const COMPACTION_DELAY_MS: Record<ClientPlatform, number>;
|
|
17
|
+
/** A mobile client declines a week whose pending entries and current segment together are larger than this. */
|
|
18
|
+
export declare const MOBILE_COMPACTION_MAX_BYTES: number;
|
|
19
|
+
/**
|
|
20
|
+
* Whether this client should compact `week` now: it is compactable, has pending entries, and this platform's delay after its
|
|
21
|
+
* `compactableAt` has passed. A mobile client also declines on a cellular or data-saver connection, and a week larger than
|
|
22
|
+
* `MOBILE_COMPACTION_MAX_BYTES` with `segmentBytes` (its current segment's size) - or of unknown size, or from a server that
|
|
23
|
+
* doesn't say when it became compactable.
|
|
24
|
+
*/
|
|
25
|
+
export declare function shouldCompact(week: IndexWeekEntries, segmentBytes: number, platform?: ClientPlatform, now?: number, constrained?: boolean): boolean;
|
|
11
26
|
/** How many times `uploadClientDocuments()` re-reads a week's client segment after another device replaced it first. */
|
|
12
27
|
export declare const CLIENT_UPLOAD_ATTEMPTS = 3;
|
|
13
28
|
/** What a pass did, for the caller's logging and tests. */
|
|
@@ -20,10 +35,26 @@ export interface ZeroAccessSyncResult {
|
|
|
20
35
|
/** Entries or segments that wouldn't decrypt with this device's keys (a key it doesn't have) - left as they are. */
|
|
21
36
|
unreadable: number;
|
|
22
37
|
}
|
|
23
|
-
/**
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
|
|
38
|
+
/** What the builder (`localIndexBuilder.ts`) knows of a zero-access message from the message list - none of it is in its document. */
|
|
39
|
+
export interface ZeroAccessRowMetadata {
|
|
40
|
+
folderUid: string;
|
|
41
|
+
entityVersion: string;
|
|
42
|
+
/** `,flag,flag,` */
|
|
43
|
+
flags: string;
|
|
44
|
+
}
|
|
45
|
+
/** At most this many messages' metadata are kept per mailbox for rows not imported yet; the oldest go first. */
|
|
46
|
+
export declare const MAX_REMEMBERED_METADATA = 50000;
|
|
47
|
+
/**
|
|
48
|
+
* Remembers zero-access message `messageUid`'s folder, version and flags for when its row is imported: the builder records them on the row
|
|
49
|
+
* as it walks, which does nothing for a row that isn't there yet (the index started empty - a new device, or after a lock - and this sync
|
|
50
|
+
* imports the rows after the build).
|
|
51
|
+
*/
|
|
52
|
+
export declare function rememberZeroAccessMetadata(mailboxUid: string, messageUid: string, metadata: ZeroAccessRowMetadata): void;
|
|
53
|
+
/** At most this many live-tail rows a pass looks up (`recordTailMetadata()`); the rest get theirs from the next build pass. */
|
|
54
|
+
export declare const MAX_TAIL_LOOKUPS = 500;
|
|
55
|
+
/** A document as a row of the local index. Folder and flags aren't in the document - the builder records them (`localIndexBuilder.ts`,
|
|
56
|
+
* here through `rememberZeroAccessMetadata()` when the row came later; a live-tail row nothing recorded is looked up,
|
|
57
|
+
* `recordTailMetadata()`), and the upsert keeps what is already there. */
|
|
27
58
|
export declare function documentEntity(mailboxUid: string, doc: ZeroAccessIndexDocument): LocalIndexEntity;
|
|
28
59
|
/** One document of a client segment: a local index row of an end-to-end message, as the device that decrypted it built it. */
|
|
29
60
|
export interface ClientIndexDocument {
|