firebase-tools
Version:
Command-Line Interface for Firebase
123 lines (122 loc) • 5.49 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.NOTIFICATION_CHANNELS_MASK_PATH = exports.EMAIL_CHANNEL_TYPE = exports.FIREBASE_EMAIL_CHANNEL_DISPLAY_NAME_SUFFIX = exports.FIREBASE_CHANNEL_LABEL = void 0;
exports.generateAlertPolicy = generateAlertPolicy;
exports.fetchFirebaseEmailChannel = fetchFirebaseEmailChannel;
exports.createEmailChannel = createEmailChannel;
exports.resolveEmailNotificationChannel = resolveEmailNotificationChannel;
exports.addChannelToPolicy = addChannelToPolicy;
exports.enableAlert = enableAlert;
exports.enableAlerts = enableAlerts;
const error_1 = require("../error");
const cloudmonitoring_1 = require("../gcp/cloudmonitoring");
const logger_1 = require("../logger");
const utils_1 = require("./utils");
exports.FIREBASE_CHANNEL_LABEL = "is_firebase_channel";
exports.FIREBASE_EMAIL_CHANNEL_DISPLAY_NAME_SUFFIX = " - for Firebase alerts";
exports.EMAIL_CHANNEL_TYPE = "email";
exports.NOTIFICATION_CHANNELS_MASK_PATH = "notification_channels";
async function generateAlertPolicy(projectId, appId, alertType) {
logger_1.logger.debug(`[crashlytics] generateAlertPolicy called with projectId: ${projectId}, appId: ${appId}, alertType: ${alertType}`);
try {
const response = await utils_1.CRASHLYTICS_API_CLIENT.request({
method: "POST",
headers: {
"Content-Type": "application/json",
},
path: `/projects/${projectId}/apps/${appId}:generateAlertPolicy`,
body: { alertType },
timeout: utils_1.TIMEOUT,
});
return response.body.alertPolicy;
}
catch (err) {
const status = (0, error_1.getErrStatus)(err);
const msg = (0, error_1.getErrMsg)(err);
throw new error_1.FirebaseError(`Failed to generate Crashlytics alert policy for ${alertType} on app ${appId} (status ${status}): ${msg}`, { original: (0, error_1.getError)(err), status });
}
}
async function fetchFirebaseEmailChannel(projectId, userEmail) {
const escapedEmail = userEmail.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
const filter = `type = "${exports.EMAIL_CHANNEL_TYPE}" AND ` +
`labels.email_address = "${escapedEmail}" AND ` +
`user_labels.${exports.FIREBASE_CHANNEL_LABEL} = "true"`;
const channels = await (0, cloudmonitoring_1.listNotificationChannels)(projectId, filter);
return channels[0];
}
async function createEmailChannel(projectId, userEmail) {
return await (0, cloudmonitoring_1.createNotificationChannel)(projectId, {
type: exports.EMAIL_CHANNEL_TYPE,
displayName: `${userEmail}${exports.FIREBASE_EMAIL_CHANNEL_DISPLAY_NAME_SUFFIX}`,
labels: {
email_address: userEmail,
},
userLabels: {
[exports.FIREBASE_CHANNEL_LABEL]: "true",
},
});
}
async function resolveEmailNotificationChannel(projectId, userEmail) {
const existingChannel = await fetchFirebaseEmailChannel(projectId, userEmail);
return existingChannel ?? (await createEmailChannel(projectId, userEmail));
}
function addChannelToPolicy(policy, channelName) {
const currentChannels = policy.notificationChannels ?? [];
if (currentChannels.includes(channelName)) {
return policy;
}
return {
...policy,
notificationChannels: [...currentChannels, channelName],
};
}
async function enableAlert(projectId, appId, alertType, target) {
let channel;
if (!target.channelName && target.userEmail) {
channel = await resolveEmailNotificationChannel(projectId, target.userEmail);
}
else if (!target.channelName) {
throw new error_1.FirebaseError("Either channelName or userEmail must be specified to enable a Crashlytics alert.", { exit: 1 });
}
const channelName = target.channelName ?? channel?.name;
if (!channelName) {
throw new error_1.FirebaseError("Resolved notification channel is missing a resource name.", {
exit: 1,
});
}
const policyResourceName = await generateAlertPolicy(projectId, appId, alertType);
const targetPolicy = await (0, cloudmonitoring_1.getAlertPolicy)(policyResourceName);
const policyWithChannel = addChannelToPolicy(targetPolicy, channelName);
const updatedPolicy = policyWithChannel === targetPolicy
? targetPolicy
: await (0, cloudmonitoring_1.updateAlertPolicy)(policyWithChannel, exports.NOTIFICATION_CHANNELS_MASK_PATH);
return { channel, policy: updatedPolicy };
}
async function enableAlerts(projectId, appId, alertTypes, userEmail) {
if (alertTypes.length === 0) {
return [];
}
const channel = await resolveEmailNotificationChannel(projectId, userEmail);
const channelName = channel.name;
if (!channelName) {
throw new error_1.FirebaseError("Notification channel for Crashlytics alerts is missing a name.", {
exit: 1,
});
}
const results = await Promise.allSettled(alertTypes.map(async (alertType) => {
const { policy } = await enableAlert(projectId, appId, alertType, {
channelName,
});
return policy;
}));
const policies = [];
for (const res of results) {
if (res.status === "fulfilled") {
policies.push(res.value);
}
else {
logger_1.logger.debug(`[crashlytics] Failed to enable alert: ${(0, error_1.getErrMsg)(res.reason)}`);
}
}
return policies;
}