@checkfirst/nestjs-outlook
Version:
An opinionated NestJS module for Microsoft Outlook integration that provides easy access to Microsoft Graph API for emails, calendars, and more.
192 lines • 10.5 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var EmailService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmailService = void 0;
const common_1 = require("@nestjs/common");
const event_emitter_1 = require("@nestjs/event-emitter");
const microsoft_graph_client_1 = require("@microsoft/microsoft-graph-client");
const axios_1 = require("axios");
const microsoft_auth_service_1 = require("../auth/microsoft-auth.service");
const constants_1 = require("../../constants");
const outlook_webhook_subscription_repository_1 = require("../../repositories/outlook-webhook-subscription.repository");
const event_types_enum_1 = require("../../enums/event-types.enum");
const microsoft_user_entity_1 = require("../../entities/microsoft-user.entity");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
let EmailService = EmailService_1 = class EmailService {
constructor(microsoftAuthService, webhookSubscriptionRepository, eventEmitter, microsoftConfig, microsoftUserRepository) {
this.microsoftAuthService = microsoftAuthService;
this.webhookSubscriptionRepository = webhookSubscriptionRepository;
this.eventEmitter = eventEmitter;
this.microsoftConfig = microsoftConfig;
this.microsoftUserRepository = microsoftUserRepository;
this.logger = new common_1.Logger(EmailService_1.name);
}
async sendEmail(message, externalUserId) {
try {
const accessToken = await this.microsoftAuthService.getUserAccessTokenByExternalUserId(externalUserId);
const client = microsoft_graph_client_1.Client.init({
authProvider: (done) => {
done(null, accessToken);
},
});
const sentMessage = await client
.api('/me/sendMail')
.post({ message });
return {
message: sentMessage,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
this.logger.error(`Failed to send email: ${errorMessage}`);
throw new Error(`Failed to send email: ${errorMessage}`);
}
}
async createWebhookSubscription(externalUserId) {
try {
const accessToken = await this.microsoftAuthService.getUserAccessTokenByExternalUserId(externalUserId);
const expirationDateTime = new Date();
expirationDateTime.setHours(expirationDateTime.getHours() + 72);
const appUrl = this.microsoftConfig.backendBaseUrl || 'http://localhost:3000';
const basePath = this.microsoftConfig.basePath;
const basePathUrl = basePath ? `${appUrl}/${basePath}` : appUrl;
const notificationUrl = `${basePathUrl}/email/webhook`;
const subscriptionData = {
changeType: 'created,updated,deleted',
notificationUrl,
lifecycleNotificationUrl: notificationUrl,
resource: '/me/messages',
expirationDateTime: expirationDateTime.toISOString(),
clientState: `user_${externalUserId}_${Math.random().toString(36).substring(2, 15)}`,
};
this.logger.debug(`Creating email webhook subscription with notificationUrl: ${notificationUrl}`);
this.logger.debug(`Subscription data: ${JSON.stringify(subscriptionData)}`);
const response = await axios_1.default.post('https://graph.microsoft.com/v1.0/subscriptions', subscriptionData, {
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
});
this.logger.log(`Created email webhook subscription ${response.data.id || 'unknown'} for user ${externalUserId}`);
const internalUserId = parseInt(externalUserId, 10);
await this.webhookSubscriptionRepository.saveSubscription({
subscriptionId: response.data.id,
userId: internalUserId,
resource: response.data.resource,
changeType: response.data.changeType,
clientState: response.data.clientState || '',
notificationUrl: response.data.notificationUrl,
expirationDateTime: response.data.expirationDateTime ? new Date(response.data.expirationDateTime) : new Date(),
});
this.logger.debug(`Stored subscription`);
return response.data;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
this.logger.error(`Failed to create email webhook subscription: ${errorMessage}`);
throw new Error(`Failed to create email webhook subscription: ${errorMessage}`);
}
}
async handleEmailWebhook(notificationItem) {
try {
const { subscriptionId, clientState, resource, changeType } = notificationItem;
this.logger.debug(`Received email webhook notification for subscription: ${subscriptionId || 'unknown'}`);
this.logger.debug(`Resource: ${resource || 'unknown'}, ChangeType: ${String(changeType || 'unknown')}`);
const subscription = await this.webhookSubscriptionRepository.findBySubscriptionId(subscriptionId || '');
if (!subscription) {
this.logger.warn(`Unknown subscription ID: ${subscriptionId || 'unknown'}`);
return { success: false, message: 'Unknown subscription' };
}
if (subscription.clientState && clientState !== subscription.clientState) {
this.logger.warn('Client state mismatch');
return { success: false, message: 'Client state mismatch' };
}
const internalUserId = subscription.userId;
if (!internalUserId) {
this.logger.warn('Could not determine user ID from client state');
return { success: false, message: 'Invalid client state format' };
}
let eventType;
switch (changeType) {
case 'created':
eventType = event_types_enum_1.OutlookEventTypes.EMAIL_RECEIVED;
break;
case 'updated':
eventType = event_types_enum_1.OutlookEventTypes.EMAIL_UPDATED;
break;
case 'deleted':
eventType = event_types_enum_1.OutlookEventTypes.EMAIL_DELETED;
break;
default:
eventType = null;
this.logger.warn(`Unknown change type received: ${String(changeType)}`);
return { success: false, message: `Unsupported change type: ${String(changeType)}` };
}
let emailData = {};
if (changeType === 'created' && resource) {
try {
const messageId = resource.split('/').pop();
if (messageId) {
const accessToken = await this.microsoftAuthService.getUserAccessTokenByUserId(internalUserId);
const client = microsoft_graph_client_1.Client.init({
authProvider: (done) => {
done(null, accessToken);
},
});
emailData = await client
.api(`/me/messages/${messageId}`)
.select('id,subject,receivedDateTime,from,toRecipients,ccRecipients,body')
.get();
this.logger.log(`Retrieved email details for message ID: ${messageId}`);
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
this.logger.error(`Failed to retrieve email details: ${errorMessage}`);
}
}
const resourceData = {
id: '',
userId: internalUserId,
subscriptionId,
resource,
changeType,
data: emailData
};
if (eventType) {
this.eventEmitter.emit(eventType, resourceData);
this.logger.log(`Processed email webhook notification: ${eventType}`);
}
return { success: true, message: 'Notification processed' };
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
this.logger.error(`Error processing email webhook notification: ${errorMessage}`);
return { success: false, message: errorMessage };
}
}
};
exports.EmailService = EmailService;
exports.EmailService = EmailService = EmailService_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, common_1.Inject)((0, common_1.forwardRef)(() => microsoft_auth_service_1.MicrosoftAuthService))),
__param(3, (0, common_1.Inject)(constants_1.MICROSOFT_CONFIG)),
__param(4, (0, typeorm_1.InjectRepository)(microsoft_user_entity_1.MicrosoftUser)),
__metadata("design:paramtypes", [microsoft_auth_service_1.MicrosoftAuthService,
outlook_webhook_subscription_repository_1.OutlookWebhookSubscriptionRepository,
event_emitter_1.EventEmitter2, Object, typeorm_2.Repository])
], EmailService);
//# sourceMappingURL=email.service.js.map