@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
|
@@ -9,38 +9,69 @@
|
|
|
9
9
|
* a per-mailbox connection map (see that file's own doc comment).
|
|
10
10
|
*
|
|
11
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
|
-
*
|
|
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`.
|
|
14
23
|
*/
|
|
15
24
|
import type { ParsedSearchQuery } from "@rapidmx/react-shared/search/queryGrammar.js";
|
|
16
25
|
import type {
|
|
17
26
|
Coverage,
|
|
27
|
+
DestroyParams,
|
|
28
|
+
GenerationParams,
|
|
18
29
|
IndexEntitiesParams,
|
|
30
|
+
IndexEntitiesResult,
|
|
31
|
+
IndexedVersionsParams,
|
|
19
32
|
InitParams,
|
|
20
33
|
LocalIndexRequest,
|
|
21
34
|
LocalIndexResponse,
|
|
22
35
|
LocalSearchPage,
|
|
36
|
+
MoveEntityParams,
|
|
37
|
+
PruneEntitiesParams,
|
|
23
38
|
RemoveEntityParams,
|
|
24
39
|
SearchParams,
|
|
25
40
|
SetBuildingParams,
|
|
26
41
|
SetWindowParams,
|
|
42
|
+
WindowState,
|
|
27
43
|
} from "./localIndexWorker.js";
|
|
28
44
|
import type { LocalIndexEntity } from "./localIndexSchema.js";
|
|
45
|
+
import { removeLocalIndexDirectories, removeLocalIndexDirectory } from "./localIndexStorage.js";
|
|
29
46
|
|
|
30
47
|
let worker: Worker | undefined;
|
|
31
48
|
let nextRequestId = 1;
|
|
32
49
|
const pending = new Map<number, { resolve: (value: unknown) => void; reject: (err: Error) => void }>();
|
|
33
50
|
|
|
34
|
-
/** Every mailboxUid `initLocalIndex()` has opened this session, not yet `destroyLocalIndex()`-ed
|
|
35
|
-
* `
|
|
36
|
-
*
|
|
37
|
-
* through. Mirrors `keySession.ts`'s own module-level `sessions` map in spirit - session-scoped,
|
|
38
|
-
* intentionally never persisted. */
|
|
51
|
+
/** Every mailboxUid `initLocalIndex()` has opened this session, not yet `destroyLocalIndex()`-ed. Gates
|
|
52
|
+
* the in-session mutation helpers (`removeLocalEntity()`/`moveLocalEntity()`) so a delete/move in a
|
|
53
|
+
* mailbox this tab never indexed doesn't spin up the Worker just to no-op. */
|
|
39
54
|
const initializedMailboxes = new Set<string>();
|
|
40
55
|
|
|
56
|
+
let generationCounter = 0;
|
|
57
|
+
|
|
58
|
+
/** A fresh generation - see this module's doc comment. */
|
|
59
|
+
export function nextLocalIndexGeneration(): number {
|
|
60
|
+
generationCounter += 1;
|
|
61
|
+
return generationCounter;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Set once this tab (or another one) signed out - no index may be opened again in this page load. */
|
|
65
|
+
let signedOut = false;
|
|
66
|
+
|
|
67
|
+
export const SIGN_OUT_CHANNEL = "rapidmx-localsearch";
|
|
68
|
+
let channel: BroadcastChannel | undefined;
|
|
69
|
+
|
|
41
70
|
function getWorker(): Worker {
|
|
42
71
|
if (!worker) {
|
|
43
|
-
|
|
72
|
+
// Named by its compiled `.js` file, like every other relative import: tsc copies the literal into
|
|
73
|
+
// `dist` unchanged, where only the `.js` file exists, and Vite maps it back to the `.ts` source.
|
|
74
|
+
worker = new Worker(new URL("./localIndexWorker.js", import.meta.url), { type: "module" });
|
|
44
75
|
worker.addEventListener("message", (event: MessageEvent<LocalIndexResponse>) => {
|
|
45
76
|
const entry = pending.get(event.data.id);
|
|
46
77
|
if (!entry) {
|
|
@@ -53,29 +84,147 @@ function getWorker(): Worker {
|
|
|
53
84
|
entry.reject(new Error(event.data.error));
|
|
54
85
|
}
|
|
55
86
|
});
|
|
87
|
+
// Only a tab with a Worker has connections to close when another tab signs out.
|
|
88
|
+
listenForSignOut();
|
|
56
89
|
}
|
|
57
90
|
return worker;
|
|
58
91
|
}
|
|
59
92
|
|
|
93
|
+
function getChannel(): BroadcastChannel | undefined {
|
|
94
|
+
if (!channel && typeof BroadcastChannel !== "undefined") {
|
|
95
|
+
channel = new BroadcastChannel(SIGN_OUT_CHANNEL);
|
|
96
|
+
}
|
|
97
|
+
return channel;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function listenForSignOut(): void {
|
|
101
|
+
getChannel()?.addEventListener("message", (event: MessageEvent<{ type?: string }>) => {
|
|
102
|
+
if (event.data?.type === "sign-out") {
|
|
103
|
+
void closeEverythingInThisTab();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Another tab signed out: stop every build here and close this tab's connections (which also removes their
|
|
109
|
+
* directories, now that nothing holds them open). Never rejects. */
|
|
110
|
+
async function closeEverythingInThisTab(): Promise<void> {
|
|
111
|
+
signedOut = true;
|
|
112
|
+
initializedMailboxes.clear();
|
|
113
|
+
await call("destroyAll", { generation: nextLocalIndexGeneration() } satisfies GenerationParams).catch(() => undefined);
|
|
114
|
+
}
|
|
115
|
+
|
|
60
116
|
function call<T>(method: LocalIndexRequest["method"], params?: unknown): Promise<T> {
|
|
61
117
|
const id = nextRequestId++;
|
|
62
118
|
return new Promise<T>((resolve, reject) => {
|
|
63
119
|
pending.set(id, { resolve: resolve as (value: unknown) => void, reject });
|
|
64
|
-
|
|
120
|
+
try {
|
|
121
|
+
getWorker().postMessage({ id, method, params } satisfies LocalIndexRequest);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
pending.delete(id);
|
|
124
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
125
|
+
}
|
|
65
126
|
});
|
|
66
127
|
}
|
|
67
128
|
|
|
129
|
+
export const PENDING_DELETIONS_KEY = "rapidmx-localsearch-pending-deletions";
|
|
130
|
+
/** Stands for "every index on this device" in the pending-deletions list - recorded before a sign-out starts,
|
|
131
|
+
* so a navigation that kills it midway still gets finished on the next load. */
|
|
132
|
+
const ALL_INDEXES = "*";
|
|
133
|
+
|
|
134
|
+
function readPendingDeletions(): string[] {
|
|
135
|
+
try {
|
|
136
|
+
const parsed: unknown = JSON.parse(localStorage.getItem(PENDING_DELETIONS_KEY) ?? "[]");
|
|
137
|
+
return Array.isArray(parsed) ? parsed.filter((uid): uid is string => typeof uid === "string") : [];
|
|
138
|
+
} catch {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function writePendingDeletions(uids: Iterable<string>): void {
|
|
144
|
+
try {
|
|
145
|
+
const list = [...new Set(uids)];
|
|
146
|
+
if (list.length === 0) {
|
|
147
|
+
localStorage.removeItem(PENDING_DELETIONS_KEY);
|
|
148
|
+
} else {
|
|
149
|
+
localStorage.setItem(PENDING_DELETIONS_KEY, JSON.stringify(list));
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
// Storage blocked - nothing to retry from, the same as before this existed.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function updatePendingDeletions(update: (current: Set<string>) => void): void {
|
|
157
|
+
const current = new Set(readPendingDeletions());
|
|
158
|
+
update(current);
|
|
159
|
+
writePendingDeletions(current);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let pendingRetry: Promise<void> | undefined;
|
|
163
|
+
|
|
164
|
+
/** Retries every deletion an earlier page load couldn't finish. Runs once per page load (memoized), before
|
|
165
|
+
* this tab's first `init` - so it never races an index this tab itself opens. Never rejects. */
|
|
166
|
+
export function retryPendingLocalIndexDeletions(): Promise<void> {
|
|
167
|
+
pendingRetry ??= (async () => {
|
|
168
|
+
const uids = readPendingDeletions();
|
|
169
|
+
if (uids.length === 0) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const failed: string[] = [];
|
|
173
|
+
if (uids.includes(ALL_INDEXES)) {
|
|
174
|
+
const result = await removeLocalIndexDirectories().catch(() => ({ failed: [ALL_INDEXES] }));
|
|
175
|
+
failed.push(...result.failed);
|
|
176
|
+
} else {
|
|
177
|
+
for (const uid of uids) {
|
|
178
|
+
await removeLocalIndexDirectory(uid).catch(() => failed.push(uid));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
writePendingDeletions(failed);
|
|
182
|
+
})();
|
|
183
|
+
return pendingRetry;
|
|
184
|
+
}
|
|
185
|
+
|
|
68
186
|
export async function initLocalIndex(params: InitParams): Promise<void> {
|
|
187
|
+
await retryPendingLocalIndexDeletions();
|
|
188
|
+
if (signedOut) {
|
|
189
|
+
throw new Error("Signed out - the local search index is unavailable in this tab.");
|
|
190
|
+
}
|
|
69
191
|
await call("init", params);
|
|
70
192
|
initializedMailboxes.add(params.mailboxUid);
|
|
71
193
|
}
|
|
72
194
|
|
|
73
|
-
export function indexLocalEntities(mailboxUid: string, entities: LocalIndexEntity[]): Promise<
|
|
74
|
-
return call("indexEntities", { mailboxUid, entities } satisfies IndexEntitiesParams);
|
|
195
|
+
export function indexLocalEntities(mailboxUid: string, entities: LocalIndexEntity[], generation?: number): Promise<IndexEntitiesResult> {
|
|
196
|
+
return call("indexEntities", { mailboxUid, entities, generation } satisfies IndexEntitiesParams);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Drops one message from this session's local index (a delete). A no-op for a mailbox not indexed in
|
|
200
|
+
* this tab - a later build pass prunes it instead. Never rejects: best-effort housekeeping. */
|
|
201
|
+
export async function removeLocalEntity(mailboxUid: string, entityUid: string): Promise<void> {
|
|
202
|
+
if (!initializedMailboxes.has(mailboxUid)) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
await call("removeEntity", { mailboxUid, entityUid } satisfies RemoveEntityParams).catch(() => undefined);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Re-points one indexed message at its new folder (archive, cancel-scheduled-send) so `folder:`-scoped
|
|
209
|
+
* local searches stay correct. Same no-op/never-rejects rules as `removeLocalEntity()`. */
|
|
210
|
+
export async function moveLocalEntity(mailboxUid: string, entityUid: string, folderUid: string): Promise<void> {
|
|
211
|
+
if (!initializedMailboxes.has(mailboxUid)) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
await call("moveEntity", { mailboxUid, entityUid, folderUid } satisfies MoveEntityParams).catch(() => undefined);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function getIndexedVersions(mailboxUid: string, entityUids: string[]): Promise<Record<string, string>> {
|
|
218
|
+
return call("indexedVersions", { mailboxUid, entityUids } satisfies IndexedVersionsParams);
|
|
75
219
|
}
|
|
76
220
|
|
|
77
|
-
export function
|
|
78
|
-
|
|
221
|
+
export function pruneLocalEntities(
|
|
222
|
+
mailboxUid: string,
|
|
223
|
+
keepEntityUids: string[],
|
|
224
|
+
since: string | undefined,
|
|
225
|
+
options: { folderUids?: string[]; generation?: number } = {},
|
|
226
|
+
): Promise<number> {
|
|
227
|
+
return call("pruneEntities", { mailboxUid, keepEntityUids, since, ...options } satisfies PruneEntitiesParams);
|
|
79
228
|
}
|
|
80
229
|
|
|
81
230
|
export function searchLocal(mailboxUid: string, parsed: ParsedSearchQuery, limit: number, offset = 0): Promise<LocalSearchPage> {
|
|
@@ -86,31 +235,84 @@ export function getLocalCoverage(mailboxUid: string): Promise<Coverage> {
|
|
|
86
235
|
return call("coverage", mailboxUid);
|
|
87
236
|
}
|
|
88
237
|
|
|
89
|
-
export function setLocalIndexWindow(mailboxUid: string, timeFloorMonths: number, byteBudgetBytes: number): Promise<
|
|
90
|
-
return call("setWindow", { mailboxUid, timeFloorMonths, byteBudgetBytes } satisfies SetWindowParams);
|
|
238
|
+
export function setLocalIndexWindow(mailboxUid: string, timeFloorMonths: number, byteBudgetBytes: number, generation?: number): Promise<WindowState> {
|
|
239
|
+
return call("setWindow", { mailboxUid, timeFloorMonths, byteBudgetBytes, generation } satisfies SetWindowParams);
|
|
91
240
|
}
|
|
92
241
|
|
|
93
|
-
export function setLocalIndexBuilding(
|
|
94
|
-
|
|
242
|
+
export function setLocalIndexBuilding(
|
|
243
|
+
mailboxUid: string,
|
|
244
|
+
building: boolean,
|
|
245
|
+
completion: { complete: boolean; coveredFrom?: string; coveredUntil?: string; generation?: number } = { complete: false },
|
|
246
|
+
): Promise<void> {
|
|
247
|
+
return call("setBuilding", { mailboxUid, building, ...completion } satisfies SetBuildingParams);
|
|
95
248
|
}
|
|
96
249
|
|
|
97
250
|
/** Destroys one mailbox's local index (both the SQLite connection and its on-disk OPFS storage) - spec
|
|
98
|
-
* §11 "MUST be destroyed on the same events that destroy private keys." Never
|
|
99
|
-
* lifecycle hooks
|
|
100
|
-
*
|
|
101
|
-
|
|
251
|
+
* §11 "MUST be destroyed on the same events that destroy private keys." Never rejects (it's called from
|
|
252
|
+
* lifecycle hooks where a failure mustn't block key destruction) but resolves `false` - and logs why -
|
|
253
|
+
* when the index could not be removed, e.g. because another tab still has it open; the deletion is then
|
|
254
|
+
* retried on the next page load. */
|
|
255
|
+
export async function destroyLocalIndex(mailboxUid: string): Promise<boolean> {
|
|
256
|
+
updatePendingDeletions((current) => current.add(mailboxUid));
|
|
102
257
|
try {
|
|
103
|
-
await call("destroy", mailboxUid);
|
|
104
|
-
|
|
105
|
-
|
|
258
|
+
await call("destroy", { mailboxUid, generation: nextLocalIndexGeneration() } satisfies DestroyParams);
|
|
259
|
+
updatePendingDeletions((current) => current.delete(mailboxUid));
|
|
260
|
+
return true;
|
|
261
|
+
} catch (err) {
|
|
262
|
+
// `call()` only ever rejects with an Error.
|
|
263
|
+
console.warn(`Could not destroy the local search index for mailbox ${mailboxUid}: ${(err as Error).message}`);
|
|
264
|
+
return false;
|
|
106
265
|
} finally {
|
|
107
266
|
initializedMailboxes.delete(mailboxUid);
|
|
108
267
|
}
|
|
109
268
|
}
|
|
110
269
|
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
270
|
+
/** How long sign-out waits for local index destruction before navigating anyway. */
|
|
271
|
+
export const DESTROY_ALL_TIMEOUT_MS = 3_000;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Destroys **every** local index on this device - not just the mailboxes this page load opened (sign-out
|
|
275
|
+
* from Calendar, say, never opened any), by enumerating OPFS for the index directory prefix - and tells every
|
|
276
|
+
* other tab to close its own connections. Closes this tab's own open connections through the Worker first
|
|
277
|
+
* (only if one was ever spawned - there's nothing to close otherwise, and booting one just to delete files
|
|
278
|
+
* would be waste), then removes the directories. Anything left behind is retried on the next load. Resolves
|
|
279
|
+
* `true` when everything was removed within `timeoutMs`; never rejects.
|
|
280
|
+
*/
|
|
281
|
+
export async function destroyAllLocalIndexes(timeoutMs = DESTROY_ALL_TIMEOUT_MS): Promise<boolean> {
|
|
282
|
+
signedOut = true;
|
|
283
|
+
writePendingDeletions([ALL_INDEXES]);
|
|
284
|
+
try {
|
|
285
|
+
getChannel()?.postMessage({ type: "sign-out" });
|
|
286
|
+
} catch {
|
|
287
|
+
// A closed/unsupported channel only loses the cross-tab notification.
|
|
288
|
+
}
|
|
289
|
+
const work = (async () => {
|
|
290
|
+
let ok = true;
|
|
291
|
+
if (worker) {
|
|
292
|
+
const { failed } = await call<{ failed: string[] }>("destroyAll", { generation: nextLocalIndexGeneration() } satisfies GenerationParams).catch(() => ({
|
|
293
|
+
failed: ["(worker)"],
|
|
294
|
+
}));
|
|
295
|
+
ok = failed.length === 0;
|
|
296
|
+
}
|
|
297
|
+
initializedMailboxes.clear();
|
|
298
|
+
const { failed } = await removeLocalIndexDirectories();
|
|
299
|
+
writePendingDeletions(failed);
|
|
300
|
+
return ok && failed.length === 0;
|
|
301
|
+
})().catch(() => false);
|
|
302
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
303
|
+
const timeout = new Promise<boolean>((resolve) => {
|
|
304
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
305
|
+
});
|
|
306
|
+
const result = await Promise.race([work, timeout]);
|
|
307
|
+
clearTimeout(timer);
|
|
308
|
+
if (!result) {
|
|
309
|
+
console.warn("Could not destroy every local search index before signing out.");
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Removes local indexes for mailboxes outside `accessibleMailboxUids` - e.g. left behind by a different
|
|
315
|
+
* user who signed in on this device without signing out. Never rejects. */
|
|
316
|
+
export async function pruneInaccessibleLocalIndexes(accessibleMailboxUids: Iterable<string>): Promise<void> {
|
|
317
|
+
await removeLocalIndexDirectories(new Set(accessibleMailboxUids)).catch(() => undefined);
|
|
116
318
|
}
|
|
@@ -15,8 +15,11 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/** Bump whenever `CREATE_SCHEMA_SQL` changes in a way existing on-disk databases can't be reconciled
|
|
18
|
-
* with in place.
|
|
19
|
-
|
|
18
|
+
* with in place. A mismatch deletes and recreates the whole database file (not just its rows), so
|
|
19
|
+
* creation-time-only settings like `auto_vacuum` also apply to upgraded indexes.
|
|
20
|
+
*
|
|
21
|
+
* 2 - added `entities.entity_version` (incremental rebuild skip) and `auto_vacuum=INCREMENTAL`. */
|
|
22
|
+
export const SCHEMA_VERSION = 2;
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* `entities` is the real row store (metadata + the plaintext content fields), `entities_fts` is an FTS5
|
|
@@ -51,7 +54,8 @@ CREATE TABLE IF NOT EXISTS entities (
|
|
|
51
54
|
subject TEXT,
|
|
52
55
|
body TEXT,
|
|
53
56
|
attachment_text TEXT,
|
|
54
|
-
byte_size INTEGER NOT NULL DEFAULT 0
|
|
57
|
+
byte_size INTEGER NOT NULL DEFAULT 0,
|
|
58
|
+
entity_version TEXT
|
|
55
59
|
);
|
|
56
60
|
CREATE INDEX IF NOT EXISTS idx_entities_date ON entities(date_for_sort);
|
|
57
61
|
CREATE INDEX IF NOT EXISTS idx_entities_mailbox ON entities(mailbox_uid);
|
|
@@ -105,18 +109,22 @@ export interface LocalIndexEntity {
|
|
|
105
109
|
/** Rough on-disk cost of this entity's own content, in bytes - what `localIndexBuilder.ts`'s
|
|
106
110
|
* byte-budget accounting (spec §11) sums against the configured budget. */
|
|
107
111
|
byteSize: number;
|
|
112
|
+
/** Opaque change marker for the source entity (the builder uses the message's `version` plus its
|
|
113
|
+
* folder) - lets a rebuild skip re-fetching/decrypting anything already indexed unchanged. */
|
|
114
|
+
entityVersion?: string;
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
/** `entity_uid` upsert - `ON CONFLICT` (SQLite's UPSERT syntax) rather than a separate delete-then-insert,
|
|
111
118
|
* so re-indexing an already-present message (a flag changed, a folder move) updates it in place and the
|
|
112
119
|
* `entities_au` trigger keeps `entities_fts` in sync automatically. */
|
|
113
120
|
export const UPSERT_ENTITY_SQL = `
|
|
114
|
-
INSERT INTO entities (entity_type, entity_uid, mailbox_uid, folder_uid, date_for_sort, participants, flags, has_attachments, subject, body, attachment_text, byte_size)
|
|
115
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
121
|
+
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
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
116
123
|
ON CONFLICT(entity_uid) DO UPDATE SET
|
|
117
124
|
folder_uid = excluded.folder_uid, date_for_sort = excluded.date_for_sort, participants = excluded.participants,
|
|
118
125
|
flags = excluded.flags, has_attachments = excluded.has_attachments, subject = excluded.subject,
|
|
119
|
-
body = excluded.body, attachment_text = excluded.attachment_text, byte_size = excluded.byte_size
|
|
126
|
+
body = excluded.body, attachment_text = excluded.attachment_text, byte_size = excluded.byte_size,
|
|
127
|
+
entity_version = excluded.entity_version
|
|
120
128
|
`;
|
|
121
129
|
|
|
122
130
|
/** Bind values for `UPSERT_ENTITY_SQL`, in column order - kept alongside it so the two can never drift
|
|
@@ -135,6 +143,7 @@ export function entityBindValues(entity: LocalIndexEntity): (string | number)[]
|
|
|
135
143
|
entity.body ?? null!,
|
|
136
144
|
entity.attachmentText ?? null!,
|
|
137
145
|
entity.byteSize,
|
|
146
|
+
entity.entityVersion ?? null!,
|
|
138
147
|
];
|
|
139
148
|
}
|
|
140
149
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* The Tier 2 local index's on-disk naming and bulk-removal helpers, shared by the Worker
|
|
7
|
+
* (`localIndexWorker.ts`) and the main thread (`localIndexRpcClient.ts`). Deliberately free of any
|
|
8
|
+
* wa-sqlite import so the main thread can use it without pulling the WASM build into its own bundle.
|
|
9
|
+
*
|
|
10
|
+
* Every mailbox's index lives in its own top-level OPFS directory named `rapidmx-localsearch-<mailboxUid>`
|
|
11
|
+
* (`AccessHandlePoolVFS` creates it from the VFS name). Enumerating that prefix is how sign-out destroys
|
|
12
|
+
* *every* index on this device - including ones for mailboxes this page load never opened, which the
|
|
13
|
+
* RPC client's own session-scoped bookkeeping can't know about - and how a sign-in prunes indexes left
|
|
14
|
+
* behind for mailboxes the current user can no longer access.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const LOCAL_INDEX_POOL_PREFIX = "rapidmx-localsearch-";
|
|
18
|
+
|
|
19
|
+
/** The OPFS directory name (and `EncryptingVFS` name) a mailbox's index lives under. */
|
|
20
|
+
export function poolNameFor(mailboxUid: string): string {
|
|
21
|
+
return `${LOCAL_INDEX_POOL_PREFIX}${mailboxUid}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Inverse of `poolNameFor()`; `undefined` for any OPFS entry that isn't a local index directory. */
|
|
25
|
+
export function mailboxUidFromPoolName(name: string): string | undefined {
|
|
26
|
+
return name.startsWith(LOCAL_INDEX_POOL_PREFIX) && name.length > LOCAL_INDEX_POOL_PREFIX.length
|
|
27
|
+
? name.slice(LOCAL_INDEX_POOL_PREFIX.length)
|
|
28
|
+
: undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** `navigator.storage.getDirectory()`, or `undefined` where OPFS isn't available at all (older browsers,
|
|
32
|
+
* jsdom/node) - there is nothing on disk to remove in that case. */
|
|
33
|
+
async function getOpfsRoot(): Promise<FileSystemDirectoryHandle | undefined> {
|
|
34
|
+
const storage = typeof navigator === "undefined" ? undefined : navigator.storage;
|
|
35
|
+
if (!storage?.getDirectory) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return storage.getDirectory();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isNotFound(err: unknown): boolean {
|
|
42
|
+
return (err as { name?: string } | undefined)?.name === "NotFoundError";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Removes one mailbox's index directory. Resolves quietly when it doesn't exist; rejects for any other
|
|
46
|
+
* failure (most commonly `NoModificationAllowedError` - another tab still holds its access handles). */
|
|
47
|
+
export async function removeLocalIndexDirectory(mailboxUid: string): Promise<void> {
|
|
48
|
+
const root = await getOpfsRoot();
|
|
49
|
+
if (!root) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
await root.removeEntry(poolNameFor(mailboxUid), { recursive: true });
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if (!isNotFound(err)) {
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface LocalIndexRemovalResult {
|
|
62
|
+
removed: string[];
|
|
63
|
+
/** Mailbox uids whose directory could not be removed (e.g. still open in another tab). */
|
|
64
|
+
failed: string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Every mailbox uid that has a local index directory on this origin. */
|
|
68
|
+
export async function listLocalIndexMailboxUids(): Promise<string[]> {
|
|
69
|
+
const root = await getOpfsRoot();
|
|
70
|
+
if (!root) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
const mailboxUids: string[] = [];
|
|
74
|
+
// `keys()` is part of the OPFS spec and every browser that has `getDirectory()` at all, but isn't in
|
|
75
|
+
// this TypeScript version's DOM lib yet.
|
|
76
|
+
for await (const name of (root as unknown as { keys(): AsyncIterable<string> }).keys()) {
|
|
77
|
+
const mailboxUid = mailboxUidFromPoolName(name);
|
|
78
|
+
if (mailboxUid) {
|
|
79
|
+
mailboxUids.push(mailboxUid);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return mailboxUids;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Removes every local index directory on this origin except those whose mailbox uid is in `keep`. */
|
|
86
|
+
export async function removeLocalIndexDirectories(keep: ReadonlySet<string> = new Set()): Promise<LocalIndexRemovalResult> {
|
|
87
|
+
const result: LocalIndexRemovalResult = { removed: [], failed: [] };
|
|
88
|
+
const mailboxUids = (await listLocalIndexMailboxUids()).filter((mailboxUid) => !keep.has(mailboxUid));
|
|
89
|
+
for (const mailboxUid of mailboxUids) {
|
|
90
|
+
try {
|
|
91
|
+
await removeLocalIndexDirectory(mailboxUid);
|
|
92
|
+
result.removed.push(mailboxUid);
|
|
93
|
+
} catch {
|
|
94
|
+
result.failed.push(mailboxUid);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
@@ -186,7 +186,38 @@ export class EncryptingVFS extends FacadeVFS {
|
|
|
186
186
|
return this.#inner.jWrite(pFile, physical, blockIndex * PHYSICAL_BLOCK_SIZE);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
/** Set once any block has failed to decrypt/authenticate (or the inner VFS failed a read/write outright)
|
|
190
|
+
* - `localIndexWorker.ts` checks this after a failed SQLite call to tell real corruption (discard and
|
|
191
|
+
* rebuild, spec §11 "Invalidation") apart from an ordinary error such as a malformed FTS5 query. */
|
|
192
|
+
corruptionDetected = false;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* `jRead`/`jWrite` MUST NOT throw: with the Asyncify build, an exception escaping an async VFS method
|
|
196
|
+
* never reaches the awaiting `sqlite3.*` call - it surfaces only as an unhandled rejection and the
|
|
197
|
+
* SQLite call hangs forever (confirmed by direct reproduction in node against the real wa-sqlite build).
|
|
198
|
+
* A thrown `PageCorruptedError` therefore used to wedge the whole connection instead of triggering a
|
|
199
|
+
* rebuild. Errors are converted to an I/O error return code here, which SQLite does propagate as a
|
|
200
|
+
* normal `SQLiteError`, and recorded on `corruptionDetected`.
|
|
201
|
+
*/
|
|
189
202
|
async jRead(pFile: number, pData: Uint8Array, iOffset: number): Promise<number> {
|
|
203
|
+
try {
|
|
204
|
+
return await this.#jReadBlocks(pFile, pData, iOffset);
|
|
205
|
+
} catch {
|
|
206
|
+
this.corruptionDetected = true;
|
|
207
|
+
return VFS.SQLITE_IOERR_READ;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async jWrite(pFile: number, pData: Uint8Array, iOffset: number): Promise<number> {
|
|
212
|
+
try {
|
|
213
|
+
return await this.#jWriteBlocks(pFile, pData, iOffset);
|
|
214
|
+
} catch {
|
|
215
|
+
this.corruptionDetected = true;
|
|
216
|
+
return VFS.SQLITE_IOERR_WRITE;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async #jReadBlocks(pFile: number, pData: Uint8Array, iOffset: number): Promise<number> {
|
|
190
221
|
const filename = this.#filenamesByFileId.get(pFile) ?? `(unknown:${pFile})`;
|
|
191
222
|
const startBlock = Math.floor(iOffset / LOGICAL_BLOCK_SIZE);
|
|
192
223
|
const endBlock = Math.floor((iOffset + pData.length - 1) / LOGICAL_BLOCK_SIZE);
|
|
@@ -207,7 +238,7 @@ export class EncryptingVFS extends FacadeVFS {
|
|
|
207
238
|
return anyAbsent ? VFS.SQLITE_IOERR_SHORT_READ : VFS.SQLITE_OK;
|
|
208
239
|
}
|
|
209
240
|
|
|
210
|
-
async
|
|
241
|
+
async #jWriteBlocks(pFile: number, pData: Uint8Array, iOffset: number): Promise<number> {
|
|
211
242
|
const filename = this.#filenamesByFileId.get(pFile) ?? `(unknown:${pFile})`;
|
|
212
243
|
const startBlock = Math.floor(iOffset / LOGICAL_BLOCK_SIZE);
|
|
213
244
|
const endBlock = Math.floor((iOffset + pData.length - 1) / LOGICAL_BLOCK_SIZE);
|