pubnub
Version:
Publish & Subscribe Real-time Messaging with PubNub
1,001 lines (862 loc) • 22.7 kB
text/typescript
// --------------------------------------------------------
// ------------------------ Types -------------------------
// --------------------------------------------------------
// region Types
// region APNS2
/**
* Payload for `pn_apns` field in published message.
*/
type APNSPayload = {
/**
* Payload for Apple Push Notification Service.
*/
aps: {
/**
* Configuration of visual notification representation.
*/
alert?: {
/**
* First line title.
*
* Title which is shown in bold on the first line of notification bubble.
*/
title?: string;
/**
* Second line title.
*
* Subtitle which is shown under main title with smaller font.
*/
subtitle?: string;
/**
* Notification body.
*
* Body which is shown to the user after interaction with notification.
*/
body?: string;
};
/**
* Unread notifications count badge value.
*/
badge?: number | null;
/**
* Name of the file from resource bundle which should be played when notification received.
*/
sound?: string;
/**
* Silent notification flag.
*/
'content-available'?: 1;
};
/**
* APNS2 payload recipients information.
*/
pn_push: PubNubAPNS2Configuration[];
};
/**
* APNS2 configuration type.
*/
type APNS2Configuration = {
/**
* Notification group / collapse identifier. Value will be used in APNS POST request as `apns-collapse-id` header
* value.
*/
collapseId?: string;
/**
* Date till which APNS will try to deliver notification to target device. Value will be used in APNS POST request as
* `apns-expiration` header value.
*/
expirationDate?: Date;
/**
* List of topics which should receive this notification.
*/
targets: APNS2Target[];
};
/**
* Preformatted for PubNub service `APNS2` configuration type.
*/
type PubNubAPNS2Configuration = {
/**
* PubNub service authentication method for APNS.
*/
auth_method: 'token';
/**
* Target entities which should receive notification.
*/
targets: PubNubAPNS2Target[];
/**
* Notifications group collapse identifier.
*/
collapse_id?: string;
/**
* Notification receive expiration date.
*
* Date after which notification won't be delivered.
*/
expiration?: string;
/**
* APNS protocol version.
*/
version: 'v2';
};
/**
* APNS2 configuration target type.
*/
type APNS2Target = {
/**
* Notifications topic name (usually it is bundle identifier of application for Apple platform).
*
* **Important:** Required only if `pushGateway` is set to `apns2`.
*/
topic: string;
/**
* Environment within which registered devices to which notifications should be delivered.
*
* Available:
* - `development`
* - `production`
*
* @default `development`
*/
environment?: 'development' | 'production';
/**
* List of devices (their push tokens) to which this notification shouldn't be delivered.
*/
excludedDevices?: string[];
};
/**
* Preformatted for PubNub service `APNS2` configuration target type.
*/
type PubNubAPNS2Target = Omit<APNS2Target, 'excludedDevices'> & {
/**
* List of devices (their push tokens) to which this notification shouldn't be delivered.
*/
excluded_devices?: string[];
};
// endregion
// region FCM
/**
* Payload for `pn_gcm` field in published message.
*/
type FCMPayload = {
/**
* Configuration of visual notification representation.
*/
notification?: {
/**
* First line title.
*
* Title which is shown in bold on the first line of notification bubble.
*/
title?: string;
/**
* Notification body.
*
* Body which is shown to the user after interaction with notification.
*/
body?: string;
/**
* Name of the icon file from resource bundle which should be shown on notification.
*/
icon?: string;
/**
* Name of the file from resource bundle which should be played when notification received.
*/
sound?: string;
tag?: string;
};
/**
* Configuration of data notification.
*
* Silent notification configuration.
*/
data?: { notification?: FCMPayload['notification'] };
};
// endregion
// endregion
/**
* Base notification payload object.
*/
class BaseNotificationPayload {
/**
* Notification main title.
*
* @internal
*/
protected _title?: string;
/**
* Notification second-line title.
*
* @internal
*/
protected _subtitle?: string;
/**
* Name of the sound which should be played for received notification.
*
* @internal
*/
protected _sound?: string;
/**
* Value which should be placed on application badge (if required).
*
* @internal
*/
protected _badge?: number | null;
/**
* Notification main body message.
*
* @internal
*/
protected _body?: string;
/**
* Object in resulting message where notification payload should be added.
*
* @internal
*/
protected _payload: unknown;
/**
* Base notification provider payload object.
*
* @internal
*
* @param payload - Object which contains vendor-specific preformatted push notification payload.
* @param title - Notification main title.
* @param body - Notification body (main messages).
*/
constructor(payload: unknown, title?: string, body?: string) {
this._payload = payload;
this.setDefaultPayloadStructure();
this.title = title;
this.body = body;
}
/**
* Retrieve resulting notification payload content for message.
*
* @returns Preformatted push notification payload data.
*/
get payload(): unknown {
return this._payload;
}
/**
* Update notification title.
*
* @param value - New notification title.
*/
set title(value: string | undefined) {
this._title = value;
}
/**
* Update notification subtitle.
*
* @param value - New second-line notification title.
*/
set subtitle(value: string | undefined) {
this._subtitle = value;
}
/**
* Update notification body.
*
* @param value - Update main notification message (shown when expanded).
*/
set body(value: string | undefined) {
this._body = value;
}
/**
* Update application badge number.
*
* @param value - Number which should be shown in application badge upon receiving notification.
*/
set badge(value: number | null | undefined) {
this._badge = value;
}
/**
* Update notification sound.
*
* @param value - Name of the sound file which should be played upon notification receive.
*/
set sound(value: string | undefined) {
this._sound = value;
}
/**
* Platform-specific structure initialization.
*
* @internal
*/
protected setDefaultPayloadStructure() {}
/**
* Translate data object into PubNub push notification payload object.
*
* @internal
*
* @returns Preformatted push notification payload.
*/
public toObject(): unknown {
return {};
}
}
/**
* Message payload for Apple Push Notification Service.
*/
export class APNSNotificationPayload extends BaseNotificationPayload {
/**
* List with notification receivers information.
*
* @internal
*/
private _configurations?: APNS2Configuration[];
/**
* Type of push notification service for which payload will be created.
*
* @internal
*/
private _apnsPushType: 'apns' | 'apns2' = 'apns';
/**
* Whether resulting payload should trigger silent notification or not.
*
* @internal
*/
private _isSilent: boolean = false;
get payload(): APNSPayload {
return this._payload as APNSPayload;
}
/**
* Update notification receivers configuration.
*
* @param value - New APNS2 configurations.
*/
set configurations(value: APNS2Configuration[]) {
if (!value || !value.length) return;
this._configurations = value;
}
/**
* Notification payload.
*
* @returns Platform-specific part of PubNub notification payload.
*/
get notification() {
return this.payload.aps;
}
/**
* Notification title.
*
* @returns Main notification title.
*/
get title() {
return this._title;
}
/**
* Update notification title.
*
* @param value - New notification title.
*/
set title(value) {
if (!value || !value.length) return;
this.payload.aps.alert!.title = value;
this._title = value;
}
/**
* Notification subtitle.
*
* @returns Second-line notification title.
*/
get subtitle() {
return this._subtitle;
}
/**
* Update notification subtitle.
*
* @param value - New second-line notification title.
*/
set subtitle(value) {
if (!value || !value.length) return;
this.payload.aps.alert!.subtitle = value;
this._subtitle = value;
}
/**
* Notification body.
*
* @returns Main notification message (shown when expanded).
*/
get body() {
return this._body;
}
/**
* Update notification body.
*
* @param value - Update main notification message (shown when expanded).
*/
set body(value) {
if (!value || !value.length) return;
this.payload.aps.alert!.body = value;
this._body = value;
}
/**
* Retrieve unread notifications number.
*
* @returns Number of unread notifications which should be shown on application badge.
*/
get badge() {
return this._badge;
}
/**
* Update application badge number.
*
* @param value - Number which should be shown in application badge upon receiving notification.
*/
set badge(value) {
if (value === undefined || value === null) return;
this.payload.aps.badge = value;
this._badge = value;
}
/**
* Retrieve notification sound file.
*
* @returns Notification sound file name from resource bundle.
*/
get sound() {
return this._sound;
}
/**
* Update notification sound.
*
* @param value - Name of the sound file which should be played upon notification receive.
*/
set sound(value) {
if (!value || !value.length) return;
this.payload.aps.sound = value;
this._sound = value;
}
/**
* Set whether notification should be silent or not.
*
* `content-available` notification type will be used to deliver silent notification if set to `true`.
*
* @param value - Whether notification should be sent as silent or not.
*/
set silent(value: boolean) {
this._isSilent = value;
}
/**
* Setup push notification payload default content.
*
* @internal
*/
protected setDefaultPayloadStructure() {
this.payload.aps = { alert: {} };
}
/**
* Translate data object into PubNub push notification payload object.
*
* @internal
*
* @returns Preformatted push notification payload.
*/
public toObject(): APNSPayload | null {
const payload = { ...this.payload };
const { aps } = payload;
let { alert } = aps;
if (this._isSilent) aps['content-available'] = 1;
if (this._apnsPushType === 'apns2') {
if (!this._configurations || !this._configurations.length)
throw new ReferenceError('APNS2 configuration is missing');
const configurations: PubNubAPNS2Configuration[] = [];
this._configurations.forEach((configuration) => {
configurations.push(this.objectFromAPNS2Configuration(configuration));
});
if (configurations.length) payload.pn_push = configurations;
}
if (!alert || !Object.keys(alert).length) delete aps.alert;
if (this._isSilent) {
delete aps.alert;
delete aps.badge;
delete aps.sound;
alert = {};
}
return this._isSilent || (alert && Object.keys(alert).length) ? payload : null;
}
/**
* Create PubNub push notification service APNS2 configuration information object.
*
* @internal
*
* @param configuration - Source user-provided APNS2 configuration.
*
* @returns Preformatted for PubNub service APNS2 configuration information.
*/
private objectFromAPNS2Configuration(configuration: APNS2Configuration): PubNubAPNS2Configuration {
if (!configuration.targets || !configuration.targets.length)
throw new ReferenceError('At least one APNS2 target should be provided');
const { collapseId, expirationDate } = configuration;
const objectifiedConfiguration: PubNubAPNS2Configuration = {
auth_method: 'token',
targets: configuration.targets.map((target) => this.objectFromAPNSTarget(target)),
version: 'v2',
};
if (collapseId && collapseId.length) objectifiedConfiguration.collapse_id = collapseId;
if (expirationDate) objectifiedConfiguration.expiration = expirationDate.toISOString();
return objectifiedConfiguration;
}
/**
* Create PubNub push notification service APNS2 target information object.
*
* @internal
*
* @param target - Source user-provided data.
*
* @returns Preformatted for PubNub service APNS2 target information.
*/
private objectFromAPNSTarget(target: APNS2Target): PubNubAPNS2Target {
if (!target.topic || !target.topic.length) throw new TypeError("Target 'topic' undefined.");
const { topic, environment = 'development', excludedDevices = [] } = target;
const objectifiedTarget: PubNubAPNS2Target = { topic, environment };
if (excludedDevices.length) objectifiedTarget.excluded_devices = excludedDevices;
return objectifiedTarget;
}
}
/**
* Message payload for Firebase Cloud Messaging service.
*/
export class FCMNotificationPayload extends BaseNotificationPayload {
/**
* Whether resulting payload should trigger silent notification or not.
*
* @internal
*/
private _isSilent?: boolean;
/**
* Name of the icon file from resource bundle which should be shown on notification.
*
* @internal
*/
private _icon?: string;
/**
* Notifications grouping tag.
*
* @internal
*/
private _tag?: string;
get payload(): FCMPayload {
return this._payload as FCMPayload;
}
/**
* Notification payload.
*
* @returns Platform-specific part of PubNub notification payload.
*/
get notification() {
return this.payload.notification;
}
/**
* Silent notification payload.
*
* @returns Silent notification payload (data notification).
*/
get data() {
return this.payload.data;
}
/**
* Notification title.
*
* @returns Main notification title.
*/
get title() {
return this._title;
}
/**
* Update notification title.
*
* @param value - New notification title.
*/
set title(value) {
if (!value || !value.length) return;
this.payload.notification!.title = value;
this._title = value;
}
/**
* Notification body.
*
* @returns Main notification message (shown when expanded).
*/
get body() {
return this._body;
}
/**
* Update notification body.
*
* @param value - Update main notification message (shown when expanded).
*/
set body(value) {
if (!value || !value.length) return;
this.payload.notification!.body = value;
this._body = value;
}
/**
* Retrieve notification sound file.
*
* @returns Notification sound file name from resource bundle.
*/
get sound() {
return this._sound;
}
/**
* Update notification sound.
*
* @param value - Name of the sound file which should be played upon notification receive.
*/
set sound(value) {
if (!value || !value.length) return;
this.payload.notification!.sound = value;
this._sound = value;
}
/**
* Retrieve notification icon file.
*
* @returns Notification icon file name from resource bundle.
*/
get icon() {
return this._icon;
}
/**
* Update notification icon.
*
* @param value - Name of the icon file which should be shown on notification.
*/
set icon(value) {
if (!value || !value.length) return;
this.payload.notification!.icon = value;
this._icon = value;
}
/**
* Retrieve notifications grouping tag.
*
* @returns Notifications grouping tag.
*/
get tag() {
return this._tag;
}
/**
* Update notifications grouping tag.
*
* @param value - String which will be used to group similar notifications in notification center.
*/
set tag(value) {
if (!value || !value.length) return;
this.payload.notification!.tag = value;
this._tag = value;
}
/**
* Set whether notification should be silent or not.
*
* All notification data will be sent under `data` field if set to `true`.
*
* @param value - Whether notification should be sent as silent or not.
*/
set silent(value: boolean) {
this._isSilent = value;
}
/**
* Setup push notification payload default content.
*
* @internal
*/
protected setDefaultPayloadStructure() {
this.payload.notification = {};
this.payload.data = {};
}
/**
* Translate data object into PubNub push notification payload object.
*
* @internal
*
* @returns Preformatted push notification payload.
*/
public toObject(): FCMPayload | null {
let data = { ...this.payload.data };
let notification = null;
const payload: FCMPayload = {};
// Check whether additional data has been passed outside 'data' object and put it into it if required.
if (Object.keys(this.payload).length > 2) {
const { notification: initialNotification, data: initialData, ...additionalData } = this.payload;
data = { ...data, ...additionalData };
}
if (this._isSilent) data.notification = this.payload.notification;
else notification = this.payload.notification;
if (Object.keys(data).length) payload.data = data;
if (notification && Object.keys(notification).length) payload.notification = notification;
return Object.keys(payload).length ? payload : null;
}
}
class NotificationsPayload {
/**
* Resulting message payload for notification services.
*
* @internal
*/
private readonly _payload;
/**
* Whether notifications debugging session should be used or not.
*
* @internal
*/
private _debugging?: boolean;
/**
* First line title.
*
* Title which is shown in bold on the first line of notification bubble.
*
* @internal
*/
private readonly _title: string;
/**
* Second line title.
*
* Subtitle which is shown under main title with smaller font.
*
* @internal
*/
private _subtitle?: string;
/**
* Notification main body message.
*
* @internal
*/
private readonly _body: string;
/**
* Value which should be placed on application badge (if required).
*
* @internal
*/
private _badge?: number;
/**
* Name of the file from resource bundle which should be played when notification received.
*
* @internal
*/
private _sound?: string;
/**
* APNS-specific message payload.
*/
public apns;
/**
* FCM-specific message payload.
*/
public fcm;
/**
* Create push notification payload holder.
*
* @internal
*
* @param title - String which will be shown at the top of the notification (below app name).
* @param body - String with message which should be shown when user will check notification.
*/
constructor(title: string, body: string) {
this._payload = { apns: {}, fcm: {} };
this._title = title;
this._body = body;
this.apns = new APNSNotificationPayload(this._payload.apns, title, body);
this.fcm = new FCMNotificationPayload(this._payload.fcm, title, body);
}
/**
* Enable or disable push notification debugging message.
*
* @param value - Whether debug message from push notification scheduler should be published to the specific
* channel or not.
*/
set debugging(value: boolean) {
this._debugging = value;
}
/**
* Notification title.
*
* @returns Main notification title.
*/
get title() {
return this._title;
}
/**
* Notification subtitle.
*
* @returns Second-line notification title.
*/
get subtitle() {
return this._subtitle;
}
/**
* Update notification subtitle.
*
* @param value - New second-line notification title.
*/
set subtitle(value) {
this._subtitle = value;
this.apns.subtitle = value;
this.fcm.subtitle = value;
}
/**
* Notification body.
*
* @returns Main notification message (shown when expanded).
*/
get body() {
return this._body;
}
/**
* Retrieve unread notifications number.
*
* @returns Number of unread notifications which should be shown on application badge.
*/
get badge() {
return this._badge;
}
/**
* Update application badge number.
*
* @param value - Number which should be shown in application badge upon receiving notification.
*/
set badge(value: number | undefined) {
this._badge = value;
this.apns.badge = value;
this.fcm.badge = value;
}
/**
* Retrieve notification sound file.
*
* @returns Notification sound file name from resource bundle.
*/
get sound() {
return this._sound;
}
/**
* Update notification sound.
*
* @param value - Name of the sound file which should be played upon notification receive.
*/
set sound(value) {
this._sound = value;
this.apns.sound = value;
this.fcm.sound = value;
}
/**
* Build notifications platform for requested platforms.
*
* @param platforms - List of platforms for which payload should be added to final dictionary. Supported values:
* fcm, apns, and apns2.
*
* @returns Object with data, which can be sent with publish method call and trigger remote notifications for
* specified platforms.
*/
buildPayload(platforms: ('apns' | 'apns2' | 'fcm')[]) {
const payload: { pn_apns?: APNSPayload; pn_gcm?: FCMPayload; pn_debug?: boolean } = {};
if (platforms.includes('apns') || platforms.includes('apns2')) {
// @ts-expect-error Override APNS version.
this.apns._apnsPushType = platforms.includes('apns') ? 'apns' : 'apns2';
const apnsPayload = this.apns.toObject();
if (apnsPayload && Object.keys(apnsPayload).length) payload.pn_apns = apnsPayload;
}
if (platforms.includes('fcm')) {
const fcmPayload = this.fcm.toObject();
if (fcmPayload && Object.keys(fcmPayload).length) payload.pn_gcm = fcmPayload;
}
if (Object.keys(payload).length && this._debugging) payload.pn_debug = true;
return payload;
}
}
export default NotificationsPayload;