diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000000000000000000000000000000000000..9b1afcba2f526c6d28c30c3ab67d6f0071f0dcaa
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,180 @@
+# Change Log
+
+## 3.0.1
+
+**Fixes:**
+
+* fix(android): add service exported flags w/ intent-filter (#173)
+* fix(android): processPushBundle flipped returned value (#160)
+* fix: doc typo (#159)
+
+**Chores:**
+
+* chore: correct Bower metadata to MIT license (#166)
+* chore(npm): rebuilt package-lock.json & fix audit
+
+**Docs:**
+
+* doc: add 2.x and 3.x to PLATFORM_SUPPORT.md
+
+## 3.0.0
+
+**Overview:**
+
+In this major release, there are many breaking changes to the Android platform. The primary changes in this release are dropping Android Support Library for AndroidX, updating the Google Services Gradle Plugin requirement, refactoring the code to Kotlin, and upgrading all other third-party libraries. See below for more details.
+
+---
+
+**Requirement Changes:**
+
+* Cordova-Android: 9.x
+
+---
+
+**Libraries & Dependencie Updates and Changes:**
+
+* Bumped `me.leolin:ShortcutBadger@1.1.22`
+* Bumped `Google Services Gradle Plugin@4.3.8`
+* Migrated from **Android Support Library** to **AndroidX**
+ * Added `androidx.core:core@1.6.+`
+
+---
+
+**Breaking:**
+
+* feat!(android): migrate source code to kotlin w/ refactors (#123)
+ * kotlin@1.5.20
+ * add before_compile hookscript for cordova-android 9.x support
+ * converted all java files to kotlin
+ * updted target-dir path for kotlin files
+ * refactored with improved null checks
+ * removed some duplicated code
+ * updated logging (more logging & better tagging)¥
+* feat!(android): bump platform requirements (#122)
+* feat!(android): bump me.leolin:ShortcutBadger@1.1.22 (#121)
+* feat!(android): bump Google Services Gradle Plugin@4.3.8 (#120)
+* feat!(android): Migrate to AndroidX (#119)
+ * feat!(android): migrate ASL to AndroidX
+ * feat!(android): swap framework support-v13 w/ androidx.core
+ * feat!(android): force AndroidXEnabled to true
+ * feat!(android): bump androidx.core:core@1.6.+
+ * doc(android): add androidx core version
+
+**Fixes:**
+
+* fix(docs): update TS type import to new package name (#109)
+
+**Chores:**
+
+* chore: rebuilt package-lock.json (#131)
+* chore: bump npm dev dependencies (#132) (#133)
+* chore(npm): bump devDep, rebuild package-lock & fix audit (#110)
+* chore(npm): bump devDep @cordova/eslint-config@^4.0.0 w/ fixes (#144)
+
+**CI:**
+
+* ci: remove old travis configs (#128)
+* ci: add codacy badge (#129)
+* ci: add gh-actions badge to readme (#130)
+
+**Docs:**
+
+* doc: fixed minor typo (#98)
+
+## 2.0.0
+
+**Overview:**
+
+This release contains breaking changes. One of these needed changes resolved issues of restoring the plugin from npm.
+
+With this breaking change, the `havesource-cordova-plugin-push` package name is no longer used. Please completely uninstall the old version before installing the new version. This will ensure that the correct package name `@havesource/cordova-plugin-push` is used.
+
+There is also an update to the installation requirements:
+
+| | Version |
+| - | - |
+| Cordova CLI | 10.0.0 |
+| Cordova Android | 8.0.0 |
+| **Cordova iOS** | **6.0.0** |
+| CocoaPods | 1.8.0 |
+
+**Breaking:**
+
+* breaking(ios): requirement bump [#80](https://github.com/havesource/cordova-plugin-push/pull/80)
+* breaking: fixed incorrect initial cordova-cli requirement [79333b2](https://github.com/havesource/cordova-plugin-push/commit/79333b25e1ff68fea377be499da91528c82fa21f)
+
+**Feature:**
+
+* feat(ios): force `cocoapods` cdn [#48](https://github.com/havesource/cordova-plugin-push/pull/48)
+* feat(ios): support `firebase/messaging` dep version override [#47](https://github.com/havesource/cordova-plugin-push/pull/47)
+
+**Chore:**
+
+* chore(`npm`): rebuilt `package-lock.json` [67e4e4b](https://github.com/havesource/cordova-plugin-push/commit/67e4e4ba185511e60b4d85cae882c41dae1c9cc0)
+* chore(`android`): remove duplicate code [#81](https://github.com/havesource/cordova-plugin-push/pull/81)
+* chore: bump dev dependencies [#79](https://github.com/havesource/cordova-plugin-push/pull/79)
+
+**CI & Docs:**
+
+* ci(gh-actions): bump dependencies [#78](https://github.com/havesource/cordova-plugin-push/pull/78)
+
+
+## 1.0.0
+
+**Breaking:**
+
+* breaking(android): bump fcm@18.+ [#19](https://github.com/havesource/cordova-plugin-push/pull/19)
+* breaking(android): drop phonegap-plugin-multidex dependency [#21](https://github.com/havesource/cordova-plugin-push/pull/21)
+* breaking(android): move clearAllNotifications to destroy from pause [#13](https://github.com/havesource/cordova-plugin-push/pull/13)
+
+**Feature:**
+
+* feat(android): notification data pass [#31](https://github.com/havesource/cordova-plugin-push/pull/31)
+* feat(ios): support critical alert notifications [#12](https://github.com/havesource/cordova-plugin-push/pull/12)
+* feat(ios): increase firebase framework to 6.32.2 [#42](https://github.com/havesource/cordova-plugin-push/pull/42)
+* feat: remove cordova-support-google-services dependency [#8](https://github.com/havesource/cordova-plugin-push/pull/8)
+
+**Fix:**
+
+* fix(android): missing channel description crash [#53](https://github.com/havesource/cordova-plugin-push/pull/53)
+* fix(android): Use app name for default channel [#49](https://github.com/havesource/cordova-plugin-push/pull/49)
+* fix(android): enable lights when lightColor is set [#15](https://github.com/havesource/cordova-plugin-push/pull/15)
+* fix(browser): corrected path to manifest file. [#32](https://github.com/havesource/cordova-plugin-push/pull/32)
+
+**Chore:**
+
+* chore(android): set requirement >= 8.0.0 [#52](https://github.com/havesource/cordova-plugin-push/pull/52)
+* chore(android): cleanup & format [#26](https://github.com/havesource/cordova-plugin-push/pull/26)
+* chore(android): bump com.android.support:support-v13:28.0.0 [#20](https://github.com/havesource/cordova-plugin-push/pull/20)
+* chore(ios): use latest firebase library [#24](https://github.com/havesource/cordova-plugin-push/pull/24)
+* chore(npm): rebuilt package-lock.json [#55](https://github.com/havesource/cordova-plugin-push/pull/55)
+* chore(npm): properly configure for scope package [#33](https://github.com/havesource/cordova-plugin-push/pull/33)
+* chore(type-definition): Update PushNotificationStatic [#14](https://github.com/havesource/cordova-plugin-push/pull/14)
+* chore(github-pages): remove config [#4](https://github.com/havesource/cordova-plugin-push/pull/4)
+* chore: update ticket management [#27](https://github.com/havesource/cordova-plugin-push/pull/27)
+* chore: add missing build of push.js [#22](https://github.com/havesource/cordova-plugin-push/pull/22)
+* chore: match plugin.xml version w/ package.json [#10](https://github.com/havesource/cordova-plugin-push/pull/10)
+* chore: update xml namespace [#9](https://github.com/havesource/cordova-plugin-push/pull/9)
+* chore: update version requirements [#7](https://github.com/havesource/cordova-plugin-push/pull/7)
+* chore: update npm & git ignore list [#6](https://github.com/havesource/cordova-plugin-push/pull/6)
+* chore: update plugin package [#1](https://github.com/havesource/cordova-plugin-push/pull/1)
+* chore: remove unused dependencies [#2](https://github.com/havesource/cordova-plugin-push/pull/2)
+
+**Refactor & Style:**
+
+* refactor(eslint): update dependencies w/ fixes [#3](https://github.com/havesource/cordova-plugin-push/pull/3)
+* style(md): format with md all in one (vscode) [#11](https://github.com/havesource/cordova-plugin-push/pull/11)
+
+**CI & Docs:**
+
+* ci(gh-actions): add workflow [#23](https://github.com/havesource/cordova-plugin-push/pull/23)
+* ci: update travis configs [#5](https://github.com/havesource/cordova-plugin-push/pull/5)
+* doc(android): enable & set notification light with lightColor [#54](https://github.com/havesource/cordova-plugin-push/pull/54)
+* doc: cleanup docs [#51](https://github.com/havesource/cordova-plugin-push/pull/51)
+* doc: update various markdown docs [#28](https://github.com/havesource/cordova-plugin-push/pull/28)
+
+## Previous Change Log
+
+Since this repo is a fork from the original Adobe PhoneGap push plugin, you can continue to read the previous changelog here:
+
+https://github.com/havesource/cordova-plugin-push/blob/phonegap-2.3.0/CHANGELOG.md
diff --git a/MIT-LICENSE b/MIT-LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..abf9a9803b89ef818f3f2bcc93a1fafe30850a35
--- /dev/null
+++ b/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright 2012-2017 Adobe Systems
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README-github.md b/README-github.md
new file mode 100644
index 0000000000000000000000000000000000000000..4f2ea2ae939eee294bda36eff33cb81e98d00db5
--- /dev/null
+++ b/README-github.md
@@ -0,0 +1,72 @@
+# Cordova Plugin Push
+
+[](https://github.com/havesource/cordova-plugin-push/actions/workflows/ci.yml) [](https://app.codacy.com/gh/havesource/cordova-plugin-push?utm_source=github.com&utm_medium=referral&utm_content=havesource/cordova-plugin-push&utm_campaign=Badge_Grade_Settings)
+
+> Register and receive push notifications
+
+# What is this?
+
+This plugin offers support to receive and handle native push notifications with a **single unified API**.
+
+This does not mean you will be able to send a single push message and have it arrive on devices running different operating systems. By default Android uses FCM and iOS uses APNS and their payloads are significantly different. Even if you are using FCM for both Android and iOS there are differences in the payload required for the plugin to work correctly. For Android **always** put your push payload in the `data` section of the push notification. For more information on why that is the case read [Notification vs Data Payload](https://github.com/havesource/cordova-plugin-push/blob/master/docs/PAYLOAD.md#notification-vs-data-payloads). For iOS follow the regular [FCM documentation](https://firebase.google.com/docs/cloud-messaging/http-server-ref).
+
+This plugin does not provide a way to determine which platform you are running on. The best way to do that is use the `device.platform` property provided by [cordova-plugin-device](https://github.com/apache/cordova-plugin-device).
+
+* [Reporting Issues](docs/ISSUES.md)
+* [Installation](docs/INSTALLATION.md)
+* [API reference](docs/API.md)
+* [Typescript support](docs/TYPESCRIPT.md)
+* [Examples](docs/EXAMPLES.md)
+* [Platform support](docs/PLATFORM_SUPPORT.md)
+* [Cloud build support (PG Build, IntelXDK)](docs/PHONEGAP_BUILD.md)
+* [Push notification payload details](docs/PAYLOAD.md)
+* [Contributing](.github/CONTRIBUTING.md)
+* [License (MIT)](MIT-LICENSE)
+
+# Do you like tutorial? You get tutorial!
+
+* [PhoneGap Day US Push Workshop 2016 (using node-gcm)](http://macdonst.github.io/push-workshop/)
+
+# Thanks to all our contributors
+
+[
](https://github.com/10ko)[
](https://github.com/TVolly)[
](https://github.com/waptaxi)[
](https://github.com/viktormuller)[
](https://github.com/devgeeks)[
](https://github.com/rastafan)
+
+[
](https://github.com/mdoelker)[
](https://github.com/markeeftb)[
](https://github.com/malwatte)[
](https://github.com/madebycm)[
](https://github.com/kelvinhokk)[
](https://github.com/keab42)
+
+[
](https://github.com/jomarocas)[
](https://github.com/giuseppelt)[
](https://github.com/ericb)[
](https://github.com/eKazim)[
](https://github.com/clementcontet)[
](https://github.com/yaswanthsvist)
+
+[
](https://github.com/Vabs28)[
](https://github.com/TillaTheHun0)[
](https://github.com/tomasvarg)[
](https://github.com/tobmaster)[
](https://github.com/ThiagoBueno)[
](https://github.com/szh)
+
+[
](https://github.com/SharUpOff)[
](https://github.com/smorstabilini)[
](https://github.com/fesor)[
](https://github.com/GreyDekart)[
](https://github.com/sebastiansier)[
](https://github.com/olastor)
+
+[
](https://github.com/tanansatpal)[
](https://github.com/SandroGrzicic)[
](https://github.com/xorxor)[
](https://github.com/rubenstolk)[
](https://github.com/roel-sluper)[
](https://github.com/pataar)
+
+[
](https://github.com/peteonrails)[
](https://github.com/pjalbuquerque)[
](https://github.com/NitroGhost)[
](https://github.com/matrosov-nikita)[
](https://github.com/Mikejo5000)[
](https://github.com/michellarcari)
+
+[
](https://github.com/adamschachne)[
](https://github.com/alharding)[
](https://github.com/albertleao)[
](https://github.com/gotev)[
](https://github.com/Alex-Sessler)[
](https://github.com/ben-8409)
+
+[
](https://github.com/bmwertman)[
](https://github.com/bmatto)[
](https://github.com/countcain)[
](https://github.com/CookieCookson)[
](https://github.com/cdorner)[
](https://github.com/colene)
+
+[
](https://github.com/cfsnyder)[
](https://github.com/cmalard)[
](https://github.com/dansumption)[
](https://github.com/dannywillems)[
](https://github.com/DrMoriarty)[
](https://github.com/eladmoshe)
+
+[
](https://github.com/mlabarca)[
](https://github.com/bromeostasis)[
](https://github.com/filmaj)[
](https://github.com/geo242)[
](https://github.com/gbenvenuti)[
](https://github.com/polyn0m)
+
+[
](https://github.com/jacquesdev)[
](https://github.com/janpio)[
](https://github.com/jakari)[
](https://github.com/purplecabbage)[
](https://github.com/theaccordance)[
](https://github.com/jonas-m-)
+
+[
](https://github.com/Chuckytuh)[
](https://github.com/leonardobazico)[
](https://github.com/loslislo-lshift)[
](https://github.com/luka5)[
](https://github.com/mac89)[
](https://github.com/markokeeffe)
+
+[
](https://github.com/mbektchiev)[
](https://github.com/goya)[
](https://github.com/slorber)[
](https://github.com/daserge)[
](https://github.com/smdvdsn)[
](https://github.com/ryanluker)
+
+[
](https://github.com/russellbeattie)[
](https://github.com/rjmunro)[
](https://github.com/hanicker)[
](https://github.com/mwbrooks)[
](https://github.com/LightZam)[
](https://github.com/laagland)
+
+[
](https://github.com/cuatl)[
](https://github.com/gianpaj)[
](https://github.com/EdMcBane)[
](https://github.com/chriswiggins)[
](https://github.com/barryvdh)[
](https://github.com/armno)
+
+[
](https://github.com/archananaik)[
](https://github.com/jakub-g)[
](https://github.com/shazron)[
](https://github.com/sclement41)[
](https://github.com/hung-doan)[
](https://github.com/BBosman)
+
+[
](https://github.com/giordanocardillo)[
](https://github.com/mikepsinn)[
](https://github.com/AdriVanHoudt)[
](https://github.com/alexislg2)[
](https://github.com/jcesarmobile)[
](https://github.com/nadyaA)
+
+[
](https://github.com/jdhiro)[
](https://github.com/edewit)[
](https://github.com/wildabeast)[
](https://github.com/mkuklis)[
](https://github.com/ashconnell)[
](https://github.com/zwacky)
+
+[
](https://github.com/rakatyal)[
](https://github.com/jtbdevelopment)[
](https://github.com/EddyVerbruggen)[
](https://github.com/fredgalvao)[
](https://github.com/bobeast)[
](https://github.com/macdonst)
+
+[
](https://github.com/larrybahr)
diff --git a/README.md b/README.md
deleted file mode 100644
index c3eff07c4aa957e7b13dd530d55b720e3c2ffcac..0000000000000000000000000000000000000000
--- a/README.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# Cordova Plugin Push
-
-
-
-## Getting started
-
-To make it easy for you to get started with GitLab, here's a list of recommended next steps.
-
-Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
-
-## Add your files
-
-- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
-- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
-
-```
-cd existing_repo
-git remote add origin http://dev.silverlakemobility.com/gitlab/cordova-custom/cordova-plugin-push.git
-git branch -M main
-git push -uf origin main
-```
-
-## Integrate with your tools
-
-- [ ] [Set up project integrations](http://dev.silverlakemobility.com/gitlab/cordova-custom/cordova-plugin-push/-/settings/integrations)
-
-## Collaborate with your team
-
-- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
-- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
-- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
-- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
-- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
-
-## Test and Deploy
-
-Use the built-in continuous integration in GitLab.
-
-- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
-- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
-- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
-- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
-- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
-
-***
-
-# Editing this README
-
-When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
-
-## Suggestions for a good README
-Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
-
-## Name
-Choose a self-explaining name for your project.
-
-## Description
-Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
-
-## Badges
-On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
-
-## Visuals
-Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
-
-## Installation
-Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
-
-## Usage
-Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
-
-## Support
-Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
-
-## Roadmap
-If you have ideas for releases in the future, it is a good idea to list them in the README.
-
-## Contributing
-State if you are open to contributions and what your requirements are for accepting them.
-
-For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
-
-You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
-
-## Authors and acknowledgment
-Show your appreciation to those who have contributed to the project.
-
-## License
-For open source projects, say how it is licensed.
-
-## Project status
-If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
diff --git a/hooks/android/beforeCompile.js b/hooks/android/beforeCompile.js
new file mode 100644
index 0000000000000000000000000000000000000000..b33cd060ea0e154e9c8fb946f137c9076c958540
--- /dev/null
+++ b/hooks/android/beforeCompile.js
@@ -0,0 +1,50 @@
+const { join } = require('path');
+const { existsSync, readFileSync, writeFileSync } = require('fs');
+const { parseElementtreeSync: ParseElementtreeSync } = require('cordova-common/src/util/xml-helpers');
+const platform = require('cordova-android');
+
+module.exports = function (context) {
+ if (!isExecutable()) {
+ console.log('[cordova-plugin-push::before-compile] skipping before_compile hookscript.');
+ return;
+ }
+
+ const buildGradleFilePath = join(context.opts.projectRoot, 'platforms/android/build.gradle');
+
+ if (!existsSync(buildGradleFilePath)) {
+ console.log('[cordova-plugin-push::before-compile] could not find "build.gradle" file.');
+ return;
+ }
+
+ updateBuildGradle(context, buildGradleFilePath);
+};
+
+/**
+ * This hookscript is executable only when the platform version less then 10.x
+ * @returns Boolean
+ */
+function isExecutable () {
+ const majorVersion = parseInt(platform.version(), 10);
+ return majorVersion < 10 && majorVersion >= 9;
+}
+
+function getPluginKotlinVersion (context) {
+ const pluginConfig = new ParseElementtreeSync(join(context.opts.projectRoot, 'plugins/@havesource/cordova-plugin-push/plugin.xml'));
+
+ return pluginConfig
+ .findall('./platform[@name="android"]').pop()
+ .findall('./config-file[@target="config.xml"]').pop()
+ .findall('preference').filter(
+ elem => elem.attrib.name.toLowerCase() === 'GradlePluginKotlinVersion'.toLowerCase()
+ ).pop().attrib.value;
+}
+
+function updateBuildGradle (context, buildGradleFilePath) {
+ const kotlinVersion = getPluginKotlinVersion(context);
+ const updateContent = readFileSync(buildGradleFilePath, 'utf8')
+ .replace(/ext.kotlin_version = ['"](.*)['"]/g, `ext.kotlin_version = '${kotlinVersion}'`);
+
+ writeFileSync(buildGradleFilePath, updateContent);
+
+ console.log(`[cordova-plugin-push::before-compile] updated "build.gradle" file with kotlin version set to: ${kotlinVersion}`);
+}
diff --git a/hooks/browser/updateManifest.js b/hooks/browser/updateManifest.js
new file mode 100644
index 0000000000000000000000000000000000000000..57551ec49ff6555dc835022c659226df4c03aa5c
--- /dev/null
+++ b/hooks/browser/updateManifest.js
@@ -0,0 +1,49 @@
+module.exports = function (context) {
+ console.log('Updating manifest.json with push properties…');
+ var path = require('path');
+ var fs = require('fs');
+
+ var platformProjPath = path.join(
+ context.opts.projectRoot,
+ 'platforms/browser'
+ );
+
+ if (!fs.existsSync(platformProjPath)) {
+ platformProjPath = context.opts.projectRoot;
+ }
+
+ var platformManifestJson = path.join(platformProjPath, 'www/manifest.json');
+
+ if (!fs.existsSync(platformManifestJson)) {
+ return;
+ }
+
+ fs.readFile(platformManifestJson, 'utf8', function (err, platformJson) {
+ if (err) throw err; // we'll not consider error handling for now
+ var platformManifest = JSON.parse(platformJson);
+
+ var pluginManifestPath = path.join(
+ context.opts.projectRoot,
+ 'plugins/@havesource/cordova-plugin-push/src/browser/manifest.json'
+ );
+
+ fs.readFile(pluginManifestPath, 'utf8', function (err, pluginJson) {
+ if (err) throw err; // we'll not consider error handling for now
+ var pluginManifest = JSON.parse(pluginJson);
+
+ platformManifest.gcm_sender_id = pluginManifest.gcm_sender_id;
+
+ fs.writeFile(
+ platformManifestJson,
+ JSON.stringify(platformManifest),
+ function (err) {
+ if (err) {
+ return console.log(err);
+ }
+
+ console.log('Manifest updated with push sender ID');
+ }
+ );
+ });
+ });
+};
diff --git a/hooks/windows/setToastCapable.js b/hooks/windows/setToastCapable.js
new file mode 100644
index 0000000000000000000000000000000000000000..ade622fe23b639eb4ae707cf7e437d53e9f0f890
--- /dev/null
+++ b/hooks/windows/setToastCapable.js
@@ -0,0 +1,18 @@
+module.exports = function (context) {
+ console.log('Updating appxmanifests with ToastCapable=true ...');
+ var path = require('path');
+ var fs = require('fs');
+
+ var platformProjPath = path.join(context.opts.projectRoot, 'platforms/windows');
+ if (!fs.existsSync(platformProjPath)) {
+ platformProjPath = context.opts.projectRoot;
+ }
+
+ var AppxManifest = require(path.join(platformProjPath, 'cordova/lib/AppxManifest'));
+
+ ['package.phone.appxmanifest', 'package.windows.appxmanifest'].forEach(function (manifestPath) {
+ var manifest = AppxManifest.get(path.join(platformProjPath, manifestPath));
+ manifest.getVisualElements().setToastCapable(true);
+ manifest.write();
+ });
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..c3277cbf8dc2d47ec2e9d2be4b11bf13c6c4ecb8
--- /dev/null
+++ b/package.json
@@ -0,0 +1,98 @@
+{
+ "_from": "git+https://github.com/havesource/cordova-plugin-push.git#master",
+ "_id": "@havesource/cordova-plugin-push@4.0.0-dev.0",
+ "_inBundle": false,
+ "_integrity": "",
+ "_location": "/@havesource/cordova-plugin-push",
+ "_phantomChildren": {},
+ "_requested": {
+ "type": "git",
+ "raw": "https://github.com/havesource/cordova-plugin-push#master",
+ "rawSpec": "https://github.com/havesource/cordova-plugin-push#master",
+ "saveSpec": "git+https://github.com/havesource/cordova-plugin-push.git#master",
+ "fetchSpec": "https://github.com/havesource/cordova-plugin-push.git",
+ "gitCommittish": "master"
+ },
+ "_requiredBy": [
+ "#USER",
+ "/"
+ ],
+ "_resolved": "git+https://github.com/havesource/cordova-plugin-push.git#c3fb5b894afe17a05e21be135961f5831bafb1e0",
+ "_spec": "https://github.com/havesource/cordova-plugin-push#master",
+ "_where": "/Users/n5648/Assorted Project/GerSg3030R3S4-CordovaAndroid11",
+ "author": {
+ "name": "Erisu"
+ },
+ "bugs": {
+ "url": "https://github.com/havesource/cordova-plugin-push/issues"
+ },
+ "bundleDependencies": false,
+ "cordova": {
+ "id": "@havesource/cordova-plugin-push",
+ "platforms": [
+ "ios",
+ "android",
+ "windows",
+ "browser"
+ ]
+ },
+ "deprecated": false,
+ "description": "Register and receive push notifications.",
+ "devDependencies": {
+ "@babel/cli": "^7.15.4",
+ "@babel/core": "^7.15.5",
+ "@babel/eslint-parser": "^7.15.4",
+ "@babel/preset-env": "^7.15.6",
+ "@cordova/eslint-config": "^4.0.0",
+ "babel-plugin-add-header-comment": "^1.0.3",
+ "fs-extra": "^10.0.0",
+ "jasmine": "^3.9.0",
+ "nodemon": "^2.0.12",
+ "nopt": "^5.0.0",
+ "xml2js": "^0.4.23"
+ },
+ "engines": {
+ "cordovaDependencies": {
+ "1.0.0": {
+ "cordova": ">=10.0.0",
+ "cordova-android": ">=8.0.0",
+ "cordova-ios": ">=5.1.1"
+ },
+ "2.0.0": {
+ "cordova": ">=10.0.0",
+ "cordova-android": ">=8.0.0",
+ "cordova-ios": ">=6.0.0"
+ },
+ "3.0.0": {
+ "cordova": ">=10.0.0",
+ "cordova-android": ">=9.0.0",
+ "cordova-ios": ">=6.0.0"
+ }
+ }
+ },
+ "homepage": "https://github.com/havesource/cordova-plugin-push#readme",
+ "keywords": [
+ "ecosystem:cordova",
+ "cordova-ios",
+ "cordova-android",
+ "cordova-windows",
+ "cordova-browser"
+ ],
+ "license": "MIT",
+ "name": "@havesource/cordova-plugin-push",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/havesource/cordova-plugin-push.git"
+ },
+ "scripts": {
+ "build": "babel src/js --out-dir www",
+ "build:watch": "nodemon -w ./src/js -e js -x npm run build",
+ "jasmine": "jasmine --config=spec/unit.json",
+ "lint": "eslint .",
+ "lint:fix": "npm run lint -- --fix",
+ "precommit-msg": "echo 'Pre-commit checks...' && exit 0",
+ "test": "npm run build && npm run lint && npm run jasmine"
+ },
+ "types": "./types/index.d.ts",
+ "version": "4.0.0-dev.0"
+}
diff --git a/plugin.xml b/plugin.xml
new file mode 100755
index 0000000000000000000000000000000000000000..33c75c2d7305c815393e6e595c4d99d7fa6b88b7
--- /dev/null
+++ b/plugin.xml
@@ -0,0 +1,145 @@
+
+
+
+ Cordova Push Plugin
+ Enable receiving push notifications on Android, iOS and Windows devices. Android uses Firebase Cloud Messaging. iOS uses Apple APNS Notifications. Windows uses Microsoft WNS Notifications.
+
+ MIT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ remote-notification
+
+
+
+
+ development
+
+
+
+ production
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/android/com/adobe/phonegap/push/BackgroundActionButtonHandler.kt b/src/android/com/adobe/phonegap/push/BackgroundActionButtonHandler.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3df4539c06c8ef12df1484e51d1aefd93946c3f6
--- /dev/null
+++ b/src/android/com/adobe/phonegap/push/BackgroundActionButtonHandler.kt
@@ -0,0 +1,54 @@
+package com.adobe.phonegap.push
+
+import android.annotation.SuppressLint
+import android.app.NotificationManager
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import androidx.core.app.RemoteInput
+
+/**
+ * Background Action Button Handler
+ */
+@Suppress("HardCodedStringLiteral")
+@SuppressLint("LongLogTag", "LogConditional")
+class BackgroundActionButtonHandler : BroadcastReceiver() {
+ companion object {
+ private const val TAG: String = "${PushPlugin.PREFIX_TAG} (BackgroundActionButtonHandler)"
+ }
+
+ /**
+ * @param context
+ * @param intent
+ */
+ override fun onReceive(context: Context, intent: Intent) {
+ val notId = intent.getIntExtra(PushConstants.NOT_ID, 0)
+ Log.d(TAG, "Not ID: $notId")
+
+ val notificationManager =
+ context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.cancel(FCMService.getAppName(context), notId)
+
+ intent.extras?.let { extras ->
+ Log.d(TAG, "Intent Extras: $extras")
+ extras.getBundle(PushConstants.PUSH_BUNDLE)?.apply {
+ putBoolean(PushConstants.FOREGROUND, false)
+ putBoolean(PushConstants.COLDSTART, false)
+ putString(
+ PushConstants.ACTION_CALLBACK,
+ extras.getString(PushConstants.CALLBACK)
+ )
+
+ RemoteInput.getResultsFromIntent(intent)?.let { remoteInputResults ->
+ val results = remoteInputResults.getCharSequence(PushConstants.INLINE_REPLY).toString()
+ Log.d(TAG, "Inline Reply: $results")
+
+ putString(PushConstants.INLINE_REPLY, results)
+ }
+ }
+
+ PushPlugin.sendExtras(extras)
+ }
+ }
+}
diff --git a/src/android/com/adobe/phonegap/push/BackgroundHandlerActivity.kt b/src/android/com/adobe/phonegap/push/BackgroundHandlerActivity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fb805731eb0400c236b1a22459bb1aeb06795620
--- /dev/null
+++ b/src/android/com/adobe/phonegap/push/BackgroundHandlerActivity.kt
@@ -0,0 +1,132 @@
+package com.adobe.phonegap.push
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.NotificationManager
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import androidx.core.app.RemoteInput
+
+/**
+ * Background Handler Activity
+ */
+@Suppress("HardCodedStringLiteral")
+@SuppressLint("LongLogTag", "LogConditional")
+class BackgroundHandlerActivity : Activity() {
+ companion object {
+ private const val TAG: String = "${PushPlugin.PREFIX_TAG} (BackgroundHandlerActivity)"
+ }
+
+ /**
+ * This activity will be started if the user touches a notification that we own.
+ * We send it's data off to the push plugin for processing.
+ * If needed, we boot up the main activity to kickstart the application.
+ *
+ * @param savedInstanceState
+ *
+ * @see android.app.Activity#onCreate(android.os.Bundle)
+ */
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ Log.v(TAG, "onCreate")
+
+ intent.extras?.let { extras ->
+ val notId = extras.getInt(PushConstants.NOT_ID, 0)
+ val callback = extras.getString(PushConstants.CALLBACK)
+ val startOnBackground = extras.getBoolean(PushConstants.START_IN_BACKGROUND, false)
+ val dismissed = extras.getBoolean(PushConstants.DISMISSED, false)
+
+ Log.d(TAG, "Not ID: $notId")
+ Log.d(TAG, "Callback: $callback")
+ Log.d(TAG, "Start In Background: $startOnBackground")
+ Log.d(TAG, "Dismissed: $dismissed")
+
+ FCMService().setNotification(notId, "")
+
+ if (!startOnBackground) {
+ val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.cancel(FCMService.getAppName(this), notId)
+ }
+
+ processPushBundle()
+ finish()
+
+ if (!dismissed) {
+ // Tap the notification, app should start.
+ if (!PushPlugin.isActive) {
+ forceMainActivityReload(false)
+ } else {
+ forceMainActivityReload(true)
+ }
+ }
+ }
+ }
+
+ private fun processPushBundle() {
+ /*
+ * Takes the pushBundle extras from the intent,
+ * and sends it through to the PushPlugin for processing.
+ */
+ intent.extras?.let { extras ->
+ var originalExtras = extras.getBundle(PushConstants.PUSH_BUNDLE)
+
+ if (originalExtras == null) {
+ originalExtras = extras
+ originalExtras.remove(PushConstants.FROM)
+ originalExtras.remove(PushConstants.MESSAGE_ID)
+ originalExtras.remove(PushConstants.COLLAPSE_KEY)
+ }
+
+ originalExtras.putBoolean(PushConstants.FOREGROUND, false)
+ originalExtras.putBoolean(PushConstants.COLDSTART, !PushPlugin.isActive)
+ originalExtras.putBoolean(PushConstants.DISMISSED, extras.getBoolean(PushConstants.DISMISSED))
+ originalExtras.putString(
+ PushConstants.ACTION_CALLBACK,
+ extras.getString(PushConstants.CALLBACK)
+ )
+ originalExtras.remove(PushConstants.NO_CACHE)
+
+ RemoteInput.getResultsFromIntent(intent)?.apply {
+ val reply = getCharSequence(PushConstants.INLINE_REPLY).toString()
+ Log.d(TAG, "Inline Reply: $reply")
+
+ originalExtras.putString(PushConstants.INLINE_REPLY, reply)
+ }
+
+ PushPlugin.sendExtras(originalExtras)
+ }
+ }
+
+ private fun forceMainActivityReload(startOnBackground: Boolean) {
+ /*
+ * Forces the main activity to re-launch if it's unloaded.
+ */
+ val launchIntent = packageManager.getLaunchIntentForPackage(applicationContext.packageName)
+
+ intent.extras?.let { extras ->
+ launchIntent?.apply {
+ extras.getBundle(PushConstants.PUSH_BUNDLE)?.let { originalExtras ->
+ putExtras(originalExtras)
+ }
+
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ addFlags(Intent.FLAG_FROM_BACKGROUND)
+ putExtra(PushConstants.START_IN_BACKGROUND, startOnBackground)
+ }
+ }
+
+ startActivity(launchIntent)
+ }
+
+ /**
+ *
+ */
+ override fun onResume() {
+ super.onResume()
+
+ val notificationManager = this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.cancelAll()
+ }
+}
diff --git a/src/android/com/adobe/phonegap/push/FCMService.kt b/src/android/com/adobe/phonegap/push/FCMService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4272899d53c323f18b48f17184491fb6bc6c152c
--- /dev/null
+++ b/src/android/com/adobe/phonegap/push/FCMService.kt
@@ -0,0 +1,1203 @@
+package com.adobe.phonegap.push
+
+import android.annotation.SuppressLint
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.graphics.*
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.provider.Settings
+import android.text.Html
+import android.text.Spanned
+import android.util.Log
+import androidx.core.app.NotificationCompat
+import androidx.core.app.RemoteInput
+import com.adobe.phonegap.push.PushPlugin.Companion.isActive
+import com.adobe.phonegap.push.PushPlugin.Companion.isInForeground
+import com.adobe.phonegap.push.PushPlugin.Companion.sendExtras
+import com.adobe.phonegap.push.PushPlugin.Companion.setApplicationIconBadgeNumber
+import com.google.firebase.messaging.FirebaseMessagingService
+import com.google.firebase.messaging.RemoteMessage
+import org.json.JSONArray
+import org.json.JSONException
+import org.json.JSONObject
+import java.io.IOException
+import java.io.InputStream
+import java.net.HttpURLConnection
+import java.net.URL
+import java.security.SecureRandom
+import java.util.*
+
+/**
+ * Firebase Cloud Messaging Service Class
+ */
+@Suppress("HardCodedStringLiteral")
+@SuppressLint("NewApi", "LongLogTag", "LogConditional")
+class FCMService : FirebaseMessagingService() {
+ companion object {
+ private const val TAG = "${PushPlugin.PREFIX_TAG} (FCMService)"
+
+ private val messageMap = HashMap>()
+
+ private val FLAG_MUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ PendingIntent.FLAG_MUTABLE
+ } else {
+ 0
+ }
+ private val FLAG_IMMUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ PendingIntent.FLAG_IMMUTABLE
+ } else {
+ 0
+ }
+
+ /**
+ * Get the Application Name from Label
+ */
+ fun getAppName(context: Context): String {
+ return context.packageManager.getApplicationLabel(context.applicationInfo) as String
+ }
+ }
+
+ private val context: Context
+ get() = applicationContext
+
+ private val pushSharedPref: SharedPreferences
+ get() = context.getSharedPreferences(
+ PushConstants.COM_ADOBE_PHONEGAP_PUSH,
+ MODE_PRIVATE
+ )
+
+ /**
+ * Called when a new token is generated, after app install or token changes.
+ *
+ * @param token
+ */
+ override fun onNewToken(token: String) {
+ super.onNewToken(token)
+ Log.d(TAG, "Refreshed token: $token")
+
+ // TODO: Implement this method to send any registration to your app's servers.
+ //sendRegistrationToServer(token);
+ }
+
+ /**
+ * Set Notification
+ * If message is empty or null, the message list is cleared.
+ *
+ * @param notId
+ * @param message
+ */
+ fun setNotification(notId: Int, message: String?) {
+ var messageList = messageMap[notId]
+
+ if (messageList == null) {
+ messageList = ArrayList()
+ messageMap[notId] = messageList
+ }
+
+ if (message == null || message.isEmpty()) {
+ messageList.clear()
+ } else {
+ messageList.add(message)
+ }
+ }
+
+ /**
+ * On Message Received
+ */
+ override fun onMessageReceived(message: RemoteMessage) {
+ val from = message.from
+ Log.d(TAG, "onMessageReceived (from=$from)")
+
+ var extras = Bundle()
+
+ message.notification?.let {
+ extras.putString(PushConstants.TITLE, it.title)
+ extras.putString(PushConstants.MESSAGE, it.body)
+ extras.putString(PushConstants.SOUND, it.sound)
+ extras.putString(PushConstants.ICON, it.icon)
+ extras.putString(PushConstants.COLOR, it.color)
+ }
+
+ for ((key, value) in message.data) {
+ extras.putString(key, value)
+ }
+
+ if (isAvailableSender(from)) {
+ val messageKey = pushSharedPref.getString(PushConstants.MESSAGE_KEY, PushConstants.MESSAGE)
+ val titleKey = pushSharedPref.getString(PushConstants.TITLE_KEY, PushConstants.TITLE)
+
+ extras = normalizeExtras(extras, messageKey, titleKey)
+
+ // Clear Badge
+ val clearBadge = pushSharedPref.getBoolean(PushConstants.CLEAR_BADGE, false)
+ if (clearBadge) {
+ setApplicationIconBadgeNumber(context, 0)
+ }
+
+ // Foreground
+ extras.putBoolean(PushConstants.FOREGROUND, isInForeground)
+
+ // if we are in the foreground and forceShow is `false` only send data
+ val forceShow = pushSharedPref.getBoolean(PushConstants.FORCE_SHOW, false)
+ if (!forceShow && isInForeground) {
+ Log.d(TAG, "Do Not Force & Is In Foreground")
+ extras.putBoolean(PushConstants.COLDSTART, false)
+ sendExtras(extras)
+ } else if (forceShow && isInForeground) {
+ Log.d(TAG, "Force & Is In Foreground")
+ extras.putBoolean(PushConstants.COLDSTART, false)
+ showNotificationIfPossible(extras)
+ } else {
+ Log.d(TAG, "In Background")
+ extras.putBoolean(PushConstants.COLDSTART, isActive)
+ showNotificationIfPossible(extras)
+ }
+ }
+ }
+
+ private fun replaceKey(oldKey: String, newKey: String, extras: Bundle, newExtras: Bundle) {
+ /*
+ * Change a values key in the extras bundle
+ */
+ var value = extras[oldKey]
+ if (value != null) {
+ when (value) {
+ is String -> {
+ value = localizeKey(newKey, value)
+ newExtras.putString(newKey, value as String?)
+ }
+
+ is Boolean -> newExtras.putBoolean(newKey, (value as Boolean?) ?: return)
+
+ is Number -> {
+ newExtras.putDouble(newKey, value.toDouble())
+ }
+
+ else -> {
+ newExtras.putString(newKey, value.toString())
+ }
+ }
+ }
+ }
+
+ private fun localizeKey(key: String, value: String): String {
+ /*
+ * Normalize localization for key
+ */
+ return when (key) {
+ PushConstants.TITLE,
+ PushConstants.MESSAGE,
+ PushConstants.SUMMARY_TEXT,
+ -> {
+ try {
+ val localeObject = JSONObject(value)
+ val localeKey = localeObject.getString(PushConstants.LOC_KEY)
+ val localeFormatData = ArrayList()
+
+ if (!localeObject.isNull(PushConstants.LOC_DATA)) {
+ val localeData = localeObject.getString(PushConstants.LOC_DATA)
+ val localeDataArray = JSONArray(localeData)
+
+ for (i in 0 until localeDataArray.length()) {
+ localeFormatData.add(localeDataArray.getString(i))
+ }
+ }
+
+ val resourceId = context.resources.getIdentifier(
+ localeKey,
+ "string",
+ context.packageName
+ )
+
+ if (resourceId != 0) {
+ context.resources.getString(resourceId, *localeFormatData.toTypedArray())
+ } else {
+ Log.d(TAG, "Can't Find Locale Resource (key=$localeKey)")
+ value
+ }
+ } catch (e: JSONException) {
+ Log.d(TAG, "No Locale Found (key= $key, error=${e.message})")
+ value
+ }
+ }
+ else -> value
+ }
+ }
+
+ private fun normalizeKey(
+ key: String,
+ messageKey: String?,
+ titleKey: String?,
+ newExtras: Bundle,
+ ): String {
+ /*
+ * Replace alternate keys with our canonical value
+ */
+ return when {
+ key == PushConstants.BODY
+ || key == PushConstants.ALERT
+ || key == PushConstants.MP_MESSAGE
+ || key == PushConstants.GCM_NOTIFICATION_BODY
+ || key == PushConstants.TWILIO_BODY
+ || key == messageKey
+ || key == PushConstants.AWS_PINPOINT_BODY
+ -> {
+ PushConstants.MESSAGE
+ }
+
+ key == PushConstants.TWILIO_TITLE || key == PushConstants.SUBJECT || key == titleKey -> {
+ PushConstants.TITLE
+ }
+
+ key == PushConstants.MSGCNT || key == PushConstants.BADGE -> {
+ PushConstants.COUNT
+ }
+
+ key == PushConstants.SOUNDNAME || key == PushConstants.TWILIO_SOUND -> {
+ PushConstants.SOUND
+ }
+
+ key == PushConstants.AWS_PINPOINT_PICTURE -> {
+ newExtras.putString(PushConstants.STYLE, PushConstants.STYLE_PICTURE)
+ PushConstants.PICTURE
+ }
+
+ key.startsWith(PushConstants.GCM_NOTIFICATION) -> {
+ key.substring(PushConstants.GCM_NOTIFICATION.length + 1, key.length)
+ }
+
+ key.startsWith(PushConstants.GCM_N) -> {
+ key.substring(PushConstants.GCM_N.length + 1, key.length)
+ }
+
+ key.startsWith(PushConstants.UA_PREFIX) -> {
+ key.substring(PushConstants.UA_PREFIX.length + 1, key.length).lowercase()
+ }
+
+ key.startsWith(PushConstants.AWS_PINPOINT_PREFIX) -> {
+ key.substring(PushConstants.AWS_PINPOINT_PREFIX.length + 1, key.length)
+ }
+
+ else -> key
+ }
+ }
+
+ private fun normalizeExtras(
+ extras: Bundle,
+ messageKey: String?,
+ titleKey: String?,
+ ): Bundle {
+ /*
+ * Parse bundle into normalized keys.
+ */
+ Log.d(TAG, "normalize extras")
+
+ val it: Iterator = extras.keySet().iterator()
+ val newExtras = Bundle()
+
+ while (it.hasNext()) {
+ val key = it.next()
+ Log.d(TAG, "key = $key")
+
+ // If normalizeKey, the key is "data" or "message" and the value is a json object extract
+ // This is to support parse.com and other services. Issue #147 and pull #218
+ if (
+ key == PushConstants.PARSE_COM_DATA ||
+ key == PushConstants.MESSAGE ||
+ key == messageKey
+ ) {
+ val json = extras[key]
+
+ // Make sure data is in json object string format
+ if (json is String && json.startsWith("{")) {
+ Log.d(TAG, "extracting nested message data from key = $key")
+
+ try {
+ // If object contains message keys promote each value to the root of the bundle
+ val data = JSONObject(json)
+ if (
+ data.has(PushConstants.ALERT)
+ || data.has(PushConstants.MESSAGE)
+ || data.has(PushConstants.BODY)
+ || data.has(PushConstants.TITLE)
+ || data.has(messageKey)
+ || data.has(titleKey)
+ ) {
+ val jsonKeys = data.keys()
+
+ while (jsonKeys.hasNext()) {
+ var jsonKey = jsonKeys.next()
+ Log.d(TAG, "key = data/$jsonKey")
+
+ var value = data.getString(jsonKey)
+ jsonKey = normalizeKey(jsonKey, messageKey, titleKey, newExtras)
+ value = localizeKey(jsonKey, value)
+ newExtras.putString(jsonKey, value)
+ }
+ } else if (data.has(PushConstants.LOC_KEY) || data.has(PushConstants.LOC_DATA)) {
+ val newKey = normalizeKey(key, messageKey, titleKey, newExtras)
+ Log.d(TAG, "replace key $key with $newKey")
+ replaceKey(key, newKey, extras, newExtras)
+ }
+ } catch (e: JSONException) {
+ Log.e(TAG, "normalizeExtras: JSON exception")
+ }
+ } else {
+ val newKey = normalizeKey(key, messageKey, titleKey, newExtras)
+ Log.d(TAG, "replace key $key with $newKey")
+ replaceKey(key, newKey, extras, newExtras)
+ }
+ } else if (key == "notification") {
+ val value = extras.getBundle(key)
+ val iterator: Iterator = value!!.keySet().iterator()
+
+ while (iterator.hasNext()) {
+ val notificationKey = iterator.next()
+ Log.d(TAG, "notificationKey = $notificationKey")
+
+ val newKey = normalizeKey(notificationKey, messageKey, titleKey, newExtras)
+ Log.d(TAG, "Replace key $notificationKey with $newKey")
+
+ var valueData = value.getString(notificationKey)
+ valueData = localizeKey(newKey, valueData!!)
+ newExtras.putString(newKey, valueData)
+ }
+ continue
+ // In case we weren't working on the payload data node or the notification node,
+ // normalize the key.
+ // This allows to have "message" as the payload data key without colliding
+ // with the other "message" key (holding the body of the payload)
+ // See issue #1663
+ } else {
+ val newKey = normalizeKey(key, messageKey, titleKey, newExtras)
+ Log.d(TAG, "replace key $key with $newKey")
+ replaceKey(key, newKey, extras, newExtras)
+ }
+ } // while
+ return newExtras
+ }
+
+ private fun extractBadgeCount(extras: Bundle?): Int {
+ var count = -1
+
+ try {
+ extras?.getString(PushConstants.COUNT)?.let {
+ count = it.toInt()
+ }
+ } catch (e: NumberFormatException) {
+ Log.e(TAG, e.localizedMessage, e)
+ }
+
+ return count
+ }
+
+ private fun showNotificationIfPossible(extras: Bundle?) {
+ // Send a notification if there is a message or title, otherwise just send data
+ extras?.let {
+ val message = it.getString(PushConstants.MESSAGE)
+ val title = it.getString(PushConstants.TITLE)
+ val contentAvailable = it.getString(PushConstants.CONTENT_AVAILABLE)
+ val forceStart = it.getString(PushConstants.FORCE_START)
+ val badgeCount = extractBadgeCount(extras)
+
+ if (badgeCount >= 0) {
+ setApplicationIconBadgeNumber(context, badgeCount)
+ }
+
+ if (badgeCount == 0) {
+ val mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ mNotificationManager.cancelAll()
+ }
+
+ Log.d(TAG, "message=$message")
+ Log.d(TAG, "title=$title")
+ Log.d(TAG, "contentAvailable=$contentAvailable")
+ Log.d(TAG, "forceStart=$forceStart")
+ Log.d(TAG, "badgeCount=$badgeCount")
+
+ val hasMessage = message != null && message.isNotEmpty()
+ val hasTitle = title != null && title.isNotEmpty()
+
+ if (hasMessage || hasTitle) {
+ Log.d(TAG, "Create Notification")
+
+ if (!hasTitle) {
+ extras.putString(PushConstants.TITLE, getAppName(this))
+ }
+
+ createNotification(extras)
+ }
+
+ if (!isActive && forceStart == "1") {
+ Log.d(TAG, "The app is not running, attempting to start in the background")
+
+ val intent = Intent(this, PushHandlerActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ putExtra(PushConstants.PUSH_BUNDLE, extras)
+ putExtra(PushConstants.START_IN_BACKGROUND, true)
+ putExtra(PushConstants.FOREGROUND, false)
+ }
+
+ startActivity(intent)
+ } else if (contentAvailable == "1") {
+ Log.d(
+ TAG,
+ "The app is not running and content available is true, sending notification event"
+ )
+
+ sendExtras(extras)
+ }
+ }
+ }
+
+ private fun createNotification(extras: Bundle?) {
+ val mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ val appName = getAppName(this)
+ val notId = parseNotificationIdToInt(extras)
+ val notificationIntent = Intent(this, PushHandlerActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ putExtra(PushConstants.PUSH_BUNDLE, extras)
+ putExtra(PushConstants.NOT_ID, notId)
+ }
+ val random = SecureRandom()
+ var requestCode = random.nextInt()
+ val contentIntent = PendingIntent.getActivity(
+ this,
+ requestCode,
+ notificationIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
+ )
+ val dismissedNotificationIntent = Intent(
+ this,
+ PushDismissedHandler::class.java
+ ).apply {
+ putExtra(PushConstants.PUSH_BUNDLE, extras)
+ putExtra(PushConstants.NOT_ID, notId)
+ putExtra(PushConstants.DISMISSED, true)
+
+ action = PushConstants.PUSH_DISMISSED
+ }
+
+ requestCode = random.nextInt()
+
+ val deleteIntent = PendingIntent.getBroadcast(
+ this,
+ requestCode,
+ dismissedNotificationIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT or FLAG_IMMUTABLE
+ )
+
+ val mBuilder: NotificationCompat.Builder =
+ createNotificationBuilder(extras, mNotificationManager)
+
+ mBuilder.setWhen(System.currentTimeMillis())
+ .setContentTitle(fromHtml(extras?.getString(PushConstants.TITLE)))
+ .setTicker(fromHtml(extras?.getString(PushConstants.TITLE)))
+ .setContentIntent(contentIntent)
+ .setDeleteIntent(deleteIntent)
+ .setAutoCancel(true)
+
+ val localIcon = pushSharedPref.getString(PushConstants.ICON, null)
+ val localIconColor = pushSharedPref.getString(PushConstants.ICON_COLOR, null)
+ val soundOption = pushSharedPref.getBoolean(PushConstants.SOUND, true)
+ val vibrateOption = pushSharedPref.getBoolean(PushConstants.VIBRATE, true)
+
+ Log.d(TAG, "stored icon=$localIcon")
+ Log.d(TAG, "stored iconColor=$localIconColor")
+ Log.d(TAG, "stored sound=$soundOption")
+ Log.d(TAG, "stored vibrate=$vibrateOption")
+
+ /*
+ * Notification Vibration
+ */
+ setNotificationVibration(extras, vibrateOption, mBuilder)
+
+ /*
+ * Notification Icon Color
+ *
+ * Sets the small-icon background color of the notification.
+ * To use, add the `iconColor` key to plugin android options
+ */
+ setNotificationIconColor(extras?.getString(PushConstants.COLOR), mBuilder, localIconColor)
+
+ /*
+ * Notification Icon
+ *
+ * Sets the small-icon of the notification.
+ *
+ * - checks the plugin options for `icon` key
+ * - if none, uses the application icon
+ *
+ * The icon value must be a string that maps to a drawable resource.
+ * If no resource is found, falls
+ */
+ setNotificationSmallIcon(extras, mBuilder, localIcon)
+
+ /*
+ * Notification Large-Icon
+ *
+ * Sets the large-icon of the notification
+ *
+ * - checks the gcm data for the `image` key
+ * - checks to see if remote image, loads it.
+ * - checks to see if assets image, Loads It.
+ * - checks to see if resource image, LOADS IT!
+ * - if none, we don't set the large icon
+ */
+ setNotificationLargeIcon(extras, mBuilder)
+
+ /*
+ * Notification Sound
+ */
+ if (soundOption) {
+ setNotificationSound(extras, mBuilder)
+ }
+
+ /*
+ * LED Notification
+ */
+ setNotificationLedColor(extras, mBuilder)
+
+ /*
+ * Priority Notification
+ */
+ setNotificationPriority(extras, mBuilder)
+
+ /*
+ * Notification message
+ */
+ setNotificationMessage(notId, extras, mBuilder)
+
+ /*
+ * Notification count
+ */
+ setNotificationCount(extras, mBuilder)
+
+ /*
+ * Notification ongoing
+ */
+ setNotificationOngoing(extras, mBuilder)
+
+ /*
+ * Notification count
+ */
+ setVisibility(extras, mBuilder)
+
+ /*
+ * Notification add actions
+ */
+ createActions(extras, mBuilder, notId)
+ mNotificationManager.notify(appName, notId, mBuilder.build())
+ }
+
+ private fun createNotificationBuilder(
+ extras: Bundle?,
+ notificationManager: NotificationManager
+ ): NotificationCompat.Builder {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ var channelID: String? = null
+
+ if (extras != null) {
+ channelID = extras.getString(PushConstants.ANDROID_CHANNEL_ID)
+ }
+
+ // if the push payload specifies a channel use it
+ return if (channelID != null) {
+ NotificationCompat.Builder(context, channelID)
+ } else {
+ val channels = notificationManager.notificationChannels
+
+ channelID = if (channels.size == 1) {
+ channels[0].id.toString()
+ } else {
+ PushConstants.DEFAULT_CHANNEL_ID
+ }
+
+ Log.d(TAG, "Using channel ID = $channelID")
+ NotificationCompat.Builder(context, channelID)
+ }
+ } else {
+ return NotificationCompat.Builder(context)
+ }
+ }
+
+ private fun updateIntent(
+ intent: Intent,
+ callback: String,
+ extras: Bundle?,
+ foreground: Boolean,
+ notId: Int,
+ ) {
+ intent.apply {
+ putExtra(PushConstants.CALLBACK, callback)
+ putExtra(PushConstants.PUSH_BUNDLE, extras)
+ putExtra(PushConstants.FOREGROUND, foreground)
+ putExtra(PushConstants.NOT_ID, notId)
+ }
+ }
+
+ private fun createActions(
+ extras: Bundle?,
+ mBuilder: NotificationCompat.Builder,
+ notId: Int,
+ ) {
+ Log.d(TAG, "create actions: with in-line")
+
+ if (extras == null) {
+ Log.d(TAG, "create actions: extras is null, skipping")
+ return
+ }
+
+ val actions = extras.getString(PushConstants.ACTIONS)
+ if (actions != null) {
+ try {
+ val actionsArray = JSONArray(actions)
+ val wActions = ArrayList()
+
+ for (i in 0 until actionsArray.length()) {
+ val min = 1
+ val max = 2000000000
+ val random = SecureRandom()
+ val uniquePendingIntentRequestCode = random.nextInt(max - min + 1) + min
+
+ Log.d(TAG, "adding action")
+
+ val action = actionsArray.getJSONObject(i)
+
+ Log.d(TAG, "adding callback = " + action.getString(PushConstants.CALLBACK))
+
+ val foreground = action.optBoolean(PushConstants.FOREGROUND, true)
+ val inline = action.optBoolean("inline", false)
+ var intent: Intent?
+ var pIntent: PendingIntent?
+ val callback = action.getString(PushConstants.CALLBACK)
+
+ when {
+ inline -> {
+ Log.d(TAG, "Version: ${Build.VERSION.SDK_INT} = ${Build.VERSION_CODES.M}")
+
+ intent = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
+ Log.d(TAG, "Push Activity")
+ Intent(this, PushHandlerActivity::class.java)
+ } else {
+ Log.d(TAG, "Push Receiver")
+ Intent(this, BackgroundActionButtonHandler::class.java)
+ }
+
+ updateIntent(intent, callback, extras, foreground, notId)
+
+ pIntent = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
+ Log.d(TAG, "push activity for notId $notId")
+
+ PendingIntent.getActivity(
+ this,
+ uniquePendingIntentRequestCode,
+ intent,
+ PendingIntent.FLAG_ONE_SHOT or FLAG_MUTABLE
+ )
+ } else {
+ Log.d(TAG, "push receiver for notId $notId")
+
+ PendingIntent.getBroadcast(
+ this,
+ uniquePendingIntentRequestCode,
+ intent,
+ PendingIntent.FLAG_ONE_SHOT or FLAG_MUTABLE
+ )
+ }
+ }
+
+ foreground -> {
+ intent = Intent(this, PushHandlerActivity::class.java)
+ updateIntent(intent, callback, extras, foreground, notId)
+ pIntent = PendingIntent.getActivity(
+ this, uniquePendingIntentRequestCode,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
+ )
+ }
+
+ else -> {
+ intent = Intent(this, BackgroundActionButtonHandler::class.java)
+ updateIntent(intent, callback, extras, foreground, notId)
+ pIntent = PendingIntent.getBroadcast(
+ this, uniquePendingIntentRequestCode,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
+ )
+ }
+ }
+ val actionBuilder = NotificationCompat.Action.Builder(
+ getImageId(action.optString(PushConstants.ICON, "")),
+ action.getString(PushConstants.TITLE),
+ pIntent
+ )
+
+ var remoteInput: RemoteInput?
+
+ if (inline) {
+ Log.d(TAG, "Create Remote Input")
+
+ val replyLabel = action.optString(
+ PushConstants.INLINE_REPLY_LABEL,
+ "Enter your reply here"
+ )
+
+ remoteInput = RemoteInput.Builder(PushConstants.INLINE_REPLY)
+ .setLabel(replyLabel)
+ .build()
+
+ actionBuilder.addRemoteInput(remoteInput)
+ }
+
+ val wAction: NotificationCompat.Action = actionBuilder.build()
+ wActions.add(actionBuilder.build())
+
+ if (inline) {
+ mBuilder.addAction(wAction)
+ } else {
+ mBuilder.addAction(
+ getImageId(action.optString(PushConstants.ICON, "")),
+ action.getString(PushConstants.TITLE),
+ pIntent
+ )
+ }
+ }
+
+ mBuilder.extend(NotificationCompat.WearableExtender().addActions(wActions))
+ wActions.clear()
+ } catch (e: JSONException) {
+ Log.e(TAG, "JSONException caught", e)
+ }
+ }
+ }
+
+ private fun setNotificationCount(extras: Bundle?, mBuilder: NotificationCompat.Builder) {
+ val count = extractBadgeCount(extras)
+ if (count >= 0) {
+ Log.d(TAG, "count =[$count]")
+ mBuilder.setNumber(count)
+ }
+ }
+
+ private fun setVisibility(extras: Bundle?, mBuilder: NotificationCompat.Builder) {
+ extras?.getString(PushConstants.VISIBILITY)?.let { visibilityStr ->
+ try {
+ val visibilityInt = visibilityStr.toInt()
+
+ if (
+ visibilityInt >= NotificationCompat.VISIBILITY_SECRET
+ && visibilityInt <= NotificationCompat.VISIBILITY_PUBLIC
+ ) {
+ mBuilder.setVisibility(visibilityInt)
+ } else {
+ Log.e(TAG, "Visibility parameter must be between -1 and 1")
+ }
+ } catch (e: NumberFormatException) {
+ }
+ }
+ }
+
+ private fun setNotificationVibration(
+ extras: Bundle?,
+ vibrateOption: Boolean,
+ mBuilder: NotificationCompat.Builder,
+ ) {
+ if (extras == null) {
+ Log.d(TAG, "setNotificationVibration: extras is null, skipping")
+ return
+ }
+
+ val vibrationPattern = extras.getString(PushConstants.VIBRATION_PATTERN)
+ if (vibrationPattern != null) {
+ val items = convertToTypedArray(vibrationPattern)
+ val results = LongArray(items.size)
+ for (i in items.indices) {
+ try {
+ results[i] = items[i].trim { it <= ' ' }.toLong()
+ } catch (nfe: NumberFormatException) {
+ Log.e(TAG, "NumberFormatException caught while parsing vibration pattern", nfe)
+ }
+ }
+ mBuilder.setVibrate(results)
+ } else {
+ if (vibrateOption) {
+ mBuilder.setDefaults(Notification.DEFAULT_VIBRATE)
+ }
+ }
+ }
+
+ private fun setNotificationOngoing(extras: Bundle?, mBuilder: NotificationCompat.Builder) {
+ extras?.getString(PushConstants.ONGOING, "false")?.let {
+ mBuilder.setOngoing(it.toBoolean())
+ }
+ }
+
+ private fun setNotificationMessage(
+ notId: Int,
+ extras: Bundle?,
+ mBuilder: NotificationCompat.Builder,
+ ) {
+ extras?.let {
+ val message = it.getString(PushConstants.MESSAGE)
+
+ when (it.getString(PushConstants.STYLE, PushConstants.STYLE_TEXT)) {
+ PushConstants.STYLE_INBOX -> {
+ setNotification(notId, message)
+ mBuilder.setContentText(fromHtml(message))
+
+ messageMap[notId]?.let { messageList ->
+ val sizeList = messageList.size
+
+ if (sizeList > 1) {
+ val sizeListMessage = sizeList.toString()
+ var stacking: String? = "$sizeList more"
+
+ it.getString(PushConstants.SUMMARY_TEXT)?.let { summaryText ->
+ stacking = summaryText.replace("%n%", sizeListMessage)
+ }
+
+ val notificationInbox = NotificationCompat.InboxStyle().run {
+ setBigContentTitle(fromHtml(it.getString(PushConstants.TITLE)))
+ setSummaryText(fromHtml(stacking))
+ }.also { inbox ->
+ for (i in messageList.indices.reversed()) {
+ inbox.addLine(fromHtml(messageList[i]))
+ }
+ }
+
+ mBuilder.setStyle(notificationInbox)
+ } else {
+ message?.let { message ->
+ val bigText = NotificationCompat.BigTextStyle().run {
+ bigText(fromHtml(message))
+ setBigContentTitle(fromHtml(it.getString(PushConstants.TITLE)))
+ }
+
+ mBuilder.setStyle(bigText)
+ }
+ }
+ }
+ }
+
+ PushConstants.STYLE_PICTURE -> {
+ setNotification(notId, "")
+ val bigPicture = NotificationCompat.BigPictureStyle().run {
+ bigPicture(getBitmapFromURL(it.getString(PushConstants.PICTURE)))
+ setBigContentTitle(fromHtml(it.getString(PushConstants.TITLE)))
+ setSummaryText(fromHtml(it.getString(PushConstants.SUMMARY_TEXT)))
+ }
+
+ mBuilder.apply {
+ setContentTitle(fromHtml(it.getString(PushConstants.TITLE)))
+ setContentText(fromHtml(message))
+ setStyle(bigPicture)
+ }
+ }
+
+ else -> {
+ setNotification(notId, "")
+
+ message?.let { messageStr ->
+ val bigText = NotificationCompat.BigTextStyle().run {
+ bigText(fromHtml(messageStr))
+ setBigContentTitle(fromHtml(it.getString(PushConstants.TITLE)))
+
+ it.getString(PushConstants.SUMMARY_TEXT)?.let { summaryText ->
+ setSummaryText(fromHtml(summaryText))
+ }
+ }
+
+ mBuilder.setContentText(fromHtml(messageStr))
+ mBuilder.setStyle(bigText)
+ }
+ }
+ }
+ }
+ }
+
+ private fun setNotificationSound(extras: Bundle?, mBuilder: NotificationCompat.Builder) {
+ extras?.let {
+ val soundName = it.getString(PushConstants.SOUNDNAME) ?: it.getString(PushConstants.SOUND)
+
+ when {
+ soundName == PushConstants.SOUND_RINGTONE -> {
+ mBuilder.setSound(Settings.System.DEFAULT_RINGTONE_URI)
+ }
+
+ soundName != null && !soundName.contentEquals(PushConstants.SOUND_DEFAULT) -> {
+ val sound = Uri.parse(
+ "${ContentResolver.SCHEME_ANDROID_RESOURCE}://${context.packageName}/raw/$soundName"
+ )
+
+ Log.d(TAG, "Sound URL: $sound")
+
+ mBuilder.setSound(sound)
+ }
+
+ else -> {
+ mBuilder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI)
+ }
+ }
+ }
+ }
+
+ private fun convertToTypedArray(item: String): Array {
+ return item.replace("\\[".toRegex(), "")
+ .replace("]".toRegex(), "")
+ .split(",")
+ .toTypedArray()
+ }
+
+ private fun setNotificationLedColor(extras: Bundle?, mBuilder: NotificationCompat.Builder) {
+ extras?.let { it ->
+ it.getString(PushConstants.LED_COLOR)?.let { ledColor ->
+ // Convert ledColor to Int Typed Array
+ val items = convertToTypedArray(ledColor)
+ val results = IntArray(items.size)
+
+ for (i in items.indices) {
+ try {
+ results[i] = items[i].trim { it <= ' ' }.toInt()
+ } catch (nfe: NumberFormatException) {
+ Log.e(TAG, "Number Format Exception: $nfe")
+ }
+ }
+
+ if (results.size == 4) {
+ val (alpha, red, green, blue) = results
+ mBuilder.setLights(Color.argb(alpha, red, green, blue), 500, 500)
+ } else {
+ Log.e(TAG, "ledColor parameter must be an array of length == 4 (ARGB)")
+ }
+ }
+ }
+ }
+
+ private fun setNotificationPriority(extras: Bundle?, mBuilder: NotificationCompat.Builder) {
+ extras?.let { it ->
+ it.getString(PushConstants.PRIORITY)?.let { priorityStr ->
+ try {
+ val priority = priorityStr.toInt()
+
+ if (
+ priority >= NotificationCompat.PRIORITY_MIN
+ && priority <= NotificationCompat.PRIORITY_MAX
+ ) {
+ mBuilder.priority = priority
+ } else {
+ Log.e(TAG, "Priority parameter must be between -2 and 2")
+ }
+ } catch (e: NumberFormatException) {
+ }
+ }
+ }
+ }
+
+ private fun getCircleBitmap(bitmap: Bitmap?): Bitmap? {
+ if (bitmap == null) {
+ return null
+ }
+
+ val output = Bitmap.createBitmap(
+ bitmap.width,
+ bitmap.height,
+ Bitmap.Config.ARGB_8888
+ )
+
+ val paint = Paint().apply {
+ isAntiAlias = true
+ color = Color.RED
+ xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
+ }
+
+ Canvas(output).apply {
+ drawARGB(0, 0, 0, 0)
+
+ val cx = (bitmap.width / 2).toFloat()
+ val cy = (bitmap.height / 2).toFloat()
+ val radius = if (cx < cy) cx else cy
+ val rect = Rect(0, 0, bitmap.width, bitmap.height)
+
+ drawCircle(cx, cy, radius, paint)
+ drawBitmap(bitmap, rect, rect, paint)
+ }
+
+ bitmap.recycle()
+ return output
+ }
+
+ private fun setNotificationLargeIcon(
+ extras: Bundle?,
+ mBuilder: NotificationCompat.Builder,
+ ) {
+ extras?.let {
+ val gcmLargeIcon = it.getString(PushConstants.IMAGE)
+ val imageType = it.getString(PushConstants.IMAGE_TYPE, PushConstants.IMAGE_TYPE_SQUARE)
+
+ if (gcmLargeIcon != null && gcmLargeIcon != "") {
+ if (
+ gcmLargeIcon.startsWith("http://")
+ || gcmLargeIcon.startsWith("https://")
+ ) {
+ val bitmap = getBitmapFromURL(gcmLargeIcon)
+
+ if (PushConstants.IMAGE_TYPE_SQUARE.equals(imageType, ignoreCase = true)) {
+ mBuilder.setLargeIcon(bitmap)
+ } else {
+ val bm = getCircleBitmap(bitmap)
+ mBuilder.setLargeIcon(bm)
+ }
+
+ Log.d(TAG, "Using remote large-icon from GCM")
+ } else {
+ try {
+ val inputStream: InputStream = assets.open(gcmLargeIcon)
+
+ val bitmap = BitmapFactory.decodeStream(inputStream)
+
+ if (PushConstants.IMAGE_TYPE_SQUARE.equals(imageType, ignoreCase = true)) {
+ mBuilder.setLargeIcon(bitmap)
+ } else {
+ val bm = getCircleBitmap(bitmap)
+ mBuilder.setLargeIcon(bm)
+ }
+
+ Log.d(TAG, "Using assets large-icon from GCM")
+ } catch (e: IOException) {
+ val largeIconId: Int = getImageId(gcmLargeIcon)
+
+ if (largeIconId != 0) {
+ val largeIconBitmap = BitmapFactory.decodeResource(context.resources, largeIconId)
+ mBuilder.setLargeIcon(largeIconBitmap)
+ Log.d(TAG, "Using resources large-icon from GCM")
+ } else {
+ Log.d(TAG, "Not large icon settings")
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun getImageId(icon: String): Int {
+ var iconId = context.resources.getIdentifier(icon, PushConstants.DRAWABLE, context.packageName)
+ if (iconId == 0) {
+ iconId = context.resources.getIdentifier(icon, "mipmap", context.packageName)
+ }
+ return iconId
+ }
+
+ private fun setNotificationSmallIcon(
+ extras: Bundle?,
+ mBuilder: NotificationCompat.Builder,
+ localIcon: String?,
+ ) {
+ extras?.let {
+ val icon = it.getString(PushConstants.ICON)
+
+ val iconId = when {
+ icon != null && icon != "" -> {
+ getImageId(icon)
+ }
+
+ localIcon != null && localIcon != "" -> {
+ getImageId(localIcon)
+ }
+
+ else -> {
+ Log.d(TAG, "No icon resource found from settings, using application icon")
+ context.applicationInfo.icon
+ }
+ }
+
+ mBuilder.setSmallIcon(iconId)
+ }
+ }
+
+ private fun setNotificationIconColor(
+ color: String?,
+ mBuilder: NotificationCompat.Builder,
+ localIconColor: String?,
+ ) {
+ val iconColor = when {
+ color != null && color != "" -> {
+ try {
+ Color.parseColor(color)
+ } catch (e: IllegalArgumentException) {
+ Log.e(TAG, "Couldn't parse color from Android options")
+ }
+ }
+
+ localIconColor != null && localIconColor != "" -> {
+ try {
+ Color.parseColor(localIconColor)
+ } catch (e: IllegalArgumentException) {
+ Log.e(TAG, "Couldn't parse color from android options")
+ }
+ }
+
+ else -> {
+ Log.d(TAG, "No icon color settings found")
+ 0
+ }
+ }
+
+ if (iconColor != 0) {
+ mBuilder.color = iconColor
+ }
+ }
+
+ private fun getBitmapFromURL(strURL: String?): Bitmap? {
+ val input: InputStream? = null;
+ return try {
+ val url = URL(strURL)
+ val connection = (url.openConnection() as HttpURLConnection).apply {
+ connectTimeout = 15000
+ doInput = true
+ connect()
+ }
+ val input = connection.inputStream
+ input.close()
+ BitmapFactory.decodeStream(input)
+ } catch (e: IOException) {
+ null
+ }
+ finally {
+ if (input != null){
+ input?.close()
+ }
+ }
+ }
+
+ private fun parseNotificationIdToInt(extras: Bundle?): Int {
+ var returnVal = 0
+
+ try {
+ returnVal = extras!!.getString(PushConstants.NOT_ID)!!.toInt()
+ } catch (e: NumberFormatException) {
+ Log.e(TAG, "NumberFormatException occurred: ${PushConstants.NOT_ID}: ${e.message}")
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception occurred when parsing ${PushConstants.NOT_ID}: ${e.message}")
+ }
+
+ return returnVal
+ }
+
+ private fun fromHtml(source: String?): Spanned? {
+ return if (source != null) Html.fromHtml(source) else null
+ }
+
+ private fun isAvailableSender(from: String?): Boolean {
+ val savedSenderID = pushSharedPref.getString(PushConstants.SENDER_ID, "")
+ Log.d(TAG, "sender id = $savedSenderID")
+ return from == savedSenderID || from!!.startsWith("/topics/")
+ }
+}
diff --git a/src/android/com/adobe/phonegap/push/PushConstants.kt b/src/android/com/adobe/phonegap/push/PushConstants.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e403e3677c184494839455de3cac814af383a145
--- /dev/null
+++ b/src/android/com/adobe/phonegap/push/PushConstants.kt
@@ -0,0 +1,113 @@
+package com.adobe.phonegap.push
+
+/**
+ * Push Constants
+ *
+ * @todo add docs about each constant.
+ */
+@Suppress("HardCodedStringLiteral")
+object PushConstants {
+ const val COM_ADOBE_PHONEGAP_PUSH: String = "com.adobe.phonegap.push"
+ const val REGISTRATION_ID: String = "registrationId"
+ const val REGISTRATION_TYPE: String = "registrationType"
+ const val FOREGROUND: String = "foreground"
+ const val TITLE: String = "title"
+ const val NOT_ID: String = "notId"
+ const val PUSH_BUNDLE: String = "pushBundle"
+ const val ICON: String = "icon"
+ const val ICON_COLOR: String = "iconColor"
+ const val SOUND: String = "sound"
+ const val SOUND_DEFAULT: String = "default"
+ const val SOUND_RINGTONE: String = "ringtone"
+ const val VIBRATE: String = "vibrate"
+ const val ACTIONS: String = "actions"
+ const val CALLBACK: String = "callback"
+ const val ACTION_CALLBACK: String = "actionCallback"
+ const val DRAWABLE: String = "drawable"
+ const val MSGCNT: String = "msgcnt"
+ const val VIBRATION_PATTERN: String = "vibrationPattern"
+ const val STYLE: String = "style"
+ const val SUMMARY_TEXT: String = "summaryText"
+ const val PICTURE: String = "picture"
+ const val GCM_N: String = "gcm.n."
+ const val GCM_NOTIFICATION: String = "gcm.notification"
+ const val GCM_NOTIFICATION_BODY: String = "gcm.notification.body"
+ const val UA_PREFIX: String = "com.urbanairship.push"
+ const val PARSE_COM_DATA: String = "data"
+ const val ALERT: String = "alert"
+ const val MESSAGE: String = "message"
+ const val BODY: String = "body"
+ const val SOUNDNAME: String = "soundname"
+ const val COLOR: String = "color"
+ const val LED_COLOR: String = "ledColor"
+ const val PRIORITY: String = "priority"
+ const val IMAGE: String = "image"
+ const val STYLE_INBOX: String = "inbox"
+ const val STYLE_PICTURE: String = "picture"
+ const val STYLE_TEXT: String = "text"
+ const val BADGE: String = "badge"
+ const val INITIALIZE: String = "init"
+ const val SUBSCRIBE: String = "subscribe"
+ const val UNSUBSCRIBE: String = "unsubscribe"
+ const val UNREGISTER: String = "unregister"
+ const val EXIT: String = "exit"
+ const val FINISH: String = "finish"
+ const val HAS_PERMISSION: String = "hasPermission"
+ const val ANDROID: String = "android"
+ const val SENDER_ID: String = "senderID"
+ const val CLEAR_BADGE: String = "clearBadge"
+ const val CLEAR_NOTIFICATIONS: String = "clearNotifications"
+ const val COLDSTART: String = "coldstart"
+ const val ADDITIONAL_DATA: String = "additionalData"
+ const val COUNT: String = "count"
+ const val FROM: String = "from"
+ const val COLLAPSE_KEY: String = "collapse_key"
+ const val FORCE_SHOW: String = "forceShow"
+ const val FCM: String = "FCM"
+ const val CONTENT_AVAILABLE: String = "content-available"
+ const val TOPICS: String = "topics"
+ const val SET_APPLICATION_ICON_BADGE_NUMBER: String = "setApplicationIconBadgeNumber"
+ const val GET_APPLICATION_ICON_BADGE_NUMBER: String = "getApplicationIconBadgeNumber"
+ const val CLEAR_ALL_NOTIFICATIONS: String = "clearAllNotifications"
+ const val VISIBILITY: String = "visibility"
+ const val INLINE_REPLY: String = "inlineReply"
+ const val INLINE_REPLY_LABEL: String = "replyLabel"
+ const val LOC_KEY: String = "locKey"
+ const val LOC_DATA: String = "locData"
+ const val TWILIO_BODY: String = "twi_body"
+ const val TWILIO_TITLE: String = "twi_title"
+ const val TWILIO_SOUND: String = "twi_sound"
+ const val AWS_PINPOINT_BODY: String = "pinpoint.notification.body"
+ const val AWS_PINPOINT_PICTURE: String = "pinpoint.notification.imageUrl"
+ const val AWS_PINPOINT_PREFIX: String = "pinpoint.notification"
+ const val MP_MESSAGE: String = "mp_message"
+ const val START_IN_BACKGROUND: String = "cdvStartInBackground"
+ const val FORCE_START: String = "force-start"
+ const val MESSAGE_KEY: String = "messageKey"
+ const val TITLE_KEY: String = "titleKey"
+ const val NO_CACHE: String = "no-cache"
+ const val DISMISSED: String = "dismissed"
+ const val IMAGE_TYPE: String = "image-type"
+ const val IMAGE_TYPE_SQUARE: String = "square"
+ const val IMAGE_TYPE_CIRCLE: String = "circle"
+ const val SUBJECT: String = "subject"
+ const val GOOGLE_APP_ID: String = "google_app_id"
+ const val GCM_DEFAULT_SENDER_ID: String = "gcm_defaultSenderId"
+ const val PUSH_DISMISSED: String = "push_dismissed"
+ const val DEFAULT_CHANNEL_ID: String = "PushPluginChannel"
+ const val CHANNELS: String = "channels"
+ const val CHANNEL_ID: String = "id"
+ const val CHANNEL_DESCRIPTION: String = "description"
+ const val CHANNEL_IMPORTANCE: String = "importance"
+ const val CHANNEL_LIGHT_COLOR: String = "lightColor"
+ const val CHANNEL_VIBRATION: String = "vibration"
+ const val ANDROID_CHANNEL_ID: String = "android_channel_id"
+ const val CHANNEL_STATE: String = "state"
+ const val CREATE_CHANNEL: String = "createChannel"
+ const val DELETE_CHANNEL: String = "deleteChannel"
+ const val ONGOING: String = "ongoing"
+ const val LIST_CHANNELS: String = "listChannels"
+ const val CLEAR_NOTIFICATION: String = "clearNotification"
+ const val MESSAGE_ID: String = "google.message_id"
+ const val IS_ENABLED: String = "isEnabled"
+}
diff --git a/src/android/com/adobe/phonegap/push/PushDismissedHandler.kt b/src/android/com/adobe/phonegap/push/PushDismissedHandler.kt
new file mode 100644
index 0000000000000000000000000000000000000000..92c19db4f7fbf6da614ca8aca3191cdfcecca47b
--- /dev/null
+++ b/src/android/com/adobe/phonegap/push/PushDismissedHandler.kt
@@ -0,0 +1,30 @@
+package com.adobe.phonegap.push
+
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+
+/**
+ *
+ */
+@Suppress("HardCodedStringLiteral")
+@SuppressLint("LongLogTag")
+class PushDismissedHandler : BroadcastReceiver() {
+ companion object {
+ private const val TAG: String = "${PushPlugin.PREFIX_TAG} (PushDismissedHandler)"
+ }
+
+ /**
+ * @param context
+ * @param intent
+ */
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == PushConstants.PUSH_DISMISSED) {
+ val notID = intent.getIntExtra(PushConstants.NOT_ID, 0)
+ Log.d(TAG, "not id = $notID")
+ FCMService().setNotification(notID, "")
+ }
+ }
+}
diff --git a/src/android/com/adobe/phonegap/push/PushHandlerActivity.kt b/src/android/com/adobe/phonegap/push/PushHandlerActivity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1a60c078f07be91dca3649c4256999906beda33b
--- /dev/null
+++ b/src/android/com/adobe/phonegap/push/PushHandlerActivity.kt
@@ -0,0 +1,142 @@
+package com.adobe.phonegap.push
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.NotificationManager
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import androidx.core.app.RemoteInput
+
+/**
+ * Push Handler Activity
+ */
+@Suppress("HardCodedStringLiteral")
+@SuppressLint("LongLogTag", "LogConditional")
+class PushHandlerActivity : Activity() {
+ companion object {
+ private const val TAG: String = "${PushPlugin.PREFIX_TAG} (PushHandlerActivity)"
+ }
+
+ /**
+ * this activity will be started if the user touches a notification that we own.
+ * We send it's data off to the push plugin for processing.
+ * If needed, we boot up the main activity to kickstart the application.
+ *
+ * @param savedInstanceState
+ *
+ * @see android.app.Activity#onCreate(android.os.Bundle)
+ */
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ Log.v(TAG, "onCreate")
+
+ intent.extras?.let { extras ->
+ val notId = extras.getInt(PushConstants.NOT_ID, 0)
+ val callback = extras.getString(PushConstants.CALLBACK)
+ var foreground = extras.getBoolean(PushConstants.FOREGROUND, true)
+ val startOnBackground = extras.getBoolean(PushConstants.START_IN_BACKGROUND, false)
+ val dismissed = extras.getBoolean(PushConstants.DISMISSED, false)
+
+ FCMService().setNotification(notId, "")
+
+ if (!startOnBackground) {
+ val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.cancel(FCMService.getAppName(this), notId)
+ }
+
+ val notHaveInlineReply = processPushBundle()
+
+ if (notHaveInlineReply && Build.VERSION.SDK_INT < Build.VERSION_CODES.N && !startOnBackground) {
+ foreground = true
+ }
+
+ Log.d(TAG, "Not ID: $notId")
+ Log.d(TAG, "Callback: $callback")
+ Log.d(TAG, "Foreground: $foreground")
+ Log.d(TAG, "Start On Background: $startOnBackground")
+ Log.d(TAG, "Dismissed: $dismissed")
+
+ finish()
+
+ if (!dismissed) {
+ Log.d(TAG, "Is Push Plugin Active: ${PushPlugin.isActive}")
+
+ if (!PushPlugin.isActive && foreground && notHaveInlineReply) {
+ Log.d(TAG, "Force Main Activity Reload: Start on Background = False")
+ forceMainActivityReload(false)
+ } else if (startOnBackground) {
+ Log.d(TAG, "Force Main Activity Reload: Start on Background = True")
+ forceMainActivityReload(true)
+ } else {
+ Log.d(TAG, "Don't Want Main Activity")
+ }
+ }
+ }
+ }
+
+ private fun processPushBundle(): Boolean {
+ /*
+ * Takes the pushBundle extras from the intent,
+ * and sends it through to the PushPlugin for processing.
+ */
+ return intent.extras?.let { extras ->
+ var notHaveInlineReply = true
+
+ extras.getBundle(PushConstants.PUSH_BUNDLE)?.apply {
+ putBoolean(PushConstants.FOREGROUND, false)
+ putBoolean(PushConstants.COLDSTART, !PushPlugin.isActive)
+ putBoolean(PushConstants.DISMISSED, extras.getBoolean(PushConstants.DISMISSED))
+ putString(
+ PushConstants.ACTION_CALLBACK,
+ extras.getString(PushConstants.CALLBACK)
+ )
+ remove(PushConstants.NO_CACHE)
+
+ RemoteInput.getResultsFromIntent(intent)?.let { results ->
+ val reply = results.getCharSequence(PushConstants.INLINE_REPLY).toString()
+ Log.d(TAG, "Inline Reply: $reply")
+
+ putString(PushConstants.INLINE_REPLY, reply)
+ notHaveInlineReply = false
+ }
+
+ PushPlugin.sendExtras(this)
+ }
+
+ return notHaveInlineReply
+ } ?: true
+ }
+
+ private fun forceMainActivityReload(startOnBackground: Boolean) {
+ /*
+ * Forces the main activity to re-launch if it's unloaded.
+ */
+ val launchIntent = packageManager.getLaunchIntentForPackage(applicationContext.packageName)
+
+ intent.extras?.let { extras ->
+ launchIntent?.apply {
+ extras.getBundle(PushConstants.PUSH_BUNDLE)?.apply {
+ putExtras(this)
+ }
+
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ addFlags(Intent.FLAG_FROM_BACKGROUND)
+ putExtra(PushConstants.START_IN_BACKGROUND, startOnBackground)
+ }
+ }
+
+ startActivity(launchIntent)
+ }
+
+ /**
+ * On Resuming of Activity
+ */
+ override fun onResume() {
+ super.onResume()
+
+ val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.cancelAll()
+ }
+}
diff --git a/src/android/com/adobe/phonegap/push/PushPlugin.kt b/src/android/com/adobe/phonegap/push/PushPlugin.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3675c069a5a86bf9831abbefc8f8b1808b8c7836
--- /dev/null
+++ b/src/android/com/adobe/phonegap/push/PushPlugin.kt
@@ -0,0 +1,875 @@
+package com.adobe.phonegap.push
+
+import android.annotation.SuppressLint
+import android.annotation.TargetApi
+import android.app.Activity
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.ContentResolver
+import android.content.Context
+import android.content.res.Resources.NotFoundException
+import android.media.AudioAttributes
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.provider.Settings
+import android.util.Log
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import com.google.android.gms.tasks.Tasks
+import com.google.firebase.messaging.FirebaseMessaging
+import me.leolin.shortcutbadger.ShortcutBadger
+import org.apache.cordova.*
+import org.json.JSONArray
+import org.json.JSONException
+import org.json.JSONObject
+import java.io.IOException
+import java.util.*
+import java.util.concurrent.ExecutionException
+
+/**
+ * Cordova Plugin Push
+ */
+@Suppress("HardCodedStringLiteral")
+@SuppressLint("LongLogTag", "LogConditional")
+class PushPlugin : CordovaPlugin() {
+ companion object {
+ const val PREFIX_TAG: String = "cordova-plugin-push"
+ private const val TAG: String = "$PREFIX_TAG (PushPlugin)"
+
+ /**
+ * Is the WebView in the foreground?
+ */
+ var isInForeground: Boolean = false
+
+ private var pushContext: CallbackContext? = null
+ private var gWebView: CordovaWebView? = null
+ private val gCachedExtras = Collections.synchronizedList(ArrayList())
+
+ /**
+ *
+ */
+ fun sendEvent(json: JSONObject?) {
+ val pluginResult = PluginResult(PluginResult.Status.OK, json)
+ .apply { keepCallback = true }
+ pushContext?.sendPluginResult(pluginResult)
+ }
+
+ /**
+ * Sends the push bundle extras to the client application. If the client
+ * application isn't currently active and the no-cache flag is not set, it is
+ * cached for later processing.
+ *
+ * @param extras
+ */
+ @JvmStatic
+ fun sendExtras(extras: Bundle?) {
+ /**
+ * Serializes a bundle to JSON.
+ *
+ * @param extras
+ *
+ * @return JSONObject|null
+ */
+ fun convertBundleToJson(extras: Bundle): JSONObject? {
+ Log.d(TAG, "Convert Extras to JSON")
+
+ try {
+ val json = JSONObject()
+ val additionalData = JSONObject()
+
+ // Add any keys that need to be in top level json to this set
+ val jsonKeySet: HashSet = HashSet()
+
+ Collections.addAll(
+ jsonKeySet,
+ PushConstants.TITLE,
+ PushConstants.MESSAGE,
+ PushConstants.COUNT,
+ PushConstants.SOUND,
+ PushConstants.IMAGE
+ )
+
+ val it: Iterator = extras.keySet().iterator()
+
+ while (it.hasNext()) {
+ val key = it.next()
+ val value = extras[key]
+
+ Log.d(TAG, "Extras Iteration: key=$key")
+
+ when {
+ jsonKeySet.contains(key) -> {
+ json.put(key, value)
+ }
+
+ key == PushConstants.COLDSTART -> {
+ additionalData.put(key, extras.getBoolean(PushConstants.COLDSTART))
+ }
+
+ key == PushConstants.FOREGROUND -> {
+ additionalData.put(key, extras.getBoolean(PushConstants.FOREGROUND))
+ }
+
+ key == PushConstants.DISMISSED -> {
+ additionalData.put(key, extras.getBoolean(PushConstants.DISMISSED))
+ }
+
+ value is String -> {
+ try {
+ // Try to figure out if the value is another JSON object
+ when {
+ value.startsWith("{") -> {
+ additionalData.put(key, JSONObject(value))
+ }
+
+ value.startsWith("[") -> {
+ additionalData.put(key, JSONArray(value))
+ }
+
+ else -> {
+ additionalData.put(key, value)
+ }
+ }
+ } catch (e: Exception) {
+ additionalData.put(key, value)
+ }
+ }
+ }
+ }
+
+ json.put(PushConstants.ADDITIONAL_DATA, additionalData)
+
+ Log.v(TAG, "Extras To JSON Result: $json")
+ return json
+ } catch (e: JSONException) {
+ Log.e(TAG, "convertBundleToJson had a JSON Exception")
+ }
+
+ return null
+ }
+
+ extras?.let {
+ val noCache = it.getString(PushConstants.NO_CACHE)
+
+ if (gWebView != null) {
+ sendEvent(convertBundleToJson(extras))
+ } else if (noCache != "1") {
+ Log.v(TAG, "sendExtras: Caching extras to send at a later time.")
+ gCachedExtras.add(extras)
+ }
+ }
+ }
+
+ /**
+ * Retrieves the badge count from SharedPreferences
+ *
+ * @param context
+ *
+ * @return Int
+ */
+ fun getApplicationIconBadgeNumber(context: Context): Int {
+ val settings = context.getSharedPreferences(PushConstants.BADGE, Context.MODE_PRIVATE)
+ return settings.getInt(PushConstants.BADGE, 0)
+ }
+
+ /**
+ * Sets badge count on application icon and in SharedPreferences
+ *
+ * @param context
+ * @param badgeCount
+ */
+ @JvmStatic
+ fun setApplicationIconBadgeNumber(context: Context, badgeCount: Int) {
+ if (badgeCount > 0) {
+ ShortcutBadger.applyCount(context, badgeCount)
+ } else {
+ ShortcutBadger.removeCount(context)
+ }
+
+ context.getSharedPreferences(PushConstants.BADGE, Context.MODE_PRIVATE)
+ .edit()?.apply {
+ putInt(PushConstants.BADGE, badgeCount.coerceAtLeast(0))
+ apply()
+ }
+ }
+
+ /**
+ * @return Boolean Active is true when the Cordova WebView is present.
+ */
+ val isActive: Boolean
+ get() = gWebView != null
+ }
+
+ private val activity: Activity
+ get() = cordova.activity
+
+ private val applicationContext: Context
+ get() = activity.applicationContext
+
+ private val notificationManager: NotificationManager
+ get() = (activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
+
+ private val appName: String
+ get() = activity.packageManager.getApplicationLabel(activity.applicationInfo) as String
+
+ @TargetApi(26)
+ @Throws(JSONException::class)
+ private fun listChannels(): JSONArray {
+ val channels = JSONArray()
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val notificationChannels = notificationManager.notificationChannels
+
+ for (notificationChannel in notificationChannels) {
+ val channel = JSONObject().apply {
+ put(PushConstants.CHANNEL_ID, notificationChannel.id)
+ put(PushConstants.CHANNEL_DESCRIPTION, notificationChannel.description)
+ }
+
+ channels.put(channel)
+ }
+ }
+
+ return channels
+ }
+
+ @TargetApi(26)
+ private fun deleteChannel(channelId: String) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ notificationManager.deleteNotificationChannel(channelId)
+ }
+ }
+
+ @TargetApi(26)
+ @Throws(JSONException::class)
+ private fun createChannel(channel: JSONObject?) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ channel?.let {
+ NotificationChannel(
+ it.getString(PushConstants.CHANNEL_ID),
+ it.optString(PushConstants.CHANNEL_DESCRIPTION, appName),
+ it.optInt(PushConstants.CHANNEL_IMPORTANCE, NotificationManager.IMPORTANCE_DEFAULT)
+ ).apply {
+ /**
+ * Enable Lights when Light Color is set.
+ */
+ val mLightColor = it.optInt(PushConstants.CHANNEL_LIGHT_COLOR, -1)
+ if (mLightColor != -1) {
+ enableLights(true)
+ lightColor = mLightColor
+ }
+
+ /**
+ * Set Lock Screen Visibility.
+ */
+ lockscreenVisibility = channel.optInt(
+ PushConstants.VISIBILITY,
+ NotificationCompat.VISIBILITY_PUBLIC
+ )
+
+ /**
+ * Set if badge should be shown
+ */
+ setShowBadge(it.optBoolean(PushConstants.BADGE, true))
+
+ /**
+ * Sound Settings
+ */
+ val (soundUri, audioAttributes) = getNotificationChannelSound(it)
+ setSound(soundUri, audioAttributes)
+
+ /**
+ * Set vibration settings.
+ * Data can be either JSONArray or Boolean value.
+ */
+ val (hasVibration, vibrationPatternArray) = getNotificationChannelVibration(it)
+ if (vibrationPatternArray != null) {
+ vibrationPattern = vibrationPatternArray
+ } else {
+ enableVibration(hasVibration)
+ }
+
+ notificationManager.createNotificationChannel(this)
+ }
+ }
+ }
+ }
+
+ private fun getNotificationChannelSound(channelData: JSONObject): Pair {
+ val audioAttributes = AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build()
+
+ val sound = channelData.optString(PushConstants.SOUND, PushConstants.SOUND_DEFAULT)
+
+ return when {
+ sound == PushConstants.SOUND_RINGTONE -> Pair(
+ Settings.System.DEFAULT_RINGTONE_URI,
+ audioAttributes
+ )
+
+ // Disable sound for this notification channel if an empty string is passed.
+ // https://stackoverflow.com/a/47144981/6194193
+ sound.isEmpty() -> Pair(null, null)
+
+ // E.g. android.resource://org.apache.cordova/raw/
+ sound != PushConstants.SOUND_DEFAULT -> {
+ val scheme = ContentResolver.SCHEME_ANDROID_RESOURCE
+ val packageName = applicationContext.packageName
+
+ Pair(
+ Uri.parse("${scheme}://$packageName/raw/$sound"),
+ audioAttributes
+ )
+ }
+
+ else -> Pair(Settings.System.DEFAULT_NOTIFICATION_URI, audioAttributes)
+ }
+ }
+
+ private fun getNotificationChannelVibration(channelData: JSONObject): Pair {
+ var patternArray: LongArray? = null
+ val mVibrationPattern = channelData.optJSONArray(PushConstants.CHANNEL_VIBRATION)
+
+ if (mVibrationPattern != null) {
+ val patternLength = mVibrationPattern.length()
+ patternArray = LongArray(patternLength)
+
+ for (i in 0 until patternLength) {
+ patternArray[i] = mVibrationPattern.optLong(i)
+ }
+ }
+
+ return Pair(
+ channelData.optBoolean(PushConstants.CHANNEL_VIBRATION, true),
+ patternArray
+ )
+ }
+
+ @TargetApi(26)
+ private fun createDefaultNotificationChannelIfNeeded(options: JSONObject?) {
+ // only call on Android O and above
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channels = notificationManager.notificationChannels
+
+ for (i in channels.indices) {
+ if (PushConstants.DEFAULT_CHANNEL_ID == channels[i].id) {
+ return
+ }
+ }
+
+ try {
+ options?.apply {
+ put(PushConstants.CHANNEL_ID, PushConstants.DEFAULT_CHANNEL_ID)
+ putOpt(PushConstants.CHANNEL_DESCRIPTION, appName)
+ }
+
+ createChannel(options)
+ } catch (e: JSONException) {
+ Log.e(TAG, "Execute: JSON Exception ${e.message}")
+ }
+ }
+ }
+
+ /**
+ * Performs various push plugin related tasks:
+ *
+ * - Initialize
+ * - Unregister
+ * - Has Notification Permission Check
+ * - Set Icon Badge Number
+ * - Get Icon Badge Number
+ * - Clear All Notifications
+ * - Clear Notification
+ * - Subscribe
+ * - Unsubscribe
+ * - Create Channel
+ * - Delete Channel
+ * - List Channels
+ *
+ * @param action
+ * @param data
+ * @param callbackContext
+ */
+ override fun execute(
+ action: String,
+ data: JSONArray,
+ callbackContext: CallbackContext
+ ): Boolean {
+ Log.v(TAG, "Execute: Action = $action")
+
+ gWebView = webView
+
+ when (action) {
+ PushConstants.INITIALIZE -> executeActionInitialize(data, callbackContext)
+ PushConstants.UNREGISTER -> executeActionUnregister(data, callbackContext)
+ PushConstants.FINISH -> callbackContext.success()
+ PushConstants.HAS_PERMISSION -> executeActionHasPermission(callbackContext)
+ PushConstants.SET_APPLICATION_ICON_BADGE_NUMBER -> executeActionSetIconBadgeNumber(
+ data, callbackContext
+ )
+ PushConstants.GET_APPLICATION_ICON_BADGE_NUMBER -> executeActionGetIconBadgeNumber(
+ callbackContext
+ )
+ PushConstants.CLEAR_ALL_NOTIFICATIONS -> executeActionClearAllNotifications(callbackContext)
+ PushConstants.SUBSCRIBE -> executeActionSubscribe(data, callbackContext)
+ PushConstants.UNSUBSCRIBE -> executeActionUnsubscribe(data, callbackContext)
+ PushConstants.CREATE_CHANNEL -> executeActionCreateChannel(data, callbackContext)
+ PushConstants.DELETE_CHANNEL -> executeActionDeleteChannel(data, callbackContext)
+ PushConstants.LIST_CHANNELS -> executeActionListChannels(callbackContext)
+ PushConstants.CLEAR_NOTIFICATION -> executeActionClearNotification(data, callbackContext)
+ else -> {
+ Log.e(TAG, "Execute: Invalid Action $action")
+ callbackContext.sendPluginResult(PluginResult(PluginResult.Status.INVALID_ACTION))
+ return false
+ }
+ }
+ return true
+ }
+
+ private fun executeActionInitialize(data: JSONArray, callbackContext: CallbackContext) {
+ // Better Logging
+ fun formatLogMessage(msg: String): String = "Execute::Initialize: ($msg)"
+
+ cordova.threadPool.execute(Runnable {
+ Log.v(TAG, formatLogMessage("Data=$data"))
+
+ pushContext = callbackContext
+
+ val sharedPref = applicationContext.getSharedPreferences(
+ PushConstants.COM_ADOBE_PHONEGAP_PUSH,
+ Context.MODE_PRIVATE
+ )
+ var jo: JSONObject? = null
+ var senderID: String? = null
+
+ try {
+ jo = data.getJSONObject(0).getJSONObject(PushConstants.ANDROID)
+
+ val senderIdResId = activity.resources.getIdentifier(
+ PushConstants.GCM_DEFAULT_SENDER_ID,
+ "string",
+ activity.packageName
+ )
+ senderID = activity.getString(senderIdResId)
+
+ // If no NotificationChannels exist create the default one
+ createDefaultNotificationChannelIfNeeded(jo)
+
+ Log.v(TAG, formatLogMessage("JSONObject=$jo"))
+ Log.v(TAG, formatLogMessage("senderID=$senderID"))
+
+ val token = try {
+ try {
+ Tasks.await(FirebaseMessaging.getInstance().token)
+ } catch (e: ExecutionException) {
+ throw e.cause ?: e
+ }
+ } catch (e: IllegalStateException) {
+ Log.e(TAG, formatLogMessage("Firebase Token Exception ${e.message}"))
+ null
+ } catch (e: ExecutionException) {
+ Log.e(TAG, formatLogMessage("Firebase Token Exception ${e.message}"))
+ null
+ } catch (e: InterruptedException) {
+ Log.e(TAG, formatLogMessage("Firebase Token Exception ${e.message}"))
+ null
+ }
+
+ if (token != "") {
+ val registration = JSONObject().put(PushConstants.REGISTRATION_ID, token).apply {
+ put(PushConstants.REGISTRATION_TYPE, PushConstants.FCM)
+ }
+
+ Log.v(TAG, formatLogMessage("onRegistered=$registration"))
+
+ val topics = jo.optJSONArray(PushConstants.TOPICS)
+ subscribeToTopics(topics)
+
+ sendEvent(registration)
+ } else {
+ callbackContext.error("Empty registration ID received from FCM")
+ return@Runnable
+ }
+ } catch (e: JSONException) {
+ Log.e(TAG, formatLogMessage("JSON Exception ${e.message}"))
+ callbackContext.error(e.message)
+ } catch (e: IOException) {
+ Log.e(TAG, formatLogMessage("IO Exception ${e.message}"))
+ callbackContext.error(e.message)
+ } catch (e: NotFoundException) {
+ Log.e(TAG, formatLogMessage("Resources NotFoundException Exception ${e.message}"))
+ callbackContext.error(e.message)
+ }
+
+ jo?.let {
+ /**
+ * Add Shared Preferences
+ *
+ * Make sure to remove the preferences in the Remove step.
+ */
+ sharedPref.edit()?.apply {
+ /**
+ * Set Icon
+ */
+ try {
+ putString(PushConstants.ICON, it.getString(PushConstants.ICON))
+ } catch (e: JSONException) {
+ Log.d(TAG, formatLogMessage("No Icon Options"))
+ }
+
+ /**
+ * Set Icon Color
+ */
+ try {
+ putString(PushConstants.ICON_COLOR, it.getString(PushConstants.ICON_COLOR))
+ } catch (e: JSONException) {
+ Log.d(TAG, formatLogMessage("No Icon Color Options"))
+ }
+
+ /**
+ * Clear badge count when true
+ */
+ val clearBadge = it.optBoolean(PushConstants.CLEAR_BADGE, false)
+ putBoolean(PushConstants.CLEAR_BADGE, clearBadge)
+
+ if (clearBadge) {
+ setApplicationIconBadgeNumber(applicationContext, 0)
+ }
+
+ /**
+ * Set Sound
+ */
+ putBoolean(PushConstants.SOUND, it.optBoolean(PushConstants.SOUND, true))
+
+ /**
+ * Set Vibrate
+ */
+ putBoolean(PushConstants.VIBRATE, it.optBoolean(PushConstants.VIBRATE, true))
+
+ /**
+ * Set Clear Notifications
+ */
+ putBoolean(
+ PushConstants.CLEAR_NOTIFICATIONS,
+ it.optBoolean(PushConstants.CLEAR_NOTIFICATIONS, true)
+ )
+
+ /**
+ * Set Force Show
+ */
+ putBoolean(
+ PushConstants.FORCE_SHOW,
+ it.optBoolean(PushConstants.FORCE_SHOW, false)
+ )
+
+ /**
+ * Set SenderID
+ */
+ putString(PushConstants.SENDER_ID, senderID)
+
+ /**
+ * Set Message Key
+ */
+ putString(PushConstants.MESSAGE_KEY, it.optString(PushConstants.MESSAGE_KEY))
+
+ /**
+ * Set Title Key
+ */
+ putString(PushConstants.TITLE_KEY, it.optString(PushConstants.TITLE_KEY))
+
+ commit()
+ }
+ }
+
+ if (gCachedExtras.isNotEmpty()) {
+ Log.v(TAG, formatLogMessage("Sending Cached Extras"))
+
+ synchronized(gCachedExtras) {
+ val gCachedExtrasIterator: Iterator = gCachedExtras.iterator()
+
+ while (gCachedExtrasIterator.hasNext()) {
+ sendExtras(gCachedExtrasIterator.next())
+ }
+ }
+
+ gCachedExtras.clear()
+ }
+ })
+ }
+
+ private fun executeActionUnregister(data: JSONArray, callbackContext: CallbackContext) {
+ // Better Logging
+ fun formatLogMessage(msg: String): String = "Execute::Unregister: ($msg)"
+
+ cordova.threadPool.execute {
+ try {
+ val sharedPref = applicationContext.getSharedPreferences(
+ PushConstants.COM_ADOBE_PHONEGAP_PUSH,
+ Context.MODE_PRIVATE
+ )
+ val topics = data.optJSONArray(0)
+
+ if (topics != null) {
+ unsubscribeFromTopics(topics)
+ } else {
+ try {
+ Tasks.await(FirebaseMessaging.getInstance().deleteToken())
+ } catch (e: ExecutionException) {
+ throw e.cause ?: e
+ }
+ Log.v(TAG, formatLogMessage("UNREGISTER"))
+
+ /**
+ * Remove Shared Preferences
+ *
+ * Make sure to remove what was in the Initialize step.
+ */
+ sharedPref.edit()?.apply {
+ remove(PushConstants.ICON)
+ remove(PushConstants.ICON_COLOR)
+ remove(PushConstants.CLEAR_BADGE)
+ remove(PushConstants.SOUND)
+ remove(PushConstants.VIBRATE)
+ remove(PushConstants.CLEAR_NOTIFICATIONS)
+ remove(PushConstants.FORCE_SHOW)
+ remove(PushConstants.SENDER_ID)
+ remove(PushConstants.MESSAGE_KEY)
+ remove(PushConstants.TITLE_KEY)
+
+ commit()
+ }
+ }
+
+ callbackContext.success()
+ } catch (e: IOException) {
+ Log.e(TAG, formatLogMessage("IO Exception ${e.message}"))
+ callbackContext.error(e.message)
+ } catch (e: InterruptedException) {
+ Log.e(TAG, formatLogMessage("Interrupted ${e.message}"))
+ callbackContext.error(e.message)
+ }
+ }
+ }
+
+ private fun executeActionHasPermission(callbackContext: CallbackContext) {
+ // Better Logging
+ fun formatLogMessage(msg: String): String = "Execute::HasPermission: ($msg)"
+
+ cordova.threadPool.execute {
+ try {
+ val isNotificationEnabled = NotificationManagerCompat.from(applicationContext)
+ .areNotificationsEnabled()
+
+ Log.d(TAG, formatLogMessage("Has Notification Permission: $isNotificationEnabled"))
+
+ val jo = JSONObject().apply {
+ put(PushConstants.IS_ENABLED, isNotificationEnabled)
+ }
+
+ val pluginResult = PluginResult(PluginResult.Status.OK, jo).apply {
+ keepCallback = true
+ }
+
+ callbackContext.sendPluginResult(pluginResult)
+ } catch (e: UnknownError) {
+ callbackContext.error(e.message)
+ } catch (e: JSONException) {
+ callbackContext.error(e.message)
+ }
+ }
+ }
+
+ private fun executeActionSetIconBadgeNumber(data: JSONArray, callbackContext: CallbackContext) {
+ fun formatLogMessage(msg: String): String = "Execute::SetIconBadgeNumber: ($msg)"
+
+ cordova.threadPool.execute {
+ Log.v(TAG, formatLogMessage("data=$data"))
+
+ try {
+ val badgeCount = data.getJSONObject(0).getInt(PushConstants.BADGE)
+ setApplicationIconBadgeNumber(applicationContext, badgeCount)
+ } catch (e: JSONException) {
+ callbackContext.error(e.message)
+ }
+
+ callbackContext.success()
+ }
+ }
+
+ private fun executeActionGetIconBadgeNumber(callbackContext: CallbackContext) {
+ cordova.threadPool.execute {
+ Log.v(TAG, "Execute::GetIconBadgeNumber")
+ callbackContext.success(getApplicationIconBadgeNumber(applicationContext))
+ }
+ }
+
+ private fun executeActionClearAllNotifications(callbackContext: CallbackContext) {
+ cordova.threadPool.execute {
+ Log.v(TAG, "Execute Clear All Notifications")
+ clearAllNotifications()
+ callbackContext.success()
+ }
+ }
+
+ private fun executeActionSubscribe(data: JSONArray, callbackContext: CallbackContext) {
+ cordova.threadPool.execute {
+ try {
+ Log.v(TAG, "Execute::Subscribe")
+ val topic = data.getString(0)
+ subscribeToTopic(topic)
+ callbackContext.success()
+ } catch (e: JSONException) {
+ callbackContext.error(e.message)
+ }
+ }
+ }
+
+ private fun executeActionUnsubscribe(data: JSONArray, callbackContext: CallbackContext) {
+ cordova.threadPool.execute {
+ try {
+ Log.v(TAG, "Execute::Unsubscribe")
+ val topic = data.getString(0)
+ unsubscribeFromTopic(topic)
+ callbackContext.success()
+ } catch (e: JSONException) {
+ callbackContext.error(e.message)
+ }
+ }
+ }
+
+ private fun executeActionCreateChannel(data: JSONArray, callbackContext: CallbackContext) {
+ cordova.threadPool.execute {
+ try {
+ Log.v(TAG, "Execute::CreateChannel")
+ createChannel(data.getJSONObject(0))
+ callbackContext.success()
+ } catch (e: JSONException) {
+ callbackContext.error(e.message)
+ }
+ }
+ }
+
+ private fun executeActionDeleteChannel(data: JSONArray, callbackContext: CallbackContext) {
+ cordova.threadPool.execute {
+ try {
+ val channelId = data.getString(0)
+ Log.v(TAG, "Execute::DeleteChannel channelId=$channelId")
+ deleteChannel(channelId)
+ callbackContext.success()
+ } catch (e: JSONException) {
+ callbackContext.error(e.message)
+ }
+ }
+ }
+
+ private fun executeActionListChannels(callbackContext: CallbackContext) {
+ cordova.threadPool.execute {
+ try {
+ Log.v(TAG, "Execute::ListChannels")
+ callbackContext.success(listChannels())
+ } catch (e: JSONException) {
+ callbackContext.error(e.message)
+ }
+ }
+ }
+
+ private fun executeActionClearNotification(data: JSONArray, callbackContext: CallbackContext) {
+ cordova.threadPool.execute {
+ try {
+ val notificationId = data.getInt(0)
+ Log.v(TAG, "Execute::ClearNotification notificationId=$notificationId")
+ clearNotification(notificationId)
+ callbackContext.success()
+ } catch (e: JSONException) {
+ callbackContext.error(e.message)
+ }
+ }
+ }
+
+ /**
+ * Initialize
+ */
+ override fun initialize(cordova: CordovaInterface, webView: CordovaWebView) {
+ super.initialize(cordova, webView)
+ isInForeground = true
+ }
+
+ /**
+ * Handle when the view is being paused
+ */
+ override fun onPause(multitasking: Boolean) {
+ isInForeground = false
+ super.onPause(multitasking)
+ }
+
+ /**
+ * Handle when the view is resuming
+ */
+ override fun onResume(multitasking: Boolean) {
+ super.onResume(multitasking)
+ isInForeground = true
+ }
+
+ /**
+ * Handle when the view is being destroyed
+ */
+ override fun onDestroy() {
+ isInForeground = false
+ gWebView = null
+
+ // Clear Notification
+ applicationContext.getSharedPreferences(
+ PushConstants.COM_ADOBE_PHONEGAP_PUSH,
+ Context.MODE_PRIVATE
+ ).apply {
+ if (getBoolean(PushConstants.CLEAR_NOTIFICATIONS, true)) {
+ clearAllNotifications()
+ }
+ }
+
+ super.onDestroy()
+ }
+
+ private fun clearAllNotifications() {
+ notificationManager.cancelAll()
+ }
+
+ private fun clearNotification(id: Int) {
+ notificationManager.cancel(appName, id)
+ }
+
+ private fun subscribeToTopics(topics: JSONArray?) {
+ topics?.let {
+ for (i in 0 until it.length()) {
+ val topicKey = it.optString(i, null)
+ subscribeToTopic(topicKey)
+ }
+ }
+ }
+
+ private fun unsubscribeFromTopics(topics: JSONArray?) {
+ topics?.let {
+ for (i in 0 until it.length()) {
+ val topic = it.optString(i, null)
+ unsubscribeFromTopic(topic)
+ }
+ }
+ }
+
+ private fun subscribeToTopic(topic: String?) {
+ topic?.let {
+ Log.d(TAG, "Subscribing to Topic: $it")
+ FirebaseMessaging.getInstance().subscribeToTopic(it)
+ }
+ }
+
+ private fun unsubscribeFromTopic(topic: String?) {
+ topic?.let {
+ Log.d(TAG, "Unsubscribing to topic: $it")
+ FirebaseMessaging.getInstance().unsubscribeFromTopic(it)
+ }
+ }
+}
diff --git a/src/browser/ServiceWorker.js b/src/browser/ServiceWorker.js
new file mode 100644
index 0000000000000000000000000000000000000000..017d695f657f91889cd055092f3897b55155d785
--- /dev/null
+++ b/src/browser/ServiceWorker.js
@@ -0,0 +1,50 @@
+var messageChannel;
+
+self.addEventListener('install', function (event) {
+ self.skipWaiting();
+});
+
+self.addEventListener('push', function (event) {
+ // parse incoming message
+ var obj = {};
+ var pushData = {
+ image: 'https://avatars1.githubusercontent.com/u/60365?v=3&s=200',
+ additionalData: {}
+ };
+ if (event.data) {
+ obj = event.data.json();
+ }
+
+ console.log(obj);
+
+ // convert to push plugin API
+ for (var key in obj) {
+ if (key === 'title') {
+ pushData.title = obj[key];
+ } else if (key === 'message' || key === 'body') {
+ pushData.message = obj[key];
+ } else if (key === 'count' || key === 'msgcnt' || key === 'badge') {
+ pushData.count = obj[key];
+ } else if (key === 'sound' || key === 'soundname') {
+ pushData.sound = obj[key];
+ } else if (key === 'image') {
+ pushData.image = obj[key];
+ } else {
+ pushData.additionalData[key] = obj[key];
+ }
+ }
+
+ event.waitUntil(
+ self.registration.showNotification(pushData.title, {
+ body: pushData.message,
+ icon: pushData.image,
+ tag: 'simple-push-demo-notification-tag'
+ })
+ );
+
+ messageChannel.ports[0].postMessage(pushData);
+});
+
+self.addEventListener('message', function (event) {
+ messageChannel = event;
+});
diff --git a/src/browser/manifest.json b/src/browser/manifest.json
new file mode 100644
index 0000000000000000000000000000000000000000..eff52c7411469671c99cb0543075329c97e4b565
--- /dev/null
+++ b/src/browser/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "Push Demo",
+ "gcm_sender_id": "996231231186"
+}
diff --git a/src/ios/AppDelegate+notification.h b/src/ios/AppDelegate+notification.h
new file mode 100644
index 0000000000000000000000000000000000000000..9479b1839e560fa77ea4b731311aa4138296fd54
--- /dev/null
+++ b/src/ios/AppDelegate+notification.h
@@ -0,0 +1,25 @@
+//
+// AppDelegate+notification.h
+// pushtest
+//
+// Created by Robert Easterday on 10/26/12.
+//
+//
+
+#import "AppDelegate.h"
+@import UserNotifications;
+
+extern NSString *const pushPluginApplicationDidBecomeActiveNotification;
+
+@interface AppDelegate (notification)
+- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
+- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
+- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:( void (^)(UIBackgroundFetchResult))completionHandler;
+- (void)pushPluginOnApplicationDidBecomeActive:(UIApplication *)application;
+- (void)checkUserHasRemoteNotificationsEnabledWithCompletionHandler:(nonnull void (^)(BOOL))completionHandler;
+- (id) getCommandInstance:(NSString*)className;
+
+@property (nonatomic, retain) NSDictionary *launchNotification;
+@property (nonatomic, retain) NSNumber *coldstart;
+
+@end
diff --git a/src/ios/AppDelegate+notification.m b/src/ios/AppDelegate+notification.m
new file mode 100644
index 0000000000000000000000000000000000000000..861b68afce986498f3140aa05219bd6b8fa22f69
--- /dev/null
+++ b/src/ios/AppDelegate+notification.m
@@ -0,0 +1,290 @@
+//
+// AppDelegate+notification.m
+// pushtest
+//
+// Created by Robert Easterday on 10/26/12.
+//
+//
+
+#import "AppDelegate+notification.h"
+#import "PushPlugin.h"
+#import
+
+static char launchNotificationKey;
+static char coldstartKey;
+NSString *const pushPluginApplicationDidBecomeActiveNotification = @"pushPluginApplicationDidBecomeActiveNotification";
+
+
+@implementation AppDelegate (notification)
+
+- (id) getCommandInstance:(NSString*)className
+{
+ return [self.viewController getCommandInstance:className];
+}
+
+// its dangerous to override a method from within a category.
+// Instead we will use method swizzling. we set this up in the load call.
++ (void)load
+{
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ Class class = [self class];
+
+ SEL originalSelector = @selector(init);
+ SEL swizzledSelector = @selector(pushPluginSwizzledInit);
+
+ Method original = class_getInstanceMethod(class, originalSelector);
+ Method swizzled = class_getInstanceMethod(class, swizzledSelector);
+
+ BOOL didAddMethod =
+ class_addMethod(class,
+ originalSelector,
+ method_getImplementation(swizzled),
+ method_getTypeEncoding(swizzled));
+
+ if (didAddMethod) {
+ class_replaceMethod(class,
+ swizzledSelector,
+ method_getImplementation(original),
+ method_getTypeEncoding(original));
+ } else {
+ method_exchangeImplementations(original, swizzled);
+ }
+ });
+}
+
+- (AppDelegate *)pushPluginSwizzledInit
+{
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ center.delegate = self;
+
+ [[NSNotificationCenter defaultCenter]addObserver:self
+ selector:@selector(pushPluginOnApplicationDidBecomeActive:)
+ name:UIApplicationDidBecomeActiveNotification
+ object:nil];
+
+ // This actually calls the original init method over in AppDelegate. Equivilent to calling super
+ // on an overrided method, this is not recursive, although it appears that way. neat huh?
+ return [self pushPluginSwizzledInit];
+}
+
+- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
+ PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
+ [pushHandler didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
+}
+
+- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
+ PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
+ [pushHandler didFailToRegisterForRemoteNotificationsWithError:error];
+}
+
+- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
+ NSLog(@"didReceiveNotification with fetchCompletionHandler");
+
+ // app is in the background or inactive, so only call notification callback if this is a silent push
+ if (application.applicationState != UIApplicationStateActive) {
+
+ NSLog(@"app in-active");
+
+ // do some convoluted logic to find out if this should be a silent push.
+ long silent = 0;
+ id aps = [userInfo objectForKey:@"aps"];
+ id contentAvailable = [aps objectForKey:@"content-available"];
+ if ([contentAvailable isKindOfClass:[NSString class]] && [contentAvailable isEqualToString:@"1"]) {
+ silent = 1;
+ } else if ([contentAvailable isKindOfClass:[NSNumber class]]) {
+ silent = [contentAvailable integerValue];
+ }
+
+ if (silent == 1) {
+ NSLog(@"this should be a silent push");
+ void (^safeHandler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result){
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completionHandler(result);
+ });
+ };
+
+ PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
+
+ if (pushHandler.handlerObj == nil) {
+ pushHandler.handlerObj = [NSMutableDictionary dictionaryWithCapacity:2];
+ }
+
+ id notId = [userInfo objectForKey:@"notId"];
+ if (notId != nil) {
+ NSLog(@"Push Plugin notId %@", notId);
+ [pushHandler.handlerObj setObject:safeHandler forKey:notId];
+ } else {
+ NSLog(@"Push Plugin notId handler");
+ [pushHandler.handlerObj setObject:safeHandler forKey:@"handler"];
+ }
+
+ pushHandler.notificationMessage = userInfo;
+ pushHandler.isInline = NO;
+ [pushHandler notificationReceived];
+ } else {
+ NSLog(@"just put it in the shade");
+ //save it for later
+ self.launchNotification = userInfo;
+ completionHandler(UIBackgroundFetchResultNewData);
+ }
+
+ } else {
+ completionHandler(UIBackgroundFetchResultNoData);
+ }
+}
+
+- (void)checkUserHasRemoteNotificationsEnabledWithCompletionHandler:(nonnull void (^)(BOOL))completionHandler
+{
+ [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
+
+ switch (settings.authorizationStatus)
+ {
+ case UNAuthorizationStatusDenied:
+ case UNAuthorizationStatusNotDetermined:
+ completionHandler(NO);
+ break;
+ case UNAuthorizationStatusAuthorized:
+ completionHandler(YES);
+ break;
+ }
+ }];
+}
+
+- (void)pushPluginOnApplicationDidBecomeActive:(NSNotification *)notification {
+
+ NSLog(@"active");
+
+ NSString *firstLaunchKey = @"firstLaunchKey";
+ NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"phonegap-plugin-push"];
+ if (![defaults boolForKey:firstLaunchKey]) {
+ NSLog(@"application first launch: remove badge icon number");
+ [defaults setBool:YES forKey:firstLaunchKey];
+ [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
+ }
+
+ UIApplication *application = notification.object;
+
+ PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
+ if (pushHandler.clearBadge) {
+ NSLog(@"PushPlugin clearing badge");
+ //zero badge
+ application.applicationIconBadgeNumber = 0;
+ } else {
+ NSLog(@"PushPlugin skip clear badge");
+ }
+
+ if (self.launchNotification) {
+ pushHandler.isInline = NO;
+ pushHandler.coldstart = [self.coldstart boolValue];
+ pushHandler.notificationMessage = self.launchNotification;
+ self.launchNotification = nil;
+ self.coldstart = [NSNumber numberWithBool:NO];
+ [pushHandler performSelectorOnMainThread:@selector(notificationReceived) withObject:pushHandler waitUntilDone:NO];
+ }
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:pushPluginApplicationDidBecomeActiveNotification object:nil];
+}
+
+- (void)userNotificationCenter:(UNUserNotificationCenter *)center
+ willPresentNotification:(UNNotification *)notification
+ withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
+{
+ NSLog( @"NotificationCenter Handle push from foreground" );
+ // custom code to handle push while app is in the foreground
+ PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
+ pushHandler.notificationMessage = notification.request.content.userInfo;
+ pushHandler.isInline = YES;
+// [pushHandler notificationReceived];
+
+ // completionHandler(UNNotificationPresentationOptionNone);
+ completionHandler(UNNotificationPresentationOptionAlert);
+}
+
+- (void)userNotificationCenter:(UNUserNotificationCenter *)center
+didReceiveNotificationResponse:(UNNotificationResponse *)response
+ withCompletionHandler:(void(^)(void))completionHandler
+{
+ NSLog(@"Push Plugin didReceiveNotificationResponse: actionIdentifier %@, notification: %@", response.actionIdentifier,
+ response.notification.request.content.userInfo);
+ NSMutableDictionary *userInfo = [response.notification.request.content.userInfo mutableCopy];
+ [userInfo setObject:response.actionIdentifier forKey:@"actionCallback"];
+ NSLog(@"Push Plugin userInfo %@", userInfo);
+
+ switch ([UIApplication sharedApplication].applicationState) {
+ case UIApplicationStateActive:
+ {
+ PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
+ pushHandler.notificationMessage = userInfo;
+ pushHandler.isInline = YES;
+ [pushHandler notificationReceived];
+ completionHandler();
+ break;
+ }
+ case UIApplicationStateInactive:
+ {
+ NSLog(@"coldstart");
+ self.launchNotification = response.notification.request.content.userInfo;
+ self.coldstart = [NSNumber numberWithBool:YES];
+ break;
+ }
+ case UIApplicationStateBackground:
+ {
+ void (^safeHandler)(void) = ^(void){
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completionHandler();
+ });
+ };
+
+ PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
+
+ if (pushHandler.handlerObj == nil) {
+ pushHandler.handlerObj = [NSMutableDictionary dictionaryWithCapacity:2];
+ }
+
+ id notId = [userInfo objectForKey:@"notId"];
+ if (notId != nil) {
+ NSLog(@"Push Plugin notId %@", notId);
+ [pushHandler.handlerObj setObject:safeHandler forKey:notId];
+ } else {
+ NSLog(@"Push Plugin notId handler");
+ [pushHandler.handlerObj setObject:safeHandler forKey:@"handler"];
+ }
+
+ pushHandler.notificationMessage = userInfo;
+ pushHandler.isInline = NO;
+
+ [pushHandler performSelectorOnMainThread:@selector(notificationReceived) withObject:pushHandler waitUntilDone:NO];
+ }
+ }
+}
+
+// The accessors use an Associative Reference since you can't define a iVar in a category
+// http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocAssociativeReferences.html
+- (NSMutableArray *)launchNotification
+{
+ return objc_getAssociatedObject(self, &launchNotificationKey);
+}
+
+- (void)setLaunchNotification:(NSDictionary *)aDictionary
+{
+ objc_setAssociatedObject(self, &launchNotificationKey, aDictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+- (NSNumber *)coldstart
+{
+ return objc_getAssociatedObject(self, &coldstartKey);
+}
+
+- (void)setColdstart:(NSNumber *)aNumber
+{
+ objc_setAssociatedObject(self, &coldstartKey, aNumber, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+- (void)dealloc
+{
+ self.launchNotification = nil; // clear the association and release the object
+ self.coldstart = nil;
+}
+
+@end
diff --git a/src/ios/PushPlugin.h b/src/ios/PushPlugin.h
new file mode 100644
index 0000000000000000000000000000000000000000..0945d5148b2e540bedc9146e86fe3b819149e9e4
--- /dev/null
+++ b/src/ios/PushPlugin.h
@@ -0,0 +1,85 @@
+/*
+ Copyright 2009-2011 Urban Airship Inc. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ 2. Redistributions in binaryform must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided withthe distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE URBAN AIRSHIP INC``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ EVENT SHALL URBAN AIRSHIP INC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+@import Foundation;
+@import UserNotifications;
+#import
+#import
+#import
+
+@protocol GGLInstanceIDDelegate;
+@protocol GCMReceiverDelegate;
+@interface PushPlugin : CDVPlugin
+{
+ NSDictionary *notificationMessage;
+ BOOL isInline;
+ NSString *notificationCallbackId;
+ NSString *callback;
+ BOOL clearBadge;
+
+ NSMutableDictionary *handlerObj;
+ void (^completionHandler)(UIBackgroundFetchResult);
+
+ BOOL ready;
+}
+
+@property (nonatomic, copy) NSString *callbackId;
+@property (nonatomic, copy) NSString *notificationCallbackId;
+@property (nonatomic, copy) NSString *callback;
+
+@property (nonatomic, strong) NSDictionary *notificationMessage;
+@property BOOL isInline;
+@property BOOL coldstart;
+@property BOOL clearBadge;
+@property (nonatomic, strong) NSMutableDictionary *handlerObj;
+
+- (void)init:(CDVInvokedUrlCommand*)command;
+- (void)unregister:(CDVInvokedUrlCommand*)command;
+- (void)subscribe:(CDVInvokedUrlCommand*)command;
+- (void)unsubscribe:(CDVInvokedUrlCommand*)command;
+- (void)clearNotification:(CDVInvokedUrlCommand*)command;
+
+- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
+- (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
+
+- (void)setNotificationMessage:(NSDictionary *)notification;
+- (void)notificationReceived;
+
+- (void)willSendDataMessageWithID:(NSString *)messageID error:(NSError *)error;
+- (void)didSendDataMessageWithID:(NSString *)messageID;
+
+// VoIP Features
+- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type;
+- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type;
+
+// FCM Features
+@property(nonatomic, assign) BOOL usesFCM;
+@property(nonatomic, strong) NSNumber *fcmSandbox;
+@property(nonatomic, strong) NSString *fcmSenderId;
+@property(nonatomic, strong) NSDictionary *fcmRegistrationOptions;
+@property(nonatomic, strong) NSString *fcmRegistrationToken;
+@property(nonatomic, strong) NSArray *fcmTopics;
+
+@end
diff --git a/src/ios/PushPlugin.m b/src/ios/PushPlugin.m
new file mode 100644
index 0000000000000000000000000000000000000000..a53f84dce6bdbbfca0422a889bf8eaf19db40151
--- /dev/null
+++ b/src/ios/PushPlugin.m
@@ -0,0 +1,680 @@
+/*
+ Copyright 2009-2011 Urban Airship Inc. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ 2. Redistributions in binaryform must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided withthe distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE URBAN AIRSHIP INC``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ EVENT SHALL URBAN AIRSHIP INC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// See GGLInstanceID.h
+#define GMP_NO_MODULES true
+
+#import "PushPlugin.h"
+#import "AppDelegate+notification.h"
+
+@import Firebase;
+@import FirebaseCore;
+// @import FirebaseInstanceID;
+@import FirebaseMessaging;
+
+@implementation PushPlugin : CDVPlugin
+
+@synthesize notificationMessage;
+@synthesize isInline;
+@synthesize coldstart;
+
+@synthesize callbackId;
+@synthesize notificationCallbackId;
+@synthesize callback;
+@synthesize clearBadge;
+@synthesize handlerObj;
+
+@synthesize usesFCM;
+@synthesize fcmSandbox;
+@synthesize fcmSenderId;
+@synthesize fcmRegistrationOptions;
+@synthesize fcmRegistrationToken;
+@synthesize fcmTopics;
+
+-(void)initRegistration;
+{
+ [[FIRMessaging messaging] tokenWithCompletion:^(NSString *token, NSError *error) {
+ if (error != nil) {
+ NSLog(@"Error getting FCM registration token: %@", error);
+ } else {
+ NSLog(@"FCM registration token: %@", token);
+
+ [self setFcmRegistrationToken: token];
+
+ NSString* message = [NSString stringWithFormat:@"Remote InstanceID token: %@", token];
+
+ id topics = [self fcmTopics];
+ if (topics != nil) {
+ for (NSString *topic in topics) {
+ NSLog(@"subscribe to topic: %@", topic);
+ id pubSub = [FIRMessaging messaging];
+ [pubSub subscribeToTopic:topic];
+ }
+ }
+
+ [self registerWithToken: token];
+ }
+ }];
+}
+
+// FCM refresh token
+// Unclear how this is testable under normal circumstances
+- (void)onTokenRefresh {
+#if !TARGET_IPHONE_SIMULATOR
+ // A rotation of the registration tokens is happening, so the app needs to request a new token.
+ NSLog(@"The FCM registration token needs to be changed.");
+ [self initRegistration];
+#endif
+}
+
+// contains error info
+- (void)didSendDataMessageWithID:messageID {
+ NSLog(@"didSendDataMessageWithID");
+}
+
+- (void)willSendDataMessageWithID:messageID error:error {
+ NSLog(@"willSendDataMessageWithID");
+}
+
+- (void)unregister:(CDVInvokedUrlCommand*)command;
+{
+ NSArray* topics = [command argumentAtIndex:0];
+
+ if (topics != nil) {
+ id pubSub = [FIRMessaging messaging];
+ for (NSString *topic in topics) {
+ NSLog(@"unsubscribe from topic: %@", topic);
+ [pubSub unsubscribeFromTopic:topic];
+ }
+ } else {
+ [[UIApplication sharedApplication] unregisterForRemoteNotifications];
+ [self successWithMessage:command.callbackId withMsg:@"unregistered"];
+ }
+}
+
+- (void)subscribe:(CDVInvokedUrlCommand*)command;
+{
+ NSString* topic = [command argumentAtIndex:0];
+
+ if (topic != nil) {
+ NSLog(@"subscribe from topic: %@", topic);
+ id pubSub = [FIRMessaging messaging];
+ [pubSub subscribeToTopic:topic];
+ NSLog(@"Successfully subscribe to topic %@", topic);
+ [self successWithMessage:command.callbackId withMsg:[NSString stringWithFormat:@"Successfully subscribe to topic %@", topic]];
+ } else {
+ NSLog(@"There is no topic to subscribe");
+ [self successWithMessage:command.callbackId withMsg:@"There is no topic to subscribe"];
+ }
+}
+
+- (void)unsubscribe:(CDVInvokedUrlCommand*)command;
+{
+ NSString* topic = [command argumentAtIndex:0];
+
+ if (topic != nil) {
+ NSLog(@"unsubscribe from topic: %@", topic);
+ id pubSub = [FIRMessaging messaging];
+ [pubSub unsubscribeFromTopic:topic];
+ NSLog(@"Successfully unsubscribe from topic %@", topic);
+ [self successWithMessage:command.callbackId withMsg:[NSString stringWithFormat:@"Successfully unsubscribe from topic %@", topic]];
+ } else {
+ NSLog(@"There is no topic to unsubscribe");
+ [self successWithMessage:command.callbackId withMsg:@"There is no topic to unsubscribe"];
+ }
+}
+
+- (void)init:(CDVInvokedUrlCommand*)command;
+{
+ NSMutableDictionary* options = [command.arguments objectAtIndex:0];
+ NSMutableDictionary* iosOptions = [options objectForKey:@"ios"];
+ id voipArg = [iosOptions objectForKey:@"voip"];
+ if (([voipArg isKindOfClass:[NSString class]] && [voipArg isEqualToString:@"true"]) || [voipArg boolValue]) {
+ [self.commandDelegate runInBackground:^ {
+ NSLog(@"Push Plugin VoIP set to true");
+
+ self.callbackId = command.callbackId;
+
+ PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
+ pushRegistry.delegate = self;
+ pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
+ }];
+ } else {
+ NSLog(@"Push Plugin VoIP missing or false");
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self selector:@selector(onTokenRefresh)
+ name:FIRMessagingRegistrationTokenRefreshedNotification object:nil];
+
+ [self.commandDelegate runInBackground:^ {
+ NSLog(@"Push Plugin register called");
+ self.callbackId = command.callbackId;
+
+ NSArray* topics = [iosOptions objectForKey:@"topics"];
+ [self setFcmTopics:topics];
+
+ UNAuthorizationOptions authorizationOptions = UNAuthorizationOptionNone;
+
+ id badgeArg = [iosOptions objectForKey:@"badge"];
+ id soundArg = [iosOptions objectForKey:@"sound"];
+ id alertArg = [iosOptions objectForKey:@"alert"];
+ id criticalArg = [iosOptions objectForKey:@"critical"];
+ id clearBadgeArg = [iosOptions objectForKey:@"clearBadge"];
+
+ if (([badgeArg isKindOfClass:[NSString class]] && [badgeArg isEqualToString:@"true"]) || [badgeArg boolValue])
+ {
+ authorizationOptions |= UNAuthorizationOptionBadge;
+ }
+
+ if (([soundArg isKindOfClass:[NSString class]] && [soundArg isEqualToString:@"true"]) || [soundArg boolValue])
+ {
+ authorizationOptions |= UNAuthorizationOptionSound;
+ }
+
+ if (([alertArg isKindOfClass:[NSString class]] && [alertArg isEqualToString:@"true"]) || [alertArg boolValue])
+ {
+ authorizationOptions |= UNAuthorizationOptionAlert;
+ }
+
+ if (@available(iOS 12.0, *))
+ {
+ if ((([criticalArg isKindOfClass:[NSString class]] && [criticalArg isEqualToString:@"true"]) || [criticalArg boolValue]))
+ {
+ authorizationOptions |= UNAuthorizationOptionCriticalAlert;
+ }
+ }
+
+ if (clearBadgeArg == nil || ([clearBadgeArg isKindOfClass:[NSString class]] && [clearBadgeArg isEqualToString:@"false"]) || ![clearBadgeArg boolValue]) {
+ NSLog(@"PushPlugin.register: setting badge to false");
+ clearBadge = NO;
+ } else {
+ NSLog(@"PushPlugin.register: setting badge to true");
+ clearBadge = YES;
+ [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
+ }
+ NSLog(@"PushPlugin.register: clear badge is set to %d", clearBadge);
+
+// isInline = NO;
+
+ NSLog(@"PushPlugin.register: better button setup");
+ // setup action buttons
+ NSMutableSet *categories = [[NSMutableSet alloc] init];
+ id categoryOptions = [iosOptions objectForKey:@"categories"];
+ if (categoryOptions != nil && [categoryOptions isKindOfClass:[NSDictionary class]]) {
+ for (id key in categoryOptions) {
+ NSLog(@"categories: key %@", key);
+ id category = [categoryOptions objectForKey:key];
+
+ id yesButton = [category objectForKey:@"yes"];
+ UNNotificationAction *yesAction;
+ if (yesButton != nil && [yesButton isKindOfClass:[NSDictionary class]]) {
+ yesAction = [self createAction: yesButton];
+ }
+ id noButton = [category objectForKey:@"no"];
+ UNNotificationAction *noAction;
+ if (noButton != nil && [noButton isKindOfClass:[NSDictionary class]]) {
+ noAction = [self createAction: noButton];
+ }
+ id maybeButton = [category objectForKey:@"maybe"];
+ UNNotificationAction *maybeAction;
+ if (maybeButton != nil && [maybeButton isKindOfClass:[NSDictionary class]]) {
+ maybeAction = [self createAction: maybeButton];
+ }
+
+ // Identifier to include in your push payload and local notification
+ NSString *identifier = key;
+
+ NSMutableArray *actions = [[NSMutableArray alloc] init];
+ if (yesButton != nil) {
+ [actions addObject:yesAction];
+ }
+ if (noButton != nil) {
+ [actions addObject:noAction];
+ }
+ if (maybeButton != nil) {
+ [actions addObject:maybeAction];
+ }
+
+ UNNotificationCategory *notificationCategory = [UNNotificationCategory categoryWithIdentifier:identifier
+ actions:actions
+ intentIdentifiers:@[]
+ options:UNNotificationCategoryOptionNone];
+
+ NSLog(@"Adding category %@", key);
+ [categories addObject:notificationCategory];
+ }
+
+ }
+
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ [center setNotificationCategories:categories];
+ [self handleNotificationSettingsWithAuthorizationOptions:[NSNumber numberWithInteger:authorizationOptions]];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(handleNotificationSettings:)
+ name:pushPluginApplicationDidBecomeActiveNotification
+ object:nil];
+
+
+
+ // Read GoogleService-Info.plist
+ NSString *path = [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"];
+
+ // Load the file content and read the data into arrays
+ NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
+ fcmSenderId = [dict objectForKey:@"GCM_SENDER_ID"];
+ BOOL isGcmEnabled = [[dict valueForKey:@"IS_GCM_ENABLED"] boolValue];
+
+ NSLog(@"FCM Sender ID %@", fcmSenderId);
+
+ // GCM options
+ [self setFcmSenderId: fcmSenderId];
+ if(isGcmEnabled && [[self fcmSenderId] length] > 0) {
+ NSLog(@"Using FCM Notification");
+ [self setUsesFCM: YES];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if([FIRApp defaultApp] == nil)
+ [FIRApp configure];
+ [self initRegistration];
+ });
+ } else {
+ NSLog(@"Using APNS Notification");
+ [self setUsesFCM:NO];
+ }
+ id fcmSandboxArg = [iosOptions objectForKey:@"fcmSandbox"];
+
+ [self setFcmSandbox:@NO];
+ if ([self usesFCM] &&
+ (([fcmSandboxArg isKindOfClass:[NSString class]] && [fcmSandboxArg isEqualToString:@"true"]) ||
+ [fcmSandboxArg boolValue]))
+ {
+ NSLog(@"Using FCM Sandbox");
+ [self setFcmSandbox:@YES];
+ }
+
+ if (notificationMessage) { // if there is a pending startup notification
+ dispatch_async(dispatch_get_main_queue(), ^{
+ // delay to allow JS event handlers to be setup
+ [self performSelector:@selector(notificationReceived) withObject:nil afterDelay: 0.5];
+ });
+ }
+
+ }];
+ }
+}
+
+- (UNNotificationAction *)createAction:(NSDictionary *)dictionary {
+ NSString *identifier = [dictionary objectForKey:@"callback"];
+ NSString *title = [dictionary objectForKey:@"title"];
+ UNNotificationActionOptions options = UNNotificationActionOptionNone;
+
+ id mode = [dictionary objectForKey:@"foreground"];
+ if (mode != nil && (([mode isKindOfClass:[NSString class]] && [mode isEqualToString:@"true"]) || [mode boolValue])) {
+ options |= UNNotificationActionOptionForeground;
+ }
+ id destructive = [dictionary objectForKey:@"destructive"];
+ if (destructive != nil && (([destructive isKindOfClass:[NSString class]] && [destructive isEqualToString:@"true"]) || [destructive boolValue])) {
+ options |= UNNotificationActionOptionDestructive;
+ }
+
+ return [UNNotificationAction actionWithIdentifier:identifier title:title options:options];
+}
+
+- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
+ if (self.callbackId == nil) {
+ NSLog(@"Unexpected call to didRegisterForRemoteNotificationsWithDeviceToken, ignoring: %@", deviceToken);
+ return;
+ }
+ NSLog(@"Push Plugin register success: %@", deviceToken);
+
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
+ // [deviceToken description] is like "{length = 32, bytes = 0xd3d997af 967d1f43 b405374a 13394d2f ... 28f10282 14af515f }"
+ NSString *token = [self hexadecimalStringFromData:deviceToken];
+#else
+ // [deviceToken description] is like "<124686a5 556a72ca d808f572 00c323b9 3eff9285 92445590 3225757d b83967be>"
+ NSString *token = [[[[deviceToken description] stringByReplacingOccurrencesOfString:@"<"withString:@""]
+ stringByReplacingOccurrencesOfString:@">" withString:@""]
+ stringByReplacingOccurrencesOfString: @" " withString: @""];
+#endif
+
+#if !TARGET_IPHONE_SIMULATOR
+ // Get FCM Token after APNS token initialized
+ [self initRegistration];
+ // Check what Notifications the user has turned on. We registered for all three, but they may have manually disabled some or all of them.
+
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ __weak PushPlugin *weakSelf = self;
+ [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
+
+ if(![weakSelf usesFCM]) {
+ [weakSelf registerWithToken: token];
+ }
+ }];
+
+
+#endif
+}
+
+- (NSString *)hexadecimalStringFromData:(NSData *)data
+{
+ NSUInteger dataLength = data.length;
+ if (dataLength == 0) {
+ return nil;
+ }
+
+ const unsigned char *dataBuffer = data.bytes;
+ NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
+ for (int i = 0; i < dataLength; ++i) {
+ [hexString appendFormat:@"%02x", dataBuffer[i]];
+ }
+ return [hexString copy];
+}
+
+- (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
+{
+ if (self.callbackId == nil) {
+ NSLog(@"Unexpected call to didFailToRegisterForRemoteNotificationsWithError, ignoring: %@", error);
+ return;
+ }
+ NSLog(@"Push Plugin register failed");
+ [self failWithMessage:self.callbackId withMsg:@"" withError:error];
+}
+
+- (void)notificationReceived {
+ NSLog(@"Notification received");
+
+ if (notificationMessage && self.callbackId != nil)
+ {
+ NSMutableDictionary* message = [NSMutableDictionary dictionaryWithCapacity:4];
+ NSMutableDictionary* additionalData = [NSMutableDictionary dictionaryWithCapacity:4];
+
+
+ for (id key in notificationMessage) {
+ if ([key isEqualToString:@"aps"]) {
+ id aps = [notificationMessage objectForKey:@"aps"];
+
+ for(id key in aps) {
+ NSLog(@"Push Plugin key: %@", key);
+ id value = [aps objectForKey:key];
+
+ if ([key isEqualToString:@"alert"]) {
+ if ([value isKindOfClass:[NSDictionary class]]) {
+ for (id messageKey in value) {
+ id messageValue = [value objectForKey:messageKey];
+ if ([messageKey isEqualToString:@"body"]) {
+ [message setObject:messageValue forKey:@"message"];
+ } else if ([messageKey isEqualToString:@"title"]) {
+ [message setObject:messageValue forKey:@"title"];
+ } else {
+ [additionalData setObject:messageValue forKey:messageKey];
+ }
+ }
+ }
+ else {
+ [message setObject:value forKey:@"message"];
+ }
+ } else if ([key isEqualToString:@"title"]) {
+ [message setObject:value forKey:@"title"];
+ } else if ([key isEqualToString:@"badge"]) {
+ [message setObject:value forKey:@"count"];
+ } else if ([key isEqualToString:@"sound"]) {
+ [message setObject:value forKey:@"sound"];
+ } else if ([key isEqualToString:@"image"]) {
+ [message setObject:value forKey:@"image"];
+ } else {
+ [additionalData setObject:value forKey:key];
+ }
+ }
+ } else {
+ [additionalData setObject:[notificationMessage objectForKey:key] forKey:key];
+ }
+ }
+
+ if (isInline) {
+ [additionalData setObject:[NSNumber numberWithBool:YES] forKey:@"foreground"];
+ } else {
+ [additionalData setObject:[NSNumber numberWithBool:NO] forKey:@"foreground"];
+ }
+
+ if (coldstart) {
+ [additionalData setObject:[NSNumber numberWithBool:YES] forKey:@"coldstart"];
+ } else {
+ [additionalData setObject:[NSNumber numberWithBool:NO] forKey:@"coldstart"];
+ }
+
+ [message setObject:additionalData forKey:@"additionalData"];
+
+ // send notification message
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:message];
+ [pluginResult setKeepCallbackAsBool:YES];
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
+
+ self.coldstart = NO;
+ self.notificationMessage = nil;
+ }
+}
+
+- (void)clearNotification:(CDVInvokedUrlCommand *)command
+{
+ NSNumber *notId = [command.arguments objectAtIndex:0];
+ [[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler:^(NSArray * _Nonnull notifications) {
+ /*
+ * If the server generates a unique "notId" for every push notification, there should only be one match in these arrays, but if not, it will delete
+ * all notifications with the same value for "notId"
+ */
+ NSPredicate *matchingNotificationPredicate = [NSPredicate predicateWithFormat:@"request.content.userInfo.notId == %@", notId];
+ NSArray *matchingNotifications = [notifications filteredArrayUsingPredicate:matchingNotificationPredicate];
+ NSMutableArray *matchingNotificationIdentifiers = [NSMutableArray array];
+ for (UNNotification *notification in matchingNotifications) {
+ [matchingNotificationIdentifiers addObject:notification.request.identifier];
+ }
+ [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:matchingNotificationIdentifiers];
+
+ NSString *message = [NSString stringWithFormat:@"Cleared notification with ID: %@", notId];
+ CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message];
+ [self.commandDelegate sendPluginResult:commandResult callbackId:command.callbackId];
+ }];
+}
+
+- (void)setApplicationIconBadgeNumber:(CDVInvokedUrlCommand *)command
+{
+ NSMutableDictionary* options = [command.arguments objectAtIndex:0];
+ int badge = [[options objectForKey:@"badge"] intValue] ?: 0;
+
+ [[UIApplication sharedApplication] setApplicationIconBadgeNumber:badge];
+
+ NSString* message = [NSString stringWithFormat:@"app badge count set to %d", badge];
+ CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message];
+ [self.commandDelegate sendPluginResult:commandResult callbackId:command.callbackId];
+}
+
+- (void)getApplicationIconBadgeNumber:(CDVInvokedUrlCommand *)command
+{
+ NSInteger badge = [UIApplication sharedApplication].applicationIconBadgeNumber;
+
+ CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(int)badge];
+ [self.commandDelegate sendPluginResult:commandResult callbackId:command.callbackId];
+}
+
+- (void)clearAllNotifications:(CDVInvokedUrlCommand *)command
+{
+ [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
+
+ NSString* message = [NSString stringWithFormat:@"cleared all notifications"];
+ CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message];
+ [self.commandDelegate sendPluginResult:commandResult callbackId:command.callbackId];
+}
+
+- (void)hasPermission:(CDVInvokedUrlCommand *)command
+{
+ id appDelegate = [UIApplication sharedApplication].delegate;
+ if ([appDelegate respondsToSelector:@selector(checkUserHasRemoteNotificationsEnabledWithCompletionHandler:)]) {
+ [appDelegate performSelector:@selector(checkUserHasRemoteNotificationsEnabledWithCompletionHandler:) withObject:^(BOOL isEnabled) {
+ NSMutableDictionary* message = [NSMutableDictionary dictionaryWithCapacity:1];
+ [message setObject:[NSNumber numberWithBool:isEnabled] forKey:@"isEnabled"];
+ CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:message];
+ [self.commandDelegate sendPluginResult:commandResult callbackId:command.callbackId];
+ }];
+ }
+}
+
+-(void)successWithMessage:(NSString *)myCallbackId withMsg:(NSString *)message
+{
+ if (myCallbackId != nil)
+ {
+ CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message];
+ [self.commandDelegate sendPluginResult:commandResult callbackId:myCallbackId];
+ }
+}
+
+-(void)registerWithToken:(NSString*)token; {
+ // Send result to trigger 'registration' event but keep callback
+ NSMutableDictionary* message = [NSMutableDictionary dictionaryWithCapacity:2];
+ [message setObject:token forKey:@"registrationId"];
+ if ([self usesFCM]) {
+ [message setObject:@"FCM" forKey:@"registrationType"];
+ } else {
+ [message setObject:@"APNS" forKey:@"registrationType"];
+ }
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:message];
+ [pluginResult setKeepCallbackAsBool:YES];
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
+}
+
+
+-(void)failWithMessage:(NSString *)myCallbackId withMsg:(NSString *)message withError:(NSError *)error
+{
+ NSString *errorMessage = (error) ? [NSString stringWithFormat:@"%@ - %@", message, [error localizedDescription]] : message;
+ CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage];
+
+ [self.commandDelegate sendPluginResult:commandResult callbackId:myCallbackId];
+}
+
+-(void) finish:(CDVInvokedUrlCommand*)command
+{
+ NSLog(@"Push Plugin finish called");
+
+ [self.commandDelegate runInBackground:^ {
+ NSString* notId = [command.arguments objectAtIndex:0];
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSTimer scheduledTimerWithTimeInterval:0.1
+ target:self
+ selector:@selector(stopBackgroundTask:)
+ userInfo:notId
+ repeats:NO];
+ });
+
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+ }];
+}
+
+-(void)stopBackgroundTask:(NSTimer*)timer
+{
+ UIApplication *app = [UIApplication sharedApplication];
+
+ NSLog(@"Push Plugin stopBackgroundTask called");
+
+ if (handlerObj) {
+ NSLog(@"Push Plugin handlerObj");
+ completionHandler = [handlerObj[[timer userInfo]] copy];
+ if (completionHandler) {
+ NSLog(@"Push Plugin: stopBackgroundTask (remaining t: %f)", app.backgroundTimeRemaining);
+ completionHandler(UIBackgroundFetchResultNewData);
+ completionHandler = nil;
+ }
+ }
+}
+
+
+- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type
+{
+ if([credentials.token length] == 0) {
+ NSLog(@"VoIPPush Plugin register error - No device token:");
+ return;
+ }
+
+ NSLog(@"VoIPPush Plugin register success");
+ const unsigned *tokenBytes = [credentials.token bytes];
+ NSString *sToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
+ ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
+ ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
+ ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
+
+ [self registerWithToken:sToken];
+}
+
+- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type
+{
+ NSLog(@"VoIP Notification received");
+ self.notificationMessage = payload.dictionaryPayload;
+ [self notificationReceived];
+}
+
+- (void)handleNotificationSettings:(NSNotification *)notification
+{
+ [self handleNotificationSettingsWithAuthorizationOptions:nil];
+}
+
+- (void)handleNotificationSettingsWithAuthorizationOptions:(NSNumber *)authorizationOptionsObject
+{
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ UNAuthorizationOptions authorizationOptions = [authorizationOptionsObject unsignedIntegerValue];
+
+ __weak UNUserNotificationCenter *weakCenter = center;
+ [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
+
+ switch (settings.authorizationStatus) {
+ case UNAuthorizationStatusNotDetermined:
+ {
+ [weakCenter requestAuthorizationWithOptions:authorizationOptions completionHandler:^(BOOL granted, NSError * _Nullable error) {
+ if (granted) {
+ [self performSelectorOnMainThread:@selector(registerForRemoteNotifications)
+ withObject:nil
+ waitUntilDone:NO];
+ }
+ }];
+ break;
+ }
+ case UNAuthorizationStatusAuthorized:
+ {
+ [self performSelectorOnMainThread:@selector(registerForRemoteNotifications)
+ withObject:nil
+ waitUntilDone:NO];
+ break;
+ }
+ case UNAuthorizationStatusDenied:
+ default:
+ break;
+ }
+ }];
+}
+
+- (void)registerForRemoteNotifications
+{
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
+}
+
+@end
diff --git a/src/js/push.js b/src/js/push.js
new file mode 100644
index 0000000000000000000000000000000000000000..3abec0b8148fe5151bed1897b80867960881e2f2
--- /dev/null
+++ b/src/js/push.js
@@ -0,0 +1,346 @@
+/*!
+ * Module dependencies.
+ */
+
+const exec = cordova.require('cordova/exec');
+
+class PushNotification {
+ /**
+ * PushNotification constructor.
+ *
+ * @param {Object} options to initiate Push Notifications.
+ * @return {PushNotification} instance that can be monitored and cancelled.
+ */
+ constructor (options) {
+ this.handlers = {
+ registration: [],
+ notification: [],
+ error: []
+ };
+
+ // require options parameter
+ if (typeof options === 'undefined') {
+ throw new Error('The options argument is required.');
+ }
+
+ // store the options to this object instance
+ this.options = options;
+
+ // triggered on registration and notification
+ const success = (result) => {
+ if (result && typeof result.registrationId !== 'undefined') {
+ this.emit('registration', result);
+ } else if (
+ result &&
+ result.additionalData &&
+ typeof result.additionalData.actionCallback !== 'undefined'
+ ) {
+ _this.emit('notification', result);
+ } else if (result) {
+ this.emit('notification', result);
+ }
+ };
+
+ // triggered on error
+ const fail = (msg) => {
+ const e = typeof msg === 'string' ? new Error(msg) : msg;
+ this.emit('error', e);
+ };
+
+ // wait at least one process tick to allow event subscriptions
+ setTimeout(() => {
+ exec(success, fail, 'PushNotification', 'init', [options]);
+ }, 10);
+ }
+
+ /**
+ * Unregister from push notifications
+ */
+ unregister (successCallback, errorCallback = () => {}, options) {
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.unregister failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log(
+ 'PushNotification.unregister failure: success callback parameter must be a function'
+ );
+ return;
+ }
+
+ const cleanHandlersAndPassThrough = () => {
+ if (!options) {
+ this.handlers = {
+ registration: [],
+ notification: [],
+ error: []
+ };
+ }
+ successCallback();
+ };
+
+ exec(cleanHandlersAndPassThrough, errorCallback, 'PushNotification', 'unregister', [options]);
+ }
+
+ /**
+ * subscribe to a topic
+ * @param {String} topic topic to subscribe
+ * @param {Function} successCallback success callback
+ * @param {Function} errorCallback error callback
+ * @return {void}
+ */
+ subscribe (topic, successCallback, errorCallback = () => {}) {
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.subscribe failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log(
+ 'PushNotification.subscribe failure: success callback parameter must be a function'
+ );
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'subscribe', [topic]);
+ }
+
+ /**
+ * unsubscribe to a topic
+ * @param {String} topic topic to unsubscribe
+ * @param {Function} successCallback success callback
+ * @param {Function} errorCallback error callback
+ * @return {void}
+ */
+ unsubscribe (topic, successCallback, errorCallback = () => {}) {
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.unsubscribe failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log(
+ 'PushNotification.unsubscribe failure: success callback parameter must be a function'
+ );
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'unsubscribe', [topic]);
+ }
+
+ /**
+ * Call this to set the application icon badge
+ */
+ setApplicationIconBadgeNumber (successCallback, errorCallback = () => {}, badge) {
+ if (typeof errorCallback !== 'function') {
+ console.log(
+ 'PushNotification.setApplicationIconBadgeNumber failure: failure ' +
+ 'parameter not a function'
+ );
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log(
+ 'PushNotification.setApplicationIconBadgeNumber failure: success ' +
+ 'callback parameter must be a function'
+ );
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'setApplicationIconBadgeNumber', [
+ { badge }
+ ]);
+ }
+
+ /**
+ * Get the application icon badge
+ */
+
+ getApplicationIconBadgeNumber (successCallback, errorCallback = () => {}) {
+ if (typeof errorCallback !== 'function') {
+ console.log(
+ 'PushNotification.getApplicationIconBadgeNumber failure: failure ' +
+ 'parameter not a function'
+ );
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log(
+ 'PushNotification.getApplicationIconBadgeNumber failure: success ' +
+ 'callback parameter must be a function'
+ );
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'getApplicationIconBadgeNumber', []);
+ }
+
+ /**
+ * Clear all notifications
+ */
+
+ clearAllNotifications (successCallback = () => {}, errorCallback = () => {}) {
+ if (typeof errorCallback !== 'function') {
+ console.log(
+ 'PushNotification.clearAllNotifications failure: failure parameter not a function'
+ );
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log(
+ 'PushNotification.clearAllNotifications failure: success callback ' +
+ 'parameter must be a function'
+ );
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'clearAllNotifications', []);
+ }
+
+ /**
+ * Clears notifications that have the ID specified.
+ * @param {Function} [successCallback] Callback function to be called on success.
+ * @param {Function} [errorCallback] Callback function to be called when an error is encountered.
+ * @param {Number} id ID of the notification to be removed.
+ */
+ clearNotification (successCallback = () => {}, errorCallback = () => {}, id) {
+ const idNumber = parseInt(id, 10);
+ if (Number.isNaN(idNumber) || idNumber > Number.MAX_SAFE_INTEGER || idNumber < 0) {
+ console.log(
+ 'PushNotification.clearNotification failure: id parameter must' +
+ 'be a valid integer.'
+ );
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'clearNotification',
+ [idNumber]);
+ }
+
+ /**
+ * Listen for an event.
+ *
+ * The following events are supported:
+ *
+ * - registration
+ * - notification
+ * - error
+ *
+ * @param {String} eventName to subscribe to.
+ * @param {Function} callback triggered on the event.
+ */
+
+ on (eventName, callback) {
+ if (!Object.prototype.hasOwnProperty.call(this.handlers, eventName)) {
+ this.handlers[eventName] = [];
+ }
+ this.handlers[eventName].push(callback);
+ }
+
+ /**
+ * Remove event listener.
+ *
+ * @param {String} eventName to match subscription.
+ * @param {Function} handle function associated with event.
+ */
+
+ off (eventName, handle) {
+ if (Object.prototype.hasOwnProperty.call(this.handlers, eventName)) {
+ const handleIndex = this.handlers[eventName].indexOf(handle);
+ if (handleIndex >= 0) {
+ this.handlers[eventName].splice(handleIndex, 1);
+ }
+ }
+ }
+
+ /**
+ * Emit an event.
+ *
+ * This is intended for internal use only.
+ *
+ * @param {String} eventName is the event to trigger.
+ * @param {*} all arguments are passed to the event listeners.
+ *
+ * @return {Boolean} is true when the event is triggered otherwise false.
+ */
+
+ emit (...args) {
+ const eventName = args.shift();
+
+ if (!Object.prototype.hasOwnProperty.call(this.handlers, eventName)) {
+ return false;
+ }
+
+ for (let i = 0, { length } = this.handlers[eventName]; i < length; i += 1) {
+ const callback = this.handlers[eventName][i];
+ if (typeof callback === 'function') {
+ callback(...args); // eslint-disable-line node/no-callback-literal
+ } else {
+ console.log(`event handler: ${eventName} must be a function`);
+ }
+ }
+
+ return true;
+ }
+
+ finish (successCallback = () => {}, errorCallback = () => {}, id = 'handler') {
+ if (typeof successCallback !== 'function') {
+ console.log('finish failure: success callback parameter must be a function');
+ return;
+ }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('finish failure: failure parameter not a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'finish', [id]);
+ }
+}
+
+/*!
+ * Push Notification Plugin.
+ */
+
+module.exports = {
+ /**
+ * Register for Push Notifications.
+ *
+ * This method will instantiate a new copy of the PushNotification object
+ * and start the registration process.
+ *
+ * @param {Object} options
+ * @return {PushNotification} instance
+ */
+
+ init: (options) => new PushNotification(options),
+
+ hasPermission: (successCallback, errorCallback) => {
+ exec(successCallback, errorCallback, 'PushNotification', 'hasPermission', []);
+ },
+
+ createChannel: (successCallback, errorCallback, channel) => {
+ exec(successCallback, errorCallback, 'PushNotification', 'createChannel', [channel]);
+ },
+
+ deleteChannel: (successCallback, errorCallback, channelId) => {
+ exec(successCallback, errorCallback, 'PushNotification', 'deleteChannel', [channelId]);
+ },
+
+ listChannels: (successCallback, errorCallback) => {
+ exec(successCallback, errorCallback, 'PushNotification', 'listChannels', []);
+ },
+
+ /**
+ * PushNotification Object.
+ *
+ * Expose the PushNotification object for direct use
+ * and testing. Typically, you should use the
+ * .init helper method.
+ */
+ PushNotification
+};
diff --git a/src/windows/PushPluginProxy.js b/src/windows/PushPluginProxy.js
new file mode 100644
index 0000000000000000000000000000000000000000..bc922790b68a8bb58650c05aa2b7d9094936611d
--- /dev/null
+++ b/src/windows/PushPluginProxy.js
@@ -0,0 +1,101 @@
+var myApp = {};
+var pushNotifications = Windows.Networking.PushNotifications;
+
+var createNotificationJSON = function (e) {
+ var result = { message: '' }; // Added to identify callback as notification type in the API in case where notification has no message
+ var notificationPayload;
+
+ switch (e.notificationType) {
+ case pushNotifications.PushNotificationType.toast:
+ case pushNotifications.PushNotificationType.tile:
+ if (e.notificationType === pushNotifications.PushNotificationType.toast) {
+ notificationPayload = e.toastNotification.content;
+ } else {
+ notificationPayload = e.tileNotification.content;
+ }
+ var texts = notificationPayload.getElementsByTagName('text');
+ if (texts.length > 1) {
+ result.title = texts[0].innerText;
+ result.message = texts[1].innerText;
+ } else if (texts.length === 1) {
+ result.message = texts[0].innerText;
+ }
+ var images = notificationPayload.getElementsByTagName('image');
+ if (images.length > 0) {
+ result.image = images[0].getAttribute('src');
+ }
+ var soundFile = notificationPayload.getElementsByTagName('audio');
+ if (soundFile.length > 0) {
+ result.sound = soundFile[0].getAttribute('src');
+ }
+ break;
+
+ case pushNotifications.PushNotificationType.badge:
+ notificationPayload = e.badgeNotification.content;
+ result.count = notificationPayload.getElementsByTagName('badge')[0].getAttribute('value');
+ break;
+
+ case pushNotifications.PushNotificationType.raw:
+ result.message = e.rawNotification.content;
+ break;
+ }
+
+ result.additionalData = { coldstart: false }; // this gets called only when the app is running
+ result.additionalData.pushNotificationReceivedEventArgs = e;
+ return result;
+};
+
+module.exports = {
+ init: function (onSuccess, onFail, args) {
+ var onNotificationReceived = function (e) {
+ var result = createNotificationJSON(e);
+ onSuccess(result, { keepCallback: true });
+ };
+
+ try {
+ pushNotifications.PushNotificationChannelManager.createPushNotificationChannelForApplicationAsync().done(
+ function (channel) {
+ var result = {};
+ result.registrationId = channel.uri;
+ myApp.channel = channel;
+ channel.addEventListener('pushnotificationreceived', onNotificationReceived);
+ myApp.notificationEvent = onNotificationReceived;
+ onSuccess(result, { keepCallback: true });
+
+ var context = cordova.require('cordova/platform').activationContext;
+ var launchArgs = context ? (context.argument || context.args) : null;
+ if (launchArgs) { // If present, app launched through push notification
+ result = { message: '' }; // Added to identify callback as notification type in the API
+ result.launchArgs = launchArgs;
+ result.additionalData = { coldstart: true };
+ onSuccess(result, { keepCallback: true });
+ }
+ }, function (error) {
+ onFail(error);
+ });
+ } catch (ex) {
+ onFail(ex);
+ }
+ },
+ unregister: function (onSuccess, onFail, args) {
+ try {
+ myApp.channel.removeEventListener('pushnotificationreceived', myApp.notificationEvent);
+ myApp.channel.close();
+ onSuccess();
+ } catch (ex) {
+ onFail(ex);
+ }
+ },
+ hasPermission: function (onSuccess) {
+ var notifier = Windows.UI.Notifications.ToastNotificationManager.createToastNotifier();
+
+ onSuccess({ isEnabled: !notifier.setting });
+ },
+ subscribe: function () {
+ console.log('Subscribe is unsupported');
+ },
+ unsubscribe: function () {
+ console.log('Subscribe is unsupported');
+ }
+};
+require('cordova/exec/proxy').add('PushNotification', module.exports);
diff --git a/types/index.d.ts b/types/index.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0c93dffcc7ee40e6f6ac0bdefd2a32e9b323861a
--- /dev/null
+++ b/types/index.d.ts
@@ -0,0 +1,386 @@
+// Type definitions for phonegap-plugin-push
+// Project: https://github.com/havesource/cordova-plugin-push
+// Definitions by: Frederico Galvão
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+
+declare namespace PhonegapPluginPush {
+ type EventResponse = RegistrationEventResponse | NotificationEventResponse | Error
+
+ interface PushNotification {
+ /**
+ * The event registration will be triggered on each successful registration with the 3rd party push service.
+ * @param event
+ * @param callback
+ */
+ on(event: "registration", callback: (response: RegistrationEventResponse) => any): void
+ /**
+ * The event notification will be triggered each time a push notification is received by a 3rd party push service on the device.
+ * @param event
+ * @param callback
+ */
+ on(event: "notification", callback: (response: NotificationEventResponse) => any): void
+ /**
+ * The event error will trigger when an internal error occurs and the cache is aborted.
+ * @param event
+ * @param callback
+ */
+ on(event: "error", callback: (response: Error) => any): void
+ /**
+ *
+ * @param event Name of the event to listen to. See below(above) for all the event names.
+ * @param callback is called when the event is triggered.
+ * @param event
+ * @param callback
+ */
+ on(event: string, callback: (response: EventResponse) => any): void
+
+ off(event: "registration", callback: (response: RegistrationEventResponse) => any): void
+ off(event: "notification", callback: (response: NotificationEventResponse) => any): void
+ off(event: "error", callback: (response: Error) => any): void
+ /**
+ * As stated in the example, you will have to store your event handler if you are planning to remove it.
+ * @param event Name of the event type. The possible event names are the same as for the push.on function.
+ * @param callback handle to the function to get removed.
+ * @param event
+ * @param callback
+ */
+ off(event: string, callback: (response: EventResponse) => any): void
+
+ /**
+ * The unregister method is used when the application no longer wants to receive push notifications.
+ * Beware that this cleans up all event handlers previously registered,
+ * so you will need to re-register them if you want them to function again without an application reload.
+ * @param successHandler
+ * @param errorHandler
+ * @param topics
+ */
+ unregister(successHandler: () => any, errorHandler?: () => any, topics?: string[]): void
+
+ /**
+ * The subscribe method is used when the application wants to subscribe a new topic to receive push notifications.
+ * @param topic Topic to subscribe to.
+ * @param successHandler Is called when the api successfully unregisters.
+ * @param errorHandler Is called when the api encounters an error while unregistering.
+ */
+ subscribe(topic: string, successHandler: () => any, errorHandler: () => any): void;
+
+ /**
+ * The unsubscribe method is used when the application no longer wants to receive push notifications
+ * from a specific topic but continue to receive other push messages.
+ * @param topic Topic to unsubscribe from.
+ * @param successHandler Is called when the api successfully unregisters.
+ * @param errorHandler Is called when the api encounters an error while unregistering.
+ */
+ unsubscribe(topic: string, successHandler: () => any, errorHandler: () => any): void;
+
+ /*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...)*/
+ /**
+ * Set the badge count visible when the app is not running
+ *
+ * The count is an integer indicating what number should show up in the badge.
+ * Passing 0 will clear the badge.
+ * Each notification event contains a data.count value which can be used to set the badge to correct number.
+ * @param successHandler
+ * @param errorHandler
+ * @param count
+ */
+ setApplicationIconBadgeNumber(successHandler: () => any, errorHandler: () => any, count: number): void
+
+ /**
+ * Get the current badge count visible when the app is not running
+ * successHandler gets called with an integer which is the current badge count
+ * @param successHandler
+ * @param errorHandler
+ */
+ getApplicationIconBadgeNumber(successHandler: (count: number) => any, errorHandler: () => any): void
+
+ /**
+ * iOS only
+ * Tells the OS that you are done processing a background push notification.
+ * successHandler gets called when background push processing is successfully completed.
+ * @param successHandler
+ * @param errorHandler
+ * @param id
+ */
+ finish(successHandler?: () => any, errorHandler?: () => any, id?: string): void
+
+ /**
+ * Tells the OS to clear all notifications from the Notification Center
+ * @param successHandler Is called when the api successfully clears the notifications.
+ * @param errorHandler Is called when the api encounters an error when attempting to clears the notifications.
+ */
+ clearAllNotifications(successHandler: () => any, errorHandler: () => any): void
+ }
+
+ /**
+ * platform specific initialization options.
+ */
+ interface InitOptions {
+ /**
+ * Android specific initialization options.
+ */
+ android?: {
+ /**
+ * The name of a drawable resource to use as the small-icon. The name should not include the extension.
+ */
+ icon?: string
+ /**
+ * Sets the background color of the small icon on Android 5.0 and greater.
+ * Supported Formats - http://developer.android.com/reference/android/graphics/Color.html#parseColor(java.lang.String)
+ */
+ iconColor?: string
+ /**
+ * If true it plays the sound specified in the push data or the default system sound. Default is true.
+ */
+ sound?: boolean
+ /**
+ * If true the device vibrates on receipt of notification. Default is true.
+ */
+ vibrate?: boolean
+ /**
+ * If true the icon badge will be cleared on init and before push messages are processed. Default is false.
+ */
+ clearBadge?: boolean
+ /**
+ * If true the app clears all pending notifications when it is closed. Default is true.
+ */
+ clearNotifications?: boolean
+ /**
+ * If true will always show a notification, even when the app is on the foreground. Default is false.
+ */
+ forceShow?: boolean
+ /**
+ * If the array contains one or more strings each string will be used to subscribe to a GcmPubSub topic.
+ */
+ topics?: string[]
+ /**
+ * The key to search for text of notification. Default is 'message'.
+ */
+ messageKey?: string
+ /**
+ * The key to search for title of notification. Default is 'title'.
+ */
+ titleKey?: string
+ }
+
+ /**
+ * Browser specific initialization options.
+ */
+ browser?: {
+ /**
+ * URL for the push server you want to use. Default is 'http://push.api.phonegap.com/v1/push'.
+ */
+ pushServiceURL?: string
+ /**
+ * Your GCM API key if you are using VAPID keys.
+ */
+ applicationServerKey?: string
+ }
+
+ /**
+ * iOS specific initialization options.
+ */
+ ios?: {
+ /**
+ * If true|"true" the device will be set up to receive VoIP Push notifications and the other options will be ignored
+ * since VoIP notifications are silent notifications that should be handled in the "notification" event.
+ * Default is false|"false".
+ */
+ voip?: boolean | string
+ /**
+ * If true|"true" the device sets the badge number on receipt of notification.
+ * Default is false|"false".
+ * Note: the value you set this option to the first time you call the init method will be how the application always acts.
+ * Once this is set programmatically in the init method it can only be changed manually by the user in Settings>Notifications>App Name.
+ * This is normal iOS behaviour.
+ */
+ badge?: boolean | string
+ /**
+ * If true|"true" the device plays a sound on receipt of notification.
+ * Default is false|"false".
+ * Note: the value you set this option to the first time you call the init method will be how the application always acts.
+ * Once this is set programmatically in the init method it can only be changed manually by the user in Settings>Notifications>App Name.
+ * This is normal iOS behaviour.
+ */
+ sound?: boolean | string
+ /**
+ * If true|"true" the device shows an alert on receipt of notification.
+ * Default is false|"false".
+ * Note: the value you set this option to the first time you call the init method will be how the application always acts.
+ * Once this is set programmatically in the init method it can only be changed manually by the user in Settings>Notifications>App Name.
+ * This is normal iOS behaviour.
+ */
+ alert?: boolean | string
+ /**
+ * If true|"true" the badge will be cleared on app startup. Defaults to false|"false".
+ */
+ clearBadge?: boolean | string
+ /**
+ * The data required in order to enable Action Buttons for iOS.
+ * Action Buttons on iOS - https://github.com/havesource/cordova-plugin-push/blob/master/docs/PAYLOAD.md#action-buttons-1
+ */
+ categories?: CategoryArray
+ /**
+ * Whether to use prod or sandbox GCM setting. Defaults to false.
+ */
+ fcmSandbox?: boolean
+ /**
+ * If the array contains one or more strings each string will be used to subscribe to a FcmPubSub topic. Defaults to [].
+ */
+ topics?: string[]
+ }
+
+ /**
+ * Windows specific initialization options.
+ */
+ windows?: {
+
+ }
+ }
+
+ interface CategoryArray {
+ [name: string]: CategoryAction
+ }
+
+ interface CategoryAction {
+ yes?: CategoryActionData
+ no?: CategoryActionData
+ maybe?: CategoryActionData
+ }
+
+ interface CategoryActionData {
+ callback: string
+ title: string
+ foreground: boolean
+ destructive: boolean
+ }
+
+ interface RegistrationEventResponse {
+ /**
+ * The registration ID provided by the 3rd party remote push service.
+ */
+ registrationId: string
+ }
+
+ interface NotificationEventResponse {
+ /**
+ * The text of the push message sent from the 3rd party service.
+ */
+ message: string
+ /**
+ * The optional title of the push message sent from the 3rd party service.
+ */
+ title?: string
+ /**
+ * The number of messages to be displayed in the badge iOS or message count in the notification shade in Android.
+ * For windows, it represents the value in the badge notification which could be a number or a status glyph.
+ */
+ count: string
+ /**
+ * The name of the sound file to be played upon receipt of the notification.
+ */
+ sound: string
+ /**
+ * The path of the image file to be displayed in the notification.
+ */
+ image: string
+ /**
+ * An optional collection of data sent by the 3rd party push service that does not fit in the above properties.
+ */
+ additionalData: NotificationEventAdditionalData
+ }
+
+ /**
+ * TODO: document all possible properties (I only got the android ones)
+ *
+ * Loosened up with a dictionary notation, but all non-defined properties need to use (map['prop']) notation
+ *
+ * Ideally the developer would overload (merged declaration) this or create a new interface that would extend this one
+ * so that he could specify any custom code without having to use array notation (map['prop']) for all of them.
+ */
+ interface NotificationEventAdditionalData {
+ [name: string]: any
+
+ /**
+ * Whether the notification was received while the app was in the foreground
+ */
+ foreground?: boolean
+ /**
+ * Will be true if the application is started by clicking on the push notification, false if the app is already started. (Android/iOS only)
+ */
+ coldstart?: boolean
+ collapse_key?: string
+ from?: string
+ notId?: string
+ }
+
+ interface Channel {
+ /**
+ * The id of the channel. Must be unique per package. The value may be truncated if it is too long.
+ */
+ id: string;
+ /**
+ * The user visible name of the channel. The recommended maximum length is 40 characters; the value may be truncated if it is too long.
+ */
+ description: string;
+ /**
+ * The importance of the channel. This controls how interruptive notifications posted to this channel are. The importance property goes from 1 = Lowest, 2 = Low, 3 = Normal, 4 = High and 5 = Highest.
+ */
+ importance: number;
+ /**
+ * The name of the sound file to be played upon receipt of the notification in this channel. Empty string to disable sound. Cannot be changed after channel is created.
+ */
+ sound?: string;
+ /**
+ * Boolean sets whether notification posted to this channel should vibrate. Array sets custom vibration pattern. Example - vibration: [2000, 1000, 500, 500]. Cannot be changed after channel is created.
+ */
+ vibration?: boolean|number[];
+ /**
+ * Sets whether notifications posted to this channel appear on the lockscreen or not, and if so, whether they appear in a redacted form. 0 = Private, 1 = Public, -1 = Secret.
+ */
+ visibility?: number;
+ }
+
+ interface PushNotificationStatic {
+ new (options: InitOptions): PushNotification
+ /**
+ * Initializes the plugin on the native side.
+ * @param options An object describing relevant specific options for all target platforms.
+ */
+ init(options: InitOptions): PushNotification
+ /**
+ * Checks whether the push notification permission has been granted.
+ * @param successCallback Is called when the api successfully retrieves the details on the permission.
+ * @param errorCallback Is called when the api fails to retrieve the details on the permission.
+ */
+ hasPermission(successCallback: (data: {isEnabled: boolean}) => void, errorCallback: () => void): void;
+ /**
+ * Android only
+ * Create a new notification channel for Android O and above.
+ * @param successCallback Is called when the api successfully creates a channel.
+ * @param errorCallback Is called when the api fails to create a channel.
+ * @param channel The options for the channel.
+ */
+ createChannel(successCallback: () => void, errorCallback: () => void, channel: Channel): void;
+ /**
+ * Android only
+ * Delete a notification channel for Android O and above.
+ * @param successCallback Is called when the api successfully deletes a channel.
+ * @param errorCallback Is called when the api fails to create a channel.
+ * @param channelId The ID of the channel.
+ */
+ deleteChannel(successCallback: () => void, errorCallback: () => void, channelId: string): void;
+ /**
+ * Android only
+ * Returns a list of currently configured channels.
+ * @param successCallback Is called when the api successfully retrieves the list of channels.
+ * @param errorCallback Is called when the api fails to retrieve the list of channels.
+ */
+ listChannels(successCallback: (channels: Channel[]) => void, errorCallback: () => void): void;
+ }
+}
+
+interface Window {
+ PushNotification: PhonegapPluginPush.PushNotificationStatic
+}
+declare var PushNotification: PhonegapPluginPush.PushNotificationStatic;
diff --git a/www/browser/push.js b/www/browser/push.js
new file mode 100644
index 0000000000000000000000000000000000000000..3fcfd8ddfcb3d99f3a9cfb76c1101adc18fd23a2
--- /dev/null
+++ b/www/browser/push.js
@@ -0,0 +1,384 @@
+/*!
+ * Module dependencies.
+ */
+cordova.require('cordova/exec');
+
+/**
+ * PushNotification constructor.
+ *
+ * @param {Object} options to initiate Push Notifications.
+ * @return {PushNotification} instance that can be monitored and cancelled.
+ */
+var serviceWorker, subscription;
+var PushNotification = function (options) {
+ this._handlers = {
+ registration: [],
+ notification: [],
+ error: []
+ };
+
+ // require options parameter
+ if (typeof options === 'undefined') {
+ throw new Error('The options argument is required.');
+ }
+
+ // store the options to this object instance
+ this.options = options;
+
+ // subscription options
+ var subOptions = { userVisibleOnly: true };
+ if (this.options.browser && this.options.browser.applicationServerKey) {
+ subOptions.applicationServerKey = urlBase64ToUint8Array(this.options.browser.applicationServerKey);
+ }
+
+ // triggered on registration and notification
+ var that = this;
+
+ // Add manifest.json to main HTML file
+ var linkElement = document.createElement('link');
+ linkElement.rel = 'manifest';
+ linkElement.href = 'manifest.json';
+ document.getElementsByTagName('head')[0].appendChild(linkElement);
+
+ if ('serviceWorker' in navigator && 'MessageChannel' in window) {
+ var result;
+ var channel = new MessageChannel();
+ channel.port1.onmessage = function (event) {
+ that.emit('notification', event.data);
+ };
+
+ navigator.serviceWorker.register('ServiceWorker.js').then(function () {
+ return navigator.serviceWorker.ready;
+ })
+ .then(function (reg) {
+ serviceWorker = reg;
+ reg.pushManager.subscribe(subOptions).then(function (sub) {
+ subscription = sub;
+ result = { registrationId: sub.endpoint.substring(sub.endpoint.lastIndexOf('/') + 1) };
+ that.emit('registration', result);
+
+ // send encryption keys to push server
+ var xmlHttp = new XMLHttpRequest();
+ var xmlURL = (options.browser.pushServiceURL || 'http://push.api.phonegap.com/v1/push') + '/keys';
+ xmlHttp.open('POST', xmlURL, true);
+
+ var formData = new FormData();
+ formData.append('subscription', JSON.stringify(sub));
+
+ xmlHttp.send(formData);
+
+ navigator.serviceWorker.controller.postMessage(result, [channel.port2]);
+ }).catch(function () {
+ if (navigator.serviceWorker.controller === null) {
+ // When you first register a SW, need a page reload to handle network operations
+ window.location.reload();
+ return;
+ }
+
+ throw new Error('Error subscribing for Push notifications.');
+ });
+ }).catch(function (error) {
+ console.log(error);
+ throw new Error('Error registering Service Worker');
+ });
+ } else {
+ throw new Error('Service Workers are not supported on your browser.');
+ }
+};
+
+/**
+ * Unregister from push notifications
+ */
+
+PushNotification.prototype.unregister = function (successCallback, errorCallback, options) {
+ if (!errorCallback) { errorCallback = function () {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.unregister failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.unregister failure: success callback parameter must be a function');
+ return;
+ }
+
+ var that = this;
+ if (!options) {
+ that._handlers = {
+ registration: [],
+ notification: [],
+ error: []
+ };
+ }
+
+ if (serviceWorker) {
+ serviceWorker.unregister().then(function (isSuccess) {
+ if (isSuccess) {
+ var deviceID = subscription.endpoint.substring(subscription.endpoint.lastIndexOf('/') + 1);
+ var xmlHttp = new XMLHttpRequest();
+ var xmlURL = (that.options.browser.pushServiceURL || 'http://push.api.phonegap.com/v1/push') +
+ '/keys/' + deviceID;
+ xmlHttp.open('DELETE', xmlURL, true);
+ xmlHttp.send();
+
+ successCallback();
+ } else {
+ errorCallback();
+ }
+ });
+ }
+};
+
+/**
+ * subscribe to a topic
+ * @param {String} topic topic to subscribe
+ * @param {Function} successCallback success callback
+ * @param {Function} errorCallback error callback
+ * @return {void}
+ */
+PushNotification.prototype.subscribe = function (topic, successCallback, errorCallback) {
+ if (!errorCallback) { errorCallback = function () {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.subscribe failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.subscribe failure: success callback parameter must be a function');
+ return;
+ }
+
+ successCallback();
+};
+
+/**
+ * unsubscribe to a topic
+ * @param {String} topic topic to unsubscribe
+ * @param {Function} successCallback success callback
+ * @param {Function} errorCallback error callback
+ * @return {void}
+ */
+PushNotification.prototype.unsubscribe = function (topic, successCallback, errorCallback) {
+ if (!errorCallback) { errorCallback = function () {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.unsubscribe failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.unsubscribe failure: success callback parameter must be a function');
+ return;
+ }
+
+ successCallback();
+};
+
+/**
+ * Call this to set the application icon badge
+ */
+
+PushNotification.prototype.setApplicationIconBadgeNumber = function (successCallback, errorCallback, badge) {
+ if (!errorCallback) { errorCallback = function () {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.setApplicationIconBadgeNumber failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.setApplicationIconBadgeNumber failure: success callback parameter must be a function');
+ return;
+ }
+
+ successCallback();
+};
+
+/**
+ * Get the application icon badge
+ */
+
+PushNotification.prototype.getApplicationIconBadgeNumber = function (successCallback, errorCallback) {
+ if (!errorCallback) { errorCallback = function () {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.getApplicationIconBadgeNumber failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.getApplicationIconBadgeNumber failure: success callback parameter must be a function');
+ return;
+ }
+
+ successCallback();
+};
+
+/**
+ * Get the application icon badge
+ */
+
+PushNotification.prototype.clearAllNotifications = function (successCallback, errorCallback) {
+ if (!errorCallback) { errorCallback = function () {}; }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.clearAllNotifications failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.clearAllNotifications failure: success callback parameter must be a function');
+ return;
+ }
+
+ successCallback();
+};
+
+/**
+ * Listen for an event.
+ *
+ * The following events are supported:
+ *
+ * - registration
+ * - notification
+ * - error
+ *
+ * @param {String} eventName to subscribe to.
+ * @param {Function} callback triggered on the event.
+ */
+
+PushNotification.prototype.on = function (eventName, callback) {
+ if (Object.prototype.hasOwnProperty.call(this._handlers, eventName)) {
+ this._handlers[eventName].push(callback);
+ }
+};
+
+/**
+ * Remove event listener.
+ *
+ * @param {String} eventName to match subscription.
+ * @param {Function} handle function associated with event.
+ */
+
+PushNotification.prototype.off = function (eventName, handle) {
+ if (Object.prototype.hasOwnProperty.call(this._handlers, eventName)) {
+ var handleIndex = this._handlers[eventName].indexOf(handle);
+ if (handleIndex >= 0) {
+ this._handlers[eventName].splice(handleIndex, 1);
+ }
+ }
+};
+
+/**
+ * Emit an event.
+ *
+ * This is intended for internal use only.
+ *
+ * @param {String} eventName is the event to trigger.
+ * @param {*} all arguments are passed to the event listeners.
+ *
+ * @return {Boolean} is true when the event is triggered otherwise false.
+ */
+
+PushNotification.prototype.emit = function () {
+ var args = Array.prototype.slice.call(arguments);
+ var eventName = args.shift();
+
+ if (!Object.prototype.hasOwnProperty.call(this._handlers, eventName)) {
+ return false;
+ }
+
+ for (var i = 0, length = this._handlers[eventName].length; i < length; i++) {
+ var callback = this._handlers[eventName][i];
+ if (typeof callback === 'function') {
+ callback.apply(undefined, args);
+ } else {
+ console.log('event handler: ' + eventName + ' must be a function');
+ }
+ }
+
+ return true;
+};
+
+PushNotification.prototype.finish = function (successCallback, errorCallback, id) {
+ if (!successCallback) { successCallback = function () {}; }
+ if (!errorCallback) { errorCallback = function () {}; }
+ if (!id) { id = 'handler'; }
+
+ if (typeof successCallback !== 'function') {
+ console.log('finish failure: success callback parameter must be a function');
+ return;
+ }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('finish failure: failure parameter not a function');
+ return;
+ }
+
+ successCallback();
+};
+
+/*!
+ * Push Notification Plugin.
+ */
+
+/**
+ * Converts the server key to an Uint8Array
+ *
+ * @param base64String
+ *
+ * @returns {Uint8Array}
+ */
+function urlBase64ToUint8Array (base64String) {
+ const padding = '='.repeat((4 - base64String.length % 4) % 4);
+ const base64 = (base64String + padding)
+ .replace(/-/g, '+')
+ .replace(/_/g, '/');
+
+ const rawData = window.atob(base64);
+ const outputArray = new Uint8Array(rawData.length);
+
+ for (var i = 0; i < rawData.length; ++i) {
+ outputArray[i] = rawData.charCodeAt(i);
+ }
+ return outputArray;
+}
+
+module.exports = {
+ /**
+ * Register for Push Notifications.
+ *
+ * This method will instantiate a new copy of the PushNotification object
+ * and start the registration process.
+ *
+ * @param {Object} options
+ * @return {PushNotification} instance
+ */
+
+ init: function (options) {
+ return new PushNotification(options);
+ },
+
+ hasPermission: function (successCallback, errorCallback) {
+ const granted = Notification && Notification.permission === 'granted';
+ successCallback({
+ isEnabled: granted
+ });
+ },
+
+ unregister: function (successCallback, errorCallback, options) {
+ PushNotification.unregister(successCallback, errorCallback, options);
+ },
+
+ /**
+ * PushNotification Object.
+ *
+ * Expose the PushNotification object for direct use
+ * and testing. Typically, you should use the
+ * .init helper method.
+ */
+
+ PushNotification: PushNotification
+};
diff --git a/www/push.js b/www/push.js
new file mode 100644
index 0000000000000000000000000000000000000000..c3419b49172582f1415191e061eb6bfba6e12060
--- /dev/null
+++ b/www/push.js
@@ -0,0 +1,382 @@
+/**
+* This file has been generated by Babel.
+*
+* DO NOT EDIT IT DIRECTLY
+*
+* Edit the JS source file src/js/push.js
+**/
+"use strict";
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+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); } }
+
+function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
+
+/*!
+ * Module dependencies.
+ */
+var exec = cordova.require('cordova/exec');
+
+var PushNotification = /*#__PURE__*/function () {
+ /**
+ * PushNotification constructor.
+ *
+ * @param {Object} options to initiate Push Notifications.
+ * @return {PushNotification} instance that can be monitored and cancelled.
+ */
+ function PushNotification(options) {
+ var _this = this;
+
+ _classCallCheck(this, PushNotification);
+
+ this.handlers = {
+ registration: [],
+ notification: [],
+ error: []
+ }; // require options parameter
+
+ if (typeof options === 'undefined') {
+ throw new Error('The options argument is required.');
+ } // store the options to this object instance
+
+
+ this.options = options; // triggered on registration and notification
+
+ var success = function success(result) {
+ if (result && typeof result.registrationId !== 'undefined') {
+ _this.emit('registration', result);
+ } else if (result && result.additionalData && typeof result.additionalData.actionCallback !== 'undefined') {
+ _this.emit('notification', result);
+ } else if (result) {
+ _this.emit('notification', result);
+ }
+ }; // triggered on error
+
+
+ var fail = function fail(msg) {
+ var e = typeof msg === 'string' ? new Error(msg) : msg;
+
+ _this.emit('error', e);
+ }; // wait at least one process tick to allow event subscriptions
+
+
+ setTimeout(function () {
+ exec(success, fail, 'PushNotification', 'init', [options]);
+ }, 10);
+ }
+ /**
+ * Unregister from push notifications
+ */
+
+
+ _createClass(PushNotification, [{
+ key: "unregister",
+ value: function unregister(successCallback) {
+ var _this2 = this;
+
+ var errorCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
+ var options = arguments.length > 2 ? arguments[2] : undefined;
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.unregister failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.unregister failure: success callback parameter must be a function');
+ return;
+ }
+
+ var cleanHandlersAndPassThrough = function cleanHandlersAndPassThrough() {
+ if (!options) {
+ _this2.handlers = {
+ registration: [],
+ notification: [],
+ error: []
+ };
+ }
+
+ successCallback();
+ };
+
+ exec(cleanHandlersAndPassThrough, errorCallback, 'PushNotification', 'unregister', [options]);
+ }
+ /**
+ * subscribe to a topic
+ * @param {String} topic topic to subscribe
+ * @param {Function} successCallback success callback
+ * @param {Function} errorCallback error callback
+ * @return {void}
+ */
+
+ }, {
+ key: "subscribe",
+ value: function subscribe(topic, successCallback) {
+ var errorCallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {};
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.subscribe failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.subscribe failure: success callback parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'subscribe', [topic]);
+ }
+ /**
+ * unsubscribe to a topic
+ * @param {String} topic topic to unsubscribe
+ * @param {Function} successCallback success callback
+ * @param {Function} errorCallback error callback
+ * @return {void}
+ */
+
+ }, {
+ key: "unsubscribe",
+ value: function unsubscribe(topic, successCallback) {
+ var errorCallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {};
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.unsubscribe failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.unsubscribe failure: success callback parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'unsubscribe', [topic]);
+ }
+ /**
+ * Call this to set the application icon badge
+ */
+
+ }, {
+ key: "setApplicationIconBadgeNumber",
+ value: function setApplicationIconBadgeNumber(successCallback) {
+ var errorCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
+ var badge = arguments.length > 2 ? arguments[2] : undefined;
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.setApplicationIconBadgeNumber failure: failure ' + 'parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.setApplicationIconBadgeNumber failure: success ' + 'callback parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'setApplicationIconBadgeNumber', [{
+ badge: badge
+ }]);
+ }
+ /**
+ * Get the application icon badge
+ */
+
+ }, {
+ key: "getApplicationIconBadgeNumber",
+ value: function getApplicationIconBadgeNumber(successCallback) {
+ var errorCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.getApplicationIconBadgeNumber failure: failure ' + 'parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.getApplicationIconBadgeNumber failure: success ' + 'callback parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'getApplicationIconBadgeNumber', []);
+ }
+ /**
+ * Clear all notifications
+ */
+
+ }, {
+ key: "clearAllNotifications",
+ value: function clearAllNotifications() {
+ var successCallback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {};
+ var errorCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
+
+ if (typeof errorCallback !== 'function') {
+ console.log('PushNotification.clearAllNotifications failure: failure parameter not a function');
+ return;
+ }
+
+ if (typeof successCallback !== 'function') {
+ console.log('PushNotification.clearAllNotifications failure: success callback ' + 'parameter must be a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'clearAllNotifications', []);
+ }
+ /**
+ * Clears notifications that have the ID specified.
+ * @param {Function} [successCallback] Callback function to be called on success.
+ * @param {Function} [errorCallback] Callback function to be called when an error is encountered.
+ * @param {Number} id ID of the notification to be removed.
+ */
+
+ }, {
+ key: "clearNotification",
+ value: function clearNotification() {
+ var successCallback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {};
+ var errorCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
+ var id = arguments.length > 2 ? arguments[2] : undefined;
+ var idNumber = parseInt(id, 10);
+
+ if (Number.isNaN(idNumber) || idNumber > Number.MAX_SAFE_INTEGER || idNumber < 0) {
+ console.log('PushNotification.clearNotification failure: id parameter must' + 'be a valid integer.');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'clearNotification', [idNumber]);
+ }
+ /**
+ * Listen for an event.
+ *
+ * The following events are supported:
+ *
+ * - registration
+ * - notification
+ * - error
+ *
+ * @param {String} eventName to subscribe to.
+ * @param {Function} callback triggered on the event.
+ */
+
+ }, {
+ key: "on",
+ value: function on(eventName, callback) {
+ if (!Object.prototype.hasOwnProperty.call(this.handlers, eventName)) {
+ this.handlers[eventName] = [];
+ }
+
+ this.handlers[eventName].push(callback);
+ }
+ /**
+ * Remove event listener.
+ *
+ * @param {String} eventName to match subscription.
+ * @param {Function} handle function associated with event.
+ */
+
+ }, {
+ key: "off",
+ value: function off(eventName, handle) {
+ if (Object.prototype.hasOwnProperty.call(this.handlers, eventName)) {
+ var handleIndex = this.handlers[eventName].indexOf(handle);
+
+ if (handleIndex >= 0) {
+ this.handlers[eventName].splice(handleIndex, 1);
+ }
+ }
+ }
+ /**
+ * Emit an event.
+ *
+ * This is intended for internal use only.
+ *
+ * @param {String} eventName is the event to trigger.
+ * @param {*} all arguments are passed to the event listeners.
+ *
+ * @return {Boolean} is true when the event is triggered otherwise false.
+ */
+
+ }, {
+ key: "emit",
+ value: function emit() {
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ var eventName = args.shift();
+
+ if (!Object.prototype.hasOwnProperty.call(this.handlers, eventName)) {
+ return false;
+ }
+
+ for (var i = 0, length = this.handlers[eventName].length; i < length; i += 1) {
+ var callback = this.handlers[eventName][i];
+
+ if (typeof callback === 'function') {
+ callback.apply(void 0, args); // eslint-disable-line node/no-callback-literal
+ } else {
+ console.log("event handler: ".concat(eventName, " must be a function"));
+ }
+ }
+
+ return true;
+ }
+ }, {
+ key: "finish",
+ value: function finish() {
+ var successCallback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {};
+ var errorCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
+ var id = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'handler';
+
+ if (typeof successCallback !== 'function') {
+ console.log('finish failure: success callback parameter must be a function');
+ return;
+ }
+
+ if (typeof errorCallback !== 'function') {
+ console.log('finish failure: failure parameter not a function');
+ return;
+ }
+
+ exec(successCallback, errorCallback, 'PushNotification', 'finish', [id]);
+ }
+ }]);
+
+ return PushNotification;
+}();
+/*!
+ * Push Notification Plugin.
+ */
+
+
+module.exports = {
+ /**
+ * Register for Push Notifications.
+ *
+ * This method will instantiate a new copy of the PushNotification object
+ * and start the registration process.
+ *
+ * @param {Object} options
+ * @return {PushNotification} instance
+ */
+ init: function init(options) {
+ return new PushNotification(options);
+ },
+ hasPermission: function hasPermission(successCallback, errorCallback) {
+ exec(successCallback, errorCallback, 'PushNotification', 'hasPermission', []);
+ },
+ createChannel: function createChannel(successCallback, errorCallback, channel) {
+ exec(successCallback, errorCallback, 'PushNotification', 'createChannel', [channel]);
+ },
+ deleteChannel: function deleteChannel(successCallback, errorCallback, channelId) {
+ exec(successCallback, errorCallback, 'PushNotification', 'deleteChannel', [channelId]);
+ },
+ listChannels: function listChannels(successCallback, errorCallback) {
+ exec(successCallback, errorCallback, 'PushNotification', 'listChannels', []);
+ },
+
+ /**
+ * PushNotification Object.
+ *
+ * Expose the PushNotification object for direct use
+ * and testing. Typically, you should use the
+ * .init helper method.
+ */
+ PushNotification: PushNotification
+};
\ No newline at end of file