@checkfirst/nestjs-outlook 9.0.2 → 9.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/interfaces/config/outlook-config.interface.d.ts +0 -4
- package/dist/repositories/outlook-webhook-subscription.repository.d.ts +0 -1
- package/dist/repositories/outlook-webhook-subscription.repository.js +0 -8
- package/dist/repositories/outlook-webhook-subscription.repository.js.map +1 -1
- package/dist/services/calendar/lifecycle-event-handler.service.d.ts +3 -1
- package/dist/services/calendar/lifecycle-event-handler.service.js +15 -6
- package/dist/services/calendar/lifecycle-event-handler.service.js.map +1 -1
- package/dist/services/subscription/microsoft-subscription.service.d.ts +9 -4
- package/dist/services/subscription/microsoft-subscription.service.js +146 -70
- package/dist/services/subscription/microsoft-subscription.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
An opinionated NestJS module for Microsoft Outlook integration that provides easy access to Microsoft Graph API for emails, calendars, and more.
|
|
16
16
|
|
|
17
|
-
## Why We Built This
|
|
17
|
+
## Why We Built This
|
|
18
18
|
|
|
19
19
|
At Checkfirst, we believe that tools should be as transparent as the systems they support. This library is part of our commitment to the Testing, Inspection, Certification, and Compliance (TICC) industry, where we help transform operations through intelligent automation. While our [ScheduleAI platform](https://www.checkfirst.ai/scheduleai) helps organizations optimize inspections, audits and fieldwork, we recognize that true innovation requires open collaboration. By sharing the Microsoft integration layer that powers our authentication, calendar, and email services, we're enabling developers to build robust, enterprise-grade applications without reimplementing complex Microsoft Graph protocols. Whether you're creating scheduling systems, communication tools, or productivity enhancers, this library embodies our philosophy that trust in technology starts with transparency and accessibility.
|
|
20
20
|
|
|
@@ -6,9 +6,6 @@ export interface MicrosoftOutlookStateConfig {
|
|
|
6
6
|
required?: boolean;
|
|
7
7
|
};
|
|
8
8
|
}
|
|
9
|
-
export interface SubscriptionHealthConfig {
|
|
10
|
-
staleNotificationThresholdHours?: number;
|
|
11
|
-
}
|
|
12
9
|
export interface MicrosoftOutlookConfig {
|
|
13
10
|
clientId: string;
|
|
14
11
|
clientSecret: string;
|
|
@@ -18,5 +15,4 @@ export interface MicrosoftOutlookConfig {
|
|
|
18
15
|
calendarWebhookPath?: string;
|
|
19
16
|
state?: MicrosoftOutlookStateConfig;
|
|
20
17
|
revocationEmitFlagTtlMs?: number;
|
|
21
|
-
subscription?: SubscriptionHealthConfig;
|
|
22
18
|
}
|
|
@@ -18,5 +18,4 @@ export declare class OutlookWebhookSubscriptionRepository {
|
|
|
18
18
|
updateLastNotificationAt(subscriptionId: string): Promise<void>;
|
|
19
19
|
findStaleSubscriptions(staleThresholdHours: number): Promise<OutlookWebhookSubscription[]>;
|
|
20
20
|
count(options: Parameters<Repository<OutlookWebhookSubscription>['count']>[0]): Promise<number>;
|
|
21
|
-
findExpiringSoon(threshold: Date): Promise<OutlookWebhookSubscription[]>;
|
|
22
21
|
}
|
|
@@ -142,14 +142,6 @@ let OutlookWebhookSubscriptionRepository = class OutlookWebhookSubscriptionRepos
|
|
|
142
142
|
async count(options) {
|
|
143
143
|
return await this.repository.count(options);
|
|
144
144
|
}
|
|
145
|
-
async findExpiringSoon(threshold) {
|
|
146
|
-
return this.repository.find({
|
|
147
|
-
where: {
|
|
148
|
-
isActive: true,
|
|
149
|
-
expirationDateTime: (0, typeorm_2.LessThanOrEqual)(threshold),
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
145
|
};
|
|
154
146
|
exports.OutlookWebhookSubscriptionRepository = OutlookWebhookSubscriptionRepository;
|
|
155
147
|
exports.OutlookWebhookSubscriptionRepository = OutlookWebhookSubscriptionRepository = __decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outlook-webhook-subscription.repository.js","sourceRoot":"","sources":["../../src/repositories/outlook-webhook-subscription.repository.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6CAAmD;AACnD,
|
|
1
|
+
{"version":3,"file":"outlook-webhook-subscription.repository.js","sourceRoot":"","sources":["../../src/repositories/outlook-webhook-subscription.repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
2
2
|
import { OutlookWebhookSubscriptionRepository } from '../../repositories/outlook-webhook-subscription.repository';
|
|
3
|
+
import { CalendarService } from './calendar.service';
|
|
3
4
|
import { MicrosoftSubscriptionService } from '../subscription/microsoft-subscription.service';
|
|
4
5
|
import { OutlookWebhookNotificationItemDto } from '../../dto/outlook-webhook-notification.dto';
|
|
5
6
|
import { UserIdConverterService } from '../shared/user-id-converter.service';
|
|
6
7
|
export declare class LifecycleEventHandlerService {
|
|
8
|
+
private readonly calendarService;
|
|
7
9
|
private readonly subscriptionService;
|
|
8
10
|
private readonly subscriptionRepository;
|
|
9
11
|
private readonly eventEmitter;
|
|
10
12
|
private readonly userIdConverter;
|
|
11
13
|
private readonly logger;
|
|
12
|
-
constructor(subscriptionService: MicrosoftSubscriptionService, subscriptionRepository: OutlookWebhookSubscriptionRepository, eventEmitter: EventEmitter2, userIdConverter: UserIdConverterService);
|
|
14
|
+
constructor(calendarService: CalendarService, subscriptionService: MicrosoftSubscriptionService, subscriptionRepository: OutlookWebhookSubscriptionRepository, eventEmitter: EventEmitter2, userIdConverter: UserIdConverterService);
|
|
13
15
|
handleLifecycleEvent(notificationItem: OutlookWebhookNotificationItemDto): Promise<{
|
|
14
16
|
success: boolean;
|
|
15
17
|
message: string;
|
|
@@ -8,6 +8,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
11
14
|
var LifecycleEventHandlerService_1;
|
|
12
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
16
|
exports.LifecycleEventHandlerService = void 0;
|
|
@@ -16,10 +19,12 @@ const event_emitter_1 = require("@nestjs/event-emitter");
|
|
|
16
19
|
const lifecycle_event_types_enum_1 = require("../../enums/lifecycle-event-types.enum");
|
|
17
20
|
const event_types_enum_1 = require("../../enums/event-types.enum");
|
|
18
21
|
const outlook_webhook_subscription_repository_1 = require("../../repositories/outlook-webhook-subscription.repository");
|
|
22
|
+
const calendar_service_1 = require("./calendar.service");
|
|
19
23
|
const microsoft_subscription_service_1 = require("../subscription/microsoft-subscription.service");
|
|
20
24
|
const user_id_converter_service_1 = require("../shared/user-id-converter.service");
|
|
21
25
|
let LifecycleEventHandlerService = LifecycleEventHandlerService_1 = class LifecycleEventHandlerService {
|
|
22
|
-
constructor(subscriptionService, subscriptionRepository, eventEmitter, userIdConverter) {
|
|
26
|
+
constructor(calendarService, subscriptionService, subscriptionRepository, eventEmitter, userIdConverter) {
|
|
27
|
+
this.calendarService = calendarService;
|
|
23
28
|
this.subscriptionService = subscriptionService;
|
|
24
29
|
this.subscriptionRepository = subscriptionRepository;
|
|
25
30
|
this.eventEmitter = eventEmitter;
|
|
@@ -130,7 +135,6 @@ let LifecycleEventHandlerService = LifecycleEventHandlerService_1 = class Lifecy
|
|
|
130
135
|
await this.subscriptionService.createWebhookSubscription(externalUserId);
|
|
131
136
|
recreated = true;
|
|
132
137
|
this.logger.log(`[SUBSCRIPTION_REMOVED] Successfully re-created subscription for user ${subscription.userId}`);
|
|
133
|
-
this.subscriptionService.triggerCatchUpReconcile(subscription.userId, 'subscription_removed');
|
|
134
138
|
this.eventEmitter.emit(event_types_enum_1.OutlookEventTypes.SUBSCRIPTION_RECREATED, {
|
|
135
139
|
subscriptionId,
|
|
136
140
|
tenantId,
|
|
@@ -183,16 +187,19 @@ let LifecycleEventHandlerService = LifecycleEventHandlerService_1 = class Lifecy
|
|
|
183
187
|
message: 'Subscription not found',
|
|
184
188
|
};
|
|
185
189
|
}
|
|
186
|
-
this.logger.log(`[MISSED]
|
|
187
|
-
this.
|
|
190
|
+
this.logger.log(`[MISSED] Starting delta sync for user ${subscription.userId} to recover missed changes`);
|
|
191
|
+
const externalUserId = await this.userIdConverter.internalToExternal(subscription.userId);
|
|
192
|
+
const changesCount = await this.calendarService.syncDeltaChanges(externalUserId, subscriptionId);
|
|
193
|
+
this.logger.log(`[MISSED] Delta sync completed. Found ${changesCount} changes for subscription ${subscriptionId}`);
|
|
188
194
|
this.eventEmitter.emit(event_types_enum_1.OutlookEventTypes.LIFECYCLE_MISSED, {
|
|
189
195
|
subscriptionId,
|
|
190
196
|
tenantId,
|
|
191
197
|
userId: subscription.userId,
|
|
198
|
+
changesFound: changesCount,
|
|
192
199
|
});
|
|
193
200
|
return {
|
|
194
201
|
success: true,
|
|
195
|
-
message:
|
|
202
|
+
message: `Delta sync completed. Found ${changesCount} changes.`,
|
|
196
203
|
};
|
|
197
204
|
}
|
|
198
205
|
catch (error) {
|
|
@@ -214,7 +221,9 @@ let LifecycleEventHandlerService = LifecycleEventHandlerService_1 = class Lifecy
|
|
|
214
221
|
exports.LifecycleEventHandlerService = LifecycleEventHandlerService;
|
|
215
222
|
exports.LifecycleEventHandlerService = LifecycleEventHandlerService = LifecycleEventHandlerService_1 = __decorate([
|
|
216
223
|
(0, common_1.Injectable)(),
|
|
217
|
-
|
|
224
|
+
__param(0, (0, common_1.Inject)((0, common_1.forwardRef)(() => calendar_service_1.CalendarService))),
|
|
225
|
+
__metadata("design:paramtypes", [calendar_service_1.CalendarService,
|
|
226
|
+
microsoft_subscription_service_1.MicrosoftSubscriptionService,
|
|
218
227
|
outlook_webhook_subscription_repository_1.OutlookWebhookSubscriptionRepository,
|
|
219
228
|
event_emitter_1.EventEmitter2,
|
|
220
229
|
user_id_converter_service_1.UserIdConverterService])
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle-event-handler.service.js","sourceRoot":"","sources":["../../../src/services/calendar/lifecycle-event-handler.service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lifecycle-event-handler.service.js","sourceRoot":"","sources":["../../../src/services/calendar/lifecycle-event-handler.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ import { OutlookWebhookSubscription } from '../../entities/outlook-webhook-subsc
|
|
|
7
7
|
import { MicrosoftUser } from '../../entities/microsoft-user.entity';
|
|
8
8
|
import { MicrosoftOutlookConfig } from '../../interfaces/config/outlook-config.interface';
|
|
9
9
|
import { UserIdConverterService } from '../shared/user-id-converter.service';
|
|
10
|
+
import { GraphRateLimiterService } from '../shared/graph-rate-limiter.service';
|
|
10
11
|
export interface MicrosoftSubscription {
|
|
11
12
|
id: string;
|
|
12
13
|
resource: string;
|
|
@@ -49,12 +50,11 @@ export declare class MicrosoftSubscriptionService {
|
|
|
49
50
|
private readonly microsoftConfig;
|
|
50
51
|
private readonly microsoftUserRepository;
|
|
51
52
|
private readonly userIdConverter;
|
|
53
|
+
private readonly rateLimiter;
|
|
52
54
|
private readonly logger;
|
|
53
55
|
private readonly graphApiBaseUrl;
|
|
54
56
|
private readonly msAuthUrl;
|
|
55
|
-
|
|
56
|
-
constructor(microsoftAuthService: MicrosoftAuthService, webhookSubscriptionRepository: OutlookWebhookSubscriptionRepository, eventEmitter: EventEmitter2, microsoftConfig: MicrosoftOutlookConfig, microsoftUserRepository: Repository<MicrosoftUser>, userIdConverter: UserIdConverterService);
|
|
57
|
-
triggerCatchUpReconcile(internalUserId: number, reason: string): void;
|
|
57
|
+
constructor(microsoftAuthService: MicrosoftAuthService, webhookSubscriptionRepository: OutlookWebhookSubscriptionRepository, eventEmitter: EventEmitter2, microsoftConfig: MicrosoftOutlookConfig, microsoftUserRepository: Repository<MicrosoftUser>, userIdConverter: UserIdConverterService, rateLimiter: GraphRateLimiterService);
|
|
58
58
|
getActiveSubscriptions(accessToken: string): Promise<MicrosoftSubscription[]>;
|
|
59
59
|
getActiveSubscriptionsForClient(clientId: number, accessToken: string): Promise<MicrosoftSubscription[]>;
|
|
60
60
|
getActiveSubscriptionsForUser(userId: number, accessToken: string): Promise<MicrosoftSubscription[]>;
|
|
@@ -72,9 +72,14 @@ export declare class MicrosoftSubscriptionService {
|
|
|
72
72
|
cleanupSubscriptionsForUserAndResource(externalUserId: string, resource: string): Promise<SubscriptionCleanupResult>;
|
|
73
73
|
revokeTokens(refreshToken: string): Promise<void>;
|
|
74
74
|
fullCleanup(refreshToken: string, accessToken: string, filter?: SubscriptionFilter): Promise<SubscriptionCleanupResult>;
|
|
75
|
-
runProactiveRenewal(): Promise<void>;
|
|
76
75
|
verifySubscriptionHealth(): Promise<void>;
|
|
77
76
|
retryFailedSubscriptions(): Promise<void>;
|
|
77
|
+
private filterExpiringSoon;
|
|
78
|
+
private groupByUserId;
|
|
78
79
|
private detectStaleSubscriptions;
|
|
80
|
+
private verifySubscriptionsForUser;
|
|
81
|
+
private verifySubscriptionsBatch;
|
|
82
|
+
private handleBatchResponseItem;
|
|
83
|
+
private maybeRenewIfExpiringSoon;
|
|
79
84
|
private handleSubscriptionNotFoundAtMicrosoft;
|
|
80
85
|
}
|
|
@@ -28,37 +28,24 @@ const constants_1 = require("../../constants");
|
|
|
28
28
|
const event_types_enum_1 = require("../../enums/event-types.enum");
|
|
29
29
|
const microsoft_user_status_enum_1 = require("../../enums/microsoft-user-status.enum");
|
|
30
30
|
const user_id_converter_service_1 = require("../shared/user-id-converter.service");
|
|
31
|
+
const graph_rate_limiter_service_1 = require("../shared/graph-rate-limiter.service");
|
|
31
32
|
const outlook_api_executor_util_1 = require("../../utils/outlook-api-executor.util");
|
|
32
|
-
const
|
|
33
|
+
const GRAPH_BATCH_SIZE = 20;
|
|
34
|
+
const HEALTH_CHECK_WINDOW_HOURS = 24;
|
|
35
|
+
const FORCE_RENEWAL_WINDOW_HOURS = 12;
|
|
36
|
+
const STALE_NOTIFICATION_THRESHOLD_HOURS = 24;
|
|
33
37
|
let MicrosoftSubscriptionService = MicrosoftSubscriptionService_1 = class MicrosoftSubscriptionService {
|
|
34
|
-
constructor(microsoftAuthService, webhookSubscriptionRepository, eventEmitter, microsoftConfig, microsoftUserRepository, userIdConverter) {
|
|
35
|
-
var _a, _b;
|
|
38
|
+
constructor(microsoftAuthService, webhookSubscriptionRepository, eventEmitter, microsoftConfig, microsoftUserRepository, userIdConverter, rateLimiter) {
|
|
36
39
|
this.microsoftAuthService = microsoftAuthService;
|
|
37
40
|
this.webhookSubscriptionRepository = webhookSubscriptionRepository;
|
|
38
41
|
this.eventEmitter = eventEmitter;
|
|
39
42
|
this.microsoftConfig = microsoftConfig;
|
|
40
43
|
this.microsoftUserRepository = microsoftUserRepository;
|
|
41
44
|
this.userIdConverter = userIdConverter;
|
|
45
|
+
this.rateLimiter = rateLimiter;
|
|
42
46
|
this.logger = new common_1.Logger(MicrosoftSubscriptionService_1.name);
|
|
43
47
|
this.graphApiBaseUrl = 'https://graph.microsoft.com/v1.0';
|
|
44
48
|
this.msAuthUrl = 'https://login.microsoftonline.com/common/oauth2/v2.0';
|
|
45
|
-
const sub = (_a = this.microsoftConfig.subscription) !== null && _a !== void 0 ? _a : {};
|
|
46
|
-
this.staleNotificationThresholdHours =
|
|
47
|
-
(_b = sub.staleNotificationThresholdHours) !== null && _b !== void 0 ? _b : DEFAULT_STALE_NOTIFICATION_THRESHOLD_HOURS;
|
|
48
|
-
}
|
|
49
|
-
triggerCatchUpReconcile(internalUserId, reason) {
|
|
50
|
-
try {
|
|
51
|
-
this.eventEmitter.emit(event_types_enum_1.OutlookEventTypes.EVENT_NOTIFICATION, {
|
|
52
|
-
userId: internalUserId,
|
|
53
|
-
resource: { id: `catchup:${reason}` },
|
|
54
|
-
changeType: 'updated',
|
|
55
|
-
});
|
|
56
|
-
this.logger.log(`[catchUp] Triggered catch-up reconcile for user ${internalUserId} (reason=${reason})`);
|
|
57
|
-
}
|
|
58
|
-
catch (err) {
|
|
59
|
-
this.logger.warn(`[catchUp] Failed to trigger catch-up reconcile for user ${internalUserId} (reason=${reason}): ` +
|
|
60
|
-
(err instanceof Error ? err.message : String(err)));
|
|
61
|
-
}
|
|
62
49
|
}
|
|
63
50
|
async getActiveSubscriptions(accessToken) {
|
|
64
51
|
try {
|
|
@@ -233,7 +220,6 @@ let MicrosoftSubscriptionService = MicrosoftSubscriptionService_1 = class Micros
|
|
|
233
220
|
}
|
|
234
221
|
this.logger.log(`[${correlationId}] Successfully renewed subscription ${subscriptionId}. ` +
|
|
235
222
|
`New expiration: ${response.data.expirationDateTime}`);
|
|
236
|
-
this.triggerCatchUpReconcile(internalUserId, 'renew');
|
|
237
223
|
return response.data;
|
|
238
224
|
}
|
|
239
225
|
catch (error) {
|
|
@@ -543,35 +529,6 @@ let MicrosoftSubscriptionService = MicrosoftSubscriptionService_1 = class Micros
|
|
|
543
529
|
this.logger.log('✅ Full cleanup completed');
|
|
544
530
|
return result;
|
|
545
531
|
}
|
|
546
|
-
async runProactiveRenewal() {
|
|
547
|
-
const correlationId = `proactive-renewal-${Date.now()}`;
|
|
548
|
-
try {
|
|
549
|
-
const threshold = new Date(Date.now() + 60 * 60 * 1000);
|
|
550
|
-
const expiringSoon = await this.webhookSubscriptionRepository.findExpiringSoon(threshold);
|
|
551
|
-
if (expiringSoon.length === 0) {
|
|
552
|
-
this.logger.debug(`[${correlationId}] No subscriptions expiring within 1 hour`);
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
this.logger.log(`[${correlationId}] Proactive renewal: ${expiringSoon.length} subscription(s) expiring within 1 hour`);
|
|
556
|
-
let renewed = 0;
|
|
557
|
-
let failed = 0;
|
|
558
|
-
for (const sub of expiringSoon) {
|
|
559
|
-
try {
|
|
560
|
-
await this.renewWebhookSubscription(sub.subscriptionId, sub.userId);
|
|
561
|
-
renewed++;
|
|
562
|
-
}
|
|
563
|
-
catch (err) {
|
|
564
|
-
failed++;
|
|
565
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
566
|
-
this.logger.error(`[${correlationId}] Failed to renew subscription ${sub.subscriptionId}: ${msg}`);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
this.logger.log(`[${correlationId}] Proactive renewal complete: ${renewed} renewed, ${failed} failed`);
|
|
570
|
-
}
|
|
571
|
-
catch (error) {
|
|
572
|
-
this.logger.error(`[${correlationId}] Proactive renewal job failed:`, error);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
532
|
async verifySubscriptionHealth() {
|
|
576
533
|
const correlationId = `health-check-${Date.now()}`;
|
|
577
534
|
try {
|
|
@@ -588,15 +545,22 @@ let MicrosoftSubscriptionService = MicrosoftSubscriptionService_1 = class Micros
|
|
|
588
545
|
this.logger.debug(`[${correlationId}] No active subscriptions to verify`);
|
|
589
546
|
return;
|
|
590
547
|
}
|
|
591
|
-
this.
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
this.
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
548
|
+
const expiringSoon = this.filterExpiringSoon(activeSubs, HEALTH_CHECK_WINDOW_HOURS);
|
|
549
|
+
this.logger.log(`[${correlationId}] Health check: ${activeSubs.length} active, ${expiringSoon.length} expiring within ${HEALTH_CHECK_WINDOW_HOURS}h`);
|
|
550
|
+
const counters = { verified: 0, recreated: 0, renewed: 0, staleDetected: 0, failed: 0 };
|
|
551
|
+
counters.staleDetected = this.detectStaleSubscriptions(activeSubs, correlationId);
|
|
552
|
+
if (expiringSoon.length > 0) {
|
|
553
|
+
const byUser = this.groupByUserId(expiringSoon);
|
|
554
|
+
for (const [userId, userSubs] of byUser) {
|
|
555
|
+
const perUser = await this.verifySubscriptionsForUser(userId, userSubs, correlationId);
|
|
556
|
+
counters.verified += perUser.verified;
|
|
557
|
+
counters.recreated += perUser.recreated;
|
|
558
|
+
counters.renewed += perUser.renewed;
|
|
559
|
+
counters.failed += perUser.failed;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
this.logger.log(`[${correlationId}] Health check complete: ${counters.verified} verified, ${counters.recreated} recreated, ${counters.renewed} renewed, ${counters.staleDetected} stale-detected, ${counters.failed} failed`);
|
|
563
|
+
this.eventEmitter.emit(event_types_enum_1.OutlookEventTypes.HEALTH_CHECK_COMPLETED, Object.assign({ correlationId, totalActive: activeSubs.length, corruptedUsersExcluded: corruptedUserIds.length }, counters));
|
|
600
564
|
}
|
|
601
565
|
catch (error) {
|
|
602
566
|
this.logger.error(`[${correlationId}] Subscription health check job failed:`, error);
|
|
@@ -650,23 +614,141 @@ let MicrosoftSubscriptionService = MicrosoftSubscriptionService_1 = class Micros
|
|
|
650
614
|
this.logger.error(`[${correlationId}] Retry failed subscriptions job failed:`, error);
|
|
651
615
|
}
|
|
652
616
|
}
|
|
617
|
+
filterExpiringSoon(subs, hoursAhead) {
|
|
618
|
+
const threshold = new Date(Date.now() + hoursAhead * 3600 * 1000);
|
|
619
|
+
return subs.filter((sub) => sub.expirationDateTime < threshold);
|
|
620
|
+
}
|
|
621
|
+
groupByUserId(subs) {
|
|
622
|
+
const byUser = new Map();
|
|
623
|
+
for (const sub of subs) {
|
|
624
|
+
const bucket = byUser.get(sub.userId);
|
|
625
|
+
if (bucket)
|
|
626
|
+
bucket.push(sub);
|
|
627
|
+
else
|
|
628
|
+
byUser.set(sub.userId, [sub]);
|
|
629
|
+
}
|
|
630
|
+
return byUser;
|
|
631
|
+
}
|
|
653
632
|
detectStaleSubscriptions(subs, correlationId) {
|
|
654
|
-
const staleThreshold = new Date(Date.now() -
|
|
633
|
+
const staleThreshold = new Date(Date.now() - STALE_NOTIFICATION_THRESHOLD_HOURS * 3600 * 1000);
|
|
655
634
|
let staleDetected = 0;
|
|
656
635
|
for (const sub of subs) {
|
|
657
636
|
if (!sub.lastNotificationAt || sub.lastNotificationAt >= staleThreshold)
|
|
658
637
|
continue;
|
|
659
|
-
this.logger.warn(`[${correlationId}] Subscription ${sub.subscriptionId} has not received notifications since ${sub.lastNotificationAt.toISOString()}.
|
|
638
|
+
this.logger.warn(`[${correlationId}] Subscription ${sub.subscriptionId} has not received notifications since ${sub.lastNotificationAt.toISOString()}. Emitting LIFECYCLE_MISSED for delta sync.`);
|
|
660
639
|
this.eventEmitter.emit(event_types_enum_1.OutlookEventTypes.LIFECYCLE_MISSED, {
|
|
661
640
|
subscriptionId: sub.subscriptionId,
|
|
662
641
|
userId: sub.userId,
|
|
663
642
|
reason: 'health_check_stale',
|
|
664
643
|
});
|
|
665
|
-
this.triggerCatchUpReconcile(sub.userId, 'health_check_stale');
|
|
666
644
|
staleDetected++;
|
|
667
645
|
}
|
|
668
646
|
return staleDetected;
|
|
669
647
|
}
|
|
648
|
+
async verifySubscriptionsForUser(userId, userSubs, correlationId) {
|
|
649
|
+
const totals = { verified: 0, recreated: 0, renewed: 0, staleDetected: 0, failed: 0 };
|
|
650
|
+
let externalUserId;
|
|
651
|
+
let accessToken;
|
|
652
|
+
try {
|
|
653
|
+
externalUserId = await this.userIdConverter.internalToExternal(userId);
|
|
654
|
+
accessToken = await this.microsoftAuthService.getUserAccessToken({ internalUserId: userId });
|
|
655
|
+
}
|
|
656
|
+
catch (error) {
|
|
657
|
+
totals.failed += userSubs.length;
|
|
658
|
+
this.logger.error(`[${correlationId}] Token fetch failed for user ${userId}; skipping ${userSubs.length} sub(s):`, error);
|
|
659
|
+
return totals;
|
|
660
|
+
}
|
|
661
|
+
for (let offset = 0; offset < userSubs.length; offset += GRAPH_BATCH_SIZE) {
|
|
662
|
+
const chunk = userSubs.slice(offset, offset + GRAPH_BATCH_SIZE);
|
|
663
|
+
const chunkTotals = await this.verifySubscriptionsBatch(chunk, userId, externalUserId, accessToken, correlationId);
|
|
664
|
+
totals.verified += chunkTotals.verified;
|
|
665
|
+
totals.recreated += chunkTotals.recreated;
|
|
666
|
+
totals.renewed += chunkTotals.renewed;
|
|
667
|
+
totals.failed += chunkTotals.failed;
|
|
668
|
+
}
|
|
669
|
+
return totals;
|
|
670
|
+
}
|
|
671
|
+
async verifySubscriptionsBatch(chunk, userId, externalUserId, accessToken, correlationId) {
|
|
672
|
+
const totals = { verified: 0, recreated: 0, renewed: 0, staleDetected: 0, failed: 0 };
|
|
673
|
+
await this.rateLimiter.acquirePermit(externalUserId);
|
|
674
|
+
const batchPayload = {
|
|
675
|
+
requests: chunk.map((sub, index) => ({
|
|
676
|
+
id: String(index),
|
|
677
|
+
method: 'GET',
|
|
678
|
+
url: `/subscriptions/${sub.subscriptionId}`,
|
|
679
|
+
headers: { 'Prefer': 'IdType="ImmutableId"' },
|
|
680
|
+
})),
|
|
681
|
+
};
|
|
682
|
+
try {
|
|
683
|
+
const response = await (0, outlook_api_executor_util_1.executeGraphApiCall)(() => axios_1.default.post(`${this.graphApiBaseUrl}/$batch`, batchPayload, {
|
|
684
|
+
headers: {
|
|
685
|
+
Authorization: `Bearer ${accessToken}`,
|
|
686
|
+
'Content-Type': 'application/json',
|
|
687
|
+
},
|
|
688
|
+
}), {
|
|
689
|
+
logger: this.logger,
|
|
690
|
+
resourceName: `verify batch (user ${userId}, ${chunk.length} subs)`,
|
|
691
|
+
maxRetries: 2,
|
|
692
|
+
});
|
|
693
|
+
if (!(response === null || response === void 0 ? void 0 : response.data)) {
|
|
694
|
+
totals.failed += chunk.length;
|
|
695
|
+
this.logger.error(`[${correlationId}] Batch verify returned null for user ${userId}`);
|
|
696
|
+
return totals;
|
|
697
|
+
}
|
|
698
|
+
for (const item of response.data.responses) {
|
|
699
|
+
const sub = chunk[parseInt(item.id, 10)];
|
|
700
|
+
if (!sub)
|
|
701
|
+
continue;
|
|
702
|
+
const itemTotals = await this.handleBatchResponseItem(sub, item, correlationId);
|
|
703
|
+
totals.verified += itemTotals.verified;
|
|
704
|
+
totals.recreated += itemTotals.recreated;
|
|
705
|
+
totals.renewed += itemTotals.renewed;
|
|
706
|
+
totals.failed += itemTotals.failed;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
catch (error) {
|
|
710
|
+
totals.failed += chunk.length;
|
|
711
|
+
this.logger.error(`[${correlationId}] Batch verify failed for user ${userId} (${chunk.length} subs):`, error);
|
|
712
|
+
}
|
|
713
|
+
return totals;
|
|
714
|
+
}
|
|
715
|
+
async handleBatchResponseItem(sub, item, correlationId) {
|
|
716
|
+
const totals = { verified: 0, recreated: 0, renewed: 0, staleDetected: 0, failed: 0 };
|
|
717
|
+
if (item.status === 200) {
|
|
718
|
+
totals.verified = 1;
|
|
719
|
+
if (await this.maybeRenewIfExpiringSoon(sub, item.body, correlationId)) {
|
|
720
|
+
totals.renewed = 1;
|
|
721
|
+
}
|
|
722
|
+
return totals;
|
|
723
|
+
}
|
|
724
|
+
if (item.status === 404) {
|
|
725
|
+
const result = await this.handleSubscriptionNotFoundAtMicrosoft(sub.subscriptionId, sub.userId, 'health_check_404', correlationId);
|
|
726
|
+
if (result === 'recreated')
|
|
727
|
+
totals.recreated = 1;
|
|
728
|
+
else
|
|
729
|
+
totals.failed = 1;
|
|
730
|
+
return totals;
|
|
731
|
+
}
|
|
732
|
+
totals.failed = 1;
|
|
733
|
+
this.logger.error(`[${correlationId}] Unexpected status ${item.status} for sub ${sub.subscriptionId}: ${JSON.stringify(item.body)}`);
|
|
734
|
+
return totals;
|
|
735
|
+
}
|
|
736
|
+
async maybeRenewIfExpiringSoon(sub, graphSub, correlationId) {
|
|
737
|
+
if (!graphSub.expirationDateTime)
|
|
738
|
+
return false;
|
|
739
|
+
const expiration = new Date(graphSub.expirationDateTime);
|
|
740
|
+
const renewalThreshold = new Date(Date.now() + FORCE_RENEWAL_WINDOW_HOURS * 3600 * 1000);
|
|
741
|
+
if (expiration >= renewalThreshold)
|
|
742
|
+
return false;
|
|
743
|
+
this.logger.log(`[${correlationId}] Subscription ${sub.subscriptionId} expires within ${FORCE_RENEWAL_WINDOW_HOURS}h. Forcing renewal.`);
|
|
744
|
+
try {
|
|
745
|
+
await this.renewWebhookSubscription(sub.subscriptionId, sub.userId);
|
|
746
|
+
}
|
|
747
|
+
catch (error) {
|
|
748
|
+
this.logger.warn(`[${correlationId}] Forced renewal of ${sub.subscriptionId} failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
749
|
+
}
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
670
752
|
async handleSubscriptionNotFoundAtMicrosoft(subscriptionId, internalUserId, reason, correlationId) {
|
|
671
753
|
this.logger.warn(`[${correlationId}] Subscription ${subscriptionId} not found at Microsoft. Deactivating and attempting re-creation.`);
|
|
672
754
|
await this.webhookSubscriptionRepository.deactivateSubscription(subscriptionId);
|
|
@@ -679,7 +761,6 @@ let MicrosoftSubscriptionService = MicrosoftSubscriptionService_1 = class Micros
|
|
|
679
761
|
userId: internalUserId,
|
|
680
762
|
reason,
|
|
681
763
|
});
|
|
682
|
-
this.triggerCatchUpReconcile(internalUserId, `recreate_${reason}`);
|
|
683
764
|
return 'recreated';
|
|
684
765
|
}
|
|
685
766
|
catch (recreateError) {
|
|
@@ -696,12 +777,6 @@ let MicrosoftSubscriptionService = MicrosoftSubscriptionService_1 = class Micros
|
|
|
696
777
|
}
|
|
697
778
|
};
|
|
698
779
|
exports.MicrosoftSubscriptionService = MicrosoftSubscriptionService;
|
|
699
|
-
__decorate([
|
|
700
|
-
(0, schedule_1.Cron)('0 * * * *'),
|
|
701
|
-
__metadata("design:type", Function),
|
|
702
|
-
__metadata("design:paramtypes", []),
|
|
703
|
-
__metadata("design:returntype", Promise)
|
|
704
|
-
], MicrosoftSubscriptionService.prototype, "runProactiveRenewal", null);
|
|
705
780
|
__decorate([
|
|
706
781
|
(0, schedule_1.Cron)('0 */6 * * *'),
|
|
707
782
|
__metadata("design:type", Function),
|
|
@@ -722,6 +797,7 @@ exports.MicrosoftSubscriptionService = MicrosoftSubscriptionService = MicrosoftS
|
|
|
722
797
|
__metadata("design:paramtypes", [microsoft_auth_service_1.MicrosoftAuthService,
|
|
723
798
|
outlook_webhook_subscription_repository_1.OutlookWebhookSubscriptionRepository,
|
|
724
799
|
event_emitter_1.EventEmitter2, Object, typeorm_2.Repository,
|
|
725
|
-
user_id_converter_service_1.UserIdConverterService
|
|
800
|
+
user_id_converter_service_1.UserIdConverterService,
|
|
801
|
+
graph_rate_limiter_service_1.GraphRateLimiterService])
|
|
726
802
|
], MicrosoftSubscriptionService);
|
|
727
803
|
//# sourceMappingURL=microsoft-subscription.service.js.map
|