@rapidmx/web-client 0.30.0 → 0.32.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.
Files changed (402) hide show
  1. package/apps/admin/audit-log/index.tsx +3 -3
  2. package/apps/admin/branding/index.tsx +3 -3
  3. package/apps/admin/data-requests/index.tsx +18 -7
  4. package/apps/admin/distribution-lists/[uid].tsx +3 -3
  5. package/apps/admin/distribution-lists/index.tsx +3 -3
  6. package/apps/admin/distribution-lists/new/index.tsx +3 -3
  7. package/apps/admin/domains/[uid].tsx +6 -5
  8. package/apps/admin/domains/index.tsx +3 -3
  9. package/apps/admin/domains/new/index.tsx +3 -3
  10. package/apps/admin/escrow-scopes/[uid].tsx +11 -5
  11. package/apps/admin/escrow-scopes/index.tsx +3 -3
  12. package/apps/admin/escrow-scopes/new/index.tsx +9 -3
  13. package/apps/admin/index.tsx +4 -4
  14. package/apps/admin/ingest-queue/index.tsx +3 -3
  15. package/apps/admin/mailboxes/[uid].tsx +15 -7
  16. package/apps/admin/quarantine/index.tsx +4 -4
  17. package/apps/admin/retention-policy/index.tsx +3 -3
  18. package/apps/admin/signing-certificates/index.tsx +3 -3
  19. package/apps/admin/transport-rules/[uid].tsx +4 -4
  20. package/apps/admin/transport-rules/index.tsx +3 -3
  21. package/apps/admin/transport-rules/new/index.tsx +3 -3
  22. package/apps/escrow/matters/[uid].tsx +26 -2
  23. package/apps/escrow/matters/new/index.tsx +4 -0
  24. package/apps/shared/calendar/calendarLiveUpdates.ts +11 -10
  25. package/apps/shared/calendar/useCalendarReminders.ts +2 -0
  26. package/apps/shared/components/admin/ActionAlert.tsx +32 -0
  27. package/apps/shared/components/admin/diagnostics/InformationPanel.tsx +3 -3
  28. package/apps/shared/components/admin/diagnostics/InstalledPlugins.tsx +1 -1
  29. package/apps/shared/components/admin/diagnostics/LogsPanel.tsx +1 -1
  30. package/apps/shared/components/admin/diagnostics/RuntimePanel.tsx +1 -1
  31. package/apps/shared/components/admin/diagnostics/SystemPanel.tsx +1 -1
  32. package/apps/shared/components/admin/diagnostics/diagnosticsApi.ts +2 -2
  33. package/apps/shared/components/admin/distributionLists/MemberListCard.tsx +4 -4
  34. package/apps/shared/components/admin/elevation.ts +19 -0
  35. package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +58 -3
  36. package/apps/shared/components/admin/layout/AdminShell.tsx +6 -1
  37. package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +3 -3
  38. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +4 -4
  39. package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +4 -4
  40. package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
  41. package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +6 -6
  42. package/apps/shared/components/admin/settings/BrandingForm.tsx +22 -9
  43. package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +4 -4
  44. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +33 -4
  45. package/apps/shared/components/admin/settings/LoadedSettingsForm.tsx +3 -3
  46. package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +8 -3
  47. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +3 -3
  48. package/apps/shared/components/admin/settings/PluginsManager.tsx +195 -46
  49. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +25 -12
  50. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +40 -7
  51. package/apps/shared/components/admin/setup/SetupWizard.tsx +8 -6
  52. package/apps/shared/components/admin/signOut.ts +3 -0
  53. package/apps/shared/components/admin/usePagedList.tsx +2 -2
  54. package/apps/shared/components/calendar/DescriptionEditor.tsx +2 -1
  55. package/apps/shared/components/calendar/EventDetails.tsx +34 -8
  56. package/apps/shared/components/calendar/EventEditor.tsx +90 -40
  57. package/apps/shared/components/calendar/EventFormParts.tsx +5 -2
  58. package/apps/shared/components/calendar/EventMatchNav.tsx +14 -2
  59. package/apps/shared/components/calendar/EventModal.tsx +25 -1
  60. package/apps/shared/components/calendar/FindATime.tsx +10 -2
  61. package/apps/shared/components/calendar/ListView.tsx +99 -25
  62. package/apps/shared/components/calendar/RecurrenceEditor.tsx +19 -8
  63. package/apps/shared/components/calendar/SplitDayView.tsx +14 -18
  64. package/apps/shared/components/calendar/TaskQuickForm.tsx +3 -2
  65. package/apps/shared/components/calendar/TimeGridView.tsx +35 -19
  66. package/apps/shared/components/calendar/gridLayout.ts +94 -0
  67. package/apps/shared/components/calendar/layout/CalendarShell.tsx +1 -1
  68. package/apps/shared/components/calendar/timePicker.ts +5 -2
  69. package/apps/shared/components/contacts/ContactCard.tsx +15 -4
  70. package/apps/shared/components/contacts/ContactDetailPane.tsx +54 -15
  71. package/apps/shared/components/contacts/ContactForm.tsx +61 -17
  72. package/apps/shared/components/contacts/ContactPhotoEditor.tsx +88 -0
  73. package/apps/shared/components/contacts/ContactPhotoField.tsx +36 -39
  74. package/apps/shared/components/contacts/layout/ContactsShell.tsx +1 -1
  75. package/apps/shared/components/contacts/participantDetails.ts +18 -1
  76. package/apps/shared/components/layout/AppShell.tsx +9 -0
  77. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +9 -4
  78. package/apps/shared/components/layout/MailboxProvisioning.tsx +31 -3
  79. package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +4 -2
  80. package/apps/shared/components/mail/ConversationList.tsx +49 -6
  81. package/apps/shared/components/mail/ConversationThreadPane.tsx +1 -1
  82. package/apps/shared/components/mail/MenuButton.tsx +3 -400
  83. package/apps/shared/components/mail/MessageDetailPane.tsx +16 -13
  84. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +11 -1
  85. package/apps/shared/components/mail/compose/ComposeWindow.tsx +57 -11
  86. package/apps/shared/components/mail/compose/GifPicker.tsx +10 -4
  87. package/apps/shared/components/mail/compose/RecipientInput.tsx +9 -7
  88. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +14 -1
  89. package/apps/shared/components/mail/compose/encryptionRequirement.ts +5 -0
  90. package/apps/shared/components/mail/compose/quotedBody.ts +31 -6
  91. package/apps/shared/components/mail/layout/MailShell.tsx +1 -1
  92. package/apps/shared/components/mail/reading/MessageBody.tsx +13 -7
  93. package/apps/shared/components/mail/reading/bodyContent.ts +2 -0
  94. package/apps/shared/components/mail/reading/bodyHtml.ts +25 -2
  95. package/apps/shared/components/mail/writableMailboxes.ts +2 -0
  96. package/apps/shared/components/rules/RuleBuilder.tsx +11 -2
  97. package/apps/shared/components/settings/GravatarSetting.tsx +27 -0
  98. package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -1
  99. package/apps/shared/components/tasks/dueDate.ts +29 -0
  100. package/apps/shared/components/tasks/layout/TasksShell.tsx +1 -1
  101. package/apps/shared/mail/decryptedMessages.ts +51 -2
  102. package/apps/shared/mail/listSnapshots.ts +2 -0
  103. package/apps/shared/mail/outbox/sendDecision.ts +14 -3
  104. package/apps/shared/mail/outbox/sendJob.ts +5 -0
  105. package/apps/shared/mail/useMailLiveUpdates.ts +7 -1
  106. package/apps/shared/mail/usePermanentDelete.tsx +3 -1
  107. package/apps/shared/notifications/apiErrors.ts +2 -0
  108. package/apps/shared/notifications/preferences.ts +1 -1
  109. package/apps/shared/notifications/store.ts +3 -1
  110. package/apps/shared/plugins/pluginNav.ts +4 -2
  111. package/apps/www/calendar/index.tsx +24 -12
  112. package/apps/www/contacts/[uid].tsx +6 -1
  113. package/apps/www/contacts/index.tsx +66 -12
  114. package/apps/www/index.tsx +91 -28
  115. package/apps/www/messages/[uid].tsx +8 -3
  116. package/apps/www/settings/auto-reply/index.tsx +13 -0
  117. package/apps/www/settings/blocked-senders/index.tsx +4 -0
  118. package/apps/www/settings/encryption/index.tsx +66 -4
  119. package/apps/www/settings/privacy/index.tsx +4 -1
  120. package/apps/www/settings/profile/index.tsx +12 -1
  121. package/apps/www/settings/sharing/index.tsx +11 -1
  122. package/apps/www/settings/signatures/[uid].tsx +6 -4
  123. package/apps/www/tasks/index.tsx +97 -29
  124. package/dist/apps/admin/audit-log/index.js +3 -3
  125. package/dist/apps/admin/branding/index.js +3 -3
  126. package/dist/apps/admin/data-requests/index.js +17 -7
  127. package/dist/apps/admin/distribution-lists/[uid].js +3 -3
  128. package/dist/apps/admin/distribution-lists/index.js +3 -3
  129. package/dist/apps/admin/distribution-lists/new/index.js +3 -3
  130. package/dist/apps/admin/domains/[uid].js +6 -5
  131. package/dist/apps/admin/domains/index.js +3 -3
  132. package/dist/apps/admin/domains/new/index.js +3 -3
  133. package/dist/apps/admin/escrow-scopes/[uid].js +11 -6
  134. package/dist/apps/admin/escrow-scopes/index.js +3 -3
  135. package/dist/apps/admin/escrow-scopes/new/index.js +9 -4
  136. package/dist/apps/admin/index.js +4 -4
  137. package/dist/apps/admin/ingest-queue/index.js +3 -3
  138. package/dist/apps/admin/mailboxes/[uid].js +15 -7
  139. package/dist/apps/admin/quarantine/index.js +4 -4
  140. package/dist/apps/admin/retention-policy/index.js +3 -3
  141. package/dist/apps/admin/signing-certificates/index.js +3 -3
  142. package/dist/apps/admin/transport-rules/[uid].js +4 -4
  143. package/dist/apps/admin/transport-rules/index.js +3 -3
  144. package/dist/apps/admin/transport-rules/new/index.js +3 -3
  145. package/dist/apps/escrow/matters/[uid].js +8 -3
  146. package/dist/apps/escrow/matters/new/index.js +4 -0
  147. package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -7
  148. package/dist/apps/shared/calendar/calendarLiveUpdates.js +10 -9
  149. package/dist/apps/shared/calendar/useCalendarReminders.js +2 -0
  150. package/dist/apps/shared/components/admin/ActionAlert.d.ts +13 -0
  151. package/dist/apps/shared/components/admin/ActionAlert.js +24 -0
  152. package/dist/apps/shared/components/admin/diagnostics/InformationPanel.js +2 -2
  153. package/dist/apps/shared/components/admin/diagnostics/InstalledPlugins.js +1 -1
  154. package/dist/apps/shared/components/admin/diagnostics/LogsPanel.js +1 -1
  155. package/dist/apps/shared/components/admin/diagnostics/RuntimePanel.js +1 -1
  156. package/dist/apps/shared/components/admin/diagnostics/SystemPanel.js +1 -1
  157. package/dist/apps/shared/components/admin/diagnostics/diagnosticsApi.d.ts +2 -2
  158. package/dist/apps/shared/components/admin/distributionLists/MemberListCard.js +4 -4
  159. package/dist/apps/shared/components/admin/elevation.d.ts +9 -0
  160. package/dist/apps/shared/components/admin/elevation.js +16 -0
  161. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.d.ts +2 -0
  162. package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +41 -1
  163. package/dist/apps/shared/components/admin/layout/AdminShell.js +2 -1
  164. package/dist/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.js +3 -3
  165. package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +4 -4
  166. package/dist/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.js +4 -4
  167. package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
  168. package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +6 -6
  169. package/dist/apps/shared/components/admin/settings/BrandingForm.js +17 -7
  170. package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +4 -4
  171. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +17 -5
  172. package/dist/apps/shared/components/admin/settings/LoadedSettingsForm.js +3 -3
  173. package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +8 -3
  174. package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +3 -3
  175. package/dist/apps/shared/components/admin/settings/PluginsManager.js +103 -18
  176. package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +17 -10
  177. package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +18 -8
  178. package/dist/apps/shared/components/admin/setup/SetupWizard.js +8 -6
  179. package/dist/apps/shared/components/admin/signOut.js +3 -0
  180. package/dist/apps/shared/components/admin/usePagedList.js +2 -2
  181. package/dist/apps/shared/components/calendar/DescriptionEditor.js +2 -1
  182. package/dist/apps/shared/components/calendar/EventDetails.d.ts +3 -1
  183. package/dist/apps/shared/components/calendar/EventDetails.js +7 -4
  184. package/dist/apps/shared/components/calendar/EventEditor.js +81 -33
  185. package/dist/apps/shared/components/calendar/EventFormParts.d.ts +3 -1
  186. package/dist/apps/shared/components/calendar/EventFormParts.js +3 -3
  187. package/dist/apps/shared/components/calendar/EventMatchNav.d.ts +1 -1
  188. package/dist/apps/shared/components/calendar/EventMatchNav.js +12 -2
  189. package/dist/apps/shared/components/calendar/EventModal.js +10 -2
  190. package/dist/apps/shared/components/calendar/FindATime.js +4 -3
  191. package/dist/apps/shared/components/calendar/ListView.d.ts +10 -4
  192. package/dist/apps/shared/components/calendar/ListView.js +92 -27
  193. package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
  194. package/dist/apps/shared/components/calendar/RecurrenceEditor.js +15 -10
  195. package/dist/apps/shared/components/calendar/SplitDayView.js +11 -17
  196. package/dist/apps/shared/components/calendar/TaskQuickForm.js +3 -2
  197. package/dist/apps/shared/components/calendar/TimeGridView.js +23 -21
  198. package/dist/apps/shared/components/calendar/gridLayout.d.ts +41 -0
  199. package/dist/apps/shared/components/calendar/gridLayout.js +83 -0
  200. package/dist/apps/shared/components/calendar/layout/CalendarShell.js +1 -1
  201. package/dist/apps/shared/components/calendar/timePicker.js +5 -2
  202. package/dist/apps/shared/components/contacts/ContactCard.js +9 -3
  203. package/dist/apps/shared/components/contacts/ContactDetailPane.d.ts +5 -3
  204. package/dist/apps/shared/components/contacts/ContactDetailPane.js +11 -5
  205. package/dist/apps/shared/components/contacts/ContactForm.js +54 -16
  206. package/dist/apps/shared/components/contacts/ContactPhotoEditor.d.ts +21 -0
  207. package/dist/apps/shared/components/contacts/ContactPhotoEditor.js +56 -0
  208. package/dist/apps/shared/components/contacts/ContactPhotoField.d.ts +7 -4
  209. package/dist/apps/shared/components/contacts/ContactPhotoField.js +19 -21
  210. package/dist/apps/shared/components/contacts/layout/ContactsShell.js +1 -1
  211. package/dist/apps/shared/components/contacts/participantDetails.d.ts +4 -1
  212. package/dist/apps/shared/components/contacts/participantDetails.js +17 -1
  213. package/dist/apps/shared/components/layout/AppShell.js +9 -0
  214. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +2 -3
  215. package/dist/apps/shared/components/layout/MailboxProvisioning.d.ts +4 -1
  216. package/dist/apps/shared/components/layout/MailboxProvisioning.js +24 -2
  217. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.d.ts +4 -2
  218. package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +4 -2
  219. package/dist/apps/shared/components/mail/ConversationList.js +49 -6
  220. package/dist/apps/shared/components/mail/ConversationThreadPane.js +1 -1
  221. package/dist/apps/shared/components/mail/MenuButton.d.ts +2 -78
  222. package/dist/apps/shared/components/mail/MenuButton.js +2 -211
  223. package/dist/apps/shared/components/mail/MessageDetailPane.js +10 -6
  224. package/dist/apps/shared/components/mail/compose/ComposeToolbar.d.ts +3 -0
  225. package/dist/apps/shared/components/mail/compose/ComposeToolbar.js +10 -1
  226. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +55 -10
  227. package/dist/apps/shared/components/mail/compose/GifPicker.js +10 -4
  228. package/dist/apps/shared/components/mail/compose/RecipientInput.js +9 -7
  229. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.d.ts +7 -1
  230. package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +13 -1
  231. package/dist/apps/shared/components/mail/compose/encryptionRequirement.d.ts +5 -0
  232. package/dist/apps/shared/components/mail/compose/quotedBody.js +28 -5
  233. package/dist/apps/shared/components/mail/layout/MailShell.js +1 -1
  234. package/dist/apps/shared/components/mail/reading/MessageBody.js +11 -6
  235. package/dist/apps/shared/components/mail/reading/bodyContent.js +2 -0
  236. package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +5 -1
  237. package/dist/apps/shared/components/mail/reading/bodyHtml.js +23 -2
  238. package/dist/apps/shared/components/mail/writableMailboxes.js +2 -0
  239. package/dist/apps/shared/components/rules/RuleBuilder.js +9 -1
  240. package/dist/apps/shared/components/settings/GravatarSetting.d.ts +6 -0
  241. package/dist/apps/shared/components/settings/GravatarSetting.js +15 -0
  242. package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -1
  243. package/dist/apps/shared/components/tasks/dueDate.d.ts +12 -0
  244. package/dist/apps/shared/components/tasks/dueDate.js +26 -0
  245. package/dist/apps/shared/components/tasks/layout/TasksShell.js +1 -1
  246. package/dist/apps/shared/mail/decryptedMessages.d.ts +8 -0
  247. package/dist/apps/shared/mail/decryptedMessages.js +46 -2
  248. package/dist/apps/shared/mail/listSnapshots.js +2 -0
  249. package/dist/apps/shared/mail/outbox/sendDecision.d.ts +6 -3
  250. package/dist/apps/shared/mail/outbox/sendDecision.js +13 -3
  251. package/dist/apps/shared/mail/outbox/sendJob.d.ts +1 -0
  252. package/dist/apps/shared/mail/outbox/sendJob.js +5 -1
  253. package/dist/apps/shared/mail/useMailLiveUpdates.d.ts +3 -0
  254. package/dist/apps/shared/mail/useMailLiveUpdates.js +6 -1
  255. package/dist/apps/shared/mail/usePermanentDelete.d.ts +2 -0
  256. package/dist/apps/shared/mail/usePermanentDelete.js +1 -1
  257. package/dist/apps/shared/notifications/apiErrors.js +2 -0
  258. package/dist/apps/shared/notifications/preferences.d.ts +1 -1
  259. package/dist/apps/shared/notifications/preferences.js +1 -1
  260. package/dist/apps/shared/notifications/store.js +3 -1
  261. package/dist/apps/shared/plugins/pluginNav.d.ts +2 -1
  262. package/dist/apps/shared/plugins/pluginNav.js +4 -2
  263. package/dist/apps/www/calendar/index.js +24 -12
  264. package/dist/apps/www/contacts/[uid].js +6 -2
  265. package/dist/apps/www/contacts/index.js +62 -11
  266. package/dist/apps/www/index.js +85 -30
  267. package/dist/apps/www/messages/[uid].js +8 -3
  268. package/dist/apps/www/settings/auto-reply/index.js +13 -1
  269. package/dist/apps/www/settings/blocked-senders/index.js +4 -0
  270. package/dist/apps/www/settings/encryption/index.js +29 -5
  271. package/dist/apps/www/settings/privacy/index.js +3 -2
  272. package/dist/apps/www/settings/profile/index.js +4 -1
  273. package/dist/apps/www/settings/sharing/index.js +10 -1
  274. package/dist/apps/www/settings/signatures/[uid].js +6 -4
  275. package/dist/apps/www/tasks/index.js +94 -32
  276. package/dist/lib/admin/auditLogApi.js +2 -2
  277. package/dist/lib/admin/distributionListsApi.js +2 -2
  278. package/dist/lib/admin/domainsApi.js +2 -2
  279. package/dist/lib/admin/escrowAuditLogApi.js +2 -2
  280. package/dist/lib/admin/escrowScopesApi.js +2 -2
  281. package/dist/lib/admin/pluginsApi.d.ts +31 -2
  282. package/dist/lib/admin/pluginsApi.js +23 -1
  283. package/dist/lib/admin/retentionPolicyApi.d.ts +3 -0
  284. package/dist/lib/admin/retentionPolicyApi.js +3 -0
  285. package/dist/lib/admin/transportRulesApi.js +2 -2
  286. package/dist/lib/appearance/preferencesApi.d.ts +1 -1
  287. package/dist/lib/appearance/preferencesApi.js +3 -14
  288. package/dist/lib/auth/session.d.ts +51 -12
  289. package/dist/lib/auth/session.js +214 -38
  290. package/dist/lib/branding/brandingApi.d.ts +3 -3
  291. package/dist/lib/branding/brandingApi.js +9 -21
  292. package/dist/lib/calendar/calendarApi.js +3 -1
  293. package/dist/lib/calendar/eventSearch.js +43 -6
  294. package/dist/lib/calendar/freeBusyApi.d.ts +2 -1
  295. package/dist/lib/calendar/freeBusyApi.js +13 -5
  296. package/dist/lib/calendar/recurrence.js +51 -3
  297. package/dist/lib/calendar/useDayKey.d.ts +7 -0
  298. package/dist/lib/calendar/useDayKey.js +43 -0
  299. package/dist/lib/components/avatar/ContactAvatar.d.ts +6 -2
  300. package/dist/lib/components/avatar/ContactAvatar.js +37 -8
  301. package/dist/lib/components/avatar/ContactPhotoAvatar.d.ts +11 -0
  302. package/dist/lib/components/avatar/ContactPhotoAvatar.js +18 -0
  303. package/dist/lib/components/forms/CameraCaptureModal.d.ts +17 -0
  304. package/dist/lib/components/forms/CameraCaptureModal.js +88 -0
  305. package/dist/lib/components/forms/ImageEditBadge.d.ts +28 -0
  306. package/dist/lib/components/forms/ImageEditBadge.js +62 -0
  307. package/dist/lib/components/menus/MenuButton.d.ts +78 -0
  308. package/dist/lib/components/menus/MenuButton.js +219 -0
  309. package/dist/lib/components/navigation/BottomTabBar.d.ts +3 -1
  310. package/dist/lib/components/navigation/BottomTabBar.js +22 -3
  311. package/dist/lib/components/overlays/PopoverPortal.d.ts +2 -1
  312. package/dist/lib/components/overlays/PopoverPortal.js +3 -2
  313. package/dist/lib/components/pickers/TimeZonePicker.js +13 -1
  314. package/dist/lib/contacts/contactsApi.d.ts +5 -2
  315. package/dist/lib/contacts/contactsApi.js +16 -19
  316. package/dist/lib/contacts/gravatar.d.ts +14 -3
  317. package/dist/lib/contacts/gravatar.js +42 -5
  318. package/dist/lib/contacts/preparePhoto.d.ts +24 -0
  319. package/dist/lib/contacts/preparePhoto.js +250 -0
  320. package/dist/lib/contacts/useContactPhotoSrc.d.ts +19 -0
  321. package/dist/lib/contacts/useContactPhotoSrc.js +153 -0
  322. package/dist/lib/contacts/vcard.d.ts +3 -0
  323. package/dist/lib/contacts/vcard.js +59 -7
  324. package/dist/lib/crypto/escrowKeys.d.ts +8 -0
  325. package/dist/lib/crypto/escrowKeys.js +20 -1
  326. package/dist/lib/crypto/messageSecurity.d.ts +7 -4
  327. package/dist/lib/crypto/messageSecurity.js +23 -5
  328. package/dist/lib/crypto/passwordUnlock.d.ts +6 -1
  329. package/dist/lib/crypto/passwordUnlock.js +13 -2
  330. package/dist/lib/crypto/smime.js +12 -2
  331. package/dist/lib/mail/compose/composeQuoting.js +3 -2
  332. package/dist/lib/mail/compose/quotedImages.d.ts +6 -3
  333. package/dist/lib/mail/compose/quotedImages.js +35 -9
  334. package/dist/lib/mail/labelsApi.js +2 -2
  335. package/dist/lib/mail/mailApi.d.ts +8 -5
  336. package/dist/lib/mail/mailApi.js +73 -39
  337. package/dist/lib/mail/mailDetailHooks.js +11 -3
  338. package/dist/lib/mail/mailFilterRulesApi.js +2 -2
  339. package/dist/lib/mail/mailSignaturesApi.js +2 -2
  340. package/dist/lib/mail/mailboxImportApi.d.ts +1 -1
  341. package/dist/lib/mail/mailboxImportApi.js +3 -15
  342. package/dist/lib/mail/messageBodySanitizer.d.ts +6 -0
  343. package/dist/lib/mail/messageBodySanitizer.js +44 -1
  344. package/dist/lib/search/queryGrammar.js +12 -4
  345. package/dist/lib/search/searchTier3.d.ts +6 -1
  346. package/dist/lib/search/searchTier3.js +54 -25
  347. package/dist/lib/tasks/tasksApi.js +3 -3
  348. package/dist/lib/util/api.d.ts +26 -4
  349. package/dist/lib/util/api.js +73 -16
  350. package/dist/lib/util/apiQuery.d.ts +11 -0
  351. package/dist/lib/util/apiQuery.js +11 -0
  352. package/dist/lib/util/settleWithLimit.d.ts +5 -0
  353. package/dist/lib/util/settleWithLimit.js +25 -0
  354. package/lib/admin/auditLogApi.ts +2 -2
  355. package/lib/admin/distributionListsApi.ts +2 -2
  356. package/lib/admin/domainsApi.ts +2 -2
  357. package/lib/admin/escrowAuditLogApi.ts +2 -2
  358. package/lib/admin/escrowScopesApi.ts +2 -2
  359. package/lib/admin/pluginsApi.ts +45 -3
  360. package/lib/admin/retentionPolicyApi.ts +4 -0
  361. package/lib/admin/transportRulesApi.ts +2 -2
  362. package/lib/appearance/preferencesApi.ts +3 -14
  363. package/lib/auth/session.ts +239 -39
  364. package/lib/branding/brandingApi.ts +9 -21
  365. package/lib/calendar/calendarApi.ts +3 -1
  366. package/lib/calendar/eventSearch.ts +46 -6
  367. package/lib/calendar/freeBusyApi.ts +14 -6
  368. package/lib/calendar/recurrence.ts +59 -8
  369. package/lib/calendar/useDayKey.ts +45 -0
  370. package/lib/components/avatar/ContactAvatar.tsx +46 -8
  371. package/lib/components/avatar/ContactPhotoAvatar.tsx +26 -0
  372. package/lib/components/forms/CameraCaptureModal.tsx +133 -0
  373. package/lib/components/forms/ImageEditBadge.tsx +121 -0
  374. package/lib/components/menus/MenuButton.tsx +408 -0
  375. package/lib/components/navigation/BottomTabBar.tsx +30 -8
  376. package/lib/components/overlays/PopoverPortal.tsx +3 -2
  377. package/lib/components/pickers/TimeZonePicker.tsx +17 -1
  378. package/lib/contacts/contactsApi.ts +18 -19
  379. package/lib/contacts/gravatar.ts +47 -5
  380. package/lib/contacts/preparePhoto.ts +273 -0
  381. package/lib/contacts/useContactPhotoSrc.ts +177 -0
  382. package/lib/contacts/vcard.ts +60 -7
  383. package/lib/crypto/escrowKeys.ts +21 -1
  384. package/lib/crypto/messageSecurity.ts +33 -9
  385. package/lib/crypto/passwordUnlock.ts +14 -2
  386. package/lib/crypto/smime.ts +13 -2
  387. package/lib/mail/compose/composeQuoting.ts +3 -2
  388. package/lib/mail/compose/quotedImages.ts +39 -9
  389. package/lib/mail/labelsApi.ts +2 -2
  390. package/lib/mail/mailApi.ts +96 -39
  391. package/lib/mail/mailDetailHooks.ts +11 -3
  392. package/lib/mail/mailFilterRulesApi.ts +2 -2
  393. package/lib/mail/mailSignaturesApi.ts +2 -2
  394. package/lib/mail/mailboxImportApi.ts +9 -15
  395. package/lib/mail/messageBodySanitizer.ts +47 -1
  396. package/lib/search/queryGrammar.ts +13 -4
  397. package/lib/search/searchTier3.ts +65 -32
  398. package/lib/tasks/tasksApi.ts +3 -3
  399. package/lib/util/api.ts +99 -16
  400. package/lib/util/apiQuery.ts +13 -0
  401. package/lib/util/settleWithLimit.ts +25 -0
  402. package/package.json +1 -1
@@ -63,7 +63,7 @@ function isEncryptedContentType(contentType) {
63
63
  return smimeType === "enveloped-data" || smimeType === "authenveloped-data";
64
64
  }
65
65
  const NO_KEY_ERROR = "This device doesn't have the key needed to decrypt this message.";
66
- const UNSUPPORTED_ENCRYPTION_ERROR = "This message uses an older, unauthenticated encryption algorithm that RapidMX doesn't accept, so it can't be opened safely.";
66
+ const UNSUPPORTED_ENCRYPTION_ERROR = "This message uses an older, unauthenticated encryption algorithm, or an encryption format, that RapidMX doesn't accept, so it can't be opened safely.";
67
67
  function renderDisplayBody(display) {
68
68
  if (display?.html !== undefined) {
69
69
  return { html: display.html };
@@ -94,6 +94,14 @@ function hasRepeatedAddressField(fields) {
94
94
  }
95
95
  return SINGLETON_ADDRESS_FIELDS.some((name) => (counts.get(name) ?? 0) > 1);
96
96
  }
97
+ /** Whether the unsigned (outer) `Date:` header value `outer` names the same instant as the signed one, `signed`. A signed date
98
+ * that is missing or unreadable leaves nothing to compare, but when there is one the outer must match it: a replayed message
99
+ * that dropped or garbled its outer Date is as changed as one that carries a new one. */
100
+ function sameInstant(signed, outer) {
101
+ // `String(undefined)` is not a date either.
102
+ const left = Date.parse(String(signed));
103
+ return Number.isNaN(left) || left === Date.parse(String(outer));
104
+ }
97
105
  function normalizePins(pins) {
98
106
  return (Array.isArray(pins) ? pins : pins === undefined ? [] : [pins]).map((pin) => pin.toLowerCase());
99
107
  }
@@ -138,7 +146,11 @@ function checkIdentityAndHeaders(input, certDer) {
138
146
  if (!sameAddressSet(extractAddresses(protectedHeaders?.cc), extractAddresses(outerHeaders["cc"]))) {
139
147
  return "header_mismatch";
140
148
  }
141
- if (input.compareSubject && normalizeSubject(protectedHeaders?.subject) !== normalizeSubject(outerHeaders["subject"])) {
149
+ // A signed-only message's outer Date is unsigned too: a signed message replayed later carries a new one. (An encrypted
150
+ // message's outer Date is checked against its HP-Outer copy instead - see `headerTamperDetected`.)
151
+ if (input.compareSubject &&
152
+ (normalizeSubject(protectedHeaders?.subject) !== normalizeSubject(outerHeaders["subject"]) ||
153
+ !sameInstant(protectedHeaders?.date, outerHeaders["date"]))) {
142
154
  return "header_mismatch";
143
155
  }
144
156
  }
@@ -250,14 +262,16 @@ async function evaluateLive(rawMime, unlocked, pinnedSignerFingerprints, readerA
250
262
  if (!parsed.decrypted) {
251
263
  return { result: { state: "encrypted", decryptError: parsed.unsupportedContentEncryption ? UNSUPPORTED_ENCRYPTION_ERROR : NO_KEY_ERROR }, kind: "encrypted" };
252
264
  }
265
+ // Anyone holding the reader's public certificate can encrypt a message with any protected headers they like, so they
266
+ // are only exposed once a signature over them verified - and a key-changed message keeps them aside for a seal.
253
267
  const content = {
254
268
  ...renderDisplayBody(parsed.displayBody),
255
269
  headerTamperDetected: parsed.headerTamperDetected,
256
270
  subject: parsed.protectedHeaders?.subject || undefined,
257
- ...exposedHeaders(parsed.protectedHeaders),
258
271
  attachments: parsed.attachments,
259
272
  ...addressing(parsed.protectedHeaders),
260
273
  };
274
+ const signedHeaders = exposedHeaders(parsed.protectedHeaders);
261
275
  if (parsed.signatureVerified === undefined) {
262
276
  return { result: { state: "encrypted", ...content }, kind: "encrypted" };
263
277
  }
@@ -266,9 +280,13 @@ async function evaluateLive(rawMime, unlocked, pinnedSignerFingerprints, readerA
266
280
  }
267
281
  const { failure, trusted, signer, acceptedSigner } = await checkSigner(parsed.signerCertificateDer, parsed.protectedHeaders, parsed.protectedHeaderFields, false);
268
282
  if (failure) {
269
- return { result: { state: "signature_failed", signatureFailureReason: failure, ...content, ...(failure === "signer_key_changed" ? acceptedSigner : signer) }, kind: "encrypted" };
283
+ return {
284
+ result: { state: "signature_failed", signatureFailureReason: failure, ...content, ...(failure === "signer_key_changed" ? acceptedSigner : signer) },
285
+ kind: "encrypted",
286
+ ...(failure === "signer_key_changed" ? { signedContent: signedHeaders } : {}),
287
+ };
270
288
  }
271
- return { result: { state: trusted ? "encrypted_verified" : "encrypted_unverified_signer", ...content, ...acceptedSigner }, kind: "encrypted" };
289
+ return { result: { state: trusted ? "encrypted_verified" : "encrypted_unverified_signer", ...content, ...signedHeaders, ...acceptedSigner }, kind: "encrypted" };
272
290
  }
273
291
  return { result: { state: "unprotected" }, kind: "other" };
274
292
  }
@@ -9,6 +9,10 @@ export interface Argon2idParams {
9
9
  * matching the doc comment on `EncryptionCertificateAuthority`'s own conservative defaults elsewhere in
10
10
  * this system). */
11
11
  export declare const DEFAULT_ARGON2ID_PARAMS: Argon2idParams;
12
+ /** The most a `MasterKeyWrap.kdf` label read back from the server may ask this device to spend: 1 GiB (in KiB),
13
+ * 10 passes and 16 lanes - well above every default this system has used, but far from what a hostile or buggy
14
+ * server could name (`m=4294967295`) to freeze the tab or exhaust WebAssembly memory. */
15
+ export declare const MAX_ARGON2ID_PARAMS: Argon2idParams;
12
16
  /** Builds the `kdf` string stored alongside a password-method `MasterKeyWrap`, so parameters can be
13
17
  * upgraded over time without breaking existing accounts (each wrap records the parameters it was
14
18
  * actually created with). */
@@ -17,7 +21,8 @@ export declare function argon2idKdfLabel(params: Argon2idParams): string;
17
21
  * created with, so unlocking always re-derives with the *original* parameters even if
18
22
  * `DEFAULT_ARGON2ID_PARAMS` has since changed (the spec's own "KDF parameters ... MUST be stored
19
23
  * alongside each wrap, so costs can be raised over time without breaking existing accounts"). Returns
20
- * `undefined` for anything that isn't a well-formed `argon2id:m=...,t=...,p=...` label — including a
24
+ * `undefined` for anything that isn't a well-formed `argon2id:m=...,t=...,p=...` label, or whose parameters are
25
+ * zero or beyond `MAX_ARGON2ID_PARAMS` — including a
21
26
  * different KDF entirely (e.g. recovery codes' own `"hkdf-sha256"` label), which callers must not
22
27
  * attempt to unlock as if it were password-derived.
23
28
  */
@@ -23,6 +23,10 @@ import { hkdfDerive } from "./masterKey.js";
23
23
  * this system). */
24
24
  export const DEFAULT_ARGON2ID_PARAMS = { memorySize: 65536, iterations: 3, parallelism: 4 };
25
25
  const ARGON2ID_HASH_LENGTH_BYTES = 32;
26
+ /** The most a `MasterKeyWrap.kdf` label read back from the server may ask this device to spend: 1 GiB (in KiB),
27
+ * 10 passes and 16 lanes - well above every default this system has used, but far from what a hostile or buggy
28
+ * server could name (`m=4294967295`) to freeze the tab or exhaust WebAssembly memory. */
29
+ export const MAX_ARGON2ID_PARAMS = { memorySize: 1048576, iterations: 10, parallelism: 16 };
26
30
  /** Builds the `kdf` string stored alongside a password-method `MasterKeyWrap`, so parameters can be
27
31
  * upgraded over time without breaking existing accounts (each wrap records the parameters it was
28
32
  * actually created with). */
@@ -33,7 +37,8 @@ export function argon2idKdfLabel(params) {
33
37
  * created with, so unlocking always re-derives with the *original* parameters even if
34
38
  * `DEFAULT_ARGON2ID_PARAMS` has since changed (the spec's own "KDF parameters ... MUST be stored
35
39
  * alongside each wrap, so costs can be raised over time without breaking existing accounts"). Returns
36
- * `undefined` for anything that isn't a well-formed `argon2id:m=...,t=...,p=...` label — including a
40
+ * `undefined` for anything that isn't a well-formed `argon2id:m=...,t=...,p=...` label, or whose parameters are
41
+ * zero or beyond `MAX_ARGON2ID_PARAMS` — including a
37
42
  * different KDF entirely (e.g. recovery codes' own `"hkdf-sha256"` label), which callers must not
38
43
  * attempt to unlock as if it were password-derived.
39
44
  */
@@ -42,7 +47,13 @@ export function parseArgon2idKdfLabel(label) {
42
47
  if (!match) {
43
48
  return undefined;
44
49
  }
45
- return { memorySize: Number(match[1]), iterations: Number(match[2]), parallelism: Number(match[3]) };
50
+ const params = { memorySize: Number(match[1]), iterations: Number(match[2]), parallelism: Number(match[3]) };
51
+ const inRange = (value, max) => value >= 1 && value <= max;
52
+ return inRange(params.memorySize, MAX_ARGON2ID_PARAMS.memorySize) &&
53
+ inRange(params.iterations, MAX_ARGON2ID_PARAMS.iterations) &&
54
+ inRange(params.parallelism, MAX_ARGON2ID_PARAMS.parallelism)
55
+ ? params
56
+ : undefined;
46
57
  }
47
58
  /** A fresh random salt for a new password enrollment. 16 bytes is Argon2id's own recommended minimum. */
48
59
  export function generateSalt(lengthBytes = 16) {
@@ -36,6 +36,9 @@ const CONTENT_ENCRYPTION_ALGORITHM = { name: "AES-GCM", length: 256 };
36
36
  const ID_DATA = pkijs.ContentInfo.DATA;
37
37
  /** AES-GCM content-encryption OIDs (id-aes128-GCM, id-aes192-GCM, id-aes256-GCM - RFC 5084). The only
38
38
  * content encryption `decryptEnvelopedData()` accepts: see `UnsupportedContentEncryptionError`. */
39
+ /** The CMS content type of RFC 5083 `AuthEnvelopedData` (`smime-type="authenveloped-data"`): authenticated, but not the
40
+ * structure `decryptEnvelopedDataWithKeys()` reads - it is reported as unsupported rather than as a missing key. */
41
+ const AUTH_ENVELOPED_DATA_OID = "1.2.840.113549.1.9.16.1.23";
39
42
  const AEAD_CONTENT_ENCRYPTION_OIDS = new Set(["2.16.840.1.101.3.4.1.6", "2.16.840.1.101.3.4.1.26", "2.16.840.1.101.3.4.1.46"]);
40
43
  /**
41
44
  * Thrown by `decryptEnvelopedData()` for content encrypted with a non-AEAD algorithm (e.g. AES-CBC, 3DES).
@@ -48,7 +51,7 @@ const AEAD_CONTENT_ENCRYPTION_OIDS = new Set(["2.16.840.1.101.3.4.1.6", "2.16.84
48
51
  */
49
52
  export class UnsupportedContentEncryptionError extends Error {
50
53
  constructor(algorithmOid) {
51
- super(`This message uses unauthenticated content encryption (${algorithmOid}), which is not accepted.`);
54
+ super(`This message uses content encryption (${algorithmOid}) that is not accepted: only AES-GCM EnvelopedData is.`);
52
55
  this.name = "UnsupportedContentEncryptionError";
53
56
  this.algorithmOid = algorithmOid;
54
57
  }
@@ -155,10 +158,12 @@ export function extractCertificateEmails(certDer) {
155
158
  return [];
156
159
  }
157
160
  const sanEmails = [];
161
+ let hasSan = false;
158
162
  for (const extension of cert.extensions ?? []) {
159
163
  if (extension.extnID !== "2.5.29.17") {
160
164
  continue;
161
165
  }
166
+ hasSan = true;
162
167
  // pkijs parses a well-formed SubjectAlternativeName into an AltName automatically, and leaves
163
168
  // `parsedValue` unset for a malformed one - a certificate asserting an unreadable SAN names nobody.
164
169
  const altNames = extension.parsedValue?.altNames;
@@ -172,7 +177,9 @@ export function extractCertificateEmails(certDer) {
172
177
  }
173
178
  }
174
179
  }
175
- if (sanEmails.length > 0) {
180
+ // The SAN is authoritative whenever the certificate has one: DNS names alone name no mailbox, so a subject email
181
+ // address next to them must not stand in for it.
182
+ if (hasSan) {
176
183
  return sanEmails;
177
184
  }
178
185
  const subjectEmails = [];
@@ -322,6 +329,9 @@ function recipientMatchesCertificate(recipientInfo, certificate) {
322
329
  */
323
330
  export async function decryptEnvelopedDataWithKeys(envelopedDer, keys) {
324
331
  const contentInfo = pkijs.ContentInfo.fromBER(toArrayBuffer(envelopedDer));
332
+ if (contentInfo.contentType === AUTH_ENVELOPED_DATA_OID) {
333
+ throw new UnsupportedContentEncryptionError(AUTH_ENVELOPED_DATA_OID);
334
+ }
325
335
  if (contentInfo.contentType !== pkijs.ContentInfo.ENVELOPED_DATA) {
326
336
  throw new Error("This CMS content is not EnvelopedData.");
327
337
  }
@@ -13,7 +13,7 @@
13
13
  * a stricter tag list for the editor. `message.bodyPreview` - a server-derived excerpt, truncated - is only the last
14
14
  * resort, when no body could be loaded.
15
15
  */
16
- import { sanitizeQuotedHtml } from "../messageBodySanitizer.js";
16
+ import { sanitizeQuotedHtml, sanitizeSignatureHtml } from "../messageBodySanitizer.js";
17
17
  function escapeHtml(text) {
18
18
  return text
19
19
  .replace(/&/g, "&amp;")
@@ -126,7 +126,8 @@ export function buildComposeBodyHtml(signatureHtml, quotedHtml) {
126
126
  }
127
127
  const separator = signatureHtml && quotedHtml ? "<p></p>" : "";
128
128
  const aboveQuote = quotedHtml ? "<p></p>" : "";
129
- return `<p></p>${signatureHtml ?? ""}${separator}${aboveQuote}${quotedHtml ?? ""}`;
129
+ // A signature can be written by someone else (a shared mailbox's delegate): it is sanitized like anything else the editor is given.
130
+ return `<p></p>${signatureHtml ? sanitizeSignatureHtml(signatureHtml) : ""}${separator}${aboveQuote}${quotedHtml ?? ""}`;
130
131
  }
131
132
  /**
132
133
  * The longest `References` chain `buildReplyThreading()` reports. A chain grows by one entry per reply forever
@@ -15,7 +15,10 @@ export interface QuotedImageReference {
15
15
  }
16
16
  /**
17
17
  * `html` with each of its embedded images (`cid:`, or no source at all) replaced by the `data:` URI `resolve` gives for it. An image
18
- * `resolve` has no picture for is left as it is (the quote's sanitizer then drops it); images that already are `data:` URIs and remote
19
- * ones are not asked about. `html` is returned as given when it has no such image, or where there is no DOM to parse it with.
18
+ * `resolve` has no picture for (or fails on) is left as it is (the quote's sanitizer then drops it); images that already are `data:` URIs and
19
+ * remote ones are not asked about. `html` is returned as given when it has no such image, or where there is no DOM to parse it with.
20
+ *
21
+ * The images are resolved a few at a time. When `signal` aborts (the caller's deadline) the wait ends there: the pictures that have arrived
22
+ * are embedded, the rest left as they were, and `resolve` - which is given `signal` to cancel its own request with - is asked for no more.
20
23
  */
21
- export declare function embedQuotedImages(html: string, resolve: (reference: QuotedImageReference) => Promise<string | undefined>): Promise<string>;
24
+ export declare function embedQuotedImages(html: string, resolve: (reference: QuotedImageReference, signal?: AbortSignal) => Promise<string | undefined>, signal?: AbortSignal): Promise<string>;
@@ -12,26 +12,52 @@
12
12
  export const MAX_QUOTED_IMAGE_BYTES = 2000000;
13
13
  /** The most picture data a quote carries, in all (as `data:` URI characters). */
14
14
  export const MAX_QUOTED_IMAGES_CHARS = 6000000;
15
+ /** How many pictures are fetched at once. */
16
+ const QUOTED_IMAGE_CONCURRENCY = 4;
15
17
  /**
16
18
  * `html` with each of its embedded images (`cid:`, or no source at all) replaced by the `data:` URI `resolve` gives for it. An image
17
- * `resolve` has no picture for is left as it is (the quote's sanitizer then drops it); images that already are `data:` URIs and remote
18
- * ones are not asked about. `html` is returned as given when it has no such image, or where there is no DOM to parse it with.
19
+ * `resolve` has no picture for (or fails on) is left as it is (the quote's sanitizer then drops it); images that already are `data:` URIs and
20
+ * remote ones are not asked about. `html` is returned as given when it has no such image, or where there is no DOM to parse it with.
21
+ *
22
+ * The images are resolved a few at a time. When `signal` aborts (the caller's deadline) the wait ends there: the pictures that have arrived
23
+ * are embedded, the rest left as they were, and `resolve` - which is given `signal` to cancel its own request with - is asked for no more.
19
24
  */
20
- export async function embedQuotedImages(html, resolve) {
25
+ export async function embedQuotedImages(html, resolve, signal) {
21
26
  if (typeof DOMParser === "undefined" || !/<img\b/i.test(html)) {
22
27
  return html;
23
28
  }
24
29
  const doc = new DOMParser().parseFromString(html, "text/html");
25
- let budget = MAX_QUOTED_IMAGES_CHARS;
26
- let changed = false;
30
+ const targets = [];
27
31
  for (const image of Array.from(doc.querySelectorAll("img"))) {
28
32
  const src = (image.getAttribute("src") ?? "").trim();
29
33
  const cid = /^cid:/i.test(src);
30
- if (!cid && src !== "") {
31
- continue;
34
+ if (cid || src === "") {
35
+ targets.push({ image, reference: { ...(cid ? { cid: src.slice(4) } : {}), alt: image.getAttribute("alt") ?? undefined } });
36
+ }
37
+ }
38
+ let next = 0;
39
+ const worker = async () => {
40
+ while (next < targets.length && !signal?.aborted) {
41
+ const target = targets[next++];
42
+ target.uri = await resolve(target.reference, signal).catch(() => undefined);
32
43
  }
33
- const uri = await resolve({ ...(cid ? { cid: src.slice(4) } : {}), alt: image.getAttribute("alt") ?? undefined });
34
- if (uri !== undefined && uri.length <= budget) {
44
+ };
45
+ let onAbort;
46
+ const deadline = new Promise((done) => {
47
+ onAbort = done;
48
+ if (signal?.aborted) {
49
+ done();
50
+ }
51
+ signal?.addEventListener("abort", onAbort, { once: true });
52
+ });
53
+ await Promise.race([Promise.all(Array.from({ length: Math.min(QUOTED_IMAGE_CONCURRENCY, targets.length) }, worker)), deadline]);
54
+ signal?.removeEventListener("abort", onAbort);
55
+ // Whatever has arrived by now is embedded, in document order, for as long as the budget lasts.
56
+ let budget = MAX_QUOTED_IMAGES_CHARS;
57
+ let changed = false;
58
+ for (const { image, uri } of targets) {
59
+ // Only a picture of the original's own is ever put in: whatever a resolver answers, a `src` that is not a `data:` image never gets in.
60
+ if (uri !== undefined && /^data:image\//i.test(uri) && uri.length <= budget) {
35
61
  budget -= uri.length;
36
62
  image.setAttribute("src", uri);
37
63
  changed = true;
@@ -10,12 +10,12 @@
10
10
  * automatically for inbound mail; there is no separate "assign" endpoint.
11
11
  */
12
12
  import { withClient } from "../util/api.js";
13
- import { buildQuery } from "../util/apiQuery.js";
13
+ import { SETTINGS_LIST_LIMIT, buildQuery } from "../util/apiQuery.js";
14
14
  /** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
15
15
  * account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
16
16
  * own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
17
17
  export function listLabels(mailboxUid, params = {}, client) {
18
- return withClient(client, `/mail/labels?${buildQuery(params, { mailboxUid })}`);
18
+ return withClient(client, `/mail/labels?${buildQuery({ limit: SETTINGS_LIST_LIMIT, ...params }, { mailboxUid })}`);
19
19
  }
20
20
  export function getLabel(uid, client) {
21
21
  return withClient(client, `/mail/labels/${encodeURIComponent(uid)}`);
@@ -692,12 +692,12 @@ export declare function attachmentContentUrl(uid: string): string;
692
692
  /**
693
693
  * Uploads a file's raw bytes as a new attachment on a not-yet-sent draft. Bypasses `apiFetch` — that helper
694
694
  * always forces `Content-Type: application/json`, which would corrupt binary content; this sends the file's
695
- * own bytes/type directly instead, matching `BaseAttachmentRoute.upload`'s expectation of a raw request body.
695
+ * own bytes/type directly instead (through `client` when one is given), matching `BaseAttachmentRoute.upload`'s expectation of a raw request body.
696
696
  */
697
697
  export declare function uploadAttachment(messageUid: string, file: File, options?: {
698
698
  inline?: boolean;
699
699
  contentId?: string;
700
- }): Promise<Attachment>;
700
+ }, client?: ApiClient): Promise<Attachment>;
701
701
  export interface ComposeRecipientInput {
702
702
  address: string;
703
703
  displayName?: string;
@@ -760,6 +760,8 @@ export declare function emptyFolder(folderUid: string, client?: ApiClient): Prom
760
760
  * itself does no MIME composition). Does not send the message.
761
761
  */
762
762
  export declare function assembleDraft(messageUid: string, input: AssembleDraftInput, client?: ApiClient): Promise<Message>;
763
+ /** Forgets the pictures attached to draft `messageUid`, for when it has been sent or deleted (they are only remembered to avoid attaching one twice). */
764
+ export declare function forgetDraftImages(messageUid: string): void;
763
765
  export interface AssembleDraftRawInput {
764
766
  to: ComposeRecipientInput[];
765
767
  cc?: ComposeRecipientInput[];
@@ -812,8 +814,9 @@ export interface QueuedSend {
812
814
  export declare function queueMessageSend(messageUid: string, client?: ApiClient): Promise<QueuedSend>;
813
815
  /**
814
816
  * Fetches a message's raw RFC 5322 MIME source — for client-side E2E decrypt/signature-verification
815
- * only (`crypto/smimeMessage.ts`), never for display. Bypasses `apiFetch()` (which only ever decodes a
816
- * JSON response body): this server-local route (`BaseMessageRawContentRoute.ts` in `server`, mounted
817
+ * only (`crypto/smimeMessage.ts`), never for display. Goes through `withClientBlob()` rather than `apiFetch()` (which
818
+ * only ever decodes a JSON response body, and renews an expired session) - the same session renewal and, given a
819
+ * `client`, the same account and token as every other call: this server-local route (`BaseMessageRawContentRoute.ts` in `server`, mounted
817
820
  * alongside `@rapidmx/restapi`'s own `MessageRoute`) returns `message/rfc822`, not JSON — the one thing
818
821
  * that library's own `GET /:id/content` deliberately never serves (see that route's own doc comment).
819
822
  *
@@ -823,7 +826,7 @@ export declare function queueMessageSend(messageUid: string, client?: ApiClient)
823
826
  * straight to `evaluateMessageSecurity()` (see `crypto/mime.ts`'s doc comment on binary strings); it is
824
827
  * not display text.
825
828
  */
826
- export declare function getMessageRawContent(messageUid: string): Promise<string>;
829
+ export declare function getMessageRawContent(messageUid: string, client?: ApiClient): Promise<string>;
827
830
  export interface ImpersonationResult {
828
831
  token: string;
829
832
  user: {
@@ -12,11 +12,12 @@
12
12
  * to see somebody's mail impersonates them (`impersonateUser()`). See `BaseMailboxRoute`'s doc comment in
13
13
  * `@rapidmx/restapi` and this repo's `.claude/NOTES.md`.
14
14
  */
15
- import { ApiRequestError, apiFetch, apiUrl, authApiFetch, withClient, withCsrfHeader } from "../util/api.js";
16
- import { buildQuery } from "../util/apiQuery.js";
15
+ import { ApiRequestError, apiFetch, apiUrl, authApiFetch, withClient, withClientBlob, withClientRaw, } from "../util/api.js";
16
+ import { buildQuery, sortQuery } from "../util/apiQuery.js";
17
17
  import { deviceTimeZone } from "../util/timeZone.js";
18
18
  import { bytesToBinaryString } from "../crypto/mime.js";
19
- import { newContentId, parseDataImage } from "./inlineImages.js";
19
+ import { EMBEDDED_IMAGE_TYPES, newContentId, parseDataImage } from "./inlineImages.js";
20
+ import { MAX_QUOTED_IMAGE_BYTES } from "./compose/quotedImages.js";
20
21
  /** Whether `mailbox` is one shared with the caller rather than their own: a delegate grant on somebody's mailbox, or a mailbox with no
21
22
  * owner at all (a shared/org mailbox they were granted). */
22
23
  export function isSharedWithMe(mailbox) {
@@ -41,7 +42,7 @@ export function safeSendersOf(mailbox) {
41
42
  /** Lists the mailboxes the caller owns or has been granted - the same for everyone, an administrator included. With
42
43
  * `scope: "admin"` (admin console only) it lists every mailbox as administrative metadata instead. */
43
44
  export function listMailboxes(params = {}, client) {
44
- return withClient(client, `/mail/mailboxes?${buildQuery(params, scopeQuery(params))}`);
45
+ return withClient(client, `/mail/mailboxes?${buildQuery(params, { ...scopeQuery(params), ...(params.scope === "admin" ? sortQuery({ uid: "ASC" }) : {}) })}`);
45
46
  }
46
47
  /** One mailbox the caller owns or has been granted; with `scope: "admin"` (admin console only), its administrative metadata. */
47
48
  export function getMailbox(uid, options = {}, client) {
@@ -103,7 +104,7 @@ export function deleteMailbox(uid, version, options = {}, client) {
103
104
  }
104
105
  /** Lists quarantined mail for a mailbox the caller owns or has been granted; with `scope: "admin"` (admin console only), for any mailbox. */
105
106
  export function listQuarantine(mailboxUid, params = {}, client) {
106
- return withClient(client, `/mail/quarantine?${buildQuery(params, { mailboxUid, ...scopeQuery(params) })}`);
107
+ return withClient(client, `/mail/quarantine?${buildQuery(params, { mailboxUid, ...scopeQuery(params), ...sortQuery({ dateCreated: "DESC", uid: "ASC" }) })}`);
107
108
  }
108
109
  /**
109
110
  * Marks a quarantined entry released. This only updates the record's metadata — it does not re-inject the
@@ -118,7 +119,7 @@ export function releaseQuarantineEntry(uid, version, releasedByUserUid, client)
118
119
  /** Lists ingest-queue entries for a mailbox the caller owns or has been granted - useful for diagnosing stuck delivery; with
119
120
  * `scope: "admin"` (admin console only), for any mailbox. */
120
121
  export function listIngestQueue(mailboxUid, params = {}, client) {
121
- return withClient(client, `/mail/ingest-queue?${buildQuery(params, { mailboxUid, ...scopeQuery(params) })}`);
122
+ return withClient(client, `/mail/ingest-queue?${buildQuery(params, { mailboxUid, ...scopeQuery(params), ...sortQuery({ dateCreated: "DESC", uid: "ASC" }) })}`);
122
123
  }
123
124
  /** Fetches a mailbox's own ACL — its `records` are its owner's/delegates' grants (see BaseACLRoute). The server only answers a
124
125
  * caller who holds full access to that mailbox as themselves (never through a trusted role): to list, grant or revoke a mailbox's
@@ -417,9 +418,9 @@ export function attachmentContentUrl(uid) {
417
418
  /**
418
419
  * Uploads a file's raw bytes as a new attachment on a not-yet-sent draft. Bypasses `apiFetch` — that helper
419
420
  * always forces `Content-Type: application/json`, which would corrupt binary content; this sends the file's
420
- * own bytes/type directly instead, matching `BaseAttachmentRoute.upload`'s expectation of a raw request body.
421
+ * own bytes/type directly instead (through `client` when one is given), matching `BaseAttachmentRoute.upload`'s expectation of a raw request body.
421
422
  */
422
- export async function uploadAttachment(messageUid, file, options = {}) {
423
+ export function uploadAttachment(messageUid, file, options = {}, client) {
423
424
  // Percent-encoded, not form-encoded (`URLSearchParams` writes a space as `+`, which the server reads as a plus sign: "My+photo.png").
424
425
  const query = { messageUid, filename: file.name, mimeType: file.type || "application/octet-stream" };
425
426
  if (options.inline) {
@@ -431,19 +432,7 @@ export async function uploadAttachment(messageUid, file, options = {}) {
431
432
  const params = Object.entries(query)
432
433
  .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
433
434
  .join("&");
434
- const res = await fetch(apiUrl(`/mail/attachments/upload?${params}`), {
435
- method: "POST",
436
- credentials: "include",
437
- headers: withCsrfHeader({ "Content-Type": file.type || "application/octet-stream" }),
438
- body: file,
439
- });
440
- const contentType = res.headers.get("content-type") ?? "";
441
- const responseBody = contentType.includes("application/json") ? await res.json().catch(() => undefined) : undefined;
442
- if (!res.ok) {
443
- const message = (responseBody && (responseBody.message || responseBody.error)) || res.statusText || "Upload failed.";
444
- throw new ApiRequestError(message, res.status, responseBody?.code);
445
- }
446
- return responseBody;
435
+ return withClientRaw(client, `/mail/attachments/upload?${params}`, "POST", file, file.type || "application/octet-stream");
447
436
  }
448
437
  /**
449
438
  * Creates a blank draft `Message` in the given folder (normally the mailbox's Drafts folder) to compose into.
@@ -506,14 +495,53 @@ export function emptyFolder(folderUid, client) {
506
495
  * itself does no MIME composition). Does not send the message.
507
496
  */
508
497
  export async function assembleDraft(messageUid, input, client) {
509
- const html = await attachDataImages(messageUid, input.html);
498
+ const html = await attachDataImages(messageUid, input.html, client);
510
499
  return withClient(client, `/mail/compose/${encodeURIComponent(messageUid)}/assemble`, {
511
500
  method: "POST",
512
501
  body: JSON.stringify({ ...input, html }),
513
502
  });
514
503
  }
515
- /** `data:` images already attached to a draft: its uid and the image, to the URL of the attachment it became. */
504
+ /** `data:` images already attached to a draft: its uid and a fingerprint of the image, to the URL of the attachment it became. */
516
505
  const attachedDataImages = new Map();
506
+ /** The most pictures remembered; the oldest go first (a draft that was never sent or discarded here would otherwise keep its pictures forever). */
507
+ const MAX_ATTACHED_DATA_IMAGES = 256;
508
+ /** The SHA-256 of the whole of `text`, in hex - what stands for a `data:` URI of up to megabytes without holding it, and which no two quoted pictures can be made to share. Where Web Crypto is not available the text itself stands for it. */
509
+ async function fingerprint(text) {
510
+ if (!globalThis.crypto?.subtle) {
511
+ return text;
512
+ }
513
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
514
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
515
+ }
516
+ /** The most pictures attached to one draft from its text; a quote cannot make a person's reply upload (and be charged for) any number of them. */
517
+ const MAX_DATA_IMAGES_PER_DRAFT = 20;
518
+ /** Whether `bytes` start the way a picture of `contentType` does - the type a quote declares is its own word, and the bytes are what the server stores and shows. */
519
+ function hasImageSignature(contentType, bytes) {
520
+ const at = (offset, text) => Array.from(text).every((char, i) => bytes[offset + i] === char.charCodeAt(0));
521
+ switch (contentType.split("/")[1]) {
522
+ case "png":
523
+ return at(0, "\x89PNG");
524
+ case "jpg":
525
+ case "jpeg":
526
+ return at(0, "\xff\xd8\xff");
527
+ case "gif":
528
+ return at(0, "GIF8");
529
+ case "webp":
530
+ return at(0, "RIFF") && at(8, "WEBP");
531
+ case "avif":
532
+ return at(4, "ftypavi");
533
+ default:
534
+ return at(0, "BM");
535
+ }
536
+ }
537
+ /** Forgets the pictures attached to draft `messageUid`, for when it has been sent or deleted (they are only remembered to avoid attaching one twice). */
538
+ export function forgetDraftImages(messageUid) {
539
+ for (const key of Array.from(attachedDataImages.keys())) {
540
+ if (key.startsWith(`${messageUid}\n`)) {
541
+ attachedDataImages.delete(key);
542
+ }
543
+ }
544
+ }
517
545
  const DATA_IMAGE_TAG = /(<img\b[^>]*?\bsrc\s*=\s*)(["'])(data:image\/[^"']*)\2/gi;
518
546
  /** Names a picture by its type, for the attachment it becomes. */
519
547
  function pictureName(contentType) {
@@ -522,28 +550,39 @@ function pictureName(contentType) {
522
550
  /**
523
551
  * `html` with each `<img>` that embeds its picture as a `data:` URI (a quoted original's image, one pasted into the editor) pointed at an
524
552
  * attachment of the draft instead: the server drops `data:` images from the message it composes, and an attachment is what it makes an
525
- * inline part of. A picture is attached once per draft however often the draft is saved.
553
+ * inline part of. A picture is attached once per draft however often the draft is saved. Only what a quote itself embeds is attached - a raster
554
+ * image (`EMBEDDED_IMAGE_TYPES`, never SVG) of at most `MAX_QUOTED_IMAGE_BYTES` whose bytes are of the type it declares, and no more than
555
+ * `MAX_DATA_IMAGES_PER_DRAFT` of them; any other is left as it is, and the server drops it.
526
556
  */
527
- async function attachDataImages(messageUid, html) {
557
+ async function attachDataImages(messageUid, html, client) {
528
558
  const found = Array.from(html.matchAll(DATA_IMAGE_TAG));
529
559
  if (found.length === 0) {
530
560
  return html;
531
561
  }
532
562
  const urls = new Map();
533
563
  for (const [, , , uri] of found) {
534
- const key = `${messageUid}\n${uri}`;
564
+ if (urls.has(uri)) {
565
+ continue;
566
+ }
567
+ if (urls.size >= MAX_DATA_IMAGES_PER_DRAFT) {
568
+ break;
569
+ }
570
+ const key = `${messageUid}\n${await fingerprint(uri)}`;
535
571
  const known = attachedDataImages.get(key);
536
572
  if (known) {
537
573
  urls.set(uri, known);
538
574
  continue;
539
575
  }
540
576
  const image = parseDataImage(uri);
541
- if (!image) {
577
+ if (!image || !EMBEDDED_IMAGE_TYPES.test(image.contentType) || image.bytes.length > MAX_QUOTED_IMAGE_BYTES || !hasImageSignature(image.contentType, image.bytes)) {
542
578
  continue;
543
579
  }
544
- const attachment = await uploadAttachment(messageUid, new File([image.bytes], pictureName(image.contentType), { type: image.contentType }), { inline: true });
580
+ const attachment = await uploadAttachment(messageUid, new File([image.bytes], pictureName(image.contentType), { type: image.contentType }), { inline: true }, client);
545
581
  const url = attachmentContentUrl(attachment.uid);
546
582
  attachedDataImages.set(key, url);
583
+ if (attachedDataImages.size > MAX_ATTACHED_DATA_IMAGES) {
584
+ attachedDataImages.delete(attachedDataImages.keys().next().value);
585
+ }
547
586
  urls.set(uri, url);
548
587
  }
549
588
  return html.replace(DATA_IMAGE_TAG, (match, start, quote, uri) => (urls.has(uri) ? `${start}${quote}${urls.get(uri)}${quote}` : match));
@@ -599,8 +638,9 @@ export async function queueMessageSend(messageUid, client) {
599
638
  }
600
639
  /**
601
640
  * Fetches a message's raw RFC 5322 MIME source — for client-side E2E decrypt/signature-verification
602
- * only (`crypto/smimeMessage.ts`), never for display. Bypasses `apiFetch()` (which only ever decodes a
603
- * JSON response body): this server-local route (`BaseMessageRawContentRoute.ts` in `server`, mounted
641
+ * only (`crypto/smimeMessage.ts`), never for display. Goes through `withClientBlob()` rather than `apiFetch()` (which
642
+ * only ever decodes a JSON response body, and renews an expired session) - the same session renewal and, given a
643
+ * `client`, the same account and token as every other call: this server-local route (`BaseMessageRawContentRoute.ts` in `server`, mounted
604
644
  * alongside `@rapidmx/restapi`'s own `MessageRoute`) returns `message/rfc822`, not JSON — the one thing
605
645
  * that library's own `GET /:id/content` deliberately never serves (see that route's own doc comment).
606
646
  *
@@ -610,17 +650,11 @@ export async function queueMessageSend(messageUid, client) {
610
650
  * straight to `evaluateMessageSecurity()` (see `crypto/mime.ts`'s doc comment on binary strings); it is
611
651
  * not display text.
612
652
  */
613
- export async function getMessageRawContent(messageUid) {
614
- const res = await fetch(apiUrl(`/mail/messages/${encodeURIComponent(messageUid)}/raw`), { credentials: "include" });
615
- if (!res.ok) {
616
- const contentType = res.headers.get("content-type") ?? "";
617
- const body = contentType.includes("application/json") ? await res.json().catch(() => undefined) : undefined;
618
- const message = (body && (body.message || body.error)) || res.statusText || "Could not load this message's raw content.";
619
- throw new ApiRequestError(message, res.status, body?.code);
620
- }
653
+ export async function getMessageRawContent(messageUid, client) {
654
+ const raw = await withClientBlob(client, `/mail/messages/${encodeURIComponent(messageUid)}/raw`, "Could not load this message's raw content.");
621
655
  // Not `new TextDecoder("latin1")`: WHATWG maps that label to windows-1252, which remaps 0x80-0x9F and so
622
656
  // isn't byte-preserving. `bytesToBinaryString()` is an exact byte -> code unit conversion.
623
- return bytesToBinaryString(new Uint8Array(await res.arrayBuffer()));
657
+ return bytesToBinaryString(new Uint8Array(await raw.arrayBuffer()));
624
658
  }
625
659
  /**
626
660
  * Trusted-role-only: starts an admin "log in as user" session. In production this calls auth-server
@@ -12,9 +12,14 @@ export function useMessageAttachments(message) {
12
12
  setAttachments([]);
13
13
  return;
14
14
  }
15
+ // A response for a message since replaced must not overwrite the one now shown.
16
+ let cancelled = false;
15
17
  listAttachments(message.folderUid, message.uid)
16
- .then(setAttachments)
17
- .catch(() => setAttachments([]));
18
+ .then((listed) => !cancelled && setAttachments(listed))
19
+ .catch(() => !cancelled && setAttachments([]));
20
+ return () => {
21
+ cancelled = true;
22
+ };
18
23
  // Only the fields the fetch actually depends on — not the whole `message` object, which gets a new
19
24
  // reference on every metadata-only patch (e.g. `useMarkMessageRead`'s `onUpdated(updated)`, or a
20
25
  // star/flag/label change wired to the same state) and would otherwise trigger a needless re-fetch.
@@ -49,5 +54,8 @@ export function useMarkMessageRead(message, onUpdated) {
49
54
  return () => {
50
55
  cancelled = true;
51
56
  };
52
- }, [message]);
57
+ // Only the message's identity and read flag: a metadata-only patch swaps the object while the request is in flight, and
58
+ // restarting then would drop the first answer and send a second request with the old version (a 409 that leaves the
59
+ // message unread).
60
+ }, [message?.uid, message?.flags.read]);
53
61
  }
@@ -11,12 +11,12 @@
11
11
  * feature, not a data-model-only stub.
12
12
  */
13
13
  import { withClient } from "../util/api.js";
14
- import { buildQuery } from "../util/apiQuery.js";
14
+ import { SETTINGS_LIST_LIMIT, buildQuery } from "../util/apiQuery.js";
15
15
  /** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
16
16
  * account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
17
17
  * own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
18
18
  export function listMailFilterRules(mailboxUid, params = {}, client) {
19
- return withClient(client, `/mail/mail-filter-rules?${buildQuery(params, { mailboxUid })}`);
19
+ return withClient(client, `/mail/mail-filter-rules?${buildQuery({ limit: SETTINGS_LIST_LIMIT, ...params }, { mailboxUid })}`);
20
20
  }
21
21
  export function getMailFilterRule(uid, client) {
22
22
  return withClient(client, `/mail/mail-filter-rules/${encodeURIComponent(uid)}`);
@@ -10,12 +10,12 @@
10
10
  * resolved signature's `contentHtml` into a new draft.
11
11
  */
12
12
  import { withClient } from "../util/api.js";
13
- import { buildQuery } from "../util/apiQuery.js";
13
+ import { SETTINGS_LIST_LIMIT, buildQuery } from "../util/apiQuery.js";
14
14
  /** `client`, given by every function below, is an explicit `ApiClient` from `createApiClient()` (e.g. one
15
15
  * account of a multi-account app) to call instead of the default global `apiFetch()` - see `withClient()`'s
16
16
  * own doc comment in `util/api.ts`. Omitted (the default), every function here behaves exactly as before. */
17
17
  export function listMailSignatures(mailboxUid, params = {}, client) {
18
- return withClient(client, `/mail/mail-signatures?${buildQuery(params, { mailboxUid })}`);
18
+ return withClient(client, `/mail/mail-signatures?${buildQuery({ limit: SETTINGS_LIST_LIMIT, ...params }, { mailboxUid })}`);
19
19
  }
20
20
  export function getMailSignature(uid, client) {
21
21
  return withClient(client, `/mail/mail-signatures/${encodeURIComponent(uid)}`);
@@ -42,7 +42,7 @@ export interface UploadMailboxImportInput {
42
42
  * with `format`/`targetFolderUid`/`mailboxUid` as query-string parameters, not a JSON body — the same
43
43
  * shape `mailApi.ts`'s `uploadAttachment()` already establishes for a raw-bytes upload.
44
44
  */
45
- export declare function uploadMailboxImport(file: File, input: UploadMailboxImportInput): Promise<MailboxImportRequest>;
45
+ export declare function uploadMailboxImport(file: File, input: UploadMailboxImportInput, client?: ApiClient): Promise<MailboxImportRequest>;
46
46
  /** `client`, given by every function below that isn't a raw-bytes upload, is an explicit `ApiClient` from
47
47
  * `createApiClient()` (e.g. one account of a multi-account app) to call instead of the default global
48
48
  * `apiFetch()` - see `withClient()`'s own doc comment in `util/api.ts`. Omitted (the default), every