@rapidmx/web-client 0.9.0 → 0.11.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 +373 -88
- package/apps/admin/_layout.tsx +8 -2
- package/apps/admin/index.tsx +7 -1
- package/apps/admin/ingest-queue/index.tsx +2 -1
- package/apps/admin/mailboxes/[uid].tsx +23 -10
- package/apps/admin/quarantine/index.tsx +2 -1
- package/apps/admin/signing-certificates/index.tsx +343 -0
- package/apps/escrow/_layout.tsx +8 -2
- package/apps/shared/appearance/AppearanceForm.tsx +392 -0
- package/apps/shared/appearance/AppearanceHead.tsx +49 -0
- package/apps/shared/appearance/AppearanceProvider.tsx +470 -0
- package/apps/shared/appearance/ColorField.tsx +107 -0
- package/apps/shared/appearance/appearanceCache.ts +106 -0
- package/apps/shared/appearance/appearanceContext.ts +77 -0
- package/apps/shared/appearance/bootScript.ts +35 -0
- package/apps/shared/appearance/color.ts +169 -0
- package/apps/shared/appearance/photo.ts +88 -0
- package/apps/shared/appearance/resolvedTheme.ts +67 -0
- package/apps/shared/appearance/theme.ts +355 -0
- package/apps/shared/auth/accountUrl.ts +9 -0
- package/apps/shared/auth/adminAccess.ts +99 -0
- package/apps/shared/components/admin/layout/AdminShell.tsx +22 -10
- package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +107 -60
- package/apps/shared/components/admin/settings/BrandingForm.tsx +24 -0
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +2 -1
- package/apps/shared/components/admin/settings/PluginsManager.tsx +228 -25
- package/apps/shared/components/admin/setup/SetupWizard.tsx +2 -1
- package/apps/shared/components/calendar/CalendarListSidebar.tsx +4 -5
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +8 -2
- package/apps/shared/components/contacts/ContactForm.tsx +11 -5
- package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -3
- package/apps/shared/components/contacts/ContactsToolbar.tsx +98 -115
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +25 -8
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
- package/apps/shared/components/layout/AppShell.tsx +477 -288
- package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -389
- package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -164
- package/apps/shared/components/layout/RailIcon.tsx +100 -0
- package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -0
- package/apps/shared/components/layout/ThemeSwitch.tsx +84 -0
- package/apps/shared/components/layout/UserMenu.tsx +372 -185
- package/apps/shared/components/mail/ConversationList.tsx +271 -262
- package/apps/shared/components/mail/ConversationThreadPane.tsx +147 -94
- package/apps/shared/components/mail/LazyReadingPane.tsx +113 -0
- package/apps/shared/components/mail/MailSelectionBar.tsx +227 -222
- package/apps/shared/components/mail/MessageDetailPane.tsx +1537 -1387
- package/apps/shared/components/mail/OutboxBadge.tsx +44 -0
- package/apps/shared/components/mail/OutboxRowStatus.tsx +31 -0
- package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -160
- package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -418
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +1644 -1736
- package/apps/shared/components/mail/compose/ComposeWindowPlaceholder.tsx +111 -0
- package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -118
- package/apps/shared/components/mail/compose/composePerf.ts +46 -0
- package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
- package/apps/shared/components/mail/compose/quotedBody.ts +161 -100
- package/apps/shared/components/mail/layout/MailShell.tsx +490 -472
- package/apps/shared/components/mail/reading/EncryptedBody.tsx +65 -0
- package/apps/shared/components/mail/reading/EncryptedPreview.tsx +35 -0
- package/apps/shared/components/mail/reading/MessageBody.tsx +240 -0
- package/apps/shared/components/mail/reading/MessageCard.tsx +174 -0
- package/apps/shared/components/mail/reading/bodyContent.ts +100 -0
- package/apps/shared/components/mail/reading/bodyHtml.ts +298 -0
- package/apps/shared/components/mail/reading/color.ts +245 -0
- package/apps/shared/components/mail/reading/frameControl.ts +142 -0
- package/apps/shared/components/mail/reading/frameDocument.ts +123 -0
- package/apps/shared/components/mail/reading/safeDocument.ts +24 -0
- package/apps/shared/components/mail/reading/themeAdaptation.ts +221 -0
- package/apps/shared/components/mail/reading/themeSurface.ts +90 -0
- package/apps/shared/components/mail/reading/viewOriginal.ts +38 -0
- package/apps/shared/components/mail/unreadStyle.tsx +75 -0
- package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +253 -240
- package/apps/shared/components/sharing/PrincipalPicker.tsx +145 -0
- package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
- package/apps/shared/components/tasks/layout/TasksShell.tsx +24 -8
- package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -0
- package/apps/shared/keyboard/ShortcutProvider.tsx +62 -0
- package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -0
- package/apps/shared/keyboard/dispatch.ts +124 -0
- package/apps/shared/keyboard/format.ts +89 -0
- package/apps/shared/keyboard/keymap.ts +114 -0
- package/apps/shared/keyboard/match.ts +44 -0
- package/apps/shared/keyboard/parse.ts +136 -0
- package/apps/shared/keyboard/platform.ts +34 -0
- package/apps/shared/keyboard/registry.ts +65 -0
- package/apps/shared/keyboard/targets.ts +79 -0
- package/apps/shared/keyboard/useShortcut.ts +50 -0
- package/apps/shared/keyboard/useShortcutProps.ts +17 -0
- package/apps/shared/mail/folderCounts.ts +308 -0
- package/apps/shared/mail/folderTree.ts +143 -0
- package/apps/shared/mail/listSnapshots.ts +87 -0
- package/apps/shared/mail/messageReadState.ts +85 -0
- package/apps/shared/mail/newMailNotifications.ts +183 -0
- package/apps/shared/mail/outbox/UnlockBridge.tsx +23 -0
- package/apps/shared/mail/outbox/composeBridge.ts +69 -0
- package/apps/shared/mail/outbox/outboxState.ts +88 -0
- package/apps/shared/mail/outbox/pendingSends.ts +146 -0
- package/apps/shared/mail/outbox/sendDecision.ts +157 -0
- package/apps/shared/mail/outbox/sendJob.ts +427 -0
- package/apps/shared/mail/outbox/sendOutcomes.ts +146 -0
- package/apps/shared/mail/outbox/sendState.ts +35 -0
- package/apps/shared/mail/outbox/useOutboxStatus.ts +61 -0
- package/apps/shared/mail/useMailConnection.ts +203 -0
- package/apps/shared/mail/useMailLiveUpdates.ts +277 -224
- package/apps/shared/mail/useMarkMessageRead.ts +47 -0
- package/apps/shared/mail/useNewMailNotifications.ts +178 -0
- package/apps/shared/mail/useUnreadTitle.ts +42 -0
- package/apps/shared/navigation/AppRouter.tsx +300 -0
- package/apps/shared/navigation/appHrefs.ts +23 -0
- package/apps/shared/navigation/frameContext.tsx +35 -0
- package/apps/shared/navigation/idle.ts +45 -0
- package/apps/shared/navigation/routerContext.tsx +83 -0
- package/apps/shared/navigation/routes.ts +72 -0
- package/apps/shared/notifications/NotificationCenter.tsx +215 -0
- package/apps/shared/notifications/NotificationHistoryDialog.tsx +90 -0
- package/apps/shared/notifications/apiErrors.ts +98 -0
- package/apps/shared/notifications/headerOffset.ts +35 -0
- package/apps/shared/notifications/pushStatus.ts +56 -0
- package/apps/shared/notifications/store.ts +549 -0
- package/apps/shared/notifications/systemErrors.ts +63 -0
- package/apps/shared/notifications/useNotifications.ts +41 -0
- package/apps/shared/search/LocalIndexLifecycle.tsx +114 -98
- package/apps/shared/signing/enrollmentStorage.ts +33 -0
- package/apps/shared/signing/enrollmentTracker.ts +385 -0
- package/apps/shared/signing/enrollmentView.ts +251 -0
- package/apps/shared/signing/signingInfo.ts +58 -0
- package/apps/shared/signing/useNow.ts +19 -0
- package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -0
- package/apps/shared/styles/app.css +108 -10
- package/apps/www/_layout.tsx +8 -2
- package/apps/www/_routedPage.tsx +24 -0
- package/apps/www/_routes.ts +36 -0
- package/apps/www/calendar/index.tsx +64 -33
- package/apps/www/contacts/[uid].tsx +109 -107
- package/apps/www/contacts/index.tsx +62 -42
- package/apps/www/index.tsx +2291 -1917
- package/apps/www/messages/[uid].tsx +108 -101
- package/apps/www/settings/appearance/index.tsx +21 -0
- package/apps/www/settings/auto-reply/index.tsx +7 -8
- package/apps/www/settings/encryption/index.tsx +1287 -1249
- package/apps/www/settings/filters/[uid].tsx +7 -2
- package/apps/www/settings/filters/index.tsx +102 -99
- package/apps/www/settings/filters/new/index.tsx +140 -133
- package/apps/www/settings/labels/index.tsx +204 -201
- package/apps/www/settings/privacy/index.tsx +7 -9
- package/apps/www/settings/read-receipts/index.tsx +7 -8
- package/apps/www/settings/sharing/index.tsx +256 -274
- package/apps/www/settings/signatures/[uid].tsx +172 -167
- package/apps/www/settings/signatures/index.tsx +88 -85
- package/apps/www/settings/signatures/new/index.tsx +135 -129
- package/apps/www/tasks/index.tsx +29 -18
- package/dist/apps/admin/_layout.d.ts +5 -1
- package/dist/apps/admin/_layout.js +3 -2
- package/dist/apps/admin/index.js +3 -2
- package/dist/apps/admin/ingest-queue/index.js +2 -1
- package/dist/apps/admin/mailboxes/[uid].js +9 -6
- package/dist/apps/admin/quarantine/index.js +2 -1
- package/dist/apps/admin/signing-certificates/index.d.ts +3 -0
- package/dist/apps/admin/signing-certificates/index.js +161 -0
- package/dist/apps/escrow/_layout.d.ts +5 -1
- package/dist/apps/escrow/_layout.js +3 -2
- package/dist/apps/shared/appearance/AppearanceForm.d.ts +7 -0
- package/dist/apps/shared/appearance/AppearanceForm.js +131 -0
- package/dist/apps/shared/appearance/AppearanceHead.d.ts +15 -0
- package/dist/apps/shared/appearance/AppearanceHead.js +31 -0
- package/dist/apps/shared/appearance/AppearanceProvider.d.ts +32 -0
- package/dist/apps/shared/appearance/AppearanceProvider.js +390 -0
- package/dist/apps/shared/appearance/ColorField.d.ts +22 -0
- package/dist/apps/shared/appearance/ColorField.js +40 -0
- package/dist/apps/shared/appearance/appearanceCache.d.ts +45 -0
- package/dist/apps/shared/appearance/appearanceCache.js +80 -0
- package/dist/apps/shared/appearance/appearanceContext.d.ts +45 -0
- package/dist/apps/shared/appearance/appearanceContext.js +29 -0
- package/dist/apps/shared/appearance/bootScript.d.ts +16 -0
- package/dist/apps/shared/appearance/bootScript.js +33 -0
- package/dist/apps/shared/appearance/color.d.ts +65 -0
- package/dist/apps/shared/appearance/color.js +132 -0
- package/dist/apps/shared/appearance/photo.d.ts +17 -0
- package/dist/apps/shared/appearance/photo.js +79 -0
- package/dist/apps/shared/appearance/resolvedTheme.d.ts +21 -0
- package/dist/apps/shared/appearance/resolvedTheme.js +55 -0
- package/dist/apps/shared/appearance/theme.d.ts +111 -0
- package/dist/apps/shared/appearance/theme.js +251 -0
- package/dist/apps/shared/auth/accountUrl.d.ts +2 -0
- package/dist/apps/shared/auth/accountUrl.js +8 -0
- package/dist/apps/shared/auth/adminAccess.d.ts +30 -0
- package/dist/apps/shared/auth/adminAccess.js +89 -0
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +16 -6
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.d.ts +10 -5
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +49 -34
- package/dist/apps/shared/components/admin/settings/BrandingForm.d.ts +2 -0
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +7 -1
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +1 -1
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +106 -20
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +2 -1
- package/dist/apps/shared/components/calendar/CalendarListSidebar.js +8 -8
- package/dist/apps/shared/components/calendar/layout/CalendarShell.d.ts +1 -1
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +8 -4
- package/dist/apps/shared/components/contacts/ContactForm.js +1 -1
- package/dist/apps/shared/components/contacts/ContactsSidebar.js +4 -2
- package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +6 -2
- package/dist/apps/shared/components/contacts/ContactsToolbar.js +28 -10
- package/dist/apps/shared/components/contacts/layout/ContactsShell.d.ts +1 -1
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +19 -6
- package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
- package/dist/apps/shared/components/layout/AppShell.d.ts +34 -4
- package/dist/apps/shared/components/layout/AppShell.js +122 -17
- package/dist/apps/shared/components/layout/BrandingChrome.d.ts +69 -10
- package/dist/apps/shared/components/layout/BrandingChrome.js +140 -12
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +10 -9
- package/dist/apps/shared/components/layout/MailboxProvisioning.js +3 -1
- package/dist/apps/shared/components/layout/RailIcon.d.ts +21 -0
- package/dist/apps/shared/components/layout/RailIcon.js +85 -0
- package/dist/apps/shared/components/layout/ResponsiveToolbar.d.ts +55 -0
- package/dist/apps/shared/components/layout/ResponsiveToolbar.js +199 -0
- package/dist/apps/shared/components/layout/ThemeSwitch.d.ts +9 -0
- package/dist/apps/shared/components/layout/ThemeSwitch.js +54 -0
- package/dist/apps/shared/components/layout/UserMenu.d.ts +25 -3
- package/dist/apps/shared/components/layout/UserMenu.js +88 -7
- package/dist/apps/shared/components/mail/ConversationList.js +5 -11
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +6 -2
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +87 -40
- package/dist/apps/shared/components/mail/LazyReadingPane.d.ts +9 -0
- package/dist/apps/shared/components/mail/LazyReadingPane.js +83 -0
- package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +6 -4
- package/dist/apps/shared/components/mail/MailSelectionBar.js +11 -4
- package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +25 -8
- package/dist/apps/shared/components/mail/MessageDetailPane.js +167 -73
- package/dist/apps/shared/components/mail/OutboxBadge.d.ts +17 -0
- package/dist/apps/shared/components/mail/OutboxBadge.js +18 -0
- package/dist/apps/shared/components/mail/OutboxRowStatus.d.ts +10 -0
- package/dist/apps/shared/components/mail/OutboxRowStatus.js +19 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +38 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.js +97 -6
- package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +4 -3
- package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +3 -2
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +316 -339
- package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.d.ts +19 -0
- package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.js +33 -0
- package/dist/apps/shared/components/mail/compose/RichTextEditor.d.ts +9 -1
- package/dist/apps/shared/components/mail/compose/RichTextEditor.js +20 -2
- package/dist/apps/shared/components/mail/compose/composePerf.d.ts +16 -0
- package/dist/apps/shared/components/mail/compose/composePerf.js +41 -0
- package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +49 -0
- package/dist/apps/shared/components/mail/compose/encryptionRequirement.js +35 -0
- package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +13 -0
- package/dist/apps/shared/components/mail/compose/quotedBody.js +67 -11
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +14 -4
- package/dist/apps/shared/components/mail/layout/MailShell.js +90 -104
- package/dist/apps/shared/components/mail/reading/EncryptedBody.d.ts +21 -0
- package/dist/apps/shared/components/mail/reading/EncryptedBody.js +22 -0
- package/dist/apps/shared/components/mail/reading/EncryptedPreview.d.ts +18 -0
- package/dist/apps/shared/components/mail/reading/EncryptedPreview.js +22 -0
- package/dist/apps/shared/components/mail/reading/MessageBody.d.ts +32 -0
- package/dist/apps/shared/components/mail/reading/MessageBody.js +136 -0
- package/dist/apps/shared/components/mail/reading/MessageCard.d.ts +62 -0
- package/dist/apps/shared/components/mail/reading/MessageCard.js +58 -0
- package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +38 -0
- package/dist/apps/shared/components/mail/reading/bodyContent.js +84 -0
- package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +47 -0
- package/dist/apps/shared/components/mail/reading/bodyHtml.js +267 -0
- package/dist/apps/shared/components/mail/reading/color.d.ts +63 -0
- package/dist/apps/shared/components/mail/reading/color.js +201 -0
- package/dist/apps/shared/components/mail/reading/frameControl.d.ts +33 -0
- package/dist/apps/shared/components/mail/reading/frameControl.js +120 -0
- package/dist/apps/shared/components/mail/reading/frameDocument.d.ts +52 -0
- package/dist/apps/shared/components/mail/reading/frameDocument.js +98 -0
- package/dist/apps/shared/components/mail/reading/safeDocument.d.ts +3 -0
- package/dist/apps/shared/components/mail/reading/safeDocument.js +12 -0
- package/dist/apps/shared/components/mail/reading/themeAdaptation.d.ts +56 -0
- package/dist/apps/shared/components/mail/reading/themeAdaptation.js +144 -0
- package/dist/apps/shared/components/mail/reading/themeSurface.d.ts +17 -0
- package/dist/apps/shared/components/mail/reading/themeSurface.js +83 -0
- package/dist/apps/shared/components/mail/reading/viewOriginal.d.ts +9 -0
- package/dist/apps/shared/components/mail/reading/viewOriginal.js +32 -0
- package/dist/apps/shared/components/mail/unreadStyle.d.ts +45 -0
- package/dist/apps/shared/components/mail/unreadStyle.js +61 -0
- package/dist/apps/shared/components/settings/SigningCertificateCard.d.ts +32 -0
- package/dist/apps/shared/components/settings/SigningCertificateCard.js +98 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.d.ts +1 -1
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +16 -5
- package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +26 -0
- package/dist/apps/shared/components/sharing/PrincipalPicker.js +77 -0
- package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
- package/dist/apps/shared/components/tasks/layout/TasksShell.d.ts +1 -1
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +18 -6
- package/dist/apps/shared/keyboard/GlobalShortcuts.d.ts +14 -0
- package/dist/apps/shared/keyboard/GlobalShortcuts.js +37 -0
- package/dist/apps/shared/keyboard/ShortcutProvider.d.ts +20 -0
- package/dist/apps/shared/keyboard/ShortcutProvider.js +52 -0
- package/dist/apps/shared/keyboard/ShortcutsDialog.d.ts +14 -0
- package/dist/apps/shared/keyboard/ShortcutsDialog.js +42 -0
- package/dist/apps/shared/keyboard/dispatch.d.ts +16 -0
- package/dist/apps/shared/keyboard/dispatch.js +109 -0
- package/dist/apps/shared/keyboard/format.d.ts +12 -0
- package/dist/apps/shared/keyboard/format.js +74 -0
- package/dist/apps/shared/keyboard/keymap.d.ts +294 -0
- package/dist/apps/shared/keyboard/keymap.js +84 -0
- package/dist/apps/shared/keyboard/match.d.ts +20 -0
- package/dist/apps/shared/keyboard/match.js +31 -0
- package/dist/apps/shared/keyboard/parse.d.ts +31 -0
- package/dist/apps/shared/keyboard/parse.js +107 -0
- package/dist/apps/shared/keyboard/platform.d.ts +15 -0
- package/dist/apps/shared/keyboard/platform.js +22 -0
- package/dist/apps/shared/keyboard/registry.d.ts +39 -0
- package/dist/apps/shared/keyboard/registry.js +33 -0
- package/dist/apps/shared/keyboard/targets.d.ts +14 -0
- package/dist/apps/shared/keyboard/targets.js +67 -0
- package/dist/apps/shared/keyboard/useShortcut.d.ts +19 -0
- package/dist/apps/shared/keyboard/useShortcut.js +35 -0
- package/dist/apps/shared/keyboard/useShortcutProps.d.ts +10 -0
- package/dist/apps/shared/keyboard/useShortcutProps.js +15 -0
- package/dist/apps/shared/mail/folderCounts.d.ts +78 -0
- package/dist/apps/shared/mail/folderCounts.js +218 -0
- package/dist/apps/shared/mail/folderTree.d.ts +55 -0
- package/dist/apps/shared/mail/folderTree.js +116 -0
- package/dist/apps/shared/mail/listSnapshots.d.ts +46 -0
- package/dist/apps/shared/mail/listSnapshots.js +43 -0
- package/dist/apps/shared/mail/messageReadState.d.ts +32 -0
- package/dist/apps/shared/mail/messageReadState.js +63 -0
- package/dist/apps/shared/mail/newMailNotifications.d.ts +62 -0
- package/dist/apps/shared/mail/newMailNotifications.js +138 -0
- package/dist/apps/shared/mail/outbox/UnlockBridge.d.ts +5 -0
- package/dist/apps/shared/mail/outbox/UnlockBridge.js +18 -0
- package/dist/apps/shared/mail/outbox/composeBridge.d.ts +34 -0
- package/dist/apps/shared/mail/outbox/composeBridge.js +32 -0
- package/dist/apps/shared/mail/outbox/outboxState.d.ts +41 -0
- package/dist/apps/shared/mail/outbox/outboxState.js +42 -0
- package/dist/apps/shared/mail/outbox/pendingSends.d.ts +39 -0
- package/dist/apps/shared/mail/outbox/pendingSends.js +109 -0
- package/dist/apps/shared/mail/outbox/sendDecision.d.ts +80 -0
- package/dist/apps/shared/mail/outbox/sendDecision.js +94 -0
- package/dist/apps/shared/mail/outbox/sendJob.d.ts +68 -0
- package/dist/apps/shared/mail/outbox/sendJob.js +319 -0
- package/dist/apps/shared/mail/outbox/sendOutcomes.d.ts +3 -0
- package/dist/apps/shared/mail/outbox/sendOutcomes.js +141 -0
- package/dist/apps/shared/mail/outbox/sendState.d.ts +25 -0
- package/dist/apps/shared/mail/outbox/sendState.js +8 -0
- package/dist/apps/shared/mail/outbox/useOutboxStatus.d.ts +11 -0
- package/dist/apps/shared/mail/outbox/useOutboxStatus.js +49 -0
- package/dist/apps/shared/mail/useMailConnection.d.ts +56 -0
- package/dist/apps/shared/mail/useMailConnection.js +138 -0
- package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +29 -9
- package/dist/apps/shared/mail/useMailLiveUpdates.js +60 -32
- package/dist/apps/shared/mail/useMarkMessageRead.d.ts +12 -0
- package/dist/apps/shared/mail/useMarkMessageRead.js +44 -0
- package/dist/apps/shared/mail/useNewMailNotifications.d.ts +35 -0
- package/dist/apps/shared/mail/useNewMailNotifications.js +130 -0
- package/dist/apps/shared/mail/useUnreadTitle.d.ts +18 -0
- package/dist/apps/shared/mail/useUnreadTitle.js +31 -0
- package/dist/apps/shared/navigation/AppRouter.d.ts +52 -0
- package/dist/apps/shared/navigation/AppRouter.js +242 -0
- package/dist/apps/shared/navigation/appHrefs.d.ts +14 -0
- package/dist/apps/shared/navigation/appHrefs.js +20 -0
- package/dist/apps/shared/navigation/frameContext.d.ts +19 -0
- package/dist/apps/shared/navigation/frameContext.js +24 -0
- package/dist/apps/shared/navigation/idle.d.ts +14 -0
- package/dist/apps/shared/navigation/idle.js +43 -0
- package/dist/apps/shared/navigation/routerContext.d.ts +37 -0
- package/dist/apps/shared/navigation/routerContext.js +56 -0
- package/dist/apps/shared/navigation/routes.d.ts +32 -0
- package/dist/apps/shared/navigation/routes.js +37 -0
- package/dist/apps/shared/notifications/NotificationCenter.d.ts +19 -0
- package/dist/apps/shared/notifications/NotificationCenter.js +90 -0
- package/dist/apps/shared/notifications/NotificationHistoryDialog.d.ts +10 -0
- package/dist/apps/shared/notifications/NotificationHistoryDialog.js +36 -0
- package/dist/apps/shared/notifications/apiErrors.d.ts +19 -0
- package/dist/apps/shared/notifications/apiErrors.js +86 -0
- package/dist/apps/shared/notifications/headerOffset.d.ts +8 -0
- package/dist/apps/shared/notifications/headerOffset.js +33 -0
- package/dist/apps/shared/notifications/pushStatus.d.ts +9 -0
- package/dist/apps/shared/notifications/pushStatus.js +54 -0
- package/dist/apps/shared/notifications/store.d.ts +136 -0
- package/dist/apps/shared/notifications/store.js +416 -0
- package/dist/apps/shared/notifications/systemErrors.d.ts +9 -0
- package/dist/apps/shared/notifications/systemErrors.js +58 -0
- package/dist/apps/shared/notifications/useNotifications.d.ts +17 -0
- package/dist/apps/shared/notifications/useNotifications.js +18 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.js +17 -3
- package/dist/apps/shared/signing/enrollmentStorage.d.ts +10 -0
- package/dist/apps/shared/signing/enrollmentStorage.js +33 -0
- package/dist/apps/shared/signing/enrollmentTracker.d.ts +73 -0
- package/dist/apps/shared/signing/enrollmentTracker.js +307 -0
- package/dist/apps/shared/signing/enrollmentView.d.ts +57 -0
- package/dist/apps/shared/signing/enrollmentView.js +214 -0
- package/dist/apps/shared/signing/signingInfo.d.ts +11 -0
- package/dist/apps/shared/signing/signingInfo.js +48 -0
- package/dist/apps/shared/signing/useNow.d.ts +2 -0
- package/dist/apps/shared/signing/useNow.js +18 -0
- package/dist/apps/shared/signing/useSigningEnrollmentWatcher.d.ts +16 -0
- package/dist/apps/shared/signing/useSigningEnrollmentWatcher.js +87 -0
- package/dist/apps/shared/styles/app.css +108 -10
- package/dist/apps/www/_layout.d.ts +5 -1
- package/dist/apps/www/_layout.js +3 -2
- package/dist/apps/www/_routedPage.d.ts +12 -0
- package/dist/apps/www/_routedPage.js +19 -0
- package/dist/apps/www/_routes.d.ts +11 -0
- package/dist/apps/www/_routes.js +30 -0
- package/dist/apps/www/calendar/index.d.ts +2 -2
- package/dist/apps/www/calendar/index.js +41 -11
- package/dist/apps/www/contacts/[uid].d.ts +3 -9
- package/dist/apps/www/contacts/[uid].js +11 -9
- package/dist/apps/www/contacts/index.d.ts +2 -2
- package/dist/apps/www/contacts/index.js +30 -24
- package/dist/apps/www/index.d.ts +2 -2
- package/dist/apps/www/index.js +332 -48
- package/dist/apps/www/messages/[uid].d.ts +3 -7
- package/dist/apps/www/messages/[uid].js +10 -5
- package/dist/apps/www/settings/appearance/index.d.ts +4 -0
- package/dist/apps/www/settings/appearance/index.js +13 -0
- package/dist/apps/www/settings/auto-reply/index.d.ts +2 -2
- package/dist/apps/www/settings/auto-reply/index.js +6 -7
- package/dist/apps/www/settings/encryption/index.d.ts +2 -2
- package/dist/apps/www/settings/encryption/index.js +106 -96
- package/dist/apps/www/settings/filters/[uid].d.ts +2 -2
- package/dist/apps/www/settings/filters/[uid].js +6 -2
- package/dist/apps/www/settings/filters/index.d.ts +2 -2
- package/dist/apps/www/settings/filters/index.js +3 -1
- package/dist/apps/www/settings/filters/new/index.d.ts +2 -2
- package/dist/apps/www/settings/filters/new/index.js +9 -3
- package/dist/apps/www/settings/labels/index.d.ts +2 -2
- package/dist/apps/www/settings/labels/index.js +3 -1
- package/dist/apps/www/settings/privacy/index.d.ts +2 -2
- package/dist/apps/www/settings/privacy/index.js +8 -9
- package/dist/apps/www/settings/read-receipts/index.d.ts +2 -2
- package/dist/apps/www/settings/read-receipts/index.js +6 -7
- package/dist/apps/www/settings/sharing/index.d.ts +2 -2
- package/dist/apps/www/settings/sharing/index.js +22 -34
- package/dist/apps/www/settings/signatures/[uid].d.ts +2 -2
- package/dist/apps/www/settings/signatures/[uid].js +6 -2
- package/dist/apps/www/settings/signatures/index.d.ts +2 -2
- package/dist/apps/www/settings/signatures/index.js +3 -1
- package/dist/apps/www/settings/signatures/new/index.d.ts +2 -2
- package/dist/apps/www/settings/signatures/new/index.js +9 -4
- package/dist/apps/www/tasks/index.d.ts +2 -2
- package/dist/apps/www/tasks/index.js +22 -16
- package/package.json +2 -2
- package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
- package/dist/apps/shared/components/mail/compose/SendFailureAlert.d.ts +0 -14
- package/dist/apps/shared/components/mail/compose/SendFailureAlert.js +0 -11
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** A `scheduledSendTime` this close to the message's last modification was set by the server as "now": a due message, not a scheduled one. */
|
|
2
|
+
export const DUE_SLACK_MS = 2000;
|
|
3
|
+
export function outboxItemStatus(message, now = Date.now()) {
|
|
4
|
+
const attempts = message.scheduledSendAttempts ?? 0;
|
|
5
|
+
const lease = message.scheduledSendLeaseExpiresAt ? Date.parse(message.scheduledSendLeaseExpiresAt) : NaN;
|
|
6
|
+
const at = message.scheduledSendTime ? Date.parse(message.scheduledSendTime) : NaN;
|
|
7
|
+
if (message.scheduledSendError && !message.scheduledSendTime && !(lease > now)) {
|
|
8
|
+
return { state: "failed", attempts, error: message.scheduledSendError };
|
|
9
|
+
}
|
|
10
|
+
if (lease > now) {
|
|
11
|
+
return { state: attempts > 0 ? "retrying" : "sending", attempts };
|
|
12
|
+
}
|
|
13
|
+
if (attempts > 0) {
|
|
14
|
+
return { state: "retrying", attempts, at: Number.isNaN(at) ? undefined : at };
|
|
15
|
+
}
|
|
16
|
+
const modified = message.dateModified ? Date.parse(message.dateModified) : NaN;
|
|
17
|
+
const dueWhenModified = at - modified <= DUE_SLACK_MS;
|
|
18
|
+
if (at > now && !dueWhenModified) {
|
|
19
|
+
return { state: "scheduled", attempts, at };
|
|
20
|
+
}
|
|
21
|
+
return { state: "sending", attempts };
|
|
22
|
+
}
|
|
23
|
+
export const EMPTY_OUTBOX_STATUS = { sending: 0, retrying: 0, failed: 0, scheduled: 0 };
|
|
24
|
+
export function summarizeOutbox(messages, now = Date.now()) {
|
|
25
|
+
const status = { ...EMPTY_OUTBOX_STATUS };
|
|
26
|
+
for (const message of messages) {
|
|
27
|
+
status[outboxItemStatus(message, now).state] += 1;
|
|
28
|
+
}
|
|
29
|
+
return status;
|
|
30
|
+
}
|
|
31
|
+
/** The text a screen reader gets for an Outbox count, and the tooltip: "2 messages sending", "3 messages, 1 failed". */
|
|
32
|
+
export function outboxLabel(total, status, pendingHere) {
|
|
33
|
+
const noun = `${total} ${total === 1 ? "message" : "messages"}`;
|
|
34
|
+
if (status && status.failed > 0) {
|
|
35
|
+
return `${noun}, ${status.failed} failed`;
|
|
36
|
+
}
|
|
37
|
+
const inFlight = (status ? status.sending + status.retrying : 0) + pendingHere;
|
|
38
|
+
if (inFlight > 0) {
|
|
39
|
+
return `${noun} sending`;
|
|
40
|
+
}
|
|
41
|
+
return status && status.scheduled > 0 && status.scheduled === total ? `${noun} scheduled` : noun;
|
|
42
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The messages this tab has been asked to send whose *client-side* work is not finished: assembling (and encrypting/signing, which takes
|
|
3
|
+
* a moment) and the request that hands them to the server. Once the server has answered `202 queued` a message is safe - the server
|
|
4
|
+
* owns it, and the Outbox folder's own count, list and push events say the rest - so it leaves this set.
|
|
5
|
+
*
|
|
6
|
+
* What this is for: the Outbox indicator counts these at once (before the server has heard of the message), a message in this set is
|
|
7
|
+
* why the tab asks "Leave site?" (`beforeunload`) and why Sign Out waits a moment before ending the session, and a second Send of the same
|
|
8
|
+
* draft is refused while it is here.
|
|
9
|
+
*/
|
|
10
|
+
export type PendingStage = "preparing" | "queuing";
|
|
11
|
+
export interface PendingSend {
|
|
12
|
+
/** The draft's uid: one send per draft at a time. */
|
|
13
|
+
draftUid: string;
|
|
14
|
+
mailboxUid: string;
|
|
15
|
+
subject: string;
|
|
16
|
+
/** Every recipient's address. */
|
|
17
|
+
recipients: string[];
|
|
18
|
+
stage: PendingStage;
|
|
19
|
+
/** A send-later message: it goes to Outbox and waits for its time, rather than being relayed. */
|
|
20
|
+
scheduled: boolean;
|
|
21
|
+
startedAt: number;
|
|
22
|
+
}
|
|
23
|
+
/** Whether a send of this draft is under way in this tab. */
|
|
24
|
+
export declare function isSendPending(draftUid: string): boolean;
|
|
25
|
+
/** Starts tracking a send. `false` (and nothing tracked) when this draft is already being sent - the caller must not send it again. */
|
|
26
|
+
export declare function beginPendingSend(entry: Omit<PendingSend, "stage" | "startedAt">): boolean;
|
|
27
|
+
export declare function setPendingStage(draftUid: string, stage: PendingStage): void;
|
|
28
|
+
/** The server has the message (or the attempt failed and is now the user's to resolve): it is no longer this tab's work. */
|
|
29
|
+
export declare function finishPendingSend(draftUid: string): void;
|
|
30
|
+
export declare function getPendingSends(): readonly PendingSend[];
|
|
31
|
+
export declare function subscribePendingSends(listener: () => void): () => void;
|
|
32
|
+
/** How many messages of `mailboxUid` are still being handed to the server: what the Outbox pill adds to its server count at once. */
|
|
33
|
+
export declare function pendingCountFor(mailboxUid: string, sends?: readonly PendingSend[]): number;
|
|
34
|
+
/** Resolves (to whether everything settled) when no send is pending, or after `timeoutMs`. */
|
|
35
|
+
export declare function whenSettled(timeoutMs: number): Promise<boolean>;
|
|
36
|
+
/** The messages being sent, for a component. */
|
|
37
|
+
export declare function usePendingSends(): readonly PendingSend[];
|
|
38
|
+
/** Forgets everything - for a test. */
|
|
39
|
+
export declare function resetPendingSends(): void;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { useSyncExternalStore } from "react";
|
|
6
|
+
import { registerComposeFlush } from "../../components/mail/compose/composeFlushRegistry.js";
|
|
7
|
+
const NO_SENDS = [];
|
|
8
|
+
let pending = [];
|
|
9
|
+
let snapshot = pending;
|
|
10
|
+
const listeners = new Set();
|
|
11
|
+
let stopFlush;
|
|
12
|
+
let waiters = [];
|
|
13
|
+
function emit() {
|
|
14
|
+
snapshot = [...pending];
|
|
15
|
+
for (const listener of [...listeners]) {
|
|
16
|
+
listener();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** `beforeunload`: leaving now would lose a message the server has not received. */
|
|
20
|
+
function handleBeforeUnload(event) {
|
|
21
|
+
// Only installed while something is pending (see `syncGuards()`).
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
event.returnValue = "";
|
|
24
|
+
}
|
|
25
|
+
/** Installs the `beforeunload` prompt and the sign-out wait while a send is pending, and takes them away when none is. Only ever called from a browser. */
|
|
26
|
+
function syncGuards() {
|
|
27
|
+
if (pending.length > 0 && !stopFlush) {
|
|
28
|
+
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
29
|
+
// Sign Out flushes compose windows before it ends the session; a message still being prepared is waited for there too.
|
|
30
|
+
stopFlush = registerComposeFlush(() => whenSettled(20000));
|
|
31
|
+
}
|
|
32
|
+
else if (pending.length === 0 && stopFlush) {
|
|
33
|
+
window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
34
|
+
stopFlush();
|
|
35
|
+
stopFlush = undefined;
|
|
36
|
+
}
|
|
37
|
+
if (pending.length === 0) {
|
|
38
|
+
const done = waiters;
|
|
39
|
+
waiters = [];
|
|
40
|
+
for (const resolve of done) {
|
|
41
|
+
resolve();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Whether a send of this draft is under way in this tab. */
|
|
46
|
+
export function isSendPending(draftUid) {
|
|
47
|
+
return pending.some((entry) => entry.draftUid === draftUid);
|
|
48
|
+
}
|
|
49
|
+
/** Starts tracking a send. `false` (and nothing tracked) when this draft is already being sent - the caller must not send it again. */
|
|
50
|
+
export function beginPendingSend(entry) {
|
|
51
|
+
if (isSendPending(entry.draftUid)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
pending.push({ ...entry, stage: "preparing", startedAt: Date.now() });
|
|
55
|
+
syncGuards();
|
|
56
|
+
emit();
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
export function setPendingStage(draftUid, stage) {
|
|
60
|
+
pending = pending.map((entry) => (entry.draftUid === draftUid ? { ...entry, stage } : entry));
|
|
61
|
+
emit();
|
|
62
|
+
}
|
|
63
|
+
/** The server has the message (or the attempt failed and is now the user's to resolve): it is no longer this tab's work. */
|
|
64
|
+
export function finishPendingSend(draftUid) {
|
|
65
|
+
pending = pending.filter((entry) => entry.draftUid !== draftUid);
|
|
66
|
+
syncGuards();
|
|
67
|
+
emit();
|
|
68
|
+
}
|
|
69
|
+
export function getPendingSends() {
|
|
70
|
+
return snapshot;
|
|
71
|
+
}
|
|
72
|
+
export function subscribePendingSends(listener) {
|
|
73
|
+
listeners.add(listener);
|
|
74
|
+
return () => {
|
|
75
|
+
listeners.delete(listener);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** How many messages of `mailboxUid` are still being handed to the server: what the Outbox pill adds to its server count at once. */
|
|
79
|
+
export function pendingCountFor(mailboxUid, sends = snapshot) {
|
|
80
|
+
return sends.filter((entry) => entry.mailboxUid === mailboxUid).length;
|
|
81
|
+
}
|
|
82
|
+
/** Resolves (to whether everything settled) when no send is pending, or after `timeoutMs`. */
|
|
83
|
+
export function whenSettled(timeoutMs) {
|
|
84
|
+
if (pending.length === 0) {
|
|
85
|
+
return Promise.resolve(true);
|
|
86
|
+
}
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
const timer = setTimeout(() => {
|
|
89
|
+
waiters = waiters.filter((waiter) => waiter !== done);
|
|
90
|
+
resolve(false);
|
|
91
|
+
}, timeoutMs);
|
|
92
|
+
function done() {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
resolve(true);
|
|
95
|
+
}
|
|
96
|
+
waiters.push(done);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/** The messages being sent, for a component. */
|
|
100
|
+
export function usePendingSends() {
|
|
101
|
+
return useSyncExternalStore(subscribePendingSends, getPendingSends, () => NO_SENDS);
|
|
102
|
+
}
|
|
103
|
+
/** Forgets everything - for a test. */
|
|
104
|
+
export function resetPendingSends() {
|
|
105
|
+
pending = [];
|
|
106
|
+
waiters = [];
|
|
107
|
+
syncGuards();
|
|
108
|
+
emit();
|
|
109
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { RecipientEncryptionStatus } from "@rapidmx/react-shared/crypto/composeSecurity.js";
|
|
2
|
+
import type { EncryptionPolicy } from "@rapidmx/react-shared/crypto/keyvaultApi.js";
|
|
3
|
+
/** Signed/encrypted bodies are built client-side from the editor's HTML verbatim, so an inline image (an uploaded attachment previewed via
|
|
4
|
+
* its content URL, or a `cid:` reference) would never be carried - only `assembleDraft()`'s server-side plaintext path rewrites those into
|
|
5
|
+
* real MIME parts. */
|
|
6
|
+
export declare const INLINE_IMAGE_PATTERN: RegExp;
|
|
7
|
+
export declare const KEYS_LOCKED_SIGN_MESSAGE = "This message can't be signed right now - your signing key is locked or your mailbox details couldn't be loaded. Unlock and send again, or send it without signing.";
|
|
8
|
+
export declare const KEYS_LOCKED_ENCRYPT_MESSAGE = "This message can't be encrypted right now - your encryption key is locked. Unlock and send again, or send it without encryption.";
|
|
9
|
+
export declare const BCC_ENCRYPTED_MESSAGE = "Bcc recipients can't be used with encrypted messages. Remove Bcc recipients or turn off encryption.";
|
|
10
|
+
export declare const LOOKUP_UNAVAILABLE_MESSAGE = "Your recipients' encryption keys couldn't be checked, so this message can't be encrypted right now. Try again, or send it without encryption.";
|
|
11
|
+
/** The plain sentence for recipients an encrypted message can't reach (no key on file, or the policy prohibits it). */
|
|
12
|
+
export declare function recipientsBlockedMessage(blocked: RecipientEncryptionStatus[]): string;
|
|
13
|
+
export declare const MAILBOX_UNAVAILABLE_MESSAGE = "Your mailbox details couldn't be loaded, so this message can't be encrypted right now. Try again, or send it without encryption.";
|
|
14
|
+
export declare const ATTACHMENTS_UNSUPPORTED_MESSAGE = "A signed or encrypted message cannot include file attachments yet - remove them before sending.";
|
|
15
|
+
export declare const INLINE_IMAGES_UNSUPPORTED_MESSAGE = "A signed or encrypted message cannot include inline images yet - remove them before sending.";
|
|
16
|
+
/** Why a send is refused *before* anything is sent: signing/encryption was asked for (or applies) but cannot happen right now. Never a silent
|
|
17
|
+
* downgrade to plaintext - the sender explicitly picks the override. */
|
|
18
|
+
export interface SendBlock {
|
|
19
|
+
kind: "signing-locked" | "encryption-locked" | "lookup-failed" | "mailbox-unavailable" | "bcc" | "recipients";
|
|
20
|
+
message: string;
|
|
21
|
+
/** The button that sends it anyway, without encryption (and without signing when that is what was blocked). */
|
|
22
|
+
overrideLabel: string;
|
|
23
|
+
/** The cause is keys that are enrolled but not unlocked in this tab: an Unlock action helps. */
|
|
24
|
+
keysLocked: boolean;
|
|
25
|
+
/** `recipients`: who has no key (or is not allowed one). */
|
|
26
|
+
blockedRecipients?: RecipientEncryptionStatus[];
|
|
27
|
+
}
|
|
28
|
+
export type SendDecision = {
|
|
29
|
+
action: "plain";
|
|
30
|
+
} | {
|
|
31
|
+
action: "sign";
|
|
32
|
+
} | {
|
|
33
|
+
action: "encrypt";
|
|
34
|
+
sign: boolean;
|
|
35
|
+
} | {
|
|
36
|
+
action: "blocked";
|
|
37
|
+
block: SendBlock;
|
|
38
|
+
}
|
|
39
|
+
/** Not sendable as composed, and no override helps: fix the message. */
|
|
40
|
+
| {
|
|
41
|
+
action: "rejected";
|
|
42
|
+
message: string;
|
|
43
|
+
};
|
|
44
|
+
export interface SendDecisionInput {
|
|
45
|
+
/** The sender's explicit choice to skip every encryption/signing check (an override button): it never encrypts, and still signs if it can. */
|
|
46
|
+
forcePlaintext: boolean;
|
|
47
|
+
/** The "sign" checkbox. */
|
|
48
|
+
signEnabled: boolean;
|
|
49
|
+
/** The signing option was shown at some point this compose session (the keys were unlocked): losing the keys then blocks, never silently unsigns. */
|
|
50
|
+
offeredSign: boolean;
|
|
51
|
+
encryptRequested: boolean;
|
|
52
|
+
policy: EncryptionPolicy | undefined;
|
|
53
|
+
mailboxLoaded: boolean;
|
|
54
|
+
hasEncryptionKey: boolean;
|
|
55
|
+
keys: {
|
|
56
|
+
/** Signing is possible now: enabled, mailbox loaded (for the protected headers), signing key unlocked. */
|
|
57
|
+
canSign: boolean;
|
|
58
|
+
/** The encryption key is unlocked. */
|
|
59
|
+
canEncryptSelf: boolean;
|
|
60
|
+
/** Any key material is unlocked in this tab. */
|
|
61
|
+
unlocked: boolean;
|
|
62
|
+
};
|
|
63
|
+
/** Every recipient (To, Cc and Bcc), resolved; `undefined` where the lookup failed - or, with `lookupsComplete: false`, was not made. */
|
|
64
|
+
recipients: (RecipientEncryptionStatus | undefined)[];
|
|
65
|
+
/** Whether the lookups were all *attempted*: `false` for the early check a compose window makes from what it already knows. */
|
|
66
|
+
lookupsComplete: boolean;
|
|
67
|
+
hasBcc: boolean;
|
|
68
|
+
hasAttachments: boolean;
|
|
69
|
+
hasInlineImages: boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* How a message may go out, decided from the facts alone (no requests, no React): plain, signed, encrypted, refused with an override the sender
|
|
73
|
+
* can choose, or rejected outright. Used twice with the same rules - by the compose window, from what it already knows, to catch a problem
|
|
74
|
+
* *before* it closes (`lookupsComplete: false`), and by the background send, once its recipient lookups are in.
|
|
75
|
+
*
|
|
76
|
+
* Encryption follows `evaluateEncryptionRequirement()` - fail open: only a message the user asked to encrypt, or one the loaded policy
|
|
77
|
+
* encrypts, is encrypted; everything unknown is plain and sends. The one place unknown blocks is a message the user *explicitly* asked to
|
|
78
|
+
* encrypt whose recipients' keys could not be looked up: they asked for encryption, and it cannot be delivered.
|
|
79
|
+
*/
|
|
80
|
+
export declare function decideSend(input: SendDecisionInput): SendDecision;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { decideMessageEncryption } from "@rapidmx/react-shared/crypto/composeSecurity.js";
|
|
6
|
+
import { evaluateEncryptionRequirement } from "../../components/mail/compose/encryptionRequirement.js";
|
|
7
|
+
/** Signed/encrypted bodies are built client-side from the editor's HTML verbatim, so an inline image (an uploaded attachment previewed via
|
|
8
|
+
* its content URL, or a `cid:` reference) would never be carried - only `assembleDraft()`'s server-side plaintext path rewrites those into
|
|
9
|
+
* real MIME parts. */
|
|
10
|
+
export const INLINE_IMAGE_PATTERN = /<img\b[^>]*\bsrc\s*=\s*["']?(?:cid:|[^"'\s>]*\/mail\/attachments\/[^"'\s>]+\/content)/i;
|
|
11
|
+
export const KEYS_LOCKED_SIGN_MESSAGE = "This message can't be signed right now - your signing key is locked or your mailbox details couldn't be loaded. Unlock and send again, or send it without signing.";
|
|
12
|
+
export const KEYS_LOCKED_ENCRYPT_MESSAGE = "This message can't be encrypted right now - your encryption key is locked. Unlock and send again, or send it without encryption.";
|
|
13
|
+
export const BCC_ENCRYPTED_MESSAGE = "Bcc recipients can't be used with encrypted messages. Remove Bcc recipients or turn off encryption.";
|
|
14
|
+
export const LOOKUP_UNAVAILABLE_MESSAGE = "Your recipients' encryption keys couldn't be checked, so this message can't be encrypted right now. Try again, or send it without encryption.";
|
|
15
|
+
/** The plain sentence for recipients an encrypted message can't reach (no key on file, or the policy prohibits it). */
|
|
16
|
+
export function recipientsBlockedMessage(blocked) {
|
|
17
|
+
const reason = blocked[0]?.prohibitedReason ? ` — ${blocked[0].prohibitedReason}` : " has no encryption key on file";
|
|
18
|
+
return `This message can’t be encrypted for everyone: ${blocked.map((r) => r.address).join(", ")}${reason}. Remove ${blocked.length > 1 ? "these recipients" : "this recipient"} from To/Cc/Bcc, or send the whole message in plaintext.`;
|
|
19
|
+
}
|
|
20
|
+
export const MAILBOX_UNAVAILABLE_MESSAGE = "Your mailbox details couldn't be loaded, so this message can't be encrypted right now. Try again, or send it without encryption.";
|
|
21
|
+
export const ATTACHMENTS_UNSUPPORTED_MESSAGE = "A signed or encrypted message cannot include file attachments yet - remove them before sending.";
|
|
22
|
+
export const INLINE_IMAGES_UNSUPPORTED_MESSAGE = "A signed or encrypted message cannot include inline images yet - remove them before sending.";
|
|
23
|
+
/**
|
|
24
|
+
* How a message may go out, decided from the facts alone (no requests, no React): plain, signed, encrypted, refused with an override the sender
|
|
25
|
+
* can choose, or rejected outright. Used twice with the same rules - by the compose window, from what it already knows, to catch a problem
|
|
26
|
+
* *before* it closes (`lookupsComplete: false`), and by the background send, once its recipient lookups are in.
|
|
27
|
+
*
|
|
28
|
+
* Encryption follows `evaluateEncryptionRequirement()` - fail open: only a message the user asked to encrypt, or one the loaded policy
|
|
29
|
+
* encrypts, is encrypted; everything unknown is plain and sends. The one place unknown blocks is a message the user *explicitly* asked to
|
|
30
|
+
* encrypt whose recipients' keys could not be looked up: they asked for encryption, and it cannot be delivered.
|
|
31
|
+
*/
|
|
32
|
+
export function decideSend(input) {
|
|
33
|
+
const { keys } = input;
|
|
34
|
+
const attachmentsAreAProblem = (encrypting) => (encrypting || keys.canSign) && input.hasAttachments;
|
|
35
|
+
const imagesAreAProblem = (encrypting) => (encrypting || keys.canSign) && input.hasInlineImages;
|
|
36
|
+
if (input.forcePlaintext) {
|
|
37
|
+
if (attachmentsAreAProblem(false)) {
|
|
38
|
+
return { action: "rejected", message: ATTACHMENTS_UNSUPPORTED_MESSAGE };
|
|
39
|
+
}
|
|
40
|
+
if (imagesAreAProblem(false)) {
|
|
41
|
+
return { action: "rejected", message: INLINE_IMAGES_UNSUPPORTED_MESSAGE };
|
|
42
|
+
}
|
|
43
|
+
return keys.canSign ? { action: "sign" } : { action: "plain" };
|
|
44
|
+
}
|
|
45
|
+
if (input.signEnabled && input.offeredSign && !keys.canSign) {
|
|
46
|
+
return {
|
|
47
|
+
action: "blocked",
|
|
48
|
+
block: { kind: "signing-locked", message: KEYS_LOCKED_SIGN_MESSAGE, overrideLabel: "Send without signing or encryption", keysLocked: !keys.unlocked },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (input.encryptRequested && !input.mailboxLoaded) {
|
|
52
|
+
// Asked for explicitly, and there is nothing to encrypt with (the sender's address and keys come from the mailbox).
|
|
53
|
+
return { action: "blocked", block: { kind: "mailbox-unavailable", message: MAILBOX_UNAVAILABLE_MESSAGE, overrideLabel: "Send without encryption", keysLocked: false } };
|
|
54
|
+
}
|
|
55
|
+
const requirement = evaluateEncryptionRequirement({
|
|
56
|
+
encryptRequested: input.encryptRequested,
|
|
57
|
+
policy: input.policy,
|
|
58
|
+
mailboxLoaded: input.mailboxLoaded,
|
|
59
|
+
hasEncryptionKey: input.hasEncryptionKey,
|
|
60
|
+
recipients: input.recipients,
|
|
61
|
+
});
|
|
62
|
+
const encrypting = requirement.required;
|
|
63
|
+
if (encrypting) {
|
|
64
|
+
const statuses = input.recipients.filter((status) => !!status);
|
|
65
|
+
const allResolved = statuses.length === input.recipients.length;
|
|
66
|
+
if (!allResolved && input.lookupsComplete) {
|
|
67
|
+
// Only reachable for an explicit request (a policy requirement needs every status): the sender asked for encryption and the
|
|
68
|
+
// recipients' keys could not be checked.
|
|
69
|
+
return { action: "blocked", block: { kind: "lookup-failed", message: LOOKUP_UNAVAILABLE_MESSAGE, overrideLabel: "Send without encryption", keysLocked: false } };
|
|
70
|
+
}
|
|
71
|
+
if (!keys.canEncryptSelf) {
|
|
72
|
+
return { action: "blocked", block: { kind: "encryption-locked", message: KEYS_LOCKED_ENCRYPT_MESSAGE, overrideLabel: "Send without encryption", keysLocked: !keys.unlocked } };
|
|
73
|
+
}
|
|
74
|
+
if (allResolved) {
|
|
75
|
+
const decision = decideMessageEncryption(statuses);
|
|
76
|
+
if (!decision.canEncryptAll) {
|
|
77
|
+
return {
|
|
78
|
+
action: "blocked",
|
|
79
|
+
block: { kind: "recipients", message: recipientsBlockedMessage(decision.blockedRecipients), overrideLabel: "Send without encryption", keysLocked: false, blockedRecipients: decision.blockedRecipients },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (input.hasBcc) {
|
|
84
|
+
return { action: "blocked", block: { kind: "bcc", message: BCC_ENCRYPTED_MESSAGE, overrideLabel: "Send without encryption", keysLocked: false } };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (attachmentsAreAProblem(encrypting)) {
|
|
88
|
+
return { action: "rejected", message: ATTACHMENTS_UNSUPPORTED_MESSAGE };
|
|
89
|
+
}
|
|
90
|
+
if (imagesAreAProblem(encrypting)) {
|
|
91
|
+
return { action: "rejected", message: INLINE_IMAGES_UNSUPPORTED_MESSAGE };
|
|
92
|
+
}
|
|
93
|
+
return encrypting ? { action: "encrypt", sign: keys.canSign } : keys.canSign ? { action: "sign" } : { action: "plain" };
|
|
94
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Attachment, ComposeRecipientInput, Mailbox, Message } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
2
|
+
import { RecipientEncryptionStatus } from "@rapidmx/react-shared/crypto/composeSecurity.js";
|
|
3
|
+
import { EncryptionPolicy } from "@rapidmx/react-shared/crypto/keyvaultApi.js";
|
|
4
|
+
import { SendDecisionInput } from "./sendDecision.js";
|
|
5
|
+
import { OutboxCountTracker } from "./sendState.js";
|
|
6
|
+
/** Everything the background send needs, captured when Send is clicked: the window it came from is gone by the time it runs. */
|
|
7
|
+
export interface SendRequest {
|
|
8
|
+
/** The window's server draft. */
|
|
9
|
+
draft: Message;
|
|
10
|
+
mailboxUid: string;
|
|
11
|
+
/** The sender's mailbox as the window had loaded it - `undefined` when that load had not succeeded (the send then tries again itself). */
|
|
12
|
+
mailbox: Mailbox | undefined;
|
|
13
|
+
/** The encryption policy as the window had loaded it, `undefined` likewise. */
|
|
14
|
+
policy: EncryptionPolicy | undefined;
|
|
15
|
+
/** The window's To/Cc/Bcc field text, kept so "Open draft" restores them exactly. */
|
|
16
|
+
toText: string;
|
|
17
|
+
ccText: string;
|
|
18
|
+
bccText: string;
|
|
19
|
+
to: ComposeRecipientInput[];
|
|
20
|
+
cc: ComposeRecipientInput[];
|
|
21
|
+
bcc: ComposeRecipientInput[];
|
|
22
|
+
subject: string;
|
|
23
|
+
html: string;
|
|
24
|
+
attachments: Attachment[];
|
|
25
|
+
requestReceipt: boolean;
|
|
26
|
+
/** Send later: an ISO time in the future. Absent for an immediate send. */
|
|
27
|
+
scheduledSendTime?: string;
|
|
28
|
+
signEnabled: boolean;
|
|
29
|
+
/** The sign / encrypt options were shown to the sender at some point this compose session (their keys were unlocked). */
|
|
30
|
+
offeredSign: boolean;
|
|
31
|
+
offeredEncrypt: boolean;
|
|
32
|
+
encryptRequested: boolean;
|
|
33
|
+
/** The sender's explicit choice to send without encryption/signing checks (an override). */
|
|
34
|
+
forcePlaintext: boolean;
|
|
35
|
+
/** Settles when the window's own autosaves have landed, so none can overwrite the assembly below. */
|
|
36
|
+
saved?: Promise<unknown>;
|
|
37
|
+
}
|
|
38
|
+
/** How long a background send waits for the mailbox / encryption policy it did not have, before it goes ahead treating them as unknown. Nobody waits for it. */
|
|
39
|
+
export declare const CONTEXT_GRACE_MS = 4000;
|
|
40
|
+
export declare function registerOutboxCounter(tracker: (mailboxUid: string) => OutboxCountTracker, onQueued: (mailboxUid: string) => void): () => void;
|
|
41
|
+
/** The request a message was queued from, while this tab still holds it: what lets "Open draft" and "Retry" work on a failure the server reports later. */
|
|
42
|
+
export declare function retainedRequest(draftUid: string): SendRequest | undefined;
|
|
43
|
+
export declare function forgetRetainedRequest(draftUid: string): void;
|
|
44
|
+
/** The sender's signing/encryption key state right now, read from the tab's key session (they can lock mid-send). */
|
|
45
|
+
export declare function readSendKeys(mailboxUid: string, signEnabled: boolean, mailbox: Mailbox | undefined): {
|
|
46
|
+
unlocked: import("@rapidmx/react-shared/crypto/keySession.js").UnlockedKeys | undefined;
|
|
47
|
+
canSign: boolean;
|
|
48
|
+
canEncryptSelf: boolean;
|
|
49
|
+
};
|
|
50
|
+
/** Whether this sender could encrypt at all: an encryption key unlocked here, enrolled on the mailbox, or offered earlier this session. */
|
|
51
|
+
export declare function senderHasEncryptionKey(request: Pick<SendRequest, "mailbox" | "offeredEncrypt">, canEncryptSelf: boolean): boolean;
|
|
52
|
+
/** The input `decideSend()` reads, from a request and what has been looked up. */
|
|
53
|
+
export declare function sendDecisionInput(request: SendRequest, mailbox: Mailbox | undefined, policy: EncryptionPolicy | undefined, recipients: (RecipientEncryptionStatus | undefined)[], lookupsComplete: boolean): SendDecisionInput;
|
|
54
|
+
/** "Message to a@b.c and 2 others (\"Subject\")" - which message a pop-up is about. */
|
|
55
|
+
export declare function describeMessage(subject: string, recipients: string): string;
|
|
56
|
+
/** Re-opens the compose window around a request's message, with everything as it was typed. */
|
|
57
|
+
export declare function openDraftFromRequest(request: SendRequest): boolean;
|
|
58
|
+
/** The short "Message sent" confirmation - several sent close together are one pop-up counting up, never a stack of them. */
|
|
59
|
+
export declare function notifySent(message?: string): void;
|
|
60
|
+
/**
|
|
61
|
+
* Sends `request`'s draft in the background and returns at once, `true` - or `false` (doing nothing) when a send of this draft is already
|
|
62
|
+
* under way here. The outcome is shown as pop-ups and the Outbox indicator, never as a window that waits: a failure at any stage (assembling,
|
|
63
|
+
* encrypting, the request) is a sticky "This message wasn't sent" with the reason, and Retry / Open draft; the server's own later outcome
|
|
64
|
+
* (`send-succeeded`, `send-failed`, `send-retrying`) is `handleSendEvent()`'s.
|
|
65
|
+
*/
|
|
66
|
+
export declare function startSend(request: SendRequest): boolean;
|
|
67
|
+
/** Tries a failed send again - unless the server already has the message (an earlier request that seemed to fail did land), which it must not send twice. */
|
|
68
|
+
export declare function retrySend(request: SendRequest): Promise<void>;
|