@rapidmx/mapi-plugin 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.
- package/LICENSE +382 -0
- package/README.md +60 -0
- package/dist/lib/BaseMapiEmsmdbRoute.js +297 -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 +119 -0
- package/dist/lib/MapiSessionManager.js.map +1 -0
- package/dist/lib/RopDispatcher.js +34 -0
- package/dist/lib/RopDispatcher.js.map +1 -0
- package/dist/lib/codec/AppointmentRecurrence.js +314 -0
- package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
- package/dist/lib/codec/BufferCursor.js +213 -0
- package/dist/lib/codec/BufferCursor.js.map +1 -0
- package/dist/lib/codec/GlobalObjectId.js +92 -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 +93 -0
- package/dist/lib/codec/MapiTimeZone.js.map +1 -0
- package/dist/lib/codec/PropertyValue.js +214 -0
- package/dist/lib/codec/PropertyValue.js.map +1 -0
- package/dist/lib/codec/RopBuffer.js +26 -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 +84 -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/CalendarEventTarget.js +27 -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 +23 -0
- package/dist/lib/rop/ContactTarget.js.map +1 -0
- package/dist/lib/rop/FastTransferStream.js +98 -0
- package/dist/lib/rop/FastTransferStream.js.map +1 -0
- package/dist/lib/rop/FolderTarget.js +111 -0
- package/dist/lib/rop/FolderTarget.js.map +1 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js +95 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
- package/dist/lib/rop/MessageBodyStream.js +42 -0
- package/dist/lib/rop/MessageBodyStream.js.map +1 -0
- package/dist/lib/rop/MessageTarget.js +44 -0
- package/dist/lib/rop/MessageTarget.js.map +1 -0
- package/dist/lib/rop/NamedPropertyRegistry.js +66 -0
- package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
- package/dist/lib/rop/PropertyResolvers.js +313 -0
- package/dist/lib/rop/PropertyResolvers.js.map +1 -0
- package/dist/lib/rop/RopCreateMessageHandler.js +53 -0
- package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js +133 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteMessagesHandler.js +70 -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 +61 -0
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +75 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js +76 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js +40 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +55 -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 +2 -0
- package/dist/lib/rop/RopHandler.js.map +1 -0
- package/dist/lib/rop/RopLogonHandler.js +132 -0
- package/dist/lib/rop/RopLogonHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenFolderHandler.js +46 -0
- package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenMessageHandler.js +81 -0
- package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenStreamHandler.js +81 -0
- package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopQueryRowsHandler.js +91 -0
- package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
- package/dist/lib/rop/RopReadStreamHandler.js +55 -0
- package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopReleaseHandler.js +20 -0
- package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js +210 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopSetColumnsHandler.js +52 -0
- package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js +162 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js +314 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopWriteStreamHandler.js +45 -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 +19 -0
- package/dist/lib/rop/TaskTarget.js.map +1 -0
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js +84 -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 +82 -0
- package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
- package/dist/types/MapiSessionManager.d.ts +130 -0
- package/dist/types/RopDispatcher.d.ts +17 -0
- package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
- package/dist/types/codec/BufferCursor.d.ts +78 -0
- package/dist/types/codec/GlobalObjectId.d.ts +11 -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 +77 -0
- package/dist/types/codec/RopBuffer.d.ts +25 -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 +19 -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 +24 -0
- package/dist/types/rop/CalendarEventTarget.d.ts +29 -0
- package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
- package/dist/types/rop/ContactTarget.d.ts +29 -0
- package/dist/types/rop/FastTransferStream.d.ts +18 -0
- package/dist/types/rop/FolderTarget.d.ts +63 -0
- package/dist/types/rop/MeetingMessageClassHandler.d.ts +36 -0
- package/dist/types/rop/MessageBodyStream.d.ts +26 -0
- package/dist/types/rop/MessageTarget.d.ts +38 -0
- package/dist/types/rop/NamedPropertyRegistry.d.ts +46 -0
- package/dist/types/rop/PropertyResolvers.d.ts +60 -0
- package/dist/types/rop/RopCreateMessageHandler.d.ts +23 -0
- package/dist/types/rop/RopDeleteFolderHandler.d.ts +38 -0
- package/dist/types/rop/RopDeleteMessagesHandler.d.ts +31 -0
- package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +24 -0
- package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +24 -0
- package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +23 -0
- package/dist/types/rop/RopGetContentsTableHandler.d.ts +31 -0
- package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +17 -0
- package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +23 -0
- package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
- package/dist/types/rop/RopHandler.d.ts +67 -0
- package/dist/types/rop/RopLogonHandler.d.ts +31 -0
- package/dist/types/rop/RopOpenFolderHandler.d.ts +19 -0
- package/dist/types/rop/RopOpenMessageHandler.d.ts +27 -0
- package/dist/types/rop/RopOpenStreamHandler.d.ts +27 -0
- package/dist/types/rop/RopQueryRowsHandler.d.ts +36 -0
- package/dist/types/rop/RopReadStreamHandler.d.ts +18 -0
- package/dist/types/rop/RopReleaseHandler.d.ts +14 -0
- package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +44 -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 +70 -0
- package/dist/types/rop/RopWriteStreamHandler.d.ts +21 -0
- package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
- package/dist/types/rop/TaskTarget.d.ts +24 -0
- package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +19 -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,84 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
9
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
10
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
11
|
+
import { CalendarEventMongo, ContactMongo, FolderMongo, LabelMongo, MailboxMongo, MessageMongo, TaskMongo } from "@rapidmx/restapi/mongo";
|
|
12
|
+
import { BaseMapiEmsmdbRoute } from "../BaseMapiEmsmdbRoute.js";
|
|
13
|
+
import { RopLogonHandler } from "../rop/RopLogonHandler.js";
|
|
14
|
+
import { RopReleaseHandler } from "../rop/RopReleaseHandler.js";
|
|
15
|
+
import { RopOpenFolderHandler } from "../rop/RopOpenFolderHandler.js";
|
|
16
|
+
import { RopGetHierarchyTableHandler } from "../rop/RopGetHierarchyTableHandler.js";
|
|
17
|
+
import { RopGetContentsTableHandler } from "../rop/RopGetContentsTableHandler.js";
|
|
18
|
+
import { RopSetColumnsHandler } from "../rop/RopSetColumnsHandler.js";
|
|
19
|
+
import { RopQueryRowsHandler } from "../rop/RopQueryRowsHandler.js";
|
|
20
|
+
import { RopOpenMessageHandler } from "../rop/RopOpenMessageHandler.js";
|
|
21
|
+
import { RopGetPropertiesSpecificHandler } from "../rop/RopGetPropertiesSpecificHandler.js";
|
|
22
|
+
import { RopOpenStreamHandler } from "../rop/RopOpenStreamHandler.js";
|
|
23
|
+
import { RopReadStreamHandler } from "../rop/RopReadStreamHandler.js";
|
|
24
|
+
import { RopCreateMessageHandler } from "../rop/RopCreateMessageHandler.js";
|
|
25
|
+
import { RopSetPropertiesHandler } from "../rop/RopSetPropertiesHandler.js";
|
|
26
|
+
import { RopWriteStreamHandler } from "../rop/RopWriteStreamHandler.js";
|
|
27
|
+
import { RopSaveChangesMessageHandler } from "../rop/RopSaveChangesMessageHandler.js";
|
|
28
|
+
import { RopSubmitMessageHandler } from "../rop/RopSubmitMessageHandler.js";
|
|
29
|
+
import { RopGetPropertyIdsFromNamesHandler } from "../rop/RopGetPropertyIdsFromNamesHandler.js";
|
|
30
|
+
import { RopDeleteMessagesHandler } from "../rop/RopDeleteMessagesHandler.js";
|
|
31
|
+
import { RopDeleteFolderHandler } from "../rop/RopDeleteFolderHandler.js";
|
|
32
|
+
import { RopFastTransferSourceCopyToHandler } from "../rop/RopFastTransferSourceCopyToHandler.js";
|
|
33
|
+
import { RopFastTransferSourceCopyPropertiesHandler } from "../rop/RopFastTransferSourceCopyPropertiesHandler.js";
|
|
34
|
+
import { RopFastTransferSourceGetBufferHandler } from "../rop/RopFastTransferSourceGetBufferHandler.js";
|
|
35
|
+
import { RouteDecorators } from "@rapidrest/service-core";
|
|
36
|
+
const { Route } = RouteDecorators;
|
|
37
|
+
/**
|
|
38
|
+
* Mongo-backed concrete `BaseMapiEmsmdbRoute`, mounted at `/mapi/emsmdb`. Exported from this plugin's
|
|
39
|
+
* `./mongo` entry point, so the server host mounts it without a wrapper class of its
|
|
40
|
+
* own.
|
|
41
|
+
*
|
|
42
|
+
* @author Jean-Philippe Steinmetz
|
|
43
|
+
*/
|
|
44
|
+
let MapiEmsmdbRouteMongo = class MapiEmsmdbRouteMongo extends BaseMapiEmsmdbRoute {
|
|
45
|
+
constructor() {
|
|
46
|
+
super(...arguments);
|
|
47
|
+
this.mailboxClass = MailboxMongo;
|
|
48
|
+
this.folderClass = FolderMongo;
|
|
49
|
+
this.messageClass = MessageMongo;
|
|
50
|
+
this.calendarEventClass = CalendarEventMongo;
|
|
51
|
+
this.contactClass = ContactMongo;
|
|
52
|
+
this.taskClass = TaskMongo;
|
|
53
|
+
this.labelClass = LabelMongo;
|
|
54
|
+
this.ropHandlerClasses = [
|
|
55
|
+
RopLogonHandler,
|
|
56
|
+
RopReleaseHandler,
|
|
57
|
+
RopOpenFolderHandler,
|
|
58
|
+
RopGetHierarchyTableHandler,
|
|
59
|
+
RopGetContentsTableHandler,
|
|
60
|
+
RopSetColumnsHandler,
|
|
61
|
+
RopQueryRowsHandler,
|
|
62
|
+
RopOpenMessageHandler,
|
|
63
|
+
RopGetPropertiesSpecificHandler,
|
|
64
|
+
RopOpenStreamHandler,
|
|
65
|
+
RopReadStreamHandler,
|
|
66
|
+
RopCreateMessageHandler,
|
|
67
|
+
RopSetPropertiesHandler,
|
|
68
|
+
RopWriteStreamHandler,
|
|
69
|
+
RopSaveChangesMessageHandler,
|
|
70
|
+
RopSubmitMessageHandler,
|
|
71
|
+
RopGetPropertyIdsFromNamesHandler,
|
|
72
|
+
RopDeleteMessagesHandler,
|
|
73
|
+
RopDeleteFolderHandler,
|
|
74
|
+
RopFastTransferSourceCopyToHandler,
|
|
75
|
+
RopFastTransferSourceCopyPropertiesHandler,
|
|
76
|
+
RopFastTransferSourceGetBufferHandler,
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
MapiEmsmdbRouteMongo = __decorate([
|
|
81
|
+
Route("/mapi/emsmdb")
|
|
82
|
+
], MapiEmsmdbRouteMongo);
|
|
83
|
+
export { MapiEmsmdbRouteMongo };
|
|
84
|
+
//# sourceMappingURL=MapiEmsmdbRouteMongo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MapiEmsmdbRouteMongo.js","sourceRoot":"","sources":["../../../src/mongo/MapiEmsmdbRouteMongo.ts"],"names":[],"mappings":";;;;;;AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC1I,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,2BAA2B,EAAE,MAAM,uCAAuC,CAAC;AACpF,OAAO,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,+BAA+B,EAAE,MAAM,2CAA2C,CAAC;AAC5F,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,4BAA4B,EAAE,MAAM,wCAAwC,CAAC;AACtF,OAAO,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,EAAE,iCAAiC,EAAE,MAAM,6CAA6C,CAAC;AAChG,OAAO,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAE,kCAAkC,EAAE,MAAM,8CAA8C,CAAC;AAClG,OAAO,EAAE,0CAA0C,EAAE,MAAM,sDAAsD,CAAC;AAClH,OAAO,EAAE,qCAAqC,EAAE,MAAM,iDAAiD,CAAC;AACxG,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,MAAM,EAAE,KAAK,EAAE,GAAG,eAAe,CAAC;AAElC;;;;;;GAMG;AAEI,IAAM,oBAAoB,GAA1B,MAAM,oBAAqB,SAAQ,mBAAiC;IAApE;;QACO,iBAAY,GAAQ,YAAY,CAAC;QACjC,gBAAW,GAAQ,WAAW,CAAC;QAC/B,iBAAY,GAAQ,YAAY,CAAC;QACjC,uBAAkB,GAAQ,kBAAkB,CAAC;QAC7C,iBAAY,GAAQ,YAAY,CAAC;QACjC,cAAS,GAAQ,SAAS,CAAC;QAC3B,eAAU,GAAQ,UAAU,CAAC;QAC7B,sBAAiB,GAAU;YACjC,eAAe;YACf,iBAAiB;YACjB,oBAAoB;YACpB,2BAA2B;YAC3B,0BAA0B;YAC1B,oBAAoB;YACpB,mBAAmB;YACnB,qBAAqB;YACrB,+BAA+B;YAC/B,oBAAoB;YACpB,oBAAoB;YACpB,uBAAuB;YACvB,uBAAuB;YACvB,qBAAqB;YACrB,4BAA4B;YAC5B,uBAAuB;YACvB,iCAAiC;YACjC,wBAAwB;YACxB,sBAAsB;YACtB,kCAAkC;YAClC,0CAA0C;YAC1C,qCAAqC;SACxC,CAAC;IACN,CAAC;CAAA,CAAA;AAhCY,oBAAoB;IADhC,KAAK,CAAC,cAAc,CAAC;GACT,oBAAoB,CAgChC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
9
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
10
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
11
|
+
import { ContactMongo, MailboxMongo } from "@rapidmx/restapi/mongo";
|
|
12
|
+
import { BaseMapiNspiRoute } from "../BaseMapiNspiRoute.js";
|
|
13
|
+
import { RouteDecorators } from "@rapidrest/service-core";
|
|
14
|
+
const { Route } = RouteDecorators;
|
|
15
|
+
/**
|
|
16
|
+
* Mongo-backed concrete `BaseMapiNspiRoute`, mounted at `/mapi/nspi`. Exported from this plugin's
|
|
17
|
+
* `./mongo` entry point, so the server host mounts it without a wrapper class of its
|
|
18
|
+
* own.
|
|
19
|
+
*
|
|
20
|
+
* @author Jean-Philippe Steinmetz
|
|
21
|
+
*/
|
|
22
|
+
let MapiNspiRouteMongo = class MapiNspiRouteMongo extends BaseMapiNspiRoute {
|
|
23
|
+
constructor() {
|
|
24
|
+
super(...arguments);
|
|
25
|
+
this.mailboxClass = MailboxMongo;
|
|
26
|
+
this.contactClass = ContactMongo;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
MapiNspiRouteMongo = __decorate([
|
|
30
|
+
Route("/mapi/nspi")
|
|
31
|
+
], MapiNspiRouteMongo);
|
|
32
|
+
export { MapiNspiRouteMongo };
|
|
33
|
+
//# sourceMappingURL=MapiNspiRouteMongo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MapiNspiRouteMongo.js","sourceRoot":"","sources":["../../../src/mongo/MapiNspiRouteMongo.ts"],"names":[],"mappings":";;;;;;AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,MAAM,EAAE,KAAK,EAAE,GAAG,eAAe,CAAC;AAElC;;;;;;GAMG;AAEI,IAAM,kBAAkB,GAAxB,MAAM,kBAAmB,SAAQ,iBAA+B;IAAhE;;QACO,iBAAY,GAAQ,YAAY,CAAC;QACjC,iBAAY,GAAQ,YAAY,CAAC;IAC/C,CAAC;CAAA,CAAA;AAHY,kBAAkB;IAD9B,KAAK,CAAC,YAAY,CAAC;GACP,kBAAkB,CAG9B"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
export * from "./MapiEmsmdbRouteMongo.js";
|
|
6
|
+
export * from "./MapiNspiRouteMongo.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/mongo/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
export * from "./mongo/index.js";
|
|
6
|
+
//# sourceMappingURL=mongo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mongo.js","sourceRoot":"","sources":["../../src/mongo.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import crypto from "crypto";
|
|
6
|
+
import { BufferWriter } from "../codec/BufferCursor.js";
|
|
7
|
+
import { encodeGuid } from "../codec/MapiGuid.js";
|
|
8
|
+
import { resolveCallerMailboxUid } from "@rapidmx/restapi";
|
|
9
|
+
/** `[MS-OXNSPI]` §2.2.1.2's own `MAPI_E_NOT_FOUND` error code, reused for "the caller has no mailbox in this
|
|
10
|
+
* deployment" - the one real failure mode `Bind` can hit in this pragmatic subset. */
|
|
11
|
+
const ERROR_NOT_FOUND = 0x8004010f;
|
|
12
|
+
/**
|
|
13
|
+
* `Bind`/`Unbind` request types (`[MS-OXCMAPIHTTP]` §2.2.5.1/§2.2.5.2, backed by `[MS-OXNSPI]`'s own `NspiBind`/
|
|
14
|
+
* `NspiUnbind` methods): establishes/tears down an NSPI Session Context, the address-book endpoint's own
|
|
15
|
+
* analog of EMSMDB's `Connect`/`Disconnect`.
|
|
16
|
+
*
|
|
17
|
+
* **No real session state is created or tracked** - a deliberate, documented simplification, not an oversight:
|
|
18
|
+
* every subsequent NSPI operation in this pragmatic subset (`GetMatches`) is independently `@Auth(["jwt"])`-
|
|
19
|
+
* protected and re-resolves the caller's own mailbox from that same JWT, exactly the way `RopLogon`/every ROP
|
|
20
|
+
* handler already does for EMSMDB - the real spec's `MapiContext`-equivalent session cookie exists mainly for
|
|
21
|
+
* multi-server-farm request affinity, not additional authentication, so this pragmatic subset mints an opaque,
|
|
22
|
+
* unvalidated cookie value purely for wire-format conformance and never checks it again. `HasState`/`State`
|
|
23
|
+
* (an optional `STAT` scoping the bind to one address-book container) is accepted but not decoded at all -
|
|
24
|
+
* this pragmatic subset has exactly one "container" (a mailbox's own `Contact` list) regardless.
|
|
25
|
+
*
|
|
26
|
+
* @author Jean-Philippe Steinmetz
|
|
27
|
+
*/
|
|
28
|
+
export async function handleNspiBind(res, user, mailboxRepo) {
|
|
29
|
+
const mailboxUid = await resolveCallerMailboxUid(mailboxRepo, user);
|
|
30
|
+
if (!mailboxUid) {
|
|
31
|
+
const body = new BufferWriter();
|
|
32
|
+
body.writeUInt32LE(ERROR_NOT_FOUND); // StatusCode - MUST NOT be 0 on failure
|
|
33
|
+
body.writeUInt32LE(0); // AuxiliaryBufferSize
|
|
34
|
+
res.status(200).send(body.toBuffer());
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
res.appendHeader("Set-Cookie", `NspiContext=${crypto.randomUUID()}`); // opaque, unvalidated - see class doc comment
|
|
38
|
+
const body = new BufferWriter();
|
|
39
|
+
body.writeUInt32LE(0); // StatusCode - success
|
|
40
|
+
body.writeUInt32LE(0); // ErrorCode - success
|
|
41
|
+
body.writeBytes(encodeGuid(crypto.randomUUID())); // ServerGuid - a fresh value per Bind is harmless, see class doc comment
|
|
42
|
+
body.writeUInt32LE(0); // AuxiliaryBufferSize
|
|
43
|
+
res.status(200).send(body.toBuffer());
|
|
44
|
+
}
|
|
45
|
+
/** `Unbind` never fails and has nothing to tear down - see `handleNspiBind`'s own doc comment for why no real
|
|
46
|
+
* session exists to release. */
|
|
47
|
+
export function handleNspiUnbind(res) {
|
|
48
|
+
const body = new BufferWriter();
|
|
49
|
+
body.writeUInt32LE(0); // StatusCode
|
|
50
|
+
body.writeUInt32LE(0); // ErrorCode
|
|
51
|
+
body.writeUInt32LE(0); // AuxiliaryBufferSize
|
|
52
|
+
res.status(200).send(body.toBuffer());
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=NspiBindHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NspiBindHandler.js","sourceRoot":"","sources":["../../../src/nspi/NspiBindHandler.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,MAAM,MAAM,QAAQ,CAAC;AAG5B,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAE3D;sFACsF;AACtF,MAAM,eAAe,GAAG,UAAU,CAAC;AAEnC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAiB,EAAE,IAAa,EAAE,WAA2B;IAC9F,MAAM,UAAU,GAAG,MAAM,uBAAuB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IACpE,IAAI,CAAC,UAAU,EAAE,CAAC;QACd,MAAM,IAAI,GAAG,IAAI,YAAY,EAAE,CAAC;QAChC,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC,wCAAwC;QAC7E,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB;QAC7C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACtC,OAAO;IACX,CAAC;IAED,GAAG,CAAC,YAAY,CAAC,YAAY,EAAE,eAAe,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,8CAA8C;IAEpH,MAAM,IAAI,GAAG,IAAI,YAAY,EAAE,CAAC;IAChC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,uBAAuB;IAC9C,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB;IAC7C,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,yEAAyE;IAC3H,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB;IAC7C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED;gCACgC;AAChC,MAAM,UAAU,gBAAgB,CAAC,GAAiB;IAC9C,MAAM,IAAI,GAAG,IAAI,YAAY,EAAE,CAAC;IAChC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa;IACpC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY;IACnC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB;IAC7C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { PropertyType, readPropertyTag, readTaggedPropertyValue, writePropertyTag, writePropertyValue, } from "../codec/PropertyValue.js";
|
|
2
|
+
export function readStat(reader) {
|
|
3
|
+
return {
|
|
4
|
+
sortType: reader.readUInt32LE(),
|
|
5
|
+
containerId: reader.readUInt32LE(),
|
|
6
|
+
currentRec: reader.readUInt32LE(),
|
|
7
|
+
delta: reader.readInt32LE(),
|
|
8
|
+
numPos: reader.readUInt32LE(),
|
|
9
|
+
totalRecs: reader.readUInt32LE(),
|
|
10
|
+
codePage: reader.readUInt32LE(),
|
|
11
|
+
templateLocale: reader.readUInt32LE(),
|
|
12
|
+
sortLocale: reader.readUInt32LE(),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function writeStat(writer, stat) {
|
|
16
|
+
writer.writeUInt32LE(stat.sortType);
|
|
17
|
+
writer.writeUInt32LE(stat.containerId);
|
|
18
|
+
writer.writeUInt32LE(stat.currentRec);
|
|
19
|
+
writer.writeInt32LE(stat.delta);
|
|
20
|
+
writer.writeUInt32LE(stat.numPos);
|
|
21
|
+
writer.writeUInt32LE(stat.totalRecs);
|
|
22
|
+
writer.writeUInt32LE(stat.codePage);
|
|
23
|
+
writer.writeUInt32LE(stat.templateLocale);
|
|
24
|
+
writer.writeUInt32LE(stat.sortLocale);
|
|
25
|
+
}
|
|
26
|
+
/** A blank `STAT` (`SortType`/`ContainerID`/... all zero) - what this pragmatic subset always echoes back
|
|
27
|
+
* (in `Bind`'s optional acknowledgement and `GetMatches`' response) regardless of the caller's own request
|
|
28
|
+
* `STAT`, since no real per-container/locale state is tracked. */
|
|
29
|
+
export const BLANK_STAT = {
|
|
30
|
+
sortType: 0,
|
|
31
|
+
containerId: 0,
|
|
32
|
+
currentRec: 0,
|
|
33
|
+
delta: 0,
|
|
34
|
+
numPos: 0,
|
|
35
|
+
totalRecs: 0,
|
|
36
|
+
codePage: 0,
|
|
37
|
+
templateLocale: 0,
|
|
38
|
+
sortLocale: 0,
|
|
39
|
+
};
|
|
40
|
+
/** `LargePropertyTagArray` (`[MS-OXCMAPIHTTP]` §2.2.1.8): a 4-byte count followed by that many `PropertyTag`
|
|
41
|
+
* structures (`[MS-OXCDATA]` §2.9 - the same `PropertyType`-then-`PropertyId` encoding `PropertyValue.ts`'s own
|
|
42
|
+
* `readPropertyTag`/`writePropertyTag` already implement). */
|
|
43
|
+
export function readLargePropertyTagArray(reader) {
|
|
44
|
+
const count = reader.readUInt32LE();
|
|
45
|
+
const tags = [];
|
|
46
|
+
for (let i = 0; i < count; i++) {
|
|
47
|
+
tags.push(readPropertyTag(reader));
|
|
48
|
+
}
|
|
49
|
+
return tags;
|
|
50
|
+
}
|
|
51
|
+
export function writeLargePropertyTagArray(writer, tags) {
|
|
52
|
+
writer.writeUInt32LE(tags.length);
|
|
53
|
+
for (const tag of tags) {
|
|
54
|
+
writePropertyTag(writer, tag);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Writes one column's `AddressBookPropertyValue` (`[MS-OXCMAPIHTTP]` §2.2.1.1): a `PtypString`/`PtypString8`/
|
|
58
|
+
* `PtypBinary` value is preceded by a `HasValue` byte (always `0xFF`/`TRUE` in this pragmatic subset, which
|
|
59
|
+
* never reports a missing column value - the same "always emit a real value of the right type" principle
|
|
60
|
+
* `PropertyResolvers.defaultValueForType` already applies for EMSMDB); every other (fixed-size) type has no
|
|
61
|
+
* such prefix. `PtypMultiple*` columns (which need a `HasValue` byte per *element*, not just once) are never
|
|
62
|
+
* produced by this pragmatic subset's own fixed GAL column set and are intentionally not handled here. */
|
|
63
|
+
function writeAddressBookPropertyValue(writer, type, value) {
|
|
64
|
+
if (type === PropertyType.PtypString || type === PropertyType.PtypString8 || type === PropertyType.PtypBinary) {
|
|
65
|
+
writer.writeUInt8(0xff); // HasValue - always TRUE, see function doc comment
|
|
66
|
+
}
|
|
67
|
+
writePropertyValue(writer, type, value);
|
|
68
|
+
}
|
|
69
|
+
/** Writes one `AddressBookPropertyRow` (`[MS-OXCMAPIHTTP]` §2.2.1.7) - always `Flags=0x00` (every column
|
|
70
|
+
* value present without error), the exact `StandardPropertyRow`-equivalent choice `RopQueryRowsHandler`/
|
|
71
|
+
* `RopGetPropertiesSpecificHandler` already make for EMSMDB's own row encoding. */
|
|
72
|
+
export function writeAddressBookPropertyRow(writer, columns, values) {
|
|
73
|
+
writer.writeUInt8(0x00); // Flags - see function doc comment
|
|
74
|
+
columns.forEach((column, index) => writeAddressBookPropertyValue(writer, column.propertyType, values[index]));
|
|
75
|
+
}
|
|
76
|
+
/** Decodes a `Restriction` (`[MS-OXCDATA]` §2.12)'s leading `RestrictType` byte and, only for a
|
|
77
|
+
* `ContentRestriction`/`RES_CONTENT` (`0x03`) - the shape a real client's own GAL "search as you type" sends -
|
|
78
|
+
* extracts the plain string search term from its `TaggedValue` field.
|
|
79
|
+
*
|
|
80
|
+
* **Throws for any other restriction type** (`AND`/`OR`/`PropertyRestriction`/...) rather than degrading
|
|
81
|
+
* gracefully - unlike this pragmatic subset's usual "return a default instead of failing" stance elsewhere,
|
|
82
|
+
* a restriction's own byte length is type-dependent (12 different packet formats, several recursively nested),
|
|
83
|
+
* so once an unrecognized `RestrictType` is seen there is no safe way to know how many bytes to skip to reach
|
|
84
|
+
* the request body's own subsequent fields (`RowCount`/`Columns`/...) - silently returning `undefined` here
|
|
85
|
+
* would leave the reader mid-structure and corrupt every field decoded afterward. Throwing is the honest,
|
|
86
|
+
* safe choice for an input this narrow codec was never meant to parse, not a design gap.
|
|
87
|
+
*
|
|
88
|
+
* `FuzzyLevelLow`/`FuzzyLevelHigh` (exact/substring/prefix matching, case sensitivity) and the restriction's
|
|
89
|
+
* own target `PropertyTag` are decoded to advance the reader correctly but not honored - this pragmatic
|
|
90
|
+
* subset always does a case-insensitive substring match against its own fixed Contact field set regardless of
|
|
91
|
+
* which property or fuzzy level the client's restriction actually named. */
|
|
92
|
+
export function extractContentRestrictionSearchTerm(reader) {
|
|
93
|
+
const restrictType = reader.readUInt8();
|
|
94
|
+
if (restrictType !== 0x03) {
|
|
95
|
+
throw new Error(`NspiCodec: unsupported restriction type 0x${restrictType.toString(16)} (only ContentRestriction/RES_CONTENT is supported)`);
|
|
96
|
+
}
|
|
97
|
+
reader.readUInt16LE(); // FuzzyLevelLow - not honored, see function doc comment
|
|
98
|
+
reader.readUInt16LE(); // FuzzyLevelHigh - not honored, see function doc comment
|
|
99
|
+
readPropertyTag(reader); // PropertyTag (the restriction's own target column) - not honored
|
|
100
|
+
const tagged = readTaggedPropertyValue(reader);
|
|
101
|
+
if (typeof tagged.value !== "string") {
|
|
102
|
+
throw new Error("NspiCodec: ContentRestriction's TaggedValue was not a string.");
|
|
103
|
+
}
|
|
104
|
+
return tagged.value;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=NspiCodec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NspiCodec.js","sourceRoot":"","sources":["../../../src/nspi/NspiCodec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { StringUtils } from "@rapidrest/core";
|
|
6
|
+
import { BufferReader, BufferWriter } from "../codec/BufferCursor.js";
|
|
7
|
+
import { PropertyType } from "../codec/PropertyValue.js";
|
|
8
|
+
import { defaultValueForType } from "../rop/PropertyResolvers.js";
|
|
9
|
+
import { BLANK_STAT, extractContentRestrictionSearchTerm, readLargePropertyTagArray, readStat, writeAddressBookPropertyRow, writeLargePropertyTagArray, writeStat, } from "./NspiCodec.js";
|
|
10
|
+
/** `PidTagDisplayName`/`PidTagEmailAddress` - the small default column set this pragmatic subset returns when
|
|
11
|
+
* a client's `GetMatches` request omits its own `Columns` field, mirroring EAS's own GAL result shape
|
|
12
|
+
* (`SearchCommand.ts`'s `DisplayName`/`EmailAddress`). */
|
|
13
|
+
const PID_TAG_DISPLAY_NAME = 0x3001;
|
|
14
|
+
const PID_TAG_EMAIL_ADDRESS = 0x3003;
|
|
15
|
+
const DEFAULT_COLUMNS = [
|
|
16
|
+
{ propertyId: PID_TAG_DISPLAY_NAME, propertyType: PropertyType.PtypString },
|
|
17
|
+
{ propertyId: PID_TAG_EMAIL_ADDRESS, propertyType: PropertyType.PtypString },
|
|
18
|
+
];
|
|
19
|
+
/** A small, non-cryptographic FNV-1a 32-bit hash of a `Contact.uid`, used as that contact's `MinimalEntryID`
|
|
20
|
+
* in a `GetMatches` response. Deterministic per `uid` but not persisted or globally coordinated - acceptable
|
|
21
|
+
* here since no other operation in this pragmatic subset ever looks a `MinimalEntryID` back up (`NspiGetProps`/
|
|
22
|
+
* `NspiQueryRows`/... are out of scope), so nothing depends on it staying stable beyond a single response. */
|
|
23
|
+
function minimalEntryIdFor(uid) {
|
|
24
|
+
let hash = 0x811c9dc5;
|
|
25
|
+
for (let i = 0; i < uid.length; i++) {
|
|
26
|
+
hash ^= uid.charCodeAt(i);
|
|
27
|
+
hash = Math.imul(hash, 0x01000193);
|
|
28
|
+
}
|
|
29
|
+
return hash >>> 0;
|
|
30
|
+
}
|
|
31
|
+
function valueForColumn(contact, column) {
|
|
32
|
+
switch (column.propertyId) {
|
|
33
|
+
case PID_TAG_DISPLAY_NAME:
|
|
34
|
+
return contact.displayName;
|
|
35
|
+
case PID_TAG_EMAIL_ADDRESS:
|
|
36
|
+
// Optional-chained on `emails` itself, not just its first element - a Contact predating this field
|
|
37
|
+
// (or otherwise missing it) would throw on a bare `contact.emails[0]` before the `?.` on `.address`
|
|
38
|
+
// ever gets a chance to help.
|
|
39
|
+
return contact.emails?.[0]?.address ?? "";
|
|
40
|
+
default:
|
|
41
|
+
return defaultValueForType(column.propertyType);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Finds every `Contact` in `mailboxUid` matching `searchTerm` (a case-insensitive substring match against
|
|
45
|
+
* `displayName`/`givenName`/`surname`/`company`, queried per-field and merged in memory - the same two-backend
|
|
46
|
+
* workaround `SearchCommand.ts` already established for its own identical `$or`-is-Mongo-only /
|
|
47
|
+
* `ILike()`-needs-wrapping gaps) - or every `Contact` in the mailbox at all when `searchTerm` is `undefined`
|
|
48
|
+
* (no filter was supplied, or (`extractContentRestrictionSearchTerm`'s own contract) the filter was already
|
|
49
|
+
* known-good by the time this is called).
|
|
50
|
+
*
|
|
51
|
+
* Uses `RepoUtils`'s `regex(...)` operator (`@rapidrest/service-core` ^2.0), not `like(...)`: `like()` compiles
|
|
52
|
+
* a **glob** pattern (`*`/`?` as wildcards), which would require wrapping the term in `*...*` to get a substring
|
|
53
|
+
* match and would still treat a literal `*`/`?` in the search term itself as a wildcard (glob syntax has no
|
|
54
|
+
* escape for those two characters). `regex()` takes a real, already-anchoring-free regular expression compiled
|
|
55
|
+
* case-insensitively on both backends (`$regex`/`~*`/`REGEXP`), so escaping the term with `StringUtils
|
|
56
|
+
* .escapeRegExp` here gives a genuine literal-substring match with no residual wildcard ambiguity. `regex()`
|
|
57
|
+
* patterns are also independently validated by the framework itself (`ModelUtils.isUnsafeRegexPattern`) against
|
|
58
|
+
* catastrophic-backtracking shapes, but that guard is for patterns a caller assembles from parts; escaping the
|
|
59
|
+
* entire term with `escapeRegExp` before it ever reaches here already prevents any of those shapes from forming
|
|
60
|
+
* in the first place, since no unescaped metacharacter survives. */
|
|
61
|
+
async function findMatchingContacts(mailboxUid, contactRepo, searchTerm) {
|
|
62
|
+
if (!searchTerm) {
|
|
63
|
+
return contactRepo.find({ mailboxUid }, { ignoreACL: true });
|
|
64
|
+
}
|
|
65
|
+
const pattern = StringUtils.escapeRegExp(searchTerm);
|
|
66
|
+
const perField = await Promise.all(["displayName", "givenName", "surname", "company"].map((field) => contactRepo.find({ mailboxUid, [field]: `regex(${pattern})` }, { ignoreACL: true })));
|
|
67
|
+
const byUid = new Map();
|
|
68
|
+
for (const contact of perField.flat()) {
|
|
69
|
+
byUid.set(contact.uid, contact);
|
|
70
|
+
}
|
|
71
|
+
return Array.from(byUid.values()).sort((a, b) => a.displayName.localeCompare(b.displayName));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* `GetMatches` request type (`[MS-OXCMAPIHTTP]` §2.2.5.5, backed by `[MS-OXNSPI]`'s own `NspiGetMatches`
|
|
75
|
+
* method): searches the GAL (this library's `Contact` records, the same source of truth EAS's own `Search`
|
|
76
|
+
* command already uses) for entries matching the client's restriction, returning up to `RowCount` matches as
|
|
77
|
+
* `AddressBookPropertyRow`s. This is the real operation behind a client's own address-book "search as you
|
|
78
|
+
* type" UI - the closest real NSPI equivalent to this plan's own "minimal prefix/substring lookups" scope.
|
|
79
|
+
*
|
|
80
|
+
* `HasState`/`State` (an input `STAT` scoping the search and reporting position) and `HasMinimalIds`/
|
|
81
|
+
* `MinimalIds` (an "Explicit Table" restricting the search to a caller-supplied candidate set) are decoded to
|
|
82
|
+
* advance the reader correctly but not honored - this pragmatic subset always searches the caller's entire
|
|
83
|
+
* Contact list fresh on every call (no real per-container/table state, see `NspiBindHandler.ts`'s own doc
|
|
84
|
+
* comment for the same reasoning applied to `Bind`). `HasFilter`'s `Filter` is decoded via
|
|
85
|
+
* `NspiCodec.extractContentRestrictionSearchTerm` - see that function's own doc comment for why an
|
|
86
|
+
* unsupported restriction type throws rather than degrading. `HasPropertyName` (an alternate open-by-named-
|
|
87
|
+
* property addressing mode) is decoded to advance the reader correctly but never itself used to select a
|
|
88
|
+
* search field.
|
|
89
|
+
*
|
|
90
|
+
* The response's own `State` is always `BLANK_STAT` (with `TotalRecs` set to the real match count) - no real
|
|
91
|
+
* per-container `STAT` is tracked, matching this pragmatic subset's stated scope.
|
|
92
|
+
*
|
|
93
|
+
* @author Jean-Philippe Steinmetz
|
|
94
|
+
*/
|
|
95
|
+
export async function handleNspiGetMatches(req, res, mailboxUid, contactRepo) {
|
|
96
|
+
const reader = new BufferReader(req.rawBody ?? Buffer.alloc(0));
|
|
97
|
+
reader.readUInt32LE(); // Reserved
|
|
98
|
+
if (reader.readUInt8()) {
|
|
99
|
+
readStat(reader); // State - not honored, see function doc comment
|
|
100
|
+
}
|
|
101
|
+
if (reader.readUInt8()) {
|
|
102
|
+
// MinimalIds (an Explicit Table) - not honored, see function doc comment
|
|
103
|
+
const minimalIdCount = reader.readUInt32LE();
|
|
104
|
+
for (let i = 0; i < minimalIdCount; i++) {
|
|
105
|
+
reader.readUInt32LE();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
reader.readUInt32LE(); // InterfaceOptionFlags - reserved, always 0
|
|
109
|
+
const searchTerm = reader.readUInt8() ? extractContentRestrictionSearchTerm(reader) : undefined;
|
|
110
|
+
if (reader.readUInt8()) {
|
|
111
|
+
reader.readBytes(16); // PropertyNameGuid - not honored, see function doc comment
|
|
112
|
+
reader.readUInt32LE(); // PropertyNameId
|
|
113
|
+
}
|
|
114
|
+
const rowCount = reader.readUInt32LE();
|
|
115
|
+
const columns = reader.readUInt8() ? readLargePropertyTagArray(reader) : DEFAULT_COLUMNS;
|
|
116
|
+
// AuxiliaryBufferSize/AuxiliaryBuffer intentionally left unread - no auxiliary-payload support.
|
|
117
|
+
const matches = await findMatchingContacts(mailboxUid, contactRepo, searchTerm);
|
|
118
|
+
const page = matches.slice(0, rowCount);
|
|
119
|
+
const body = new BufferWriter();
|
|
120
|
+
body.writeUInt32LE(0); // StatusCode - success
|
|
121
|
+
body.writeUInt32LE(0); // ErrorCode - success
|
|
122
|
+
body.writeUInt8(1); // HasState
|
|
123
|
+
writeStat(body, { ...BLANK_STAT, totalRecs: matches.length });
|
|
124
|
+
body.writeUInt8(page.length > 0 ? 1 : 0); // HasMinimalIds
|
|
125
|
+
if (page.length > 0) {
|
|
126
|
+
body.writeUInt32LE(page.length);
|
|
127
|
+
for (const contact of page) {
|
|
128
|
+
body.writeUInt32LE(minimalEntryIdFor(contact.uid));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
body.writeUInt8(page.length > 0 ? 1 : 0); // HasColumnsAndRows
|
|
132
|
+
if (page.length > 0) {
|
|
133
|
+
writeLargePropertyTagArray(body, columns);
|
|
134
|
+
body.writeUInt32LE(page.length);
|
|
135
|
+
for (const contact of page) {
|
|
136
|
+
writeAddressBookPropertyRow(body, columns, columns.map((column) => valueForColumn(contact, column)));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
body.writeUInt32LE(0); // AuxiliaryBufferSize
|
|
140
|
+
res.status(200).send(body.toBuffer());
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=NspiGetMatchesHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NspiGetMatchesHandler.js","sourceRoot":"","sources":["../../../src/nspi/NspiGetMatchesHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BusyStatus } from "@rapidmx/restapi";
|
|
2
|
+
/** Degrades to empty-looking values for a `"calendarEvent:<uid>"` target whose real `CalendarEvent` has since
|
|
3
|
+
* vanished (soft-deleted or otherwise) - the same "don't fail the whole ROP over one stale row" principle
|
|
4
|
+
* `MessageTarget.resolveMessageInfo`/`FolderTarget.resolveFolderInfo` already apply. */
|
|
5
|
+
export async function resolveCalendarEventInfo(target, calendarEventRepo) {
|
|
6
|
+
const uid = target.slice("calendarEvent:".length);
|
|
7
|
+
const event = await calendarEventRepo.findOne(uid, { ignoreACL: true });
|
|
8
|
+
return {
|
|
9
|
+
title: event?.title ?? "",
|
|
10
|
+
location: event?.location,
|
|
11
|
+
startDate: event?.startDate ?? new Date(0),
|
|
12
|
+
endDate: event?.endDate ?? new Date(0),
|
|
13
|
+
timezone: event?.timezone ?? "UTC",
|
|
14
|
+
busyStatus: event?.busyStatus ?? BusyStatus.BUSY,
|
|
15
|
+
recurrenceRule: event?.recurrenceRule,
|
|
16
|
+
reminderMinutesBeforeStart: event?.reminderMinutesBeforeStart,
|
|
17
|
+
organizerAddress: event?.organizer?.address ?? "",
|
|
18
|
+
attendees: event?.attendees ?? [],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Resolves the calendar events directly in `folderUid`, as `"calendarEvent:<uid>"` target strings, for
|
|
22
|
+
* `RopGetContentsTable` against a `CALENDAR`-type folder. */
|
|
23
|
+
export async function resolveFolderCalendarEvents(folderUid, calendarEventRepo) {
|
|
24
|
+
const events = await calendarEventRepo.find({ folderUid }, { ignoreACL: true });
|
|
25
|
+
return events.map((e) => `calendarEvent:${e.uid}`);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=CalendarEventTarget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CalendarEventTarget.js","sourceRoot":"","sources":["../../../src/rop/CalendarEventTarget.ts"],"names":[],"mappings":"AAKA,OAAO,EAAY,UAAU,EAAiC,MAAM,kBAAkB,CAAC;AAuBvF;;wFAEwF;AACxF,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,MAAc,EAAE,iBAAiC;IAC5F,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,KAAK,GAA8B,MAAM,iBAAiB,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnG,OAAO;QACH,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE;QACzB,QAAQ,EAAE,KAAK,EAAE,QAAQ;QACzB,SAAS,EAAE,KAAK,EAAE,SAAS,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC;QAC1C,OAAO,EAAE,KAAK,EAAE,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC;QACtC,QAAQ,EAAE,KAAK,EAAE,QAAQ,IAAI,KAAK;QAClC,UAAU,EAAE,KAAK,EAAE,UAAU,IAAI,UAAU,CAAC,IAAI;QAChD,cAAc,EAAE,KAAK,EAAE,cAAc;QACrC,0BAA0B,EAAE,KAAK,EAAE,0BAA0B;QAC7D,gBAAgB,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,IAAI,EAAE;QACjD,SAAS,EAAE,KAAK,EAAE,SAAS,IAAI,EAAE;KACpC,CAAC;AACN,CAAC;AAED;6DAC6D;AAC7D,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAAC,SAAiB,EAAE,iBAAiC;IAClG,MAAM,MAAM,GAAoB,MAAM,iBAAiB,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjG,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { AttendeeResponseStatus, BusyStatus } from "@rapidmx/restapi";
|
|
6
|
+
/**
|
|
7
|
+
* The Calendar named-property identity table (`(PropertySet GUID, LID)` pairs) this pragmatic subset supports,
|
|
8
|
+
* per `[MS-OXPROPS]` - each pinned against its own MS-OXPROPS page (not invented), shared by `PropertyResolvers.ts`
|
|
9
|
+
* (the read side: `calendarEventValueFor`) and `RopSaveChangesMessageHandler.ts` (the write side: decoding a
|
|
10
|
+
* draft's accumulated `RopSetProperties` values back into a real `CalendarEvent`) so both directions of this
|
|
11
|
+
* mapping stay in exactly one place. See the architecture plan's "Calendar support" section for the full table.
|
|
12
|
+
*/
|
|
13
|
+
export const PSETID_APPOINTMENT = "00062002-0000-0000-c000-000000000046";
|
|
14
|
+
export const PSETID_COMMON = "00062008-0000-0000-c000-000000000046";
|
|
15
|
+
export const PSETID_MEETING = "6ed8da90-450b-101b-98da-00aa003f1305";
|
|
16
|
+
export const LID_LOCATION = 0x8208;
|
|
17
|
+
export const LID_APPOINTMENT_START_WHOLE = 0x820d;
|
|
18
|
+
export const LID_APPOINTMENT_END_WHOLE = 0x820e;
|
|
19
|
+
export const LID_BUSY_STATUS = 0x8205;
|
|
20
|
+
export const LID_RECURRING = 0x8223;
|
|
21
|
+
export const LID_REMINDER_SET = 0x8503;
|
|
22
|
+
export const LID_REMINDER_DELTA = 0x8501;
|
|
23
|
+
export const LID_RESPONSE_STATUS = 0x8218;
|
|
24
|
+
export const LID_APPOINTMENT_RECUR = 0x8216;
|
|
25
|
+
export const LID_TIME_ZONE_STRUCT = 0x8233;
|
|
26
|
+
/** `PidLidGlobalObjectId` (`PSETID_Meeting`) - see `GlobalObjectId.ts` for the BLOB it carries and
|
|
27
|
+
* `MeetingMessageClassHandler.ts` for how a meeting response's own copy of it is correlated back to the
|
|
28
|
+
* original `CalendarEvent`. */
|
|
29
|
+
export const LID_GLOBAL_OBJECT_ID = 0x00000003;
|
|
30
|
+
/** `PidLidBusyStatus`'s wire values (`[MS-OXOCAL]` §2.2.1.2, confirmed this session) - a different numbering
|
|
31
|
+
* than `CalendarSyncAdapter`'s own `BUSY_STATUS_CODES` table for the unrelated MS-ASCAL `BusyStatus` field.
|
|
32
|
+
* `olWorkingElsewhere` (0x00000004) has no equivalent in this library's own `BusyStatus` enum - never produced,
|
|
33
|
+
* and decoded (via `BUSY_STATUS_FROM_CODE`) with a fallback to `BUSY`, a documented, harmless approximation. */
|
|
34
|
+
export const BUSY_STATUS_CODES = {
|
|
35
|
+
[BusyStatus.FREE]: 0,
|
|
36
|
+
[BusyStatus.TENTATIVE]: 1,
|
|
37
|
+
[BusyStatus.BUSY]: 2,
|
|
38
|
+
[BusyStatus.OUT_OF_OFFICE]: 3,
|
|
39
|
+
};
|
|
40
|
+
export const BUSY_STATUS_FROM_CODE = {
|
|
41
|
+
0: BusyStatus.FREE,
|
|
42
|
+
1: BusyStatus.TENTATIVE,
|
|
43
|
+
2: BusyStatus.BUSY,
|
|
44
|
+
3: BusyStatus.OUT_OF_OFFICE,
|
|
45
|
+
};
|
|
46
|
+
/** `PidLidResponseStatus`'s wire values (`[MS-OXOCAL]` §2.2.1.11, confirmed this session) for the caller's own
|
|
47
|
+
* attendee record. `respOrganized` (0x00000001) is returned when the caller *is* the organizer - not a value
|
|
48
|
+
* `AttendeeResponseStatus` itself has a case for, since that enum only models an attendee's own response. */
|
|
49
|
+
export const RESPONSE_STATUS_CODES = {
|
|
50
|
+
[AttendeeResponseStatus.NEEDS_ACTION]: 5, // respNotResponded
|
|
51
|
+
[AttendeeResponseStatus.TENTATIVE]: 2, // respTentative
|
|
52
|
+
[AttendeeResponseStatus.ACCEPTED]: 3, // respAccepted
|
|
53
|
+
[AttendeeResponseStatus.DECLINED]: 4, // respDeclined
|
|
54
|
+
};
|
|
55
|
+
export const RESPONSE_STATUS_ORGANIZED = 1; // respOrganized
|
|
56
|
+
export const RESPONSE_STATUS_NONE = 0; // respNone
|
|
57
|
+
//# sourceMappingURL=CalendarNamedProperties.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CalendarNamedProperties.js","sourceRoot":"","sources":["../../../src/rop/CalendarNamedProperties.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,sCAAsC,CAAC;AACzE,MAAM,CAAC,MAAM,aAAa,GAAG,sCAAsC,CAAC;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG,sCAAsC,CAAC;AAErE,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC;AACnC,MAAM,CAAC,MAAM,2BAA2B,GAAG,MAAM,CAAC;AAClD,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAChD,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC;AACtC,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC;AACpC,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC;AACvC,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AACzC,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAC1C,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAC5C,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAE3C;;+BAE+B;AAC/B,MAAM,CAAC,MAAM,oBAAoB,GAAG,UAAU,CAAC;AAE/C;;;gHAGgH;AAChH,MAAM,CAAC,MAAM,iBAAiB,GAA+B;IACzD,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;IACpB,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;IACzB,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;IACpB,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;CAChC,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAA+B;IAC7D,CAAC,EAAE,UAAU,CAAC,IAAI;IAClB,CAAC,EAAE,UAAU,CAAC,SAAS;IACvB,CAAC,EAAE,UAAU,CAAC,IAAI;IAClB,CAAC,EAAE,UAAU,CAAC,aAAa;CAC9B,CAAC;AAEF;;6GAE6G;AAC7G,MAAM,CAAC,MAAM,qBAAqB,GAA2C;IACzE,CAAC,sBAAsB,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,mBAAmB;IAC7D,CAAC,sBAAsB,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,gBAAgB;IACvD,CAAC,sBAAsB,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,eAAe;IACrD,CAAC,sBAAsB,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,eAAe;CACxD,CAAC;AACF,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,CAAC,gBAAgB;AAC5D,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,CAAC,WAAW"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Degrades to empty-looking values for a `"contact:<uid>"` target whose real `Contact` has since vanished
|
|
2
|
+
* (soft-deleted or otherwise) - the same "don't fail the whole ROP over one stale row" principle
|
|
3
|
+
* `MessageTarget.resolveMessageInfo`/`CalendarEventTarget.resolveCalendarEventInfo` already apply. */
|
|
4
|
+
export async function resolveContactInfo(target, contactRepo) {
|
|
5
|
+
const uid = target.slice("contact:".length);
|
|
6
|
+
const contact = await contactRepo.findOne(uid, { ignoreACL: true });
|
|
7
|
+
return {
|
|
8
|
+
displayName: contact?.displayName ?? "",
|
|
9
|
+
givenName: contact?.givenName,
|
|
10
|
+
surname: contact?.surname,
|
|
11
|
+
email: contact?.emails?.[0]?.address,
|
|
12
|
+
businessPhone: contact?.phones?.[0]?.phoneNumber,
|
|
13
|
+
companyName: contact?.company,
|
|
14
|
+
jobTitle: contact?.jobTitle,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Resolves the contacts directly in `folderUid`, as `"contact:<uid>"` target strings, for
|
|
18
|
+
* `RopGetContentsTable` against a `CONTACTS`-type folder. */
|
|
19
|
+
export async function resolveFolderContacts(folderUid, contactRepo) {
|
|
20
|
+
const contacts = await contactRepo.find({ folderUid }, { ignoreACL: true });
|
|
21
|
+
return contacts.map((c) => `contact:${c.uid}`);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=ContactTarget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContactTarget.js","sourceRoot":"","sources":["../../../src/rop/ContactTarget.ts"],"names":[],"mappings":"AA6BA;;sGAEsG;AACtG,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAc,EAAE,WAA2B;IAChF,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAwB,MAAM,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzF,OAAO;QACH,WAAW,EAAE,OAAO,EAAE,WAAW,IAAI,EAAE;QACvC,SAAS,EAAE,OAAO,EAAE,SAAS;QAC7B,OAAO,EAAE,OAAO,EAAE,OAAO;QACzB,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO;QACpC,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW;QAChD,WAAW,EAAE,OAAO,EAAE,OAAO;QAC7B,QAAQ,EAAE,OAAO,EAAE,QAAQ;KAC9B,CAAC;AACN,CAAC;AAED;6DAC6D;AAC7D,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,SAAiB,EAAE,WAA2B;IACtF,MAAM,QAAQ,GAAc,MAAM,WAAW,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvF,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;AACnD,CAAC"}
|