├── .gitignore ├── Example ├── www │ ├── beep.wav │ ├── PushNotification.js │ └── index.html └── server │ ├── pushGCM.rb │ ├── pushAPNS.rb │ └── pushADM.js ├── src ├── android │ ├── libs │ │ └── gcm.jar │ └── com │ │ └── plugin │ │ └── gcm │ │ ├── CordovaGCMBroadcastReceiver.java │ │ ├── PushHandlerActivity.java │ │ ├── GCMIntentService.java │ │ └── PushPlugin.java ├── wp8 │ ├── Newtonsoft.Json.dll │ └── PushPlugin.cs ├── windows │ └── PushPluginProxy.js ├── ios │ ├── AppDelegate+notification.h │ ├── PushPlugin.h │ ├── AppDelegate+notification.m │ └── PushPlugin.m └── amazon │ ├── ADMHandlerActivity.java │ ├── ADMMessageHandler.java │ └── PushPlugin.java ├── spec ├── html │ ├── SuiteView.js │ ├── HtmlReporterHelpers.js │ ├── SpecView.js │ ├── HtmlReporter.js │ ├── ReporterView.js │ └── TrivialReporter.js ├── genericpush.tests.js ├── test-runner.js ├── index.html └── jasmine.css ├── MIT-LICENSE ├── www ├── PushNotification.js └── blackberry10 │ └── PushPluginProxy.js ├── plugin.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /Example/www/beep.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eajitesh/PushPlugin/master/Example/www/beep.wav -------------------------------------------------------------------------------- /src/android/libs/gcm.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eajitesh/PushPlugin/master/src/android/libs/gcm.jar -------------------------------------------------------------------------------- /src/wp8/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eajitesh/PushPlugin/master/src/wp8/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /Example/server/pushGCM.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'pushmeup' 3 | GCM.host = 'https://android.googleapis.com/gcm/send' 4 | GCM.format = :json 5 | GCM.key = "API_KEY_GOES_HERE" 6 | destination = ["REGISTRATION_ID_GOES_HERE"] 7 | data = {:message => "PhoneGap Build rocks!", :msgcnt => "1", :soundname => "beep.wav"} 8 | 9 | GCM.send_notification( destination, data) 10 | -------------------------------------------------------------------------------- /Example/server/pushAPNS.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'pushmeup' 3 | 4 | 5 | APNS.host = 'gateway.sandbox.push.apple.com' 6 | APNS.port = 2195 7 | APNS.pem = '' 8 | APNS.pass = '' 9 | 10 | device_token = '' 11 | # APNS.send_notification(device_token, 'Hello iPhone!' ) 12 | APNS.send_notification(device_token, :alert => 'PushPlugin works!!', :badge => 1, :sound => 'beep.wav') 13 | -------------------------------------------------------------------------------- /src/android/com/plugin/gcm/CordovaGCMBroadcastReceiver.java: -------------------------------------------------------------------------------- 1 | package com.plugin.gcm; 2 | 3 | import android.content.Context; 4 | 5 | import com.google.android.gcm.GCMBroadcastReceiver; 6 | import static com.google.android.gcm.GCMConstants.DEFAULT_INTENT_SERVICE_CLASS_NAME; 7 | 8 | /* 9 | * Implementation of GCMBroadcastReceiver that hard-wires the intent service to be 10 | * com.plugin.gcm.GCMIntentService, instead of your_package.GCMIntentService 11 | */ 12 | public class CordovaGCMBroadcastReceiver extends GCMBroadcastReceiver { 13 | 14 | @Override 15 | protected String getGCMIntentServiceClassName(Context context) { 16 | return "com.plugin.gcm" + DEFAULT_INTENT_SERVICE_CLASS_NAME; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/windows/PushPluginProxy.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Open Technologies, Inc. Licensed under the MIT license. 2 | module.exports = { 3 | register: function (success, fail, args) { 4 | try { 5 | var onNotificationReceived = window[args[0].ecb]; 6 | 7 | Windows.Networking.PushNotifications.PushNotificationChannelManager.createPushNotificationChannelForApplicationAsync().then( 8 | function (channel) { 9 | channel.addEventListener("pushnotificationreceived", onNotificationReceived); 10 | success(channel); 11 | }, fail); 12 | } catch(ex) { 13 | fail(ex); 14 | } 15 | } 16 | }; 17 | require("cordova/exec/proxy").add("PushPlugin", module.exports); 18 | -------------------------------------------------------------------------------- /spec/html/SuiteView.js: -------------------------------------------------------------------------------- 1 | jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { 2 | this.suite = suite; 3 | this.dom = dom; 4 | this.views = views; 5 | 6 | this.element = this.createDom('div', { className: 'suite' }, 7 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description) 8 | ); 9 | 10 | this.appendToSummary(this.suite, this.element); 11 | }; 12 | 13 | jasmine.HtmlReporter.SuiteView.prototype.status = function() { 14 | return this.getSpecStatus(this.suite); 15 | }; 16 | 17 | jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { 18 | this.element.className += " " + this.status(); 19 | }; 20 | 21 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); 22 | 23 | -------------------------------------------------------------------------------- /src/ios/AppDelegate+notification.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate+notification.h 3 | // pushtest 4 | // 5 | // Created by Robert Easterday on 10/26/12. 6 | // 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate (notification) 12 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; 13 | - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error; 14 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo; 15 | - (void)applicationDidBecomeActive:(UIApplication *)application; 16 | - (id) getCommandInstance:(NSString*)className; 17 | 18 | @property (nonatomic, retain) NSDictionary *launchNotification; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 Bob Easterday, Adobe Systems 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /spec/html/HtmlReporterHelpers.js: -------------------------------------------------------------------------------- 1 | jasmine.HtmlReporterHelpers = {}; 2 | 3 | jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { 4 | var el = document.createElement(type); 5 | 6 | for (var i = 2; i < arguments.length; i++) { 7 | var child = arguments[i]; 8 | 9 | if (typeof child === 'string') { 10 | el.appendChild(document.createTextNode(child)); 11 | } else { 12 | if (child) { 13 | el.appendChild(child); 14 | } 15 | } 16 | } 17 | 18 | for (var attr in attrs) { 19 | if (attr == "className") { 20 | el[attr] = attrs[attr]; 21 | } else { 22 | el.setAttribute(attr, attrs[attr]); 23 | } 24 | } 25 | 26 | return el; 27 | }; 28 | 29 | jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { 30 | var results = child.results(); 31 | var status = results.passed() ? 'passed' : 'failed'; 32 | if (results.skipped) { 33 | status = 'skipped'; 34 | } 35 | 36 | return status; 37 | }; 38 | 39 | jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { 40 | var parentDiv = this.dom.summary; 41 | var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; 42 | var parent = child[parentSuite]; 43 | 44 | if (parent) { 45 | if (typeof this.views.suites[parent.id] == 'undefined') { 46 | this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); 47 | } 48 | parentDiv = this.views.suites[parent.id].element; 49 | } 50 | 51 | parentDiv.appendChild(childElement); 52 | }; 53 | 54 | 55 | jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { 56 | for(var fn in jasmine.HtmlReporterHelpers) { 57 | ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; 58 | } 59 | }; 60 | 61 | -------------------------------------------------------------------------------- /spec/genericpush.tests.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | describe('Plugin object (window.plugins)', function () { 23 | it("should exist", function() { 24 | expect(window.plugins).toBeDefined(); 25 | }); 26 | 27 | it("should contain a pushNotification object", function() { 28 | expect(window.plugins.pushNotification).toBeDefined(); 29 | expect(typeof window.plugins.pushNotification == 'object').toBe(true); 30 | }); 31 | 32 | it("should contain a register function", function() { 33 | expect(window.plugins.pushNotification.register).toBeDefined(); 34 | expect(typeof window.plugins.pushNotification.register == 'function').toBe(true); 35 | }); 36 | 37 | it("should contain an unregister function", function() { 38 | expect(window.plugins.pushNotification.unregister).toBeDefined(); 39 | expect(typeof window.plugins.pushNotification.unregister == 'function').toBe(true); 40 | }); 41 | 42 | it("should contain a setApplicationIconBadgeNumber function", function() { 43 | expect(window.plugins.pushNotification.setApplicationIconBadgeNumber).toBeDefined(); 44 | expect(typeof window.plugins.pushNotification.setApplicationIconBadgeNumber == 'function').toBe(true); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/android/com/plugin/gcm/PushHandlerActivity.java: -------------------------------------------------------------------------------- 1 | package com.plugin.gcm; 2 | 3 | import android.app.Activity; 4 | import android.app.NotificationManager; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.os.Bundle; 9 | import android.util.Log; 10 | 11 | public class PushHandlerActivity extends Activity 12 | { 13 | private static String TAG = "PushHandlerActivity"; 14 | 15 | /* 16 | * this activity will be started if the user touches a notification that we own. 17 | * We send it's data off to the push plugin for processing. 18 | * If needed, we boot up the main activity to kickstart the application. 19 | * @see android.app.Activity#onCreate(android.os.Bundle) 20 | */ 21 | @Override 22 | public void onCreate(Bundle savedInstanceState) 23 | { 24 | super.onCreate(savedInstanceState); 25 | Log.v(TAG, "onCreate"); 26 | 27 | boolean isPushPluginActive = PushPlugin.isActive(); 28 | processPushBundle(isPushPluginActive); 29 | 30 | finish(); 31 | 32 | if (!isPushPluginActive) { 33 | forceMainActivityReload(); 34 | } 35 | } 36 | 37 | /** 38 | * Takes the pushBundle extras from the intent, 39 | * and sends it through to the PushPlugin for processing. 40 | */ 41 | private void processPushBundle(boolean isPushPluginActive) 42 | { 43 | Bundle extras = getIntent().getExtras(); 44 | 45 | if (extras != null) { 46 | Bundle originalExtras = extras.getBundle("pushBundle"); 47 | 48 | originalExtras.putBoolean("foreground", false); 49 | originalExtras.putBoolean("coldstart", !isPushPluginActive); 50 | 51 | PushPlugin.sendExtras(originalExtras); 52 | } 53 | } 54 | 55 | /** 56 | * Forces the main activity to re-launch if it's unloaded. 57 | */ 58 | private void forceMainActivityReload() 59 | { 60 | PackageManager pm = getPackageManager(); 61 | Intent launchIntent = pm.getLaunchIntentForPackage(getApplicationContext().getPackageName()); 62 | startActivity(launchIntent); 63 | } 64 | 65 | @Override 66 | protected void onResume() { 67 | super.onResume(); 68 | final NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); 69 | notificationManager.cancelAll(); 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /spec/test-runner.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | if (window.sessionStorage != null) { 23 | window.sessionStorage.clear(); 24 | } 25 | 26 | // Timeout is 2 seconds to allow physical devices enough 27 | // time to query the response. This is important for some 28 | // Android devices. 29 | var Tests = function() {}; 30 | Tests.TEST_TIMEOUT = 7500; 31 | 32 | // Creates a spy that will fail if called. 33 | function createDoNotCallSpy(name, opt_extraMessage) { 34 | return jasmine.createSpy().andCallFake(function() { 35 | var errorMessage = name + ' should not have been called.'; 36 | if (arguments.length) { 37 | errorMessage += ' Got args: ' + JSON.stringify(arguments); 38 | } 39 | if (opt_extraMessage) { 40 | errorMessage += '\n' + opt_extraMessage; 41 | } 42 | expect(false).toBe(true, errorMessage); 43 | }); 44 | } 45 | 46 | // Waits for any of the given spys to be called. 47 | // Last param may be a custom timeout duration. 48 | function waitsForAny() { 49 | var spys = [].slice.call(arguments); 50 | var timeout = Tests.TEST_TIMEOUT; 51 | if (typeof spys[spys.length - 1] == 'number') { 52 | timeout = spys.pop(); 53 | } 54 | waitsFor(function() { 55 | for (var i = 0; i < spys.length; ++i) { 56 | if (spys[i].wasCalled) { 57 | return true; 58 | } 59 | } 60 | return false; 61 | }, "Expecting callbacks to be called.", timeout); 62 | } 63 | -------------------------------------------------------------------------------- /src/ios/PushPlugin.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009-2011 Urban Airship Inc. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binaryform must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided withthe distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE URBAN AIRSHIP INC``AS IS'' AND ANY EXPRESS OR 15 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 16 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 17 | EVENT SHALL URBAN AIRSHIP INC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 18 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 19 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 22 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #import 27 | #import 28 | #import 29 | 30 | @interface PushPlugin : CDVPlugin 31 | { 32 | NSDictionary *notificationMessage; 33 | BOOL isInline; 34 | NSString *notificationCallbackId; 35 | NSString *callback; 36 | 37 | BOOL ready; 38 | } 39 | 40 | @property (nonatomic, copy) NSString *callbackId; 41 | @property (nonatomic, copy) NSString *notificationCallbackId; 42 | @property (nonatomic, copy) NSString *callback; 43 | 44 | @property (nonatomic, strong) NSDictionary *notificationMessage; 45 | @property BOOL isInline; 46 | 47 | - (void)register:(CDVInvokedUrlCommand*)command; 48 | 49 | - (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; 50 | - (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error; 51 | 52 | - (void)setNotificationMessage:(NSDictionary *)notification; 53 | - (void)notificationReceived; 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /Example/www/PushNotification.js: -------------------------------------------------------------------------------- 1 | 2 | var PushNotification = function() { 3 | }; 4 | 5 | 6 | // Call this to register for push notifications. Content of [options] depends on whether we are working with APNS (iOS) or GCM (Android) 7 | PushNotification.prototype.register = function(successCallback, errorCallback, options) { 8 | if (errorCallback == null) { errorCallback = function() {}} 9 | 10 | if (typeof errorCallback != "function") { 11 | console.log("PushNotification.register failure: failure parameter not a function"); 12 | return; 13 | } 14 | 15 | if (typeof successCallback != "function") { 16 | console.log("PushNotification.register failure: success callback parameter must be a function"); 17 | return; 18 | } 19 | 20 | cordova.exec(successCallback, errorCallback, "PushPlugin", "register", [options]); 21 | }; 22 | 23 | // Call this to unregister for push notifications 24 | PushNotification.prototype.unregister = function(successCallback, errorCallback) { 25 | if (errorCallback == null) { errorCallback = function() {}} 26 | 27 | if (typeof errorCallback != "function") { 28 | console.log("PushNotification.unregister failure: failure parameter not a function"); 29 | return; 30 | } 31 | 32 | if (typeof successCallback != "function") { 33 | console.log("PushNotification.unregister failure: success callback parameter must be a function"); 34 | return; 35 | } 36 | 37 | cordova.exec(successCallback, errorCallback, "PushPlugin", "unregister", []); 38 | }; 39 | 40 | 41 | // Call this to set the application icon badge 42 | PushNotification.prototype.setApplicationIconBadgeNumber = function(successCallback, badge) { 43 | if (errorCallback == null) { errorCallback = function() {}} 44 | 45 | if (typeof errorCallback != "function") { 46 | console.log("PushNotification.setApplicationIconBadgeNumber failure: failure parameter not a function"); 47 | return; 48 | } 49 | 50 | if (typeof successCallback != "function") { 51 | console.log("PushNotification.setApplicationIconBadgeNumber failure: success callback parameter must be a function"); 52 | return; 53 | } 54 | 55 | cordova.exec(successCallback, successCallback, "PushPlugin", "setApplicationIconBadgeNumber", [{badge: badge}]); 56 | }; 57 | 58 | //------------------------------------------------------------------- 59 | 60 | if(!window.plugins) { 61 | window.plugins = {}; 62 | } 63 | if (!window.plugins.pushNotification) { 64 | window.plugins.pushNotification = new PushNotification(); 65 | } 66 | -------------------------------------------------------------------------------- /spec/html/SpecView.js: -------------------------------------------------------------------------------- 1 | jasmine.HtmlReporter.SpecView = function(spec, dom, views) { 2 | this.spec = spec; 3 | this.dom = dom; 4 | this.views = views; 5 | 6 | this.symbol = this.createDom('li', { className: 'pending' }); 7 | this.dom.symbolSummary.appendChild(this.symbol); 8 | 9 | this.summary = this.createDom('div', { className: 'specSummary' }, 10 | this.createDom('a', { 11 | className: 'description', 12 | href: '?spec=' + encodeURIComponent(this.spec.getFullName()), 13 | title: this.spec.getFullName() 14 | }, this.spec.description) 15 | ); 16 | 17 | this.detail = this.createDom('div', { className: 'specDetail' }, 18 | this.createDom('a', { 19 | className: 'description', 20 | href: '?spec=' + encodeURIComponent(this.spec.getFullName()), 21 | title: this.spec.getFullName() 22 | }, this.spec.getFullName()) 23 | ); 24 | }; 25 | 26 | jasmine.HtmlReporter.SpecView.prototype.status = function() { 27 | return this.getSpecStatus(this.spec); 28 | }; 29 | 30 | jasmine.HtmlReporter.SpecView.prototype.refresh = function() { 31 | this.symbol.className = this.status(); 32 | 33 | switch (this.status()) { 34 | case 'skipped': 35 | break; 36 | 37 | case 'passed': 38 | this.appendSummaryToSuiteDiv(); 39 | break; 40 | 41 | case 'failed': 42 | this.appendSummaryToSuiteDiv(); 43 | this.appendFailureDetail(); 44 | break; 45 | } 46 | }; 47 | 48 | jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { 49 | this.summary.className += ' ' + this.status(); 50 | this.appendToSummary(this.spec, this.summary); 51 | }; 52 | 53 | jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { 54 | this.detail.className += ' ' + this.status(); 55 | 56 | var resultItems = this.spec.results().getItems(); 57 | var messagesDiv = this.createDom('div', { className: 'messages' }); 58 | 59 | for (var i = 0; i < resultItems.length; i++) { 60 | var result = resultItems[i]; 61 | 62 | if (result.type == 'log') { 63 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 64 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 65 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 66 | 67 | if (result.trace.stack) { 68 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 69 | } 70 | } 71 | } 72 | 73 | if (messagesDiv.childNodes.length > 0) { 74 | this.detail.appendChild(messagesDiv); 75 | this.dom.details.appendChild(this.detail); 76 | } 77 | }; 78 | 79 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView); -------------------------------------------------------------------------------- /src/amazon/ADMHandlerActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.amazon.cordova.plugin; 18 | 19 | import android.app.Activity; 20 | import android.content.Intent; 21 | import android.content.pm.PackageManager; 22 | import android.os.Bundle; 23 | 24 | public class ADMHandlerActivity extends Activity { 25 | 26 | /* 27 | * this activity will be started if the user touches a notification that we own. We send it's data off to the push 28 | * plugin for processing. If needed, we boot up the main activity to kickstart the application. 29 | * @see android.app.Activity#onCreate(android.os.Bundle) 30 | */ 31 | @Override 32 | public void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | boolean isPushPluginActive = PushPlugin.isActive(); 35 | processPushBundle(isPushPluginActive); 36 | finish(); 37 | if (!isPushPluginActive) { 38 | forceMainActivityReload(); 39 | } 40 | } 41 | 42 | /** 43 | * Takes the pushBundle extras from the intent, and sends it through to the PushPlugin for processing. 44 | */ 45 | private void processPushBundle(boolean isCordovaActive) { 46 | Bundle extras = getIntent().getExtras(); 47 | 48 | if (extras != null) { 49 | Bundle originalExtras = extras 50 | .getBundle(ADMMessageHandler.PUSH_BUNDLE); 51 | originalExtras.putBoolean(PushPlugin.COLDSTART, !isCordovaActive); 52 | ADMMessageHandler.cancelNotification(this); 53 | PushPlugin.sendExtras(originalExtras); 54 | // clean up the noticiationIntent extra 55 | ADMMessageHandler.cleanupNotificationIntent(); 56 | } 57 | } 58 | 59 | /** 60 | * Forces the main activity to re-launch if it's unloaded. 61 | */ 62 | private void forceMainActivityReload(/* Bundle extras */) { 63 | PackageManager pm = getPackageManager(); 64 | Intent launchIntent = pm 65 | .getLaunchIntentForPackage(getApplicationContext() 66 | .getPackageName()); 67 | startActivity(launchIntent); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /spec/index.html: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 24 | 25 | 26 | Cordova: API Specs 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 70 | 71 | 72 | 73 | Back 74 | 75 | 76 | -------------------------------------------------------------------------------- /spec/html/HtmlReporter.js: -------------------------------------------------------------------------------- 1 | jasmine.HtmlReporter = function(_doc) { 2 | var self = this; 3 | var doc = _doc || window.document; 4 | 5 | var reporterView; 6 | 7 | var dom = {}; 8 | 9 | // Jasmine Reporter Public Interface 10 | self.logRunningSpecs = false; 11 | 12 | self.reportRunnerStarting = function(runner) { 13 | var specs = runner.specs() || []; 14 | 15 | if (specs.length == 0) { 16 | return; 17 | } 18 | 19 | createReporterDom(runner.env.versionString()); 20 | doc.body.appendChild(dom.reporter); 21 | 22 | reporterView = new jasmine.HtmlReporter.ReporterView(dom); 23 | reporterView.addSpecs(specs, self.specFilter); 24 | }; 25 | 26 | self.reportRunnerResults = function(runner) { 27 | reporterView && reporterView.complete(); 28 | }; 29 | 30 | self.reportSuiteResults = function(suite) { 31 | reporterView.suiteComplete(suite); 32 | }; 33 | 34 | self.reportSpecStarting = function(spec) { 35 | if (self.logRunningSpecs) { 36 | self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 37 | } 38 | }; 39 | 40 | self.reportSpecResults = function(spec) { 41 | reporterView.specComplete(spec); 42 | }; 43 | 44 | self.log = function() { 45 | var console = jasmine.getGlobal().console; 46 | if (console && console.log) { 47 | if (console.log.apply) { 48 | console.log.apply(console, arguments); 49 | } else { 50 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 51 | } 52 | } 53 | }; 54 | 55 | self.specFilter = function(spec) { 56 | if (!focusedSpecName()) { 57 | return true; 58 | } 59 | 60 | return spec.getFullName().indexOf(focusedSpecName()) === 0; 61 | }; 62 | 63 | return self; 64 | 65 | function focusedSpecName() { 66 | var specName; 67 | 68 | (function memoizeFocusedSpec() { 69 | if (specName) { 70 | return; 71 | } 72 | 73 | var paramMap = []; 74 | var params = doc.location.search.substring(1).split('&'); 75 | 76 | for (var i = 0; i < params.length; i++) { 77 | var p = params[i].split('='); 78 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 79 | } 80 | 81 | specName = paramMap.spec; 82 | })(); 83 | 84 | return specName; 85 | } 86 | 87 | function createReporterDom(version) { 88 | dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, 89 | dom.banner = self.createDom('div', { className: 'banner' }, 90 | self.createDom('span', { className: 'title' }, "Jasmine "), 91 | self.createDom('span', { className: 'version' }, version)), 92 | 93 | dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), 94 | dom.alert = self.createDom('div', {className: 'alert'}), 95 | dom.results = self.createDom('div', {className: 'results'}, 96 | dom.summary = self.createDom('div', { className: 'summary' }), 97 | dom.details = self.createDom('div', { id: 'details' })) 98 | ); 99 | } 100 | }; 101 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); 102 | -------------------------------------------------------------------------------- /www/PushNotification.js: -------------------------------------------------------------------------------- 1 | var PushNotification = function() { 2 | }; 3 | 4 | 5 | // Call this to register for push notifications. Content of [options] depends on whether we are working with APNS (iOS) or GCM (Android) 6 | PushNotification.prototype.register = function(successCallback, errorCallback, options) { 7 | if (errorCallback == null) { errorCallback = function() {}} 8 | 9 | if (typeof errorCallback != "function") { 10 | console.log("PushNotification.register failure: failure parameter not a function"); 11 | return 12 | } 13 | 14 | if (typeof successCallback != "function") { 15 | console.log("PushNotification.register failure: success callback parameter must be a function"); 16 | return 17 | } 18 | 19 | cordova.exec(successCallback, errorCallback, "PushPlugin", "register", [options]); 20 | }; 21 | 22 | // Call this to unregister for push notifications 23 | PushNotification.prototype.unregister = function(successCallback, errorCallback, options) { 24 | if (errorCallback == null) { errorCallback = function() {}} 25 | 26 | if (typeof errorCallback != "function") { 27 | console.log("PushNotification.unregister failure: failure parameter not a function"); 28 | return 29 | } 30 | 31 | if (typeof successCallback != "function") { 32 | console.log("PushNotification.unregister failure: success callback parameter must be a function"); 33 | return 34 | } 35 | 36 | cordova.exec(successCallback, errorCallback, "PushPlugin", "unregister", [options]); 37 | }; 38 | 39 | // Call this if you want to show toast notification on WP8 40 | PushNotification.prototype.showToastNotification = function (successCallback, errorCallback, options) { 41 | if (errorCallback == null) { errorCallback = function () { } } 42 | 43 | if (typeof errorCallback != "function") { 44 | console.log("PushNotification.register failure: failure parameter not a function"); 45 | return 46 | } 47 | 48 | cordova.exec(successCallback, errorCallback, "PushPlugin", "showToastNotification", [options]); 49 | } 50 | // Call this to set the application icon badge 51 | PushNotification.prototype.setApplicationIconBadgeNumber = function(successCallback, errorCallback, badge) { 52 | if (errorCallback == null) { errorCallback = function() {}} 53 | 54 | if (typeof errorCallback != "function") { 55 | console.log("PushNotification.setApplicationIconBadgeNumber failure: failure parameter not a function"); 56 | return 57 | } 58 | 59 | if (typeof successCallback != "function") { 60 | console.log("PushNotification.setApplicationIconBadgeNumber failure: success callback parameter must be a function"); 61 | return 62 | } 63 | 64 | cordova.exec(successCallback, errorCallback, "PushPlugin", "setApplicationIconBadgeNumber", [{badge: badge}]); 65 | }; 66 | 67 | //------------------------------------------------------------------- 68 | 69 | if(!window.plugins) { 70 | window.plugins = {}; 71 | } 72 | if (!window.plugins.pushNotification) { 73 | window.plugins.pushNotification = new PushNotification(); 74 | } 75 | 76 | if (typeof module != 'undefined' && module.exports) { 77 | module.exports = PushNotification; 78 | } -------------------------------------------------------------------------------- /src/ios/AppDelegate+notification.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate+notification.m 3 | // pushtest 4 | // 5 | // Created by Robert Easterday on 10/26/12. 6 | // 7 | // 8 | 9 | #import "AppDelegate+notification.h" 10 | #import "PushPlugin.h" 11 | #import 12 | 13 | static char launchNotificationKey; 14 | 15 | @implementation AppDelegate (notification) 16 | 17 | - (id) getCommandInstance:(NSString*)className 18 | { 19 | return [self.viewController getCommandInstance:className]; 20 | } 21 | 22 | // its dangerous to override a method from within a category. 23 | // Instead we will use method swizzling. we set this up in the load call. 24 | + (void)load 25 | { 26 | Method original, swizzled; 27 | 28 | original = class_getInstanceMethod(self, @selector(init)); 29 | swizzled = class_getInstanceMethod(self, @selector(swizzled_init)); 30 | method_exchangeImplementations(original, swizzled); 31 | } 32 | 33 | - (AppDelegate *)swizzled_init 34 | { 35 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(createNotificationChecker:) 36 | name:@"UIApplicationDidFinishLaunchingNotification" object:nil]; 37 | 38 | // This actually calls the original init method over in AppDelegate. Equivilent to calling super 39 | // on an overrided method, this is not recursive, although it appears that way. neat huh? 40 | return [self swizzled_init]; 41 | } 42 | 43 | // This code will be called immediately after application:didFinishLaunchingWithOptions:. We need 44 | // to process notifications in cold-start situations 45 | - (void)createNotificationChecker:(NSNotification *)notification 46 | { 47 | if (notification) 48 | { 49 | NSDictionary *launchOptions = [notification userInfo]; 50 | if (launchOptions) 51 | self.launchNotification = [launchOptions objectForKey: @"UIApplicationLaunchOptionsRemoteNotificationKey"]; 52 | } 53 | } 54 | 55 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { 56 | PushPlugin *pushHandler = [self getCommandInstance:@"PushPlugin"]; 57 | [pushHandler didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; 58 | } 59 | 60 | - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { 61 | PushPlugin *pushHandler = [self getCommandInstance:@"PushPlugin"]; 62 | [pushHandler didFailToRegisterForRemoteNotificationsWithError:error]; 63 | } 64 | 65 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { 66 | NSLog(@"didReceiveNotification"); 67 | 68 | // Get application state for iOS4.x+ devices, otherwise assume active 69 | UIApplicationState appState = UIApplicationStateActive; 70 | if ([application respondsToSelector:@selector(applicationState)]) { 71 | appState = application.applicationState; 72 | } 73 | 74 | if (appState == UIApplicationStateActive) { 75 | PushPlugin *pushHandler = [self getCommandInstance:@"PushPlugin"]; 76 | pushHandler.notificationMessage = userInfo; 77 | pushHandler.isInline = YES; 78 | [pushHandler notificationReceived]; 79 | } else { 80 | //save it for later 81 | self.launchNotification = userInfo; 82 | } 83 | } 84 | 85 | - (void)applicationDidBecomeActive:(UIApplication *)application { 86 | 87 | NSLog(@"active"); 88 | 89 | //zero badge 90 | application.applicationIconBadgeNumber = 0; 91 | 92 | if (self.launchNotification) { 93 | PushPlugin *pushHandler = [self getCommandInstance:@"PushPlugin"]; 94 | 95 | pushHandler.notificationMessage = self.launchNotification; 96 | self.launchNotification = nil; 97 | [pushHandler performSelectorOnMainThread:@selector(notificationReceived) withObject:pushHandler waitUntilDone:NO]; 98 | } 99 | } 100 | 101 | // The accessors use an Associative Reference since you can't define a iVar in a category 102 | // http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocAssociativeReferences.html 103 | - (NSMutableArray *)launchNotification 104 | { 105 | return objc_getAssociatedObject(self, &launchNotificationKey); 106 | } 107 | 108 | - (void)setLaunchNotification:(NSDictionary *)aDictionary 109 | { 110 | objc_setAssociatedObject(self, &launchNotificationKey, aDictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 111 | } 112 | 113 | - (void)dealloc 114 | { 115 | self.launchNotification = nil; // clear the association and release the object 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /src/android/com/plugin/gcm/GCMIntentService.java: -------------------------------------------------------------------------------- 1 | package com.plugin.gcm; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | import android.annotation.SuppressLint; 7 | import android.app.Notification; 8 | import android.app.NotificationManager; 9 | import android.app.PendingIntent; 10 | import android.content.Context; 11 | import android.content.Intent; 12 | import android.os.Bundle; 13 | import android.support.v4.app.NotificationCompat; 14 | import android.util.Log; 15 | 16 | import com.google.android.gcm.GCMBaseIntentService; 17 | 18 | @SuppressLint("NewApi") 19 | public class GCMIntentService extends GCMBaseIntentService { 20 | 21 | private static final String TAG = "GCMIntentService"; 22 | 23 | public GCMIntentService() { 24 | super("GCMIntentService"); 25 | } 26 | 27 | @Override 28 | public void onRegistered(Context context, String regId) { 29 | 30 | Log.v(TAG, "onRegistered: "+ regId); 31 | 32 | JSONObject json; 33 | 34 | try 35 | { 36 | json = new JSONObject().put("event", "registered"); 37 | json.put("regid", regId); 38 | 39 | Log.v(TAG, "onRegistered: " + json.toString()); 40 | 41 | // Send this JSON data to the JavaScript application above EVENT should be set to the msg type 42 | // In this case this is the registration ID 43 | PushPlugin.sendJavascript( json ); 44 | 45 | } 46 | catch( JSONException e) 47 | { 48 | // No message to the user is sent, JSON failed 49 | Log.e(TAG, "onRegistered: JSON exception"); 50 | } 51 | } 52 | 53 | @Override 54 | public void onUnregistered(Context context, String regId) { 55 | Log.d(TAG, "onUnregistered - regId: " + regId); 56 | } 57 | 58 | @Override 59 | protected void onMessage(Context context, Intent intent) { 60 | Log.d(TAG, "onMessage - context: " + context); 61 | 62 | // Extract the payload from the message 63 | Bundle extras = intent.getExtras(); 64 | if (extras != null) 65 | { 66 | // if we are in the foreground, just surface the payload, else post it to the statusbar 67 | if (PushPlugin.isInForeground()) { 68 | extras.putBoolean("foreground", true); 69 | PushPlugin.sendExtras(extras); 70 | } 71 | else { 72 | extras.putBoolean("foreground", false); 73 | 74 | // Send a notification if there is a message 75 | if (extras.getString("message") != null && extras.getString("message").length() != 0) { 76 | createNotification(context, extras); 77 | } 78 | } 79 | } 80 | } 81 | 82 | public void createNotification(Context context, Bundle extras) 83 | { 84 | NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 85 | String appName = getAppName(this); 86 | 87 | Intent notificationIntent = new Intent(this, PushHandlerActivity.class); 88 | notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); 89 | notificationIntent.putExtra("pushBundle", extras); 90 | 91 | PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); 92 | 93 | int defaults = Notification.DEFAULT_ALL; 94 | 95 | if (extras.getString("defaults") != null) { 96 | try { 97 | defaults = Integer.parseInt(extras.getString("defaults")); 98 | } catch (NumberFormatException e) {} 99 | } 100 | 101 | NotificationCompat.Builder mBuilder = 102 | new NotificationCompat.Builder(context) 103 | .setDefaults(defaults) 104 | .setSmallIcon(context.getApplicationInfo().icon) 105 | .setWhen(System.currentTimeMillis()) 106 | .setContentTitle(extras.getString("title")) 107 | .setTicker(extras.getString("title")) 108 | .setContentIntent(contentIntent) 109 | .setAutoCancel(true); 110 | 111 | String message = extras.getString("message"); 112 | if (message != null) { 113 | mBuilder.setContentText(message); 114 | } else { 115 | mBuilder.setContentText(""); 116 | } 117 | 118 | String msgcnt = extras.getString("msgcnt"); 119 | if (msgcnt != null) { 120 | mBuilder.setNumber(Integer.parseInt(msgcnt)); 121 | } 122 | 123 | int notId = 0; 124 | 125 | try { 126 | notId = Integer.parseInt(extras.getString("notId")); 127 | } 128 | catch(NumberFormatException e) { 129 | Log.e(TAG, "Number format exception - Error parsing Notification ID: " + e.getMessage()); 130 | } 131 | catch(Exception e) { 132 | Log.e(TAG, "Number format exception - Error parsing Notification ID" + e.getMessage()); 133 | } 134 | 135 | mNotificationManager.notify((String) appName, notId, mBuilder.build()); 136 | } 137 | 138 | private static String getAppName(Context context) 139 | { 140 | CharSequence appName = 141 | context 142 | .getPackageManager() 143 | .getApplicationLabel(context.getApplicationInfo()); 144 | 145 | return (String)appName; 146 | } 147 | 148 | @Override 149 | public void onError(Context context, String errorId) { 150 | Log.e(TAG, "onError - errorId: " + errorId); 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /Example/server/pushADM.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | // Client ID and Client Secret received from ADM 5 | // For more info, see: https://developer.amazon.com/public/apis/engage/device-messaging/tech-docs/02-obtaining-adm-credentials 6 | var CLIENT_ID = "amzn1.application-oa2-client.8e838f6629554e26ae3f43a6c663cd60"; 7 | var CLIENT_SECRET = "0af96083320f5d70dc4f358cc783ac65a22e78b297ba257df34d5f723f24543f"; 8 | 9 | // Registration ID, received on device after it registers with ADM server 10 | var REGISTRATION_IDS = ["amzn1.adm-registration.v2.Y29tLmFtYXpvbi5EZXZpY2VNZXNzYWdpbmcuUmVnaXN0cmF0aW9uSWRFbmNyeXB0aW9uS2V5ITEhOE9rZ2h5TXlhVEFFczg2ejNWL3JMcmhTa255Uk5BclhBbE1XMFZzcnU1aFF6cTlvdU5FbVEwclZmdk5oTFBVRXVDN1luQlRSNnRVRUViREdQSlBvSzRNaXVRRUlyUy9NYWZCYS9VWTJUaGZwb3ZVTHhlRTM0MGhvampBK01hVktsMEhxakdmQStOSXRjUXBTQUhNU1NlVVVUVkFreVRhRTBCYktaQ2ZkUFdqSmIwcHgzRDhMQnllVXdxQ2EwdHNXRmFVNklYL0U4UXovcHg0K3Jjb25VbVFLRUVVOFVabnh4RDhjYmtIcHd1ZThiekorbGtzR2taMG95cC92Y3NtZytrcTRPNjhXUUpiZEk3QzFvQThBRTFWWXM2NHkyMjdYVGV5RlhhMWNHS0k9IW5GNEJMSXNleC9xbWpHSU52NnczY0E9PQ"]; 11 | 12 | // Message payload to be sent to client 13 | var payload = { 14 | data: { 15 | message: "PushPlugin works!!", 16 | sound: "beep.wav", 17 | url: "http://www.amazon.com", 18 | timeStamp: new Date().toISOString(), 19 | foo: "baz" 20 | }, 21 | consolidationKey: "my app", 22 | expiresAfter: 3600 23 | }; 24 | 25 | 26 | //********************************* 27 | 28 | 29 | var https = require("https"); 30 | var querystring = require("querystring"); 31 | 32 | 33 | if(CLIENT_ID == "" || CLIENT_SECRET == "" || REGISTRATION_IDS.length == 0){ 34 | console.log("******************\nSetup Error: \nYou need to edit the pushADM.js file and enter your ADM credentials and device registration ID(s).\n******************"); 35 | process.exit(1); 36 | } 37 | 38 | 39 | // Get access token from server, and use it to post message to device 40 | getAccessToken(function(accessToken){ 41 | 42 | for(var i = 0; i < REGISTRATION_IDS.length; i++){ 43 | 44 | var registrationID = REGISTRATION_IDS[i]; 45 | 46 | postMessage(accessToken, registrationID, payload); 47 | } 48 | 49 | }); 50 | 51 | 52 | 53 | 54 | // Query OAuth server for access token 55 | // For more info, see: https://developer.amazon.com/public/apis/engage/device-messaging/tech-docs/05-requesting-an-access-token 56 | 57 | function getAccessToken(callback){ 58 | 59 | console.log("Requesting access token from server..."); 60 | 61 | var credentials = { 62 | scope: "messaging:push", 63 | grant_type: "client_credentials", 64 | client_id: CLIENT_ID, 65 | client_secret: CLIENT_SECRET 66 | } 67 | 68 | var post_data = querystring.stringify(credentials); 69 | 70 | var post_options = { 71 | host: "api.amazon.com", 72 | port: "443", 73 | path: "/auth/O2/token", 74 | method: "POST", 75 | headers: { 76 | "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" 77 | } 78 | }; 79 | 80 | var req = https.request(post_options, function(res) { 81 | 82 | var data = ""; 83 | 84 | res.on("data", function (chunk) { 85 | data += chunk; 86 | }); 87 | 88 | res.on("end", function() { 89 | console.log("\nAccess token response:", data); 90 | var accessToken = JSON.parse(data).access_token; 91 | callback(accessToken); 92 | }); 93 | 94 | }); 95 | 96 | req.on("error", function(e) { 97 | console.log("\nProblem with access token request: ", e.message); 98 | }); 99 | 100 | req.write(post_data); 101 | req.end(); 102 | 103 | } 104 | 105 | 106 | // Post message payload to ADM server 107 | // For more info, see: https://developer.amazon.com/public/apis/engage/device-messaging/tech-docs/06-sending-a-message 108 | 109 | function postMessage(accessToken, registrationID, payload){ 110 | 111 | if(accessToken == undefined || registrationID == undefined || payload == undefined){ 112 | return; 113 | } 114 | 115 | console.log("\nSending message..."); 116 | 117 | var post_data = JSON.stringify(payload); 118 | 119 | var api_path = "/messaging/registrations/" + registrationID + "/messages"; 120 | 121 | var post_options = { 122 | host: "api.amazon.com", 123 | port: "443", 124 | path: api_path, 125 | method: "POST", 126 | headers: { 127 | "Authorization": "Bearer " + accessToken, 128 | "X-Amzn-Type-Version": "com.amazon.device.messaging.ADMMessage@1.0", 129 | "X-Amzn-Accept-Type" : "com.amazon.device.messaging.ADMSendResult@1.0", 130 | "Content-Type": "application/json", 131 | "Accept": "application/json", 132 | } 133 | }; 134 | 135 | var req = https.request(post_options, function(res) { 136 | 137 | var data = ""; 138 | 139 | res.on("data", function (chunk) { 140 | data += chunk; 141 | }); 142 | 143 | res.on("end", function() { 144 | console.log("\nSend message response: ", data); 145 | }); 146 | 147 | }); 148 | 149 | req.on("error", function(e) { 150 | console.log("\nProblem with send message request: ", e.message); 151 | }); 152 | 153 | req.write(post_data); 154 | req.end(); 155 | 156 | } 157 | 158 | 159 | -------------------------------------------------------------------------------- /spec/html/ReporterView.js: -------------------------------------------------------------------------------- 1 | jasmine.HtmlReporter.ReporterView = function(dom) { 2 | this.startedAt = new Date(); 3 | this.runningSpecCount = 0; 4 | this.completeSpecCount = 0; 5 | this.passedCount = 0; 6 | this.failedCount = 0; 7 | this.skippedCount = 0; 8 | 9 | this.createResultsMenu = function() { 10 | this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, 11 | this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), 12 | ' | ', 13 | this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); 14 | 15 | this.summaryMenuItem.onclick = function() { 16 | dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); 17 | }; 18 | 19 | this.detailsMenuItem.onclick = function() { 20 | showDetails(); 21 | }; 22 | }; 23 | 24 | this.addSpecs = function(specs, specFilter) { 25 | this.totalSpecCount = specs.length; 26 | 27 | this.views = { 28 | specs: {}, 29 | suites: {} 30 | }; 31 | 32 | for (var i = 0; i < specs.length; i++) { 33 | var spec = specs[i]; 34 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); 35 | if (specFilter(spec)) { 36 | this.runningSpecCount++; 37 | } 38 | } 39 | }; 40 | 41 | this.specComplete = function(spec) { 42 | this.completeSpecCount++; 43 | 44 | if (isUndefined(this.views.specs[spec.id])) { 45 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); 46 | } 47 | 48 | var specView = this.views.specs[spec.id]; 49 | 50 | switch (specView.status()) { 51 | case 'passed': 52 | this.passedCount++; 53 | break; 54 | 55 | case 'failed': 56 | this.failedCount++; 57 | break; 58 | 59 | case 'skipped': 60 | this.skippedCount++; 61 | break; 62 | } 63 | 64 | specView.refresh(); 65 | this.refresh(); 66 | }; 67 | 68 | this.suiteComplete = function(suite) { 69 | var suiteView = this.views.suites[suite.id]; 70 | if (isUndefined(suiteView)) { 71 | return; 72 | } 73 | suiteView.refresh(); 74 | }; 75 | 76 | this.refresh = function() { 77 | 78 | if (isUndefined(this.resultsMenu)) { 79 | this.createResultsMenu(); 80 | } 81 | 82 | // currently running UI 83 | if (isUndefined(this.runningAlert)) { 84 | this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"}); 85 | dom.alert.appendChild(this.runningAlert); 86 | } 87 | this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); 88 | 89 | // skipped specs UI 90 | if (isUndefined(this.skippedAlert)) { 91 | this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"}); 92 | } 93 | 94 | this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 95 | 96 | if (this.skippedCount === 1 && isDefined(dom.alert)) { 97 | dom.alert.appendChild(this.skippedAlert); 98 | } 99 | 100 | // passing specs UI 101 | if (isUndefined(this.passedAlert)) { 102 | this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"}); 103 | } 104 | this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); 105 | 106 | // failing specs UI 107 | if (isUndefined(this.failedAlert)) { 108 | this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); 109 | } 110 | this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); 111 | 112 | if (this.failedCount === 1 && isDefined(dom.alert)) { 113 | dom.alert.appendChild(this.failedAlert); 114 | dom.alert.appendChild(this.resultsMenu); 115 | } 116 | 117 | // summary info 118 | this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); 119 | this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; 120 | }; 121 | 122 | this.complete = function() { 123 | dom.alert.removeChild(this.runningAlert); 124 | 125 | this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 126 | 127 | if (this.failedCount === 0) { 128 | dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); 129 | } else { 130 | showDetails(); 131 | } 132 | 133 | dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); 134 | }; 135 | 136 | return this; 137 | 138 | function showDetails() { 139 | if (dom.reporter.className.search(/showDetails/) === -1) { 140 | dom.reporter.className += " showDetails"; 141 | } 142 | } 143 | 144 | function isUndefined(obj) { 145 | return typeof obj === 'undefined'; 146 | } 147 | 148 | function isDefined(obj) { 149 | return !isUndefined(obj); 150 | } 151 | 152 | function specPluralizedFor(count) { 153 | var str = count + " spec"; 154 | if (count > 1) { 155 | str += "s" 156 | } 157 | return str; 158 | } 159 | 160 | }; 161 | 162 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); 163 | 164 | 165 | -------------------------------------------------------------------------------- /spec/jasmine.css: -------------------------------------------------------------------------------- 1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } 2 | 3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | #HTMLReporter a { text-decoration: none; } 5 | #HTMLReporter a:hover { text-decoration: underline; } 6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } 7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } 8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; } 9 | #HTMLReporter .version { color: #aaaaaa; } 10 | #HTMLReporter .banner { margin-top: 14px; } 11 | #HTMLReporter .duration { color: #aaaaaa; float: right; } 12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } 13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } 14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; } 15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } 16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; } 17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } 18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; } 19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } 20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; } 21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } 22 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 23 | #HTMLReporter .runningAlert { background-color: #666666; } 24 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; } 25 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; } 26 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } 27 | #HTMLReporter .passingAlert { background-color: #a6b779; } 28 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } 29 | #HTMLReporter .failingAlert { background-color: #cf867e; } 30 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; } 31 | #HTMLReporter .results { margin-top: 14px; } 32 | #HTMLReporter #details { display: none; } 33 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } 34 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 35 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 36 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 37 | #HTMLReporter.showDetails .summary { display: none; } 38 | #HTMLReporter.showDetails #details { display: block; } 39 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 40 | #HTMLReporter .summary { margin-top: 14px; } 41 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } 42 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } 43 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; } 44 | #HTMLReporter .description + .suite { margin-top: 0; } 45 | #HTMLReporter .suite { margin-top: 14px; } 46 | #HTMLReporter .suite a { color: #333333; } 47 | #HTMLReporter #details .specDetail { margin-bottom: 28px; } 48 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } 49 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } 50 | #HTMLReporter .resultMessage span.result { display: block; } 51 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 52 | 53 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } 54 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; } 55 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } 56 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } 57 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } 58 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } 59 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } 60 | #TrivialReporter .runner.running { background-color: yellow; } 61 | #TrivialReporter .options { text-align: right; font-size: .8em; } 62 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } 63 | #TrivialReporter .suite .suite { margin: 5px; } 64 | #TrivialReporter .suite.passed { background-color: #dfd; } 65 | #TrivialReporter .suite.failed { background-color: #fdd; } 66 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } 67 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } 68 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } 69 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } 70 | #TrivialReporter .spec.skipped { background-color: #bbb; } 71 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } 72 | #TrivialReporter .passed { background-color: #cfc; display: none; } 73 | #TrivialReporter .failed { background-color: #fbb; } 74 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } 75 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } 76 | #TrivialReporter .resultMessage .mismatch { color: black; } 77 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } 78 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } 79 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } 80 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; } 81 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } 82 | -------------------------------------------------------------------------------- /src/android/com/plugin/gcm/PushPlugin.java: -------------------------------------------------------------------------------- 1 | package com.plugin.gcm; 2 | 3 | import android.app.NotificationManager; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | import com.google.android.gcm.GCMRegistrar; 8 | import org.apache.cordova.CallbackContext; 9 | import org.apache.cordova.CordovaInterface; 10 | import org.apache.cordova.CordovaPlugin; 11 | import org.apache.cordova.CordovaWebView; 12 | import org.json.JSONArray; 13 | import org.json.JSONException; 14 | import org.json.JSONObject; 15 | 16 | import java.util.Iterator; 17 | 18 | /** 19 | * @author awysocki 20 | */ 21 | 22 | public class PushPlugin extends CordovaPlugin { 23 | public static final String TAG = "PushPlugin"; 24 | 25 | public static final String REGISTER = "register"; 26 | public static final String UNREGISTER = "unregister"; 27 | public static final String EXIT = "exit"; 28 | 29 | private static CordovaWebView gWebView; 30 | private static String gECB; 31 | private static String gSenderID; 32 | private static Bundle gCachedExtras = null; 33 | private static boolean gForeground = false; 34 | 35 | /** 36 | * Gets the application context from cordova's main activity. 37 | * @return the application context 38 | */ 39 | private Context getApplicationContext() { 40 | return this.cordova.getActivity().getApplicationContext(); 41 | } 42 | 43 | @Override 44 | public boolean execute(String action, JSONArray data, CallbackContext callbackContext) { 45 | 46 | boolean result = false; 47 | 48 | Log.v(TAG, "execute: action=" + action); 49 | 50 | if (REGISTER.equals(action)) { 51 | 52 | Log.v(TAG, "execute: data=" + data.toString()); 53 | 54 | try { 55 | JSONObject jo = data.getJSONObject(0); 56 | 57 | gWebView = this.webView; 58 | Log.v(TAG, "execute: jo=" + jo.toString()); 59 | 60 | gECB = (String) jo.get("ecb"); 61 | gSenderID = (String) jo.get("senderID"); 62 | 63 | Log.v(TAG, "execute: ECB=" + gECB + " senderID=" + gSenderID); 64 | 65 | GCMRegistrar.register(getApplicationContext(), gSenderID); 66 | result = true; 67 | callbackContext.success(); 68 | } catch (JSONException e) { 69 | Log.e(TAG, "execute: Got JSON Exception " + e.getMessage()); 70 | result = false; 71 | callbackContext.error(e.getMessage()); 72 | } 73 | 74 | if ( gCachedExtras != null) { 75 | Log.v(TAG, "sending cached extras"); 76 | sendExtras(gCachedExtras); 77 | gCachedExtras = null; 78 | } 79 | 80 | } else if (UNREGISTER.equals(action)) { 81 | 82 | GCMRegistrar.unregister(getApplicationContext()); 83 | 84 | Log.v(TAG, "UNREGISTER"); 85 | result = true; 86 | callbackContext.success(); 87 | } else { 88 | result = false; 89 | Log.e(TAG, "Invalid action : " + action); 90 | callbackContext.error("Invalid action : " + action); 91 | } 92 | 93 | return result; 94 | } 95 | 96 | /* 97 | * Sends a json object to the client as parameter to a method which is defined in gECB. 98 | */ 99 | public static void sendJavascript(JSONObject _json) { 100 | String _d = "javascript:" + gECB + "(" + _json.toString() + ")"; 101 | Log.v(TAG, "sendJavascript: " + _d); 102 | 103 | if (gECB != null && gWebView != null) { 104 | gWebView.sendJavascript(_d); 105 | } 106 | } 107 | 108 | /* 109 | * Sends the pushbundle extras to the client application. 110 | * If the client application isn't currently active, it is cached for later processing. 111 | */ 112 | public static void sendExtras(Bundle extras) 113 | { 114 | if (extras != null) { 115 | if (gECB != null && gWebView != null) { 116 | sendJavascript(convertBundleToJson(extras)); 117 | } else { 118 | Log.v(TAG, "sendExtras: caching extras to send at a later time."); 119 | gCachedExtras = extras; 120 | } 121 | } 122 | } 123 | 124 | @Override 125 | public void initialize(CordovaInterface cordova, CordovaWebView webView) { 126 | super.initialize(cordova, webView); 127 | gForeground = true; 128 | } 129 | 130 | @Override 131 | public void onPause(boolean multitasking) { 132 | super.onPause(multitasking); 133 | gForeground = false; 134 | final NotificationManager notificationManager = (NotificationManager) cordova.getActivity().getSystemService(Context.NOTIFICATION_SERVICE); 135 | notificationManager.cancelAll(); 136 | } 137 | 138 | @Override 139 | public void onResume(boolean multitasking) { 140 | super.onResume(multitasking); 141 | gForeground = true; 142 | } 143 | 144 | @Override 145 | public void onDestroy() { 146 | super.onDestroy(); 147 | gForeground = false; 148 | gECB = null; 149 | gWebView = null; 150 | } 151 | 152 | /* 153 | * serializes a bundle to JSON. 154 | */ 155 | private static JSONObject convertBundleToJson(Bundle extras) 156 | { 157 | try 158 | { 159 | JSONObject json; 160 | json = new JSONObject().put("event", "message"); 161 | 162 | JSONObject jsondata = new JSONObject(); 163 | Iterator it = extras.keySet().iterator(); 164 | while (it.hasNext()) 165 | { 166 | String key = it.next(); 167 | Object value = extras.get(key); 168 | 169 | // System data from Android 170 | if (key.equals("from") || key.equals("collapse_key")) 171 | { 172 | json.put(key, value); 173 | } 174 | else if (key.equals("foreground")) 175 | { 176 | json.put(key, extras.getBoolean("foreground")); 177 | } 178 | else if (key.equals("coldstart")) 179 | { 180 | json.put(key, extras.getBoolean("coldstart")); 181 | } 182 | else 183 | { 184 | // Maintain backwards compatibility 185 | if (key.equals("message") || key.equals("msgcnt") || key.equals("soundname")) 186 | { 187 | json.put(key, value); 188 | } 189 | 190 | if ( value instanceof String ) { 191 | // Try to figure out if the value is another JSON object 192 | 193 | String strValue = (String)value; 194 | if (strValue.startsWith("{")) { 195 | try { 196 | JSONObject json2 = new JSONObject(strValue); 197 | jsondata.put(key, json2); 198 | } 199 | catch (Exception e) { 200 | jsondata.put(key, value); 201 | } 202 | // Try to figure out if the value is another JSON array 203 | } 204 | else if (strValue.startsWith("[")) 205 | { 206 | try 207 | { 208 | JSONArray json2 = new JSONArray(strValue); 209 | jsondata.put(key, json2); 210 | } 211 | catch (Exception e) 212 | { 213 | jsondata.put(key, value); 214 | } 215 | } 216 | else 217 | { 218 | jsondata.put(key, value); 219 | } 220 | } 221 | } 222 | } // while 223 | json.put("payload", jsondata); 224 | 225 | Log.v(TAG, "extrasToJSON: " + json.toString()); 226 | 227 | return json; 228 | } 229 | catch( JSONException e) 230 | { 231 | Log.e(TAG, "extrasToJSON: JSON exception"); 232 | } 233 | return null; 234 | } 235 | 236 | public static boolean isInForeground() 237 | { 238 | return gForeground; 239 | } 240 | 241 | public static boolean isActive() 242 | { 243 | return gWebView != null; 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /Example/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.PhoneGap.c2dm 5 | 6 | 7 | 8 | /* 9 | NOTE: 10 | This demo uses these plugins: 11 | Cordova Device Plugin: http://plugins.cordova.io/#/package/org.apache.cordova.device 12 | Cordova Media Plugin: http://plugins.cordova.io/#/package/org.apache.cordova.media 13 | 14 | To add them via the CLI: 15 | $ cordova plugin add org.apache.cordova.device 16 | $ cordova plugin add org.apache.cordova.media 17 | */ 18 | 19 | 20 | 21 | 22 | 23 | 158 |
159 |
160 |
    161 |
  • Cordova PushNotification Plugin Demo
  • 162 |
163 |
164 |
165 | 166 | 167 | -------------------------------------------------------------------------------- /spec/html/TrivialReporter.js: -------------------------------------------------------------------------------- 1 | /* @deprecated Use jasmine.HtmlReporter instead 2 | */ 3 | jasmine.TrivialReporter = function(doc) { 4 | this.document = doc || document; 5 | this.suiteDivs = {}; 6 | this.logRunningSpecs = false; 7 | }; 8 | 9 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 10 | var el = document.createElement(type); 11 | 12 | for (var i = 2; i < arguments.length; i++) { 13 | var child = arguments[i]; 14 | 15 | if (typeof child === 'string') { 16 | el.appendChild(document.createTextNode(child)); 17 | } else { 18 | if (child) { el.appendChild(child); } 19 | } 20 | } 21 | 22 | for (var attr in attrs) { 23 | if (attr == "className") { 24 | el[attr] = attrs[attr]; 25 | } else { 26 | el.setAttribute(attr, attrs[attr]); 27 | } 28 | } 29 | 30 | return el; 31 | }; 32 | 33 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 34 | var showPassed, showSkipped; 35 | 36 | this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, 37 | this.createDom('div', { className: 'banner' }, 38 | this.createDom('div', { className: 'logo' }, 39 | this.createDom('span', { className: 'title' }, "Jasmine"), 40 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 41 | this.createDom('div', { className: 'options' }, 42 | "Show ", 43 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 44 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 45 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 46 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 47 | ) 48 | ), 49 | 50 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 51 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 52 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 53 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 54 | ); 55 | 56 | this.document.body.appendChild(this.outerDiv); 57 | 58 | var suites = runner.suites(); 59 | for (var i = 0; i < suites.length; i++) { 60 | var suite = suites[i]; 61 | var suiteDiv = this.createDom('div', { className: 'suite' }, 62 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 63 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 64 | this.suiteDivs[suite.id] = suiteDiv; 65 | var parentDiv = this.outerDiv; 66 | if (suite.parentSuite) { 67 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 68 | } 69 | parentDiv.appendChild(suiteDiv); 70 | } 71 | 72 | this.startedAt = new Date(); 73 | 74 | var self = this; 75 | showPassed.onclick = function(evt) { 76 | if (showPassed.checked) { 77 | self.outerDiv.className += ' show-passed'; 78 | } else { 79 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 80 | } 81 | }; 82 | 83 | showSkipped.onclick = function(evt) { 84 | if (showSkipped.checked) { 85 | self.outerDiv.className += ' show-skipped'; 86 | } else { 87 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 88 | } 89 | }; 90 | }; 91 | 92 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 93 | var results = runner.results(); 94 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 95 | this.runnerDiv.setAttribute("class", className); 96 | //do it twice for IE 97 | this.runnerDiv.setAttribute("className", className); 98 | var specs = runner.specs(); 99 | var specCount = 0; 100 | for (var i = 0; i < specs.length; i++) { 101 | if (this.specFilter(specs[i])) { 102 | specCount++; 103 | } 104 | } 105 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 106 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 107 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 108 | 109 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 110 | }; 111 | 112 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 113 | var results = suite.results(); 114 | var status = results.passed() ? 'passed' : 'failed'; 115 | if (results.totalCount === 0) { // todo: change this to check results.skipped 116 | status = 'skipped'; 117 | } 118 | this.suiteDivs[suite.id].className += " " + status; 119 | }; 120 | 121 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 122 | if (this.logRunningSpecs) { 123 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 124 | } 125 | }; 126 | 127 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 128 | var results = spec.results(); 129 | var status = results.passed() ? 'passed' : 'failed'; 130 | if (results.skipped) { 131 | status = 'skipped'; 132 | } 133 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 134 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 135 | this.createDom('a', { 136 | className: 'description', 137 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 138 | title: spec.getFullName() 139 | }, spec.description)); 140 | 141 | 142 | var resultItems = results.getItems(); 143 | var messagesDiv = this.createDom('div', { className: 'messages' }); 144 | for (var i = 0; i < resultItems.length; i++) { 145 | var result = resultItems[i]; 146 | 147 | if (result.type == 'log') { 148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 149 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 150 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 151 | 152 | if (result.trace.stack) { 153 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 154 | } 155 | } 156 | } 157 | 158 | if (messagesDiv.childNodes.length > 0) { 159 | specDiv.appendChild(messagesDiv); 160 | } 161 | 162 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 163 | }; 164 | 165 | jasmine.TrivialReporter.prototype.log = function() { 166 | var console = jasmine.getGlobal().console; 167 | if (console && console.log) { 168 | if (console.log.apply) { 169 | console.log.apply(console, arguments); 170 | } else { 171 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 172 | } 173 | } 174 | }; 175 | 176 | jasmine.TrivialReporter.prototype.getLocation = function() { 177 | return this.document.location; 178 | }; 179 | 180 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 181 | var paramMap = {}; 182 | var params = this.getLocation().search.substring(1).split('&'); 183 | for (var i = 0; i < params.length; i++) { 184 | var p = params[i].split('='); 185 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 186 | } 187 | 188 | if (!paramMap.spec) { 189 | return true; 190 | } 191 | return spec.getFullName().indexOf(paramMap.spec) === 0; 192 | }; 193 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | PushPlugin 10 | Bob Easterday 11 | 12 | 13 | This plugin allows your application to receive push notifications on Android, iOS, WP8 and Windows8 devices. 14 | Android uses Google Cloud Messaging. 15 | iOS uses Apple APNS Notifications. 16 | WP8 uses Microsoft MPNS Notifications. 17 | Windows8 uses Microsoft WNS Notifications. 18 | 19 | 20 | MIT 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /src/wp8/PushPlugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Runtime.Serialization; 6 | using System.Windows; 7 | using Microsoft.Phone.Controls; 8 | using Microsoft.Phone.Notification; 9 | using Microsoft.Phone.Shell; 10 | using Newtonsoft.Json; 11 | 12 | namespace WPCordovaClassLib.Cordova.Commands 13 | { 14 | public class PushPlugin : BaseCommand 15 | { 16 | private const string InvalidRegistrationError = "Unable to open a channel with the specified name. The most probable cause is that you have already registered a channel with a different name. Call unregister(old-channel-name) or uninstall and redeploy your application."; 17 | private const string MissingChannelError = "Couldn't find a channel with the specified name."; 18 | private Options pushOptions; 19 | 20 | public void register(string options) 21 | { 22 | if (!TryDeserializeOptions(options, out this.pushOptions)) 23 | { 24 | this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); 25 | return; 26 | } 27 | 28 | var pushChannel = HttpNotificationChannel.Find(this.pushOptions.ChannelName); 29 | if (pushChannel == null) 30 | { 31 | pushChannel = new HttpNotificationChannel(this.pushOptions.ChannelName); 32 | SubscribePushChannelEvents(pushChannel); 33 | try 34 | { 35 | var count = 0; 36 | while(count < 3 && pushChannel.ChannelUri == null) 37 | { 38 | pushChannel.Open(); 39 | count++; 40 | } 41 | } 42 | catch (InvalidOperationException) 43 | { 44 | this.DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, InvalidRegistrationError)); 45 | return; 46 | } 47 | 48 | pushChannel.BindToShellToast(); 49 | pushChannel.BindToShellTile(); 50 | } 51 | else 52 | { 53 | SubscribePushChannelEvents(pushChannel); 54 | } 55 | var result = new RegisterResult 56 | { 57 | ChannelName = this.pushOptions.ChannelName, 58 | Uri = pushChannel.ChannelUri == null ? string.Empty : pushChannel.ChannelUri.ToString() 59 | }; 60 | 61 | this.DispatchCommandResult(new PluginResult(PluginResult.Status.OK, result)); 62 | } 63 | 64 | public void unregister(string options) 65 | { 66 | Options unregisterOptions; 67 | if (!TryDeserializeOptions(options, out unregisterOptions)) 68 | { 69 | this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); 70 | return; 71 | } 72 | var pushChannel = HttpNotificationChannel.Find(unregisterOptions.ChannelName); 73 | if (pushChannel != null) 74 | { 75 | pushChannel.UnbindToShellTile(); 76 | pushChannel.UnbindToShellToast(); 77 | pushChannel.Close(); 78 | pushChannel.Dispose(); 79 | this.DispatchCommandResult(new PluginResult(PluginResult.Status.OK, "Channel " + unregisterOptions.ChannelName + " is closed!")); 80 | } 81 | else 82 | { 83 | this.DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, MissingChannelError)); 84 | } 85 | } 86 | 87 | public void showToastNotification(string options) 88 | { 89 | ShellToast toast; 90 | if (!TryDeserializeOptions(options, out toast)) 91 | { 92 | this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); 93 | return; 94 | } 95 | 96 | Deployment.Current.Dispatcher.BeginInvoke(toast.Show); 97 | } 98 | 99 | void PushChannel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e) 100 | { 101 | // return uri to js 102 | var result = new RegisterResult 103 | { 104 | ChannelName = this.pushOptions.ChannelName, 105 | Uri = e.ChannelUri.ToString() 106 | }; 107 | this.ExecuteCallback(this.pushOptions.UriChangedCallback, JsonConvert.SerializeObject(result)); 108 | } 109 | 110 | void PushChannel_ErrorOccurred(object sender, NotificationChannelErrorEventArgs e) 111 | { 112 | // call error handler and return uri 113 | var err = new RegisterError 114 | { 115 | Code = e.ErrorCode.ToString(), 116 | Message = e.Message 117 | }; 118 | this.ExecuteCallback(this.pushOptions.ErrorCallback, JsonConvert.SerializeObject(err)); 119 | } 120 | 121 | void PushChannel_ShellToastNotificationReceived(object sender, NotificationEventArgs e) 122 | { 123 | var toast = new PushNotification 124 | { 125 | Type = "toast" 126 | }; 127 | 128 | foreach (var item in e.Collection) 129 | { 130 | toast.JsonContent.Add(item.Key, item.Value); 131 | } 132 | 133 | this.ExecuteCallback(this.pushOptions.NotificationCallback, JsonConvert.SerializeObject(toast)); 134 | } 135 | 136 | void PushChannel_HttpNotificationReceived(object sender, HttpNotificationEventArgs e) 137 | { 138 | var raw = new PushNotification 139 | { 140 | Type = "raw" 141 | }; 142 | 143 | using (var reader = new StreamReader(e.Notification.Body)) 144 | { 145 | raw.JsonContent.Add("Body", reader.ReadToEnd()); 146 | } 147 | 148 | this.ExecuteCallback(this.pushOptions.NotificationCallback, JsonConvert.SerializeObject(raw)); 149 | } 150 | 151 | void ExecuteCallback(string callback, string callbackResult) 152 | { 153 | Deployment.Current.Dispatcher.BeginInvoke(() => 154 | { 155 | PhoneApplicationFrame frame; 156 | PhoneApplicationPage page; 157 | CordovaView cView; 158 | 159 | if (TryCast(Application.Current.RootVisual, out frame) && 160 | TryCast(frame.Content, out page) && 161 | TryCast(page.FindName("CordovaView"), out cView)) 162 | { 163 | cView.Browser.Dispatcher.BeginInvoke(() => 164 | { 165 | try 166 | { 167 | cView.Browser.InvokeScript("execScript", callback + "(" + callbackResult + ")"); 168 | } 169 | catch (Exception ex) 170 | { 171 | Debug.WriteLine("ERROR: Exception in InvokeScriptCallback :: " + ex.Message); 172 | } 173 | }); 174 | } 175 | }); 176 | } 177 | 178 | static bool TryDeserializeOptions(string options, out T result) where T : class 179 | { 180 | result = null; 181 | try 182 | { 183 | var args = JsonConvert.DeserializeObject(options); 184 | result = JsonConvert.DeserializeObject(args[0]); 185 | return true; 186 | } 187 | catch 188 | { 189 | return false; 190 | } 191 | } 192 | 193 | static bool TryCast(object obj, out T result) where T : class 194 | { 195 | result = obj as T; 196 | return result != null; 197 | } 198 | 199 | void SubscribePushChannelEvents(HttpNotificationChannel channel) 200 | { 201 | channel.ChannelUriUpdated += new EventHandler(PushChannel_ChannelUriUpdated); 202 | channel.ErrorOccurred += new EventHandler(PushChannel_ErrorOccurred); 203 | channel.ShellToastNotificationReceived += new EventHandler(PushChannel_ShellToastNotificationReceived); 204 | channel.HttpNotificationReceived += new EventHandler(PushChannel_HttpNotificationReceived); 205 | } 206 | 207 | [DataContract] 208 | public class Options 209 | { 210 | [DataMember(Name = "channelName", IsRequired = true)] 211 | public string ChannelName { get; set; } 212 | 213 | [DataMember(Name = "ecb", IsRequired = false)] 214 | public string NotificationCallback { get; set; } 215 | 216 | [DataMember(Name = "errcb", IsRequired = false)] 217 | public string ErrorCallback { get; set; } 218 | 219 | [DataMember(Name = "uccb", IsRequired = false)] 220 | public string UriChangedCallback { get; set; } 221 | } 222 | 223 | [DataContract] 224 | public class RegisterResult 225 | { 226 | [DataMember(Name = "uri", IsRequired = true)] 227 | public string Uri { get; set; } 228 | 229 | [DataMember(Name = "channel", IsRequired = true)] 230 | public string ChannelName { get; set; } 231 | } 232 | 233 | [DataContract] 234 | public class PushNotification 235 | { 236 | public PushNotification() 237 | { 238 | this.JsonContent = new Dictionary(); 239 | } 240 | 241 | [DataMember(Name = "jsonContent", IsRequired = true)] 242 | public IDictionary JsonContent { get; set; } 243 | 244 | [DataMember(Name = "type", IsRequired = true)] 245 | public string Type { get; set; } 246 | } 247 | 248 | [DataContract] 249 | public class RegisterError 250 | { 251 | [DataMember(Name = "code", IsRequired = true)] 252 | public string Code { get; set; } 253 | 254 | [DataMember(Name = "message", IsRequired = true)] 255 | public string Message { get; set; } 256 | } 257 | } 258 | } -------------------------------------------------------------------------------- /src/ios/PushPlugin.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2009-2011 Urban Airship Inc. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binaryform must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided withthe distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE URBAN AIRSHIP INC``AS IS'' AND ANY EXPRESS OR 15 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 16 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 17 | EVENT SHALL URBAN AIRSHIP INC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 18 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 19 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 22 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #import "PushPlugin.h" 27 | 28 | @implementation PushPlugin 29 | 30 | @synthesize notificationMessage; 31 | @synthesize isInline; 32 | 33 | @synthesize callbackId; 34 | @synthesize notificationCallbackId; 35 | @synthesize callback; 36 | 37 | 38 | - (void)unregister:(CDVInvokedUrlCommand*)command; 39 | { 40 | self.callbackId = command.callbackId; 41 | 42 | [[UIApplication sharedApplication] unregisterForRemoteNotifications]; 43 | [self successWithMessage:@"unregistered"]; 44 | } 45 | 46 | - (void)register:(CDVInvokedUrlCommand*)command; 47 | { 48 | self.callbackId = command.callbackId; 49 | 50 | NSMutableDictionary* options = [command.arguments objectAtIndex:0]; 51 | 52 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 53 | UIUserNotificationType UserNotificationTypes = UIUserNotificationTypeNone; 54 | #endif 55 | UIRemoteNotificationType notificationTypes = UIRemoteNotificationTypeNone; 56 | 57 | id badgeArg = [options objectForKey:@"badge"]; 58 | id soundArg = [options objectForKey:@"sound"]; 59 | id alertArg = [options objectForKey:@"alert"]; 60 | 61 | if ([badgeArg isKindOfClass:[NSString class]]) 62 | { 63 | if ([badgeArg isEqualToString:@"true"]) { 64 | notificationTypes |= UIRemoteNotificationTypeBadge; 65 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 66 | UserNotificationTypes |= UIUserNotificationTypeBadge; 67 | #endif 68 | } 69 | } 70 | else if ([badgeArg boolValue]) { 71 | notificationTypes |= UIRemoteNotificationTypeBadge; 72 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 73 | UserNotificationTypes |= UIUserNotificationTypeBadge; 74 | #endif 75 | } 76 | 77 | if ([soundArg isKindOfClass:[NSString class]]) 78 | { 79 | if ([soundArg isEqualToString:@"true"]) { 80 | notificationTypes |= UIRemoteNotificationTypeSound; 81 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 82 | UserNotificationTypes |= UIUserNotificationTypeSound; 83 | #endif 84 | } 85 | } 86 | else if ([soundArg boolValue]) { 87 | notificationTypes |= UIRemoteNotificationTypeSound; 88 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 89 | UserNotificationTypes |= UIUserNotificationTypeSound; 90 | #endif 91 | } 92 | 93 | if ([alertArg isKindOfClass:[NSString class]]) 94 | { 95 | if ([alertArg isEqualToString:@"true"]) { 96 | notificationTypes |= UIRemoteNotificationTypeAlert; 97 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 98 | UserNotificationTypes |= UIUserNotificationTypeAlert; 99 | #endif 100 | } 101 | } 102 | else if ([alertArg boolValue]) { 103 | notificationTypes |= UIRemoteNotificationTypeAlert; 104 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 105 | UserNotificationTypes |= UIUserNotificationTypeAlert; 106 | #endif 107 | } 108 | 109 | notificationTypes |= UIRemoteNotificationTypeNewsstandContentAvailability; 110 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 111 | UserNotificationTypes |= UIUserNotificationActivationModeBackground; 112 | #endif 113 | 114 | self.callback = [options objectForKey:@"ecb"]; 115 | 116 | if (notificationTypes == UIRemoteNotificationTypeNone) 117 | NSLog(@"PushPlugin.register: Push notification type is set to none"); 118 | 119 | isInline = NO; 120 | 121 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 122 | if ([[UIApplication sharedApplication]respondsToSelector:@selector(registerUserNotificationSettings:)]) { 123 | UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UserNotificationTypes categories:nil]; 124 | [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; 125 | [[UIApplication sharedApplication] registerForRemoteNotifications]; 126 | } else { 127 | [[UIApplication sharedApplication] registerForRemoteNotificationTypes:notificationTypes]; 128 | } 129 | #else 130 | [[UIApplication sharedApplication] registerForRemoteNotificationTypes:notificationTypes]; 131 | #endif 132 | 133 | if (notificationMessage) // if there is a pending startup notification 134 | [self notificationReceived]; // go ahead and process it 135 | } 136 | 137 | /* 138 | - (void)isEnabled:(NSMutableArray *)arguments withDict:(NSMutableDictionary *)options { 139 | UIRemoteNotificationType type = [[UIApplication sharedApplication] enabledRemoteNotificationTypes]; 140 | NSString *jsStatement = [NSString stringWithFormat:@"navigator.PushPlugin.isEnabled = %d;", type != UIRemoteNotificationTypeNone]; 141 | NSLog(@"JSStatement %@",jsStatement); 142 | } 143 | */ 144 | 145 | - (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { 146 | 147 | NSMutableDictionary *results = [NSMutableDictionary dictionary]; 148 | NSString *token = [[[[deviceToken description] stringByReplacingOccurrencesOfString:@"<"withString:@""] 149 | stringByReplacingOccurrencesOfString:@">" withString:@""] 150 | stringByReplacingOccurrencesOfString: @" " withString: @""]; 151 | [results setValue:token forKey:@"deviceToken"]; 152 | 153 | #if !TARGET_IPHONE_SIMULATOR 154 | // Get Bundle Info for Remote Registration (handy if you have more than one app) 155 | [results setValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"] forKey:@"appName"]; 156 | [results setValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] forKey:@"appVersion"]; 157 | 158 | // Check what Notifications the user has turned on. We registered for all three, but they may have manually disabled some or all of them. 159 | NSUInteger rntypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes]; 160 | 161 | // Set the defaults to disabled unless we find otherwise... 162 | NSString *pushBadge = @"disabled"; 163 | NSString *pushAlert = @"disabled"; 164 | NSString *pushSound = @"disabled"; 165 | 166 | // Check what Registered Types are turned on. This is a bit tricky since if two are enabled, and one is off, it will return a number 2... not telling you which 167 | // one is actually disabled. So we are literally checking to see if rnTypes matches what is turned on, instead of by number. The "tricky" part is that the 168 | // single notification types will only match if they are the ONLY one enabled. Likewise, when we are checking for a pair of notifications, it will only be 169 | // true if those two notifications are on. This is why the code is written this way 170 | if(rntypes & UIRemoteNotificationTypeBadge){ 171 | pushBadge = @"enabled"; 172 | } 173 | if(rntypes & UIRemoteNotificationTypeAlert) { 174 | pushAlert = @"enabled"; 175 | } 176 | if(rntypes & UIRemoteNotificationTypeSound) { 177 | pushSound = @"enabled"; 178 | } 179 | 180 | [results setValue:pushBadge forKey:@"pushBadge"]; 181 | [results setValue:pushAlert forKey:@"pushAlert"]; 182 | [results setValue:pushSound forKey:@"pushSound"]; 183 | 184 | // Get the users Device Model, Display Name, Token & Version Number 185 | UIDevice *dev = [UIDevice currentDevice]; 186 | [results setValue:dev.name forKey:@"deviceName"]; 187 | [results setValue:dev.model forKey:@"deviceModel"]; 188 | [results setValue:dev.systemVersion forKey:@"deviceSystemVersion"]; 189 | 190 | [self successWithMessage:[NSString stringWithFormat:@"%@", token]]; 191 | #endif 192 | } 193 | 194 | - (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error 195 | { 196 | [self failWithMessage:@"" withError:error]; 197 | } 198 | 199 | - (void)notificationReceived { 200 | NSLog(@"Notification received"); 201 | 202 | if (notificationMessage && self.callback) 203 | { 204 | NSMutableString *jsonStr = [NSMutableString stringWithString:@"{"]; 205 | 206 | [self parseDictionary:notificationMessage intoJSON:jsonStr]; 207 | 208 | if (isInline) 209 | { 210 | [jsonStr appendFormat:@"foreground:\"%d\"", 1]; 211 | isInline = NO; 212 | } 213 | else 214 | [jsonStr appendFormat:@"foreground:\"%d\"", 0]; 215 | 216 | [jsonStr appendString:@"}"]; 217 | 218 | NSLog(@"Msg: %@", jsonStr); 219 | 220 | NSString * jsCallBack = [NSString stringWithFormat:@"%@(%@);", self.callback, jsonStr]; 221 | [self.webView stringByEvaluatingJavaScriptFromString:jsCallBack]; 222 | 223 | self.notificationMessage = nil; 224 | } 225 | } 226 | 227 | // reentrant method to drill down and surface all sub-dictionaries' key/value pairs into the top level json 228 | -(void)parseDictionary:(NSDictionary *)inDictionary intoJSON:(NSMutableString *)jsonString 229 | { 230 | NSArray *keys = [inDictionary allKeys]; 231 | NSString *key; 232 | 233 | for (key in keys) 234 | { 235 | id thisObject = [inDictionary objectForKey:key]; 236 | 237 | if ([thisObject isKindOfClass:[NSDictionary class]]) 238 | [self parseDictionary:thisObject intoJSON:jsonString]; 239 | else if ([thisObject isKindOfClass:[NSString class]]) 240 | [jsonString appendFormat:@"\"%@\":\"%@\",", 241 | key, 242 | [[[[inDictionary objectForKey:key] 243 | stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"] 244 | stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""] 245 | stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"]]; 246 | else { 247 | [jsonString appendFormat:@"\"%@\":\"%@\",", key, [inDictionary objectForKey:key]]; 248 | } 249 | } 250 | } 251 | 252 | - (void)setApplicationIconBadgeNumber:(CDVInvokedUrlCommand *)command { 253 | 254 | self.callbackId = command.callbackId; 255 | 256 | NSMutableDictionary* options = [command.arguments objectAtIndex:0]; 257 | int badge = [[options objectForKey:@"badge"] intValue] ?: 0; 258 | 259 | [[UIApplication sharedApplication] setApplicationIconBadgeNumber:badge]; 260 | 261 | [self successWithMessage:[NSString stringWithFormat:@"app badge count set to %d", badge]]; 262 | } 263 | -(void)successWithMessage:(NSString *)message 264 | { 265 | if (self.callbackId != nil) 266 | { 267 | CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message]; 268 | [self.commandDelegate sendPluginResult:commandResult callbackId:self.callbackId]; 269 | } 270 | } 271 | 272 | -(void)failWithMessage:(NSString *)message withError:(NSError *)error 273 | { 274 | NSString *errorMessage = (error) ? [NSString stringWithFormat:@"%@ - %@", message, [error localizedDescription]] : message; 275 | CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; 276 | 277 | [self.commandDelegate sendPluginResult:commandResult callbackId:self.callbackId]; 278 | } 279 | 280 | @end 281 | -------------------------------------------------------------------------------- /src/amazon/ADMMessageHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.amazon.cordova.plugin; 18 | 19 | import org.apache.cordova.CordovaActivity; 20 | import org.json.JSONObject; 21 | import android.app.Notification; 22 | import android.app.NotificationManager; 23 | import android.app.PendingIntent; 24 | import android.content.Context; 25 | import android.content.Intent; 26 | import android.content.SharedPreferences; 27 | import android.os.Bundle; 28 | import android.text.Html; 29 | import android.text.TextUtils; 30 | import android.util.Log; 31 | import android.app.Notification.Builder; 32 | 33 | import com.amazon.device.messaging.ADMMessageHandlerBase; 34 | import com.amazon.device.messaging.ADMMessageReceiver; 35 | 36 | /** 37 | * The ADMMessageHandler class receives messages sent by ADM via the receiver. 38 | */ 39 | 40 | public class ADMMessageHandler extends ADMMessageHandlerBase { 41 | 42 | private static final String ERROR_EVENT = "error"; 43 | public static final String PUSH_BUNDLE = "pushBundle"; 44 | public static final String ERROR_MSG = "msg"; 45 | private static final String SHOW_MESSAGE_PREF = "showmessageinnotification"; 46 | private static final String DEFAULT_MESSAGE_PREF = "defaultnotificationmessage"; 47 | private static boolean shouldShowOfflineMessage = false; 48 | private static String defaultOfflineMessage = null; 49 | private static final String PREFS_NAME = "PushPluginPrefs"; 50 | private static final String DEFAULT_MESSAGE_TEXT = "You have a new message."; 51 | 52 | // An identifier for ADM notification unique within your application 53 | // It allows you to update the same notification later on 54 | public static final int NOTIFICATION_ID = 519; 55 | static Intent notificationIntent = null; 56 | 57 | /** 58 | * Class constructor. 59 | */ 60 | public ADMMessageHandler() { 61 | super(ADMMessageHandler.class.getName()); 62 | } 63 | 64 | /** 65 | * Class constructor, including the className argument. 66 | * 67 | * @param className 68 | * The name of the class. 69 | */ 70 | public ADMMessageHandler(final String className) { 71 | super(className); 72 | } 73 | 74 | /** 75 | * The Receiver class listens for messages from ADM and forwards them to the ADMMessageHandler class. 76 | */ 77 | public static class Receiver extends ADMMessageReceiver { 78 | public Receiver() { 79 | super(ADMMessageHandler.class); 80 | 81 | } 82 | 83 | // Nothing else is required here; your broadcast receiver automatically 84 | // forwards intents to your service for processing. 85 | } 86 | 87 | /** {@inheritDoc} */ 88 | @Override 89 | protected void onRegistered(final String newRegistrationId) { 90 | // You start the registration process by calling startRegister() in your Main Activity. 91 | // When the registration ID is ready, ADM calls onRegistered() 92 | // on your app. Transmit the passed-in registration ID to your server, so 93 | // your server can send messages to this app instance. onRegistered() is also 94 | // called if your registration ID is rotated or changed for any reason; 95 | // your app should pass the new registration ID to your server if this occurs. 96 | 97 | // we fire the register event in the web app, register handler should 98 | // fire to send the registration ID to your server via a header key/value pair over HTTP.(AJAX) 99 | PushPlugin.sendRegistrationIdWithEvent(PushPlugin.REGISTER_EVENT, 100 | newRegistrationId); 101 | } 102 | 103 | /** {@inheritDoc} */ 104 | @Override 105 | protected void onUnregistered(final String registrationId) { 106 | // If your app is unregistered on this device, inform your server that 107 | // this app instance is no longer a valid target for messages. 108 | PushPlugin.sendRegistrationIdWithEvent(PushPlugin.UNREGISTER_EVENT, 109 | registrationId); 110 | } 111 | 112 | /** {@inheritDoc} */ 113 | @Override 114 | protected void onRegistrationError(final String errorId) { 115 | // You should consider a registration error fatal. In response, your app 116 | // may degrade gracefully, or you may wish to notify the user that this part 117 | // of your app's functionality is not available. 118 | try { 119 | JSONObject json; 120 | json = new JSONObject().put(PushPlugin.EVENT, ERROR_EVENT); 121 | json.put(ADMMessageHandler.ERROR_MSG, errorId); 122 | 123 | PushPlugin.sendJavascript(json); 124 | } catch (Exception e) { 125 | Log.getStackTraceString(e); 126 | } 127 | } 128 | 129 | /** {@inheritDoc} */ 130 | @Override 131 | protected void onMessage(final Intent intent) { 132 | // Extract the message content from the set of extras attached to 133 | // the com.amazon.device.messaging.intent.RECEIVE intent. 134 | 135 | // Extract the payload from the message 136 | Bundle extras = intent.getExtras(); 137 | if (extras != null && (extras.getString(PushPlugin.MESSAGE) != null)) { 138 | // if we are in the foreground, just surface the payload, else post 139 | // it to the statusbar 140 | if (PushPlugin.isInForeground()) { 141 | extras.putBoolean(PushPlugin.FOREGROUND, true); 142 | PushPlugin.sendExtras(extras); 143 | } else { 144 | extras.putBoolean(PushPlugin.FOREGROUND, false); 145 | createNotification(this, extras); 146 | } 147 | } 148 | } 149 | 150 | /** 151 | * Creates a notification when app is not running or is not in foreground. It puts the message info into the Intent 152 | * extra 153 | * 154 | * @param context 155 | * @param extras 156 | */ 157 | public void createNotification(Context context, Bundle extras) { 158 | NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 159 | String appName = getAppName(this); 160 | 161 | // reuse the intent so that we can combine multiple messages into extra 162 | if (notificationIntent == null) { 163 | notificationIntent = new Intent(this, ADMHandlerActivity.class); 164 | } 165 | notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP 166 | | Intent.FLAG_ACTIVITY_CLEAR_TOP); 167 | notificationIntent.putExtra("pushBundle", extras); 168 | 169 | PendingIntent contentIntent = PendingIntent.getActivity(this, 0, 170 | notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); 171 | 172 | final Builder notificationBuilder = new Notification.Builder(context); 173 | notificationBuilder.setSmallIcon(context.getApplicationInfo().icon) 174 | .setWhen(System.currentTimeMillis()) 175 | .setContentIntent(contentIntent); 176 | 177 | if (this.shouldShowMessageInNotification()) { 178 | String message = extras.getString(PushPlugin.MESSAGE); 179 | notificationBuilder.setContentText(Html.fromHtml(message).toString()); 180 | } else { 181 | notificationBuilder.setContentText(this.defaultMessageTextInNotification()); 182 | } 183 | 184 | String title = appName; 185 | notificationBuilder.setContentTitle(title).setTicker(title); 186 | notificationBuilder.setAutoCancel(true); 187 | // Because the ID remains unchanged, the existing notification is updated. 188 | notificationManager.notify((String) appName, NOTIFICATION_ID, 189 | notificationBuilder.getNotification()); 190 | } 191 | 192 | public static void cancelNotification(Context context) { 193 | NotificationManager mNotificationManager = (NotificationManager) context 194 | .getSystemService(Context.NOTIFICATION_SERVICE); 195 | mNotificationManager.cancel((String) getAppName(context), 196 | NOTIFICATION_ID); 197 | } 198 | 199 | private static String getAppName(Context context) { 200 | CharSequence appName = context.getPackageManager().getApplicationLabel( 201 | context.getApplicationInfo()); 202 | return (String) appName; 203 | } 204 | 205 | // clean up the message in the intent 206 | static void cleanupNotificationIntent() { 207 | if (notificationIntent != null) { 208 | Bundle pushBundle = notificationIntent.getExtras().getBundle( 209 | PUSH_BUNDLE); 210 | if (pushBundle != null) { 211 | pushBundle.clear(); 212 | } 213 | 214 | } 215 | } 216 | 217 | public static Bundle getOfflineMessage() { 218 | Bundle pushBundle = null; 219 | if (notificationIntent != null) { 220 | pushBundle = notificationIntent.getExtras().getBundle(PUSH_BUNDLE); 221 | if (pushBundle.isEmpty()) { 222 | pushBundle = null; 223 | } 224 | } 225 | return pushBundle; 226 | } 227 | 228 | /** 229 | * Reads "shownotificationmessage" & "defaultnotificationmessage" config options 230 | * If this is first-time it saves them to sharedPreferences so they can be read 231 | * when app is forced-stop or killed 232 | */ 233 | public static void saveConfigOptions(Context context) { 234 | if (context != null && TextUtils.isEmpty(defaultOfflineMessage)) { 235 | // read config options from config.xml 236 | shouldShowOfflineMessage = ((CordovaActivity) context) 237 | .getBooleanProperty(SHOW_MESSAGE_PREF, false); 238 | defaultOfflineMessage = ((CordovaActivity) context) 239 | .getStringProperty(DEFAULT_MESSAGE_PREF, null); 240 | 241 | // save them to sharedPreferences if necessary 242 | SharedPreferences config = context.getApplicationContext().getSharedPreferences(PREFS_NAME, 0); 243 | SharedPreferences.Editor editor = config.edit(); 244 | editor.putBoolean(SHOW_MESSAGE_PREF, shouldShowOfflineMessage); 245 | editor.putString(DEFAULT_MESSAGE_PREF, defaultOfflineMessage); 246 | // save prefs to disk 247 | editor.commit(); 248 | } 249 | 250 | } 251 | 252 | /** 253 | * Gets "shownotificationmessage" config option 254 | * 255 | * @return returns boolean- true is shownotificationmessage is set to true in config.xml/sharedPreferences otherwise false 256 | */ 257 | private boolean shouldShowMessageInNotification() { 258 | //check if have cached copy of this option 259 | if (TextUtils.isEmpty(defaultOfflineMessage)) { 260 | //need to read it from sharedPreferences 261 | SharedPreferences config = this.getApplicationContext().getSharedPreferences(PREFS_NAME,0); 262 | if (config != null) { 263 | shouldShowOfflineMessage = config.getBoolean(SHOW_MESSAGE_PREF, true); 264 | } 265 | } 266 | return shouldShowOfflineMessage; 267 | } 268 | 269 | /** 270 | * Gets "defaultnotificationmessage" config option 271 | * 272 | * @return returns default message provided by user in cofing.xml/sharedPreferences 273 | */ 274 | private String defaultMessageTextInNotification() { 275 | //check if have cached copy of this option 276 | if (TextUtils.isEmpty(defaultOfflineMessage)) { 277 | SharedPreferences config = this.getApplicationContext().getSharedPreferences(PREFS_NAME,0); 278 | if (config != null) { 279 | defaultOfflineMessage = config.getString(DEFAULT_MESSAGE_PREF, DEFAULT_MESSAGE_TEXT); 280 | } 281 | } 282 | return defaultOfflineMessage; 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /www/blackberry10/PushPluginProxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Licensed to the Apache Software Foundation (ASF) under one 4 | * or more contributor license agreements. See the NOTICE file 5 | * distributed with this work for additional information 6 | * regarding copyright ownership. The ASF licenses this file 7 | * to you under the Apache License, Version 2.0 (the 8 | * "License"); you may not use this file except in compliance 9 | * with the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, 14 | * software distributed under the License is distributed on an 15 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | * KIND, either express or implied. See the License for the 17 | * specific language governing permissions and limitations 18 | * under the License. 19 | * 20 | */ 21 | 22 | var pushServiceObj, 23 | ecb; 24 | 25 | function createChannel(success, error) { 26 | if (pushServiceObj) { 27 | pushServiceObj.createChannel(function(result, token) { 28 | if (result == blackberry.push.PushService.SUCCESS) { 29 | if (success) { 30 | success({ 31 | status: result, 32 | token: token 33 | }); 34 | } 35 | } else { 36 | if (result == blackberry.push.PushService.INTERNAL_ERROR) { 37 | error("Error: An internal error occurred during the create channel. Try registering again."); 38 | } else if (result == blackberry.push.PushService.CREATE_SESSION_NOT_DONE) { 39 | error("Error: No call to blackberry.push.PushService.create " 40 | + "was done before creating the channel. It usually means a programming error."); 41 | } else if (result == blackberry.push.PushService.MISSING_PORT_FROM_PPG) { 42 | error("Error: A port could not be obtained from the " 43 | + "PPG during the create channel. Try registering again."); 44 | } else if (result == blackberry.push.PushService.INVALID_DEVICE_PIN) { 45 | // This error code only applies to a consumer application using the public/BIS PPG 46 | error("Error: The PPG obtained the device's PIN during " 47 | + "the create channel and considered it invalid. Try registering again."); 48 | } else if (result == blackberry.push.PushService.INVALID_PROVIDER_APPLICATION_ID) { 49 | // This error code only applies to a consumer application using the public/BIS PPG 50 | error("Error: The application ID was considered invalid or missing during the create channel."); 51 | } else if (result == blackberry.push.PushService.INVALID_PPG_SUBSCRIBER_STATE) { 52 | // This error code only applies to a consumer application using the public/BIS PPG 53 | error("Error: The subscriber on the PPG end reached an " 54 | + "invalid state. Report this issue to the BlackBerry support team."); 55 | } else if (result == blackberry.push.PushService.EXPIRED_AUTHENTICATION_TOKEN_PROVIDED_TO_PPG) { 56 | // This error code only applies to a consumer application using the public/BIS PPG 57 | error("Error: An expired authentication token was" 58 | + "passed to the PPG internally during the create channel. Try registering again."); 59 | } else if (result == blackberry.push.PushService.INVALID_AUTHENTICATION_TOKEN_PROVIDED_TO_PPG) { 60 | // This error code only applies to a consumer application using the public/BIS PPG 61 | error("Error: An invalid authentication token was passed " 62 | + "to the PPG internally during the create channel. Report this issue to the BlackBerry support team."); 63 | } else if (result == blackberry.push.PushService.PPG_SUBSCRIBER_LIMIT_REACHED) { 64 | // This error code only applies to a consumer application using the public/BIS PPG 65 | error("Error: Too many devices have already peformed a " 66 | + "create channel for this application ID. Contact BlackBerry to increase the subscription limit for this app."); 67 | } else if (result == blackberry.push.PushService.INVALID_OS_VERSION_OR_DEVICE_MODEL_NUMBER) { 68 | // This error code only applies to a consumer application using the public/BIS PPG 69 | error("Error: This device was found to have an invalid OS " 70 | + " version or device model number during the create channel. Consider updating the OS on the device."); 71 | } else if (result == blackberry.push.PushService.MISSING_PPG_URL) { 72 | // This error code only applies to a consumer application using the public/BIS PPG 73 | error("Error: The PPG URL was considered " 74 | + "invalid or missing during the create channel."); 75 | } else if (result == blackberry.push.PushService.PUSH_TRANSPORT_UNAVAILABLE) { 76 | // This error code only applies to a consumer application using the public/BIS PPG 77 | error("Error: Create channel failed as the push transport " 78 | + "is unavailable. Verify your mobile network and/or Wi-Fi are turned on. If they are on, you will " 79 | + "be notified when the push transport is available again."); 80 | } else if (result == blackberry.push.PushService.PPG_SERVER_ERROR) { 81 | // This error code only applies to a consumer application using the public/BIS PPG 82 | error("Error: Create channel failed as the PPG is " 83 | + "currently returning a server error. You will be notified when the PPG is available again."); 84 | } else if (result == blackberry.push.PushService.MISSING_SUBSCRIPTION_RETURN_CODE_FROM_PPG) { 85 | // This error code only applies to a consumer application using the public/BIS PPG 86 | error("Error: There was an internal issue obtaining " 87 | + "the subscription return code from the PPG during the create channel. Try registering again."); 88 | } else if (result == blackberry.push.PushService.INVALID_PPG_URL) { 89 | // This error code only applies to a consumer application using the public/BIS PPG 90 | error("Error: The PPG URL was considered invalid during the create channel."); 91 | } else { 92 | error("Error: Received error code (" + result + ") when creating channel"); 93 | } 94 | } 95 | 96 | 97 | }); 98 | } 99 | 100 | 101 | } 102 | 103 | function onInvoked(invokeRequest) { 104 | var pushPayload, 105 | pushCallback; 106 | 107 | if (invokeRequest.action && invokeRequest.action == "bb.action.PUSH") { 108 | if (ecb) { 109 | pushCallback = eval(ecb); 110 | 111 | if (typeof pushCallback === "function") { 112 | pushPayload = pushServiceObj.extractPushPayload(invokeRequest); 113 | pushCallback(pushPayload); 114 | } 115 | } 116 | } 117 | } 118 | 119 | module.exports = { 120 | 121 | register: function(success, error, args) { 122 | var ops = args[0], 123 | simChangeCallback = ops.simChangeCallback, 124 | pushTransportReadyCallback = ops.pushTransportReadyCallback, 125 | launchApplicationOnPush = ops.launchApplicationOnPush !== undefined ? ops.launchApplicationOnPush : true; 126 | 127 | ecb = ops.ecb; 128 | 129 | blackberry.push.PushService.create(ops, function(obj) { 130 | pushServiceObj = obj; 131 | 132 | // Add an event listener to handle incoming invokes 133 | document.addEventListener("invoked", onInvoked, false); 134 | pushServiceObj.launchApplicationOnPush(launchApplicationOnPush , function (result) { 135 | if (result != blackberry.push.PushService.SUCCESS ) { 136 | if (result == blackberry.push.PushService.INTERNAL_ERROR) { 137 | error("Error: An internal error occurred while calling launchApplicationOnPush."); 138 | } else if (result == blackberry.push.PushService.CREATE_SESSION_NOT_DONE) { 139 | error("Error: Called launchApplicationOnPush without an " 140 | + "existing session. It usually means a programming error."); 141 | } else { 142 | error("Error: Received error code (" + result + ") after calling launchApplicationOnPush."); 143 | } 144 | } 145 | }); 146 | 147 | createChannel(success, error); 148 | }, function(result) { 149 | if (result == blackberry.push.PushService.INTERNAL_ERROR) { 150 | error("Error: An internal error occurred while calling " 151 | + "blackberry.push.PushService.create. Try restarting the application."); 152 | } else if (result == blackberry.push.PushService.INVALID_PROVIDER_APPLICATION_ID) { 153 | // This error only applies to consumer applications that use a public/BIS PPG 154 | error("Error: Called blackberry.push.PushService.create with a missing " 155 | + "or invalid appId value. It usually means a programming error."); 156 | } else if (result == blackberry.push.PushService.MISSING_INVOKE_TARGET_ID) { 157 | error("Error: Called blackberry.push.PushService.create with a missing " 158 | + "invokeTargetId value. It usually means a programming error."); 159 | } else if (result == blackberry.push.PushService.SESSION_ALREADY_EXISTS) { 160 | error("Error: Called blackberry.push.PushService.create with an appId or " 161 | + "invokeTargetId value that matches another application. It usually means a " 162 | + "programming error."); 163 | } else { 164 | error("Error: Received error code (" + result + ") after " 165 | + "calling blackberry.push.PushService.create."); 166 | } 167 | }, simChangeCallback, pushTransportReadyCallback); 168 | }, 169 | 170 | unregister: function(success, error, args) { 171 | if (pushServiceObj) { 172 | pushServiceObj.destroyChannel(function(result) { 173 | 174 | document.removeEventListener("invoked", onInvoked, false); 175 | 176 | if (result == blackberry.push.PushService.SUCCESS || 177 | result == blackberry.push.PushService.CHANNEL_ALREADY_DESTROYED || 178 | result == blackberry.push.PushService.CHANNEL_ALREADY_DESTROYED_BY_PROVIDER || 179 | result == blackberry.push.PushService.CHANNEL_SUSPENDED_BY_PROVIDER || 180 | result == blackberry.push.PushService.PPG_SUBSCRIBER_NOT_FOUND || 181 | result == blackberry.push.PushService.CREATE_CHANNEL_NOT_DONE) { 182 | 183 | success( { status: result } ); 184 | } else { 185 | if (result == blackberry.push.PushService.INTERNAL_ERROR) { 186 | error("Error: An internal error occurred during " 187 | + "the destroy channel. Try unregistering again."); 188 | } else if (result == blackberry.push.PushService.CREATE_SESSION_NOT_DONE) { 189 | error("Error: No call to blackberry.push.PushService.create " 190 | + "was done before destroying the channel. It usually means a programming error."); 191 | } else if (result == blackberry.push.PushService.INVALID_DEVICE_PIN) { 192 | // This error code only applies to a consumer application using the public/BIS PPG 193 | error("Error: The PPG obtained the device's PIN during " 194 | + "the destroy channel and considered it invalid. Try unregistering again."); 195 | } else if (result == blackberry.push.PushService.INVALID_PROVIDER_APPLICATION_ID) { 196 | // This error code only applies to a consumer application using the public/BIS PPG 197 | error("Error: The application ID was considered invalid or missing during the destroy channel."); 198 | } else if (result == blackberry.push.PushService.INVALID_PPG_SUBSCRIBER_STATE) { 199 | // This error code only applies to a consumer application using the public/BIS PPG 200 | error("Error: The subscriber on the PPG end reached an " 201 | + "invalid state. Report this issue to the BlackBerry support team."); 202 | } else if (result == blackberry.push.PushService.EXPIRED_AUTHENTICATION_TOKEN_PROVIDED_TO_PPG) { 203 | // This error code only applies to a consumer application using the public/BIS PPG 204 | error("Error: An expired authentication token was" 205 | + "passed to the PPG internally during the destroy channel. Try unregistering again."); 206 | } else if (result == blackberry.push.PushService.INVALID_AUTHENTICATION_TOKEN_PROVIDED_TO_PPG) { 207 | // This error code only applies to a consumer application using the public/BIS PPG 208 | error("Error: An invalid authentication token was passed " 209 | + "to the PPG internally during the destroy channel. Report this issue to the BlackBerry support team."); 210 | } else if (result == blackberry.push.PushService.PUSH_TRANSPORT_UNAVAILABLE) { 211 | // This error code only applies to a consumer application using the public/BIS PPG 212 | error("Error: Destroy channel failed as the push transport " 213 | + "is unavailable. Verify your mobile network and/or Wi-Fi are turned on. If they are on, you will " 214 | + "be notified when the push transport is available again."); 215 | } else if (result == blackberry.push.PushService.PPG_SERVER_ERROR) { 216 | // This error code only applies to a consumer application using the public/BIS PPG 217 | error("Error: Destroy channel failed as the PPG is " 218 | + "currently returning a server error. You will be notified when the PPG is available again."); 219 | } else if (result == blackberry.push.PushService.INVALID_PPG_URL) { 220 | // This error code only applies to a consumer application using the public/BIS PPG 221 | error("Error: The PPG URL was considered invalid during the destroy channel."); 222 | } else { 223 | error("Error: Received error code (" + result + ") from the destroy channel."); 224 | } 225 | } 226 | }); 227 | } 228 | } 229 | }; 230 | require("cordova/exec/proxy").add("PushPlugin", module.exports); 231 | -------------------------------------------------------------------------------- /src/amazon/PushPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.amazon.cordova.plugin; 18 | 19 | import org.apache.cordova.CallbackContext; 20 | import org.apache.cordova.CordovaInterface; 21 | import org.apache.cordova.CordovaPlugin; 22 | import org.apache.cordova.CordovaWebView; 23 | import org.apache.cordova.CordovaActivity; 24 | import org.apache.cordova.LOG; 25 | import org.json.JSONArray; 26 | import org.json.JSONException; 27 | import com.amazon.device.messaging.ADM; 28 | import android.app.Activity; 29 | import android.os.Bundle; 30 | import android.text.TextUtils; 31 | import android.util.Log; 32 | 33 | import java.util.Iterator; 34 | 35 | import org.json.JSONObject; 36 | 37 | public class PushPlugin extends CordovaPlugin { 38 | 39 | private static String TAG = "PushPlugin"; 40 | /** 41 | * @uml.property name="adm" 42 | * @uml.associationEnd 43 | */ 44 | private ADM adm = null; 45 | /** 46 | * @uml.property name="activity" 47 | * @uml.associationEnd 48 | */ 49 | private Activity activity = null; 50 | private static CordovaWebView webview = null; 51 | private static String notificationHandlerCallBack; 52 | private static boolean isForeground = false; 53 | private static Bundle gCachedExtras = null; 54 | 55 | 56 | public static final String REGISTER = "register"; 57 | public static final String UNREGISTER = "unregister"; 58 | public static final String REGISTER_EVENT = "registered"; 59 | public static final String UNREGISTER_EVENT = "unregistered"; 60 | public static final String MESSAGE = "message"; 61 | public static final String ECB = "ecb"; 62 | public static final String EVENT = "event"; 63 | public static final String PAYLOAD = "payload"; 64 | public static final String FOREGROUND = "foreground"; 65 | public static final String REG_ID = "regid"; 66 | public static final String COLDSTART = "coldstart"; 67 | 68 | private static final String NON_AMAZON_DEVICE_ERROR = "PushNotifications using Amazon Device Messaging is only supported on Kindle Fire devices (2nd Generation and Later only)."; 69 | private static final String ADM_NOT_SUPPORTED_ERROR = "Amazon Device Messaging is not supported on this device."; 70 | private static final String REGISTER_OPTIONS_NULL = "Register options are not specified."; 71 | private static final String ECB_NOT_SPECIFIED = "ecb(eventcallback) option is not specified in register()."; 72 | private static final String ECB_NAME_NOT_SPECIFIED = "ecb(eventcallback) value is missing in options for register()."; 73 | private static final String REGISTRATION_SUCCESS_RESPONSE = "Registration started..."; 74 | private static final String UNREGISTRATION_SUCCESS_RESPONSE = "Unregistration started..."; 75 | 76 | private static final String MODEL_FIRST_GEN = "Kindle Fire"; 77 | 78 | public enum ADMReadiness { 79 | INITIALIZED, NON_AMAZON_DEVICE, ADM_NOT_SUPPORTED 80 | } 81 | 82 | /** 83 | * Sets the context of the Command. This can then be used to do things like get file paths associated with the 84 | * Activity. 85 | * 86 | * @param cordova 87 | * The context of the main Activity. 88 | * @param webView 89 | * The associated CordovaWebView. 90 | */ 91 | @Override 92 | public void initialize(CordovaInterface cordova, CordovaWebView webView) { 93 | super.initialize(cordova, webView); 94 | // Initialize only for Amazon devices 2nd Generation and later 95 | if (this.isAmazonDevice() && !isFirstGenKindleFireDevice()) { 96 | adm = new ADM(cordova.getActivity()); 97 | activity = (CordovaActivity) cordova.getActivity(); 98 | webview = this.webView; 99 | isForeground = true; 100 | ADMMessageHandler.saveConfigOptions(activity); 101 | } else { 102 | LOG.e(TAG, NON_AMAZON_DEVICE_ERROR); 103 | } 104 | } 105 | 106 | /** 107 | * Checks if current device manufacturer is Amazon by using android.os.Build.MANUFACTURER property 108 | * 109 | * @return returns true for all Kindle Fire OS devices. 110 | */ 111 | private boolean isAmazonDevice() { 112 | String deviceMaker = android.os.Build.MANUFACTURER; 113 | return deviceMaker.equalsIgnoreCase("Amazon"); 114 | } 115 | 116 | /** 117 | * Check if device is First generation Kindle 118 | * 119 | * @return if device is First generation Kindle 120 | */ 121 | private static boolean isFirstGenKindleFireDevice() { 122 | return android.os.Build.MODEL.equals(MODEL_FIRST_GEN); 123 | } 124 | /** 125 | * Checks if ADM is available and supported - could be one of three 1. Non Amazon device, hence no ADM support 2. 126 | * ADM is not supported on this Kindle device (1st generation) 3. ADM is successfully initialized and ready to be 127 | * used 128 | * 129 | * @return returns true for all Kindle Fire OS devices. 130 | */ 131 | public ADMReadiness isPushPluginReady() { 132 | if (adm == null) { 133 | return ADMReadiness.NON_AMAZON_DEVICE; 134 | } else if (!adm.isSupported()) { 135 | return ADMReadiness.ADM_NOT_SUPPORTED; 136 | } 137 | return ADMReadiness.INITIALIZED; 138 | } 139 | 140 | /** 141 | * @see Plugin#execute(String, JSONArray, String) 142 | */ 143 | @Override 144 | public boolean execute(final String request, final JSONArray args, 145 | CallbackContext callbackContext) throws JSONException { 146 | try { 147 | // check ADM readiness 148 | ADMReadiness ready = isPushPluginReady(); 149 | if (ready == ADMReadiness.NON_AMAZON_DEVICE) { 150 | callbackContext.error(NON_AMAZON_DEVICE_ERROR); 151 | return false; 152 | } else if (ready == ADMReadiness.ADM_NOT_SUPPORTED) { 153 | callbackContext.error(ADM_NOT_SUPPORTED_ERROR); 154 | return false; 155 | } else if (callbackContext == null) { 156 | LOG.e(TAG, 157 | "CallbackConext is null. Notification to WebView is not possible. Can not proceed."); 158 | return false; 159 | } 160 | 161 | // Process the request here 162 | if (REGISTER.equals(request)) { 163 | 164 | if (args == null) { 165 | LOG.e(TAG, REGISTER_OPTIONS_NULL); 166 | callbackContext.error(REGISTER_OPTIONS_NULL); 167 | return false; 168 | } 169 | 170 | // parse args to get eventcallback name 171 | if (args.isNull(0)) { 172 | LOG.e(TAG, ECB_NOT_SPECIFIED); 173 | callbackContext.error(ECB_NOT_SPECIFIED); 174 | return false; 175 | } 176 | 177 | JSONObject jo = args.getJSONObject(0); 178 | if (jo.getString("ecb").isEmpty()) { 179 | LOG.e(TAG, ECB_NAME_NOT_SPECIFIED); 180 | callbackContext.error(ECB_NAME_NOT_SPECIFIED); 181 | return false; 182 | } 183 | callbackContext.success(REGISTRATION_SUCCESS_RESPONSE); 184 | notificationHandlerCallBack = jo.getString(ECB); 185 | String regId = adm.getRegistrationId(); 186 | LOG.d(TAG, "regId = " + regId); 187 | if (regId == null) { 188 | adm.startRegister(); 189 | } else { 190 | sendRegistrationIdWithEvent(REGISTER_EVENT, regId); 191 | } 192 | 193 | // see if there are any messages while app was in background and 194 | // launched via app icon 195 | LOG.d(TAG,"checking for offline message.."); 196 | deliverPendingMessageAndCancelNotifiation(); 197 | return true; 198 | 199 | } else if (UNREGISTER.equals(request)) { 200 | adm.startUnregister(); 201 | callbackContext.success(UNREGISTRATION_SUCCESS_RESPONSE); 202 | return true; 203 | } else { 204 | LOG.e(TAG, "Invalid action : " + request); 205 | callbackContext.error("Invalid action : " + request); 206 | return false; 207 | } 208 | } catch (final Exception e) { 209 | callbackContext.error(e.getMessage()); 210 | } 211 | 212 | return false; 213 | } 214 | 215 | /** 216 | * Checks if any bundle extras were cached while app was not running 217 | * 218 | * @return returns tru if cached Bundle is not null otherwise true. 219 | */ 220 | public boolean cachedExtrasAvailable() { 221 | return (gCachedExtras != null); 222 | } 223 | 224 | /** 225 | * Checks if offline message was pending to be delivered from notificationIntent. Sends it to webView(JS) if it is 226 | * and also clears notification from the NotificaitonCenter. 227 | */ 228 | private boolean deliverOfflineMessages() { 229 | LOG.d(TAG,"deliverOfflineMessages()"); 230 | Bundle pushBundle = ADMMessageHandler.getOfflineMessage(); 231 | if (pushBundle != null) { 232 | LOG.d(TAG,"Sending offline message..."); 233 | sendExtras(pushBundle); 234 | ADMMessageHandler.cleanupNotificationIntent(); 235 | return true; 236 | } 237 | return false; 238 | } 239 | 240 | // lifecyle callback to set the isForeground 241 | @Override 242 | public void onPause(boolean multitasking) { 243 | LOG.d(TAG, "onPause"); 244 | super.onPause(multitasking); 245 | isForeground = false; 246 | } 247 | 248 | @Override 249 | public void onResume(boolean multitasking) { 250 | LOG.d(TAG, "onResume"); 251 | super.onResume(multitasking); 252 | isForeground = true; 253 | //Check if there are any offline messages? 254 | deliverPendingMessageAndCancelNotifiation(); 255 | } 256 | 257 | @Override 258 | public void onDestroy() { 259 | LOG.d(TAG, "onDestroy"); 260 | super.onDestroy(); 261 | isForeground = false; 262 | webview = null; 263 | notificationHandlerCallBack = null; 264 | } 265 | 266 | /** 267 | * Indicates if app is in foreground or not. 268 | * 269 | * @return returns true if app is running otherwise false. 270 | */ 271 | public static boolean isInForeground() { 272 | return isForeground; 273 | } 274 | 275 | /** 276 | * Indicates if app is killed or not 277 | * 278 | * @return returns true if app is killed otherwise false. 279 | */ 280 | public static boolean isActive() { 281 | return webview != null; 282 | } 283 | 284 | /** 285 | * Delivers pending/offline messages if any 286 | * 287 | * @return returns true if there were any pending messages otherwise false. 288 | */ 289 | public boolean deliverPendingMessageAndCancelNotifiation() { 290 | boolean delivered = false; 291 | LOG.d(TAG,"deliverPendingMessages()"); 292 | if (cachedExtrasAvailable()) { 293 | LOG.v(TAG, "sending cached extras"); 294 | sendExtras(gCachedExtras); 295 | gCachedExtras = null; 296 | delivered = true; 297 | } else { 298 | delivered = deliverOfflineMessages(); 299 | } 300 | // Clear the notification if any exists 301 | ADMMessageHandler.cancelNotification(activity); 302 | 303 | return delivered; 304 | } 305 | /** 306 | * Sends register/unregiste events to JS 307 | * 308 | * @param String 309 | * - eventName - "register", "unregister" 310 | * @param String 311 | * - valid registrationId 312 | */ 313 | public static void sendRegistrationIdWithEvent(String event, 314 | String registrationId) { 315 | if (TextUtils.isEmpty(event) || TextUtils.isEmpty(registrationId)) { 316 | return; 317 | } 318 | try { 319 | JSONObject json; 320 | json = new JSONObject().put(EVENT, event); 321 | json.put(REG_ID, registrationId); 322 | 323 | sendJavascript(json); 324 | } catch (Exception e) { 325 | Log.getStackTraceString(e); 326 | } 327 | } 328 | 329 | /** 330 | * Sends events to JS using cordova nativeToJS bridge. 331 | * 332 | * @param JSONObject 333 | */ 334 | public static boolean sendJavascript(JSONObject json) { 335 | if (json == null) { 336 | LOG.i(TAG, "JSON object is empty. Nothing to send to JS."); 337 | return true; 338 | } 339 | 340 | if (notificationHandlerCallBack != null && webview != null) { 341 | String jsToSend = "javascript:" + notificationHandlerCallBack + "(" 342 | + json.toString() + ")"; 343 | LOG.v(TAG, "sendJavascript: " + jsToSend); 344 | webview.sendJavascript(jsToSend); 345 | return true; 346 | } 347 | return false; 348 | } 349 | 350 | /* 351 | * Sends the pushbundle extras to the client application. If the client application isn't currently active, it is 352 | * cached for later processing. 353 | */ 354 | public static void sendExtras(Bundle extras) { 355 | if (extras != null) { 356 | if (!sendJavascript(convertBundleToJson(extras))) { 357 | LOG.v(TAG, 358 | "sendExtras: could not send to JS. Caching extras to send at a later time."); 359 | gCachedExtras = extras; 360 | } 361 | } 362 | } 363 | 364 | // serializes a bundle to JSON. 365 | private static JSONObject convertBundleToJson(Bundle extras) { 366 | if (extras == null) { 367 | return null; 368 | } 369 | 370 | try { 371 | JSONObject json; 372 | json = new JSONObject().put(EVENT, MESSAGE); 373 | 374 | JSONObject jsondata = new JSONObject(); 375 | Iterator it = extras.keySet().iterator(); 376 | while (it.hasNext()) { 377 | String key = it.next(); 378 | Object value = extras.get(key); 379 | 380 | // System data from Android 381 | if (key.equals(FOREGROUND)) { 382 | json.put(key, extras.getBoolean(FOREGROUND)); 383 | } else if (key.equals(COLDSTART)) { 384 | json.put(key, extras.getBoolean(COLDSTART)); 385 | } else { 386 | // we encourage put the message content into message value 387 | // when server send out notification 388 | if (key.equals(MESSAGE)) { 389 | json.put(key, value); 390 | } 391 | 392 | if (value instanceof String) { 393 | // Try to figure out if the value is another JSON object 394 | 395 | String strValue = (String) value; 396 | if (strValue.startsWith("{")) { 397 | try { 398 | JSONObject json2 = new JSONObject(strValue); 399 | jsondata.put(key, json2); 400 | } catch (Exception e) { 401 | jsondata.put(key, value); 402 | } 403 | // Try to figure out if the value is another JSON 404 | // array 405 | } else if (strValue.startsWith("[")) { 406 | try { 407 | JSONArray json2 = new JSONArray(strValue); 408 | jsondata.put(key, json2); 409 | } catch (Exception e) { 410 | jsondata.put(key, value); 411 | } 412 | } else { 413 | jsondata.put(key, value); 414 | } 415 | } 416 | } // while 417 | } 418 | json.put(PAYLOAD, jsondata); 419 | LOG.v(TAG, "extrasToJSON: " + json.toString()); 420 | 421 | return json; 422 | } catch (JSONException e) { 423 | LOG.e(TAG, "extrasToJSON: JSON exception"); 424 | } 425 | return null; 426 | } 427 | 428 | } 429 | 430 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [DEPRECATED] Cordova Push Notifications Plugin for Android, iOS, WP8, Windows8, BlackBerry 10 and Amazon Fire OS 2 | 3 | 4 | ### _This plugin is deprecated, i.e. it is no longer maintained. Going forward additional features and bug fixes will be added to the new [phonegap-plugin-push](https://github.com/phonegap/phonegap-plugin-push) repository._ 5 | 6 | 7 | ## DESCRIPTION 8 | 9 | This plugin is for use with [Cordova](http://incubator.apache.org/cordova/), and allows your application to receive push notifications on Amazon Fire OS, Android, iOS, Windows Phone and Windows8 devices. 10 | * The Amazon Fire OS implementation uses [Amazon's ADM(Amazon Device Messaging) service](https://developer.amazon.com/sdk/adm.html). 11 | * The Android implementation uses [Google's GCM (Google Cloud Messaging) service](http://developer.android.com/guide/google/gcm/index.html). 12 | * The BlackBerry 10 version uses [blackberry push service](https://developer.blackberry.com/devzone/develop/platform_services/push_service_overview.html). 13 | * The iOS version is based on [Apple APNS Notifications](http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html). 14 | * The WP8 implementation is based on [MPNS](http://msdn.microsoft.com/en-us/library/windowsphone/develop/ff402558(v=vs.105).aspx). 15 | * Windows8 uses [Microsoft WNS Notifications](http://msdn.microsoft.com/en-us/library/windows/apps/hh913756.aspx). 16 | 17 | **Important** - Push notifications are intended for real devices. They are not tested for WP8 Emulator. The registration process will fail on the iOS simulator. Notifications can be made to work on the Android Emulator, however doing so requires installation of some helper libraries, as outlined [here,](http://www.androidhive.info/2012/10/android-push-notifications-using-google-cloud-messaging-gcm-php-and-mysql/) under the section titled "Installing helper libraries and setting up the Emulator". 18 | 19 | ### Contents 20 | 21 | - [LICENSE](#license) 22 | - [Manual Installation](#manual_installation) 23 | - [Automatic Installation](#automatic_installation) 24 | - [Plugin API](#plugin_api) 25 | - [Testing](#testing) 26 | - [Additional Resources](#additional_resources) 27 | - [Acknowledgments](#acknowledgments) 28 | 29 | 30 | 31 | ## LICENSE 32 | 33 | The MIT License 34 | 35 | Copyright (c) 2012 Adobe Systems, inc. 36 | portions Copyright (c) 2012 Olivier Louvignes 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining a copy 39 | of this software and associated documentation files (the "Software"), to deal 40 | in the Software without restriction, including without limitation the rights 41 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 42 | copies of the Software, and to permit persons to whom the Software is 43 | furnished to do so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be included in 46 | all copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 49 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 50 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 51 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 52 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 53 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 54 | THE SOFTWARE. 55 | 56 | 57 | 58 | 59 | ##Manual Installation 60 | 61 | ### Manual Installation for Amazon Fire OS 62 | 63 | 1) Install the ADM library 64 | 65 | - Download the [Amazon Mobile App SDK](https://developer.amazon.com/public/resources/development-tools/sdk) and unzip. 66 | - Create a folder called `ext_libs` in your project's `platforms/amazon-fireos` folder. 67 | - Copy `amazon-device-messaging-x.x.x.jar` into the `ext_libs` folder above. 68 | - Create a new text file called `ant.properties` in the `platforms/amazon-fireos` folder, and add a java.compiler.classpath entry pointing at the library. For example: `java.compiler.classpath=./ext_libs/amazon-device-messaging-1.0.1.jar` 69 | 70 | 71 | 2) Copy the contents of the Push Notification Plugin's `src/amazon/com` folder to your project's `platforms/amazon-fireos/src/com` folder. 72 | 73 | 3) Modify your `AndroidManifest.xml` and add the following lines to your manifest tag: 74 | 75 | ```xml 76 | 77 | 78 | 79 | 80 | ``` 81 | 82 | 4) Modify your `AndroidManifest.xml` and add the following **activity**, **receiver** and **service** tags to your **application** section. 83 | 84 | ```xml 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | ``` 96 | 97 | 5) If you are using Cordova 3.4.0 or earlier, modify your `AndroidManifest.xml` and add "amazon" XML namespace to tag: 98 | 99 | ```xml 100 | xmlns:amazon="http://schemas.amazon.com/apk/res/android" 101 | ``` 102 | 103 | 6) Modify `res/xml/config.xml` to add a reference to PushPlugin: 104 | 105 | ```xml 106 | 107 | 108 | 109 | ``` 110 | 111 | 7) Modify `res/xml/config.xml` to set config options to let Cordova know whether to display ADM message in the notification center or not. If not, provide the default message. By default, message will be visible in the notification. These config options are used if message arrives and app is not in the foreground (either killed or running in the background). 112 | 113 | ```xml 114 | 115 | 116 | ``` 117 | 118 | 8) Create an file called `api_key.txt` in the platforms/amazon-fireos/assets folder containing the API Key from the "Security Profile Android/Kindle Settings" tab on the [Amazon Developer Portal](https://developer.amazon.com/sdk/adm.html). For detailed steps on how to register for ADM please refer to section below: [Registering your app for Amazon Device Messaging (ADM)](#registering_for_adm) 119 | 120 | 121 | 122 | ### Manual Installation for Android 123 | 124 | 1) Install GCM support files 125 | 126 | - copy the contents of `src/android/com/` to your project's `src/com/` folder. 127 | - copy the contents of `libs/` to your `libs/` folder. 128 | - copy `{android_sdk_path}/extras/android/support/v13/android-support-v13.jar` to your `libs/` folder. 129 | 130 | The final hierarchy will likely look something like this: 131 | 132 | {project_folder} 133 | libs 134 | gcm.jar 135 | android-support-v13.jar 136 | cordova-3.4.0.jar 137 | src 138 | com 139 | plugin 140 | gcm 141 | CordovaGCMBroadcastReceiver.java 142 | GCMIntentService.java 143 | PushHandlerActivity.java 144 | PushPlugin.java 145 | {company_name} 146 | {intent_name} 147 | {intent_name}.java 148 | 149 | 2) Modify your `AndroidManifest.xml` and add the following lines to your manifest tag: 150 | 151 | ```xml 152 | 153 | 154 | 155 | 156 | 157 | 158 | ``` 159 | 160 | 3) Modify your `AndroidManifest.xml` and add the following **activity**, **receiver** and **service** tags to your **application** section. (See the Sample_AndroidManifest.xml file in the Example folder.) 161 | 162 | ```xml 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | ``` 173 | 174 | 4) Check that the launch mode for the main Cordova Activity is one of the **[singleXXX](http://developer.android.com/guide/topics/manifest/activity-element.html#lmode)** options in **AndroidManifest.xml**. 175 | 176 | ```xml 177 | 178 | ``` 179 | 180 | Otherwise a new activity instance, with a new webview, will be created when activating the notifications. 181 | 182 | 5) Modify your `res/xml/config.xml` to include the following line in order to tell Cordova to include this plugin and where it can be found: (See the Sample_config.xml file in the Example folder) 183 | 184 | ```xml 185 | 186 | 187 | 188 | ``` 189 | 190 | 6) Add the `PushNotification.js` script to your assets/www folder (or javascripts folder, wherever you want really) and reference it in your main index.html file. This file's usage is described in the **Plugin API** section below. 191 | 192 | ```html 193 | 194 | ``` 195 | 196 | ### Manual Installation for iOS 197 | 198 | Copy the following files to your project's Plugins folder: 199 | 200 | ``` 201 | AppDelegate+notification.h 202 | AppDelegate+notification.m 203 | PushPlugin.h 204 | PushPlugin.m 205 | ``` 206 | 207 | Add a reference for this plugin to the plugins section in `config.xml`: 208 | 209 | ```xml 210 | 211 | 212 | 213 | ``` 214 | 215 | Add the `PushNotification.js` script to your assets/www folder (or javascripts folder, wherever you want really) and reference it in your main index.html file. 216 | 217 | ```html 218 | 219 | ``` 220 | 221 | ### Manual Installation for WP8 222 | 223 | Copy the following files to your project's Commands folder and add it to the VS project: 224 | 225 | ``` 226 | PushPlugin.cs 227 | ``` 228 | 229 | Add a reference to this plugin in `config.xml`: 230 | 231 | ```xml 232 | 233 | 234 | 235 | ``` 236 | 237 | Add the `PushNotification.js` script to your assets/www folder (or javascripts folder, wherever you want really) and reference it in your main index.html file. 238 | ```html 239 | 240 | ``` 241 | 242 | Do not forget to reference the `cordova.js` as well. 243 | 244 | 245 | 246 | In your Visual Studio project add reference to the `Newtonsoft.Json.dll` as well - it is needed for serialization and deserialization of the objects. 247 | 248 | Also you need to enable the **"ID_CAP_PUSH_NOTIFICATION"** capability in **Properties->WMAppManifest.xml** of your project. 249 | 250 | ### Manual Installation for Windows8 251 | 252 | Add the `src/windows8/PushPluginProxy.js` script to your `www` folder and reference it in your main index.html file. 253 | ```html 254 | 255 | ``` 256 | 257 | Do not forget to reference the `cordova.js` as well. 258 | 259 | 260 | 261 | To receive toast notifications additional [toastCapable=’true’](http://msdn.microsoft.com/en-us/library/windows/apps/hh781238.aspx) attribute is required to be manually added in manifest file. 262 | 263 | 264 | 265 | ##Automatic Installation 266 | 267 | Below are the methods for installing this plugin automatically using command line tools. For additional info, take a look at the [Plugman Documentation](https://github.com/apache/cordova-plugman/blob/master/README.md) and [Cordova Plugin Specification](https://github.com/alunny/cordova-plugin-spec). 268 | 269 | **Note:** For each service supported - ADM, APNS, GCM or MPNS - you may need to download the SDK and other support files. See the [Manual Installation](#manual_installation) instructions below for more details about each platform. 270 | 271 | ### Cordova 272 | 273 | The plugin can be installed via the Cordova command line interface: 274 | 275 | 1) Navigate to the root folder for your phonegap project. 2) Run the command. 276 | 277 | ```sh 278 | cordova plugin add https://github.com/phonegap-build/PushPlugin.git 279 | ``` 280 | 281 | ### Phonegap 282 | 283 | The plugin can be installed using the Phonegap command line interface: 284 | 285 | 1) Navigate to the root folder for your phonegap project. 2) Run the command. 286 | 287 | ```sh 288 | phonegap local plugin add https://github.com/phonegap-build/PushPlugin.git 289 | ``` 290 | 291 | ### Plugman 292 | 293 | The plugin is based on [plugman](https://github.com/apache/cordova-plugman) and can be installed using the Plugman command line interface: 294 | 295 | ```sh 296 | plugman install --platform [PLATFORM] --project [TARGET-PATH] --plugin [PLUGIN-PATH] 297 | 298 | where 299 | [PLATFORM] = ios, amazon-fireos, android, wp8, windows8 or blackberry10 300 | [TARGET-PATH] = path to folder containing your phonegap project 301 | [PLUGIN-PATH] = path to folder containing this plugin 302 | ``` 303 | 304 | 305 | 306 | 307 | 308 | ## Plugin API 309 | 310 | In the plugin `examples` folder you will find a sample implementation showing how to interact with the PushPlugin. Modify it to suit your needs. 311 | 312 | #### pushNotification 313 | The plugin instance variable. 314 | 315 | ```js 316 | var pushNotification; 317 | 318 | document.addEventListener("deviceready", function(){ 319 | pushNotification = window.plugins.pushNotification; 320 | ... 321 | }); 322 | ``` 323 | 324 | #### register 325 | To be called as soon as the device becomes ready. 326 | 327 | ```js 328 | $("#app-status-ul").append('
  • registering ' + device.platform + '
  • '); 329 | if ( device.platform == 'android' || device.platform == 'Android' || device.platform == "amazon-fireos" ){ 330 | pushNotification.register( 331 | successHandler, 332 | errorHandler, 333 | { 334 | "senderID":"replace_with_sender_id", 335 | "ecb":"onNotification" 336 | }); 337 | } else if ( device.platform == 'blackberry10'){ 338 | pushNotification.register( 339 | successHandler, 340 | errorHandler, 341 | { 342 | invokeTargetId : "replace_with_invoke_target_id", 343 | appId: "replace_with_app_id", 344 | ppgUrl:"replace_with_ppg_url", //remove for BES pushes 345 | ecb: "pushNotificationHandler", 346 | simChangeCallback: replace_with_simChange_callback, 347 | pushTransportReadyCallback: replace_with_pushTransportReady_callback, 348 | launchApplicationOnPush: true 349 | }); 350 | } else { 351 | pushNotification.register( 352 | tokenHandler, 353 | errorHandler, 354 | { 355 | "badge":"true", 356 | "sound":"true", 357 | "alert":"true", 358 | "ecb":"onNotificationAPN" 359 | }); 360 | } 361 | ``` 362 | 363 | On success, you will get a call to tokenHandler (iOS), onNotification (Android and Amazon Fire OS), onNotificationWP8 (WP8) or successHandler (Blackberry10), allowing you to obtain the device token or registration ID, or push channel name and Uri respectively. Those values will typically get posted to your intermediary push server so it knows who it can send notifications to. 364 | 365 | ***Note*** 366 | 367 | - **Amazon Fire OS**: "ecb" MUST be provided in order to get callback notifications. If you have not already registered with Amazon developer portal,you will have to obtain credentials and api_key for your app. This is described more in detail in the [Registering your app for Amazon Device Messaging (ADM)](#registering_for_adm) section below. 368 | 369 | - **Android**: If you have not already done so, you'll need to set up a Google API project, to generate your senderID. [Follow these steps](http://developer.android.com/guide/google/gcm/gs.html) to do so. This is described more fully in the **Testing** section below. In this example, be sure and substitute your own senderID. Get your senderID by signing into to your [google dashboard](https://code.google.com/apis/console/). The senderID is found at *Overview->Dashboard->Project Number*. 370 | 371 | - **BlackBerry10**: "ecb" MUST be provided to get notified of incoming push notifications. Also note, if doing a public consumer (BIS) push, you need to manually add the _sys_use_consumer_push permission to config.xml. `_sys_use_consumer_push`. In order to receieve notifications, an invoke target must be [setup](http://developer.blackberry.com/html5/documentation/v2_1/rim_invoke-target.html) for push. See [BlackBerry Push Service](http://developer.blackberry.com/html5/apis/v2_1/blackberry.push.pushservice.html) for additional information about blackberry push options. 372 | 373 | 374 | 375 | 376 | #### successHandler 377 | Called when a plugin method returns without error 378 | 379 | ```js 380 | // result contains any message sent from the plugin call 381 | function successHandler (result) { 382 | alert('result = ' + result); 383 | } 384 | ``` 385 | 386 | #### errorHandler 387 | Called when the plugin returns an error 388 | 389 | ```js 390 | // result contains any error description text returned from the plugin call 391 | function errorHandler (error) { 392 | alert('error = ' + error); 393 | } 394 | ``` 395 | 396 | #### ecb (Amazon Fire OS, Android and iOS) 397 | Event callback that gets called when your device receives a notification 398 | 399 | ```js 400 | // iOS 401 | function onNotificationAPN (event) { 402 | if ( event.alert ) 403 | { 404 | navigator.notification.alert(event.alert); 405 | } 406 | 407 | if ( event.sound ) 408 | { 409 | var snd = new Media(event.sound); 410 | snd.play(); 411 | } 412 | 413 | if ( event.badge ) 414 | { 415 | pushNotification.setApplicationIconBadgeNumber(successHandler, errorHandler, event.badge); 416 | } 417 | } 418 | ``` 419 | 420 | ```js 421 | // Android and Amazon Fire OS 422 | function onNotification(e) { 423 | $("#app-status-ul").append('
  • EVENT -> RECEIVED:' + e.event + '
  • '); 424 | 425 | switch( e.event ) 426 | { 427 | case 'registered': 428 | if ( e.regid.length > 0 ) 429 | { 430 | $("#app-status-ul").append('
  • REGISTERED -> REGID:' + e.regid + "
  • "); 431 | // Your GCM push server needs to know the regID before it can push to this device 432 | // here is where you might want to send it the regID for later use. 433 | console.log("regID = " + e.regid); 434 | } 435 | break; 436 | 437 | case 'message': 438 | // if this flag is set, this notification happened while we were in the foreground. 439 | // you might want to play a sound to get the user's attention, throw up a dialog, etc. 440 | if ( e.foreground ) 441 | { 442 | $("#app-status-ul").append('
  • --INLINE NOTIFICATION--' + '
  • '); 443 | 444 | // on Android soundname is outside the payload. 445 | // On Amazon FireOS all custom attributes are contained within payload 446 | var soundfile = e.soundname || e.payload.sound; 447 | // if the notification contains a soundname, play it. 448 | var my_media = new Media("/android_asset/www/"+ soundfile); 449 | my_media.play(); 450 | } 451 | else 452 | { // otherwise we were launched because the user touched a notification in the notification tray. 453 | if ( e.coldstart ) 454 | { 455 | $("#app-status-ul").append('
  • --COLDSTART NOTIFICATION--' + '
  • '); 456 | } 457 | else 458 | { 459 | $("#app-status-ul").append('
  • --BACKGROUND NOTIFICATION--' + '
  • '); 460 | } 461 | } 462 | 463 | $("#app-status-ul").append('
  • MESSAGE -> MSG: ' + e.payload.message + '
  • '); 464 | //Only works for GCM 465 | $("#app-status-ul").append('
  • MESSAGE -> MSGCNT: ' + e.payload.msgcnt + '
  • '); 466 | //Only works on Amazon Fire OS 467 | $status.append('
  • MESSAGE -> TIME: ' + e.payload.timeStamp + '
  • '); 468 | break; 469 | 470 | case 'error': 471 | $("#app-status-ul").append('
  • ERROR -> MSG:' + e.msg + '
  • '); 472 | break; 473 | 474 | default: 475 | $("#app-status-ul").append('
  • EVENT -> Unknown, an event was received and we do not know what it is
  • '); 476 | break; 477 | } 478 | } 479 | ``` 480 | 481 | ```js 482 | // BlackBerry10 483 | function pushNotificationHandler(pushpayload) { 484 | var contentType = pushpayload.headers["Content-Type"], 485 | id = pushpayload.id, 486 | data = pushpayload.data;//blob 487 | 488 | // If an acknowledgement of the push is required (that is, the push was sent as a confirmed push 489 | // - which is equivalent terminology to the push being sent with application level reliability), 490 | // then you must either accept the push or reject the push 491 | if (pushpayload.isAcknowledgeRequired) { 492 | // In our sample, we always accept the push, but situations might arise where an application 493 | // might want to reject the push (for example, after looking at the headers that came with the push 494 | // or the data of the push, we might decide that the push received did not match what we expected 495 | // and so we might want to reject it) 496 | pushpayload.acknowledge(true); 497 | } 498 | }; 499 | ``` 500 | 501 | Looking at the above message handling code for Android/Amazon Fire OS, a few things bear explanation. Your app may receive a notification while it is active (INLINE). If you background the app by hitting the Home button on your device, you may later receive a status bar notification. Selecting that notification from the status will bring your app to the front and allow you to process the notification (BACKGROUND). Finally, should you completely exit the app by hitting the back button from the home page, you may still receive a notification. Touching that notification in the notification tray will relaunch your app and allow you to process the notification (COLDSTART). In this case the **coldstart** flag will be set on the incoming event. You can look at the **foreground** flag on the event to determine whether you are processing a background or an in-line notification. You may choose, for example to play a sound or show a dialog only for inline or coldstart notifications since the user has already been alerted via the status bar. 502 | 503 | For Amazon Fire OS, offline message can also be received when app is launched via carousel or by tapping on app icon from apps. In either case once app delivers the offline message to JS, notification will be cleared. 504 | 505 | Since the Android and Amazon Fire OS notification data models are much more flexible than that of iOS, there may be additional elements beyond **message**. You can access those elements and any additional ones via the **payload** element. This means that if your data model should change in the future, there will be no need to change and recompile the plugin. 506 | 507 | 508 | 509 | #### senderID (Android only) 510 | This is the Google project ID you need to obtain by [registering your application](http://developer.android.com/guide/google/gcm/gs.html) for GCM 511 | 512 | 513 | #### tokenHandler (iOS only) 514 | Called when the device has registered with a unique device token. 515 | 516 | ```js 517 | function tokenHandler (result) { 518 | // Your iOS push server needs to know the token before it can push to this device 519 | // here is where you might want to send it the token for later use. 520 | alert('device token = ' + result); 521 | } 522 | ``` 523 | 524 | #### setApplicationIconBadgeNumber (iOS only) 525 | Set the badge count visible when the app is not running 526 | 527 | ```js 528 | pushNotification.setApplicationIconBadgeNumber(successCallback, errorCallback, badgeCount); 529 | ``` 530 | 531 | The `badgeCount` is an integer indicating what number should show up in the badge. Passing 0 will clear the badge. 532 | 533 | #### unregister (Amazon Fire OS, Android and iOS) 534 | You will typically call this when your app is exiting, to cleanup any used resources. Its not strictly necessary to call it, and indeed it may be desireable to NOT call it if you are debugging your intermediarry push server. When you call unregister(), the current token for a particular device will get invalidated, and the next call to register() will return a new token. If you do NOT call unregister(), the last token will remain in effect until it is invalidated for some reason at the GCM/ADM side. Since such invalidations are beyond your control, its recommended that, in a production environment, that you have a matching unregister() call, for every call to register(), and that your server updates the devices' records each time. 535 | 536 | ```js 537 | pushNotification.unregister(successHandler, errorHandler, options); 538 | ``` 539 | 540 | 541 | ### WP8 542 | 543 | #### register (WP8 Only) 544 | 545 | ```js 546 | 547 | if(device.platform == "Win32NT"){ 548 | pushNotification.register( 549 | channelHandler, 550 | errorHandler, 551 | { 552 | "channelName": "channelName", 553 | "ecb": onNotificationWP8, 554 | "uccb": channelHandler, 555 | "errcb": jsonErrorHandler 556 | }); 557 | } 558 | 559 | ``` 560 | Make sure that date and time settings are correct for your device/emulator before registering for push notifications. 561 | 562 | #### channelHandler (WP8 only) 563 | Called after a push notification channel is opened and push notification URI is returned. [The application is now set to receive notifications.](http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202940(v=vs.105).aspx) 564 | 565 | 566 | #### ecb (WP8 Only) 567 | Event callback that gets called when your device receives a notification. This is fired if the app is running when you receive the toast notification, or raw notification. 568 | 569 | ```js 570 | //handle MPNS notifications for WP8 571 | function onNotificationWP8(e) { 572 | 573 | if (e.type == "toast" && e.jsonContent) { 574 | pushNotification.showToastNotification(successHandler, errorHandler, 575 | { 576 | "Title": e.jsonContent["wp:Text1"], "Subtitle": e.jsonContent["wp:Text2"], "NavigationUri": e.jsonContent["wp:Param"] 577 | }); 578 | } 579 | 580 | if (e.type == "raw" && e.jsonContent) { 581 | alert(e.jsonContent.Body); 582 | } 583 | } 584 | ``` 585 | 586 | #### uccb (WP8 only) 587 | Event callback that gets called when the channel you have opened gets its Uri updated. This function is needed in case the MPNS updates the opened channel Uri. This function will take care of showing updated Uri. 588 | 589 | 590 | #### errcb (WP8 only) 591 | Event callback that gets called when server error occurs when receiving notification from the MPNS server (for example invalid format of the notification). 592 | 593 | ```js 594 | function jsonErrorHandler(error) { 595 | $("#app-status-ul").append('
  • error:' + error.code + '
  • '); 596 | $("#app-status-ul").append('
  • error:' + error.message + '
  • '); 597 | } 598 | ``` 599 | 600 | #### showToastNotification (WP8 only) 601 | Show toast notification if app is deactivated. 602 | 603 | pushNotification.showToastNotification(successCallback, errorCallback, options); 604 | 605 | The toast notification's properties are set explicitly using json. They can be get in onNotificationWP8 and used for whatever purposes needed. 606 | 607 | 608 | To control the launch page when the user taps on your toast notification when the app is not running, add the following code to your mainpage.xaml.cs 609 | ```cs 610 | protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 611 | { 612 | base.OnNavigatedTo(e); 613 | try 614 | { 615 | if (this.NavigationContext.QueryString["NavigatedFrom"] == "toast") // this is set on the server 616 | { 617 | this.PGView.StartPageUri = new Uri("//www/index.html#notification-page", UriKind.Relative); 618 | } 619 | } 620 | catch (KeyNotFoundException) 621 | { 622 | } 623 | } 624 | ``` 625 | Or you can add another `Page2.xaml` just for testing toast navigate url. Like the [MSDN Toast Sample](http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202967(v=vs.105).aspx) 626 | 627 | To test the tile notification, you will need to add tile images like the [MSDN Tile Sample](http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202970(v=vs.105).aspx#BKMK_CreatingaPushClienttoReceiveTileNotifications) 628 | 629 | #### unregister (WP8 Only) 630 | 631 | When using the plugin for wp8 you will need to unregister the push channel you have register in case you would want to open another one. You need to know the name of the channel you have opened in order to close it. Please keep in mind that one application can have only one opened channel at time and in order to open another you will have to close any already opened channel. 632 | 633 | ```cs 634 | function unregister() { 635 | var channelName = $("#channel-btn").val(); 636 | pushNotification.unregister( 637 | successHandler, errorHandler, 638 | { 639 | "channelName": channelName 640 | }); 641 | } 642 | ``` 643 | 644 | You'll probably want to trap on the **backbutton** event and only call this when the home page is showing. Remember, the back button on android is not the same as the Home button. When you hit the back button from the home page, your activity gets dismissed. Here is an example of how to trap the backbutton event; 645 | 646 | ```js 647 | function onDeviceReady() { 648 | $("#app-status-ul").append('
  • deviceready event received
  • '); 649 | 650 | document.addEventListener("backbutton", function(e) 651 | { 652 | $("#app-status-ul").append('
  • backbutton event received
  • '); 653 | 654 | if( $("#home").length > 0 ) 655 | { 656 | e.preventDefault(); 657 | pushNotification.unregister(successHandler, errorHandler); 658 | navigator.app.exitApp(); 659 | } 660 | else 661 | { 662 | navigator.app.backHistory(); 663 | } 664 | }, false); 665 | 666 | // additional onDeviceReady work... 667 | } 668 | ``` 669 | 670 | For the above to work, make sure the content for your home page is wrapped in an element with an id of home, like this; 671 | 672 | ```html 673 |
    674 |
    675 |
      676 |
    • Cordova PushNotification Plugin Demo
    • 677 |
    678 |
    679 |
    680 | ``` 681 | 682 | ### windows 683 | Sample usage is showed below. **Note**. To be able to receive toast notifications additional [toastCapable=’true’](http://msdn.microsoft.com/en-us/library/windows/apps/hh781238.aspx) attribute is required in manifest file. Cordova-windows 4.0.0 release adds this property to config.xml. You can use: 684 | `` in config.xml. However, you will need Cordova 5.1.1 which pins Cordova-windows 4.0.0. 685 | 686 | ```js 687 | // fired when push notification is received 688 | window.onNotification = function (e) { 689 | navigator.notification.alert('Notification received: ' + JSON.stringify(e)); 690 | } 691 | var pushNotification = window.plugins.pushNotification; 692 | pushNotification.register(successHandler, errorHandler, {"channelName":"your_channel_name","ecb":"onNotification"}); 693 | 694 | function successHandler(result) { 695 | console.log('registered###' + result.uri); 696 | // send uri to your notification server 697 | } 698 | function errorHandler(error) { 699 | console.log('error###' + error); 700 | } 701 | ``` 702 | See [Sending push notifications with WNS](http://msdn.microsoft.com/en-us/library/windows/apps/hh465460.aspx) to send test push notification. 703 | 704 | 705 | 706 | ## Testing 707 | The notification system consists of several interdependent components. 708 | 709 | 1. The client application which runs on a device and receives notifications. 710 | 2. The notification service provider (ADM for Amazon Fire OS, APNS for Apple, GCM for Google, MPNS for WP8) 711 | 3. Intermediary servers that collect device IDs from clients and push notifications through ADM, APNS GCM or MPNS. 712 | 713 | This plugin and its target Cordova application comprise the client application.The ADM, APNS, GCM and MPNS infrastructure are maintained by Amazon, Apple, Google and Microsoft, respectively. In order to send push notifications to your users, you would typically run an intermediary server or employ a 3rd party push service. This is true for all ADM (Amazon), APNS (iOS), GCM (Android) and MPNS (WP8) notifications. However, when testing the notification client applications, it may be desirable to be able to push notifications directly from your desktop, without having to design and build those server's first. There are a number of solutions out there to allow you to push from a desktop machine, sans server. 714 | 715 | ### Testing APNS and GCM notifications 716 | 717 | An easy solution to test APNS and GCM is a ruby gem called [pushmeup](http://rubygems.org/gems/pushmeup) (tested only on Mac, but it probably works fine on Windows as well). 718 | 719 | #### Prerequisites: 720 | 721 | - Ruby gems is installed and working. 722 | - You have successfully built a client with this plugin, on both iOS and Android and have installed them on a device. 723 | - You have installed the [PushMeUp gem](https://github.com/NicosKaralis/pushmeup): `$ sudo gem install pushmeup` 724 | 725 | 726 | 727 | #### APNS/iOS Setup 728 | [Follow this tutorial](http://www.raywenderlich.com/3443/apple-push-notification-services-tutorial-part-12) to create a file called ck.pem. 729 | 730 | Start at the section entitled "Generating the Certificate Signing Request (CSR)", and substitute your own Bundle Identifier, and Description. 731 | 732 | 1. Go the this plugin's Example/server folder and open pushAPNS.rb in the text editor of your choice. 733 | 2. Set the APNS.pem variable to the path of the ck.pem file you just created 734 | 3. Set APNS.pass to the password associated with the certificate you just created. (warning this is cleartext, so don't share this file) 735 | 4. Set device_token to the token for the device you want to send a push to. (you can run the Cordova app / plugin in Xcode and extract the token from the log messages) 736 | 5. Save your changes. 737 | 738 | 739 | #### Android/GCM Setup 740 | [Follow these steps](http://developer.android.com/guide/google/gcm/gs.html) to generate a project ID and a server based API key. 741 | 742 | 1. Go the this plugin's Example/server folder and open pushGCM.rb in the text editor of your choice. 743 | 2. Set the GCM.key variable to the API key you just generated. 744 | 3. Set the destination variable to the Registration ID of the device. (you can run the Cordova app / plugin in on a device via Eclipse and extract the regID from the log messages) 745 | 746 | #### Sending a test notification 747 | 748 | 1. cd to the directory containing the two .rb files we just edited. 749 | 2. Run the Cordova app / plugin on both the Android and iOS devices you used to obtain the regID / device token, respectively. 750 | 3. `$ ruby pushGCM.rb` or `$ ruby pushAPNS.rb` 751 | 752 | If you run this demo using the emulator you will not receive notifications from GCM. You need to run it on an actual device to receive messages or install the proper libraries on your emulator (You can follow [this guide](http://www.androidhive.info/2012/10/android-push-notifications-using-google-cloud-messaging-gcm-php-and-mysql/) under the section titled "Installing helper libraries and setting up the Emulator") If everything seems right and you are not receiving a registration id response back from Google, try uninstalling and reinstalling your app. That has worked for some devs out there. 753 | 754 | While the data model for iOS is somewhat fixed, it should be noted that GCM is far more flexible. The Android implementation in this plugin, for example, assumes the incoming message will contain a '**message**' and a '**msgcnt**' node. This is reflected in both the plugin (see GCMIntentService.java) as well as in provided example ruby script (pushGCM.rb). Should you employ a commercial service, their data model may differ. As mentioned earlier, this is where you will want to take a look at the **payload** element of the message event. In addition to the cannonical message and msgcnt elements, any additional elements in the incoming JSON object will be accessible here, obviating the need to edit and recompile the plugin. Many thanks to Tobias Hößl for this functionality! 755 | 756 | ### Testing ADM Notifications for Amazon Fire OS 757 | 758 | ####Register your app for Amazon Device Messaging (ADM) 759 | 760 | 1. Create a developer account on [Amazon Developer Portal](https://developer.amazon.com/home.html) 761 | 2. [Add a new app](https://developer.amazon.com/application/new.html) and turn Device Messaging switch to ON. Create a sample app for your device so you have the app name and package name used to register online. 762 | 3. Create [Security Profile](https://developer.amazon.com/iba-sp/overview.html) and obtain [ADM credentials](https://developer.amazon.com/sdk/adm/credentials.html) for your app. 763 | 764 | #### Sending a test notification 765 | 766 | 1. Inside the plugin's examples/server folder, open the `pushADM.js` NodeJS script with a text editor. (You should already have NodeJS installed). 767 | 2. Edit the CLIENT_ID and CLIENT_SECRET variables with the values from the ADM Security Profile page for your app. This will allow your app to securely identify itself to Amazon services. 768 | 3. Compile and run the sample app on your device. Note the sample app requires the Cordova Device and Media plugins to work. 769 | 4. The sample app will display your device's registration ID. Copy that value (it's very long) from your device into `pushADM.js`, entered in the REGISTRATION_IDS array. To test sending messages to more than one device, you can enter in multiple REGISTRATION_IDS into the array. 770 | 5. To send a test push notification, run the test script via a command line using NodeJS: `$ node pushADM.js`. 771 | 772 | 773 | ### Testing MPNS Notification for WP8 774 | The simplest way to test the plugin is to create an ASP.NET webpage that sends different notifications by using the URI that is returned when the push channel is created on the device. 775 | 776 | You can see how to create one from MSDN Samples: 777 | 778 | - [Send Toast Notifications (MSDN Sample)](http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202967(v=vs.105).aspx#BKMK_SendingaToastNotification) 779 | - [Send Tile Notification (MSDN Sample)](http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202970(v=vs.105).aspx#BKMK_SendingaTileNotification) 780 | - [Send Raw Notification (MSDN Sample)](http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202977(v=vs.105).aspx#BKMK_RunningtheRawNotificationSample) 781 | 782 | 783 | ### Sending push notifications on BlackBerry10 784 | If doing a BES push, ensure the device has been enterprise activated, has network access (wifi or sim) and your app is installed in the work permiter. You also need to make sure the _sys_use_consumer_push permission is NOT specified in the config.xml. This permission is meant only for public consumer BIS pushes and will cause an error when registering. 785 | 786 | If doing a public consumer BIS push, please ensure the _sys_use_consumer_push permission is added to the config.xml. 787 | 788 | Both types of pushes require the use of a Push Initiator. 789 | 790 | - [App based BIS Initiator](https://github.com/blackberry/BB10-WebWorks-Samples/tree/master/pushCaptureBasics/pushInitiator) 791 | - [Web based BIS Initiator (Push Service SDK)](https://developer.blackberry.com/services/push/) 792 | - [Web based BES Initiator](https://github.com/blackberry/BES10-WebWorks/tree/master/SimplePushTest/WW2.0/server) 793 | 794 | For additional information on BlackBerry Push see https://developer.blackberry.com/services/push/. 795 | 796 | ### Troubleshooting and next steps 797 | If all went well, you should see a notification show up on each device. If not, make sure you are not being blocked by a firewall, and that you have internet access. Check and recheck the token id, the registration ID and the certificate generating process. 798 | 799 | In a production environment, your app, upon registration, would send the device id (iOS) or the registration id (Android/Amazon), to your intermediary push server. For iOS, the push certificate would also be stored there, and would be used to authenticate push requests to the APNS server. When a push request is processed, this information is then used to target specific apps running on individual devices. 800 | 801 | If you're not up to building and maintaining your own intermediary push server, there are a number of commercial push services out there which support both APNS and GCM. 802 | 803 | - [Amazon Simple Notification Service](https://aws.amazon.com/sns/) 804 | - [kony](http://www.kony.com/push-notification-services) 805 | - [openpush](http://openpush.im) 806 | - [Pushwoosh](http://www.pushwoosh.com/) 807 | - [Urban Airship](http://urbanairship.com/products/push-notifications/) 808 | - etc. 809 | 810 | 811 | 812 | 813 | ##Additional Resources 814 | 815 | - [Amazon Device Messaging](https://developer.amazon.com/sdk/adm/credentials.html) 816 | - [Apple Push Notification Services Tutorial: Part 1/2](http://www.raywenderlich.com/3443/apple-push-notification-services-tutorial-part-12) 817 | - [Apple Push Notification Services Tutorial: Part 2/2](http://www.raywenderlich.com/3525/apple-push-notification-services-tutorial-part-2) 818 | - [Google Cloud Messaging for Android](http://developer.android.com/guide/google/gcm/index.html) (Android) 819 | - [How to Implement Push Notifications for Android](http://tokudu.com/2010/how-to-implement-push-notifications-for-android/) 820 | - [Local and Push Notification Programming Guide](http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html) (Apple) 821 | 822 | 823 | ## Acknowledgments 824 | 825 | Huge thanks to Mark Nutter whose [GCM-Cordova plugin](https://github.com/marknutter/GCM-Cordova) forms the basis for the Android side implimentation. 826 | 827 | Likewise, the iOS side was inspired by Olivier Louvignes' [Cordova PushNotification Plugin](https://github.com/phonegap/phonegap-plugins/tree/master/iOS/PushNotification) (Copyright (c) 2012 Olivier Louvignes) for iOS. 828 | 829 | Props to [Tobias Hößl](https://github.com/CatoTH), who provided the code to surface the full JSON object up to the JS layer. 830 | --------------------------------------------------------------------------------