@rapidmx/web-client 0.35.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/escrow/matters/[uid].tsx +4 -3
- package/apps/shared/calendar/calendarReminders.ts +35 -0
- package/apps/shared/calendar/sealedEvents.ts +113 -0
- 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 +89 -3
- 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 +8 -4
- 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 +30 -1
- package/apps/shared/components/layout/SharedMailboxKeyManager.tsx +122 -0
- package/apps/shared/components/layout/UnlockPromptProvider.tsx +34 -0
- package/apps/shared/components/mail/ConversationList.tsx +1 -1
- package/apps/shared/components/mail/InviteCard.tsx +11 -1
- package/apps/shared/components/mail/MessageDetailPane.tsx +59 -26
- 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/draftResume.ts +28 -3
- package/apps/shared/components/mail/compose/protectedContactSuggestions.ts +121 -0
- package/apps/shared/components/mail/compose/quotedBody.ts +4 -3
- package/apps/shared/components/mail/invite/inviteStore.ts +17 -1
- package/apps/shared/components/mail/layout/MailShell.tsx +2 -0
- package/apps/shared/components/mail/pinnedSigners.ts +14 -0
- package/apps/shared/components/mail/reading/conversationPrint.ts +3 -3
- package/apps/shared/components/mail/reading/useMessageActions.ts +4 -1
- package/apps/shared/components/settings/KeyExportSetting.tsx +173 -0
- package/apps/shared/components/settings/OpenPgpKeySetting.tsx +240 -0
- package/apps/shared/components/settings/PolicyLockedNotice.tsx +23 -0
- package/apps/shared/components/settings/ZeroAccessSetting.tsx +128 -0
- package/apps/shared/components/settings/ZeroAccessTypes.tsx +175 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +9 -2
- package/apps/shared/mail/decryptedMessages.ts +11 -3
- package/apps/shared/mail/outbox/sendJob.ts +349 -50
- package/apps/shared/mail/outbox/sendOutcomes.ts +91 -20
- package/apps/shared/mail/reportNotices.ts +3 -1
- package/apps/shared/mail/zeroAccessSource.ts +27 -0
- package/apps/shared/search/LocalIndexLifecycle.tsx +84 -24
- package/apps/shared/search/localIndexBuilder.ts +57 -20
- package/apps/shared/search/localIndexRpcClient.ts +96 -12
- package/apps/shared/search/localIndexSchema.ts +20 -4
- package/apps/shared/search/localIndexTransport.ts +6 -2
- package/apps/shared/search/localIndexWorker.ts +37 -18
- package/apps/shared/search/searchTier2.ts +5 -0
- package/apps/shared/search/zeroAccessIndexSync.ts +679 -0
- 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 +225 -61
- package/apps/www/contacts/index.tsx +67 -11
- package/apps/www/index.tsx +19 -3
- package/apps/www/settings/encryption/index.tsx +21 -1
- package/apps/www/settings/privacy/index.tsx +89 -39
- package/apps/www/tasks/index.tsx +24 -6
- package/dist/apps/escrow/matters/[uid].js +2 -2
- 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 +14 -0
- package/dist/apps/shared/calendar/sealedEvents.js +85 -0
- 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 +25 -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 +8 -4
- 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 +27 -2
- package/dist/apps/shared/components/layout/SharedMailboxKeyManager.d.ts +17 -0
- package/dist/apps/shared/components/layout/SharedMailboxKeyManager.js +98 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.d.ts +3 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.js +32 -0
- package/dist/apps/shared/components/mail/ConversationList.js +1 -1
- package/dist/apps/shared/components/mail/InviteCard.js +11 -1
- package/dist/apps/shared/components/mail/MessageDetailPane.js +46 -21
- 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/draftResume.d.ts +2 -0
- package/dist/apps/shared/components/mail/compose/draftResume.js +28 -3
- 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/compose/quotedBody.d.ts +1 -1
- package/dist/apps/shared/components/mail/compose/quotedBody.js +4 -3
- package/dist/apps/shared/components/mail/invite/inviteStore.js +16 -1
- package/dist/apps/shared/components/mail/layout/MailShell.js +2 -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/mail/reading/useMessageActions.js +4 -1
- package/dist/apps/shared/components/settings/KeyExportSetting.d.ts +13 -0
- package/dist/apps/shared/components/settings/KeyExportSetting.js +90 -0
- 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/PolicyLockedNotice.d.ts +11 -0
- package/dist/apps/shared/components/settings/PolicyLockedNotice.js +9 -0
- package/dist/apps/shared/components/settings/ZeroAccessSetting.d.ts +16 -0
- package/dist/apps/shared/components/settings/ZeroAccessSetting.js +80 -0
- 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/decryptedMessages.d.ts +1 -1
- package/dist/apps/shared/mail/decryptedMessages.js +11 -3
- 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/mail/reportNotices.js +3 -1
- package/dist/apps/shared/mail/zeroAccessSource.d.ts +8 -0
- package/dist/apps/shared/mail/zeroAccessSource.js +26 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.d.ts +2 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.js +74 -24
- package/dist/apps/shared/search/localIndexBuilder.d.ts +9 -1
- package/dist/apps/shared/search/localIndexBuilder.js +50 -17
- package/dist/apps/shared/search/localIndexRpcClient.d.ts +5 -39
- package/dist/apps/shared/search/localIndexRpcClient.js +129 -11
- package/dist/apps/shared/search/localIndexSchema.d.ts +14 -2
- package/dist/apps/shared/search/localIndexSchema.js +18 -4
- package/dist/apps/shared/search/localIndexTransport.d.ts +6 -2
- package/dist/apps/shared/search/localIndexWorker.d.ts +11 -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 +102 -0
- package/dist/apps/shared/search/zeroAccessIndexSync.js +544 -0
- 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 +88 -20
- package/dist/apps/www/contacts/index.js +64 -11
- package/dist/apps/www/index.js +19 -3
- package/dist/apps/www/settings/encryption/index.js +16 -4
- package/dist/apps/www/settings/privacy/index.js +47 -6
- package/dist/apps/www/tasks/index.js +21 -7
- package/dist/lib/admin/domainsApi.d.ts +1 -1
- package/dist/lib/admin/escrowAccessRequestsApi.d.ts +6 -0
- package/dist/lib/calendar/calendarApi.d.ts +52 -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 +86 -7
- 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 +9 -0
- package/dist/lib/calendar/sealedInvites.js +65 -0
- 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.d.ts +54 -0
- package/dist/lib/crypto/keyExport.js +161 -0
- package/dist/lib/crypto/keyPairMatch.d.ts +9 -0
- package/dist/lib/crypto/keyPairMatch.js +65 -0
- package/dist/lib/crypto/keySession.d.ts +64 -3
- package/dist/lib/crypto/keySession.js +179 -12
- package/dist/lib/crypto/keyvaultApi.d.ts +129 -8
- package/dist/lib/crypto/keyvaultApi.js +45 -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 +56 -3
- package/dist/lib/crypto/messageSecurity.js +201 -13
- package/dist/lib/crypto/mime.d.ts +5 -0
- package/dist/lib/crypto/mime.js +25 -0
- 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 +62 -0
- package/dist/lib/crypto/sharedMailboxKeys.js +337 -0
- package/dist/lib/crypto/smimeMessage.d.ts +21 -0
- package/dist/lib/crypto/smimeMessage.js +11 -7
- package/dist/lib/crypto/zeroAccessKey.d.ts +32 -0
- package/dist/lib/crypto/zeroAccessKey.js +71 -0
- package/dist/lib/mail/conversationsApi.d.ts +6 -0
- package/dist/lib/mail/mailApi.d.ts +63 -2
- package/dist/lib/mail/mailApi.js +5 -1
- package/dist/lib/mail/mailboxImportApi.d.ts +10 -0
- package/dist/lib/mail/mailboxImportApi.js +15 -0
- package/dist/lib/search/zeroAccessIndexApi.d.ts +85 -0
- package/dist/lib/search/zeroAccessIndexApi.js +35 -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/admin/escrowAccessRequestsApi.ts +6 -0
- package/lib/calendar/calendarApi.ts +143 -17
- package/lib/calendar/calendarMutations.ts +79 -12
- package/lib/calendar/inviteApi.ts +101 -8
- package/lib/calendar/protectedEvents.ts +143 -0
- package/lib/calendar/recurrence.ts +17 -0
- package/lib/calendar/sealedInvites.ts +73 -0
- 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 +229 -0
- package/lib/crypto/keyPairMatch.ts +69 -0
- package/lib/crypto/keySession.ts +216 -12
- package/lib/crypto/keyvaultApi.ts +174 -10
- package/lib/crypto/meetingKeys.ts +118 -0
- package/lib/crypto/messageSecurity.ts +284 -16
- package/lib/crypto/mime.ts +26 -0
- 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 +402 -0
- package/lib/crypto/smimeMessage.ts +11 -7
- package/lib/crypto/zeroAccessKey.ts +102 -0
- package/lib/mail/conversationsApi.ts +3 -0
- package/lib/mail/mailApi.ts +60 -3
- package/lib/mail/mailboxImportApi.ts +24 -0
- package/lib/search/zeroAccessIndexApi.ts +123 -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 +14 -5
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
* replacement. `searchTier2.ts` additionally races its own calls against a timeout, since a Worker that's
|
|
27
27
|
* merely stuck (not crashed) posts no `error` event at all.
|
|
28
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
|
+
*
|
|
29
34
|
* **Swappable transport**: everything above is specific to this one implementation - the Worker/OPFS-backed
|
|
30
35
|
* default. Every function this module exports below (`initLocalIndex`, `searchLocal`, `destroyLocalIndex`, ...)
|
|
31
36
|
* is a thin delegator to whichever `LocalIndexTransport` (`localIndexTransport.ts`) is "current," which defaults
|
|
@@ -38,6 +43,7 @@
|
|
|
38
43
|
* plain exports, not part of the swappable interface - they name this one transport's own cross-tab/localStorage
|
|
39
44
|
* mechanics, which a different transport has no reason to share.
|
|
40
45
|
*/
|
|
46
|
+
import { subscribeKeySession } from "../../../lib/crypto/keySession.js";
|
|
41
47
|
import type { ParsedSearchQuery } from "../../../lib/search/queryGrammar.js";
|
|
42
48
|
import type {
|
|
43
49
|
Coverage,
|
|
@@ -48,6 +54,7 @@ import type {
|
|
|
48
54
|
IndexedVersionsParams,
|
|
49
55
|
InitParams,
|
|
50
56
|
LocalIndexRequest,
|
|
57
|
+
LocalIndexResetNotice,
|
|
51
58
|
LocalIndexResponse,
|
|
52
59
|
LocalSearchPage,
|
|
53
60
|
MoveEntityParams,
|
|
@@ -60,6 +67,7 @@ import type {
|
|
|
60
67
|
} from "./localIndexWorker.js";
|
|
61
68
|
import type { LocalIndexEntity } from "./localIndexSchema.js";
|
|
62
69
|
import { removeLocalIndexDirectories, removeLocalIndexDirectory } from "./localIndexStorage.js";
|
|
70
|
+
import { forgetAllZeroAccessSync, forgetZeroAccessSync, forgetZeroAccessSyncExcept } from "./zeroAccessSyncState.js";
|
|
63
71
|
import type { LocalIndexTransport, PruneLocalEntitiesOptions, SetLocalIndexBuildingCompletion } from "./localIndexTransport.js";
|
|
64
72
|
|
|
65
73
|
let worker: Worker | undefined;
|
|
@@ -94,7 +102,12 @@ function getWorker(): Worker {
|
|
|
94
102
|
// listener's own comment for why that guard matters.
|
|
95
103
|
const created = new Worker(new URL("./localIndexWorker.js", import.meta.url), { type: "module" });
|
|
96
104
|
worker = created;
|
|
97
|
-
created.addEventListener("message", (event: MessageEvent<LocalIndexResponse>) => {
|
|
105
|
+
created.addEventListener("message", (event: MessageEvent<LocalIndexResponse | LocalIndexResetNotice>) => {
|
|
106
|
+
if ("type" in event.data) {
|
|
107
|
+
// The Worker discarded a corrupted index: what sync passes remembered of it goes too, or a rebuilt one would skip it.
|
|
108
|
+
forgetZeroAccessSync(event.data.mailboxUid);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
98
111
|
const entry = pending.get(event.data.id);
|
|
99
112
|
if (!entry) {
|
|
100
113
|
return;
|
|
@@ -251,11 +264,19 @@ async function removeLocalEntityImpl(mailboxUid: string, entityUid: string): Pro
|
|
|
251
264
|
|
|
252
265
|
/** Re-points one indexed message at its new folder (archive, cancel-scheduled-send) so `folder:`-scoped
|
|
253
266
|
* local searches stay correct. Same no-op/never-rejects rules as `removeLocalEntityImpl()`. */
|
|
254
|
-
async function moveLocalEntityImpl(mailboxUid: string, entityUid: string, folderUid: string): Promise<void> {
|
|
267
|
+
async function moveLocalEntityImpl(mailboxUid: string, entityUid: string, folderUid: string, entityVersion?: string, flags?: string): Promise<void> {
|
|
255
268
|
if (!initializedMailboxes.has(mailboxUid)) {
|
|
256
269
|
return;
|
|
257
270
|
}
|
|
258
|
-
await call("moveEntity", {
|
|
271
|
+
await call("moveEntity", {
|
|
272
|
+
mailboxUid,
|
|
273
|
+
entityUid,
|
|
274
|
+
folderUid,
|
|
275
|
+
...(entityVersion !== undefined ? { entityVersion } : {}),
|
|
276
|
+
...(flags !== undefined ? { flags } : {}),
|
|
277
|
+
} satisfies MoveEntityParams).catch(
|
|
278
|
+
() => undefined,
|
|
279
|
+
);
|
|
259
280
|
}
|
|
260
281
|
|
|
261
282
|
function getIndexedVersionsImpl(mailboxUid: string, entityUids: string[]): Promise<Record<string, string>> {
|
|
@@ -288,6 +309,9 @@ function setLocalIndexBuildingImpl(mailboxUid: string, building: boolean, comple
|
|
|
288
309
|
* when the index could not be removed, e.g. because another tab still has it open; the deletion is then
|
|
289
310
|
* retried on the next page load. */
|
|
290
311
|
async function destroyLocalIndexImpl(mailboxUid: string): Promise<boolean> {
|
|
312
|
+
// What zero-access sync passes remembered of it goes with it, here too, so a host wrapper destroying mailboxes one by one through this
|
|
313
|
+
// transport (`tauri-client`'s scoped sign-out) forgets theirs.
|
|
314
|
+
forgetZeroAccessSync(mailboxUid);
|
|
291
315
|
updatePendingDeletions((current) => current.add(mailboxUid));
|
|
292
316
|
try {
|
|
293
317
|
await call("destroy", { mailboxUid, generation: nextLocalIndexGenerationImpl() } satisfies DestroyParams);
|
|
@@ -315,6 +339,9 @@ export const DESTROY_ALL_TIMEOUT_MS = 3_000;
|
|
|
315
339
|
*/
|
|
316
340
|
async function destroyAllLocalIndexesImpl(timeoutMs: number): Promise<boolean> {
|
|
317
341
|
signedOut = true;
|
|
342
|
+
// Here, in the transport, as for prune: a host wrapper that destroys only some accounts' indexes (`tauri-client`, several accounts in
|
|
343
|
+
// one origin) keeps the other accounts' sync state too.
|
|
344
|
+
forgetAllZeroAccessSync();
|
|
318
345
|
writePendingDeletions([ALL_INDEXES]);
|
|
319
346
|
try {
|
|
320
347
|
getChannel()?.postMessage({ type: "sign-out" });
|
|
@@ -347,9 +374,13 @@ async function destroyAllLocalIndexesImpl(timeoutMs: number): Promise<boolean> {
|
|
|
347
374
|
}
|
|
348
375
|
|
|
349
376
|
/** Removes local indexes for mailboxes outside `accessibleMailboxUids` - e.g. left behind by a different
|
|
350
|
-
* user who signed in on this device without signing out
|
|
377
|
+
* user who signed in on this device without signing out - and what zero-access sync passes remembered of them, as on a destroy. Both
|
|
378
|
+
* here, in the transport, so a host's wrapper that keeps other indexes (`tauri-client`, several accounts in one origin) keeps their sync
|
|
379
|
+
* state too. Never rejects. */
|
|
351
380
|
async function pruneInaccessibleLocalIndexesImpl(accessibleMailboxUids: Iterable<string>): Promise<void> {
|
|
352
|
-
|
|
381
|
+
const accessible = new Set(accessibleMailboxUids);
|
|
382
|
+
forgetZeroAccessSyncExcept(accessible);
|
|
383
|
+
await removeLocalIndexDirectories(accessible).catch(() => undefined);
|
|
353
384
|
}
|
|
354
385
|
|
|
355
386
|
/**
|
|
@@ -394,8 +425,49 @@ export function nextLocalIndexGeneration(): number {
|
|
|
394
425
|
return currentTransport.nextGeneration();
|
|
395
426
|
}
|
|
396
427
|
|
|
397
|
-
|
|
398
|
-
|
|
428
|
+
/** Every mailbox whose index `initLocalIndex()` opened, through whichever transport, and that wasn't destroyed since. */
|
|
429
|
+
const openedMailboxes = new Set<string>();
|
|
430
|
+
/** How many times each mailbox's keys were destroyed this page load - an `initLocalIndex()` that a lock overtook destroys what it opened. */
|
|
431
|
+
const lockCounts = new Map<string, number>();
|
|
432
|
+
let watchingLocks = false;
|
|
433
|
+
|
|
434
|
+
/** Cancels `mailboxUid`'s build and destroys its index, as `LocalIndexLifecycle` does on a lock. Never rejects. */
|
|
435
|
+
function destroyOnLock(mailboxUid: string): void {
|
|
436
|
+
// Stop a build pass still running with the destroyed keys; the destroy also makes the Worker reject anything that pass still sends.
|
|
437
|
+
// Loaded lazily (it imports this module, and carries the message security code).
|
|
438
|
+
import("./localIndexBuilder.js")
|
|
439
|
+
.then(({ cancelLocalIndexBuild }) => cancelLocalIndexBuild(mailboxUid))
|
|
440
|
+
.catch(() => undefined);
|
|
441
|
+
void destroyLocalIndex(mailboxUid);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** Subscribes, once, to key destruction: see this module's doc comment ("A lock destroys the index"). */
|
|
445
|
+
function watchKeyLocks(): void {
|
|
446
|
+
if (watchingLocks) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
watchingLocks = true;
|
|
450
|
+
subscribeKeySession(({ mailboxUid, state }) => {
|
|
451
|
+
if (state !== "locked") {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
lockCounts.set(mailboxUid, (lockCounts.get(mailboxUid) ?? 0) + 1);
|
|
455
|
+
if (openedMailboxes.has(mailboxUid)) {
|
|
456
|
+
destroyOnLock(mailboxUid);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export async function initLocalIndex(params: InitParams): Promise<void> {
|
|
462
|
+
watchKeyLocks();
|
|
463
|
+
const locks = lockCounts.get(params.mailboxUid) ?? 0;
|
|
464
|
+
await currentTransport.init(params);
|
|
465
|
+
if ((lockCounts.get(params.mailboxUid) ?? 0) !== locks) {
|
|
466
|
+
// Its keys were destroyed while it opened: it never counted as open, so the lock's own destroy didn't reach it.
|
|
467
|
+
destroyOnLock(params.mailboxUid);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
openedMailboxes.add(params.mailboxUid);
|
|
399
471
|
}
|
|
400
472
|
|
|
401
473
|
export function indexLocalEntities(mailboxUid: string, entities: LocalIndexEntity[], generation?: number): Promise<IndexEntitiesResult> {
|
|
@@ -409,9 +481,15 @@ export function removeLocalEntity(mailboxUid: string, entityUid: string): Promis
|
|
|
409
481
|
}
|
|
410
482
|
|
|
411
483
|
/** Re-points one indexed message at its new folder (archive, cancel-scheduled-send) so `folder:`-scoped
|
|
412
|
-
* local searches stay correct. Same no-op/never-rejects rules as `removeLocalEntity()`.
|
|
413
|
-
|
|
414
|
-
|
|
484
|
+
* local searches stay correct. Same no-op/never-rejects rules as `removeLocalEntity()`. `flags` (`,flag,flag,`) sets the row's flags
|
|
485
|
+
* too - a zero-access row's, which nothing else records. */
|
|
486
|
+
export function moveLocalEntity(mailboxUid: string, entityUid: string, folderUid: string, entityVersion?: string, flags?: string): Promise<void> {
|
|
487
|
+
if (flags !== undefined) {
|
|
488
|
+
return currentTransport.moveEntity(mailboxUid, entityUid, folderUid, entityVersion, flags);
|
|
489
|
+
}
|
|
490
|
+
return entityVersion !== undefined
|
|
491
|
+
? currentTransport.moveEntity(mailboxUid, entityUid, folderUid, entityVersion)
|
|
492
|
+
: currentTransport.moveEntity(mailboxUid, entityUid, folderUid);
|
|
415
493
|
}
|
|
416
494
|
|
|
417
495
|
export function getIndexedVersions(mailboxUid: string, entityUids: string[]): Promise<Record<string, string>> {
|
|
@@ -448,19 +526,25 @@ export function setLocalIndexBuilding(mailboxUid: string, building: boolean, com
|
|
|
448
526
|
* resolves `false` when the index could not be removed - see the current transport's own doc comment for what
|
|
449
527
|
* that means for it (the Worker transport also logs why, and retries on the next page load). */
|
|
450
528
|
export function destroyLocalIndex(mailboxUid: string): Promise<boolean> {
|
|
529
|
+
openedMailboxes.delete(mailboxUid);
|
|
530
|
+
// What zero-access sync passes remembered importing into it goes with it, whatever the transport.
|
|
531
|
+
forgetZeroAccessSync(mailboxUid);
|
|
451
532
|
return currentTransport.destroy(mailboxUid);
|
|
452
533
|
}
|
|
453
534
|
|
|
454
535
|
/** Destroys **every** local index this transport knows of - not just the mailboxes this session opened - e.g.
|
|
455
536
|
* on sign-out. Never rejects. Resolves `true` only if everything was removed within `timeoutMs`. */
|
|
456
537
|
export function destroyAllLocalIndexes(timeoutMs = DESTROY_ALL_TIMEOUT_MS): Promise<boolean> {
|
|
538
|
+
// `openedMailboxes` is left as it is: a host transport may keep other accounts' indexes open (`tauri-client`), and a lock of one
|
|
539
|
+
// already destroyed finds nothing to close.
|
|
457
540
|
return currentTransport.destroyAll(timeoutMs);
|
|
458
541
|
}
|
|
459
542
|
|
|
460
543
|
/** Removes local indexes for mailboxes outside `accessibleMailboxUids` - e.g. left behind by a different
|
|
461
|
-
* user who signed in on this device without signing out.
|
|
544
|
+
* user who signed in on this device without signing out. The transport decides what goes, zero-access sync state included (the
|
|
545
|
+
* Worker transport forgets it with the index; a host wrapper that skips the prune keeps both). Never rejects. */
|
|
462
546
|
export function pruneInaccessibleLocalIndexes(accessibleMailboxUids: Iterable<string>): Promise<void> {
|
|
463
|
-
return currentTransport.pruneInaccessible(accessibleMailboxUids);
|
|
547
|
+
return currentTransport.pruneInaccessible(new Set(accessibleMailboxUids));
|
|
464
548
|
}
|
|
465
549
|
|
|
466
550
|
/** Retries every deletion an earlier session couldn't finish. Never rejects. */
|
|
@@ -114,17 +114,33 @@ export interface LocalIndexEntity {
|
|
|
114
114
|
entityVersion?: string;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
/** The change marker a message's row stores (`LocalIndexEntity.entityVersion`): a new server `version` (flags, content) or a folder move
|
|
118
|
+
* both invalidate the indexed copy. */
|
|
119
|
+
export function messageRowVersion(message: { version: number; folderUid: string }): string {
|
|
120
|
+
return `${message.version}:${message.folderUid}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A message's set flags as a row stores them. Leading/trailing comma so `flags LIKE '%,x,%'` (`buildSearchPredicates()`) matches
|
|
124
|
+
* correctly even for the first/last flag in the list. */
|
|
125
|
+
export function messageRowFlags(message: { flags: object }): string {
|
|
126
|
+
const set = Object.entries(message.flags as Record<string, unknown>)
|
|
127
|
+
.filter(([, value]) => value)
|
|
128
|
+
.map(([key]) => key);
|
|
129
|
+
return `,${set.join(",")},`;
|
|
130
|
+
}
|
|
131
|
+
|
|
117
132
|
/** `entity_uid` upsert - `ON CONFLICT` (SQLite's UPSERT syntax) rather than a separate delete-then-insert,
|
|
118
133
|
* so re-indexing an already-present message (a flag changed, a folder move) updates it in place and the
|
|
119
|
-
* `entities_au` trigger keeps `entities_fts` in sync automatically.
|
|
134
|
+
* `entities_au` trigger keeps `entities_fts` in sync automatically. A row written without a folder, flags or version (a zero-access
|
|
135
|
+
* document, which carries none) keeps the ones recorded: re-importing it must not make it look unrecorded again. */
|
|
120
136
|
export const UPSERT_ENTITY_SQL = `
|
|
121
137
|
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)
|
|
122
138
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
123
139
|
ON CONFLICT(entity_uid) DO UPDATE SET
|
|
124
|
-
folder_uid = excluded.folder_uid, date_for_sort = excluded.date_for_sort, participants = excluded.participants,
|
|
125
|
-
flags = excluded.flags, has_attachments = excluded.has_attachments, subject = excluded.subject,
|
|
140
|
+
folder_uid = COALESCE(excluded.folder_uid, entities.folder_uid), date_for_sort = excluded.date_for_sort, participants = excluded.participants,
|
|
141
|
+
flags = CASE WHEN excluded.flags = '' THEN entities.flags ELSE excluded.flags END, has_attachments = excluded.has_attachments, subject = excluded.subject,
|
|
126
142
|
body = excluded.body, attachment_text = excluded.attachment_text, byte_size = excluded.byte_size,
|
|
127
|
-
entity_version = excluded.entity_version
|
|
143
|
+
entity_version = COALESCE(excluded.entity_version, entities.entity_version)
|
|
128
144
|
`;
|
|
129
145
|
|
|
130
146
|
/** Bind values for `UPSERT_ENTITY_SQL`, in column order - kept alongside it so the two can never drift
|
|
@@ -59,7 +59,9 @@ export interface LocalIndexTransport {
|
|
|
59
59
|
/** Drops one indexed entity. */
|
|
60
60
|
removeEntity(mailboxUid: string, entityUid: string): Promise<void>;
|
|
61
61
|
/** Re-points one indexed entity at its new folder. */
|
|
62
|
-
|
|
62
|
+
/** `entityVersion`, when given, is recorded with the folder (a transport that can't record it leaves the row's version cleared), and
|
|
63
|
+
* `flags` replaces the row's flags (a transport that can't record them leaves them as they are). */
|
|
64
|
+
moveEntity(mailboxUid: string, entityUid: string, folderUid: string, entityVersion?: string, flags?: string): Promise<void>;
|
|
63
65
|
/** The indexed version marker (`Message.version` + folder) currently stored for each of the given entity uids. */
|
|
64
66
|
indexedVersions(mailboxUid: string, entityUids: string[]): Promise<Record<string, string>>;
|
|
65
67
|
/** Removes indexed rows outside `keepEntityUids` (and `since`/`options.folderUids`'s own scope). Returns how many rows were pruned. */
|
|
@@ -77,7 +79,9 @@ export interface LocalIndexTransport {
|
|
|
77
79
|
/** Destroys every index this transport knows of, e.g. on sign-out. Never rejects; resolves `true` only if
|
|
78
80
|
* everything was removed within `timeoutMs`. */
|
|
79
81
|
destroyAll(timeoutMs: number): Promise<boolean>;
|
|
80
|
-
/** Removes indexes for mailboxes outside `accessibleMailboxUids
|
|
82
|
+
/** Removes indexes for mailboxes outside `accessibleMailboxUids`, and what zero-access sync remembered of each one it removes
|
|
83
|
+
* (`forgetZeroAccessSyncExcept()`; `pruneInaccessibleLocalIndexes()` leaves that to the transport, so a host can keep both). Never
|
|
84
|
+
* rejects. */
|
|
81
85
|
pruneInaccessible(accessibleMailboxUids: Iterable<string>): Promise<void>;
|
|
82
86
|
/** Retries any deletion an earlier session couldn't finish. Never rejects. */
|
|
83
87
|
retryPendingDeletions(): Promise<void>;
|
|
@@ -72,6 +72,12 @@ export type LocalIndexResponse =
|
|
|
72
72
|
| { id: number; ok: true; result: unknown }
|
|
73
73
|
| { id: number; ok: false; error: string };
|
|
74
74
|
|
|
75
|
+
/** Posted unprompted when a corrupted index was discarded (`resetCorruptedConnection()`), so the main thread forgets what it remembered of it. */
|
|
76
|
+
export interface LocalIndexResetNotice {
|
|
77
|
+
type: "reset";
|
|
78
|
+
mailboxUid: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
75
81
|
/**
|
|
76
82
|
* Carried by every call a build pass makes (`init`, `setWindow`, `setBuilding`, `indexEntities`,
|
|
77
83
|
* `pruneEntities`). Generations come from one monotonic counter on the main thread
|
|
@@ -124,6 +130,10 @@ export interface MoveEntityParams {
|
|
|
124
130
|
mailboxUid: string;
|
|
125
131
|
entityUid: string;
|
|
126
132
|
folderUid: string;
|
|
133
|
+
/** Recorded as the row's `entity_version`; cleared (the row then counts as changed) when absent. */
|
|
134
|
+
entityVersion?: string;
|
|
135
|
+
/** The row's flags (`,flag,flag,`), set too when given - a zero-access row, whose imported document carries none. */
|
|
136
|
+
flags?: string;
|
|
127
137
|
}
|
|
128
138
|
|
|
129
139
|
export interface IndexedVersionsParams {
|
|
@@ -519,6 +529,7 @@ async function withCurrentConnection<T>(params: GenerationParams & { mailboxUid:
|
|
|
519
529
|
async function resetCorruptedConnection(mailboxUid: string, connection: OpenConnection): Promise<void> {
|
|
520
530
|
console.warn(`localIndexWorker: local search index for mailbox ${mailboxUid} is corrupted; discarding and rebuilding.`);
|
|
521
531
|
connections.delete(mailboxUid);
|
|
532
|
+
self.postMessage({ type: "reset", mailboxUid } satisfies LocalIndexResetNotice);
|
|
522
533
|
await closeRawConnection(connection).catch(() => undefined);
|
|
523
534
|
try {
|
|
524
535
|
await removeLocalIndexDirectory(mailboxUid);
|
|
@@ -635,31 +646,39 @@ async function removeEntity(connection: OpenConnection, entityUid: string): Prom
|
|
|
635
646
|
}
|
|
636
647
|
|
|
637
648
|
/** Re-points an indexed message at a new folder in place (archive, cancel-scheduled-send). Clears
|
|
638
|
-
* `entity_version` so the next build pass re-validates the row against the server. */
|
|
639
|
-
async function moveEntity(connection: OpenConnection, entityUid: string, folderUid: string): Promise<void> {
|
|
640
|
-
|
|
641
|
-
|
|
649
|
+
* `entity_version` so the next build pass re-validates the row against the server. Sets its flags too when `flags` is given. */
|
|
650
|
+
async function moveEntity(connection: OpenConnection, entityUid: string, folderUid: string, entityVersion?: string, flags?: string): Promise<void> {
|
|
651
|
+
const [sql, values] =
|
|
652
|
+
flags === undefined
|
|
653
|
+
? ["UPDATE entities SET folder_uid = ?, entity_version = ? WHERE entity_uid = ?", [folderUid, entityVersion ?? null, entityUid]]
|
|
654
|
+
: ["UPDATE entities SET folder_uid = ?, entity_version = ?, flags = ? WHERE entity_uid = ?", [folderUid, entityVersion ?? null, flags, entityUid]];
|
|
655
|
+
for await (const stmt of connection.sqlite3.statements(connection.db, sql)) {
|
|
656
|
+
connection.sqlite3.bind_collection(stmt, values);
|
|
642
657
|
await connection.sqlite3.step(stmt);
|
|
643
658
|
}
|
|
644
659
|
}
|
|
645
660
|
|
|
661
|
+
/** How many uids one `indexedVersions()` statement binds. */
|
|
662
|
+
export const INDEXED_VERSIONS_BATCH = 500;
|
|
663
|
+
|
|
646
664
|
/** `entity_version` for whichever of `entityUids` are already indexed - one batched query per builder page,
|
|
647
665
|
* so a rebuild can skip re-fetching/decrypting unchanged messages. */
|
|
648
666
|
async function indexedVersions(connection: OpenConnection, entityUids: string[]): Promise<Record<string, string>> {
|
|
649
667
|
const versions: Record<string, string> = {};
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
668
|
+
// In statements of at most `INDEXED_VERSIONS_BATCH` uids: SQLite refuses one binding more than its variable limit (32766 here).
|
|
669
|
+
for (let i = 0; i < entityUids.length; i += INDEXED_VERSIONS_BATCH) {
|
|
670
|
+
const batch: string[] = entityUids.slice(i, i + INDEXED_VERSIONS_BATCH);
|
|
671
|
+
const placeholders = batch.map(() => "?").join(", ");
|
|
672
|
+
for await (const stmt of connection.sqlite3.statements(
|
|
673
|
+
connection.db,
|
|
674
|
+
`SELECT entity_uid, entity_version FROM entities WHERE entity_uid IN (${placeholders})`,
|
|
675
|
+
)) {
|
|
676
|
+
connection.sqlite3.bind_collection(stmt, batch);
|
|
677
|
+
while ((await connection.sqlite3.step(stmt)) === SQLite.SQLITE_ROW) {
|
|
678
|
+
const version = connection.sqlite3.column(stmt, 1) as string | null;
|
|
679
|
+
if (version !== null) {
|
|
680
|
+
versions[connection.sqlite3.column(stmt, 0) as string] = version;
|
|
681
|
+
}
|
|
663
682
|
}
|
|
664
683
|
}
|
|
665
684
|
}
|
|
@@ -871,7 +890,7 @@ export async function handleRequest(method: LocalIndexRequest["method"], params:
|
|
|
871
890
|
}
|
|
872
891
|
case "moveEntity": {
|
|
873
892
|
const p = params as MoveEntityParams;
|
|
874
|
-
return runExclusive(p.mailboxUid, () => withConnection(p.mailboxUid, (c) => moveEntity(c, p.entityUid, p.folderUid)));
|
|
893
|
+
return runExclusive(p.mailboxUid, () => withConnection(p.mailboxUid, (c) => moveEntity(c, p.entityUid, p.folderUid, p.entityVersion, p.flags)));
|
|
875
894
|
}
|
|
876
895
|
case "indexedVersions": {
|
|
877
896
|
const p = params as IndexedVersionsParams;
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* search* fail just because this one, most novel piece had a bad moment.
|
|
19
19
|
*/
|
|
20
20
|
import type { UnlockedKeys } from "../../../lib/crypto/keySession.js";
|
|
21
|
+
import { KeysLockedError } from "../../../lib/crypto/masterKey.js";
|
|
21
22
|
import type { ParsedSearchQuery } from "../../../lib/search/queryGrammar.js";
|
|
22
23
|
import type { SearchResult } from "../../../lib/search/searchApi.js";
|
|
23
24
|
import { deriveLocalIndexKey } from "./localIndexKey.js";
|
|
@@ -41,6 +42,10 @@ export interface Tier2SearchOutcome {
|
|
|
41
42
|
* wins, and the other is a cheap no-op. */
|
|
42
43
|
async function ensureInitialized(mailboxUid: string, unlocked: UnlockedKeys): Promise<void> {
|
|
43
44
|
const indexKey = await deriveLocalIndexKey(unlocked.masterKey, mailboxUid);
|
|
45
|
+
// Locked during the derive, which still succeeds: open nothing with the destroyed keys (the lock's own destroy found nothing open).
|
|
46
|
+
if (unlocked.destroyed) {
|
|
47
|
+
throw new KeysLockedError();
|
|
48
|
+
}
|
|
44
49
|
await initLocalIndex({ mailboxUid, indexKey });
|
|
45
50
|
}
|
|
46
51
|
|