@memberjunction/communication-ms-graph 3.4.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +251 -0
- package/dist/MSGraphProvider.d.ts +284 -0
- package/dist/MSGraphProvider.d.ts.map +1 -0
- package/dist/MSGraphProvider.js +350 -220
- package/dist/MSGraphProvider.js.map +1 -1
- package/dist/auth.d.ts +18 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +29 -39
- package/dist/auth.js.map +1 -1
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +13 -14
- package/dist/config.js.map +1 -1
- package/dist/generic/models.d.ts +28 -0
- package/dist/generic/models.d.ts.map +1 -0
- package/dist/generic/models.js +1 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -21
- package/dist/index.js.map +1 -1
- package/package.json +18 -17
- package/readme.md +188 -195
package/dist/MSGraphProvider.js
CHANGED
|
@@ -1,63 +1,65 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
1
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
2
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
3
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
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;
|
|
22
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
6
|
};
|
|
24
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
25
|
-
if (mod && mod.__esModule) return mod;
|
|
26
|
-
var result = {};
|
|
27
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
28
|
-
__setModuleDefault(result, mod);
|
|
29
|
-
return result;
|
|
30
|
-
};
|
|
31
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
32
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
33
9
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
10
|
+
import { BaseCommunicationProvider, resolveCredentialValue, validateRequiredCredentials } from "@memberjunction/communication-types";
|
|
11
|
+
import { Client } from '@microsoft/microsoft-graph-client';
|
|
12
|
+
import { ClientSecretCredential } from '@azure/identity';
|
|
13
|
+
import { TokenCredentialAuthenticationProvider } from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials";
|
|
14
|
+
import { RegisterClass } from "@memberjunction/global";
|
|
15
|
+
import { LogError, LogStatus } from "@memberjunction/core";
|
|
16
|
+
import { compile } from 'html-to-text';
|
|
17
|
+
import * as Auth from "./auth.js";
|
|
18
|
+
import * as Config from "./config.js";
|
|
19
|
+
/**
|
|
20
|
+
* Implementation of the MS Graph provider for sending and receiving messages.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* Microsoft Graph provides full mailbox access. This provider supports:
|
|
24
|
+
* - Sending messages
|
|
25
|
+
* - Fetching messages from inbox
|
|
26
|
+
* - Forwarding messages
|
|
27
|
+
* - Replying to messages
|
|
28
|
+
* - Creating drafts
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // Using environment credentials (default)
|
|
33
|
+
* await engine.SendSingleMessage('Microsoft Graph', 'Standard Email', message);
|
|
34
|
+
*
|
|
35
|
+
* // Using per-request credentials
|
|
36
|
+
* await engine.SendSingleMessage('Microsoft Graph', 'Standard Email', message, undefined, false, {
|
|
37
|
+
* tenantId: 'customer-tenant-id',
|
|
38
|
+
* clientId: 'customer-client-id',
|
|
39
|
+
* clientSecret: 'customer-client-secret',
|
|
40
|
+
* accountEmail: 'customer@domain.com'
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
let MSGraphProvider = class MSGraphProvider extends BaseCommunicationProvider {
|
|
46
45
|
constructor() {
|
|
47
46
|
super();
|
|
48
|
-
|
|
47
|
+
// Cache clients by credential hash for performance with per-request credentials
|
|
49
48
|
this.clientCache = new Map();
|
|
50
|
-
this.HTMLConverter =
|
|
49
|
+
this.HTMLConverter = compile({
|
|
51
50
|
wordwrap: 130
|
|
52
51
|
});
|
|
53
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolves MS Graph credentials from request and environment.
|
|
55
|
+
*/
|
|
54
56
|
resolveCredentials(credentials) {
|
|
55
57
|
const disableFallback = credentials?.disableEnvironmentFallback ?? false;
|
|
56
|
-
const tenantId =
|
|
57
|
-
const clientId =
|
|
58
|
-
const clientSecret =
|
|
59
|
-
const accountEmail =
|
|
60
|
-
|
|
58
|
+
const tenantId = resolveCredentialValue(credentials?.tenantId, Config.AZURE_TENANT_ID, disableFallback);
|
|
59
|
+
const clientId = resolveCredentialValue(credentials?.clientId, Config.AZURE_CLIENT_ID, disableFallback);
|
|
60
|
+
const clientSecret = resolveCredentialValue(credentials?.clientSecret, Config.AZURE_CLIENT_SECRET, disableFallback);
|
|
61
|
+
const accountEmail = resolveCredentialValue(credentials?.accountEmail, Config.AZURE_ACCOUNT_EMAIL, disableFallback);
|
|
62
|
+
validateRequiredCredentials({ tenantId, clientId, clientSecret, accountEmail }, ['tenantId', 'clientId', 'clientSecret', 'accountEmail'], 'Microsoft Graph');
|
|
61
63
|
return {
|
|
62
64
|
tenantId: tenantId,
|
|
63
65
|
clientId: clientId,
|
|
@@ -65,46 +67,55 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
65
67
|
accountEmail: accountEmail
|
|
66
68
|
};
|
|
67
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Gets or creates a Graph client for the given credentials.
|
|
72
|
+
* Uses cached client if credentials match environment (default case).
|
|
73
|
+
*/
|
|
68
74
|
getGraphClient(creds) {
|
|
75
|
+
// Check if using environment credentials (can use shared client)
|
|
69
76
|
const isEnvCredentials = creds.tenantId === Config.AZURE_TENANT_ID &&
|
|
70
77
|
creds.clientId === Config.AZURE_CLIENT_ID &&
|
|
71
78
|
creds.clientSecret === Config.AZURE_CLIENT_SECRET;
|
|
72
79
|
if (isEnvCredentials) {
|
|
80
|
+
// Use the shared Auth.GraphClient for environment credentials
|
|
73
81
|
return Auth.GraphClient;
|
|
74
82
|
}
|
|
83
|
+
// For per-request credentials, use cached client by credential key
|
|
75
84
|
const cacheKey = `${creds.tenantId}:${creds.clientId}`;
|
|
76
85
|
let client = this.clientCache.get(cacheKey);
|
|
77
86
|
if (!client) {
|
|
78
|
-
const credential = new
|
|
79
|
-
const authProvider = new
|
|
87
|
+
const credential = new ClientSecretCredential(creds.tenantId, creds.clientId, creds.clientSecret);
|
|
88
|
+
const authProvider = new TokenCredentialAuthenticationProvider(credential, {
|
|
80
89
|
scopes: ['https://graph.microsoft.com/.default'],
|
|
81
90
|
});
|
|
82
|
-
client =
|
|
91
|
+
client = Client.initWithMiddleware({ authProvider });
|
|
83
92
|
this.clientCache.set(cacheKey, client);
|
|
84
93
|
}
|
|
85
94
|
return client;
|
|
86
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Gets the API URI for Graph API calls.
|
|
98
|
+
*/
|
|
87
99
|
getApiUri() {
|
|
88
100
|
return Auth.ApiConfig.uri;
|
|
89
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Sends a single email message via MS Graph.
|
|
104
|
+
*
|
|
105
|
+
* @requires MS Graph Scope: Mail.Send (Application)
|
|
106
|
+
*/
|
|
90
107
|
async SendSingleMessage(message, credentials) {
|
|
91
108
|
try {
|
|
109
|
+
// Resolve credentials
|
|
92
110
|
const creds = this.resolveCredentials(credentials);
|
|
93
111
|
const client = this.getGraphClient(creds);
|
|
112
|
+
// Smart selection: use message.From if provided and different from resolved accountEmail
|
|
94
113
|
let senderEmail = creds.accountEmail;
|
|
95
114
|
if (message.From &&
|
|
96
115
|
message.From.trim() !== '' &&
|
|
97
116
|
message.From !== creds.accountEmail) {
|
|
98
117
|
senderEmail = message.From;
|
|
99
118
|
}
|
|
100
|
-
const user = await this.GetServiceAccountWithClient(client, senderEmail);
|
|
101
|
-
if (!user) {
|
|
102
|
-
return {
|
|
103
|
-
Message: message,
|
|
104
|
-
Success: false,
|
|
105
|
-
Error: 'Service account not found'
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
119
|
if (!message) {
|
|
109
120
|
return {
|
|
110
121
|
Message: message,
|
|
@@ -140,12 +151,14 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
140
151
|
saveToSentItems: message.ContextData?.saveToSentItems ?? false
|
|
141
152
|
};
|
|
142
153
|
if (message.Headers) {
|
|
154
|
+
// Convert Headers (Record<string, string>) to internetMessageHeaders (Array[{key:value}])
|
|
143
155
|
sendMail.message.internetMessageHeaders = Object.entries(message.Headers).map(([key, value]) => ({
|
|
144
156
|
name: key.startsWith('X-') ? key : `X-${key}`,
|
|
145
157
|
value: value
|
|
146
158
|
}));
|
|
147
159
|
}
|
|
148
|
-
|
|
160
|
+
// Use email address directly in API path instead of looking up user ID
|
|
161
|
+
const sendMessagePath = `${this.getApiUri()}/${encodeURIComponent(senderEmail)}/sendMail`;
|
|
149
162
|
await client.api(sendMessagePath).post(sendMail);
|
|
150
163
|
return {
|
|
151
164
|
Message: message,
|
|
@@ -154,7 +167,7 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
154
167
|
};
|
|
155
168
|
}
|
|
156
169
|
catch (ex) {
|
|
157
|
-
|
|
170
|
+
LogError(ex);
|
|
158
171
|
return {
|
|
159
172
|
Message: message,
|
|
160
173
|
Success: false,
|
|
@@ -162,17 +175,15 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
162
175
|
};
|
|
163
176
|
}
|
|
164
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Replies to an email message via MS Graph.
|
|
180
|
+
*
|
|
181
|
+
* @requires MS Graph Scope: Mail.Send (Application)
|
|
182
|
+
*/
|
|
165
183
|
async ReplyToMessage(params, credentials) {
|
|
166
184
|
try {
|
|
167
185
|
const creds = this.resolveCredentials(credentials);
|
|
168
186
|
const client = this.getGraphClient(creds);
|
|
169
|
-
const user = await this.GetServiceAccountWithClient(client, creds.accountEmail);
|
|
170
|
-
if (!user) {
|
|
171
|
-
return {
|
|
172
|
-
Success: false,
|
|
173
|
-
ErrorMessage: 'Service account not found'
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
187
|
const reply = {
|
|
177
188
|
message: {
|
|
178
189
|
toRecipients: [
|
|
@@ -195,7 +206,8 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
195
206
|
},
|
|
196
207
|
comment: params.Message.ProcessedBody || params.Message.ProcessedHTMLBody
|
|
197
208
|
};
|
|
198
|
-
|
|
209
|
+
// Use email address directly in API path
|
|
210
|
+
const sendMessagePath = `${this.getApiUri()}/${encodeURIComponent(creds.accountEmail)}/messages/${params.MessageID}/reply`;
|
|
199
211
|
const result = await client.api(sendMessagePath).post(reply);
|
|
200
212
|
return {
|
|
201
213
|
Success: true,
|
|
@@ -203,25 +215,23 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
203
215
|
};
|
|
204
216
|
}
|
|
205
217
|
catch (ex) {
|
|
206
|
-
|
|
218
|
+
LogError(ex);
|
|
207
219
|
return {
|
|
208
220
|
Success: false,
|
|
209
221
|
ErrorMessage: 'Error sending message'
|
|
210
222
|
};
|
|
211
223
|
}
|
|
212
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Retrieves email messages from a mailbox via MS Graph.
|
|
227
|
+
*
|
|
228
|
+
* @requires MS Graph Scope: Mail.Read (Application), or Mail.ReadWrite if using ContextData.MarkAsRead option
|
|
229
|
+
*/
|
|
213
230
|
async GetMessages(params, credentials) {
|
|
214
231
|
const creds = this.resolveCredentials(credentials);
|
|
215
232
|
const client = this.getGraphClient(creds);
|
|
216
233
|
const contextData = params.ContextData;
|
|
217
234
|
const emailToUse = params.Identifier || contextData?.Email || creds.accountEmail;
|
|
218
|
-
const user = await this.GetServiceAccountWithClient(client, emailToUse);
|
|
219
|
-
if (!user || !user.id) {
|
|
220
|
-
return {
|
|
221
|
-
Success: false,
|
|
222
|
-
Messages: []
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
235
|
let filter = "";
|
|
226
236
|
const top = params.NumMessages;
|
|
227
237
|
if (params.UnreadOnly) {
|
|
@@ -230,7 +240,8 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
230
240
|
if (contextData && contextData.Filter) {
|
|
231
241
|
filter = contextData.Filter;
|
|
232
242
|
}
|
|
233
|
-
|
|
243
|
+
// Use email address directly in API path
|
|
244
|
+
const messagesPath = `${this.getApiUri()}/${encodeURIComponent(emailToUse)}/messages`;
|
|
234
245
|
const response = await client.api(messagesPath)
|
|
235
246
|
.filter(filter).top(top).get();
|
|
236
247
|
if (!response) {
|
|
@@ -246,14 +257,19 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
246
257
|
Messages: []
|
|
247
258
|
};
|
|
248
259
|
let headers = null;
|
|
260
|
+
// GetHeaders is an async function for one specific message. I need
|
|
261
|
+
// to resolve all of them into a structure, indexed by sourceMessage.id
|
|
262
|
+
// and then apply that in the mapped message below.
|
|
249
263
|
if (params.IncludeHeaders) {
|
|
250
|
-
const headersPromises = sourceMessages.map((message) => this.
|
|
264
|
+
const headersPromises = sourceMessages.map((message) => this.GetHeadersWithEmail(client, emailToUse, params, message.id));
|
|
251
265
|
headers = await Promise.all(headersPromises);
|
|
252
266
|
}
|
|
253
267
|
const messages = sourceMessages.map((message, index) => {
|
|
254
268
|
const msgTyped = message;
|
|
255
269
|
const replyTo = msgTyped.replyTo?.map((replyTo) => replyTo.emailAddress?.address || '') || [];
|
|
256
270
|
const primaryToRecipient = replyTo.length > 0 ? replyTo[0] : '';
|
|
271
|
+
// the blow hokey thing with ReturnAsPlainTex without the t is to have
|
|
272
|
+
// back-compat with the old code when this typo existed
|
|
257
273
|
return {
|
|
258
274
|
From: msgTyped.from?.emailAddress?.address || '',
|
|
259
275
|
To: primaryToRecipient,
|
|
@@ -272,11 +288,16 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
272
288
|
messageResults.Messages = messages;
|
|
273
289
|
if (contextData && contextData.MarkAsRead) {
|
|
274
290
|
for (const message of messages) {
|
|
275
|
-
this.
|
|
291
|
+
this.MarkMessageAsReadWithEmail(client, emailToUse, message.ExternalSystemRecordID);
|
|
276
292
|
}
|
|
277
293
|
}
|
|
278
294
|
return messageResults;
|
|
279
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Forwards an email message via MS Graph.
|
|
298
|
+
*
|
|
299
|
+
* @requires MS Graph Scope: Mail.Send (Application)
|
|
300
|
+
*/
|
|
280
301
|
async ForwardMessage(params, credentials) {
|
|
281
302
|
try {
|
|
282
303
|
if (!params.MessageID) {
|
|
@@ -287,13 +308,6 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
287
308
|
}
|
|
288
309
|
const creds = this.resolveCredentials(credentials);
|
|
289
310
|
const client = this.getGraphClient(creds);
|
|
290
|
-
const user = await this.GetServiceAccountWithClient(client, creds.accountEmail);
|
|
291
|
-
if (!user) {
|
|
292
|
-
return {
|
|
293
|
-
Success: false,
|
|
294
|
-
ErrorMessage: 'Service account not found'
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
311
|
const forward = {
|
|
298
312
|
comment: params.Message,
|
|
299
313
|
toRecipients: params.ToRecipients.map((recipient) => ({
|
|
@@ -312,7 +326,8 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
312
326
|
}
|
|
313
327
|
}))
|
|
314
328
|
};
|
|
315
|
-
|
|
329
|
+
// Use email address directly in API path
|
|
330
|
+
const sendMessagePath = `${this.getApiUri()}/${encodeURIComponent(creds.accountEmail)}/messages/${params.MessageID}/forward`;
|
|
316
331
|
const forwardResult = await client.api(sendMessagePath).post(forward);
|
|
317
332
|
return {
|
|
318
333
|
Success: true,
|
|
@@ -320,16 +335,23 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
320
335
|
};
|
|
321
336
|
}
|
|
322
337
|
catch (ex) {
|
|
323
|
-
|
|
338
|
+
LogError(ex);
|
|
324
339
|
return {
|
|
325
340
|
ErrorMessage: 'An Error occurred while forwarding the message',
|
|
326
341
|
Success: false
|
|
327
342
|
};
|
|
328
343
|
}
|
|
329
344
|
}
|
|
330
|
-
|
|
345
|
+
/**
|
|
346
|
+
* Gets headers for a specific message using email address directly.
|
|
347
|
+
* This is the preferred method for new code as it avoids an extra API call to look up the user.
|
|
348
|
+
*
|
|
349
|
+
* @protected
|
|
350
|
+
* @requires MS Graph Scope: Mail.Read (Application)
|
|
351
|
+
*/
|
|
352
|
+
async GetHeadersWithEmail(client, emailAddress, params, messageID) {
|
|
331
353
|
if (params.IncludeHeaders && messageID) {
|
|
332
|
-
const messageHeaderPath = `${this.getApiUri()}/${
|
|
354
|
+
const messageHeaderPath = `${this.getApiUri()}/${encodeURIComponent(emailAddress)}/messages/${messageID}?$select=internetMessageHeaders`;
|
|
333
355
|
const messageHeaderResponse = await client.api(messageHeaderPath).get();
|
|
334
356
|
return messageHeaderResponse.internetMessageHeaders?.map((header) => ({
|
|
335
357
|
[header.name]: header.value
|
|
@@ -337,69 +359,144 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
337
359
|
}
|
|
338
360
|
return {};
|
|
339
361
|
}
|
|
340
|
-
|
|
341
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Marks a message as read using email address directly.
|
|
364
|
+
* This is the preferred method for new code as it avoids an extra API call to look up the user.
|
|
365
|
+
*
|
|
366
|
+
* @protected
|
|
367
|
+
* @requires MS Graph Scope: Mail.ReadWrite (Application)
|
|
368
|
+
*/
|
|
369
|
+
async MarkMessageAsReadWithEmail(client, emailAddress, messageID) {
|
|
370
|
+
try {
|
|
371
|
+
const updatePath = `${this.getApiUri()}/${encodeURIComponent(emailAddress)}/messages/${messageID}`;
|
|
372
|
+
const updatedMessage = {
|
|
373
|
+
isRead: true
|
|
374
|
+
};
|
|
375
|
+
await client.api(updatePath).update(updatedMessage);
|
|
376
|
+
LogStatus(`Message ${messageID} marked as read`);
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
catch (ex) {
|
|
380
|
+
LogError(ex);
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
342
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Gets the user account information by making an API call to MS Graph.
|
|
386
|
+
* This method looks up the full User object from the Graph API.
|
|
387
|
+
*
|
|
388
|
+
* @protected
|
|
389
|
+
* @requires MS Graph Scope: User.Read.All (Application) - Required to look up user information
|
|
390
|
+
*/
|
|
343
391
|
async GetServiceAccountWithClient(client, email) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
392
|
+
try {
|
|
393
|
+
const endpoint = `${this.getApiUri()}/${encodeURIComponent(email)}`;
|
|
394
|
+
const user = await client.api(endpoint).get();
|
|
395
|
+
if (!user) {
|
|
396
|
+
LogError('Error: could not get user info');
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
const userID = user.id;
|
|
400
|
+
if (!userID) {
|
|
401
|
+
LogError('Error: userID not set for user');
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
return user;
|
|
349
405
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
(0, core_1.LogError)('Error: userID not set for user');
|
|
406
|
+
catch (ex) {
|
|
407
|
+
LogError('Error getting service account', undefined, ex);
|
|
353
408
|
return null;
|
|
354
409
|
}
|
|
355
|
-
return user;
|
|
356
410
|
}
|
|
411
|
+
/**
|
|
412
|
+
* Gets the service account using default client.
|
|
413
|
+
* Caches the result for subsequent calls.
|
|
414
|
+
*
|
|
415
|
+
* @protected
|
|
416
|
+
* @requires MS Graph Scope: User.Read.All (Application) - Required to look up user information
|
|
417
|
+
*/
|
|
357
418
|
async GetServiceAccount(email) {
|
|
358
|
-
if (this.ServiceAccount) {
|
|
359
|
-
return this.ServiceAccount;
|
|
360
|
-
}
|
|
361
419
|
const accountEmail = email || Config.AZURE_ACCOUNT_EMAIL;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
420
|
+
return this.GetServiceAccountWithClient(Auth.GraphClient, accountEmail);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Gets headers for a message using User object (requires prior user lookup).
|
|
424
|
+
* For better performance, consider using GetHeadersWithEmail() instead.
|
|
425
|
+
*
|
|
426
|
+
* @protected
|
|
427
|
+
* @requires MS Graph Scope: Mail.Read (Application)
|
|
428
|
+
*/
|
|
429
|
+
async GetHeadersWithClient(client, params, user, messageID) {
|
|
430
|
+
const userId = user.id || user.userPrincipalName;
|
|
431
|
+
if (!userId) {
|
|
432
|
+
return {};
|
|
433
|
+
}
|
|
434
|
+
if (params.IncludeHeaders && messageID) {
|
|
435
|
+
const messageHeaderPath = `${this.getApiUri()}/${encodeURIComponent(userId)}/messages/${messageID}?$select=internetMessageHeaders`;
|
|
436
|
+
const messageHeaderResponse = await client.api(messageHeaderPath).get();
|
|
437
|
+
return messageHeaderResponse.internetMessageHeaders?.map((header) => ({
|
|
438
|
+
[header.name]: header.value
|
|
439
|
+
})) || {};
|
|
365
440
|
}
|
|
366
|
-
return
|
|
441
|
+
return {};
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Gets headers using default client.
|
|
445
|
+
*
|
|
446
|
+
* @protected
|
|
447
|
+
* @requires MS Graph Scope: Mail.Read (Application)
|
|
448
|
+
*/
|
|
449
|
+
async GetHeaders(params, user, messageID) {
|
|
450
|
+
return this.GetHeadersWithClient(Auth.GraphClient, params, user, messageID);
|
|
367
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* Marks a message as read using User ID.
|
|
454
|
+
* For better performance, consider using MarkMessageAsReadWithEmail() instead.
|
|
455
|
+
*
|
|
456
|
+
* @protected
|
|
457
|
+
* @requires MS Graph Scope: Mail.ReadWrite (Application)
|
|
458
|
+
*/
|
|
368
459
|
async MarkMessageAsReadWithClient(client, userID, messageID) {
|
|
369
460
|
try {
|
|
370
|
-
const updatePath = `${this.getApiUri()}/${userID}/messages/${messageID}`;
|
|
461
|
+
const updatePath = `${this.getApiUri()}/${encodeURIComponent(userID)}/messages/${messageID}`;
|
|
371
462
|
const updatedMessage = {
|
|
372
463
|
isRead: true
|
|
373
464
|
};
|
|
374
465
|
await client.api(updatePath).update(updatedMessage);
|
|
375
|
-
|
|
466
|
+
LogStatus(`Message ${messageID} marked as read`);
|
|
376
467
|
return true;
|
|
377
468
|
}
|
|
378
469
|
catch (ex) {
|
|
379
|
-
|
|
470
|
+
LogError(ex);
|
|
380
471
|
return false;
|
|
381
472
|
}
|
|
382
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* Marks a message as read using default client.
|
|
476
|
+
*
|
|
477
|
+
* @protected
|
|
478
|
+
* @requires MS Graph Scope: Mail.ReadWrite (Application)
|
|
479
|
+
*/
|
|
383
480
|
async MarkMessageAsRead(userID, messageID) {
|
|
384
481
|
return this.MarkMessageAsReadWithClient(Auth.GraphClient, userID, messageID);
|
|
385
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* Creates a draft email message via MS Graph.
|
|
485
|
+
*
|
|
486
|
+
* @requires MS Graph Scope: Mail.ReadWrite (Application)
|
|
487
|
+
*/
|
|
386
488
|
async CreateDraft(params, credentials) {
|
|
387
489
|
try {
|
|
388
490
|
const creds = this.resolveCredentials(credentials);
|
|
389
491
|
const client = this.getGraphClient(creds);
|
|
492
|
+
// Smart selection: use message.From if provided and different from resolved accountEmail
|
|
390
493
|
let senderEmail = creds.accountEmail;
|
|
391
494
|
if (params.Message.From &&
|
|
392
495
|
params.Message.From.trim() !== '' &&
|
|
393
496
|
params.Message.From !== creds.accountEmail) {
|
|
394
497
|
senderEmail = params.Message.From;
|
|
395
498
|
}
|
|
396
|
-
|
|
397
|
-
if (!user) {
|
|
398
|
-
return {
|
|
399
|
-
Success: false,
|
|
400
|
-
ErrorMessage: 'Service account not found'
|
|
401
|
-
};
|
|
402
|
-
}
|
|
499
|
+
// Build message object (similar to SendSingleMessage but saved as draft)
|
|
403
500
|
const draftMessage = {
|
|
404
501
|
subject: params.Message.ProcessedSubject,
|
|
405
502
|
body: {
|
|
@@ -417,15 +514,17 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
417
514
|
}))
|
|
418
515
|
};
|
|
419
516
|
if (params.Message.Headers) {
|
|
517
|
+
// Convert Headers (Record<string, string>) to internetMessageHeaders (Array[{key:value}])
|
|
420
518
|
draftMessage.internetMessageHeaders = Object.entries(params.Message.Headers)
|
|
421
519
|
.map(([key, value]) => ({
|
|
422
520
|
name: key.startsWith('X-') ? key : `X-${key}`,
|
|
423
521
|
value: value
|
|
424
522
|
}));
|
|
425
523
|
}
|
|
426
|
-
|
|
524
|
+
// Create draft by POSTing to messages endpoint (not sendMail) - use email address directly
|
|
525
|
+
const createDraftPath = `${this.getApiUri()}/${encodeURIComponent(senderEmail)}/messages`;
|
|
427
526
|
const result = await client.api(createDraftPath).post(draftMessage);
|
|
428
|
-
|
|
527
|
+
LogStatus(`Draft created via MS Graph: ${result.id}`);
|
|
429
528
|
return {
|
|
430
529
|
Success: true,
|
|
431
530
|
DraftID: result.id,
|
|
@@ -433,13 +532,20 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
433
532
|
};
|
|
434
533
|
}
|
|
435
534
|
catch (ex) {
|
|
436
|
-
|
|
535
|
+
LogError('Error creating draft via MS Graph', undefined, ex);
|
|
437
536
|
return {
|
|
438
537
|
Success: false,
|
|
439
538
|
ErrorMessage: 'Error creating draft'
|
|
440
539
|
};
|
|
441
540
|
}
|
|
442
541
|
}
|
|
542
|
+
// ========================================================================
|
|
543
|
+
// EXTENDED OPERATIONS - MS Graph supports full mailbox access
|
|
544
|
+
// ========================================================================
|
|
545
|
+
/**
|
|
546
|
+
* Returns the list of operations supported by MS Graph provider.
|
|
547
|
+
* MS Graph supports all mailbox operations.
|
|
548
|
+
*/
|
|
443
549
|
getSupportedOperations() {
|
|
444
550
|
return [
|
|
445
551
|
'SendSingleMessage',
|
|
@@ -458,19 +564,18 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
458
564
|
'DownloadAttachment'
|
|
459
565
|
];
|
|
460
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* Gets a single message by ID from MS Graph.
|
|
569
|
+
*
|
|
570
|
+
* @requires MS Graph Scope: Mail.Read (Application)
|
|
571
|
+
*/
|
|
461
572
|
async GetSingleMessage(params, credentials) {
|
|
462
573
|
try {
|
|
463
574
|
const creds = this.resolveCredentials(credentials);
|
|
464
575
|
const client = this.getGraphClient(creds);
|
|
465
576
|
const emailToUse = params.ContextData?.Email || creds.accountEmail;
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
return {
|
|
469
|
-
Success: false,
|
|
470
|
-
ErrorMessage: 'Service account not found'
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
const messagePath = `${this.getApiUri()}/${user.id}/messages/${params.MessageID}`;
|
|
577
|
+
// Use email address directly in API path
|
|
578
|
+
const messagePath = `${this.getApiUri()}/${encodeURIComponent(emailToUse)}/messages/${params.MessageID}`;
|
|
474
579
|
const msgResponse = await client.api(messagePath).get();
|
|
475
580
|
if (!msgResponse) {
|
|
476
581
|
return {
|
|
@@ -499,68 +604,70 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
499
604
|
};
|
|
500
605
|
}
|
|
501
606
|
catch (ex) {
|
|
502
|
-
|
|
607
|
+
LogError('Error getting single message via MS Graph', undefined, ex);
|
|
503
608
|
return {
|
|
504
609
|
Success: false,
|
|
505
610
|
ErrorMessage: `Error getting message: ${ex instanceof Error ? ex.message : String(ex)}`
|
|
506
611
|
};
|
|
507
612
|
}
|
|
508
613
|
}
|
|
614
|
+
/**
|
|
615
|
+
* Deletes a message using MS Graph.
|
|
616
|
+
* If PermanentDelete is false, moves to Deleted Items folder.
|
|
617
|
+
*
|
|
618
|
+
* @requires MS Graph Scope: Mail.ReadWrite (Application)
|
|
619
|
+
*/
|
|
509
620
|
async DeleteMessage(params, credentials) {
|
|
510
621
|
try {
|
|
511
622
|
const creds = this.resolveCredentials(credentials);
|
|
512
623
|
const client = this.getGraphClient(creds);
|
|
513
624
|
const emailToUse = params.ContextData?.Email || creds.accountEmail;
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
return {
|
|
517
|
-
Success: false,
|
|
518
|
-
ErrorMessage: 'Service account not found'
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
const messagePath = `${this.getApiUri()}/${user.id}/messages/${params.MessageID}`;
|
|
625
|
+
// Use email address directly in API path
|
|
626
|
+
const messagePath = `${this.getApiUri()}/${encodeURIComponent(emailToUse)}/messages/${params.MessageID}`;
|
|
522
627
|
if (params.PermanentDelete) {
|
|
628
|
+
// Permanently delete the message
|
|
523
629
|
await client.api(messagePath).delete();
|
|
524
630
|
}
|
|
525
631
|
else {
|
|
526
|
-
|
|
632
|
+
// Move to Deleted Items (soft delete)
|
|
633
|
+
const deletedItemsFolder = await this.findSystemFolder(client, emailToUse, 'deleteditems');
|
|
527
634
|
if (deletedItemsFolder) {
|
|
528
635
|
await client.api(`${messagePath}/move`).post({
|
|
529
636
|
destinationId: deletedItemsFolder
|
|
530
637
|
});
|
|
531
638
|
}
|
|
532
639
|
else {
|
|
640
|
+
// Fall back to permanent delete if we can't find Deleted Items
|
|
533
641
|
await client.api(messagePath).delete();
|
|
534
642
|
}
|
|
535
643
|
}
|
|
536
|
-
|
|
644
|
+
LogStatus(`Message ${params.MessageID} deleted via MS Graph`);
|
|
537
645
|
return { Success: true };
|
|
538
646
|
}
|
|
539
647
|
catch (ex) {
|
|
540
|
-
|
|
648
|
+
LogError('Error deleting message via MS Graph', undefined, ex);
|
|
541
649
|
return {
|
|
542
650
|
Success: false,
|
|
543
651
|
ErrorMessage: `Error deleting message: ${ex instanceof Error ? ex.message : String(ex)}`
|
|
544
652
|
};
|
|
545
653
|
}
|
|
546
654
|
}
|
|
655
|
+
/**
|
|
656
|
+
* Moves a message to a different folder using MS Graph.
|
|
657
|
+
*
|
|
658
|
+
* @requires MS Graph Scope: Mail.ReadWrite (Application)
|
|
659
|
+
*/
|
|
547
660
|
async MoveMessage(params, credentials) {
|
|
548
661
|
try {
|
|
549
662
|
const creds = this.resolveCredentials(credentials);
|
|
550
663
|
const client = this.getGraphClient(creds);
|
|
551
664
|
const emailToUse = params.ContextData?.Email || creds.accountEmail;
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
return {
|
|
555
|
-
Success: false,
|
|
556
|
-
ErrorMessage: 'Service account not found'
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
const movePath = `${this.getApiUri()}/${user.id}/messages/${params.MessageID}/move`;
|
|
665
|
+
// Use email address directly in API path
|
|
666
|
+
const movePath = `${this.getApiUri()}/${encodeURIComponent(emailToUse)}/messages/${params.MessageID}/move`;
|
|
560
667
|
const result = await client.api(movePath).post({
|
|
561
668
|
destinationId: params.DestinationFolderID
|
|
562
669
|
});
|
|
563
|
-
|
|
670
|
+
LogStatus(`Message ${params.MessageID} moved to folder ${params.DestinationFolderID}`);
|
|
564
671
|
return {
|
|
565
672
|
Success: true,
|
|
566
673
|
NewMessageID: result?.id,
|
|
@@ -568,31 +675,30 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
568
675
|
};
|
|
569
676
|
}
|
|
570
677
|
catch (ex) {
|
|
571
|
-
|
|
678
|
+
LogError('Error moving message via MS Graph', undefined, ex);
|
|
572
679
|
return {
|
|
573
680
|
Success: false,
|
|
574
681
|
ErrorMessage: `Error moving message: ${ex instanceof Error ? ex.message : String(ex)}`
|
|
575
682
|
};
|
|
576
683
|
}
|
|
577
684
|
}
|
|
685
|
+
/**
|
|
686
|
+
* Lists mail folders using MS Graph.
|
|
687
|
+
*
|
|
688
|
+
* @requires MS Graph Scope: Mail.Read (Application)
|
|
689
|
+
*/
|
|
578
690
|
async ListFolders(params, credentials) {
|
|
579
691
|
try {
|
|
580
692
|
const creds = this.resolveCredentials(credentials);
|
|
581
693
|
const client = this.getGraphClient(creds);
|
|
582
694
|
const emailToUse = params.ContextData?.Email || creds.accountEmail;
|
|
583
|
-
|
|
584
|
-
if (!user || !user.id) {
|
|
585
|
-
return {
|
|
586
|
-
Success: false,
|
|
587
|
-
ErrorMessage: 'Service account not found'
|
|
588
|
-
};
|
|
589
|
-
}
|
|
695
|
+
// Use email address directly in API path
|
|
590
696
|
let foldersPath;
|
|
591
697
|
if (params.ParentFolderID) {
|
|
592
|
-
foldersPath = `${this.getApiUri()}/${
|
|
698
|
+
foldersPath = `${this.getApiUri()}/${encodeURIComponent(emailToUse)}/mailFolders/${params.ParentFolderID}/childFolders`;
|
|
593
699
|
}
|
|
594
700
|
else {
|
|
595
|
-
foldersPath = `${this.getApiUri()}/${
|
|
701
|
+
foldersPath = `${this.getApiUri()}/${encodeURIComponent(emailToUse)}/mailFolders`;
|
|
596
702
|
}
|
|
597
703
|
const response = await client.api(foldersPath).get();
|
|
598
704
|
if (!response?.value) {
|
|
@@ -617,56 +723,55 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
617
723
|
};
|
|
618
724
|
}
|
|
619
725
|
catch (ex) {
|
|
620
|
-
|
|
726
|
+
LogError('Error listing folders via MS Graph', undefined, ex);
|
|
621
727
|
return {
|
|
622
728
|
Success: false,
|
|
623
729
|
ErrorMessage: `Error listing folders: ${ex instanceof Error ? ex.message : String(ex)}`
|
|
624
730
|
};
|
|
625
731
|
}
|
|
626
732
|
}
|
|
733
|
+
/**
|
|
734
|
+
* Marks messages as read or unread using MS Graph.
|
|
735
|
+
*
|
|
736
|
+
* @requires MS Graph Scope: Mail.ReadWrite (Application)
|
|
737
|
+
*/
|
|
627
738
|
async MarkAsRead(params, credentials) {
|
|
628
739
|
try {
|
|
629
740
|
const creds = this.resolveCredentials(credentials);
|
|
630
741
|
const client = this.getGraphClient(creds);
|
|
631
742
|
const emailToUse = params.ContextData?.Email || creds.accountEmail;
|
|
632
|
-
|
|
633
|
-
if (!user || !user.id) {
|
|
634
|
-
return {
|
|
635
|
-
Success: false,
|
|
636
|
-
ErrorMessage: 'Service account not found'
|
|
637
|
-
};
|
|
638
|
-
}
|
|
743
|
+
// Use email address directly in API path - update each message
|
|
639
744
|
const updatePromises = params.MessageIDs.map(async (messageId) => {
|
|
640
|
-
const updatePath = `${this.getApiUri()}/${
|
|
745
|
+
const updatePath = `${this.getApiUri()}/${encodeURIComponent(emailToUse)}/messages/${messageId}`;
|
|
641
746
|
return client.api(updatePath).update({ isRead: params.IsRead });
|
|
642
747
|
});
|
|
643
748
|
await Promise.all(updatePromises);
|
|
644
|
-
|
|
749
|
+
LogStatus(`Marked ${params.MessageIDs.length} message(s) as ${params.IsRead ? 'read' : 'unread'}`);
|
|
645
750
|
return { Success: true };
|
|
646
751
|
}
|
|
647
752
|
catch (ex) {
|
|
648
|
-
|
|
753
|
+
LogError('Error marking messages as read via MS Graph', undefined, ex);
|
|
649
754
|
return {
|
|
650
755
|
Success: false,
|
|
651
756
|
ErrorMessage: `Error marking messages: ${ex instanceof Error ? ex.message : String(ex)}`
|
|
652
757
|
};
|
|
653
758
|
}
|
|
654
759
|
}
|
|
760
|
+
/**
|
|
761
|
+
* Archives a message by moving it to the Archive folder using MS Graph.
|
|
762
|
+
*
|
|
763
|
+
* @requires MS Graph Scope: Mail.ReadWrite (Application)
|
|
764
|
+
*/
|
|
655
765
|
async ArchiveMessage(params, credentials) {
|
|
656
766
|
try {
|
|
657
767
|
const creds = this.resolveCredentials(credentials);
|
|
658
768
|
const client = this.getGraphClient(creds);
|
|
659
769
|
const emailToUse = params.ContextData?.Email || creds.accountEmail;
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
return {
|
|
663
|
-
Success: false,
|
|
664
|
-
ErrorMessage: 'Service account not found'
|
|
665
|
-
};
|
|
666
|
-
}
|
|
667
|
-
let archiveFolderId = await this.findSystemFolder(client, user.id, 'archive');
|
|
770
|
+
// Find or create the Archive folder - use email address directly
|
|
771
|
+
let archiveFolderId = await this.findSystemFolder(client, emailToUse, 'archive');
|
|
668
772
|
if (!archiveFolderId) {
|
|
669
|
-
|
|
773
|
+
// Try to create an Archive folder
|
|
774
|
+
archiveFolderId = await this.createMailFolder(client, emailToUse, 'Archive');
|
|
670
775
|
}
|
|
671
776
|
if (!archiveFolderId) {
|
|
672
777
|
return {
|
|
@@ -674,41 +779,43 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
674
779
|
ErrorMessage: 'Could not find or create Archive folder'
|
|
675
780
|
};
|
|
676
781
|
}
|
|
677
|
-
|
|
782
|
+
// Move the message to Archive - use email address directly in API path
|
|
783
|
+
const movePath = `${this.getApiUri()}/${encodeURIComponent(emailToUse)}/messages/${params.MessageID}/move`;
|
|
678
784
|
await client.api(movePath).post({
|
|
679
785
|
destinationId: archiveFolderId
|
|
680
786
|
});
|
|
681
|
-
|
|
787
|
+
LogStatus(`Message ${params.MessageID} archived`);
|
|
682
788
|
return { Success: true };
|
|
683
789
|
}
|
|
684
790
|
catch (ex) {
|
|
685
|
-
|
|
791
|
+
LogError('Error archiving message via MS Graph', undefined, ex);
|
|
686
792
|
return {
|
|
687
793
|
Success: false,
|
|
688
794
|
ErrorMessage: `Error archiving message: ${ex instanceof Error ? ex.message : String(ex)}`
|
|
689
795
|
};
|
|
690
796
|
}
|
|
691
797
|
}
|
|
798
|
+
/**
|
|
799
|
+
* Searches messages using MS Graph search or filter.
|
|
800
|
+
*
|
|
801
|
+
* @requires MS Graph Scope: Mail.Read (Application)
|
|
802
|
+
*/
|
|
692
803
|
async SearchMessages(params, credentials) {
|
|
693
804
|
try {
|
|
694
805
|
const creds = this.resolveCredentials(credentials);
|
|
695
806
|
const client = this.getGraphClient(creds);
|
|
696
807
|
const emailToUse = params.ContextData?.Email || creds.accountEmail;
|
|
697
|
-
|
|
698
|
-
if (!user || !user.id) {
|
|
699
|
-
return {
|
|
700
|
-
Success: false,
|
|
701
|
-
ErrorMessage: 'Service account not found'
|
|
702
|
-
};
|
|
703
|
-
}
|
|
808
|
+
// Build search path - use email address directly in API path
|
|
704
809
|
let messagesPath;
|
|
705
810
|
if (params.FolderID) {
|
|
706
|
-
messagesPath = `${this.getApiUri()}/${
|
|
811
|
+
messagesPath = `${this.getApiUri()}/${encodeURIComponent(emailToUse)}/mailFolders/${params.FolderID}/messages`;
|
|
707
812
|
}
|
|
708
813
|
else {
|
|
709
|
-
messagesPath = `${this.getApiUri()}/${
|
|
814
|
+
messagesPath = `${this.getApiUri()}/${encodeURIComponent(emailToUse)}/messages`;
|
|
710
815
|
}
|
|
816
|
+
// Build filter conditions
|
|
711
817
|
const filters = [];
|
|
818
|
+
// Date range filters
|
|
712
819
|
if (params.FromDate) {
|
|
713
820
|
filters.push(`receivedDateTime ge ${params.FromDate.toISOString()}`);
|
|
714
821
|
}
|
|
@@ -716,6 +823,7 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
716
823
|
filters.push(`receivedDateTime le ${params.ToDate.toISOString()}`);
|
|
717
824
|
}
|
|
718
825
|
let apiRequest = client.api(messagesPath);
|
|
826
|
+
// Use $search for text queries (MS Graph supports KQL)
|
|
719
827
|
if (params.Query) {
|
|
720
828
|
apiRequest = apiRequest.search(`"${params.Query}"`);
|
|
721
829
|
}
|
|
@@ -750,26 +858,25 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
750
858
|
};
|
|
751
859
|
}
|
|
752
860
|
catch (ex) {
|
|
753
|
-
|
|
861
|
+
LogError('Error searching messages via MS Graph', undefined, ex);
|
|
754
862
|
return {
|
|
755
863
|
Success: false,
|
|
756
864
|
ErrorMessage: `Error searching messages: ${ex instanceof Error ? ex.message : String(ex)}`
|
|
757
865
|
};
|
|
758
866
|
}
|
|
759
867
|
}
|
|
868
|
+
/**
|
|
869
|
+
* Lists attachments on a message using MS Graph.
|
|
870
|
+
*
|
|
871
|
+
* @requires MS Graph Scope: Mail.Read (Application)
|
|
872
|
+
*/
|
|
760
873
|
async ListAttachments(params, credentials) {
|
|
761
874
|
try {
|
|
762
875
|
const creds = this.resolveCredentials(credentials);
|
|
763
876
|
const client = this.getGraphClient(creds);
|
|
764
877
|
const emailToUse = params.ContextData?.Email || creds.accountEmail;
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
return {
|
|
768
|
-
Success: false,
|
|
769
|
-
ErrorMessage: 'Service account not found'
|
|
770
|
-
};
|
|
771
|
-
}
|
|
772
|
-
const attachmentsPath = `${this.getApiUri()}/${user.id}/messages/${params.MessageID}/attachments`;
|
|
878
|
+
// Use email address directly in API path
|
|
879
|
+
const attachmentsPath = `${this.getApiUri()}/${encodeURIComponent(emailToUse)}/messages/${params.MessageID}/attachments`;
|
|
773
880
|
const response = await client.api(attachmentsPath).get();
|
|
774
881
|
if (!response?.value) {
|
|
775
882
|
return {
|
|
@@ -792,26 +899,25 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
792
899
|
};
|
|
793
900
|
}
|
|
794
901
|
catch (ex) {
|
|
795
|
-
|
|
902
|
+
LogError('Error listing attachments via MS Graph', undefined, ex);
|
|
796
903
|
return {
|
|
797
904
|
Success: false,
|
|
798
905
|
ErrorMessage: `Error listing attachments: ${ex instanceof Error ? ex.message : String(ex)}`
|
|
799
906
|
};
|
|
800
907
|
}
|
|
801
908
|
}
|
|
909
|
+
/**
|
|
910
|
+
* Downloads an attachment from a message using MS Graph.
|
|
911
|
+
*
|
|
912
|
+
* @requires MS Graph Scope: Mail.Read (Application)
|
|
913
|
+
*/
|
|
802
914
|
async DownloadAttachment(params, credentials) {
|
|
803
915
|
try {
|
|
804
916
|
const creds = this.resolveCredentials(credentials);
|
|
805
917
|
const client = this.getGraphClient(creds);
|
|
806
918
|
const emailToUse = params.ContextData?.Email || creds.accountEmail;
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
return {
|
|
810
|
-
Success: false,
|
|
811
|
-
ErrorMessage: 'Service account not found'
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
const attachmentPath = `${this.getApiUri()}/${user.id}/messages/${params.MessageID}/attachments/${params.AttachmentID}`;
|
|
919
|
+
// Use email address directly in API path
|
|
920
|
+
const attachmentPath = `${this.getApiUri()}/${encodeURIComponent(emailToUse)}/messages/${params.MessageID}/attachments/${params.AttachmentID}`;
|
|
815
921
|
const response = await client.api(attachmentPath).get();
|
|
816
922
|
if (!response) {
|
|
817
923
|
return {
|
|
@@ -819,6 +925,7 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
819
925
|
ErrorMessage: 'Attachment not found'
|
|
820
926
|
};
|
|
821
927
|
}
|
|
928
|
+
// MS Graph returns file attachments with contentBytes as base64
|
|
822
929
|
const contentBase64 = response.contentBytes;
|
|
823
930
|
return {
|
|
824
931
|
Success: true,
|
|
@@ -830,15 +937,25 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
830
937
|
};
|
|
831
938
|
}
|
|
832
939
|
catch (ex) {
|
|
833
|
-
|
|
940
|
+
LogError('Error downloading attachment via MS Graph', undefined, ex);
|
|
834
941
|
return {
|
|
835
942
|
Success: false,
|
|
836
943
|
ErrorMessage: `Error downloading attachment: ${ex instanceof Error ? ex.message : String(ex)}`
|
|
837
944
|
};
|
|
838
945
|
}
|
|
839
946
|
}
|
|
840
|
-
|
|
947
|
+
// ========================================================================
|
|
948
|
+
// HELPER METHODS
|
|
949
|
+
// ========================================================================
|
|
950
|
+
/**
|
|
951
|
+
* Finds a system folder by well-known name.
|
|
952
|
+
*
|
|
953
|
+
* @private
|
|
954
|
+
* @requires MS Graph Scope: Mail.Read (Application)
|
|
955
|
+
*/
|
|
956
|
+
async findSystemFolder(client, emailAddress, folderName) {
|
|
841
957
|
try {
|
|
958
|
+
// MS Graph well-known folder names
|
|
842
959
|
const wellKnownNames = {
|
|
843
960
|
'inbox': 'inbox',
|
|
844
961
|
'sent': 'sentitems',
|
|
@@ -851,13 +968,15 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
851
968
|
};
|
|
852
969
|
const normalizedName = folderName.toLowerCase();
|
|
853
970
|
const graphFolderName = wellKnownNames[normalizedName] || normalizedName;
|
|
854
|
-
|
|
971
|
+
// Try well-known folder endpoint first - use email address directly
|
|
972
|
+
const folderPath = `${this.getApiUri()}/${encodeURIComponent(emailAddress)}/mailFolders/${graphFolderName}`;
|
|
855
973
|
const folder = await client.api(folderPath).get();
|
|
856
974
|
return folder?.id || null;
|
|
857
975
|
}
|
|
858
976
|
catch {
|
|
977
|
+
// Folder not found or access denied - try search
|
|
859
978
|
try {
|
|
860
|
-
const foldersPath = `${this.getApiUri()}/${
|
|
979
|
+
const foldersPath = `${this.getApiUri()}/${encodeURIComponent(emailAddress)}/mailFolders`;
|
|
861
980
|
const response = await client.api(foldersPath)
|
|
862
981
|
.filter(`displayName eq '${folderName}'`)
|
|
863
982
|
.get();
|
|
@@ -866,24 +985,35 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
866
985
|
}
|
|
867
986
|
}
|
|
868
987
|
catch {
|
|
988
|
+
// Ignore search errors
|
|
869
989
|
}
|
|
870
990
|
return null;
|
|
871
991
|
}
|
|
872
992
|
}
|
|
873
|
-
|
|
993
|
+
/**
|
|
994
|
+
* Creates a new mail folder.
|
|
995
|
+
*
|
|
996
|
+
* @private
|
|
997
|
+
* @requires MS Graph Scope: Mail.ReadWrite (Application)
|
|
998
|
+
*/
|
|
999
|
+
async createMailFolder(client, emailAddress, folderName) {
|
|
874
1000
|
try {
|
|
875
|
-
|
|
1001
|
+
// Use email address directly in API path
|
|
1002
|
+
const foldersPath = `${this.getApiUri()}/${encodeURIComponent(emailAddress)}/mailFolders`;
|
|
876
1003
|
const result = await client.api(foldersPath).post({
|
|
877
1004
|
displayName: folderName
|
|
878
1005
|
});
|
|
879
|
-
|
|
1006
|
+
LogStatus(`Created mail folder '${folderName}' with ID: ${result.id}`);
|
|
880
1007
|
return result.id;
|
|
881
1008
|
}
|
|
882
1009
|
catch (ex) {
|
|
883
|
-
|
|
1010
|
+
LogError(`Error creating mail folder '${folderName}'`, undefined, ex);
|
|
884
1011
|
return null;
|
|
885
1012
|
}
|
|
886
1013
|
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Checks if a folder name is a system folder.
|
|
1016
|
+
*/
|
|
887
1017
|
isSystemFolder(displayName) {
|
|
888
1018
|
const systemFolders = [
|
|
889
1019
|
'inbox', 'sent items', 'drafts', 'deleted items', 'junk email',
|
|
@@ -891,6 +1021,9 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
891
1021
|
];
|
|
892
1022
|
return systemFolders.includes(displayName.toLowerCase());
|
|
893
1023
|
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Maps folder display name to system folder type.
|
|
1026
|
+
*/
|
|
894
1027
|
mapSystemFolderType(displayName) {
|
|
895
1028
|
const mapping = {
|
|
896
1029
|
'inbox': 'inbox',
|
|
@@ -903,12 +1036,9 @@ let MSGraphProvider = class MSGraphProvider extends communication_types_1.BaseCo
|
|
|
903
1036
|
return mapping[displayName.toLowerCase()] || 'other';
|
|
904
1037
|
}
|
|
905
1038
|
};
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
(0, global_1.RegisterClass)(communication_types_1.BaseCommunicationProvider, 'Microsoft Graph'),
|
|
1039
|
+
MSGraphProvider = __decorate([
|
|
1040
|
+
RegisterClass(BaseCommunicationProvider, 'Microsoft Graph'),
|
|
909
1041
|
__metadata("design:paramtypes", [])
|
|
910
1042
|
], MSGraphProvider);
|
|
911
|
-
|
|
912
|
-
}
|
|
913
|
-
exports.LoadMSGraphProvider = LoadMSGraphProvider;
|
|
1043
|
+
export { MSGraphProvider };
|
|
914
1044
|
//# sourceMappingURL=MSGraphProvider.js.map
|