n8n-nodes-gmail-custom 0.1.8 → 0.2.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.
@@ -161,6 +161,58 @@ async function buildMimeMessage(options) {
161
161
  .replace(/=+$/, '');
162
162
  }
163
163
 
164
+ async function getOrRefreshAccessToken(ctx, credentials, fromEmail) {
165
+ const cacheKey = `${credentials.email}:${fromEmail}`;
166
+ let token = TOKEN_CACHE[cacheKey];
167
+ if (token && token.expiresAt > Date.now()) {
168
+ return token.accessToken;
169
+ }
170
+
171
+ const privateKey = formatPrivateKey(credentials.privateKey);
172
+ const now = Math.floor(Date.now() / 1000);
173
+
174
+ let jwt;
175
+ const payload = {
176
+ iss: credentials.email,
177
+ scope: 'https://mail.google.com/',
178
+ aud: 'https://oauth2.googleapis.com/token',
179
+ exp: now + 3600,
180
+ iat: now,
181
+ sub: fromEmail,
182
+ };
183
+
184
+ if (jwtLib) {
185
+ jwt = jwtLib.sign(payload, privateKey, {
186
+ algorithm: 'RS256',
187
+ header: { kid: privateKey, typ: 'JWT', alg: 'RS256' },
188
+ });
189
+ } else {
190
+ const header = { alg: 'RS256', typ: 'JWT', kid: privateKey };
191
+ const signatureInput = base64UrlEncode(JSON.stringify(header)) + '.' + base64UrlEncode(JSON.stringify(payload));
192
+ const signer = crypto.createSign('RSA-SHA256');
193
+ signer.update(signatureInput);
194
+ const sig = signer.sign(privateKey);
195
+ jwt = signatureInput + '.' + base64urlEscape(sig.toString('base64'));
196
+ }
197
+
198
+ const response = await ctx.helpers.httpRequest({
199
+ method: 'POST',
200
+ url: 'https://oauth2.googleapis.com/token',
201
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
202
+ body: new URLSearchParams({
203
+ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
204
+ assertion: jwt,
205
+ }).toString(),
206
+ });
207
+
208
+ TOKEN_CACHE[cacheKey] = {
209
+ accessToken: response.access_token,
210
+ expiresAt: Date.now() + 3500 * 1000,
211
+ };
212
+
213
+ return response.access_token;
214
+ }
215
+
164
216
  class GmailCustom {
165
217
 
166
218
  constructor() {
@@ -306,10 +358,15 @@ class GmailCustom {
306
358
 
307
359
  async execute() {
308
360
  const items = this.getInputData();
361
+ if (!items || items.length === 0) {
362
+ return [[]];
363
+ }
364
+
309
365
  const returnData = [];
310
366
 
311
367
  for (let i = 0; i < items.length; i++) {
312
368
  try {
369
+ const item = items[i];
313
370
  const fromEmail = this.getNodeParameter('fromEmail', i, '');
314
371
  const sendTo = this.getNodeParameter('sendTo', i, '');
315
372
  const subject = this.getNodeParameter('subject', i, '');
@@ -339,83 +396,15 @@ class GmailCustom {
339
396
  }
340
397
 
341
398
  if (appendAttribution) {
342
- const attributionText = '\n\n---\nThis email was sent automatically with n8n';
343
399
  if (emailType === 'html') {
344
400
  bodyHtml += '<br><br>---<br><em>This email was sent automatically with n8n</em>';
345
401
  } else {
346
- bodyText += attributionText;
402
+ bodyText += '\n\n---\nThis email was sent automatically with n8n';
347
403
  }
348
404
  }
349
405
 
350
406
  const credentials = await this.getCredentials('googleApi');
351
- const cacheKey = `${credentials.email}:${fromEmail}`;
352
- let accessToken;
353
- let expiresAt = 0;
354
-
355
- if (TOKEN_CACHE[cacheKey]) {
356
- expiresAt = TOKEN_CACHE[cacheKey].expiresAt;
357
- accessToken = TOKEN_CACHE[cacheKey].accessToken;
358
- }
359
-
360
- if (!accessToken || Date.now() >= expiresAt) {
361
- const privateKey = formatPrivateKey(credentials.privateKey);
362
- const now = Math.floor(Date.now() / 1000);
363
-
364
- let jwt;
365
- if (jwtLib) {
366
- jwt = jwtLib.sign(
367
- {
368
- iss: credentials.email,
369
- scope: 'https://mail.google.com/',
370
- aud: 'https://oauth2.googleapis.com/token',
371
- exp: now + 3600,
372
- iat: now,
373
- sub: fromEmail,
374
- },
375
- privateKey,
376
- {
377
- algorithm: 'RS256',
378
- header: {
379
- kid: privateKey,
380
- typ: 'JWT',
381
- alg: 'RS256',
382
- },
383
- },
384
- );
385
- } else {
386
- const header = { alg: 'RS256', typ: 'JWT', kid: privateKey };
387
- const payload = {
388
- iss: credentials.email,
389
- scope: 'https://mail.google.com/',
390
- aud: 'https://oauth2.googleapis.com/token',
391
- exp: now + 3600,
392
- iat: now,
393
- sub: fromEmail,
394
- };
395
- const signatureInput = base64UrlEncode(JSON.stringify(header)) + '.' + base64UrlEncode(JSON.stringify(payload));
396
- const signer = crypto.createSign('RSA-SHA256');
397
- signer.update(signatureInput);
398
- const sig = signer.sign(privateKey);
399
- jwt = signatureInput + '.' + base64urlEscape(sig.toString('base64'));
400
- }
401
-
402
- const response = await this.helpers.request({
403
- method: 'POST',
404
- uri: 'https://oauth2.googleapis.com/token',
405
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
406
- form: {
407
- grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
408
- assertion: jwt,
409
- },
410
- json: true,
411
- });
412
-
413
- accessToken = response.access_token;
414
- TOKEN_CACHE[cacheKey] = {
415
- accessToken,
416
- expiresAt: Date.now() + 3500 * 1000,
417
- };
418
- }
407
+ const accessToken = await getOrRefreshAccessToken(this, credentials, fromEmail);
419
408
 
420
409
  let attachments = [];
421
410
  if (options.attachmentsUi && options.attachmentsUi.attachmentsBinary) {
@@ -488,10 +477,11 @@ class GmailCustom {
488
477
  if (lastError) throw lastError;
489
478
 
490
479
  } catch (error) {
491
- const continueOnFail = typeof this.continueOnFail === 'function' ? this.continueOnFail() : false;
480
+ let continueOnFail = false;
481
+ try { continueOnFail = this.continueOnFail(); } catch (e) {}
492
482
  if (continueOnFail) {
493
483
  returnData.push({
494
- json: { error: error.message },
484
+ json: { error: error.message || 'Unknown error' },
495
485
  pairedItem: { item: i },
496
486
  });
497
487
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-gmail-custom",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "Custom Gmail node for n8n with token caching and 429 retry",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",