@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
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React, { RefObject } from "react";
|
|
2
|
+
export interface EmojiPickerProps {
|
|
3
|
+
anchorRef: RefObject<HTMLElement | null>;
|
|
4
|
+
onSelect: (native: string) => void;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* "Insert emoji" popup — a scrollable grid of every standard unicode emoji, grouped by category (see
|
|
9
|
+
* `emojiData.ts`). Deliberately no search box, unlike `GifPicker`'s — the user's own request only
|
|
10
|
+
* asked for a browsable grid here, and 8 category headers over ~1900 emojis is already a reasonably
|
|
11
|
+
* navigable amount without one. Rendered via `PopoverPortal` — see that component's own doc comment
|
|
12
|
+
* on why (escaping the toolbar's `overflow-hidden` ancestors).
|
|
13
|
+
*/
|
|
14
|
+
export default function EmojiPicker({ anchorRef, onSelect, onClose }: EmojiPickerProps): React.JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { RefObject } from "react";
|
|
2
|
+
export interface GifPickerProps {
|
|
3
|
+
anchorRef: RefObject<HTMLElement | null>;
|
|
4
|
+
onSelect: (url: string) => void;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
}
|
|
7
|
+
/** "Insert GIF" popup — a search box plus a scrollable grid of Giphy thumbnails (trending by default,
|
|
8
|
+
* live-searched as the user types). Selecting one inserts its full-resolution URL into the message
|
|
9
|
+
* body — see `ComposeToolbar.tsx`'s own doc comment on why that's a plain `<img>` reference rather
|
|
10
|
+
* than an uploaded attachment (unlike "Insert image"): a GIF is already hosted at a stable public
|
|
11
|
+
* URL, so there's nothing to upload. Rendered via `PopoverPortal` — see that component's own doc
|
|
12
|
+
* comment on why (escaping the toolbar's `overflow-hidden` ancestors). */
|
|
13
|
+
export default function GifPicker({ anchorRef, onSelect, onClose }: GifPickerProps): React.JSX.Element;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface RichTextEditorProps {
|
|
3
|
+
value: string;
|
|
4
|
+
onChange: (value: string) => void;
|
|
5
|
+
height?: string;
|
|
6
|
+
/** When true, the editor fills its parent's height via flexbox instead of a fixed `height` — for
|
|
7
|
+
* embedding inside a flex-column container of variable height (the floating Compose window) where
|
|
8
|
+
* a literal pixel height can't be computed up front. `height` is ignored when this is set. */
|
|
9
|
+
fill?: boolean;
|
|
10
|
+
/** Passed straight through to `ComposeToolbar` — see its own doc comment on this prop. */
|
|
11
|
+
onUploadImage: (file: File) => Promise<string | null>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A WYSIWYG rich-text editor for the Compose window's message body — replaces `MonacoHtmlEditor` (an HTML
|
|
15
|
+
* *source* editor) with a real Outlook-Home-tab-style formatting experience, built on TipTap/ProseMirror.
|
|
16
|
+
* Used by `ComposeWindow` (the floating overlay, not a dedicated page — see `ComposeContext.tsx`'s doc
|
|
17
|
+
* comment).
|
|
18
|
+
*
|
|
19
|
+
* `value` seeds the editor's initial content only — like `MonacoHtmlEditor`, this never re-syncs from a
|
|
20
|
+
* later `value` prop change; TipTap owns its own document once created. Live edits flow out as HTML (via
|
|
21
|
+
* `editor.getHTML()`) through `onChange`.
|
|
22
|
+
*
|
|
23
|
+
* `immediatelyRender: false` is required for this framework's SSR: the page embedding `ComposeWindow` is
|
|
24
|
+
* rendered server-side under plain Node before hydration (see `ReactRoute`), and TipTap's default
|
|
25
|
+
* (`immediatelyRender: true`) tries to mount its ProseMirror view during that very first render, which
|
|
26
|
+
* both can't work without a real DOM and produces a hydration mismatch. With it `false`, `useEditor`
|
|
27
|
+
* returns `null` until the editor actually mounts client-side after hydration — `ComposeToolbar`/
|
|
28
|
+
* `EditorContent` both already handle a `null` editor by rendering their disabled/empty state, so this
|
|
29
|
+
* needs no extra loading-placeholder logic here. Unlike `MonacoHtmlEditor`, no dynamic `import()` is
|
|
30
|
+
* needed to defer loading TipTap's modules off the SSR path — `immediatelyRender: false` alone is
|
|
31
|
+
* sufficient here since TipTap (unlike Monaco) never touches the DOM at module-evaluation time, only when
|
|
32
|
+
* an editor view actually mounts.
|
|
33
|
+
*/
|
|
34
|
+
export default function RichTextEditor({ value, onChange, height, fill, onUploadImage }: RichTextEditorProps): React.JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React, { RefObject } from "react";
|
|
2
|
+
export interface ScheduleSendPickerProps {
|
|
3
|
+
anchorRef: RefObject<HTMLElement | null>;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
/** Called with the chosen instant as a UTC ISO string, already validated to be in the future. */
|
|
6
|
+
onSchedule: (scheduledSendTimeIso: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The "Send later" popover next to Compose's Send button — same `PopoverPortal` shell
|
|
10
|
+
* `EmojiPicker`/`GifPicker`/`ResourcePicker` already use, just holding a single `datetime-local` input
|
|
11
|
+
* instead of a grid/list. Validates the chosen time is genuinely in the future before calling
|
|
12
|
+
* `onSchedule` — `@rapidmx/restapi`'s own `send()` only defers when `scheduledSendTime` is strictly
|
|
13
|
+
* later than "now", so a past/immediate pick here would silently send right away instead of scheduling,
|
|
14
|
+
* which would surprise a reader expecting the picker's own choice to be honored.
|
|
15
|
+
*/
|
|
16
|
+
export default function ScheduleSendPicker({ anchorRef, onClose, onSchedule }: ScheduleSendPickerProps): React.JSX.Element;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Saves a compose window's unsaved edits (or waits for a save already on the wire), resolving to `false` when
|
|
2
|
+
* something the window holds is still unsaved afterwards (any other value counts as saved). Never rejects. */
|
|
3
|
+
export type ComposeFlush = () => Promise<unknown>;
|
|
4
|
+
/**
|
|
5
|
+
* Marks the app as signing out (explicitly, or forced by a sign-out in another tab), so compose windows stop
|
|
6
|
+
* asking the browser to confirm leaving the page - the "Leave site?" prompt would otherwise let the user cancel
|
|
7
|
+
* the sign-out's own navigation and stay on a page whose session is being ended. Call it before flushing drafts
|
|
8
|
+
* and navigating. `clearSigningOut()` undoes it (a sign-out that didn't leave the page after all).
|
|
9
|
+
*/
|
|
10
|
+
export declare function markSigningOut(): void;
|
|
11
|
+
/** Undoes `markSigningOut()`. */
|
|
12
|
+
export declare function clearSigningOut(): void;
|
|
13
|
+
/** Whether `markSigningOut()` has been called (and not cleared). */
|
|
14
|
+
export declare function isSigningOut(): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Registers an open compose window's draft flush, so leaving the app on purpose (Sign Out) can save the
|
|
17
|
+
* last edits still waiting on the autosave debounce before the session ends. Returns the unregister
|
|
18
|
+
* function. Module-level rather than React context: `AppShell` (the sign-out handler) renders the
|
|
19
|
+
* `ComposeProvider` itself, so it can't read that provider's context.
|
|
20
|
+
*/
|
|
21
|
+
export declare function registerComposeFlush(flush: ComposeFlush): () => void;
|
|
22
|
+
/**
|
|
23
|
+
* Runs every registered flush and waits for all of them, but no longer than `timeoutMs`. Resolves to `true`
|
|
24
|
+
* only when every window reported its content saved in time (a window whose save failed also shows the user
|
|
25
|
+
* why, with Discard / Keep editing). Never rejects.
|
|
26
|
+
*/
|
|
27
|
+
export declare function flushComposeDrafts(timeoutMs: number): Promise<boolean>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
const flushers = new Set();
|
|
6
|
+
let signingOut = false;
|
|
7
|
+
/**
|
|
8
|
+
* Marks the app as signing out (explicitly, or forced by a sign-out in another tab), so compose windows stop
|
|
9
|
+
* asking the browser to confirm leaving the page - the "Leave site?" prompt would otherwise let the user cancel
|
|
10
|
+
* the sign-out's own navigation and stay on a page whose session is being ended. Call it before flushing drafts
|
|
11
|
+
* and navigating. `clearSigningOut()` undoes it (a sign-out that didn't leave the page after all).
|
|
12
|
+
*/
|
|
13
|
+
export function markSigningOut() {
|
|
14
|
+
signingOut = true;
|
|
15
|
+
}
|
|
16
|
+
/** Undoes `markSigningOut()`. */
|
|
17
|
+
export function clearSigningOut() {
|
|
18
|
+
signingOut = false;
|
|
19
|
+
}
|
|
20
|
+
/** Whether `markSigningOut()` has been called (and not cleared). */
|
|
21
|
+
export function isSigningOut() {
|
|
22
|
+
return signingOut;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Registers an open compose window's draft flush, so leaving the app on purpose (Sign Out) can save the
|
|
26
|
+
* last edits still waiting on the autosave debounce before the session ends. Returns the unregister
|
|
27
|
+
* function. Module-level rather than React context: `AppShell` (the sign-out handler) renders the
|
|
28
|
+
* `ComposeProvider` itself, so it can't read that provider's context.
|
|
29
|
+
*/
|
|
30
|
+
export function registerComposeFlush(flush) {
|
|
31
|
+
flushers.add(flush);
|
|
32
|
+
return () => {
|
|
33
|
+
flushers.delete(flush);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Runs every registered flush and waits for all of them, but no longer than `timeoutMs`. Resolves to `true`
|
|
38
|
+
* only when every window reported its content saved in time (a window whose save failed also shows the user
|
|
39
|
+
* why, with Discard / Keep editing). Never rejects.
|
|
40
|
+
*/
|
|
41
|
+
export async function flushComposeDrafts(timeoutMs) {
|
|
42
|
+
let timer;
|
|
43
|
+
const timeout = new Promise((resolve) => {
|
|
44
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
45
|
+
});
|
|
46
|
+
const all = Promise.all([...flushers].map((flush) => flush().catch(() => false))).then((results) => results.every((result) => result !== false));
|
|
47
|
+
const saved = await Promise.race([all, timeout]);
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
return saved;
|
|
50
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React, { PropsWithChildren } from "react";
|
|
2
|
+
import { Folder, Mailbox } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
3
|
+
import { AppShellProps } from "../../layout/AppShell.js";
|
|
4
|
+
export type MailShellProps = Omit<AppShellProps, "active">;
|
|
5
|
+
/** One mailbox's own mail folders (already filtered to `MAIL_FOLDER_TYPES` and unsorted) - one entry per
|
|
6
|
+
* mailbox in `mailboxes`, fetched in parallel so every accessible mailbox's folder tree can render
|
|
7
|
+
* simultaneously (see `MailShell`'s own doc comment on why this replaced the old single-mailbox `folders`
|
|
8
|
+
* field). `error` is set (and `folders` left empty) for a mailbox whose own `listFolders()` call failed -
|
|
9
|
+
* one mailbox's fetch failure must never blank out every other mailbox's section. */
|
|
10
|
+
export interface MailboxFolders {
|
|
11
|
+
mailbox: Mailbox;
|
|
12
|
+
folders: Folder[];
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
/** The well-known mail folder types an "aggregate" pseudo-folder can merge across every accessible
|
|
16
|
+
* mailbox - a fixed, small set: `outbox` is deliberately excluded (transient per-mailbox send-queue
|
|
17
|
+
* state, not a "merge across mailboxes" concept), and there's no aggregate concept for `calendar`/
|
|
18
|
+
* `contacts`/`tasks`/`notes`/`user` folders (Mail's own sidebar already excludes those - see
|
|
19
|
+
* `MAIL_FOLDER_TYPES`). */
|
|
20
|
+
export declare const AGGREGATE_FOLDER_TYPES: readonly ["inbox", "sent_items", "drafts", "deleted_items", "junk"];
|
|
21
|
+
export type AggregateFolderType = (typeof AGGREGATE_FOLDER_TYPES)[number];
|
|
22
|
+
export interface MailShellContextValue {
|
|
23
|
+
/** The mailbox currently selected (`?mailboxUid=`) - `undefined` while an aggregate pseudo-folder is
|
|
24
|
+
* active instead (`aggregateFolderType` set), since there's no single mailbox to speak of then. */
|
|
25
|
+
mailboxUid?: string;
|
|
26
|
+
/** The folder currently selected (`?folderUid=`, or the selected mailbox's Inbox) - `undefined` in
|
|
27
|
+
* aggregate mode, for the same reason. */
|
|
28
|
+
folderUid?: string;
|
|
29
|
+
/** Set only when an aggregate pseudo-folder (`?aggregate=inbox` etc.) is selected instead of a real,
|
|
30
|
+
* single mailbox+folder - mutually exclusive with `mailboxUid`/`folderUid` above. */
|
|
31
|
+
aggregateFolderType?: AggregateFolderType;
|
|
32
|
+
mailboxes: Mailbox[];
|
|
33
|
+
/** Every accessible mailbox's own mail folders - replaces the old single-mailbox `folders: Folder[]`
|
|
34
|
+
* now that every mailbox's tree renders at once (see `MailboxFolders`'s own doc comment). */
|
|
35
|
+
mailboxFolders: MailboxFolders[];
|
|
36
|
+
}
|
|
37
|
+
/** Reads the mailbox/folder a page is currently showing, as resolved by the enclosing `MailShell`. */
|
|
38
|
+
export declare function useMailShell(): MailShellContextValue;
|
|
39
|
+
/** Page size of the one `listMailboxes()` call this shell makes. */
|
|
40
|
+
export declare const MAILBOX_LIST_LIMIT = 100;
|
|
41
|
+
/**
|
|
42
|
+
* Mail's own contextual sidebar (every accessible mailbox's own folder tree, plus a merged "All Mailboxes"
|
|
43
|
+
* aggregate section) + content area, rendered inside the shared `AppShell` chrome (icon rail, header,
|
|
44
|
+
* impersonation banner — see that component). There is no client-side router in this framework (see
|
|
45
|
+
* `ReactRoute`'s file-convention resolver) — the selected mailbox/folder (or aggregate pseudo-folder) live
|
|
46
|
+
* in the URL's `?mailboxUid=`/`?folderUid=`/`?aggregate=` query params, read once on mount (never during
|
|
47
|
+
* the initial render itself, matching every other query-param reader in this codebase — e.g.
|
|
48
|
+
* `apps/admin/quarantine`'s `readMailboxUid()` — so the server-rendered and just-hydrated client markup
|
|
49
|
+
* match).
|
|
50
|
+
*
|
|
51
|
+
* Every accessible mailbox's folder tree renders at once - there is no "switch mailbox" affordance
|
|
52
|
+
* anymore (the mailbox `<select>` this shell used to have is gone) - so a single-mailbox user sees zero
|
|
53
|
+
* behavior change from before this, and a multi-mailbox user sees every mailbox's folders (and the new
|
|
54
|
+
* aggregate section) simultaneously, matching the shared-mailbox feature's own "display as a separate set
|
|
55
|
+
* of folders" requirement.
|
|
56
|
+
*/
|
|
57
|
+
export default function MailShell({ userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, pluginNav, children, }: PropsWithChildren<MailShellProps>): React.JSX.Element;
|
|
@@ -57,6 +57,8 @@ function sortedFoldersOf(folders) {
|
|
|
57
57
|
function aggregateUnreadCount(mailboxFolders, type) {
|
|
58
58
|
return mailboxFolders.reduce((total, mf) => total + (mf.folders.find((f) => f.type === type)?.unreadCount ?? 0), 0);
|
|
59
59
|
}
|
|
60
|
+
/** Page size of the one `listMailboxes()` call this shell makes. */
|
|
61
|
+
export const MAILBOX_LIST_LIMIT = 100;
|
|
60
62
|
/**
|
|
61
63
|
* A separate component (not inlined into `MailShell`'s own render) so `useCompose()` resolves against
|
|
62
64
|
* `ComposeProvider` correctly: that provider is rendered *inside* the `AppShell` that `MailShell` itself
|
|
@@ -88,7 +90,7 @@ function ComposeButton() {
|
|
|
88
90
|
* aggregate section) simultaneously, matching the shared-mailbox feature's own "display as a separate set
|
|
89
91
|
* of folders" requirement.
|
|
90
92
|
*/
|
|
91
|
-
export default function MailShell({ userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, children, }) {
|
|
93
|
+
export default function MailShell({ userUid, authServerUrl, impersonating, impersonationBaseUrl, trusted, pluginNav, children, }) {
|
|
92
94
|
const [status, setStatus] = useState("checking");
|
|
93
95
|
const [error, setError] = useState(null);
|
|
94
96
|
const [mailboxes, setMailboxes] = useState([]);
|
|
@@ -108,7 +110,7 @@ export default function MailShell({ userUid, authServerUrl, impersonating, imper
|
|
|
108
110
|
if (!userUid) {
|
|
109
111
|
return;
|
|
110
112
|
}
|
|
111
|
-
listMailboxes({ limit:
|
|
113
|
+
listMailboxes({ limit: MAILBOX_LIST_LIMIT })
|
|
112
114
|
.then((result) => {
|
|
113
115
|
setMailboxes(result);
|
|
114
116
|
setStatus("ready");
|
|
@@ -138,7 +140,7 @@ export default function MailShell({ userUid, authServerUrl, impersonating, imper
|
|
|
138
140
|
return;
|
|
139
141
|
}
|
|
140
142
|
setFoldersLoading(true);
|
|
141
|
-
Promise.all(mailboxes.map((mailbox) => listFolders(mailbox.uid)
|
|
143
|
+
void Promise.all(mailboxes.map((mailbox) => listFolders(mailbox.uid)
|
|
142
144
|
.then((result) => ({ mailbox, folders: result.filter((f) => MAIL_FOLDER_TYPES.has(f.type)) }))
|
|
143
145
|
.catch((err) => ({
|
|
144
146
|
mailbox,
|
|
@@ -210,5 +212,12 @@ export default function MailShell({ userUid, authServerUrl, impersonating, imper
|
|
|
210
212
|
// (ComposeWindow, MessageDetailPane) request an unlock on demand via useUnlockPrompt() instead.
|
|
211
213
|
// First-time provisioning (a mailbox with no vault at all yet) still always blocks - see this
|
|
212
214
|
// prop's own doc comment on KeyEnrollmentGateProps.
|
|
213
|
-
blocking: false,
|
|
215
|
+
blocking: false,
|
|
216
|
+
// A shared/delegated mailbox the caller doesn't own is never provisioned from here - its keys
|
|
217
|
+
// belong to its owner (or its admins), not to whoever happens to open it first - and neither is
|
|
218
|
+
// the impersonated user's own mailbox: an administrator must never choose its password/recovery codes.
|
|
219
|
+
canProvision: !impersonating && activeMailbox?.ownerUserUid === userUid, children: [_jsx(LocalIndexLifecycle, { mailboxUid: activeMailboxUid, mailboxKeys: activeMailbox?.keys, folders: mailboxFolders.find((mf) => mf.mailbox.uid === activeMailboxUid)?.folders ?? [],
|
|
220
|
+
// A full page may be truncated - pruning against it would delete indexes of accessible mailboxes
|
|
221
|
+
// beyond the limit, so it's only trusted as the complete list when it came back short.
|
|
222
|
+
accessibleMailboxUids: status === "ready" && mailboxes.length < MAILBOX_LIST_LIMIT ? mailboxes.map((mb) => mb.uid) : undefined }), _jsx(AppShell, { active: "mail", userUid: userUid, authServerUrl: authServerUrl, impersonating: impersonating, impersonationBaseUrl: impersonationBaseUrl, trusted: trusted, pluginNav: pluginNav, children: inner })] }));
|
|
214
223
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The trusted signing-key fingerprints for a received message's sender, for `evaluateMessageSecurity()`'s
|
|
3
|
+
* `pinnedSignerFingerprints`: the keys key discovery already pinned on the contacts in the reading mailbox's own
|
|
4
|
+
* contacts folder(s). Reads only stored contacts - never triggers a Discovery lookup, which must not happen on
|
|
5
|
+
* message receipt.
|
|
6
|
+
*
|
|
7
|
+
* `MessageDetailPane` renders once per opened message (and once per message in a thread), so each mailbox's
|
|
8
|
+
* contact list is fetched once and reused for `CONTACTS_CACHE_TTL_MS`; a failed load isn't cached. Any failure
|
|
9
|
+
* leaves the caller with no pins, which `evaluateMessageSecurity()` reports as an unverified signer - never as
|
|
10
|
+
* verified.
|
|
11
|
+
*
|
|
12
|
+
* The cache is dropped (`clearPinnedSignerCache()`) whenever a key session locks (subscribed on first use), on sign-out
|
|
13
|
+
* (`AppShell`), and after the reader adds, edits or deletes contacts (the contacts pages) - so a contact removed
|
|
14
|
+
* because its key was revoked doesn't keep vouching for signatures until the TTL runs out.
|
|
15
|
+
*/
|
|
16
|
+
import { SignerKeyState } from "@rapidmx/react-shared/contacts/contactsApi.js";
|
|
17
|
+
/** How long one mailbox's loaded contact list is reused before it is fetched again. */
|
|
18
|
+
export declare const CONTACTS_CACHE_TTL_MS = 60000;
|
|
19
|
+
/** Forgets every cached contact list - on sign-out, a key-session lock, after the reader changes their contacts, and
|
|
20
|
+
* in tests. */
|
|
21
|
+
export declare function clearPinnedSignerCache(): void;
|
|
22
|
+
/** The sender `address`'s pinned signing fingerprints from `mailboxUid`'s contacts. Rejects if they can't be loaded. */
|
|
23
|
+
export declare function getPinnedSignerFingerprints(mailboxUid: string, address: string): Promise<string[]>;
|
|
24
|
+
/** A sender's signing-key state (`signerKeyStateFor()`) plus what a key-changed notice needs to show and link. */
|
|
25
|
+
export interface SenderKeyState extends SignerKeyState {
|
|
26
|
+
/** The contact holding the first pinned signing key, else the one holding the conflict, else the first match. */
|
|
27
|
+
contactUid?: string;
|
|
28
|
+
/** When the first pinned signing key started being trusted - see `keyPinnedSince()`. */
|
|
29
|
+
pinnedSince?: number;
|
|
30
|
+
}
|
|
31
|
+
/** The sender `address`'s signing-key state from `mailboxUid`'s (cached) contacts, for a "signing key changed" comparison
|
|
32
|
+
* or to spot a recorded conflict. Reads stored contacts only. Rejects if they can't be loaded. */
|
|
33
|
+
export declare function getSignerKeyState(mailboxUid: string, address: string): Promise<SenderKeyState>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* The trusted signing-key fingerprints for a received message's sender, for `evaluateMessageSecurity()`'s
|
|
7
|
+
* `pinnedSignerFingerprints`: the keys key discovery already pinned on the contacts in the reading mailbox's own
|
|
8
|
+
* contacts folder(s). Reads only stored contacts - never triggers a Discovery lookup, which must not happen on
|
|
9
|
+
* message receipt.
|
|
10
|
+
*
|
|
11
|
+
* `MessageDetailPane` renders once per opened message (and once per message in a thread), so each mailbox's
|
|
12
|
+
* contact list is fetched once and reused for `CONTACTS_CACHE_TTL_MS`; a failed load isn't cached. Any failure
|
|
13
|
+
* leaves the caller with no pins, which `evaluateMessageSecurity()` reports as an unverified signer - never as
|
|
14
|
+
* verified.
|
|
15
|
+
*
|
|
16
|
+
* The cache is dropped (`clearPinnedSignerCache()`) whenever a key session locks (subscribed on first use), on sign-out
|
|
17
|
+
* (`AppShell`), and after the reader adds, edits or deletes contacts (the contacts pages) - so a contact removed
|
|
18
|
+
* because its key was revoked doesn't keep vouching for signatures until the TTL runs out.
|
|
19
|
+
*/
|
|
20
|
+
import { PINNED_FINGERPRINT_MAX_PAGES, PINNED_FINGERPRINT_PAGE_SIZE, listContacts, pinnedSigningFingerprintsFor, signerKeyStateFor, } from "@rapidmx/react-shared/contacts/contactsApi.js";
|
|
21
|
+
import { keyPinnedSince } from "../contacts/contactKeys.js";
|
|
22
|
+
import { listFolders } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
23
|
+
import { subscribeKeySession } from "@rapidmx/react-shared/crypto/keySession.js";
|
|
24
|
+
/** How long one mailbox's loaded contact list is reused before it is fetched again. */
|
|
25
|
+
export const CONTACTS_CACHE_TTL_MS = 60000;
|
|
26
|
+
const contactsCache = new Map();
|
|
27
|
+
/** Forgets every cached contact list - on sign-out, a key-session lock, after the reader changes their contacts, and
|
|
28
|
+
* in tests. */
|
|
29
|
+
export function clearPinnedSignerCache() {
|
|
30
|
+
contactsCache.clear();
|
|
31
|
+
}
|
|
32
|
+
let lockSubscribed = false;
|
|
33
|
+
/** Subscribes (once, lazily - a module-level subscription would run in every page that merely imports this) to key
|
|
34
|
+
* session changes, clearing the cache whenever any mailbox's keys are locked. */
|
|
35
|
+
function subscribeToLocksOnce() {
|
|
36
|
+
if (lockSubscribed) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
lockSubscribed = true;
|
|
40
|
+
subscribeKeySession((event) => {
|
|
41
|
+
if (event.state === "locked") {
|
|
42
|
+
clearPinnedSignerCache();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async function loadMailboxContacts(mailboxUid) {
|
|
47
|
+
const folders = await listFolders(mailboxUid);
|
|
48
|
+
const contacts = [];
|
|
49
|
+
for (const folder of folders.filter((f) => f.type === "contacts")) {
|
|
50
|
+
for (let page = 0; page < PINNED_FINGERPRINT_MAX_PAGES; page++) {
|
|
51
|
+
const batch = await listContacts(folder.uid, { limit: PINNED_FINGERPRINT_PAGE_SIZE, page });
|
|
52
|
+
contacts.push(...batch);
|
|
53
|
+
if (batch.length < PINNED_FINGERPRINT_PAGE_SIZE) {
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return contacts;
|
|
59
|
+
}
|
|
60
|
+
/** `mailboxUid`'s contacts, from the cache when fresh. Rejects if they can't be loaded. */
|
|
61
|
+
function cachedMailboxContacts(mailboxUid) {
|
|
62
|
+
subscribeToLocksOnce();
|
|
63
|
+
let entry = contactsCache.get(mailboxUid);
|
|
64
|
+
if (!entry || Date.now() - entry.loadedAt > CONTACTS_CACHE_TTL_MS) {
|
|
65
|
+
const created = { loadedAt: Date.now(), contacts: loadMailboxContacts(mailboxUid) };
|
|
66
|
+
contactsCache.set(mailboxUid, created);
|
|
67
|
+
created.contacts.catch(() => {
|
|
68
|
+
if (contactsCache.get(mailboxUid) === created) {
|
|
69
|
+
contactsCache.delete(mailboxUid);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
entry = created;
|
|
73
|
+
}
|
|
74
|
+
return entry.contacts;
|
|
75
|
+
}
|
|
76
|
+
/** The sender `address`'s pinned signing fingerprints from `mailboxUid`'s contacts. Rejects if they can't be loaded. */
|
|
77
|
+
export async function getPinnedSignerFingerprints(mailboxUid, address) {
|
|
78
|
+
return pinnedSigningFingerprintsFor(await cachedMailboxContacts(mailboxUid), address);
|
|
79
|
+
}
|
|
80
|
+
/** The sender `address`'s signing-key state from `mailboxUid`'s (cached) contacts, for a "signing key changed" comparison
|
|
81
|
+
* or to spot a recorded conflict. Reads stored contacts only. Rejects if they can't be loaded. */
|
|
82
|
+
export async function getSignerKeyState(mailboxUid, address) {
|
|
83
|
+
const contacts = await cachedMailboxContacts(mailboxUid);
|
|
84
|
+
const state = signerKeyStateFor(contacts, address);
|
|
85
|
+
const wanted = address.trim().toLowerCase();
|
|
86
|
+
const matching = contacts.filter((contact) => contact.emails.some((email) => email.address.trim().toLowerCase() === wanted));
|
|
87
|
+
// `signerKeyStateFor()` keeps the contacts' own key objects, so the holder is found by identity.
|
|
88
|
+
const pinned = state.pinned[0];
|
|
89
|
+
const holder = matching.find((candidate) => (candidate.keys ?? []).includes(pinned));
|
|
90
|
+
const contact = holder ??
|
|
91
|
+
matching.find((candidate) => (candidate.keyConflicts ?? []).some((conflict) => conflict.useType === "sign")) ??
|
|
92
|
+
matching[0];
|
|
93
|
+
return {
|
|
94
|
+
...state,
|
|
95
|
+
...(contact ? { contactUid: contact.uid } : {}),
|
|
96
|
+
...(holder ? { pinnedSince: keyPinnedSince(holder, pinned) } : {}),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Message } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
2
|
+
import type { SealToWrite } from "@rapidmx/react-shared/crypto/messageSecurity.js";
|
|
3
|
+
/** How long one mailbox's vault generation is reused before it is read again. */
|
|
4
|
+
export declare const VAULT_GENERATION_TTL_MS = 60000;
|
|
5
|
+
/** Forgets the cached vault generations and this session's seal writes - in tests. */
|
|
6
|
+
export declare function clearVerificationSealCache(): void;
|
|
7
|
+
/** Reads `mailboxUid`'s current vault `masterKeyGeneration`, uncached: `undefined` when the vault can't be read or
|
|
8
|
+
* reports none. Never rejects. */
|
|
9
|
+
export declare function readVaultGeneration(mailboxUid: string): Promise<number | undefined>;
|
|
10
|
+
/** `mailboxUid`'s current vault `masterKeyGeneration`, or `undefined` when it can't be read. Never rejects. */
|
|
11
|
+
export declare function getVaultGeneration(mailboxUid: string): Promise<number | undefined>;
|
|
12
|
+
/** The seal to check `message` against: the one this session stored for it when that is newer than the message's own
|
|
13
|
+
* copy (which may predate the write), else the message's `verificationSeal`/`verificationSealGeneration`. */
|
|
14
|
+
export declare function currentVerificationSeal(message: Pick<Message, "uid" | "verificationSeal" | "verificationSealGeneration">): {
|
|
15
|
+
seal?: string;
|
|
16
|
+
sealGeneration?: number;
|
|
17
|
+
};
|
|
18
|
+
/** Whether `sendVerificationSeal()` would still send a seal for `messageUid` at `generation`. */
|
|
19
|
+
export declare function verificationSealPending(messageUid: string, generation: number): boolean;
|
|
20
|
+
/** Stores `sealToWrite` on `messageUid`, best effort - see this module's doc comment. Resolves `true` when a request was
|
|
21
|
+
* sent, `false` when the write was skipped as already attempted. Never rejects. */
|
|
22
|
+
export declare function sendVerificationSeal(messageUid: string, sealToWrite: SealToWrite): Promise<boolean>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* The web client's side of verification seals (`@rapidmx/react-shared`'s `crypto/verificationSeal.ts`): the vault's
|
|
7
|
+
* current `masterKeyGeneration` a seal is checked and written under, and best-effort seal writes.
|
|
8
|
+
*
|
|
9
|
+
* **Vault generation.** `getVaultGeneration()` reads `KeyVault.masterKeyGeneration` once per mailbox and reuses it for
|
|
10
|
+
* `VAULT_GENERATION_TTL_MS`; a failed read isn't cached. A vault that doesn't report a non-negative integer generation
|
|
11
|
+
* (a server predating it) counts as unavailable, and callers then evaluate without seals at all. The cache is dropped
|
|
12
|
+
* whenever a key session locks (subscribed on first use), so a rekey followed by a re-unlock is picked up.
|
|
13
|
+
*
|
|
14
|
+
* **Writes.** `sendVerificationSeal()` stores `sealToWrite` with `setMessageVerificationSeal()` and never rejects. Each
|
|
15
|
+
* message is written at most once per master key generation per page session - a seal carries the time it was built,
|
|
16
|
+
* so every evaluation of the same message produces a different one, and sending each would only draw 409s. A `409`
|
|
17
|
+
* (the server kept its seal), `400`, `403` or `404` is final; any other failure (a network error) lets a later
|
|
18
|
+
* evaluation try again, never a retry loop. A stored seal is remembered (`currentVerificationSeal()`) so a message
|
|
19
|
+
* reopened before its list copy is refreshed opens that seal instead of building another.
|
|
20
|
+
*/
|
|
21
|
+
import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
|
|
22
|
+
import { getKeyVault } from "@rapidmx/react-shared/crypto/keyvaultApi.js";
|
|
23
|
+
import { subscribeKeySession } from "@rapidmx/react-shared/crypto/keySession.js";
|
|
24
|
+
import { setMessageVerificationSeal } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
25
|
+
/** How long one mailbox's vault generation is reused before it is read again. */
|
|
26
|
+
export const VAULT_GENERATION_TTL_MS = 60000;
|
|
27
|
+
/** Statuses after which a seal write is never attempted again for that message and generation. */
|
|
28
|
+
const FINAL_WRITE_STATUSES = new Set([400, 403, 404, 409]);
|
|
29
|
+
const generationCache = new Map();
|
|
30
|
+
/** `messageUid:generation` for every write sent (or in flight) this session. */
|
|
31
|
+
const attemptedWrites = new Set();
|
|
32
|
+
/** Seals this session stored, by message uid. */
|
|
33
|
+
const storedSeals = new Map();
|
|
34
|
+
let lockSubscribed = false;
|
|
35
|
+
/** Forgets the cached vault generations and this session's seal writes - in tests. */
|
|
36
|
+
export function clearVerificationSealCache() {
|
|
37
|
+
generationCache.clear();
|
|
38
|
+
attemptedWrites.clear();
|
|
39
|
+
storedSeals.clear();
|
|
40
|
+
}
|
|
41
|
+
function subscribeToLocksOnce() {
|
|
42
|
+
if (lockSubscribed) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
lockSubscribed = true;
|
|
46
|
+
subscribeKeySession((event) => {
|
|
47
|
+
if (event.state === "locked") {
|
|
48
|
+
generationCache.clear();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** Reads `mailboxUid`'s current vault `masterKeyGeneration`, uncached: `undefined` when the vault can't be read or
|
|
53
|
+
* reports none. Never rejects. */
|
|
54
|
+
export async function readVaultGeneration(mailboxUid) {
|
|
55
|
+
try {
|
|
56
|
+
const { masterKeyGeneration } = await getKeyVault(mailboxUid);
|
|
57
|
+
return Number.isSafeInteger(masterKeyGeneration) && masterKeyGeneration >= 0 ? masterKeyGeneration : undefined;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** `mailboxUid`'s current vault `masterKeyGeneration`, or `undefined` when it can't be read. Never rejects. */
|
|
64
|
+
export function getVaultGeneration(mailboxUid) {
|
|
65
|
+
subscribeToLocksOnce();
|
|
66
|
+
const entry = generationCache.get(mailboxUid);
|
|
67
|
+
if (entry && Date.now() - entry.loadedAt <= VAULT_GENERATION_TTL_MS) {
|
|
68
|
+
return entry.generation;
|
|
69
|
+
}
|
|
70
|
+
const created = { loadedAt: Date.now(), generation: readVaultGeneration(mailboxUid) };
|
|
71
|
+
generationCache.set(mailboxUid, created);
|
|
72
|
+
void created.generation.then((generation) => {
|
|
73
|
+
// Only a readable generation is reused; an unavailable one is read again next time.
|
|
74
|
+
if (generation === undefined && generationCache.get(mailboxUid) === created) {
|
|
75
|
+
generationCache.delete(mailboxUid);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return created.generation;
|
|
79
|
+
}
|
|
80
|
+
/** The seal to check `message` against: the one this session stored for it when that is newer than the message's own
|
|
81
|
+
* copy (which may predate the write), else the message's `verificationSeal`/`verificationSealGeneration`. */
|
|
82
|
+
export function currentVerificationSeal(message) {
|
|
83
|
+
const stored = storedSeals.get(message.uid);
|
|
84
|
+
if (stored && (message.verificationSealGeneration === undefined || stored.masterKeyGeneration > message.verificationSealGeneration)) {
|
|
85
|
+
return { seal: stored.seal, sealGeneration: stored.masterKeyGeneration };
|
|
86
|
+
}
|
|
87
|
+
return { seal: message.verificationSeal, sealGeneration: message.verificationSealGeneration };
|
|
88
|
+
}
|
|
89
|
+
/** Whether `sendVerificationSeal()` would still send a seal for `messageUid` at `generation`. */
|
|
90
|
+
export function verificationSealPending(messageUid, generation) {
|
|
91
|
+
return !attemptedWrites.has(`${messageUid}:${generation}`);
|
|
92
|
+
}
|
|
93
|
+
/** Stores `sealToWrite` on `messageUid`, best effort - see this module's doc comment. Resolves `true` when a request was
|
|
94
|
+
* sent, `false` when the write was skipped as already attempted. Never rejects. */
|
|
95
|
+
export async function sendVerificationSeal(messageUid, sealToWrite) {
|
|
96
|
+
const key = `${messageUid}:${sealToWrite.masterKeyGeneration}`;
|
|
97
|
+
if (attemptedWrites.has(key)) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
attemptedWrites.add(key);
|
|
101
|
+
try {
|
|
102
|
+
await setMessageVerificationSeal(messageUid, sealToWrite.seal, sealToWrite.masterKeyGeneration);
|
|
103
|
+
storedSeals.set(messageUid, sealToWrite);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
// `VerificationSealConflictError` is an `ApiRequestError` with status 409.
|
|
107
|
+
if (!(err instanceof ApiRequestError && FINAL_WRITE_STATUSES.has(err.status))) {
|
|
108
|
+
attemptedWrites.delete(key);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Mailbox } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
2
|
+
/** `true` writable, `false` known view-only, `undefined` couldn't tell (treated as writable). */
|
|
3
|
+
export type MailboxWritability = boolean | undefined;
|
|
4
|
+
/** Forgets every cached answer - for tests, and anything that changes the caller's access mid-session. */
|
|
5
|
+
export declare function clearMailboxWritabilityCache(): void;
|
|
6
|
+
/** The already-known answer for `mailbox`, without making a request. */
|
|
7
|
+
export declare function peekMailboxWritability(mailbox: Mailbox, userUid: string | undefined, trusted?: boolean): MailboxWritability;
|
|
8
|
+
/** Whether the caller can create items in `mailbox`. Never rejects. */
|
|
9
|
+
export declare function getMailboxWritability(mailbox: Mailbox, userUid: string | undefined, trusted?: boolean): Promise<MailboxWritability>;
|
|
10
|
+
/** Per-mailbox writability answers for `mailboxes`, filled in as each one arrives (already-known answers are
|
|
11
|
+
* present from the first render). */
|
|
12
|
+
export declare function useMailboxWritability(mailboxes: Mailbox[], userUid: string | undefined, trusted?: boolean): Record<string, MailboxWritability>;
|
|
13
|
+
/** `mailboxes` minus any known to be view-only, always keeping `keepUid` (the picker's current/default value,
|
|
14
|
+
* so the control never shows a value missing from its own options). Lists everything until told otherwise. */
|
|
15
|
+
export declare function useWritableMailboxes(mailboxes: Mailbox[], userUid: string | undefined, keepUid?: string, trusted?: boolean): Mailbox[];
|