API/api.medcify.app/node_modules/firebase-admin/lib/messaging/messaging-internal.js

485 lines
23 KiB
JavaScript
Raw Permalink Normal View History

2022-09-26 06:11:44 +00:00
/*! firebase-admin v11.0.1 */
"use strict";
/*!
* Copyright 2020 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateMessage = exports.BLACKLISTED_OPTIONS_KEYS = exports.BLACKLISTED_DATA_PAYLOAD_KEYS = void 0;
const index_1 = require("../utils/index");
const error_1 = require("../utils/error");
const validator = require("../utils/validator");
// Keys which are not allowed in the messaging data payload object.
exports.BLACKLISTED_DATA_PAYLOAD_KEYS = ['from'];
// Keys which are not allowed in the messaging options object.
exports.BLACKLISTED_OPTIONS_KEYS = [
'condition', 'data', 'notification', 'registrationIds', 'registration_ids', 'to',
];
/**
* Checks if the given Message object is valid. Recursively validates all the child objects
* included in the message (android, apns, data etc.). If successful, transforms the message
* in place by renaming the keys to what's expected by the remote FCM service.
*
* @param {Message} Message An object to be validated.
*/
function validateMessage(message) {
if (!validator.isNonNullObject(message)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'Message must be a non-null object');
}
const anyMessage = message;
if (anyMessage.topic) {
// If the topic name is prefixed, remove it.
if (anyMessage.topic.startsWith('/topics/')) {
anyMessage.topic = anyMessage.topic.replace(/^\/topics\//, '');
}
// Checks for illegal characters and empty string.
if (!/^[a-zA-Z0-9-_.~%]+$/.test(anyMessage.topic)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'Malformed topic name');
}
}
const targets = [anyMessage.token, anyMessage.topic, anyMessage.condition];
if (targets.filter((v) => validator.isNonEmptyString(v)).length !== 1) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'Exactly one of topic, token or condition is required');
}
validateStringMap(message.data, 'data');
validateAndroidConfig(message.android);
validateWebpushConfig(message.webpush);
validateApnsConfig(message.apns);
validateFcmOptions(message.fcmOptions);
validateNotification(message.notification);
}
exports.validateMessage = validateMessage;
/**
* Checks if the given object only contains strings as child values.
*
* @param {object} map An object to be validated.
* @param {string} label A label to be included in the errors thrown.
*/
function validateStringMap(map, label) {
if (typeof map === 'undefined') {
return;
}
else if (!validator.isNonNullObject(map)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, `${label} must be a non-null object`);
}
Object.keys(map).forEach((key) => {
if (!validator.isString(map[key])) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, `${label} must only contain string values`);
}
});
}
/**
* Checks if the given WebpushConfig object is valid. The object must have valid headers and data.
*
* @param {WebpushConfig} config An object to be validated.
*/
function validateWebpushConfig(config) {
if (typeof config === 'undefined') {
return;
}
else if (!validator.isNonNullObject(config)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'webpush must be a non-null object');
}
validateStringMap(config.headers, 'webpush.headers');
validateStringMap(config.data, 'webpush.data');
}
/**
* Checks if the given ApnsConfig object is valid. The object must have valid headers and a
* payload.
*
* @param {ApnsConfig} config An object to be validated.
*/
function validateApnsConfig(config) {
if (typeof config === 'undefined') {
return;
}
else if (!validator.isNonNullObject(config)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'apns must be a non-null object');
}
validateStringMap(config.headers, 'apns.headers');
validateApnsPayload(config.payload);
validateApnsFcmOptions(config.fcmOptions);
}
/**
* Checks if the given ApnsFcmOptions object is valid.
*
* @param {ApnsFcmOptions} fcmOptions An object to be validated.
*/
function validateApnsFcmOptions(fcmOptions) {
if (typeof fcmOptions === 'undefined') {
return;
}
else if (!validator.isNonNullObject(fcmOptions)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'fcmOptions must be a non-null object');
}
if (typeof fcmOptions.imageUrl !== 'undefined' &&
!validator.isURL(fcmOptions.imageUrl)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'imageUrl must be a valid URL string');
}
if (typeof fcmOptions.analyticsLabel !== 'undefined' && !validator.isString(fcmOptions.analyticsLabel)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'analyticsLabel must be a string value');
}
const propertyMappings = {
imageUrl: 'image',
};
Object.keys(propertyMappings).forEach((key) => {
if (key in fcmOptions && propertyMappings[key] in fcmOptions) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, `Multiple specifications for ${key} in ApnsFcmOptions`);
}
});
(0, index_1.renameProperties)(fcmOptions, propertyMappings);
}
/**
* Checks if the given FcmOptions object is valid.
*
* @param {FcmOptions} fcmOptions An object to be validated.
*/
function validateFcmOptions(fcmOptions) {
if (typeof fcmOptions === 'undefined') {
return;
}
else if (!validator.isNonNullObject(fcmOptions)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'fcmOptions must be a non-null object');
}
if (typeof fcmOptions.analyticsLabel !== 'undefined' && !validator.isString(fcmOptions.analyticsLabel)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'analyticsLabel must be a string value');
}
}
/**
* Checks if the given Notification object is valid.
*
* @param {Notification} notification An object to be validated.
*/
function validateNotification(notification) {
if (typeof notification === 'undefined') {
return;
}
else if (!validator.isNonNullObject(notification)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'notification must be a non-null object');
}
if (typeof notification.imageUrl !== 'undefined' && !validator.isURL(notification.imageUrl)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'notification.imageUrl must be a valid URL string');
}
const propertyMappings = {
imageUrl: 'image',
};
Object.keys(propertyMappings).forEach((key) => {
if (key in notification && propertyMappings[key] in notification) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, `Multiple specifications for ${key} in Notification`);
}
});
(0, index_1.renameProperties)(notification, propertyMappings);
}
/**
* Checks if the given ApnsPayload object is valid. The object must have a valid aps value.
*
* @param {ApnsPayload} payload An object to be validated.
*/
function validateApnsPayload(payload) {
if (typeof payload === 'undefined') {
return;
}
else if (!validator.isNonNullObject(payload)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload must be a non-null object');
}
validateAps(payload.aps);
}
/**
* Checks if the given Aps object is valid. The object must have a valid alert. If the validation
* is successful, transforms the input object by renaming the keys to valid APNS payload keys.
*
* @param {Aps} aps An object to be validated.
*/
function validateAps(aps) {
if (typeof aps === 'undefined') {
return;
}
else if (!validator.isNonNullObject(aps)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps must be a non-null object');
}
validateApsAlert(aps.alert);
validateApsSound(aps.sound);
const propertyMappings = {
contentAvailable: 'content-available',
mutableContent: 'mutable-content',
threadId: 'thread-id',
};
Object.keys(propertyMappings).forEach((key) => {
if (key in aps && propertyMappings[key] in aps) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, `Multiple specifications for ${key} in Aps`);
}
});
(0, index_1.renameProperties)(aps, propertyMappings);
const contentAvailable = aps['content-available'];
if (typeof contentAvailable !== 'undefined' && contentAvailable !== 1) {
if (contentAvailable === true) {
aps['content-available'] = 1;
}
else {
delete aps['content-available'];
}
}
const mutableContent = aps['mutable-content'];
if (typeof mutableContent !== 'undefined' && mutableContent !== 1) {
if (mutableContent === true) {
aps['mutable-content'] = 1;
}
else {
delete aps['mutable-content'];
}
}
}
function validateApsSound(sound) {
if (typeof sound === 'undefined' || validator.isNonEmptyString(sound)) {
return;
}
else if (!validator.isNonNullObject(sound)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.sound must be a non-empty string or a non-null object');
}
if (!validator.isNonEmptyString(sound.name)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.sound.name must be a non-empty string');
}
const volume = sound.volume;
if (typeof volume !== 'undefined') {
if (!validator.isNumber(volume)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.sound.volume must be a number');
}
if (volume < 0 || volume > 1) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.sound.volume must be in the interval [0, 1]');
}
}
const soundObject = sound;
const key = 'critical';
const critical = soundObject[key];
if (typeof critical !== 'undefined' && critical !== 1) {
if (critical === true) {
soundObject[key] = 1;
}
else {
delete soundObject[key];
}
}
}
/**
* Checks if the given alert object is valid. Alert could be a string or a complex object.
* If specified as an object, it must have valid localization parameters. If successful, transforms
* the input object by renaming the keys to valid APNS payload keys.
*
* @param {string | ApsAlert} alert An alert string or an object to be validated.
*/
function validateApsAlert(alert) {
if (typeof alert === 'undefined' || validator.isString(alert)) {
return;
}
else if (!validator.isNonNullObject(alert)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.alert must be a string or a non-null object');
}
const apsAlert = alert;
if (validator.isNonEmptyArray(apsAlert.locArgs) &&
!validator.isNonEmptyString(apsAlert.locKey)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.alert.locKey is required when specifying locArgs');
}
if (validator.isNonEmptyArray(apsAlert.titleLocArgs) &&
!validator.isNonEmptyString(apsAlert.titleLocKey)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.alert.titleLocKey is required when specifying titleLocArgs');
}
if (validator.isNonEmptyArray(apsAlert.subtitleLocArgs) &&
!validator.isNonEmptyString(apsAlert.subtitleLocKey)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'apns.payload.aps.alert.subtitleLocKey is required when specifying subtitleLocArgs');
}
const propertyMappings = {
locKey: 'loc-key',
locArgs: 'loc-args',
titleLocKey: 'title-loc-key',
titleLocArgs: 'title-loc-args',
subtitleLocKey: 'subtitle-loc-key',
subtitleLocArgs: 'subtitle-loc-args',
actionLocKey: 'action-loc-key',
launchImage: 'launch-image',
};
(0, index_1.renameProperties)(apsAlert, propertyMappings);
}
/**
* Checks if the given AndroidConfig object is valid. The object must have valid ttl, data,
* and notification fields. If successful, transforms the input object by renaming keys to valid
* Android keys. Also transforms the ttl value to the format expected by FCM service.
*
* @param config - An object to be validated.
*/
function validateAndroidConfig(config) {
if (typeof config === 'undefined') {
return;
}
else if (!validator.isNonNullObject(config)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'android must be a non-null object');
}
if (typeof config.ttl !== 'undefined') {
if (!validator.isNumber(config.ttl) || config.ttl < 0) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'TTL must be a non-negative duration in milliseconds');
}
const duration = (0, index_1.transformMillisecondsToSecondsString)(config.ttl);
config.ttl = duration;
}
validateStringMap(config.data, 'android.data');
validateAndroidNotification(config.notification);
validateAndroidFcmOptions(config.fcmOptions);
const propertyMappings = {
collapseKey: 'collapse_key',
restrictedPackageName: 'restricted_package_name',
};
(0, index_1.renameProperties)(config, propertyMappings);
}
/**
* Checks if the given AndroidNotification object is valid. The object must have valid color and
* localization parameters. If successful, transforms the input object by renaming keys to valid
* Android keys.
*
* @param {AndroidNotification} notification An object to be validated.
*/
function validateAndroidNotification(notification) {
if (typeof notification === 'undefined') {
return;
}
else if (!validator.isNonNullObject(notification)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification must be a non-null object');
}
if (typeof notification.color !== 'undefined' && !/^#[0-9a-fA-F]{6}$/.test(notification.color)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.color must be in the form #RRGGBB');
}
if (validator.isNonEmptyArray(notification.bodyLocArgs) &&
!validator.isNonEmptyString(notification.bodyLocKey)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.bodyLocKey is required when specifying bodyLocArgs');
}
if (validator.isNonEmptyArray(notification.titleLocArgs) &&
!validator.isNonEmptyString(notification.titleLocKey)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.titleLocKey is required when specifying titleLocArgs');
}
if (typeof notification.imageUrl !== 'undefined' &&
!validator.isURL(notification.imageUrl)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.imageUrl must be a valid URL string');
}
if (typeof notification.eventTimestamp !== 'undefined') {
if (!(notification.eventTimestamp instanceof Date)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.eventTimestamp must be a valid `Date` object');
}
// Convert timestamp to RFC3339 UTC "Zulu" format, example "2014-10-02T15:01:23.045123456Z"
const zuluTimestamp = notification.eventTimestamp.toISOString();
notification.eventTimestamp = zuluTimestamp;
}
if (typeof notification.vibrateTimingsMillis !== 'undefined') {
if (!validator.isNonEmptyArray(notification.vibrateTimingsMillis)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.vibrateTimingsMillis must be a non-empty array of numbers');
}
const vibrateTimings = [];
notification.vibrateTimingsMillis.forEach((value) => {
if (!validator.isNumber(value) || value < 0) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.vibrateTimingsMillis must be non-negative durations in milliseconds');
}
const duration = (0, index_1.transformMillisecondsToSecondsString)(value);
vibrateTimings.push(duration);
});
notification.vibrateTimingsMillis = vibrateTimings;
}
if (typeof notification.priority !== 'undefined') {
const priority = 'PRIORITY_' + notification.priority.toUpperCase();
notification.priority = priority;
}
if (typeof notification.visibility !== 'undefined') {
const visibility = notification.visibility.toUpperCase();
notification.visibility = visibility;
}
validateLightSettings(notification.lightSettings);
const propertyMappings = {
clickAction: 'click_action',
bodyLocKey: 'body_loc_key',
bodyLocArgs: 'body_loc_args',
titleLocKey: 'title_loc_key',
titleLocArgs: 'title_loc_args',
channelId: 'channel_id',
imageUrl: 'image',
eventTimestamp: 'event_time',
localOnly: 'local_only',
priority: 'notification_priority',
vibrateTimingsMillis: 'vibrate_timings',
defaultVibrateTimings: 'default_vibrate_timings',
defaultSound: 'default_sound',
lightSettings: 'light_settings',
defaultLightSettings: 'default_light_settings',
notificationCount: 'notification_count',
};
(0, index_1.renameProperties)(notification, propertyMappings);
}
/**
* Checks if the given LightSettings object is valid. The object must have valid color and
* light on/off duration parameters. If successful, transforms the input object by renaming
* keys to valid Android keys.
*
* @param {LightSettings} lightSettings An object to be validated.
*/
function validateLightSettings(lightSettings) {
if (typeof lightSettings === 'undefined') {
return;
}
else if (!validator.isNonNullObject(lightSettings)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.lightSettings must be a non-null object');
}
if (!validator.isNumber(lightSettings.lightOnDurationMillis) || lightSettings.lightOnDurationMillis < 0) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.lightSettings.lightOnDurationMillis must be a non-negative duration in milliseconds');
}
const durationOn = (0, index_1.transformMillisecondsToSecondsString)(lightSettings.lightOnDurationMillis);
lightSettings.lightOnDurationMillis = durationOn;
if (!validator.isNumber(lightSettings.lightOffDurationMillis) || lightSettings.lightOffDurationMillis < 0) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.lightSettings.lightOffDurationMillis must be a non-negative duration in milliseconds');
}
const durationOff = (0, index_1.transformMillisecondsToSecondsString)(lightSettings.lightOffDurationMillis);
lightSettings.lightOffDurationMillis = durationOff;
if (!validator.isString(lightSettings.color) ||
(!/^#[0-9a-fA-F]{6}$/.test(lightSettings.color) && !/^#[0-9a-fA-F]{8}$/.test(lightSettings.color))) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'android.notification.lightSettings.color must be in the form #RRGGBB or #RRGGBBAA format');
}
const colorString = lightSettings.color.length === 7 ? lightSettings.color + 'FF' : lightSettings.color;
const rgb = /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/i.exec(colorString);
if (!rgb || rgb.length < 4) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INTERNAL_ERROR, 'regex to extract rgba values from ' + colorString + ' failed.');
}
const color = {
red: parseInt(rgb[1], 16) / 255.0,
green: parseInt(rgb[2], 16) / 255.0,
blue: parseInt(rgb[3], 16) / 255.0,
alpha: parseInt(rgb[4], 16) / 255.0,
};
lightSettings.color = color;
const propertyMappings = {
lightOnDurationMillis: 'light_on_duration',
lightOffDurationMillis: 'light_off_duration',
};
(0, index_1.renameProperties)(lightSettings, propertyMappings);
}
/**
* Checks if the given AndroidFcmOptions object is valid.
*
* @param {AndroidFcmOptions} fcmOptions An object to be validated.
*/
function validateAndroidFcmOptions(fcmOptions) {
if (typeof fcmOptions === 'undefined') {
return;
}
else if (!validator.isNonNullObject(fcmOptions)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'fcmOptions must be a non-null object');
}
if (typeof fcmOptions.analyticsLabel !== 'undefined' && !validator.isString(fcmOptions.analyticsLabel)) {
throw new error_1.FirebaseMessagingError(error_1.MessagingClientErrorCode.INVALID_PAYLOAD, 'analyticsLabel must be a string value');
}
}