@nativescript/local-notifications
Version:
The Local Notifications plugin allows your app to show notifications when the app is not running.
492 lines • 25 kB
JavaScript
import { File, ImageSource, knownFolders, path as fsPath } from '@nativescript/core';
import { SharedNotificationDelegate } from '@nativescript/shared-notification-delegate';
import { LocalNotificationsCommon } from './common';
export class LocalNotificationsImpl extends LocalNotificationsCommon {
constructor() {
super();
this.pendingReceivedNotifications = [];
if (LocalNotificationsImpl.isUNUserNotificationCenterAvailable()) {
this.observer = new LocalNotificationsDelegateObserverImpl(new WeakRef(this));
SharedNotificationDelegate.addObserver(this.observer);
}
else {
// grab 'em here, store 'em in JS, and give them to the callback when addOnMessageReceivedCallback is wired
this.notificationReceivedObserver = LocalNotificationsImpl.addObserver('notificationReceived', (result) => {
const notificationDetails = JSON.parse(result.userInfo.objectForKey('message'));
this.addOrProcessNotification(notificationDetails);
});
this.notificationHandler = Notification.new();
this.notificationManager = NotificationManager.new();
}
}
static isUNUserNotificationCenterAvailable() {
try {
// available since iOS 10
return !!UNUserNotificationCenter;
}
catch (ignore) {
return false;
}
}
static hasPermission() {
const settings = UIApplication.sharedApplication.currentUserNotificationSettings;
const types = 4 /* UIUserNotificationType.Alert */ | 1 /* UIUserNotificationType.Badge */ | 2 /* UIUserNotificationType.Sound */;
return (settings.types & types) > 0;
}
static getImageName(imageURL = '', extension = 'png') {
const name = imageURL.split(/[\/\.]/).slice(-2, -1)[0] || LocalNotificationsImpl.generateUUID();
return [name, `${name}.${extension}`];
}
static addObserver(eventName, callback) {
return NSNotificationCenter.defaultCenter.addObserverForNameObjectQueueUsingBlock(eventName, null, NSOperationQueue.mainQueue, callback);
}
static getInterval(interval) {
if (interval === 'minute') {
return 128 /* NSCalendarUnit.CalendarUnitSecond */;
}
else if (interval === 'hour') {
return 64 /* NSCalendarUnit.CalendarUnitMinute */ | 128 /* NSCalendarUnit.CalendarUnitSecond */;
}
else if (interval === 'day') {
return 32 /* NSCalendarUnit.CalendarUnitHour */ | 64 /* NSCalendarUnit.CalendarUnitMinute */ | 128 /* NSCalendarUnit.CalendarUnitSecond */;
}
else if (interval === 'week') {
return 512 /* NSCalendarUnit.CalendarUnitWeekday */ | 32 /* NSCalendarUnit.CalendarUnitHour */ | 64 /* NSCalendarUnit.CalendarUnitMinute */ | 128 /* NSCalendarUnit.CalendarUnitSecond */;
}
else if (interval === 'month') {
return 16 /* NSCalendarUnit.CalendarUnitDay */ | 32 /* NSCalendarUnit.CalendarUnitHour */ | 64 /* NSCalendarUnit.CalendarUnitMinute */ | 128 /* NSCalendarUnit.CalendarUnitSecond */;
}
else if (interval === 'year') {
return 8 /* NSCalendarUnit.CalendarUnitMonth */ | 16 /* NSCalendarUnit.CalendarUnitDay */ | 32 /* NSCalendarUnit.CalendarUnitHour */ | 64 /* NSCalendarUnit.CalendarUnitMinute */ | 128 /* NSCalendarUnit.CalendarUnitSecond */;
}
else if (typeof interval === 'number') {
return interval;
}
else {
return 4 /* NSCalendarUnit.CalendarUnitYear */ | 8 /* NSCalendarUnit.CalendarUnitMonth */ | 16 /* NSCalendarUnit.CalendarUnitDay */ | 32 /* NSCalendarUnit.CalendarUnitHour */ | 64 /* NSCalendarUnit.CalendarUnitMinute */ | 128 /* NSCalendarUnit.CalendarUnitSecond */;
}
}
static getIntervalSeconds(interval, ticks = 1) {
let multiplier = 1;
switch (interval) {
default:
case 'second':
multiplier = 1;
break;
case 'minute':
multiplier = 60;
break;
case 'hour':
multiplier = 60 * 60;
break;
case 'day':
multiplier = 60 * 60 * 24;
break;
case 'week':
multiplier = 60 * 60 * 24 * 7;
break;
// close enough
case 'month':
60 * 60 * 24 * 30.438;
break;
case 'quarter':
60 * 60 * 24 * 91.313;
break;
case 'year':
multiplier = 60 * 60 * 24 * 365;
break;
}
return Math.abs(ticks) * multiplier;
}
static async schedulePendingNotifications(scheduleOptions) {
if (LocalNotificationsImpl.isUNUserNotificationCenterAvailable()) {
return await LocalNotificationsImpl.schedulePendingNotificationsNew(scheduleOptions);
}
else {
return LocalNotificationsImpl.schedulePendingNotificationsLegacy(scheduleOptions);
}
}
static async schedulePendingNotificationsNew(scheduleOptions) {
const scheduledIds = [];
for (const s of scheduleOptions) {
const entry = LocalNotificationsImpl.createScheduleEntry(s);
const { interval, ticks } = LocalNotificationsImpl.getIntervalData(entry);
// Notification content
const content = UNMutableNotificationContent.new();
const { title, subtitle, body } = entry;
content.title = body || subtitle ? title : undefined;
content.subtitle = body ? subtitle : undefined;
// On iOS, a notification with no body won't show up, so the subtitle or title will be used in this case as body
// instead. If none of them is set, we set it to ' ' and will show up as an empty line in the notification:
content.body = body || subtitle || title || ' ';
content.badge = entry.badge;
if (entry.sound === undefined || entry.sound === 'default') {
content.sound = UNNotificationSound.defaultSound;
}
else {
content.sound = UNNotificationSound.soundNamed(entry.sound);
}
const userInfoDict = new NSMutableDictionary({ capacity: 3 });
userInfoDict.setObjectForKey('nativescript-local-notifications', '__NotificationType');
userInfoDict.setObjectForKey(entry.forceShowWhenInForeground, 'forceShowWhenInForeground');
userInfoDict.setObjectForKey(entry.priority || 0, 'priority');
if ('payload' in entry) {
userInfoDict.setObjectForKey(JSON.stringify(entry.payload), 'payload');
}
content.userInfo = userInfoDict;
// Notification trigger and repeat
let trigger;
if (entry.at) {
const cal = LocalNotificationsImpl.calendarWithMondayAsFirstDay();
const date = cal.componentsFromDate(LocalNotificationsImpl.getInterval(interval), entry.at);
date.timeZone = NSTimeZone.defaultTimeZone;
trigger = UNCalendarNotificationTrigger.triggerWithDateMatchingComponentsRepeats(date, interval != null);
}
else if (interval) {
trigger = UNTimeIntervalNotificationTrigger.triggerWithTimeIntervalRepeats(this.getIntervalSeconds(interval, ticks), true);
}
else {
trigger = UNTimeIntervalNotificationTrigger.triggerWithTimeIntervalRepeats(2, false);
}
// actions
if (entry.actions) {
let categoryIdentifier = 'CATEGORY';
const actions = [];
entry.actions.forEach((action) => {
categoryIdentifier += '_' + action.id;
let notificationActionOptions = UNNotificationActionOptionNone;
if (action.launch) {
notificationActionOptions = 4 /* UNNotificationActionOptions.Foreground */;
}
if (action.type === 'input') {
actions.push(UNTextInputNotificationAction.actionWithIdentifierTitleOptionsTextInputButtonTitleTextInputPlaceholder('' + action.id, action.title, notificationActionOptions, action.submitLabel || 'Submit', action.placeholder));
}
else if (action.type === 'button') {
actions.push(UNNotificationAction.actionWithIdentifierTitleOptions('' + action.id, action.title, notificationActionOptions));
}
else {
console.log('Unsupported action type: ' + action.type);
}
});
const notificationCategory = UNNotificationCategory.categoryWithIdentifierActionsIntentIdentifiersOptions(categoryIdentifier, actions, [], 1 /* UNNotificationCategoryOptions.CustomDismissAction */);
content.categoryIdentifier = categoryIdentifier;
UNUserNotificationCenter.currentNotificationCenter().getNotificationCategoriesWithCompletionHandler((categories) => {
if (categories) {
UNUserNotificationCenter.currentNotificationCenter().setNotificationCategories(categories.setByAddingObject(notificationCategory));
}
else {
UNUserNotificationCenter.currentNotificationCenter().setNotificationCategories(NSSet.setWithObject(notificationCategory));
}
});
}
if (entry.image) {
ImageSource.fromUrl(entry.image).then((image) => {
const [imageName, imageNameWithExtension] = LocalNotificationsImpl.getImageName(entry.image, 'png');
const path = fsPath.join(knownFolders.temp().path, imageNameWithExtension);
const saved = image.saveToFile(path, 'png');
if (saved || File.exists(path)) {
try {
content.attachments = NSArray.arrayWithObject(UNNotificationAttachment.attachmentWithIdentifierURLOptionsError(imageName, NSURL.fileURLWithPath(path), null));
}
catch (err) {
console.log('Error adding image attachment - ignoring the image. Error: ' + err);
// Just fall back to a normal notification...
}
}
});
}
scheduledIds.push(await registerNotification(entry.id, content, trigger));
if (interval && entry.displayImmediately) {
const id = LocalNotificationsImpl.generateNotificationID();
scheduledIds.push(await registerNotification(id, content, UNTimeIntervalNotificationTrigger.triggerWithTimeIntervalRepeats(2, false)));
}
}
function registerNotification(id, content, trigger) {
return new Promise((resolve) => {
UNUserNotificationCenter.currentNotificationCenter().addNotificationRequestWithCompletionHandler(UNNotificationRequest.requestWithIdentifierContentTrigger('' + id, content, trigger), (error) => {
if (error) {
console.log(`Error scheduling notification (id ${id}): ${error.localizedDescription}`);
}
else {
console.log(`Notification (id ${id}) scheduled successfully`);
}
resolve(id);
});
});
}
return scheduledIds;
}
static calendarWithMondayAsFirstDay() {
const cal = NSCalendar.alloc().initWithCalendarIdentifier(NSCalendarIdentifierISO8601);
cal.firstWeekday = 2;
cal.minimumDaysInFirstWeek = 1;
return cal;
}
static schedulePendingNotificationsLegacy(scheduleOptions) {
const scheduledIds = [];
for (const s of scheduleOptions) {
const entry = LocalNotificationsImpl.createScheduleEntry(s);
scheduledIds.push(entry.id);
const notification = UILocalNotification.new();
notification.fireDate = entry.at ? entry.at : new Date();
notification.alertTitle = entry.title;
notification.alertBody = entry.body;
notification.timeZone = NSTimeZone.defaultTimeZone;
notification.applicationIconBadgeNumber = entry.badge;
// these are sent back to the plugin when a notification is received
const userInfoDict = NSMutableDictionary.alloc().initWithCapacity(4);
userInfoDict.setObjectForKey(entry.id, 'id');
userInfoDict.setObjectForKey(entry.title, 'title');
userInfoDict.setObjectForKey(entry.body, 'body');
userInfoDict.setObjectForKey(entry.interval, 'interval');
if ('payload' in entry) {
userInfoDict.setObjectForKey(JSON.stringify(entry.payload), 'payload');
}
notification.userInfo = userInfoDict;
switch (entry.sound) {
case null:
case false:
case '':
break;
case undefined:
case 'default':
notification.soundName = UILocalNotificationDefaultSoundName;
break;
default:
notification.soundName = entry.sound;
break;
}
// Used when restoring the notification after a reboot
// Note: This is no longer used
//entry.repeatInterval = LocalNotificationsImpl.getInterval(entry.interval);
// notification.soundName = custom..;
// notification.resumeApplicationInBackground = true;
UIApplication.sharedApplication.scheduleLocalNotification(notification);
}
return scheduledIds;
}
addOrProcessNotification(notificationDetails) {
if (this.receivedNotificationCallback) {
this.receivedNotificationCallback(notificationDetails);
}
else {
this.pendingReceivedNotifications.push(notificationDetails);
}
}
hasPermission() {
return new Promise((resolve, reject) => {
try {
resolve(LocalNotificationsImpl.hasPermission());
}
catch (ex) {
console.log('Error in LocalNotifications.hasPermission: ' + ex);
reject(ex);
}
});
}
requestPermission() {
return new Promise((resolve, reject) => {
if (LocalNotificationsImpl.isUNUserNotificationCenterAvailable()) {
// iOS >= 10
const center = UNUserNotificationCenter.currentNotificationCenter();
center.requestAuthorizationWithOptionsCompletionHandler(4 /* UNAuthorizationOptions.Alert */ | 1 /* UNAuthorizationOptions.Badge */ | 2 /* UNAuthorizationOptions.Sound */, (granted, error) => resolve(granted));
}
else {
// iOS < 10
LocalNotificationsImpl.didRegisterUserNotificationSettingsObserver = LocalNotificationsImpl.addObserver('didRegisterUserNotificationSettings', (result) => {
NSNotificationCenter.defaultCenter.removeObserver(LocalNotificationsImpl.didRegisterUserNotificationSettingsObserver);
LocalNotificationsImpl.didRegisterUserNotificationSettingsObserver = undefined;
const granted = result.userInfo.objectForKey('message');
resolve(granted !== 'false' && granted !== false);
});
const types = UIApplication.sharedApplication.currentUserNotificationSettings.types | 4 /* UIUserNotificationType.Alert */ | 1 /* UIUserNotificationType.Badge */ | 2 /* UIUserNotificationType.Sound */;
const settings = UIUserNotificationSettings.settingsForTypesCategories(types, null);
UIApplication.sharedApplication.registerUserNotificationSettings(settings);
}
});
}
addOnMessageReceivedCallback(onReceived) {
return new Promise((resolve, reject) => {
try {
this.receivedNotificationCallback = onReceived;
for (const pendingReceivedNotification of this.pendingReceivedNotifications) {
onReceived(pendingReceivedNotification);
}
this.pendingReceivedNotifications = [];
resolve(true);
}
catch (ex) {
console.log('Error in LocalNotifications.addOnMessageReceivedCallback: ' + ex);
reject(ex);
}
});
}
addOnMessageClearedCallback(onReceived) {
// Not possible on iOS. It looks like this would only work if the notification has categories set, which might not
// be the case. Therefore, this method is just a placeholder in case users use it without checking the platform
// they are in. See:
// - https://stackoverflow.com/questions/44009707/customdismissaction-not-working-for-remote-notifications
// - https://stackoverflow.com/questions/31929274/know-if-ios-notification-was-dismiss.
return Promise.resolve(false);
}
cancel(id) {
return new Promise((resolve, reject) => {
try {
if (LocalNotificationsImpl.isUNUserNotificationCenterAvailable()) {
UNUserNotificationCenter.currentNotificationCenter().removePendingNotificationRequestsWithIdentifiers(['' + id]);
UNUserNotificationCenter.currentNotificationCenter().removeDeliveredNotificationsWithIdentifiers(['' + id]);
resolve(true);
}
else {
const scheduled = UIApplication.sharedApplication.scheduledLocalNotifications;
for (let i = 0, l = scheduled.count; i < l; i++) {
const noti = scheduled.objectAtIndex(i);
if (id === +noti.userInfo.valueForKey('id')) {
UIApplication.sharedApplication.cancelLocalNotification(noti);
resolve(true);
return;
}
}
resolve(false);
}
}
catch (ex) {
console.log('Error in LocalNotifications.cancel: ' + ex);
reject(ex);
}
});
}
cancelAll() {
return new Promise((resolve, reject) => {
try {
if (LocalNotificationsImpl.isUNUserNotificationCenterAvailable()) {
UNUserNotificationCenter.currentNotificationCenter().removeAllPendingNotificationRequests();
UNUserNotificationCenter.currentNotificationCenter().removeAllDeliveredNotifications();
}
else {
UIApplication.sharedApplication.cancelAllLocalNotifications();
}
UIApplication.sharedApplication.applicationIconBadgeNumber = 0;
resolve();
}
catch (ex) {
console.log('Error in LocalNotifications.cancelAll: ' + ex);
reject(ex);
}
});
}
getScheduledIds() {
return new Promise((resolve, reject) => {
try {
const scheduledIds = [];
if (LocalNotificationsImpl.isUNUserNotificationCenterAvailable()) {
UNUserNotificationCenter.currentNotificationCenter().getPendingNotificationRequestsWithCompletionHandler((notRequests) => {
if (notRequests) {
for (let i = 0; i < notRequests.count; i++) {
scheduledIds.push(notRequests[i].identifier);
}
}
resolve(scheduledIds.map(Number));
});
}
else {
const scheduled = UIApplication.sharedApplication.scheduledLocalNotifications;
for (let i = 0, l = scheduled.count; i < l; i++) {
scheduledIds.push(scheduled.objectAtIndex(i).userInfo.valueForKey('id'));
}
resolve(scheduledIds.map(Number));
}
}
catch (ex) {
console.log('Error in LocalNotifications.getScheduledIds: ' + ex);
reject(ex);
}
});
}
schedule(scheduleOptions) {
return new Promise(async (resolve, reject) => {
try {
if (!LocalNotificationsImpl.hasPermission()) {
this.requestPermission().then(async (granted) => {
if (granted) {
resolve(await LocalNotificationsImpl.schedulePendingNotifications(scheduleOptions));
}
else {
reject('Permission not granted');
}
});
}
else {
resolve(await LocalNotificationsImpl.schedulePendingNotifications(scheduleOptions));
}
}
catch (ex) {
console.log('Error in LocalNotifications.schedule: ' + ex);
reject(ex);
}
});
}
}
class LocalNotificationsDelegateObserverImpl {
constructor(owner) {
this.receivedInForeground = false;
this.observerUniqueKey = 'nativescript-local-notifications';
this._owner = owner;
}
/**
* Called when the app was opened by a notification.
*/
userNotificationCenterDidReceiveNotificationResponseWithCompletionHandler(center, notificationResponse, completionHandler, next) {
if (notificationResponse.notification.request.content.userInfo.valueForKey('__NotificationType') !== 'nativescript-local-notifications') {
next();
return;
}
const request = notificationResponse.notification.request, notificationContent = request.content, action = notificationResponse.actionIdentifier;
// let's ignore dismiss actions
if (action === UNNotificationDismissActionIdentifier) {
completionHandler();
return;
}
let event = 'default';
if (action !== UNNotificationDefaultActionIdentifier) {
event = notificationResponse instanceof UNTextInputNotificationResponse ? 'input' : 'button';
}
let response = notificationResponse.actionIdentifier;
if (response === UNNotificationDefaultActionIdentifier) {
response = undefined;
}
else if (notificationResponse instanceof UNTextInputNotificationResponse) {
response = notificationResponse.userText;
}
this._owner.get().addOrProcessNotification({
id: +request.identifier,
title: notificationContent.title,
body: notificationContent.body,
payload: JSON.parse(notificationResponse.notification.request.content.userInfo.valueForKey('payload')),
foreground: this.receivedInForeground || UIApplication.sharedApplication.applicationState === 0 /* UIApplicationState.Active */,
event,
response,
});
this.receivedInForeground = false;
completionHandler();
}
/**
* Called when the app is in the foreground.
*/
userNotificationCenterWillPresentNotificationWithCompletionHandler(center, notification, completionHandler, next) {
if (notification.request.content.userInfo.valueForKey('__NotificationType') !== 'nativescript-local-notifications' || notification.request.trigger instanceof UNPushNotificationTrigger) {
next();
return;
}
this.receivedInForeground = true;
if (notification.request.content.userInfo.valueForKey('forceShowWhenInForeground') || notification.request.content.userInfo.valueForKey('priority')) {
completionHandler(1 /* UNNotificationPresentationOptions.Badge */ | 2 /* UNNotificationPresentationOptions.Sound */ | 4 /* UNNotificationPresentationOptions.Alert */);
}
else {
completionHandler(1 /* UNNotificationPresentationOptions.Badge */ | 2 /* UNNotificationPresentationOptions.Sound */);
}
}
}
const instance = new LocalNotificationsImpl();
export const LocalNotifications = instance;
//# sourceMappingURL=index.ios.js.map