@rapidmx/web-client 0.4.0 → 0.6.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/README.md +88 -0
- package/apps/admin/audit-log/index.tsx +35 -8
- package/apps/admin/data-requests/index.tsx +480 -485
- package/apps/admin/distribution-lists/[uid].tsx +19 -1
- package/apps/admin/escrow-scopes/[uid].tsx +348 -237
- package/apps/admin/escrow-scopes/new/index.tsx +8 -2
- package/apps/admin/index.tsx +19 -1
- package/apps/admin/ingest-queue/index.tsx +44 -5
- package/apps/admin/mailboxes/[uid].tsx +211 -184
- package/apps/admin/quarantine/index.tsx +46 -14
- package/apps/admin/transport-rules/[uid].tsx +30 -1
- package/apps/admin/transport-rules/_transportRuleConfig.tsx +38 -2
- package/apps/admin/transport-rules/index.tsx +24 -6
- package/apps/admin/transport-rules/new/index.tsx +30 -1
- package/apps/escrow/_layout.tsx +2 -3
- package/apps/escrow/audit-log/index.tsx +196 -168
- package/apps/escrow/matters/[uid].tsx +621 -521
- package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +22 -1
- package/apps/shared/components/admin/layout/AdminShell.tsx +28 -9
- package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -0
- package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
- package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +55 -11
- package/apps/shared/components/admin/settings/BrandingForm.tsx +15 -4
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +18 -7
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +10 -2
- package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +6 -0
- package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +45 -12
- package/apps/shared/components/admin/settings/PluginsManager.tsx +1235 -595
- package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +174 -98
- package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +49 -13
- package/apps/shared/components/admin/setup/SetupWizard.tsx +150 -34
- package/apps/shared/components/admin/signOut.ts +79 -0
- package/apps/shared/components/admin/usePagedList.tsx +129 -0
- package/apps/shared/components/calendar/EventModal.tsx +717 -549
- package/apps/shared/components/calendar/MonthView.tsx +173 -139
- package/apps/shared/components/calendar/RecurrenceEditor.tsx +222 -180
- package/apps/shared/components/calendar/SplitDayView.tsx +143 -137
- package/apps/shared/components/calendar/TimeGridView.tsx +244 -198
- package/apps/shared/components/calendar/allDay.ts +124 -0
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +194 -192
- package/apps/shared/components/contacts/ContactDetailPane.tsx +100 -21
- package/apps/shared/components/contacts/ContactForm.tsx +377 -348
- package/apps/shared/components/contacts/ContactsSidebar.tsx +6 -2
- package/apps/shared/components/contacts/KeyChangeReview.tsx +192 -0
- package/apps/shared/components/contacts/contactKeys.ts +78 -0
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +199 -197
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +148 -134
- package/apps/shared/components/layout/AppShell.tsx +123 -33
- package/apps/shared/components/layout/BrandingChrome.tsx +48 -10
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +389 -298
- package/apps/shared/components/layout/MailboxProvisioning.tsx +19 -3
- package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +350 -0
- package/apps/shared/components/layout/UnlockPromptProvider.tsx +257 -128
- package/apps/shared/components/mail/ConversationThreadPane.tsx +216 -181
- package/apps/shared/components/mail/MessageDetailPane.tsx +1318 -630
- package/apps/shared/components/mail/compose/ComposeContext.tsx +20 -15
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +854 -132
- package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +420 -408
- package/apps/shared/components/mail/pinnedSigners.ts +124 -0
- package/apps/shared/components/mail/verificationSeals.ts +125 -0
- package/apps/shared/components/mail/writableMailboxes.ts +101 -0
- package/apps/shared/components/rules/RuleBuilder.tsx +311 -276
- package/apps/shared/components/settings/layout/SettingsShell.tsx +23 -9
- package/apps/shared/components/tasks/layout/TasksShell.tsx +201 -199
- package/apps/shared/mail/listAllPages.ts +39 -0
- package/apps/shared/plugins/pluginNav.ts +73 -0
- package/apps/shared/search/LocalIndexLifecycle.tsx +64 -55
- package/apps/shared/search/localIndexBuilder.ts +481 -179
- package/apps/shared/search/localIndexRpcClient.ts +231 -29
- package/apps/shared/search/localIndexSchema.ts +15 -6
- package/apps/shared/search/localIndexStorage.ts +98 -0
- package/apps/shared/search/localIndexVFS.ts +32 -1
- package/apps/shared/search/localIndexWorker.ts +648 -186
- package/apps/www/calendar/index.tsx +438 -417
- package/apps/www/contacts/[uid].tsx +21 -2
- package/apps/www/contacts/index.tsx +116 -12
- package/apps/www/index.tsx +1314 -1041
- package/apps/www/messages/[uid].tsx +18 -6
- package/apps/www/settings/auto-reply/index.tsx +134 -129
- package/apps/www/settings/encryption/index.tsx +739 -216
- package/apps/www/settings/filters/[uid].tsx +171 -155
- package/apps/www/settings/filters/new/index.tsx +8 -1
- package/apps/www/settings/focused-inbox/index.tsx +150 -148
- package/apps/www/settings/privacy/index.tsx +494 -436
- package/apps/www/settings/read-receipts/index.tsx +148 -125
- package/apps/www/settings/sharing/index.tsx +17 -14
- package/apps/www/tasks/index.tsx +638 -579
- package/dist/apps/admin/_404.d.ts +2 -0
- package/dist/apps/admin/_500.d.ts +2 -0
- package/dist/apps/admin/_layout.d.ts +8 -0
- package/dist/apps/admin/audit-log/index.d.ts +11 -0
- package/dist/apps/admin/audit-log/index.js +38 -8
- package/dist/apps/admin/branding/index.d.ts +3 -0
- package/dist/apps/admin/data-requests/index.d.ts +3 -0
- package/dist/apps/admin/data-requests/index.js +52 -72
- package/dist/apps/admin/distribution-lists/[uid].d.ts +7 -0
- package/dist/apps/admin/distribution-lists/[uid].js +1 -1
- package/dist/apps/admin/distribution-lists/index.d.ts +3 -0
- package/dist/apps/admin/distribution-lists/new/index.d.ts +3 -0
- package/dist/apps/admin/domains/[uid].d.ts +7 -0
- package/dist/apps/admin/domains/index.d.ts +3 -0
- package/dist/apps/admin/domains/new/index.d.ts +3 -0
- package/dist/apps/admin/encryption-policy/index.d.ts +3 -0
- package/dist/apps/admin/escrow-scopes/[uid].d.ts +21 -0
- package/dist/apps/admin/escrow-scopes/[uid].js +91 -12
- package/dist/apps/admin/escrow-scopes/index.d.ts +3 -0
- package/dist/apps/admin/escrow-scopes/new/index.d.ts +3 -0
- package/dist/apps/admin/escrow-scopes/new/index.js +8 -3
- package/dist/apps/admin/index.d.ts +3 -0
- package/dist/apps/admin/index.js +4 -1
- package/dist/apps/admin/ingest-queue/index.d.ts +4 -0
- package/dist/apps/admin/ingest-queue/index.js +24 -6
- package/dist/apps/admin/mailbox-policy/index.d.ts +3 -0
- package/dist/apps/admin/mailboxes/[uid].d.ts +7 -0
- package/dist/apps/admin/mailboxes/[uid].js +20 -4
- package/dist/apps/admin/mailboxes/new/index.d.ts +3 -0
- package/dist/apps/admin/plugins/index.d.ts +3 -0
- package/dist/apps/admin/quarantine/index.d.ts +6 -0
- package/dist/apps/admin/quarantine/index.js +20 -11
- package/dist/apps/admin/retention-policy/index.d.ts +3 -0
- package/dist/apps/admin/setup/index.d.ts +3 -0
- package/dist/apps/admin/transport-rules/[uid].d.ts +7 -0
- package/dist/apps/admin/transport-rules/[uid].js +18 -2
- package/dist/apps/admin/transport-rules/_transportRuleConfig.d.ts +21 -0
- package/dist/apps/admin/transport-rules/_transportRuleConfig.js +19 -0
- package/dist/apps/admin/transport-rules/index.d.ts +3 -0
- package/dist/apps/admin/transport-rules/index.js +22 -6
- package/dist/apps/admin/transport-rules/new/index.d.ts +3 -0
- package/dist/apps/admin/transport-rules/new/index.js +18 -2
- package/dist/apps/escrow/_layout.d.ts +10 -0
- package/dist/apps/escrow/_layout.js +3 -3
- package/dist/apps/escrow/audit-log/index.d.ts +9 -0
- package/dist/apps/escrow/audit-log/index.js +26 -1
- package/dist/apps/escrow/index.d.ts +3 -0
- package/dist/apps/escrow/matters/[uid].d.ts +7 -0
- package/dist/apps/escrow/matters/[uid].js +77 -42
- package/dist/apps/escrow/matters/new/index.d.ts +3 -0
- package/dist/apps/shared/components/admin/distributionLists/MemberListCard.d.ts +17 -0
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.d.ts +32 -0
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +11 -2
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +32 -0
- package/dist/apps/shared/components/admin/layout/AdminShell.js +15 -6
- package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.d.ts +12 -0
- package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +72 -0
- package/dist/apps/shared/components/admin/mailboxes/MailboxTable.d.ts +6 -0
- package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.d.ts +19 -0
- package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +12 -0
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +22 -4
- package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +9 -0
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +15 -4
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.d.ts +12 -0
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +13 -7
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.d.ts +12 -0
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +7 -2
- package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.d.ts +9 -0
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.d.ts +19 -0
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +6 -0
- package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.d.ts +10 -0
- package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +34 -12
- package/dist/apps/shared/components/admin/settings/PluginsManager.d.ts +4 -0
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +390 -78
- package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.d.ts +9 -0
- package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +40 -11
- package/dist/apps/shared/components/admin/setup/EscrowSetupStep.d.ts +14 -0
- package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +29 -13
- package/dist/apps/shared/components/admin/setup/SetupWizard.d.ts +36 -0
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +87 -21
- package/dist/apps/shared/components/admin/signOut.d.ts +23 -0
- package/dist/apps/shared/components/admin/signOut.js +75 -0
- package/dist/apps/shared/components/admin/usePagedList.d.ts +39 -0
- package/dist/apps/shared/components/admin/usePagedList.js +99 -0
- package/dist/apps/shared/components/calendar/CalendarListSidebar.d.ts +26 -0
- package/dist/apps/shared/components/calendar/EventModal.d.ts +46 -0
- package/dist/apps/shared/components/calendar/EventModal.js +142 -21
- package/dist/apps/shared/components/calendar/MonthView.d.ts +15 -0
- package/dist/apps/shared/components/calendar/MonthView.js +12 -5
- package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +20 -0
- package/dist/apps/shared/components/calendar/RecurrenceEditor.js +40 -5
- package/dist/apps/shared/components/calendar/ResourcePicker.d.ts +19 -0
- package/dist/apps/shared/components/calendar/SplitDayView.d.ts +28 -0
- package/dist/apps/shared/components/calendar/SplitDayView.js +10 -4
- package/dist/apps/shared/components/calendar/TimeGridView.d.ts +17 -0
- package/dist/apps/shared/components/calendar/TimeGridView.js +23 -7
- package/dist/apps/shared/components/calendar/allDay.d.ts +51 -0
- package/dist/apps/shared/components/calendar/allDay.js +111 -0
- package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +40 -0
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +3 -3
- package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +23 -0
- package/dist/apps/shared/components/contacts/ContactDetailPane.js +43 -5
- package/dist/apps/shared/components/contacts/ContactForm.d.ts +20 -0
- package/dist/apps/shared/components/contacts/ContactForm.js +32 -10
- package/dist/apps/shared/components/contacts/ContactsSidebar.d.ts +40 -0
- package/dist/apps/shared/components/contacts/ContactsSidebar.js +2 -2
- package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +25 -0
- package/dist/apps/shared/components/contacts/KeyChangeReview.d.ts +40 -0
- package/dist/apps/shared/components/contacts/KeyChangeReview.js +63 -0
- package/dist/apps/shared/components/contacts/contactKeys.d.ts +29 -0
- package/dist/apps/shared/components/contacts/contactKeys.js +64 -0
- package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +21 -0
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +2 -2
- package/dist/apps/shared/components/escrow/layout/EscrowShell.d.ts +25 -0
- package/dist/apps/shared/components/escrow/layout/EscrowShell.js +24 -5
- package/dist/apps/shared/components/forms/StringListField.d.ts +22 -0
- package/dist/apps/shared/components/layout/AppShell.d.ts +49 -0
- package/dist/apps/shared/components/layout/AppShell.js +97 -26
- package/dist/apps/shared/components/layout/BrandingChrome.d.ts +18 -0
- package/dist/apps/shared/components/layout/BrandingChrome.js +45 -10
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.d.ts +51 -0
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +49 -12
- package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +13 -0
- package/dist/apps/shared/components/layout/MailboxProvisioning.js +10 -2
- package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +66 -0
- package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +174 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.d.ts +45 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.js +73 -14
- package/dist/apps/shared/components/layout/UserMenu.d.ts +23 -0
- package/dist/apps/shared/components/mail/ConversationList.d.ts +11 -0
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +23 -0
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +50 -12
- package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +93 -0
- package/dist/apps/shared/components/mail/MessageDetailPane.js +441 -36
- package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +58 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.js +9 -6
- package/dist/apps/shared/components/mail/compose/ComposeToolbar.d.ts +15 -0
- package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +37 -0
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +699 -128
- package/dist/apps/shared/components/mail/compose/EmojiPicker.d.ts +14 -0
- package/dist/apps/shared/components/mail/compose/GifPicker.d.ts +13 -0
- package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +34 -0
- package/dist/apps/shared/components/mail/compose/ScheduleSendPicker.d.ts +16 -0
- package/dist/apps/shared/components/mail/compose/composeFlushRegistry.d.ts +27 -0
- package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +50 -0
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +57 -0
- package/dist/apps/shared/components/mail/layout/MailShell.js +13 -4
- package/dist/apps/shared/components/mail/pinnedSigners.d.ts +33 -0
- package/dist/apps/shared/components/mail/pinnedSigners.js +98 -0
- package/dist/apps/shared/components/mail/verificationSeals.d.ts +22 -0
- package/dist/apps/shared/components/mail/verificationSeals.js +112 -0
- package/dist/apps/shared/components/mail/writableMailboxes.d.ts +15 -0
- package/dist/apps/shared/components/mail/writableMailboxes.js +87 -0
- package/dist/apps/shared/components/rules/RuleBuilder.d.ts +70 -0
- package/dist/apps/shared/components/rules/RuleBuilder.js +34 -6
- package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +41 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +19 -9
- package/dist/apps/shared/components/tasks/TasksSidebar.d.ts +41 -0
- package/dist/apps/shared/components/tasks/TasksToolbar.d.ts +14 -0
- package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +24 -0
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +2 -2
- package/dist/apps/shared/mail/findWellKnownFolderUid.d.ts +5 -0
- package/dist/apps/shared/mail/listAllPages.d.ts +17 -0
- package/dist/apps/shared/mail/listAllPages.js +25 -0
- package/dist/apps/shared/plugins/pluginNav.d.ts +39 -0
- package/dist/apps/shared/plugins/pluginNav.js +33 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.d.ts +13 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.js +58 -54
- package/dist/apps/shared/search/localIndexBlockCipher.d.ts +36 -0
- package/dist/apps/shared/search/localIndexBuilder.d.ts +85 -0
- package/dist/apps/shared/search/localIndexBuilder.js +292 -37
- package/dist/apps/shared/search/localIndexKey.d.ts +4 -0
- package/dist/apps/shared/search/localIndexRpcClient.d.ts +71 -0
- package/dist/apps/shared/search/localIndexRpcClient.js +191 -26
- package/dist/apps/shared/search/localIndexSchema.d.ts +111 -0
- package/dist/apps/shared/search/localIndexSchema.js +12 -6
- package/dist/apps/shared/search/localIndexSizePreference.d.ts +27 -0
- package/dist/apps/shared/search/localIndexStorage.d.ts +28 -0
- package/dist/apps/shared/search/localIndexStorage.js +86 -0
- package/dist/apps/shared/search/localIndexVFS.d.ts +101 -0
- package/dist/apps/shared/search/localIndexVFS.js +68 -40
- package/dist/apps/shared/search/localIndexWorker.d.ts +145 -0
- package/dist/apps/shared/search/localIndexWorker.js +519 -173
- package/dist/apps/shared/search/searchTier2.d.ts +30 -0
- package/dist/apps/www/_404.d.ts +2 -0
- package/dist/apps/www/_500.d.ts +2 -0
- package/dist/apps/www/_layout.d.ts +14 -0
- package/dist/apps/www/calendar/index.d.ts +3 -0
- package/dist/apps/www/calendar/index.js +28 -10
- package/dist/apps/www/contacts/[uid].d.ts +13 -0
- package/dist/apps/www/contacts/[uid].js +13 -2
- package/dist/apps/www/contacts/index.d.ts +3 -0
- package/dist/apps/www/contacts/index.js +80 -10
- package/dist/apps/www/index.d.ts +3 -0
- package/dist/apps/www/index.js +339 -108
- package/dist/apps/www/messages/[uid].d.ts +11 -0
- package/dist/apps/www/messages/[uid].js +18 -6
- package/dist/apps/www/settings/auto-reply/index.d.ts +4 -0
- package/dist/apps/www/settings/auto-reply/index.js +9 -4
- package/dist/apps/www/settings/encryption/index.d.ts +7 -0
- package/dist/apps/www/settings/encryption/index.js +514 -97
- package/dist/apps/www/settings/filters/[uid].d.ts +8 -0
- package/dist/apps/www/settings/filters/[uid].js +22 -7
- package/dist/apps/www/settings/filters/_mailFilterRuleConfig.d.ts +11 -0
- package/dist/apps/www/settings/filters/index.d.ts +4 -0
- package/dist/apps/www/settings/filters/new/index.d.ts +4 -0
- package/dist/apps/www/settings/filters/new/index.js +8 -2
- package/dist/apps/www/settings/focused-inbox/index.d.ts +4 -0
- package/dist/apps/www/settings/focused-inbox/index.js +3 -1
- package/dist/apps/www/settings/labels/index.d.ts +4 -0
- package/dist/apps/www/settings/privacy/index.d.ts +4 -0
- package/dist/apps/www/settings/privacy/index.js +57 -22
- package/dist/apps/www/settings/read-receipts/index.d.ts +4 -0
- package/dist/apps/www/settings/read-receipts/index.js +10 -3
- package/dist/apps/www/settings/sharing/index.d.ts +4 -0
- package/dist/apps/www/settings/sharing/index.js +7 -11
- package/dist/apps/www/settings/signatures/[uid].d.ts +8 -0
- package/dist/apps/www/settings/signatures/index.d.ts +4 -0
- package/dist/apps/www/settings/signatures/new/index.d.ts +4 -0
- package/dist/apps/www/settings/signatures/signatureDefaults.d.ts +10 -0
- package/dist/apps/www/tasks/index.d.ts +3 -0
- package/dist/apps/www/tasks/index.js +50 -10
- package/package.json +3 -3
- package/apps/shared/components/booking/AvailabilityEditor.tsx +0 -118
- package/apps/www/settings/booking-types/[uid].tsx +0 -300
- package/apps/www/settings/booking-types/index.tsx +0 -98
- package/apps/www/settings/booking-types/new/index.tsx +0 -212
- package/dist/apps/shared/components/booking/AvailabilityEditor.js +0 -45
- package/dist/apps/www/settings/booking-types/[uid].js +0 -129
- package/dist/apps/www/settings/booking-types/index.js +0 -29
- package/dist/apps/www/settings/booking-types/new/index.js +0 -86
|
@@ -23,10 +23,23 @@
|
|
|
23
23
|
* own precedent in `apps/www/index.tsx`'s inbox-list decrypt work. An unencrypted message is already
|
|
24
24
|
* fully searchable via Tier 1; indexing it here too would spend this index's bounded byte budget on
|
|
25
25
|
* content that didn't need it.
|
|
26
|
+
*
|
|
27
|
+
* **Verification seals.** A message this pass decrypts that has no seal for the vault's current `masterKeyGeneration` is
|
|
28
|
+
* evaluated with `evaluateMessageSecurityWithSeal()`, against the sender's pinned signing keys from the reader's
|
|
29
|
+
* contacts (`pinnedSigners.ts`, the message pane's own source, looked up once per sender per pass), so a verified
|
|
30
|
+
* message gets its seal without being opened (see `apps/shared/components/mail/verificationSeals.ts`). Pins only change
|
|
31
|
+
* the security state, never the recovered subject or body this index stores. Seal writes are best effort and bounded:
|
|
32
|
+
* at most `SEAL_WRITE_CONCURRENCY` in flight, at most `MAX_SEAL_WRITES_PER_PASS` per pass (past it, messages are
|
|
33
|
+
* evaluated without seals), failures ignored, and nothing new starts once the pass is aborted or its keys are destroyed.
|
|
34
|
+
* A seal carries only a hash, fingerprint, state and time, never plaintext. With no readable vault generation the pass
|
|
35
|
+
* seals nothing. Signed-only (unencrypted) messages aren't sealed here: this pass never fetches their raw MIME (see
|
|
36
|
+
* above), and fetching it only to seal would cost a download per message; they are sealed when opened.
|
|
26
37
|
*/
|
|
27
|
-
import { getMessageRawContent, listMessages } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
28
|
-
import { evaluateMessageSecurity } from "@rapidmx/react-shared/crypto/messageSecurity.js";
|
|
29
|
-
import {
|
|
38
|
+
import { getMessageRawContent, listFolders, listMessages } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
39
|
+
import { evaluateMessageSecurity, evaluateMessageSecurityWithSeal } from "@rapidmx/react-shared/crypto/messageSecurity.js";
|
|
40
|
+
import { getPinnedSignerFingerprints } from "../components/mail/pinnedSigners.js";
|
|
41
|
+
import { currentVerificationSeal, readVaultGeneration, sendVerificationSeal, verificationSealPending } from "../components/mail/verificationSeals.js";
|
|
42
|
+
import { getIndexedVersions, indexLocalEntities, initLocalIndex, nextLocalIndexGeneration, pruneLocalEntities, setLocalIndexBuilding, setLocalIndexWindow, } from "./localIndexRpcClient.js";
|
|
30
43
|
import { deriveLocalIndexKey } from "./localIndexKey.js";
|
|
31
44
|
import { getLocalIndexByteBudget } from "./localIndexSizePreference.js";
|
|
32
45
|
/** The RFC 9788 placeholder subject every encrypted message's outer envelope carries server-side - see
|
|
@@ -37,32 +50,143 @@ const ENCRYPTED_SUBJECT_PLACEHOLDER = "[...]";
|
|
|
37
50
|
* validated"). Used as-is rather than invented/adjusted here. Not user-adjustable today (unlike the byte
|
|
38
51
|
* budget - see `localIndexSizePreference.ts`) - nothing in this codebase has asked for that yet. */
|
|
39
52
|
export const WEB_TIME_FLOOR_MONTHS = 12;
|
|
40
|
-
/** Folder types that actually hold messages
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
/** Folder types that actually hold messages, including `user` (custom folders). Inbox and Sent first: the two
|
|
54
|
+
* folders a "did I find that email" search is overwhelmingly likely to land in, so they're covered soonest if
|
|
55
|
+
* the build is interrupted (tab closed, idle timeout) partway through. */
|
|
56
|
+
const MESSAGE_FOLDER_TYPES = new Set(["inbox", "sent_items", "drafts", "deleted_items", "outbox", "junk", "archive", "user"]);
|
|
57
|
+
/** Folder types known not to hold mail. Any folder in neither set is skipped, and the pass is then not
|
|
58
|
+
* complete - a type this module doesn't know about may well hold messages. */
|
|
59
|
+
const NON_MESSAGE_FOLDER_TYPES = new Set(["calendar", "contacts", "tasks", "notes"]);
|
|
45
60
|
const FOLDER_PRIORITY = { inbox: 0, sent_items: 1 };
|
|
61
|
+
/** How many times a pass walks a folder whose total count changed during the walk. `listMessages()` only
|
|
62
|
+
* pages by offset, so a message removed from an already-walked page shifts a later one onto it unseen;
|
|
63
|
+
* only a folder whose count held steady across a whole walk is trusted for completeness and pruning. */
|
|
64
|
+
export const MAX_WALK_ATTEMPTS = 3;
|
|
65
|
+
/** Subtracted from the pass's start time before it's recorded as the end of guaranteed coverage - a
|
|
66
|
+
* message's server-assigned `receivedDate` can be ahead of this device's clock. */
|
|
67
|
+
export const CLOCK_SKEW_MARGIN_MS = 10 * 60 * 1000;
|
|
46
68
|
const PAGE_SIZE = 100;
|
|
47
69
|
const MAX_APPROX_BYTES_PER_MESSAGE_PADDING = 512; // subject/participants/flags overhead beyond raw text length
|
|
70
|
+
const FETCH_FAILED = Symbol("fetch-failed");
|
|
71
|
+
/** How many raw-MIME fetch+decrypt calls run at once - a page of 100 encrypted messages used to fire all
|
|
72
|
+
* 100 requests simultaneously. */
|
|
73
|
+
export const FETCH_CONCURRENCY = 6;
|
|
74
|
+
/** How many verification seal writes run at once during a pass. */
|
|
75
|
+
export const SEAL_WRITE_CONCURRENCY = 2;
|
|
76
|
+
/** The most verification seals one pass writes. */
|
|
77
|
+
export const MAX_SEAL_WRITES_PER_PASS = 200;
|
|
78
|
+
function createPassSealer(mailboxUid, unlocked, signal) {
|
|
79
|
+
let generation;
|
|
80
|
+
const pins = new Map();
|
|
81
|
+
const queue = [];
|
|
82
|
+
let queued = 0;
|
|
83
|
+
let active = 0;
|
|
84
|
+
let idle;
|
|
85
|
+
const stopped = () => signal.aborted || !!unlocked.destroyed;
|
|
86
|
+
const vaultGeneration = () => (generation ?? (generation = readVaultGeneration(mailboxUid)));
|
|
87
|
+
const pinsFor = (address) => {
|
|
88
|
+
const key = address.toLowerCase();
|
|
89
|
+
let entry = pins.get(key);
|
|
90
|
+
if (!entry) {
|
|
91
|
+
entry = getPinnedSignerFingerprints(mailboxUid, address).then((fingerprints) => (fingerprints.length > 0 ? fingerprints : undefined), () => undefined);
|
|
92
|
+
pins.set(key, entry);
|
|
93
|
+
}
|
|
94
|
+
return entry;
|
|
95
|
+
};
|
|
96
|
+
function pump() {
|
|
97
|
+
if (stopped()) {
|
|
98
|
+
queue.length = 0;
|
|
99
|
+
}
|
|
100
|
+
while (active < SEAL_WRITE_CONCURRENCY && queue.length > 0) {
|
|
101
|
+
const write = queue.shift();
|
|
102
|
+
active++;
|
|
103
|
+
void write().finally(() => {
|
|
104
|
+
active--;
|
|
105
|
+
pump();
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (active === 0 && queue.length === 0) {
|
|
109
|
+
idle?.();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
async evaluate(message, rawMime) {
|
|
114
|
+
const current = await vaultGeneration();
|
|
115
|
+
const { seal, sealGeneration } = currentVerificationSeal(message);
|
|
116
|
+
if (current === undefined || stopped() || queued >= MAX_SEAL_WRITES_PER_PASS || !verificationSealPending(message.uid, current) || (seal && sealGeneration === current)) {
|
|
117
|
+
return evaluateMessageSecurity(rawMime, unlocked);
|
|
118
|
+
}
|
|
119
|
+
const result = await evaluateMessageSecurityWithSeal(rawMime, unlocked, await pinsFor(message.from.address), undefined, {
|
|
120
|
+
mailboxUid,
|
|
121
|
+
messageUid: message.uid,
|
|
122
|
+
seal,
|
|
123
|
+
sealGeneration,
|
|
124
|
+
masterKeyGeneration: current,
|
|
125
|
+
});
|
|
126
|
+
const sealToWrite = result.sealToWrite;
|
|
127
|
+
if (sealToWrite && queued < MAX_SEAL_WRITES_PER_PASS) {
|
|
128
|
+
queued++;
|
|
129
|
+
queue.push(() => sendVerificationSeal(message.uid, sealToWrite));
|
|
130
|
+
pump();
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
},
|
|
134
|
+
drain() {
|
|
135
|
+
return new Promise((resolve) => {
|
|
136
|
+
idle = resolve;
|
|
137
|
+
pump();
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
48
142
|
/** `cutoff === undefined` means no time floor at all (an unbounded `windowConfig`) - every message is
|
|
49
143
|
* "within" it, so the caller's own end-of-folder check (`messages.length < PAGE_SIZE`) becomes the only
|
|
50
144
|
* stopping condition. */
|
|
51
145
|
function isWithinTimeFloor(receivedDate, cutoff) {
|
|
52
146
|
return !cutoff || new Date(receivedDate).getTime() >= cutoff.getTime();
|
|
53
147
|
}
|
|
148
|
+
/** A relative weight only - the worker enforces the budget against the database's real measured size and
|
|
149
|
+
* uses these per-row values just to decide how much of the oldest range to evict at once. UTF-8 bytes,
|
|
150
|
+
* not UTF-16 code units. */
|
|
54
151
|
function estimateByteSize(entity) {
|
|
55
|
-
const
|
|
56
|
-
|
|
152
|
+
const encoder = new TextEncoder();
|
|
153
|
+
const textBytes = [entity.subject, entity.body, entity.attachmentText, entity.participants].reduce((total, value) => total + (value ? encoder.encode(value).length : 0), 0);
|
|
154
|
+
return textBytes + MAX_APPROX_BYTES_PER_MESSAGE_PADDING;
|
|
155
|
+
}
|
|
156
|
+
/** The change marker stored per row: a new server `version` (flags, content) or a folder move both
|
|
157
|
+
* invalidate the indexed copy. */
|
|
158
|
+
function versionOf(message) {
|
|
159
|
+
return `${message.version}:${message.folderUid}`;
|
|
160
|
+
}
|
|
161
|
+
/** `Promise.all(items.map(fn))`, but with at most `limit` calls in flight. Preserves input order. */
|
|
162
|
+
async function mapWithConcurrency(items, limit, fn) {
|
|
163
|
+
const results = new Array(items.length);
|
|
164
|
+
let next = 0;
|
|
165
|
+
async function worker() {
|
|
166
|
+
while (next < items.length) {
|
|
167
|
+
const index = next++;
|
|
168
|
+
results[index] = await fn(items[index]);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
172
|
+
return results;
|
|
57
173
|
}
|
|
58
174
|
/** Decrypts one message and shapes it into a `LocalIndexEntity`, or `undefined` when nothing usable was
|
|
59
175
|
* recovered (a decrypt failure, or a message that turns out not to actually be encrypted despite the
|
|
60
176
|
* placeholder subject) - mirrors `searchTier3.ts`'s own `!security.html && !security.subject` discard
|
|
61
177
|
* rule exactly, for the same reason. */
|
|
62
|
-
async function buildEntity(message,
|
|
178
|
+
async function buildEntity(message, sealer) {
|
|
179
|
+
let rawMime;
|
|
180
|
+
try {
|
|
181
|
+
rawMime = await getMessageRawContent(message.uid);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Distinct from "undecryptable": a message that couldn't even be fetched may well be searchable,
|
|
185
|
+
// so the pass can't claim complete coverage.
|
|
186
|
+
return FETCH_FAILED;
|
|
187
|
+
}
|
|
63
188
|
try {
|
|
64
|
-
const
|
|
65
|
-
const security = await evaluateMessageSecurity(rawMime, unlocked);
|
|
189
|
+
const security = await sealer.evaluate(message, rawMime);
|
|
66
190
|
if (!security.subject && !security.html) {
|
|
67
191
|
return undefined;
|
|
68
192
|
}
|
|
@@ -87,6 +211,7 @@ async function buildEntity(message, unlocked) {
|
|
|
87
211
|
subject: security.subject,
|
|
88
212
|
body: security.html,
|
|
89
213
|
byteSize: 0, // filled in below, after the fields above are known
|
|
214
|
+
entityVersion: versionOf(message),
|
|
90
215
|
};
|
|
91
216
|
entity.byteSize = estimateByteSize(entity);
|
|
92
217
|
return entity;
|
|
@@ -100,10 +225,15 @@ async function buildEntity(message, unlocked) {
|
|
|
100
225
|
/** Runs one full incremental build pass for `mailboxUid`: sets the window, walks mail folders newest-first
|
|
101
226
|
* (per this module's own doc comment on the folder-order simplification), decrypts and indexes only
|
|
102
227
|
* `"[...]"`-subject messages until each folder's own coverage passes the time floor, then clears the
|
|
103
|
-
* `building` flag.
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
228
|
+
* `building` flag. Messages already indexed at the same version are skipped, fetches run at most
|
|
229
|
+
* `FETCH_CONCURRENCY` at a time, and messages older than the eviction watermark (`WindowState.evictedBefore`)
|
|
230
|
+
* are neither fetched nor walked past. A failure partway through leaves whatever was indexed so far in place
|
|
231
|
+
* and records the pass as incomplete (spec §11's "incomplete-index UX... MUST indicate that coverage is
|
|
232
|
+
* partial" - see `Coverage.complete`). Rows the server no longer lists (deleted or moved elsewhere by any
|
|
233
|
+
* client) are pruned only within folders whose count held steady for a whole walk (see `MAX_WALK_ATTEMPTS`).
|
|
234
|
+
*
|
|
235
|
+
* Only one pass runs per mailbox (a newer call cancels the older), and every Worker call carries this pass's
|
|
236
|
+
* generation, so a pass outlived by a destroy can't write anything (see `cancelLocalIndexBuild()`).
|
|
107
237
|
*
|
|
108
238
|
* `windowConfig` defaults to this device's own configured byte budget (`getLocalIndexByteBudget()` -
|
|
109
239
|
* 500 MB in a browser tab, 1 GB in Electron, or whatever the user has since set in Settings > Encryption)
|
|
@@ -112,42 +242,167 @@ async function buildEntity(message, unlocked) {
|
|
|
112
242
|
* unlock), without requiring a reload.
|
|
113
243
|
*/
|
|
114
244
|
export async function buildLocalIndex(mailboxUid, unlocked, folders, windowConfig = { timeFloorMonths: WEB_TIME_FLOOR_MONTHS, byteBudgetBytes: getLocalIndexByteBudget() }) {
|
|
245
|
+
// At most one pass per mailbox: a newer one cancels the older and waits for it to wind down first.
|
|
246
|
+
const previous = activeBuilds.get(mailboxUid);
|
|
247
|
+
const controller = new AbortController();
|
|
248
|
+
const run = (async () => {
|
|
249
|
+
if (previous) {
|
|
250
|
+
previous.controller.abort();
|
|
251
|
+
await previous.settled;
|
|
252
|
+
}
|
|
253
|
+
await runBuildPass(mailboxUid, unlocked, folders, windowConfig, controller.signal);
|
|
254
|
+
})();
|
|
255
|
+
const entry = { controller, settled: run.catch(() => undefined) };
|
|
256
|
+
activeBuilds.set(mailboxUid, entry);
|
|
257
|
+
try {
|
|
258
|
+
await run;
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
if (activeBuilds.get(mailboxUid) === entry) {
|
|
262
|
+
activeBuilds.delete(mailboxUid);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const activeBuilds = new Map();
|
|
267
|
+
/** Stops a mailbox's running build pass, if any (e.g. its keys were just destroyed), resolving once it has
|
|
268
|
+
* wound down. The Worker independently rejects the pass's later calls once the index is destroyed (see
|
|
269
|
+
* `GenerationParams`); this just stops the fetching and decrypting too. Never rejects. */
|
|
270
|
+
export async function cancelLocalIndexBuild(mailboxUid) {
|
|
271
|
+
const active = activeBuilds.get(mailboxUid);
|
|
272
|
+
if (active) {
|
|
273
|
+
active.controller.abort();
|
|
274
|
+
await active.settled;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function isOlderThan(date, watermark) {
|
|
278
|
+
return !!watermark && new Date(date).getTime() < new Date(watermark).getTime();
|
|
279
|
+
}
|
|
280
|
+
async function runBuildPass(mailboxUid, unlocked, folders, windowConfig, signal) {
|
|
281
|
+
const generation = nextLocalIndexGeneration();
|
|
115
282
|
const indexKey = await deriveLocalIndexKey(unlocked.masterKey, mailboxUid);
|
|
116
|
-
|
|
117
|
-
await
|
|
118
|
-
await
|
|
283
|
+
signal.throwIfAborted();
|
|
284
|
+
await initLocalIndex({ mailboxUid, indexKey, generation });
|
|
285
|
+
let { evictedBefore } = await setLocalIndexWindow(mailboxUid, windowConfig.timeFloorMonths, windowConfig.byteBudgetBytes, generation);
|
|
286
|
+
await setLocalIndexBuilding(mailboxUid, true, { complete: false, generation });
|
|
287
|
+
// Mail arriving after this moment isn't guaranteed to be picked up by this pass.
|
|
288
|
+
const coveredUntil = new Date(Date.now() - CLOCK_SKEW_MARGIN_MS).toISOString();
|
|
289
|
+
let cutoff;
|
|
290
|
+
if (windowConfig.timeFloorMonths > 0) {
|
|
291
|
+
cutoff = new Date();
|
|
292
|
+
cutoff.setMonth(cutoff.getMonth() - windowConfig.timeFloorMonths);
|
|
293
|
+
}
|
|
294
|
+
// A pass is "complete" only if every folder was walked back to the time floor (or its end) with a stable
|
|
295
|
+
// count, no listing/fetch failure and no budget cut-off - the only case where coverage can be used to
|
|
296
|
+
// narrow Tier 3.
|
|
297
|
+
let complete = true;
|
|
298
|
+
const sealer = createPassSealer(mailboxUid, unlocked, signal);
|
|
119
299
|
try {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
cutoff = new Date();
|
|
123
|
-
cutoff.setMonth(cutoff.getMonth() - windowConfig.timeFloorMonths);
|
|
300
|
+
if (folders.some((f) => !MESSAGE_FOLDER_TYPES.has(f.type) && !NON_MESSAGE_FOLDER_TYPES.has(f.type))) {
|
|
301
|
+
complete = false;
|
|
124
302
|
}
|
|
125
303
|
const mailFolders = folders
|
|
126
304
|
.filter((f) => MESSAGE_FOLDER_TYPES.has(f.type))
|
|
127
305
|
.sort((a, b) => (FOLDER_PRIORITY[a.type] ?? 99) - (FOLDER_PRIORITY[b.type] ?? 99));
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
for (;;) {
|
|
131
|
-
|
|
306
|
+
const seenEncryptedUids = new Set();
|
|
307
|
+
async function walkFolder(folder) {
|
|
308
|
+
for (let page = 0;; page++) {
|
|
309
|
+
signal.throwIfAborted();
|
|
310
|
+
let messages;
|
|
311
|
+
try {
|
|
312
|
+
messages = await listMessages(folder.uid, { page, limit: PAGE_SIZE });
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return "failed";
|
|
316
|
+
}
|
|
132
317
|
if (messages.length === 0) {
|
|
133
|
-
|
|
318
|
+
return "done";
|
|
134
319
|
}
|
|
135
320
|
const encrypted = messages.filter((m) => m.subject === ENCRYPTED_SUBJECT_PLACEHOLDER);
|
|
136
|
-
|
|
137
|
-
|
|
321
|
+
for (const m of encrypted) {
|
|
322
|
+
seenEncryptedUids.add(m.uid);
|
|
323
|
+
}
|
|
324
|
+
// Anything older than the eviction watermark would only be inserted and evicted again.
|
|
325
|
+
const candidates = encrypted.filter((m) => !isOlderThan(m.receivedDate, evictedBefore));
|
|
326
|
+
if (candidates.length > 0) {
|
|
327
|
+
// Skip anything already indexed at the same version - a re-unlock used to re-fetch and
|
|
328
|
+
// re-decrypt the entire window every time.
|
|
329
|
+
const indexed = await getIndexedVersions(mailboxUid, candidates.map((m) => m.uid));
|
|
330
|
+
const changed = candidates.filter((m) => indexed[m.uid] !== versionOf(m));
|
|
331
|
+
const built = await mapWithConcurrency(changed, FETCH_CONCURRENCY, (m) => buildEntity(m, sealer));
|
|
332
|
+
signal.throwIfAborted();
|
|
333
|
+
if (built.includes(FETCH_FAILED)) {
|
|
334
|
+
complete = false;
|
|
335
|
+
}
|
|
336
|
+
const entities = built.filter((e) => e !== undefined && e !== FETCH_FAILED);
|
|
138
337
|
if (entities.length > 0) {
|
|
139
|
-
await indexLocalEntities(mailboxUid, entities);
|
|
338
|
+
({ evictedBefore } = await indexLocalEntities(mailboxUid, entities, generation));
|
|
140
339
|
}
|
|
141
340
|
}
|
|
142
|
-
const oldestOnPage = messages[messages.length - 1];
|
|
143
|
-
if (
|
|
144
|
-
|
|
341
|
+
const oldestOnPage = messages[messages.length - 1].receivedDate;
|
|
342
|
+
if (evictedBefore && isOlderThan(oldestOnPage, evictedBefore) && isWithinTimeFloor(evictedBefore, cutoff)) {
|
|
343
|
+
return "budget";
|
|
344
|
+
}
|
|
345
|
+
if (!isWithinTimeFloor(oldestOnPage, cutoff) || messages.length < PAGE_SIZE) {
|
|
346
|
+
return "done";
|
|
145
347
|
}
|
|
146
|
-
page += 1;
|
|
147
348
|
}
|
|
148
349
|
}
|
|
350
|
+
/** Each folder's current total, or `undefined` if the listing failed. */
|
|
351
|
+
async function folderTotals() {
|
|
352
|
+
try {
|
|
353
|
+
return new Map((await listFolders(mailboxUid)).map((f) => [f.uid, f.totalCount]));
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// Folders walked with a steady count and no listing failure - the only ones whose unseen rows can be
|
|
360
|
+
// pruned (deleted, or moved elsewhere by any client).
|
|
361
|
+
const reliable = [];
|
|
362
|
+
let totalsBefore = await folderTotals();
|
|
363
|
+
let toWalk = mailFolders;
|
|
364
|
+
for (let attempt = 0; attempt < MAX_WALK_ATTEMPTS && toWalk.length > 0; attempt++) {
|
|
365
|
+
const outcomes = new Map();
|
|
366
|
+
for (const folder of toWalk) {
|
|
367
|
+
outcomes.set(folder.uid, await walkFolder(folder));
|
|
368
|
+
}
|
|
369
|
+
const totalsAfter = await folderTotals();
|
|
370
|
+
const changed = [];
|
|
371
|
+
for (const folder of toWalk) {
|
|
372
|
+
const outcome = outcomes.get(folder.uid);
|
|
373
|
+
if (outcome === "failed" || !totalsBefore || !totalsAfter) {
|
|
374
|
+
// A failed listing, or no counts to vouch for the walk with: indexed as far as it got, but
|
|
375
|
+
// neither complete nor prunable (see the `reliable` check below).
|
|
376
|
+
}
|
|
377
|
+
else if (totalsAfter.get(folder.uid) === totalsBefore.get(folder.uid)) {
|
|
378
|
+
reliable.push(folder.uid);
|
|
379
|
+
complete && (complete = outcome === "done");
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
changed.push(folder);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
totalsBefore = totalsAfter;
|
|
386
|
+
toWalk = changed;
|
|
387
|
+
}
|
|
388
|
+
// Failed, count-less, or still changing after every attempt.
|
|
389
|
+
if (reliable.length < mailFolders.length) {
|
|
390
|
+
complete = false;
|
|
391
|
+
}
|
|
392
|
+
if (reliable.length > 0) {
|
|
393
|
+
// Every reliable folder was walked back to at least the watermark (or the floor).
|
|
394
|
+
const since = evictedBefore && isWithinTimeFloor(evictedBefore, cutoff) ? evictedBefore : cutoff?.toISOString();
|
|
395
|
+
await pruneLocalEntities(mailboxUid, [...seenEncryptedUids], since, { folderUids: reliable, generation });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
complete = false;
|
|
400
|
+
throw err;
|
|
149
401
|
}
|
|
150
402
|
finally {
|
|
151
|
-
|
|
403
|
+
// Rejected (harmlessly) when the index was destroyed mid-pass.
|
|
404
|
+
await setLocalIndexBuilding(mailboxUid, false, { complete, coveredFrom: cutoff?.toISOString(), coveredUntil, generation }).catch(() => undefined);
|
|
405
|
+
// At most `SEAL_WRITE_CONCURRENCY` writes are still in flight; queued ones are dropped if the pass was aborted.
|
|
406
|
+
await sealer.drain();
|
|
152
407
|
}
|
|
153
408
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Derives this mailbox's local-index page-encryption key from its already-unlocked master key. Every
|
|
2
|
+
* call with the same `(masterKey, mailboxUid)` pair returns the identical key - callers never need to
|
|
3
|
+
* persist it themselves. */
|
|
4
|
+
export declare function deriveLocalIndexKey(masterKey: Uint8Array, mailboxUid: string): Promise<Uint8Array>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main-thread Promise-based RPC wrapper around `localIndexWorker.ts`. One Worker per tab (spawned
|
|
3
|
+
* lazily, on first use, not at module load - a page that never touches search shouldn't pay for booting
|
|
4
|
+
* the WASM module at all), shared across every mailbox `init()`-ed this session - the Worker itself keeps
|
|
5
|
+
* a per-mailbox connection map (see that file's own doc comment).
|
|
6
|
+
*
|
|
7
|
+
* `postMessage` has no native request/response pairing, so every call generates its own numeric `id` and
|
|
8
|
+
* this module correlates the eventual matching response via a pending-request map.
|
|
9
|
+
*
|
|
10
|
+
* **Generations**: `nextLocalIndexGeneration()` hands out one monotonic counter shared by build passes and
|
|
11
|
+
* destroys, so the Worker can reject a build call issued before the index was destroyed (see
|
|
12
|
+
* `GenerationParams`).
|
|
13
|
+
*
|
|
14
|
+
* **Sign-out reaches every tab**: `destroyAllLocalIndexes()` broadcasts on a `BroadcastChannel`; any other
|
|
15
|
+
* tab running a Worker closes its connections and refuses further `init`s.
|
|
16
|
+
*
|
|
17
|
+
* **Failed deletions are retried**: a destroy that couldn't remove a directory (another tab held it, the page
|
|
18
|
+
* navigated away first) is recorded in `localStorage` and retried before this tab's first `init`.
|
|
19
|
+
*/
|
|
20
|
+
import type { ParsedSearchQuery } from "@rapidmx/react-shared/search/queryGrammar.js";
|
|
21
|
+
import type { Coverage, IndexEntitiesResult, InitParams, LocalSearchPage, WindowState } from "./localIndexWorker.js";
|
|
22
|
+
import type { LocalIndexEntity } from "./localIndexSchema.js";
|
|
23
|
+
/** A fresh generation - see this module's doc comment. */
|
|
24
|
+
export declare function nextLocalIndexGeneration(): number;
|
|
25
|
+
export declare const SIGN_OUT_CHANNEL = "rapidmx-localsearch";
|
|
26
|
+
export declare const PENDING_DELETIONS_KEY = "rapidmx-localsearch-pending-deletions";
|
|
27
|
+
/** Retries every deletion an earlier page load couldn't finish. Runs once per page load (memoized), before
|
|
28
|
+
* this tab's first `init` - so it never races an index this tab itself opens. Never rejects. */
|
|
29
|
+
export declare function retryPendingLocalIndexDeletions(): Promise<void>;
|
|
30
|
+
export declare function initLocalIndex(params: InitParams): Promise<void>;
|
|
31
|
+
export declare function indexLocalEntities(mailboxUid: string, entities: LocalIndexEntity[], generation?: number): Promise<IndexEntitiesResult>;
|
|
32
|
+
/** Drops one message from this session's local index (a delete). A no-op for a mailbox not indexed in
|
|
33
|
+
* this tab - a later build pass prunes it instead. Never rejects: best-effort housekeeping. */
|
|
34
|
+
export declare function removeLocalEntity(mailboxUid: string, entityUid: string): Promise<void>;
|
|
35
|
+
/** Re-points one indexed message at its new folder (archive, cancel-scheduled-send) so `folder:`-scoped
|
|
36
|
+
* local searches stay correct. Same no-op/never-rejects rules as `removeLocalEntity()`. */
|
|
37
|
+
export declare function moveLocalEntity(mailboxUid: string, entityUid: string, folderUid: string): Promise<void>;
|
|
38
|
+
export declare function getIndexedVersions(mailboxUid: string, entityUids: string[]): Promise<Record<string, string>>;
|
|
39
|
+
export declare function pruneLocalEntities(mailboxUid: string, keepEntityUids: string[], since: string | undefined, options?: {
|
|
40
|
+
folderUids?: string[];
|
|
41
|
+
generation?: number;
|
|
42
|
+
}): Promise<number>;
|
|
43
|
+
export declare function searchLocal(mailboxUid: string, parsed: ParsedSearchQuery, limit: number, offset?: number): Promise<LocalSearchPage>;
|
|
44
|
+
export declare function getLocalCoverage(mailboxUid: string): Promise<Coverage>;
|
|
45
|
+
export declare function setLocalIndexWindow(mailboxUid: string, timeFloorMonths: number, byteBudgetBytes: number, generation?: number): Promise<WindowState>;
|
|
46
|
+
export declare function setLocalIndexBuilding(mailboxUid: string, building: boolean, completion?: {
|
|
47
|
+
complete: boolean;
|
|
48
|
+
coveredFrom?: string;
|
|
49
|
+
coveredUntil?: string;
|
|
50
|
+
generation?: number;
|
|
51
|
+
}): Promise<void>;
|
|
52
|
+
/** Destroys one mailbox's local index (both the SQLite connection and its on-disk OPFS storage) - spec
|
|
53
|
+
* §11 "MUST be destroyed on the same events that destroy private keys." Never rejects (it's called from
|
|
54
|
+
* lifecycle hooks where a failure mustn't block key destruction) but resolves `false` - and logs why -
|
|
55
|
+
* when the index could not be removed, e.g. because another tab still has it open; the deletion is then
|
|
56
|
+
* retried on the next page load. */
|
|
57
|
+
export declare function destroyLocalIndex(mailboxUid: string): Promise<boolean>;
|
|
58
|
+
/** How long sign-out waits for local index destruction before navigating anyway. */
|
|
59
|
+
export declare const DESTROY_ALL_TIMEOUT_MS = 3000;
|
|
60
|
+
/**
|
|
61
|
+
* Destroys **every** local index on this device - not just the mailboxes this page load opened (sign-out
|
|
62
|
+
* from Calendar, say, never opened any), by enumerating OPFS for the index directory prefix - and tells every
|
|
63
|
+
* other tab to close its own connections. Closes this tab's own open connections through the Worker first
|
|
64
|
+
* (only if one was ever spawned - there's nothing to close otherwise, and booting one just to delete files
|
|
65
|
+
* would be waste), then removes the directories. Anything left behind is retried on the next load. Resolves
|
|
66
|
+
* `true` when everything was removed within `timeoutMs`; never rejects.
|
|
67
|
+
*/
|
|
68
|
+
export declare function destroyAllLocalIndexes(timeoutMs?: number): Promise<boolean>;
|
|
69
|
+
/** Removes local indexes for mailboxes outside `accessibleMailboxUids` - e.g. left behind by a different
|
|
70
|
+
* user who signed in on this device without signing out. Never rejects. */
|
|
71
|
+
export declare function pruneInaccessibleLocalIndexes(accessibleMailboxUids: Iterable<string>): Promise<void>;
|