notification-services
Version:
Use email, sms and custom notification services for node.js application easily
227 lines (226 loc) • 7.31 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FCMv1 = exports.fcmSendMulticast = exports.fcmSendToCondition = exports.fcmSendToTopic = exports.fcmSendToToken = exports.fcmSend = exports.fcmConfig = void 0;
const axios_1 = __importDefault(require("axios"));
const google_auth_library_1 = require("google-auth-library");
// Cache for auth tokens
const authTokenCache = {};
class FCMv1 {
constructor(config) {
this.googleAuth = null;
this.projectId = config.projectId;
this.serviceAccountKey = config.serviceAccountKey;
}
/**
* Initialize Google Auth client
*/
async initializeAuth() {
if (this.googleAuth) {
return this.googleAuth;
}
let keyFile;
let credentials;
if (typeof this.serviceAccountKey === 'string') {
// Path to service account key file
keyFile = this.serviceAccountKey;
}
else {
// Service account key object
credentials = this.serviceAccountKey;
}
this.googleAuth = new google_auth_library_1.GoogleAuth({
keyFile,
credentials,
scopes: ['https://www.googleapis.com/auth/firebase.messaging'],
});
return this.googleAuth;
}
/**
* Get OAuth2 access token with caching
*/
async getAccessToken() {
const cacheKey = `fcm_${this.projectId}`;
const cached = authTokenCache[cacheKey];
if (cached && Date.now() < cached.expiresAt) {
return cached.token;
}
try {
const auth = await this.initializeAuth();
const client = await auth.getClient();
const tokenResponse = await client.getAccessToken();
if (!tokenResponse.token) {
throw new Error('Failed to obtain access token');
}
// Cache token for 50 minutes (tokens are valid for 1 hour)
authTokenCache[cacheKey] = {
token: tokenResponse.token,
expiresAt: Date.now() + (50 * 60 * 1000),
};
return tokenResponse.token;
}
catch (error) {
throw new Error(`Failed to get access token: ${error.message}`);
}
}
/**
* Send FCM message
*/
async send(options) {
const { message, validateOnly = false } = options;
// Validate message
if (!message.token && !message.topic && !message.condition) {
throw new Error('Message must have either token, topic, or condition');
}
try {
const accessToken = await this.getAccessToken();
const url = `https://fcm.googleapis.com/v1/projects/${this.projectId}/messages:send`;
const payload = {
message,
validate_only: validateOnly,
};
const headers = {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
};
const response = await axios_1.default.post(url, payload, { headers });
return response.data;
}
catch (error) {
if (error.response && error.response.data) {
return error.response.data;
}
throw new Error(`FCM send failed: ${error.message}`);
}
}
/**
* Send message to single token
*/
async sendToToken(token, messageOptions) {
const message = {
...messageOptions,
token,
};
return this.send({ message });
}
/**
* Send message to topic
*/
async sendToTopic(topic, messageOptions) {
const message = {
...messageOptions,
topic,
};
return this.send({ message });
}
/**
* Send message to condition
*/
async sendToCondition(condition, messageOptions) {
const message = {
...messageOptions,
condition,
};
return this.send({ message });
}
/**
* Send to multiple tokens (sends individual messages)
*/
async sendMulticast(tokens, messageOptions) {
const promises = tokens.map(token => this.sendToToken(token, messageOptions));
const responses = await Promise.allSettled(promises);
const results = [];
let successCount = 0;
let failureCount = 0;
responses.forEach((result) => {
if (result.status === 'fulfilled') {
results.push(result.value);
if (!result.value.error) {
successCount++;
}
else {
failureCount++;
}
}
else {
results.push({
error: {
code: 500,
message: result.reason.message || 'Unknown error',
status: 'INTERNAL',
},
});
failureCount++;
}
});
return {
responses: results,
successCount,
failureCount,
};
}
}
exports.FCMv1 = FCMv1;
// Convenience functions for backward compatibility and ease of use
let fcmInstance = null;
/**
* Configure FCM v1 client
*/
const fcmConfig = (config) => {
fcmInstance = new FCMv1(config);
return fcmInstance;
};
exports.fcmConfig = fcmConfig;
/**
* Send FCM message using the configured instance
*/
const fcmSend = async (options) => {
if (!fcmInstance) {
throw new Error('FCM not configured. Please call fcmConfig() first.');
}
return fcmInstance.send(options);
};
exports.fcmSend = fcmSend;
/**
* Send to single token using the configured instance
*/
const fcmSendToToken = async (token, messageOptions) => {
if (!fcmInstance) {
throw new Error('FCM not configured. Please call fcmConfig() first.');
}
return fcmInstance.sendToToken(token, messageOptions);
};
exports.fcmSendToToken = fcmSendToToken;
/**
* Send to topic using the configured instance
*/
const fcmSendToTopic = async (topic, messageOptions) => {
if (!fcmInstance) {
throw new Error('FCM not configured. Please call fcmConfig() first.');
}
return fcmInstance.sendToTopic(topic, messageOptions);
};
exports.fcmSendToTopic = fcmSendToTopic;
/**
* Send to condition using the configured instance
*/
const fcmSendToCondition = async (condition, messageOptions) => {
if (!fcmInstance) {
throw new Error('FCM not configured. Please call fcmConfig() first.');
}
return fcmInstance.sendToCondition(condition, messageOptions);
};
exports.fcmSendToCondition = fcmSendToCondition;
/**
* Send to multiple tokens using the configured instance
*/
const fcmSendMulticast = async (tokens, messageOptions) => {
if (!fcmInstance) {
throw new Error('FCM not configured. Please call fcmConfig() first.');
}
return fcmInstance.sendMulticast(tokens, messageOptions);
};
exports.fcmSendMulticast = fcmSendMulticast;
exports.default = FCMv1;