@rapidmx/web-client 0.34.0 → 0.36.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 (148) hide show
  1. package/apps/escrow/matters/[uid].tsx +4 -3
  2. package/apps/shared/calendar/calendarLiveUpdates.ts +2 -5
  3. package/apps/shared/calendar/sealedEvents.ts +80 -0
  4. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +61 -3
  5. package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -1
  6. package/apps/shared/components/contacts/contactCardData.ts +8 -4
  7. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +23 -1
  8. package/apps/shared/components/layout/SharedMailboxKeyManager.tsx +91 -0
  9. package/apps/shared/components/layout/UnlockPromptProvider.tsx +34 -0
  10. package/apps/shared/components/mail/ConversationList.tsx +1 -1
  11. package/apps/shared/components/mail/MessageDetailPane.tsx +91 -20
  12. package/apps/shared/components/mail/compose/draftResume.ts +28 -3
  13. package/apps/shared/components/mail/compose/quotedBody.ts +31 -15
  14. package/apps/shared/components/mail/invite/inviteStore.ts +7 -1
  15. package/apps/shared/components/mail/layout/MailShell.tsx +2 -0
  16. package/apps/shared/components/mail/reading/MessageBody.tsx +4 -1
  17. package/apps/shared/components/mail/reading/bodyContent.ts +27 -17
  18. package/apps/shared/components/mail/reading/conversationPrint.ts +7 -5
  19. package/apps/shared/components/mail/reading/useMessageActions.ts +4 -1
  20. package/apps/shared/components/settings/KeyExportSetting.tsx +170 -0
  21. package/apps/shared/components/settings/PolicyLockedNotice.tsx +23 -0
  22. package/apps/shared/components/settings/ZeroAccessSetting.tsx +117 -0
  23. package/apps/shared/live/useFolderLiveUpdates.ts +59 -0
  24. package/apps/shared/mail/decryptedMessages.ts +11 -3
  25. package/apps/shared/mail/outbox/sendJob.ts +2 -2
  26. package/apps/shared/mail/outbox/sendOutcomes.ts +1 -3
  27. package/apps/shared/mail/reportNotices.ts +3 -1
  28. package/apps/shared/mail/zeroAccessSource.ts +27 -0
  29. package/apps/shared/search/LocalIndexLifecycle.tsx +31 -0
  30. package/apps/shared/search/localIndexBuilder.ts +27 -1
  31. package/apps/shared/search/localIndexRpcClient.ts +8 -4
  32. package/apps/shared/search/localIndexSchema.ts +2 -2
  33. package/apps/shared/search/localIndexTransport.ts +2 -1
  34. package/apps/shared/search/localIndexWorker.ts +6 -4
  35. package/apps/shared/search/zeroAccessIndexSync.ts +487 -0
  36. package/apps/www/calendar/index.tsx +180 -58
  37. package/apps/www/contacts/index.tsx +58 -2
  38. package/apps/www/index.tsx +10 -3
  39. package/apps/www/settings/encryption/index.tsx +5 -0
  40. package/apps/www/settings/privacy/index.tsx +2 -1
  41. package/apps/www/tasks/index.tsx +42 -0
  42. package/dist/apps/escrow/matters/[uid].js +2 -2
  43. package/dist/apps/shared/calendar/calendarLiveUpdates.js +2 -5
  44. package/dist/apps/shared/calendar/sealedEvents.d.ts +13 -0
  45. package/dist/apps/shared/calendar/sealedEvents.js +54 -0
  46. package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +18 -2
  47. package/dist/apps/shared/components/contacts/ContactsSidebar.d.ts +3 -1
  48. package/dist/apps/shared/components/contacts/ContactsSidebar.js +2 -2
  49. package/dist/apps/shared/components/contacts/contactCardData.d.ts +2 -2
  50. package/dist/apps/shared/components/contacts/contactCardData.js +8 -4
  51. package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +21 -2
  52. package/dist/apps/shared/components/layout/SharedMailboxKeyManager.d.ts +15 -0
  53. package/dist/apps/shared/components/layout/SharedMailboxKeyManager.js +72 -0
  54. package/dist/apps/shared/components/layout/UnlockPromptProvider.d.ts +3 -0
  55. package/dist/apps/shared/components/layout/UnlockPromptProvider.js +32 -0
  56. package/dist/apps/shared/components/mail/ConversationList.js +1 -1
  57. package/dist/apps/shared/components/mail/MessageDetailPane.js +73 -17
  58. package/dist/apps/shared/components/mail/compose/draftResume.d.ts +2 -0
  59. package/dist/apps/shared/components/mail/compose/draftResume.js +28 -3
  60. package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +4 -3
  61. package/dist/apps/shared/components/mail/compose/quotedBody.js +28 -15
  62. package/dist/apps/shared/components/mail/invite/inviteStore.js +6 -1
  63. package/dist/apps/shared/components/mail/layout/MailShell.js +2 -1
  64. package/dist/apps/shared/components/mail/reading/MessageBody.js +4 -1
  65. package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +4 -2
  66. package/dist/apps/shared/components/mail/reading/bodyContent.js +27 -16
  67. package/dist/apps/shared/components/mail/reading/conversationPrint.d.ts +3 -2
  68. package/dist/apps/shared/components/mail/reading/conversationPrint.js +7 -5
  69. package/dist/apps/shared/components/mail/reading/useMessageActions.js +4 -1
  70. package/dist/apps/shared/components/settings/KeyExportSetting.d.ts +13 -0
  71. package/dist/apps/shared/components/settings/KeyExportSetting.js +88 -0
  72. package/dist/apps/shared/components/settings/PolicyLockedNotice.d.ts +11 -0
  73. package/dist/apps/shared/components/settings/PolicyLockedNotice.js +9 -0
  74. package/dist/apps/shared/components/settings/ZeroAccessSetting.d.ts +14 -0
  75. package/dist/apps/shared/components/settings/ZeroAccessSetting.js +71 -0
  76. package/dist/apps/shared/live/useFolderLiveUpdates.d.ts +19 -0
  77. package/dist/apps/shared/live/useFolderLiveUpdates.js +41 -0
  78. package/dist/apps/shared/mail/decryptedMessages.d.ts +1 -1
  79. package/dist/apps/shared/mail/decryptedMessages.js +11 -3
  80. package/dist/apps/shared/mail/outbox/sendJob.js +2 -2
  81. package/dist/apps/shared/mail/outbox/sendOutcomes.js +1 -3
  82. package/dist/apps/shared/mail/reportNotices.js +3 -1
  83. package/dist/apps/shared/mail/zeroAccessSource.d.ts +8 -0
  84. package/dist/apps/shared/mail/zeroAccessSource.js +26 -0
  85. package/dist/apps/shared/search/LocalIndexLifecycle.d.ts +2 -0
  86. package/dist/apps/shared/search/LocalIndexLifecycle.js +28 -0
  87. package/dist/apps/shared/search/localIndexBuilder.d.ts +5 -0
  88. package/dist/apps/shared/search/localIndexBuilder.js +24 -2
  89. package/dist/apps/shared/search/localIndexRpcClient.d.ts +1 -1
  90. package/dist/apps/shared/search/localIndexRpcClient.js +6 -4
  91. package/dist/apps/shared/search/localIndexSchema.d.ts +1 -1
  92. package/dist/apps/shared/search/localIndexSchema.js +2 -2
  93. package/dist/apps/shared/search/localIndexTransport.d.ts +2 -1
  94. package/dist/apps/shared/search/localIndexWorker.d.ts +2 -0
  95. package/dist/apps/shared/search/localIndexWorker.js +4 -4
  96. package/dist/apps/shared/search/zeroAccessIndexSync.d.ts +71 -0
  97. package/dist/apps/shared/search/zeroAccessIndexSync.js +382 -0
  98. package/dist/apps/www/calendar/index.js +48 -22
  99. package/dist/apps/www/contacts/index.js +49 -3
  100. package/dist/apps/www/index.js +10 -3
  101. package/dist/apps/www/settings/encryption/index.js +3 -1
  102. package/dist/apps/www/settings/privacy/index.js +1 -1
  103. package/dist/apps/www/tasks/index.js +29 -1
  104. package/dist/lib/admin/escrowAccessRequestsApi.d.ts +6 -0
  105. package/dist/lib/calendar/calendarApi.d.ts +6 -0
  106. package/dist/lib/calendar/inviteApi.js +27 -5
  107. package/dist/lib/calendar/sealedInvites.d.ts +11 -0
  108. package/dist/lib/calendar/sealedInvites.js +28 -0
  109. package/dist/lib/crypto/keyExport.d.ts +54 -0
  110. package/dist/lib/crypto/keyExport.js +161 -0
  111. package/dist/lib/crypto/keySession.d.ts +45 -1
  112. package/dist/lib/crypto/keySession.js +93 -2
  113. package/dist/lib/crypto/keyvaultApi.d.ts +88 -3
  114. package/dist/lib/crypto/keyvaultApi.js +32 -0
  115. package/dist/lib/crypto/messageSecurity.d.ts +30 -1
  116. package/dist/lib/crypto/messageSecurity.js +80 -4
  117. package/dist/lib/crypto/mime.d.ts +5 -0
  118. package/dist/lib/crypto/mime.js +25 -0
  119. package/dist/lib/crypto/sharedMailboxKeys.d.ts +50 -0
  120. package/dist/lib/crypto/sharedMailboxKeys.js +264 -0
  121. package/dist/lib/crypto/zeroAccessKey.d.ts +32 -0
  122. package/dist/lib/crypto/zeroAccessKey.js +66 -0
  123. package/dist/lib/mail/conversationsApi.d.ts +6 -0
  124. package/dist/lib/mail/mailApi.d.ts +30 -2
  125. package/dist/lib/mail/mailApi.js +5 -1
  126. package/dist/lib/mail/pushClient.d.ts +10 -3
  127. package/dist/lib/mail/pushClient.js +18 -4
  128. package/dist/lib/search/zeroAccessIndexApi.d.ts +81 -0
  129. package/dist/lib/search/zeroAccessIndexApi.js +35 -0
  130. package/dist/lib/tasks/tasksApi.d.ts +1 -0
  131. package/dist/lib/tasks/tasksApi.js +3 -0
  132. package/lib/admin/escrowAccessRequestsApi.ts +6 -0
  133. package/lib/calendar/calendarApi.ts +3 -0
  134. package/lib/calendar/inviteApi.ts +28 -5
  135. package/lib/calendar/sealedInvites.ts +33 -0
  136. package/lib/crypto/keyExport.ts +229 -0
  137. package/lib/crypto/keySession.ts +117 -3
  138. package/lib/crypto/keyvaultApi.ts +125 -3
  139. package/lib/crypto/messageSecurity.ts +110 -3
  140. package/lib/crypto/mime.ts +26 -0
  141. package/lib/crypto/sharedMailboxKeys.ts +324 -0
  142. package/lib/crypto/zeroAccessKey.ts +97 -0
  143. package/lib/mail/conversationsApi.ts +3 -0
  144. package/lib/mail/mailApi.ts +33 -3
  145. package/lib/mail/pushClient.ts +18 -4
  146. package/lib/search/zeroAccessIndexApi.ts +119 -0
  147. package/lib/tasks/tasksApi.ts +4 -0
  148. package/package.json +3 -3
@@ -26,7 +26,7 @@ import {
26
26
  } from "../util/api.js";
27
27
  import { ListParams, buildQuery, sortQuery } from "../util/apiQuery.js";
28
28
  import { deviceTimeZone } from "../util/timeZone.js";
29
- import type { EncryptionPreference, PublicKey } from "../crypto/keyvaultApi.js";
29
+ import type { EncryptionPreference, PublicKey, ZeroAccessKey } from "../crypto/keyvaultApi.js";
30
30
  import { bytesToBinaryString } from "../crypto/mime.js";
31
31
  import { EMBEDDED_IMAGE_TYPES, newContentId, parseDataImage } from "./inlineImages.js";
32
32
  import { MAX_QUOTED_IMAGE_BYTES } from "./compose/quotedImages.js";
@@ -129,6 +129,12 @@ export interface Mailbox {
129
129
  * per the spec's own `PublicKey` doc comment. Absent/empty means no keys enrolled yet
130
130
  * (`KeyEnrollmentGate` handles that state). */
131
131
  keys?: PublicKey[];
132
+ /** The mailbox's zero-access certificates, managed by the server (never written by a client). Read them from
133
+ * `KeyVault.zeroAccessKeys` to unlock; here only so a settings page can tell whether one is enrolled. */
134
+ zeroAccessKeys?: ZeroAccessKey[];
135
+ /** "Enable zero-access encryption": unset means on. Only the mailbox's owner can change it - restapi drops anybody
136
+ * else's value. Has no effect while the server's `EncryptionPolicy.allowZeroAccess` is off. */
137
+ zeroAccessEnabled?: boolean;
132
138
  /** How the caller reaches this mailbox: `"owner"` (theirs) or `"delegate"` (shared with them). Set by the plain list and read of
133
139
  * mailboxes - not in an administration-scope answer - so a client can label the shared ones; see `isSharedWithMe()`. */
134
140
  accessRole?: "owner" | "delegate";
@@ -281,6 +287,8 @@ export interface UpdateMailboxInput {
281
287
  autoSendReceiptsInternal?: boolean;
282
288
  autoSendReceiptsFederated?: boolean;
283
289
  autoSendReceiptsExternal?: boolean;
290
+ /** Owner-only server-side (anybody else's value is dropped) - see `Mailbox.zeroAccessEnabled`. */
291
+ zeroAccessEnabled?: boolean;
284
292
  /** Trusted-caller-only server-side. `null` (or `""`) unassigns the mailbox's escrow scope; the
285
293
  * referenced `EscrowScope` must exist otherwise. See `Mailbox.escrowScopeId`. */
286
294
  escrowScopeId?: string | null;
@@ -594,6 +602,10 @@ export interface Message {
594
602
  * stored body, not from a flag this API exposes. Always present on a real `Message` (defaults
595
603
  * `false` server-side), same optional-for-old-fixtures caveat as `Mailbox.oofEnabled`. */
596
604
  encrypted?: boolean;
605
+ /** Set when the server sealed this message at rest on arrival (zero-access, restapi `specs/search.md` §3): its stored body is an
606
+ * S/MIME message only the mailbox's zero-access key opens, `subject` is `[...]` and `bodyPreview` empty - the real ones are in
607
+ * `sealedSummary`. */
608
+ zeroAccess?: { keyFingerprint: string; sealedSummary: string };
597
609
  /** The raw RFC 2369/8058 `List-Unsubscribe` header value, if present — computed server-side by
598
610
  * `@rapidmx/restapi`'s `ScanPipeline` from the stored MIME, the strongest single bulk-mail signal it
599
611
  * captures. Used client-side to detect list traffic when replying, per
@@ -797,7 +809,7 @@ export type MessageReportKind = "junk" | "phishing" | "not_junk";
797
809
  /** Why the server did not teach its spam filter from a report: `encrypted` (it has no plaintext to teach), `disabled` (learning is switched off),
798
810
  * `unsupported` (no spam engine, or one that cannot learn), `too_large` (over the size it reads) or `failed` (the engine could not be reached,
799
811
  * refused, or the message could not be read). The report itself still happened. */
800
- export type MessageLearnSkipped = "encrypted" | "disabled" | "unsupported" | "too_large" | "failed";
812
+ export type MessageLearnSkipped = "encrypted" | "zero_access" | "disabled" | "unsupported" | "too_large" | "failed";
801
813
 
802
814
  /** What `reportMessage()` answers. */
803
815
  export interface MessageReportResult {
@@ -820,6 +832,9 @@ export interface ReportMessageOptions {
820
832
  /** With `not_junk` only (else a 400): also add the message's From address to the mailbox's safe senders. Needs full access to the mailbox (403 otherwise,
821
833
  * before anything is done). */
822
834
  alwaysTrustSender?: boolean;
835
+ /** For a zero-access message only (else a 400): its original source, base64-encoded, decrypted here - the server holds only the sealed
836
+ * copy, so this is what its spam filter learns from (never stored). Without it the server skips learning (`learnSkipped: "zero_access"`). */
837
+ rawMessage?: string;
823
838
  }
824
839
 
825
840
  /**
@@ -837,7 +852,11 @@ export function reportMessage(
837
852
  ): Promise<MessageReportResult> {
838
853
  return withClient(client, `/mail/messages/${encodeURIComponent(uid)}/report`, {
839
854
  method: "POST",
840
- body: JSON.stringify({ kind, ...(options.alwaysTrustSender ? { alwaysTrustSender: true } : {}) }),
855
+ body: JSON.stringify({
856
+ kind,
857
+ ...(options.alwaysTrustSender ? { alwaysTrustSender: true } : {}),
858
+ ...(options.rawMessage ? { rawMessage: options.rawMessage } : {}),
859
+ }),
841
860
  });
842
861
  }
843
862
 
@@ -1073,8 +1092,17 @@ export interface AssembleDraftInput {
1073
1092
  bcc?: ComposeRecipientInput[];
1074
1093
  subject?: string;
1075
1094
  html: string;
1095
+ /** See `AssembleIntent`. */
1096
+ intent?: AssembleIntent;
1076
1097
  }
1077
1098
 
1099
+ /**
1100
+ * Why a draft is being assembled (server `ComposeIntent`). `"save"`, the default, keeps an edit: in a zero-access mailbox the server
1101
+ * stores it sealed, which it can't send. `"send"` is the assemble right before `sendMessage()`/`queueMessageSend()`: stored readable
1102
+ * until it is relayed, then sealed as the Sent Items copy.
1103
+ */
1104
+ export type AssembleIntent = "save" | "send";
1105
+
1078
1106
  /** What a draft records about the thread it belongs to - see `createDraft()`. */
1079
1107
  export interface DraftThreading {
1080
1108
  /** The `messageId` of the message being replied to (`Message.inReplyTo`). */
@@ -1266,6 +1294,8 @@ export interface AssembleDraftRawInput {
1266
1294
  * `crypto/smimeMessage.ts`'s `buildSignedOnlyMessage()`/`buildEncryptedMessage()`, combined with
1267
1295
  * the outer envelope headers (`From`/`To`/`Cc`/`Date`/`Message-ID`/`MIME-Version`) by the caller. */
1268
1296
  rawMime: string;
1297
+ /** See `AssembleIntent`. */
1298
+ intent?: AssembleIntent;
1269
1299
  }
1270
1300
 
1271
1301
  /**
@@ -66,6 +66,9 @@ const SOCKET_OPEN = 1;
66
66
  */
67
67
  export const PUSH_MAX_CHANNELS = 40;
68
68
 
69
+ /** The group `setChannels()` sets when no other is named: Mail's. */
70
+ export const DEFAULT_CHANNEL_GROUP = "mail";
71
+
69
72
  /** The first reconnect waits about this long, and each further failure doubles it ... */
70
73
  export const PUSH_BACKOFF_BASE_MS = 1_000;
71
74
  /** ... up to this. */
@@ -102,6 +105,8 @@ export class PushClient {
102
105
  private current: PushStatus = "idle";
103
106
  /** What the caller wants to hear about, most important first. */
104
107
  private desired: string[] = [];
108
+ /** The channels each caller asked for (`setChannels()`'s `group`), merged into `desired`. */
109
+ private groups = new Map<string, string[]>();
105
110
  /** What this socket is subscribed to (including whatever the server put it on at connect). */
106
111
  private subscribed = new Set<string>();
107
112
  /** The channels this client itself asked for and was granted - the only ones it may unsubscribe. */
@@ -152,11 +157,20 @@ export class PushClient {
152
157
  }
153
158
 
154
159
  /**
155
- * Sets the channels (folder and mailbox uids) to receive events for. Deduplicated and cut to `PUSH_MAX_CHANNELS`; on
156
- * an open socket the difference is subscribed/unsubscribed at once, otherwise it is sent when the socket opens.
160
+ * Sets the channels (folder and mailbox uids) one caller wants events for: Mail's, in the default group, or a page's
161
+ * own (the Calendar's calendars - Mail subscribes to mail folders only). Every group's channels are wanted together,
162
+ * each page's own group ahead of the default one, since a page's are what is on screen; deduplicated and cut to
163
+ * `PUSH_MAX_CHANNELS`. An empty list drops the group. On an open socket the difference is subscribed/unsubscribed at
164
+ * once, otherwise it is sent when the socket opens.
157
165
  */
158
- setChannels(channels: readonly string[]): void {
159
- this.desired = [...new Set(channels)].slice(0, PUSH_MAX_CHANNELS);
166
+ setChannels(channels: readonly string[], group = DEFAULT_CHANNEL_GROUP): void {
167
+ if (channels.length > 0) {
168
+ this.groups.set(group, [...channels]);
169
+ } else {
170
+ this.groups.delete(group);
171
+ }
172
+ const ordered = [...this.groups.entries()].sort(([a], [b]) => Number(a === DEFAULT_CHANNEL_GROUP) - Number(b === DEFAULT_CHANNEL_GROUP));
173
+ this.desired = [...new Set(ordered.flatMap(([, list]) => list))].slice(0, PUSH_MAX_CHANNELS);
160
174
  if (this.ready) {
161
175
  this.syncSubscriptions();
162
176
  }
@@ -0,0 +1,119 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ /**
6
+ * Typed wrappers over `@rapidmx/restapi`'s zero-access search index (`/mail/mailboxes/:id/search-index`, `specs/search.md` §7-8): the
7
+ * sealed index entries the server writes at delivery and the weekly segments clients compact them into. Everything here is
8
+ * ciphertext encrypted to the mailbox's zero-access key - `apps/shared/search/zeroAccessIndexSync.ts` decrypts and imports it.
9
+ */
10
+ import { type ApiClient, withClient, withClientBlob } from "../util/api.js";
11
+
12
+ export type SegmentOrigin = "zero-access" | "client";
13
+
14
+ /** A compacted segment as the manifest lists it. */
15
+ export interface IndexSegment {
16
+ weekId: string;
17
+ generation: number;
18
+ origin: SegmentOrigin;
19
+ bytes: number;
20
+ etag: string;
21
+ schemaVersion: number;
22
+ keyFingerprint: string;
23
+ entryCount: number;
24
+ }
25
+
26
+ /** A week's entries not yet compacted. */
27
+ export interface IndexWeekEntries {
28
+ weekId: string;
29
+ pendingEntries: number;
30
+ latestEntryAt: string;
31
+ /** The week has closed and its grace period has passed: a client may compact it. */
32
+ compactable: boolean;
33
+ }
34
+
35
+ export interface SegmentManifest {
36
+ mailboxUid: string;
37
+ segments: IndexSegment[];
38
+ weeks: IndexWeekEntries[];
39
+ }
40
+
41
+ /** One pending entry: `sealed` is base64 CMS `EnvelopedData` of a gzipped JSON `ZeroAccessIndexDocument`. */
42
+ export interface SealedIndexEntry {
43
+ uid: string;
44
+ weekId: string;
45
+ messageUid: string;
46
+ createdAt: string;
47
+ keyFingerprint: string;
48
+ sealed: string;
49
+ }
50
+
51
+ export interface SealedIndexEntryPage {
52
+ entries: SealedIndexEntry[];
53
+ next?: string;
54
+ }
55
+
56
+ /** What a sealed entry (and each document of a segment) decrypts to - restapi's `ZeroAccessIndexDocument`. */
57
+ export interface ZeroAccessIndexDocument {
58
+ v: 1;
59
+ messageUid: string;
60
+ subject: string;
61
+ body: string;
62
+ attachmentText: string[];
63
+ attachmentNames: string[];
64
+ deferredAttachments: string[];
65
+ from: string;
66
+ to: string[];
67
+ cc: string[];
68
+ participants: string[];
69
+ dateForSort: number;
70
+ }
71
+
72
+ /** `PUT .../segments/:weekId`. */
73
+ export interface SegmentUpload {
74
+ expectedGeneration: number;
75
+ /** Base64 of the sealed file. */
76
+ segment: string;
77
+ keyFingerprint: string;
78
+ schemaVersion: number;
79
+ origin?: SegmentOrigin;
80
+ foldedEntryUids?: string[];
81
+ }
82
+
83
+ const base = (mailboxUid: string): string => `/mail/mailboxes/${encodeURIComponent(mailboxUid)}/search-index`;
84
+
85
+ export function getSearchIndexManifest(mailboxUid: string, client?: ApiClient): Promise<SegmentManifest> {
86
+ return withClient(client, `${base(mailboxUid)}/manifest`);
87
+ }
88
+
89
+ /** One page of pending entries: of `week` when given, written at or after `since` when given, after uid `after`. */
90
+ export function listSearchIndexEntries(
91
+ mailboxUid: string,
92
+ options: { week?: string; since?: string; after?: string; limit?: number } = {},
93
+ client?: ApiClient,
94
+ ): Promise<SealedIndexEntryPage> {
95
+ const query = new URLSearchParams();
96
+ for (const [key, value] of Object.entries(options)) {
97
+ if (value !== undefined) {
98
+ query.set(key, String(value));
99
+ }
100
+ }
101
+ const search = query.toString();
102
+ return withClient(client, `${base(mailboxUid)}/entries${search ? `?${search}` : ""}`);
103
+ }
104
+
105
+ /** The sealed file of a week's segment generation. */
106
+ export async function getSearchIndexSegment(
107
+ mailboxUid: string,
108
+ segment: Pick<IndexSegment, "weekId" | "generation" | "origin">,
109
+ client?: ApiClient,
110
+ ): Promise<Uint8Array> {
111
+ const query = `generation=${segment.generation}${segment.origin === "client" ? "&origin=client" : ""}`;
112
+ const blob = await withClientBlob(client, `${base(mailboxUid)}/segments/${encodeURIComponent(segment.weekId)}?${query}`, "Could not download a search index segment.");
113
+ return new Uint8Array(await blob.arrayBuffer());
114
+ }
115
+
116
+ /** Stores a compaction. Rejects with a `409` when the week's segment changed since `upload.expectedGeneration` was read. */
117
+ export function putSearchIndexSegment(mailboxUid: string, weekId: string, upload: SegmentUpload, client?: ApiClient): Promise<IndexSegment> {
118
+ return withClient(client, `${base(mailboxUid)}/segments/${encodeURIComponent(weekId)}`, { method: "PUT", body: JSON.stringify(upload) });
119
+ }
@@ -42,6 +42,10 @@ export function listTasks(folderUid: string, params: ListParams = {}, client?: A
42
42
  return withClient(client, `/mail/tasks?${buildQuery(params, { folderUid, sort: JSON.stringify({ dueDate: "ASC", uid: "ASC" }) })}`);
43
43
  }
44
44
 
45
+ export function getTask(uid: string, client?: ApiClient): Promise<Task> {
46
+ return withClient(client, `/mail/tasks/${encodeURIComponent(uid)}`);
47
+ }
48
+
45
49
  export interface CreateTaskInput {
46
50
  mailboxUid: string;
47
51
  folderUid: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rapidmx/web-client",
3
- "version": "0.34.0",
3
+ "version": "0.36.0",
4
4
  "description": "RapidMX's webmail and admin console React UI for web-based clients.",
5
5
  "type": "module",
6
6
  "repository": "https://github.com/rapidmx/web-client",
@@ -37,7 +37,7 @@
37
37
  "trailingComma": "es5"
38
38
  },
39
39
  "peerDependencies": {
40
- "@rapidrest/react": ">=2.1.0 <3",
40
+ "@rapidrest/react": "2.x",
41
41
  "react": "19.x",
42
42
  "react-dom": "19.x"
43
43
  },
@@ -67,7 +67,7 @@
67
67
  "rrule": "^2.8.1"
68
68
  },
69
69
  "devDependencies": {
70
- "@rapidrest/react": "^2.1.0",
70
+ "@rapidrest/react": "^2.2.0",
71
71
  "@tailwindcss/vite": "^4.3.3",
72
72
  "@testing-library/dom": "^10.4.1",
73
73
  "@testing-library/jest-dom": "^7.0.1",