From 63916ea931d1a37b0dada8ff027f2cd184ce7512 Mon Sep 17 00:00:00 2001 From: ipanardian Date: Tue, 3 Jan 2017 14:45:24 +0700 Subject: [PATCH 01/17] Implement service worker api Tested on Android 6.0.1 Chrome 55 Firefox 50 --- package.json | 3 +- src/BrowserNotif.ts | 76 +++- src/ServiceWorkerApi.d.ts | 921 ++++++++++++++++++++++++++++++++++++++ src/sw.ts | 0 tsconfig.json | 13 +- 5 files changed, 995 insertions(+), 18 deletions(-) create mode 100644 src/ServiceWorkerApi.d.ts create mode 100644 src/sw.ts diff --git a/package.json b/package.json index cc0ef60..5481d26 100644 --- a/package.json +++ b/package.json @@ -36,5 +36,6 @@ "typescript": "^2.1.4", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0" - } + }, + "dependencies": {} } diff --git a/src/BrowserNotif.ts b/src/BrowserNotif.ts index c10fc9b..e2a060f 100644 --- a/src/BrowserNotif.ts +++ b/src/BrowserNotif.ts @@ -9,8 +9,7 @@ */ /// - -"use strict"; +/// /** * Interface for BrowserNotif configuration @@ -41,7 +40,6 @@ interface BrowserNotifOptions extends NotificationOptions { * Interface for BrowserNotif */ interface BrowserNotifInterface { - // requestPermission(callback: (ev: string) => void): BrowserNotif notify(title: string, body: string, callback: (notif: Notification) => void): BrowserNotif click(callback: () => void): BrowserNotif close(): void @@ -118,8 +116,6 @@ export default class BrowserNotif implements BrowserNotifInterface if (!BrowserNotif.isSupported()) { console.warn('This browser does not support system notifications'); } - - // Navigator.serviceWorker.register('sw.js') } /** @@ -146,18 +142,60 @@ export default class BrowserNotif implements BrowserNotifInterface } /** - * Get request permission + * Register serviceWorker and Get request permission * @param {string} callback - * @return {BrowserNotif} */ public static requestPermission(callback: (permission: NotificationPermission) => void): void { + BrowserNotif._registerServiceWorker() + Notification.requestPermission((permission: NotificationPermission) => { if (typeof callback === 'function') { - callback.call(this, permission); + callback.call(this, permission) } }); } + /** + * Register serviceWorker + * This is an experimental technology! + */ + protected static _registerServiceWorker(): void { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('sw.js').then(serviceWorkerRegistration => { + console.log('Service Worker is ready :', serviceWorkerRegistration) + }) + .catch(e => console.warn('BrowserNotif: ', e)) + } + } + + /** + * Show notification from serviceWorker + * This is an experimental technology! + */ + protected _showNotifServiceWorker(callback?: () => void): void { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.showNotification(this.title, this.notifOptions).then(() => { + callback.call(this) + }) + }) + .catch(e => console.error('BrowserNotif: ', e)) + } + } + + protected _getNotifServiceWorker(callback: (notification: Notification) => void): void { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.getNotifications({tag: this.options.tag}).then(notifications => { + if (notifications.length > 0) { + callback.call(this, notifications[0]) + } + }) + }) + .catch(e => console.error('BrowserNotif: ', e)) + } + } + /** * Trigger notify * @param {string} title @@ -193,14 +231,26 @@ export default class BrowserNotif implements BrowserNotifInterface /** * Create an instance of Notification API * @param {Notification} callback - * @return {[type]} */ protected _notify(callback?: (notif: Notification) => void): void { - this.notification = new Notification(this.title, this.notifOptions) - this._closeNotification() - if (typeof callback === 'function') { - callback.call(this, this.notification); + if ('serviceWorker' in navigator) { + this._showNotifServiceWorker(() => { + this._getNotifServiceWorker(notification => { + this.notification = notification + if (typeof callback === 'function') { + callback.call(this, this.notification) + } + }) + }) } + else { + this.notification = new Notification(this.title, this.notifOptions) + this._closeNotification() + if (typeof callback === 'function') { + callback.call(this, this.notification) + } + } + } /** diff --git a/src/ServiceWorkerApi.d.ts b/src/ServiceWorkerApi.d.ts new file mode 100644 index 0000000..6ee13f5 --- /dev/null +++ b/src/ServiceWorkerApi.d.ts @@ -0,0 +1,921 @@ +// Type definitions for service_worker_api +// Project: https://developer.mozilla.org/fr/docs/Web/API/ServiceWorker_API +// Definitions by: Tristan Caron +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +// // REMOVED third "/" so this doesn't fire. Problem with duplicate Promises +// between es6 and typescript - https://github.com/DefinitelyTyped/DefinitelyTyped/issues/5015 + +/** + * Provides methods relating to the body of the response/request, allowing you + * to declare what its content type is and how it should be handled. + */ +interface Body { + /** + * Contains a Boolean that indicates whether the body has been read. + * @readonly + */ + bodyUsed: boolean; + + /** + * Takes a Response stream and reads it to completion. + * It returns a promise that resolves with an ArrayBuffer. + */ + arrayBuffer(): Promise; + + /** + * Takes a Response stream and reads it to completion. + * It returns a promise that resolves with a Blob. + */ + blob(): Promise; + + /** + * Takes a Response stream and reads it to completion. + * It returns a promise that resolves with a FormData object. + */ + formData(): Promise; + + /** + * Takes a Response stream and reads it to completion. + * It returns a promise that resolves with a JSON object. + */ + json(): Promise; + + /** + * Takes a Response stream and reads it to completion. + * It returns a promise that resolves with a USVString (text). + */ + text(): Promise; + +} + +/** + * Represents response/request headers, allowing you to query them and take + * different actions depending on the results. + */ +interface Header { + new(): Header; + + /** + * Appends a new value onto an existing header inside a Headers object, or + * adds the header if it does not already exist. + * + * @param name The name of the HTTP header you want to add to the Headers + * object. + * @param value The value of the HTTP header you want to add. + */ + append(name: string, value: string): void; + + /** + * Deletes a header from a Headers object. + * + * @param name The name of the HTTP header you want to delete from the + * Headers object. + */ + delete(name: string): void; + + /** + * Returns the first value of a given header from within a Headers object. + * + * @param name The name of the HTTP header whose value you want to retrieve + * from the Headers object. If the given name is not the name of an + * HTTP header, this method throws a TypeError. + */ + get(name: string): string; + + /** + * Returns an array of all the values of a header within a Headers object + * with a given name. + * + * @param name The name of the HTTP header whose values you want to retrieve + * from the Headers object. If the given name is not the name of an + * HTTP header, this method throws a TypeError. + */ + getAll(name:string): Array; + + /** + * Returns a boolean stating whether a Headers object contains a + * certain header. + * + * @param name The name of the HTTP header you want to test for. If the + * given name is not the name of an HTTP header, this method throws + * a TypeError. + */ + has(name: string): boolean; + + /** + * Sets a new value for an existing header inside a Headers object, or + * adds the header if it does not already exist. + * + * @param name The name of the HTTP header you want to set to a new value. + * If the given name is not the name of an HTTP header, this method throws + * a TypeError. + * @param value The new value you want to set. + */ + set(name: string, value: string): void; +} + +/** + * Represents the response to a request. + */ +interface Response extends Body { + new(): Response; + + /** + * Contains the type of the response (e.g., basic, cors). + * @readonly + */ + type: string; + + /** + * Contains the URL of the response. + * @readonly + */ + url: string; + + /** + * Contains a boolean stating whether this is the final URL of the response. + */ + useFinalURL: boolean; + + /** + * Contains the status code of the response (e.g., 200 for a success). + * @readonly + */ + status: number; + + /** + * Contains a boolean stating whether the response was successful + * (status in the range 200-299) or not. + * @readonly + */ + ok: boolean; + + /** + * Contains the status message corresponding to the status code + * (e.g., OK for 200). + * @readonly + */ + statusText: string; + + /** + * Contains the Headers object associated with the response. + * @readonly + */ + headers: Header; + + /** + * Creates a clone of a Response object. + */ + clone(): Response; + + /** + * Returns a new Response object associated with a network error. + */ + error(): Response; + + /** + * Creates a new response with a different URL. + */ + redirect(): Response; +} + +/** + * Represents a resource request. + */ +interface Request extends Body { + new(): Request; + + /** + * Contains the request's method (GET, POST, etc.). + * @readonly + */ + method: string; + + /** + * Contains the URL of the request. + * @readonly + */ + url: string; + + /** + * Contains the associated Headers object of the request. + * @readonly + */ + headers: Header; + + /** + * Contains the context of the request (e.g., audio, image, iframe, etc.). + * @readonly + */ + context: string; + + /** + * Contains the referrer of the request (e.g., client). + * @readonly + */ + referrer: string; + + /** + * Contains the mode of the request (e.g., cors, no-cors, same-origin). + * @readonly + */ + mode: string; + + /** + * Contains the credentials of the request (e.g., omit, same-origin). + * @readonly + */ + credentials: string; + + /** + * Contains the cache mode of the request (e.g., default, reload, no-cache). + * @readonly + */ + cache: string; + + /** + * Creates a copy of the current Request object. + */ + clone(): Request; +} + +/** + * An CacheOptions object allowing you to set specific control options for the + * matching done in the match operation. + * + * @property [ignoreSearch] A Boolean that specifies whether the matching + * process should ignore the query string in the url. If set to true, + * the ?value=bar part of http://foo.com/?value=bar would be ignored when + * performing a match. It defaults to false. + * + * @property [ignoreMethod] A Boolean that, when set to true, prevents matching + * operations from validating the Request http method (normally only GET + * and HEAD are allowed.) It defaults to false. + * + * @property [ignoreVary] A Boolean that when set to true tells the matching + * operation not to perform VARY header matching — i.e. if the URL matches you + * will get a match regardless of the Response object having a VARY header or + * not. It defaults to false. + * + * @property [cacheName] A DOMString that represents a specific cache to search + * within. Note that this option is ignored by Cache.match(). + */ +interface CacheOptions { + ignoreSearch?: boolean; + ignoreMethod?: boolean; + ignoreVary?: boolean; + cacheName?: string; +} + +/** + * Represents the storage for Request / Response object pairs that are cached as + * part of the ServiceWorker life cycle. + */ +interface Cache { + /** + * Returns a Promise that resolves to the response associated with the first + * matching request in the Cache object. + * + * @param request The Request you are attempting to find in the Cache. + * @param {CacheOptions} options + */ + match(request: Request | string, options?: CacheOptions): Promise; + + /** + * Returns a Promise that resolves to an array of all matching responses in + * the Cache object. + * + * @param request The Request you are attempting to find in the Cache. + * @param {CacheOptions} options + */ + matchAll(request: Request | string, options?: CacheOptions): Promise>; + + /** + * Returns a Promise that resolves to a new Cache entry whose key + * is the request. + * + * @param request The Request you want to add to the cache. + */ + add(request: Request | string): Promise; + + /** + * Returns a Promise that resolves to a new array of Cache entries whose + * keys are the requests. + * + * @param request An array of Request objects you want to add to the cache. + */ + addAll(requests: Array): Promise; + + /** + * Adds additional key/value pairs to the current Cache object. + * + * @param request The Request you want to add to the cache. + * @param response The response you want to match up to the request. + */ + put(request: Request, response: Response): Promise; + + /** + * Finds the Cache entry whose key is the request, and if found, deletes the + * Cache entry and returns a Promise that resolves to true. If no Cache + * entry is found, it returns false. + * + * @param request The Request you are looking to delete. + * @param {CacheOptions} options + */ + delete(request: Request | string, options?: CacheOptions): Promise; + + /** + * Returns a Promise that resolves to an array of Cache keys. + * + * @param request The Request want to return, if a specific key is desired. + * @param {CacheOptions} options + */ + keys(request?: Request, options?: CacheOptions): Promise>; +} + +/** + * Represents the storage for Cache objects. It provides a master directory of + * all the named caches that a ServiceWorker can access and maintains a mapping + * of string names to corresponding Cache objects. + */ +interface CacheStorage { + /** + * Checks if a given Request is a key in any of the Cache objects that the + * CacheStorage object tracks and returns a Promise that resolves + * to that match. + * + * @param request The Request you are looking for a match for in the CacheStorage. + * @param {CacheOptions} options + */ + match(request: Request | string, options?: CacheOptions): Promise; + + /** + * Returns a Promise that resolves to true if a Cache object matching + * the cacheName exists. + * + * @param cacheName The Request you are looking for a match for in the + * CacheStorage. + */ + has(cacheName: string): Promise; + + /** + * Returns a Promise that resolves to the Cache object matching + * the cacheName. + * + * @param cacheName The name of the cache you want to open. + */ + open(cacheName: string): Promise; + + /** + * Finds the Cache object matching the cacheName, and if found, deletes the + * Cache object and returns a Promise that resolves to true. If no + * Cache object is found, it returns false. + * + * @param cacheName The name of the cache you want to delete. + */ + delete(cacheName: string): Promise; + + /** + * Returns a Promise that will resolve with an array containing strings + * corresponding to all of the named Cache objects tracked by the + * CacheStorage. Use this method to iterate over a list of all the + * Cache objects. + */ + keys(): Promise>; +} + +/** + * Represents the scope of a service worker client. A service worker client is + * either a document in a browser context or a SharedWorker, which is controlled + * by an active worker. + */ +interface ServiceWorkerClient { + /** + * Allows a service worker client to send a message to a ServiceWorker. + * + * @param message The message to send to the service worker. + * @param [transfer] A transferable object such as, for example, a reference + * to a port. + */ + postMessage(message: string, transfer?: Object): void; + + /** + * Indicates the type of browsing context of the current client. + * This value can be one of auxiliary, top-level, nested, or none. + * @readonly + */ + frameType: string; + + /** + * Returns the id of the Client object. + * @readonly + */ + id: string; + + /** + * The URL of the current service worker client. + * @readonly + */ + url: string; +} + +interface WindowClient extends ServiceWorkerClient { + /** + * Gives user input focus to the current client. + */ + focus(): Promise; + + /** + * A boolean that indicates whether the current client has focus. + * @readonly + */ + focused: boolean; + + /** + * Indicates the visibility of the current client. This value can be one of + * hidden, visible, prerender, or unloaded. + * @readonly + */ + visibilityState: string; +} + +interface ServiceWorkerClientsMatchOptions { + includeUncontrolled?: boolean; + type?: string; +} + +/** + * Represents a container for a list of Client objects; the main way to access + * the active service worker clients at the current origin. + */ +interface ServiceWorkerClients { + /** + * Gets a list of service worker clients and returns them in a Promise. + * Include the options parameter to return all service worker clients whose + * origin is the same as the associated service worker's origin. If options + * are not included, the method returns only the service worker clients + * controlled by the service worker. + * + * @param options + */ + matchAll(options: ServiceWorkerClientsMatchOptions): Promise>; + + /** + * Opens a service worker Client in a new browser window. + * + * @param url A string representing the URL of the client you want to open + * in the window. + */ + openWindow(url: string): Promise; + + /** + * Allows an active Service Worker to set itself as the active worker for a + * client page when the worker and the page are in the same scope. + */ + claim(): Promise; +} + +/** + * Extends the lifetime of the install and activate events dispatched on the + * ServiceWorkerGlobalScope as part of the service worker lifecycle. This + * ensures that any functional events (like FetchEvent) are not dispatched to + * the ServiceWorker until it upgrades database schemas, deletes outdated cache + * entries, etc. + */ +interface ExtendableEvent extends Event { + /** + * Extends the lifetime of the event. + * It is intended to be called in the install EventHandler for the + * installing worker and on the active EventHandler for the active worker. + * + * @param all + */ + waitUntil(all: any): any; +} + +/** + * The parameter passed into the ServiceWorkerGlobalScope.onfetch handler, + * FetchEvent represents a fetch action that is dispatched on the + * ServiceWorkerGlobalScope of a ServiceWorker. It contains information about + * the request and resulting response, and provides the FetchEvent.respondWith() + * method, which allows us to provide an arbitrary response back to the + * controlled page. + */ +interface FetchEvent extends Event { + /** + * Returns a Boolean that is true if the event was dispatched with the + * user's intention for the page to reload, and false otherwise. Typically, + * pressing the refresh button in a browser is a reload, while clicking a + * link and pressing the back button is not. + * @readonly + */ + isReload: boolean; + + /** + * Returns the Request that triggered the event handler. + * @readonly + */ + request: Request; + + /** + * Returns the Client that the current service worker is controlling. + * @readonly + */ + client: ServiceWorkerClient; + + /** + * Resolves by returning a Response or a network error to Fetch. + * + * @param all + */ + respondWith(all: any): Response; +} + +/** + * Represents a service worker. Multiple browsing contexts (e.g. pages, workers, + * etc.) can be associated with the same ServiceWorker object. + */ +interface ServiceWorker extends Worker { + /** + * Returns the ServiceWorker serialized script URL defined as part of + * ServiceWorkerRegistration. The URL must be on the same origin as the + * document that registers the ServiceWorker. + * @readonly + */ + scriptURL: string; + + /** + * Returns the state of the service worker. It returns one of the following + * values: installing, installed, activating, activated, or redundant. + * @readonly + */ + state: string; + + /** + * An EventListener property called whenever an event of type statechange + * is fired; it is basically fired anytime the ServiceWorker.state changes. + * + * @param [statechangeevent] + */ + onstatechange: (statechangeevent?: any) => void; +} + +/** + * The PushSubscription interface provides a subcription's URL endpoint and + * subscription ID. + */ +interface PushSubscription { + /** + * The endpoint associated with the push subscription. + * @readonly + */ + endpoint: any; + + /** + * The subscription ID associated with the push subscription. + * @readonly + */ + subscriptionId: any; +} + +/** + * Object containing optional subscribe parameters. + */ +interface PushSubscriptionOptions { + /** + * A boolean indicating that the returned push subscription will only be used for + * messages whose effect is made visible to the user. + * @readonly + */ + userVisibleOnly: boolean; + + /** + * A public key your push server will use to send messages to client apps via a push server. + * This value is part of a signing key pair generated by your application server and usable + * with elliptic curve digital signature (ECDSA) over the P-256 curve. + * @readonly + */ + applicationServerKey?: Uint8Array; +} + +/** + * The PushManager interface provides a way to receive notifications from + * third-party servers as well as request URLs for push notifications. + * This interface has replaced functionality offered by the obsolete + * PushRegistrationManager. + */ +interface PushManager { + /** + * Returns a promise that resolves to a PushSubscription with details of a + * new push subscription. + */ + subscribe(options?: PushSubscriptionOptions): Promise; + + /** + * Returns a promise that resolves to a PushSubscription details of + * the retrieved push subscription. + */ + getSubscription(): Promise; + + /** + * Returns a promise that resolves to the PushPermissionStatus of the + * requesting webapp, which will be one of granted, denied, or default. + */ + hasPermission(): Promise; +} + +/** + * Represents a service worker registration. + */ +interface ServiceWorkerRegistration extends EventTarget { + /** + * Returns a unique identifier for a service worker registration. + * This must be on the same origin as the document that registers + * the ServiceWorker. + * @readonly + */ + scope: any; + + /** + * Returns a service worker whose state is installing. This is initially + * set to null. + * @readonly + */ + installing: ServiceWorker; + + /** + * Returns a service worker whose state is installed. This is initially + * set to null. + * @readonly + */ + waiting: ServiceWorker; + + /** + * Returns a service worker whose state is either activating or activated. + * This is initially set to null. An active worker will control a + * ServiceWorkerClient if the client's URL falls within the scope of the + * registration (the scope option set when ServiceWorkerContainer.register + * is first called). + * @readonly + */ + active: ServiceWorker; + + /** + * Returns an interface to for managing push subscriptions, including + * subcribing, getting an anctive subscription, and accessing push + * permission status. + * @readonly + */ + pushManager: PushManager; + + /** + * An EventListener property called whenever an event of type updatefound + * is fired; it is fired any time the ServiceWorkerRegistration.installing + * property acquires a new service worker. + */ + onupdatefound: () => void; + + /** + * Allows you to update a service worker. + */ + update(): void; + + /** + * Unregisters the service worker registration and returns a promise + * (see Promise). The service worker will finish any ongoing operations + * before it is unregistered. + */ + unregister(): Promise; + + showNotification(title: string, options?: NotificationOptions): Promise; + + getNotifications(options: NotificationOptions): Promise<[Notification]>; +} + +interface ServiceWorkerRegisterOptions { + scope: string; +} + +/** + * Provides an object representing the service worker as an overall unit in the + * network ecosystem, including facilities to register, unregister and update + * service workers, and access the state of service workers + * and their registrations. + */ +interface ServiceWorkerContainer { + /** + * Returns a ServiceWorker object if its state is activated (the same object + * returned by ServiceWorkerRegistration.active). This property returns null + * if the request is a force refresh (Shift + refresh) or if there is no + * active worker. + * @readonly + */ + controller: ServiceWorker; + + /** + * Defines whether a service worker is ready to control a page or not. + * It returns a Promise that will never reject, which resolves to a + * ServiceWorkerRegistration with an ServiceWorkerRegistration.active worker. + * @readonly + */ + ready: Promise; + + /** + * An event handler fired whenever a controllerchange event occurs — when + * the document's associated ServiceWorkerRegistration acquires a new + * ServiceWorkerRegistration.active worker. + * + * @param [controllerchangeevent] + */ + oncontrollerchange: (controllerchangeevent?: Event) => void; + + /** + * An event handler fired whenever an error event occurs in the associated + * service workers. + * + * @param [errorevent] + */ + onerror: (errorevent?: ErrorEvent) => void; + + /** + * An event handler fired whenever a message event occurs — when incoming + * messages are received to the ServiceWorkerContainer object (e.g. via a + * MessagePort.postMessage() call.) + * + * @param [messageevent] + */ + onmessage: (messageevent?: MessageEvent) => void; + + /** + * Creates or updates a ServiceWorkerRegistration for the given scriptURL. + * + * @param scriptURL The URL of the service worker script. + * @param [options] An options object to provide options upon registration. + * Currently available options are: scope: A USVString representing a URL + * that defines a service worker's registration scope; what range of URLs a + * service worker can control. This is usually a relative URL, and it + * defaults to '/' when not specified. + */ + register(scriptURL: string, options?: ServiceWorkerRegisterOptions): Promise; + + /** + * Gets a ServiceWorkerRegistration object whose scope URL matches the + * provided document URL. If the method can't return a + * ServiceWorkerRegistration, it returns a Promise. + * + * @param [scope] A unique identifier for a service worker registration — the + * scope URL of the registration object you want to return. This is usually + * a relative URL. + */ + getRegistration(scope?: string): Promise; + + /** + * Returns all ServiceWorkerRegistrations associated with a + * ServiceWorkerContainer in an array. If the method can't return + * ServiceWorkerRegistrations, it returns a Promise. + */ + getRegistrations(): Promise>; +} + +/** + * The parameter passed into the oninstall handler, the InstallEvent interface + * represents an install action that is dispatched on the + * ServiceWorkerGlobalScope of a ServiceWorker. As a child of ExtendableEvent, + * it ensures that functional events such as FetchEvent are not dispatched + * during installation. + */ +interface InstallEvent extends ExtendableEvent { + /** + * Returns the ServiceWorker that is currently actively controlling the page. + * @readonly + */ + activeWorker: ServiceWorker; +} + +interface ServiceWorkerGlobalScope { + /** + * Contains the Clients object associated with the service worker. + * @readonly + */ + clients: ServiceWorkerClients; + + /** + * Contains the ServiceWorkerRegistration object that represents the + * service worker's registration. + * @readonly + */ + registration: ServiceWorkerRegistration; + + /** + * An event handler fired whenever an activate event occurs — when a + * ServiceWorkerRegistration acquires a new ServiceWorkerRegistration.active + * worker. + * + * @param [activateevent] + */ + onactivate: (activateevent?: ExtendableEvent) => void; + + /** + * Not defined in the spec yet, but it looks like this will be fired when + * the device is nearly out of storage space, prompting the UA to start + * claiming back some space from web apps that are using client-side storage, + * and the current app is targeted. + * + * @param [beforeevictedevent] + */ + onbeforeevicted: (beforeevictedevent?: Event) => void; + + /** + * Not defined in the spec yet, but it looks like this will be fired when + * the device is out of storage space, and the UA claims back some space + * from the current app. + * + * @param [evictedevent] + */ + onevicted: (evictedevent?: Event) => void; + + /** + * An event handler fired whenever a fetch event occurs — when a fetch() + * is called. + * + * @param [fetchevent] + */ + onfetch: (fetchevent?: FetchEvent) => void; + + /** + * An event handler fired whenever an install event occurs — when a + * ServiceWorkerRegistration acquires a new + * ServiceWorkerRegistration.installing worker. + * + * @param [installevent] + */ + oninstall: (installevent?: InstallEvent) => void; + + /** + * An event handler fired whenever a message event occurs — when incoming + * messages are received. Controlled pages can use the + * MessagePort.postMessage() method to send messages to service workers. + * The service worker can optionally send a response back via the + * MessagePort exposed in event.data.port, corresponding to the controlled + * page. + * + * @param [messageevent] + */ + onmessage: (messageevent?: MessageEvent) => void; + + /** + * An event handler fired whenever a notificationclick event occurs — when + * a user clicks on a displayed notification. + * + * @param [notificationclickevent] + */ + onnotificationclick: (notificationclickevent?: NotificationEvent) => void; + + /** + * An event handler fired whenever a push event occurs — when a server + * push notification is received. + * + * @param [onpushevent] + */ + onpush: (onpushevent?: Event) => void; + + /** + * An event handler fired whenever a pushsubscriptionchange event occurs — + * when a push subscription has been invalidated, or is about to be + * invalidated (e.g. when a push service sets an expiration time). + * + * @param [pushsubscriptionchangeevent] + */ + onpushsubscriptionchange: (pushsubscriptionchangeevent?: Event) => void; + + /** + * Allows the current service worker registration to progress from waiting + * to active state while service worker clients are using it. + */ + skipWaiting(): Promise; + + /** + * TODO GlobalFetch + * @param url + * @param init + */ + fetch(url: string | Request, init?: Object): Promise; +} + +interface Navigator { + /** + * Returns a ServiceWorkerContainer object, which provides access to + * registration, removal, upgrade, and communication with the ServiceWorker + * objects for the associated document. + */ + serviceWorker: ServiceWorkerContainer; +} + +interface Window extends ServiceWorkerGlobalScope { + caches: CacheStorage; +} + +interface NotificationEvent extends Event, ExtendableEvent { + notification: Notification; +} diff --git a/src/sw.ts b/src/sw.ts new file mode 100644 index 0000000..e69de29 diff --git a/tsconfig.json b/tsconfig.json index 6c925ab..bb40f1f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,17 @@ { - "include": [ - "src/*" - ], "compileOnSave": false, "compilerOptions": { "module": "es6", "target": "es6", "noImplicitAny": true, "sourceMap": false - } + }, + "include": [ + "src/*" + ], + "exclude": [ + "node_modules", + "typings/browser", + "typings/browser.d.ts" + ] } \ No newline at end of file From a4fd4c8739b09f217edc6451f8fc9faf56bf369b Mon Sep 17 00:00:00 2001 From: ipanardian Date: Tue, 3 Jan 2017 16:06:06 +0700 Subject: [PATCH 02/17] Auto generate tag if undefined --- src/BrowserNotif.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/BrowserNotif.ts b/src/BrowserNotif.ts index e2a060f..2aefbb5 100644 --- a/src/BrowserNotif.ts +++ b/src/BrowserNotif.ts @@ -87,6 +87,12 @@ export default class BrowserNotif implements BrowserNotifInterface */ protected timeout: number = 0 + /** + * Service Worker Path. Default : sw.js + * @type {string} + */ + protected serviceWorkerPath: string = 'sw.js' + /** * Permission Type * @type {PermissionInterface} @@ -135,7 +141,7 @@ export default class BrowserNotif implements BrowserNotifInterface */ protected _setOptions(options: BrowserNotifOptions): void { for (let option in options) { - if (['timeout'].indexOf(option) == -1) { + if (['timeout', 'serviceWorkerPath'].indexOf(option) == -1) { this.notifOptions[option] = options[option] } } @@ -146,8 +152,6 @@ export default class BrowserNotif implements BrowserNotifInterface * @param {string} callback */ public static requestPermission(callback: (permission: NotificationPermission) => void): void { - BrowserNotif._registerServiceWorker() - Notification.requestPermission((permission: NotificationPermission) => { if (typeof callback === 'function') { callback.call(this, permission) @@ -159,9 +163,9 @@ export default class BrowserNotif implements BrowserNotifInterface * Register serviceWorker * This is an experimental technology! */ - protected static _registerServiceWorker(): void { + protected _registerServiceWorker(): void { if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('sw.js').then(serviceWorkerRegistration => { + navigator.serviceWorker.register(this.serviceWorkerPath).then(serviceWorkerRegistration => { console.log('Service Worker is ready :', serviceWorkerRegistration) }) .catch(e => console.warn('BrowserNotif: ', e)) @@ -175,6 +179,9 @@ export default class BrowserNotif implements BrowserNotifInterface protected _showNotifServiceWorker(callback?: () => void): void { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then(registration => { + if (!this.notifOptions.tag) { + this.notifOptions.tag = 'browserNotif_'+ Math.random().toString().substr(3, 10) + } registration.showNotification(this.title, this.notifOptions).then(() => { callback.call(this) }) @@ -186,7 +193,7 @@ export default class BrowserNotif implements BrowserNotifInterface protected _getNotifServiceWorker(callback: (notification: Notification) => void): void { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then(registration => { - registration.getNotifications({tag: this.options.tag}).then(notifications => { + registration.getNotifications({tag: this.notifOptions.tag}).then(notifications => { if (notifications.length > 0) { callback.call(this, notifications[0]) } @@ -234,6 +241,7 @@ export default class BrowserNotif implements BrowserNotifInterface */ protected _notify(callback?: (notif: Notification) => void): void { if ('serviceWorker' in navigator) { + this._registerServiceWorker() this._showNotifServiceWorker(() => { this._getNotifServiceWorker(notification => { this.notification = notification From b416a9a86f61121748ca4ca2f1f2036854907fdd Mon Sep 17 00:00:00 2001 From: ipanardian Date: Wed, 4 Jan 2017 14:52:33 +0700 Subject: [PATCH 03/17] Event onClick on ServiceWorker --- src/BrowserNotif.ts | 70 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/src/BrowserNotif.ts b/src/BrowserNotif.ts index 2aefbb5..2940e02 100644 --- a/src/BrowserNotif.ts +++ b/src/BrowserNotif.ts @@ -55,6 +55,14 @@ interface PermissionInterface { Denied: string } +/** + * Interface for Data + */ +interface Data { + [key: string]: any + clickOnServiceWorker?: string +} + export default class BrowserNotif implements BrowserNotifInterface { /** @@ -80,6 +88,12 @@ export default class BrowserNotif implements BrowserNotifInterface * @type {NotificationOptions} */ protected notifOptions: NotificationOptions = {} + + /** + * Arbitrary data + * @type {Data} + */ + protected data: Data = {} /** * How long notification will appear in second. Set to 0 to make always visible @@ -88,10 +102,10 @@ export default class BrowserNotif implements BrowserNotifInterface protected timeout: number = 0 /** - * Service Worker Path. Default : sw.js + * Service Worker Path. Default : sw.min.js * @type {string} */ - protected serviceWorkerPath: string = 'sw.js' + protected serviceWorkerPath: string = 'sw.min.js' /** * Permission Type @@ -101,7 +115,13 @@ export default class BrowserNotif implements BrowserNotifInterface Default: 'default', Granted: 'granted', Denied: 'denied' - } + } + + /** + * Readonly Win property + * @type {Window} + */ + protected static readonly Win: Window = window /** * BrowserNotif constructor @@ -129,7 +149,7 @@ export default class BrowserNotif implements BrowserNotifInterface * @return {boolean} */ public static isSupported(): boolean { - if (!("Notification" in window)) { + if (!("Notification" in BrowserNotif.Win)) { return false } return true @@ -175,6 +195,7 @@ export default class BrowserNotif implements BrowserNotifInterface /** * Show notification from serviceWorker * This is an experimental technology! + * @param {()} callback */ protected _showNotifServiceWorker(callback?: () => void): void { if ('serviceWorker' in navigator) { @@ -182,6 +203,9 @@ export default class BrowserNotif implements BrowserNotifInterface if (!this.notifOptions.tag) { this.notifOptions.tag = 'browserNotif_'+ Math.random().toString().substr(3, 10) } + if (typeof this.data != 'undefined') { + this.notifOptions.data = JSON.stringify(this.data) + } registration.showNotification(this.title, this.notifOptions).then(() => { callback.call(this) }) @@ -190,6 +214,10 @@ export default class BrowserNotif implements BrowserNotifInterface } } + /** + * Get notification object from serviceWorker + * @param {Notification} callback + */ protected _getNotifServiceWorker(callback: (notification: Notification) => void): void { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then(registration => { @@ -215,9 +243,10 @@ export default class BrowserNotif implements BrowserNotifInterface alert(`${title}\n\n${body}`) return this } + this._validateTitle(title) - this.title = title; - this.notifOptions.body = body; + this.title = title; + this.notifOptions.body = body; if (this.Permission.Granted === Notification.permission) { this._notify(callback); } @@ -235,12 +264,25 @@ export default class BrowserNotif implements BrowserNotifInterface return this } + /** + * Validate title of Notification + * @param {string} title + */ + protected _validateTitle(title: string): void { + if (typeof title != 'string') { + throw new Error('BrowserNotif: Title of notification must be a string'); + } + else if (title.trim() == '') { + throw new Error('BrowserNotif: Title of notification could not be empty'); + } + } + /** * Create an instance of Notification API * @param {Notification} callback */ protected _notify(callback?: (notif: Notification) => void): void { - if ('serviceWorker' in navigator) { + if (!('Notification' in BrowserNotif.Win)) { this._registerServiceWorker() this._showNotifServiceWorker(() => { this._getNotifServiceWorker(notification => { @@ -248,6 +290,7 @@ export default class BrowserNotif implements BrowserNotifInterface if (typeof callback === 'function') { callback.call(this, this.notification) } + }) }) } @@ -281,6 +324,19 @@ export default class BrowserNotif implements BrowserNotifInterface callback.call(this); } } + this.notification.close() + return this + } + + /** + * Click event on serviceWorker Notification + * @param {} callback + * @return {BrowserNotif} + */ + public clickOnServiceWorker(callback: () => void): BrowserNotif { + if (typeof callback === 'function') { + this.data.clickOnServiceWorker = callback.toString() + } return this } From 0ad1c318adb6540cc598a76ed69f4d978e7a1266 Mon Sep 17 00:00:00 2001 From: ipanardian Date: Wed, 4 Jan 2017 15:24:56 +0700 Subject: [PATCH 04/17] ServiceWorkerApi script --- src/sw.ts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/sw.ts b/src/sw.ts index e69de29..9908bc3 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -0,0 +1,35 @@ +/** +* BrowserNotif.JS +* (c) 2016 Ipan Ardian +* +* Lets a web page send notifications that are displayed outside the page at the system level. +* This lets web apps send information to a user even if the application is idle, in the background, switched tabs or moved to a different app. +* For details, see the web site: https://github.com/ipanardian/browser-notif +* The MIT License +* +* This is ServiceWorkerApi script +*/ + +console.log('Started'); +self.addEventListener('install', function(event) { + self.skipWaiting(); + console.log('Installed'); +}); +self.addEventListener('activate', function(event) { + console.log('Activated'); +}); +self.onnotificationclick = function(event) { + if (typeof event.notification.data != 'null') { + try { + var data = JSON.parse(event.notification.data) + } catch (error) { + console.error('BrowserNotif: Error parse '+ error) + } + try { + Function("(" +data['clickOnServiceWorker']+ ")()")() + } catch (error) { + console.error('BrowserNotif: Error clickOnServiceWorker '+ error) + } + } + event.notification.close() +}; \ No newline at end of file From 85b6aa06ea284d9d4de92dc8469f1375982cb595 Mon Sep 17 00:00:00 2001 From: ipanardian Date: Wed, 4 Jan 2017 16:11:03 +0700 Subject: [PATCH 05/17] Close notification after clicked --- src/BrowserNotif.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BrowserNotif.ts b/src/BrowserNotif.ts index 2940e02..02de87c 100644 --- a/src/BrowserNotif.ts +++ b/src/BrowserNotif.ts @@ -321,10 +321,10 @@ export default class BrowserNotif implements BrowserNotifInterface public click(callback: () => void): BrowserNotif { if (typeof callback === 'function' && this.notification instanceof Notification) { this.notification.onclick = () => { + this.notification.close() callback.call(this); } } - this.notification.close() return this } From f53aabf7fcdb03067c2e832bd166f49c77075d59 Mon Sep 17 00:00:00 2001 From: ipanardian Date: Wed, 4 Jan 2017 16:15:27 +0700 Subject: [PATCH 06/17] Added serviceWorkerPath into interface --- src/BrowserNotif.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/BrowserNotif.ts b/src/BrowserNotif.ts index 02de87c..1db66da 100644 --- a/src/BrowserNotif.ts +++ b/src/BrowserNotif.ts @@ -30,10 +30,12 @@ * data?: any; * actions?: NotificationAction[] * timeout?: number + * serviceWorkerPath?: string */ interface BrowserNotifOptions extends NotificationOptions { [key: string]: any timeout?: number + serviceWorkerPath?: string } /** @@ -66,7 +68,7 @@ interface Data { export default class BrowserNotif implements BrowserNotifInterface { /** - * Title notification + * Title of Notification * @type {string} */ protected title: string @@ -125,7 +127,7 @@ export default class BrowserNotif implements BrowserNotifInterface /** * BrowserNotif constructor - * @param {BrowserNotifOptions} options Optional config in object literal form + * @param {BrowserNotifOptions} options Optional options in object literal form * e.g {icon: 'image.png', timeout: 10} */ constructor (options?: BrowserNotifOptions) { From d78f71dc8cc75bf212e96a9d68ed9ecda00751d9 Mon Sep 17 00:00:00 2001 From: ipanardian Date: Wed, 4 Jan 2017 21:54:40 +0700 Subject: [PATCH 07/17] Added mobile detection --- src/BrowserNotif.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/BrowserNotif.ts b/src/BrowserNotif.ts index 1db66da..d6d8f1d 100644 --- a/src/BrowserNotif.ts +++ b/src/BrowserNotif.ts @@ -46,6 +46,7 @@ interface BrowserNotifInterface { click(callback: () => void): BrowserNotif close(): void error(callback: () => void): BrowserNotif + isMobile(): boolean } /** @@ -205,7 +206,7 @@ export default class BrowserNotif implements BrowserNotifInterface if (!this.notifOptions.tag) { this.notifOptions.tag = 'browserNotif_'+ Math.random().toString().substr(3, 10) } - if (typeof this.data != 'undefined') { + if (Object.keys(this.data).length > 0) { this.notifOptions.data = JSON.stringify(this.data) } registration.showNotification(this.title, this.notifOptions).then(() => { @@ -284,7 +285,7 @@ export default class BrowserNotif implements BrowserNotifInterface * @param {Notification} callback */ protected _notify(callback?: (notif: Notification) => void): void { - if (!('Notification' in BrowserNotif.Win)) { + if (this.isMobile()) { this._registerServiceWorker() this._showNotifServiceWorker(() => { this._getNotifServiceWorker(notification => { @@ -292,7 +293,6 @@ export default class BrowserNotif implements BrowserNotifInterface if (typeof callback === 'function') { callback.call(this, this.notification) } - }) }) } @@ -364,4 +364,18 @@ export default class BrowserNotif implements BrowserNotifInterface } return this } + + /** + * Detect mobile device + * @return {boolean} + */ + public isMobile(): boolean { + let mobileExp: RegExp = new RegExp(` + Android|webOS|iPhone|iPad| + BlackBerry|Windows Phone| + Opera Mini|IEMobile|Mobile`, + 'i'); + + return mobileExp.test(navigator.userAgent) + } } \ No newline at end of file From 97b1609fada17dd29bc87020f849454b5a793681 Mon Sep 17 00:00:00 2001 From: ipanardian Date: Wed, 4 Jan 2017 22:59:07 +0700 Subject: [PATCH 08/17] Fixed error handling on sw.js --- src/sw.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/sw.ts b/src/sw.ts index 9908bc3..a6496a2 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -23,12 +23,14 @@ self.onnotificationclick = function(event) { try { var data = JSON.parse(event.notification.data) } catch (error) { - console.error('BrowserNotif: Error parse '+ error) + throw new Error('BrowserNotif: Error parse '+ error) } try { - Function("(" +data['clickOnServiceWorker']+ ")()")() + if (data !== null && !data.clickOnServiceWorker) { + Function("(" +data['clickOnServiceWorker']+ ")()")() + } } catch (error) { - console.error('BrowserNotif: Error clickOnServiceWorker '+ error) + throw new Error('BrowserNotif: Error clickOnServiceWorker '+ error) } } event.notification.close() From 86715e88b5eecc8f8d96a4ed34175e41e4961035 Mon Sep 17 00:00:00 2001 From: ipanardian Date: Wed, 4 Jan 2017 22:59:40 +0700 Subject: [PATCH 09/17] Enhance serviceWorkerPath --- src/BrowserNotif.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/BrowserNotif.ts b/src/BrowserNotif.ts index d6d8f1d..e77b022 100644 --- a/src/BrowserNotif.ts +++ b/src/BrowserNotif.ts @@ -29,8 +29,8 @@ * requireInteraction?: boolean; * data?: any; * actions?: NotificationAction[] - * timeout?: number - * serviceWorkerPath?: string + * timeout?: number; + * serviceWorkerPath?: string; Default : sw.js */ interface BrowserNotifOptions extends NotificationOptions { [key: string]: any @@ -104,12 +104,6 @@ export default class BrowserNotif implements BrowserNotifInterface */ protected timeout: number = 0 - /** - * Service Worker Path. Default : sw.min.js - * @type {string} - */ - protected serviceWorkerPath: string = 'sw.min.js' - /** * Permission Type * @type {PermissionInterface} @@ -188,7 +182,7 @@ export default class BrowserNotif implements BrowserNotifInterface */ protected _registerServiceWorker(): void { if ('serviceWorker' in navigator) { - navigator.serviceWorker.register(this.serviceWorkerPath).then(serviceWorkerRegistration => { + navigator.serviceWorker.register(this.options.serviceWorkerPath || 'sw.js').then(serviceWorkerRegistration => { console.log('Service Worker is ready :', serviceWorkerRegistration) }) .catch(e => console.warn('BrowserNotif: ', e)) @@ -285,7 +279,7 @@ export default class BrowserNotif implements BrowserNotifInterface * @param {Notification} callback */ protected _notify(callback?: (notif: Notification) => void): void { - if (this.isMobile()) { + if (!this.isMobile()) { this._registerServiceWorker() this._showNotifServiceWorker(() => { this._getNotifServiceWorker(notification => { From 1aa0ca828732a34d83baaec0f6ece08c4f8fd431 Mon Sep 17 00:00:00 2001 From: ipanardian Date: Wed, 4 Jan 2017 23:04:04 +0700 Subject: [PATCH 10/17] Fixed checking --- src/sw.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sw.ts b/src/sw.ts index a6496a2..9e32bf9 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -26,7 +26,7 @@ self.onnotificationclick = function(event) { throw new Error('BrowserNotif: Error parse '+ error) } try { - if (data !== null && !data.clickOnServiceWorker) { + if (data !== null && data.clickOnServiceWorker !== undefined) { Function("(" +data['clickOnServiceWorker']+ ")()")() } } catch (error) { From 0b01aaba56516382e7cd01f40c4dc2b483df756b Mon Sep 17 00:00:00 2001 From: ipanardian Date: Wed, 4 Jan 2017 23:05:32 +0700 Subject: [PATCH 11/17] Remove debug sw --- src/BrowserNotif.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BrowserNotif.ts b/src/BrowserNotif.ts index e77b022..514309d 100644 --- a/src/BrowserNotif.ts +++ b/src/BrowserNotif.ts @@ -279,7 +279,7 @@ export default class BrowserNotif implements BrowserNotifInterface * @param {Notification} callback */ protected _notify(callback?: (notif: Notification) => void): void { - if (!this.isMobile()) { + if (this.isMobile()) { this._registerServiceWorker() this._showNotifServiceWorker(() => { this._getNotifServiceWorker(notification => { From 938f58fb61537fe3425f139ad855df37c8efd5a9 Mon Sep 17 00:00:00 2001 From: Ipan Ardian Date: Wed, 4 Jan 2017 23:33:50 +0700 Subject: [PATCH 12/17] Update README.md --- README.md | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 189bc4d..2c4ae79 100755 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ BrowserNotif.requestPermission(p => console.log(p)) let notif1 = new BrowserNotif() notif1 - .notify('First Notif', 'Hai there! Nice to meet you.', (n) => { + .notify('First Notif', 'Hi there! Nice to meet you.', (n) => { console.log('First Notif fired!', n) }) .click(() => { @@ -60,7 +60,7 @@ BrowserNotif.default.requestPermission(function(p) { var notif = new BrowserNotif.default({icon: 'icon.png'}) notif - .notify('First Notif', 'Hai there! Nice to meet you.', function(n) { + .notify('First Notif', 'Hi there! Nice to meet you.', function(n) { console.log('First Notif fired!', n) }) .click(function() { @@ -68,6 +68,22 @@ notif }) ``` +## Notification On Mobile Devices +Notification on mobile devices is using ```Service Worker```. A service worker is an event-driven worker registered against an origin and a path. Service worker runs in the background and only run over HTTPS. + +> Put file 'sw.js' on root directory + +```js +var notif = new BrowserNotif.default({icon: 'icon.png'}) +notif + .clickOnServiceWorker(function(){ + clients.openWindow('//ipanardian.com') + }) + .notify('Notif from Ipan Ardian', 'Hi there! Nice to meet you.', function(n) { + console.log(n) + }) +``` + ## Install ``` npm i browser-notif @@ -89,9 +105,12 @@ Check 'dist' folder. - BrowserNotif.js - BrowserNotif.min.js - BrowserNotif.min.js.map +- sw.js ## Browser compatibility If browser not support Notification API then native alert will be triggered. + +### Desktop
@@ -211,6 +230,138 @@ If browser not support Notification API then native alert will be triggered.
+### Mobile +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureAndroidAndroid WebviewEdgeFirefox Mobile (Gecko)Firefox OSIE MobileOpera MobileSafari MobileChrome for Android
Basic support? +

(Yes)

+
(Yes)4.0moz[2]
+ 22
1.0.1moz[2]
+ 1.2
No support?No support +

(Yes)

+
icon?(Yes)?4.0moz[2]
+ 22
1.0.1moz[2]
+ 1.2
No support?No support(Yes)
Available in workers?(Yes)?41.0 (41.0)????(Yes)
silentNo support43.0?No supportNo supportNo supportNo supportNo support43.0
noscreen, stickyNo supportNo support?No supportNo supportNo supportNo supportNo supportNo support
soundNo support(Yes)?No supportNo supportNo supportNo supportNo support(Yes)
renotifyNo support50.0?No supportNo supportNo supportNo supportNo supportNo support
Promise-based Notification.requestPermission()???47.0 (47.0)?????
vibrate, actionsNo support53.0?   39 53.0
badgeNo support53.0?   39 53.0
imageNo supportNo support?   ? 55.0
+ ## License The MIT License (MIT) From 97489506dba2268c4d8f1090e9a08ff77be1a55f Mon Sep 17 00:00:00 2001 From: Ipan Ardian Date: Thu, 5 Jan 2017 21:04:21 +0700 Subject: [PATCH 13/17] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2c4ae79..024d3c4 100755 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ notif console.log(n) }) ``` +![gif](http://i.giphy.com/l3vRfm7aebpZjQHf2.gif) ## Install ``` From 9c1f888c96ea58e5847b1c9d0b75aa84b192a89f Mon Sep 17 00:00:00 2001 From: Ipan Ardian Date: Thu, 5 Jan 2017 21:58:16 +0700 Subject: [PATCH 14/17] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 024d3c4..54f230c 100755 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This lets web apps send information to a user even if the application is idle, i ## Usage ### Typescript ```js -// Import module +// Import import BrowserNotif from './BrowserNotif' // If you want to explicitly call request permission. Usually this is only called once. @@ -51,7 +51,7 @@ notif1.close() ``` ### Javascript -In Javascript module is transpiled by Babel into UMD module pattern. Also used Polyfill for ```Object.assign```. +In Javascript BrowserNotif use UMD module pattern and Polyfill for ```Object.assign```. ```js BrowserNotif.default.requestPermission(function(p) { console.log(p) From e6238bf70aeadf5b892df0e3c3d285c4d7ccd035 Mon Sep 17 00:00:00 2001 From: Ipan Ardian Date: Sat, 7 Jan 2017 19:52:48 +0700 Subject: [PATCH 15/17] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 54f230c..6fc4618 100755 --- a/README.md +++ b/README.md @@ -6,7 +6,12 @@ [![GitHub license](https://img.shields.io/badge/license-MIT-red.svg)](https://raw.githubusercontent.com/ipanardian/browser-notif/master/LICENSE) Lets a web page send notifications that are displayed outside the page at the system level. -This lets web apps send information to a user even if the application is idle, in the background, switched tabs or moved to a different app. +This lets web apps send information to a user even if the application is idle, in the background, switched tabs or moved to a different app. + +## Install +``` +npm install browser-notif --save +``` ## Demo [http://ipanardian.github.io/browser-notif](http://ipanardian.github.io/browser-notif) @@ -85,11 +90,6 @@ notif ``` ![gif](http://i.giphy.com/l3vRfm7aebpZjQHf2.gif) -## Install -``` -npm i browser-notif -``` - ## Build ``` // Install Dev Dependencies From b6d2e6e4375d361ac7c85b6c2b22722b42e0124d Mon Sep 17 00:00:00 2001 From: ipanardian Date: Tue, 10 Jan 2017 15:32:49 +0700 Subject: [PATCH 16/17] Close formerly notif if has an instance --- src/BrowserNotif.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/BrowserNotif.ts b/src/BrowserNotif.ts index 514309d..ba5f2a8 100644 --- a/src/BrowserNotif.ts +++ b/src/BrowserNotif.ts @@ -291,6 +291,9 @@ export default class BrowserNotif implements BrowserNotifInterface }) } else { + if (this.notification instanceof Notification) { + this.notification.close() + } this.notification = new Notification(this.title, this.notifOptions) this._closeNotification() if (typeof callback === 'function') { From 3229af9cb5114585b0fea44389083d1a14e59779 Mon Sep 17 00:00:00 2001 From: ipanardian Date: Tue, 10 Jan 2017 15:53:23 +0700 Subject: [PATCH 17/17] Update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5481d26..26269fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "browser-notif", - "version": "2.0.0", + "version": "2.1.0", "description": "Lets a web page send notifications that are displayed outside the page at the system level", "main": "browser-notif.js", "scripts": {