@rapidmx/activesync 1.0.0-beta.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/LICENSE +382 -0
- package/README.md +57 -0
- package/dist/lib/BaseDeviceSyncStateRoute.js +90 -0
- package/dist/lib/BaseDeviceSyncStateRoute.js.map +1 -0
- package/dist/lib/BaseEasRoute.js +213 -0
- package/dist/lib/BaseEasRoute.js.map +1 -0
- package/dist/lib/CompactDateTime.js +43 -0
- package/dist/lib/CompactDateTime.js.map +1 -0
- package/dist/lib/EasCommandHandler.js +2 -0
- package/dist/lib/EasCommandHandler.js.map +1 -0
- package/dist/lib/EasSyncKeyUtils.js +119 -0
- package/dist/lib/EasSyncKeyUtils.js.map +1 -0
- package/dist/lib/adapters/CalendarSyncAdapter.js +255 -0
- package/dist/lib/adapters/CalendarSyncAdapter.js.map +1 -0
- package/dist/lib/adapters/ContactsSyncAdapter.js +164 -0
- package/dist/lib/adapters/ContactsSyncAdapter.js.map +1 -0
- package/dist/lib/adapters/EasCollectionSyncAdapter.js +2 -0
- package/dist/lib/adapters/EasCollectionSyncAdapter.js.map +1 -0
- package/dist/lib/adapters/EmailSyncAdapter.js +230 -0
- package/dist/lib/adapters/EmailSyncAdapter.js.map +1 -0
- package/dist/lib/adapters/TasksSyncAdapter.js +100 -0
- package/dist/lib/adapters/TasksSyncAdapter.js.map +1 -0
- package/dist/lib/codec/WbxmlCodePages.js +706 -0
- package/dist/lib/codec/WbxmlCodePages.js.map +1 -0
- package/dist/lib/codec/WbxmlDecoder.js +154 -0
- package/dist/lib/codec/WbxmlDecoder.js.map +1 -0
- package/dist/lib/codec/WbxmlElement.js +31 -0
- package/dist/lib/codec/WbxmlElement.js.map +1 -0
- package/dist/lib/codec/WbxmlEncoder.js +88 -0
- package/dist/lib/codec/WbxmlEncoder.js.map +1 -0
- package/dist/lib/commands/ComposeMailCommand.js +187 -0
- package/dist/lib/commands/ComposeMailCommand.js.map +1 -0
- package/dist/lib/commands/FolderSyncCommand.js +150 -0
- package/dist/lib/commands/FolderSyncCommand.js.map +1 -0
- package/dist/lib/commands/GetItemEstimateCommand.js +149 -0
- package/dist/lib/commands/GetItemEstimateCommand.js.map +1 -0
- package/dist/lib/commands/ItemOperationsCommand.js +337 -0
- package/dist/lib/commands/ItemOperationsCommand.js.map +1 -0
- package/dist/lib/commands/MeetingResponseCommand.js +120 -0
- package/dist/lib/commands/MeetingResponseCommand.js.map +1 -0
- package/dist/lib/commands/MoveItemsCommand.js +103 -0
- package/dist/lib/commands/MoveItemsCommand.js.map +1 -0
- package/dist/lib/commands/PingCommand.js +145 -0
- package/dist/lib/commands/PingCommand.js.map +1 -0
- package/dist/lib/commands/ProvisionCommand.js +164 -0
- package/dist/lib/commands/ProvisionCommand.js.map +1 -0
- package/dist/lib/commands/ResolveRecipientsCommand.js +124 -0
- package/dist/lib/commands/ResolveRecipientsCommand.js.map +1 -0
- package/dist/lib/commands/SearchCommand.js +137 -0
- package/dist/lib/commands/SearchCommand.js.map +1 -0
- package/dist/lib/commands/SendMailCommand.js +18 -0
- package/dist/lib/commands/SendMailCommand.js.map +1 -0
- package/dist/lib/commands/SettingsCommand.js +138 -0
- package/dist/lib/commands/SettingsCommand.js.map +1 -0
- package/dist/lib/commands/SmartForwardCommand.js +22 -0
- package/dist/lib/commands/SmartForwardCommand.js.map +1 -0
- package/dist/lib/commands/SmartReplyCommand.js +22 -0
- package/dist/lib/commands/SmartReplyCommand.js.map +1 -0
- package/dist/lib/commands/SyncCommand.js +406 -0
- package/dist/lib/commands/SyncCommand.js.map +1 -0
- package/dist/lib/commands/mongo/FolderSyncCommandMongo.js +16 -0
- package/dist/lib/commands/mongo/FolderSyncCommandMongo.js.map +1 -0
- package/dist/lib/commands/mongo/GetItemEstimateCommandMongo.js +21 -0
- package/dist/lib/commands/mongo/GetItemEstimateCommandMongo.js.map +1 -0
- package/dist/lib/commands/mongo/ItemOperationsCommandMongo.js +18 -0
- package/dist/lib/commands/mongo/ItemOperationsCommandMongo.js.map +1 -0
- package/dist/lib/commands/mongo/MeetingResponseCommandMongo.js +17 -0
- package/dist/lib/commands/mongo/MeetingResponseCommandMongo.js.map +1 -0
- package/dist/lib/commands/mongo/MoveItemsCommandMongo.js +17 -0
- package/dist/lib/commands/mongo/MoveItemsCommandMongo.js.map +1 -0
- package/dist/lib/commands/mongo/ResolveRecipientsCommandMongo.js +20 -0
- package/dist/lib/commands/mongo/ResolveRecipientsCommandMongo.js.map +1 -0
- package/dist/lib/commands/mongo/SearchCommandMongo.js +20 -0
- package/dist/lib/commands/mongo/SearchCommandMongo.js.map +1 -0
- package/dist/lib/commands/mongo/SendMailCommandMongo.js +17 -0
- package/dist/lib/commands/mongo/SendMailCommandMongo.js.map +1 -0
- package/dist/lib/commands/mongo/SettingsCommandMongo.js +16 -0
- package/dist/lib/commands/mongo/SettingsCommandMongo.js.map +1 -0
- package/dist/lib/commands/mongo/SmartForwardCommandMongo.js +17 -0
- package/dist/lib/commands/mongo/SmartForwardCommandMongo.js.map +1 -0
- package/dist/lib/commands/mongo/SmartReplyCommandMongo.js +17 -0
- package/dist/lib/commands/mongo/SmartReplyCommandMongo.js.map +1 -0
- package/dist/lib/commands/mongo/SyncCommandMongo.js +26 -0
- package/dist/lib/commands/mongo/SyncCommandMongo.js.map +1 -0
- package/dist/lib/commands/mongo/index.js +14 -0
- package/dist/lib/commands/mongo/index.js.map +1 -0
- package/dist/lib/commands/sql/FolderSyncCommandSQL.js +16 -0
- package/dist/lib/commands/sql/FolderSyncCommandSQL.js.map +1 -0
- package/dist/lib/commands/sql/GetItemEstimateCommandSQL.js +21 -0
- package/dist/lib/commands/sql/GetItemEstimateCommandSQL.js.map +1 -0
- package/dist/lib/commands/sql/ItemOperationsCommandSQL.js +18 -0
- package/dist/lib/commands/sql/ItemOperationsCommandSQL.js.map +1 -0
- package/dist/lib/commands/sql/MeetingResponseCommandSQL.js +17 -0
- package/dist/lib/commands/sql/MeetingResponseCommandSQL.js.map +1 -0
- package/dist/lib/commands/sql/MoveItemsCommandSQL.js +17 -0
- package/dist/lib/commands/sql/MoveItemsCommandSQL.js.map +1 -0
- package/dist/lib/commands/sql/ResolveRecipientsCommandSQL.js +20 -0
- package/dist/lib/commands/sql/ResolveRecipientsCommandSQL.js.map +1 -0
- package/dist/lib/commands/sql/SearchCommandSQL.js +20 -0
- package/dist/lib/commands/sql/SearchCommandSQL.js.map +1 -0
- package/dist/lib/commands/sql/SendMailCommandSQL.js +17 -0
- package/dist/lib/commands/sql/SendMailCommandSQL.js.map +1 -0
- package/dist/lib/commands/sql/SettingsCommandSQL.js +16 -0
- package/dist/lib/commands/sql/SettingsCommandSQL.js.map +1 -0
- package/dist/lib/commands/sql/SmartForwardCommandSQL.js +17 -0
- package/dist/lib/commands/sql/SmartForwardCommandSQL.js.map +1 -0
- package/dist/lib/commands/sql/SmartReplyCommandSQL.js +17 -0
- package/dist/lib/commands/sql/SmartReplyCommandSQL.js.map +1 -0
- package/dist/lib/commands/sql/SyncCommandSQL.js +26 -0
- package/dist/lib/commands/sql/SyncCommandSQL.js.map +1 -0
- package/dist/lib/commands/sql/index.js +14 -0
- package/dist/lib/commands/sql/index.js.map +1 -0
- package/dist/lib/index.js +56 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/mongo/DeviceSyncStateRouteMongo.js +16 -0
- package/dist/lib/mongo/DeviceSyncStateRouteMongo.js.map +1 -0
- package/dist/lib/mongo/EasRouteMongo.js +51 -0
- package/dist/lib/mongo/EasRouteMongo.js.map +1 -0
- package/dist/lib/mongo/index.js +8 -0
- package/dist/lib/mongo/index.js.map +1 -0
- package/dist/lib/mongo.js +6 -0
- package/dist/lib/mongo.js.map +1 -0
- package/dist/lib/sql/DeviceSyncStateRouteSQL.js +16 -0
- package/dist/lib/sql/DeviceSyncStateRouteSQL.js.map +1 -0
- package/dist/lib/sql/EasRouteSQL.js +50 -0
- package/dist/lib/sql/EasRouteSQL.js.map +1 -0
- package/dist/lib/sql/index.js +8 -0
- package/dist/lib/sql/index.js.map +1 -0
- package/dist/lib/sql.js +6 -0
- package/dist/lib/sql.js.map +1 -0
- package/dist/types/BaseDeviceSyncStateRoute.d.ts +34 -0
- package/dist/types/BaseEasRoute.d.ts +64 -0
- package/dist/types/CompactDateTime.d.ts +22 -0
- package/dist/types/EasCommandHandler.d.ts +59 -0
- package/dist/types/EasSyncKeyUtils.d.ts +89 -0
- package/dist/types/adapters/CalendarSyncAdapter.d.ts +52 -0
- package/dist/types/adapters/ContactsSyncAdapter.d.ts +33 -0
- package/dist/types/adapters/EasCollectionSyncAdapter.d.ts +52 -0
- package/dist/types/adapters/EmailSyncAdapter.d.ts +53 -0
- package/dist/types/adapters/TasksSyncAdapter.d.ts +32 -0
- package/dist/types/codec/WbxmlCodePages.d.ts +45 -0
- package/dist/types/codec/WbxmlDecoder.d.ts +33 -0
- package/dist/types/codec/WbxmlElement.d.ts +43 -0
- package/dist/types/codec/WbxmlEncoder.d.ts +20 -0
- package/dist/types/commands/ComposeMailCommand.d.ts +50 -0
- package/dist/types/commands/FolderSyncCommand.d.ts +25 -0
- package/dist/types/commands/GetItemEstimateCommand.d.ts +43 -0
- package/dist/types/commands/ItemOperationsCommand.d.ts +72 -0
- package/dist/types/commands/MeetingResponseCommand.d.ts +32 -0
- package/dist/types/commands/MoveItemsCommand.d.ts +29 -0
- package/dist/types/commands/PingCommand.d.ts +33 -0
- package/dist/types/commands/ProvisionCommand.d.ts +54 -0
- package/dist/types/commands/ResolveRecipientsCommand.d.ts +34 -0
- package/dist/types/commands/SearchCommand.d.ts +39 -0
- package/dist/types/commands/SendMailCommand.d.ts +10 -0
- package/dist/types/commands/SettingsCommand.d.ts +37 -0
- package/dist/types/commands/SmartForwardCommand.d.ts +17 -0
- package/dist/types/commands/SmartReplyCommand.d.ts +17 -0
- package/dist/types/commands/SyncCommand.d.ts +100 -0
- package/dist/types/commands/mongo/FolderSyncCommandMongo.d.ts +8 -0
- package/dist/types/commands/mongo/GetItemEstimateCommandMongo.d.ts +7 -0
- package/dist/types/commands/mongo/ItemOperationsCommandMongo.d.ts +9 -0
- package/dist/types/commands/mongo/MeetingResponseCommandMongo.d.ts +8 -0
- package/dist/types/commands/mongo/MoveItemsCommandMongo.d.ts +8 -0
- package/dist/types/commands/mongo/ResolveRecipientsCommandMongo.d.ts +8 -0
- package/dist/types/commands/mongo/SearchCommandMongo.d.ts +8 -0
- package/dist/types/commands/mongo/SendMailCommandMongo.d.ts +8 -0
- package/dist/types/commands/mongo/SettingsCommandMongo.d.ts +7 -0
- package/dist/types/commands/mongo/SmartForwardCommandMongo.d.ts +8 -0
- package/dist/types/commands/mongo/SmartReplyCommandMongo.d.ts +8 -0
- package/dist/types/commands/mongo/SyncCommandMongo.d.ts +8 -0
- package/dist/types/commands/mongo/index.d.ts +9 -0
- package/dist/types/commands/sql/FolderSyncCommandSQL.d.ts +8 -0
- package/dist/types/commands/sql/GetItemEstimateCommandSQL.d.ts +7 -0
- package/dist/types/commands/sql/ItemOperationsCommandSQL.d.ts +9 -0
- package/dist/types/commands/sql/MeetingResponseCommandSQL.d.ts +8 -0
- package/dist/types/commands/sql/MoveItemsCommandSQL.d.ts +8 -0
- package/dist/types/commands/sql/ResolveRecipientsCommandSQL.d.ts +8 -0
- package/dist/types/commands/sql/SearchCommandSQL.d.ts +8 -0
- package/dist/types/commands/sql/SendMailCommandSQL.d.ts +8 -0
- package/dist/types/commands/sql/SettingsCommandSQL.d.ts +7 -0
- package/dist/types/commands/sql/SmartForwardCommandSQL.d.ts +8 -0
- package/dist/types/commands/sql/SmartReplyCommandSQL.d.ts +8 -0
- package/dist/types/commands/sql/SyncCommandSQL.d.ts +8 -0
- package/dist/types/commands/sql/index.d.ts +9 -0
- package/dist/types/index.d.ts +51 -0
- package/dist/types/mongo/DeviceSyncStateRouteMongo.d.ts +8 -0
- package/dist/types/mongo/EasRouteMongo.d.ts +14 -0
- package/dist/types/mongo/index.d.ts +3 -0
- package/dist/types/mongo.d.ts +1 -0
- package/dist/types/sql/DeviceSyncStateRouteSQL.d.ts +8 -0
- package/dist/types/sql/EasRouteSQL.d.ts +13 -0
- package/dist/types/sql/index.d.ts +3 -0
- package/dist/types/sql.d.ts +1 -0
- package/package.json +94 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type WbxmlElement } from "../codec/WbxmlElement.js";
|
|
2
|
+
import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handles EAS `ItemOperations`: `Fetch` (a `Message`'s full body or an `Attachment`'s binary content, by the
|
|
5
|
+
* same `ServerId`/uid this library already exposes elsewhere) and `EmptyFolderContents`.
|
|
6
|
+
*
|
|
7
|
+
* Per the published [MS-ASCMD] `ItemOperations` request schema (confirmed directly, not assumed), the command
|
|
8
|
+
* is a strict choice of exactly three operations - `Fetch` (unbounded), `EmptyFolderContents`, and `Move` -
|
|
9
|
+
* with no fourth "write a new item" capability anywhere in it; `Store` is a required *child* of `Fetch`
|
|
10
|
+
* (`"Mailbox"` or `"DocumentLibrary"`, the same store-selector role it plays in `SearchCommand`), not a
|
|
11
|
+
* separate write/upload command as its name might suggest.
|
|
12
|
+
*
|
|
13
|
+
* **Pragmatic subset, deliberately not the full MS-ASCMD `ItemOperations` semantics**:
|
|
14
|
+
* - `Move` moves an entire *conversation* (by `ConversationId`, opaque binary - see `EmailSyncAdapter`'s
|
|
15
|
+
* `encodeConversationId`/`decodeConversationId`) to a destination folder - unrelated to the standalone
|
|
16
|
+
* `MoveItems` command's per-message `SrcFldId`/`SrcMsgId`/`DstFldId` shape. Every `Message` sharing the
|
|
17
|
+
* decoded `conversationId` across the whole mailbox (not just one folder) that the caller has `UPDATE` on is
|
|
18
|
+
* relocated to `DstFldId`; one lacking permission is silently skipped rather than failing the whole move (a
|
|
19
|
+
* conversation can legitimately span folders the caller doesn't control, e.g. a shared mailbox's Inbox). An
|
|
20
|
+
* optional `MoveAlways` (a hint to keep auto-moving future messages in this conversation) is accepted but not
|
|
21
|
+
* acted on - this library's `MailFilterRule` has no conversation-scoped condition to key an ongoing rule off
|
|
22
|
+
* of, a documented simplification, not silent data loss (the move itself still happens).
|
|
23
|
+
* - `Store: "DocumentLibrary"` is rejected per-`Fetch` - matches `SearchCommand`'s own GAL-only scope decision;
|
|
24
|
+
* this library has no document-library model.
|
|
25
|
+
* - A `Fetch` failure (not found, no permission, malformed) aborts the whole request via an HTTP-level error
|
|
26
|
+
* rather than an embedded per-`Fetch` `Status` code the way `Sync`/`MoveItems`/`ResolveRecipients` report
|
|
27
|
+
* their own per-item failures - a deliberate, documented simplification carried over unchanged from this
|
|
28
|
+
* command's original single-`Fetch` design, not a new gap introduced by adding multi-`Fetch` support.
|
|
29
|
+
* - Only the "inline" delivery method is used (content embedded directly in the WBXML response) - the real
|
|
30
|
+
* spec's "multipart" alternative (WBXML as one part, binary content as a separate part) is not implemented;
|
|
31
|
+
* every attachment this library's own `ScanPipeline` already accepts is assumed to fit comfortably in memory
|
|
32
|
+
* for one response, the same assumption `BaseAttachmentRoute.download()` already makes.
|
|
33
|
+
* - `Options/BodyPreference`'s `Type`/`TruncationSize` are honored for a `Message` body fetch (plain text,
|
|
34
|
+
* HTML, or - `Type 4` - the raw MIME source verbatim); byte-range fetching (`Range`) is not implemented.
|
|
35
|
+
* - `EmptyFolderContents`'s `DeleteSubFolders` option is rejected outright rather than silently ignored -
|
|
36
|
+
* recursive subfolder deletion is out of scope for this pragmatic subset; emptying a single folder's own
|
|
37
|
+
* `Message`s is the common case this implements.
|
|
38
|
+
*
|
|
39
|
+
* `folderClass`/`messageClass`/`attachmentClass` are supplied by the Mongo/SQL concrete subclasses.
|
|
40
|
+
*
|
|
41
|
+
* @author Jean-Philippe Steinmetz
|
|
42
|
+
*/
|
|
43
|
+
export declare abstract class ItemOperationsCommand implements EasCommandHandler {
|
|
44
|
+
readonly command = "ItemOperations";
|
|
45
|
+
protected abstract folderClass: any;
|
|
46
|
+
protected abstract messageClass: any;
|
|
47
|
+
protected abstract attachmentClass: any;
|
|
48
|
+
private _objectFactory?;
|
|
49
|
+
private folderRepo?;
|
|
50
|
+
private messageRepo?;
|
|
51
|
+
private attachmentRepo?;
|
|
52
|
+
private blobStore?;
|
|
53
|
+
private aclUtils?;
|
|
54
|
+
private maxFetchesPerRequest;
|
|
55
|
+
private batchSize;
|
|
56
|
+
init(): Promise<void>;
|
|
57
|
+
handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
|
|
58
|
+
private fetchMessage;
|
|
59
|
+
private emptyFolderContents;
|
|
60
|
+
/**
|
|
61
|
+
* Handles `ItemOperations`' `Move`: relocates every `Message` sharing a decoded `ConversationId` to
|
|
62
|
+
* `DstFldId`. Unlike `emptyFolderContents`/`fetchMessage` (which fail the whole request via a thrown
|
|
63
|
+
* `ApiError`), this reports failure through the embedded `Status` the same way `MoveItemsCommand` does for
|
|
64
|
+
* its own per-item results - a malformed/unresolvable `Move` is a normal outcome for this operation, not an
|
|
65
|
+
* exceptional one. Reports failure (`Status 3`) if not even one message was actually moved - including when
|
|
66
|
+
* every message sharing the conversation sits in a folder the caller lacks `UPDATE` on, which would
|
|
67
|
+
* otherwise silently fall through to a false "success" with nothing having moved.
|
|
68
|
+
*/
|
|
69
|
+
private moveConversation;
|
|
70
|
+
private moveResponse;
|
|
71
|
+
private fetchAttachment;
|
|
72
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type WbxmlElement } from "../codec/WbxmlElement.js";
|
|
2
|
+
import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handles EAS `MeetingResponse`: records the caller's own accept/tentative/decline response to a meeting on
|
|
5
|
+
* the referenced `CalendarEvent`. `RequestId` is the same `Message`/`CalendarEvent.uid` this library already
|
|
6
|
+
* exposes as `ServerId` elsewhere (Sync/FolderSync) - no separate lookup table is needed.
|
|
7
|
+
*
|
|
8
|
+
* **Pragmatic subset**: only the first `<Request>` in the command is processed (the real spec allows several
|
|
9
|
+
* per request, matching `Sync`'s own single-`Collection`-per-request scope in this library). A **decline**
|
|
10
|
+
* soft-deletes the `CalendarEvent` (matching real Exchange behavior) rather than merely flipping the caller's
|
|
11
|
+
* own `Attendee.responseStatus` - each attendee has their own row already (`CalendarEvent.mailboxUid` scopes
|
|
12
|
+
* every event to a single mailbox, per this library's architecture), so deleting *this* row only removes the
|
|
13
|
+
* meeting from the declining attendee's own calendar, leaving the organizer's and every other attendee's own
|
|
14
|
+
* copy untouched. Accept/Tentative still update `Attendee.responseStatus` in place. The response omits
|
|
15
|
+
* `CalendarId` for a decline either way, matching the spec's own convention, so a client relying on that
|
|
16
|
+
* signal isn't misled into thinking a new item was created.
|
|
17
|
+
*
|
|
18
|
+
* `calendarEventClass`/`mailboxClass` are supplied by the Mongo/SQL concrete subclasses.
|
|
19
|
+
*
|
|
20
|
+
* @author Jean-Philippe Steinmetz
|
|
21
|
+
*/
|
|
22
|
+
export declare abstract class MeetingResponseCommand implements EasCommandHandler {
|
|
23
|
+
readonly command = "MeetingResponse";
|
|
24
|
+
protected abstract calendarEventClass: any;
|
|
25
|
+
protected abstract mailboxClass: any;
|
|
26
|
+
private _objectFactory?;
|
|
27
|
+
private calendarEventRepo?;
|
|
28
|
+
private mailboxRepo?;
|
|
29
|
+
private aclUtils?;
|
|
30
|
+
init(): Promise<void>;
|
|
31
|
+
handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type WbxmlElement } from "../codec/WbxmlElement.js";
|
|
2
|
+
import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handles the standalone EAS `MoveItems` command: moves one or more `Message`s between folders in the caller's
|
|
5
|
+
* own mailbox by `ServerId`/`uid`.
|
|
6
|
+
*
|
|
7
|
+
* **Pragmatic subset**: `Message` only - `Contacts`/`Calendar`/`Tasks` moves are rare in practice (unlike
|
|
8
|
+
* `Message`, whose Inbox-to-subfolder filing is a real, common client action) and would each need their own
|
|
9
|
+
* ACL/ownership verification path for comparatively little value; a client attempting one gets `Status 3`
|
|
10
|
+
* rather than being silently ignored. `DstMsgId` in the response is always the same `uid` as `SrcMsgId` - this
|
|
11
|
+
* library never mints a new identifier on move, unlike a real Exchange server, which sometimes does.
|
|
12
|
+
*
|
|
13
|
+
* `messageClass`/`folderClass` are supplied by the Mongo/SQL concrete subclasses.
|
|
14
|
+
*
|
|
15
|
+
* @author Jean-Philippe Steinmetz
|
|
16
|
+
*/
|
|
17
|
+
export declare abstract class MoveItemsCommand implements EasCommandHandler {
|
|
18
|
+
readonly command = "MoveItems";
|
|
19
|
+
protected abstract messageClass: any;
|
|
20
|
+
protected abstract folderClass: any;
|
|
21
|
+
private _objectFactory?;
|
|
22
|
+
private messageRepo?;
|
|
23
|
+
private folderRepo?;
|
|
24
|
+
private aclUtils?;
|
|
25
|
+
init(): Promise<void>;
|
|
26
|
+
handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
|
|
27
|
+
private moveOne;
|
|
28
|
+
private responseElement;
|
|
29
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type WbxmlElement } from "../codec/WbxmlElement.js";
|
|
2
|
+
import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handles EAS `Ping`: a long-poll HTTP request that blocks for up to `HeartbeatInterval` seconds waiting for a
|
|
5
|
+
* change in any of the client-specified folders, then reports which (if any) actually changed. No websocket
|
|
6
|
+
* needed - subscribes a plain, request-scoped Redis client (the same construction `BasePushRoute.connect()`
|
|
7
|
+
* uses, minus its own per-socket bookkeeping this one-shot request doesn't need) directly to the requested
|
|
8
|
+
* `folderUid` channels. Every `Message`/`CalendarEvent`/etc. mutation already publishes to exactly these
|
|
9
|
+
* channels via `BaseScopedChildRoute.notify()`/`RepoUtils`'s own push - `Ping` needs no new publish call site
|
|
10
|
+
* of its own, only a subscriber.
|
|
11
|
+
*
|
|
12
|
+
* Requested folder uids are filtered down to only those the caller currently has `READ` on before subscribing -
|
|
13
|
+
* matching every other command's ownership check on a client-supplied id, since without it a device could
|
|
14
|
+
* long-poll indefinitely on any folder uid it happens to know (including one it used to have legitimate access
|
|
15
|
+
* to and no longer does) and learn purely from this channel when that other mailbox's data changes.
|
|
16
|
+
*
|
|
17
|
+
* @author Jean-Philippe Steinmetz
|
|
18
|
+
*/
|
|
19
|
+
export declare class PingCommand implements EasCommandHandler {
|
|
20
|
+
readonly command = "Ping";
|
|
21
|
+
private redisConfig;
|
|
22
|
+
private minHeartbeatSeconds;
|
|
23
|
+
private maxHeartbeatSeconds;
|
|
24
|
+
private aclUtils?;
|
|
25
|
+
handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
|
|
26
|
+
private statusResponse;
|
|
27
|
+
/** Subscribes to `folderUids` and resolves with whichever of them published a change first, or `[]` if
|
|
28
|
+
* `timeoutSeconds` elapses with no publish. A missing `datastores:events` config (Redis pub/sub not set up
|
|
29
|
+
* for this deployment) fails open to "no changes" rather than blocking forever or throwing - `Ping` is a
|
|
30
|
+
* best-effort low-latency notification path, not the only way a client ever discovers new mail (it will
|
|
31
|
+
* eventually poll a real `Sync`/`FolderSync` regardless). */
|
|
32
|
+
private waitForChange;
|
|
33
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type WbxmlElement } from "../codec/WbxmlElement.js";
|
|
2
|
+
import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handles the two-request EAS `Provision` handshake (MS-ASPROV) every client must complete before any other
|
|
5
|
+
* command is honored (see `BaseEasRoute`'s provisioning gate), plus the three-step `RemoteWipe` sub-flow that
|
|
6
|
+
* rides the same command.
|
|
7
|
+
*
|
|
8
|
+
* **Policy issuance/enforcement**: the password/encryption requirements sent back in request 1's policy
|
|
9
|
+
* document are sourced from `@Config("mail:eas:provision:*")` (defaults below are permissive but not
|
|
10
|
+
* `0`/`false` across the board, unlike the old hardcoded document) - a deployment can tighten them without
|
|
11
|
+
* code changes. Enforcement itself is honest but shallow: request 2 must carry back `Status: 1` on its own
|
|
12
|
+
* `Policy` (a device that reports it could *not* apply the policy, or omits `Status` entirely, is rejected
|
|
13
|
+
* without provisioning) - this library does not itself verify the device's actual password/encryption state
|
|
14
|
+
* beyond trusting that self-reported status, matching MS-ASPROV's own protocol design (the wire protocol has
|
|
15
|
+
* no way for the server to inspect device state directly either).
|
|
16
|
+
*
|
|
17
|
+
* - **Request 1** (no `PolicyKey` in the body): mint a new policy key, store it on `DeviceSyncState` (not yet
|
|
18
|
+
* provisioned), and send back the policy document under that key. If a `RemoteWipe` was requested for this
|
|
19
|
+
* device (`DeviceSyncState.remoteWipeRequested`, set by an admin - see the remote-wipe route), skip normal
|
|
20
|
+
* policy issuance entirely and send the `RemoteWipe` directive instead.
|
|
21
|
+
* - **Request 2** (client echoes the `PolicyKey` back, acknowledging the policy): if the key matches what was
|
|
22
|
+
* minted in request 1 *and* the client's own `Status` is `1`, mark the device provisioned and re-confirm the
|
|
23
|
+
* same key; anything else (a stale/replayed key, a device that never actually saw request 1's response, or a
|
|
24
|
+
* device reporting it could not comply) is rejected without provisioning.
|
|
25
|
+
* - **RemoteWipe acknowledgement**: after wiping itself, a device sends a bare `<Provision><RemoteWipe>
|
|
26
|
+
* <Status>1</Status></RemoteWipe></Provision>` (no `Policies`). Detected first, ahead of the normal
|
|
27
|
+
* issue/acknowledge branching. Clears `remoteWipeRequested` and stamps `remoteWipeAcknowledgedAt` for audit,
|
|
28
|
+
* but deliberately leaves `provisioned` untouched (`false`, from when the wipe was requested) - the device
|
|
29
|
+
* must complete a genuine fresh Provision handshake to re-add the account, it does not fall straight back into
|
|
30
|
+
* "provisioned". `remoteWipeAccountOnly` is recorded for admin audit only; the wire directive sent to the
|
|
31
|
+
* device is identical either way (a real "wipe just this account's data" vs. "wipe the whole device"
|
|
32
|
+
* distinction would require an MDM-capable client extension this library doesn't implement).
|
|
33
|
+
*
|
|
34
|
+
* @author Jean-Philippe Steinmetz
|
|
35
|
+
*/
|
|
36
|
+
export declare class ProvisionCommand implements EasCommandHandler {
|
|
37
|
+
readonly command = "Provision";
|
|
38
|
+
private passwordEnabled;
|
|
39
|
+
private minPasswordLength;
|
|
40
|
+
private maxFailedAttempts;
|
|
41
|
+
private requireDeviceEncryption;
|
|
42
|
+
private allowSimplePassword;
|
|
43
|
+
handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
|
|
44
|
+
/** Request 1: mint and store a new policy key, send the policy document - or, if a remote wipe is
|
|
45
|
+
* pending for this device, the `RemoteWipe` directive instead. */
|
|
46
|
+
private issuePolicy;
|
|
47
|
+
/** Request 2: the client acknowledges the policy key it was handed in request 1, self-reporting whether
|
|
48
|
+
* it actually applied the policy via its own `Status`. */
|
|
49
|
+
private acknowledgePolicy;
|
|
50
|
+
/** The device has wiped itself and is acknowledging - clear the pending flag but leave `provisioned`
|
|
51
|
+
* alone (still `false`, from when the wipe was requested) so a genuine re-provision is required. */
|
|
52
|
+
private acknowledgeRemoteWipe;
|
|
53
|
+
private persist;
|
|
54
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type WbxmlElement } from "../codec/WbxmlElement.js";
|
|
2
|
+
import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handles EAS `ResolveRecipients`: resolves each `<To>` value (a display name, partial name, or address) the
|
|
5
|
+
* client is unsure how to reach against the mailbox's own `Contact` (GAL) store - the same substring-match
|
|
6
|
+
* approach `SearchCommand` uses for its own `Store Name="GAL"` lookups, duplicated rather than shared (see
|
|
7
|
+
* `escapeForLikeQuery`'s own doc comment).
|
|
8
|
+
*
|
|
9
|
+
* **Pragmatic subset**: no free-busy `Availability`, no S/MIME `Certificates`/`Options` handling at all - this
|
|
10
|
+
* command's real-world use is overwhelmingly enterprise S/MIME certificate lookup, which this library doesn't
|
|
11
|
+
* implement elsewhere either (`ComposeMailCommand` sends plain MIME, never signed/encrypted). A `<To>` value
|
|
12
|
+
* that already looks like an email address (`LOOKS_LIKE_EMAIL`) is echoed straight back as its own single
|
|
13
|
+
* exact match rather than searched for, matching a real client's own common case of resolving an address it
|
|
14
|
+
* already typed correctly.
|
|
15
|
+
*
|
|
16
|
+
* `contactClass` is supplied by the Mongo/SQL concrete subclasses.
|
|
17
|
+
*
|
|
18
|
+
* @author Jean-Philippe Steinmetz
|
|
19
|
+
*/
|
|
20
|
+
export declare abstract class ResolveRecipientsCommand implements EasCommandHandler {
|
|
21
|
+
readonly command = "ResolveRecipients";
|
|
22
|
+
protected abstract contactClass: any;
|
|
23
|
+
/** Same two-backend `like()` quirk `SearchCommand.likePattern` documents - supplied by the Mongo/SQL
|
|
24
|
+
* concrete subclasses. */
|
|
25
|
+
protected abstract likePattern(escaped: string): string;
|
|
26
|
+
private maxMatches;
|
|
27
|
+
private _objectFactory?;
|
|
28
|
+
private contactRepo?;
|
|
29
|
+
init(): Promise<void>;
|
|
30
|
+
handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
|
|
31
|
+
private resolveOne;
|
|
32
|
+
private recipientElement;
|
|
33
|
+
private responseElement;
|
|
34
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type WbxmlElement } from "../codec/WbxmlElement.js";
|
|
2
|
+
import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handles EAS `Search` for the `GAL` store only - this library's `Contact` records are also the source of
|
|
5
|
+
* truth for GAL lookups against a mailbox's own address book (see the architecture note on `Contact` itself);
|
|
6
|
+
* searching a mailbox's item contents or a document library (the other two `Search` store types the real spec
|
|
7
|
+
* defines) is out of scope for this pragmatic subset.
|
|
8
|
+
*
|
|
9
|
+
* Per the plan's own scope: a simple case-insensitive substring match via `RepoUtils.find()` directly, not the
|
|
10
|
+
* heavier `SearchProvider` full-text index - GAL lookups are small-scale exact/prefix matching against a
|
|
11
|
+
* personal address book, not relevance-ranked full text over large content. Only `displayName`/`givenName`/
|
|
12
|
+
* `surname`/`company` are matched - `Contact.emails`/`phones` are embedded arrays of objects, which a plain
|
|
13
|
+
* per-field regex query can't reach into on either backend (confirmed: MongoDB's `$regex` against an
|
|
14
|
+
* array-of-objects field matches nothing useful, and this library's own query-injection guard rejects
|
|
15
|
+
* dot-notation field paths like `"emails.address"` outright) - a documented gap, not an oversight.
|
|
16
|
+
*
|
|
17
|
+
* `contactClass` is supplied by the Mongo/SQL concrete subclasses.
|
|
18
|
+
*
|
|
19
|
+
* @author Jean-Philippe Steinmetz
|
|
20
|
+
*/
|
|
21
|
+
export declare abstract class SearchCommand implements EasCommandHandler {
|
|
22
|
+
readonly command = "Search";
|
|
23
|
+
protected abstract contactClass: any;
|
|
24
|
+
/** Wraps an already regex-escaped substring for this backend's `like()` operator. A second real
|
|
25
|
+
* cross-backend gap (confirmed by reading both implementations, not assumed): Mongo's `like()` compiles to
|
|
26
|
+
* an unanchored `$regex` (a substring match with no wrapping needed), but the SQL backend's compiles to
|
|
27
|
+
* TypeORM's `ILike()` (a plain SQL `LIKE`, which is an *exact* case-insensitive match unless the pattern
|
|
28
|
+
* itself carries `%` wildcards) - so the same escaped pattern needs `%pattern%` on SQL but must NOT get
|
|
29
|
+
* literal `%` characters on Mongo, where they'd be matched as themselves in the regex and never found.
|
|
30
|
+
* Supplied by the Mongo/SQL concrete subclasses. */
|
|
31
|
+
protected abstract likePattern(escaped: string): string;
|
|
32
|
+
private _objectFactory?;
|
|
33
|
+
private contactRepo?;
|
|
34
|
+
private defaultRangeEnd;
|
|
35
|
+
private maxRangeEnd;
|
|
36
|
+
init(): Promise<void>;
|
|
37
|
+
handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
|
|
38
|
+
private contactToResult;
|
|
39
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ComposeMailCommand } from "./ComposeMailCommand.js";
|
|
2
|
+
/**
|
|
3
|
+
* Handles EAS `SendMail`: relays a freshly composed message with no referenced original - see
|
|
4
|
+
* `ComposeMailCommand`'s own doc comment for the full shared implementation.
|
|
5
|
+
*
|
|
6
|
+
* @author Jean-Philippe Steinmetz
|
|
7
|
+
*/
|
|
8
|
+
export declare abstract class SendMailCommand extends ComposeMailCommand {
|
|
9
|
+
readonly command = "SendMail";
|
|
10
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type WbxmlElement } from "../codec/WbxmlElement.js";
|
|
2
|
+
import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handles EAS `Settings`: the first-run/general-purpose device<->server settings exchange. This pragmatic
|
|
5
|
+
* subset supports the sub-elements every real client actually depends on:
|
|
6
|
+
*
|
|
7
|
+
* - `UserInformation`/`Get`: returns the mailbox's `primarySmtpAddress`/`aliasAddresses` as `EmailAddresses`.
|
|
8
|
+
* - `DeviceInformation`/`Set`: acknowledged with `Status 1` but not persisted anywhere - `DeviceSyncState` has
|
|
9
|
+
* no fields for a device's model/IMEI/OS/friendly name, and nothing else in this library currently consumes
|
|
10
|
+
* them. A real client only requires the acknowledgement to proceed past first-run setup, not that the values
|
|
11
|
+
* are retrievable later.
|
|
12
|
+
* - `Oof`/`Get` and `Oof`/`Set`: reads/writes `Mailbox.oofEnabled`/`oofMessage`/`oofStartTime`/`oofEndTime`.
|
|
13
|
+
* `StartTime`/`EndTime` use MS-ASDTYPE's plain `dateTime` type (`Date.prototype.toISOString()`), not Compact
|
|
14
|
+
* DateTime - confirmed against MS-ASSETTINGS directly, unlike `Calendar`/`Tasks`' timestamp fields (see
|
|
15
|
+
* `CompactDateTime.ts`'s own doc comment on that exact distinction). A single combined reply message is stored
|
|
16
|
+
* rather than the spec's three audience-specific `OofMessage` variants (internal/external-known/
|
|
17
|
+
* external-unknown) - matches this codebase's existing "one thing, not three" simplification precedent (e.g.
|
|
18
|
+
* `ContactsSyncAdapter`'s single-slot phone/email handling).
|
|
19
|
+
* - `RightsManagementInformation` is not implemented - deferred, matching this library's "pragmatic subset"
|
|
20
|
+
* precedent elsewhere (e.g. `ComposeMailCommand`'s own documented gaps).
|
|
21
|
+
*
|
|
22
|
+
* `mailboxClass` is supplied by the Mongo/SQL concrete subclasses.
|
|
23
|
+
*
|
|
24
|
+
* @author Jean-Philippe Steinmetz
|
|
25
|
+
*/
|
|
26
|
+
export declare abstract class SettingsCommand implements EasCommandHandler {
|
|
27
|
+
readonly command = "Settings";
|
|
28
|
+
protected abstract mailboxClass: any;
|
|
29
|
+
private _objectFactory?;
|
|
30
|
+
private mailboxRepo?;
|
|
31
|
+
init(): Promise<void>;
|
|
32
|
+
handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
|
|
33
|
+
/** `OofState`: `0`=disabled, `1`=enabled indefinitely, `2`=time-based (only while now is within
|
|
34
|
+
* `StartTime`/`EndTime`). */
|
|
35
|
+
private getOof;
|
|
36
|
+
private setOof;
|
|
37
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ComposeMailCommand } from "./ComposeMailCommand.js";
|
|
2
|
+
import type { EasCommandContext } from "../EasCommandHandler.js";
|
|
3
|
+
import type { Message } from "@rapidmx/restapi";
|
|
4
|
+
/**
|
|
5
|
+
* Handles EAS `SmartForward`: relays a freshly composed message threaded to (and referencing) the original via
|
|
6
|
+
* `<Source>`, then flips the original's `Forwarded` flag - see `ComposeMailCommand`'s own doc comment for the
|
|
7
|
+
* full shared implementation and this pragmatic subset's scope.
|
|
8
|
+
*
|
|
9
|
+
* @author Jean-Philippe Steinmetz
|
|
10
|
+
*/
|
|
11
|
+
export declare abstract class SmartForwardCommand extends ComposeMailCommand {
|
|
12
|
+
readonly command = "SmartForward";
|
|
13
|
+
protected markOriginal(ctx: EasCommandContext, original: Message & {
|
|
14
|
+
uid: string;
|
|
15
|
+
version: number;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ComposeMailCommand } from "./ComposeMailCommand.js";
|
|
2
|
+
import type { EasCommandContext } from "../EasCommandHandler.js";
|
|
3
|
+
import type { Message } from "@rapidmx/restapi";
|
|
4
|
+
/**
|
|
5
|
+
* Handles EAS `SmartReply`: relays a freshly composed message threaded to (and referencing) the original via
|
|
6
|
+
* `<Source>`, then flips the original's `Answered` flag - see `ComposeMailCommand`'s own doc comment for the
|
|
7
|
+
* full shared implementation and this pragmatic subset's scope.
|
|
8
|
+
*
|
|
9
|
+
* @author Jean-Philippe Steinmetz
|
|
10
|
+
*/
|
|
11
|
+
export declare abstract class SmartReplyCommand extends ComposeMailCommand {
|
|
12
|
+
readonly command = "SmartReply";
|
|
13
|
+
protected markOriginal(ctx: EasCommandContext, original: Message & {
|
|
14
|
+
uid: string;
|
|
15
|
+
version: number;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type RecoverableBaseEntity } from "@rapidrest/service-core";
|
|
2
|
+
import { type WbxmlElement } from "../codec/WbxmlElement.js";
|
|
3
|
+
import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
|
|
4
|
+
/** Binds one MS-ASCMD `Class` value (`"Email"`, `"Contacts"`, ...) to the concrete entity class `SyncCommand`
|
|
5
|
+
* should build a `RepoUtils` for, and the adapter class that maps that entity to/from `ApplicationData`.
|
|
6
|
+
* Supplied by the Mongo/SQL concrete subclasses, one map entry per supported collection type.
|
|
7
|
+
*
|
|
8
|
+
* `adapterClass`, not a pre-built `adapter` instance: `SyncCommand.init()` instantiates each one itself via
|
|
9
|
+
* `ObjectFactory`, so an adapter can `@Inject` its own dependencies (`EmailSyncAdapter` needs `BlobStore` for a
|
|
10
|
+
* Draft's body) exactly like any other DI-managed class in this library - a bare `new EmailSyncAdapter()` has
|
|
11
|
+
* no way to satisfy that. */
|
|
12
|
+
export interface SyncCollectionBinding<T extends RecoverableBaseEntity> {
|
|
13
|
+
entityClass: any;
|
|
14
|
+
adapterClass: any;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Handles EAS `Sync`: enumerates `Add`/`Change`/`Delete`s for a single folder's contents since the device's last
|
|
18
|
+
* `Sync` of that folder, using the same watermark-based cursor mechanism `FolderSyncCommand` uses (via
|
|
19
|
+
* `EasSyncKeyUtils`), scoped by `folderUid` instead of `mailboxUid`, and keyed per-folder in
|
|
20
|
+
* `DeviceSyncState.folderSyncKeys` (the `CollectionId` a client sends *is* the `folderUid` - this library never
|
|
21
|
+
* invents a separate collection identifier).
|
|
22
|
+
*
|
|
23
|
+
* **Multi-collection requests**: every `<Collection>` in a request's `<Collections>` is processed and gets its
|
|
24
|
+
* own `<Collection>` entry in the response, each with its own independent `SyncKey`/`Status` - a client
|
|
25
|
+
* syncing several folders in one round trip (the common case once the initial per-folder backlog is done)
|
|
26
|
+
* gets one response covering all of them. All per-collection `SyncKey`/remembered-`Class` writes for the whole
|
|
27
|
+
* request are batched into a single `persistDeviceSyncState` call after every collection has been processed
|
|
28
|
+
* (never one call per collection) - see `EasSyncKeyUtils.persistDeviceSyncState`'s own doc comment for why a
|
|
29
|
+
* second write to the same `DeviceSyncState` within one request must never be built off a stale copy.
|
|
30
|
+
*
|
|
31
|
+
* **`Class` is only required on a collection's first (`SyncKey "0"`) request**, per `[MS-ASCMD]` - once seen,
|
|
32
|
+
* it's remembered in `DeviceSyncState.folderCollectionClasses` (keyed by `folderUid`) so a later request may
|
|
33
|
+
* omit it; omitting it for a folder never previously synced still gets `Status 4` (nothing to fall back to).
|
|
34
|
+
*
|
|
35
|
+
* **Every `CollectionId` is ACL-checked against the caller before it's touched**: `processCollection()` requires
|
|
36
|
+
* `ACLAction.READ` on the folder before enumerating or accepting any Commands for it at all (a folder the caller
|
|
37
|
+
* can't read is reported the same as an unrecognized collection - Status `4` - rather than leaking whether it
|
|
38
|
+
* exists); `applyAdd`/`applyChange`/`applyDelete` additionally require `CREATE`/`UPDATE`/`DELETE` respectively,
|
|
39
|
+
* and `applyChange`/`applyDelete` re-verify the resolved item's own `folderUid` actually matches the collection
|
|
40
|
+
* being synced (treating a mismatch identically to "not found" - Status `8` - never revealing that the
|
|
41
|
+
* `ServerId` resolves to something real elsewhere). Without this, a client could supply any other mailbox's
|
|
42
|
+
* folder/item uid as its own `CollectionId`/`ServerId` and read or mutate that mailbox's data directly - the
|
|
43
|
+
* same ownership-verification-after-an-`ignoreACL`-lookup pattern `ItemOperationsCommand`/`MoveItemsCommand`
|
|
44
|
+
* already use, applied consistently here too.
|
|
45
|
+
*
|
|
46
|
+
* **Pragmatic subset, deliberately not the full MS-ASCMD `Sync` surface**:
|
|
47
|
+
* - **Client-originated `Add`/`Change`/`Delete` commands are accepted for every collection type**, including
|
|
48
|
+
* `Email` (a device creating/editing a Draft, or deleting a message locally - see `applyAdd`/`applyChange`/
|
|
49
|
+
* `applyDelete`). `[MS-ASCMD]` itself disallows `Add`/`Change` for any *non-draft* `Email` item - this library
|
|
50
|
+
* doesn't verify a Sync `Email` Add/Change actually targets the caller's own Drafts folder *specifically*
|
|
51
|
+
* (only that it's a folder the caller actually owns/can write to - see above), matching how Contacts/Calendar/
|
|
52
|
+
* Tasks folder targeting is equally unchecked beyond ownership elsewhere. A collection whose adapter has no
|
|
53
|
+
* `fromApplicationData` at all would get Status `6` for `Add`/`Change` instead, but every adapter today
|
|
54
|
+
* implements it. Per `[MS-ASCMD]`'s own "Add (Sync)"/"Status (Sync)" pages: `Add` always gets a `Responses`
|
|
55
|
+
* entry (it must report the assigned `ServerId`); `Change`/`Delete` only get one on **failure** - a silent
|
|
56
|
+
* success means "assume it worked."
|
|
57
|
+
* - Only a body preview is returned per item (see `EmailSyncAdapter`'s own doc comment) - full body content is
|
|
58
|
+
* fetched separately via `ItemOperationsCommand`. A Draft's `Email` Add/Change is plain-text-only, with no
|
|
59
|
+
* attachment support (mirrors `ComposeMailCommand`'s own already-documented attachment gap).
|
|
60
|
+
*
|
|
61
|
+
* @author Jean-Philippe Steinmetz
|
|
62
|
+
*/
|
|
63
|
+
export declare abstract class SyncCommand implements EasCommandHandler {
|
|
64
|
+
readonly command = "Sync";
|
|
65
|
+
protected abstract collectionBindings: Record<string, SyncCollectionBinding<any>>;
|
|
66
|
+
/** Supplied by the Mongo/SQL concrete subclasses so a client-originated `Add`'s `newEntityDefaults()` can
|
|
67
|
+
* be given the caller's own `Mailbox` (`EmailSyncAdapter` needs it for a new Draft's `from`) - same
|
|
68
|
+
* one-line-per-backend pattern `MeetingResponseCommand`/`SettingsCommand` already use. */
|
|
69
|
+
protected abstract mailboxClass: any;
|
|
70
|
+
private windowSize;
|
|
71
|
+
private _objectFactory?;
|
|
72
|
+
private aclUtils?;
|
|
73
|
+
private repos;
|
|
74
|
+
private adapters;
|
|
75
|
+
private mailboxRepo?;
|
|
76
|
+
init(): Promise<void>;
|
|
77
|
+
/** Resolves the caller's own `Mailbox` at most once per request, and only if actually needed - most `Sync`
|
|
78
|
+
* requests contain no client-originated `Add` at all, so most requests never pay this extra round trip. */
|
|
79
|
+
private mailboxLoader;
|
|
80
|
+
handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
|
|
81
|
+
/** Processes one `<Collection>` from the request into its own `<Collection>` response element, plus (when
|
|
82
|
+
* this round advanced anything) the `folderUid`/new `SyncKey`/remembered `Class` for `handle()` to fold
|
|
83
|
+
* into its single end-of-request `persistDeviceSyncState` call - this method itself never persists
|
|
84
|
+
* anything, so it's safe to call once per collection in a request without the write-batching hazard
|
|
85
|
+
* `EasSyncKeyUtils.persistDeviceSyncState`'s doc comment describes. */
|
|
86
|
+
private processCollection;
|
|
87
|
+
private itemToCommandElement;
|
|
88
|
+
/** One client-originated command's outcome: `response` is a `Responses/{Add,Change,Delete}` entry to
|
|
89
|
+
* include (per MS-ASCMD, always present for `Add`, only present on failure for `Change`/`Delete`);
|
|
90
|
+
* `writtenAt` is the resulting `dateModified` of whatever was actually written, used to advance the
|
|
91
|
+
* persisted watermark past this round's own writes (see `handle()`'s own comment on why). */
|
|
92
|
+
private addResponseElement;
|
|
93
|
+
private statusResponseElement;
|
|
94
|
+
private applyAdd;
|
|
95
|
+
private applyChange;
|
|
96
|
+
private applyDelete;
|
|
97
|
+
/** Builds one `<Collection>` response element - `handle()` collects one of these per request `<Collection>`
|
|
98
|
+
* and wraps the whole set in a single `<Sync><Collections>`. */
|
|
99
|
+
private collectionResponse;
|
|
100
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FolderMongo } from "@rapidmx/restapi/mongo";
|
|
2
|
+
import { FolderSyncCommand } from "../FolderSyncCommand.js";
|
|
3
|
+
/**
|
|
4
|
+
* @author Jean-Philippe Steinmetz
|
|
5
|
+
*/
|
|
6
|
+
export declare class FolderSyncCommandMongo extends FolderSyncCommand<FolderMongo> {
|
|
7
|
+
protected folderClass: any;
|
|
8
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { GetItemEstimateCommand, type EstimateCollectionBinding } from "../GetItemEstimateCommand.js";
|
|
2
|
+
/**
|
|
3
|
+
* @author Jean-Philippe Steinmetz
|
|
4
|
+
*/
|
|
5
|
+
export declare class GetItemEstimateCommandMongo extends GetItemEstimateCommand {
|
|
6
|
+
protected collectionBindings: Record<string, EstimateCollectionBinding>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ItemOperationsCommand } from "../ItemOperationsCommand.js";
|
|
2
|
+
/**
|
|
3
|
+
* @author Jean-Philippe Steinmetz
|
|
4
|
+
*/
|
|
5
|
+
export declare class ItemOperationsCommandMongo extends ItemOperationsCommand {
|
|
6
|
+
protected folderClass: any;
|
|
7
|
+
protected messageClass: any;
|
|
8
|
+
protected attachmentClass: any;
|
|
9
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MeetingResponseCommand } from "../MeetingResponseCommand.js";
|
|
2
|
+
/**
|
|
3
|
+
* @author Jean-Philippe Steinmetz
|
|
4
|
+
*/
|
|
5
|
+
export declare class MeetingResponseCommandMongo extends MeetingResponseCommand {
|
|
6
|
+
protected calendarEventClass: any;
|
|
7
|
+
protected mailboxClass: any;
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ResolveRecipientsCommand } from "../ResolveRecipientsCommand.js";
|
|
2
|
+
/**
|
|
3
|
+
* @author Jean-Philippe Steinmetz
|
|
4
|
+
*/
|
|
5
|
+
export declare class ResolveRecipientsCommandMongo extends ResolveRecipientsCommand {
|
|
6
|
+
protected contactClass: any;
|
|
7
|
+
protected likePattern(escaped: string): string;
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SyncCommand, type SyncCollectionBinding } from "../SyncCommand.js";
|
|
2
|
+
/**
|
|
3
|
+
* @author Jean-Philippe Steinmetz
|
|
4
|
+
*/
|
|
5
|
+
export declare class SyncCommandMongo extends SyncCommand {
|
|
6
|
+
protected mailboxClass: any;
|
|
7
|
+
protected collectionBindings: Record<string, SyncCollectionBinding<any>>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./FolderSyncCommandMongo.js";
|
|
2
|
+
export * from "./SyncCommandMongo.js";
|
|
3
|
+
export * from "./SendMailCommandMongo.js";
|
|
4
|
+
export * from "./SmartForwardCommandMongo.js";
|
|
5
|
+
export * from "./SmartReplyCommandMongo.js";
|
|
6
|
+
export * from "./ItemOperationsCommandMongo.js";
|
|
7
|
+
export * from "./SearchCommandMongo.js";
|
|
8
|
+
export * from "./MeetingResponseCommandMongo.js";
|
|
9
|
+
export * from "./SettingsCommandMongo.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FolderSQL } from "@rapidmx/restapi/sql";
|
|
2
|
+
import { FolderSyncCommand } from "../FolderSyncCommand.js";
|
|
3
|
+
/**
|
|
4
|
+
* @author Jean-Philippe Steinmetz
|
|
5
|
+
*/
|
|
6
|
+
export declare class FolderSyncCommandSQL extends FolderSyncCommand<FolderSQL> {
|
|
7
|
+
protected folderClass: any;
|
|
8
|
+
}
|