emailengine-app 2.63.4 → 2.64.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/data/google-crawlers.json +1 -1
  3. package/eslint.config.js +2 -0
  4. package/lib/account.js +6 -2
  5. package/lib/consts.js +17 -1
  6. package/lib/email-client/gmail/gmail-api.js +1 -12
  7. package/lib/email-client/imap-client.js +5 -3
  8. package/lib/email-client/outlook/graph-api.js +7 -13
  9. package/lib/email-client/outlook-client.js +363 -167
  10. package/lib/imapproxy/imap-server.js +1 -0
  11. package/lib/oauth/gmail.js +12 -1
  12. package/lib/oauth/pubsub/google.js +253 -85
  13. package/lib/oauth2-apps.js +554 -377
  14. package/lib/routes-ui.js +186 -91
  15. package/lib/schemas.js +18 -1
  16. package/lib/ui-routes/account-routes.js +1 -1
  17. package/lib/ui-routes/admin-entities-routes.js +3 -3
  18. package/lib/ui-routes/oauth-routes.js +9 -3
  19. package/package.json +9 -9
  20. package/sbom.json +1 -1
  21. package/server.js +54 -22
  22. package/static/licenses.html +27 -27
  23. package/translations/de.mo +0 -0
  24. package/translations/de.po +54 -42
  25. package/translations/en.mo +0 -0
  26. package/translations/en.po +55 -43
  27. package/translations/et.mo +0 -0
  28. package/translations/et.po +54 -42
  29. package/translations/fr.mo +0 -0
  30. package/translations/fr.po +54 -42
  31. package/translations/ja.mo +0 -0
  32. package/translations/ja.po +54 -42
  33. package/translations/messages.pot +74 -52
  34. package/translations/nl.mo +0 -0
  35. package/translations/nl.po +54 -42
  36. package/translations/pl.mo +0 -0
  37. package/translations/pl.po +54 -42
  38. package/views/config/oauth/app.hbs +12 -0
  39. package/views/config/oauth/index.hbs +2 -0
  40. package/views/config/oauth/subscriptions.hbs +175 -0
  41. package/views/error.hbs +4 -4
  42. package/views/partials/oauth_tabs.hbs +8 -0
  43. package/workers/api.js +174 -96
  44. package/workers/documents.js +1 -0
  45. package/workers/imap.js +30 -47
  46. package/workers/smtp.js +1 -0
  47. package/workers/submit.js +1 -0
  48. package/workers/webhooks.js +42 -30
package/lib/routes-ui.js CHANGED
@@ -58,7 +58,6 @@ const { Client: ElasticSearch } = require('@elastic/elasticsearch');
58
58
  const { llmPreProcess } = require('./llm-pre-process');
59
59
  const { locales } = require('./translations');
60
60
  const capa = require('./capa');
61
- const exampleWebhookPayloads = require('./payload-examples-webhooks.json');
62
61
  const exampleDocumentsPayloads = require('./payload-examples-documents.json');
63
62
  const { defaultMappings } = require('./es');
64
63
  const { getESClient } = require('../lib/document-store');
@@ -719,34 +718,6 @@ async function getOpenAiError(gt) {
719
718
  return openAiError;
720
719
  }
721
720
 
722
- async function getExampleWebhookPayloads() {
723
- let serviceUrl = await settings.get('serviceUrl');
724
- let date = new Date().toISOString();
725
-
726
- let examplePayloads = structuredClone(exampleWebhookPayloads);
727
-
728
- examplePayloads.forEach(payload => {
729
- if (payload && payload.content) {
730
- if (typeof payload.content.serviceUrl === 'string') {
731
- payload.content.serviceUrl = serviceUrl;
732
- }
733
-
734
- if (typeof payload.content.date === 'string') {
735
- payload.content.date = date;
736
- }
737
-
738
- if (payload.content.data && typeof payload.content.data.date === 'string') {
739
- payload.content.data.date = date;
740
- }
741
-
742
- if (payload.content.data && typeof payload.content.data.created === 'string') {
743
- payload.content.data.created = date;
744
- }
745
- }
746
- });
747
- return examplePayloads;
748
- }
749
-
750
721
  async function getExampleDocumentsPayloads() {
751
722
  let date = new Date().toISOString();
752
723
 
@@ -886,6 +857,21 @@ function applyRoutes(server, call) {
886
857
  throw error;
887
858
  }
888
859
 
860
+ /**
861
+ * Fetch the list of Pub/Sub apps and mark the one matching selectedId as selected.
862
+ * Returns the apps array ready for template rendering.
863
+ */
864
+ async function getPubSubAppsForSelect(selectedId) {
865
+ let result = await oauth2Apps.list(0, 1000, { pubsub: true });
866
+ let apps = (result && result.apps) || [];
867
+ for (let app of apps) {
868
+ if (app.id === selectedId) {
869
+ app.selected = true;
870
+ }
871
+ }
872
+ return apps;
873
+ }
874
+
889
875
  // List exports for account
890
876
  server.route({
891
877
  method: 'GET',
@@ -2568,6 +2554,7 @@ return true;`
2568
2554
  pageTitle: 'OAuth2',
2569
2555
  menuConfig: true,
2570
2556
  menuConfigOauth: true,
2557
+ activeApplications: true,
2571
2558
 
2572
2559
  newLink: newLink.pathname + newLink.search,
2573
2560
 
@@ -2614,6 +2601,160 @@ return true;`
2614
2601
  }
2615
2602
  });
2616
2603
 
2604
+ // GET /admin/config/oauth/subscriptions - Gmail Pub/Sub subscriptions list
2605
+ server.route({
2606
+ method: 'GET',
2607
+ path: '/admin/config/oauth/subscriptions',
2608
+ async handler(request, h) {
2609
+ try {
2610
+ let data = await oauth2Apps.list(request.query.page - 1, request.query.pageSize, { pubsub: true });
2611
+
2612
+ let gmailSubscriptionTtl = await settings.get('gmailSubscriptionTtl');
2613
+
2614
+ // Compute human-readable expiration for each app
2615
+ // meta.subscriptionExpiration is:
2616
+ // undefined - no data yet (app predates this feature or ensurePubsub hasn't run)
2617
+ // null - indefinite (no TTL set, ensurePubsub confirmed this)
2618
+ // "Ns" - TTL in seconds (e.g. "2678400s" for 31 days)
2619
+ let gt = request.app.gt;
2620
+ for (let app of data.apps) {
2621
+ if (!app.pubSubSubscription) {
2622
+ app.expirationLabel = '';
2623
+ continue;
2624
+ }
2625
+
2626
+ let meta = app.meta || {};
2627
+ if (!('subscriptionExpiration' in meta)) {
2628
+ app.expirationLabel = gt.gettext('Unknown');
2629
+ continue;
2630
+ }
2631
+
2632
+ let seconds = parseInt(meta.subscriptionExpiration, 10);
2633
+ if (seconds > 0) {
2634
+ let days = Math.round(seconds / 86400);
2635
+ app.expirationLabel = util.format(gt.ngettext('%d day', '%d days', days), days);
2636
+ } else {
2637
+ app.expirationLabel = gt.gettext('Indefinite');
2638
+ }
2639
+ }
2640
+
2641
+ let nextPage = false;
2642
+ let prevPage = false;
2643
+
2644
+ let getPagingUrl = page => {
2645
+ let url = new URL(`admin/config/oauth/subscriptions`, 'http://localhost');
2646
+
2647
+ if (page) {
2648
+ url.searchParams.append('page', page);
2649
+ }
2650
+
2651
+ if (request.query.pageSize !== DEFAULT_PAGE_SIZE) {
2652
+ url.searchParams.append('pageSize', request.query.pageSize);
2653
+ }
2654
+
2655
+ return url.pathname + url.search;
2656
+ };
2657
+
2658
+ if (data.pages > data.page + 1) {
2659
+ nextPage = getPagingUrl(data.page + 2);
2660
+ }
2661
+
2662
+ if (data.page > 0) {
2663
+ prevPage = getPagingUrl(data.page);
2664
+ }
2665
+
2666
+ return h.view(
2667
+ 'config/oauth/subscriptions',
2668
+ {
2669
+ pageTitle: 'OAuth2',
2670
+ menuConfig: true,
2671
+ menuConfigOauth: true,
2672
+ activeSubscriptions: true,
2673
+
2674
+ showPaging: data.pages > 1,
2675
+ nextPage,
2676
+ prevPage,
2677
+ firstPage: data.page === 0,
2678
+ pageLinks: new Array(data.pages || 1).fill(0).map((z, i) => ({
2679
+ url: getPagingUrl(i + 1),
2680
+ title: i + 1,
2681
+ active: i === data.page
2682
+ })),
2683
+
2684
+ apps: data.apps,
2685
+
2686
+ values: {
2687
+ gmailSubscriptionTtl: typeof gmailSubscriptionTtl === 'number' ? gmailSubscriptionTtl : ''
2688
+ }
2689
+ },
2690
+ {
2691
+ layout: 'app'
2692
+ }
2693
+ );
2694
+ } catch (err) {
2695
+ request.logger.error({ msg: 'Failed to load subscriptions page', err });
2696
+ throwAsBoom(err);
2697
+ }
2698
+ },
2699
+
2700
+ options: {
2701
+ validate: {
2702
+ options: {
2703
+ stripUnknown: true,
2704
+ abortEarly: false,
2705
+ convert: true
2706
+ },
2707
+
2708
+ async failAction(request, h /*, err*/) {
2709
+ return h.redirect('/admin/config/oauth/subscriptions').takeover();
2710
+ },
2711
+
2712
+ query: Joi.object({
2713
+ page: Joi.number().integer().min(1).max(1000000).default(1),
2714
+ pageSize: Joi.number().integer().min(1).max(250).default(DEFAULT_PAGE_SIZE)
2715
+ })
2716
+ }
2717
+ }
2718
+ });
2719
+
2720
+ server.route({
2721
+ method: 'POST',
2722
+ path: '/admin/config/oauth/subscriptions',
2723
+ async handler(request, h) {
2724
+ try {
2725
+ // Joi .empty('').allow(null) ensures this is either null or a number
2726
+ let ttl = request.payload.gmailSubscriptionTtl != null ? request.payload.gmailSubscriptionTtl : null;
2727
+ await settings.set('gmailSubscriptionTtl', ttl);
2728
+
2729
+ await request.flash({ type: 'info', message: 'Configuration updated' });
2730
+ return h.redirect('/admin/config/oauth/subscriptions');
2731
+ } catch (err) {
2732
+ await request.flash({ type: 'danger', message: 'Failed to save settings' });
2733
+ request.logger.error({ msg: 'Failed to save subscription settings', err });
2734
+ return h.redirect('/admin/config/oauth/subscriptions');
2735
+ }
2736
+ },
2737
+ options: {
2738
+ validate: {
2739
+ options: {
2740
+ stripUnknown: true,
2741
+ abortEarly: false,
2742
+ convert: true
2743
+ },
2744
+
2745
+ async failAction(request, h /*, err*/) {
2746
+ await request.flash({ type: 'danger', message: 'Invalid setting value' });
2747
+ return h.redirect('/admin/config/oauth/subscriptions').takeover();
2748
+ },
2749
+
2750
+ payload: Joi.object({
2751
+ gmailSubscriptionTtl: Joi.number().integer().empty('').allow(null).min(0).max(365),
2752
+ crumb: Joi.string().max(256)
2753
+ })
2754
+ }
2755
+ }
2756
+ });
2757
+
2617
2758
  server.route({
2618
2759
  method: 'GET',
2619
2760
  path: '/admin/config/oauth/app/{app}',
@@ -2724,6 +2865,12 @@ return true;`
2724
2865
  try {
2725
2866
  await oauth2Apps.del(request.payload.app);
2726
2867
 
2868
+ try {
2869
+ await call({ cmd: 'googlePubSubRemove', app: request.payload.app });
2870
+ } catch (err) {
2871
+ request.logger.error({ msg: 'Failed to notify workers about OAuth2 app deletion', err, app: request.payload.app });
2872
+ }
2873
+
2727
2874
  await request.flash({ type: 'info', message: `OAuth2 app deleted` });
2728
2875
 
2729
2876
  return h.redirect('/admin/config/oauth');
@@ -2743,7 +2890,7 @@ return true;`
2743
2890
 
2744
2891
  async failAction(request, h, err) {
2745
2892
  await request.flash({ type: 'danger', message: `Couldn't delete OAuth2 app. Try again.` });
2746
- request.logger.error({ msg: 'Failed to delete delete the OAuth2 application', err });
2893
+ request.logger.error({ msg: 'Failed to delete the OAuth2 application', err });
2747
2894
 
2748
2895
  return h.redirect('/admin/config/oauth').takeover();
2749
2896
  },
@@ -2768,8 +2915,6 @@ return true;`
2768
2915
  defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
2769
2916
  }
2770
2917
 
2771
- let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
2772
-
2773
2918
  return h.view(
2774
2919
  'config/oauth/new',
2775
2920
  {
@@ -2787,7 +2932,7 @@ return true;`
2787
2932
  baseScopesApi: false,
2788
2933
  baseScopesPubsub: false,
2789
2934
 
2790
- pubSubApps: pubSubApps && pubSubApps.apps,
2935
+ pubSubApps: await getPubSubAppsForSelect(null),
2791
2936
 
2792
2937
  azureClouds: structuredClone(AZURE_CLOUDS).map(entry => {
2793
2938
  if (entry.id === 'global') {
@@ -2858,7 +3003,7 @@ return true;`
2858
3003
  throw new Error('Unexpected result');
2859
3004
  }
2860
3005
 
2861
- if (oauth2App && oauth2App.pubsubUpdates && oauth2App.pubsubUpdates.pubSubSubscription) {
3006
+ if (oauth2App && oauth2App.pubsubUpdates && Object.keys(oauth2App.pubsubUpdates).length > 0) {
2862
3007
  await call({ cmd: 'googlePubSub', app: oauth2App.id });
2863
3008
  }
2864
3009
 
@@ -2881,8 +3026,6 @@ return true;`
2881
3026
  defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
2882
3027
  }
2883
3028
 
2884
- let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
2885
-
2886
3029
  return h.view(
2887
3030
  'config/oauth/new',
2888
3031
  {
@@ -2896,15 +3039,7 @@ return true;`
2896
3039
  providerData,
2897
3040
  defaultRedirectUrl,
2898
3041
 
2899
- pubSubApps:
2900
- pubSubApps &&
2901
- pubSubApps.apps &&
2902
- pubSubApps.apps.map(app => {
2903
- if (app.id === request.payload.pubSubApp) {
2904
- app.selected = true;
2905
- }
2906
- return app;
2907
- }),
3042
+ pubSubApps: await getPubSubAppsForSelect(request.payload.pubSubApp),
2908
3043
 
2909
3044
  baseScopesApi: baseScopes === 'api',
2910
3045
  baseScopesImap: baseScopes === 'imap' || !baseScopes,
@@ -2961,8 +3096,6 @@ return true;`
2961
3096
  defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
2962
3097
  }
2963
3098
 
2964
- let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
2965
-
2966
3099
  return h
2967
3100
  .view(
2968
3101
  'config/oauth/new',
@@ -2977,15 +3110,7 @@ return true;`
2977
3110
  providerData,
2978
3111
  defaultRedirectUrl,
2979
3112
 
2980
- pubSubApps:
2981
- pubSubApps &&
2982
- pubSubApps.apps &&
2983
- pubSubApps.apps.map(app => {
2984
- if (app.id === request.payload.pubSubApp) {
2985
- app.selected = true;
2986
- }
2987
- return app;
2988
- }),
3113
+ pubSubApps: await getPubSubAppsForSelect(request.payload.pubSubApp),
2989
3114
 
2990
3115
  baseScopesApi: baseScopes === 'api',
2991
3116
  baseScopesImap: baseScopes === 'imap' || !baseScopes,
@@ -3041,8 +3166,6 @@ return true;`
3041
3166
  tenant: appData.authority && !['common', 'organizations', 'consumers'].includes(appData.authority) ? appData.authority : ''
3042
3167
  });
3043
3168
 
3044
- let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
3045
-
3046
3169
  return h.view(
3047
3170
  'config/oauth/edit',
3048
3171
  {
@@ -3059,15 +3182,7 @@ return true;`
3059
3182
  hasClientSecret: !!appData.clientSecret,
3060
3183
  hasServiceKey: !!appData.serviceKey,
3061
3184
 
3062
- pubSubApps:
3063
- pubSubApps &&
3064
- pubSubApps.apps &&
3065
- pubSubApps.apps.map(app => {
3066
- if (app.id === values.pubSubApp) {
3067
- app.selected = true;
3068
- }
3069
- return app;
3070
- }),
3185
+ pubSubApps: await getPubSubAppsForSelect(values.pubSubApp),
3071
3186
 
3072
3187
  values,
3073
3188
 
@@ -3142,7 +3257,7 @@ return true;`
3142
3257
  throw new Error('Unexpected result');
3143
3258
  }
3144
3259
 
3145
- if (oauth2App && oauth2App.pubsubUpdates && oauth2App.pubsubUpdates.pubSubSubscription) {
3260
+ if (oauth2App && oauth2App.pubsubUpdates && Object.keys(oauth2App.pubsubUpdates).length > 0) {
3146
3261
  await call({ cmd: 'googlePubSub', app: oauth2App.id });
3147
3262
  }
3148
3263
 
@@ -3160,8 +3275,6 @@ return true;`
3160
3275
  defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
3161
3276
  }
3162
3277
 
3163
- let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
3164
-
3165
3278
  return h.view(
3166
3279
  'config/oauth/edit',
3167
3280
  {
@@ -3177,15 +3290,7 @@ return true;`
3177
3290
  hasClientSecret: !!appData.clientSecret,
3178
3291
  hasServiceKey: !!appData.serviceKey,
3179
3292
 
3180
- pubSubApps:
3181
- pubSubApps &&
3182
- pubSubApps.apps &&
3183
- pubSubApps.apps.map(app => {
3184
- if (app.id === request.payload.pubSubApp) {
3185
- app.selected = true;
3186
- }
3187
- return app;
3188
- }),
3293
+ pubSubApps: await getPubSubAppsForSelect(request.payload.pubSubApp),
3189
3294
 
3190
3295
  baseScopesApi: request.payload.baseScopes === 'api',
3191
3296
  baseScopesImap: request.payload.baseScopes === 'imap' || !request.payload.baseScopes,
@@ -3249,8 +3354,6 @@ return true;`
3249
3354
  defaultRedirectUrl = defaultRedirectUrl.replace(/^http:\/\/127\.0\.0\.1\b/i, 'http://localhost');
3250
3355
  }
3251
3356
 
3252
- let pubSubApps = await oauth2Apps.list(0, 1000, { pubsub: true });
3253
-
3254
3357
  return h
3255
3358
  .view(
3256
3359
  'config/oauth/edit',
@@ -3268,15 +3371,7 @@ return true;`
3268
3371
  hasClientSecret: !!appData.clientSecret,
3269
3372
  hasServiceKey: !!appData.serviceKey,
3270
3373
 
3271
- pubSubApps:
3272
- pubSubApps &&
3273
- pubSubApps.apps &&
3274
- pubSubApps.apps.map(app => {
3275
- if (app.id === request.payload.pubSubApp) {
3276
- app.selected = true;
3277
- }
3278
- return app;
3279
- }),
3374
+ pubSubApps: await getPubSubAppsForSelect(request.payload.pubSubApp),
3280
3375
 
3281
3376
  baseScopesApi: request.payload.baseScopes === 'api',
3282
3377
  baseScopesImap: request.payload.baseScopes === 'imap' || !request.payload.baseScopes,
@@ -5473,7 +5568,7 @@ ${Buffer.from(data.content, 'base64url').toString('base64')}
5473
5568
 
5474
5569
  async failAction(request, h, err) {
5475
5570
  await request.flash({ type: 'danger', message: `Couldn't delete account. Try again.` });
5476
- request.logger.error({ msg: 'Failed to delete delete the account', err });
5571
+ request.logger.error({ msg: 'Failed to delete the account', err });
5477
5572
 
5478
5573
  return h.redirect('/admin/accounts').takeover();
5479
5574
  },
package/lib/schemas.js CHANGED
@@ -450,6 +450,14 @@ const settingsSchema = {
450
450
  queueKeep: Joi.number().integer().empty('').min(0).description('Number of completed and failed queue entries to retain for debugging'),
451
451
  deliveryAttempts: Joi.number().integer().empty('').min(0).description('Maximum number of delivery attempts before marking a message as permanently failed'),
452
452
 
453
+ gmailSubscriptionTtl: Joi.number()
454
+ .integer()
455
+ .empty('')
456
+ .min(0)
457
+ .max(365)
458
+ .description('Gmail Pub/Sub subscription inactivity expiration in days. Empty for Google default (31 days), 0 for no expiration.')
459
+ .label('GmailSubscriptionTTL'),
460
+
453
461
  /* ────────────── Templates ────────────── */
454
462
 
455
463
  templateHeader: Joi.string()
@@ -1159,6 +1167,13 @@ const lastErrorSchema = Joi.object({
1159
1167
  .label('OAuthTokenRequestError')
1160
1168
  }).label('AccountErrorEntry');
1161
1169
 
1170
+ const pubSubErrorSchema = Joi.object({
1171
+ message: Joi.string().example('Failed to process subscription loop').description('Error message'),
1172
+ description: Joi.string().allow(null).example('Subscription not found').description('Error details')
1173
+ })
1174
+ .description('Pub/Sub subscription error, if any')
1175
+ .label('PubSubError');
1176
+
1162
1177
  const templateSchemas = {
1163
1178
  subject: Joi.string()
1164
1179
  .allow('')
@@ -1350,6 +1365,7 @@ const googleProjectIdSchema = Joi.string()
1350
1365
  .trim()
1351
1366
  .allow('', false, null)
1352
1367
  .max(256)
1368
+ .pattern(/^[a-z][a-z0-9-]{4,28}[a-z0-9]$/)
1353
1369
  .example('project-name-425411')
1354
1370
  .description('Google Cloud Project ID')
1355
1371
  .label('GoogleProjectId');
@@ -1880,7 +1896,8 @@ module.exports = {
1880
1896
  exportStatusSchema,
1881
1897
  exportListSchema,
1882
1898
  exportProgressSchema,
1883
- exportIdSchema
1899
+ exportIdSchema,
1900
+ pubSubErrorSchema
1884
1901
  };
1885
1902
 
1886
1903
  /*
@@ -1352,7 +1352,7 @@ function init(args) {
1352
1352
 
1353
1353
  async failAction(request, h, err) {
1354
1354
  await request.flash({ type: 'danger', message: `Couldn't delete account. Try again.` });
1355
- request.logger.error({ msg: 'Failed to delete delete the account', err });
1355
+ request.logger.error({ msg: 'Failed to delete the account', err });
1356
1356
 
1357
1357
  return h.redirect('/admin/accounts').takeover();
1358
1358
  },
@@ -766,7 +766,7 @@ return payload;`)
766
766
 
767
767
  async failAction(request, h, err) {
768
768
  await request.flash({ type: 'danger', message: `Couldn't delete webhook. Try again.` });
769
- request.logger.error({ msg: 'Failed to delete delete Webhook Route', err });
769
+ request.logger.error({ msg: 'Failed to delete Webhook Route', err });
770
770
 
771
771
  return h.redirect('/admin/webhooks').takeover();
772
772
  },
@@ -1415,7 +1415,7 @@ return payload;`)
1415
1415
 
1416
1416
  async failAction(request, h, err) {
1417
1417
  await request.flash({ type: 'danger', message: `Couldn't delete account. Try again.` });
1418
- request.logger.error({ msg: 'Failed to delete delete the account', err });
1418
+ request.logger.error({ msg: 'Failed to delete the account', err });
1419
1419
 
1420
1420
  return h.redirect('/admin/templates').takeover();
1421
1421
  },
@@ -2122,7 +2122,7 @@ return payload;`)
2122
2122
 
2123
2123
  async failAction(request, h, err) {
2124
2124
  await request.flash({ type: 'danger', message: `Couldn't delete gateway. Try again.` });
2125
- request.logger.error({ msg: 'Failed to delete delete the gateway', err });
2125
+ request.logger.error({ msg: 'Failed to delete the gateway', err });
2126
2126
 
2127
2127
  return h.redirect('/admin/gateways').takeover();
2128
2128
  },
@@ -402,6 +402,12 @@ function init({ server, call }) {
402
402
  try {
403
403
  await oauth2Apps.del(request.payload.app);
404
404
 
405
+ try {
406
+ await call({ cmd: 'googlePubSubRemove', app: request.payload.app });
407
+ } catch (err) {
408
+ request.logger.error({ msg: 'Failed to notify workers about OAuth2 app deletion', err, app: request.payload.app });
409
+ }
410
+
405
411
  await request.flash({ type: 'info', message: `OAuth2 app deleted` });
406
412
 
407
413
  return h.redirect('/admin/config/oauth');
@@ -421,7 +427,7 @@ function init({ server, call }) {
421
427
 
422
428
  async failAction(request, h, err) {
423
429
  await request.flash({ type: 'danger', message: `Couldn't delete OAuth2 app. Try again.` });
424
- request.logger.error({ msg: 'Failed to delete delete the OAuth2 application', err });
430
+ request.logger.error({ msg: 'Failed to delete the OAuth2 application', err });
425
431
 
426
432
  return h.redirect('/admin/config/oauth').takeover();
427
433
  },
@@ -538,7 +544,7 @@ function init({ server, call }) {
538
544
  throw new Error('Unexpected result');
539
545
  }
540
546
 
541
- if (oauth2App && oauth2App.pubsubUpdates && oauth2App.pubsubUpdates.pubSubSubscription) {
547
+ if (oauth2App && oauth2App.pubsubUpdates && Object.keys(oauth2App.pubsubUpdates).length > 0) {
542
548
  await call({ cmd: 'googlePubSub', app: oauth2App.id });
543
549
  }
544
550
 
@@ -824,7 +830,7 @@ function init({ server, call }) {
824
830
  throw new Error('Unexpected result');
825
831
  }
826
832
 
827
- if (oauth2App && oauth2App.pubsubUpdates && oauth2App.pubsubUpdates.pubSubSubscription) {
833
+ if (oauth2App && oauth2App.pubsubUpdates && Object.keys(oauth2App.pubsubUpdates).length > 0) {
828
834
  await call({ cmd: 'googlePubSub', app: oauth2App.id });
829
835
  }
830
836
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emailengine-app",
3
- "version": "2.63.4",
3
+ "version": "2.64.0",
4
4
  "private": false,
5
5
  "productTitle": "EmailEngine",
6
6
  "description": "Email Sync Engine",
@@ -44,8 +44,8 @@
44
44
  "homepage": "https://emailengine.app/",
45
45
  "dependencies": {
46
46
  "@bugsnag/js": "8.8.1",
47
- "@bull-board/api": "6.20.3",
48
- "@bull-board/hapi": "6.20.3",
47
+ "@bull-board/api": "6.20.5",
48
+ "@bull-board/hapi": "6.20.5",
49
49
  "@elastic/elasticsearch": "8.15.3",
50
50
  "@hapi/accept": "6.0.3",
51
51
  "@hapi/bell": "13.1.0",
@@ -56,11 +56,11 @@
56
56
  "@hapi/inert": "7.1.0",
57
57
  "@hapi/vision": "7.0.3",
58
58
  "@phc/pbkdf2": "1.1.14",
59
- "@postalsys/bounce-classifier": "^2.0.0",
59
+ "@postalsys/bounce-classifier": "^2.1.0",
60
60
  "@postalsys/certs": "1.0.12",
61
61
  "@postalsys/ee-client": "1.3.0",
62
- "@postalsys/email-ai-tools": "1.11.4",
63
- "@postalsys/email-text-tools": "2.4.2",
62
+ "@postalsys/email-ai-tools": "1.12.0",
63
+ "@postalsys/email-text-tools": "2.4.3",
64
64
  "@postalsys/gettext": "4.1.1",
65
65
  "@postalsys/joi-messages": "1.0.5",
66
66
  "@postalsys/templates": "2.0.0",
@@ -68,7 +68,7 @@
68
68
  "@zone-eu/wild-config": "1.7.3",
69
69
  "ace-builds": "1.43.6",
70
70
  "base32.js": "0.1.0",
71
- "bullmq": "5.70.4",
71
+ "bullmq": "5.71.0",
72
72
  "compare-versions": "6.1.1",
73
73
  "dotenv": "17.3.1",
74
74
  "encoding-japanese": "2.2.0",
@@ -82,7 +82,7 @@
82
82
  "html-to-text": "9.0.5",
83
83
  "ical.js": "1.5.0",
84
84
  "iconv-lite": "0.7.2",
85
- "imapflow": "1.2.13",
85
+ "imapflow": "1.2.15",
86
86
  "ioredfour": "1.4.0",
87
87
  "ioredis": "5.10.0",
88
88
  "ipaddr.js": "2.3.0",
@@ -111,7 +111,7 @@
111
111
  "speakeasy": "2.0.0",
112
112
  "startbootstrap-sb-admin-2": "3.3.7",
113
113
  "timezones-list": "3.1.0",
114
- "undici": "7.22.0",
114
+ "undici": "7.24.4",
115
115
  "xml2js": "0.6.2"
116
116
  },
117
117
  "devDependencies": {