@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.
- package/apps/escrow/matters/[uid].tsx +4 -3
- package/apps/shared/calendar/calendarLiveUpdates.ts +2 -5
- package/apps/shared/calendar/sealedEvents.ts +80 -0
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +61 -3
- package/apps/shared/components/contacts/ContactsSidebar.tsx +5 -1
- package/apps/shared/components/contacts/contactCardData.ts +8 -4
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +23 -1
- package/apps/shared/components/layout/SharedMailboxKeyManager.tsx +91 -0
- package/apps/shared/components/layout/UnlockPromptProvider.tsx +34 -0
- package/apps/shared/components/mail/ConversationList.tsx +1 -1
- package/apps/shared/components/mail/MessageDetailPane.tsx +91 -20
- package/apps/shared/components/mail/compose/draftResume.ts +28 -3
- package/apps/shared/components/mail/compose/quotedBody.ts +31 -15
- package/apps/shared/components/mail/invite/inviteStore.ts +7 -1
- package/apps/shared/components/mail/layout/MailShell.tsx +2 -0
- package/apps/shared/components/mail/reading/MessageBody.tsx +4 -1
- package/apps/shared/components/mail/reading/bodyContent.ts +27 -17
- package/apps/shared/components/mail/reading/conversationPrint.ts +7 -5
- package/apps/shared/components/mail/reading/useMessageActions.ts +4 -1
- package/apps/shared/components/settings/KeyExportSetting.tsx +170 -0
- package/apps/shared/components/settings/PolicyLockedNotice.tsx +23 -0
- package/apps/shared/components/settings/ZeroAccessSetting.tsx +117 -0
- package/apps/shared/live/useFolderLiveUpdates.ts +59 -0
- package/apps/shared/mail/decryptedMessages.ts +11 -3
- package/apps/shared/mail/outbox/sendJob.ts +2 -2
- package/apps/shared/mail/outbox/sendOutcomes.ts +1 -3
- package/apps/shared/mail/reportNotices.ts +3 -1
- package/apps/shared/mail/zeroAccessSource.ts +27 -0
- package/apps/shared/search/LocalIndexLifecycle.tsx +31 -0
- package/apps/shared/search/localIndexBuilder.ts +27 -1
- package/apps/shared/search/localIndexRpcClient.ts +8 -4
- package/apps/shared/search/localIndexSchema.ts +2 -2
- package/apps/shared/search/localIndexTransport.ts +2 -1
- package/apps/shared/search/localIndexWorker.ts +6 -4
- package/apps/shared/search/zeroAccessIndexSync.ts +487 -0
- package/apps/www/calendar/index.tsx +180 -58
- package/apps/www/contacts/index.tsx +58 -2
- package/apps/www/index.tsx +10 -3
- package/apps/www/settings/encryption/index.tsx +5 -0
- package/apps/www/settings/privacy/index.tsx +2 -1
- package/apps/www/tasks/index.tsx +42 -0
- package/dist/apps/escrow/matters/[uid].js +2 -2
- package/dist/apps/shared/calendar/calendarLiveUpdates.js +2 -5
- package/dist/apps/shared/calendar/sealedEvents.d.ts +13 -0
- package/dist/apps/shared/calendar/sealedEvents.js +54 -0
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +18 -2
- package/dist/apps/shared/components/contacts/ContactsSidebar.d.ts +3 -1
- package/dist/apps/shared/components/contacts/ContactsSidebar.js +2 -2
- package/dist/apps/shared/components/contacts/contactCardData.d.ts +2 -2
- package/dist/apps/shared/components/contacts/contactCardData.js +8 -4
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +21 -2
- package/dist/apps/shared/components/layout/SharedMailboxKeyManager.d.ts +15 -0
- package/dist/apps/shared/components/layout/SharedMailboxKeyManager.js +72 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.d.ts +3 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.js +32 -0
- package/dist/apps/shared/components/mail/ConversationList.js +1 -1
- package/dist/apps/shared/components/mail/MessageDetailPane.js +73 -17
- package/dist/apps/shared/components/mail/compose/draftResume.d.ts +2 -0
- package/dist/apps/shared/components/mail/compose/draftResume.js +28 -3
- package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +4 -3
- package/dist/apps/shared/components/mail/compose/quotedBody.js +28 -15
- package/dist/apps/shared/components/mail/invite/inviteStore.js +6 -1
- package/dist/apps/shared/components/mail/layout/MailShell.js +2 -1
- package/dist/apps/shared/components/mail/reading/MessageBody.js +4 -1
- package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +4 -2
- package/dist/apps/shared/components/mail/reading/bodyContent.js +27 -16
- package/dist/apps/shared/components/mail/reading/conversationPrint.d.ts +3 -2
- package/dist/apps/shared/components/mail/reading/conversationPrint.js +7 -5
- package/dist/apps/shared/components/mail/reading/useMessageActions.js +4 -1
- package/dist/apps/shared/components/settings/KeyExportSetting.d.ts +13 -0
- package/dist/apps/shared/components/settings/KeyExportSetting.js +88 -0
- package/dist/apps/shared/components/settings/PolicyLockedNotice.d.ts +11 -0
- package/dist/apps/shared/components/settings/PolicyLockedNotice.js +9 -0
- package/dist/apps/shared/components/settings/ZeroAccessSetting.d.ts +14 -0
- package/dist/apps/shared/components/settings/ZeroAccessSetting.js +71 -0
- package/dist/apps/shared/live/useFolderLiveUpdates.d.ts +19 -0
- package/dist/apps/shared/live/useFolderLiveUpdates.js +41 -0
- package/dist/apps/shared/mail/decryptedMessages.d.ts +1 -1
- package/dist/apps/shared/mail/decryptedMessages.js +11 -3
- package/dist/apps/shared/mail/outbox/sendJob.js +2 -2
- package/dist/apps/shared/mail/outbox/sendOutcomes.js +1 -3
- package/dist/apps/shared/mail/reportNotices.js +3 -1
- package/dist/apps/shared/mail/zeroAccessSource.d.ts +8 -0
- package/dist/apps/shared/mail/zeroAccessSource.js +26 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.d.ts +2 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.js +28 -0
- package/dist/apps/shared/search/localIndexBuilder.d.ts +5 -0
- package/dist/apps/shared/search/localIndexBuilder.js +24 -2
- package/dist/apps/shared/search/localIndexRpcClient.d.ts +1 -1
- package/dist/apps/shared/search/localIndexRpcClient.js +6 -4
- package/dist/apps/shared/search/localIndexSchema.d.ts +1 -1
- package/dist/apps/shared/search/localIndexSchema.js +2 -2
- package/dist/apps/shared/search/localIndexTransport.d.ts +2 -1
- package/dist/apps/shared/search/localIndexWorker.d.ts +2 -0
- package/dist/apps/shared/search/localIndexWorker.js +4 -4
- package/dist/apps/shared/search/zeroAccessIndexSync.d.ts +71 -0
- package/dist/apps/shared/search/zeroAccessIndexSync.js +382 -0
- package/dist/apps/www/calendar/index.js +48 -22
- package/dist/apps/www/contacts/index.js +49 -3
- package/dist/apps/www/index.js +10 -3
- package/dist/apps/www/settings/encryption/index.js +3 -1
- package/dist/apps/www/settings/privacy/index.js +1 -1
- package/dist/apps/www/tasks/index.js +29 -1
- package/dist/lib/admin/escrowAccessRequestsApi.d.ts +6 -0
- package/dist/lib/calendar/calendarApi.d.ts +6 -0
- package/dist/lib/calendar/inviteApi.js +27 -5
- package/dist/lib/calendar/sealedInvites.d.ts +11 -0
- package/dist/lib/calendar/sealedInvites.js +28 -0
- package/dist/lib/crypto/keyExport.d.ts +54 -0
- package/dist/lib/crypto/keyExport.js +161 -0
- package/dist/lib/crypto/keySession.d.ts +45 -1
- package/dist/lib/crypto/keySession.js +93 -2
- package/dist/lib/crypto/keyvaultApi.d.ts +88 -3
- package/dist/lib/crypto/keyvaultApi.js +32 -0
- package/dist/lib/crypto/messageSecurity.d.ts +30 -1
- package/dist/lib/crypto/messageSecurity.js +80 -4
- package/dist/lib/crypto/mime.d.ts +5 -0
- package/dist/lib/crypto/mime.js +25 -0
- package/dist/lib/crypto/sharedMailboxKeys.d.ts +50 -0
- package/dist/lib/crypto/sharedMailboxKeys.js +264 -0
- package/dist/lib/crypto/zeroAccessKey.d.ts +32 -0
- package/dist/lib/crypto/zeroAccessKey.js +66 -0
- package/dist/lib/mail/conversationsApi.d.ts +6 -0
- package/dist/lib/mail/mailApi.d.ts +30 -2
- package/dist/lib/mail/mailApi.js +5 -1
- package/dist/lib/mail/pushClient.d.ts +10 -3
- package/dist/lib/mail/pushClient.js +18 -4
- package/dist/lib/search/zeroAccessIndexApi.d.ts +81 -0
- package/dist/lib/search/zeroAccessIndexApi.js +35 -0
- package/dist/lib/tasks/tasksApi.d.ts +1 -0
- package/dist/lib/tasks/tasksApi.js +3 -0
- package/lib/admin/escrowAccessRequestsApi.ts +6 -0
- package/lib/calendar/calendarApi.ts +3 -0
- package/lib/calendar/inviteApi.ts +28 -5
- package/lib/calendar/sealedInvites.ts +33 -0
- package/lib/crypto/keyExport.ts +229 -0
- package/lib/crypto/keySession.ts +117 -3
- package/lib/crypto/keyvaultApi.ts +125 -3
- package/lib/crypto/messageSecurity.ts +110 -3
- package/lib/crypto/mime.ts +26 -0
- package/lib/crypto/sharedMailboxKeys.ts +324 -0
- package/lib/crypto/zeroAccessKey.ts +97 -0
- package/lib/mail/conversationsApi.ts +3 -0
- package/lib/mail/mailApi.ts +33 -3
- package/lib/mail/pushClient.ts +18 -4
- package/lib/search/zeroAccessIndexApi.ts +119 -0
- package/lib/tasks/tasksApi.ts +4 -0
- package/package.json +3 -3
package/lib/mail/mailApi.ts
CHANGED
|
@@ -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({
|
|
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
|
/**
|
package/lib/mail/pushClient.ts
CHANGED
|
@@ -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)
|
|
156
|
-
*
|
|
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
|
-
|
|
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
|
+
}
|
package/lib/tasks/tasksApi.ts
CHANGED
|
@@ -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.
|
|
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": "
|
|
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.
|
|
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",
|