onesignal-web-sdk
Version:
Web push notifications from OneSignal.
979 lines (890 loc) • 39.4 kB
text/typescript
import bowser from 'bowser';
import Environment from './Environment';
import { InvalidArgumentError, InvalidArgumentReason } from './errors/InvalidArgumentError';
import { InvalidStateError, InvalidStateReason } from './errors/InvalidStateError';
import { NotSubscribedError, NotSubscribedReason } from './errors/NotSubscribedError';
import { SdkInitError, SdkInitErrorKind } from './errors/SdkInitError';
import Event from './Event';
import EventHelper from './helpers/EventHelper';
import HttpHelper from './helpers/HttpHelper';
import InitHelper, { RegisterOptions } from './helpers/InitHelper';
import MainHelper from './helpers/MainHelper';
import SubscriptionHelper from './helpers/SubscriptionHelper';
import TestHelper from './helpers/TestHelper';
import LimitStore from './LimitStore';
import AltOriginManager from './managers/AltOriginManager';
import LegacyManager from './managers/LegacyManager';
import SdkEnvironment from './managers/SdkEnvironment';
import { AppConfig, AppUserConfig, AppUserConfigNotifyButton } from './models/AppConfig';
import Context from './models/Context';
import { Notification } from './models/Notification';
import { NotificationActionButton } from './models/NotificationActionButton';
import { NotificationPermission } from './models/NotificationPermission';
import { WindowEnvironmentKind } from './models/WindowEnvironmentKind';
import ProxyFrame from './modules/frames/ProxyFrame';
import ProxyFrameHost from './modules/frames/ProxyFrameHost';
import SubscriptionModal from './modules/frames/SubscriptionModal';
import SubscriptionModalHost from './modules/frames/SubscriptionModalHost';
import SubscriptionPopup from './modules/frames/SubscriptionPopup';
import SubscriptionPopupHost from './modules/frames/SubscriptionPopupHost';
import OneSignalApi from './OneSignalApi';
import Popover from './popover/Popover';
import Database from './services/Database';
import IndexedDb from './services/IndexedDb';
import {
awaitOneSignalInitAndSupported,
awaitSdkEvent,
executeCallback,
getConsoleStyle,
isValidEmail,
logMethodCall,
} from './utils';
import { ValidatorUtils } from './utils/ValidatorUtils';
import { DeviceRecord } from './models/DeviceRecord';
import TimedLocalStorage from './modules/TimedLocalStorage';
import { EmailProfile } from './models/EmailProfile';
import { EmailDeviceRecord } from './models/EmailDeviceRecord';
import Emitter, { EventHandler } from './libraries/Emitter';
import Log from './libraries/Log';
import ConfigManager from "./managers/ConfigManager";
import OneSignalUtils from "./utils/OneSignalUtils";
import { ProcessOneSignalPushCalls } from "./utils/ProcessOneSignalPushCalls";
import { AutoPromptOptions } from "./managers/PromptsManager";
export default class OneSignal {
/**
* Pass in the full URL of the default page you want to open when a notification is clicked.
* @PublicApi
*/
static async setDefaultNotificationUrl(url: string) {
if (!ValidatorUtils.isValidUrl(url, { allowNull: true }))
throw new InvalidArgumentError('url', InvalidArgumentReason.Malformed);
await awaitOneSignalInitAndSupported();
logMethodCall('setDefaultNotificationUrl', url);
const appState = await Database.getAppState();
appState.defaultNotificationUrl = url;
await Database.setAppState(appState);
}
/**
* Sets the default title to display on notifications. Will default to the page's document.title if you don't call this.
* @remarks Either DB value defaultTitle or pageTitle is used when showing a notification title.
* @PublicApi
*/
static async setDefaultTitle(title: string) {
await awaitOneSignalInitAndSupported();
logMethodCall('setDefaultTitle', title);
const appState = await Database.getAppState();
appState.defaultNotificationTitle = title;
await Database.setAppState(appState);
}
/**
* @PublicApi
*/
static async setEmail(email: string, options?: SetEmailOptions): Promise<string> {
if (!email)
throw new InvalidArgumentError('email', InvalidArgumentReason.Empty);
if (!isValidEmail(email))
throw new InvalidArgumentError('email', InvalidArgumentReason.Malformed);
// emailAuthHash is expected to be a 64 character SHA-256 hex hash
if (options && options.emailAuthHash && options.emailAuthHash.length !== 64) {
throw new InvalidArgumentError('options.emailAuthHash', InvalidArgumentReason.Malformed);
}
await awaitOneSignalInitAndSupported();
logMethodCall('setEmail', email, options);
const appConfig = await Database.getAppConfig();
const { deviceId } = await Database.getSubscription();
const existingEmailProfile = await Database.getEmailProfile();
if (appConfig.emailAuthRequired && !(options && options.emailAuthHash)) {
throw new InvalidArgumentError('options.emailAuthHash', InvalidArgumentReason.Empty);
}
const newEmailProfile = new EmailProfile(existingEmailProfile.emailId, email);
if (options && options.emailAuthHash) {
newEmailProfile.emailAuthHash = options.emailAuthHash
}
const isExistingEmailSaved = !!existingEmailProfile.emailId;
if (isExistingEmailSaved && appConfig.emailAuthRequired) {
// If we already have a saved email player ID, make a PUT call to update the existing email record
newEmailProfile.emailId = await OneSignalApi.updateEmailRecord(
appConfig,
newEmailProfile,
deviceId
);
} else {
// Otherwise, make a POST call to create a new email record
newEmailProfile.emailId = await OneSignalApi.createEmailRecord(
appConfig,
newEmailProfile,
deviceId
);
}
const isExistingPushRecordSaved = deviceId;
if (
/* If we are subscribed to web push */
isExistingPushRecordSaved &&
(
/* And if we previously saved an email ID and it's different from the new returned ID */
(
!isExistingEmailSaved ||
existingEmailProfile.emailId !== newEmailProfile.emailId
) ||
/* Or if we previously saved an email and the email changed */
(
!existingEmailProfile.emailAddress ||
newEmailProfile.emailAddress !== existingEmailProfile.emailAddress
)
)
) {
// Then update the push device record with a reference to the new email ID and email address
await OneSignalApi.updatePlayer(
appConfig.appId,
deviceId,
{
parent_player_id: newEmailProfile.emailId,
email: newEmailProfile.emailAddress
}
);
}
await Database.setEmailProfile(newEmailProfile);
return newEmailProfile.emailId;
}
/**
* @PublicApi
*/
static async logoutEmail() {
await awaitOneSignalInitAndSupported();
const appConfig = await Database.getAppConfig();
const emailProfile = await Database.getEmailProfile();
const { deviceId } = await Database.getSubscription();
if (!emailProfile.emailId) {
Log.warn(new NotSubscribedError(NotSubscribedReason.NoEmailSet));
return;
}
if (!deviceId) {
Log.warn(new NotSubscribedError(NotSubscribedReason.NoDeviceId));
return;
}
if (!await OneSignalApi.logoutEmail(appConfig, emailProfile, deviceId)) {
Log.warn("Failed to logout email.");
return;
}
await Database.setEmailProfile(new EmailProfile());
}
/**
* Returns true if the current browser supports web push.
* @PublicApi
*/
static isPushNotificationsSupported(): boolean {
logMethodCall('isPushNotificationsSupported');
/*
Push notification support is checked in the initial entry code. If in an unsupported environment, a stubbed empty
version of the SDK will be loaded instead. This file will only be loaded if push notifications are supported.
*/
return true;
}
static async initializeConfig(options: AppUserConfig) {
const appConfig = await new ConfigManager().getAppConfig(options);
Log.debug(`OneSignal: Final web app config: %c${JSON.stringify(appConfig, null, 4)}`, getConsoleStyle('code'));
OneSignal.context = new Context(appConfig);
OneSignal.config = OneSignal.context.appConfig;
}
/**
* Initializes the SDK, called by the developer.
* @PublicApi
*/
static async init(options: AppUserConfig) {
logMethodCall('init');
await InitHelper.polyfillSafariFetch();
InitHelper.errorIfInitAlreadyCalled();
await OneSignal.initializeConfig(options);
if (!OneSignal.config) {
throw new Error("OneSignal config not initialized!");
}
if (bowser.safari && !OneSignal.config.safariWebId) {
/**
* Don't throw an error for missing Safari config; many users set up
* support on Chrome/Firefox and don't intend to support Safari but don't
* place conditional initialization checks.
*/
Log.warn(new SdkInitError(SdkInitErrorKind.MissingSafariWebId));
return;
}
if (OneSignal.config.userConfig.requiresUserPrivacyConsent) {
const providedConsent = await Database.getProvideUserConsent();
if (!providedConsent) {
OneSignal.pendingInit = true;
return;
}
}
await OneSignal.delayedInit();
}
private static async delayedInit(): Promise<void> {
OneSignal.pendingInit = false;
// Ignore Promise as doesn't return until the service worker becomes active.
OneSignal.context.workerMessenger.listen();
async function __init() {
if (OneSignal.__initAlreadyCalled)
return;
OneSignal.__initAlreadyCalled = true;
OneSignal.emitter.on(OneSignal.EVENTS.NATIVE_PROMPT_PERMISSIONCHANGED, EventHelper.onNotificationPermissionChange);
OneSignal.emitter.on(OneSignal.EVENTS.SUBSCRIPTION_CHANGED, EventHelper._onSubscriptionChanged);
OneSignal.emitter.on(OneSignal.EVENTS.SDK_INITIALIZED, InitHelper.onSdkInitialized);
if (OneSignalUtils.isUsingSubscriptionWorkaround()) {
/**
* The user may have forgot to choose a subdomain in his web app setup.
*
* Or, the user may have an HTTP & HTTPS site while using an HTTPS-only
* config on both variants. This would cause the HTTPS site to work
* perfectly, while causing errors and preventing web push from working
* on the HTTP site.
*/
if (!OneSignal.config || !OneSignal.config.subdomain)
throw new SdkInitError(SdkInitErrorKind.MissingSubdomain);
/**
* The iFrame may never load (e.g. OneSignal might be down), in which
* case the rest of the SDK's initialization will be blocked. This is a
* good thing! We don't want to access IndexedDb before we know which
* origin to store data on.
*/
OneSignal.proxyFrameHost = await AltOriginManager.discoverAltOrigin(OneSignal.config);
}
window.addEventListener('focus', () => {
// Checks if permission changed every time a user focuses on the page,
// since a user has to click out of and back on the page to check permissions
MainHelper.checkAndTriggerNotificationPermissionChanged();
});
await InitHelper.initSaveState(document.title);
await InitHelper.saveInitOptions();
if (SdkEnvironment.getWindowEnv() === WindowEnvironmentKind.CustomIframe)
await Event.trigger(OneSignal.EVENTS.SDK_INITIALIZED);
else
await InitHelper.internalInit();
}
if (document.readyState === 'complete' || document.readyState === 'interactive')
await __init();
else {
Log.debug('OneSignal: Waiting for DOMContentLoaded or readyStateChange event before continuing' +
' initialization...');
window.addEventListener('DOMContentLoaded', () => { __init(); });
document.onreadystatechange = () => {
if (document.readyState === 'complete' || document.readyState === 'interactive')
__init();
};
}
}
/**
* Call after use accepts your user consent agreement
* @PublicApi
*/
public static async provideUserConsent(consent: boolean): Promise<void> {
await Database.setProvideUserConsent(consent);
if (consent && OneSignal.pendingInit)
await OneSignal.delayedInit();
}
/**
* Prompts the user to subscribe using the remote local notification workaround for HTTP sites.
* @PublicApi
*/
public static async showHttpPermissionRequest(options?: AutoPromptOptions): Promise<any> {
Log.debug('Called showHttpPermissionRequest(), redirecting to HTTP prompt.');
OneSignal.showHttpPrompt(options).catch(e => Log.info(e));
}
/**
* Shows a sliding modal prompt on the page for users to trigger the HTTP popup window to subscribe.
* @PublicApi
*/
public static async showHttpPrompt(options?: AutoPromptOptions) {
await awaitOneSignalInitAndSupported();
await OneSignal.context.promptsManager.internalShowSlidedownPrompt(options);
}
/**
* Shows a native browser prompt.
* @PublicApi
*/
public static async showNativePrompt(): Promise<void> {
await awaitOneSignalInitAndSupported();
await OneSignal.context.promptsManager.internalShowNativePrompt();
}
/**
* Shows a sliding modal prompt on the page for users.
* @PublicApi
*/
public static async showSlidedownPrompt(options?: AutoPromptOptions): Promise<void> {
await awaitOneSignalInitAndSupported();
await OneSignal.context.promptsManager.internalShowSlidedownPrompt(options);
}
/**
* Prompts the user to subscribe.
* @PublicApi
*/
static async registerForPushNotifications(options?: RegisterOptions): Promise<void> {
if (!OneSignal.initialized) {
await new Promise((resolve, _reject) => {
OneSignal.emitter.once(OneSignal.EVENTS.SDK_INITIALIZED, async () => {
await InitHelper.registerForPushNotifications(options);
return resolve();
});
})
} else
return await InitHelper.registerForPushNotifications(options);
}
/**
* Returns a promise that resolves to the browser's current notification permission as
* 'default', 'granted', or 'denied'.
* @param callback A callback function that will be called when the browser's current notification permission
* has been obtained, with one of 'default', 'granted', or 'denied'.
* @PublicApi
*/
public static async getNotificationPermission(onComplete?: Function): Promise<NotificationPermission> {
await awaitOneSignalInitAndSupported();
return OneSignal.privateGetNotificationPermission(onComplete);
}
static async privateGetNotificationPermission(onComplete?: Function): Promise<NotificationPermission> {
const permission = await OneSignal.context.permissionManager.getNotificationPermission(
OneSignal.config!.safariWebId
);
if (onComplete)
onComplete(permission);
return permission;
}
/**
* @PublicApi
*/
static async getTags(callback?: Action<any>) {
await awaitOneSignalInitAndSupported();
logMethodCall('getTags', callback);
const { appId } = await Database.getAppConfig();
const { deviceId } = await Database.getSubscription();
if (!deviceId) {
// TODO: Throw an error here in future v2; for now it may break existing client implementations.
Log.info(new NotSubscribedError(NotSubscribedReason.NoDeviceId));
return null;
}
const { tags } = await OneSignalApi.getPlayer(appId, deviceId);
executeCallback(callback, tags);
return tags;
}
/**
* @PublicApi
*/
static async sendTag(key: string, value: any, callback?: Action<Object>): Promise<Object | null> {
const tag = {} as {[key: string]: any};
tag[key] = value;
return await OneSignal.sendTags(tag, callback);
}
/**
* @PublicApi
*/
static async sendTags(tags: {[key: string]: any}, callback?: Action<Object>): Promise<Object | null> {
await awaitOneSignalInitAndSupported();
logMethodCall('sendTags', tags, callback);
if (!tags || Object.keys(tags).length === 0) {
// TODO: Throw an error here in future v2; for now it may break existing client implementations.
Log.info(new InvalidArgumentError('tags', InvalidArgumentReason.Empty));
return null;
}
// Our backend considers false as removing a tag, so convert false -> "false" to allow storing as a value
Object.keys(tags).forEach(key => {
if (tags[key] === false)
tags[key] = "false";
});
const { appId } = await Database.getAppConfig();
const emailProfile = await Database.getEmailProfile();
if (emailProfile.emailId) {
await OneSignalApi.updatePlayer(appId, emailProfile.emailId, {
tags: tags,
email_auth_hash: emailProfile.emailAuthHash,
});
}
var { deviceId } = await Database.getSubscription();
if (!deviceId) {
await awaitSdkEvent(OneSignal.EVENTS.REGISTERED);
}
// After the user subscribers, he will have a device ID, so get it again
var { deviceId: newDeviceId } = await Database.getSubscription();
await OneSignalApi.updatePlayer(appId, newDeviceId, {
tags: tags
});
executeCallback(callback, tags);
return tags;
}
/**
* @PublicApi
*/
static async deleteTag(tag: string): Promise<Array<string>> {
return await OneSignal.deleteTags([tag]);
}
/**
* @PublicApi
*/
static async deleteTags(tags: Array<string>, callback?: Action<Array<string>>): Promise<Array<string>> {
await awaitOneSignalInitAndSupported();
logMethodCall('deleteTags', tags, callback);
if (!ValidatorUtils.isValidArray(tags))
throw new InvalidArgumentError('tags', InvalidArgumentReason.Malformed);
if (tags.length === 0) {
// TODO: Throw an error here in future v2; for now it may break existing client implementations.
Log.info(new InvalidArgumentError('tags', InvalidArgumentReason.Empty));
}
const tagsToSend = {} as {[key: string]: string};
for (let tag of tags) {
tagsToSend[tag] = '';
}
const deletedTags = await OneSignal.sendTags(tagsToSend);
if (deletedTags) {
const deletedTagKeys = Object.keys(deletedTags);
executeCallback(callback, deletedTagKeys);
return deletedTagKeys;
}
return [];
}
/**
* @PublicApi
*/
public static async setExternalUserId(externalUserId: string | undefined | null): Promise<void> {
await awaitOneSignalInitAndSupported();
logMethodCall("setExternalUserId");
const isExistingUser = await this.context.subscriptionManager.isAlreadyRegisteredWithOneSignal();
if (!isExistingUser) {
await awaitSdkEvent(OneSignal.EVENTS.REGISTERED);
}
await Promise.all([
OneSignal.database.setExternalUserId(externalUserId),
OneSignal.context.updateManager.sendExternalUserIdUpdate(externalUserId),
]);
}
/**
* @PublicApi
*/
public static async getExternalUserId(): Promise<string | undefined | null> {
await awaitOneSignalInitAndSupported();
logMethodCall("getExternalUserId");
return await OneSignal.database.getExternalUserId();
}
/**
* @PublicApi
*/
public static async removeExternalUserId(): Promise<void> {
await awaitOneSignalInitAndSupported();
logMethodCall("removeExternalUserId");
const isExistingUser = await this.context.subscriptionManager.isAlreadyRegisteredWithOneSignal();
if (!isExistingUser) {
Log.warn("User is not subscribed, cannot remove external user id.");
return;
}
await Promise.all([
OneSignal.database.setExternalUserId(undefined),
OneSignal.context.updateManager.sendExternalUserIdUpdate(undefined),
]);
}
/**
* @PublicApi
*/
static async addListenerForNotificationOpened(callback?: Action<Notification>) {
await awaitOneSignalInitAndSupported();
logMethodCall('addListenerForNotificationOpened', callback);
OneSignal.emitter.once(OneSignal.EVENTS.NOTIFICATION_CLICKED, notification => {
executeCallback(callback, notification);
});
if (OneSignal.config) {
EventHelper.fireStoredNotificationClicks(OneSignal.config.pageUrl || OneSignal.config.userConfig.pageUrl);
}
}
/**
* @PublicApi
* @Deprecated
*/
static async getIdsAvailable(
callback?: Action<{userId: string | undefined | null, registrationId: string | undefined | null}>):
Promise<{userId: string | undefined | null, registrationId: string | undefined | null}> {
await awaitOneSignalInitAndSupported();
logMethodCall('getIdsAvailable', callback);
const { deviceId, subscriptionToken } = await Database.getSubscription();
const bundle = {
userId: deviceId,
registrationId: subscriptionToken
};
executeCallback(callback, bundle);
return bundle;
}
/**
* Returns a promise that resolves to true if all required conditions for push messaging are met; otherwise resolves to false.
* @param callback A callback function that will be called when the current subscription status has been obtained.
* @PublicApi
*/
static async isPushNotificationsEnabled(callback?: Action<boolean>): Promise<boolean> {
await awaitOneSignalInitAndSupported();
return OneSignal.privateIsPushNotificationsEnabled(callback);
}
static async privateIsPushNotificationsEnabled(callback?: Action<boolean>): Promise<boolean> {
logMethodCall('isPushNotificationsEnabled', callback);
const context: Context = OneSignal.context;
const subscriptionState = await context.subscriptionManager.getSubscriptionState();
executeCallback(callback, subscriptionState.subscribed && !subscriptionState.optedOut);
return subscriptionState.subscribed && !subscriptionState.optedOut;
}
/**
* @PublicApi
*/
static async setSubscription(newSubscription: boolean): Promise<void> {
await awaitOneSignalInitAndSupported();
logMethodCall('setSubscription', newSubscription);
const appConfig = await Database.getAppConfig();
const { appId } = appConfig;
const subscription = await Database.getSubscription();
const { deviceId } = subscription;
if (!appConfig.appId)
throw new InvalidStateError(InvalidStateReason.MissingAppId);
if (!ValidatorUtils.isValidBoolean(newSubscription))
throw new InvalidArgumentError('newSubscription', InvalidArgumentReason.Malformed);
if (!deviceId) {
// TODO: Throw an error here in future v2; for now it may break existing client implementations.
Log.info(new NotSubscribedError(NotSubscribedReason.NoDeviceId));
return;
}
subscription.optedOut = !newSubscription;
await OneSignalApi.updatePlayer(appId, deviceId, {
notification_types: MainHelper.getNotificationTypeFromOptIn(newSubscription)
});
await Database.setSubscription(subscription);
EventHelper.onInternalSubscriptionSet(subscription.optedOut);
EventHelper.checkAndTriggerSubscriptionChanged();
}
/**
* @PendingPublicApi
*/
static async isOptedOut(callback?: Action<boolean | undefined | null>):
Promise<boolean | undefined | null> {
await awaitOneSignalInitAndSupported();
return OneSignal.internalIsOptedOut(callback);
}
static async internalIsOptedOut(callback?: Action<boolean | undefined | null>):
Promise<boolean | undefined | null> {
logMethodCall('isOptedOut', callback);
const { optedOut } = await Database.getSubscription();
executeCallback(callback, optedOut);
return optedOut;
}
/**
* Returns a promise that resolves once the manual subscription override has been set.
* @private
* @PendingPublicApi
*/
static async optOut(doOptOut: boolean, callback?: Action<void>): Promise<void> {
await awaitOneSignalInitAndSupported();
logMethodCall('optOut', doOptOut, callback);
if (!ValidatorUtils.isValidBoolean(doOptOut))
throw new InvalidArgumentError('doOptOut', InvalidArgumentReason.Malformed);
await OneSignal.setSubscription(!doOptOut);
executeCallback(callback);
}
/**
* Returns a promise that resolves to the stored OneSignal email ID if one is set; otherwise null.
* @param callback A function accepting one parameter for the OneSignal email ID.
* @PublicApi
*/
static async getEmailId(callback?: Action<string | undefined>): Promise<string | undefined> {
await awaitOneSignalInitAndSupported();
logMethodCall('getEmailId', callback);
const emailProfile = await Database.getEmailProfile();
const emailId = emailProfile.emailId;
executeCallback(callback, emailId);
return emailId;
}
/**
* Returns a promise that resolves to the stored OneSignal user ID if one is set; otherwise null.
* @param callback A function accepting one parameter for the OneSignal user ID.
* @PublicApi
*/
static async getUserId(callback?: Action<string | undefined | null>): Promise<string | undefined | null> {
await awaitOneSignalInitAndSupported();
logMethodCall('getUserId', callback);
const subscription = await Database.getSubscription();
const deviceId = subscription.deviceId;
executeCallback(callback, deviceId);
return deviceId;
}
/**
* Returns a promise that resolves to the stored push token if one is set; otherwise null.
* @PublicApi
*/
static async getRegistrationId(callback?: Action<string | undefined | null>):
Promise<string | undefined | null> {
await awaitOneSignalInitAndSupported();
logMethodCall('getRegistrationId', callback);
const subscription = await Database.getSubscription();
const subscriptionToken = subscription.subscriptionToken;
executeCallback(callback, subscriptionToken);
return subscriptionToken;
}
/**
* Returns a promise that resolves to false if setSubscription(false) is "in effect". Otherwise returns true.
* This means a return value of true does not mean the user is subscribed, only that the user did not call
* setSubcription(false).
* @private
* @PublicApi (given to customers)
*/
static async getSubscription(callback?: Action<boolean>): Promise<boolean> {
await awaitOneSignalInitAndSupported();
return await OneSignal.privateGetSubscription(callback);
}
static async privateGetSubscription(callback?: Action<boolean>): Promise<boolean> {
logMethodCall('getSubscription', callback);
const subscription = await Database.getSubscription();
const subscriptionStatus = !subscription.optedOut;
executeCallback(callback, subscriptionStatus);
return subscriptionStatus;
}
/**
* @PublicApi
*/
static async sendSelfNotification(title: string = 'OneSignal Test Message',
message: string = 'This is an example notification.',
url: string = new URL(location.href).origin + '?_osp=do_not_open',
icon: URL,
data: Map<String, any>,
buttons: Array<NotificationActionButton>): Promise<void> {
await awaitOneSignalInitAndSupported();
logMethodCall('sendSelfNotification', title, message, url, icon, data, buttons);
const appConfig = await Database.getAppConfig();
const subscription = await Database.getSubscription();
if (!appConfig.appId)
throw new InvalidStateError(InvalidStateReason.MissingAppId);
if (!(await OneSignal.isPushNotificationsEnabled()))
throw new NotSubscribedError(NotSubscribedReason.NoDeviceId);
if (!ValidatorUtils.isValidUrl(url))
throw new InvalidArgumentError('url', InvalidArgumentReason.Malformed);
if (!ValidatorUtils.isValidUrl(icon, { allowEmpty: true, requireHttps: true }))
throw new InvalidArgumentError('icon', InvalidArgumentReason.Malformed);
if (subscription.deviceId) {
await OneSignalApi.sendNotification(appConfig.appId, [subscription.deviceId], {'en': title}, {'en': message},
url, icon, data, buttons);
}
}
/**
* Used to load OneSignal asynchronously from a webpage
* Allows asynchronous function queuing while the SDK loads in the browser with <script src="..." async/>
* @PublicApi
* @param item - Ether a function or an arry with a OneSignal function name followed by it's parameters
* @Example
* OneSignal.push(["functionName", param1, param2]);
* OneSignal.push(function() { OneSignal.functionName(param1, param2); });
*/
static push(item: Function | object[]) {
ProcessOneSignalPushCalls.processItem(OneSignal, item);
}
/**
* Used to subscribe to OneSignal events such as "subscriptionChange"
* Fires each time the event occurs
* @param event - Event name to subscribe to
* @param listener - Listener to fire when event happens
* @PublicApi
*/
static on(event: string, listener: EventHandler): Emitter {
return this.emitter.on(event, listener);
}
/**
* Used to un-subscribe from OneSignal events such as "subscriptionChange"
* @param event - Event name to un-subscribe from
* @param listener - Event listener to remove from the collection for a specified event
* @PublicApi
*/
static off(event: string, listener: EventHandler): Emitter {
return this.emitter.off(event, listener);
}
/**
* Used to subscribe to OneSignal events such as "subscriptionChange"
* Fires only once
* @param event - Event name to subscribe to
* @param listener - Listener to fire when event happens
* @PublicApi
*/
static once(event: string, listener: EventHandler): Emitter {
return this.emitter.once(event, listener);
}
static __doNotShowWelcomeNotification: boolean;
static VERSION = Environment.version();
static _VERSION = Environment.version();
static sdkEnvironment = SdkEnvironment;
static _notificationOpenedCallbacks = [];
static _idsAvailable_callback = [];
static _defaultLaunchURL = null;
static config: AppConfig | null = null;
static _sessionInitAlreadyRunning = false;
static _isNotificationEnabledCallback = [];
static _subscriptionSet = true;
static modalUrl = null;
static _windowWidth = 650;
static _windowHeight = 568;
static _isNewVisitor = false;
static _channel = null;
static timedLocalStorage = TimedLocalStorage;
static initialized = false;
static notifyButton: AppUserConfigNotifyButton | null = null;
static store = LimitStore;
static environment = Environment;
static database = Database;
static event = Event;
static browser = bowser;
static popover: Popover | null = null;
static log = Log;
static api = OneSignalApi;
static indexedDb = IndexedDb;
static mainHelper = MainHelper;
static subscriptionHelper = SubscriptionHelper;
static httpHelper = HttpHelper;
static eventHelper = EventHelper;
static initHelper = InitHelper;
static testHelper = TestHelper;
private static pendingInit: boolean = true;
static subscriptionPopup: SubscriptionPopup;
static subscriptionPopupHost: SubscriptionPopupHost;
static subscriptionModal: SubscriptionModal;
static subscriptionModalHost: SubscriptionModalHost;
static proxyFrameHost: ProxyFrameHost;
static proxyFrame: ProxyFrame;
static emitter: Emitter = new Emitter();
/**
* The additional path to the worker file.
*
* Usually just the filename (in case the file is named differently), but also supports cases where the folder
* is different.
*
* However, the init options 'path' should be used to specify the folder path instead since service workers will not
* auto-update correctly on HTTPS site load if the config init options 'path' is not set.
*/
static SERVICE_WORKER_UPDATER_PATH = 'OneSignalSDKUpdaterWorker.js';
static SERVICE_WORKER_PATH = 'OneSignalSDKWorker.js';
/**
* By default, the service worker is expected to be accessible at the root scope. If the service worker is only
* available with in a sub-directory, SERVICE_WORKER_PARAM must be changed to the sub-directory (with a trailing
* slash). This would allow pages to function correctly as not to block the service worker ready call, which would
* hang indefinitely if we requested root scope registration but the service was only available in a child scope.
*/
static SERVICE_WORKER_PARAM: { scope: string } = {scope: '/'};
static _LOGGING = false;
static LOGGING = false;
static _usingNativePermissionHook = false;
static _initCalled = false;
static __initAlreadyCalled = false;
static context: Context;
static checkAndWipeUserSubscription = function () { }
static DeviceRecord = DeviceRecord;
static EmailDeviceRecord = EmailDeviceRecord;
static notificationPermission = NotificationPermission;
/**
* Used by Rails-side HTTP popup. Must keep the same name.
* @InternalApi
*/
static _initHttp = HttpHelper.initHttp;
/**
* Used by Rails-side HTTP popup. Must keep the same name.
* @InternalApi
*/
static _initPopup = () => OneSignal.subscriptionPopup.subscribe();
static POSTMAM_COMMANDS = {
CONNECTED: 'connect',
REMOTE_NOTIFICATION_PERMISSION: 'postmam.remoteNotificationPermission',
REMOTE_DATABASE_GET: 'postmam.remoteDatabaseGet',
REMOTE_DATABASE_PUT: 'postmam.remoteDatabasePut',
REMOTE_DATABASE_REMOVE: 'postmam.remoteDatabaseRemove',
REMOTE_OPERATION_COMPLETE: 'postman.operationComplete',
REMOTE_RETRIGGER_EVENT: 'postmam.remoteRetriggerEvent',
MODAL_LOADED: 'postmam.modalPrompt.loaded',
MODAL_PROMPT_ACCEPTED: 'postmam.modalPrompt.accepted',
MODAL_PROMPT_REJECTED: 'postmam.modalPrompt.canceled',
POPUP_LOADED: 'postmam.popup.loaded',
POPUP_ACCEPTED: 'postmam.popup.accepted',
POPUP_REJECTED: 'postmam.popup.canceled',
POPUP_CLOSING: 'postman.popup.closing',
REMOTE_NOTIFICATION_PERMISSION_CHANGED: 'postmam.remoteNotificationPermissionChanged',
IFRAME_POPUP_INITIALIZE: 'postmam.iframePopupInitialize',
UNSUBSCRIBE_FROM_PUSH: 'postmam.unsubscribeFromPush',
SET_SESSION_COUNT: 'postmam.setSessionCount',
REQUEST_HOST_URL: 'postmam.requestHostUrl',
WINDOW_TIMEOUT: 'postmam.windowTimeout',
FINISH_REMOTE_REGISTRATION: 'postmam.finishRemoteRegistration',
FINISH_REMOTE_REGISTRATION_IN_PROGRESS: 'postmam.finishRemoteRegistrationInProgress',
POPUP_BEGIN_MESSAGEPORT_COMMS: 'postmam.beginMessagePortComms',
SERVICEWORKER_COMMAND_REDIRECT: 'postmam.command.redirect',
MARK_PROMPT_DISMISSED: 'postmam.markPromptDismissed',
IS_SUBSCRIBED: 'postmam.isSubscribed',
UNSUBSCRIBE_PROXY_FRAME: 'postman.unsubscribeProxyFrame',
GET_EVENT_LISTENER_COUNT: 'postmam.getEventListenerCount',
SERVICE_WORKER_STATE: 'postmam.serviceWorkerState',
GET_WORKER_VERSION: 'postmam.getWorkerVersion',
SUBSCRIPTION_EXPIRATION_STATE: 'postmam.subscriptionExpirationState',
PROCESS_EXPIRING_SUBSCRIPTIONS: 'postmam.processExpiringSubscriptions',
GET_SUBSCRIPTION_STATE: 'postmam.getSubscriptionState',
};
static EVENTS = {
/**
* Occurs when the user clicks the "Continue" or "No Thanks" button on the HTTP popup or HTTPS modal prompt.
* For HTTP sites (and HTTPS sites using the modal prompt), this event is fired before the native permission
* prompt is shown. This event is mostly used for HTTP sites.
*/
CUSTOM_PROMPT_CLICKED: 'customPromptClick',
/**
* Occurs when the user clicks "Allow" or "Block" on the native permission prompt on Chrome, Firefox, or Safari.
* This event is used for both HTTP and HTTPS sites and occurs after the user actually grants notification
* permissions for the site. Occurs before the user is actually subscribed to push notifications.
*/
NATIVE_PROMPT_PERMISSIONCHANGED: 'notificationPermissionChange',
/**
* Occurs after the user is officially subscribed to push notifications. The service worker is fully registered
* and activated and the user is eligible to receive push notifications at any point after this.
*/
SUBSCRIPTION_CHANGED: 'subscriptionChange',
/**
* Occurs after a POST call to OneSignal's server to send the welcome notification has completed. The actual
* notification arrives shortly after.
*/
WELCOME_NOTIFICATION_SENT: 'sendWelcomeNotification',
/**
* Occurs when a notification is displayed.
*/
NOTIFICATION_DISPLAYED: 'notificationDisplay',
/**
* Occurs when a notification is dismissed by the user either clicking 'X' or clearing all notifications
* (available in Android). This event is NOT called if the user clicks the notification's body or any of the
* action buttons.
*/
NOTIFICATION_DISMISSED: 'notificationDismiss',
/**
* New event replacing legacy addNotificationOpenedHandler(). Used when the notification was clicked.
*/
NOTIFICATION_CLICKED: 'notificationClick',
/**
* Occurs after the document ready event fires and, for HTTP sites, the iFrame to subdomain.onesignal.com has
* loaded.
* Before this event, IndexedDB access is not possible for HTTP sites.
*/
SDK_INITIALIZED: 'initializeInternal',
/**
* Occurs after the SDK finishes its final internal initialization. The final initialization event.
*/
SDK_INITIALIZED_PUBLIC: 'initialize',
/**
* Occurs after the user subscribes to push notifications and a new user entry is created on OneSignal's server,
* and also occurs when the user begins a new site session and the last_session and last_active is updated on
* OneSignal's server.
*/
REGISTERED: 'register',
/**
* Occurs as the HTTP popup is closing.
*/
POPUP_CLOSING: 'popupClose',
/**
* Occurs when the native permission prompt is displayed.
*/
PERMISSION_PROMPT_DISPLAYED: 'permissionPromptDisplay',
/**
* For internal testing only. Used for all sorts of things.
*/
TEST_INIT_OPTION_DISABLED: 'testInitOptionDisabled',
TEST_WOULD_DISPLAY: 'testWouldDisplay',
POPUP_WINDOW_TIMEOUT: 'popupWindowTimeout',
};
}
LegacyManager.ensureBackwardsCompatibility(OneSignal);
Log.info(`%cOneSignal Web SDK loaded (version ${OneSignal._VERSION}, ${SdkEnvironment.getWindowEnv().toString()} environment).`, getConsoleStyle('bold'));
Log.debug(`Current Page URL: ${typeof location === "undefined" ? "NodeJS" : location.href}`);
Log.debug(`Browser Environment: ${bowser.name} ${bowser.version}`);