@rapidmx/mapi-plugin 1.0.0-beta.10
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 +60 -0
- package/dist/lib/BaseMapiEmsmdbRoute.js +489 -0
- package/dist/lib/BaseMapiEmsmdbRoute.js.map +1 -0
- package/dist/lib/BaseMapiNspiRoute.js +114 -0
- package/dist/lib/BaseMapiNspiRoute.js.map +1 -0
- package/dist/lib/MapiSessionManager.js +475 -0
- package/dist/lib/MapiSessionManager.js.map +1 -0
- package/dist/lib/RegexPatternUtils.js +30 -0
- package/dist/lib/RegexPatternUtils.js.map +1 -0
- package/dist/lib/RopDispatcher.js +145 -0
- package/dist/lib/RopDispatcher.js.map +1 -0
- package/dist/lib/codec/AppointmentRecurrence.js +315 -0
- package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
- package/dist/lib/codec/BufferCursor.js +242 -0
- package/dist/lib/codec/BufferCursor.js.map +1 -0
- package/dist/lib/codec/GlobalObjectId.js +98 -0
- package/dist/lib/codec/GlobalObjectId.js.map +1 -0
- package/dist/lib/codec/MapiGuid.js +41 -0
- package/dist/lib/codec/MapiGuid.js.map +1 -0
- package/dist/lib/codec/MapiTimeZone.js +126 -0
- package/dist/lib/codec/MapiTimeZone.js.map +1 -0
- package/dist/lib/codec/PropertyValue.js +240 -0
- package/dist/lib/codec/PropertyValue.js.map +1 -0
- package/dist/lib/codec/RopBuffer.js +38 -0
- package/dist/lib/codec/RopBuffer.js.map +1 -0
- package/dist/lib/codec/TypedString.js +54 -0
- package/dist/lib/codec/TypedString.js.map +1 -0
- package/dist/lib/index.js +99 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/mongo/MapiEmsmdbRouteMongo.js +85 -0
- package/dist/lib/mongo/MapiEmsmdbRouteMongo.js.map +1 -0
- package/dist/lib/mongo/MapiNspiRouteMongo.js +33 -0
- package/dist/lib/mongo/MapiNspiRouteMongo.js.map +1 -0
- package/dist/lib/mongo/index.js +7 -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/nspi/NspiBindHandler.js +54 -0
- package/dist/lib/nspi/NspiBindHandler.js.map +1 -0
- package/dist/lib/nspi/NspiCodec.js +106 -0
- package/dist/lib/nspi/NspiCodec.js.map +1 -0
- package/dist/lib/nspi/NspiGetMatchesHandler.js +142 -0
- package/dist/lib/nspi/NspiGetMatchesHandler.js.map +1 -0
- package/dist/lib/rop/AddressList.js +114 -0
- package/dist/lib/rop/AddressList.js.map +1 -0
- package/dist/lib/rop/CalendarEventTarget.js +30 -0
- package/dist/lib/rop/CalendarEventTarget.js.map +1 -0
- package/dist/lib/rop/CalendarNamedProperties.js +57 -0
- package/dist/lib/rop/CalendarNamedProperties.js.map +1 -0
- package/dist/lib/rop/ContactTarget.js +19 -0
- package/dist/lib/rop/ContactTarget.js.map +1 -0
- package/dist/lib/rop/ContentsTable.js +60 -0
- package/dist/lib/rop/ContentsTable.js.map +1 -0
- package/dist/lib/rop/ExecuteBudget.js +82 -0
- package/dist/lib/rop/ExecuteBudget.js.map +1 -0
- package/dist/lib/rop/FastTransferStream.js +195 -0
- package/dist/lib/rop/FastTransferStream.js.map +1 -0
- package/dist/lib/rop/FolderTarget.js +118 -0
- package/dist/lib/rop/FolderTarget.js.map +1 -0
- package/dist/lib/rop/HandleDataCache.js +412 -0
- package/dist/lib/rop/HandleDataCache.js.map +1 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js +241 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
- package/dist/lib/rop/MessageBodyStream.js +68 -0
- package/dist/lib/rop/MessageBodyStream.js.map +1 -0
- package/dist/lib/rop/MessageTarget.js +57 -0
- package/dist/lib/rop/MessageTarget.js.map +1 -0
- package/dist/lib/rop/NamedPropertyRegistry.js +69 -0
- package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
- package/dist/lib/rop/PropertyResolvers.js +353 -0
- package/dist/lib/rop/PropertyResolvers.js.map +1 -0
- package/dist/lib/rop/RepoPaging.js +54 -0
- package/dist/lib/rop/RepoPaging.js.map +1 -0
- package/dist/lib/rop/RestapiRules.js +52 -0
- package/dist/lib/rop/RestapiRules.js.map +1 -0
- package/dist/lib/rop/RopCreateMessageHandler.js +55 -0
- package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js +174 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteMessagesHandler.js +110 -0
- package/dist/lib/rop/RopDeleteMessagesHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js +61 -0
- package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js +60 -0
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +76 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js +55 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js +42 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +56 -0
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js.map +1 -0
- package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js +92 -0
- package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js.map +1 -0
- package/dist/lib/rop/RopHandler.js +6 -0
- package/dist/lib/rop/RopHandler.js.map +1 -0
- package/dist/lib/rop/RopLogonHandler.js +135 -0
- package/dist/lib/rop/RopLogonHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenFolderHandler.js +48 -0
- package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenMessageHandler.js +83 -0
- package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenStreamHandler.js +84 -0
- package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopQueryRowsHandler.js +120 -0
- package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
- package/dist/lib/rop/RopReadStreamHandler.js +73 -0
- package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopReleaseHandler.js +23 -0
- package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js +269 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopSetColumnsHandler.js +50 -0
- package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js +173 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js +342 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopWriteStreamHandler.js +72 -0
- package/dist/lib/rop/RopWriteStreamHandler.js.map +1 -0
- package/dist/lib/rop/TaskNamedProperties.js +29 -0
- package/dist/lib/rop/TaskNamedProperties.js.map +1 -0
- package/dist/lib/rop/TaskTarget.js +15 -0
- package/dist/lib/rop/TaskTarget.js.map +1 -0
- package/dist/lib/rop/TransportSend.js +16 -0
- package/dist/lib/rop/TransportSend.js.map +1 -0
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js +85 -0
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js.map +1 -0
- package/dist/lib/sql/MapiNspiRouteSQL.js +33 -0
- package/dist/lib/sql/MapiNspiRouteSQL.js.map +1 -0
- package/dist/lib/sql/index.js +7 -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/BaseMapiEmsmdbRoute.d.ts +139 -0
- package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
- package/dist/types/MapiSessionManager.d.ts +285 -0
- package/dist/types/RegexPatternUtils.d.ts +14 -0
- package/dist/types/RopDispatcher.d.ts +53 -0
- package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
- package/dist/types/codec/BufferCursor.d.ts +92 -0
- package/dist/types/codec/GlobalObjectId.d.ts +15 -0
- package/dist/types/codec/MapiGuid.d.ts +6 -0
- package/dist/types/codec/MapiTimeZone.d.ts +7 -0
- package/dist/types/codec/PropertyValue.d.ts +87 -0
- package/dist/types/codec/RopBuffer.d.ts +32 -0
- package/dist/types/codec/TypedString.d.ts +17 -0
- package/dist/types/index.d.ts +94 -0
- package/dist/types/mongo/MapiEmsmdbRouteMongo.d.ts +20 -0
- package/dist/types/mongo/MapiNspiRouteMongo.d.ts +13 -0
- package/dist/types/mongo/index.d.ts +2 -0
- package/dist/types/mongo.d.ts +1 -0
- package/dist/types/nspi/NspiBindHandler.d.ts +22 -0
- package/dist/types/nspi/NspiCodec.d.ts +59 -0
- package/dist/types/nspi/NspiGetMatchesHandler.d.ts +26 -0
- package/dist/types/rop/AddressList.d.ts +48 -0
- package/dist/types/rop/CalendarEventTarget.d.ts +33 -0
- package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
- package/dist/types/rop/ContactTarget.d.ts +29 -0
- package/dist/types/rop/ContentsTable.d.ts +16 -0
- package/dist/types/rop/ExecuteBudget.d.ts +48 -0
- package/dist/types/rop/FastTransferStream.d.ts +57 -0
- package/dist/types/rop/FolderTarget.d.ts +64 -0
- package/dist/types/rop/HandleDataCache.d.ts +145 -0
- package/dist/types/rop/MeetingMessageClassHandler.d.ts +30 -0
- package/dist/types/rop/MessageBodyStream.d.ts +35 -0
- package/dist/types/rop/MessageTarget.d.ts +45 -0
- package/dist/types/rop/NamedPropertyRegistry.d.ts +50 -0
- package/dist/types/rop/PropertyResolvers.d.ts +86 -0
- package/dist/types/rop/RepoPaging.d.ts +30 -0
- package/dist/types/rop/RestapiRules.d.ts +32 -0
- package/dist/types/rop/RopCreateMessageHandler.d.ts +24 -0
- package/dist/types/rop/RopDeleteFolderHandler.d.ts +53 -0
- package/dist/types/rop/RopDeleteMessagesHandler.d.ts +43 -0
- package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +25 -0
- package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +25 -0
- package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +22 -0
- package/dist/types/rop/RopGetContentsTableHandler.d.ts +25 -0
- package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +18 -0
- package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +26 -0
- package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
- package/dist/types/rop/RopHandler.d.ts +106 -0
- package/dist/types/rop/RopLogonHandler.d.ts +31 -0
- package/dist/types/rop/RopOpenFolderHandler.d.ts +20 -0
- package/dist/types/rop/RopOpenMessageHandler.d.ts +28 -0
- package/dist/types/rop/RopOpenStreamHandler.d.ts +28 -0
- package/dist/types/rop/RopQueryRowsHandler.d.ts +43 -0
- package/dist/types/rop/RopReadStreamHandler.d.ts +20 -0
- package/dist/types/rop/RopReleaseHandler.d.ts +15 -0
- package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +59 -0
- package/dist/types/rop/RopSetColumnsHandler.d.ts +21 -0
- package/dist/types/rop/RopSetPropertiesHandler.d.ts +38 -0
- package/dist/types/rop/RopSubmitMessageHandler.d.ts +80 -0
- package/dist/types/rop/RopWriteStreamHandler.d.ts +31 -0
- package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
- package/dist/types/rop/TaskTarget.d.ts +24 -0
- package/dist/types/rop/TransportSend.d.ts +10 -0
- package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +20 -0
- package/dist/types/sql/MapiNspiRouteSQL.d.ts +13 -0
- package/dist/types/sql/index.d.ts +2 -0
- package/dist/types/sql.d.ts +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BufferReader, BufferWriter } from "./BufferCursor.js";
|
|
2
|
+
/**
|
|
3
|
+
* `TypedString` (`[MS-OXCDATA]` §2.11.7): a leading `StringType` byte followed by an optional string in the
|
|
4
|
+
* format that byte names - used by `RopOpenMessage`'s success response (`SubjectPrefix`/`NormalizedSubject`).
|
|
5
|
+
* `writeTypedString` never produces the "reduced Unicode" (`0x03`) form - that's purely a wire-size
|
|
6
|
+
* optimization real Exchange applies when every character happens to fit in one byte, not something a peer is
|
|
7
|
+
* ever required to send, so this codec always writes plain UTF-16LE (`0x04`) for non-empty strings. `undefined`
|
|
8
|
+
* writes as "no string" (`0x00`), matching a field the spec allows to be entirely absent (e.g. no subject
|
|
9
|
+
* prefix at all, as opposed to an empty one).
|
|
10
|
+
*/
|
|
11
|
+
export declare function writeTypedString(writer: BufferWriter, value: string | undefined): void;
|
|
12
|
+
/** Reads a `TypedString`, supporting every `StringType` value a peer could legally send (`0x00`-`0x04`) even
|
|
13
|
+
* though `writeTypedString` only ever produces `0x00`/`0x01`/`0x04` itself - this side of the codec is only
|
|
14
|
+
* ever exercised in this library's own round-trip tests, since a real MAPI/HTTP client never sends a
|
|
15
|
+
* `TypedString` value to the server. Returns `undefined` for `StringType = 0x00` ("no string"), matching
|
|
16
|
+
* `writeTypedString`'s own `undefined` input. */
|
|
17
|
+
export declare function readTypedString(reader: BufferReader): string | undefined;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MAPI-over-HTTP protocol compatibility (Phase 3 of this library's roadmap): a hand-built ROP (Remote
|
|
3
|
+
* Operation) codec and buffer-framing layer (`[MS-OXCROPS]`), JWT-secured EMSMDB transport (`BaseMapiEmsmdbRoute`,
|
|
4
|
+
* reusing the framework's existing `@Auth(["jwt"])` strategy - no MAPI-specific auth code of its own), and a
|
|
5
|
+
* minimal NSPI address-book endpoint (`BaseMapiNspiRoute`) for GAL "search as you type" lookups.
|
|
6
|
+
*
|
|
7
|
+
* This module exports only the backend-agnostic surface: the wire-format codecs, the abstract route base
|
|
8
|
+
* classes, `MapiSessionManager`, the ROP dispatcher, and every ROP/NSPI handler class (each takes a generic
|
|
9
|
+
* `RopContext`/repo argument, not a concrete entity class, so none of them are backend-specific). The concrete
|
|
10
|
+
* Mongo/SQL classes a deployment actually instantiates (`MapiEmsmdbRouteMongo`/`SQL`,
|
|
11
|
+
* `MapiNspiRouteMongo`/`SQL`) are exported from this package's `./mongo`/`./sql` subpaths instead, alongside
|
|
12
|
+
* every other entity/route/job this library defines - see `src/eas/index.ts`'s identical doc comment for the
|
|
13
|
+
* same convention.
|
|
14
|
+
*
|
|
15
|
+
* A deployment mounts both MAPI endpoints with a trivial one-line subclass each, the same pattern
|
|
16
|
+
* `src/eas/index.ts`'s doc comment describes for `BaseEasRoute`:
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { MapiEmsmdbRouteMongo, MapiNspiRouteMongo } from "@rapidrest/mail/mongo";
|
|
19
|
+
* import { RouteDecorators } from "@rapidrest/service-core";
|
|
20
|
+
* const { Route } = RouteDecorators;
|
|
21
|
+
*
|
|
22
|
+
* @Route("/mapi/emsmdb")
|
|
23
|
+
* export class MyMapiEmsmdbRoute extends MapiEmsmdbRouteMongo {}
|
|
24
|
+
*
|
|
25
|
+
* @Route("/mapi/nspi")
|
|
26
|
+
* export class MyMapiNspiRoute extends MapiNspiRouteMongo {}
|
|
27
|
+
* ```
|
|
28
|
+
* Real Outlook desktop locates both URLs via `@rapidrest/mail/autodiscover`'s Outlook/EXCH response
|
|
29
|
+
* (`BaseAutodiscoverRoute`'s `mapiUrl` property) - see that module's own doc comment.
|
|
30
|
+
*
|
|
31
|
+
* **Documented gaps in this pragmatic subset** (see the architecture plan's "MAPI over HTTP (Phase 3)" section
|
|
32
|
+
* for the full reasoning behind each):
|
|
33
|
+
* - Incremental sync (`RopFastTransferSourceCopyTo`/`CopyProperties`/`GetBuffer`) always builds a full,
|
|
34
|
+
* non-differential dump, not real IDSET-based ICS (`[MS-OXCFXICS]`) - a real client still works correctly
|
|
35
|
+
* against this, just less efficiently than byte-perfect ICS.
|
|
36
|
+
* - Calendar/meetings: no counter-proposals, meeting-forwarding, delegate scheduling, or resource-booking
|
|
37
|
+
* auto-accept; no DST-aware timezones (fixed-offset approximation only); no recurrence exceptions/modified
|
|
38
|
+
* instances; no Hijri/`MonthEnd` recurrence patterns.
|
|
39
|
+
* - No `RopModifyRecipients` - attendees/recipients are always added via the same `PidTagDisplayTo`/`Cc`
|
|
40
|
+
* pragmatic path used for ordinary mail.
|
|
41
|
+
* - No public-folder support, no delegate/shared-mailbox access, no rules/permissions/search-folder ROPs, no
|
|
42
|
+
* client-certificate enrollment.
|
|
43
|
+
* - Calendar named properties (location/recurrence/timezone/etc.) are excluded from FastTransfer streams -
|
|
44
|
+
* only the columns in `FastTransferStream.ts`'s `DEFAULT_*_COLUMNS` tables are included.
|
|
45
|
+
* - NSPI is limited to `Bind`/`Unbind`/`GetMatches` only - no row/name lookups beyond a single content-
|
|
46
|
+
* restriction search, no directory replication.
|
|
47
|
+
*/
|
|
48
|
+
export * from "./codec/BufferCursor.js";
|
|
49
|
+
export * from "./codec/MapiGuid.js";
|
|
50
|
+
export * from "./codec/TypedString.js";
|
|
51
|
+
export * from "./codec/PropertyValue.js";
|
|
52
|
+
export * from "./codec/RopBuffer.js";
|
|
53
|
+
export * from "./codec/AppointmentRecurrence.js";
|
|
54
|
+
export * from "./codec/MapiTimeZone.js";
|
|
55
|
+
export * from "./codec/GlobalObjectId.js";
|
|
56
|
+
export * from "./RopDispatcher.js";
|
|
57
|
+
export * from "./MapiSessionManager.js";
|
|
58
|
+
export * from "./BaseMapiEmsmdbRoute.js";
|
|
59
|
+
export * from "./BaseMapiNspiRoute.js";
|
|
60
|
+
export * from "./rop/RopHandler.js";
|
|
61
|
+
export * from "./rop/FolderTarget.js";
|
|
62
|
+
export * from "./rop/MessageTarget.js";
|
|
63
|
+
export * from "./rop/CalendarEventTarget.js";
|
|
64
|
+
export * from "./rop/MessageBodyStream.js";
|
|
65
|
+
export * from "./rop/PropertyResolvers.js";
|
|
66
|
+
export * from "./rop/NamedPropertyRegistry.js";
|
|
67
|
+
export * from "./rop/CalendarNamedProperties.js";
|
|
68
|
+
export * from "./rop/FastTransferStream.js";
|
|
69
|
+
export * from "./rop/MeetingMessageClassHandler.js";
|
|
70
|
+
export * from "./rop/RopLogonHandler.js";
|
|
71
|
+
export * from "./rop/RopReleaseHandler.js";
|
|
72
|
+
export * from "./rop/RopOpenFolderHandler.js";
|
|
73
|
+
export * from "./rop/RopGetHierarchyTableHandler.js";
|
|
74
|
+
export * from "./rop/RopSetColumnsHandler.js";
|
|
75
|
+
export * from "./rop/RopQueryRowsHandler.js";
|
|
76
|
+
export * from "./rop/RopGetContentsTableHandler.js";
|
|
77
|
+
export * from "./rop/RopOpenMessageHandler.js";
|
|
78
|
+
export * from "./rop/RopGetPropertiesSpecificHandler.js";
|
|
79
|
+
export * from "./rop/RopOpenStreamHandler.js";
|
|
80
|
+
export * from "./rop/RopReadStreamHandler.js";
|
|
81
|
+
export * from "./rop/RopCreateMessageHandler.js";
|
|
82
|
+
export * from "./rop/RopSetPropertiesHandler.js";
|
|
83
|
+
export * from "./rop/RopWriteStreamHandler.js";
|
|
84
|
+
export * from "./rop/RopSaveChangesMessageHandler.js";
|
|
85
|
+
export * from "./rop/RopSubmitMessageHandler.js";
|
|
86
|
+
export * from "./rop/RopGetPropertyIdsFromNamesHandler.js";
|
|
87
|
+
export * from "./rop/RopDeleteMessagesHandler.js";
|
|
88
|
+
export * from "./rop/RopDeleteFolderHandler.js";
|
|
89
|
+
export * from "./rop/RopFastTransferSourceCopyToHandler.js";
|
|
90
|
+
export * from "./rop/RopFastTransferSourceCopyPropertiesHandler.js";
|
|
91
|
+
export * from "./rop/RopFastTransferSourceGetBufferHandler.js";
|
|
92
|
+
export * from "./nspi/NspiCodec.js";
|
|
93
|
+
export * from "./nspi/NspiBindHandler.js";
|
|
94
|
+
export * from "./nspi/NspiGetMatchesHandler.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { MailboxMongo } from "@rapidmx/restapi/mongo";
|
|
2
|
+
import { BaseMapiEmsmdbRoute } from "../BaseMapiEmsmdbRoute.js";
|
|
3
|
+
/**
|
|
4
|
+
* Mongo-backed concrete `BaseMapiEmsmdbRoute`, mounted at `/mapi/emsmdb`. Exported from this plugin's
|
|
5
|
+
* `./mongo` entry point, so the server host mounts it without a wrapper class of its
|
|
6
|
+
* own.
|
|
7
|
+
*
|
|
8
|
+
* @author Jean-Philippe Steinmetz
|
|
9
|
+
*/
|
|
10
|
+
export declare class MapiEmsmdbRouteMongo extends BaseMapiEmsmdbRoute<MailboxMongo> {
|
|
11
|
+
protected mailboxClass: any;
|
|
12
|
+
protected folderClass: any;
|
|
13
|
+
protected messageClass: any;
|
|
14
|
+
protected calendarEventClass: any;
|
|
15
|
+
protected contactClass: any;
|
|
16
|
+
protected taskClass: any;
|
|
17
|
+
protected labelClass: any;
|
|
18
|
+
protected auditLogClass: any;
|
|
19
|
+
protected ropHandlerClasses: any[];
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MailboxMongo } from "@rapidmx/restapi/mongo";
|
|
2
|
+
import { BaseMapiNspiRoute } from "../BaseMapiNspiRoute.js";
|
|
3
|
+
/**
|
|
4
|
+
* Mongo-backed concrete `BaseMapiNspiRoute`, mounted at `/mapi/nspi`. Exported from this plugin's
|
|
5
|
+
* `./mongo` entry point, so the server host mounts it without a wrapper class of its
|
|
6
|
+
* own.
|
|
7
|
+
*
|
|
8
|
+
* @author Jean-Philippe Steinmetz
|
|
9
|
+
*/
|
|
10
|
+
export declare class MapiNspiRouteMongo extends BaseMapiNspiRoute<MailboxMongo> {
|
|
11
|
+
protected mailboxClass: any;
|
|
12
|
+
protected contactClass: any;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./mongo/index.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { JWTUser } from "@rapidrest/core";
|
|
2
|
+
import type { HttpResponse, RepoUtils } from "@rapidrest/service-core";
|
|
3
|
+
/**
|
|
4
|
+
* `Bind`/`Unbind` request types (`[MS-OXCMAPIHTTP]` §2.2.5.1/§2.2.5.2, backed by `[MS-OXNSPI]`'s own `NspiBind`/
|
|
5
|
+
* `NspiUnbind` methods): establishes/tears down an NSPI Session Context, the address-book endpoint's own
|
|
6
|
+
* analog of EMSMDB's `Connect`/`Disconnect`.
|
|
7
|
+
*
|
|
8
|
+
* **No real session state is created or tracked** - a deliberate, documented simplification, not an oversight:
|
|
9
|
+
* every subsequent NSPI operation in this pragmatic subset (`GetMatches`) is independently `@Auth(["jwt"])`-
|
|
10
|
+
* protected and re-resolves the caller's own mailbox from that same JWT, exactly the way `RopLogon`/every ROP
|
|
11
|
+
* handler already does for EMSMDB - the real spec's `MapiContext`-equivalent session cookie exists mainly for
|
|
12
|
+
* multi-server-farm request affinity, not additional authentication, so this pragmatic subset mints an opaque,
|
|
13
|
+
* unvalidated cookie value purely for wire-format conformance and never checks it again. `HasState`/`State`
|
|
14
|
+
* (an optional `STAT` scoping the bind to one address-book container) is accepted but not decoded at all -
|
|
15
|
+
* this pragmatic subset has exactly one "container" (a mailbox's own `Contact` list) regardless.
|
|
16
|
+
*
|
|
17
|
+
* @author Jean-Philippe Steinmetz
|
|
18
|
+
*/
|
|
19
|
+
export declare function handleNspiBind(res: HttpResponse, user: JWTUser, mailboxRepo: RepoUtils<any>): Promise<void>;
|
|
20
|
+
/** `Unbind` never fails and has nothing to tear down - see `handleNspiBind`'s own doc comment for why no real
|
|
21
|
+
* session exists to release. */
|
|
22
|
+
export declare function handleNspiUnbind(res: HttpResponse): void;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
|
|
2
|
+
import { type PropertyTag, type PropertyValueData } from "../codec/PropertyValue.js";
|
|
3
|
+
/**
|
|
4
|
+
* Shared wire-format helpers for the NSPI-over-HTTP address-book endpoint (`[MS-OXCMAPIHTTP]` §2.2.5,
|
|
5
|
+
* `[MS-OXNSPI]`) - a genuinely different transport shape from EMSMDB's ROP buffers: each `X-RequestType` value
|
|
6
|
+
* (`Bind`/`Unbind`/`GetMatches`/...) has its own flat request/response body, not a multiplexed ROP stream, so
|
|
7
|
+
* there is no generic framing codec to share the way `RopBuffer.ts` does for EMSMDB - just these small
|
|
8
|
+
* structures every NSPI operation body embeds.
|
|
9
|
+
*
|
|
10
|
+
* @author Jean-Philippe Steinmetz
|
|
11
|
+
*/
|
|
12
|
+
/** `STAT` (`[MS-OXNSPI]` §2.2.8, confirmed this session): 9 `DWORD`-sized fields (36 bytes total, `Delta`
|
|
13
|
+
* signed, every other field unsigned) describing an address-book container's paging/locale state. This
|
|
14
|
+
* pragmatic subset never persists a `STAT` across calls (see `NspiGetMatchesHandler.ts`'s own doc comment for
|
|
15
|
+
* why) - `readStat`/`writeStat` exist purely to keep the surrounding request/response body's own field
|
|
16
|
+
* offsets correct, not to drive any real positioning logic. */
|
|
17
|
+
export interface Stat {
|
|
18
|
+
sortType: number;
|
|
19
|
+
containerId: number;
|
|
20
|
+
currentRec: number;
|
|
21
|
+
delta: number;
|
|
22
|
+
numPos: number;
|
|
23
|
+
totalRecs: number;
|
|
24
|
+
codePage: number;
|
|
25
|
+
templateLocale: number;
|
|
26
|
+
sortLocale: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function readStat(reader: BufferReader): Stat;
|
|
29
|
+
export declare function writeStat(writer: BufferWriter, stat: Stat): void;
|
|
30
|
+
/** A blank `STAT` (`SortType`/`ContainerID`/... all zero) - what this pragmatic subset always echoes back
|
|
31
|
+
* (in `Bind`'s optional acknowledgement and `GetMatches`' response) regardless of the caller's own request
|
|
32
|
+
* `STAT`, since no real per-container/locale state is tracked. */
|
|
33
|
+
export declare const BLANK_STAT: Stat;
|
|
34
|
+
/** `LargePropertyTagArray` (`[MS-OXCMAPIHTTP]` §2.2.1.8): a 4-byte count followed by that many `PropertyTag`
|
|
35
|
+
* structures (`[MS-OXCDATA]` §2.9 - the same `PropertyType`-then-`PropertyId` encoding `PropertyValue.ts`'s own
|
|
36
|
+
* `readPropertyTag`/`writePropertyTag` already implement). */
|
|
37
|
+
export declare function readLargePropertyTagArray(reader: BufferReader): PropertyTag[];
|
|
38
|
+
export declare function writeLargePropertyTagArray(writer: BufferWriter, tags: PropertyTag[]): void;
|
|
39
|
+
/** Writes one `AddressBookPropertyRow` (`[MS-OXCMAPIHTTP]` §2.2.1.7) - always `Flags=0x00` (every column
|
|
40
|
+
* value present without error), the exact `StandardPropertyRow`-equivalent choice `RopQueryRowsHandler`/
|
|
41
|
+
* `RopGetPropertiesSpecificHandler` already make for EMSMDB's own row encoding. */
|
|
42
|
+
export declare function writeAddressBookPropertyRow(writer: BufferWriter, columns: PropertyTag[], values: PropertyValueData[]): void;
|
|
43
|
+
/** Decodes a `Restriction` (`[MS-OXCDATA]` §2.12)'s leading `RestrictType` byte and, only for a
|
|
44
|
+
* `ContentRestriction`/`RES_CONTENT` (`0x03`) - the shape a real client's own GAL "search as you type" sends -
|
|
45
|
+
* extracts the plain string search term from its `TaggedValue` field.
|
|
46
|
+
*
|
|
47
|
+
* **Throws for any other restriction type** (`AND`/`OR`/`PropertyRestriction`/...) rather than degrading
|
|
48
|
+
* gracefully - unlike this pragmatic subset's usual "return a default instead of failing" stance elsewhere,
|
|
49
|
+
* a restriction's own byte length is type-dependent (12 different packet formats, several recursively nested),
|
|
50
|
+
* so once an unrecognized `RestrictType` is seen there is no safe way to know how many bytes to skip to reach
|
|
51
|
+
* the request body's own subsequent fields (`RowCount`/`Columns`/...) - silently returning `undefined` here
|
|
52
|
+
* would leave the reader mid-structure and corrupt every field decoded afterward. Throwing is the honest,
|
|
53
|
+
* safe choice for an input this narrow codec was never meant to parse, not a design gap.
|
|
54
|
+
*
|
|
55
|
+
* `FuzzyLevelLow`/`FuzzyLevelHigh` (exact/substring/prefix matching, case sensitivity) and the restriction's
|
|
56
|
+
* own target `PropertyTag` are decoded to advance the reader correctly but not honored - this pragmatic
|
|
57
|
+
* subset always does a case-insensitive substring match against its own fixed Contact field set regardless of
|
|
58
|
+
* which property or fuzzy level the client's restriction actually named. */
|
|
59
|
+
export declare function extractContentRestrictionSearchTerm(reader: BufferReader): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { HttpRequest, HttpResponse, RepoUtils } from "@rapidrest/service-core";
|
|
2
|
+
/** The most rows one `GetMatches` lookup asks the repo for per query - `RepoUtils.find()`'s own page cap. */
|
|
3
|
+
export declare const MAX_MATCH_ROWS = 1000;
|
|
4
|
+
/**
|
|
5
|
+
* `GetMatches` request type (`[MS-OXCMAPIHTTP]` §2.2.5.5, backed by `[MS-OXNSPI]`'s own `NspiGetMatches`
|
|
6
|
+
* method): searches the GAL (this library's `Contact` records, the same source of truth EAS's own `Search`
|
|
7
|
+
* command already uses) for entries matching the client's restriction, returning up to `RowCount` matches as
|
|
8
|
+
* `AddressBookPropertyRow`s. This is the real operation behind a client's own address-book "search as you
|
|
9
|
+
* type" UI - the closest real NSPI equivalent to this plan's own "minimal prefix/substring lookups" scope.
|
|
10
|
+
*
|
|
11
|
+
* `HasState`/`State` (an input `STAT` scoping the search and reporting position) and `HasMinimalIds`/
|
|
12
|
+
* `MinimalIds` (an "Explicit Table" restricting the search to a caller-supplied candidate set) are decoded to
|
|
13
|
+
* advance the reader correctly but not honored - this pragmatic subset always searches the caller's entire
|
|
14
|
+
* Contact list fresh on every call (no real per-container/table state, see `NspiBindHandler.ts`'s own doc
|
|
15
|
+
* comment for the same reasoning applied to `Bind`). `HasFilter`'s `Filter` is decoded via
|
|
16
|
+
* `NspiCodec.extractContentRestrictionSearchTerm` - see that function's own doc comment for why an
|
|
17
|
+
* unsupported restriction type throws rather than degrading. `HasPropertyName` (an alternate open-by-named-
|
|
18
|
+
* property addressing mode) is decoded to advance the reader correctly but never itself used to select a
|
|
19
|
+
* search field.
|
|
20
|
+
*
|
|
21
|
+
* The response's own `State` is always `BLANK_STAT` (with `TotalRecs` set to the real match count) - no real
|
|
22
|
+
* per-container `STAT` is tracked, matching this pragmatic subset's stated scope.
|
|
23
|
+
*
|
|
24
|
+
* @author Jean-Philippe Steinmetz
|
|
25
|
+
*/
|
|
26
|
+
export declare function handleNspiGetMatches(req: HttpRequest, res: HttpResponse, mailboxUid: string, contactRepo: RepoUtils<any>): Promise<void>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { RopContext } from "./RopHandler.js";
|
|
2
|
+
/** Splits a `PidTagDisplayTo`/`Cc`/`Bcc`-style string on the semicolons Outlook separates recipients with,
|
|
3
|
+
* trimming and dropping empty entries. Commas are not separators: a resolved recipient's display name commonly
|
|
4
|
+
* contains one (`Doe, Jane <jane@example.com>`). */
|
|
5
|
+
export declare function splitAddressList(value: string | undefined): string[];
|
|
6
|
+
/** `true` for a plain SMTP address (see `PLAIN_EMAIL_ADDRESS`) no longer than RFC 5321's 254 characters. These
|
|
7
|
+
* strings reach MIME headers, the SMTP envelope and iCalendar `mailto:` lines, where a CR/LF would inject content. */
|
|
8
|
+
export declare function isPlainEmailAddress(value: string): boolean;
|
|
9
|
+
/** One entry of a display string: an address with an optional display name, a display name alone (`address`
|
|
10
|
+
* undefined, still to be resolved), or something that can't be used (`invalid`). */
|
|
11
|
+
export interface RecipientEntry {
|
|
12
|
+
name: string;
|
|
13
|
+
address?: string;
|
|
14
|
+
invalid?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parses one `;`-separated entry with nodemailer's `addressparser`: `jane@example.com`, `Jane <jane@example.com>`,
|
|
18
|
+
* `"Doe, Jane" <jane@example.com>` or a bare display name `Jane Doe`. An entry holding several addresses yields one
|
|
19
|
+
* recipient each. Any address that isn't a plain SMTP address marks the entry invalid.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseRecipientEntry(entry: string): RecipientEntry[];
|
|
22
|
+
/** Every entry of a `PidTagDisplayTo`/`Cc`/`Bcc`-style string - see `parseRecipientEntry`. */
|
|
23
|
+
export declare function parseRecipientList(value: string | undefined): RecipientEntry[];
|
|
24
|
+
/** The plain addresses in a `PidTagDisplayTo`/`Cc`/`Bcc`-style string, including those written as
|
|
25
|
+
* `Name <address>`, skipping display names without an address and anything invalid. */
|
|
26
|
+
export declare function parseAddressList(value: string | undefined): string[];
|
|
27
|
+
/** A recipient ready to send to. */
|
|
28
|
+
export interface ResolvedRecipient {
|
|
29
|
+
name: string;
|
|
30
|
+
address: string;
|
|
31
|
+
}
|
|
32
|
+
/** The outcome of `resolveRecipientList`: the usable recipients, and the entries that were display names with no
|
|
33
|
+
* single matching contact (`unresolved`) or not usable at all (`invalid`). */
|
|
34
|
+
export interface RecipientResolution {
|
|
35
|
+
recipients: ResolvedRecipient[];
|
|
36
|
+
unresolved: string[];
|
|
37
|
+
invalid: string[];
|
|
38
|
+
}
|
|
39
|
+
/** The most recipients (To, Cc and Bcc together) one submitted message or meeting may have - the same limit
|
|
40
|
+
* `@rapidmx/activesync`'s `ComposeMailCommand` enforces (`MAX_COMPOSE_RECIPIENTS`). */
|
|
41
|
+
export declare const MAX_RECIPIENTS_PER_MESSAGE = 500;
|
|
42
|
+
/**
|
|
43
|
+
* Resolves a display string into addresses. An entry carrying an address is used as is; a bare display name (Outlook
|
|
44
|
+
* writes names, not addresses, into `PidTagDisplayTo` once it has resolved a recipient against an address book) is
|
|
45
|
+
* looked up among the caller's own contacts by exact display name and used when exactly one contact matches, with
|
|
46
|
+
* that contact's first valid email address.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveRecipientList(value: string | undefined, context: Pick<RopContext, "mailboxUid" | "contactRepo" | "budget">): Promise<RecipientResolution>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RepoUtils } from "@rapidrest/service-core";
|
|
2
|
+
import { Attendee, BusyStatus, RecurrenceRule } from "@rapidmx/restapi";
|
|
3
|
+
import type { ExecuteBudget } from "./ExecuteBudget.js";
|
|
4
|
+
import { type RepoSort } from "./RepoPaging.js";
|
|
5
|
+
/**
|
|
6
|
+
* The `RopGetContentsTable`/`RopOpenMessage` analog of `MessageTarget.ts`, for a `Folder` of type `CALENDAR`:
|
|
7
|
+
* resolves a `"calendarEvent:<uid>"` row/handle target into the display data a calendar-item row or an opened
|
|
8
|
+
* appointment's properties need. A `CalendarEvent` is addressed by MID exactly the same way a `Message` is -
|
|
9
|
+
* `MessageTarget.assignOrGetMid`/`session.messageIds` are reused as-is (see that file's own doc comment; the
|
|
10
|
+
* mechanism is already generic over the target-string prefix, not message-specific despite its file location),
|
|
11
|
+
* so this file adds no MID-registry code of its own.
|
|
12
|
+
*/
|
|
13
|
+
export interface CalendarEventTargetInfo {
|
|
14
|
+
title: string;
|
|
15
|
+
location?: string;
|
|
16
|
+
startDate: Date;
|
|
17
|
+
endDate: Date;
|
|
18
|
+
timezone: string;
|
|
19
|
+
busyStatus: BusyStatus;
|
|
20
|
+
recurrenceRule?: RecurrenceRule;
|
|
21
|
+
reminderMinutesBeforeStart?: number;
|
|
22
|
+
organizerAddress: string;
|
|
23
|
+
attendees: Attendee[];
|
|
24
|
+
}
|
|
25
|
+
/** Degrades to empty-looking values for a `"calendarEvent:<uid>"` target whose real `CalendarEvent` has since
|
|
26
|
+
* vanished (soft-deleted or otherwise) - the same "don't fail the whole ROP over one stale row" principle
|
|
27
|
+
* `MessageTarget.resolveMessageInfo`/`FolderTarget.resolveFolderInfo` already apply. */
|
|
28
|
+
export declare function resolveCalendarEventInfo(target: string, calendarEventRepo: RepoUtils<any>): Promise<CalendarEventTargetInfo>;
|
|
29
|
+
/** The order a calendar folder's events are listed in: latest start first, ties broken by `uid`. */
|
|
30
|
+
export declare const CALENDAR_EVENT_SORT: RepoSort;
|
|
31
|
+
/** Resolves the calendar events directly in `folderUid`, latest first and capped at `MAX_COLLECTION_ROWS`, as
|
|
32
|
+
* `"calendarEvent:<uid>"` target strings. */
|
|
33
|
+
export declare function resolveFolderCalendarEvents(folderUid: string, calendarEventRepo: RepoUtils<any>, budget?: ExecuteBudget): Promise<string[]>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { AttendeeResponseStatus, BusyStatus } from "@rapidmx/restapi";
|
|
2
|
+
/**
|
|
3
|
+
* The Calendar named-property identity table (`(PropertySet GUID, LID)` pairs) this pragmatic subset supports,
|
|
4
|
+
* per `[MS-OXPROPS]` - each pinned against its own MS-OXPROPS page (not invented), shared by `PropertyResolvers.ts`
|
|
5
|
+
* (the read side: `calendarEventValueFor`) and `RopSaveChangesMessageHandler.ts` (the write side: decoding a
|
|
6
|
+
* draft's accumulated `RopSetProperties` values back into a real `CalendarEvent`) so both directions of this
|
|
7
|
+
* mapping stay in exactly one place. See the architecture plan's "Calendar support" section for the full table.
|
|
8
|
+
*/
|
|
9
|
+
export declare const PSETID_APPOINTMENT = "00062002-0000-0000-c000-000000000046";
|
|
10
|
+
export declare const PSETID_COMMON = "00062008-0000-0000-c000-000000000046";
|
|
11
|
+
export declare const PSETID_MEETING = "6ed8da90-450b-101b-98da-00aa003f1305";
|
|
12
|
+
export declare const LID_LOCATION = 33288;
|
|
13
|
+
export declare const LID_APPOINTMENT_START_WHOLE = 33293;
|
|
14
|
+
export declare const LID_APPOINTMENT_END_WHOLE = 33294;
|
|
15
|
+
export declare const LID_BUSY_STATUS = 33285;
|
|
16
|
+
export declare const LID_RECURRING = 33315;
|
|
17
|
+
export declare const LID_REMINDER_SET = 34051;
|
|
18
|
+
export declare const LID_REMINDER_DELTA = 34049;
|
|
19
|
+
export declare const LID_RESPONSE_STATUS = 33304;
|
|
20
|
+
export declare const LID_APPOINTMENT_RECUR = 33302;
|
|
21
|
+
export declare const LID_TIME_ZONE_STRUCT = 33331;
|
|
22
|
+
/** `PidLidGlobalObjectId` (`PSETID_Meeting`) - see `GlobalObjectId.ts` for the BLOB it carries and
|
|
23
|
+
* `MeetingMessageClassHandler.ts` for how a meeting response's own copy of it is correlated back to the
|
|
24
|
+
* original `CalendarEvent`. */
|
|
25
|
+
export declare const LID_GLOBAL_OBJECT_ID = 3;
|
|
26
|
+
/** `PidLidBusyStatus`'s wire values (`[MS-OXOCAL]` §2.2.1.2, confirmed this session) - a different numbering
|
|
27
|
+
* than `CalendarSyncAdapter`'s own `BUSY_STATUS_CODES` table for the unrelated MS-ASCAL `BusyStatus` field.
|
|
28
|
+
* `olWorkingElsewhere` (0x00000004) has no equivalent in this library's own `BusyStatus` enum - never produced,
|
|
29
|
+
* and decoded (via `BUSY_STATUS_FROM_CODE`) with a fallback to `BUSY`, a documented, harmless approximation. */
|
|
30
|
+
export declare const BUSY_STATUS_CODES: Record<BusyStatus, number>;
|
|
31
|
+
export declare const BUSY_STATUS_FROM_CODE: Record<number, BusyStatus>;
|
|
32
|
+
/** `PidLidResponseStatus`'s wire values (`[MS-OXOCAL]` §2.2.1.11, confirmed this session) for the caller's own
|
|
33
|
+
* attendee record. `respOrganized` (0x00000001) is returned when the caller *is* the organizer - not a value
|
|
34
|
+
* `AttendeeResponseStatus` itself has a case for, since that enum only models an attendee's own response. */
|
|
35
|
+
export declare const RESPONSE_STATUS_CODES: Record<AttendeeResponseStatus, number>;
|
|
36
|
+
export declare const RESPONSE_STATUS_ORGANIZED = 1;
|
|
37
|
+
export declare const RESPONSE_STATUS_NONE = 0;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { RepoUtils } from "@rapidrest/service-core";
|
|
2
|
+
import type { RepoSort } from "./RepoPaging.js";
|
|
3
|
+
/**
|
|
4
|
+
* The `RopGetContentsTable`/`RopOpenMessage` analog of `MessageTarget.ts`, for a `Folder` of type `CONTACTS`:
|
|
5
|
+
* resolves a `"contact:<uid>"` row/handle target into the display data a contact-item row or an opened
|
|
6
|
+
* address-card's properties need. A `Contact` is addressed by MID exactly the same way a `Message`/
|
|
7
|
+
* `CalendarEvent` is - `MessageTarget.assignOrGetMid`/`session.messageIds` are reused as-is (see that file's own
|
|
8
|
+
* doc comment), so this file adds no MID-registry code of its own.
|
|
9
|
+
*
|
|
10
|
+
* Read-only in this pragmatic subset - a client editing/creating a Contact via MAPI (`RopSetProperties`/
|
|
11
|
+
* `RopSaveChangesMessage` against a `"contact:"` handle) is a documented gap, the same "browse only" limitation
|
|
12
|
+
* this codebase already accepts for Tasks (`TaskTarget.ts`); NSPI's own `Bind`/`Unbind`/`GetMatches` already
|
|
13
|
+
* covers the "search the GAL" case this data also backs.
|
|
14
|
+
*/
|
|
15
|
+
export interface ContactTargetInfo {
|
|
16
|
+
displayName: string;
|
|
17
|
+
givenName?: string;
|
|
18
|
+
surname?: string;
|
|
19
|
+
email?: string;
|
|
20
|
+
businessPhone?: string;
|
|
21
|
+
companyName?: string;
|
|
22
|
+
jobTitle?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Degrades to empty-looking values for a `"contact:<uid>"` target whose real `Contact` has since vanished
|
|
25
|
+
* (soft-deleted or otherwise) - the same "don't fail the whole ROP over one stale row" principle
|
|
26
|
+
* `MessageTarget.resolveMessageInfo`/`CalendarEventTarget.resolveCalendarEventInfo` already apply. */
|
|
27
|
+
export declare function resolveContactInfo(target: string, contactRepo: RepoUtils<any>): Promise<ContactTargetInfo>;
|
|
28
|
+
/** The order a `CONTACTS` folder's contents table lists contacts in (see `ContentsTable.ts`). */
|
|
29
|
+
export declare const CONTACT_SORT: RepoSort;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MapiObjectHandle } from "../MapiSessionManager.js";
|
|
2
|
+
import type { RopContext } from "./RopHandler.js";
|
|
3
|
+
export type ContentsKind = NonNullable<MapiObjectHandle["contentsKind"]>;
|
|
4
|
+
/** Which repo a folder's items live in, decided by the folder's own `type`: a `Contact`/`Task`/`CalendarEvent` is
|
|
5
|
+
* its own entity, never a `Message` row. */
|
|
6
|
+
export declare function resolveContentsKind(folderUid: string, context: RopContext): Promise<ContentsKind>;
|
|
7
|
+
/**
|
|
8
|
+
* The `"<kind>:<uid>"` row targets at positions `[start, start + count)` of a contents table (a `"folder:<uid>"`
|
|
9
|
+
* table with `contentsKind` set), read straight from the database in a fixed, stable order. A context without the
|
|
10
|
+
* matching optional repo yields no rows.
|
|
11
|
+
*
|
|
12
|
+
* Reading a window per `RopQueryRows` instead of snapshotting every row at `RopGetContentsTable` time keeps session
|
|
13
|
+
* state small and makes no folder too large to page through. The trade-off is that a message added or removed
|
|
14
|
+
* between two `RopQueryRows` calls can shift the rows that follow.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveContentsWindow(context: RopContext, table: MapiObjectHandle, start: number, count: number): Promise<string[]>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** The most rows (items or folders) whose properties one `Execute` resolves, across all of its ROPs. */
|
|
2
|
+
export declare const MAX_ROWS_RESOLVED_PER_EXECUTE = 20000;
|
|
3
|
+
/** The most bytes one `Execute` builds or parses (FastTransfer streams and message bodies), across all of its ROPs. */
|
|
4
|
+
export declare const MAX_BYTES_BUILT_PER_EXECUTE: number;
|
|
5
|
+
/** The most database queries (paged reads, table lookups, deletes) one `Execute` runs through the budgeted paths. */
|
|
6
|
+
export declare const MAX_QUERIES_PER_EXECUTE = 20000;
|
|
7
|
+
/** The most rows one `Execute` reads back from paged queries (folder lists, folder walks, item lists). */
|
|
8
|
+
export declare const MAX_ROWS_FETCHED_PER_EXECUTE = 100000;
|
|
9
|
+
/** The most `RopSubmitMessage`s one `Execute` runs. Each one can relay mail, so a request can't repeat it 1024 times. */
|
|
10
|
+
export declare const MAX_SUBMITS_PER_EXECUTE = 16;
|
|
11
|
+
/** Thrown when an `Execute` has used up its `ExecuteBudget`. The ROP that hit it fails; see `RopDispatcher`. */
|
|
12
|
+
export declare class WorkBudgetExceededError extends Error {
|
|
13
|
+
constructor(what: string);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The work one `Execute` may do. Limits on ROP count and per-ROP sizes alone still let one request chain many
|
|
17
|
+
* expensive ROPs (a dozen 10000-row FastTransfer dumps, the same large body parsed by several streams, or a thousand
|
|
18
|
+
* hierarchy tables or folder walks), so rows resolved, bytes built, queries run, rows fetched and submits are counted
|
|
19
|
+
* across the whole request. Every charge happens before the work it pays for. Also dedupes message body parses within
|
|
20
|
+
* the request (`bodies`).
|
|
21
|
+
*/
|
|
22
|
+
export declare class ExecuteBudget {
|
|
23
|
+
rowsRemaining: number;
|
|
24
|
+
bytesRemaining: number;
|
|
25
|
+
queriesRemaining: number;
|
|
26
|
+
fetchedRowsRemaining: number;
|
|
27
|
+
submitsRemaining: number;
|
|
28
|
+
/** Message uid -> its decoded body, so opening the same body twice in one request parses it once. */
|
|
29
|
+
readonly bodies: Map<string, Promise<Buffer<ArrayBufferLike>>>;
|
|
30
|
+
constructor(maxRows?: number, maxBytes?: number, limits?: {
|
|
31
|
+
maxQueries?: number;
|
|
32
|
+
maxFetchedRows?: number;
|
|
33
|
+
maxSubmits?: number;
|
|
34
|
+
});
|
|
35
|
+
/** Uses `count` rows of the budget, throwing `WorkBudgetExceededError` once it is exhausted. */
|
|
36
|
+
chargeRows(count?: number): void;
|
|
37
|
+
/** Uses `count` bytes of the budget, throwing `WorkBudgetExceededError` once it is exhausted. */
|
|
38
|
+
chargeBytes(count: number): void;
|
|
39
|
+
/** Throws `WorkBudgetExceededError` when no bytes are left, so work that can't be sized up front (fetching a blob)
|
|
40
|
+
* isn't started at all. */
|
|
41
|
+
assertBytesLeft(): void;
|
|
42
|
+
/** Uses `count` queries of the budget. Called before running them. */
|
|
43
|
+
chargeQueries(count?: number): void;
|
|
44
|
+
/** Uses `count` fetched rows of the budget. */
|
|
45
|
+
chargeFetchedRows(count: number): void;
|
|
46
|
+
/** Uses one submit of the budget. */
|
|
47
|
+
chargeSubmit(): void;
|
|
48
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { PropertyType } from "../codec/PropertyValue.js";
|
|
2
|
+
import { type MapiObjectHandle } from "../MapiSessionManager.js";
|
|
3
|
+
import { type RopContext } from "./RopHandler.js";
|
|
4
|
+
interface PropertyColumn {
|
|
5
|
+
propertyId: number;
|
|
6
|
+
propertyType: PropertyType;
|
|
7
|
+
}
|
|
8
|
+
/** The largest FastTransfer stream built for one handle; a larger source fails with `MAPI_E_TOO_BIG`. */
|
|
9
|
+
export declare const MAX_FAST_TRANSFER_BYTES: number;
|
|
10
|
+
/** How long a built stream is kept for paging out. A client pages a stream out right after opening it; one that
|
|
11
|
+
* leaves it longer than this has to open the transfer again. */
|
|
12
|
+
export declare const FAST_TRANSFER_TTL_SECONDS: number;
|
|
13
|
+
/** Thrown while building a stream that has grown past its byte limit. */
|
|
14
|
+
export declare class FastTransferTooBigError extends Error {
|
|
15
|
+
constructor();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Builds the complete FastTransfer stream for `handle` (a `"folder"` or `"message"` Server object). `columns`,
|
|
19
|
+
* when given (a `RopFastTransferSourceCopyProperties` explicit include list), replaces every default column set
|
|
20
|
+
* uniformly at both folder- and message-level; `excludePropertyIds` (a `RopFastTransferSourceCopyTo` exclude
|
|
21
|
+
* list) is only ever applied to this pragmatic subset's own default columns, per the same reasoning.
|
|
22
|
+
*
|
|
23
|
+
* The size is checked after every item, so a huge folder stops being built as soon as it passes `maxBytes`
|
|
24
|
+
* (`FastTransferTooBigError`) instead of being fully built first. Every item resolved and the finished stream's bytes
|
|
25
|
+
* count against the request's `ExecuteBudget` when there is one.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildFastTransferStream(handle: MapiObjectHandle, context: RopContext, options?: {
|
|
28
|
+
columns?: PropertyColumn[];
|
|
29
|
+
excludePropertyIds?: ReadonlySet<number>;
|
|
30
|
+
maxBytes?: number;
|
|
31
|
+
}): Promise<Buffer>;
|
|
32
|
+
/** Why `loadFastTransferBuffer` has no stream: `"tooBig"` when a rebuild passed `MAX_FAST_TRANSFER_BYTES`, `"lost"`
|
|
33
|
+
* when the stored stream is gone part-way through paging. */
|
|
34
|
+
export type FastTransferLoadFailure = "tooBig" | "lost";
|
|
35
|
+
/**
|
|
36
|
+
* The built stream for the `"fastTransfer"` handle at `handleIndex`, from the shared `HandleDataStore` (Redis when
|
|
37
|
+
* configured, so any replica can page it out).
|
|
38
|
+
*
|
|
39
|
+
* When the stream is gone (expired, evicted, or built by a replica without shared storage), it is rebuilt only if
|
|
40
|
+
* nothing has been paged out yet. Part-way through, a rebuild could differ from what the client already has (items
|
|
41
|
+
* added or changed since), and continuing at the old offset would hand it bytes from a different stream, so that
|
|
42
|
+
* case reports `"lost"` and the client restarts the transfer. A rebuild is held to `MAX_FAST_TRANSFER_BYTES` like
|
|
43
|
+
* the original build, and storing it to the session's and user's handle data quota (`handleDataOwners`); either
|
|
44
|
+
* failing reports `"tooBig"`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function loadFastTransferBuffer(context: RopContext, handleIndex: number, transfer: MapiObjectHandle): Promise<Buffer | FastTransferLoadFailure>;
|
|
47
|
+
/**
|
|
48
|
+
* Shared by `RopFastTransferSourceCopyTo`/`CopyProperties`: builds the stream for `source`, and on success stores a
|
|
49
|
+
* `"fastTransfer"` handle at `outputHandleIndex` with the stream in the `HandleDataStore`. Returns `false`, storing
|
|
50
|
+
* nothing, when the stream exceeds `MAX_FAST_TRANSFER_BYTES` or the session's or user's handle data quota
|
|
51
|
+
* (`handleDataOwners`) has no room for it.
|
|
52
|
+
*/
|
|
53
|
+
export declare function openFastTransferHandle(context: RopContext, outputHandleIndex: number, source: MapiObjectHandle, options: {
|
|
54
|
+
columns?: PropertyColumn[];
|
|
55
|
+
excludePropertyIds?: number[];
|
|
56
|
+
}): Promise<boolean>;
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { RepoUtils } from "@rapidrest/service-core";
|
|
2
|
+
import { Folder } from "@rapidmx/restapi";
|
|
3
|
+
import type { MapiSessionContext } from "../MapiSessionManager.js";
|
|
4
|
+
import type { ExecuteBudget } from "./ExecuteBudget.js";
|
|
5
|
+
export interface FolderTargetInfo {
|
|
6
|
+
displayName: string;
|
|
7
|
+
unreadCount: number;
|
|
8
|
+
totalCount: number;
|
|
9
|
+
hasChildren: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** An optional, purely request-scoped (never persisted to the session) memo for `resolveFolderChildren`'s own
|
|
12
|
+
* full-mailbox `folderRepo.find({ mailboxUid })` fetch - see that function's own doc comment for why this
|
|
13
|
+
* matters. Deliberately just a plain object a caller creates fresh per ROP-handler invocation and threads
|
|
14
|
+
* through every `resolveFolderInfo`/`resolvePropertyValues` call in that same batch, not session state - unlike
|
|
15
|
+
* the FID/MID/named-property registries, there's no cross-request reuse to gain here (a mailbox's folder list
|
|
16
|
+
* can change between requests), only cross-*row* reuse within one table page. */
|
|
17
|
+
export interface FolderResolutionCache {
|
|
18
|
+
allFolders?: Folder[];
|
|
19
|
+
}
|
|
20
|
+
/** Resolves a target string into the display data a folder-table row needs. A `"folder:<uid>"` target whose
|
|
21
|
+
* `Folder` has since been deleted (soft-deleted or otherwise vanished) degrades to empty-looking values rather
|
|
22
|
+
* than throwing - the row simply won't be interesting to a client, not a reason to fail the whole ROP. */
|
|
23
|
+
export declare function resolveFolderInfo(mailboxUid: string, target: string, folderRepo: RepoUtils<any>, cache?: FolderResolutionCache, budget?: ExecuteBudget): Promise<FolderTargetInfo>;
|
|
24
|
+
/**
|
|
25
|
+
* Resolves the direct children of `target`, as an array of the same target-string format, for
|
|
26
|
+
* `RopGetHierarchyTable`. Only `"virtual:root"`/`"virtual:ipmSubtree"` (this mailbox's top-level real
|
|
27
|
+
* folders - both collapse to the same "top of the visible tree" concept in this pragmatic subset, see
|
|
28
|
+
* `RopLogonHandler`'s own doc comment) and a real folder (its own real children) have any children at all;
|
|
29
|
+
* every other virtual folder (Deferred Action, Spooler Queue, ...) is permanently empty, since this data
|
|
30
|
+
* model has no concept of nesting anything under them.
|
|
31
|
+
*
|
|
32
|
+
* Filters an already-fetched full folder list in application code rather than querying by
|
|
33
|
+
* `parentFolderUid: undefined` directly - query-DSL semantics for "field is unset" aren't reliably consistent
|
|
34
|
+
* across backends (the exact kind of gap this project's own testing philosophy has caught before, e.g. `$or`
|
|
35
|
+
* being Mongo-only), so filtering a fetched array sidesteps the question entirely rather than risking it.
|
|
36
|
+
* The comparison itself uses `== null` (matching both `null` and `undefined`), not `=== undefined` - a
|
|
37
|
+
* top-level folder's unset `parentFolderUid` round-trips as genuine `undefined` from Mongo but as `null` from
|
|
38
|
+
* a SQL `nullable` column, a real, previously-confirmed cross-backend discrepancy in this codebase (caught by
|
|
39
|
+
* this exact test against a real SQLite-backed server, not assumed).
|
|
40
|
+
*
|
|
41
|
+
* **`cache`**: without it, this fetches every folder in the mailbox on every call - fine for the single call
|
|
42
|
+
* `RopGetHierarchyTableHandler` makes to build a table, but `resolveFolderInfo` also calls this once per row
|
|
43
|
+
* just to compute `hasChildren`, and a table page can hold many rows. A caller resolving a whole page (
|
|
44
|
+
* `RopQueryRowsHandler`/`RopGetPropertiesSpecificHandler`) passes one shared cache object through every call in
|
|
45
|
+
* that batch so the full list is fetched at most once per batch instead of once per row - see
|
|
46
|
+
* `FolderResolutionCache`'s own doc comment for why this isn't session state.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveFolderChildren(mailboxUid: string, target: string, folderRepo: RepoUtils<any>, cache?: FolderResolutionCache, budget?: ExecuteBudget): Promise<string[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Returns `target`'s existing FID if `RopLogon` or an earlier `RopGetHierarchyTable` row already assigned
|
|
51
|
+
* one, otherwise assigns and remembers the next free small integer FID. This is what lets a client
|
|
52
|
+
* `RopOpenFolder` a child folder it only ever learned about via a `RopQueryRows` row's `PidTagFolderId`
|
|
53
|
+
* column - without this, only the 13 `RopLogon`-time special folders could ever be opened.
|
|
54
|
+
*
|
|
55
|
+
* Backed by `session.folderTargetIds` (target -> FID) and `session.nextFolderId`, an O(1) reverse index/counter
|
|
56
|
+
* pair rather than a linear scan of `session.folderIds` plus a `Math.max(...spread)` over its keys - both real
|
|
57
|
+
* costs, not just theoretical ones: `folderTargetIds` only ever grows for a session's lifetime as a client
|
|
58
|
+
* browses more of a mailbox's folder tree, so the old scan-per-lookup approach was quadratic across a session
|
|
59
|
+
* that opens many folders, and `Math.max` spreading an unbounded array as call arguments risked a stack
|
|
60
|
+
* overflow past V8's argument-count limit on a mailbox with enough folders. `RopLogonHandler` keeps
|
|
61
|
+
* `folderTargetIds`/`nextFolderId` in sync with `folderIds` for the 13 special folders it assigns directly,
|
|
62
|
+
* rather than going through this function, since it assigns all 13 as one batch up front.
|
|
63
|
+
*/
|
|
64
|
+
export declare function assignOrGetFid(session: MapiSessionContext, target: string): number;
|