@rapidmx/activesync-plugin 1.0.0-beta.2 → 1.0.0-beta.3

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 (201) hide show
  1. package/README.md +4 -4
  2. package/dist/lib/BaseDeviceSyncStateRoute.js +32 -0
  3. package/dist/lib/BaseDeviceSyncStateRoute.js.map +1 -1
  4. package/dist/lib/BaseEasRoute.js +91 -15
  5. package/dist/lib/BaseEasRoute.js.map +1 -1
  6. package/dist/lib/EasAuditLog.js +56 -0
  7. package/dist/lib/EasAuditLog.js.map +1 -0
  8. package/dist/lib/EasCollectionLease.js +199 -0
  9. package/dist/lib/EasCollectionLease.js.map +1 -0
  10. package/dist/lib/EasCollectionStore.js +102 -0
  11. package/dist/lib/EasCollectionStore.js.map +1 -0
  12. package/dist/lib/EasCollectionSync.js +294 -0
  13. package/dist/lib/EasCollectionSync.js.map +1 -0
  14. package/dist/lib/EasSyncKeyUtils.js +125 -62
  15. package/dist/lib/EasSyncKeyUtils.js.map +1 -1
  16. package/dist/lib/MessageMoveRules.js +100 -0
  17. package/dist/lib/MessageMoveRules.js.map +1 -0
  18. package/dist/lib/MimeHeaderUtils.js +395 -0
  19. package/dist/lib/MimeHeaderUtils.js.map +1 -0
  20. package/dist/lib/RegexPatternUtils.js +30 -0
  21. package/dist/lib/RegexPatternUtils.js.map +1 -0
  22. package/dist/lib/RestapiCompat.js +41 -0
  23. package/dist/lib/RestapiCompat.js.map +1 -0
  24. package/dist/lib/adapters/CalendarSyncAdapter.js +142 -33
  25. package/dist/lib/adapters/CalendarSyncAdapter.js.map +1 -1
  26. package/dist/lib/adapters/EmailSyncAdapter.js +114 -27
  27. package/dist/lib/adapters/EmailSyncAdapter.js.map +1 -1
  28. package/dist/lib/adapters/mongo/EmailSyncAdapterMongo.js +2 -1
  29. package/dist/lib/adapters/mongo/EmailSyncAdapterMongo.js.map +1 -1
  30. package/dist/lib/adapters/sql/EmailSyncAdapterSQL.js +2 -1
  31. package/dist/lib/adapters/sql/EmailSyncAdapterSQL.js.map +1 -1
  32. package/dist/lib/codec/WbxmlDecoder.js +86 -34
  33. package/dist/lib/codec/WbxmlDecoder.js.map +1 -1
  34. package/dist/lib/codec/WbxmlEncoder.js +103 -22
  35. package/dist/lib/codec/WbxmlEncoder.js.map +1 -1
  36. package/dist/lib/commands/ComposeMailCommand.js +105 -31
  37. package/dist/lib/commands/ComposeMailCommand.js.map +1 -1
  38. package/dist/lib/commands/FolderSyncCommand.js +67 -27
  39. package/dist/lib/commands/FolderSyncCommand.js.map +1 -1
  40. package/dist/lib/commands/GetItemEstimateCommand.js +80 -59
  41. package/dist/lib/commands/GetItemEstimateCommand.js.map +1 -1
  42. package/dist/lib/commands/ItemOperationsCommand.js +219 -56
  43. package/dist/lib/commands/ItemOperationsCommand.js.map +1 -1
  44. package/dist/lib/commands/MeetingResponseCommand.js +212 -53
  45. package/dist/lib/commands/MeetingResponseCommand.js.map +1 -1
  46. package/dist/lib/commands/MoveItemsCommand.js +54 -21
  47. package/dist/lib/commands/MoveItemsCommand.js.map +1 -1
  48. package/dist/lib/commands/PingCommand.js +288 -56
  49. package/dist/lib/commands/PingCommand.js.map +1 -1
  50. package/dist/lib/commands/ProvisionCommand.js +34 -17
  51. package/dist/lib/commands/ProvisionCommand.js.map +1 -1
  52. package/dist/lib/commands/ResolveRecipientsCommand.js +43 -5
  53. package/dist/lib/commands/ResolveRecipientsCommand.js.map +1 -1
  54. package/dist/lib/commands/SearchCommand.js +92 -17
  55. package/dist/lib/commands/SearchCommand.js.map +1 -1
  56. package/dist/lib/commands/SettingsCommand.js +2 -1
  57. package/dist/lib/commands/SettingsCommand.js.map +1 -1
  58. package/dist/lib/commands/SmartForwardCommand.js +2 -1
  59. package/dist/lib/commands/SmartForwardCommand.js.map +1 -1
  60. package/dist/lib/commands/SmartReplyCommand.js +2 -1
  61. package/dist/lib/commands/SmartReplyCommand.js.map +1 -1
  62. package/dist/lib/commands/SyncCommand.js +481 -244
  63. package/dist/lib/commands/SyncCommand.js.map +1 -1
  64. package/dist/lib/commands/mongo/GetItemEstimateCommandMongo.js +6 -1
  65. package/dist/lib/commands/mongo/GetItemEstimateCommandMongo.js.map +1 -1
  66. package/dist/lib/commands/mongo/ItemOperationsCommandMongo.js +3 -1
  67. package/dist/lib/commands/mongo/ItemOperationsCommandMongo.js.map +1 -1
  68. package/dist/lib/commands/mongo/MeetingResponseCommandMongo.js +2 -1
  69. package/dist/lib/commands/mongo/MeetingResponseCommandMongo.js.map +1 -1
  70. package/dist/lib/commands/mongo/PingCommandMongo.js +23 -0
  71. package/dist/lib/commands/mongo/PingCommandMongo.js.map +1 -0
  72. package/dist/lib/commands/mongo/SearchCommandMongo.js +3 -1
  73. package/dist/lib/commands/mongo/SearchCommandMongo.js.map +1 -1
  74. package/dist/lib/commands/mongo/SendMailCommandMongo.js +2 -1
  75. package/dist/lib/commands/mongo/SendMailCommandMongo.js.map +1 -1
  76. package/dist/lib/commands/mongo/SmartForwardCommandMongo.js +2 -1
  77. package/dist/lib/commands/mongo/SmartForwardCommandMongo.js.map +1 -1
  78. package/dist/lib/commands/mongo/SmartReplyCommandMongo.js +2 -1
  79. package/dist/lib/commands/mongo/SmartReplyCommandMongo.js.map +1 -1
  80. package/dist/lib/commands/mongo/SyncCommandMongo.js +7 -1
  81. package/dist/lib/commands/mongo/SyncCommandMongo.js.map +1 -1
  82. package/dist/lib/commands/mongo/index.js +1 -0
  83. package/dist/lib/commands/mongo/index.js.map +1 -1
  84. package/dist/lib/commands/sql/GetItemEstimateCommandSQL.js +6 -1
  85. package/dist/lib/commands/sql/GetItemEstimateCommandSQL.js.map +1 -1
  86. package/dist/lib/commands/sql/ItemOperationsCommandSQL.js +3 -1
  87. package/dist/lib/commands/sql/ItemOperationsCommandSQL.js.map +1 -1
  88. package/dist/lib/commands/sql/MeetingResponseCommandSQL.js +2 -1
  89. package/dist/lib/commands/sql/MeetingResponseCommandSQL.js.map +1 -1
  90. package/dist/lib/commands/sql/PingCommandSQL.js +23 -0
  91. package/dist/lib/commands/sql/PingCommandSQL.js.map +1 -0
  92. package/dist/lib/commands/sql/SearchCommandSQL.js +3 -1
  93. package/dist/lib/commands/sql/SearchCommandSQL.js.map +1 -1
  94. package/dist/lib/commands/sql/SendMailCommandSQL.js +2 -1
  95. package/dist/lib/commands/sql/SendMailCommandSQL.js.map +1 -1
  96. package/dist/lib/commands/sql/SmartForwardCommandSQL.js +2 -1
  97. package/dist/lib/commands/sql/SmartForwardCommandSQL.js.map +1 -1
  98. package/dist/lib/commands/sql/SmartReplyCommandSQL.js +2 -1
  99. package/dist/lib/commands/sql/SmartReplyCommandSQL.js.map +1 -1
  100. package/dist/lib/commands/sql/SyncCommandSQL.js +7 -1
  101. package/dist/lib/commands/sql/SyncCommandSQL.js.map +1 -1
  102. package/dist/lib/commands/sql/index.js +1 -0
  103. package/dist/lib/commands/sql/index.js.map +1 -1
  104. package/dist/lib/index.js +5 -0
  105. package/dist/lib/index.js.map +1 -1
  106. package/dist/lib/jobs/EasDeviceStateCleanupJob.js +44 -2
  107. package/dist/lib/jobs/EasDeviceStateCleanupJob.js.map +1 -1
  108. package/dist/lib/jobs/mongo/EasDeviceStateCleanupJobMongo.js +4 -0
  109. package/dist/lib/jobs/mongo/EasDeviceStateCleanupJobMongo.js.map +1 -1
  110. package/dist/lib/jobs/sql/EasDeviceStateCleanupJobSQL.js +9 -0
  111. package/dist/lib/jobs/sql/EasDeviceStateCleanupJobSQL.js.map +1 -1
  112. package/dist/lib/models/EasCollectionChunk.js +2 -0
  113. package/dist/lib/models/EasCollectionChunk.js.map +1 -0
  114. package/dist/lib/models/EasCollectionState.js +2 -0
  115. package/dist/lib/models/EasCollectionState.js.map +1 -0
  116. package/dist/lib/models/mongo/DeviceSyncStateMongo.js +7 -0
  117. package/dist/lib/models/mongo/DeviceSyncStateMongo.js.map +1 -1
  118. package/dist/lib/models/mongo/EasCollectionChunkMongo.js +83 -0
  119. package/dist/lib/models/mongo/EasCollectionChunkMongo.js.map +1 -0
  120. package/dist/lib/models/mongo/EasCollectionStateMongo.js +162 -0
  121. package/dist/lib/models/mongo/EasCollectionStateMongo.js.map +1 -0
  122. package/dist/lib/models/sql/DeviceSyncStateSQL.js +7 -0
  123. package/dist/lib/models/sql/DeviceSyncStateSQL.js.map +1 -1
  124. package/dist/lib/models/sql/EasCollectionChunkSQL.js +83 -0
  125. package/dist/lib/models/sql/EasCollectionChunkSQL.js.map +1 -0
  126. package/dist/lib/models/sql/EasCollectionStateSQL.js +162 -0
  127. package/dist/lib/models/sql/EasCollectionStateSQL.js.map +1 -0
  128. package/dist/lib/mongo/EasRouteMongo.js +2 -2
  129. package/dist/lib/mongo/EasRouteMongo.js.map +1 -1
  130. package/dist/lib/mongo.js +2 -0
  131. package/dist/lib/mongo.js.map +1 -1
  132. package/dist/lib/sql/EasRouteSQL.js +2 -2
  133. package/dist/lib/sql/EasRouteSQL.js.map +1 -1
  134. package/dist/lib/sql.js +2 -0
  135. package/dist/lib/sql.js.map +1 -1
  136. package/dist/types/BaseDeviceSyncStateRoute.d.ts +10 -0
  137. package/dist/types/BaseEasRoute.d.ts +15 -4
  138. package/dist/types/EasAuditLog.d.ts +47 -0
  139. package/dist/types/EasCollectionLease.d.ts +60 -0
  140. package/dist/types/EasCollectionStore.d.ts +51 -0
  141. package/dist/types/EasCollectionSync.d.ts +96 -0
  142. package/dist/types/EasCommandHandler.d.ts +7 -4
  143. package/dist/types/EasSyncKeyUtils.d.ts +73 -42
  144. package/dist/types/MessageMoveRules.d.ts +45 -0
  145. package/dist/types/MimeHeaderUtils.d.ts +73 -0
  146. package/dist/types/RegexPatternUtils.d.ts +14 -0
  147. package/dist/types/RestapiCompat.d.ts +24 -0
  148. package/dist/types/adapters/CalendarSyncAdapter.d.ts +47 -18
  149. package/dist/types/adapters/EasCollectionSyncAdapter.d.ts +18 -2
  150. package/dist/types/adapters/EmailSyncAdapter.d.ts +24 -7
  151. package/dist/types/adapters/mongo/EmailSyncAdapterMongo.d.ts +1 -0
  152. package/dist/types/adapters/sql/EmailSyncAdapterSQL.d.ts +1 -0
  153. package/dist/types/codec/WbxmlDecoder.d.ts +39 -0
  154. package/dist/types/codec/WbxmlEncoder.d.ts +36 -1
  155. package/dist/types/commands/ComposeMailCommand.d.ts +33 -15
  156. package/dist/types/commands/FolderSyncCommand.d.ts +4 -1
  157. package/dist/types/commands/GetItemEstimateCommand.d.ts +20 -15
  158. package/dist/types/commands/ItemOperationsCommand.d.ts +29 -4
  159. package/dist/types/commands/MeetingResponseCommand.d.ts +52 -13
  160. package/dist/types/commands/MoveItemsCommand.d.ts +13 -3
  161. package/dist/types/commands/PingCommand.d.ts +72 -14
  162. package/dist/types/commands/ProvisionCommand.d.ts +16 -11
  163. package/dist/types/commands/ResolveRecipientsCommand.d.ts +4 -0
  164. package/dist/types/commands/SearchCommand.d.ts +13 -1
  165. package/dist/types/commands/SyncCommand.d.ts +102 -62
  166. package/dist/types/commands/mongo/GetItemEstimateCommandMongo.d.ts +3 -0
  167. package/dist/types/commands/mongo/ItemOperationsCommandMongo.d.ts +2 -0
  168. package/dist/types/commands/mongo/MeetingResponseCommandMongo.d.ts +1 -0
  169. package/dist/types/commands/mongo/PingCommandMongo.d.ts +8 -0
  170. package/dist/types/commands/mongo/SearchCommandMongo.d.ts +2 -0
  171. package/dist/types/commands/mongo/SendMailCommandMongo.d.ts +1 -0
  172. package/dist/types/commands/mongo/SmartForwardCommandMongo.d.ts +1 -0
  173. package/dist/types/commands/mongo/SmartReplyCommandMongo.d.ts +1 -0
  174. package/dist/types/commands/mongo/SyncCommandMongo.d.ts +4 -0
  175. package/dist/types/commands/mongo/index.d.ts +1 -0
  176. package/dist/types/commands/sql/GetItemEstimateCommandSQL.d.ts +3 -0
  177. package/dist/types/commands/sql/ItemOperationsCommandSQL.d.ts +2 -0
  178. package/dist/types/commands/sql/MeetingResponseCommandSQL.d.ts +1 -0
  179. package/dist/types/commands/sql/PingCommandSQL.d.ts +8 -0
  180. package/dist/types/commands/sql/SearchCommandSQL.d.ts +2 -0
  181. package/dist/types/commands/sql/SendMailCommandSQL.d.ts +1 -0
  182. package/dist/types/commands/sql/SmartForwardCommandSQL.d.ts +1 -0
  183. package/dist/types/commands/sql/SmartReplyCommandSQL.d.ts +1 -0
  184. package/dist/types/commands/sql/SyncCommandSQL.d.ts +4 -0
  185. package/dist/types/commands/sql/index.d.ts +1 -0
  186. package/dist/types/index.d.ts +5 -0
  187. package/dist/types/jobs/EasDeviceStateCleanupJob.d.ts +15 -0
  188. package/dist/types/jobs/mongo/EasDeviceStateCleanupJobMongo.d.ts +2 -0
  189. package/dist/types/jobs/sql/EasDeviceStateCleanupJobSQL.d.ts +4 -0
  190. package/dist/types/models/DeviceSyncState.d.ts +13 -2
  191. package/dist/types/models/EasCollectionChunk.d.ts +20 -0
  192. package/dist/types/models/EasCollectionState.d.ts +79 -0
  193. package/dist/types/models/mongo/DeviceSyncStateMongo.d.ts +1 -0
  194. package/dist/types/models/mongo/EasCollectionChunkMongo.d.ts +16 -0
  195. package/dist/types/models/mongo/EasCollectionStateMongo.d.ts +27 -0
  196. package/dist/types/models/sql/DeviceSyncStateSQL.d.ts +1 -0
  197. package/dist/types/models/sql/EasCollectionChunkSQL.d.ts +16 -0
  198. package/dist/types/models/sql/EasCollectionStateSQL.d.ts +27 -0
  199. package/dist/types/mongo.d.ts +2 -0
  200. package/dist/types/sql.d.ts +2 -0
  201. package/package.json +267 -238
@@ -13,7 +13,12 @@ import { type Mailbox, type Message } from "@rapidmx/restapi";
13
13
  * the only `Email` write EAS itself allows) - a plain-text-only pragmatic subset: no HTML body, no attachments
14
14
  * (mirrors `ComposeMailCommand`'s own already-documented attachment gap). `To`/`Cc`/`Bcc` (the latter MS-ASEMAIL2's
15
15
  * own `Bcc` tag) are ghosted independently per recipient type, not as one combined group - a `Change` touching
16
- * only one of them leaves the others untouched, carried over from `existing.recipients`.
16
+ * only one of them leaves the others untouched, carried over from `existing.recipients`. A new Draft always gets a body
17
+ * blob (empty when no `Body` was sent), so `ItemOperations` can fetch it like any other message.
18
+ *
19
+ * `Flag` is the MS-ASEMAIL container form in both directions: `<Flag><Status>2</Status></Flag>` (tokenized as
20
+ * `FlagStatus`) for a flagged message, an empty `<Flag/>` otherwise; `Status` 0/1 (cleared/complete) or an empty
21
+ * `Flag` from the device clears `flags.flagged`.
17
22
  *
18
23
  * Emits MS-ASEMAIL2's `Email2:ConversationId` (read-only - no `fromApplicationData` handling, since EAS itself
19
24
  * never lets a client set it) whenever `Message.conversationId` is populated, so a device's threaded-view UI can
@@ -27,23 +32,31 @@ import { type Mailbox, type Message } from "@rapidmx/restapi";
27
32
  * already takes for a stale search-index entry. Deliberately not writable: unlike `Contact.categories` (a
28
33
  * plain free-form string array with no separate entity behind it), a `Label` is a real mailbox-scoped entity
29
34
  * referenced by uid - a write path would need to resolve category name strings back to `Label`s and create new
30
- * ones on the fly for names that don't exist yet, real added scope this pragmatic subset defers. One `find()`
31
- * per message that actually has labels (most won't, since labels are opt-in) - not batched across a whole Sync
32
- * page/search result set, a documented, modest N+1 tradeoff rather than widening this adapter's own interface
33
- * further to let a caller pre-resolve names for a whole batch.
35
+ * ones on the fly for names that don't exist yet, real added scope this pragmatic subset defers. Callers
36
+ * rendering a whole Sync page/search result set use `toApplicationDataBatch()`, which resolves every referenced
37
+ * label with one `in(...)` query per mailbox rather than one per labelled message.
34
38
  *
35
- * `labelClass` is supplied by the Mongo/SQL concrete subclasses.
39
+ * `labelClass`/`folderClass` are supplied by the Mongo/SQL concrete subclasses.
36
40
  *
37
41
  * @author Jean-Philippe Steinmetz
38
42
  */
39
43
  export declare abstract class EmailSyncAdapter implements EasCollectionSyncAdapter<Message> {
40
44
  readonly collectionClass = "Email";
41
45
  protected abstract labelClass: any;
46
+ protected abstract folderClass: any;
42
47
  private _objectFactory?;
43
48
  private labelRepo?;
49
+ private folderRepo?;
44
50
  private blobStore?;
45
51
  init(): Promise<void>;
46
52
  toApplicationData(message: Message): Promise<WbxmlElement>;
53
+ /** Renders a whole page of messages, resolving every referenced `Label` with one `find()` per distinct
54
+ * mailbox (in practice one per page) instead of one per labelled message. */
55
+ toApplicationDataBatch(messages: Message[]): Promise<WbxmlElement[]>;
56
+ private render;
57
+ /** Resolves every distinct `labelUids` entry across `messages` to its `Label.name`, keyed by `labelKey()`.
58
+ * Labels are mailbox-scoped, so uids are grouped per `mailboxUid` and each group is fetched with a single
59
+ * `in(...)` query (chunked to stay within `RepoUtils`' page size). A stale uid simply has no entry. */
47
60
  private resolveLabelNames;
48
61
  /**
49
62
  * `Message.bodyBlobKey` is documented (see the `Message` interface itself) as holding raw MIME "unmodified
@@ -53,7 +66,11 @@ export declare abstract class EmailSyncAdapter implements EasCollectionSyncAdapt
53
66
  * for every consumer, not just this write path - a Draft created/edited via `Sync` must `Fetch` correctly
54
67
  * the same way any other message does.
55
68
  */
56
- fromApplicationData(el: WbxmlElement, existing?: Message): Promise<Partial<Message>>;
69
+ fromApplicationData(el: WbxmlElement, existing?: Message, mailbox?: Mailbox): Promise<Partial<Message>>;
70
+ /** Whether `message` is a genuine draft (`MessageMoveRules.isGenuineDraft`): in its mailbox's Drafts folder, and
71
+ * never delivered. Moving any other message into Drafts is refused over ActiveSync, so its body can't be rewritten
72
+ * here by first moving it there. */
73
+ private isDraft;
57
74
  /** Defaults for a brand-new Draft created via a client-originated `Add` - `from` is the caller's own
58
75
  * mailbox address, per `ComposeMailCommand`'s identical `{ address, type: RecipientType.TO }` shape
59
76
  * convention for a `from` field (the `Recipient` struct's `type` is only meaningful for real recipients;
@@ -4,4 +4,5 @@ import { EmailSyncAdapter } from "../EmailSyncAdapter.js";
4
4
  */
5
5
  export declare class EmailSyncAdapterMongo extends EmailSyncAdapter {
6
6
  protected labelClass: any;
7
+ protected folderClass: any;
7
8
  }
@@ -4,4 +4,5 @@ import { EmailSyncAdapter } from "../EmailSyncAdapter.js";
4
4
  */
5
5
  export declare class EmailSyncAdapterSQL extends EmailSyncAdapter {
6
6
  protected labelClass: any;
7
+ protected folderClass: any;
7
8
  }
@@ -1,9 +1,41 @@
1
1
  import type { WbxmlElement } from "./WbxmlElement.js";
2
+ /** Default cap on the total number of elements decoded from one document. An element costs as little as one
3
+ * wire byte (an empty tag token), so without this a small body could allocate millions of element objects. */
4
+ export declare const WBXML_DEFAULT_MAX_ELEMENTS = 50000;
5
+ /** Default cap on the number of direct children of any single element. */
6
+ export declare const WBXML_DEFAULT_MAX_CHILDREN_PER_ELEMENT = 10000;
7
+ /** Default cap on element nesting depth (the root is depth 1). Nesting costs ~2 wire bytes per level and drives
8
+ * the decoder's recursion, so it needs its own bound; real EAS documents stay well under 20 levels. */
9
+ export declare const WBXML_DEFAULT_MAX_DEPTH = 64;
10
+ /** Limits applied by `WbxmlDecoder`. Every value must be a positive integer; omitted values use the defaults. */
11
+ export interface WbxmlDecoderOptions {
12
+ /** Maximum total elements in the document. Default `WBXML_DEFAULT_MAX_ELEMENTS`. */
13
+ maxElements?: number;
14
+ /** Maximum direct children of any one element. Default `WBXML_DEFAULT_MAX_CHILDREN_PER_ELEMENT`. */
15
+ maxChildrenPerElement?: number;
16
+ /** Maximum nesting depth (root = 1). Default `WBXML_DEFAULT_MAX_DEPTH`. */
17
+ maxDepth?: number;
18
+ }
19
+ /** Thrown by `WbxmlDecoder.decode()` for malformed or unsupported input (truncated buffer, unterminated string,
20
+ * attribute-flagged tag, a length field running past the end of the buffer, ...). */
21
+ export declare class WbxmlDecodeError extends Error {
22
+ constructor(message: string);
23
+ }
24
+ /** Thrown by `WbxmlDecoder.decode()` when a document exceeds a configured resource limit (element count,
25
+ * children per element, or nesting depth). A subclass of `WbxmlDecodeError`, so callers that treat every
26
+ * decoder failure as a bad request can catch just the base class. */
27
+ export declare class WbxmlLimitError extends WbxmlDecodeError {
28
+ constructor(message: string);
29
+ }
2
30
  /**
3
31
  * Decodes a WBXML byte stream (an EAS request/response body) back into a `WbxmlElement` tree — the exact
4
32
  * inverse of `WbxmlEncoder`. Reads the fixed EAS document header, skips its (always-empty, in real
5
33
  * ActiveSync traffic) string table, then parses the single root element.
6
34
  *
35
+ * Input is untrusted, so decoding is bounded: total elements, children per element and nesting depth are
36
+ * capped (see `WbxmlDecoderOptions`), throwing `WbxmlLimitError` when exceeded; all other malformed input throws
37
+ * `WbxmlDecodeError`. Text and opaque payloads are bounded by the input buffer itself.
38
+ *
7
39
  * The `publicid` header field is read via the same generic `mb_u_int32` reader used everywhere else, which is
8
40
  * only a partial implementation of the full WBXML spec for that field (a raw leading `0x00` byte would
9
41
  * signal "public identifier is a string-table reference" under the full spec, a form real ActiveSync traffic
@@ -13,12 +45,19 @@ import type { WbxmlElement } from "./WbxmlElement.js";
13
45
  * @author Jean-Philippe Steinmetz
14
46
  */
15
47
  export declare class WbxmlDecoder {
48
+ private readonly maxElements;
49
+ private readonly maxChildrenPerElement;
50
+ private readonly maxDepth;
16
51
  private buf;
17
52
  private pos;
18
53
  private currentPage;
19
54
  private depth;
55
+ private elementCount;
56
+ constructor(options?: WbxmlDecoderOptions);
20
57
  decode(data: Buffer): WbxmlElement;
21
58
  private readByte;
59
+ /** Throws unless at least `length` bytes remain from the current position. */
60
+ private requireAvailable;
22
61
  /** Decodes a WBXML `mb_u_int32`: base-128 digits, most significant group first, every byte but the last
23
62
  * carrying the 0x80 continuation bit. Mirrors `WbxmlEncoder.writeMbUint()`. */
24
63
  private readMbUint;
@@ -1,17 +1,52 @@
1
1
  import type { WbxmlElement } from "./WbxmlElement.js";
2
+ /** Token bytes are buffered in a small scratch array and flushed into a `Buffer` chunk once it reaches this
3
+ * many bytes (or before a text/opaque payload is appended as its own chunk). */
4
+ export declare const WBXML_ENCODER_SCRATCH_SIZE = 4096;
5
+ /** Options for `WbxmlEncoder`. */
6
+ export interface WbxmlEncoderOptions {
7
+ /** Maximum size in bytes of an encoded document (header included). Must be a positive integer or
8
+ * `Infinity`; default `Infinity` (no limit). Exceeding it throws `WbxmlSizeLimitError`. */
9
+ maxBytes?: number;
10
+ }
11
+ /** Thrown by `WbxmlEncoder.encode()` when the encoded document would exceed the configured `maxBytes`. */
12
+ export declare class WbxmlSizeLimitError extends Error {
13
+ readonly maxBytes: number;
14
+ constructor(maxBytes: number);
15
+ }
16
+ /** Returns the exact wire size of an `OPAQUE` token carrying `payloadLength` bytes (token + length + payload),
17
+ * so callers can check a large payload against a size budget before building the element. */
18
+ export declare function wbxmlOpaqueSize(payloadLength: number): number;
2
19
  /**
3
20
  * Encodes a `WbxmlElement` tree into a WBXML byte stream, per MS-ASWBXML's encoding algorithm. Emits the
4
21
  * fixed EAS document header (`version=1.3, publicid=unknown, charset=UTF-8, empty string table`) followed by
5
22
  * the token stream for the given root element, switching code pages only when the page actually changes from
6
23
  * whatever was last active (starting from `AirSync`, page 0, the implicit default per spec).
7
24
  *
25
+ * Output is accumulated as a list of `Buffer` chunks (token bytes batched in a small scratch array, text/opaque
26
+ * payloads appended whole) and joined once at the end, so large payloads are never copied byte-by-byte. An
27
+ * optional `maxBytes` bounds the output size, throwing `WbxmlSizeLimitError` as soon as it would be exceeded.
28
+ *
8
29
  * @author Jean-Philippe Steinmetz
9
30
  */
10
31
  export declare class WbxmlEncoder {
11
- private bytes;
32
+ private readonly maxBytes;
33
+ private chunks;
34
+ private scratch;
35
+ private size;
12
36
  private currentPage;
37
+ constructor(options?: WbxmlEncoderOptions);
13
38
  encode(root: WbxmlElement): Buffer;
39
+ /** Accounts for `length` more output bytes, throwing before anything is appended if that would exceed
40
+ * `maxBytes`. */
41
+ private reserve;
42
+ private writeByte;
43
+ /** Appends a payload as its own chunk, after flushing any pending token bytes to preserve ordering. */
44
+ private writeBuffer;
45
+ private flush;
14
46
  private writeElement;
47
+ /** A NUL byte terminates a WBXML inline string, so any U+0000 in `text` (e.g. a label name or subject
48
+ * controlled by another user) would end the string early and let the remaining bytes be parsed as tokens.
49
+ * UTF-8 never produces a 0x00 byte for any other code point, so stripping U+0000 is sufficient. */
15
50
  private writeStrI;
16
51
  private writeOpaque;
17
52
  /** Encodes `value` as a WBXML multi-byte unsigned integer (`mb_u_int32`): base-128 digits, most
@@ -1,6 +1,11 @@
1
+ import { RepoUtils } from "@rapidrest/service-core";
1
2
  import { type Message, RecoverableRepoUtils } from "@rapidmx/restapi";
2
3
  import { type WbxmlElement } from "../codec/WbxmlElement.js";
4
+ import { stripHeader } from "../MimeHeaderUtils.js";
3
5
  import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
6
+ /** Most envelope recipients (To + Cc + Bcc) one composed message may carry. */
7
+ export declare const MAX_COMPOSE_RECIPIENTS = 500;
8
+ export { stripHeader };
4
9
  /**
5
10
  * Shared implementation for EAS `SendMail`, `SmartForward`, and `SmartReply` (MS-ASCMD `ComposeMail` namespace)
6
11
  * — all three submit a client-composed raw MIME body directly (`<Mime>`, opaque WBXML content) rather than
@@ -8,23 +13,33 @@ import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.
8
13
  * `BaseMessageRoute.send()`, which this class's `scanAndRelay()` call shares its scan-then-relay core with via
9
14
  * `MailSendUtils.ts`).
10
15
  *
16
+ * **Sender and envelope checks** (the MIME is entirely device-controlled): the raw bytes pass restapi's own originator
17
+ * rules before anything parses them (`MimeHeaderUtils.checkComposedOriginators`: an inline copy of restapi's
18
+ * `checkOriginatorHeaders` with `rejectAddressLikeDisplayNames`): exactly one `From`, at most one `Sender` (found by a tolerant lexer - `From :`, folded lines and bare-CR
19
+ * line breaks included), every address in them - group members too - the caller's own mailbox's primary or alias
20
+ * address, no address-like text outside an address (`<me@x> <victim@y>`), and no address in a display name or comment
21
+ * (`"ceo@y" <me@x>`, `me@x (victim@y)`), and - stricter than restapi - no empty group (`victims:;, me@x`). Anything
22
+ * else is HTTP 403 (a MIME with no `From` at all is HTTP 400), so a device can't send as, or appear to be, anyone else;
23
+ * the envelope sender is the validated `From`. A message with no To/Cc/Bcc address is answered with the command's own
24
+ * `Status` 119 (MessageHasNoRecipient), nothing relayed. The envelope is capped at `MAX_COMPOSE_RECIPIENTS`
25
+ * recipients (HTTP 400). `Bcc` recipients are delivered via the envelope, but every `Bcc` header field (found by the
26
+ * same lexer, so `Bcc :` too) is stripped from the relayed copy so other recipients never see it (the Sent Items copy
27
+ * keeps it). The Sent Items copy records the relay's own `Message-ID`/`conversationId` (bounded like restapi's), so
28
+ * recall and threading match what recipients received.
29
+ *
11
30
  * **Pragmatic subset, deliberately not the full MS-ASCMD semantics**:
12
31
  * - `SmartForward`/`SmartReply`'s `<Source>` (the message being forwarded/replied to) is used only to thread
13
32
  * the outgoing message (`inReplyTo`/`references`) and to flip the original's `Answered`/`Forwarded` flag - the
14
- * real spec has the *server* splice the original message's full content into the outgoing MIME so the client
15
- * never has to download-then-reupload it; this pragmatic subset instead expects the client's own `<Mime>` to
16
- * already be the complete outgoing message (which is what every mainstream client's own compose UI naturally
17
- * produces once it has fetched the original for display), matching this library's "pragmatic subset, not full
18
- * fidelity" precedent elsewhere (e.g. `FolderSyncCommand`'s SyncKey replay-protection gap).
33
+ * real spec has the *server* splice the original message's full content into the outgoing MIME; this subset
34
+ * expects the client's own `<Mime>` to already be the complete outgoing message. The flag flip needs `UPDATE` on
35
+ * the original's folder and is best-effort: the message has already been sent, so a denied or conflicting flag
36
+ * update is logged, never turned into a failed request.
19
37
  * - `ReplaceMime`/`AccountId`/`InstanceId` are not read - single-account, non-recurring-meeting compose only.
20
- * - Attachments present in the composed MIME are relayed correctly (`scanAndRelay()`'s `ScanPipeline` handles
21
- * the full raw message) but are not additionally persisted as `Attachment` records on the saved Sent Items
22
- * copy - `Message.hasAttachments` is still set correctly from the parsed MIME, just not each attachment's own
23
- * row (deferred, matching `ItemOperationsCommand`'s own future `Fetch`-of-Sent-Items scope).
38
+ * - Attachments present in the composed MIME are relayed correctly but are not additionally persisted as
39
+ * `Attachment` records on the saved Sent Items copy (`Message.hasAttachments` is still set).
24
40
  *
25
- * `folderClass`/`messageClass` are supplied by the Mongo/SQL concrete subclasses, and `markOriginal()` by the
26
- * `SmartForwardCommand`/`SmartReplyCommand` subclasses (a no-op here, since plain `SendMailCommand` never has a
27
- * `<Source>` to act on).
41
+ * `folderClass`/`messageClass`/`mailboxClass` are supplied by the Mongo/SQL concrete subclasses, and
42
+ * `markOriginal()` by the `SmartForwardCommand`/`SmartReplyCommand` subclasses.
28
43
  *
29
44
  * @author Jean-Philippe Steinmetz
30
45
  */
@@ -32,17 +47,20 @@ export declare abstract class ComposeMailCommand implements EasCommandHandler {
32
47
  abstract readonly command: string;
33
48
  protected abstract folderClass: any;
34
49
  protected abstract messageClass: any;
50
+ protected abstract mailboxClass: any;
35
51
  private _objectFactory?;
36
52
  protected folderRepo?: RecoverableRepoUtils<any>;
37
53
  protected messageRepo?: RecoverableRepoUtils<any>;
54
+ protected mailboxRepo?: RepoUtils<any>;
38
55
  private blobStore?;
39
56
  private mailTransport?;
40
57
  private scanPipeline?;
41
58
  private aclUtils?;
59
+ private logger;
42
60
  init(): Promise<void>;
43
- /** Called once the outgoing message has been sent, only when the request carried a `<Source>` (i.e. this
44
- * is a `SmartForward`/`SmartReply`, never a plain `SendMail`) - flips the referenced original message's own
45
- * `Answered`/`Forwarded` flag. A no-op here; overridden by the two subclasses that need it. */
61
+ /** Called once the outgoing message has been sent, only when the request carried a `<Source>` the caller may
62
+ * update - flips the referenced original message's own `Answered`/`Forwarded` flag. A no-op here; overridden by
63
+ * the two subclasses that need it. */
46
64
  protected markOriginal(_ctx: EasCommandContext, _original: Message & {
47
65
  uid: string;
48
66
  }): Promise<void>;
@@ -7,6 +7,10 @@ import { Folder } from "@rapidmx/restapi";
7
7
  * `EasSyncKeyUtils.ts` (scoped by `mailboxUid` over the `Folder` collection, rather than `folderUid` over a
8
8
  * per-folder item collection the way `SyncCommand` will be).
9
9
  *
10
+ * **Keys**: an unknown `SyncKey` is answered with Status 9 ([MS-ASCMD] FolderSync: "synchronization key mismatch or
11
+ * invalid synchronization key"; the client restarts from `0`). The key issued before the current one is still
12
+ * accepted: the key itself encodes its cursor, so a retry simply recomputes that round's changes.
13
+ *
10
14
  * `folderClass` is supplied by the Mongo/SQL concrete subclasses, following the exact one-line-per-backend
11
15
  * pattern used throughout this library's other routes/jobs.
12
16
  *
@@ -21,5 +25,4 @@ export declare abstract class FolderSyncCommand<F extends Folder> implements Eas
21
25
  init(): Promise<void>;
22
26
  handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
23
27
  private folderToChangeElement;
24
- private persistSyncKey;
25
28
  }
@@ -1,8 +1,7 @@
1
1
  import { type WbxmlElement } from "../codec/WbxmlElement.js";
2
2
  import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
3
- /** Binds one MS-ASCMD `Class` value to the concrete entity class this command counts against - a lighter
4
- * version of `SyncCommand`'s own `SyncCollectionBinding` (no adapter needed at all, since `GetItemEstimate`
5
- * never serializes an item, only counts them). Supplied by the Mongo/SQL concrete subclasses. */
3
+ /** Binds one MS-ASCMD `Class` value to the concrete entity class this command counts against. Supplied by the
4
+ * Mongo/SQL concrete subclasses. */
6
5
  export interface EstimateCollectionBinding {
7
6
  entityClass: any;
8
7
  }
@@ -12,32 +11,38 @@ export interface EstimateCollectionBinding {
12
11
  * consumes a `SyncKey` itself.
13
12
  *
14
13
  * The modern (14.0+) request/response reuses `WbxmlCodePage.AirSync`'s own `Collections`/`Collection`/`Class`/
15
- * `CollectionId`/`SyncKey` via `SWITCH_PAGE` rather than this page's own legacy (`Folders`/`Folder`/`FolderId`)
16
- * shape - see `WbxmlCodePages.ts`'s own doc comment on `WbxmlCodePage.ItemEstimate` for why, and
17
- * `ItemOperationsCommand.fetchMessage`'s identical cross-page-reuse precedent.
14
+ * `CollectionId`/`SyncKey` via `SWITCH_PAGE` rather than this page's own legacy (`Folders`/`Folder`/`FolderId`) shape.
18
15
  *
19
- * A `SyncKey` of `"0"` (or one this device has never synced this folder with before) reports the folder's
20
- * total live item count - what a first `Sync` would report as `Add`s. Otherwise reuses `EasSyncKeyUtils.
21
- * computeChanges()` (the same enumeration `SyncCommand` itself uses), capped at `DEFAULT_MAX_COUNT` - see its
22
- * own doc comment for why this is a real, documented approximation on a very active folder rather than a
23
- * precise unbounded count.
16
+ * The collection's `EasCollectionState` (the same per-device row `SyncCommand` keeps) decides the estimate: a
17
+ * `SyncKey` of `"0"` reports the folder's total live item count (what the first `Sync` would `Add`); the
18
+ * collection's current `SyncKey` runs `SyncCommand`'s own enumeration as a dry run (nothing persisted), capped at
19
+ * `mail:eas:item_estimate_max_count`; any other key is Status 2. A request without `Class` falls back to the class
20
+ * remembered for the collection, then to the folder's type.
24
21
  *
25
- * **ACL-checked like `Sync`**: `estimateCollection()` requires `ACLAction.READ` on the client-supplied
26
- * `CollectionId` before counting anything - without it, a crafted `CollectionId` belonging to another
27
- * mailbox's folder would return a real pending-change count for it. A denied folder is reported identically to
28
- * an unrecognized collection (`Status 2`), never distinguishable from "you don't have this collection at all".
22
+ * **Bounded like `Sync`**: at most `MAX_SYNC_COLLECTIONS` collections per request (more is a single Status 2), a
23
+ * `CollectionId` repeated within one request is estimated once, and a `SyncKey 0` count is capped at the same maximum.
24
+ *
25
+ * **ACL-checked like `Sync`**: `READ` on the client-supplied `CollectionId` is required before counting anything;
26
+ * a denied folder is reported identically to an unrecognized collection (`Status 2`).
29
27
  *
30
28
  * @author Jean-Philippe Steinmetz
31
29
  */
32
30
  export declare abstract class GetItemEstimateCommand implements EasCommandHandler {
33
31
  readonly command = "GetItemEstimate";
34
32
  protected abstract collectionBindings: Record<string, EstimateCollectionBinding>;
33
+ protected abstract folderClass: any;
34
+ protected abstract collectionStateClass: any;
35
+ protected abstract collectionChunkClass: any;
35
36
  private maxCount;
36
37
  private _objectFactory?;
37
38
  private aclUtils?;
38
39
  private repos;
40
+ private folderRepo?;
41
+ private collectionStateRepo?;
42
+ private collectionChunkRepo?;
39
43
  init(): Promise<void>;
40
44
  handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
45
+ private statusResponse;
41
46
  private estimateCollection;
42
47
  private collectionElement;
43
48
  }
@@ -1,5 +1,7 @@
1
1
  import { type WbxmlElement } from "../codec/WbxmlElement.js";
2
2
  import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
3
+ /** Most batches (of `mail:eas:itemoperations_batch_size` messages) one `EmptyFolderContents` deletes per request. */
4
+ export declare const MAX_EMPTY_FOLDER_BATCHES = 20;
3
5
  /**
4
6
  * Handles EAS `ItemOperations`: `Fetch` (a `Message`'s full body or an `Attachment`'s binary content, by the
5
7
  * same `ServerId`/uid this library already exposes elsewhere) and `EmptyFolderContents`.
@@ -16,7 +18,10 @@ import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.
16
18
  * `MoveItems` command's per-message `SrcFldId`/`SrcMsgId`/`DstFldId` shape. Every `Message` sharing the
17
19
  * decoded `conversationId` across the whole mailbox (not just one folder) that the caller has `UPDATE` on is
18
20
  * 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
21
+ * conversation can legitimately span folders the caller doesn't control, e.g. a shared mailbox's Inbox). The
22
+ * `ConversationId` is looked up bounded and exact-matched in memory (it derives from sender-controlled headers), and
23
+ * each message's move follows `MessageMoveRules.planMessageMove` (never into Outbox, into Drafts only for drafts, out of
24
+ * Outbox cancels the scheduled send) - a refused message counts as failed. An
20
25
  * optional `MoveAlways` (a hint to keep auto-moving future messages in this conversation) is accepted but not
21
26
  * acted on - this library's `MailFilterRule` has no conversation-scoped condition to key an ongoing rule off
22
27
  * of, a documented simplification, not silent data loss (the move itself still happens).
@@ -28,15 +33,29 @@ import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.
28
33
  * command's original single-`Fetch` design, not a new gap introduced by adding multi-`Fetch` support.
29
34
  * - Only the "inline" delivery method is used (content embedded directly in the WBXML response) - the real
30
35
  * 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.
36
+ * the combined size of the bodies/attachments embedded in one response is capped at
37
+ * `mail:eas:itemoperations_max_response_bytes` (default 64 MB) - a Fetch that would exceed it gets Status 11
38
+ * ("data too large") instead of content. Attachment access is checked against the owning message's *current*
39
+ * folder (`Attachment.folderUid` is not updated when a message moves).
33
40
  * - `Options/BodyPreference`'s `Type`/`TruncationSize` are honored for a `Message` body fetch (plain text,
34
41
  * HTML, or - `Type 4` - the raw MIME source verbatim); byte-range fetching (`Range`) is not implemented.
35
42
  * - `EmptyFolderContents`'s `DeleteSubFolders` option is rejected outright rather than silently ignored -
36
43
  * recursive subfolder deletion is out of scope for this pragmatic subset; emptying a single folder's own
37
44
  * `Message`s is the common case this implements.
38
45
  *
39
- * `folderClass`/`messageClass`/`attachmentClass` are supplied by the Mongo/SQL concrete subclasses.
46
+ * **Bulk operations are bounded and per item**: `EmptyFolderContents` deletes at most `MAX_EMPTY_FOLDER_BATCHES`
47
+ * batches per request, and `Move` at most one batch; a message that fails to delete or move (e.g. a concurrent
48
+ * edit's version conflict) is skipped rather than failing the request, and the operation then reports Status 17
49
+ * (partial success) - or Status 3 when nothing at all succeeded - so the client can retry for the rest.
50
+ * `EmptyFolderContents` skips (as failed) a message whose send is in flight (`MessageMoveRules.hasLiveSendLease`).
51
+ *
52
+ * **Audit**: in a mailbox the caller doesn't own (restapi's `isNonOwnerAccess()` - an administrator or a delegate), a
53
+ * `Fetch` that returns a body or an attachment records one `MESSAGE_CONTENT_ACCESSED` entry per item, and
54
+ * `EmptyFolderContents` records one `MESSAGE_DELETE` entry per delete batch, listing the deleted message uids
55
+ * (`EasAuditLog`).
56
+ *
57
+ * `folderClass`/`messageClass`/`attachmentClass`/`mailboxClass`/`auditLogClass` are supplied by the Mongo/SQL concrete
58
+ * subclasses.
40
59
  *
41
60
  * @author Jean-Philippe Steinmetz
42
61
  */
@@ -45,13 +64,19 @@ export declare abstract class ItemOperationsCommand implements EasCommandHandler
45
64
  protected abstract folderClass: any;
46
65
  protected abstract messageClass: any;
47
66
  protected abstract attachmentClass: any;
67
+ protected abstract mailboxClass: any;
68
+ protected abstract auditLogClass: any;
48
69
  private _objectFactory?;
70
+ private config?;
71
+ private logger;
49
72
  private folderRepo?;
73
+ private mailboxRepo?;
50
74
  private messageRepo?;
51
75
  private attachmentRepo?;
52
76
  private blobStore?;
53
77
  private aclUtils?;
54
78
  private maxFetchesPerRequest;
79
+ private maxResponseBytes;
55
80
  private batchSize;
56
81
  init(): Promise<void>;
57
82
  handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
@@ -1,21 +1,43 @@
1
1
  import { type WbxmlElement } from "../codec/WbxmlElement.js";
2
2
  import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
3
+ /** Most `Request` elements one `MeetingResponse` may carry. */
4
+ export declare const MAX_MEETING_RESPONSES = 100;
3
5
  /**
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.
6
+ * Handles EAS `MeetingResponse`: records the caller's own accept/tentative/decline response to each meeting the
7
+ * request's `<Request>` elements name, each answered with its own `<Result>`.
7
8
  *
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.
9
+ * **`RequestId`** is either the `CalendarEvent.uid` (a response from the calendar) or the `Message.uid` of the
10
+ * meeting request in the Inbox (a response from the mail view - what most clients send). For a message, the
11
+ * caller needs `READ` on its folder; its `text/calendar` part's `UID` is then resolved to the caller's own copy of
12
+ * the event (`icalUid` within the caller's mailbox) - looked up bounded (`boundIndexedValue`, as restapi stores it) and
13
+ * exact-matched in memory, since the UID is sender-controlled text a query parser could read as an operator.
17
14
  *
18
- * `calendarEventClass`/`mailboxClass` are supplied by the Mongo/SQL concrete subclasses.
15
+ * **Someone else's calendar**: whether a copy is the organizer's is judged against the event's own mailbox, not the
16
+ * caller's. A response to an event in another mailbox (a delegate with `UPDATE` on it) is refused with Status 2 when
17
+ * that event is its owner's organizer copy - it can only ever be an attendee copy. Updates are version-checked on both
18
+ * backends (`asEntity`).
19
+ *
20
+ * **Effect**: the caller must be an attendee and have `UPDATE` on the event's folder. Accept/Tentative update
21
+ * the caller's `Attendee.responseStatus`. A decline removes the caller's own copy of the event (matching Exchange)
22
+ * when the caller also has `DELETE` on the folder, and otherwise just records the declined status - a delegate
23
+ * with edit-only rights can't delete. Each mailbox has its own event row, so neither touches the organizer's or
24
+ * any other attendee's copy. `CalendarId` is omitted for a removed event. Since the caller's copy is an attendee's copy
25
+ * of someone else's meeting, neither write may look like the organizer acting to restapi's `MeetingSchedulingJob`:
26
+ * a removed copy is stamped `cancelNoticeSentAt` first (no CANCEL mailed as the organizer), and an updated copy keeps
27
+ * `inviteSequenceSent` equal to its `sequence` (no REQUEST).
28
+ *
29
+ * **Reply to the organizer**: in protocol 16.x the client asks the server to notify the organizer with
30
+ * `SendResponse`; when it's present an iTIP `REPLY` (built with restapi's `buildEventIcs`, the same payload
31
+ * `BaseCalendarEventRoute.respond` sends) is mailed from the caller's attendee address to the organizer,
32
+ * and a transport that accepts none of it (or rejects the organizer) is reported as Status 4 - the response itself is
33
+ * already recorded, so a retry only re-sends the reply. Without it (14.x clients send their own reply via `SendMail`) nothing is mailed, so the organizer
34
+ * never receives two replies.
35
+ *
36
+ * Failures are per request: an unknown/unresolvable meeting, a meeting the caller may not respond to, or a
37
+ * malformed `Request` gets Status 2 (indistinguishable, so nothing about other mailboxes leaks); a failed write
38
+ * Status 3.
39
+ *
40
+ * `calendarEventClass`/`mailboxClass`/`messageClass` are supplied by the Mongo/SQL concrete subclasses.
19
41
  *
20
42
  * @author Jean-Philippe Steinmetz
21
43
  */
@@ -23,10 +45,27 @@ export declare abstract class MeetingResponseCommand implements EasCommandHandle
23
45
  readonly command = "MeetingResponse";
24
46
  protected abstract calendarEventClass: any;
25
47
  protected abstract mailboxClass: any;
48
+ protected abstract messageClass: any;
26
49
  private _objectFactory?;
27
50
  private calendarEventRepo?;
28
51
  private mailboxRepo?;
52
+ private messageRepo?;
29
53
  private aclUtils?;
54
+ private blobStore?;
55
+ private mailTransport?;
56
+ private logger;
30
57
  init(): Promise<void>;
31
58
  handle(ctx: EasCommandContext): Promise<WbxmlElement | undefined>;
59
+ private result;
60
+ private respond;
61
+ /** Resolves `requestId` to the caller's event: an event uid directly, or a meeting request message's
62
+ * `text/calendar` `UID` looked up among the caller's own mailbox's events. */
63
+ private resolveEvent;
64
+ /** The iCalendar `UID` of a meeting request message's `text/calendar` part, if it has one. */
65
+ private meetingUidOf;
66
+ /** Mails an iTIP `REPLY` for `attendee`'s response to the event's organizer. Returns `false` (after logging) when
67
+ * the transport threw or didn't accept the message for the organizer - checked the way restapi's `sendOrThrow`
68
+ * does, since every bundled transport reports a relay failure through `TransportResult.rejected` rather than
69
+ * throwing. */
70
+ private sendReply;
32
71
  }
@@ -1,13 +1,23 @@
1
1
  import { type WbxmlElement } from "../codec/WbxmlElement.js";
2
2
  import type { EasCommandContext, EasCommandHandler } from "../EasCommandHandler.js";
3
+ /** Most `Move` elements one `MoveItems` request may carry - more is rejected with HTTP 400. */
4
+ export declare const MAX_MOVES_PER_REQUEST = 500;
3
5
  /**
4
6
  * Handles the standalone EAS `MoveItems` command: moves one or more `Message`s between folders in the caller's
5
- * own mailbox by `ServerId`/`uid`.
7
+ * own mailbox by `ServerId`/`uid`. A move never crosses mailboxes (the destination folder must belong to the
8
+ * message's own mailbox) and a request may carry at most `MAX_MOVES_PER_REQUEST` moves.
9
+ *
10
+ * Each move gets its own [MS-ASCMD] status: `3` success; `1` an unknown/unreadable message, or a `SrcFldId` that
11
+ * isn't where it lives (or can't be updated); `2` an unknown destination, one in another mailbox, one the caller
12
+ * can't create in, Outbox, or Drafts for a message that isn't already a draft (`MessageMoveRules.planMessageMove`);
13
+ * `4` source and destination are the same folder; `7` the update itself failed (e.g. a concurrent edit's version
14
+ * conflict - the update is version-checked on both backends), a send in flight, or the message already left Outbox as sent - one failing
15
+ * move never aborts the others. Moving a message out of Outbox cancels its scheduled send.
6
16
  *
7
17
  * **Pragmatic subset**: `Message` only - `Contacts`/`Calendar`/`Tasks` moves are rare in practice (unlike
8
18
  * `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
19
+ * ACL/ownership verification path for comparatively little value; a client attempting one gets Status 1 (its
20
+ * `SrcMsgId` isn't a message) rather than being silently ignored. `DstMsgId` in the response is always the same `uid` as `SrcMsgId` - this
11
21
  * library never mints a new identifier on move, unlike a real Exchange server, which sometimes does.
12
22
  *
13
23
  * `messageClass`/`folderClass` are supplied by the Mongo/SQL concrete subclasses.