@rapidmx/web-client 0.10.0 → 0.12.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 +183 -2
- 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/mailboxes/new/index.tsx +3 -3
- 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/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +4 -4
- 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 +60 -14
- package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +65 -16
- 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/contacts/ContactForm.tsx +11 -5
- package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -3
- package/apps/shared/components/contacts/ContactsToolbar.tsx +25 -52
- package/apps/shared/components/contacts/layout/ContactsShell.tsx +10 -5
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +13 -6
- package/apps/shared/components/layout/AppShell.tsx +98 -32
- package/apps/shared/components/layout/BrandingChrome.tsx +200 -13
- 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 +75 -6
- package/apps/shared/components/mail/ConversationList.tsx +4 -2
- package/apps/shared/components/mail/ConversationThreadPane.tsx +487 -475
- package/apps/shared/components/mail/LazyReadingPane.tsx +14 -16
- package/apps/shared/components/mail/MailSelectionBar.tsx +2 -10
- package/apps/shared/components/mail/MessageDetailPane.tsx +366 -287
- 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 +35 -2
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +239 -437
- package/apps/shared/components/mail/compose/encryptionRequirement.ts +86 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +39 -25
- 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/settings/SigningCertificateCard.tsx +344 -0
- package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -0
- package/apps/shared/components/sharing/PrincipalListField.tsx +82 -0
- package/apps/shared/components/sharing/PrincipalPicker.tsx +76 -0
- package/apps/shared/components/sharing/PrincipalResolver.tsx +164 -0
- package/apps/shared/components/tasks/TasksSidebar.tsx +5 -3
- package/apps/shared/components/tasks/layout/TasksShell.tsx +9 -5
- package/apps/shared/mail/folderCounts.ts +9 -3
- package/apps/shared/mail/folderTree.ts +143 -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 +81 -28
- package/apps/shared/mail/useMailLiveUpdates.ts +55 -10
- package/apps/shared/mail/useNewMailNotifications.ts +47 -32
- package/apps/shared/navigation/AppRouter.tsx +2 -2
- 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/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 +80 -0
- package/apps/www/_layout.tsx +8 -2
- package/apps/www/_routes.ts +1 -0
- package/apps/www/calendar/index.tsx +9 -14
- package/apps/www/contacts/[uid].tsx +5 -8
- package/apps/www/contacts/index.tsx +587 -584
- package/apps/www/index.tsx +38 -27
- package/apps/www/messages/[uid].tsx +3 -1
- package/apps/www/settings/appearance/index.tsx +21 -0
- package/apps/www/settings/auto-reply/index.tsx +3 -7
- package/apps/www/settings/encryption/index.tsx +151 -116
- package/apps/www/settings/filters/[uid].tsx +3 -1
- package/apps/www/settings/filters/new/index.tsx +3 -1
- package/apps/www/settings/privacy/index.tsx +3 -8
- package/apps/www/settings/read-receipts/index.tsx +3 -7
- package/apps/www/settings/sharing/index.tsx +44 -65
- package/apps/www/settings/signatures/[uid].tsx +3 -1
- package/apps/www/settings/signatures/new/index.tsx +3 -2
- package/apps/www/tasks/index.tsx +11 -16
- 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/mailboxes/new/index.js +1 -1
- 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/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +3 -2
- 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 +25 -5
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +18 -7
- 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/contacts/ContactForm.js +1 -1
- package/dist/apps/shared/components/contacts/ContactsSidebar.js +4 -2
- package/dist/apps/shared/components/contacts/ContactsToolbar.d.ts +3 -1
- package/dist/apps/shared/components/contacts/ContactsToolbar.js +24 -9
- package/dist/apps/shared/components/contacts/layout/ContactsShell.js +4 -1
- package/dist/apps/shared/components/escrow/layout/EscrowShell.js +7 -4
- package/dist/apps/shared/components/layout/AppShell.d.ts +6 -1
- package/dist/apps/shared/components/layout/AppShell.js +59 -13
- 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/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 +7 -1
- package/dist/apps/shared/components/layout/UserMenu.js +41 -7
- package/dist/apps/shared/components/mail/ConversationList.js +3 -2
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +2 -1
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +42 -20
- package/dist/apps/shared/components/mail/LazyReadingPane.js +9 -8
- package/dist/apps/shared/components/mail/MailSelectionBar.d.ts +3 -4
- package/dist/apps/shared/components/mail/MailSelectionBar.js +2 -3
- package/dist/apps/shared/components/mail/MessageDetailPane.d.ts +18 -7
- package/dist/apps/shared/components/mail/MessageDetailPane.js +86 -41
- 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 +8 -0
- package/dist/apps/shared/components/mail/compose/ComposeContext.js +28 -3
- package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +2 -1
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +229 -328
- 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/layout/MailShell.d.ts +2 -0
- package/dist/apps/shared/components/mail/layout/MailShell.js +20 -14
- 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/settings/SigningCertificateCard.d.ts +32 -0
- package/dist/apps/shared/components/settings/SigningCertificateCard.js +98 -0
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -0
- package/dist/apps/shared/components/sharing/PrincipalListField.d.ts +24 -0
- package/dist/apps/shared/components/sharing/PrincipalListField.js +23 -0
- package/dist/apps/shared/components/sharing/PrincipalPicker.d.ts +28 -0
- package/dist/apps/shared/components/sharing/PrincipalPicker.js +24 -0
- package/dist/apps/shared/components/sharing/PrincipalResolver.d.ts +44 -0
- package/dist/apps/shared/components/sharing/PrincipalResolver.js +92 -0
- package/dist/apps/shared/components/tasks/TasksSidebar.js +4 -2
- package/dist/apps/shared/components/tasks/layout/TasksShell.js +3 -1
- package/dist/apps/shared/mail/folderCounts.d.ts +1 -1
- package/dist/apps/shared/mail/folderCounts.js +9 -3
- package/dist/apps/shared/mail/folderTree.d.ts +55 -0
- package/dist/apps/shared/mail/folderTree.js +116 -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 +9 -8
- package/dist/apps/shared/mail/useMailConnection.js +64 -22
- package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +17 -4
- package/dist/apps/shared/mail/useMailLiveUpdates.js +36 -10
- package/dist/apps/shared/mail/useNewMailNotifications.d.ts +4 -10
- package/dist/apps/shared/mail/useNewMailNotifications.js +43 -23
- package/dist/apps/shared/navigation/AppRouter.js +2 -2
- 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/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 +80 -0
- package/dist/apps/www/_layout.d.ts +5 -1
- package/dist/apps/www/_layout.js +3 -2
- package/dist/apps/www/_routes.js +1 -0
- package/dist/apps/www/calendar/index.js +3 -4
- package/dist/apps/www/contacts/[uid].js +5 -7
- package/dist/apps/www/contacts/index.js +14 -21
- package/dist/apps/www/index.js +37 -19
- package/dist/apps/www/messages/[uid].js +3 -1
- 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.js +3 -6
- package/dist/apps/www/settings/encryption/index.js +103 -95
- package/dist/apps/www/settings/filters/[uid].js +3 -1
- package/dist/apps/www/settings/filters/new/index.js +3 -1
- package/dist/apps/www/settings/privacy/index.js +5 -8
- package/dist/apps/www/settings/read-receipts/index.js +3 -6
- package/dist/apps/www/settings/sharing/index.js +19 -33
- package/dist/apps/www/settings/signatures/[uid].js +3 -1
- package/dist/apps/www/settings/signatures/new/index.js +3 -2
- package/dist/apps/www/tasks/index.js +8 -14
- package/package.json +2 -2
- package/apps/shared/components/mail/NewMailToasts.tsx +0 -141
- package/apps/shared/components/mail/compose/SendFailureAlert.tsx +0 -48
- package/dist/apps/shared/components/mail/NewMailToasts.d.ts +0 -19
- package/dist/apps/shared/components/mail/NewMailToasts.js +0 -55
- 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,146 @@
|
|
|
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
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The messages this tab has been asked to send whose *client-side* work is not finished: assembling (and encrypting/signing, which takes
|
|
10
|
+
* a moment) and the request that hands them to the server. Once the server has answered `202 queued` a message is safe - the server
|
|
11
|
+
* owns it, and the Outbox folder's own count, list and push events say the rest - so it leaves this set.
|
|
12
|
+
*
|
|
13
|
+
* 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
|
|
14
|
+
* 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
|
|
15
|
+
* draft is refused while it is here.
|
|
16
|
+
*/
|
|
17
|
+
export type PendingStage = "preparing" | "queuing";
|
|
18
|
+
|
|
19
|
+
export interface PendingSend {
|
|
20
|
+
/** The draft's uid: one send per draft at a time. */
|
|
21
|
+
draftUid: string;
|
|
22
|
+
mailboxUid: string;
|
|
23
|
+
subject: string;
|
|
24
|
+
/** Every recipient's address. */
|
|
25
|
+
recipients: string[];
|
|
26
|
+
stage: PendingStage;
|
|
27
|
+
/** A send-later message: it goes to Outbox and waits for its time, rather than being relayed. */
|
|
28
|
+
scheduled: boolean;
|
|
29
|
+
startedAt: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const NO_SENDS: readonly PendingSend[] = [];
|
|
33
|
+
let pending: PendingSend[] = [];
|
|
34
|
+
let snapshot: readonly PendingSend[] = pending;
|
|
35
|
+
const listeners = new Set<() => void>();
|
|
36
|
+
let stopFlush: (() => void) | undefined;
|
|
37
|
+
let waiters: (() => void)[] = [];
|
|
38
|
+
|
|
39
|
+
function emit(): void {
|
|
40
|
+
snapshot = [...pending];
|
|
41
|
+
for (const listener of [...listeners]) {
|
|
42
|
+
listener();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** `beforeunload`: leaving now would lose a message the server has not received. */
|
|
47
|
+
function handleBeforeUnload(event: BeforeUnloadEvent): void {
|
|
48
|
+
// Only installed while something is pending (see `syncGuards()`).
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
event.returnValue = "";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 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. */
|
|
54
|
+
function syncGuards(): void {
|
|
55
|
+
if (pending.length > 0 && !stopFlush) {
|
|
56
|
+
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
57
|
+
// Sign Out flushes compose windows before it ends the session; a message still being prepared is waited for there too.
|
|
58
|
+
stopFlush = registerComposeFlush(() => whenSettled(20_000));
|
|
59
|
+
} else if (pending.length === 0 && stopFlush) {
|
|
60
|
+
window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
61
|
+
stopFlush();
|
|
62
|
+
stopFlush = undefined;
|
|
63
|
+
}
|
|
64
|
+
if (pending.length === 0) {
|
|
65
|
+
const done = waiters;
|
|
66
|
+
waiters = [];
|
|
67
|
+
for (const resolve of done) {
|
|
68
|
+
resolve();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Whether a send of this draft is under way in this tab. */
|
|
74
|
+
export function isSendPending(draftUid: string): boolean {
|
|
75
|
+
return pending.some((entry) => entry.draftUid === draftUid);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Starts tracking a send. `false` (and nothing tracked) when this draft is already being sent - the caller must not send it again. */
|
|
79
|
+
export function beginPendingSend(entry: Omit<PendingSend, "stage" | "startedAt">): boolean {
|
|
80
|
+
if (isSendPending(entry.draftUid)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
pending.push({ ...entry, stage: "preparing", startedAt: Date.now() });
|
|
84
|
+
syncGuards();
|
|
85
|
+
emit();
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function setPendingStage(draftUid: string, stage: PendingStage): void {
|
|
90
|
+
pending = pending.map((entry) => (entry.draftUid === draftUid ? { ...entry, stage } : entry));
|
|
91
|
+
emit();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** 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. */
|
|
95
|
+
export function finishPendingSend(draftUid: string): void {
|
|
96
|
+
pending = pending.filter((entry) => entry.draftUid !== draftUid);
|
|
97
|
+
syncGuards();
|
|
98
|
+
emit();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getPendingSends(): readonly PendingSend[] {
|
|
102
|
+
return snapshot;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function subscribePendingSends(listener: () => void): () => void {
|
|
106
|
+
listeners.add(listener);
|
|
107
|
+
return () => {
|
|
108
|
+
listeners.delete(listener);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** How many messages of `mailboxUid` are still being handed to the server: what the Outbox pill adds to its server count at once. */
|
|
113
|
+
export function pendingCountFor(mailboxUid: string, sends: readonly PendingSend[] = snapshot): number {
|
|
114
|
+
return sends.filter((entry) => entry.mailboxUid === mailboxUid).length;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Resolves (to whether everything settled) when no send is pending, or after `timeoutMs`. */
|
|
118
|
+
export function whenSettled(timeoutMs: number): Promise<boolean> {
|
|
119
|
+
if (pending.length === 0) {
|
|
120
|
+
return Promise.resolve(true);
|
|
121
|
+
}
|
|
122
|
+
return new Promise<boolean>((resolve) => {
|
|
123
|
+
const timer = setTimeout(() => {
|
|
124
|
+
waiters = waiters.filter((waiter) => waiter !== done);
|
|
125
|
+
resolve(false);
|
|
126
|
+
}, timeoutMs);
|
|
127
|
+
function done() {
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
resolve(true);
|
|
130
|
+
}
|
|
131
|
+
waiters.push(done);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The messages being sent, for a component. */
|
|
136
|
+
export function usePendingSends(): readonly PendingSend[] {
|
|
137
|
+
return useSyncExternalStore(subscribePendingSends, getPendingSends, () => NO_SENDS);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Forgets everything - for a test. */
|
|
141
|
+
export function resetPendingSends(): void {
|
|
142
|
+
pending = [];
|
|
143
|
+
waiters = [];
|
|
144
|
+
syncGuards();
|
|
145
|
+
emit();
|
|
146
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { RecipientEncryptionStatus, decideMessageEncryption } from "@rapidmx/react-shared/crypto/composeSecurity.js";
|
|
6
|
+
import type { EncryptionPolicy } from "@rapidmx/react-shared/crypto/keyvaultApi.js";
|
|
7
|
+
import { evaluateEncryptionRequirement } from "../../components/mail/compose/encryptionRequirement.js";
|
|
8
|
+
|
|
9
|
+
/** Signed/encrypted bodies are built client-side from the editor's HTML verbatim, so an inline image (an uploaded attachment previewed via
|
|
10
|
+
* its content URL, or a `cid:` reference) would never be carried - only `assembleDraft()`'s server-side plaintext path rewrites those into
|
|
11
|
+
* real MIME parts. */
|
|
12
|
+
export const INLINE_IMAGE_PATTERN = /<img\b[^>]*\bsrc\s*=\s*["']?(?:cid:|[^"'\s>]*\/mail\/attachments\/[^"'\s>]+\/content)/i;
|
|
13
|
+
|
|
14
|
+
export const KEYS_LOCKED_SIGN_MESSAGE =
|
|
15
|
+
"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.";
|
|
16
|
+
export const KEYS_LOCKED_ENCRYPT_MESSAGE =
|
|
17
|
+
"This message can't be encrypted right now - your encryption key is locked. Unlock and send again, or send it without encryption.";
|
|
18
|
+
export const BCC_ENCRYPTED_MESSAGE = "Bcc recipients can't be used with encrypted messages. Remove Bcc recipients or turn off encryption.";
|
|
19
|
+
export const LOOKUP_UNAVAILABLE_MESSAGE =
|
|
20
|
+
"Your recipients' encryption keys couldn't be checked, so this message can't be encrypted right now. Try again, or send it without encryption.";
|
|
21
|
+
/** The plain sentence for recipients an encrypted message can't reach (no key on file, or the policy prohibits it). */
|
|
22
|
+
export function recipientsBlockedMessage(blocked: RecipientEncryptionStatus[]): string {
|
|
23
|
+
const reason = blocked[0]?.prohibitedReason ? ` — ${blocked[0].prohibitedReason}` : " has no encryption key on file";
|
|
24
|
+
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.`;
|
|
25
|
+
}
|
|
26
|
+
export const MAILBOX_UNAVAILABLE_MESSAGE =
|
|
27
|
+
"Your mailbox details couldn't be loaded, so this message can't be encrypted right now. Try again, or send it without encryption.";
|
|
28
|
+
export const ATTACHMENTS_UNSUPPORTED_MESSAGE = "A signed or encrypted message cannot include file attachments yet - remove them before sending.";
|
|
29
|
+
export const INLINE_IMAGES_UNSUPPORTED_MESSAGE = "A signed or encrypted message cannot include inline images yet - remove them before sending.";
|
|
30
|
+
|
|
31
|
+
/** Why a send is refused *before* anything is sent: signing/encryption was asked for (or applies) but cannot happen right now. Never a silent
|
|
32
|
+
* downgrade to plaintext - the sender explicitly picks the override. */
|
|
33
|
+
export interface SendBlock {
|
|
34
|
+
kind: "signing-locked" | "encryption-locked" | "lookup-failed" | "mailbox-unavailable" | "bcc" | "recipients";
|
|
35
|
+
message: string;
|
|
36
|
+
/** The button that sends it anyway, without encryption (and without signing when that is what was blocked). */
|
|
37
|
+
overrideLabel: string;
|
|
38
|
+
/** The cause is keys that are enrolled but not unlocked in this tab: an Unlock action helps. */
|
|
39
|
+
keysLocked: boolean;
|
|
40
|
+
/** `recipients`: who has no key (or is not allowed one). */
|
|
41
|
+
blockedRecipients?: RecipientEncryptionStatus[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type SendDecision =
|
|
45
|
+
| { action: "plain" }
|
|
46
|
+
| { action: "sign" }
|
|
47
|
+
| { action: "encrypt"; sign: boolean }
|
|
48
|
+
| { action: "blocked"; block: SendBlock }
|
|
49
|
+
/** Not sendable as composed, and no override helps: fix the message. */
|
|
50
|
+
| { action: "rejected"; message: string };
|
|
51
|
+
|
|
52
|
+
export interface SendDecisionInput {
|
|
53
|
+
/** The sender's explicit choice to skip every encryption/signing check (an override button): it never encrypts, and still signs if it can. */
|
|
54
|
+
forcePlaintext: boolean;
|
|
55
|
+
/** The "sign" checkbox. */
|
|
56
|
+
signEnabled: boolean;
|
|
57
|
+
/** The signing option was shown at some point this compose session (the keys were unlocked): losing the keys then blocks, never silently unsigns. */
|
|
58
|
+
offeredSign: boolean;
|
|
59
|
+
encryptRequested: boolean;
|
|
60
|
+
policy: EncryptionPolicy | undefined;
|
|
61
|
+
mailboxLoaded: boolean;
|
|
62
|
+
hasEncryptionKey: boolean;
|
|
63
|
+
keys: {
|
|
64
|
+
/** Signing is possible now: enabled, mailbox loaded (for the protected headers), signing key unlocked. */
|
|
65
|
+
canSign: boolean;
|
|
66
|
+
/** The encryption key is unlocked. */
|
|
67
|
+
canEncryptSelf: boolean;
|
|
68
|
+
/** Any key material is unlocked in this tab. */
|
|
69
|
+
unlocked: boolean;
|
|
70
|
+
};
|
|
71
|
+
/** Every recipient (To, Cc and Bcc), resolved; `undefined` where the lookup failed - or, with `lookupsComplete: false`, was not made. */
|
|
72
|
+
recipients: (RecipientEncryptionStatus | undefined)[];
|
|
73
|
+
/** Whether the lookups were all *attempted*: `false` for the early check a compose window makes from what it already knows. */
|
|
74
|
+
lookupsComplete: boolean;
|
|
75
|
+
hasBcc: boolean;
|
|
76
|
+
hasAttachments: boolean;
|
|
77
|
+
hasInlineImages: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* How a message may go out, decided from the facts alone (no requests, no React): plain, signed, encrypted, refused with an override the sender
|
|
82
|
+
* can choose, or rejected outright. Used twice with the same rules - by the compose window, from what it already knows, to catch a problem
|
|
83
|
+
* *before* it closes (`lookupsComplete: false`), and by the background send, once its recipient lookups are in.
|
|
84
|
+
*
|
|
85
|
+
* Encryption follows `evaluateEncryptionRequirement()` - fail open: only a message the user asked to encrypt, or one the loaded policy
|
|
86
|
+
* encrypts, is encrypted; everything unknown is plain and sends. The one place unknown blocks is a message the user *explicitly* asked to
|
|
87
|
+
* encrypt whose recipients' keys could not be looked up: they asked for encryption, and it cannot be delivered.
|
|
88
|
+
*/
|
|
89
|
+
export function decideSend(input: SendDecisionInput): SendDecision {
|
|
90
|
+
const { keys } = input;
|
|
91
|
+
const attachmentsAreAProblem = (encrypting: boolean) => (encrypting || keys.canSign) && input.hasAttachments;
|
|
92
|
+
const imagesAreAProblem = (encrypting: boolean) => (encrypting || keys.canSign) && input.hasInlineImages;
|
|
93
|
+
|
|
94
|
+
if (input.forcePlaintext) {
|
|
95
|
+
if (attachmentsAreAProblem(false)) {
|
|
96
|
+
return { action: "rejected", message: ATTACHMENTS_UNSUPPORTED_MESSAGE };
|
|
97
|
+
}
|
|
98
|
+
if (imagesAreAProblem(false)) {
|
|
99
|
+
return { action: "rejected", message: INLINE_IMAGES_UNSUPPORTED_MESSAGE };
|
|
100
|
+
}
|
|
101
|
+
return keys.canSign ? { action: "sign" } : { action: "plain" };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (input.signEnabled && input.offeredSign && !keys.canSign) {
|
|
105
|
+
return {
|
|
106
|
+
action: "blocked",
|
|
107
|
+
block: { kind: "signing-locked", message: KEYS_LOCKED_SIGN_MESSAGE, overrideLabel: "Send without signing or encryption", keysLocked: !keys.unlocked },
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (input.encryptRequested && !input.mailboxLoaded) {
|
|
112
|
+
// Asked for explicitly, and there is nothing to encrypt with (the sender's address and keys come from the mailbox).
|
|
113
|
+
return { action: "blocked", block: { kind: "mailbox-unavailable", message: MAILBOX_UNAVAILABLE_MESSAGE, overrideLabel: "Send without encryption", keysLocked: false } };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const requirement = evaluateEncryptionRequirement({
|
|
117
|
+
encryptRequested: input.encryptRequested,
|
|
118
|
+
policy: input.policy,
|
|
119
|
+
mailboxLoaded: input.mailboxLoaded,
|
|
120
|
+
hasEncryptionKey: input.hasEncryptionKey,
|
|
121
|
+
recipients: input.recipients,
|
|
122
|
+
});
|
|
123
|
+
const encrypting = requirement.required;
|
|
124
|
+
|
|
125
|
+
if (encrypting) {
|
|
126
|
+
const statuses = input.recipients.filter((status): status is RecipientEncryptionStatus => !!status);
|
|
127
|
+
const allResolved = statuses.length === input.recipients.length;
|
|
128
|
+
if (!allResolved && input.lookupsComplete) {
|
|
129
|
+
// Only reachable for an explicit request (a policy requirement needs every status): the sender asked for encryption and the
|
|
130
|
+
// recipients' keys could not be checked.
|
|
131
|
+
return { action: "blocked", block: { kind: "lookup-failed", message: LOOKUP_UNAVAILABLE_MESSAGE, overrideLabel: "Send without encryption", keysLocked: false } };
|
|
132
|
+
}
|
|
133
|
+
if (!keys.canEncryptSelf) {
|
|
134
|
+
return { action: "blocked", block: { kind: "encryption-locked", message: KEYS_LOCKED_ENCRYPT_MESSAGE, overrideLabel: "Send without encryption", keysLocked: !keys.unlocked } };
|
|
135
|
+
}
|
|
136
|
+
if (allResolved) {
|
|
137
|
+
const decision = decideMessageEncryption(statuses);
|
|
138
|
+
if (!decision.canEncryptAll) {
|
|
139
|
+
return {
|
|
140
|
+
action: "blocked",
|
|
141
|
+
block: { kind: "recipients", message: recipientsBlockedMessage(decision.blockedRecipients), overrideLabel: "Send without encryption", keysLocked: false, blockedRecipients: decision.blockedRecipients },
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (input.hasBcc) {
|
|
146
|
+
return { action: "blocked", block: { kind: "bcc", message: BCC_ENCRYPTED_MESSAGE, overrideLabel: "Send without encryption", keysLocked: false } };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (attachmentsAreAProblem(encrypting)) {
|
|
151
|
+
return { action: "rejected", message: ATTACHMENTS_UNSUPPORTED_MESSAGE };
|
|
152
|
+
}
|
|
153
|
+
if (imagesAreAProblem(encrypting)) {
|
|
154
|
+
return { action: "rejected", message: INLINE_IMAGES_UNSUPPORTED_MESSAGE };
|
|
155
|
+
}
|
|
156
|
+
return encrypting ? { action: "encrypt", sign: keys.canSign } : keys.canSign ? { action: "sign" } : { action: "plain" };
|
|
157
|
+
}
|