├── .babelrc ├── .editorconfig ├── .eslintrc ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── lock.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── MIT-LICENSE ├── README.md ├── _config.yml ├── bower.json ├── docs ├── API.md ├── EXAMPLES.md ├── INSTALLATION.md ├── ISSUES.md ├── PAYLOAD.md ├── PHONEGAP_BUILD.md ├── PLATFORM_SUPPORT.md └── TYPESCRIPT.md ├── example └── server │ ├── pushADM.js │ ├── pushAPNS.rb │ ├── pushAzure.js │ └── pushGCM.rb ├── hooks ├── browser │ └── updateManifest.js └── windows │ └── setToastCapable.js ├── package.json ├── plugin.xml ├── spec ├── .eslintrc.yml ├── helper │ └── cordova.js ├── index.spec.js └── unit.json ├── src ├── android │ └── com │ │ └── adobe │ │ └── phonegap │ │ └── push │ │ ├── BackgroundActionButtonHandler.java │ │ ├── FCMService.java │ │ ├── PushConstants.java │ │ ├── PushDismissedHandler.java │ │ ├── PushHandlerActivity.java │ │ ├── PushInstanceIDListenerService.java │ │ └── PushPlugin.java ├── browser │ ├── ServiceWorker.js │ └── manifest.json ├── ios │ ├── AppDelegate+notification.h │ ├── AppDelegate+notification.m │ ├── PushPlugin.h │ └── PushPlugin.m ├── js │ └── push.js └── windows │ └── PushPluginProxy.js ├── types └── index.d.ts └── www ├── browser └── push.js └── push.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | "plugins": [ 6 | ["add-header-comment", { 7 | "files": { 8 | "src/js/push.js": { 9 | "header": [ 10 | "This file has been generated by Babel.\n", 11 | "DO NOT EDIT IT DIRECTLY\n", 12 | "Edit the JS source file src/js/push.js" 13 | ] 14 | } 15 | } 16 | }] 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style of different editors and IDEs. 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | indent_size = 2 10 | indent_style = space 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.json] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "parser": "babel-eslint", 4 | "rules": { 5 | "spaced-comment": 0, 6 | "no-console": 0, 7 | "no-unused-expressions": [2, { "allowShortCircuit": true }], 8 | "class-methods-use-this": 0 9 | }, 10 | "env": { 11 | "browser": true 12 | }, 13 | "globals": { 14 | "cordova": false 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We love pull requests from everyone. 4 | 5 | [Fork](https://help.github.com/articles/fork-a-repo/), then [clone](https://help.github.com/articles/cloning-a-repository/) the repo: 6 | 7 | ``` 8 | git clone git@github.com:your-username/phonegap-plugin-push.git 9 | ``` 10 | 11 | Set up a branch for your feature or bugfix with a link to the original repo: 12 | 13 | ``` 14 | git checkout -b my-awesome-new-feature 15 | git push --set-upstream origin my-awesome-new-feature 16 | git remote add upstream https://github.com/phonegap/phonegap-plugin-push.git 17 | ``` 18 | 19 | Set up the project: 20 | 21 | ``` 22 | npm install 23 | ``` 24 | 25 | Make sure the tests pass before changing anything: 26 | 27 | ``` 28 | npm test 29 | ``` 30 | 31 | Make your change. Add tests for your change. Make the tests pass: 32 | 33 | ``` 34 | npm test 35 | ``` 36 | 37 | Commit changes: 38 | 39 | ``` 40 | git commit -m "Cool stuff" 41 | ``` 42 | 43 | Consider starting the commit message with an applicable emoji: 44 | * :art: `:art:` when improving the format/structure of the code 45 | * :zap: `:zap:` when improving performance 46 | * :non-potable_water: `:non-potable_water:` when plugging memory leaks 47 | * :memo: `:memo:` when writing docs 48 | * :ambulance: `:ambulance:` a critical hotfix. 49 | * :sparkles: `:sparkles:` when introducing new features 50 | * :bookmark: `:bookmark:` when releasing / version tags 51 | * :rocket: `:rocket:` when deploying stuff 52 | * :penguin: `:penguin:` when fixing something on Android 53 | * :apple: `:apple:` when fixing something on iOS 54 | * :checkered_flag: `:checkered_flag:` when fixing something on Windows 55 | * :bug: `:bug:` when fixing a bug 56 | * :fire: `:fire:` when removing code or files 57 | * :green_heart: `:green_heart:` when fixing the CI build 58 | * :white_check_mark: `:white_check_mark:` when adding tests 59 | * :lock: `:lock:` when dealing with security 60 | * :arrow_up: `:arrow_up:` when upgrading dependencies 61 | * :arrow_down: `:arrow_down:` when downgrading dependencies 62 | * :shirt: `:shirt:` when removing linter warnings 63 | * :hammer: `:hammer:` when doing heavy refactoring 64 | * :heavy_minus_sign: `:heavy_minus_sign:` when removing a dependency. 65 | * :heavy_plus_sign: `:heavy_plus_sign:` when adding a dependency. 66 | * :wrench: `:wrench:` when changing configuration files. 67 | * :globe_with_meridians: `:globe_with_meridians:` when dealing with internationalization and localization. 68 | * :pencil2: `:pencil2:` when fixing typos. 69 | * :hankey: `:hankey:` when writing bad code that needs to be improved. 70 | * :package: `:package:` when updating compiled files or packages. 71 | 72 | Make sure your branch is up to date with the original repo: 73 | 74 | ``` 75 | git fetch upstream 76 | git merge upstream/master 77 | ``` 78 | 79 | Review your changes and any possible conflicts and push to your fork: 80 | 81 | ``` 82 | git push origin 83 | ``` 84 | 85 | [Submit a pull request](https://help.github.com/articles/creating-a-pull-request/). 86 | 87 | At this point you're waiting on us. We do our best to keep on top of all the pull requests. We may suggest some changes, improvements or alternatives. 88 | 89 | Some things that will increase the chance that your pull request is accepted: 90 | 91 | - Write tests. 92 | - Write a [good commit message](http://chris.beams.io/posts/git-commit/). 93 | - Make sure the PR merges cleanly with the latest master. 94 | - Describe your feature/bugfix and why it's needed/important in the pull request description. 95 | 96 | 97 | ## Editor Config 98 | 99 | The project uses [.editorconfig](http://editorconfig.org/) to define the coding 100 | style of each file. We recommend that you install the Editor Config extension 101 | for your preferred IDE. Consistency is key. 102 | 103 | ## ESLint 104 | 105 | The project uses [.eslint](http://eslint.org/) to define the JavaScript 106 | coding conventions. Most editors now have a ESLint add-on to provide on-save 107 | or on-edit linting. 108 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Expected Behaviour 2 | 3 | ### Actual Behaviour 4 | 5 | ### Reproduce Scenario (including but not limited to) 6 | 7 | #### Steps to Reproduce 8 | 9 | #### Platform and Version (eg. Android 5.0 or iOS 9.2.1) 10 | 11 | #### (Android) What device vendor (e.g. Samsung, HTC, Sony...) 12 | 13 | #### Cordova CLI version and cordova platform version 14 | 15 | cordova --version # e.g. 6.0.0 16 | cordova platform version android # e.g. 4.1.1 17 | 18 | #### Plugin version 19 | 20 | cordova plugin version | grep phonegap-plugin-push # e.g. 1.5.3 21 | 22 | #### Sample Push Data Payload 23 | 24 | #### Sample Code that illustrates the problem 25 | 26 | #### Logs taken while reproducing problem 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Related Issue 7 | 8 | 9 | 10 | 11 | 12 | ## Motivation and Context 13 | 14 | 15 | ## How Has This Been Tested? 16 | 17 | 18 | 19 | 20 | ## Screenshots (if appropriate): 21 | 22 | ## Types of changes 23 | 24 | - [ ] Bug fix (non-breaking change which fixes an issue) 25 | - [ ] New feature (non-breaking change which adds functionality) 26 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 27 | 28 | ## Checklist: 29 | 30 | 31 | - [ ] My code follows the code style of this project. 32 | - [ ] My change requires a change to the documentation. 33 | - [ ] I have updated the documentation accordingly. 34 | - [ ] I have read the **CONTRIBUTING** document. 35 | - [ ] I have added tests to cover my changes. 36 | - [ ] All new and existing tests passed. 37 | -------------------------------------------------------------------------------- /.github/lock.yml: -------------------------------------------------------------------------------- 1 | # Configuration for lock-threads - https://github.com/dessant/lock-threads 2 | 3 | # Number of days of inactivity before a closed issue or pull request is locked 4 | daysUntilLock: 30 5 | 6 | # Issues and pull requests with these labels will not be locked. Set to `[]` to disable 7 | exemptLabels: [] 8 | 9 | # Label to add before locking, such as `outdated`. Set to `false` to disable 10 | lockLabel: false 11 | 12 | # Comment to post before locking. Set to `false` to disable 13 | lockComment: This thread has been automatically locked. 14 | 15 | # Limit to only `issues` or `pulls` 16 | # only: issues 17 | 18 | # Optionally, specify configuration settings just for `issues` or `pulls` 19 | # issues: 20 | # exemptLabels: 21 | # - help-wanted 22 | # lockLabel: outdated 23 | 24 | # pulls: 25 | # daysUntilLock: 30 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .DS_Store 4 | /node_modules/ 5 | npm-debug.log 6 | .tern-project 7 | package-lock.json 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 8 3 | branches: 4 | only: 5 | - master 6 | notifications: 7 | slack: 8 | secure: QI+nroFHoaX5vBTtMiJwBt9pYBvFKC8TPwyREEY0yZNjp4+bF/rk7Sj7nNK136m4+nP+wPrAPSC+8jk7jdjRWP2j+CRbnGCSf/29xeDWgXpRUoOGTe8/XWhHlLKwwJ6zm+eB6kwNN3wqrQ+C/9L7gckbj6BCvpp9SwH1q02lpNU= 9 | email: 10 | - PhoneGapCI@adobe.com 11 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012-2017 Adobe Systems 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | This project is not under active development. Folks who are users of this plugin should switch to using [cordova-plugin-push](https://github.com/havesource/cordova-plugin-push) which is a fork of this project. 4 | 5 | # phonegap-plugin-push [![Build Status](https://travis-ci.org/phonegap/phonegap-plugin-push.svg)](https://travis-ci.org/phonegap/phonegap-plugin-push) 6 | 7 | > Register and receive push notifications 8 | 9 | # Warning 10 | 11 | The links below take you to the version 2.x documentation which includes a 12 | number of breaking API changes from version 1.x, mostly the move from GCM to 13 | FCM. If you are using version 1.x please reference the docs in the 14 | [v1.x branch](https://github.com/phonegap/phonegap-plugin-push/tree/v1.x). 15 | 16 | # What is this? 17 | 18 | This plugin offers support to receive and handle native push notifications with 19 | a **single unified API**. 20 | 21 | This does not mean you will be able to send a single push message and have it 22 | arrive on devices running different operating systems. By default Android uses 23 | FCM and iOS uses APNS and their payloads are significantly different. Even if 24 | you are using FCM for both Android and iOS there are differences in the payload 25 | required for the plugin to work correctly. For Android **always** put your push 26 | payload in the `data` section of the push notification. For more information on 27 | why that is the case read 28 | [Notification vs Data Payload](https://github.com/phonegap/phonegap-plugin-push/blob/master/docs/PAYLOAD.md#notification-vs-data-payloads). 29 | For iOS follow the regular 30 | [FCM documentation](https://firebase.google.com/docs/cloud-messaging/http-server-ref). 31 | 32 | This plugin does not provide a way to determine which platform you are running 33 | on. The best way to do that is use the `device.platform` property provided by 34 | [cordova-plugin-device](https://github.com/apache/cordova-plugin-device). 35 | 36 | Starting with version `2.0.0`, this plugin will support `CocoaPods` installation 37 | of the `Firebase Cloud Messaging` library. More details are available in the 38 | [Installation](docs/INSTALLATION.md#cocoapods) documentation. 39 | 40 | * [Reporting Issues](docs/ISSUES.md) 41 | * [Installation](docs/INSTALLATION.md) 42 | * [API reference](docs/API.md) 43 | * [Typescript support](docs/TYPESCRIPT.md) 44 | * [Examples](docs/EXAMPLES.md) 45 | * [Platform support](docs/PLATFORM_SUPPORT.md) 46 | * [Cloud build support (PG Build, IntelXDK)](docs/PHONEGAP_BUILD.md) 47 | * [Push notification payload details](docs/PAYLOAD.md) 48 | * [Contributing](.github/CONTRIBUTING.md) 49 | * [License (MIT)](MIT-LICENSE) 50 | 51 | # Do you like tutorial? You get tutorial! 52 | 53 | * [PhoneGap Day US Push Workshop 2016 (using node-gcm)](http://macdonst.github.io/push-workshop/) 54 | 55 | # Thanks to all our contributors 56 | 57 | [10ko](https://github.com/10ko)[TVolly](https://github.com/TVolly)[waptaxi](https://github.com/waptaxi)[viktormuller](https://github.com/viktormuller)[devgeeks](https://github.com/devgeeks)[rastafan](https://github.com/rastafan) 58 | 59 | [mdoelker](https://github.com/mdoelker)[markeeftb](https://github.com/markeeftb)[malwatte](https://github.com/malwatte)[madebycm](https://github.com/madebycm)[kelvinhokk](https://github.com/kelvinhokk)[keab42](https://github.com/keab42) 60 | 61 | [jomarocas](https://github.com/jomarocas)[giuseppelt](https://github.com/giuseppelt)[ericb](https://github.com/ericb)[eKazim](https://github.com/eKazim)[clementcontet](https://github.com/clementcontet)[yaswanthsvist](https://github.com/yaswanthsvist) 62 | 63 | [Vabs28](https://github.com/Vabs28)[TillaTheHun0](https://github.com/TillaTheHun0)[tomasvarg](https://github.com/tomasvarg)[tobmaster](https://github.com/tobmaster)[ThiagoBueno](https://github.com/ThiagoBueno)[szh](https://github.com/szh) 64 | 65 | [SharUpOff](https://github.com/SharUpOff)[smorstabilini](https://github.com/smorstabilini)[fesor](https://github.com/fesor)[GreyDekart](https://github.com/GreyDekart)[sebastiansier](https://github.com/sebastiansier)[olastor](https://github.com/olastor) 66 | 67 | [tanansatpal](https://github.com/tanansatpal)[SandroGrzicic](https://github.com/SandroGrzicic)[xorxor](https://github.com/xorxor)[rubenstolk](https://github.com/rubenstolk)[roel-sluper](https://github.com/roel-sluper)[pataar](https://github.com/pataar) 68 | 69 | [peteonrails](https://github.com/peteonrails)[pjalbuquerque](https://github.com/pjalbuquerque)[NitroGhost](https://github.com/NitroGhost)[matrosov-nikita](https://github.com/matrosov-nikita)[Mikejo5000](https://github.com/Mikejo5000)[michellarcari](https://github.com/michellarcari) 70 | 71 | [adamschachne](https://github.com/adamschachne)[alharding](https://github.com/alharding)[albertleao](https://github.com/albertleao)[gotev](https://github.com/gotev)[Alex-Sessler](https://github.com/Alex-Sessler)[ben-8409](https://github.com/ben-8409) 72 | 73 | [bmwertman](https://github.com/bmwertman)[bmatto](https://github.com/bmatto)[countcain](https://github.com/countcain)[CookieCookson](https://github.com/CookieCookson)[cdorner](https://github.com/cdorner)[colene](https://github.com/colene) 74 | 75 | [cfsnyder](https://github.com/cfsnyder)[cmalard](https://github.com/cmalard)[dansumption](https://github.com/dansumption)[dannywillems](https://github.com/dannywillems)[DrMoriarty](https://github.com/DrMoriarty)[eladmoshe](https://github.com/eladmoshe) 76 | 77 | [mlabarca](https://github.com/mlabarca)[bromeostasis](https://github.com/bromeostasis)[filmaj](https://github.com/filmaj)[geo242](https://github.com/geo242)[gbenvenuti](https://github.com/gbenvenuti)[polyn0m](https://github.com/polyn0m) 78 | 79 | [jacquesdev](https://github.com/jacquesdev)[janpio](https://github.com/janpio)[jakari](https://github.com/jakari)[purplecabbage](https://github.com/purplecabbage)[theaccordance](https://github.com/theaccordance)[jonas-m-](https://github.com/jonas-m-) 80 | 81 | [Chuckytuh](https://github.com/Chuckytuh)[leonardobazico](https://github.com/leonardobazico)[loslislo-lshift](https://github.com/loslislo-lshift)[luka5](https://github.com/luka5)[mac89](https://github.com/mac89)[markokeeffe](https://github.com/markokeeffe) 82 | 83 | [mbektchiev](https://github.com/mbektchiev)[goya](https://github.com/goya)[slorber](https://github.com/slorber)[daserge](https://github.com/daserge)[smdvdsn](https://github.com/smdvdsn)[ryanluker](https://github.com/ryanluker) 84 | 85 | [russellbeattie](https://github.com/russellbeattie)[rjmunro](https://github.com/rjmunro)[hanicker](https://github.com/hanicker)[mwbrooks](https://github.com/mwbrooks)[LightZam](https://github.com/LightZam)[laagland](https://github.com/laagland) 86 | 87 | [cuatl](https://github.com/cuatl)[gianpaj](https://github.com/gianpaj)[EdMcBane](https://github.com/EdMcBane)[chriswiggins](https://github.com/chriswiggins)[barryvdh](https://github.com/barryvdh)[armno](https://github.com/armno) 88 | 89 | [archananaik](https://github.com/archananaik)[jakub-g](https://github.com/jakub-g)[shazron](https://github.com/shazron)[sclement41](https://github.com/sclement41)[hung-doan](https://github.com/hung-doan)[BBosman](https://github.com/BBosman) 90 | 91 | [giordanocardillo](https://github.com/giordanocardillo)[mikepsinn](https://github.com/mikepsinn)[AdriVanHoudt](https://github.com/AdriVanHoudt)[alexislg2](https://github.com/alexislg2)[jcesarmobile](https://github.com/jcesarmobile)[nadyaA](https://github.com/nadyaA) 92 | 93 | [jdhiro](https://github.com/jdhiro)[edewit](https://github.com/edewit)[wildabeast](https://github.com/wildabeast)[mkuklis](https://github.com/mkuklis)[ashconnell](https://github.com/ashconnell)[zwacky](https://github.com/zwacky) 94 | 95 | [rakatyal](https://github.com/rakatyal)[jtbdevelopment](https://github.com/jtbdevelopment)[EddyVerbruggen](https://github.com/EddyVerbruggen)[fredgalvao](https://github.com/fredgalvao)[bobeast](https://github.com/bobeast)[macdonst](https://github.com/macdonst) 96 | 97 | [larrybahr](https://github.com/larrybahr) 98 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phonegap-plugin-push", 3 | "description": "bower package for server side javascript", 4 | "main": "www/push.js", 5 | "authors": [ 6 | "Adobe PhoneGap Team" 7 | ], 8 | "license": "APL", 9 | "keywords": [ 10 | "ecosystem:cordova", 11 | "ecosystem:phonegap", 12 | "cordova-ios", 13 | "cordova-android", 14 | "cordova-windows", 15 | "cordova-browser" 16 | ], 17 | "homepage": "https://github.com/phonegap/phonegap-plugin-push", 18 | "moduleType": [], 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /docs/EXAMPLES.md: -------------------------------------------------------------------------------- 1 | # Creating a Project From a Template 2 | 3 | If you want to get started with a sample project you can create a new project from the example template. 4 | 5 | ``` 6 | phonegap create my-app --template phonegap-template-push 7 | ``` 8 | 9 | ## Quick Example 10 | 11 | ```javascript 12 | const push = PushNotification.init({ 13 | android: { 14 | }, 15 | browser: { 16 | pushServiceURL: 'http://push.api.phonegap.com/v1/push' 17 | }, 18 | ios: { 19 | alert: "true", 20 | badge: "true", 21 | sound: "true" 22 | }, 23 | windows: {} 24 | }); 25 | 26 | push.on('registration', (data) => { 27 | // data.registrationId 28 | }); 29 | 30 | push.on('notification', (data) => { 31 | // data.message, 32 | // data.title, 33 | // data.count, 34 | // data.sound, 35 | // data.image, 36 | // data.additionalData 37 | }); 38 | 39 | push.on('error', (e) => { 40 | // e.message 41 | }); 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/INSTALLATION.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | * [Installation Requirements](#installation-requirements) 4 | * [Android details](#android-details) 5 | * [Compilation](#compilation) 6 | * [Co-existing with Facebook Plugin](#co-existing-with-facebook-plugin) 7 | * [Co-existing with plugins that use Firebase](#co-existing-with-plugins-that-use-firebase) 8 | * [Common errors](#common-errors) 9 | * [minSdkVersion === 14](#minsdkversion--14) - [Multidex](#multidex) - [More than one library with package name 'com.google.android.gms'](#more-than-one-library-with-package-name-comgoogleandroidgms) 10 | * [Browser details](#browser-details) 11 | * [Browser quirks](#browser-quirks) 12 | * [Browser Support](#browser-support) 13 | * [iOS details](#ios-details) 14 | * [Xcode](#xcode) 15 | * [Bitcode](#bitcode) 16 | * [CocoaPods](#cocoapods) 17 | * [Common CocoaPod Installation issues](#common-cocoapod-installation-issues) 18 | * [CocoaPod Disk Space](#cocoapod-disk-space) 19 | * [Library not found for -lPods-Appname](#library-not-found-for--lpods-appname) 20 | * [Library not found for -lGoogleToolboxForMac](#library-not-found-for--lgoogletoolboxformac) 21 | * [Module FirebaseInstanceID not found](#module-firebaseinstanceid-not-found) 22 | * [Additional Resources](#additional-resources) 23 | 24 | ## Installation Requirements 25 | 26 | | Plugin version | Cordova CLI | Cordova Android | Cordova iOS | CocoaPods | 27 | | -------------- | ----------- | --------------- | ----------- | --------- | 28 | | 2.2.0 | 7.1.0 | 7.1.0 | 4.5.0 | 1.1.1 | 29 | | 2.1.2 | 7.1.0 | 6.3.0 | 4.5.0 | 1.1.1 | 30 | | 2.1.0 | 7.1.0 | 6.3.0 | 4.4.0 | 1.1.1 | 31 | | 2.0.0 | 7.0.0 | 6.2.1 | 4.4.0 | 1.1.1 | 32 | | 1.9.0 | 6.4.0 | 6.0.0 | 4.3.0 | 1.1.1 | 33 | | 1.8.0 | 3.6.3 | 4.0.0 | 4.1.0 | N/A | 34 | 35 | To install from the command line: 36 | 37 | ```bash 38 | phonegap plugin add phonegap-plugin-push 39 | ``` 40 | 41 | or 42 | 43 | ```bash 44 | cordova plugin add phonegap-plugin-push 45 | ``` 46 | 47 | It is also possible to install via repo url directly ( unstable ) 48 | 49 | ```bash 50 | phonegap plugin add https://github.com/phonegap/phonegap-plugin-push 51 | ``` 52 | 53 | or 54 | 55 | ```bash 56 | cordova plugin add https://github.com/phonegap/phonegap-plugin-push 57 | ``` 58 | 59 | As of version 2.0.0 the SENDER_ID parameter has been removed at install time. Instead you put your google-services.json (Android) and/or GoogleService-Info.plist in the root folder of your project and then add the following lines into your config.xml. 60 | 61 | In the platform tag for Android add the following resource-file tag if you are using cordova-android 7.0 or greater: 62 | 63 | ```xml 64 | 65 | 66 | 67 | ``` 68 | 69 | If you are using cordova-android 6.x or earlier, add the following resource-file tag: 70 | 71 | ```xml 72 | 73 | 74 | 75 | ``` 76 | 77 | By default, on iOS, the plugin will register with APNS. If you want to use FCM on iOS, in the platform tag for iOS add the resource-file tag: 78 | 79 | ```xml 80 | 81 | 82 | 83 | ``` 84 | 85 | > Note: if you are using Ionic you may need to specify the SENDER_ID variable in your package.json. 86 | 87 | ```json 88 | "cordovaPlugins": [ 89 | { 90 | "locator": "phonegap-plugin-push" 91 | } 92 | ] 93 | ``` 94 | 95 | > Note: You need to specify the SENDER_ID variable in your config.xml if you plan on installing/restoring plugins using the prepare method. The prepare method will skip installing the plugin otherwise. 96 | 97 | ```xml 98 | 99 | ``` 100 | 101 | ## Android details 102 | 103 | ### Compilation 104 | 105 | As of version 2.1.0 the plugin has been switched to using pinned version of Gradle libraries. You will need to ensure that you have installed the following items through the Android SDK Manager: 106 | 107 | * Android Support Repository version 47+ 108 | 109 | ![android support library](https://user-images.githubusercontent.com/353180/33042340-7ea60aaa-ce0f-11e7-99f7-4631e4c3d7be.png) 110 | 111 | For more detailed instructions on how to install the Android Support Library visit [Google's documentation](https://developer.android.com/tools/support-library/setup.html). 112 | 113 | _Note:_ if you are using an IDE to like Eclipse, Xamarin, etc. then the Android SDK installed by those tools may not be the same version as the one used by the Cordova/PhoneGap CLI while building. Please make sure your command line tooling is up to date with the software versions above. An easy way to make sure you up to date is to run the following command: 114 | 115 | ```bash 116 | android update sdk --no-ui --filter "extra" 117 | ``` 118 | 119 | ### Co-existing with Facebook Plugin 120 | 121 | There are a number of Cordova Facebook Plugins available but the one that we recommend is [Jeduan's fork](https://github.com/jeduan/cordova-plugin-facebook4) of the original Wizcorp plugin. It is setup to use Gradle/Maven and the latest Facebook SDK properly. 122 | 123 | To add to your app: 124 | 125 | ```bash 126 | phonegap plugin add --save cordova-plugin-facebook4 --variable APP_ID="App ID" --variable APP_NAME="App Name" 127 | ``` 128 | 129 | or 130 | 131 | ```bash 132 | cordova plugin add --save cordova-plugin-facebook4 --variable APP_ID="App ID" --variable APP_NAME="App Name" 133 | ``` 134 | 135 | ### Co-existing with plugins that use Firebase 136 | 137 | Problems may arise when push plugin is used along plugins that implement Firebase functionality (cordova-plugin-firebase-analytics, for example). Both plugins include a version of the FCM libraries. 138 | 139 | To make the two work together, you need to migrate your GCM project from Google console to Firebase console: 140 | 141 | 1. In Firebase console - [import your existing GCM project](https://firebase.google.com/support/guides/google-android#migrate_your_console_project), don't create a new one. 142 | 2. Set your `FCM_VERSION` variable to match the version used in the other plugin. In case of cordova, your `config.xml` would look something like this: 143 | 144 | ```xml 145 | 146 | 147 | 148 | ``` 149 | 150 | _Note:_ No changes on the back-end side are needed: [even though recommended](https://developers.google.com/cloud-messaging/android/android-migrate-fcm#update_server_endpoints), it isn't yet required and sending messages through GCM gateway should work just fine. 151 | 152 | ### Common errors 153 | 154 | #### minSdkVersion === 14 155 | 156 | If you have an issue compiling the app and you are getting an error similar to this: 157 | 158 | ``` 159 | * What went wrong: 160 | Execution failed for task ':processDebugManifest'. 161 | > Manifest merger failed : uses-sdk:minSdkVersion 14 cannot be smaller than version 15 declared in library .../platforms/android/build/intermediates/exploded-aar/com.facebook.android/facebook-android-sdk/4.6.0/AndroidManifest.xml 162 | Suggestion: use tools:overrideLibrary="com.facebook" to force usage 163 | ``` 164 | 165 | Then you can add the following entry into your config.xml file in the android platform tag: 166 | 167 | ```xml 168 | 169 | 170 | 171 | ``` 172 | 173 | or compile your project using the following command, if the solution above doesn't work for you. Basically add `-- --minSdkVersion=15` to the end of the command line (mind the extra `--`, it's needed): 174 | 175 | ```bash 176 | cordova compile android -- --minSdkVersion=15 177 | cordova build android -- --minSdkVersion=15 178 | cordova run android -- --minSdkVersion=15 179 | cordova emulate android -- --minSdkVersion=15 180 | ``` 181 | 182 | #### Multidex 183 | 184 | If you have an issue compiling the app and you're getting an error similar to this (`com.android.dex.DexException: Multiple dex files define`): 185 | 186 | ``` 187 | UNEXPECTED TOP-LEVEL EXCEPTION: 188 | com.android.dex.DexException: Multiple dex files define Landroid/support/annotation/AnimRes; 189 | at com.android.dx.merge.DexMerger.readSortableTypes(DexMerger.java:596) 190 | at com.android.dx.merge.DexMerger.getSortedTypes(DexMerger.java:554) 191 | at com.android.dx.merge.DexMerger.mergeClassDefs(DexMerger.java:535) 192 | at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:171) 193 | at com.android.dx.merge.DexMerger.merge(DexMerger.java:189) 194 | at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:502) 195 | at com.android.dx.command.dexer.Main.runMonoDex(Main.java:334) 196 | at com.android.dx.command.dexer.Main.run(Main.java:277) 197 | at com.android.dx.command.dexer.Main.main(Main.java:245) 198 | at com.android.dx.command.Main.main(Main.java:106) 199 | ``` 200 | 201 | Then at least one other plugin you have installed is using an outdated way to declare dependencies such as `android-support` or `play-services-gcm`. 202 | This causes gradle to fail, and you'll need to identify which plugin is causing it and request an update to the plugin author, so that it uses the proper way to declare dependencies for cordova. 203 | See [this for the reference on the cordova plugin specification](https://cordova.apache.org/docs/en/5.4.0/plugin_ref/spec.html#link-18), it'll be usefull to mention it when creating an issue or requesting that plugin to be updated. 204 | 205 | Common plugins to suffer from this outdated dependency management are plugins related to _facebook_, _google+_, _notifications_, _crosswalk_ and _google maps_. 206 | 207 | #### More than one library with package name 'com.google.android.gms' 208 | 209 | When some other packages include `cordova-google-play-services` as a dependency, such as is the case with the cordova-admob and cordova-plugin-analytics plugins, it is impossible to also add the phonegap-plugin-push, for the following error will rise during the build process: 210 | 211 | ``` 212 | :processDebugResources FAILED 213 | FAILURE: Build failed with an exception. 214 | 215 | What went wrong: Execution failed for task ':processDebugResources'. > Error: more than one library with package name 'com.google.android.gms' 216 | ``` 217 | 218 | Those plugins should be using gradle to include the Google Play Services package but instead they include the play services jar directly or via a plugin dependency. So all of that is bad news. These plugins should be updated to use gradle. Please raise issues on those plugins as the change is not hard to make. 219 | 220 | In fact there is a PR open to do just that appfeel/analytics-google#11 for cordova-plugin-analytics. You should bug the team at appfeel to merge that PR. 221 | 222 | Alternatively, switch to another plugin that provides the same functionality but uses gradle: 223 | 224 | [https://github.com/danwilson/google-analytics-plugin](https://github.com/danwilson/google-analytics-plugin) 225 | [https://github.com/cmackay/google-analytics-plugin](https://github.com/cmackay/google-analytics-plugin) 226 | 227 | ## Browser details 228 | 229 | ### Browser quirks 230 | 231 | For the time being push support on the browser will only work using the PhoneGap push server. 232 | 233 | When you run `phonegap serve` to test browser push point your browser at `http://localhost:3000`. The browser push implementation uses the W3C Push Spec's implementation which relies on ServiceWorkers and ServiceWorkers can only be accessed via the `https` protocol or via `http://localhost`. Pointing your browser at `localhost` will be the easiest way to test. 234 | 235 | ### Browser Support 236 | 237 | Chrome 49+ 238 | Firefox 46+ 239 | 240 | ## iOS details 241 | 242 | ### Xcode 243 | 244 | Xcode version 8.0 or greater is required for building this plugin. 245 | 246 | ### Bitcode 247 | 248 | If you are running into a problem where the linker is complaining about bit code. For instance: 249 | 250 | ``` 251 | ld: '' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. for architecture arm64 clang: error: linker command failed with exit code 1 (use -v to see invocation) 252 | ``` 253 | 254 | You have two options. The first is to [disable bitcode as per this StackOverflow answer](http://stackoverflow.com/a/32466484/41679) or [upgrade to cordova-ios 4 or greater](https://cordova.apache.org/announcements/2015/12/08/cordova-ios-4.0.0.html). 255 | 256 | ``` 257 | cordova platform update ios@4.0.0 258 | ``` 259 | 260 | ### CocoaPods 261 | 262 | Required `cordova-cli` minimum version: `6.4.0` 263 | 264 | Required `cordova-ios` minimum version: `4.3.0` 265 | 266 | Required `CocoaPods` minimum version: `1.0.1` 267 | 268 | To install CocoaPods, please follow the installation instructions [here](https://guides.cocoapods.org/using/getting-started). After installing CocoaPods, please run: 269 | 270 | pod setup 271 | 272 | This will clone the required CocoaPods specs-repo into your home folder at `~/.cocoapods/repos`, so it might take a while. See the [CocoaPod Disk Space](#cocoapod-disk-space) section below for more information. 273 | 274 | Version `2.0.0` (and above) of this plugin supports [CocoaPods](https://cocoapods.org) installation of the [Firebase Cloud Messaging](https://cocoapods.org/pods/FirebaseMessaging) library. 275 | 276 | If you are installing this plugin using `npm`, and you are using version `6.1.0` or greater of the `cordova-cli`, it will automatically download the right version of this plugin for both your platform and cli. 277 | 278 | If you are on a `cordova-cli` version less than `6.1.0`, you will either have to upgrade your `cordova-cli` version, or install the plugin explicitly: 279 | 280 | i.e. 281 | 282 | ```bash 283 | cordova plugin add phonegap-plugin-push@1.8.1 284 | ``` 285 | 286 | If you are installing this plugin using a `local file reference` or a `git url`, you will have to specify the version of this plugin explicitly (see above) if you don't fulfill the `cordova-cli` and `cordova-ios` requirements. 287 | 288 | #### Common CocoaPod Installation issues 289 | 290 | If you are attempting to install this plugin and you run into this error: 291 | 292 | ``` 293 | Installing "phonegap-plugin-push" for ios 294 | Failed to install 'phonegap-plugin-push':Error: pod: Command failed with exit code 1 295 | at ChildProcess.whenDone (/Users/smacdona/code/push151/platforms/ios/cordova/node_modules/cordova-common/src/superspawn.js:169:23) 296 | at emitTwo (events.js:87:13) 297 | at ChildProcess.emit (events.js:172:7) 298 | at maybeClose (internal/child_process.js:818:16) 299 | at Process.ChildProcess._handle.onexit (internal/child_process.js:211:5) 300 | Error: pod: Command failed with exit code 1 301 | ``` 302 | 303 | Please run the command `pod repo update` and re-install the plugin. You would only run `pod repo update` if you have the specs-repo already cloned on your machine through `pod setup`. 304 | 305 | ##### CocoaPod Disk Space 306 | 307 | Running `pod setup` can take over 1 GB of disk space and that can take quite some time to download over a slow internet connection. If you are having issues with disk space/network try this neat hack from @VinceOPS. 308 | 309 | ```bash 310 | git clone --verbose --depth=1 https://github.com/CocoaPods/Specs.git ~/.cocoapods/repos/master 311 | pod setup --verbose 312 | ``` 313 | 314 | ##### Library not found for -lPods-Appname 315 | 316 | If you open the app in Xcode and you get an error like: 317 | 318 | ``` 319 | ld: library not found for -lPods-Appname 320 | clang: error: linker command failed with exit code 1 321 | ``` 322 | 323 | Then you are opening the .xcodeproj file when you should be opening the .xcworkspace file. 324 | 325 | ##### Library not found for -lGoogleToolboxForMac 326 | 327 | Trying to build for iOS using the latest cocoapods (1.2.1) but failed with the following error (from terminal running cordova build ios): 328 | 329 | ``` 330 | ld: library not found for -lGoogleToolboxForMac 331 | ``` 332 | 333 | Workarounds are to add the platform first and install the plugins later, or to manually run pod install on projectName/platforms/ios. 334 | 335 | Another workaround is to go to build phases in your project at Link Binary Libraries and add `libPods-PROJECTNAME.a` and `libGoogleToolboxForMac.a` 336 | 337 | ##### Module FirebaseInstanceID not found 338 | 339 | If you run into an error like: 340 | 341 | ``` 342 | module FirebaseInstanceID not found 343 | ``` 344 | 345 | You may be running into a bug in cordova-ios. The current workaround is to run `pod install` manually. 346 | 347 | ```bash 348 | cd platforms/ios 349 | pod install 350 | ``` 351 | 352 | ## Additional Resources 353 | 354 | The push plugin enables you to play sounds and display different icons during push (Android only). These additional resources need to be added to your projects `platforms` directory in order for them to be included into your final application binary. 355 | 356 | You can now use the `resource-file` tag to deliver the image and sound files to your application. For example if you wanted to include an extra image file for only your Android build you would add the `resource-file` tag to your android `platform` tag: 357 | 358 | ```xml 359 | 360 | 361 | 362 | ``` 363 | 364 | or if you wanted to include a sound file for iOS: 365 | 366 | ```xml 367 | 368 | 369 | 370 | ``` 371 | -------------------------------------------------------------------------------- /docs/ISSUES.md: -------------------------------------------------------------------------------- 1 | # ISSUES 2 | 3 | - [Read the docs](#read-the-docs) 4 | - [Search the issues](#search-the-issues) 5 | - [Opening a new issue](#opening-a-new-issue) 6 | - [Provide details](#provide-details) 7 | - [An example issue](#an-example-issue) 8 | - [Voting on an issue](#voting-on-an-issue) 9 | 10 | The following tips are for users of this plugin who want to get help. 11 | 12 | ## Read the docs 13 | 14 | I'll be the first to admit that the docs are not perfect but start here at the [README](https://github.com/phonegap/phonegap-plugin-push/blob/master/README.md) to see if your problem is documented. If it isn't continue on but if you do get an answer then consider sending a documentation pull request. 15 | 16 | ## Search the issues 17 | 18 | Your question may have already been answered. Make sure you search at least the repo's [issues](https://github.com/phonegap/phonegap-plugin-push/issues) before you create a new one. 19 | 20 | ## Opening a new issue 21 | 22 | If you have searched the issues and haven't found anything that resembles your problem then follow these guidelines in creating a new issue. 23 | 24 | ### Provide details 25 | 26 | Give as many details as possible. Issues without many details will be more difficult to debug and will encounter delays. 27 | 28 | Select a concise, informative title for the issue. Here's a good article on writing [subject lines](https://www.nngroup.com/articles/microcontent-how-to-write-headlines-page-titles-and-subject-lines/). 29 | 30 | Include the following at a minimum: 31 | _ what version number of plugin are you using? 32 | - which platform and version you are testing on? iOS 9.0, Android 5.0, etc. 33 | - a detailed description of your problem. Including: 34 | - steps to reproduce 35 | - expected result 36 | - actual result 37 | - how you are sending the push data to the device, including an example payload 38 | 39 | You may also want to include: 40 | - some sample code that illustrates the problem. 41 | - logs taken while the problem was reproduced. 42 | - screenshots! 43 | 44 | If the code or logs are huge, let's say over 20 lines please think about using a web service like [Gist](https://gist.github.com/) or [Pastebin](http://pastebin.com/). 45 | 46 | ### An example issue 47 | 48 | **The wrong way** 49 | 50 | *Title:* This plugin does not work for me 51 | 52 | *Details:* Please fix quickly as my business depends on this plugin. 53 | 54 | **The right way** 55 | 56 | *Title:* Registration event never received on Samsung Galaxy S running Android 2.3 57 | 58 | *Details:* I'm using version 1.5.2 of this plugin on my Samsung Galaxy S5 device which runs Android 4.4. I never receiving the `registration` event in my application when I expect it to return a value I can send to my push service. 59 | 60 | You can see the code I'm using in this gist: [https://gist.github.com/macdonst/191f74ac75b6802c047d](https://gist.github.com/macdonst/191f74ac75b6802c047d) 61 | 62 | And an output of the logs when trying to run the app are in this gist: [https://gist.github.com/macdonst/47549150c299080c455c](https://gist.github.com/macdonst/47549150c299080c455c) 63 | 64 | Please point me in the right direction. 65 | 66 | *Response:* 67 | 68 | Thanks for the detailed logs and example code by looking them over I'm sure of what your problem is. If you look at line [334](https://gist.github.com/macdonst/47549150c299080c455c#file-logcat-txt-L334) of your logcat you will see that it complains that: 69 | 70 | ``` 71 | I/chromium(11669): [INFO:CONSOLE(54)] "Uncaught ReferenceError: PushNotification is not defined", source: file:///android_asset/www/js/index.js (54) 72 | ``` 73 | 74 | This leads me to line [4](https://gist.github.com/macdonst/191f74ac75b6802c047d#file-app-js-L4) of your code where you are initializing push before you get the `deviceready` event. Like all Cordova API's you have to wait until you receive the `deviceready` event before you initialize Push. 75 | 76 | Check out [https://github.com/phonegap/phonegap-plugin-push/blob/20f489a90cf519f962fd957700f92115f142594b/example/www/js/index.js](https://github.com/phonegap/phonegap-plugin-push/blob/20f489a90cf519f962fd957700f92115f142594b/example/www/js/index.js) for an example of how to wait for `deviceready`. 77 | 78 | ## Voting on an issue 79 | 80 | Did you know you can vote on issues in the phonegap-plugin-push repository? If you install the [ZenHub](https://chrome.google.com/webstore/detail/zenhub-for-github/ogcgkffhplmphkaahpmffcafajaocjbd) Chrome Extension you will be able to +1 issues to indicate how popular they are to the community. It's a way better way for the contributors to keep track of important issues. 81 | -------------------------------------------------------------------------------- /docs/PHONEGAP_BUILD.md: -------------------------------------------------------------------------------- 1 | # Cloud Build Services 2 | 3 | * [PhoneGap Build Support](#phonegap-build-support) 4 | * [Including the plugin](#including-the-plugin) 5 | * [Adding Resources](#adding-resources) 6 | * [IntelXDK Support](#intelxdk-support) 7 | * [Ionic Cloud Build](#ionic-cloud-build) 8 | 9 | ## PhoneGap Build Support 10 | 11 | ### Including the plugin 12 | 13 | Including this plugin in a project that is built by PhoneGap Build is as easy as adding (replacing `123456789` with your own, that is): 14 | 15 | ```xml 16 | 17 | 18 | 19 | 20 | ``` 21 | 22 | into your app's `config.xml` file. PhoneGap Build will pick up the latest version of phonegap-plugin-push published on npm. If you want to specify a particular version of the plugin you can add the `spec` attribute to the `plugin` tag. 23 | 24 | ```xml 25 | 26 | 27 | ``` 28 | 29 | Note: version 1.3.0 of this plugin begins to use Gradle to install the Android Support Framework. Support for Gradle has recently been added to PhoneGap Build. Please read [this blog post](http://phonegap.com/blog/2015/09/28/android-using-gradle/) for more information. 30 | 31 | ### Adding resources 32 | 33 | Because PhoneGap Build does not support running hooks if you want to include custom image or sounds you will need to use a _beta_ feature to include these files. 34 | 35 | #### Android 36 | 37 | To add custom files, create a directory called `locales/android/` in the root of your PGB application zip / repo, and place your resource files there. The contents will be copied into the Android `res/` directory, and any nested sub-directory structures will persist. Here's an example of how these files will be compiled into your APK: 38 | 39 | ``` 40 | /locales/android/drawables/logo.png --> /res/drawables/logo.png 41 | /locales/android/raw/beep.mp3 --> /res/raw/beep.mp3 42 | /locales/android/values-fr/strings.xml --> /res/values-fr/strings.xml 43 | ``` 44 | 45 | Existing directories will be merged, but at this time any individual files you include will overwrite their target if it exists. 46 | 47 | ## IntelXDK Support 48 | 49 | 1. Do pre-requisite setup on [the iOS Provisioning Portal](https://developer.apple.com/account/ios/identifier/bundle). Refer to [this guide](https://www.raywenderlich.com/123862/push-notifications-tutorial) or Apple docs for detailed steps. 50 | a. make a new App ID (you'll need to set this in Intel XDK config later) 51 | b. enable push notifications 52 | c. iOS Distribution cert: create (if needed), download and install (if needed), export as a .p12 (set and remember the password as you'll need this to import into Intel XDK later) 53 | **NOTE**: Intel XDK does not support Development certs, so you MUST use your Distribution cert. 54 | d. Make an AdHoc Provisioning Profile using your App ID from (1a) and your cert from (1c). Make sure your test device is enabled. Download and save with a name you will recognize. (you'll need to add this to your Intel XDK project later) 55 | e. make a push cert, download it, install it, export it to .p12, convert it to .pem (this is for the push server that will send the notification - you'll need this later to test your Intel XDK app) 56 | 57 | 2. In Intel XDK, make a new Cordova CLI 5.4.1 project using the HTML5+Cordova Blank Template, then replace the contents of www with [the contents of www from the PhoneGap Push Template](https://github.com/phonegap/phonegap-template-push/tree/master/template_src/www). 58 | 59 | 3. Delete www/config.xml (optional? Intel XDK does not use config.xml) 60 | 61 | 4. Intel XDK Project Settings 62 | a. set the iOS App ID to match the App ID from (1a) 63 | b. (if needed) import your .p12 from (1c) - Account Settings->Developer Certificates->iOS, then select it as the Developer Certificate for the project 64 | c. Select "adhoc" for Provisioning Profile 65 | d. copy your provisioning profile from (1d) into www/, then click "Ad hoc Provisioning Profile" and select the profile 66 | e. Add the latest version of phonegap-plugin-push as a "Third-Party Plugin" (at time of testing this was 1.6.4) 67 | f. **After the plugin is added, you will need to edit plugins/phonegap-plugin-push/plugin.xml**. Intel XDK 3357 does not support plugins with gradle references, so the gradle reference must be commented out (this will prevent this version of the plugin from working for Android but is needed for the iOS build to succeed): 68 | `` 69 | A future version of Intel XDK will support gradle references. 70 | 71 | 5. XDK Build Tab 72 | a. Enable iOS build (click the checkmark) 73 | b. Unlock your iOS certificate (click the lock and enter the password from (1c)) 74 | c. click Start Builds 75 | d. once the build completes, download and install the app 76 | 77 | 6. connect test device by USB and open XCode Devices window (probably could also use Safari Web Inspector + Cordova Console plugin) - start the app and a log message should be written into the console that looks like "Push Plugin register success: \" 78 | 79 | 7. exit the app (close with home button then swipe it off the multitask view) 80 | 81 | 8. The angle brackets and everything between (from (5)) is the device token - copy it into a text file 82 | 83 | 9. Add the device token to your server and send a push notification 84 | a. I used [phonegap-plugin-push/example/server/pushAPNS.rb](https://github.com/phonegap/phonegap-plugin-push/blob/master/example/server/pushAPNS.rb) for this 85 | b. APNS.host = 'gateway.push.apple.com' 86 | c. APNS.pem = 'PGPush926Prod.pem' #path to your pem file from (1e) 87 | d. device_token = '\' #the device token from (7) 88 | e. edit the alert message and badge number 89 | f. you probably need to install the required gem (`gem install pushmeup`) 90 | g. send the notification (`ruby pushAPNS.rb`) 91 | 92 | 10. See notification on device! 93 | 94 | ## Ionic Cloud Build 95 | 96 | Users have reported issues with Ionic Cloud Build. Apparently there are some differences in the way variables are handled. If your app has an issue where the `PushNotification` object can't be found try the following. 97 | 98 | 1. Remove the inclusion of `phonegap-plugin-push` from config.xml. That is delete lines that look like this: 99 | 100 | ```xml 101 | 102 | 103 | 104 | ``` 105 | 106 | 2. Add the following lines into `package.json` in the `cordovaPlugins` array. 107 | 108 | ```json 109 | { 110 | "variables": { 111 | "SENDER_ID": "xxx" 112 | }, 113 | "locator": "phonegap-plugin-push" 114 | } 115 | ``` 116 | -------------------------------------------------------------------------------- /docs/PLATFORM_SUPPORT.md: -------------------------------------------------------------------------------- 1 | ## Supported Platforms 2 | 3 | ### Version 1.9.x 4 | 5 | - Cordova CLI (6.4.0 or newer) 6 | - Android (`cordova-android` 6.0.0 or higher) 7 | - Browser 8 | - iOS (`cordova-ios` 4.3.0 or higher) 9 | - Windows Universal (not Windows Phone 8) 10 | 11 | ### Version 1.8.x 12 | 13 | - Cordova CLI (3.6.3 or newer) 14 | - Android (`cordova-android` 4.0.0 or higher) 15 | - Browser 16 | - iOS (`cordova-ios` 4.1.0 or higher) 17 | - Windows Universal (not Windows Phone 8) 18 | -------------------------------------------------------------------------------- /docs/TYPESCRIPT.md: -------------------------------------------------------------------------------- 1 | # Typescript definitions 2 | 3 | For those of you who use Typescript, we're glad to say that we provide the complete definition file along with our package. 4 | 5 | ## Example usage 6 | 7 | All objects will be understood as having a defined type, including init options and eventHandler parameters. 8 | All available attributes and properties will have autocomplete support and type checkings. 9 | 10 | ```typescript 11 | import 'phonegap-plugin-push/types'; 12 | 13 | const push = PushNotification.init({ 14 | android: { 15 | }, 16 | ios: { 17 | alert: "true", 18 | badge: true, 19 | sound: 'false' 20 | }, 21 | windows: {} 22 | }); 23 | 24 | push.on('registration', (data) => { 25 | console.log(data.registrationId); 26 | }); 27 | 28 | push.on('notification', (data) => { 29 | console.log(data.message); 30 | console.log(data.title); 31 | console.log(data.count); 32 | console.log(data.sound); 33 | console.log(data.image); 34 | console.log(data.additionalData); 35 | }); 36 | 37 | push.on('error', (e) => { 38 | console.log(e.message); 39 | }); 40 | ``` 41 | 42 | If you have custom attributes being sent from the server on the payload, you can define them on a custom interface extending the standard one: 43 | 44 | ```typescript 45 | module my.custom { 46 | export interface NotificationEventResponse extends PhonegapPluginPush.NotificationEventResponse { 47 | additionalData: NotificationEventAdditionalData; 48 | } 49 | 50 | export interface NotificationEventAdditionalData extends PhonegapPluginPush.NotificationEventAdditionalData { 51 | bacon?: boolean; 52 | } 53 | } 54 | 55 | push.on('notification', (data: my.custom.NotificationEventResponse) => { 56 | //standard attributes 57 | console.log(data.message); 58 | console.log(data.title); 59 | console.log(data.count); 60 | console.log(data.sound); 61 | console.log(data.image); 62 | console.log(data.additionalData); 63 | 64 | //custom attributes 65 | console.log(data.additionalData.bacon); 66 | }); 67 | ``` 68 | -------------------------------------------------------------------------------- /example/server/pushADM.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | // Client ID and Client Secret received from ADM 5 | // For more info, see: https://developer.amazon.com/public/apis/engage/device-messaging/tech-docs/02-obtaining-adm-credentials 6 | var CLIENT_ID = "amzn1.application-oa2-client.8e838f6629554e26ae3f43a6c663cd60"; 7 | var CLIENT_SECRET = "0af96083320f5d70dc4f358cc783ac65a22e78b297ba257df34d5f723f24543f"; 8 | 9 | // Registration ID, received on device after it registers with ADM server 10 | var REGISTRATION_IDS = ["amzn1.adm-registration.v2.Y29tLmFtYXpvbi5EZXZpY2VNZXNzYWdpbmcuUmVnaXN0cmF0aW9uSWRFbmNyeXB0aW9uS2V5ITEhOE9rZ2h5TXlhVEFFczg2ejNWL3JMcmhTa255Uk5BclhBbE1XMFZzcnU1aFF6cTlvdU5FbVEwclZmdk5oTFBVRXVDN1luQlRSNnRVRUViREdQSlBvSzRNaXVRRUlyUy9NYWZCYS9VWTJUaGZwb3ZVTHhlRTM0MGhvampBK01hVktsMEhxakdmQStOSXRjUXBTQUhNU1NlVVVUVkFreVRhRTBCYktaQ2ZkUFdqSmIwcHgzRDhMQnllVXdxQ2EwdHNXRmFVNklYL0U4UXovcHg0K3Jjb25VbVFLRUVVOFVabnh4RDhjYmtIcHd1ZThiekorbGtzR2taMG95cC92Y3NtZytrcTRPNjhXUUpiZEk3QzFvQThBRTFWWXM2NHkyMjdYVGV5RlhhMWNHS0k9IW5GNEJMSXNleC9xbWpHSU52NnczY0E9PQ"]; 11 | 12 | // Message payload to be sent to client 13 | var payload = { 14 | data: { 15 | message: "PushPlugin works!!", 16 | sound: "beep.wav", 17 | url: "http://www.amazon.com", 18 | timeStamp: new Date().toISOString(), 19 | foo: "baz" 20 | }, 21 | consolidationKey: "my app", 22 | expiresAfter: 3600 23 | }; 24 | 25 | 26 | //********************************* 27 | 28 | 29 | var https = require("https"); 30 | var querystring = require("querystring"); 31 | 32 | 33 | if(CLIENT_ID == "" || CLIENT_SECRET == "" || REGISTRATION_IDS.length == 0){ 34 | console.log("******************\nSetup Error: \nYou need to edit the pushADM.js file and enter your ADM credentials and device registration ID(s).\n******************"); 35 | process.exit(1); 36 | } 37 | 38 | 39 | // Get access token from server, and use it to post message to device 40 | getAccessToken(function(accessToken){ 41 | 42 | for(var i = 0; i < REGISTRATION_IDS.length; i++){ 43 | 44 | var registrationID = REGISTRATION_IDS[i]; 45 | 46 | postMessage(accessToken, registrationID, payload); 47 | } 48 | 49 | }); 50 | 51 | 52 | 53 | 54 | // Query OAuth server for access token 55 | // For more info, see: https://developer.amazon.com/public/apis/engage/device-messaging/tech-docs/05-requesting-an-access-token 56 | 57 | function getAccessToken(callback){ 58 | 59 | console.log("Requesting access token from server..."); 60 | 61 | var credentials = { 62 | scope: "messaging:push", 63 | grant_type: "client_credentials", 64 | client_id: CLIENT_ID, 65 | client_secret: CLIENT_SECRET 66 | } 67 | 68 | var post_data = querystring.stringify(credentials); 69 | 70 | var post_options = { 71 | host: "api.amazon.com", 72 | port: "443", 73 | path: "/auth/O2/token", 74 | method: "POST", 75 | headers: { 76 | "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" 77 | } 78 | }; 79 | 80 | var req = https.request(post_options, function(res) { 81 | 82 | var data = ""; 83 | 84 | res.on("data", function (chunk) { 85 | data += chunk; 86 | }); 87 | 88 | res.on("end", function() { 89 | console.log("\nAccess token response:", data); 90 | var accessToken = JSON.parse(data).access_token; 91 | callback(accessToken); 92 | }); 93 | 94 | }); 95 | 96 | req.on("error", function(e) { 97 | console.log("\nProblem with access token request: ", e.message); 98 | }); 99 | 100 | req.write(post_data); 101 | req.end(); 102 | 103 | } 104 | 105 | 106 | // Post message payload to ADM server 107 | // For more info, see: https://developer.amazon.com/public/apis/engage/device-messaging/tech-docs/06-sending-a-message 108 | 109 | function postMessage(accessToken, registrationID, payload){ 110 | 111 | if(accessToken == undefined || registrationID == undefined || payload == undefined){ 112 | return; 113 | } 114 | 115 | console.log("\nSending message..."); 116 | 117 | var post_data = JSON.stringify(payload); 118 | 119 | var api_path = "/messaging/registrations/" + registrationID + "/messages"; 120 | 121 | var post_options = { 122 | host: "api.amazon.com", 123 | port: "443", 124 | path: api_path, 125 | method: "POST", 126 | headers: { 127 | "Authorization": "Bearer " + accessToken, 128 | "X-Amzn-Type-Version": "com.amazon.device.messaging.ADMMessage@1.0", 129 | "X-Amzn-Accept-Type" : "com.amazon.device.messaging.ADMSendResult@1.0", 130 | "Content-Type": "application/json", 131 | "Accept": "application/json", 132 | } 133 | }; 134 | 135 | var req = https.request(post_options, function(res) { 136 | 137 | var data = ""; 138 | 139 | res.on("data", function (chunk) { 140 | data += chunk; 141 | }); 142 | 143 | res.on("end", function() { 144 | console.log("\nSend message response: ", data); 145 | }); 146 | 147 | }); 148 | 149 | req.on("error", function(e) { 150 | console.log("\nProblem with send message request: ", e.message); 151 | }); 152 | 153 | req.write(post_data); 154 | req.end(); 155 | 156 | } 157 | 158 | 159 | -------------------------------------------------------------------------------- /example/server/pushAPNS.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'pushmeup' 3 | 4 | 5 | APNS.host = 'gateway.sandbox.push.apple.com' 6 | APNS.port = 2195 7 | APNS.pem = '' 8 | APNS.pass = '' 9 | 10 | device_token = '' 11 | # APNS.send_notification(device_token, 'Hello iPhone!' ) 12 | APNS.send_notification(device_token, :alert => 'PushPlugin works!!', :badge => 1, :sound => 'beep.wav') 13 | -------------------------------------------------------------------------------- /example/server/pushAzure.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** Sample Table Definition - this supports the Azure Mobile Apps 3 | ** TodoItem product 4 | ** https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-cordova-get-started/ 5 | */ 6 | var azureMobileApps = require('azure-mobile-apps'), 7 | promises = require('azure-mobile-apps/src/utilities/promises'), 8 | logger = require('azure-mobile-apps/src/logger'); 9 | 10 | // Create a new table definition 11 | var table = azureMobileApps.table(); 12 | 13 | // In the TodoItem product, sends a push notification 14 | // when a new item inserted into the table. 15 | table.insert(function (context) { 16 | // For more information about the Notification Hubs JavaScript SDK, 17 | // see http://aka.ms/nodejshubs 18 | logger.info('Running TodoItem.insert'); 19 | 20 | // Define the push notification template payload. 21 | // Requires template specified in the client app. See 22 | // https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-cordova-get-started-push/ 23 | var payload = '{"message": "' + context.item.text + '" }'; 24 | 25 | // Execute the insert. The insert returns the results as a Promise, 26 | // Do the push as a post-execute action within the promise flow. 27 | return context.execute() 28 | .then(function (results) { 29 | // Only do the push if configured 30 | if (context.push) { 31 | 32 | context.push.send(null, payload, function (error) { 33 | if (error) { 34 | logger.error('Error while sending push notification: ', error); 35 | } else { 36 | logger.info('Push notification sent successfully!'); 37 | } 38 | }); 39 | } 40 | // Don't forget to return the results from the context.execute() 41 | return results; 42 | }) 43 | .catch(function (error) { 44 | logger.error('Error while running context.execute: ', error); 45 | }); 46 | }); 47 | 48 | module.exports = table; 49 | -------------------------------------------------------------------------------- /example/server/pushGCM.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'pushmeup' 3 | GCM.host = 'https://android.googleapis.com/gcm/send' 4 | GCM.format = :json 5 | GCM.key = "API_KEY_GOES_HERE" 6 | destination = ["REGISTRATION_ID_GOES_HERE"] 7 | data = {:message => "PhoneGap Build rocks!", :msgcnt => "1", :soundname => "beep.wav"} 8 | 9 | GCM.send_notification( destination, data) 10 | -------------------------------------------------------------------------------- /hooks/browser/updateManifest.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context) { 2 | console.log('Updating manifest.json with push properties…'); 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | 6 | var platformProjPath = path.join( 7 | context.opts.projectRoot, 8 | 'platforms/browser' 9 | ); 10 | 11 | if (!fs.existsSync(platformProjPath)) { 12 | platformProjPath = context.opts.projectRoot; 13 | } 14 | 15 | var platformManifestJson = path.join(platformProjPath, 'www/manifest.json'); 16 | 17 | if (!fs.existsSync(platformManifestJson)) { 18 | return; 19 | } 20 | 21 | fs.readFile(platformManifestJson, 'utf8', function(err, platformJson) { 22 | if (err) throw err; // we'll not consider error handling for now 23 | var platformManifest = JSON.parse(platformJson); 24 | 25 | var pluginManifestPath = path.join( 26 | context.opts.projectRoot, 27 | 'plugins/phonegap-plugin-push/src/browser/manifest.json' 28 | ); 29 | 30 | fs.readFile(pluginManifestPath, 'utf8', function(err, pluginJson) { 31 | if (err) throw err; // we'll not consider error handling for now 32 | var pluginManifest = JSON.parse(pluginJson); 33 | 34 | platformManifest['gcm_sender_id'] = pluginManifest['gcm_sender_id']; 35 | 36 | fs.writeFile( 37 | platformManifestJson, 38 | JSON.stringify(platformManifest), 39 | function(err) { 40 | if (err) { 41 | return console.log(err); 42 | } 43 | 44 | console.log('Manifest updated with push sender ID'); 45 | } 46 | ); 47 | }); 48 | }); 49 | }; 50 | -------------------------------------------------------------------------------- /hooks/windows/setToastCapable.js: -------------------------------------------------------------------------------- 1 | module.exports = function(context) { 2 | console.log('Updating appxmanifests with ToastCapable=true ...'); 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | 6 | var platformProjPath = path.join(context.opts.projectRoot, 'platforms/windows'); 7 | if (!fs.existsSync(platformProjPath)) { 8 | platformProjPath = context.opts.projectRoot; 9 | } 10 | 11 | var AppxManifest = require(path.join(platformProjPath, 'cordova/lib/AppxManifest')); 12 | 13 | ['package.phone.appxmanifest', 'package.windows.appxmanifest'].forEach(function(manifestPath) { 14 | var manifest = AppxManifest.get(path.join(platformProjPath, manifestPath)); 15 | manifest.getVisualElements().setToastCapable(true); 16 | manifest.write(); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phonegap-plugin-push", 3 | "description": "Register and receive push notifications.", 4 | "types": "./types/index.d.ts", 5 | "version": "2.3.0", 6 | "homepage": "http://github.com/phonegap/phonegap-plugin-push#readme", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/phonegap/phonegap-plugin-push.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/phonegap/phonegap-plugin-push/issues" 13 | }, 14 | "cordova": { 15 | "id": "phonegap-plugin-push", 16 | "platforms": [ 17 | "ios", 18 | "android", 19 | "windows", 20 | "browser" 21 | ] 22 | }, 23 | "keywords": [ 24 | "ecosystem:cordova", 25 | "ecosystem:phonegap", 26 | "cordova-ios", 27 | "cordova-android", 28 | "cordova-windows8", 29 | "cordova-windows", 30 | "cordova-wp8", 31 | "cordova-browser" 32 | ], 33 | "engines": { 34 | "cordovaDependencies": { 35 | "1.8.2": { 36 | "cordova": ">=3.6.3", 37 | "cordova-android": ">=4.0.0", 38 | "cordova-ios": ">=4.1.0" 39 | }, 40 | "<2.0.0": { 41 | "cordova-ios": ">=4.3.0", 42 | "cordova-android": ">=6.0.0", 43 | "cordova": ">=6.4.0" 44 | }, 45 | "2.0.0": { 46 | "cordova-ios": ">=4.4.0", 47 | "cordova-android": ">=6.2.1", 48 | "cordova": ">=7.0.0" 49 | }, 50 | "<2.1.2": { 51 | "cordova-ios": ">=4.4.0", 52 | "cordova-android": ">=6.3.0", 53 | "cordova": ">=7.1.0" 54 | }, 55 | "<2.2.0": { 56 | "cordova-ios": ">=4.5.0", 57 | "cordova-android": ">=6.3.0", 58 | "cordova": ">=7.1.0" 59 | }, 60 | "2.2.0": { 61 | "cordova-ios": ">=4.5.0", 62 | "cordova-android": ">=7.1.0", 63 | "cordova": ">=7.1.0" 64 | } 65 | } 66 | }, 67 | "author": "Adobe PhoneGap Team", 68 | "license": "MIT", 69 | "scripts": { 70 | "build": "babel src/js --out-dir www", 71 | "build:watch": "nodemon -w ./src/js -e js -x npm run build", 72 | "eslint": "eslint src/js", 73 | "jasmine": "jasmine --config=spec/unit.json", 74 | "precommit-msg": "echo 'Pre-commit checks...' && exit 0", 75 | "test": "npm run build && npm run eslint && npm run jasmine" 76 | }, 77 | "devDependencies": { 78 | "@babel/cli": "^7.5.5", 79 | "@babel/core": "^7.5.5", 80 | "@babel/preset-env": "^7.5.5", 81 | "babel-eslint": "^10.0.3", 82 | "babel-plugin-add-header-comment": "^1.0.3", 83 | "eslint": "^6.2.2", 84 | "eslint-config-airbnb-base": "^14.0.0", 85 | "eslint-plugin-import": "^2.18.2", 86 | "jasmine": "^3.4.0", 87 | "nodemon": "^1.19.1", 88 | "pluginpub": "^0.0.9" 89 | }, 90 | "dependencies": { 91 | "install": "^0.8.2" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | PushPlugin 8 | This plugin allows your application to receive push notifications on Android, iOS and Windows devices. Android uses Firebase Cloud Messaging. iOS uses Apple APNS Notifications. Windows uses Microsoft WNS Notifications. 9 | MIT 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | remote-notification 77 | 78 | 79 | 80 | development 81 | 82 | 83 | production 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /spec/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | jasmine: true -------------------------------------------------------------------------------- /spec/helper/cordova.js: -------------------------------------------------------------------------------- 1 | /* global cordova:true */ 2 | 3 | /*! 4 | * Module dependencies. 5 | */ 6 | 7 | /** 8 | * cordova.js for node. 9 | * 10 | * Think of this as cordova-node, which would be simliar to cordova-android 11 | * or cordova-browser. The purpose of this module is to enable testing 12 | * of a plugin's JavaScript interface. 13 | * 14 | * When this module is first required, it will insert a global cordova 15 | * instance, which can hijack cordova-specific commands within the pluin's 16 | * implementation. 17 | * 18 | * Remember to require this module before the plugin that you want to test. 19 | * 20 | * Example: 21 | * 22 | * var cordova = require('./helper/cordova'), 23 | * myPlugin = require('../www/myPlugin'); 24 | */ 25 | 26 | module.exports = global.cordova = cordova = { 27 | 28 | /** 29 | * cordova.require Mock. 30 | * 31 | * Hijacks all cordova.requires. By default, it returns an empty function. 32 | * You can define your own implementation of each required module before 33 | * or after it has been required. 34 | * 35 | * See `cordova.required` to learn how to add your own module implemtnation. 36 | */ 37 | 38 | require: function(moduleId) { 39 | // define a default function if it doesn't exist 40 | if (!cordova.required[moduleId]) { 41 | cordova.required[moduleId] = function() {}; 42 | } 43 | // create a new module mapping between the module Id and cordova.required. 44 | return new ModuleMap(moduleId); 45 | }, 46 | 47 | /** 48 | * Cordova module implementations. 49 | * 50 | * A key-value hash, where the key is the module such as 'cordova/exec' 51 | * and the value is the function or object returned. 52 | * 53 | * For example: 54 | * 55 | * var exec = require('cordova/exec'); 56 | * 57 | * Will map to: 58 | * 59 | * cordova.required['cordova/exec']; 60 | */ 61 | 62 | required: { 63 | // populated at runtime 64 | } 65 | }; 66 | 67 | /** 68 | * Module Mapper. 69 | * 70 | * Returns a function that when executed will lookup the implementation 71 | * in cordova.required[id]. 72 | * 73 | * @param {String} moduleId is the module name/path, such as 'cordova/exec' 74 | * @return {Function}. 75 | */ 76 | 77 | function ModuleMap(moduleId) { 78 | return function() { 79 | // lookup and execute the module's mock implementation, passing 80 | // in any parameters that were provided. 81 | return cordova.required[moduleId].apply(this, arguments); 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /spec/index.spec.js: -------------------------------------------------------------------------------- 1 | /* globals require */ 2 | 3 | /*! 4 | * Module dependencies. 5 | */ 6 | 7 | const cordova = require('./helper/cordova'); 8 | const PushNotification = require('../www/push'); 9 | let execSpy; 10 | let execWin; 11 | let options; 12 | 13 | /*! 14 | * Specification. 15 | */ 16 | 17 | describe('phonegap-plugin-push', () => { 18 | beforeEach(() => { 19 | options = { android: {}, ios: {}, windows: {} }; 20 | execWin = jasmine.createSpy(); 21 | execSpy = spyOn(cordova.required, 'cordova/exec').and.callFake(execWin); 22 | }); 23 | 24 | describe('PushNotification', () => { 25 | it('should exist', () => { 26 | expect(PushNotification).toBeDefined(); 27 | expect(typeof PushNotification === 'object').toBe(true); 28 | }); 29 | 30 | it('should contain a init function', () => { 31 | expect(PushNotification.init).toBeDefined(); 32 | expect(typeof PushNotification.init === 'function').toBe(true); 33 | }); 34 | 35 | it('should contain a hasPermission function', () => { 36 | expect(PushNotification.hasPermission).toBeDefined(); 37 | expect(typeof PushNotification.hasPermission === 'function').toBe(true); 38 | }); 39 | 40 | it('should contain a createChannel function', () => { 41 | expect(PushNotification.createChannel).toBeDefined(); 42 | expect(typeof PushNotification.createChannel === 'function').toBe(true); 43 | }); 44 | 45 | it('should contain a deleteChannel function', () => { 46 | expect(PushNotification.deleteChannel).toBeDefined(); 47 | expect(typeof PushNotification.deleteChannel === 'function').toBe(true); 48 | }); 49 | 50 | it('should contain a listChannels function', () => { 51 | expect(PushNotification.listChannels).toBeDefined(); 52 | expect(typeof PushNotification.listChannels === 'function').toBe(true); 53 | }); 54 | 55 | it('should contain a unregister function', () => { 56 | const push = PushNotification.init({}); 57 | expect(push.unregister).toBeDefined(); 58 | expect(typeof push.unregister === 'function').toBe(true); 59 | }); 60 | 61 | it('should contain a getApplicationIconBadgeNumber function', () => { 62 | const push = PushNotification.init({}); 63 | expect(push.getApplicationIconBadgeNumber).toBeDefined(); 64 | expect(typeof push.getApplicationIconBadgeNumber === 'function').toBe(true); 65 | }); 66 | 67 | it('should contain a setApplicationIconBadgeNumber function', () => { 68 | const push = PushNotification.init({}); 69 | expect(push.setApplicationIconBadgeNumber).toBeDefined(); 70 | expect(typeof push.setApplicationIconBadgeNumber === 'function').toBe(true); 71 | }); 72 | 73 | it('should contain a clearAllNotifications function', () => { 74 | const push = PushNotification.init({}); 75 | expect(push.clearAllNotifications).toBeDefined(); 76 | expect(typeof push.clearAllNotifications === 'function').toBe(true); 77 | }); 78 | 79 | it('should contain a clearNotification function', () => { 80 | const push = PushNotification.init({}); 81 | expect(push.clearNotification).toBeDefined(); 82 | expect(typeof push.clearNotification === 'function').toBe(true); 83 | }); 84 | 85 | it('should contain a subscribe function', () => { 86 | const push = PushNotification.init({}); 87 | expect(push.subscribe).toBeDefined(); 88 | expect(typeof push.subscribe === 'function').toBe(true); 89 | }); 90 | 91 | it('should contain a unsubscribe function', () => { 92 | const push = PushNotification.init({}); 93 | expect(push.unsubscribe).toBeDefined(); 94 | expect(typeof push.unsubscribe === 'function').toBe(true); 95 | }); 96 | }); 97 | 98 | describe('PushNotification instance', () => { 99 | describe('cordova.exec', () => { 100 | it('should call cordova.exec on next process tick', (done) => { 101 | PushNotification.init(options); 102 | setTimeout(() => { 103 | expect(execSpy).toHaveBeenCalledWith( 104 | jasmine.any(Function), 105 | jasmine.any(Function), 106 | 'PushNotification', 107 | 'init', 108 | jasmine.any(Object) 109 | ); 110 | done(); 111 | }, 100); 112 | }); 113 | }); 114 | 115 | describe('on "registration" event', () => { 116 | it('should be emitted with an argument', (done) => { 117 | execSpy.and.callFake((win, fail, service, id, args) => { 118 | win({ registrationId: 1 }); 119 | }); 120 | const push = PushNotification.init(options); 121 | push.on('registration', (data) => { 122 | expect(data.registrationId).toEqual(1); 123 | done(); 124 | }); 125 | }); 126 | }); 127 | 128 | describe('on "notification" event', () => { 129 | beforeEach(() => { 130 | execSpy.and.callFake((win, fail, service, id, args) => { 131 | win({ 132 | message: 'Message', 133 | title: 'Title', 134 | count: 1, 135 | sound: 'beep', 136 | image: 'Image', 137 | additionalData: {}, 138 | }); 139 | }); 140 | }); 141 | 142 | it('should be emitted on success', (done) => { 143 | const push = PushNotification.init(options); 144 | push.on('notification', (data) => { 145 | done(); 146 | }); 147 | }); 148 | 149 | it('should provide the data.message argument', (done) => { 150 | const push = PushNotification.init(options); 151 | push.on('notification', (data) => { 152 | expect(data.message).toEqual('Message'); 153 | done(); 154 | }); 155 | }); 156 | 157 | it('should provide the data.title argument', (done) => { 158 | const push = PushNotification.init(options); 159 | push.on('notification', (data) => { 160 | expect(data.title).toEqual('Title'); 161 | done(); 162 | }); 163 | }); 164 | 165 | it('should provide the data.count argument', (done) => { 166 | const push = PushNotification.init(options); 167 | push.on('notification', (data) => { 168 | expect(data.count).toEqual(1); 169 | done(); 170 | }); 171 | }); 172 | 173 | it('should provide the data.sound argument', (done) => { 174 | const push = PushNotification.init(options); 175 | push.on('notification', (data) => { 176 | expect(data.sound).toEqual('beep'); 177 | done(); 178 | }); 179 | }); 180 | 181 | it('should provide the data.image argument', (done) => { 182 | const push = PushNotification.init(options); 183 | push.on('notification', (data) => { 184 | expect(data.image).toEqual('Image'); 185 | done(); 186 | }); 187 | }); 188 | 189 | it('should provide the data.additionalData argument', (done) => { 190 | const push = PushNotification.init(options); 191 | push.on('notification', (data) => { 192 | expect(data.additionalData).toEqual({}); 193 | done(); 194 | }); 195 | }); 196 | }); 197 | 198 | describe('on "error" event', () => { 199 | it('should be emitted with an Error', (done) => { 200 | execSpy.and.callFake((win, fail, service, id, args) => { 201 | fail('something went wrong'); 202 | }); 203 | const push = PushNotification.init(options); 204 | push.on('error', (e) => { 205 | expect(e).toEqual(jasmine.any(Error)); 206 | expect(e.message).toEqual('something went wrong'); 207 | done(); 208 | }); 209 | }); 210 | }); 211 | 212 | describe('off "notification" event', () => { 213 | it('should exist and be registered a callback handle', (done) => { 214 | const push = PushNotification.init(options), 215 | eventHandler = () => {}; 216 | 217 | push.on('notification', eventHandler); 218 | 219 | push.off('notification', eventHandler); 220 | 221 | expect(push.handlers.notification.indexOf(eventHandler)).toEqual(-1); 222 | done(); 223 | }); 224 | }); 225 | 226 | describe('off "registration" event', () => { 227 | it('should exist and be registered a callback handle', (done) => { 228 | const push = PushNotification.init(options), 229 | eventHandler = () => {}; 230 | 231 | push.on('registration', eventHandler); 232 | 233 | push.off('registration', eventHandler); 234 | 235 | expect(push.handlers.registration.indexOf(eventHandler)).toEqual(-1); 236 | done(); 237 | }); 238 | }); 239 | 240 | describe('off "error" event', () => { 241 | it('should exist and be registered a callback handle', (done) => { 242 | const push = PushNotification.init(options), 243 | eventHandler = () => {}; 244 | 245 | push.on('error', eventHandler); 246 | push.off('error', eventHandler); 247 | 248 | expect(push.handlers.error.indexOf(eventHandler)).toEqual(-1); 249 | done(); 250 | }); 251 | }); 252 | 253 | describe('unregister method', () => { 254 | it('should clear "registration" event handlers', (done) => { 255 | const push = PushNotification.init(options); 256 | const eventHandler = () => {}; 257 | 258 | expect(push.handlers.registration.length).toEqual(0); 259 | 260 | push.on('registration', eventHandler); 261 | 262 | expect(push.handlers.registration.length).toEqual(1); 263 | expect(push.handlers.registration.indexOf(eventHandler)).toBeGreaterThan(-1); 264 | 265 | execSpy.and.callFake((win, fail, service, id, args) => { 266 | win(); 267 | }); 268 | push.unregister(() => { 269 | expect(push.handlers.registration.length).toEqual(0); 270 | expect(push.handlers.registration.indexOf(eventHandler)).toEqual(-1); 271 | done(); 272 | }); 273 | }); 274 | 275 | it('should clear "notification" event handlers', (done) => { 276 | const push = PushNotification.init(options); 277 | const eventHandler = () => {}; 278 | 279 | expect(push.handlers.notification.length).toEqual(0); 280 | 281 | push.on('notification', eventHandler); 282 | 283 | expect(push.handlers.notification.length).toEqual(1); 284 | expect(push.handlers.notification.indexOf(eventHandler)).toBeGreaterThan(-1); 285 | 286 | execSpy.and.callFake((win, fail, service, id, args) => { 287 | win(); 288 | }); 289 | push.unregister(() => { 290 | expect(push.handlers.notification.length).toEqual(0); 291 | expect(push.handlers.notification.indexOf(eventHandler)).toEqual(-1); 292 | done(); 293 | }); 294 | }); 295 | 296 | it('should clear "error" event handlers', (done) => { 297 | const push = PushNotification.init(options); 298 | const eventHandler = () => {}; 299 | 300 | expect(push.handlers.error.length).toEqual(0); 301 | 302 | push.on('error', eventHandler); 303 | 304 | expect(push.handlers.error.length).toEqual(1); 305 | expect(push.handlers.error.indexOf(eventHandler)).toBeGreaterThan(-1); 306 | 307 | execSpy.and.callFake((win, fail, service, id, args) => { 308 | win(); 309 | }); 310 | push.unregister(() => { 311 | expect(push.handlers.error.length).toEqual(0); 312 | expect(push.handlers.error.indexOf(eventHandler)).toEqual(-1); 313 | done(); 314 | }); 315 | }); 316 | }); 317 | 318 | describe('unregister topics method', () => { 319 | it('should not clear "registration" event handlers', (done) => { 320 | const push = PushNotification.init(options); 321 | const eventHandler = () => {}; 322 | 323 | expect(push.handlers.registration.length).toEqual(0); 324 | 325 | push.on('registration', eventHandler); 326 | 327 | expect(push.handlers.registration.length).toEqual(1); 328 | expect(push.handlers.registration.indexOf(eventHandler)).toBeGreaterThan(-1); 329 | 330 | execSpy.and.callFake((win, fail, service, id, args) => { 331 | win(); 332 | }); 333 | push.unregister( 334 | () => { 335 | expect(push.handlers.registration.length).toEqual(1); 336 | expect(push.handlers.registration.indexOf(eventHandler)).toBeGreaterThan(-1); 337 | done(); 338 | }, 339 | () => {}, 340 | ['foo', 'bar'] 341 | ); 342 | }); 343 | 344 | it('should not clear "notification" event handlers', (done) => { 345 | const push = PushNotification.init(options); 346 | const eventHandler = () => {}; 347 | 348 | expect(push.handlers.notification.length).toEqual(0); 349 | 350 | push.on('notification', eventHandler); 351 | 352 | expect(push.handlers.notification.length).toEqual(1); 353 | expect(push.handlers.notification.indexOf(eventHandler)).toBeGreaterThan(-1); 354 | 355 | execSpy.and.callFake((win, fail, service, id, args) => { 356 | win(); 357 | }); 358 | push.unregister( 359 | () => { 360 | expect(push.handlers.notification.length).toEqual(1); 361 | expect(push.handlers.notification.indexOf(eventHandler)).toBeGreaterThan(-1); 362 | done(); 363 | }, 364 | () => {}, 365 | ['foo', 'bar'] 366 | ); 367 | }); 368 | 369 | it('should not clear "error" event handlers', (done) => { 370 | const push = PushNotification.init(options); 371 | const eventHandler = () => {}; 372 | 373 | expect(push.handlers.error.length).toEqual(0); 374 | 375 | push.on('error', eventHandler); 376 | 377 | expect(push.handlers.error.length).toEqual(1); 378 | expect(push.handlers.error.indexOf(eventHandler)).toBeGreaterThan(-1); 379 | 380 | execSpy.and.callFake((win, fail, service, id, args) => { 381 | win(); 382 | }); 383 | push.unregister( 384 | () => { 385 | expect(push.handlers.error.length).toEqual(1); 386 | expect(push.handlers.error.indexOf(eventHandler)).toBeGreaterThan(-1); 387 | done(); 388 | }, 389 | () => {}, 390 | ['foo', 'bar'] 391 | ); 392 | }); 393 | }); 394 | 395 | describe('subscribe topic method', () => { 396 | describe('cordova.exec', () => { 397 | it('should call cordova.exec on next process tick', (done) => { 398 | const push = PushNotification.init(options); 399 | push.subscribe('foo', () => {}, () => {}); 400 | setTimeout(() => { 401 | expect(execSpy).toHaveBeenCalledWith( 402 | jasmine.any(Function), 403 | jasmine.any(Function), 404 | 'PushNotification', 405 | 'subscribe', 406 | jasmine.any(Object) 407 | ); 408 | done(); 409 | }, 100); 410 | }); 411 | }); 412 | }); 413 | 414 | describe('unsubscribe topic method', () => { 415 | describe('cordova.exec', () => { 416 | it('should call cordova.exec on next process tick', (done) => { 417 | const push = PushNotification.init(options); 418 | push.unsubscribe('foo', () => {}, () => {}); 419 | setTimeout(() => { 420 | expect(execSpy).toHaveBeenCalledWith( 421 | jasmine.any(Function), 422 | jasmine.any(Function), 423 | 'PushNotification', 424 | 'unsubscribe', 425 | jasmine.any(Object) 426 | ); 427 | done(); 428 | }, 100); 429 | }); 430 | }); 431 | }); 432 | 433 | describe('clear notification method', () => { 434 | describe('cordova.exec', () => { 435 | it('should call cordova.exec on next process tick using number argument', (done) => { 436 | const push = PushNotification.init(options); 437 | push.clearNotification(() => {}, () => {}, 145); 438 | setTimeout(() => { 439 | expect(execSpy).toHaveBeenCalledWith( 440 | jasmine.any(Function), 441 | jasmine.any(Function), 442 | 'PushNotification', 443 | 'clearNotification', 444 | [145] 445 | ); 446 | done(); 447 | }, 100); 448 | }); 449 | 450 | it('should call cordova.exec on next process tick using string argument', (done) => { 451 | const push = PushNotification.init(options); 452 | push.clearNotification(() => {}, () => {}, '145'); 453 | setTimeout(() => { 454 | expect(execSpy).toHaveBeenCalledWith( 455 | jasmine.any(Function), 456 | jasmine.any(Function), 457 | 'PushNotification', 458 | 'clearNotification', 459 | [145] 460 | ); 461 | done(); 462 | }, 100); 463 | }); 464 | }); 465 | }); 466 | }); 467 | }); 468 | -------------------------------------------------------------------------------- /spec/unit.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "stopSpecOnExpectationFailure": false, 7 | "random": false 8 | } -------------------------------------------------------------------------------- /src/android/com/adobe/phonegap/push/BackgroundActionButtonHandler.java: -------------------------------------------------------------------------------- 1 | package com.adobe.phonegap.push; 2 | 3 | import android.app.NotificationManager; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.util.Log; 9 | import android.support.v4.app.RemoteInput; 10 | 11 | public class BackgroundActionButtonHandler extends BroadcastReceiver implements PushConstants { 12 | private static String LOG_TAG = "Push_BGActionButton"; 13 | 14 | @Override 15 | public void onReceive(Context context, Intent intent) { 16 | Bundle extras = intent.getExtras(); 17 | Log.d(LOG_TAG, "BackgroundActionButtonHandler = " + extras); 18 | 19 | int notId = intent.getIntExtra(NOT_ID, 0); 20 | Log.d(LOG_TAG, "not id = " + notId); 21 | NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 22 | notificationManager.cancel(FCMService.getAppName(context), notId); 23 | 24 | if (extras != null) { 25 | Bundle originalExtras = extras.getBundle(PUSH_BUNDLE); 26 | 27 | originalExtras.putBoolean(FOREGROUND, false); 28 | originalExtras.putBoolean(COLDSTART, false); 29 | originalExtras.putString(ACTION_CALLBACK, extras.getString(CALLBACK)); 30 | 31 | Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); 32 | if (remoteInput != null) { 33 | String inputString = remoteInput.getCharSequence(INLINE_REPLY).toString(); 34 | Log.d(LOG_TAG, "response: " + inputString); 35 | originalExtras.putString(INLINE_REPLY, inputString); 36 | } 37 | 38 | PushPlugin.sendExtras(originalExtras); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/android/com/adobe/phonegap/push/PushConstants.java: -------------------------------------------------------------------------------- 1 | package com.adobe.phonegap.push; 2 | 3 | public interface PushConstants { 4 | public static final String COM_ADOBE_PHONEGAP_PUSH = "com.adobe.phonegap.push"; 5 | public static final String REGISTRATION_ID = "registrationId"; 6 | public static final String REGISTRATION_TYPE = "registrationType"; 7 | public static final String FOREGROUND = "foreground"; 8 | public static final String TITLE = "title"; 9 | public static final String NOT_ID = "notId"; 10 | public static final String PUSH_BUNDLE = "pushBundle"; 11 | public static final String ICON = "icon"; 12 | public static final String ICON_COLOR = "iconColor"; 13 | public static final String SOUND = "sound"; 14 | public static final String SOUND_DEFAULT = "default"; 15 | public static final String SOUND_RINGTONE = "ringtone"; 16 | public static final String VIBRATE = "vibrate"; 17 | public static final String ACTIONS = "actions"; 18 | public static final String CALLBACK = "callback"; 19 | public static final String ACTION_CALLBACK = "actionCallback"; 20 | public static final String DRAWABLE = "drawable"; 21 | public static final String MSGCNT = "msgcnt"; 22 | public static final String VIBRATION_PATTERN = "vibrationPattern"; 23 | public static final String STYLE = "style"; 24 | public static final String SUMMARY_TEXT = "summaryText"; 25 | public static final String PICTURE = "picture"; 26 | public static final String GCM_N = "gcm.n."; 27 | public static final String GCM_NOTIFICATION = "gcm.notification"; 28 | public static final String GCM_NOTIFICATION_BODY = "gcm.notification.body"; 29 | public static final String UA_PREFIX = "com.urbanairship.push"; 30 | public static final String PARSE_COM_DATA = "data"; 31 | public static final String ALERT = "alert"; 32 | public static final String MESSAGE = "message"; 33 | public static final String BODY = "body"; 34 | public static final String SOUNDNAME = "soundname"; 35 | public static final String COLOR = "color"; 36 | public static final String LED_COLOR = "ledColor"; 37 | public static final String PRIORITY = "priority"; 38 | public static final String IMAGE = "image"; 39 | public static final String STYLE_INBOX = "inbox"; 40 | public static final String STYLE_PICTURE = "picture"; 41 | public static final String STYLE_TEXT = "text"; 42 | public static final String BADGE = "badge"; 43 | public static final String INITIALIZE = "init"; 44 | public static final String SUBSCRIBE = "subscribe"; 45 | public static final String UNSUBSCRIBE = "unsubscribe"; 46 | public static final String UNREGISTER = "unregister"; 47 | public static final String EXIT = "exit"; 48 | public static final String FINISH = "finish"; 49 | public static final String HAS_PERMISSION = "hasPermission"; 50 | public static final String ANDROID = "android"; 51 | public static final String SENDER_ID = "senderID"; 52 | public static final String CLEAR_BADGE = "clearBadge"; 53 | public static final String CLEAR_NOTIFICATIONS = "clearNotifications"; 54 | public static final String COLDSTART = "coldstart"; 55 | public static final String ADDITIONAL_DATA = "additionalData"; 56 | public static final String COUNT = "count"; 57 | public static final String FROM = "from"; 58 | public static final String COLLAPSE_KEY = "collapse_key"; 59 | public static final String FORCE_SHOW = "forceShow"; 60 | public static final String FCM = "FCM"; 61 | public static final String CONTENT_AVAILABLE = "content-available"; 62 | public static final String TOPICS = "topics"; 63 | public static final String SET_APPLICATION_ICON_BADGE_NUMBER = "setApplicationIconBadgeNumber"; 64 | public static final String GET_APPLICATION_ICON_BADGE_NUMBER = "getApplicationIconBadgeNumber"; 65 | public static final String CLEAR_ALL_NOTIFICATIONS = "clearAllNotifications"; 66 | public static final String VISIBILITY = "visibility"; 67 | public static final String INLINE_REPLY = "inlineReply"; 68 | public static final String INLINE_REPLY_LABEL = "replyLabel"; 69 | public static final String LOC_KEY = "locKey"; 70 | public static final String LOC_DATA = "locData"; 71 | public static final String TWILIO_BODY = "twi_body"; 72 | public static final String TWILIO_TITLE = "twi_title"; 73 | public static final String TWILIO_SOUND = "twi_sound"; 74 | public static final String AWS_PINPOINT_BODY = "pinpoint.notification.body"; 75 | public static final String AWS_PINPOINT_PICTURE = "pinpoint.notification.imageUrl"; 76 | public static final String AWS_PINPOINT_PREFIX = "pinpoint.notification"; 77 | public static final String MP_MESSAGE = "mp_message"; 78 | public static final String START_IN_BACKGROUND = "cdvStartInBackground"; 79 | public static final String FORCE_START = "force-start"; 80 | public static final String MESSAGE_KEY = "messageKey"; 81 | public static final String TITLE_KEY = "titleKey"; 82 | public static final String NO_CACHE = "no-cache"; 83 | public static final String DISMISSED = "dismissed"; 84 | public static final String IMAGE_TYPE = "image-type"; 85 | public static final String IMAGE_TYPE_SQUARE = "square"; 86 | public static final String IMAGE_TYPE_CIRCLE = "circle"; 87 | public static final String SUBJECT = "subject"; 88 | public static final String GOOGLE_APP_ID = "google_app_id"; 89 | public static final String GCM_DEFAULT_SENDER_ID = "gcm_defaultSenderId"; 90 | public static final String PUSH_DISMISSED = "push_dismissed"; 91 | public static final String DEFAULT_CHANNEL_ID = "PushPluginChannel"; 92 | public static final String CHANNELS = "channels"; 93 | public static final String CHANNEL_ID = "id"; 94 | public static final String CHANNEL_DESCRIPTION = "description"; 95 | public static final String CHANNEL_IMPORTANCE = "importance"; 96 | public static final String CHANNEL_LIGHT_COLOR = "lightColor"; 97 | public static final String CHANNEL_VIBRATION = "vibration"; 98 | public static final String ANDROID_CHANNEL_ID = "android_channel_id"; 99 | public static final String CHANNEL_STATE = "state"; 100 | public static final String CREATE_CHANNEL = "createChannel"; 101 | public static final String DELETE_CHANNEL = "deleteChannel"; 102 | public static final String ONGOING = "ongoing"; 103 | public static final String LIST_CHANNELS = "listChannels"; 104 | public static final String CLEAR_NOTIFICATION = "clearNotification"; 105 | } 106 | -------------------------------------------------------------------------------- /src/android/com/adobe/phonegap/push/PushDismissedHandler.java: -------------------------------------------------------------------------------- 1 | package com.adobe.phonegap.push; 2 | import android.content.BroadcastReceiver; 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | 8 | public class PushDismissedHandler extends BroadcastReceiver implements PushConstants { 9 | private static String LOG_TAG = "Push_DismissedHandler"; 10 | 11 | @Override 12 | public void onReceive(Context context, Intent intent) { 13 | Bundle extras = intent.getExtras(); 14 | FCMService fcm = new FCMService(); 15 | String action = intent.getAction(); 16 | int notID = intent.getIntExtra(NOT_ID, 0); 17 | 18 | if (action.equals(PUSH_DISMISSED)) { 19 | Log.d(LOG_TAG, "PushDismissedHandler = " + extras); 20 | Log.d(LOG_TAG, "not id = " + notID); 21 | 22 | fcm.setNotification(notID, ""); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/android/com/adobe/phonegap/push/PushHandlerActivity.java: -------------------------------------------------------------------------------- 1 | package com.adobe.phonegap.push; 2 | 3 | import android.app.Activity; 4 | import android.app.NotificationManager; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.os.Bundle; 9 | import android.util.Log; 10 | import android.support.v4.app.RemoteInput; 11 | 12 | 13 | public class PushHandlerActivity extends Activity implements PushConstants { 14 | private static String LOG_TAG = "Push_HandlerActivity"; 15 | 16 | /* 17 | * this activity will be started if the user touches a notification that we own. 18 | * We send it's data off to the push plugin for processing. 19 | * If needed, we boot up the main activity to kickstart the application. 20 | * @see android.app.Activity#onCreate(android.os.Bundle) 21 | */ 22 | @Override 23 | public void onCreate(Bundle savedInstanceState) { 24 | FCMService gcm = new FCMService(); 25 | 26 | Intent intent = getIntent(); 27 | 28 | int notId = intent.getExtras().getInt(NOT_ID, 0); 29 | Log.d(LOG_TAG, "not id = " + notId); 30 | gcm.setNotification(notId, ""); 31 | super.onCreate(savedInstanceState); 32 | Log.v(LOG_TAG, "onCreate"); 33 | String callback = getIntent().getExtras().getString("callback"); 34 | Log.d(LOG_TAG, "callback = " + callback); 35 | boolean foreground = getIntent().getExtras().getBoolean("foreground", true); 36 | boolean startOnBackground = getIntent().getExtras().getBoolean(START_IN_BACKGROUND, false); 37 | boolean dismissed = getIntent().getExtras().getBoolean(DISMISSED, false); 38 | Log.d(LOG_TAG, "dismissed = " + dismissed); 39 | 40 | if(!startOnBackground){ 41 | NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 42 | notificationManager.cancel(FCMService.getAppName(this), notId); 43 | } 44 | 45 | boolean isPushPluginActive = PushPlugin.isActive(); 46 | boolean inline = processPushBundle(isPushPluginActive, intent); 47 | 48 | if(inline && android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.N && !startOnBackground){ 49 | foreground = true; 50 | } 51 | 52 | Log.d(LOG_TAG, "bringToForeground = " + foreground); 53 | 54 | finish(); 55 | 56 | if(!dismissed) { 57 | Log.d(LOG_TAG, "isPushPluginActive = " + isPushPluginActive); 58 | if (!isPushPluginActive && foreground && inline) { 59 | Log.d(LOG_TAG, "forceMainActivityReload"); 60 | forceMainActivityReload(false); 61 | } else if(startOnBackground) { 62 | Log.d(LOG_TAG, "startOnBackgroundTrue"); 63 | forceMainActivityReload(true); 64 | } else { 65 | Log.d(LOG_TAG, "don't want main activity"); 66 | } 67 | } 68 | } 69 | 70 | /** 71 | * Takes the pushBundle extras from the intent, 72 | * and sends it through to the PushPlugin for processing. 73 | */ 74 | private boolean processPushBundle(boolean isPushPluginActive, Intent intent) { 75 | Bundle extras = getIntent().getExtras(); 76 | Bundle remoteInput = null; 77 | 78 | if (extras != null) { 79 | Bundle originalExtras = extras.getBundle(PUSH_BUNDLE); 80 | 81 | originalExtras.putBoolean(FOREGROUND, false); 82 | originalExtras.putBoolean(COLDSTART, !isPushPluginActive); 83 | originalExtras.putBoolean(DISMISSED, extras.getBoolean(DISMISSED)); 84 | originalExtras.putString(ACTION_CALLBACK, extras.getString(CALLBACK)); 85 | originalExtras.remove(NO_CACHE); 86 | 87 | remoteInput = RemoteInput.getResultsFromIntent(intent); 88 | if (remoteInput != null) { 89 | String inputString = remoteInput.getCharSequence(INLINE_REPLY).toString(); 90 | Log.d(LOG_TAG, "response: " + inputString); 91 | originalExtras.putString(INLINE_REPLY, inputString); 92 | } 93 | 94 | PushPlugin.sendExtras(originalExtras); 95 | } 96 | return remoteInput == null; 97 | } 98 | 99 | /** 100 | * Forces the main activity to re-launch if it's unloaded. 101 | */ 102 | private void forceMainActivityReload(boolean startOnBackground) { 103 | PackageManager pm = getPackageManager(); 104 | Intent launchIntent = pm.getLaunchIntentForPackage(getApplicationContext().getPackageName()); 105 | 106 | Bundle extras = getIntent().getExtras(); 107 | if (extras != null) { 108 | Bundle originalExtras = extras.getBundle(PUSH_BUNDLE); 109 | if (originalExtras != null) { 110 | launchIntent.putExtras(originalExtras); 111 | } 112 | launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 113 | launchIntent.addFlags(Intent.FLAG_FROM_BACKGROUND); 114 | launchIntent.putExtra(START_IN_BACKGROUND, startOnBackground); 115 | } 116 | 117 | startActivity(launchIntent); 118 | } 119 | 120 | @Override 121 | protected void onResume() { 122 | super.onResume(); 123 | final NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); 124 | notificationManager.cancelAll(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/android/com/adobe/phonegap/push/PushInstanceIDListenerService.java: -------------------------------------------------------------------------------- 1 | package com.adobe.phonegap.push; 2 | 3 | import android.content.Intent; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.util.Log; 7 | 8 | import com.google.firebase.iid.FirebaseInstanceId; 9 | import com.google.firebase.iid.FirebaseInstanceIdService; 10 | 11 | import org.json.JSONException; 12 | 13 | import java.io.IOException; 14 | 15 | public class PushInstanceIDListenerService extends FirebaseInstanceIdService implements PushConstants { 16 | public static final String LOG_TAG = "Push_InsIdService"; 17 | 18 | @Override 19 | public void onTokenRefresh() { 20 | // Get updated InstanceID token. 21 | String refreshedToken = FirebaseInstanceId.getInstance().getToken(); 22 | Log.d(LOG_TAG, "Refreshed token: " + refreshedToken); 23 | // TODO: Implement this method to send any registration to your app's servers. 24 | //sendRegistrationToServer(refreshedToken); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/android/com/adobe/phonegap/push/PushPlugin.java: -------------------------------------------------------------------------------- 1 | package com.adobe.phonegap.push; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.app.NotificationChannel; 6 | import android.app.NotificationManager; 7 | import android.content.ContentResolver; 8 | import android.content.Context; 9 | import android.content.SharedPreferences; 10 | import android.content.res.Resources; 11 | import android.media.AudioAttributes; 12 | import android.net.Uri; 13 | import android.os.Build; 14 | import android.os.Bundle; 15 | import android.support.v4.app.NotificationCompat; 16 | import android.support.v4.app.NotificationManagerCompat; 17 | import android.util.Log; 18 | 19 | import com.google.firebase.iid.FirebaseInstanceId; 20 | import com.google.firebase.messaging.FirebaseMessaging; 21 | 22 | import org.apache.cordova.CallbackContext; 23 | import org.apache.cordova.CordovaInterface; 24 | import org.apache.cordova.CordovaPlugin; 25 | import org.apache.cordova.CordovaWebView; 26 | import org.apache.cordova.PluginResult; 27 | import org.json.JSONArray; 28 | import org.json.JSONException; 29 | import org.json.JSONObject; 30 | 31 | import java.io.IOException; 32 | import java.util.Collections; 33 | import java.util.HashSet; 34 | import java.util.Iterator; 35 | import java.util.ArrayList; 36 | import java.util.List; 37 | 38 | import me.leolin.shortcutbadger.ShortcutBadger; 39 | 40 | public class PushPlugin extends CordovaPlugin implements PushConstants { 41 | 42 | public static final String LOG_TAG = "Push_Plugin"; 43 | 44 | private static CallbackContext pushContext; 45 | private static CordovaWebView gWebView; 46 | private static List gCachedExtras = Collections.synchronizedList(new ArrayList()); 47 | private static boolean gForeground = false; 48 | 49 | private static String registration_id = ""; 50 | 51 | /** 52 | * Gets the application context from cordova's main activity. 53 | * 54 | * @return the application context 55 | */ 56 | private Context getApplicationContext() { 57 | return this.cordova.getActivity().getApplicationContext(); 58 | } 59 | 60 | @TargetApi(26) 61 | private JSONArray listChannels() throws JSONException { 62 | JSONArray channels = new JSONArray(); 63 | // only call on Android O and above 64 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 65 | final NotificationManager notificationManager = (NotificationManager) cordova.getActivity() 66 | .getSystemService(Context.NOTIFICATION_SERVICE); 67 | List notificationChannels = notificationManager.getNotificationChannels(); 68 | for (NotificationChannel notificationChannel : notificationChannels) { 69 | JSONObject channel = new JSONObject(); 70 | channel.put(CHANNEL_ID, notificationChannel.getId()); 71 | channel.put(CHANNEL_DESCRIPTION, notificationChannel.getDescription()); 72 | channels.put(channel); 73 | } 74 | } 75 | return channels; 76 | } 77 | 78 | @TargetApi(26) 79 | private void deleteChannel(String channelId) { 80 | // only call on Android O and above 81 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 82 | final NotificationManager notificationManager = (NotificationManager) cordova.getActivity() 83 | .getSystemService(Context.NOTIFICATION_SERVICE); 84 | notificationManager.deleteNotificationChannel(channelId); 85 | } 86 | } 87 | 88 | @TargetApi(26) 89 | private void createChannel(JSONObject channel) throws JSONException { 90 | // only call on Android O and above 91 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 92 | final NotificationManager notificationManager = (NotificationManager) cordova.getActivity() 93 | .getSystemService(Context.NOTIFICATION_SERVICE); 94 | 95 | String packageName = getApplicationContext().getPackageName(); 96 | NotificationChannel mChannel = new NotificationChannel(channel.getString(CHANNEL_ID), 97 | channel.optString(CHANNEL_DESCRIPTION, ""), 98 | channel.optInt(CHANNEL_IMPORTANCE, NotificationManager.IMPORTANCE_DEFAULT)); 99 | 100 | int lightColor = channel.optInt(CHANNEL_LIGHT_COLOR, -1); 101 | if (lightColor != -1) { 102 | mChannel.setLightColor(lightColor); 103 | } 104 | 105 | int visibility = channel.optInt(VISIBILITY, NotificationCompat.VISIBILITY_PUBLIC); 106 | mChannel.setLockscreenVisibility(visibility); 107 | 108 | boolean badge = channel.optBoolean(BADGE, true); 109 | mChannel.setShowBadge(badge); 110 | 111 | String sound = channel.optString(SOUND, "default"); 112 | AudioAttributes audioAttributes = new AudioAttributes.Builder() 113 | .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 114 | .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build(); 115 | if (SOUND_RINGTONE.equals(sound)) { 116 | mChannel.setSound(android.provider.Settings.System.DEFAULT_RINGTONE_URI, audioAttributes); 117 | } else if (sound != null && sound.isEmpty()) { 118 | // Disable sound for this notification channel if an empty string is passed. 119 | // https://stackoverflow.com/a/47144981/6194193 120 | mChannel.setSound(null, null); 121 | } else if (sound != null && !sound.contentEquals(SOUND_DEFAULT)) { 122 | Uri soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + packageName + "/raw/" + sound); 123 | mChannel.setSound(soundUri, audioAttributes); 124 | } else { 125 | mChannel.setSound(android.provider.Settings.System.DEFAULT_NOTIFICATION_URI, audioAttributes); 126 | } 127 | 128 | // If vibration settings is an array set vibration pattern, else set enable 129 | // vibration. 130 | JSONArray pattern = channel.optJSONArray(CHANNEL_VIBRATION); 131 | if (pattern != null) { 132 | int patternLength = pattern.length(); 133 | long[] patternArray = new long[patternLength]; 134 | for (int i = 0; i < patternLength; i++) { 135 | patternArray[i] = pattern.optLong(i); 136 | } 137 | mChannel.setVibrationPattern(patternArray); 138 | } else { 139 | boolean vibrate = channel.optBoolean(CHANNEL_VIBRATION, true); 140 | mChannel.enableVibration(vibrate); 141 | } 142 | 143 | notificationManager.createNotificationChannel(mChannel); 144 | } 145 | } 146 | 147 | @TargetApi(26) 148 | private void createDefaultNotificationChannelIfNeeded(JSONObject options) { 149 | String id; 150 | // only call on Android O and above 151 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 152 | final NotificationManager notificationManager = (NotificationManager) cordova.getActivity() 153 | .getSystemService(Context.NOTIFICATION_SERVICE); 154 | List channels = notificationManager.getNotificationChannels(); 155 | 156 | for (int i = 0; i < channels.size(); i++) { 157 | id = channels.get(i).getId(); 158 | if (id.equals(DEFAULT_CHANNEL_ID)) { 159 | return; 160 | } 161 | } 162 | try { 163 | options.put(CHANNEL_ID, DEFAULT_CHANNEL_ID); 164 | options.putOpt(CHANNEL_DESCRIPTION, "PhoneGap PushPlugin"); 165 | createChannel(options); 166 | } catch (JSONException e) { 167 | Log.e(LOG_TAG, "execute: Got JSON Exception " + e.getMessage()); 168 | } 169 | } 170 | } 171 | 172 | @Override 173 | public boolean execute(final String action, final JSONArray data, final CallbackContext callbackContext) { 174 | Log.v(LOG_TAG, "execute: action=" + action); 175 | gWebView = this.webView; 176 | 177 | if (INITIALIZE.equals(action)) { 178 | cordova.getThreadPool().execute(new Runnable() { 179 | public void run() { 180 | pushContext = callbackContext; 181 | JSONObject jo = null; 182 | 183 | Log.v(LOG_TAG, "execute: data=" + data.toString()); 184 | SharedPreferences sharedPref = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, 185 | Context.MODE_PRIVATE); 186 | String token = null; 187 | String senderID = null; 188 | 189 | try { 190 | jo = data.getJSONObject(0).getJSONObject(ANDROID); 191 | 192 | // If no NotificationChannels exist create the default one 193 | createDefaultNotificationChannelIfNeeded(jo); 194 | 195 | Log.v(LOG_TAG, "execute: jo=" + jo.toString()); 196 | 197 | senderID = getStringResourceByName(GCM_DEFAULT_SENDER_ID); 198 | 199 | Log.v(LOG_TAG, "execute: senderID=" + senderID); 200 | 201 | try { 202 | token = FirebaseInstanceId.getInstance().getToken(); 203 | } catch (IllegalStateException e) { 204 | Log.e(LOG_TAG, "Exception raised while getting Firebase token " + e.getMessage()); 205 | } 206 | 207 | if (token == null) { 208 | try { 209 | token = FirebaseInstanceId.getInstance().getToken(senderID, FCM); 210 | } catch (IllegalStateException e) { 211 | Log.e(LOG_TAG, "Exception raised while getting Firebase token " + e.getMessage()); 212 | } 213 | } 214 | 215 | if (!"".equals(token)) { 216 | JSONObject json = new JSONObject().put(REGISTRATION_ID, token); 217 | json.put(REGISTRATION_TYPE, FCM); 218 | 219 | Log.v(LOG_TAG, "onRegistered: " + json.toString()); 220 | 221 | JSONArray topics = jo.optJSONArray(TOPICS); 222 | subscribeToTopics(topics, registration_id); 223 | 224 | PushPlugin.sendEvent(json); 225 | } else { 226 | callbackContext.error("Empty registration ID received from FCM"); 227 | return; 228 | } 229 | } catch (JSONException e) { 230 | Log.e(LOG_TAG, "execute: Got JSON Exception " + e.getMessage()); 231 | callbackContext.error(e.getMessage()); 232 | } catch (IOException e) { 233 | Log.e(LOG_TAG, "execute: Got IO Exception " + e.getMessage()); 234 | callbackContext.error(e.getMessage()); 235 | } catch (Resources.NotFoundException e) { 236 | 237 | Log.e(LOG_TAG, "execute: Got Resources NotFoundException " + e.getMessage()); 238 | callbackContext.error(e.getMessage()); 239 | } 240 | 241 | if (jo != null) { 242 | SharedPreferences.Editor editor = sharedPref.edit(); 243 | try { 244 | editor.putString(ICON, jo.getString(ICON)); 245 | } catch (JSONException e) { 246 | Log.d(LOG_TAG, "no icon option"); 247 | } 248 | try { 249 | editor.putString(ICON_COLOR, jo.getString(ICON_COLOR)); 250 | } catch (JSONException e) { 251 | Log.d(LOG_TAG, "no iconColor option"); 252 | } 253 | 254 | boolean clearBadge = jo.optBoolean(CLEAR_BADGE, false); 255 | if (clearBadge) { 256 | setApplicationIconBadgeNumber(getApplicationContext(), 0); 257 | } 258 | 259 | editor.putBoolean(SOUND, jo.optBoolean(SOUND, true)); 260 | editor.putBoolean(VIBRATE, jo.optBoolean(VIBRATE, true)); 261 | editor.putBoolean(CLEAR_BADGE, clearBadge); 262 | editor.putBoolean(CLEAR_NOTIFICATIONS, jo.optBoolean(CLEAR_NOTIFICATIONS, true)); 263 | editor.putBoolean(FORCE_SHOW, jo.optBoolean(FORCE_SHOW, false)); 264 | editor.putString(SENDER_ID, senderID); 265 | editor.putString(MESSAGE_KEY, jo.optString(MESSAGE_KEY)); 266 | editor.putString(TITLE_KEY, jo.optString(TITLE_KEY)); 267 | editor.commit(); 268 | 269 | } 270 | 271 | if (!gCachedExtras.isEmpty()) { 272 | Log.v(LOG_TAG, "sending cached extras"); 273 | synchronized (gCachedExtras) { 274 | Iterator gCachedExtrasIterator = gCachedExtras.iterator(); 275 | while (gCachedExtrasIterator.hasNext()) { 276 | sendExtras(gCachedExtrasIterator.next()); 277 | } 278 | } 279 | gCachedExtras.clear(); 280 | } 281 | } 282 | }); 283 | } else if (UNREGISTER.equals(action)) { 284 | cordova.getThreadPool().execute(new Runnable() { 285 | public void run() { 286 | try { 287 | SharedPreferences sharedPref = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, 288 | Context.MODE_PRIVATE); 289 | JSONArray topics = data.optJSONArray(0); 290 | if (topics != null && !"".equals(registration_id)) { 291 | unsubscribeFromTopics(topics, registration_id); 292 | } else { 293 | FirebaseInstanceId.getInstance().deleteInstanceId(); 294 | Log.v(LOG_TAG, "UNREGISTER"); 295 | 296 | // Remove shared prefs 297 | SharedPreferences.Editor editor = sharedPref.edit(); 298 | editor.remove(SOUND); 299 | editor.remove(VIBRATE); 300 | editor.remove(CLEAR_BADGE); 301 | editor.remove(CLEAR_NOTIFICATIONS); 302 | editor.remove(FORCE_SHOW); 303 | editor.remove(SENDER_ID); 304 | editor.commit(); 305 | } 306 | 307 | callbackContext.success(); 308 | } catch (IOException e) { 309 | Log.e(LOG_TAG, "execute: Got JSON Exception " + e.getMessage()); 310 | callbackContext.error(e.getMessage()); 311 | } 312 | } 313 | }); 314 | } else if (FINISH.equals(action)) { 315 | callbackContext.success(); 316 | } else if (HAS_PERMISSION.equals(action)) { 317 | cordova.getThreadPool().execute(new Runnable() { 318 | public void run() { 319 | JSONObject jo = new JSONObject(); 320 | try { 321 | Log.d(LOG_TAG, 322 | "has permission: " + NotificationManagerCompat.from(getApplicationContext()).areNotificationsEnabled()); 323 | jo.put("isEnabled", NotificationManagerCompat.from(getApplicationContext()).areNotificationsEnabled()); 324 | PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, jo); 325 | pluginResult.setKeepCallback(true); 326 | callbackContext.sendPluginResult(pluginResult); 327 | } catch (UnknownError e) { 328 | callbackContext.error(e.getMessage()); 329 | } catch (JSONException e) { 330 | callbackContext.error(e.getMessage()); 331 | } 332 | } 333 | }); 334 | } else if (SET_APPLICATION_ICON_BADGE_NUMBER.equals(action)) { 335 | cordova.getThreadPool().execute(new Runnable() { 336 | public void run() { 337 | Log.v(LOG_TAG, "setApplicationIconBadgeNumber: data=" + data.toString()); 338 | try { 339 | setApplicationIconBadgeNumber(getApplicationContext(), data.getJSONObject(0).getInt(BADGE)); 340 | } catch (JSONException e) { 341 | callbackContext.error(e.getMessage()); 342 | } 343 | callbackContext.success(); 344 | } 345 | }); 346 | } else if (GET_APPLICATION_ICON_BADGE_NUMBER.equals(action)) { 347 | cordova.getThreadPool().execute(new Runnable() { 348 | public void run() { 349 | Log.v(LOG_TAG, "getApplicationIconBadgeNumber"); 350 | callbackContext.success(getApplicationIconBadgeNumber(getApplicationContext())); 351 | } 352 | }); 353 | } else if (CLEAR_ALL_NOTIFICATIONS.equals(action)) { 354 | cordova.getThreadPool().execute(new Runnable() { 355 | public void run() { 356 | Log.v(LOG_TAG, "clearAllNotifications"); 357 | clearAllNotifications(); 358 | callbackContext.success(); 359 | } 360 | }); 361 | } else if (SUBSCRIBE.equals(action)) { 362 | // Subscribing for a topic 363 | cordova.getThreadPool().execute(new Runnable() { 364 | public void run() { 365 | try { 366 | String topic = data.getString(0); 367 | subscribeToTopic(topic, registration_id); 368 | callbackContext.success(); 369 | } catch (JSONException e) { 370 | callbackContext.error(e.getMessage()); 371 | } 372 | } 373 | }); 374 | } else if (UNSUBSCRIBE.equals(action)) { 375 | // un-subscribing for a topic 376 | cordova.getThreadPool().execute(new Runnable() { 377 | public void run() { 378 | try { 379 | String topic = data.getString(0); 380 | unsubscribeFromTopic(topic, registration_id); 381 | callbackContext.success(); 382 | } catch (JSONException e) { 383 | callbackContext.error(e.getMessage()); 384 | } 385 | } 386 | }); 387 | } else if (CREATE_CHANNEL.equals(action)) { 388 | // un-subscribing for a topic 389 | cordova.getThreadPool().execute(new Runnable() { 390 | public void run() { 391 | try { 392 | // call create channel 393 | createChannel(data.getJSONObject(0)); 394 | callbackContext.success(); 395 | } catch (JSONException e) { 396 | callbackContext.error(e.getMessage()); 397 | } 398 | } 399 | }); 400 | } else if (DELETE_CHANNEL.equals(action)) { 401 | // un-subscribing for a topic 402 | cordova.getThreadPool().execute(new Runnable() { 403 | public void run() { 404 | try { 405 | String channelId = data.getString(0); 406 | deleteChannel(channelId); 407 | callbackContext.success(); 408 | } catch (JSONException e) { 409 | callbackContext.error(e.getMessage()); 410 | } 411 | } 412 | }); 413 | } else if (LIST_CHANNELS.equals(action)) { 414 | // un-subscribing for a topic 415 | cordova.getThreadPool().execute(new Runnable() { 416 | public void run() { 417 | try { 418 | callbackContext.success(listChannels()); 419 | } catch (JSONException e) { 420 | callbackContext.error(e.getMessage()); 421 | } 422 | } 423 | }); 424 | } else if (CLEAR_NOTIFICATION.equals(action)) { 425 | // clearing a single notification 426 | cordova.getThreadPool().execute(new Runnable() { 427 | public void run() { 428 | try { 429 | Log.v(LOG_TAG, "clearNotification"); 430 | int id = data.getInt(0); 431 | clearNotification(id); 432 | callbackContext.success(); 433 | } catch (JSONException e) { 434 | callbackContext.error(e.getMessage()); 435 | } 436 | } 437 | }); 438 | } else { 439 | Log.e(LOG_TAG, "Invalid action : " + action); 440 | callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.INVALID_ACTION)); 441 | return false; 442 | } 443 | 444 | return true; 445 | } 446 | 447 | public static void sendEvent(JSONObject _json) { 448 | PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, _json); 449 | pluginResult.setKeepCallback(true); 450 | if (pushContext != null) { 451 | pushContext.sendPluginResult(pluginResult); 452 | } 453 | } 454 | 455 | public static void sendError(String message) { 456 | PluginResult pluginResult = new PluginResult(PluginResult.Status.ERROR, message); 457 | pluginResult.setKeepCallback(true); 458 | if (pushContext != null) { 459 | pushContext.sendPluginResult(pluginResult); 460 | } 461 | } 462 | 463 | /* 464 | * Sends the pushbundle extras to the client application. If the client 465 | * application isn't currently active and the no-cache flag is not set, it is 466 | * cached for later processing. 467 | */ 468 | public static void sendExtras(Bundle extras) { 469 | if (extras != null) { 470 | String noCache = extras.getString(NO_CACHE); 471 | if (gWebView != null) { 472 | sendEvent(convertBundleToJson(extras)); 473 | } else if (!"1".equals(noCache)) { 474 | Log.v(LOG_TAG, "sendExtras: caching extras to send at a later time."); 475 | gCachedExtras.add(extras); 476 | } 477 | } 478 | } 479 | 480 | /* 481 | * Retrives badge count from SharedPreferences 482 | */ 483 | public static int getApplicationIconBadgeNumber(Context context) { 484 | SharedPreferences settings = context.getSharedPreferences(BADGE, Context.MODE_PRIVATE); 485 | return settings.getInt(BADGE, 0); 486 | } 487 | 488 | /* 489 | * Sets badge count on application icon and in SharedPreferences 490 | */ 491 | public static void setApplicationIconBadgeNumber(Context context, int badgeCount) { 492 | if (badgeCount > 0) { 493 | ShortcutBadger.applyCount(context, badgeCount); 494 | } else { 495 | ShortcutBadger.removeCount(context); 496 | } 497 | 498 | SharedPreferences.Editor editor = context.getSharedPreferences(BADGE, Context.MODE_PRIVATE).edit(); 499 | editor.putInt(BADGE, Math.max(badgeCount, 0)); 500 | editor.apply(); 501 | } 502 | 503 | @Override 504 | public void initialize(CordovaInterface cordova, CordovaWebView webView) { 505 | super.initialize(cordova, webView); 506 | gForeground = true; 507 | } 508 | 509 | @Override 510 | public void onPause(boolean multitasking) { 511 | super.onPause(multitasking); 512 | gForeground = false; 513 | 514 | SharedPreferences prefs = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, 515 | Context.MODE_PRIVATE); 516 | if (prefs.getBoolean(CLEAR_NOTIFICATIONS, true)) { 517 | clearAllNotifications(); 518 | } 519 | } 520 | 521 | @Override 522 | public void onResume(boolean multitasking) { 523 | super.onResume(multitasking); 524 | gForeground = true; 525 | } 526 | 527 | @Override 528 | public void onDestroy() { 529 | super.onDestroy(); 530 | gForeground = false; 531 | gWebView = null; 532 | } 533 | 534 | private void clearAllNotifications() { 535 | final NotificationManager notificationManager = (NotificationManager) cordova.getActivity() 536 | .getSystemService(Context.NOTIFICATION_SERVICE); 537 | notificationManager.cancelAll(); 538 | } 539 | 540 | private void clearNotification(int id) { 541 | final NotificationManager notificationManager = (NotificationManager) cordova.getActivity() 542 | .getSystemService(Context.NOTIFICATION_SERVICE); 543 | String appName = (String) this.cordova.getActivity().getPackageManager() 544 | .getApplicationLabel(this.cordova.getActivity().getApplicationInfo()); 545 | notificationManager.cancel(appName, id); 546 | } 547 | 548 | private void subscribeToTopics(JSONArray topics, String registrationToken) { 549 | if (topics != null) { 550 | String topic = null; 551 | for (int i = 0; i < topics.length(); i++) { 552 | topic = topics.optString(i, null); 553 | subscribeToTopic(topic, registrationToken); 554 | } 555 | } 556 | } 557 | 558 | private void subscribeToTopic(String topic, String registrationToken) { 559 | if (topic != null) { 560 | Log.d(LOG_TAG, "Subscribing to topic: " + topic); 561 | FirebaseMessaging.getInstance().subscribeToTopic(topic); 562 | } 563 | } 564 | 565 | private void unsubscribeFromTopics(JSONArray topics, String registrationToken) { 566 | if (topics != null) { 567 | String topic = null; 568 | for (int i = 0; i < topics.length(); i++) { 569 | topic = topics.optString(i, null); 570 | unsubscribeFromTopic(topic, registrationToken); 571 | } 572 | } 573 | } 574 | 575 | private void unsubscribeFromTopic(String topic, String registrationToken) { 576 | if (topic != null) { 577 | Log.d(LOG_TAG, "Unsubscribing to topic: " + topic); 578 | FirebaseMessaging.getInstance().unsubscribeFromTopic(topic); 579 | } 580 | } 581 | 582 | /* 583 | * serializes a bundle to JSON. 584 | */ 585 | private static JSONObject convertBundleToJson(Bundle extras) { 586 | Log.d(LOG_TAG, "convert extras to json"); 587 | try { 588 | JSONObject json = new JSONObject(); 589 | JSONObject additionalData = new JSONObject(); 590 | 591 | // Add any keys that need to be in top level json to this set 592 | HashSet jsonKeySet = new HashSet(); 593 | Collections.addAll(jsonKeySet, TITLE, MESSAGE, COUNT, SOUND, IMAGE); 594 | 595 | Iterator it = extras.keySet().iterator(); 596 | while (it.hasNext()) { 597 | String key = it.next(); 598 | Object value = extras.get(key); 599 | 600 | Log.d(LOG_TAG, "key = " + key); 601 | 602 | if (jsonKeySet.contains(key)) { 603 | json.put(key, value); 604 | } else if (key.equals(COLDSTART)) { 605 | additionalData.put(key, extras.getBoolean(COLDSTART)); 606 | } else if (key.equals(FOREGROUND)) { 607 | additionalData.put(key, extras.getBoolean(FOREGROUND)); 608 | } else if (key.equals(DISMISSED)) { 609 | additionalData.put(key, extras.getBoolean(DISMISSED)); 610 | } else if (value instanceof String) { 611 | String strValue = (String) value; 612 | try { 613 | // Try to figure out if the value is another JSON object 614 | if (strValue.startsWith("{")) { 615 | additionalData.put(key, new JSONObject(strValue)); 616 | } 617 | // Try to figure out if the value is another JSON array 618 | else if (strValue.startsWith("[")) { 619 | additionalData.put(key, new JSONArray(strValue)); 620 | } else { 621 | additionalData.put(key, value); 622 | } 623 | } catch (Exception e) { 624 | additionalData.put(key, value); 625 | } 626 | } 627 | } // while 628 | 629 | json.put(ADDITIONAL_DATA, additionalData); 630 | Log.v(LOG_TAG, "extrasToJSON: " + json.toString()); 631 | 632 | return json; 633 | } catch (JSONException e) { 634 | Log.e(LOG_TAG, "extrasToJSON: JSON exception"); 635 | } 636 | return null; 637 | } 638 | 639 | private String getStringResourceByName(String aString) { 640 | Activity activity = cordova.getActivity(); 641 | String packageName = activity.getPackageName(); 642 | int resId = activity.getResources().getIdentifier(aString, "string", packageName); 643 | return activity.getString(resId); 644 | } 645 | 646 | public static boolean isInForeground() { 647 | return gForeground; 648 | } 649 | 650 | public static boolean isActive() { 651 | return gWebView != null; 652 | } 653 | 654 | protected static void setRegistrationID(String token) { 655 | registration_id = token; 656 | } 657 | } 658 | -------------------------------------------------------------------------------- /src/browser/ServiceWorker.js: -------------------------------------------------------------------------------- 1 | var messageChannel; 2 | 3 | self.addEventListener('install', function(event) { 4 | self.skipWaiting(); 5 | }); 6 | 7 | self.addEventListener('push', function(event) { 8 | // parse incoming message 9 | var obj = {}; 10 | var pushData = { 11 | image: 'https://avatars1.githubusercontent.com/u/60365?v=3&s=200', 12 | additionalData: {} 13 | }; 14 | if (event.data) { 15 | obj = event.data.json(); 16 | } 17 | 18 | console.log(obj); 19 | 20 | // convert to push plugin API 21 | for (var key in obj) { 22 | if (key === 'title') { 23 | pushData.title = obj[key]; 24 | } else if (key === 'message' || key === 'body') { 25 | pushData.message = obj[key]; 26 | } else if (key === 'count' || key === 'msgcnt' || key === 'badge') { 27 | pushData.count = obj[key]; 28 | } else if (key === 'sound' || key === 'soundname') { 29 | pushData.sound = obj[key]; 30 | } else if (key === 'image') { 31 | pushData.image = obj[key]; 32 | } else { 33 | pushData.additionalData[key] = obj[key]; 34 | } 35 | } 36 | 37 | event.waitUntil( 38 | self.registration.showNotification(pushData.title, { 39 | body: pushData.message, 40 | icon: pushData.image, 41 | tag: 'simple-push-demo-notification-tag' 42 | }) 43 | ); 44 | 45 | messageChannel.ports[0].postMessage(pushData); 46 | 47 | }); 48 | 49 | self.addEventListener('message', function(event) { 50 | messageChannel = event; 51 | }); 52 | -------------------------------------------------------------------------------- /src/browser/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Push Demo", 3 | "gcm_sender_id": "996231231186" 4 | } 5 | -------------------------------------------------------------------------------- /src/ios/AppDelegate+notification.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate+notification.h 3 | // pushtest 4 | // 5 | // Created by Robert Easterday on 10/26/12. 6 | // 7 | // 8 | 9 | #import "AppDelegate.h" 10 | @import UserNotifications; 11 | 12 | extern NSString *const pushPluginApplicationDidBecomeActiveNotification; 13 | 14 | @interface AppDelegate (notification) 15 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; 16 | - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error; 17 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:( void (^)(UIBackgroundFetchResult))completionHandler; 18 | - (void)pushPluginOnApplicationDidBecomeActive:(UIApplication *)application; 19 | - (void)checkUserHasRemoteNotificationsEnabledWithCompletionHandler:(nonnull void (^)(BOOL))completionHandler; 20 | - (id) getCommandInstance:(NSString*)className; 21 | 22 | @property (nonatomic, retain) NSDictionary *launchNotification; 23 | @property (nonatomic, retain) NSNumber *coldstart; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /src/ios/AppDelegate+notification.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate+notification.m 3 | // pushtest 4 | // 5 | // Created by Robert Easterday on 10/26/12. 6 | // 7 | // 8 | 9 | #import "AppDelegate+notification.h" 10 | #import "PushPlugin.h" 11 | #import 12 | 13 | static char launchNotificationKey; 14 | static char coldstartKey; 15 | NSString *const pushPluginApplicationDidBecomeActiveNotification = @"pushPluginApplicationDidBecomeActiveNotification"; 16 | 17 | 18 | @implementation AppDelegate (notification) 19 | 20 | - (id) getCommandInstance:(NSString*)className 21 | { 22 | return [self.viewController getCommandInstance:className]; 23 | } 24 | 25 | // its dangerous to override a method from within a category. 26 | // Instead we will use method swizzling. we set this up in the load call. 27 | + (void)load 28 | { 29 | static dispatch_once_t onceToken; 30 | dispatch_once(&onceToken, ^{ 31 | Class class = [self class]; 32 | 33 | SEL originalSelector = @selector(init); 34 | SEL swizzledSelector = @selector(pushPluginSwizzledInit); 35 | 36 | Method original = class_getInstanceMethod(class, originalSelector); 37 | Method swizzled = class_getInstanceMethod(class, swizzledSelector); 38 | 39 | BOOL didAddMethod = 40 | class_addMethod(class, 41 | originalSelector, 42 | method_getImplementation(swizzled), 43 | method_getTypeEncoding(swizzled)); 44 | 45 | if (didAddMethod) { 46 | class_replaceMethod(class, 47 | swizzledSelector, 48 | method_getImplementation(original), 49 | method_getTypeEncoding(original)); 50 | } else { 51 | method_exchangeImplementations(original, swizzled); 52 | } 53 | }); 54 | } 55 | 56 | - (AppDelegate *)pushPluginSwizzledInit 57 | { 58 | UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; 59 | center.delegate = self; 60 | 61 | [[NSNotificationCenter defaultCenter]addObserver:self 62 | selector:@selector(pushPluginOnApplicationDidBecomeActive:) 63 | name:UIApplicationDidBecomeActiveNotification 64 | object:nil]; 65 | 66 | // This actually calls the original init method over in AppDelegate. Equivilent to calling super 67 | // on an overrided method, this is not recursive, although it appears that way. neat huh? 68 | return [self pushPluginSwizzledInit]; 69 | } 70 | 71 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { 72 | PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"]; 73 | [pushHandler didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; 74 | } 75 | 76 | - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { 77 | PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"]; 78 | [pushHandler didFailToRegisterForRemoteNotificationsWithError:error]; 79 | } 80 | 81 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { 82 | NSLog(@"didReceiveNotification with fetchCompletionHandler"); 83 | 84 | // app is in the background or inactive, so only call notification callback if this is a silent push 85 | if (application.applicationState != UIApplicationStateActive) { 86 | 87 | NSLog(@"app in-active"); 88 | 89 | // do some convoluted logic to find out if this should be a silent push. 90 | long silent = 0; 91 | id aps = [userInfo objectForKey:@"aps"]; 92 | id contentAvailable = [aps objectForKey:@"content-available"]; 93 | if ([contentAvailable isKindOfClass:[NSString class]] && [contentAvailable isEqualToString:@"1"]) { 94 | silent = 1; 95 | } else if ([contentAvailable isKindOfClass:[NSNumber class]]) { 96 | silent = [contentAvailable integerValue]; 97 | } 98 | 99 | if (silent == 1) { 100 | NSLog(@"this should be a silent push"); 101 | void (^safeHandler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result){ 102 | dispatch_async(dispatch_get_main_queue(), ^{ 103 | completionHandler(result); 104 | }); 105 | }; 106 | 107 | PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"]; 108 | 109 | if (pushHandler.handlerObj == nil) { 110 | pushHandler.handlerObj = [NSMutableDictionary dictionaryWithCapacity:2]; 111 | } 112 | 113 | id notId = [userInfo objectForKey:@"notId"]; 114 | if (notId != nil) { 115 | NSLog(@"Push Plugin notId %@", notId); 116 | [pushHandler.handlerObj setObject:safeHandler forKey:notId]; 117 | } else { 118 | NSLog(@"Push Plugin notId handler"); 119 | [pushHandler.handlerObj setObject:safeHandler forKey:@"handler"]; 120 | } 121 | 122 | pushHandler.notificationMessage = userInfo; 123 | pushHandler.isInline = NO; 124 | [pushHandler notificationReceived]; 125 | } else { 126 | NSLog(@"just put it in the shade"); 127 | //save it for later 128 | self.launchNotification = userInfo; 129 | completionHandler(UIBackgroundFetchResultNewData); 130 | } 131 | 132 | } else { 133 | completionHandler(UIBackgroundFetchResultNoData); 134 | } 135 | } 136 | 137 | - (void)checkUserHasRemoteNotificationsEnabledWithCompletionHandler:(nonnull void (^)(BOOL))completionHandler 138 | { 139 | [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { 140 | 141 | switch (settings.authorizationStatus) 142 | { 143 | case UNAuthorizationStatusDenied: 144 | case UNAuthorizationStatusNotDetermined: 145 | completionHandler(NO); 146 | break; 147 | case UNAuthorizationStatusAuthorized: 148 | completionHandler(YES); 149 | break; 150 | } 151 | }]; 152 | } 153 | 154 | - (void)pushPluginOnApplicationDidBecomeActive:(NSNotification *)notification { 155 | 156 | NSLog(@"active"); 157 | 158 | NSString *firstLaunchKey = @"firstLaunchKey"; 159 | NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"phonegap-plugin-push"]; 160 | if (![defaults boolForKey:firstLaunchKey]) { 161 | NSLog(@"application first launch: remove badge icon number"); 162 | [defaults setBool:YES forKey:firstLaunchKey]; 163 | [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; 164 | } 165 | 166 | UIApplication *application = notification.object; 167 | 168 | PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"]; 169 | if (pushHandler.clearBadge) { 170 | NSLog(@"PushPlugin clearing badge"); 171 | //zero badge 172 | application.applicationIconBadgeNumber = 0; 173 | } else { 174 | NSLog(@"PushPlugin skip clear badge"); 175 | } 176 | 177 | if (self.launchNotification) { 178 | pushHandler.isInline = NO; 179 | pushHandler.coldstart = [self.coldstart boolValue]; 180 | pushHandler.notificationMessage = self.launchNotification; 181 | self.launchNotification = nil; 182 | self.coldstart = [NSNumber numberWithBool:NO]; 183 | [pushHandler performSelectorOnMainThread:@selector(notificationReceived) withObject:pushHandler waitUntilDone:NO]; 184 | } 185 | 186 | [[NSNotificationCenter defaultCenter] postNotificationName:pushPluginApplicationDidBecomeActiveNotification object:nil]; 187 | } 188 | 189 | - (void)userNotificationCenter:(UNUserNotificationCenter *)center 190 | willPresentNotification:(UNNotification *)notification 191 | withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler 192 | { 193 | NSLog( @"NotificationCenter Handle push from foreground" ); 194 | // custom code to handle push while app is in the foreground 195 | PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"]; 196 | pushHandler.notificationMessage = notification.request.content.userInfo; 197 | pushHandler.isInline = YES; 198 | [pushHandler notificationReceived]; 199 | 200 | completionHandler(UNNotificationPresentationOptionNone); 201 | } 202 | 203 | - (void)userNotificationCenter:(UNUserNotificationCenter *)center 204 | didReceiveNotificationResponse:(UNNotificationResponse *)response 205 | withCompletionHandler:(void(^)(void))completionHandler 206 | { 207 | NSLog(@"Push Plugin didReceiveNotificationResponse: actionIdentifier %@, notification: %@", response.actionIdentifier, 208 | response.notification.request.content.userInfo); 209 | NSMutableDictionary *userInfo = [response.notification.request.content.userInfo mutableCopy]; 210 | [userInfo setObject:response.actionIdentifier forKey:@"actionCallback"]; 211 | NSLog(@"Push Plugin userInfo %@", userInfo); 212 | 213 | switch ([UIApplication sharedApplication].applicationState) { 214 | case UIApplicationStateActive: 215 | { 216 | PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"]; 217 | pushHandler.notificationMessage = userInfo; 218 | pushHandler.isInline = NO; 219 | [pushHandler notificationReceived]; 220 | completionHandler(); 221 | break; 222 | } 223 | case UIApplicationStateInactive: 224 | { 225 | NSLog(@"coldstart"); 226 | self.launchNotification = response.notification.request.content.userInfo; 227 | self.coldstart = [NSNumber numberWithBool:YES]; 228 | break; 229 | } 230 | case UIApplicationStateBackground: 231 | { 232 | void (^safeHandler)(void) = ^(void){ 233 | dispatch_async(dispatch_get_main_queue(), ^{ 234 | completionHandler(); 235 | }); 236 | }; 237 | 238 | PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"]; 239 | 240 | if (pushHandler.handlerObj == nil) { 241 | pushHandler.handlerObj = [NSMutableDictionary dictionaryWithCapacity:2]; 242 | } 243 | 244 | id notId = [userInfo objectForKey:@"notId"]; 245 | if (notId != nil) { 246 | NSLog(@"Push Plugin notId %@", notId); 247 | [pushHandler.handlerObj setObject:safeHandler forKey:notId]; 248 | } else { 249 | NSLog(@"Push Plugin notId handler"); 250 | [pushHandler.handlerObj setObject:safeHandler forKey:@"handler"]; 251 | } 252 | 253 | pushHandler.notificationMessage = userInfo; 254 | pushHandler.isInline = NO; 255 | 256 | [pushHandler performSelectorOnMainThread:@selector(notificationReceived) withObject:pushHandler waitUntilDone:NO]; 257 | } 258 | } 259 | } 260 | 261 | // The accessors use an Associative Reference since you can't define a iVar in a category 262 | // http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocAssociativeReferences.html 263 | - (NSMutableArray *)launchNotification 264 | { 265 | return objc_getAssociatedObject(self, &launchNotificationKey); 266 | } 267 | 268 | - (void)setLaunchNotification:(NSDictionary *)aDictionary 269 | { 270 | objc_setAssociatedObject(self, &launchNotificationKey, aDictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 271 | } 272 | 273 | - (NSNumber *)coldstart 274 | { 275 | return objc_getAssociatedObject(self, &coldstartKey); 276 | } 277 | 278 | - (void)setColdstart:(NSNumber *)aNumber 279 | { 280 | objc_setAssociatedObject(self, &coldstartKey, aNumber, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 281 | } 282 | 283 | - (void)dealloc 284 | { 285 | self.launchNotification = nil; // clear the association and release the object 286 | self.coldstart = nil; 287 | } 288 | 289 | @end 290 | -------------------------------------------------------------------------------- /src/ios/PushPlugin.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009-2011 Urban Airship Inc. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binaryform must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided withthe distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE URBAN AIRSHIP INC``AS IS'' AND ANY EXPRESS OR 15 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 16 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 17 | EVENT SHALL URBAN AIRSHIP INC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 18 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 19 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 22 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | @import Foundation; 27 | @import UserNotifications; 28 | #import 29 | #import 30 | #import 31 | 32 | @protocol GGLInstanceIDDelegate; 33 | @protocol GCMReceiverDelegate; 34 | @interface PushPlugin : CDVPlugin 35 | { 36 | NSDictionary *notificationMessage; 37 | BOOL isInline; 38 | NSString *notificationCallbackId; 39 | NSString *callback; 40 | BOOL clearBadge; 41 | 42 | NSMutableDictionary *handlerObj; 43 | void (^completionHandler)(UIBackgroundFetchResult); 44 | 45 | BOOL ready; 46 | } 47 | 48 | @property (nonatomic, copy) NSString *callbackId; 49 | @property (nonatomic, copy) NSString *notificationCallbackId; 50 | @property (nonatomic, copy) NSString *callback; 51 | 52 | @property (nonatomic, strong) NSDictionary *notificationMessage; 53 | @property BOOL isInline; 54 | @property BOOL coldstart; 55 | @property BOOL clearBadge; 56 | @property (nonatomic, strong) NSMutableDictionary *handlerObj; 57 | 58 | - (void)init:(CDVInvokedUrlCommand*)command; 59 | - (void)unregister:(CDVInvokedUrlCommand*)command; 60 | - (void)subscribe:(CDVInvokedUrlCommand*)command; 61 | - (void)unsubscribe:(CDVInvokedUrlCommand*)command; 62 | - (void)clearNotification:(CDVInvokedUrlCommand*)command; 63 | 64 | - (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; 65 | - (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error; 66 | 67 | - (void)setNotificationMessage:(NSDictionary *)notification; 68 | - (void)notificationReceived; 69 | 70 | - (void)willSendDataMessageWithID:(NSString *)messageID error:(NSError *)error; 71 | - (void)didSendDataMessageWithID:(NSString *)messageID; 72 | - (void)didDeleteMessagesOnServer; 73 | 74 | // VoIP Features 75 | - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type; 76 | - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type; 77 | 78 | // FCM Features 79 | @property(nonatomic, assign) BOOL usesFCM; 80 | @property(nonatomic, strong) NSNumber *fcmSandbox; 81 | @property(nonatomic, strong) NSString *fcmSenderId; 82 | @property(nonatomic, strong) NSDictionary *fcmRegistrationOptions; 83 | @property(nonatomic, strong) NSString *fcmRegistrationToken; 84 | @property(nonatomic, strong) NSArray *fcmTopics; 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /src/js/push.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Module dependencies. 3 | */ 4 | 5 | const exec = cordova.require('cordova/exec'); 6 | 7 | class PushNotification { 8 | /** 9 | * PushNotification constructor. 10 | * 11 | * @param {Object} options to initiate Push Notifications. 12 | * @return {PushNotification} instance that can be monitored and cancelled. 13 | */ 14 | constructor(options) { 15 | this.handlers = { 16 | registration: [], 17 | notification: [], 18 | error: [], 19 | }; 20 | 21 | // require options parameter 22 | if (typeof options === 'undefined') { 23 | throw new Error('The options argument is required.'); 24 | } 25 | 26 | // store the options to this object instance 27 | this.options = options; 28 | 29 | // triggered on registration and notification 30 | const success = (result) => { 31 | if (result && typeof result.registrationId !== 'undefined') { 32 | this.emit('registration', result); 33 | } else if ( 34 | result 35 | && result.additionalData 36 | && typeof result.additionalData.actionCallback !== 'undefined' 37 | ) { 38 | this.emit(result.additionalData.actionCallback, result); 39 | } else if (result) { 40 | this.emit('notification', result); 41 | } 42 | }; 43 | 44 | // triggered on error 45 | const fail = (msg) => { 46 | const e = typeof msg === 'string' ? new Error(msg) : msg; 47 | this.emit('error', e); 48 | }; 49 | 50 | // wait at least one process tick to allow event subscriptions 51 | setTimeout(() => { 52 | exec(success, fail, 'PushNotification', 'init', [options]); 53 | }, 10); 54 | } 55 | 56 | /** 57 | * Unregister from push notifications 58 | */ 59 | unregister(successCallback, errorCallback = () => {}, options) { 60 | if (typeof errorCallback !== 'function') { 61 | console.log('PushNotification.unregister failure: failure parameter not a function'); 62 | return; 63 | } 64 | 65 | if (typeof successCallback !== 'function') { 66 | console.log( 67 | 'PushNotification.unregister failure: success callback parameter must be a function', 68 | ); 69 | return; 70 | } 71 | 72 | const cleanHandlersAndPassThrough = () => { 73 | if (!options) { 74 | this.handlers = { 75 | registration: [], 76 | notification: [], 77 | error: [], 78 | }; 79 | } 80 | successCallback(); 81 | }; 82 | 83 | exec(cleanHandlersAndPassThrough, errorCallback, 'PushNotification', 'unregister', [options]); 84 | } 85 | 86 | /** 87 | * subscribe to a topic 88 | * @param {String} topic topic to subscribe 89 | * @param {Function} successCallback success callback 90 | * @param {Function} errorCallback error callback 91 | * @return {void} 92 | */ 93 | subscribe(topic, successCallback, errorCallback = () => {}) { 94 | if (typeof errorCallback !== 'function') { 95 | console.log('PushNotification.subscribe failure: failure parameter not a function'); 96 | return; 97 | } 98 | 99 | if (typeof successCallback !== 'function') { 100 | console.log( 101 | 'PushNotification.subscribe failure: success callback parameter must be a function', 102 | ); 103 | return; 104 | } 105 | 106 | exec(successCallback, errorCallback, 'PushNotification', 'subscribe', [topic]); 107 | } 108 | 109 | /** 110 | * unsubscribe to a topic 111 | * @param {String} topic topic to unsubscribe 112 | * @param {Function} successCallback success callback 113 | * @param {Function} errorCallback error callback 114 | * @return {void} 115 | */ 116 | unsubscribe(topic, successCallback, errorCallback = () => {}) { 117 | if (typeof errorCallback !== 'function') { 118 | console.log('PushNotification.unsubscribe failure: failure parameter not a function'); 119 | return; 120 | } 121 | 122 | if (typeof successCallback !== 'function') { 123 | console.log( 124 | 'PushNotification.unsubscribe failure: success callback parameter must be a function', 125 | ); 126 | return; 127 | } 128 | 129 | exec(successCallback, errorCallback, 'PushNotification', 'unsubscribe', [topic]); 130 | } 131 | 132 | /** 133 | * Call this to set the application icon badge 134 | */ 135 | setApplicationIconBadgeNumber(successCallback, errorCallback = () => {}, badge) { 136 | if (typeof errorCallback !== 'function') { 137 | console.log( 138 | 'PushNotification.setApplicationIconBadgeNumber failure: failure ' 139 | + 'parameter not a function', 140 | ); 141 | return; 142 | } 143 | 144 | if (typeof successCallback !== 'function') { 145 | console.log( 146 | 'PushNotification.setApplicationIconBadgeNumber failure: success ' 147 | + 'callback parameter must be a function', 148 | ); 149 | return; 150 | } 151 | 152 | exec(successCallback, errorCallback, 'PushNotification', 'setApplicationIconBadgeNumber', [ 153 | { badge }, 154 | ]); 155 | } 156 | 157 | /** 158 | * Get the application icon badge 159 | */ 160 | 161 | getApplicationIconBadgeNumber(successCallback, errorCallback = () => {}) { 162 | if (typeof errorCallback !== 'function') { 163 | console.log( 164 | 'PushNotification.getApplicationIconBadgeNumber failure: failure ' 165 | + 'parameter not a function', 166 | ); 167 | return; 168 | } 169 | 170 | if (typeof successCallback !== 'function') { 171 | console.log( 172 | 'PushNotification.getApplicationIconBadgeNumber failure: success ' 173 | + 'callback parameter must be a function', 174 | ); 175 | return; 176 | } 177 | 178 | exec(successCallback, errorCallback, 'PushNotification', 'getApplicationIconBadgeNumber', []); 179 | } 180 | 181 | /** 182 | * Clear all notifications 183 | */ 184 | 185 | clearAllNotifications(successCallback = () => {}, errorCallback = () => {}) { 186 | if (typeof errorCallback !== 'function') { 187 | console.log( 188 | 'PushNotification.clearAllNotifications failure: failure parameter not a function', 189 | ); 190 | return; 191 | } 192 | 193 | if (typeof successCallback !== 'function') { 194 | console.log( 195 | 'PushNotification.clearAllNotifications failure: success callback ' 196 | + 'parameter must be a function', 197 | ); 198 | return; 199 | } 200 | 201 | exec(successCallback, errorCallback, 'PushNotification', 'clearAllNotifications', []); 202 | } 203 | 204 | /** 205 | * Clears notifications that have the ID specified. 206 | * @param {Function} [successCallback] Callback function to be called on success. 207 | * @param {Function} [errorCallback] Callback function to be called when an error is encountered. 208 | * @param {Number} id ID of the notification to be removed. 209 | */ 210 | clearNotification(successCallback = () => {}, errorCallback = () => {}, id) { 211 | const idNumber = parseInt(id, 10); 212 | if (Number.isNaN(idNumber) || idNumber > Number.MAX_SAFE_INTEGER || idNumber < 0) { 213 | console.log( 214 | 'PushNotification.clearNotification failure: id parameter must' 215 | + 'be a valid integer.', 216 | ); 217 | return; 218 | } 219 | 220 | exec(successCallback, errorCallback, 'PushNotification', 'clearNotification', 221 | [idNumber]); 222 | } 223 | 224 | /** 225 | * Listen for an event. 226 | * 227 | * The following events are supported: 228 | * 229 | * - registration 230 | * - notification 231 | * - error 232 | * 233 | * @param {String} eventName to subscribe to. 234 | * @param {Function} callback triggered on the event. 235 | */ 236 | 237 | on(eventName, callback) { 238 | if (!Object.prototype.hasOwnProperty.call(this.handlers, eventName)) { 239 | this.handlers[eventName] = []; 240 | } 241 | this.handlers[eventName].push(callback); 242 | } 243 | 244 | /** 245 | * Remove event listener. 246 | * 247 | * @param {String} eventName to match subscription. 248 | * @param {Function} handle function associated with event. 249 | */ 250 | 251 | off(eventName, handle) { 252 | if (Object.prototype.hasOwnProperty.call(this.handlers, eventName)) { 253 | const handleIndex = this.handlers[eventName].indexOf(handle); 254 | if (handleIndex >= 0) { 255 | this.handlers[eventName].splice(handleIndex, 1); 256 | } 257 | } 258 | } 259 | 260 | /** 261 | * Emit an event. 262 | * 263 | * This is intended for internal use only. 264 | * 265 | * @param {String} eventName is the event to trigger. 266 | * @param {*} all arguments are passed to the event listeners. 267 | * 268 | * @return {Boolean} is true when the event is triggered otherwise false. 269 | */ 270 | 271 | emit(...args) { 272 | const eventName = args.shift(); 273 | 274 | if (!Object.prototype.hasOwnProperty.call(this.handlers, eventName)) { 275 | return false; 276 | } 277 | 278 | for (let i = 0, { length } = this.handlers[eventName]; i < length; i += 1) { 279 | const callback = this.handlers[eventName][i]; 280 | if (typeof callback === 'function') { 281 | callback(...args); 282 | } else { 283 | console.log(`event handler: ${eventName} must be a function`); 284 | } 285 | } 286 | 287 | return true; 288 | } 289 | 290 | finish(successCallback = () => {}, errorCallback = () => {}, id = 'handler') { 291 | if (typeof successCallback !== 'function') { 292 | console.log('finish failure: success callback parameter must be a function'); 293 | return; 294 | } 295 | 296 | if (typeof errorCallback !== 'function') { 297 | console.log('finish failure: failure parameter not a function'); 298 | return; 299 | } 300 | 301 | exec(successCallback, errorCallback, 'PushNotification', 'finish', [id]); 302 | } 303 | } 304 | 305 | /*! 306 | * Push Notification Plugin. 307 | */ 308 | 309 | module.exports = { 310 | /** 311 | * Register for Push Notifications. 312 | * 313 | * This method will instantiate a new copy of the PushNotification object 314 | * and start the registration process. 315 | * 316 | * @param {Object} options 317 | * @return {PushNotification} instance 318 | */ 319 | 320 | init: (options) => new PushNotification(options), 321 | 322 | hasPermission: (successCallback, errorCallback) => { 323 | exec(successCallback, errorCallback, 'PushNotification', 'hasPermission', []); 324 | }, 325 | 326 | createChannel: (successCallback, errorCallback, channel) => { 327 | exec(successCallback, errorCallback, 'PushNotification', 'createChannel', [channel]); 328 | }, 329 | 330 | deleteChannel: (successCallback, errorCallback, channelId) => { 331 | exec(successCallback, errorCallback, 'PushNotification', 'deleteChannel', [channelId]); 332 | }, 333 | 334 | listChannels: (successCallback, errorCallback) => { 335 | exec(successCallback, errorCallback, 'PushNotification', 'listChannels', []); 336 | }, 337 | 338 | /** 339 | * PushNotification Object. 340 | * 341 | * Expose the PushNotification object for direct use 342 | * and testing. Typically, you should use the 343 | * .init helper method. 344 | */ 345 | PushNotification, 346 | }; 347 | -------------------------------------------------------------------------------- /src/windows/PushPluginProxy.js: -------------------------------------------------------------------------------- 1 | var myApp = {}; 2 | var pushNotifications = Windows.Networking.PushNotifications; 3 | 4 | var createNotificationJSON = function (e) { 5 | var result = { message: '' }; //Added to identify callback as notification type in the API in case where notification has no message 6 | var notificationPayload; 7 | 8 | switch (e.notificationType) { 9 | case pushNotifications.PushNotificationType.toast: 10 | case pushNotifications.PushNotificationType.tile: 11 | if (e.notificationType === pushNotifications.PushNotificationType.toast) { 12 | notificationPayload = e.toastNotification.content; 13 | } 14 | else { 15 | notificationPayload = e.tileNotification.content; 16 | } 17 | var texts = notificationPayload.getElementsByTagName("text"); 18 | if (texts.length > 1) { 19 | result.title = texts[0].innerText; 20 | result.message = texts[1].innerText; 21 | } 22 | else if(texts.length === 1) { 23 | result.message = texts[0].innerText; 24 | } 25 | var images = notificationPayload.getElementsByTagName("image"); 26 | if (images.length > 0) { 27 | result.image = images[0].getAttribute("src"); 28 | } 29 | var soundFile = notificationPayload.getElementsByTagName("audio"); 30 | if (soundFile.length > 0) { 31 | result.sound = soundFile[0].getAttribute("src"); 32 | } 33 | break; 34 | 35 | case pushNotifications.PushNotificationType.badge: 36 | notificationPayload = e.badgeNotification.content; 37 | result.count = notificationPayload.getElementsByTagName("badge")[0].getAttribute("value"); 38 | break; 39 | 40 | case pushNotifications.PushNotificationType.raw: 41 | result.message = e.rawNotification.content; 42 | break; 43 | } 44 | 45 | result.additionalData = { coldstart: false }; // this gets called only when the app is running 46 | result.additionalData.pushNotificationReceivedEventArgs = e; 47 | return result; 48 | } 49 | 50 | module.exports = { 51 | init: function (onSuccess, onFail, args) { 52 | 53 | var onNotificationReceived = function (e) { 54 | var result = createNotificationJSON(e); 55 | onSuccess(result, { keepCallback: true }); 56 | } 57 | 58 | try { 59 | pushNotifications.PushNotificationChannelManager.createPushNotificationChannelForApplicationAsync().done( 60 | function (channel) { 61 | var result = {}; 62 | result.registrationId = channel.uri; 63 | myApp.channel = channel; 64 | channel.addEventListener("pushnotificationreceived", onNotificationReceived); 65 | myApp.notificationEvent = onNotificationReceived; 66 | onSuccess(result, { keepCallback: true }); 67 | 68 | var context = cordova.require('cordova/platform').activationContext; 69 | var launchArgs = context ? (context.argument || context.args) : null; 70 | if (launchArgs) { //If present, app launched through push notification 71 | var result = { message: '' }; //Added to identify callback as notification type in the API 72 | result.launchArgs = launchArgs; 73 | result.additionalData = { coldstart: true }; 74 | onSuccess(result, { keepCallback: true }); 75 | } 76 | }, function (error) { 77 | onFail(error); 78 | }); 79 | } catch (ex) { 80 | onFail(ex); 81 | } 82 | }, 83 | unregister: function (onSuccess, onFail, args) { 84 | try { 85 | myApp.channel.removeEventListener("pushnotificationreceived", myApp.notificationEvent); 86 | myApp.channel.close(); 87 | onSuccess(); 88 | } catch(ex) { 89 | onFail(ex); 90 | } 91 | }, 92 | hasPermission: function (onSuccess) { 93 | var notifier = Windows.UI.Notifications.ToastNotificationManager.createToastNotifier(); 94 | 95 | onSuccess({ isEnabled: !notifier.setting }); 96 | }, 97 | subscribe: function() { 98 | console.log("Subscribe is unsupported"); 99 | }, 100 | unsubscribe: function() { 101 | console.log("Subscribe is unsupported"); 102 | } 103 | }; 104 | require("cordova/exec/proxy").add("PushNotification", module.exports); 105 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for phonegap-plugin-push 2 | // Project: https://github.com/phonegap/phonegap-plugin-push 3 | // Definitions by: Frederico Galvão 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | 6 | declare namespace PhonegapPluginPush { 7 | type EventResponse = RegistrationEventResponse | NotificationEventResponse | Error 8 | 9 | interface PushNotification { 10 | /** 11 | * The event registration will be triggered on each successful registration with the 3rd party push service. 12 | * @param event 13 | * @param callback 14 | */ 15 | on(event: "registration", callback: (response: RegistrationEventResponse) => any): void 16 | /** 17 | * The event notification will be triggered each time a push notification is received by a 3rd party push service on the device. 18 | * @param event 19 | * @param callback 20 | */ 21 | on(event: "notification", callback: (response: NotificationEventResponse) => any): void 22 | /** 23 | * The event error will trigger when an internal error occurs and the cache is aborted. 24 | * @param event 25 | * @param callback 26 | */ 27 | on(event: "error", callback: (response: Error) => any): void 28 | /** 29 | * 30 | * @param event Name of the event to listen to. See below(above) for all the event names. 31 | * @param callback is called when the event is triggered. 32 | * @param event 33 | * @param callback 34 | */ 35 | on(event: string, callback: (response: EventResponse) => any): void 36 | 37 | off(event: "registration", callback: (response: RegistrationEventResponse) => any): void 38 | off(event: "notification", callback: (response: NotificationEventResponse) => any): void 39 | off(event: "error", callback: (response: Error) => any): void 40 | /** 41 | * As stated in the example, you will have to store your event handler if you are planning to remove it. 42 | * @param event Name of the event type. The possible event names are the same as for the push.on function. 43 | * @param callback handle to the function to get removed. 44 | * @param event 45 | * @param callback 46 | */ 47 | off(event: string, callback: (response: EventResponse) => any): void 48 | 49 | /** 50 | * The unregister method is used when the application no longer wants to receive push notifications. 51 | * Beware that this cleans up all event handlers previously registered, 52 | * so you will need to re-register them if you want them to function again without an application reload. 53 | * @param successHandler 54 | * @param errorHandler 55 | * @param topics 56 | */ 57 | unregister(successHandler: () => any, errorHandler?: () => any, topics?: string[]): void 58 | 59 | /** 60 | * The subscribe method is used when the application wants to subscribe a new topic to receive push notifications. 61 | * @param topic Topic to subscribe to. 62 | * @param successHandler Is called when the api successfully unregisters. 63 | * @param errorHandler Is called when the api encounters an error while unregistering. 64 | */ 65 | subscribe(topic: string, successHandler: () => any, errorHandler: () => any): void; 66 | 67 | /** 68 | * The unsubscribe method is used when the application no longer wants to receive push notifications 69 | * from a specific topic but continue to receive other push messages. 70 | * @param topic Topic to unsubscribe from. 71 | * @param successHandler Is called when the api successfully unregisters. 72 | * @param errorHandler Is called when the api encounters an error while unregistering. 73 | */ 74 | unsubscribe(topic: string, successHandler: () => any, errorHandler: () => any): void; 75 | 76 | /*TODO according to js source code, "errorHandler" is optional, but is "count" also optional? I can't read objetive-C code (can anyone at all? I wonder...)*/ 77 | /** 78 | * Set the badge count visible when the app is not running 79 | * 80 | * The count is an integer indicating what number should show up in the badge. 81 | * Passing 0 will clear the badge. 82 | * Each notification event contains a data.count value which can be used to set the badge to correct number. 83 | * @param successHandler 84 | * @param errorHandler 85 | * @param count 86 | */ 87 | setApplicationIconBadgeNumber(successHandler: () => any, errorHandler: () => any, count: number): void 88 | 89 | /** 90 | * Get the current badge count visible when the app is not running 91 | * successHandler gets called with an integer which is the current badge count 92 | * @param successHandler 93 | * @param errorHandler 94 | */ 95 | getApplicationIconBadgeNumber(successHandler: (count: number) => any, errorHandler: () => any): void 96 | 97 | /** 98 | * iOS only 99 | * Tells the OS that you are done processing a background push notification. 100 | * successHandler gets called when background push processing is successfully completed. 101 | * @param successHandler 102 | * @param errorHandler 103 | * @param id 104 | */ 105 | finish(successHandler?: () => any, errorHandler?: () => any, id?: string): void 106 | 107 | /** 108 | * Tells the OS to clear all notifications from the Notification Center 109 | * @param successHandler Is called when the api successfully clears the notifications. 110 | * @param errorHandler Is called when the api encounters an error when attempting to clears the notifications. 111 | */ 112 | clearAllNotifications(successHandler: () => any, errorHandler: () => any): void 113 | } 114 | 115 | /** 116 | * platform specific initialization options. 117 | */ 118 | interface InitOptions { 119 | /** 120 | * Android specific initialization options. 121 | */ 122 | android?: { 123 | /** 124 | * The name of a drawable resource to use as the small-icon. The name should not include the extension. 125 | */ 126 | icon?: string 127 | /** 128 | * Sets the background color of the small icon on Android 5.0 and greater. 129 | * Supported Formats - http://developer.android.com/reference/android/graphics/Color.html#parseColor(java.lang.String) 130 | */ 131 | iconColor?: string 132 | /** 133 | * If true it plays the sound specified in the push data or the default system sound. Default is true. 134 | */ 135 | sound?: boolean 136 | /** 137 | * If true the device vibrates on receipt of notification. Default is true. 138 | */ 139 | vibrate?: boolean 140 | /** 141 | * If true the icon badge will be cleared on init and before push messages are processed. Default is false. 142 | */ 143 | clearBadge?: boolean 144 | /** 145 | * If true the app clears all pending notifications when it is closed. Default is true. 146 | */ 147 | clearNotifications?: boolean 148 | /** 149 | * If true will always show a notification, even when the app is on the foreground. Default is false. 150 | */ 151 | forceShow?: boolean 152 | /** 153 | * If the array contains one or more strings each string will be used to subscribe to a GcmPubSub topic. 154 | */ 155 | topics?: string[] 156 | /** 157 | * The key to search for text of notification. Default is 'message'. 158 | */ 159 | messageKey?: string 160 | /** 161 | * The key to search for title of notification. Default is 'title'. 162 | */ 163 | titleKey?: string 164 | } 165 | 166 | /** 167 | * Browser specific initialization options. 168 | */ 169 | browser?: { 170 | /** 171 | * URL for the push server you want to use. Default is 'http://push.api.phonegap.com/v1/push'. 172 | */ 173 | pushServiceURL?: string 174 | /** 175 | * Your GCM API key if you are using VAPID keys. 176 | */ 177 | applicationServerKey?: string 178 | } 179 | 180 | /** 181 | * iOS specific initialization options. 182 | */ 183 | ios?: { 184 | /** 185 | * If true|"true" the device will be set up to receive VoIP Push notifications and the other options will be ignored 186 | * since VoIP notifications are silent notifications that should be handled in the "notification" event. 187 | * Default is false|"false". 188 | */ 189 | voip?: boolean | string 190 | /** 191 | * If true|"true" the device sets the badge number on receipt of notification. 192 | * Default is false|"false". 193 | * Note: the value you set this option to the first time you call the init method will be how the application always acts. 194 | * Once this is set programmatically in the init method it can only be changed manually by the user in Settings>Notifications>App Name. 195 | * This is normal iOS behaviour. 196 | */ 197 | badge?: boolean | string 198 | /** 199 | * If true|"true" the device plays a sound on receipt of notification. 200 | * Default is false|"false". 201 | * Note: the value you set this option to the first time you call the init method will be how the application always acts. 202 | * Once this is set programmatically in the init method it can only be changed manually by the user in Settings>Notifications>App Name. 203 | * This is normal iOS behaviour. 204 | */ 205 | sound?: boolean | string 206 | /** 207 | * If true|"true" the device shows an alert on receipt of notification. 208 | * Default is false|"false". 209 | * Note: the value you set this option to the first time you call the init method will be how the application always acts. 210 | * Once this is set programmatically in the init method it can only be changed manually by the user in Settings>Notifications>App Name. 211 | * This is normal iOS behaviour. 212 | */ 213 | alert?: boolean | string 214 | /** 215 | * If true|"true" the badge will be cleared on app startup. Defaults to false|"false". 216 | */ 217 | clearBadge?: boolean | string 218 | /** 219 | * The data required in order to enable Action Buttons for iOS. 220 | * Action Buttons on iOS - https://github.com/phonegap/phonegap-plugin-push/blob/master/docs/PAYLOAD.md#action-buttons-1 221 | */ 222 | categories?: CategoryArray 223 | /** 224 | * Whether to use prod or sandbox GCM setting. Defaults to false. 225 | */ 226 | fcmSandbox?: boolean 227 | /** 228 | * If the array contains one or more strings each string will be used to subscribe to a FcmPubSub topic. Defaults to []. 229 | */ 230 | topics?: string[] 231 | } 232 | 233 | /** 234 | * Windows specific initialization options. 235 | */ 236 | windows?: { 237 | 238 | } 239 | } 240 | 241 | interface CategoryArray { 242 | [name: string]: CategoryAction 243 | } 244 | 245 | interface CategoryAction { 246 | yes?: CategoryActionData 247 | no?: CategoryActionData 248 | maybe?: CategoryActionData 249 | } 250 | 251 | interface CategoryActionData { 252 | callback: string 253 | title: string 254 | foreground: boolean 255 | destructive: boolean 256 | } 257 | 258 | interface RegistrationEventResponse { 259 | /** 260 | * The registration ID provided by the 3rd party remote push service. 261 | */ 262 | registrationId: string 263 | } 264 | 265 | interface NotificationEventResponse { 266 | /** 267 | * The text of the push message sent from the 3rd party service. 268 | */ 269 | message: string 270 | /** 271 | * The optional title of the push message sent from the 3rd party service. 272 | */ 273 | title?: string 274 | /** 275 | * The number of messages to be displayed in the badge iOS or message count in the notification shade in Android. 276 | * For windows, it represents the value in the badge notification which could be a number or a status glyph. 277 | */ 278 | count: string 279 | /** 280 | * The name of the sound file to be played upon receipt of the notification. 281 | */ 282 | sound: string 283 | /** 284 | * The path of the image file to be displayed in the notification. 285 | */ 286 | image: string 287 | /** 288 | * An optional collection of data sent by the 3rd party push service that does not fit in the above properties. 289 | */ 290 | additionalData: NotificationEventAdditionalData 291 | } 292 | 293 | /** 294 | * TODO: document all possible properties (I only got the android ones) 295 | * 296 | * Loosened up with a dictionary notation, but all non-defined properties need to use (map['prop']) notation 297 | * 298 | * Ideally the developer would overload (merged declaration) this or create a new interface that would extend this one 299 | * so that he could specify any custom code without having to use array notation (map['prop']) for all of them. 300 | */ 301 | interface NotificationEventAdditionalData { 302 | [name: string]: any 303 | 304 | /** 305 | * Whether the notification was received while the app was in the foreground 306 | */ 307 | foreground?: boolean 308 | /** 309 | * Will be true if the application is started by clicking on the push notification, false if the app is already started. (Android/iOS only) 310 | */ 311 | coldstart?: boolean 312 | collapse_key?: string 313 | from?: string 314 | notId?: string 315 | } 316 | 317 | interface PushNotificationStatic { 318 | init(options: InitOptions): PushNotification 319 | new (options: InitOptions): PushNotification 320 | } 321 | } 322 | 323 | interface Window { 324 | PushNotification: PhonegapPluginPush.PushNotificationStatic 325 | } 326 | declare var PushNotification: PhonegapPluginPush.PushNotificationStatic; 327 | -------------------------------------------------------------------------------- /www/browser/push.js: -------------------------------------------------------------------------------- 1 | /* global cordova:false */ 2 | /* globals window, document, navigator */ 3 | 4 | /*! 5 | * Module dependencies. 6 | */ 7 | 8 | var exec = cordova.require('cordova/exec'); 9 | 10 | /** 11 | * PushNotification constructor. 12 | * 13 | * @param {Object} options to initiate Push Notifications. 14 | * @return {PushNotification} instance that can be monitored and cancelled. 15 | */ 16 | var serviceWorker, subscription; 17 | var PushNotification = function(options) { 18 | this._handlers = { 19 | 'registration': [], 20 | 'notification': [], 21 | 'error': [] 22 | }; 23 | 24 | // require options parameter 25 | if (typeof options === 'undefined') { 26 | throw new Error('The options argument is required.'); 27 | } 28 | 29 | // store the options to this object instance 30 | this.options = options; 31 | 32 | // subscription options 33 | var subOptions = {userVisibleOnly: true}; 34 | if (this.options.browser && this.options.browser.applicationServerKey) { 35 | subOptions.applicationServerKey = urlBase64ToUint8Array(this.options.browser.applicationServerKey); 36 | } 37 | 38 | // triggered on registration and notification 39 | var that = this; 40 | 41 | // Add manifest.json to main HTML file 42 | var linkElement = document.createElement('link'); 43 | linkElement.rel = 'manifest'; 44 | linkElement.href = 'manifest.json'; 45 | document.getElementsByTagName('head')[0].appendChild(linkElement); 46 | 47 | if ('serviceWorker' in navigator && 'MessageChannel' in window) { 48 | var result; 49 | var channel = new MessageChannel(); 50 | channel.port1.onmessage = function(event) { 51 | that.emit('notification', event.data); 52 | }; 53 | 54 | navigator.serviceWorker.register('ServiceWorker.js').then(function() { 55 | return navigator.serviceWorker.ready; 56 | }) 57 | .then(function(reg) { 58 | serviceWorker = reg; 59 | reg.pushManager.subscribe(subOptions).then(function(sub) { 60 | subscription = sub; 61 | result = { 'registrationId': sub.endpoint.substring(sub.endpoint.lastIndexOf('/') + 1) }; 62 | that.emit('registration', result); 63 | 64 | // send encryption keys to push server 65 | var xmlHttp = new XMLHttpRequest(); 66 | var xmlURL = (options.browser.pushServiceURL || 'http://push.api.phonegap.com/v1/push') + '/keys'; 67 | xmlHttp.open('POST', xmlURL, true); 68 | 69 | var formData = new FormData(); 70 | formData.append('subscription', JSON.stringify(sub)); 71 | 72 | xmlHttp.send(formData); 73 | 74 | navigator.serviceWorker.controller.postMessage(result, [channel.port2]); 75 | }).catch(function(error) { 76 | if (navigator.serviceWorker.controller === null) { 77 | // When you first register a SW, need a page reload to handle network operations 78 | window.location.reload(); 79 | return; 80 | } 81 | 82 | throw new Error('Error subscribing for Push notifications.'); 83 | }); 84 | }).catch(function(error) { 85 | console.log(error); 86 | throw new Error('Error registering Service Worker'); 87 | }); 88 | } else { 89 | throw new Error('Service Workers are not supported on your browser.'); 90 | } 91 | }; 92 | 93 | /** 94 | * Unregister from push notifications 95 | */ 96 | 97 | PushNotification.prototype.unregister = function(successCallback, errorCallback, options) { 98 | if (!errorCallback) { errorCallback = function() {}; } 99 | 100 | if (typeof errorCallback !== 'function') { 101 | console.log('PushNotification.unregister failure: failure parameter not a function'); 102 | return; 103 | } 104 | 105 | if (typeof successCallback !== 'function') { 106 | console.log('PushNotification.unregister failure: success callback parameter must be a function'); 107 | return; 108 | } 109 | 110 | var that = this; 111 | if (!options) { 112 | that._handlers = { 113 | 'registration': [], 114 | 'notification': [], 115 | 'error': [] 116 | }; 117 | } 118 | 119 | if (serviceWorker) { 120 | serviceWorker.unregister().then(function(isSuccess) { 121 | if (isSuccess) { 122 | var deviceID = subscription.endpoint.substring(subscription.endpoint.lastIndexOf('/') + 1); 123 | var xmlHttp = new XMLHttpRequest(); 124 | var xmlURL = (that.options.browser.pushServiceURL || 'http://push.api.phonegap.com/v1/push') 125 | + '/keys/' + deviceID; 126 | xmlHttp.open('DELETE', xmlURL, true); 127 | xmlHttp.send(); 128 | 129 | successCallback(); 130 | } else { 131 | errorCallback(); 132 | } 133 | }); 134 | } 135 | }; 136 | 137 | /** 138 | * subscribe to a topic 139 | * @param {String} topic topic to subscribe 140 | * @param {Function} successCallback success callback 141 | * @param {Function} errorCallback error callback 142 | * @return {void} 143 | */ 144 | PushNotification.prototype.subscribe = function(topic, successCallback, errorCallback) { 145 | if (!errorCallback) { errorCallback = function() {}; } 146 | 147 | if (typeof errorCallback !== 'function') { 148 | console.log('PushNotification.subscribe failure: failure parameter not a function'); 149 | return; 150 | } 151 | 152 | if (typeof successCallback !== 'function') { 153 | console.log('PushNotification.subscribe failure: success callback parameter must be a function'); 154 | return; 155 | } 156 | 157 | successCallback(); 158 | }; 159 | 160 | /** 161 | * unsubscribe to a topic 162 | * @param {String} topic topic to unsubscribe 163 | * @param {Function} successCallback success callback 164 | * @param {Function} errorCallback error callback 165 | * @return {void} 166 | */ 167 | PushNotification.prototype.unsubscribe = function(topic, successCallback, errorCallback) { 168 | if (!errorCallback) { errorCallback = function() {}; } 169 | 170 | if (typeof errorCallback !== 'function') { 171 | console.log('PushNotification.unsubscribe failure: failure parameter not a function'); 172 | return; 173 | } 174 | 175 | if (typeof successCallback !== 'function') { 176 | console.log('PushNotification.unsubscribe failure: success callback parameter must be a function'); 177 | return; 178 | } 179 | 180 | successCallback(); 181 | }; 182 | 183 | /** 184 | * Call this to set the application icon badge 185 | */ 186 | 187 | PushNotification.prototype.setApplicationIconBadgeNumber = function(successCallback, errorCallback, badge) { 188 | if (!errorCallback) { errorCallback = function() {}; } 189 | 190 | if (typeof errorCallback !== 'function') { 191 | console.log('PushNotification.setApplicationIconBadgeNumber failure: failure parameter not a function'); 192 | return; 193 | } 194 | 195 | if (typeof successCallback !== 'function') { 196 | console.log('PushNotification.setApplicationIconBadgeNumber failure: success callback parameter must be a function'); 197 | return; 198 | } 199 | 200 | successCallback(); 201 | }; 202 | 203 | /** 204 | * Get the application icon badge 205 | */ 206 | 207 | PushNotification.prototype.getApplicationIconBadgeNumber = function(successCallback, errorCallback) { 208 | if (!errorCallback) { errorCallback = function() {}; } 209 | 210 | if (typeof errorCallback !== 'function') { 211 | console.log('PushNotification.getApplicationIconBadgeNumber failure: failure parameter not a function'); 212 | return; 213 | } 214 | 215 | if (typeof successCallback !== 'function') { 216 | console.log('PushNotification.getApplicationIconBadgeNumber failure: success callback parameter must be a function'); 217 | return; 218 | } 219 | 220 | successCallback(); 221 | }; 222 | 223 | /** 224 | * Get the application icon badge 225 | */ 226 | 227 | PushNotification.prototype.clearAllNotifications = function(successCallback, errorCallback) { 228 | if (!errorCallback) { errorCallback = function() {}; } 229 | 230 | if (typeof errorCallback !== 'function') { 231 | console.log('PushNotification.clearAllNotifications failure: failure parameter not a function'); 232 | return; 233 | } 234 | 235 | if (typeof successCallback !== 'function') { 236 | console.log('PushNotification.clearAllNotifications failure: success callback parameter must be a function'); 237 | return; 238 | } 239 | 240 | successCallback(); 241 | }; 242 | 243 | /** 244 | * Listen for an event. 245 | * 246 | * The following events are supported: 247 | * 248 | * - registration 249 | * - notification 250 | * - error 251 | * 252 | * @param {String} eventName to subscribe to. 253 | * @param {Function} callback triggered on the event. 254 | */ 255 | 256 | PushNotification.prototype.on = function(eventName, callback) { 257 | if (this._handlers.hasOwnProperty(eventName)) { 258 | this._handlers[eventName].push(callback); 259 | } 260 | }; 261 | 262 | /** 263 | * Remove event listener. 264 | * 265 | * @param {String} eventName to match subscription. 266 | * @param {Function} handle function associated with event. 267 | */ 268 | 269 | PushNotification.prototype.off = function (eventName, handle) { 270 | if (this._handlers.hasOwnProperty(eventName)) { 271 | var handleIndex = this._handlers[eventName].indexOf(handle); 272 | if (handleIndex >= 0) { 273 | this._handlers[eventName].splice(handleIndex, 1); 274 | } 275 | } 276 | }; 277 | 278 | /** 279 | * Emit an event. 280 | * 281 | * This is intended for internal use only. 282 | * 283 | * @param {String} eventName is the event to trigger. 284 | * @param {*} all arguments are passed to the event listeners. 285 | * 286 | * @return {Boolean} is true when the event is triggered otherwise false. 287 | */ 288 | 289 | PushNotification.prototype.emit = function() { 290 | var args = Array.prototype.slice.call(arguments); 291 | var eventName = args.shift(); 292 | 293 | if (!this._handlers.hasOwnProperty(eventName)) { 294 | return false; 295 | } 296 | 297 | for (var i = 0, length = this._handlers[eventName].length; i < length; i++) { 298 | var callback = this._handlers[eventName][i]; 299 | if (typeof callback === 'function') { 300 | callback.apply(undefined,args); 301 | } else { 302 | console.log('event handler: ' + eventName + ' must be a function'); 303 | } 304 | } 305 | 306 | return true; 307 | }; 308 | 309 | PushNotification.prototype.finish = function(successCallback, errorCallback, id) { 310 | if (!successCallback) { successCallback = function() {}; } 311 | if (!errorCallback) { errorCallback = function() {}; } 312 | if (!id) { id = 'handler'; } 313 | 314 | if (typeof successCallback !== 'function') { 315 | console.log('finish failure: success callback parameter must be a function'); 316 | return; 317 | } 318 | 319 | if (typeof errorCallback !== 'function') { 320 | console.log('finish failure: failure parameter not a function'); 321 | return; 322 | } 323 | 324 | successCallback(); 325 | }; 326 | 327 | /*! 328 | * Push Notification Plugin. 329 | */ 330 | 331 | /** 332 | * Converts the server key to an Uint8Array 333 | * 334 | * @param base64String 335 | * 336 | * @returns {Uint8Array} 337 | */ 338 | function urlBase64ToUint8Array(base64String) { 339 | const padding = '='.repeat((4 - base64String.length % 4) % 4); 340 | const base64 = (base64String + padding) 341 | .replace(/\-/g, '+') 342 | .replace(/_/g, '/'); 343 | 344 | const rawData = window.atob(base64); 345 | const outputArray = new Uint8Array(rawData.length); 346 | 347 | for (var i = 0; i < rawData.length; ++i) { 348 | outputArray[i] = rawData.charCodeAt(i); 349 | } 350 | return outputArray; 351 | } 352 | 353 | 354 | module.exports = { 355 | /** 356 | * Register for Push Notifications. 357 | * 358 | * This method will instantiate a new copy of the PushNotification object 359 | * and start the registration process. 360 | * 361 | * @param {Object} options 362 | * @return {PushNotification} instance 363 | */ 364 | 365 | init: function(options) { 366 | return new PushNotification(options); 367 | }, 368 | 369 | hasPermission: function(successCallback, errorCallback) { 370 | const granted = Notification && Notification.permission === 'granted'; 371 | successCallback({ 372 | isEnabled: granted 373 | }); 374 | }, 375 | 376 | unregister: function(successCallback, errorCallback, options) { 377 | PushNotification.unregister(successCallback, errorCallback, options); 378 | }, 379 | 380 | /** 381 | * PushNotification Object. 382 | * 383 | * Expose the PushNotification object for direct use 384 | * and testing. Typically, you should use the 385 | * .init helper method. 386 | */ 387 | 388 | PushNotification: PushNotification 389 | }; 390 | -------------------------------------------------------------------------------- /www/push.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file has been generated by Babel. 3 | * 4 | * DO NOT EDIT IT DIRECTLY 5 | * 6 | * Edit the JS source file src/js/push.js 7 | **/ 8 | "use strict"; 9 | 10 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 11 | 12 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 13 | 14 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 15 | 16 | /*! 17 | * Module dependencies. 18 | */ 19 | var exec = cordova.require('cordova/exec'); 20 | 21 | var PushNotification = 22 | /*#__PURE__*/ 23 | function () { 24 | /** 25 | * PushNotification constructor. 26 | * 27 | * @param {Object} options to initiate Push Notifications. 28 | * @return {PushNotification} instance that can be monitored and cancelled. 29 | */ 30 | function PushNotification(options) { 31 | var _this = this; 32 | 33 | _classCallCheck(this, PushNotification); 34 | 35 | this.handlers = { 36 | registration: [], 37 | notification: [], 38 | error: [] 39 | }; // require options parameter 40 | 41 | if (typeof options === 'undefined') { 42 | throw new Error('The options argument is required.'); 43 | } // store the options to this object instance 44 | 45 | 46 | this.options = options; // triggered on registration and notification 47 | 48 | var success = function success(result) { 49 | if (result && typeof result.registrationId !== 'undefined') { 50 | _this.emit('registration', result); 51 | } else if (result && result.additionalData && typeof result.additionalData.actionCallback !== 'undefined') { 52 | _this.emit(result.additionalData.actionCallback, result); 53 | } else if (result) { 54 | _this.emit('notification', result); 55 | } 56 | }; // triggered on error 57 | 58 | 59 | var fail = function fail(msg) { 60 | var e = typeof msg === 'string' ? new Error(msg) : msg; 61 | 62 | _this.emit('error', e); 63 | }; // wait at least one process tick to allow event subscriptions 64 | 65 | 66 | setTimeout(function () { 67 | exec(success, fail, 'PushNotification', 'init', [options]); 68 | }, 10); 69 | } 70 | /** 71 | * Unregister from push notifications 72 | */ 73 | 74 | 75 | _createClass(PushNotification, [{ 76 | key: "unregister", 77 | value: function unregister(successCallback) { 78 | var _this2 = this; 79 | 80 | var errorCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; 81 | var options = arguments.length > 2 ? arguments[2] : undefined; 82 | 83 | if (typeof errorCallback !== 'function') { 84 | console.log('PushNotification.unregister failure: failure parameter not a function'); 85 | return; 86 | } 87 | 88 | if (typeof successCallback !== 'function') { 89 | console.log('PushNotification.unregister failure: success callback parameter must be a function'); 90 | return; 91 | } 92 | 93 | var cleanHandlersAndPassThrough = function cleanHandlersAndPassThrough() { 94 | if (!options) { 95 | _this2.handlers = { 96 | registration: [], 97 | notification: [], 98 | error: [] 99 | }; 100 | } 101 | 102 | successCallback(); 103 | }; 104 | 105 | exec(cleanHandlersAndPassThrough, errorCallback, 'PushNotification', 'unregister', [options]); 106 | } 107 | /** 108 | * subscribe to a topic 109 | * @param {String} topic topic to subscribe 110 | * @param {Function} successCallback success callback 111 | * @param {Function} errorCallback error callback 112 | * @return {void} 113 | */ 114 | 115 | }, { 116 | key: "subscribe", 117 | value: function subscribe(topic, successCallback) { 118 | var errorCallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {}; 119 | 120 | if (typeof errorCallback !== 'function') { 121 | console.log('PushNotification.subscribe failure: failure parameter not a function'); 122 | return; 123 | } 124 | 125 | if (typeof successCallback !== 'function') { 126 | console.log('PushNotification.subscribe failure: success callback parameter must be a function'); 127 | return; 128 | } 129 | 130 | exec(successCallback, errorCallback, 'PushNotification', 'subscribe', [topic]); 131 | } 132 | /** 133 | * unsubscribe to a topic 134 | * @param {String} topic topic to unsubscribe 135 | * @param {Function} successCallback success callback 136 | * @param {Function} errorCallback error callback 137 | * @return {void} 138 | */ 139 | 140 | }, { 141 | key: "unsubscribe", 142 | value: function unsubscribe(topic, successCallback) { 143 | var errorCallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {}; 144 | 145 | if (typeof errorCallback !== 'function') { 146 | console.log('PushNotification.unsubscribe failure: failure parameter not a function'); 147 | return; 148 | } 149 | 150 | if (typeof successCallback !== 'function') { 151 | console.log('PushNotification.unsubscribe failure: success callback parameter must be a function'); 152 | return; 153 | } 154 | 155 | exec(successCallback, errorCallback, 'PushNotification', 'unsubscribe', [topic]); 156 | } 157 | /** 158 | * Call this to set the application icon badge 159 | */ 160 | 161 | }, { 162 | key: "setApplicationIconBadgeNumber", 163 | value: function setApplicationIconBadgeNumber(successCallback) { 164 | var errorCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; 165 | var badge = arguments.length > 2 ? arguments[2] : undefined; 166 | 167 | if (typeof errorCallback !== 'function') { 168 | console.log('PushNotification.setApplicationIconBadgeNumber failure: failure ' + 'parameter not a function'); 169 | return; 170 | } 171 | 172 | if (typeof successCallback !== 'function') { 173 | console.log('PushNotification.setApplicationIconBadgeNumber failure: success ' + 'callback parameter must be a function'); 174 | return; 175 | } 176 | 177 | exec(successCallback, errorCallback, 'PushNotification', 'setApplicationIconBadgeNumber', [{ 178 | badge: badge 179 | }]); 180 | } 181 | /** 182 | * Get the application icon badge 183 | */ 184 | 185 | }, { 186 | key: "getApplicationIconBadgeNumber", 187 | value: function getApplicationIconBadgeNumber(successCallback) { 188 | var errorCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; 189 | 190 | if (typeof errorCallback !== 'function') { 191 | console.log('PushNotification.getApplicationIconBadgeNumber failure: failure ' + 'parameter not a function'); 192 | return; 193 | } 194 | 195 | if (typeof successCallback !== 'function') { 196 | console.log('PushNotification.getApplicationIconBadgeNumber failure: success ' + 'callback parameter must be a function'); 197 | return; 198 | } 199 | 200 | exec(successCallback, errorCallback, 'PushNotification', 'getApplicationIconBadgeNumber', []); 201 | } 202 | /** 203 | * Clear all notifications 204 | */ 205 | 206 | }, { 207 | key: "clearAllNotifications", 208 | value: function clearAllNotifications() { 209 | var successCallback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {}; 210 | var errorCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; 211 | 212 | if (typeof errorCallback !== 'function') { 213 | console.log('PushNotification.clearAllNotifications failure: failure parameter not a function'); 214 | return; 215 | } 216 | 217 | if (typeof successCallback !== 'function') { 218 | console.log('PushNotification.clearAllNotifications failure: success callback ' + 'parameter must be a function'); 219 | return; 220 | } 221 | 222 | exec(successCallback, errorCallback, 'PushNotification', 'clearAllNotifications', []); 223 | } 224 | /** 225 | * Clears notifications that have the ID specified. 226 | * @param {Function} [successCallback] Callback function to be called on success. 227 | * @param {Function} [errorCallback] Callback function to be called when an error is encountered. 228 | * @param {Number} id ID of the notification to be removed. 229 | */ 230 | 231 | }, { 232 | key: "clearNotification", 233 | value: function clearNotification() { 234 | var successCallback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {}; 235 | var errorCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; 236 | var id = arguments.length > 2 ? arguments[2] : undefined; 237 | var idNumber = parseInt(id, 10); 238 | 239 | if (Number.isNaN(idNumber) || idNumber > Number.MAX_SAFE_INTEGER || idNumber < 0) { 240 | console.log('PushNotification.clearNotification failure: id parameter must' + 'be a valid integer.'); 241 | return; 242 | } 243 | 244 | exec(successCallback, errorCallback, 'PushNotification', 'clearNotification', [idNumber]); 245 | } 246 | /** 247 | * Listen for an event. 248 | * 249 | * The following events are supported: 250 | * 251 | * - registration 252 | * - notification 253 | * - error 254 | * 255 | * @param {String} eventName to subscribe to. 256 | * @param {Function} callback triggered on the event. 257 | */ 258 | 259 | }, { 260 | key: "on", 261 | value: function on(eventName, callback) { 262 | if (!Object.prototype.hasOwnProperty.call(this.handlers, eventName)) { 263 | this.handlers[eventName] = []; 264 | } 265 | 266 | this.handlers[eventName].push(callback); 267 | } 268 | /** 269 | * Remove event listener. 270 | * 271 | * @param {String} eventName to match subscription. 272 | * @param {Function} handle function associated with event. 273 | */ 274 | 275 | }, { 276 | key: "off", 277 | value: function off(eventName, handle) { 278 | if (Object.prototype.hasOwnProperty.call(this.handlers, eventName)) { 279 | var handleIndex = this.handlers[eventName].indexOf(handle); 280 | 281 | if (handleIndex >= 0) { 282 | this.handlers[eventName].splice(handleIndex, 1); 283 | } 284 | } 285 | } 286 | /** 287 | * Emit an event. 288 | * 289 | * This is intended for internal use only. 290 | * 291 | * @param {String} eventName is the event to trigger. 292 | * @param {*} all arguments are passed to the event listeners. 293 | * 294 | * @return {Boolean} is true when the event is triggered otherwise false. 295 | */ 296 | 297 | }, { 298 | key: "emit", 299 | value: function emit() { 300 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 301 | args[_key] = arguments[_key]; 302 | } 303 | 304 | var eventName = args.shift(); 305 | 306 | if (!Object.prototype.hasOwnProperty.call(this.handlers, eventName)) { 307 | return false; 308 | } 309 | 310 | for (var i = 0, length = this.handlers[eventName].length; i < length; i += 1) { 311 | var callback = this.handlers[eventName][i]; 312 | 313 | if (typeof callback === 'function') { 314 | callback.apply(void 0, args); 315 | } else { 316 | console.log("event handler: ".concat(eventName, " must be a function")); 317 | } 318 | } 319 | 320 | return true; 321 | } 322 | }, { 323 | key: "finish", 324 | value: function finish() { 325 | var successCallback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {}; 326 | var errorCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; 327 | var id = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'handler'; 328 | 329 | if (typeof successCallback !== 'function') { 330 | console.log('finish failure: success callback parameter must be a function'); 331 | return; 332 | } 333 | 334 | if (typeof errorCallback !== 'function') { 335 | console.log('finish failure: failure parameter not a function'); 336 | return; 337 | } 338 | 339 | exec(successCallback, errorCallback, 'PushNotification', 'finish', [id]); 340 | } 341 | }]); 342 | 343 | return PushNotification; 344 | }(); 345 | /*! 346 | * Push Notification Plugin. 347 | */ 348 | 349 | 350 | module.exports = { 351 | /** 352 | * Register for Push Notifications. 353 | * 354 | * This method will instantiate a new copy of the PushNotification object 355 | * and start the registration process. 356 | * 357 | * @param {Object} options 358 | * @return {PushNotification} instance 359 | */ 360 | init: function init(options) { 361 | return new PushNotification(options); 362 | }, 363 | hasPermission: function hasPermission(successCallback, errorCallback) { 364 | exec(successCallback, errorCallback, 'PushNotification', 'hasPermission', []); 365 | }, 366 | createChannel: function createChannel(successCallback, errorCallback, channel) { 367 | exec(successCallback, errorCallback, 'PushNotification', 'createChannel', [channel]); 368 | }, 369 | deleteChannel: function deleteChannel(successCallback, errorCallback, channelId) { 370 | exec(successCallback, errorCallback, 'PushNotification', 'deleteChannel', [channelId]); 371 | }, 372 | listChannels: function listChannels(successCallback, errorCallback) { 373 | exec(successCallback, errorCallback, 'PushNotification', 'listChannels', []); 374 | }, 375 | 376 | /** 377 | * PushNotification Object. 378 | * 379 | * Expose the PushNotification object for direct use 380 | * and testing. Typically, you should use the 381 | * .init helper method. 382 | */ 383 | PushNotification: PushNotification 384 | }; --------------------------------------------------------------------------------