@memberjunction/communication-gmail 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.
@@ -1,54 +1,37 @@
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
- Object.defineProperty(exports, "__esModule", { value: true });
32
- exports.LoadProvider = exports.GmailProvider = void 0;
33
- const communication_types_1 = require("@memberjunction/communication-types");
34
- const global_1 = require("@memberjunction/global");
35
- const core_1 = require("@memberjunction/core");
36
- const Config = __importStar(require("./config"));
37
- const googleApis = __importStar(require("googleapis"));
38
- let GmailProvider = class GmailProvider extends communication_types_1.BaseCommunicationProvider {
7
+ import { BaseCommunicationProvider, resolveCredentialValue, validateRequiredCredentials } from "@memberjunction/communication-types";
8
+ import { RegisterClass } from "@memberjunction/global";
9
+ import { LogError, LogStatus } from "@memberjunction/core";
10
+ import * as Config from "./config.js";
11
+ import googleApis from 'googleapis';
12
+ /**
13
+ * Implementation of the Gmail provider for sending and receiving messages
14
+ */
15
+ let GmailProvider = class GmailProvider extends BaseCommunicationProvider {
39
16
  constructor() {
40
17
  super(...arguments);
18
+ /** Cached Gmail client for environment credentials */
41
19
  this.envGmailClient = null;
20
+ /** Cache of Gmail clients for per-request credentials */
42
21
  this.clientCache = new Map();
43
22
  }
23
+ /**
24
+ * Resolves credentials by merging request credentials with environment fallback
25
+ */
44
26
  resolveCredentials(credentials) {
45
27
  const disableFallback = credentials?.disableEnvironmentFallback ?? false;
46
- const clientId = (0, communication_types_1.resolveCredentialValue)(credentials?.clientId, Config.GMAIL_CLIENT_ID, disableFallback);
47
- const clientSecret = (0, communication_types_1.resolveCredentialValue)(credentials?.clientSecret, Config.GMAIL_CLIENT_SECRET, disableFallback);
48
- const redirectUri = (0, communication_types_1.resolveCredentialValue)(credentials?.redirectUri, Config.GMAIL_REDIRECT_URI, disableFallback);
49
- const refreshToken = (0, communication_types_1.resolveCredentialValue)(credentials?.refreshToken, Config.GMAIL_REFRESH_TOKEN, disableFallback);
50
- const serviceAccountEmail = (0, communication_types_1.resolveCredentialValue)(credentials?.serviceAccountEmail, Config.GMAIL_SERVICE_ACCOUNT_EMAIL, disableFallback);
51
- (0, communication_types_1.validateRequiredCredentials)({ clientId, clientSecret, redirectUri, refreshToken }, ['clientId', 'clientSecret', 'redirectUri', 'refreshToken'], 'Gmail');
28
+ const clientId = resolveCredentialValue(credentials?.clientId, Config.GMAIL_CLIENT_ID, disableFallback);
29
+ const clientSecret = resolveCredentialValue(credentials?.clientSecret, Config.GMAIL_CLIENT_SECRET, disableFallback);
30
+ const redirectUri = resolveCredentialValue(credentials?.redirectUri, Config.GMAIL_REDIRECT_URI, disableFallback);
31
+ const refreshToken = resolveCredentialValue(credentials?.refreshToken, Config.GMAIL_REFRESH_TOKEN, disableFallback);
32
+ const serviceAccountEmail = resolveCredentialValue(credentials?.serviceAccountEmail, Config.GMAIL_SERVICE_ACCOUNT_EMAIL, disableFallback);
33
+ // Validate required credentials
34
+ validateRequiredCredentials({ clientId, clientSecret, redirectUri, refreshToken }, ['clientId', 'clientSecret', 'redirectUri', 'refreshToken'], 'Gmail');
52
35
  return {
53
36
  clientId: clientId,
54
37
  clientSecret: clientSecret,
@@ -57,17 +40,27 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
57
40
  serviceAccountEmail: serviceAccountEmail || ''
58
41
  };
59
42
  }
43
+ /**
44
+ * Creates a Gmail client with the given credentials
45
+ */
60
46
  createGmailClient(creds) {
47
+ // Create OAuth2 client
61
48
  const oauth2Client = new googleApis.google.auth.OAuth2(creds.clientId, creds.clientSecret, creds.redirectUri);
49
+ // Set refresh token to automatically refresh access tokens
62
50
  oauth2Client.setCredentials({
63
51
  refresh_token: creds.refreshToken
64
52
  });
53
+ // Create Gmail API client
65
54
  return googleApis.google.gmail({
66
55
  version: 'v1',
67
56
  auth: oauth2Client
68
57
  });
69
58
  }
59
+ /**
60
+ * Gets a Gmail client for the given credentials, using caching for efficiency
61
+ */
70
62
  getGmailClient(creds) {
63
+ // Check if using environment credentials (can use shared client)
71
64
  const isEnvCredentials = creds.clientId === Config.GMAIL_CLIENT_ID &&
72
65
  creds.clientSecret === Config.GMAIL_CLIENT_SECRET &&
73
66
  creds.refreshToken === Config.GMAIL_REFRESH_TOKEN;
@@ -80,6 +73,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
80
73
  }
81
74
  return this.envGmailClient;
82
75
  }
76
+ // For per-request credentials, use cached client by credential key
83
77
  const cacheKey = `${creds.clientId}:${creds.refreshToken.substring(0, 10)}`;
84
78
  let cached = this.clientCache.get(cacheKey);
85
79
  if (!cached) {
@@ -91,11 +85,15 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
91
85
  }
92
86
  return cached;
93
87
  }
88
+ /**
89
+ * Gets the authenticated user's email address for a given cached client
90
+ */
94
91
  async getUserEmail(cached) {
95
92
  if (cached.userEmail) {
96
93
  return cached.userEmail;
97
94
  }
98
95
  try {
96
+ // Get user profile to verify authentication
99
97
  const response = await cached.client.users.getProfile({
100
98
  userId: 'me'
101
99
  });
@@ -106,37 +104,48 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
106
104
  return null;
107
105
  }
108
106
  catch (error) {
109
- (0, core_1.LogError)('Failed to get Gmail user email', undefined, error);
107
+ LogError('Failed to get Gmail user email', undefined, error);
110
108
  return null;
111
109
  }
112
110
  }
111
+ /**
112
+ * Encode and format email content for Gmail API
113
+ */
113
114
  createEmailContent(message, creds) {
115
+ // Get sender email
114
116
  const from = message.From || creds.serviceAccountEmail;
115
117
  const fromName = message.FromName || '';
116
118
  const fromHeader = fromName ? `${fromName} <${from}>` : from;
119
+ // Create email content
117
120
  const subject = message.ProcessedSubject;
118
121
  const to = message.To;
119
122
  const cc = message.CCRecipients?.join(', ') || '';
120
123
  const bcc = message.BCCRecipients?.join(', ') || '';
124
+ // Headers
121
125
  let emailContent = [
122
126
  `From: ${fromHeader}`,
123
127
  `To: ${to}`,
124
128
  `Subject: ${subject}`
125
129
  ];
130
+ // Add CC and BCC if present
126
131
  if (cc)
127
132
  emailContent.push(`Cc: ${cc}`);
128
133
  if (bcc)
129
134
  emailContent.push(`Bcc: ${bcc}`);
135
+ // Add content type and message body
130
136
  if (message.ProcessedHTMLBody) {
137
+ // For HTML emails
131
138
  const boundary = `boundary_${Math.random().toString(36).substring(2)}`;
132
139
  emailContent.push('MIME-Version: 1.0');
133
140
  emailContent.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);
134
141
  emailContent.push('');
142
+ // Text part
135
143
  emailContent.push(`--${boundary}`);
136
144
  emailContent.push('Content-Type: text/plain; charset=UTF-8');
137
145
  emailContent.push('');
138
146
  emailContent.push(message.ProcessedBody || '');
139
147
  emailContent.push('');
148
+ // HTML part
140
149
  emailContent.push(`--${boundary}`);
141
150
  emailContent.push('Content-Type: text/html; charset=UTF-8');
142
151
  emailContent.push('');
@@ -145,16 +154,26 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
145
154
  emailContent.push(`--${boundary}--`);
146
155
  }
147
156
  else {
157
+ // Plain text email
148
158
  emailContent.push('Content-Type: text/plain; charset=UTF-8');
149
159
  emailContent.push('');
150
160
  emailContent.push(message.ProcessedBody || '');
151
161
  }
152
162
  return Buffer.from(emailContent.join('\r\n')).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
153
163
  }
164
+ /**
165
+ * Sends a single message using the Gmail API
166
+ * @param message - The message to send
167
+ * @param credentials - Optional credentials override for this request.
168
+ * If not provided, uses environment variables.
169
+ * Set `credentials.disableEnvironmentFallback = true` to require explicit credentials.
170
+ */
154
171
  async SendSingleMessage(message, credentials) {
155
172
  try {
173
+ // Resolve credentials (request credentials with env fallback)
156
174
  const creds = this.resolveCredentials(credentials);
157
175
  const cached = this.getGmailClient(creds);
176
+ // Get user email
158
177
  const userEmail = await this.getUserEmail(cached);
159
178
  if (!userEmail) {
160
179
  return {
@@ -163,7 +182,9 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
163
182
  Error: 'Could not get user email'
164
183
  };
165
184
  }
185
+ // Create raw email content in base64 URL-safe format
166
186
  const raw = this.createEmailContent(message, creds);
187
+ // Send the email
167
188
  const result = await cached.client.users.messages.send({
168
189
  userId: 'me',
169
190
  requestBody: {
@@ -171,7 +192,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
171
192
  }
172
193
  });
173
194
  if (result && result.status >= 200 && result.status < 300) {
174
- (0, core_1.LogStatus)(`Email sent via Gmail: ${result.statusText}`);
195
+ LogStatus(`Email sent via Gmail: ${result.statusText}`);
175
196
  return {
176
197
  Message: message,
177
198
  Success: true,
@@ -179,7 +200,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
179
200
  };
180
201
  }
181
202
  else {
182
- (0, core_1.LogError)('Failed to send email via Gmail', undefined, result);
203
+ LogError('Failed to send email via Gmail', undefined, result);
183
204
  return {
184
205
  Message: message,
185
206
  Success: false,
@@ -189,7 +210,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
189
210
  }
190
211
  catch (error) {
191
212
  const errorMessage = error instanceof Error ? error.message : 'Error sending message';
192
- (0, core_1.LogError)('Error sending message via Gmail', undefined, error);
213
+ LogError('Error sending message via Gmail', undefined, error);
193
214
  return {
194
215
  Message: message,
195
216
  Success: false,
@@ -197,8 +218,16 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
197
218
  };
198
219
  }
199
220
  }
221
+ /**
222
+ * Gets messages from Gmail
223
+ * @param params - Parameters for fetching messages
224
+ * @param credentials - Optional credentials override for this request.
225
+ * If not provided, uses environment variables.
226
+ * Set `credentials.disableEnvironmentFallback = true` to require explicit credentials.
227
+ */
200
228
  async GetMessages(params, credentials) {
201
229
  try {
230
+ // Resolve credentials (request credentials with env fallback)
202
231
  const creds = this.resolveCredentials(credentials);
203
232
  const cached = this.getGmailClient(creds);
204
233
  const userEmail = await this.getUserEmail(cached);
@@ -209,6 +238,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
209
238
  ErrorMessage: 'Could not get user email'
210
239
  };
211
240
  }
241
+ // Build query
212
242
  let query = '';
213
243
  if (params.UnreadOnly) {
214
244
  query = 'is:unread';
@@ -216,6 +246,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
216
246
  if (params.ContextData?.query) {
217
247
  query = params.ContextData.query;
218
248
  }
249
+ // Get messages
219
250
  const response = await cached.client.users.messages.list({
220
251
  userId: 'me',
221
252
  maxResults: params.NumMessages,
@@ -227,6 +258,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
227
258
  Messages: []
228
259
  };
229
260
  }
261
+ // Get full message details for each message ID
230
262
  const messagePromises = response.data.messages.map(async (message) => {
231
263
  const fullMessage = await cached.client.users.messages.get({
232
264
  userId: 'me',
@@ -236,7 +268,9 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
236
268
  return fullMessage.data;
237
269
  });
238
270
  const fullMessages = await Promise.all(messagePromises);
271
+ // Process messages into standard format
239
272
  const processedMessages = fullMessages.map(message => {
273
+ // Extract headers
240
274
  const headers = message.payload?.headers || [];
241
275
  const getHeader = (name) => {
242
276
  const header = headers.find(h => h.name?.toLowerCase() === name.toLowerCase());
@@ -246,11 +280,14 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
246
280
  const to = getHeader('to');
247
281
  const subject = getHeader('subject');
248
282
  const replyTo = getHeader('reply-to') ? [getHeader('reply-to')] : [from];
283
+ // Extract body
249
284
  let body = '';
250
285
  if (message.payload?.body?.data) {
286
+ // Base64 encoded data
251
287
  body = Buffer.from(message.payload.body.data, 'base64').toString('utf-8');
252
288
  }
253
289
  else if (message.payload?.parts) {
290
+ // Multipart message, try to find text part
254
291
  const textPart = message.payload.parts.find(part => part.mimeType === 'text/plain');
255
292
  if (textPart && textPart.body?.data) {
256
293
  body = Buffer.from(textPart.body.data, 'base64').toString('utf-8');
@@ -266,6 +303,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
266
303
  ThreadID: message.threadId || ''
267
304
  };
268
305
  });
306
+ // Mark as read if requested
269
307
  if (params.ContextData?.MarkAsRead) {
270
308
  for (const message of fullMessages) {
271
309
  if (message.id) {
@@ -281,7 +319,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
281
319
  }
282
320
  catch (error) {
283
321
  const errorMessage = error instanceof Error ? error.message : 'Error getting messages';
284
- (0, core_1.LogError)('Error getting messages from Gmail', undefined, error);
322
+ LogError('Error getting messages from Gmail', undefined, error);
285
323
  return {
286
324
  Success: false,
287
325
  Messages: [],
@@ -289,6 +327,13 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
289
327
  };
290
328
  }
291
329
  }
330
+ /**
331
+ * Reply to a message using Gmail API
332
+ * @param params - Parameters for replying to a message
333
+ * @param credentials - Optional credentials override for this request.
334
+ * If not provided, uses environment variables.
335
+ * Set `credentials.disableEnvironmentFallback = true` to require explicit credentials.
336
+ */
292
337
  async ReplyToMessage(params, credentials) {
293
338
  try {
294
339
  if (!params.MessageID) {
@@ -297,8 +342,10 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
297
342
  ErrorMessage: 'Message ID not provided'
298
343
  };
299
344
  }
345
+ // Resolve credentials (request credentials with env fallback)
300
346
  const creds = this.resolveCredentials(credentials);
301
347
  const cached = this.getGmailClient(creds);
348
+ // Get the original message to obtain threadId
302
349
  const originalMessage = await cached.client.users.messages.get({
303
350
  userId: 'me',
304
351
  id: params.MessageID
@@ -309,7 +356,9 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
309
356
  ErrorMessage: 'Could not get thread ID from original message'
310
357
  };
311
358
  }
359
+ // Create raw email content
312
360
  const raw = this.createEmailContent(params.Message, creds);
361
+ // Send the reply in the same thread
313
362
  const result = await cached.client.users.messages.send({
314
363
  userId: 'me',
315
364
  requestBody: {
@@ -332,13 +381,20 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
332
381
  }
333
382
  catch (error) {
334
383
  const errorMessage = error instanceof Error ? error.message : 'Error replying to message';
335
- (0, core_1.LogError)('Error replying to message via Gmail', undefined, error);
384
+ LogError('Error replying to message via Gmail', undefined, error);
336
385
  return {
337
386
  Success: false,
338
387
  ErrorMessage: errorMessage
339
388
  };
340
389
  }
341
390
  }
391
+ /**
392
+ * Forward a message using Gmail API
393
+ * @param params - Parameters for forwarding a message
394
+ * @param credentials - Optional credentials override for this request.
395
+ * If not provided, uses environment variables.
396
+ * Set `credentials.disableEnvironmentFallback = true` to require explicit credentials.
397
+ */
342
398
  async ForwardMessage(params, credentials) {
343
399
  try {
344
400
  if (!params.MessageID) {
@@ -347,8 +403,10 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
347
403
  ErrorMessage: 'Message ID not provided'
348
404
  };
349
405
  }
406
+ // Resolve credentials (request credentials with env fallback)
350
407
  const creds = this.resolveCredentials(credentials);
351
408
  const cached = this.getGmailClient(creds);
409
+ // Get the original message
352
410
  const originalMessage = await cached.client.users.messages.get({
353
411
  userId: 'me',
354
412
  id: params.MessageID,
@@ -360,26 +418,33 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
360
418
  ErrorMessage: 'Could not get raw content of original message'
361
419
  };
362
420
  }
421
+ // Convert raw message to proper format
363
422
  const rawContent = Buffer.from(originalMessage.data.raw, 'base64').toString('utf-8');
423
+ // Build forwarded message
364
424
  const userEmail = await this.getUserEmail(cached);
365
425
  const to = params.ToRecipients.join(', ');
366
426
  const cc = params.CCRecipients?.join(', ') || '';
367
427
  const bcc = params.BCCRecipients?.join(', ') || '';
428
+ // Parse the original email to extract subject
368
429
  const subjectMatch = rawContent.match(/Subject: (.*?)(\r?\n)/);
369
430
  const subject = subjectMatch ? `Fwd: ${subjectMatch[1]}` : 'Fwd: ';
431
+ // Headers for new message
370
432
  const emailContent = [
371
433
  `From: ${userEmail}`,
372
434
  `To: ${to}`,
373
435
  `Subject: ${subject}`
374
436
  ];
437
+ // Add CC and BCC if present
375
438
  if (cc)
376
439
  emailContent.push(`Cc: ${cc}`);
377
440
  if (bcc)
378
441
  emailContent.push(`Bcc: ${bcc}`);
442
+ // Add content type
379
443
  const boundary = `boundary_${Math.random().toString(36).substring(2)}`;
380
444
  emailContent.push('MIME-Version: 1.0');
381
445
  emailContent.push(`Content-Type: multipart/mixed; boundary="${boundary}"`);
382
446
  emailContent.push('');
447
+ // Forward comment
383
448
  if (params.Message) {
384
449
  emailContent.push(`--${boundary}`);
385
450
  emailContent.push('Content-Type: text/plain; charset=UTF-8');
@@ -387,6 +452,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
387
452
  emailContent.push(params.Message);
388
453
  emailContent.push('');
389
454
  }
455
+ // Original message as attachment
390
456
  emailContent.push(`--${boundary}`);
391
457
  emailContent.push('Content-Type: message/rfc822; name="forwarded_message.eml"');
392
458
  emailContent.push('Content-Disposition: attachment; filename="forwarded_message.eml"');
@@ -394,10 +460,12 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
394
460
  emailContent.push(rawContent);
395
461
  emailContent.push('');
396
462
  emailContent.push(`--${boundary}--`);
463
+ // Encode email content
397
464
  const raw = Buffer.from(emailContent.join('\r\n')).toString('base64')
398
465
  .replace(/\+/g, '-')
399
466
  .replace(/\//g, '_')
400
467
  .replace(/=+$/, '');
468
+ // Send the forwarded message
401
469
  const result = await cached.client.users.messages.send({
402
470
  userId: 'me',
403
471
  requestBody: {
@@ -419,13 +487,16 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
419
487
  }
420
488
  catch (error) {
421
489
  const errorMessage = error instanceof Error ? error.message : 'Error forwarding message';
422
- (0, core_1.LogError)('Error forwarding message via Gmail', undefined, error);
490
+ LogError('Error forwarding message via Gmail', undefined, error);
423
491
  return {
424
492
  Success: false,
425
493
  ErrorMessage: errorMessage
426
494
  };
427
495
  }
428
496
  }
497
+ /**
498
+ * Helper to mark a message as read
499
+ */
429
500
  async markMessageAsRead(gmailClient, messageId) {
430
501
  try {
431
502
  await gmailClient.users.messages.modify({
@@ -438,12 +509,20 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
438
509
  return true;
439
510
  }
440
511
  catch (error) {
441
- (0, core_1.LogError)(`Error marking message ${messageId} as read`, undefined, error);
512
+ LogError(`Error marking message ${messageId} as read`, undefined, error);
442
513
  return false;
443
514
  }
444
515
  }
516
+ /**
517
+ * Creates a draft message in Gmail
518
+ * @param params - Parameters for creating a draft
519
+ * @param credentials - Optional credentials override for this request.
520
+ * If not provided, uses environment variables.
521
+ * Set `credentials.disableEnvironmentFallback = true` to require explicit credentials.
522
+ */
445
523
  async CreateDraft(params, credentials) {
446
524
  try {
525
+ // Resolve credentials (request credentials with env fallback)
447
526
  const creds = this.resolveCredentials(credentials);
448
527
  const cached = this.getGmailClient(creds);
449
528
  const userEmail = await this.getUserEmail(cached);
@@ -453,7 +532,9 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
453
532
  ErrorMessage: 'Could not get user email'
454
533
  };
455
534
  }
535
+ // Reuse existing email content creation logic
456
536
  const raw = this.createEmailContent(params.Message, creds);
537
+ // Create draft using Gmail API
457
538
  const result = await cached.client.users.drafts.create({
458
539
  userId: 'me',
459
540
  requestBody: {
@@ -461,7 +542,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
461
542
  }
462
543
  });
463
544
  if (result && result.status >= 200 && result.status < 300) {
464
- (0, core_1.LogStatus)(`Draft created via Gmail: ${result.data.id}`);
545
+ LogStatus(`Draft created via Gmail: ${result.data.id}`);
465
546
  return {
466
547
  Success: true,
467
548
  DraftID: result.data.id || undefined,
@@ -469,7 +550,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
469
550
  };
470
551
  }
471
552
  else {
472
- (0, core_1.LogError)('Failed to create draft via Gmail', undefined, result);
553
+ LogError('Failed to create draft via Gmail', undefined, result);
473
554
  return {
474
555
  Success: false,
475
556
  ErrorMessage: `Failed to create draft: ${result?.statusText || 'Unknown error'}`
@@ -478,13 +559,20 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
478
559
  }
479
560
  catch (error) {
480
561
  const errorMessage = error instanceof Error ? error.message : 'Error creating draft';
481
- (0, core_1.LogError)('Error creating draft via Gmail', undefined, error);
562
+ LogError('Error creating draft via Gmail', undefined, error);
482
563
  return {
483
564
  Success: false,
484
565
  ErrorMessage: errorMessage
485
566
  };
486
567
  }
487
568
  }
569
+ // ========================================================================
570
+ // EXTENDED OPERATIONS - Gmail supports all mailbox operations via labels
571
+ // ========================================================================
572
+ /**
573
+ * Returns the list of operations supported by the Gmail provider.
574
+ * Gmail supports all operations through its label-based system.
575
+ */
488
576
  getSupportedOperations() {
489
577
  return [
490
578
  'SendSingleMessage',
@@ -495,7 +583,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
495
583
  'CreateDraft',
496
584
  'DeleteMessage',
497
585
  'MoveMessage',
498
- 'ListFolders',
586
+ 'ListFolders', // Gmail uses labels instead of folders
499
587
  'MarkAsRead',
500
588
  'ArchiveMessage',
501
589
  'SearchMessages',
@@ -503,6 +591,11 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
503
591
  'DownloadAttachment'
504
592
  ];
505
593
  }
594
+ /**
595
+ * Gets a single message by ID
596
+ * @param params - Parameters for retrieving the message
597
+ * @param credentials - Optional credentials override for this request
598
+ */
506
599
  async GetSingleMessage(params, credentials) {
507
600
  try {
508
601
  const creds = this.resolveCredentials(credentials);
@@ -527,53 +620,68 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
527
620
  }
528
621
  catch (error) {
529
622
  const errorMessage = error instanceof Error ? error.message : 'Error getting message';
530
- (0, core_1.LogError)(`Error getting message ${params.MessageID} from Gmail`, undefined, error);
623
+ LogError(`Error getting message ${params.MessageID} from Gmail`, undefined, error);
531
624
  return {
532
625
  Success: false,
533
626
  ErrorMessage: errorMessage
534
627
  };
535
628
  }
536
629
  }
630
+ /**
631
+ * Deletes a message from Gmail
632
+ * @param params - Parameters for deleting the message
633
+ * @param credentials - Optional credentials override for this request
634
+ */
537
635
  async DeleteMessage(params, credentials) {
538
636
  try {
539
637
  const creds = this.resolveCredentials(credentials);
540
638
  const cached = this.getGmailClient(creds);
541
639
  if (params.PermanentDelete) {
640
+ // Permanently delete the message
542
641
  await cached.client.users.messages.delete({
543
642
  userId: 'me',
544
643
  id: params.MessageID
545
644
  });
546
645
  }
547
646
  else {
647
+ // Move to trash (adds TRASH label, removes INBOX)
548
648
  await cached.client.users.messages.trash({
549
649
  userId: 'me',
550
650
  id: params.MessageID
551
651
  });
552
652
  }
553
- (0, core_1.LogStatus)(`Message ${params.MessageID} deleted from Gmail (permanent: ${params.PermanentDelete})`);
653
+ LogStatus(`Message ${params.MessageID} deleted from Gmail (permanent: ${params.PermanentDelete})`);
554
654
  return {
555
655
  Success: true
556
656
  };
557
657
  }
558
658
  catch (error) {
559
659
  const errorMessage = error instanceof Error ? error.message : 'Error deleting message';
560
- (0, core_1.LogError)(`Error deleting message ${params.MessageID} from Gmail`, undefined, error);
660
+ LogError(`Error deleting message ${params.MessageID} from Gmail`, undefined, error);
561
661
  return {
562
662
  Success: false,
563
663
  ErrorMessage: errorMessage
564
664
  };
565
665
  }
566
666
  }
667
+ /**
668
+ * Moves a message to a different label (Gmail's equivalent of folders)
669
+ * In Gmail, moving is done by adding/removing labels
670
+ * @param params - Parameters for moving the message
671
+ * @param credentials - Optional credentials override for this request
672
+ */
567
673
  async MoveMessage(params, credentials) {
568
674
  try {
569
675
  const creds = this.resolveCredentials(credentials);
570
676
  const cached = this.getGmailClient(creds);
677
+ // First get current labels on the message
571
678
  const message = await cached.client.users.messages.get({
572
679
  userId: 'me',
573
680
  id: params.MessageID,
574
681
  format: 'minimal'
575
682
  });
576
683
  const currentLabels = message.data.labelIds || [];
684
+ // Remove INBOX and other category labels, add the destination label
577
685
  const labelsToRemove = currentLabels.filter(label => label === 'INBOX' ||
578
686
  label === 'CATEGORY_PERSONAL' ||
579
687
  label === 'CATEGORY_SOCIAL' ||
@@ -588,21 +696,26 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
588
696
  removeLabelIds: labelsToRemove
589
697
  }
590
698
  });
591
- (0, core_1.LogStatus)(`Message ${params.MessageID} moved to label ${params.DestinationFolderID}`);
699
+ LogStatus(`Message ${params.MessageID} moved to label ${params.DestinationFolderID}`);
592
700
  return {
593
701
  Success: true,
594
- NewMessageID: params.MessageID
702
+ NewMessageID: params.MessageID // Gmail doesn't change message ID on move
595
703
  };
596
704
  }
597
705
  catch (error) {
598
706
  const errorMessage = error instanceof Error ? error.message : 'Error moving message';
599
- (0, core_1.LogError)(`Error moving message ${params.MessageID} in Gmail`, undefined, error);
707
+ LogError(`Error moving message ${params.MessageID} in Gmail`, undefined, error);
600
708
  return {
601
709
  Success: false,
602
710
  ErrorMessage: errorMessage
603
711
  };
604
712
  }
605
713
  }
714
+ /**
715
+ * Lists Gmail labels (Gmail's equivalent of folders)
716
+ * @param params - Parameters for listing labels
717
+ * @param credentials - Optional credentials override for this request
718
+ */
606
719
  async ListFolders(params, credentials) {
607
720
  try {
608
721
  const creds = this.resolveCredentials(credentials);
@@ -616,6 +729,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
616
729
  Folders: []
617
730
  };
618
731
  }
732
+ // Get detailed info for each label if counts requested
619
733
  let labels = response.data.labels;
620
734
  if (params.IncludeCounts) {
621
735
  const detailedLabels = await Promise.all(labels.map(async (label) => {
@@ -642,6 +756,8 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
642
756
  IsSystemFolder: label.type === 'system',
643
757
  SystemFolderType: this.mapGmailLabelToSystemFolder(label.id || '')
644
758
  }));
759
+ // Filter by parent if specified (Gmail doesn't have nested labels in the API the same way)
760
+ // User labels can have "/" in names to simulate hierarchy
645
761
  if (params.ParentFolderID) {
646
762
  const parent = folders.find(f => f.ID === params.ParentFolderID);
647
763
  if (parent) {
@@ -661,17 +777,23 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
661
777
  }
662
778
  catch (error) {
663
779
  const errorMessage = error instanceof Error ? error.message : 'Error listing labels';
664
- (0, core_1.LogError)('Error listing labels from Gmail', undefined, error);
780
+ LogError('Error listing labels from Gmail', undefined, error);
665
781
  return {
666
782
  Success: false,
667
783
  ErrorMessage: errorMessage
668
784
  };
669
785
  }
670
786
  }
787
+ /**
788
+ * Marks messages as read or unread
789
+ * @param params - Parameters for marking messages
790
+ * @param credentials - Optional credentials override for this request
791
+ */
671
792
  async MarkAsRead(params, credentials) {
672
793
  try {
673
794
  const creds = this.resolveCredentials(credentials);
674
795
  const cached = this.getGmailClient(creds);
796
+ // Process all messages
675
797
  await Promise.all(params.MessageIDs.map(async (messageId) => {
676
798
  await cached.client.users.messages.modify({
677
799
  userId: 'me',
@@ -681,24 +803,30 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
681
803
  : { addLabelIds: ['UNREAD'] }
682
804
  });
683
805
  }));
684
- (0, core_1.LogStatus)(`Marked ${params.MessageIDs.length} message(s) as ${params.IsRead ? 'read' : 'unread'}`);
806
+ LogStatus(`Marked ${params.MessageIDs.length} message(s) as ${params.IsRead ? 'read' : 'unread'}`);
685
807
  return {
686
808
  Success: true
687
809
  };
688
810
  }
689
811
  catch (error) {
690
812
  const errorMessage = error instanceof Error ? error.message : 'Error marking messages';
691
- (0, core_1.LogError)('Error marking messages as read/unread in Gmail', undefined, error);
813
+ LogError('Error marking messages as read/unread in Gmail', undefined, error);
692
814
  return {
693
815
  Success: false,
694
816
  ErrorMessage: errorMessage
695
817
  };
696
818
  }
697
819
  }
820
+ /**
821
+ * Archives a message (removes INBOX label in Gmail)
822
+ * @param params - Parameters for archiving the message
823
+ * @param credentials - Optional credentials override for this request
824
+ */
698
825
  async ArchiveMessage(params, credentials) {
699
826
  try {
700
827
  const creds = this.resolveCredentials(credentials);
701
828
  const cached = this.getGmailClient(creds);
829
+ // In Gmail, archiving is simply removing the INBOX label
702
830
  await cached.client.users.messages.modify({
703
831
  userId: 'me',
704
832
  id: params.MessageID,
@@ -706,25 +834,32 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
706
834
  removeLabelIds: ['INBOX']
707
835
  }
708
836
  });
709
- (0, core_1.LogStatus)(`Message ${params.MessageID} archived in Gmail`);
837
+ LogStatus(`Message ${params.MessageID} archived in Gmail`);
710
838
  return {
711
839
  Success: true
712
840
  };
713
841
  }
714
842
  catch (error) {
715
843
  const errorMessage = error instanceof Error ? error.message : 'Error archiving message';
716
- (0, core_1.LogError)(`Error archiving message ${params.MessageID} in Gmail`, undefined, error);
844
+ LogError(`Error archiving message ${params.MessageID} in Gmail`, undefined, error);
717
845
  return {
718
846
  Success: false,
719
847
  ErrorMessage: errorMessage
720
848
  };
721
849
  }
722
850
  }
851
+ /**
852
+ * Searches messages using Gmail's search syntax
853
+ * @param params - Parameters for searching messages
854
+ * @param credentials - Optional credentials override for this request
855
+ */
723
856
  async SearchMessages(params, credentials) {
724
857
  try {
725
858
  const creds = this.resolveCredentials(credentials);
726
859
  const cached = this.getGmailClient(creds);
860
+ // Build Gmail search query
727
861
  let query = params.Query;
862
+ // Add date filters if specified
728
863
  if (params.FromDate) {
729
864
  const fromDateStr = this.formatDateForGmail(params.FromDate);
730
865
  query += ` after:${fromDateStr}`;
@@ -733,9 +868,11 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
733
868
  const toDateStr = this.formatDateForGmail(params.ToDate);
734
869
  query += ` before:${toDateStr}`;
735
870
  }
871
+ // Add folder/label filter
736
872
  if (params.FolderID) {
737
873
  query += ` label:${params.FolderID}`;
738
874
  }
875
+ // Search messages
739
876
  const response = await cached.client.users.messages.list({
740
877
  userId: 'me',
741
878
  q: query,
@@ -748,6 +885,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
748
885
  TotalCount: 0
749
886
  };
750
887
  }
888
+ // Get full message details
751
889
  const fullMessages = await Promise.all(response.data.messages.map(async (msg) => {
752
890
  const full = await cached.client.users.messages.get({
753
891
  userId: 'me',
@@ -766,13 +904,18 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
766
904
  }
767
905
  catch (error) {
768
906
  const errorMessage = error instanceof Error ? error.message : 'Error searching messages';
769
- (0, core_1.LogError)('Error searching messages in Gmail', undefined, error);
907
+ LogError('Error searching messages in Gmail', undefined, error);
770
908
  return {
771
909
  Success: false,
772
910
  ErrorMessage: errorMessage
773
911
  };
774
912
  }
775
913
  }
914
+ /**
915
+ * Lists attachments on a message
916
+ * @param params - Parameters for listing attachments
917
+ * @param credentials - Optional credentials override for this request
918
+ */
776
919
  async ListAttachments(params, credentials) {
777
920
  try {
778
921
  const creds = this.resolveCredentials(credentials);
@@ -798,17 +941,23 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
798
941
  }
799
942
  catch (error) {
800
943
  const errorMessage = error instanceof Error ? error.message : 'Error listing attachments';
801
- (0, core_1.LogError)(`Error listing attachments for message ${params.MessageID}`, undefined, error);
944
+ LogError(`Error listing attachments for message ${params.MessageID}`, undefined, error);
802
945
  return {
803
946
  Success: false,
804
947
  ErrorMessage: errorMessage
805
948
  };
806
949
  }
807
950
  }
951
+ /**
952
+ * Downloads an attachment from a message
953
+ * @param params - Parameters for downloading the attachment
954
+ * @param credentials - Optional credentials override for this request
955
+ */
808
956
  async DownloadAttachment(params, credentials) {
809
957
  try {
810
958
  const creds = this.resolveCredentials(credentials);
811
959
  const cached = this.getGmailClient(creds);
960
+ // First get attachment metadata to find filename and content type
812
961
  const message = await cached.client.users.messages.get({
813
962
  userId: 'me',
814
963
  id: params.MessageID,
@@ -818,6 +967,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
818
967
  if (message.data.payload) {
819
968
  attachmentInfo = this.findAttachmentInfo(message.data.payload, params.AttachmentID);
820
969
  }
970
+ // Download the attachment
821
971
  const response = await cached.client.users.messages.attachments.get({
822
972
  userId: 'me',
823
973
  messageId: params.MessageID,
@@ -829,6 +979,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
829
979
  ErrorMessage: 'Attachment content not found'
830
980
  };
831
981
  }
982
+ // Gmail returns base64url encoded data, convert to standard base64
832
983
  const base64Data = response.data.data.replace(/-/g, '+').replace(/_/g, '/');
833
984
  const content = Buffer.from(base64Data, 'base64');
834
985
  return {
@@ -842,13 +993,19 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
842
993
  }
843
994
  catch (error) {
844
995
  const errorMessage = error instanceof Error ? error.message : 'Error downloading attachment';
845
- (0, core_1.LogError)(`Error downloading attachment ${params.AttachmentID}`, undefined, error);
996
+ LogError(`Error downloading attachment ${params.AttachmentID}`, undefined, error);
846
997
  return {
847
998
  Success: false,
848
999
  ErrorMessage: errorMessage
849
1000
  };
850
1001
  }
851
1002
  }
1003
+ // ========================================================================
1004
+ // HELPER METHODS
1005
+ // ========================================================================
1006
+ /**
1007
+ * Parses a Gmail message into the standard GetMessageMessage format
1008
+ */
852
1009
  parseGmailMessage(message) {
853
1010
  const headers = message.payload?.headers || [];
854
1011
  const getHeader = (name) => {
@@ -860,6 +1017,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
860
1017
  const subject = getHeader('subject');
861
1018
  const replyTo = getHeader('reply-to') ? [getHeader('reply-to')] : [from];
862
1019
  const dateStr = getHeader('date');
1020
+ // Extract body
863
1021
  let body = '';
864
1022
  if (message.payload?.body?.data) {
865
1023
  body = Buffer.from(message.payload.body.data, 'base64').toString('utf-8');
@@ -870,14 +1028,17 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
870
1028
  body = Buffer.from(textPart.body.data, 'base64').toString('utf-8');
871
1029
  }
872
1030
  }
1031
+ // Parse date
873
1032
  let receivedAt;
874
1033
  if (dateStr) {
875
1034
  try {
876
1035
  receivedAt = new Date(dateStr);
877
1036
  }
878
1037
  catch {
1038
+ // Ignore parse errors
879
1039
  }
880
1040
  }
1041
+ // Internal date from Gmail (epoch milliseconds)
881
1042
  let createdAt;
882
1043
  if (message.internalDate) {
883
1044
  createdAt = new Date(parseInt(message.internalDate, 10));
@@ -894,6 +1055,9 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
894
1055
  CreatedAt: createdAt
895
1056
  };
896
1057
  }
1058
+ /**
1059
+ * Maps Gmail label IDs to system folder types
1060
+ */
897
1061
  mapGmailLabelToSystemFolder(labelId) {
898
1062
  const labelMap = {
899
1063
  'INBOX': 'inbox',
@@ -912,13 +1076,20 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
912
1076
  };
913
1077
  return labelMap[labelId] || undefined;
914
1078
  }
1079
+ /**
1080
+ * Formats a date for Gmail search query (YYYY/MM/DD)
1081
+ */
915
1082
  formatDateForGmail(date) {
916
1083
  const year = date.getFullYear();
917
1084
  const month = (date.getMonth() + 1).toString().padStart(2, '0');
918
1085
  const day = date.getDate().toString().padStart(2, '0');
919
1086
  return `${year}/${month}/${day}`;
920
1087
  }
1088
+ /**
1089
+ * Recursively extracts attachment information from message parts
1090
+ */
921
1091
  extractAttachments(part, attachments) {
1092
+ // Check if this part is an attachment
922
1093
  if (part.filename && part.body?.attachmentId) {
923
1094
  attachments.push({
924
1095
  ID: part.body.attachmentId,
@@ -930,12 +1101,16 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
930
1101
  ContentID: part.headers?.find(h => h.name?.toLowerCase() === 'content-id')?.value?.replace(/[<>]/g, '') || undefined
931
1102
  });
932
1103
  }
1104
+ // Recursively process nested parts
933
1105
  if (part.parts) {
934
1106
  for (const nestedPart of part.parts) {
935
1107
  this.extractAttachments(nestedPart, attachments);
936
1108
  }
937
1109
  }
938
1110
  }
1111
+ /**
1112
+ * Finds attachment info (filename, content type) by attachment ID
1113
+ */
939
1114
  findAttachmentInfo(part, attachmentId) {
940
1115
  if (part.body?.attachmentId === attachmentId) {
941
1116
  return {
@@ -953,11 +1128,8 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
953
1128
  return null;
954
1129
  }
955
1130
  };
956
- exports.GmailProvider = GmailProvider;
957
- exports.GmailProvider = GmailProvider = __decorate([
958
- (0, global_1.RegisterClass)(communication_types_1.BaseCommunicationProvider, 'Gmail')
1131
+ GmailProvider = __decorate([
1132
+ RegisterClass(BaseCommunicationProvider, 'Gmail')
959
1133
  ], GmailProvider);
960
- function LoadProvider() {
961
- }
962
- exports.LoadProvider = LoadProvider;
1134
+ export { GmailProvider };
963
1135
  //# sourceMappingURL=GmailProvider.js.map