├── cordova-airship ├── src │ ├── ios │ │ ├── AirshipCordova-Bridging-Header.h │ │ ├── AirshipCordovaVersion.swift │ │ ├── AirshipCordovaAutopilot.swift │ │ ├── AirshipCordovaProxyDataMigrator.swift │ │ ├── AirshipCordovaPluginSettings.swift │ │ └── AirshipCordova.swift │ └── android │ │ ├── build-extras.gradle │ │ ├── AirshipCordovaVersion.kt │ │ ├── CordovaAutopilot.kt │ │ ├── ProxyDataMigrator.kt │ │ ├── CordovaSettings.kt │ │ └── AirshipCordova.kt ├── package.json ├── plugin.xml ├── package-lock.json └── www │ └── Airship.js ├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── workflows │ ├── CI.yaml │ ├── release.yaml │ └── prep-release.yaml ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── scripts ├── config.sh ├── check_version.sh ├── upload_docs.sh ├── update_proxy_version.sh ├── update_version.sh ├── check_plugin_version.sh ├── create_sample.sh ├── update_changelog.sh ├── run_ci_tasks.sh └── prep_release.sh ├── .gitattributes ├── cordova-airship-hms ├── src │ └── android │ │ ├── build-extras.gradle │ │ └── app-build-extras.gradle ├── README.md ├── package-lock.json ├── plugin.xml └── package.json ├── .gitignore ├── Example ├── myMessageCenterStyleConfig.plist ├── css │ ├── mobiscroll.jqm-2.0.1.css │ ├── index.css │ └── mobiscroll.core-2.0.1.css └── index.html ├── README.md ├── package.json ├── CONTRIBUTORS ├── COPYING ├── DEV_README.md ├── tsconfig.json ├── config_sample.xml └── MIGRATION.md /cordova-airship/src/ios/AirshipCordova-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # https://help.github.com/en/articles/about-code-owners 2 | * @urbanairship/mobile -------------------------------------------------------------------------------- /scripts/config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CORDOVA_VERSION=12.0.0 4 | IOS_CORDOVA_VERSION=7.1.1 5 | ANDROID_CORDOVA_VERSION=13.0.0 -------------------------------------------------------------------------------- /cordova-airship/src/android/build-extras.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api "com.urbanairship.android:airship-framework-proxy:14.10.1" 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | src/ios/Airship/libUAirship-9.4.0.a filter=lfs diff=lfs merge=lfs -text 2 | src/ios/Airship/libUAirship-10.0.0.a filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /cordova-airship/src/ios/AirshipCordovaVersion.swift: -------------------------------------------------------------------------------- 1 | /* Copyright Airship and Contributors */ 2 | 3 | import Foundation 4 | 5 | class AirshipCordovaVersion { 6 | static let version = "17.5.1" 7 | } 8 | -------------------------------------------------------------------------------- /cordova-airship/src/android/AirshipCordovaVersion.kt: -------------------------------------------------------------------------------- 1 | /* Copyright Urban Airship and Contributors */ 2 | 3 | package com.urbanairship.cordova 4 | 5 | object AirshipCordovaVersion { 6 | var version = "17.5.1" 7 | } -------------------------------------------------------------------------------- /scripts/check_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -x 4 | 5 | ROOT_PATH=`dirname "${0}"`/.. 6 | 7 | version=$(node -p "require('$ROOT_PATH/cordova-airship/package.json').version") 8 | 9 | if [ $1 = $version ]; then 10 | exit 0 11 | else 12 | exit 1 13 | fi -------------------------------------------------------------------------------- /cordova-airship-hms/src/android/build-extras.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | google() 3 | mavenCentral() 4 | maven { url 'https://developer.huawei.com/repo/' } 5 | } 6 | 7 | dependencies { 8 | implementation "com.urbanairship.android:airship-framework-proxy-hms:14.10.1" 9 | implementation 'com.huawei.hms:push:6.13.0.300' 10 | } 11 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing Code 2 | 3 | We accept pull requests! If you would like to submit a pull request, please fill out and submit our 4 | [Contributor License Agreement](https://docs.google.com/forms/d/e/1FAIpQLScErfiz-fXSPpVZ9r8Di2Tr2xDFxt5MgzUel0__9vqUgvko7Q/viewform). 5 | 6 | One of our engineers will verify receipt of the agreement before approving your pull request. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | *.zip 4 | urbanairship-phonegap-* 5 | npm-debug.log 6 | src/android/.idea/ 7 | src/android/android.iml 8 | src/android/local.properties 9 | out/* 10 | node_modules/* 11 | scripts/update_cordova/node_modules/* 12 | config_sample.xml 13 | cordova-airship/docs 14 | google-services.json 15 | cordova-airship/node_modules/* 16 | cordova-airship-hms/node_modules/* -------------------------------------------------------------------------------- /cordova-airship-hms/src/android/app-build-extras.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | maven { url 'https://developer.huawei.com/repo/' } 6 | } 7 | 8 | dependencies { 9 | classpath 'com.huawei.agconnect:agcp:1.6.0.300' 10 | } 11 | } 12 | 13 | ext.postBuildExtras = { 14 | apply plugin: 'com.huawei.agconnect' 15 | } -------------------------------------------------------------------------------- /Example/myMessageCenterStyleConfig.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | titleColor 6 | #ba1c8b 7 | navigationBarColor 8 | #8f0b70 9 | iconsEnabled 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cordova Airship Plugin 2 | 3 | A Cordova plugin for Airship's iOS and Android SDK. 4 | 5 | ### Resources 6 | 7 | * [Getting started guide](https://docs.airship.com/platform/mobile/setup/sdk/cordova/) 8 | * [API docs](https://docs.airship.com/reference/libraries/urbanairship-cordova/latest/) 9 | 10 | ### Issues 11 | 12 | Please visit https://support.airship.com/ for any issues integrating or using this plugin. 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": { 4 | "packages": [ 5 | "urbanairship-cordova", 6 | "urbanairship-hms-cordova" 7 | ] 8 | }, 9 | "scripts": { 10 | "bootstrap": "npm ci && npm --prefix urbanairship-cordova ci", 11 | "generate-docs": "npm run --prefix urbanairship-cordova generate-docs", 12 | "create-sample": "bash ./scripts/create_sample.sh ../cordova-sample" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | This library is officially supported by Urban Airship. Main developers at UA who worked on it are: 2 | 3 | Marc Sciglimpaglia 4 | Jeff Towle 5 | Don Perconti 6 | Eric Holscher 7 | Ryan Lepinski 8 | Helen Crowell 9 | David Crow 10 | Gatlin Hebert 11 | 12 | As with anything open source, we couldn't do it with a little help from our friends: 13 | 14 | Keenan Keeling 15 | Pablo Marti 16 | Nate Guerin 17 | Valentin Polushkin 18 | Alexander Sandstrom 19 | -------------------------------------------------------------------------------- /scripts/upload_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | set -e 3 | set -x 4 | 5 | if [ -z "$1" ]; 6 | then 7 | echo "No version supplied" 8 | exit 1 9 | fi 10 | 11 | if [ ! -d "$2" ]; 12 | then 13 | echo "Missing docs $2" 14 | exit 1 15 | fi 16 | 17 | ROOT_PATH=`dirname "${0}"`/.. 18 | TAR_NAME="$1.tar.gz" 19 | 20 | cd $2 21 | tar -czf $TAR_NAME * 22 | cd - 23 | 24 | gsutil cp "$ROOT_PATH/$2/$TAR_NAME" gs://ua-web-ci-prod-docs-transfer/libraries/urbanairship-cordova/$TAR_NAME 25 | -------------------------------------------------------------------------------- /cordova-airship-hms/README.md: -------------------------------------------------------------------------------- 1 | # Urban Airship HMS Cordova Plugin 2 | 3 | This plugin supports Cordova apps running on both iOS and Android. 4 | 5 | ### Issues 6 | 7 | Please visit http://support.urbanairship.com/ for any issues integrating or using this plugin. 8 | 9 | ### Requirements: 10 | - cordova >= 9.0.1 11 | - urbanairship-cordova >= 11.0.1 12 | 13 | ### Quickstart 14 | 15 | Install this plugin using Cordova CLI: 16 | 17 | cordova plugin add urbanairship-hms-cordova 18 | 19 | -------------------------------------------------------------------------------- /cordova-airship-hms/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ua/cordova-airship-hms", 3 | "version": "17.5.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@ua/cordova-airship-hms", 9 | "version": "17.5.1", 10 | "engines": [ 11 | { 12 | "name": "cordova-android", 13 | "version": ">=11.0.0" 14 | }, 15 | { 16 | "name": "cordova-plugman", 17 | "version": ">=4.2.0" 18 | } 19 | ], 20 | "license": "Apache 2.0" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright 2010-2024 Airship and Contributors 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /DEV_README.md: -------------------------------------------------------------------------------- 1 | # Airship Cordova 2 | 3 | ## Update Plugin and SDK versions instructions 4 | 5 | * Plugin version: 6 | * In the root directory run: `scripts/update_version.sh ` 7 | * iOS SDK version 8 | * update plugin.xml with new iOS version, i.e. 6.4.0 9 | * Android SDK version 10 | * update src/android/build-extras.gradle to point to the latest android versions (our SDK & external libraries) 11 | 12 | ## Development 13 | 14 | Basic setup: 15 | 16 | 1) Install everything 17 | 18 | ``` 19 | npm run bootstrap 20 | ``` 21 | 22 | 2) Create a sample app 23 | 24 | ``` 25 | npm run create-sample 26 | ``` 27 | 28 | 29 | When working with the plugin, you most likely will use the sample app. After making the changes you need you will copy the files back to the plugin. -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@ua/airship-cordova": ["./types/index.d.ts"] 6 | }, 7 | "allowUnreachableCode": false, 8 | "allowUnusedLabels": false, 9 | "esModuleInterop": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "lib": ["esnext"], 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "noFallthroughCasesInSwitch": true, 15 | "noImplicitReturns": true, 16 | "noImplicitUseStrict": false, 17 | "noStrictGenericChecks": false, 18 | "noUncheckedIndexedAccess": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "resolveJsonModule": true, 22 | "skipLibCheck": true, 23 | "strict": true, 24 | "target": "esnext" 25 | } 26 | } -------------------------------------------------------------------------------- /.github/workflows/CI.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request] 4 | 5 | concurrency: 6 | group: ${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | android: 11 | runs-on: macos-15-xlarge 12 | timeout-minutes: 10 13 | env: 14 | DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer 15 | steps: 16 | - name: Install Android Build Tools 17 | run: | 18 | echo "y" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "build-tools;34.0.0" 19 | - uses: actions/checkout@v4 20 | - name: Run CI 21 | run: bash ./scripts/run_ci_tasks.sh -a 22 | ios: 23 | runs-on: macos-15-xlarge 24 | timeout-minutes: 10 25 | env: 26 | DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: Run CI 30 | run: bash ./scripts/run_ci_tasks.sh -i 31 | -------------------------------------------------------------------------------- /scripts/update_proxy_version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euxo pipefail 3 | 4 | SCRIPT_DIRECTORY="$(cd "$(dirname "$0")" && pwd)" 5 | ROOT_PATH="$SCRIPT_DIRECTORY/.." 6 | 7 | PROXY_VERSION="$1" 8 | if [ -z "$PROXY_VERSION" ]; then 9 | echo "No proxy version supplied" 10 | exit 1 11 | fi 12 | 13 | # Update plugin.xml 14 | sed -i.bak -E "s/(pod name=\"AirshipFrameworkProxy\" spec=\")[^\"]*\"/\1$PROXY_VERSION\"/" "$ROOT_PATH/cordova-airship/plugin.xml" 15 | 16 | # Update Android build-extras.gradle 17 | sed -i.bak -E "s/(api \"com.urbanairship.android:airship-framework-proxy:)[^\"]*\"/\1$PROXY_VERSION\"/" "$ROOT_PATH/cordova-airship/src/android/build-extras.gradle" 18 | 19 | # Update HMS build-extras.gradle 20 | sed -i.bak -E "s/(implementation \"com.urbanairship.android:airship-framework-proxy-hms:)[^\"]*\"/\1$PROXY_VERSION\"/" "$ROOT_PATH/cordova-airship-hms/src/android/build-extras.gradle" 21 | 22 | # Clean up backup files 23 | find "$ROOT_PATH" -name "*.bak" -delete 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | ### What do these changes do? 13 | 14 | 15 | ### Why are these changes necessary? 16 | 18 | 19 | ### How did you verify these changes? 20 | 23 | 24 | #### Verification Screenshots: 25 | 26 | 27 | ### Anything else a reviewer should know? 28 | 29 | -------------------------------------------------------------------------------- /cordova-airship-hms/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Airship HMS 5 | Airship HMS Cordova plugin 6 | Apache 2.0 7 | cordova,urbanairship 8 | https://github.com/urbanairship/urbanairship-hms-cordova.git 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Example/css/mobiscroll.jqm-2.0.1.css: -------------------------------------------------------------------------------- 1 | /* jQuery Mobile Theme */ 2 | .jqm .dwo { 3 | background: none; 4 | } 5 | .jqm .dw { 6 | padding: 6px; 7 | z-index: 1003; 8 | } 9 | .jqm .dwv { 10 | position: static; 11 | width: auto; 12 | padding: .7em 15px .7em 15px; 13 | border: 0; 14 | } 15 | .jqm .dwwr { 16 | border: 0; 17 | } 18 | .jqm .dwpm .dwwo { 19 | background: none; 20 | } 21 | .jqm .dwc { 22 | margin: 0; 23 | padding: 30px 5px 5px 5px; 24 | } 25 | .jqm .dwhl { 26 | padding: 5px; 27 | } 28 | .jqm .dwwb { 29 | margin: 0; 30 | border: 0; 31 | } 32 | .jqm .dwwb span { 33 | padding: 0; 34 | } 35 | .jqm .dwwbp .ui-btn-inner { 36 | -webkit-border-radius: 3px 3px 0 0; 37 | -moz-border-radius: 3px 3px 0 0; 38 | border-radius: 3px 3px 0 0; 39 | } 40 | .jqm .dwwbm .ui-btn-inner { 41 | -webkit-border-radius: 0 0 3px 3px; 42 | -moz-border-radius: 0 0 3px 3px; 43 | border-radius: 0 0 3px 3px; 44 | } 45 | .jqm .dwwbp span { 46 | font-weight: normal; 47 | } 48 | -------------------------------------------------------------------------------- /cordova-airship-hms/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ua/cordova-airship-hms", 3 | "version": "17.5.1", 4 | "description": "Airship HMS Cordova plugin", 5 | "cordova": { 6 | "id": "@ua/cordova-airship-hms", 7 | "platforms": [ 8 | "android" 9 | ] 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/urbanairship/urbanairship-cordova.git" 14 | }, 15 | "publishConfig": { 16 | "access": "public", 17 | "registry": "https://registry.npmjs.org/" 18 | }, 19 | "keywords": [ 20 | "ecosystem:cordova", 21 | "urbanairship", 22 | "hms", 23 | "huawei", 24 | "cordova-android" 25 | ], 26 | "engines": [ 27 | { 28 | "name": "cordova-android", 29 | "version": ">=11.0.0" 30 | }, 31 | { 32 | "name": "cordova-plugman", 33 | "version": ">=4.2.0" 34 | } 35 | ], 36 | "author": "Urban Airship", 37 | "license": "Apache 2.0", 38 | "homepage": "https://github.com/urbanairship/urbanairship-cordova.git", 39 | "bugs": { 40 | "url": "https://github.com/urbanairship/urbanairship-cordova/issues" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cordova-airship/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ua/cordova-airship", 3 | "version": "17.5.1", 4 | "description": "Airship Cordova plugin", 5 | "cordova": { 6 | "id": "@ua/cordova-airship", 7 | "platforms": [ 8 | "android", 9 | "ios" 10 | ] 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/urbanairship/urbanairship-cordova.git" 15 | }, 16 | "publishConfig": { 17 | "access": "public", 18 | "registry": "https://registry.npmjs.org/" 19 | }, 20 | "keywords": [ 21 | "cordova", 22 | "urbanairship", 23 | "airship", 24 | "ecosystem:cordova", 25 | "cordova-android", 26 | "cordova-ios" 27 | ], 28 | "engines": [ 29 | { 30 | "name": "cordova-android", 31 | "version": ">=13.0.0" 32 | }, 33 | { 34 | "name": "cordova-ios", 35 | "version": ">=7.0.0" 36 | }, 37 | { 38 | "name": "cordova-plugman", 39 | "version": ">=4.2.0" 40 | } 41 | ], 42 | "author": "Airship", 43 | "license": "Apache 2.0", 44 | "homepage": "https://github.com/urbanairship/urbanairship-cordova.git", 45 | "devDependencies": { 46 | "typescript": "~4.8.0", 47 | "typedoc": "0.23.24" 48 | }, 49 | "scripts": { 50 | "generate-docs": "typedoc types/index.d.ts" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ❗For how-to inquiries involving Airship functionality or use cases, please 4 | contact (support)[https://support.airship.com/]. 5 | 6 | 15 | 16 | # Preliminary Info 17 | 18 | ### What Airship dependencies are you using? 19 | 20 | 21 | ### What are the versions of any relevant development tools you are using? 22 | 24 | 25 | # Report 26 | 27 | ### What unexpected behavior are you seeing? 28 | 29 | 30 | ### What is the expected behavior? 31 | 32 | 33 | ### What are the steps to reproduce the unexpected behavior? 34 | 35 | 36 | ### Do you have logging for the issue? 37 | 38 | -------------------------------------------------------------------------------- /scripts/update_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | VERSION=$1 3 | 4 | ROOT_PATH=`dirname "${0}"`/.. 5 | CORE_PACKAGE_PATH="$ROOT_PATH/cordova-airship" 6 | HMS_PACKAGE_PATH="$ROOT_PATH/cordova-airship-hms" 7 | ANDROID_VERISON_PATH="$ROOT_PATH/cordova-airship/src/android/AirshipCordovaVersion.kt" 8 | IOS_VERISON_PATH="$ROOT_PATH/cordova-airship/src/ios/AirshipCordovaVersion.swift" 9 | HMS_PLUGIN_XML_PATH="$ROOT_PATH/cordova-airship-hms/plugin.xml" 10 | CORE_PLUGIN_XML_PATH="$ROOT_PATH/cordova-airship/plugin.xml" 11 | 12 | 13 | if [ -z "$1" ] 14 | then 15 | echo "No version number supplied" 16 | exit 17 | fi 18 | 19 | 20 | sed -i '' "s/var version = \"[-0-9.a-zA-Z]*\"/var version = \"$VERSION\"/" $ANDROID_VERISON_PATH 21 | sed -i '' "s/static let version = \"[-0-9.a-zA-Z]*\"/static let version = \"$VERSION\"/" $IOS_VERISON_PATH 22 | sed -i '' "s///" $HMS_PLUGIN_XML_PATH 25 | npm --prefix $CORE_PACKAGE_PATH version $VERSION 26 | npm --prefix $HMS_PACKAGE_PATH version $VERSION -------------------------------------------------------------------------------- /scripts/check_plugin_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | ROOT_PATH=`dirname "${0}"`/.. 5 | 6 | CORE_PACKAGE_PATH="$ROOT_PATH/cordova-airship/package.json" 7 | HMS_PACKAGE_PATH="$ROOT_PATH/cordova-airship-hms/package.json" 8 | ANDROID_VERISON_PATH="$ROOT_PATH/cordova-airship/src/android/AirshipCordovaVersion.kt" 9 | IOS_VERISON_PATH="$ROOT_PATH/cordova-airship/src/ios/AirshipCordovaVersion.swift" 10 | HMS_PLUGIN_XML_PATH="$ROOT_PATH/cordova-airship-hms/plugin.xml" 11 | 12 | coreVersion=$(node -p "require('$CORE_PACKAGE_PATH').version") 13 | echo "core package version: $coreVersion" 14 | 15 | hmsVersion=$(node -p "require('$HMS_PACKAGE_PATH').version") 16 | echo "hms package version: $hmsVersion" 17 | 18 | androidVersion=$(grep "var version" $ANDROID_VERISON_PATH | awk -F'"' '{print $2}') 19 | echo "android: $androidVersion" 20 | 21 | iosVersion=$(grep "static let version" $IOS_VERISON_PATH | awk -F'"' '{print $2}') 22 | echo "ios: $iosVersion" 23 | 24 | hmsDependencyVersion=$(grep ' 2 | 6 | 7 | Test 8 | 9 | A sample Apache Cordova application that responds to the deviceready event. 10 | 11 | 12 | Apache Cordova Team 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /scripts/create_sample.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | cd `dirname "${0}"`/../ 6 | ROOT_PATH="$(pwd)" 7 | CORDOVA_AIRSHIP_MODULE="$ROOT_PATH/cordova-airship" 8 | CORDOVA_AIRSHIP_HMS_MODULE="$ROOT_PATH/cordova-airship-hms" 9 | cd - 10 | 11 | source "$ROOT_PATH/scripts/config.sh" 12 | 13 | CORDOVA_PATH=$1 14 | 15 | if [ -z "$1" ] 16 | then 17 | echo "No test path supplied" 18 | exit 19 | fi 20 | 21 | # Set up script to create a sample for iOS and android testing. 22 | # Follow the steps below. 23 | # 1. Add the UA credentials to the `config_sample.xml` file and save. 24 | # 2. Run the script with the command `./scripts/create_sample.sh ` 25 | # 3. Build the platform you want to test (see comments below). 26 | 27 | # create cordova directory 28 | mkdir -p $CORDOVA_PATH 29 | cd $CORDOVA_PATH 30 | 31 | # create the test project 32 | rm -rf test 33 | npm install cordova@$CORDOVA_VERSION 34 | npx cordova create test com.urbanairship.sample Test 35 | cd test 36 | npm install cordova@$CORDOVA_VERSION 37 | 38 | # add the plugins 39 | npx cordova plugin add $CORDOVA_AIRSHIP_MODULE 40 | npx cordova plugin add $CORDOVA_AIRSHIP_HMS_MODULE 41 | 42 | # copy config and example files 43 | cp "$ROOT_PATH/config_sample.xml" config.xml 44 | cp "$ROOT_PATH/Example/index.html" www/index.html 45 | cp "$ROOT_PATH/Example/css/"* www/css 46 | cp "$ROOT_PATH/Example/js/"* www/js 47 | cp "$ROOT_PATH/Example/myMessageCenterStyleConfig.plist" myMessageCenterStyleConfig.plist 48 | 49 | # copy mock google-services.json 50 | cp "$ROOT_PATH/scripts/mock-google-services.json" google-services.json 51 | 52 | 53 | # add required plugins 54 | npx cordova plugin add cordova-plugin-device 55 | 56 | # set up iOS 57 | npx cordova platform add ios@$IOS_CORDOVA_VERSION 58 | 59 | # Build with command `cordova build ios --emulator` in project directory 60 | # After successful build, connect iOS device to test 61 | # Test with command `cordova run ios --device --developmentTeam=XXXXXXXXXX` 62 | # Please refer to https://cordova.apache.org/docs/en/latest/guide/platforms/ios/#signing-an-app for more information about code signing. 63 | 64 | # Open workspace in Xcode with 'open' command, e.g. `open platforms/ios/Test.xcworkspace` 65 | 66 | # set up android 67 | npx cordova platform add android@$ANDROID_CORDOVA_VERSION 68 | 69 | # Build with command `cordova build android` in project directory 70 | # After successful build, connect android device to test 71 | # Test with command `cordova run android` 72 | -------------------------------------------------------------------------------- /scripts/update_changelog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -x 4 | 5 | SCRIPT_DIRECTORY="$(dirname "$0")" 6 | ROOT_PATH=$SCRIPT_DIRECTORY/../ 7 | 8 | # First argument is always the version 9 | VERSION=$1 10 | shift 11 | 12 | # Process remaining arguments as named parameters 13 | while [[ $# -gt 0 ]]; do 14 | case $1 in 15 | --ios) 16 | IOS_VERSION="$2" 17 | shift 2 18 | ;; 19 | --android) 20 | ANDROID_VERSION="$2" 21 | shift 2 22 | ;; 23 | *) 24 | echo "Unknown parameter: $1" 25 | exit 1 26 | ;; 27 | esac 28 | done 29 | 30 | if [ -z "$VERSION" ]; then 31 | echo "Error: Version is required" 32 | exit 1 33 | fi 34 | 35 | RELEASE_DATE=$(date +"%B %-d, %Y") 36 | 37 | # Determine release type based on version 38 | if [[ $VERSION =~ \.0\.0$ ]]; then 39 | RELEASE_TYPE="Major" 40 | elif [[ $VERSION =~ \.0$ ]]; then 41 | RELEASE_TYPE="Minor" 42 | else 43 | RELEASE_TYPE="Patch" 44 | fi 45 | 46 | # Create changelog entry 47 | NEW_ENTRY="## Version $VERSION - $RELEASE_DATE\n\n" 48 | 49 | if [ -n "$IOS_VERSION" ] || [ -n "$ANDROID_VERSION" ]; then 50 | NEW_ENTRY+="$RELEASE_TYPE release that updates" 51 | 52 | if [ -n "$ANDROID_VERSION" ]; then 53 | NEW_ENTRY+=" the Android SDK to $ANDROID_VERSION" 54 | fi 55 | 56 | if [ -n "$IOS_VERSION" ] && [ -n "$ANDROID_VERSION" ]; then 57 | NEW_ENTRY+=" and" 58 | fi 59 | 60 | if [ -n "$IOS_VERSION" ]; then 61 | NEW_ENTRY+=" the iOS SDK to $IOS_VERSION" 62 | fi 63 | 64 | NEW_ENTRY+="\n\n### Changes\n" 65 | 66 | if [ -n "$ANDROID_VERSION" ]; then 67 | NEW_ENTRY+="- Updated Android SDK to [$ANDROID_VERSION](https://github.com/urbanairship/android-library/releases/tag/$ANDROID_VERSION)" 68 | fi 69 | 70 | if [ -n "$IOS_VERSION" ]; then 71 | NEW_ENTRY+="\n" 72 | NEW_ENTRY+="- Updated iOS SDK to [$IOS_VERSION](https://github.com/urbanairship/ios-library/releases/tag/$IOS_VERSION)" 73 | fi 74 | else 75 | NEW_ENTRY+="$RELEASE_TYPE release." 76 | fi 77 | 78 | # Create temporary file with new content 79 | TEMP_FILE=$(mktemp) 80 | 81 | # Add the header line 82 | echo "# Cordova Plugin Changelog" > "$TEMP_FILE" 83 | echo -e "\n$NEW_ENTRY" >> "$TEMP_FILE" 84 | 85 | # Append the rest of the existing changelog (skipping the header) 86 | tail -n +2 "$ROOT_PATH/CHANGELOG.md" >> "$TEMP_FILE" 87 | 88 | # Replace original file with new content 89 | mv "$TEMP_FILE" "$ROOT_PATH/CHANGELOG.md" 90 | -------------------------------------------------------------------------------- /scripts/run_ci_tasks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # run_ci_tasks.sh [OPTIONS] [PATH TO SAMPLE APP] 5 | # where OPTIONS are: 6 | # -a to run Android CI tasks. 7 | # -i to run iOS CI tasks. 8 | # Defaults to -a -i. 9 | # 10 | 11 | set -euxo pipefail 12 | 13 | SCRIPT_DIRECTORY=`dirname "$0"` 14 | SCRIPT_NAME=`basename "$0"` 15 | 16 | # get platforms to build 17 | ANDROID=false 18 | IOS=false 19 | 20 | # Parse arguments 21 | OPTS=`getopt haid $*` 22 | 23 | if [ $? != 0 ] ; then echo "Failed parsing options." >&2 ; exit 1 ; fi 24 | eval set -- "$OPTS" 25 | 26 | if [ "$1" == "--" ]; then 27 | # set the default options 28 | eval set -- "-a" "-i" $@ 29 | fi 30 | 31 | while true; do 32 | case "${1:-}" in 33 | -h ) echo -ne "\n${SCRIPT_NAME} [OPTIONS] [PATH TO SAMPLE APP]\nwhere OPTIONS are:\n -a to run Android CI tasks.\n -i to run iOS CI tasks.\n Defaults to -a -i. \n"; exit 0;; 34 | -a ) ANDROID=true;; 35 | -i ) IOS=true;; 36 | -- ) ;; 37 | * ) break ;; 38 | esac 39 | shift 40 | done 41 | 42 | # ckeck for version 43 | ${SCRIPT_DIRECTORY}/check_plugin_version.sh 44 | 45 | SAMPLE_APP_PATH=${1:-} 46 | 47 | if [ "$ANDROID" = "true" ] || [ "$IOS" = "true" ]; then 48 | # create the sample for building 49 | if [ -z "$SAMPLE_APP_PATH" ]; then 50 | SAMPLE_APP_PATH=$(mktemp -d /tmp/cordova-sample-app-XXXXX) 51 | fi 52 | 53 | # if sample app doesn't already exist, create it 54 | if [[ ! -d $SAMPLE_APP_PATH/test ]]; then 55 | ${SCRIPT_DIRECTORY}/create_sample.sh $SAMPLE_APP_PATH 56 | fi 57 | 58 | cd $SAMPLE_APP_PATH/test 59 | fi 60 | 61 | if [ "$ANDROID" = "true" ]; then 62 | # Build android 63 | npx cordova build android -- --gradleArg="-PuaSkipApplyGoogleServicesPlugin=true" 2>&1 | tee -a /tmp/CORDOVA-$$.out 64 | 65 | # check for failures 66 | if grep "BUILD FAILED" /tmp/CORDOVA-$$.out; then 67 | # Set build status to failed 68 | echo "ANDROID BUILD FAILED" 69 | exit 1 70 | else 71 | echo "ANDROID BUILD SUCCEEDED" 72 | fi 73 | fi 74 | 75 | if [ "$IOS" = "true" ]; then 76 | npx cordova run ios --list 77 | 78 | # Build ios 79 | npx cordova build ios --target="iPhone-16-Pro, 18.2" --emulator 2>&1 | tee -a /tmp/CORDOVA-$$.out 80 | 81 | # check for failures 82 | if grep "BUILD FAILED" /tmp/CORDOVA-$$.out; then 83 | # Set build status to failed 84 | echo "iOS BUILD FAILED" 85 | exit 1 86 | fi 87 | 88 | if grep "Failed to install 'com.urbanairship.cordova'" /tmp/CORDOVA-$$.out; then 89 | # Set build status to failed 90 | echo "iOS BUILD FAILED" 91 | exit 1 92 | fi 93 | 94 | echo "iOS BUILD SUCCEEDED" 95 | 96 | fi 97 | 98 | echo "CI TASKS SUCCEEDED" 99 | -------------------------------------------------------------------------------- /cordova-airship/src/ios/AirshipCordovaAutopilot.swift: -------------------------------------------------------------------------------- 1 | /* Copyright Airship and Contributors */ 2 | 3 | import AirshipKit 4 | import AirshipFrameworkProxy 5 | 6 | @objc(AirshipPluginLoader) 7 | @MainActor 8 | public class AirshipPluginLoader: NSObject, AirshipPluginLoaderProtocol { 9 | @objc 10 | public static var disabled: Bool = false 11 | 12 | public static func onApplicationDidFinishLaunching( 13 | launchOptions: [UIApplication.LaunchOptionsKey : Any]? 14 | ) { 15 | AirshipCordovaAutopilot.shared.onLoad() 16 | } 17 | } 18 | 19 | @MainActor 20 | final class AirshipCordovaAutopilot { 21 | 22 | public static let shared: AirshipCordovaAutopilot = AirshipCordovaAutopilot() 23 | private var settings: AirshipCordovaPluginSettings? 24 | private var pluginInitialized: Bool = false 25 | private var loaded: Bool = false 26 | 27 | func pluginInitialized(settings: AirshipCordovaPluginSettings?) { 28 | self.pluginInitialized = true 29 | self.settings = settings 30 | AirshipProxy.shared.delegate = self 31 | 32 | if pluginInitialized, loaded { 33 | try? AirshipProxy.shared.attemptTakeOff() 34 | } 35 | } 36 | 37 | func onLoad() { 38 | self.loaded = true 39 | if pluginInitialized, loaded { 40 | try? AirshipProxy.shared.attemptTakeOff() 41 | } 42 | } 43 | 44 | func attemptTakeOff(json: Any) throws -> Bool { 45 | return try AirshipProxy.shared.takeOff( 46 | json: json 47 | ) 48 | } 49 | } 50 | 51 | extension AirshipCordovaAutopilot: AirshipProxyDelegate { 52 | public func migrateData(store: AirshipFrameworkProxy.ProxyStore) { 53 | AirshipCordovaProxyDataMigrator().migrateData(store: store) 54 | store.defaultPresentationOptions = settings?.presentationOptions ?? [] 55 | store.defaultAutoDisplayMessageCenter = settings?.autoLaunchMessageCenter ?? true 56 | } 57 | 58 | public func loadDefaultConfig() -> AirshipConfig { 59 | var airshipConfig = (try? AirshipConfig.default()) ?? AirshipConfig() 60 | if let settings { 61 | airshipConfig.applyPluginSettings(settings) 62 | } 63 | return airshipConfig 64 | } 65 | 66 | @MainActor 67 | public func onAirshipReady() { 68 | Airship.analytics.registerSDKExtension( 69 | AirshipSDKExtension.cordova, 70 | version: AirshipCordovaVersion.version 71 | ) 72 | 73 | if settings?.clearBadgeOnLaunch == true { 74 | Task { 75 | try? await Airship.push.resetBadge() 76 | } 77 | } 78 | 79 | if settings?.enablePushOnLaunch == true { 80 | Airship.push.userPushNotificationsEnabled = true 81 | } 82 | } 83 | } 84 | 85 | 86 | -------------------------------------------------------------------------------- /Example/css/index.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | * { 20 | -webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */ 21 | } 22 | 23 | body { 24 | -webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */ 25 | -webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */ 26 | -webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */ 27 | background-color:#E4E4E4; 28 | background-image:linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%); 29 | background-image:-webkit-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%); 30 | background-image:-ms-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%); 31 | background-image:-webkit-gradient( 32 | linear, 33 | left top, 34 | left bottom, 35 | color-stop(0, #A7A7A7), 36 | color-stop(0.51, #E4E4E4) 37 | ); 38 | background-attachment:fixed; 39 | font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif; 40 | font-size:12px; 41 | height:100%; 42 | margin:0px; 43 | padding:0px; 44 | text-transform:uppercase; 45 | width:100%; 46 | } 47 | 48 | /* Portrait layout (default) */ 49 | .app { 50 | background:url(../img/logo.png) no-repeat center top; /* 170px x 200px */ 51 | position:absolute; /* position in the center of the screen */ 52 | left:50%; 53 | top:50%; 54 | height:50px; /* text area height */ 55 | width:225px; /* text area width */ 56 | text-align:center; 57 | padding:180px 0px 0px 0px; /* image height is 200px (bottom 20px are overlapped with text) */ 58 | margin:-115px 0px 0px -112px; /* offset vertical: half of image height and text area height */ 59 | /* offset horizontal: half of text area width */ 60 | } 61 | 62 | /* Landscape layout (with min-width) */ 63 | @media screen and (min-aspect-ratio: 1/1) and (min-width:400px) { 64 | .app { 65 | background-position:left center; 66 | padding:75px 0px 75px 170px; /* padding-top + padding-bottom + text area = image height */ 67 | margin:-90px 0px 0px -198px; /* offset vertical: half of image height */ 68 | /* offset horizontal: half of image width and text area width */ 69 | } 70 | } 71 | 72 | h1 { 73 | font-size:24px; 74 | font-weight:normal; 75 | margin:0px; 76 | overflow:visible; 77 | padding:0px; 78 | text-align:center; 79 | } 80 | 81 | .event { 82 | border-radius:4px; 83 | -webkit-border-radius:4px; 84 | color:#FFFFFF; 85 | font-size:12px; 86 | margin:0px 30px; 87 | padding:2px 0px; 88 | } 89 | 90 | .event.listening { 91 | background-color:#333333; 92 | display:block; 93 | } 94 | 95 | .event.received { 96 | background-color:#4B946A; 97 | display:none; 98 | } 99 | 100 | @keyframes fade { 101 | from { opacity: 1.0; } 102 | 50% { opacity: 0.4; } 103 | to { opacity: 1.0; } 104 | } 105 | 106 | @-webkit-keyframes fade { 107 | from { opacity: 1.0; } 108 | 50% { opacity: 0.4; } 109 | to { opacity: 1.0; } 110 | } 111 | 112 | .blink { 113 | animation:fade 3000ms infinite; 114 | -webkit-animation:fade 3000ms infinite; 115 | } 116 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "[0-9]+.[0-9]+.[0-9]+*" 7 | 8 | jobs: 9 | build: 10 | runs-on: macos-15-xlarge 11 | timeout-minutes: 20 12 | env: 13 | DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer 14 | 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v4 18 | - name: Check Version 19 | run: bash ./scripts/check_version.sh ${GITHUB_REF/refs\/tags\//} 20 | - name: Slack Notification 21 | uses: lazy-actions/slatify@master 22 | with: 23 | type: ${{ job.status }} 24 | job_name: "Airship Cordova Plugin Release Started!" 25 | url: ${{ secrets.SLACK_WEBHOOK }} 26 | - name: Install Android Build Tools 27 | run: | 28 | echo "y" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "build-tools;34.0.0" 29 | - name: Run CI 30 | run: | 31 | bash ./scripts/run_ci_tasks.sh -a -i 32 | - name: Slack Notification 33 | uses: lazy-actions/slatify@master 34 | if: failure() 35 | with: 36 | type: ${{ job.status }} 37 | job_name: "Airship Cordova Plugin Release Failed :(" 38 | url: ${{ secrets.SLACK_WEBHOOK }} 39 | 40 | deploy: 41 | needs: [build] 42 | runs-on: ubuntu-latest 43 | permissions: 44 | contents: write 45 | steps: 46 | - name: Checkout repository 47 | uses: actions/checkout@v4 48 | - name: Get the version 49 | id: get_version 50 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 51 | - name: Get the release notes 52 | id: get_release_notes 53 | run: | 54 | VERSION=${{ steps.get_version.outputs.VERSION }} 55 | NOTES="$(awk "/## Version $VERSION/{flag=1;next}/## Version/{flag=0}flag" CHANGELOG.md)" 56 | NOTES="${NOTES//'%'/'%25'}" 57 | NOTES="${NOTES//$'\n'/'%0A'}" 58 | NOTES="${NOTES//$'\r'/'%0D'}" 59 | echo ::set-output name=NOTES::"$NOTES" 60 | - name: Setup python 61 | uses: actions/setup-python@v4 62 | with: 63 | python-version: '3.9' 64 | - name: Export gcloud related env variable 65 | run: export CLOUDSDK_PYTHON="/usr/bin/python3" 66 | - uses: actions/setup-node@master 67 | with: 68 | node-version: 14 69 | registry-url: https://registry.npmjs.org/ 70 | - uses: google-github-actions/setup-gcloud@v0 71 | with: 72 | version: '351.0.0' 73 | service_account_email: ${{ secrets.GCP_SA_EMAIL }} 74 | service_account_key: ${{ secrets.GCP_SA_KEY }} 75 | 76 | - name: Publish modules 77 | run: | 78 | cd cordova-airship/ 79 | npm publish --access public 80 | cd - 81 | cd cordova-airship-hms/ 82 | npm publish --access public 83 | cd - 84 | env: 85 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 86 | 87 | - name: Docs 88 | run: | 89 | cd cordova-airship/ 90 | npm ci 91 | npm run generate-docs 92 | cd - 93 | bash ./scripts/upload_docs.sh ${GITHUB_REF/refs\/tags\//} ./cordova-airship/docs 94 | 95 | 96 | - name: Github Release 97 | id: create_release 98 | uses: softprops/action-gh-release@v1 99 | env: 100 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 101 | with: 102 | tag_name: ${{ steps.get_version.outputs.VERSION }} 103 | release_name: ${{ steps.get_version.outputs.VERSION }} 104 | body: ${{ steps.get_release_notes.outputs.NOTES }} 105 | draft: false 106 | prerelease: false 107 | 108 | - name: Slack Notification 109 | uses: lazy-actions/slatify@master 110 | with: 111 | type: ${{ job.status }} 112 | job_name: "Airship Cordova Plugin Released!" 113 | url: ${{ secrets.SLACK_WEBHOOK }} 114 | 115 | - name: Slack Notification 116 | uses: lazy-actions/slatify@master 117 | if: failure() 118 | with: 119 | type: ${{ job.status }} 120 | job_name: "Airship Cordova Plugin Release Failed :(" 121 | url: ${{ secrets.SLACK_WEBHOOK }} 122 | -------------------------------------------------------------------------------- /cordova-airship/src/android/CordovaAutopilot.kt: -------------------------------------------------------------------------------- 1 | /* Copyright Urban Airship and Contributors */ 2 | 3 | package com.urbanairship.cordova 4 | 5 | import android.content.Context 6 | import android.content.SharedPreferences 7 | import android.util.Log 8 | import androidx.annotation.Keep 9 | import androidx.core.app.NotificationManagerCompat 10 | import androidx.core.content.edit 11 | import com.urbanairship.AirshipConfigOptions 12 | import com.urbanairship.UAirship 13 | import com.urbanairship.analytics.Extension 14 | import com.urbanairship.android.framework.proxy.BaseAutopilot 15 | import com.urbanairship.android.framework.proxy.ProxyStore 16 | 17 | @Keep 18 | class CordovaAutopilot : BaseAutopilot() { 19 | 20 | companion object { 21 | private const val PREFERENCE_FILE = "com.urbanairship.ua_plugin_shared_preferences" 22 | private const val PROCESSED_NOTIFICATIONS_OPTED_OUT_FLAG = "com.urbanairship.PROCESSED_NOTIFICATIONS_OPTED_OUT_FLAG" 23 | } 24 | 25 | private var _settings: CordovaSettings? = null 26 | private fun settings(context: Context): CordovaSettings { 27 | return _settings ?: CordovaSettings.fromConfig(context).also { _settings = it } 28 | } 29 | 30 | private var _preferences: SharedPreferences? = null 31 | private fun preferences(context: Context): SharedPreferences { 32 | return _preferences ?: context.getSharedPreferences( 33 | PREFERENCE_FILE, 34 | Context.MODE_PRIVATE 35 | ).also { _preferences = it } 36 | } 37 | 38 | override fun onReady(context: Context, airship: UAirship) { 39 | Log.i("CordovaAutopilot", "onAirshipReady") 40 | 41 | airship.analytics.registerSDKExtension(Extension.CORDOVA, AirshipCordovaVersion.version); 42 | val settings = settings(context) 43 | val preferences = preferences(context) 44 | 45 | settings.disableNotificationsOnOptOut?.let { 46 | if (!NotificationManagerCompat.from(context).areNotificationsEnabled()) { 47 | when (it) { 48 | CordovaSettings.OptOutFrequency.ALWAYS -> { 49 | airship.pushManager.userNotificationsEnabled = false 50 | } 51 | 52 | CordovaSettings.OptOutFrequency.ONCE -> { 53 | if (!preferences.getBoolean(PROCESSED_NOTIFICATIONS_OPTED_OUT_FLAG, false)) { 54 | airship.pushManager.userNotificationsEnabled = false 55 | } 56 | } 57 | } 58 | } 59 | preferences.edit { putBoolean(PROCESSED_NOTIFICATIONS_OPTED_OUT_FLAG, true) } 60 | } 61 | 62 | if (settings.enablePushOnLaunch == true) { 63 | airship.pushManager.userNotificationsEnabled = true 64 | } 65 | } 66 | 67 | override fun onMigrateData(context: Context, proxyStore: ProxyStore) { 68 | val settings = settings(context) 69 | ProxyDataMigrator(preferences(context)).migrate(proxyStore, settings) 70 | proxyStore.defaultAutoLaunchMessageCenter = settings.autoLaunchMessageCenter ?: true 71 | } 72 | 73 | override fun createConfigBuilder(context: Context): AirshipConfigOptions.Builder { 74 | return super.createConfigBuilder(context).also { builder -> 75 | val settings = settings(context) 76 | 77 | if (settings.developmentAppKey != null && settings.developmentAppSecret != null) { 78 | builder.setDevelopmentAppKey(settings.developmentAppKey) 79 | builder.setDevelopmentAppSecret(settings.developmentAppSecret) 80 | } 81 | 82 | if (settings.productionAppKey != null && settings.productionAppSecret != null) { 83 | builder.setProductionAppKey(settings.productionAppKey) 84 | builder.setProductionAppSecret(settings.productionAppSecret) 85 | } 86 | 87 | settings.developmentLogLevel?.apply { builder.setDevelopmentLogLevel(this) } 88 | settings.productionLogLevel?.apply { builder.setProductionLogLevel(this) } 89 | settings.logPrivacyLevel?.apply { 90 | builder.setDevelopmentLogPrivacyLevel(this) 91 | builder.setProductionLogPrivacyLevel(this) 92 | } 93 | settings.inProduction?.apply { builder.setInProduction(this) } 94 | settings.enableAnalytics?.apply { builder.setAnalyticsEnabled(this) } 95 | settings.cloudSite?.apply { builder.setSite(this) } 96 | settings.defaultChannelId?.apply { builder.setNotificationChannel(this) } 97 | settings.fcmFirebaseAppName?.apply { builder.setFcmFirebaseAppName(this) } 98 | settings.initialConfigUrl?.apply { builder.setInitialConfigUrl(this) } 99 | 100 | settings.notificationIcon?.apply { builder.setNotificationIcon(this) } 101 | settings.notificationLargeIcon?.apply { builder.setNotificationLargeIcon(this) } 102 | settings.notificationAccentColor?.apply { builder.setNotificationAccentColor(this) } 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /cordova-airship/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Airship 5 | Airship Cordova plugin 6 | Apache 2.0 7 | cordova,urbanairship,airship 8 | https://github.com/urbanairship/urbanairship-cordova.git 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | remote-notification 93 | 94 | 95 | 96 | 97 | 17.3.0 98 | 99 | 100 | 101 | 102 | 105 | 108 | 109 | 110 | 111 | 112 | development 113 | 114 | 115 | 116 | production 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /cordova-airship/src/android/ProxyDataMigrator.kt: -------------------------------------------------------------------------------- 1 | /* Copyright Urban Airship and Contributors */ 2 | 3 | package com.urbanairship.cordova 4 | 5 | import android.content.SharedPreferences 6 | import androidx.core.content.edit 7 | import com.urbanairship.android.framework.proxy.NotificationConfig 8 | import com.urbanairship.android.framework.proxy.ProxyConfig 9 | import com.urbanairship.android.framework.proxy.ProxyStore 10 | 11 | internal class ProxyDataMigrator(private val preferences: SharedPreferences) { 12 | 13 | companion object { 14 | private const val PRODUCTION_KEY = "com.urbanairship.production_app_key" 15 | private const val PRODUCTION_SECRET = "com.urbanairship.production_app_secret" 16 | private const val DEVELOPMENT_KEY = "com.urbanairship.development_app_key" 17 | private const val DEVELOPMENT_SECRET = "com.urbanairship.development_app_secret" 18 | 19 | private const val DEFAULT_NOTIFICATION_CHANNEL_ID = "com.urbanairship.default_notification_channel_id" 20 | private const val AUTO_LAUNCH_MESSAGE_CENTER = "com.urbanairship.auto_launch_message_center" 21 | 22 | private const val NOTIFICATION_ICON = "com.urbanairship.notification_icon" 23 | private const val NOTIFICATION_LARGE_ICON = "com.urbanairship.notification_large_icon" 24 | private const val NOTIFICATION_ACCENT_COLOR = "com.urbanairship.notification_accent_color" 25 | 26 | private const val PREFERENCE_CENTER_PREFIX = "preference_" 27 | private const val PREFERENCE_CENTER_SUFFIX = "_use_custom_ui" 28 | 29 | private const val FOREGROUND_NOTIFICATIONS = "com.urbanairship.foreground_notifications" 30 | 31 | } 32 | 33 | internal fun migrate(store: ProxyStore, settings: CordovaSettings) { 34 | migrateAppConfig(store, settings) 35 | migrateAutoLaunchMessageCenter(store) 36 | migrateShowForegroundNotifications(store) 37 | migrateNotificationConfig(store) 38 | migrateAutoLaunchPreferenceCenter(store) 39 | } 40 | 41 | private fun migrateAppConfig(store: ProxyStore, settings: CordovaSettings) { 42 | val productionAppKey = preferences.getString(PRODUCTION_KEY, null) 43 | val productionAppSecret = preferences.getString(PRODUCTION_SECRET, null) 44 | val developmentAppKey = preferences.getString(DEVELOPMENT_KEY, null) 45 | val developmentAppSecret = preferences.getString(DEVELOPMENT_SECRET, null) 46 | 47 | var production: ProxyConfig.Environment? = null 48 | if (productionAppKey != null && productionAppSecret != null) { 49 | production = ProxyConfig.Environment( 50 | appKey = productionAppKey, 51 | appSecret = productionAppSecret, 52 | logLevel = settings.productionLogLevel 53 | ) 54 | } 55 | 56 | var development: ProxyConfig.Environment? = null 57 | if (developmentAppKey != null && developmentAppSecret != null) { 58 | development = ProxyConfig.Environment( 59 | appKey = productionAppKey, 60 | appSecret = productionAppSecret, 61 | logLevel = settings.productionLogLevel 62 | ) 63 | } 64 | 65 | if (development != null || production != null) { 66 | store.airshipConfig = ProxyConfig( 67 | productionEnvironment = production, 68 | developmentEnvironment = development 69 | ) 70 | 71 | preferences.edit { 72 | this.remove(DEVELOPMENT_KEY) 73 | this.remove(DEVELOPMENT_SECRET) 74 | this.remove(PRODUCTION_KEY) 75 | this.remove(PRODUCTION_SECRET) 76 | } 77 | } 78 | } 79 | 80 | private fun migrateAutoLaunchMessageCenter(store: ProxyStore) { 81 | if (!preferences.contains(AUTO_LAUNCH_MESSAGE_CENTER)) { 82 | return 83 | } 84 | 85 | store.isAutoLaunchMessageCenterEnabled = preferences.getBoolean(AUTO_LAUNCH_MESSAGE_CENTER, true) 86 | preferences.edit { this.remove(AUTO_LAUNCH_MESSAGE_CENTER) } 87 | } 88 | 89 | private fun migrateShowForegroundNotifications(store: ProxyStore) { 90 | if (!preferences.contains(FOREGROUND_NOTIFICATIONS)) { 91 | return 92 | } 93 | 94 | store.isForegroundNotificationsEnabled = preferences.getBoolean(FOREGROUND_NOTIFICATIONS, true) 95 | preferences.edit { this.remove(FOREGROUND_NOTIFICATIONS) } 96 | } 97 | 98 | private fun migrateNotificationConfig(store: ProxyStore) { 99 | val icon = preferences.getString(NOTIFICATION_ICON, null) 100 | val largeIcon = preferences.getString(NOTIFICATION_LARGE_ICON, null) 101 | val accentColor = preferences.getString(NOTIFICATION_ACCENT_COLOR, null) 102 | val defaultChannelId = preferences.getString(DEFAULT_NOTIFICATION_CHANNEL_ID, null) 103 | 104 | if (icon != null || largeIcon != null || accentColor != null || defaultChannelId != null) { 105 | store.notificationConfig = NotificationConfig( 106 | icon = icon, 107 | largeIcon = largeIcon, 108 | accentColor = accentColor, 109 | defaultChannelId = defaultChannelId 110 | ) 111 | } 112 | 113 | preferences.edit { 114 | this.remove(NOTIFICATION_ICON) 115 | this.remove(NOTIFICATION_LARGE_ICON) 116 | this.remove(NOTIFICATION_ACCENT_COLOR) 117 | this.remove(DEFAULT_NOTIFICATION_CHANNEL_ID) 118 | } 119 | } 120 | 121 | private fun migrateAutoLaunchPreferenceCenter(store: ProxyStore) { 122 | val keysToRemove = mutableListOf() 123 | // Preference Center 124 | preferences.all.filter { 125 | it.key.startsWith(PREFERENCE_CENTER_PREFIX) 126 | }.mapValues { 127 | if (it.value is Boolean) { it.value as Boolean } else { false } 128 | }.forEach { 129 | val preferenceCenterId = it.key 130 | .removePrefix(PREFERENCE_CENTER_PREFIX) 131 | .removeSuffix(PREFERENCE_CENTER_SUFFIX) 132 | 133 | store.setAutoLaunchPreferenceCenter(preferenceCenterId, !it.value) 134 | keysToRemove.add(it.key) 135 | } 136 | 137 | if (keysToRemove.isNotEmpty()) { 138 | preferences.edit { 139 | keysToRemove.forEach { this.remove(it) } 140 | } 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /cordova-airship/src/ios/AirshipCordovaProxyDataMigrator.swift: -------------------------------------------------------------------------------- 1 | /* Copyright Airship and Contributors */ 2 | 3 | import Foundation 4 | import AirshipFrameworkProxy 5 | import UserNotifications 6 | import AirshipKit 7 | 8 | struct AirshipCordovaProxyDataMigrator { 9 | 10 | private let defaults = UserDefaults.standard 11 | 12 | private static let productionAppKey = "com.urbanairship.production_app_key" 13 | private static let productionAppSecret = "com.urbanairship.production_app_secret" 14 | private static let site = "com.urbanairship.site" 15 | private static let developmentAppKey = "com.urbanairship.development_app_key" 16 | private static let developmentAppSecret = "com.urbanairship.development_app_secret" 17 | 18 | private static let messageCenterStyleConfigKey = "com.urbanairship.message.center.style.file" 19 | private static let autoLaunchMessageCenterKey = "com.urbanairship.auto_launch_message_center" 20 | 21 | private static let autoLaunchPreferenceCenterPrefix = "com.urbanairship.preference_" 22 | private static let autoLaunchPreferenceCenterSuffix = "_custom_ui" 23 | 24 | private static let notificationPresentationAlertKey = "com.urbanairship.ios_foreground_notification_presentation_alert" 25 | private static let notificationPresentationBadgeKey = "com.urbanairship.ios_foreground_notification_presentation_badge" 26 | private static let notificationPresentationSoundKey = "com.urbanairship.ios_foreground_notification_presentation_sound" 27 | 28 | 29 | @MainActor 30 | func migrateData(store: ProxyStore) { 31 | migrateConfig(store: store) 32 | migrateAutoLaunchMessageCenter(store: store) 33 | migrateAutoLaunchPreferenceCenter(store: store) 34 | migratePresentationOptions(store: store) 35 | } 36 | 37 | @MainActor 38 | private func migrateConfig(store: ProxyStore) { 39 | let productionAppKey = defaults.string(forKey: Self.productionAppKey) 40 | let productionAppSecret = defaults.string(forKey: Self.productionAppSecret) 41 | let developmentAppKey = defaults.string(forKey: Self.developmentAppKey) 42 | let developmentAppSecret = defaults.string(forKey: Self.developmentAppSecret) 43 | let messageCenterStyleConfig = defaults.string(forKey: Self.messageCenterStyleConfigKey) 44 | 45 | let site: ProxyConfig.Site? = if let string = defaults.string(forKey: Self.site) { 46 | AirshipCordovaSite(rawValue: string)?.proxyValue 47 | } else { 48 | nil 49 | } 50 | 51 | var production: ProxyConfig.Environment? 52 | if let productionAppKey, let productionAppSecret { 53 | production = ProxyConfig.Environment( 54 | logLevel: nil, 55 | appKey: productionAppKey, 56 | appSecret: productionAppSecret, 57 | ios: nil 58 | ) 59 | } 60 | 61 | var development: ProxyConfig.Environment? 62 | if let developmentAppKey, let developmentAppSecret { 63 | development = ProxyConfig.Environment( 64 | logLevel: nil, 65 | appKey: developmentAppKey, 66 | appSecret: developmentAppSecret, 67 | ios: nil 68 | ) 69 | } 70 | 71 | if (production != nil || development != nil ) { 72 | store.config = ProxyConfig( 73 | productionEnvironment: production, 74 | developmentEnvironment: development, 75 | ios: ProxyConfig.PlatformConfig( 76 | messageCenterStyleConfig: messageCenterStyleConfig 77 | ), 78 | site: site 79 | ) 80 | } 81 | 82 | [ 83 | Self.messageCenterStyleConfigKey, 84 | Self.productionAppKey, 85 | Self.productionAppSecret, 86 | Self.developmentAppKey, 87 | Self.developmentAppSecret, 88 | Self.site, 89 | ].forEach { 90 | defaults.removeObject(forKey: $0) 91 | } 92 | } 93 | 94 | @MainActor 95 | private func migrateAutoLaunchMessageCenter(store: ProxyStore) { 96 | guard 97 | let autoLaunchMessageCenter = defaults.object( 98 | forKey: Self.autoLaunchMessageCenterKey 99 | ) as? Bool 100 | else { 101 | return 102 | } 103 | 104 | store.autoDisplayMessageCenter = autoLaunchMessageCenter 105 | defaults.removeObject(forKey: Self.autoLaunchMessageCenterKey) 106 | } 107 | 108 | @MainActor 109 | private func migrateAutoLaunchPreferenceCenter(store: ProxyStore) { 110 | // Preference center 111 | defaults.dictionaryRepresentation().keys.forEach { key in 112 | if key.hasPrefix(Self.autoLaunchPreferenceCenterPrefix), 113 | key.hasSuffix(Self.autoLaunchPreferenceCenterSuffix) 114 | { 115 | var preferenceCenterID = String( 116 | key.dropFirst("Self.autoLaunchPreferenceCenterPrefix".count) 117 | ) 118 | 119 | preferenceCenterID = String( 120 | preferenceCenterID.dropLast(Self.autoLaunchPreferenceCenterSuffix.count) 121 | ) 122 | 123 | store.setAutoLaunchPreferenceCenter( 124 | preferenceCenterID, 125 | autoLaunch: defaults.bool(forKey: key) 126 | ) 127 | 128 | defaults.removeObject( 129 | forKey: key 130 | ) 131 | } 132 | } 133 | } 134 | 135 | @MainActor 136 | private func migratePresentationOptions(store: ProxyStore) { 137 | let alert = defaults.object(forKey: Self.notificationPresentationAlertKey) as? Bool 138 | let badge = defaults.object(forKey: Self.notificationPresentationBadgeKey) as? Bool 139 | let sound = defaults.object(forKey: Self.notificationPresentationSoundKey) as? Bool 140 | 141 | guard alert != nil || badge != nil || sound != nil else { return } 142 | 143 | var presentationOptions: UNNotificationPresentationOptions = [] 144 | 145 | if alert == true { 146 | presentationOptions.insert(.list) 147 | presentationOptions.insert(.banner) 148 | } 149 | 150 | if badge == true { 151 | presentationOptions.insert(.badge) 152 | } 153 | 154 | if sound == true { 155 | presentationOptions.insert(.sound) 156 | } 157 | 158 | store.foregroundPresentationOptions = presentationOptions 159 | 160 | [ 161 | Self.notificationPresentationAlertKey, 162 | Self.notificationPresentationBadgeKey, 163 | Self.notificationPresentationSoundKey 164 | ].forEach { 165 | defaults.removeObject(forKey: $0) 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /.github/workflows/prep-release.yaml: -------------------------------------------------------------------------------- 1 | name: Prepare Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | cordova_version: 7 | description: 'Cordova Version (x.y.z)' 8 | required: true 9 | pattern: '^\d+\.\d+\.\d+$' 10 | proxy_version: 11 | description: 'Airship Framework Proxy Version (x.y.z)' 12 | required: true 13 | pattern: '^\d+\.\d+\.\d+$' 14 | ios_version: 15 | description: 'iOS SDK Version (x.y.z)' 16 | required: false 17 | pattern: '^\d+\.\d+\.\d+$' 18 | android_version: 19 | description: 'Android SDK Version (x.y.z)' 20 | required: false 21 | pattern: '^\d+\.\d+\.\d+$' 22 | draft: 23 | description: 'Create as draft PR' 24 | type: boolean 25 | default: false 26 | 27 | permissions: 28 | contents: write 29 | pull-requests: write 30 | 31 | jobs: 32 | prepare-release: 33 | runs-on: macos-latest 34 | timeout-minutes: 15 35 | 36 | steps: 37 | - uses: actions/checkout@v4 38 | with: 39 | ref: main 40 | fetch-depth: 0 41 | token: ${{ secrets.GITHUB_TOKEN }} 42 | 43 | - name: Prepare Release 44 | run: ./scripts/prep_release.sh 45 | env: 46 | PLUGIN_VERSION: ${{ github.event.inputs.cordova_version }} 47 | PROXY_VERSION: ${{ github.event.inputs.proxy_version }} 48 | IOS_VERSION: ${{ github.event.inputs.ios_version }} 49 | ANDROID_VERSION: ${{ github.event.inputs.android_version }} 50 | GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} 51 | 52 | - name: Verify Changes 53 | id: verify 54 | run: | 55 | CHANGED_FILES=$(git diff --name-only) 56 | if [ -z "$CHANGED_FILES" ]; then 57 | echo "No files were changed!" 58 | exit 1 59 | fi 60 | echo "Changed files:" 61 | echo "$CHANGED_FILES" 62 | echo "changed_files<> $GITHUB_OUTPUT 63 | echo "$CHANGED_FILES" >> $GITHUB_OUTPUT 64 | echo "EOF" >> $GITHUB_OUTPUT 65 | 66 | - name: Create Pull Request 67 | id: create-pr 68 | uses: peter-evans/create-pull-request@v5 69 | with: 70 | token: ${{ secrets.GITHUB_TOKEN }} 71 | commit-message: | 72 | Release ${{ github.event.inputs.cordova_version }} 73 | title: "Release ${{ github.event.inputs.cordova_version }}" 74 | body: | 75 | - Framework Proxy: ${{ github.event.inputs.proxy_version }} 76 | - iOS SDK: ${{ github.event.inputs.ios_version }} 77 | - Android SDK: ${{ github.event.inputs.android_version }} 78 | 79 | ## Changed Files: 80 | ``` 81 | ${{ steps.verify.outputs.changed_files }} 82 | ``` 83 | branch: cordova-${{ github.event.inputs.cordova_version }} 84 | base: main 85 | labels: | 86 | release 87 | automated pr 88 | draft: ${{ github.event.inputs.draft }} 89 | delete-branch: true 90 | 91 | - name: Handle Success 92 | if: success() && steps.create-pr.outputs.pull-request-number 93 | run: | 94 | echo "Pull request created successfully" 95 | echo "PR Number: ${{ steps.create-pr.outputs.pull-request-number }}" 96 | echo "PR URL: ${{ steps.create-pr.outputs.pull-request-url }}" 97 | 98 | - name: Gemini PR Review 99 | if: success() && steps.create-pr.outputs.pull-request-number && env.GEMINI_API_KEY != '' 100 | continue-on-error: true 101 | env: 102 | GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} 103 | run: | 104 | # Get PR diff 105 | PR_DIFF=$(git diff main...cordova-${{ github.event.inputs.cordova_version }}) 106 | 107 | # Create review prompt 108 | PROMPT="Review this Cordova Airship plugin release PR. Check for: 109 | 1. Version consistency across plugin.xml, package.json, and version files 110 | 2. Changelog format and completeness 111 | 3. SDK version links are correct 112 | 4. Proxy version is updated correctly in both core and HMS packages 113 | 114 | PR Details: 115 | - Plugin version: ${{ github.event.inputs.cordova_version }} 116 | - Proxy version: ${{ github.event.inputs.proxy_version }} 117 | - iOS SDK: ${{ github.event.inputs.ios_version }} 118 | - Android SDK: ${{ github.event.inputs.android_version }} 119 | 120 | Diff: 121 | \`\`\` 122 | ${PR_DIFF} 123 | \`\`\` 124 | 125 | Provide a concise review with any issues or suggestions." 126 | 127 | # Call Gemini API 128 | RESPONSE=$(curl -s -X POST \ 129 | "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${GEMINI_API_KEY}" \ 130 | -H "Content-Type: application/json" \ 131 | -d "{ 132 | \"contents\": [{ 133 | \"parts\": [{\"text\": $(echo "$PROMPT" | jq -Rs .)}] 134 | }], 135 | \"generationConfig\": { 136 | \"temperature\": 0.3, 137 | \"maxOutputTokens\": 2048 138 | } 139 | }") 140 | 141 | # Extract review text 142 | REVIEW=$(echo "$RESPONSE" | jq -r '.candidates[0].content.parts[0].text // empty') 143 | 144 | if [ -n "$REVIEW" ]; then 145 | # Post review as PR comment 146 | gh pr comment ${{ steps.create-pr.outputs.pull-request-number }} \ 147 | --body "## 🤖 Gemini AI Review 148 | 149 | ${REVIEW} 150 | 151 | --- 152 | *This is an automated review. Please verify all changes manually.*" 153 | else 154 | echo "Gemini review failed or returned empty response" 155 | fi 156 | 157 | - name: Slack Notification (Success) 158 | if: success() && steps.create-pr.outputs.pull-request-number 159 | uses: homoluctus/slatify@master 160 | with: 161 | type: success 162 | job_name: ":tada: Cordova plugin release pull request generated :tada:" 163 | message: "@mobile-team A new Cordova plugin release pull request for (v${{ github.event.inputs.cordova_version }}) is ready! :rocket:" 164 | url: ${{ secrets.MOBILE_SLACK_WEBHOOK }} 165 | 166 | - name: Handle Failure 167 | if: failure() 168 | run: | 169 | echo "::error::Release preparation failed. Please check the logs above for details." 170 | exit 1 171 | 172 | - name: Slack Notification (Failure) 173 | if: failure() 174 | uses: homoluctus/slatify@master 175 | with: 176 | type: failure 177 | job_name: ":disappointed: Cordova Plugin Release Failed :disappointed:" 178 | message: "@crow The release preparation failed. Please check the workflow logs. :sob:" 179 | url: ${{ secrets.MOBILE_SLACK_WEBHOOK }} 180 | -------------------------------------------------------------------------------- /Example/css/mobiscroll.core-2.0.1.css: -------------------------------------------------------------------------------- 1 | /* Datewheel overlay */ 2 | .dw { 3 | min-width:170px; 4 | padding: 0 10px; 5 | position: absolute; 6 | top: 5%; 7 | left: 0; 8 | z-index: 1001; 9 | color: #000; 10 | font-family: arial, verdana, sans-serif; 11 | font-size: 12px; 12 | text-shadow: none; 13 | } 14 | .dwi { 15 | position: static; 16 | margin: 5px; 17 | display: inline-block; 18 | } 19 | .dwwr { 20 | zoom: 1; 21 | } 22 | /* Datewheel overlay background */ 23 | .dwo { 24 | width: 100%; 25 | background: #000; 26 | position: absolute; 27 | top: 0; 28 | left: 0; 29 | z-index: 1000; 30 | opacity: .7; 31 | filter:Alpha(Opacity=70); 32 | } 33 | /* Datewheel wheel container wrapper */ 34 | .dwc { 35 | float: left; 36 | margin: 0 2px 5px 2px; 37 | padding-top: 30px; 38 | } 39 | .dwcc { 40 | clear: both; 41 | } 42 | /* Datewheel label */ 43 | .dwl { 44 | /*margin: 0 2px;*/ 45 | text-align: center; 46 | line-height: 30px; 47 | height: 30px; 48 | white-space: nowrap; 49 | position: absolute; 50 | top: -30px; 51 | width: 100%; 52 | } 53 | /* Datewheel value */ 54 | .dwv { 55 | padding: 10px 0; 56 | border-bottom: 1px solid #000; 57 | } 58 | /* Datewheel wheel container */ 59 | .dwrc { 60 | -webkit-border-radius: 3px; 61 | -moz-border-radius: 3px; 62 | border-radius: 3px; 63 | } 64 | .dwwc { 65 | margin: 0; 66 | padding: 0 2px; 67 | position: relative; 68 | background: #000; 69 | zoom:1; 70 | } 71 | /* Datewheel wheels */ 72 | .dwwl { 73 | margin: 4px 2px; 74 | position: relative; 75 | background: #888; 76 | background: -webkit-gradient(linear,left bottom,left top,color-stop(0, #000),color-stop(0.35, #333),color-stop(0.50, #888),color-stop(0.65, #333),color-stop(1, #000)); 77 | background: -moz-linear-gradient(#000 0%,#333 35%, #888 50%,#333 65%,#000 100%); 78 | background: -ms-linear-gradient(#000 0%,#333 35%, #888 50%,#333 65%,#000 100%); 79 | background: -o-linear-gradient(#000 0%,#333 35%, #888 50%,#333 65%,#000 100%); 80 | } 81 | .dww { 82 | margin: 0 2px; 83 | overflow: hidden; 84 | position: relative; 85 | /*top: -30px;*/ 86 | } 87 | .dwsc .dww { 88 | color: #fff; 89 | background: #444; 90 | background: -webkit-gradient(linear,left bottom,left top,color-stop(0, #000),color-stop(0.45, #444),color-stop(0.55, #444),color-stop(1, #000)); 91 | background: -moz-linear-gradient(#000 0%,#444 45%, #444 55%, #000 100%); 92 | background: -ms-linear-gradient(#000 0%,#444 45%, #444 55%, #000 100%); 93 | background: -o-linear-gradient(#000 0%,#444 45%, #444 55%, #000 100%); 94 | } 95 | .dww ul { 96 | list-style: none; 97 | margin: 0; 98 | padding: 0; 99 | /*display: block; 100 | width: 100%;*/ 101 | position: relative; 102 | z-index: 2; 103 | } 104 | .dww li { 105 | list-style: none; 106 | margin: 0; 107 | padding: 0 5px; 108 | display: block; 109 | text-align: center; 110 | line-height: 40px; 111 | font-size: 26px; 112 | white-space: nowrap; 113 | text-shadow: 0 1px 1px #000; 114 | opacity: .3; 115 | filter: Alpha(Opacity=30); 116 | } 117 | /* Valid entry */ 118 | .dww li.dw-v { 119 | opacity: 1; 120 | filter: Alpha(Opacity=100); 121 | } 122 | /* Hidden entry */ 123 | .dww li.dw-h { 124 | visibility: hidden; 125 | } 126 | /* Wheel +/- buttons */ 127 | .dwwb { 128 | position: absolute; 129 | z-index: 4; 130 | left: 0; 131 | cursor: pointer; 132 | width: 100%; 133 | height: 40px; 134 | text-align: center; 135 | } 136 | .dwwbp { 137 | top: 0; 138 | -webkit-border-radius: 3px 3px 0 0; 139 | -moz-border-radius: 3px 3px 0 0; 140 | border-radius: 3px 3px 0 0; 141 | font-size: 40px; 142 | } 143 | .dwwbm { 144 | bottom: 0; 145 | -webkit-border-radius: 0 0 3px 3px; 146 | -moz-border-radius: 0 0 3px 3px; 147 | border-radius: 0 0 3px 3px; 148 | font-size: 32px; 149 | font-weight: bold; 150 | } 151 | .dwpm .dwwc { 152 | background: transparent; 153 | } 154 | .dwpm .dww { 155 | margin: -1px; 156 | } 157 | .dwpm .dww li { 158 | text-shadow: none; 159 | } 160 | .dwpm .dwwol { 161 | display: none; 162 | } 163 | /* Datewheel wheel overlay */ 164 | .dwwo { 165 | position: absolute; 166 | z-index: 3; 167 | top: 0; 168 | left: 0; 169 | width: 100%; 170 | height: 100%; 171 | background: -webkit-gradient(linear,left bottom,left top,color-stop(0, #000),color-stop(0.52, rgba(44,44,44,0)),color-stop(0.48, rgba(44,44,44,0)),color-stop(1, #000)); 172 | background: -moz-linear-gradient(#000 0%,rgba(44,44,44,0) 52%, rgba(44,44,44,0) 48%, #000 100%); 173 | background: -ms-linear-gradient(#000 0%,rgba(44,44,44,0) 52%, rgba(44,44,44,0) 48%, #000 100%); 174 | background: -o-linear-gradient(#000 0%,rgba(44,44,44,0) 52%, rgba(44,44,44,0) 48%, #000 100%); 175 | } 176 | /* Background line */ 177 | .dwwol { 178 | position: absolute; 179 | z-index: 1; 180 | top: 50%; 181 | left: 0; 182 | width: 100%; 183 | height: 0; 184 | margin-top: -1px; 185 | border-top: 1px solid #333; 186 | border-bottom: 1px solid #555; 187 | } 188 | /* Datewheel button */ 189 | .dwbg .dwb { 190 | display: block; 191 | height: 40px; 192 | line-height: 40px; 193 | padding: 0 15px; 194 | margin: 0 2px; 195 | font-size: 14px; 196 | font-weight: bold; 197 | text-decoration: none; 198 | text-shadow:0 -1px 1px #000; 199 | border-radius: 5px; 200 | -moz-border-radius: 5px; 201 | -webkit-border-radius:5px; 202 | box-shadow:0 1px 3px rgba(0,0,0,0.5); 203 | -moz-box-shadow:0 1px 3px rgba(0,0,0,0.5); 204 | -webkit-box-shadow:0 1px 3px rgba(0,0,0,0.5); 205 | color: #fff; 206 | background:#000; 207 | background:-webkit-gradient(linear,left bottom,left top,color-stop(0.5, #000),color-stop(0.5, #6e6e6e)); 208 | background:-moz-linear-gradient(#6e6e6e 50%,#000 50%); 209 | background:-ms-linear-gradient(#6e6e6e 50%,#000 50%); 210 | background:-o-linear-gradient(#6e6e6e 50%,#000 50%); 211 | } 212 | /* Datewheel button container */ 213 | .dwbc { 214 | /*margin-top: 5px;*/ 215 | padding: 5px 0; 216 | text-align: center; 217 | clear: both; 218 | } 219 | /* Datewheel button wrapper */ 220 | .dwbw { 221 | display: inline-block; 222 | width: 50%; 223 | } 224 | /* Hidden label */ 225 | .dwhl { 226 | padding-top: 10px; 227 | } 228 | .dwhl .dwl { 229 | display: none; 230 | } 231 | /* Backgrounds */ 232 | .dwbg { 233 | background: #fff; 234 | border-radius: 3px; 235 | -webkit-border-radius: 3px; 236 | -moz-border-radius: 3px; 237 | } 238 | .dwbg .dwpm .dww { 239 | color: #000; 240 | background: #fff; 241 | border: 1px solid #AAA; 242 | } 243 | .dwbg .dwwb { 244 | background: #ccc; 245 | color: #888; 246 | text-shadow: 0 -1px 1px #333; 247 | box-shadow: 0 0 5px #333; 248 | -webkit-box-shadow: 0 0 5px #333; 249 | -moz-box-shadow: 0 0 5px #333; 250 | } 251 | .dwbg .dwwbp { 252 | background: -webkit-gradient(linear,left bottom,left top,color-stop(0, #bdbdbd),color-stop(1, #f7f7f7)); 253 | background: -moz-linear-gradient(#f7f7f7,#bdbdbd); 254 | background: -ms-linear-gradient(#f7f7f7,#bdbdbd); 255 | background: -o-linear-gradient(#f7f7f7,#bdbdbd); 256 | } 257 | .dwbg .dwwbm { 258 | background: -webkit-gradient(linear,left bottom,left top,color-stop(0, #f7f7f7),color-stop(1, #bdbdbd)); 259 | background: -moz-linear-gradient(#bdbdbd,#f7f7f7); 260 | background: -ms-linear-gradient(#bdbdbd,#f7f7f7); 261 | background: -o-linear-gradient(#bdbdbd,#f7f7f7); 262 | } 263 | .dwbg .dwb-a { 264 | background:#3c7500; 265 | background:-webkit-gradient(linear,left bottom,left top,color-stop(0.5, #3c7500),color-stop(0.5, #94c840)); 266 | background:-moz-linear-gradient(#94c840 50%,#3c7500 50%); 267 | background:-ms-linear-gradient(#94c840 50%,#3c7500 50%); 268 | background:-o-linear-gradient(#94c840 50%,#3c7500 50%); 269 | } 270 | .dwbg .dwwl .dwb-a { 271 | background:#3c7500; 272 | background:-webkit-gradient(linear,left bottom,left top,color-stop(0, #3c7500),color-stop(1, #94c840)); 273 | background:-moz-linear-gradient(#94c840,#3c7500); 274 | background:-ms-linear-gradient(#94c840,#3c7500); 275 | background:-o-linear-gradient(#94c840,#3c7500); 276 | } 277 | -------------------------------------------------------------------------------- /scripts/prep_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Unified release preparation script for Cordova Airship 5 | # Combines: update_version.sh, update_proxy_version.sh, update_changelog.sh 6 | # Usage: ./prep_release.sh [--dry-run] 7 | # Env vars: PLUGIN_VERSION, PROXY_VERSION, IOS_VERSION, ANDROID_VERSION, GEMINI_API_KEY 8 | 9 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 10 | REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" 11 | 12 | # Parse arguments 13 | DRY_RUN=false 14 | if [ "$1" = "--dry-run" ]; then 15 | DRY_RUN=true 16 | echo "🔍 DRY RUN MODE - No changes will be made" 17 | echo "" 18 | fi 19 | 20 | # Required env vars 21 | if [ -z "$PLUGIN_VERSION" ]; then 22 | echo "Error: PLUGIN_VERSION environment variable required" 23 | exit 1 24 | fi 25 | 26 | # Optional env vars 27 | PROXY_VERSION="${PROXY_VERSION:-}" 28 | IOS_VERSION="${IOS_VERSION:-}" 29 | ANDROID_VERSION="${ANDROID_VERSION:-}" 30 | GEMINI_API_KEY="${GEMINI_API_KEY:-}" 31 | 32 | echo "Cordova Airship Release Preparation" 33 | echo "====================================" 34 | echo "Plugin version: $PLUGIN_VERSION" 35 | echo "Proxy version: ${PROXY_VERSION:-not specified}" 36 | echo "iOS SDK: ${IOS_VERSION:-not specified}" 37 | echo "Android SDK: ${ANDROID_VERSION:-not specified}" 38 | echo "" 39 | 40 | # Function to polish changelog with Gemini (graceful degradation) 41 | polish_changelog() { 42 | local changelog_text="$1" 43 | 44 | if [ -z "$GEMINI_API_KEY" ]; then 45 | echo "$changelog_text" 46 | return 0 47 | fi 48 | 49 | local prompt="Polish this Cordova Airship changelog entry. Keep it concise and professional. 50 | 51 | Changelog: 52 | $changelog_text 53 | 54 | Return ONLY the polished changelog text." 55 | 56 | local response 57 | response=$(curl -s -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=$GEMINI_API_KEY" \ 58 | -H "Content-Type: application/json" \ 59 | -d "{ 60 | \"contents\": [{ 61 | \"parts\": [{\"text\": \"$(echo "$prompt" | sed 's/"/\\"/g' | tr '\n' ' ')\"}] 62 | }], 63 | \"generationConfig\": { 64 | \"temperature\": 0.3, 65 | \"maxOutputTokens\": 1024 66 | } 67 | }" 2>&1) 68 | 69 | if echo "$response" | grep -q "error"; then 70 | echo "$changelog_text" 71 | return 0 72 | fi 73 | 74 | local polished 75 | polished=$(echo "$response" | grep -o '"text"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/"text"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//' | head -1) 76 | 77 | if [ -n "$polished" ]; then 78 | echo "$polished" 79 | else 80 | echo "$changelog_text" 81 | fi 82 | } 83 | 84 | # File paths 85 | CORE_PACKAGE_PATH="$REPO_ROOT/cordova-airship" 86 | HMS_PACKAGE_PATH="$REPO_ROOT/cordova-airship-hms" 87 | ANDROID_VERSION_PATH="$REPO_ROOT/cordova-airship/src/android/AirshipCordovaVersion.kt" 88 | IOS_VERSION_PATH="$REPO_ROOT/cordova-airship/src/ios/AirshipCordovaVersion.swift" 89 | HMS_PLUGIN_XML_PATH="$REPO_ROOT/cordova-airship-hms/plugin.xml" 90 | CORE_PLUGIN_XML_PATH="$REPO_ROOT/cordova-airship/plugin.xml" 91 | 92 | # Step 1: Update plugin version 93 | echo "[1/4] Updating plugin version..." 94 | if [ "$DRY_RUN" = "false" ]; then 95 | sed -i '' "s/var version = \"[-0-9.a-zA-Z]*\"/var version = \"$PLUGIN_VERSION\"/" "$ANDROID_VERSION_PATH" 96 | sed -i '' "s/static let version = \"[-0-9.a-zA-Z]*\"/static let version = \"$PLUGIN_VERSION\"/" "$IOS_VERSION_PATH" 97 | sed -i '' "s///" "$HMS_PLUGIN_XML_PATH" 100 | npm --prefix "$CORE_PACKAGE_PATH" version "$PLUGIN_VERSION" --no-git-tag-version 101 | npm --prefix "$HMS_PACKAGE_PATH" version "$PLUGIN_VERSION" --no-git-tag-version 102 | echo " ✓ Updated version files and package.json" 103 | else 104 | echo " Would update:" 105 | echo " - $ANDROID_VERSION_PATH" 106 | echo " - $IOS_VERSION_PATH" 107 | echo " - $CORE_PLUGIN_XML_PATH" 108 | echo " - $HMS_PLUGIN_XML_PATH" 109 | echo " - $CORE_PACKAGE_PATH/package.json" 110 | echo " - $HMS_PACKAGE_PATH/package.json" 111 | fi 112 | 113 | # Step 2: Update proxy version (if specified) 114 | if [ -n "$PROXY_VERSION" ]; then 115 | echo "[2/4] Updating proxy version..." 116 | if [ "$DRY_RUN" = "false" ]; then 117 | sed -i '' -E "s/(pod name=\"AirshipFrameworkProxy\" spec=\")[^\"]*\"/\1$PROXY_VERSION\"/" "$CORE_PLUGIN_XML_PATH" 118 | sed -i '' -E "s/(api \"com.urbanairship.android:airship-framework-proxy:)[^\"]*\"/\1$PROXY_VERSION\"/" "$REPO_ROOT/cordova-airship/src/android/build-extras.gradle" 119 | sed -i '' -E "s/(implementation \"com.urbanairship.android:airship-framework-proxy-hms:)[^\"]*\"/\1$PROXY_VERSION\"/" "$REPO_ROOT/cordova-airship-hms/src/android/build-extras.gradle" 120 | echo " ✓ Updated plugin.xml and build-extras.gradle" 121 | else 122 | echo " Would update proxy version to $PROXY_VERSION" 123 | fi 124 | else 125 | echo "[2/4] Skipping proxy version update (not specified)" 126 | fi 127 | 128 | # Step 3: Generate changelog 129 | echo "[3/4] Generating changelog..." 130 | 131 | RELEASE_DATE=$(date +"%B %-d, %Y") 132 | 133 | # Determine release type 134 | if [[ $PLUGIN_VERSION =~ \.0\.0$ ]]; then 135 | RELEASE_TYPE="Major" 136 | elif [[ $PLUGIN_VERSION =~ \.0$ ]]; then 137 | RELEASE_TYPE="Minor" 138 | else 139 | RELEASE_TYPE="Patch" 140 | fi 141 | 142 | # Build changelog entry 143 | NEW_ENTRY="## Version $PLUGIN_VERSION - $RELEASE_DATE\n\n" 144 | 145 | if [ -n "$IOS_VERSION" ] || [ -n "$ANDROID_VERSION" ]; then 146 | NEW_ENTRY+="$RELEASE_TYPE release that updates" 147 | 148 | if [ -n "$ANDROID_VERSION" ]; then 149 | NEW_ENTRY+=" the Android SDK to $ANDROID_VERSION" 150 | fi 151 | 152 | if [ -n "$IOS_VERSION" ] && [ -n "$ANDROID_VERSION" ]; then 153 | NEW_ENTRY+=" and" 154 | fi 155 | 156 | if [ -n "$IOS_VERSION" ]; then 157 | NEW_ENTRY+=" the iOS SDK to $IOS_VERSION" 158 | fi 159 | 160 | NEW_ENTRY+="\n\n### Changes\n" 161 | 162 | if [ -n "$ANDROID_VERSION" ]; then 163 | NEW_ENTRY+="- Updated Android SDK to [$ANDROID_VERSION](https://github.com/urbanairship/android-library/releases/tag/$ANDROID_VERSION)" 164 | fi 165 | 166 | if [ -n "$IOS_VERSION" ]; then 167 | NEW_ENTRY+="\n" 168 | NEW_ENTRY+="- Updated iOS SDK to [$IOS_VERSION](https://github.com/urbanairship/ios-library/releases/tag/$IOS_VERSION)" 169 | fi 170 | else 171 | NEW_ENTRY+="$RELEASE_TYPE release." 172 | fi 173 | 174 | # Polish with Gemini if available 175 | if [ "$DRY_RUN" = "false" ]; then 176 | POLISHED_ENTRY=$(polish_changelog "$(echo -e "$NEW_ENTRY")") 177 | 178 | # Update CHANGELOG.md 179 | TEMP_FILE=$(mktemp) 180 | echo "# Cordova Plugin Changelog" > "$TEMP_FILE" 181 | echo -e "\n$POLISHED_ENTRY" >> "$TEMP_FILE" 182 | tail -n +2 "$REPO_ROOT/CHANGELOG.md" >> "$TEMP_FILE" 183 | mv "$TEMP_FILE" "$REPO_ROOT/CHANGELOG.md" 184 | echo " ✓ Updated CHANGELOG.md" 185 | else 186 | echo " Would add to CHANGELOG.md:" 187 | echo -e "$NEW_ENTRY" | sed 's/^/ /' 188 | fi 189 | 190 | # Step 4: Validation 191 | echo "[4/4] Validation..." 192 | if [ "$DRY_RUN" = "false" ]; then 193 | # Verify changes 194 | if grep -q "var version = \"$PLUGIN_VERSION\"" "$ANDROID_VERSION_PATH"; then 195 | echo " ✓ Android version verified" 196 | else 197 | echo " ✗ Android version mismatch" 198 | exit 1 199 | fi 200 | 201 | if grep -q "static let version = \"$PLUGIN_VERSION\"" "$IOS_VERSION_PATH"; then 202 | echo " ✓ iOS version verified" 203 | else 204 | echo " ✗ iOS version mismatch" 205 | exit 1 206 | fi 207 | 208 | if [ -n "$PROXY_VERSION" ]; then 209 | if grep -q "pod name=\"AirshipFrameworkProxy\" spec=\"$PROXY_VERSION\"" "$CORE_PLUGIN_XML_PATH"; then 210 | echo " ✓ Proxy version verified" 211 | else 212 | echo " ✗ Proxy version mismatch" 213 | exit 1 214 | fi 215 | fi 216 | 217 | echo "" 218 | echo "✅ Release preparation complete!" 219 | echo "" 220 | echo "Next steps:" 221 | echo "1. Review changes: git diff" 222 | echo "2. Commit changes" 223 | echo "3. Create PR" 224 | else 225 | echo " Dry-run complete - no changes made" 226 | fi 227 | -------------------------------------------------------------------------------- /cordova-airship/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ua/cordova-airship", 3 | "version": "17.5.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@ua/cordova-airship", 9 | "version": "17.5.1", 10 | "engines": [ 11 | { 12 | "name": "cordova-android", 13 | "version": ">=11.0.0" 14 | }, 15 | { 16 | "name": "cordova-ios", 17 | "version": ">=7.0.0" 18 | }, 19 | { 20 | "name": "cordova-plugman", 21 | "version": ">=4.2.0" 22 | } 23 | ], 24 | "license": "Apache 2.0", 25 | "devDependencies": { 26 | "typedoc": "0.23.24", 27 | "typescript": "~4.8.0" 28 | } 29 | }, 30 | "node_modules/balanced-match": { 31 | "version": "1.0.2", 32 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 33 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 34 | "dev": true 35 | }, 36 | "node_modules/brace-expansion": { 37 | "version": "2.0.1", 38 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 39 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 40 | "dev": true, 41 | "dependencies": { 42 | "balanced-match": "^1.0.0" 43 | } 44 | }, 45 | "node_modules/jsonc-parser": { 46 | "version": "3.2.1", 47 | "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", 48 | "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", 49 | "dev": true 50 | }, 51 | "node_modules/lunr": { 52 | "version": "2.3.9", 53 | "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", 54 | "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", 55 | "dev": true 56 | }, 57 | "node_modules/marked": { 58 | "version": "4.3.0", 59 | "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", 60 | "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", 61 | "dev": true, 62 | "bin": { 63 | "marked": "bin/marked.js" 64 | }, 65 | "engines": { 66 | "node": ">= 12" 67 | } 68 | }, 69 | "node_modules/minimatch": { 70 | "version": "5.1.6", 71 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", 72 | "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", 73 | "dev": true, 74 | "dependencies": { 75 | "brace-expansion": "^2.0.1" 76 | }, 77 | "engines": { 78 | "node": ">=10" 79 | } 80 | }, 81 | "node_modules/shiki": { 82 | "version": "0.12.1", 83 | "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.12.1.tgz", 84 | "integrity": "sha512-aieaV1m349rZINEBkjxh2QbBvFFQOlgqYTNtCal82hHj4dDZ76oMlQIX+C7ryerBTDiga3e5NfH6smjdJ02BbQ==", 85 | "dev": true, 86 | "dependencies": { 87 | "jsonc-parser": "^3.2.0", 88 | "vscode-oniguruma": "^1.7.0", 89 | "vscode-textmate": "^8.0.0" 90 | } 91 | }, 92 | "node_modules/typedoc": { 93 | "version": "0.23.24", 94 | "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.24.tgz", 95 | "integrity": "sha512-bfmy8lNQh+WrPYcJbtjQ6JEEsVl/ce1ZIXyXhyW+a1vFrjO39t6J8sL/d6FfAGrJTc7McCXgk9AanYBSNvLdIA==", 96 | "dev": true, 97 | "dependencies": { 98 | "lunr": "^2.3.9", 99 | "marked": "^4.2.5", 100 | "minimatch": "^5.1.2", 101 | "shiki": "^0.12.1" 102 | }, 103 | "bin": { 104 | "typedoc": "bin/typedoc" 105 | }, 106 | "engines": { 107 | "node": ">= 14.14" 108 | }, 109 | "peerDependencies": { 110 | "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x" 111 | } 112 | }, 113 | "node_modules/typescript": { 114 | "version": "4.8.4", 115 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", 116 | "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", 117 | "dev": true, 118 | "bin": { 119 | "tsc": "bin/tsc", 120 | "tsserver": "bin/tsserver" 121 | }, 122 | "engines": { 123 | "node": ">=4.2.0" 124 | } 125 | }, 126 | "node_modules/vscode-oniguruma": { 127 | "version": "1.7.0", 128 | "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", 129 | "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", 130 | "dev": true 131 | }, 132 | "node_modules/vscode-textmate": { 133 | "version": "8.0.0", 134 | "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", 135 | "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", 136 | "dev": true 137 | } 138 | }, 139 | "dependencies": { 140 | "balanced-match": { 141 | "version": "1.0.2", 142 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 143 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 144 | "dev": true 145 | }, 146 | "brace-expansion": { 147 | "version": "2.0.1", 148 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 149 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 150 | "dev": true, 151 | "requires": { 152 | "balanced-match": "^1.0.0" 153 | } 154 | }, 155 | "jsonc-parser": { 156 | "version": "3.2.1", 157 | "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", 158 | "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", 159 | "dev": true 160 | }, 161 | "lunr": { 162 | "version": "2.3.9", 163 | "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", 164 | "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", 165 | "dev": true 166 | }, 167 | "marked": { 168 | "version": "4.3.0", 169 | "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", 170 | "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", 171 | "dev": true 172 | }, 173 | "minimatch": { 174 | "version": "5.1.6", 175 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", 176 | "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", 177 | "dev": true, 178 | "requires": { 179 | "brace-expansion": "^2.0.1" 180 | } 181 | }, 182 | "shiki": { 183 | "version": "0.12.1", 184 | "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.12.1.tgz", 185 | "integrity": "sha512-aieaV1m349rZINEBkjxh2QbBvFFQOlgqYTNtCal82hHj4dDZ76oMlQIX+C7ryerBTDiga3e5NfH6smjdJ02BbQ==", 186 | "dev": true, 187 | "requires": { 188 | "jsonc-parser": "^3.2.0", 189 | "vscode-oniguruma": "^1.7.0", 190 | "vscode-textmate": "^8.0.0" 191 | } 192 | }, 193 | "typedoc": { 194 | "version": "0.23.24", 195 | "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.24.tgz", 196 | "integrity": "sha512-bfmy8lNQh+WrPYcJbtjQ6JEEsVl/ce1ZIXyXhyW+a1vFrjO39t6J8sL/d6FfAGrJTc7McCXgk9AanYBSNvLdIA==", 197 | "dev": true, 198 | "requires": { 199 | "lunr": "^2.3.9", 200 | "marked": "^4.2.5", 201 | "minimatch": "^5.1.2", 202 | "shiki": "^0.12.1" 203 | } 204 | }, 205 | "typescript": { 206 | "version": "4.8.4", 207 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", 208 | "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", 209 | "dev": true 210 | }, 211 | "vscode-oniguruma": { 212 | "version": "1.7.0", 213 | "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", 214 | "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", 215 | "dev": true 216 | }, 217 | "vscode-textmate": { 218 | "version": "8.0.0", 219 | "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", 220 | "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", 221 | "dev": true 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /cordova-airship/src/android/CordovaSettings.kt: -------------------------------------------------------------------------------- 1 | /* Copyright Urban Airship and Contributors */ 2 | 3 | package com.urbanairship.cordova 4 | 5 | import android.content.Context 6 | import android.content.res.XmlResourceParser 7 | import android.graphics.Color 8 | import android.util.Log 9 | import androidx.annotation.ColorInt 10 | import com.urbanairship.AirshipConfigOptions 11 | import com.urbanairship.UALog 12 | 13 | internal data class CordovaSettings( 14 | val cloudSite: String?, 15 | val productionAppKey: String?, 16 | val productionAppSecret: String?, 17 | val productionLogLevel: Int?, 18 | val developmentAppKey: String?, 19 | val developmentAppSecret: String?, 20 | val developmentLogLevel: Int?, 21 | val autoLaunchMessageCenter: Boolean?, 22 | val enablePushOnLaunch: Boolean?, 23 | val enableAnalytics: Boolean?, 24 | val notificationIcon: Int?, 25 | val notificationLargeIcon: Int?, 26 | val notificationAccentColor: Int?, 27 | val inProduction: Boolean?, 28 | val fcmFirebaseAppName: String?, 29 | val initialConfigUrl: String?, 30 | val defaultChannelId: String?, 31 | val disableNotificationsOnOptOut: OptOutFrequency?, 32 | val logPrivacyLevel: AirshipConfigOptions.PrivacyLevel? 33 | ) { 34 | 35 | enum class OptOutFrequency { 36 | ALWAYS, ONCE 37 | } 38 | 39 | companion object { 40 | private const val UA_PREFIX = "com.urbanairship" 41 | 42 | private const val PRODUCTION_KEY = "com.urbanairship.production_app_key" 43 | private const val PRODUCTION_SECRET = "com.urbanairship.production_app_secret" 44 | private const val DEVELOPMENT_KEY = "com.urbanairship.development_app_key" 45 | private const val DEVELOPMENT_SECRET = "com.urbanairship.development_app_secret" 46 | 47 | private const val NOTIFICATION_ICON = "com.urbanairship.notification_icon" 48 | private const val NOTIFICATION_LARGE_ICON = "com.urbanairship.notification_large_icon" 49 | private const val NOTIFICATION_ACCENT_COLOR = "com.urbanairship.notification_accent_color" 50 | private const val DEFAULT_NOTIFICATION_CHANNEL_ID = "com.urbanairship.default_notification_channel_id" 51 | 52 | private const val AUTO_LAUNCH_MESSAGE_CENTER = "com.urbanairship.auto_launch_message_center" 53 | 54 | private const val PRODUCTION_LOG_LEVEL = "com.urbanairship.production_log_level" 55 | private const val DEVELOPMENT_LOG_LEVEL = "com.urbanairship.development_log_level" 56 | private const val IN_PRODUCTION = "com.urbanairship.in_production" 57 | 58 | private const val ENABLE_PUSH_ONLAUNCH = "com.urbanairship.enable_push_onlaunch" 59 | 60 | private const val DISABLE_ANDROID_NOTIFICATIONS_ON_OPT_OUT = "com.urbanairship.android.disable_user_notifications_on_system_opt_out" 61 | 62 | private const val ENABLE_ANALYTICS = "com.urbanairship.enable_analytics" 63 | 64 | private const val CLOUD_SITE = "com.urbanairship.site" 65 | private const val FCM_FIREBASE_APP_NAME = "com.urbanairship.fcm_firebase_app_name" 66 | private const val INITIAL_CONFIG_URL = "com.urbanairship.initial_config_url" 67 | private const val LOG_PRIVACY_LEVEL = "com.urbanairship.log_privacy_level" 68 | 69 | fun fromConfig(context: Context): CordovaSettings { 70 | val config = parseConfigXml(context) 71 | return CordovaSettings( 72 | cloudSite = parseCloudSite(config[CLOUD_SITE]), 73 | productionAppKey = config[PRODUCTION_KEY], 74 | productionAppSecret = config[PRODUCTION_SECRET], 75 | productionLogLevel = parseLogLevel(config[PRODUCTION_LOG_LEVEL]), 76 | developmentAppKey = config[DEVELOPMENT_KEY], 77 | developmentAppSecret = config[DEVELOPMENT_SECRET], 78 | developmentLogLevel = parseLogLevel(config[DEVELOPMENT_LOG_LEVEL]), 79 | autoLaunchMessageCenter = config[AUTO_LAUNCH_MESSAGE_CENTER]?.toBoolean(), 80 | enablePushOnLaunch = config[ENABLE_PUSH_ONLAUNCH]?.toBoolean(), 81 | enableAnalytics = config[ENABLE_ANALYTICS]?.toBoolean(), 82 | notificationIcon = parseIcon(context, config[NOTIFICATION_ICON]), 83 | notificationLargeIcon = parseIcon(context, config[NOTIFICATION_LARGE_ICON]), 84 | notificationAccentColor = parseColor(config[NOTIFICATION_ACCENT_COLOR]), 85 | inProduction = config[IN_PRODUCTION]?.toBoolean(), 86 | fcmFirebaseAppName = config[FCM_FIREBASE_APP_NAME], 87 | initialConfigUrl = config[INITIAL_CONFIG_URL], 88 | defaultChannelId = config[DEFAULT_NOTIFICATION_CHANNEL_ID], 89 | disableNotificationsOnOptOut = parseFrequency(config[DISABLE_ANDROID_NOTIFICATIONS_ON_OPT_OUT]), 90 | logPrivacyLevel = parseLogPrivacyLevel(config[LOG_PRIVACY_LEVEL]) 91 | ) 92 | } 93 | 94 | private fun parseFrequency(value: String?): OptOutFrequency? { 95 | if (value.isNullOrEmpty()) { return null } 96 | try { 97 | return OptOutFrequency.valueOf(value.uppercase()) 98 | } catch(e: IllegalArgumentException) { 99 | UALog.e("Invalid frequency $value", e) 100 | return null 101 | } 102 | } 103 | 104 | private fun parseLogPrivacyLevel(privacyLevel: String?): AirshipConfigOptions.PrivacyLevel? { 105 | if (privacyLevel.isNullOrEmpty()) { return null } 106 | return when (privacyLevel.lowercase()) { 107 | "public" -> AirshipConfigOptions.PrivacyLevel.PUBLIC 108 | "private" -> AirshipConfigOptions.PrivacyLevel.PRIVATE 109 | else -> { 110 | UALog.e("Invalid log privacy level: $privacyLevel") 111 | null 112 | } 113 | } 114 | } 115 | 116 | private fun parseLogLevel(logLevel: String?): Int? { 117 | if (logLevel.isNullOrEmpty()) { return null } 118 | return when (logLevel.lowercase()) { 119 | "verbose" -> Log.VERBOSE 120 | "debug" -> Log.DEBUG 121 | "info" -> Log.INFO 122 | "warn" -> Log.WARN 123 | "error" -> Log.ERROR 124 | "none" -> Log.ASSERT 125 | else -> { 126 | UALog.e("Unexpected log level $logLevel") 127 | null 128 | } 129 | } 130 | } 131 | 132 | @AirshipConfigOptions.Site 133 | private fun parseCloudSite(site: String?): String? { 134 | if (site.isNullOrEmpty()) { return null } 135 | return when (site.uppercase()) { 136 | AirshipConfigOptions.SITE_EU -> AirshipConfigOptions.SITE_EU 137 | AirshipConfigOptions.SITE_US -> AirshipConfigOptions.SITE_US 138 | else -> { 139 | UALog.e("Unexpected site $site") 140 | null 141 | } 142 | } 143 | } 144 | 145 | private fun parseIcon(context: Context, name: String?): Int? { 146 | if (name.isNullOrEmpty()) { return null } 147 | val id = context.resources.getIdentifier(name, "drawable", context.packageName) 148 | return if (id != 0) { 149 | id 150 | } else { 151 | UALog.e("Unable to find drawable with name: $name") 152 | null 153 | } 154 | } 155 | 156 | @ColorInt 157 | private fun parseColor(color: String?): Int? { 158 | if (color.isNullOrEmpty()) { return null } 159 | return try { 160 | Color.parseColor(color) 161 | } catch (e: IllegalArgumentException) { 162 | UALog.e(e) { "Unable to parse color: $color" } 163 | null 164 | } 165 | } 166 | 167 | private fun parseConfigXml(context: Context): Map { 168 | val config: MutableMap = HashMap() 169 | val id = context.resources.getIdentifier("config", "xml", context.packageName) 170 | if (id == 0) { 171 | return config 172 | } 173 | val xml = context.resources.getXml(id) 174 | var eventType = -1 175 | while (eventType != XmlResourceParser.END_DOCUMENT) { 176 | if (eventType == XmlResourceParser.START_TAG) { 177 | if (xml.name == "preference") { 178 | val name = xml.getAttributeValue(null, "name").lowercase() 179 | val value = xml.getAttributeValue(null, "value") 180 | if (name.startsWith(UA_PREFIX) && value != null) { 181 | config[name] = value.trim() 182 | } 183 | } 184 | } 185 | try { 186 | eventType = xml.next() 187 | } catch (e: Exception) { 188 | UALog.e(e) { "Error parsing config file" } 189 | } 190 | } 191 | return config 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /cordova-airship/src/ios/AirshipCordovaPluginSettings.swift: -------------------------------------------------------------------------------- 1 | /* Copyright Airship and Contributors */ 2 | 3 | import Foundation 4 | import AirshipKit 5 | import AirshipFrameworkProxy 6 | 7 | enum AirshipCordovaSite: String, Decodable, Sendable { 8 | case us = "US" 9 | case eu = "EU" 10 | 11 | var airshipValue: CloudSite { 12 | switch(self) { 13 | case .us: 14 | return .us 15 | case .eu: 16 | return .eu 17 | } 18 | } 19 | 20 | var proxyValue: ProxyConfig.Site { 21 | switch(self) { 22 | case .us: 23 | return .us 24 | case .eu: 25 | return .eu 26 | } 27 | } 28 | } 29 | 30 | struct AirshipCordovaPluginSettings: Decodable, Sendable { 31 | 32 | enum LogLevel: String, Decodable, Sendable { 33 | case none 34 | case error 35 | case warn 36 | case info 37 | case debug 38 | case verbose 39 | } 40 | 41 | var productionAppKey: String? 42 | var productionAppSecret: String? 43 | var productionLogLevel: LogLevel? 44 | 45 | var developmentAppKey: String? 46 | var developmentAppSecret: String? 47 | var developmentLogLevel: LogLevel? 48 | 49 | var inProduction: Bool? 50 | var site: AirshipCordovaSite? 51 | 52 | var clearBadgeOnLaunch: Bool? 53 | var enablePushOnLaunch: Bool? 54 | var analyticsEnabled: Bool? 55 | 56 | var initialConfigURL: String? 57 | var presentationOptions: UNNotificationPresentationOptions 58 | var autoLaunchMessageCenter: Bool? 59 | var messageCenterStyleConfig: String? 60 | 61 | enum CodingKeys: String, CodingKey, CaseIterable { 62 | case productionAppKey = "com.urbanairship.production_app_key" 63 | case productionAppSecret = "com.urbanairship.production_app_secret" 64 | case productionLogLevel = "com.urbanairship.production_log_level" 65 | case developmentAppKey = "com.urbanairship.development_app_key" 66 | case developmentAppSecret = "com.urbanairship.development_app_secret" 67 | case developmentLogLevel = "com.urbanairship.development_log_level" 68 | case inProduction = "com.urbanairship.in_production" 69 | case site = "com.urbanairship.site" 70 | case clearBadgeOnLaunch = "com.urbanairship.clear_badge_onlaunch" 71 | case enablePushOnLaunch = "com.urbanairship.enable_push_onlaunch" 72 | case initialConfigURL = "com.urbanairship.initial_config_url" 73 | case analyticsEnabled = "com.urbanairship.enable_analytic" 74 | case alertPresentationOption = "com.urbanairship.ios_foreground_notification_presentation_alert" 75 | case badgePresentationOption = "com.urbanairship.ios_foreground_notification_presentation_badge" 76 | case soundPresentationOption = "com.urbanairship.ios_foreground_notification_presentation_sound" 77 | case autoLaunchMessageCenter = "com.urbanairship.auto_launch_message_center" 78 | case messageCenterStyleConfig = "com.urbanairship.message.center.style.file" 79 | } 80 | 81 | static func from(settings: [AnyHashable: Any]?) -> AirshipCordovaPluginSettings? { 82 | guard let settings = settings else { return nil } 83 | let knownKeys = CodingKeys.allCases.map { $0.rawValue } 84 | let filtered = settings.filter { key, value in 85 | guard let key = key as? String else { return false } 86 | guard (value as? String) != nil else { return false } 87 | return knownKeys.contains(key) 88 | } 89 | 90 | do { 91 | return try AirshipJSON.wrap(filtered).decode() 92 | } catch { 93 | AirshipLogger.error("Failed to parse cordova settings \(filtered) error \(error)") 94 | return nil 95 | } 96 | } 97 | 98 | init(from decoder: Decoder) throws { 99 | let container = try decoder.container(keyedBy: CodingKeys.self) 100 | self.productionAppKey = try container.decodeIfPresent(String.self, forKey: .productionAppKey) 101 | self.productionAppSecret = try container.decodeIfPresent(String.self, forKey: .productionAppSecret) 102 | self.productionLogLevel = try container.decodeIfPresent(AirshipCordovaPluginSettings.LogLevel.self, forKey: .productionLogLevel) 103 | self.developmentAppKey = try container.decodeIfPresent(String.self, forKey: .developmentAppKey) 104 | self.developmentAppSecret = try container.decodeIfPresent(String.self, forKey: .developmentAppSecret) 105 | self.developmentLogLevel = try container.decodeIfPresent(AirshipCordovaPluginSettings.LogLevel.self, forKey: .developmentLogLevel) 106 | self.inProduction = try container.decodeIfPresent(String.self, forKey: .inProduction)?.asBool 107 | self.site = try container.decodeIfPresent(AirshipCordovaSite.self, forKey: .site) 108 | self.clearBadgeOnLaunch = try container.decodeIfPresent(String.self, forKey: .clearBadgeOnLaunch)?.asBool 109 | self.enablePushOnLaunch = try container.decodeIfPresent(String.self, forKey: .enablePushOnLaunch)?.asBool 110 | self.initialConfigURL = try container.decodeIfPresent(String.self, forKey: .initialConfigURL) 111 | self.analyticsEnabled = try container.decodeIfPresent(String.self, forKey: .analyticsEnabled)?.asBool 112 | self.autoLaunchMessageCenter = try container.decodeIfPresent(String.self, forKey: .autoLaunchMessageCenter)?.asBool 113 | self.messageCenterStyleConfig = try container.decodeIfPresent(String.self, forKey: .messageCenterStyleConfig) 114 | 115 | 116 | let alert = try container.decodeIfPresent(String.self, forKey: .alertPresentationOption)?.asBool 117 | let badge = try container.decodeIfPresent(String.self, forKey: .badgePresentationOption)?.asBool 118 | let sound = try container.decodeIfPresent(String.self, forKey: .soundPresentationOption)?.asBool 119 | 120 | var presentationOptions: UNNotificationPresentationOptions = [] 121 | if alert == true { 122 | presentationOptions.insert(.list) 123 | presentationOptions.insert(.banner) 124 | } 125 | 126 | if badge == true { 127 | presentationOptions.insert(.badge) 128 | } 129 | 130 | if sound == true { 131 | presentationOptions.insert(.sound) 132 | } 133 | 134 | self.presentationOptions = presentationOptions 135 | } 136 | } 137 | 138 | extension AirshipConfig { 139 | mutating func applyPluginSettings(_ settings: AirshipCordovaPluginSettings) { 140 | if let appSecret = settings.developmentAppSecret { 141 | self.developmentAppSecret = appSecret 142 | } 143 | 144 | if let appKey = settings.developmentAppKey { 145 | self.developmentAppKey = appKey 146 | } 147 | 148 | if let logLevel = settings.developmentLogLevel { 149 | self.developmentLogLevel = logLevel.airshipValue 150 | } 151 | 152 | if let appSecret = settings.productionAppSecret { 153 | self.productionAppSecret = appSecret 154 | } 155 | 156 | if let appKey = settings.productionAppKey { 157 | self.productionAppKey = appKey 158 | } 159 | 160 | if let logLevel = settings.productionLogLevel { 161 | self.productionLogLevel = logLevel.airshipValue 162 | } 163 | 164 | if let site = settings.site { 165 | self.site = site.airshipValue 166 | } 167 | 168 | if let inProduction = settings.inProduction { 169 | self.inProduction = inProduction 170 | } 171 | 172 | if let initialConfigURL = settings.initialConfigURL { 173 | self.initialConfigURL = initialConfigURL 174 | } 175 | 176 | if let analyticsEnabled = settings.analyticsEnabled { 177 | self.isAnalyticsEnabled = analyticsEnabled 178 | } 179 | 180 | if let messageCenterStyleConfig = settings.messageCenterStyleConfig { 181 | self.messageCenterStyleConfig = messageCenterStyleConfig 182 | } 183 | } 184 | } 185 | 186 | extension String { 187 | var asBool: Bool { 188 | get throws { 189 | guard let bool = Bool(self.lowercased()) else { 190 | throw AirshipErrors.error("Failed to parse bool \(self)") 191 | } 192 | return bool 193 | } 194 | } 195 | } 196 | 197 | 198 | extension AirshipCordovaPluginSettings.LogLevel { 199 | var airshipValue: AirshipLogLevel { 200 | switch(self) { 201 | case .none: 202 | return .none 203 | case .error: 204 | return .error 205 | case .warn: 206 | return .warn 207 | case .info: 208 | return .info 209 | case .debug: 210 | return .debug 211 | case .verbose: 212 | return .verbose 213 | } 214 | } 215 | 216 | var proxyValue: ProxyConfig.LogLevel { 217 | switch(self) { 218 | case .none: 219 | return .none 220 | case .error: 221 | return .error 222 | case .warn: 223 | return .warning 224 | case .info: 225 | return .info 226 | case .debug: 227 | return .debug 228 | case .verbose: 229 | return .verbose 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /Example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Cordova Sample App 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 225 | 226 | 227 |
228 | 229 |
230 |
231 | Urban Airship 232 |
233 |
234 | 235 |
236 |
237 |
238 | 239 |
240 |
241 | 242 |

Push Settings

243 | 244 |
245 | 246 |
247 | 248 |
249 | 250 | 254 |
255 | 256 |
257 | 258 | 262 |
263 | 264 |
265 | 266 | 267 | 271 | 272 |
273 | 274 |
275 | 276 | :
277 | 278 | : 279 | 280 |
281 | 282 |
283 | 284 | 285 |
286 |
287 | 288 | 289 | 290 | 291 | 292 |
293 |
294 |
295 | 296 |
297 | 298 | 299 | -------------------------------------------------------------------------------- /cordova-airship/src/android/AirshipCordova.kt: -------------------------------------------------------------------------------- 1 | /* Copyright Urban Airship and Contributors */ 2 | 3 | package com.urbanairship.cordova 4 | 5 | import android.content.Context 6 | import android.os.Build 7 | import com.urbanairship.Autopilot 8 | import com.urbanairship.UALog 9 | import com.urbanairship.actions.ActionResult 10 | import com.urbanairship.android.framework.proxy.EventType 11 | import com.urbanairship.android.framework.proxy.events.EventEmitter 12 | import com.urbanairship.android.framework.proxy.proxies.AirshipProxy 13 | import com.urbanairship.android.framework.proxy.proxies.FeatureFlagProxy 14 | import com.urbanairship.json.JsonList 15 | import com.urbanairship.json.JsonMap 16 | import com.urbanairship.json.JsonSerializable 17 | import com.urbanairship.json.JsonValue 18 | import kotlinx.coroutines.CoroutineScope 19 | import kotlinx.coroutines.Dispatchers 20 | import kotlinx.coroutines.SupervisorJob 21 | import kotlinx.coroutines.launch 22 | import kotlinx.coroutines.plus 23 | import org.apache.cordova.CallbackContext 24 | import org.apache.cordova.CordovaInterface 25 | import org.apache.cordova.CordovaPlugin 26 | import org.apache.cordova.CordovaWebView 27 | import org.apache.cordova.PluginResult 28 | import org.json.JSONArray 29 | import org.json.JSONObject 30 | 31 | class AirshipCordova : CordovaPlugin() { 32 | 33 | internal data class Listener( 34 | val listenerId: Int, 35 | val callbackContext: CallbackContext 36 | ) 37 | 38 | private var listeners: MutableMap> = mutableMapOf() 39 | 40 | companion object { 41 | private val EVENT_NAME_MAP = mapOf( 42 | EventType.BACKGROUND_NOTIFICATION_RESPONSE_RECEIVED to "airship.event.notification_response", 43 | EventType.FOREGROUND_NOTIFICATION_RESPONSE_RECEIVED to "airship.event.notification_response", 44 | EventType.CHANNEL_CREATED to "airship.event.channel_created", 45 | EventType.DEEP_LINK_RECEIVED to "airship.event.deep_link_received", 46 | EventType.DISPLAY_MESSAGE_CENTER to "airship.event.display_message_center", 47 | EventType.DISPLAY_PREFERENCE_CENTER to "airship.event.display_preference_center", 48 | EventType.MESSAGE_CENTER_UPDATED to "airship.event.message_center_updated", 49 | EventType.PUSH_TOKEN_RECEIVED to "airship.event.push_token_received", 50 | EventType.FOREGROUND_PUSH_RECEIVED to "airship.event.push_received", 51 | EventType.BACKGROUND_PUSH_RECEIVED to "airship.event.push_received", 52 | EventType.NOTIFICATION_STATUS_CHANGED to "airship.event.notification_status_changed" 53 | ) 54 | } 55 | 56 | private lateinit var applicationContext: Context 57 | private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main) + SupervisorJob() 58 | 59 | override fun execute( 60 | action: String, 61 | args: JSONArray, 62 | callbackContext: CallbackContext 63 | ): Boolean { 64 | try { 65 | when (action) { 66 | "perform" -> perform(args, callbackContext) 67 | "addListener" -> addListener(args, callbackContext) 68 | "removeListener" -> removeListener(args) 69 | else -> return false 70 | } 71 | return true 72 | } catch (exception: java.lang.Exception) { 73 | callbackContext.error(action, exception) 74 | } 75 | return false 76 | } 77 | 78 | override fun initialize(cordova: CordovaInterface, webView: CordovaWebView) { 79 | super.initialize(cordova, webView) 80 | UALog.i { "Initializing Urban Airship cordova plugin." } 81 | applicationContext = cordova.getActivity().applicationContext 82 | Autopilot.automaticTakeOff(applicationContext) 83 | 84 | scope.launch { 85 | EventEmitter.shared().pendingEventListener.collect { 86 | notifyPendingEvents() 87 | } 88 | } 89 | } 90 | 91 | override fun onReset() { 92 | super.onReset() 93 | this.listeners.clear() 94 | } 95 | 96 | override fun onDestroy() { 97 | super.onDestroy() 98 | this.listeners.clear() 99 | } 100 | 101 | private fun addListener(args: JSONArray, callbackContext: CallbackContext) { 102 | val jsonArgs = JsonValue.wrap(args).requireList() 103 | 104 | val eventName = jsonArgs.get(0).requireString() 105 | 106 | val listener = Listener( 107 | listenerId = jsonArgs.get(1).requireInt(), 108 | callbackContext = callbackContext 109 | ) 110 | 111 | this.listeners.getOrPut(eventName) { mutableListOf() }.add(listener) 112 | notifyPendingEvents() 113 | } 114 | 115 | private fun removeListener(args: JSONArray) { 116 | val jsonArgs = JsonValue.wrap(args).requireList() 117 | 118 | val eventName = jsonArgs.get(0).requireString() 119 | 120 | val listenerId = jsonArgs.get(1).requireInt() 121 | this.listeners[eventName]?.removeAll { 122 | it.listenerId == listenerId 123 | } 124 | } 125 | 126 | private fun notifyPendingEvents() { 127 | EventType.entries.forEach { eventType -> 128 | val listeners = this.listeners[EVENT_NAME_MAP[eventType]] 129 | if (listeners?.isNotEmpty() == true) { 130 | EventEmitter.shared().processPending(listOf(eventType)) { event -> 131 | listeners.forEach { listeners -> 132 | val pluginResult = event.body.pluginResult() 133 | pluginResult.keepCallback = true 134 | listeners.callbackContext.sendPluginResult(pluginResult) 135 | } 136 | true 137 | } 138 | } 139 | } 140 | } 141 | 142 | private fun perform(args: JSONArray, callback: CallbackContext) { 143 | val jsonArgs = JsonValue.wrap(args).requireList() 144 | val method = jsonArgs.get(0).requireString() 145 | val arg: JsonValue = if (jsonArgs.size() == 2) { jsonArgs.get(1) } else { JsonValue.NULL } 146 | 147 | val proxy = AirshipProxy.shared(applicationContext) 148 | 149 | scope.launch { 150 | when (method) { 151 | // Airship 152 | "takeOff" -> callback.resolve(scope, method) { proxy.takeOff(arg) } 153 | "isFlying" -> callback.resolve(scope, method) { proxy.isFlying() } 154 | 155 | // Channel 156 | "channel#getChannelId" -> callback.resolve(scope, method) { proxy.channel.getChannelId() } 157 | "channel#waitForChannelId" -> callback.resolve(scope, method) { proxy.channel.waitForChannelId() } 158 | 159 | "channel#editTags" -> callback.resolve(scope, method) { proxy.channel.editTags(arg) } 160 | "channel#getTags" -> callback.resolve(scope, method) { proxy.channel.getTags().toList() } 161 | "channel#editTagGroups" -> callback.resolve(scope, method) { proxy.channel.editTagGroups(arg) } 162 | "channel#editSubscriptionLists" -> callback.resolve(scope, method) { proxy.channel.editSubscriptionLists(arg) } 163 | "channel#editAttributes" -> callback.resolve(scope, method) { proxy.channel.editAttributes(arg) } 164 | "channel#getSubscriptionLists" -> callback.resolve(scope, method) { proxy.channel.getSubscriptionLists() } 165 | "channel#enableChannelCreation" -> callback.resolve(scope, method) { proxy.channel.enableChannelCreation() } 166 | 167 | // Contact 168 | "contact#reset" -> callback.resolve(scope, method) { proxy.contact.reset() } 169 | "contact#notifyRemoteLogin" -> callback.resolve(scope, method) { proxy.contact.notifyRemoteLogin() } 170 | "contact#identify" -> callback.resolve(scope, method) { proxy.contact.identify(arg.requireString()) } 171 | "contact#getNamedUserId" -> callback.resolve(scope, method) { proxy.contact.getNamedUserId() } 172 | "contact#editTagGroups" -> callback.resolve(scope, method) { proxy.contact.editTagGroups(arg) } 173 | "contact#editSubscriptionLists" -> callback.resolve(scope, method) { proxy.contact.editSubscriptionLists(arg) } 174 | "contact#editAttributes" -> callback.resolve(scope, method) { proxy.contact.editAttributes(arg) } 175 | "contact#getSubscriptionLists" -> callback.resolve(scope, method) { proxy.contact.getSubscriptionLists() } 176 | 177 | // Push 178 | "push#setUserNotificationsEnabled" -> callback.resolve(scope, method) { proxy.push.setUserNotificationsEnabled(arg.requireBoolean()) } 179 | "push#enableUserNotifications" -> callback.resolve(scope, method) { proxy.push.enableUserPushNotifications() } 180 | "push#isUserNotificationsEnabled" -> callback.resolve(scope, method) { proxy.push.isUserNotificationsEnabled() } 181 | "push#getNotificationStatus" -> callback.resolve(scope, method) { proxy.push.getNotificationStatus() } 182 | "push#getActiveNotifications" -> callback.resolve(scope, method) { proxy.push.getActiveNotifications() } 183 | "push#clearNotification" -> callback.resolve(scope, method) { proxy.push.clearNotification(arg.requireString()) } 184 | "push#clearNotifications" -> callback.resolve(scope, method) { proxy.push.clearNotifications() } 185 | "push#getPushToken" -> callback.resolve(scope, method) { proxy.push.getRegistrationToken() } 186 | "push#android#isNotificationChannelEnabled" -> callback.resolve(scope, method) { 187 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 188 | proxy.push.isNotificationChannelEnabled(arg.requireString()) 189 | } else { 190 | true 191 | } 192 | } 193 | "push#android#setNotificationConfig" -> callback.resolve(scope, method) { proxy.push.setNotificationConfig(arg) } 194 | "push#android#setForegroundNotificationsEnabled" -> callback.resolve(scope, method) { 195 | proxy.push.isForegroundNotificationsEnabled = arg.requireBoolean() 196 | return@resolve Unit 197 | } 198 | "push#android#isForegroundNotificationsEnabled" -> callback.resolve(scope, method) { 199 | proxy.push.isForegroundNotificationsEnabled 200 | } 201 | 202 | // In-App 203 | "inApp#setPaused" -> callback.resolve(scope, method) { proxy.inApp.setPaused(arg.getBoolean(false)) } 204 | "inApp#isPaused" -> callback.resolve(scope, method) { proxy.inApp.isPaused() } 205 | "inApp#setDisplayInterval" -> callback.resolve(scope, method) { proxy.inApp.setDisplayInterval(arg.getLong(0)) } 206 | "inApp#getDisplayInterval" -> callback.resolve(scope, method) { proxy.inApp.getDisplayInterval() } 207 | 208 | // Analytics 209 | "analytics#trackScreen" -> callback.resolve(scope, method) { proxy.analytics.trackScreen(arg.string) } 210 | "analytics#addCustomEvent" -> callback.resolve(scope, method) { proxy.analytics.addEvent(arg) } 211 | "analytics#associateIdentifier" -> { 212 | val associatedIdentifierArgs = arg.requireStringList() 213 | proxy.analytics.associateIdentifier( 214 | associatedIdentifierArgs[0], 215 | associatedIdentifierArgs.getOrNull(1) 216 | ) 217 | } 218 | 219 | // Message Center 220 | "messageCenter#getMessages" -> callback.resolve(scope, method) { 221 | JsonValue.wrapOpt(proxy.messageCenter.getMessages()) 222 | } 223 | "messageCenter#dismiss" -> callback.resolve(scope, method) { proxy.messageCenter.dismiss() } 224 | "messageCenter#display" -> callback.resolve(scope, method) { proxy.messageCenter.display(arg.string) } 225 | "messageCenter#showMessageView" -> callback.resolve(scope, method) { proxy.messageCenter.showMessageView(arg.requireString()) } 226 | "messageCenter#markMessageRead" -> callback.resolve(scope, method) { proxy.messageCenter.markMessageRead(arg.requireString()) } 227 | "messageCenter#deleteMessage" -> callback.resolve(scope, method) { proxy.messageCenter.deleteMessage(arg.requireString()) } 228 | "messageCenter#getUnreadMessageCount" -> callback.resolve(scope, method) { proxy.messageCenter.getUnreadMessagesCount() } 229 | "messageCenter#setAutoLaunchDefaultMessageCenter" -> callback.resolve(scope, method) { proxy.messageCenter.setAutoLaunchDefaultMessageCenter(arg.requireBoolean()) } 230 | "messageCenter#refreshMessages" -> callback.resolve(scope, method) { 231 | if (!proxy.messageCenter.refreshInbox()) { 232 | throw Exception("Failed to refresh") 233 | } 234 | return@resolve Unit 235 | } 236 | 237 | // Preference Center 238 | "preferenceCenter#display" -> callback.resolve(scope, method) { proxy.preferenceCenter.displayPreferenceCenter(arg.requireString()) } 239 | "preferenceCenter#getConfig" -> callback.resolve(scope, method) { 240 | proxy.preferenceCenter.getPreferenceCenterConfig( 241 | arg.requireString() 242 | ) 243 | } 244 | "preferenceCenter#setAutoLaunchPreferenceCenter" -> callback.resolve(scope, method) { 245 | val autoLaunchArgs = arg.requireList() 246 | proxy.preferenceCenter.setAutoLaunchPreferenceCenter( 247 | autoLaunchArgs.get(0).requireString(), 248 | autoLaunchArgs.get(1).getBoolean(false) 249 | ) 250 | } 251 | 252 | // Privacy Manager 253 | "privacyManager#setEnabledFeatures" -> callback.resolve(scope, method) { proxy.privacyManager.setEnabledFeatures(arg.requireStringList()) } 254 | "privacyManager#getEnabledFeatures" -> callback.resolve(scope, method) { proxy.privacyManager.getFeatureNames() } 255 | "privacyManager#enableFeatures" -> callback.resolve(scope, method) { proxy.privacyManager.enableFeatures(arg.requireStringList()) } 256 | "privacyManager#disableFeatures" -> callback.resolve(scope, method) { proxy.privacyManager.disableFeatures(arg.requireStringList()) } 257 | "privacyManager#isFeaturesEnabled" -> callback.resolve(scope, method) { proxy.privacyManager.isFeatureEnabled(arg.requireStringList()) } 258 | 259 | // Locale 260 | "locale#setLocaleOverride" -> callback.resolve(scope, method) { proxy.locale.setCurrentLocale(arg.requireString()) } 261 | "locale#getCurrentLocale" -> callback.resolve(scope, method) { proxy.locale.getCurrentLocale() } 262 | "locale#clearLocaleOverride" -> callback.resolve(scope, method) { proxy.locale.clearLocale() } 263 | 264 | // Actions 265 | "actions#run" -> callback.resolve(scope, method) { 266 | val actionArgs = arg.requireList() 267 | val name = actionArgs.get(0).requireString() 268 | val value: JsonValue? = if (actionArgs.size() == 2) { 269 | actionArgs.get(1) 270 | } else { 271 | null 272 | } 273 | 274 | val result = proxy.actions.runAction(name, value) 275 | if (result.status == ActionResult.STATUS_COMPLETED) { 276 | result.value 277 | } else { 278 | throw Exception("Action failed ${result.status}") 279 | } 280 | } 281 | 282 | 283 | // Feature Flag 284 | "featureFlagManager#flag" -> callback.resolve(scope, method) { 285 | proxy.featureFlagManager.flag(arg.requireString()) 286 | } 287 | 288 | "featureFlagManager#trackInteraction" -> { 289 | callback.resolve(scope, method) { 290 | val featureFlagProxy = FeatureFlagProxy(arg) 291 | proxy.featureFlagManager.trackInteraction(flag = featureFlagProxy) 292 | } 293 | } 294 | 295 | else -> callback.error("Not implemented") 296 | } 297 | } 298 | 299 | } 300 | } 301 | 302 | 303 | 304 | internal fun CallbackContext.error(method: String, exception: java.lang.Exception) { 305 | this.error("AIRSHIP_ERROR(method=$method, exception=$exception)") 306 | } 307 | 308 | internal fun CallbackContext.resolve(scope: CoroutineScope, method: String, function: suspend () -> Any?) { 309 | scope.launch { 310 | try { 311 | when (val result = function()) { 312 | is Unit -> { 313 | this@resolve.success() 314 | } 315 | 316 | is Int -> { 317 | this@resolve.success(result) 318 | } 319 | 320 | is String -> { 321 | this@resolve.success(result) 322 | } 323 | 324 | is Boolean -> { 325 | sendPluginResult( 326 | PluginResult( 327 | PluginResult.Status.OK, 328 | result 329 | ) 330 | ) 331 | } 332 | else -> { 333 | sendPluginResult( 334 | JsonValue.wrap(result).pluginResult() 335 | ) 336 | } 337 | } 338 | } catch (e: Exception) { 339 | this@resolve.error(method, e) 340 | } 341 | } 342 | } 343 | 344 | internal fun JsonValue.requireBoolean(): Boolean { 345 | require(this.isBoolean) 346 | return this.getBoolean(false) 347 | } 348 | 349 | internal fun JsonValue.requireStringList(): List { 350 | return this.requireList().list.map { it.requireString() } 351 | } 352 | 353 | internal fun JsonValue.requireInt(): Int { 354 | require(this.isInteger) 355 | return this.getInt(0) 356 | } 357 | 358 | internal fun JsonSerializable.pluginResult(): PluginResult { 359 | val json = this.toJsonValue() 360 | 361 | return when { 362 | json.isNull -> PluginResult(PluginResult.Status.OK, null as String?) 363 | json.isString -> PluginResult(PluginResult.Status.OK, json.requireString()) 364 | json.isBoolean -> PluginResult(PluginResult.Status.OK, json.requireBoolean()) 365 | json.isInteger -> PluginResult(PluginResult.Status.OK, json.getInt(0)) 366 | json.isNumber -> PluginResult(PluginResult.Status.OK, json.getFloat(0F)) 367 | json.isJsonList -> { 368 | PluginResult(PluginResult.Status.OK, json.requireList().toJSONArray()) 369 | } 370 | json.isJsonMap -> { 371 | PluginResult(PluginResult.Status.OK, json.requireMap().toJSONObject()) 372 | } 373 | 374 | else -> PluginResult(PluginResult.Status.OK, json.toString()) 375 | } 376 | } 377 | 378 | internal fun JsonList.toJSONArray(): JSONArray { 379 | val array = JSONArray() 380 | this.forEach { 381 | array.put(it.toCordovaJSON()) 382 | } 383 | return array 384 | } 385 | 386 | internal fun JsonMap.toJSONObject(): JSONObject { 387 | return JSONObject(map.mapValues { it.value.toCordovaJSON() }) 388 | } 389 | 390 | internal fun JsonSerializable.toCordovaJSON(): Any? { 391 | val json = this.toJsonValue() 392 | return when { 393 | json.isNull -> null 394 | json.isJsonList -> json.requireList().toJSONArray() 395 | json.isJsonMap -> json.requireMap().toJSONObject() 396 | else -> json.value 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /MIGRATION.md: -------------------------------------------------------------------------------- 1 | # Migration Guide 2 | 3 | ## 14.x to 15.x 4 | 5 | ### Requirements 6 | 7 | - cordova-android: 12.x 8 | - cordova-ios: 7.x 9 | - Xcode: 15.2+ 10 | 11 | ### Config.xml Changes 12 | 13 | In config.xml, you must set the min deployment target for iOS to 14 and enable swift support: 14 | 15 | ``` 16 | 17 | 18 | 19 | 20 | 21 | ``` 22 | 23 | For Android to enable FCM, enable the Google Services plugin: 24 | ``` 25 | 26 | ``` 27 | 28 | 29 | ### Package Changes 30 | 31 | The npm packages are now published under new names: 32 | 33 | | 14.x | 15.x | Notes | 34 | |--------------------------------|-------------------------|--------------------------------------------------------------------------------------------------| 35 | | urbanairship-cordova | @ua/cordova-airship | The plugin id is `@ua/cordova-airship`. | 36 | | urbanairship-cordova-hms | @ua/cordova-airship-hms | The plugin id is `@ua/cordova-airship-hms`. | 37 | | urbanairship-accengage-cordova | removed | Package is no longer needed. It was only needed during the transition from Accengage to Airship. | 38 | 39 | 40 | ### API 41 | 42 | The public API has been rewritten. Most methods have a one off replacement. See the table below for the method mapping. 43 | 44 | | 14.x | 15.x | Notes | 45 | |---------------------------------------------------|---------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------| 46 | | UAirship.takeOff | Airship.takeOff | The option `messageCenterStyleConfig` moved to AirshipConfig.ios.messageCenterStyleConfig. | 47 | | UAirship.setAndroidNotificationConfig | Airship.push.android.setNotificationConfig | You can now also android notification config during takeOff | 48 | | UAirship.setAutoLaunchDefaultMessageCenter | Airship.messageCenter.setAutoLaunchDefaultMessageCenter | | 49 | | UAirship.displayMessageCenter | Airship.messageCenter.display | If `setAutoLaunchDefaultMessageCenter` is enabled, this will show a message center UI. If no, it will generate events. | 50 | | UAirship.dismissMessageCenter | Airship.messageCenter.dismiss | | 51 | | UAirship.dismissInboxMessage | Airship.messageCenter.dismiss | | 52 | | UAirship.getInboxMessages | Airship.messageCenter.getMessages | | 53 | | UAirship.markInboxMessageRead | Airship.messageCenter.markMessageRead | | 54 | | UAirship.deleteInboxMessage | Airship.messageCenter.deleteMessage | | 55 | | UAirship.displayInboxMessage | Airship.messageCenter.showMessageView | | 56 | | UAirship.refreshInbox | Airship.messageCenter.refreshMessages | | 57 | | UAirship.setUserNotificationsEnabled | Airship.push.setUserNotificationsEnabled | | 58 | | UAirship.isUserNotificationsEnabled | Airship.push.isUserNotificationsEnabled | | 59 | | UAirship.enableUserNotifications | Airship.push.enableUserNotifications | | 60 | | UAirship.isAppNotificationsEnabled | Airship.push.getNotificationStatus | Use the flag `areNotificationsAllowed` on the status object | 61 | | UAirship.isQuietTimeEnabled | Airship.push.isQuietTimeEnabled | | 62 | | UAirship.setQuietTimeEnabled | Airship.push.setQuietTimeEnabled | | 63 | | UAirship.isInQuietTime | No replacement | If needed please file a github issue with usage. | 64 | | UAirship.setQuietTime | Airship.push.setQuietTime | API now takes an object with startHour, endHour, startMinute, endMinute | 65 | | UAirship.getQuietTime | Airship.push.getQuietTime | | 66 | | UAirship.clearNotification | Airship.push.clearNotification | | 67 | | UAirship.clearNotifications | Airship.push.clearNotifications | | 68 | | UAirship.getActiveNotifications | Airship.push.getActiveNotifications | | 69 | | UAirship.setAutobadgeEnabled | Airship.push.ios.setAutobadgeEnabled | | 70 | | UAirship.setBadgeNumber | Airship.push.ios.setBadgeNumber | | 71 | | UAirship.setBadgeNumber | Airship.push.ios.setBadgeNumber | | 72 | | UAirship.getBadgeNumber | Airship.push.ios.getBadgeNumber | | 73 | | UAirship.resetBadge | Airship.push.ios.resetBadge | | 74 | | UAirship.setNotificationTypes | Airship.push.ios.setNotificationOptions | | 75 | | UAirship.setPresentationOptions | Airship.push.ios.setForegroundPresentationOptions | | 76 | | UAirship.setAndroidForegroundNotificationsEnabled | Airship.push.android.setForegroundNotificationsEnabled | | 77 | | UAirship.isSoundEnabled | No replacement | Use notification categories/channel instead | 78 | | UAirship.setSoundEnabled | No replacement | Use notification categories/channel instead | 79 | | UAirship.isVibrateEnabled | No replacement | Use notification categories/channel instead | 80 | | UAirship.setVibrateEnabled | No replacement | Use notification categories/channel instead | 81 | | UAirship.setAnalyticsEnabled | Airship.privacyManager.enable/disable | Enable/disable "analytics" flag on privacy manager | 82 | | UAirship.isAnalyticsEnabled | Airship.privacyManager.isFeaturesEnabled("analytics") | | 83 | | UAirship.setAssociatedIdentifier | Airship.analytics.setAssociatedIdentifier | | 84 | | UAirship.addCustomEvent | Airship.analytics.addCustomEvent | The field `name` is now `eventName`, `value` is `eventValue`, `properties` can be any valid json object. | 85 | | UAirship.trackScreen | Airship.analytics.trackScreen | | 86 | | UAirship.getChannelID | Airship.channel.getChannelId | | 87 | | UAirship.getTags | Airship.channel.getTags | | 88 | | UAirship.setTags | Airship.channel.editTags | Use the editor to add and remove tags | 89 | | UAirship.editChannelTagGroups | Airship.channel.editTagGroups | | 90 | | UAirship.editChannelAttributes | Airship.channel.editAttributes | | 91 | | UAirship.editChannelSubscriptionLists | Airship.channel.editSubscriptionLists | | 92 | | UAirship.getChannelSubscriptionLists | Airship.channel.getSubscriptionLists | | 93 | | UAirship.getAlias | Airship.contact.getNamedUserId | | 94 | | UAirship.setAlias | Airship.contact.identify | | 95 | | UAirship.getNamedUser | Airship.contact.getNamedUserId | | 96 | | UAirship.setNamedUser | Airship.contact.identify/reset | Use identify to set the named user, reset to clear it | 97 | | UAirship.editNamedUserTagGroups | Airship.contact.editTagGroups | | 98 | | UAirship.editNamedUserAttributes | Airship.contact.editAttributes | | 99 | | UAirship.editContactSubscriptionLists | Airship.contact.editSubscriptionLists | | 100 | | UAirship.getContactSubscriptionLists | Airship.contact.getSubscriptionLists | | 101 | | UAirship.getLaunchNotification | No replacement | Use the Airship.push.onNotificationResponse listener | 102 | | UAirship.getDeepLink | No replacement | Use the Airship.onDeepLink listener | 103 | | UAirship.runAction | Airship.actions.run | | 104 | | UAirship.enableFeature | Airship.privacyManager.enableFeature | Feature constants, see below for more info. | 105 | | UAirship.disableFeature | Airship.privacyManager.disableFeature | Feature constants, see below for more info. | 106 | | UAirship.setEnabledFeatures | Airship.privacyManager.setEnabledFeatures | Feature constants, see below for more info. | 107 | | UAirship.getEnabledFeatures | Airship.privacyManager.getEnabledFeatures | Feature constants, see below for more info. | 108 | | UAirship.isFeatureEnabled | Airship.privacyManager.isFeatureEnabled | Feature constants, see below for more info. | 109 | | UAirship.openPreferenceCenter | Airship.preferenceCenter.display | | 110 | | UAirship.getPreferenceCenterConfig | Airship.preferenceCenter.getConfig | | 111 | | UAirship.setUseCustomPreferenceCenterUi | Airship.preferenceCenter.setAutoLaunchDefaultPreferenceCenter | | 112 | | UAirship.setCurrentLocale | Airship.locale.setLocaleOverride | | 113 | | UAirship.getCurrentLocale | Airship.locale.getLocale | | 114 | | UAirship.clearLocale | Airship.locale.clearLocaleOverride | | 115 | | UAirship.reattach | No replacement | Events are no longer sent on the document. See events for replacements | 116 | 117 | ### Privacy Manager Flags 118 | 119 | The flag constants are no longer all uppercase and some options have been removed. The 15.x flags are: 120 | 121 | ``` 122 | /** 123 | * Enum of authorized Features. 124 | */ 125 | export enum Feature { 126 | InAppAutomation = 'in_app_automation', 127 | MessageCenter = 'message_center', 128 | Push = 'push', 129 | Analytics = 'analytics', 130 | TagsAndAttributes = 'tags_and_attributes', 131 | Contacts = 'contacts', 132 | } 133 | ``` 134 | 135 | ### Events 136 | 137 | Events are no longer sent as document events. Events are also now queued up until a listener is added, so there is no longer a need to return `launchNotification` or `deepLink` since its now possible for the app to receive those when ready. 138 | 139 | | 14.x | 15.x | Notes | 140 | |--------------------------------------------------------------------------------|-----------------------------------------------|-------------------------------------------------| 141 | | document.addEventListener("urbanairship.deep_link", callback) | Airship.onDeepLink(callback) | | 142 | | document.addEventListener("urbanairship.registration", callback) | Airship.channel.onChannelCreated(callback) | For channel ID. Use `event.channelId` | 143 | | document.addEventListener("urbanairship.registration", callback) | Airship.push.onPushTokenReceived(callback) | For push token. Use `event.pushToken` | 144 | | document.addEventListener("urbanairship.push", callback) | Airship.push.onPushReceived(callback) | The event has changed, see below for more info. | 145 | | document.addEventListener("urbanairship.notification_opened", callback) | Airship.push.onNotificationResponse(callback) | The event has changed, see below for more info. | 146 | | document.addEventListener("urbanairship.notification_opt_in_status", callback) | Airship.onNotificationStatusChanged(callback) | The event has changed, see below for more info. | 147 | | document.addEventListener("urbanairship.inbox_updated", callback) | Airship.messageCenter.onUpdated(callback) | | 148 | | document.addEventListener("urbanairship.show_inbox", callback) | Airship.messageCenter.onDisplay(callback) | | 149 | | document.addEventListener("urbanairship.open_preference_center", callback) | Airship.preferenceCenter.onDisplay(callback) | | 150 | 151 | #### Push Received 152 | 153 | The push payload is now grouped under the `pushPayload` field. The old `message` field has been renamed to `alert` (`pushPayload.alert`). 154 | 155 | #### Push Response 156 | 157 | The push payload is now grouped under the `pushPayload` field. The old `message` field has been renamed to `alert` (`pushPayload.alert`). The field `actionID` has been renamed to `actionId`. 158 | 159 | 160 | #### Notification Opt In Status Event 161 | 162 | Notification status event now provides an object with several flags that you can use to determine the exact reason why the device is opted out: 163 | 164 | ``` 165 | /** 166 | * Push notification status. 167 | */ 168 | export interface PushNotificationStatus { 169 | /** 170 | * If user notifications are enabled on [Airship.push]. 171 | */ 172 | isUserNotificationsEnabled: boolean; 173 | 174 | /** 175 | * If notifications are allowed at the system level for the application. 176 | */ 177 | areNotificationsAllowed: boolean; 178 | 179 | /** 180 | * If the push feature is enabled on [Airship.privacyManager]. 181 | */ 182 | isPushPrivacyFeatureEnabled: boolean; 183 | 184 | /* 185 | * If push registration was able to generate a token. 186 | */ 187 | isPushTokenRegistered: boolean; 188 | 189 | /* 190 | * If Airship is able to send and display a push notification. 191 | */ 192 | isOptedIn: boolean; 193 | 194 | /* 195 | * Checks for isUserNotificationsEnabled, areNotificationsAllowed, and isPushPrivacyFeatureEnabled. If this flag 196 | * is true but `isOptedIn` is false, that means push token was not able to be registered. 197 | */ 198 | isUserOptedIn: boolean; 199 | } 200 | 201 | /** 202 | * Event fired when the notification status changes. 203 | */ 204 | export interface PushNotificationStatusChangedEvent { 205 | /** 206 | * The push notification status. 207 | */ 208 | status: PushNotificationStatus 209 | } 210 | ``` -------------------------------------------------------------------------------- /cordova-airship/src/ios/AirshipCordova.swift: -------------------------------------------------------------------------------- 1 | /* Copyright Airship and Contributors */ 2 | 3 | import Foundation 4 | import AirshipKit 5 | import AirshipFrameworkProxy 6 | 7 | @objc(AirshipCordova) 8 | public final class AirshipCordova: CDVPlugin { 9 | 10 | struct Listener { 11 | let callbackID: String 12 | let listenerID: Int 13 | } 14 | 15 | @MainActor 16 | private var eventListeners: [AirshipProxyEventType: [Listener]] = [:] 17 | 18 | private static let eventNames: [AirshipProxyEventType: String] = [ 19 | .authorizedNotificationSettingsChanged: "airship.event.ios_authorized_notification_settings_changed", 20 | .pushTokenReceived: "airship.event.push_token_received", 21 | .deepLinkReceived: "airship.event.deep_link_received", 22 | .channelCreated: "airship.event.channel_created", 23 | .messageCenterUpdated: "airship.event.message_center_updated", 24 | .displayMessageCenter: "airship.event.display_message_center", 25 | .displayPreferenceCenter: "airship.event.display_preference_center", 26 | .notificationResponseReceived: "airship.event.notification_response", 27 | .pushReceived: "airship.event.push_received", 28 | .notificationStatusChanged: "airship.event.notification_status_changed" 29 | ] 30 | 31 | @MainActor 32 | public override func pluginInitialize() { 33 | let settings = AirshipCordovaPluginSettings.from( 34 | settings: self.commandDelegate.settings 35 | ) 36 | 37 | AirshipCordovaAutopilot.shared.pluginInitialized(settings: settings) 38 | 39 | Task { 40 | for await _ in AirshipProxyEventEmitter.shared.pendingEventAdded { 41 | await self.notifyPendingEvents() 42 | } 43 | } 44 | } 45 | 46 | @objc 47 | @MainActor 48 | func removeListener(_ command: CDVInvokedUrlCommand) { 49 | guard 50 | command.arguments.count == 2, 51 | let listenerID = command.arguments.last as? NSNumber, 52 | let eventName = command.arguments.first as? String 53 | else { 54 | AirshipLogger.error("Failed to add listener, invalid command \(command)") 55 | return 56 | } 57 | 58 | guard 59 | let eventType = Self.eventNames.first(where: { key, value in 60 | value == eventName 61 | })?.key 62 | else { 63 | AirshipLogger.error("Failed to add listener, invalid name \(eventName)") 64 | return 65 | } 66 | 67 | self.eventListeners[eventType]?.removeAll(where: { listener in 68 | listener.listenerID == listenerID.intValue 69 | }) 70 | } 71 | 72 | @objc 73 | @MainActor 74 | func addListener(_ command: CDVInvokedUrlCommand) { 75 | guard 76 | let callbackID = command.callbackId, 77 | command.arguments.count == 2, 78 | let listenerID = command.arguments.last as? NSNumber, 79 | let eventName = command.arguments.first as? String 80 | else { 81 | AirshipLogger.error("Failed to add listener, invalid command \(command)") 82 | return 83 | } 84 | 85 | guard 86 | let eventType = Self.eventNames.first(where: { key, value in 87 | value == eventName 88 | })?.key 89 | else { 90 | AirshipLogger.error("Failed to add listener, invalid name \(eventName)") 91 | return 92 | } 93 | 94 | if self.eventListeners[eventType] == nil { 95 | self.eventListeners[eventType] = [] 96 | } 97 | 98 | self.eventListeners[eventType]?.append( 99 | Listener(callbackID: callbackID, listenerID: listenerID.intValue) 100 | ) 101 | 102 | Task { 103 | await notifyPendingEvents() 104 | } 105 | } 106 | 107 | @MainActor 108 | private func notifyPendingEvents() async { 109 | let listeners = self.eventListeners 110 | 111 | for eventType in AirshipProxyEventType.allCases { 112 | AirshipProxyEventEmitter.shared.sendPendingEvents( 113 | eventType: eventType, 114 | listeners: listeners[eventType], 115 | commandDelegate: self.commandDelegate 116 | ) 117 | } 118 | } 119 | 120 | @objc 121 | func perform(_ command: CDVInvokedUrlCommand) { 122 | Task { 123 | do { 124 | let result = try await self.handle(command: command) 125 | let pluginResult = try CDVPluginResult.successResult(value: result) 126 | self.commandDelegate?.send(pluginResult, callbackId: command.callbackId) 127 | } catch { 128 | let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: error.localizedDescription) 129 | self.commandDelegate?.send(pluginResult, callbackId: command.callbackId) 130 | } 131 | } 132 | } 133 | 134 | 135 | 136 | @MainActor 137 | private func handle(command: CDVInvokedUrlCommand) async throws -> (any Sendable)? { 138 | guard let method = command.arguments[0] as? String else { 139 | throw AirshipErrors.error("Invalid command \(command)") 140 | } 141 | 142 | switch method { 143 | 144 | // Airship 145 | case "takeOff": 146 | return try AirshipCordovaAutopilot.shared.attemptTakeOff( 147 | json: try command.requireAnyArg() 148 | ) 149 | 150 | case "isFlying": 151 | return AirshipProxy.shared.isFlying() 152 | 153 | // Channel 154 | case "channel#getChannelId": 155 | return try AirshipProxy.shared.channel.channelID 156 | 157 | case "channel#waitForChannelId": 158 | return try await AirshipProxy.shared.channel.waitForChannelID() 159 | 160 | case "channel#editTags": 161 | try AirshipProxy.shared.channel.editTags( 162 | operations: try command.requireCodableArg() 163 | ) 164 | return nil 165 | 166 | case "channel#getTags": 167 | return try AirshipProxy.shared.channel.tags 168 | 169 | case "channel#editTagGroups": 170 | try AirshipProxy.shared.channel.editTagGroups( 171 | operations: try command.requireCodableArg() 172 | ) 173 | return nil 174 | 175 | case "channel#editSubscriptionLists": 176 | try AirshipProxy.shared.channel.editSubscriptionLists( 177 | operations: try command.requireCodableArg() 178 | ) 179 | return nil 180 | 181 | case "channel#editAttributes": 182 | try AirshipProxy.shared.channel.editAttributes( 183 | operations: try command.requireCodableArg() 184 | ) 185 | return nil 186 | 187 | case "channel#getSubscriptionLists": 188 | return try await AirshipProxy.shared.channel.fetchSubscriptionLists() 189 | 190 | case "channel#enableChannelCreation": 191 | return try AirshipProxy.shared.channel.enableChannelCreation() 192 | 193 | // Contact 194 | case "contact#editTagGroups": 195 | try AirshipProxy.shared.contact.editTagGroups( 196 | operations: try command.requireCodableArg() 197 | ) 198 | return nil 199 | 200 | case "contact#editSubscriptionLists": 201 | try AirshipProxy.shared.contact.editSubscriptionLists( 202 | operations: try command.requireCodableArg() 203 | ) 204 | return nil 205 | 206 | case "contact#editAttributes": 207 | try AirshipProxy.shared.contact.editAttributes( 208 | operations: try command.requireCodableArg() 209 | ) 210 | return nil 211 | 212 | case "contact#getSubscriptionLists": 213 | return try await AirshipProxy.shared.contact.getSubscriptionLists() 214 | 215 | case "contact#identify": 216 | try AirshipProxy.shared.contact.identify( 217 | try command.requireStringArg() 218 | ) 219 | return nil 220 | 221 | case "contact#reset": 222 | try AirshipProxy.shared.contact.reset() 223 | return nil 224 | 225 | case "contact#notifyRemoteLogin": 226 | try AirshipProxy.shared.contact.notifyRemoteLogin() 227 | return nil 228 | 229 | case "contact#getNamedUserId": 230 | return try await AirshipProxy.shared.contact.namedUserID 231 | 232 | 233 | // Push 234 | case "push#getPushToken": 235 | return try AirshipProxy.shared.push.getRegistrationToken() 236 | 237 | case "push#setUserNotificationsEnabled": 238 | try AirshipProxy.shared.push.setUserNotificationsEnabled( 239 | try command.requireBooleanArg() 240 | ) 241 | return nil 242 | 243 | case "push#enableUserNotifications": 244 | return try await AirshipProxy.shared.push.enableUserPushNotifications() 245 | 246 | case "push#isUserNotificationsEnabled": 247 | return try AirshipProxy.shared.push.isUserNotificationsEnabled() 248 | 249 | case "push#getNotificationStatus": 250 | return try await AirshipProxy.shared.push.notificationStatus 251 | 252 | case "push#getActiveNotifications": 253 | return try await AirshipProxy.shared.push.getActiveNotifications() 254 | 255 | case "push#clearNotification": 256 | AirshipProxy.shared.push.clearNotification( 257 | try command.requireStringArg() 258 | ) 259 | return nil 260 | 261 | case "push#clearNotifications": 262 | AirshipProxy.shared.push.clearNotifications() 263 | return nil 264 | 265 | case "push#ios#getBadgeNumber": 266 | return try AirshipProxy.shared.push.getBadgeNumber() 267 | 268 | case "push#ios#setBadgeNumber": 269 | try await AirshipProxy.shared.push.setBadgeNumber( 270 | try command.requireIntArg() 271 | ) 272 | return nil 273 | 274 | case "push#ios#setAutobadgeEnabled": 275 | try AirshipProxy.shared.push.setAutobadgeEnabled( 276 | try command.requireBooleanArg() 277 | ) 278 | return nil 279 | 280 | case "push#ios#isAutobadgeEnabled": 281 | return try AirshipProxy.shared.push.isAutobadgeEnabled() 282 | 283 | case "push#ios#resetBadge": 284 | try await AirshipProxy.shared.push.setBadgeNumber(0) 285 | return nil 286 | 287 | case "push#ios#setNotificationOptions": 288 | try AirshipProxy.shared.push.setNotificationOptions( 289 | names: try command.requireStringArrayArg() 290 | ) 291 | return nil 292 | 293 | case "push#ios#setForegroundPresentationOptions": 294 | try AirshipProxy.shared.push.setForegroundPresentationOptions( 295 | names: try command.requireStringArrayArg() 296 | ) 297 | return nil 298 | 299 | case "push#ios#getAuthorizedNotificationStatus": 300 | return try AirshipProxy.shared.push.getAuthroizedNotificationStatus() 301 | 302 | case "push#ios#getAuthorizedNotificationSettings": 303 | return try AirshipProxy.shared.push.getAuthorizedNotificationSettings() 304 | 305 | case "push#ios#setQuietTimeEnabled": 306 | try AirshipProxy.shared.push.setQuietTimeEnabled( 307 | try command.requireBooleanArg() 308 | ) 309 | return nil 310 | 311 | case "push#ios#isQuietTimeEnabled": 312 | return try AirshipProxy.shared.push.isQuietTimeEnabled() 313 | 314 | case "push#ios#setQuietTime": 315 | try AirshipProxy.shared.push.setQuietTime( 316 | try command.requireCodableArg() 317 | ) 318 | return nil 319 | 320 | case "push#ios#getQuietTime": 321 | return try AirshipJSON.wrap(try AirshipProxy.shared.push.getQuietTime()) 322 | 323 | // In-App 324 | case "inApp#setPaused": 325 | try AirshipProxy.shared.inApp.setPaused( 326 | try command.requireBooleanArg() 327 | ) 328 | return nil 329 | 330 | case "inApp#isPaused": 331 | return try AirshipProxy.shared.inApp.isPaused() 332 | 333 | case "inApp#setDisplayInterval": 334 | try AirshipProxy.shared.inApp.setDisplayInterval( 335 | milliseconds: try command.requireIntArg() 336 | ) 337 | return nil 338 | 339 | case "inApp#getDisplayInterval": 340 | return try AirshipProxy.shared.inApp.getDisplayInterval() 341 | 342 | // Analytics 343 | case "analytics#trackScreen": 344 | try AirshipProxy.shared.analytics.trackScreen( 345 | try? command.requireStringArg() 346 | ) 347 | return nil 348 | 349 | case "analytics#addCustomEvent": 350 | try AirshipProxy.shared.analytics.addEvent( 351 | command.requireAnyArg() 352 | ) 353 | return nil 354 | 355 | case "analytics#associateIdentifier": 356 | let args = try command.requireStringArrayArg() 357 | guard args.count == 1 || args.count == 2 else { 358 | throw AirshipErrors.error("Call requires 1 to 2 strings.") 359 | } 360 | try AirshipProxy.shared.analytics.associateIdentifier( 361 | identifier: args.count == 2 ? args[1] : nil, 362 | key: args[0] 363 | ) 364 | return nil 365 | 366 | // Message Center 367 | case "messageCenter#getMessages": 368 | return try await AirshipProxy.shared.messageCenter.messages 369 | 370 | case "messageCenter#display": 371 | try AirshipProxy.shared.messageCenter.display( 372 | messageID: try? command.requireStringArg() 373 | ) 374 | return nil 375 | 376 | case "messageCenter#showMessageView": 377 | try AirshipProxy.shared.messageCenter.showMessageView( 378 | messageID: try command.requireStringArg() 379 | ) 380 | return nil 381 | 382 | case "messageCenter#dismiss": 383 | try AirshipProxy.shared.messageCenter.dismiss() 384 | return nil 385 | 386 | case "messageCenter#markMessageRead": 387 | try await AirshipProxy.shared.messageCenter.markMessageRead( 388 | messageID: command.requireStringArg() 389 | ) 390 | return nil 391 | 392 | case "messageCenter#deleteMessage": 393 | try await AirshipProxy.shared.messageCenter.deleteMessage( 394 | messageID: command.requireStringArg() 395 | ) 396 | return nil 397 | 398 | case "messageCenter#getUnreadMessageCount": 399 | return try await AirshipProxy.shared.messageCenter.unreadCount 400 | 401 | case "messageCenter#refreshMessages": 402 | try await AirshipProxy.shared.messageCenter.refresh() 403 | return nil 404 | 405 | case "messageCenter#setAutoLaunchDefaultMessageCenter": 406 | AirshipProxy.shared.messageCenter.setAutoLaunchDefaultMessageCenter( 407 | try command.requireBooleanArg() 408 | ) 409 | return nil 410 | 411 | // Preference Center 412 | case "preferenceCenter#display": 413 | try AirshipProxy.shared.preferenceCenter.displayPreferenceCenter( 414 | preferenceCenterID: try command.requireStringArg() 415 | ) 416 | return nil 417 | 418 | case "preferenceCenter#getConfig": 419 | return try await AirshipProxy.shared.preferenceCenter.getPreferenceCenterConfig( 420 | preferenceCenterID: try command.requireStringArg() 421 | ) 422 | 423 | case "preferenceCenter#setAutoLaunchPreferenceCenter": 424 | let args = try command.requireArrayArg() 425 | guard 426 | args.count == 2, 427 | let identifier = args[0] as? String, 428 | let autoLaunch = args[1] as? Bool 429 | else { 430 | throw AirshipErrors.error("Call requires [String, Bool]") 431 | } 432 | 433 | AirshipProxy.shared.preferenceCenter.setAutoLaunchPreferenceCenter( 434 | autoLaunch, 435 | preferenceCenterID: identifier 436 | ) 437 | return nil 438 | 439 | // Privacy Manager 440 | case "privacyManager#setEnabledFeatures": 441 | try AirshipProxy.shared.privacyManager.setEnabled( 442 | featureNames: try command.requireStringArrayArg() 443 | ) 444 | return nil 445 | 446 | case "privacyManager#getEnabledFeatures": 447 | return try AirshipProxy.shared.privacyManager.getEnabledNames() 448 | 449 | case "privacyManager#enableFeatures": 450 | try AirshipProxy.shared.privacyManager.enable( 451 | featureNames: try command.requireStringArrayArg() 452 | ) 453 | return nil 454 | 455 | case "privacyManager#disableFeatures": 456 | try AirshipProxy.shared.privacyManager.disable( 457 | featureNames: try command.requireStringArrayArg() 458 | ) 459 | return nil 460 | 461 | case "privacyManager#isFeaturesEnabled": 462 | return try AirshipProxy.shared.privacyManager.isEnabled( 463 | featuresNames: try command.requireStringArrayArg() 464 | ) 465 | 466 | // Locale 467 | case "locale#setLocaleOverride": 468 | try AirshipProxy.shared.locale.setCurrentLocale( 469 | try command.requireStringArg() 470 | ) 471 | return nil 472 | 473 | case "locale#clearLocaleOverride": 474 | try AirshipProxy.shared.locale.clearLocale() 475 | return nil 476 | 477 | case "locale#getCurrentLocale": 478 | return try AirshipProxy.shared.locale.currentLocale 479 | 480 | // Actions 481 | case "actions#run": 482 | let args = try command.requireArrayArg() 483 | guard 484 | args.count == 1 || args.count == 2, 485 | let actionName = args[0] as? String 486 | else { 487 | throw AirshipErrors.error("Call requires [String, Any?]") 488 | } 489 | 490 | let arg = try? AirshipJSON.wrap(args[1]) 491 | return try await AirshipProxy.shared.action.runAction( 492 | actionName, 493 | value: args.count == 2 ? arg : nil 494 | ) 495 | 496 | // Feature Flag 497 | case "featureFlagManager#flag": 498 | return try await AirshipProxy.shared.featureFlagManager.flag( 499 | name: try command.requireStringArg() 500 | ) 501 | 502 | case "featureFlagManager#trackInteraction": 503 | try AirshipProxy.shared.featureFlagManager.trackInteraction( 504 | flag: command.requireCodableArg() 505 | ) 506 | 507 | return nil 508 | default: 509 | throw AirshipErrors.error("Unavailable command \(method)") 510 | } 511 | } 512 | } 513 | 514 | 515 | extension CDVInvokedUrlCommand { 516 | 517 | func requireCodableArg() throws -> T { 518 | guard 519 | self.arguments.count >= 2 520 | else { 521 | throw AirshipErrors.error("Missing argument") 522 | } 523 | 524 | return try AirshipJSON.wrap(self.arguments[1]).decode() 525 | } 526 | 527 | func requireArrayArg() throws -> [Any] { 528 | guard 529 | self.arguments.count >= 2, 530 | let args = self.arguments[1] as? [Any] 531 | else { 532 | throw AirshipErrors.error("Argument must be an array") 533 | } 534 | 535 | return args 536 | } 537 | 538 | func requireArrayArg(count: UInt, parse: (Any) throws -> T) throws -> [T] { 539 | guard 540 | self.arguments.count >= 2, 541 | let args = self.arguments[1] as? [Any], 542 | args.count == count 543 | else { 544 | throw AirshipErrors.error("Invalid argument array") 545 | } 546 | 547 | return try args.map { try parse($0) } 548 | } 549 | 550 | func requireStringArrayArg() throws -> [String] { 551 | guard 552 | self.arguments.count >= 2, 553 | let args = self.arguments[1] as? [String] 554 | else { 555 | throw AirshipErrors.error("Argument must be a string array") 556 | } 557 | 558 | return args 559 | } 560 | 561 | func requireAnyArg() throws -> Any { 562 | guard 563 | self.arguments.count >= 2 564 | else { 565 | throw AirshipErrors.error("Argument must not be null") 566 | } 567 | 568 | return self.arguments[1] 569 | } 570 | 571 | func requireBooleanArg() throws -> Bool { 572 | guard 573 | self.arguments.count >= 2, 574 | let args = self.arguments[1] as? Bool 575 | else { 576 | throw AirshipErrors.error("Argument must be a boolean") 577 | } 578 | 579 | return args 580 | } 581 | 582 | func requireStringArg() throws -> String { 583 | guard 584 | self.arguments.count >= 2, 585 | let args = self.arguments[1] as? String 586 | else { 587 | throw AirshipErrors.error("Argument must be a string") 588 | } 589 | 590 | return args 591 | } 592 | 593 | func requireIntArg() throws -> Int { 594 | let value = try requireAnyArg() 595 | 596 | if let int = value as? Int { 597 | return int 598 | } 599 | 600 | if let double = value as? Double { 601 | return Int(double) 602 | } 603 | 604 | if let number = value as? NSNumber { 605 | return number.intValue 606 | } 607 | 608 | throw AirshipErrors.error("Argument must be an int") 609 | } 610 | 611 | func requireDoubleArg() throws -> Double { 612 | let value = try requireAnyArg() 613 | 614 | if let double = value as? Double { 615 | return double 616 | } 617 | 618 | if let int = value as? Int { 619 | return Double(int) 620 | } 621 | 622 | if let number = value as? NSNumber { 623 | return number.doubleValue 624 | } 625 | 626 | throw AirshipErrors.error("Argument must be a double") 627 | } 628 | } 629 | 630 | extension AirshipProxyEventEmitter { 631 | func sendPendingEvents( 632 | eventType: AirshipProxyEventType, 633 | listeners: [AirshipCordova.Listener]?, 634 | commandDelegate: CDVCommandDelegate? 635 | ) { 636 | guard 637 | let commandDelegate = commandDelegate, 638 | let listeners = listeners, 639 | listeners.count > 0 640 | else { 641 | return 642 | } 643 | 644 | self.processPendingEvents(type: eventType) { event in 645 | let result = try? CDVPluginResult.successResult(value: event.body) 646 | result?.keepCallback = true 647 | 648 | listeners.forEach { listener in 649 | commandDelegate.send(result, callbackId: listener.callbackID) 650 | } 651 | 652 | return true 653 | } 654 | } 655 | } 656 | 657 | fileprivate extension Encodable { 658 | func unwrapped() throws -> T { 659 | guard let value = try AirshipJSON.wrap(self).unWrap() as? T else { 660 | throw AirshipErrors.error("Failed to unwrap codable") 661 | } 662 | return value 663 | } 664 | } 665 | 666 | fileprivate extension CDVPluginResult { 667 | static func successResult(value: Any?, fallbackJSON: Bool = true) throws -> CDVPluginResult { 668 | guard let value = value else { 669 | return CDVPluginResult(status: CDVCommandStatus_OK) 670 | } 671 | 672 | if let string = value as? String { 673 | return CDVPluginResult(status: CDVCommandStatus_OK, messageAs: string) 674 | } 675 | 676 | if let bool = value as? Bool { 677 | return CDVPluginResult(status: CDVCommandStatus_OK, messageAs: bool) 678 | } 679 | 680 | if let int = value as? Int { 681 | return CDVPluginResult(status: CDVCommandStatus_OK, messageAs: int) 682 | } 683 | 684 | if let int = value as? UInt { 685 | return CDVPluginResult(status: CDVCommandStatus_OK, messageAs: int) 686 | } 687 | 688 | if let number = value as? NSNumber { 689 | return CDVPluginResult(status: CDVCommandStatus_OK, messageAs: number.doubleValue) 690 | } 691 | 692 | if let array = value as? Array { 693 | return CDVPluginResult( 694 | status: CDVCommandStatus_OK, 695 | messageAs: try AirshipJSON.wrap(array).unWrap() as? Array 696 | ) 697 | } 698 | 699 | if let dictionary = value as? [String: Any] { 700 | return CDVPluginResult( 701 | status: CDVCommandStatus_OK, 702 | messageAs: try AirshipJSON.wrap(dictionary).unWrap() as? [String: Any] 703 | ) 704 | } 705 | 706 | if fallbackJSON { 707 | return try successResult(value: try AirshipJSON.wrap(value).unWrap(), fallbackJSON: false) 708 | } else { 709 | throw AirshipErrors.error("Invalid result \(value)") 710 | } 711 | } 712 | } 713 | -------------------------------------------------------------------------------- /cordova-airship/www/Airship.js: -------------------------------------------------------------------------------- 1 | /* Copyright Airship and Contributors */ 2 | 3 | var cordova = require("cordova"), 4 | exec = require("cordova/exec"), 5 | argscheck = require('cordova/argscheck'), 6 | airship = { 7 | contact: {}, 8 | channel: {}, 9 | analytics: {}, 10 | locale: {}, 11 | messageCenter: {}, 12 | featureFlagManager: {}, 13 | preferenceCenter: {}, 14 | push: { 15 | ios: {}, 16 | android: {} 17 | }, 18 | privacyManager: {}, 19 | actions: {}, 20 | inApp: {} 21 | } 22 | 23 | // Argcheck values: 24 | // * : allow anything,  25 | // f : function 26 | // a : array 27 | // d : date 28 | // n : number 29 | // s : string 30 | // o : object 31 | // lowercase = required, uppercase = optional 32 | 33 | function perform(name, args, success, failure) { 34 | exec(success, failure, "AirshipCordova", "perform", [name, args]) 35 | } 36 | 37 | var callbackId = 0 38 | function registerListener(name, callback) { 39 | var isCancelled = false 40 | let subCallbackId = callbackId 41 | callbackId += 1 42 | 43 | exec(function (event) { 44 | if (!isCancelled) { 45 | callback(event) 46 | } 47 | }, {}, "AirshipCordova", "addListener", [name, subCallbackId]) 48 | 49 | let subscription = {} 50 | subscription.cancel = function () { 51 | isCancelled = true 52 | exec({}, {}, "AirshipCordova", "removeListener", [name, subCallbackId]) 53 | } 54 | return subscription 55 | } 56 | 57 | function TagEditor(methodPrefix, nativeMethod) { 58 | var operations = [] 59 | var editor = {} 60 | 61 | editor.addTags = function (tags) { 62 | argscheck.checkArgs('a', methodPrefix + "#addTags", arguments) 63 | var operation = { "operationType": "add", "tags": tags } 64 | operations.push(operation) 65 | return editor 66 | } 67 | 68 | editor.removeTags = function (tags) { 69 | argscheck.checkArgs('a', methodPrefix + "#removeTags", arguments) 70 | var operation = { "operationType": "remove", "tags": tags } 71 | operations.push(operation) 72 | return editor 73 | } 74 | 75 | editor.apply = function (success, failure) { 76 | argscheck.checkArgs('FF', methodPrefix + "#apply", arguments) 77 | perform(nativeMethod, operations, success, failure) 78 | operations = [] 79 | return editor 80 | } 81 | 82 | return editor 83 | } 84 | 85 | function TagGroupEditor(methodPrefix, nativeMethod) { 86 | var operations = [] 87 | var editor = {} 88 | 89 | editor.addTags = function (tagGroup, tags) { 90 | argscheck.checkArgs('sa', methodPrefix + "#addTags", arguments) 91 | var operation = { "operationType": "add", "group": tagGroup, "tags": tags } 92 | operations.push(operation) 93 | return editor 94 | } 95 | 96 | editor.removeTags = function (tagGroup, tags) { 97 | argscheck.checkArgs('sa', methodPrefix + "#removeTags", arguments) 98 | var operation = { "operationType": "remove", "group": tagGroup, "tags": tags } 99 | operations.push(operation) 100 | return editor 101 | } 102 | 103 | editor.setTags = function (tagGroup, tags) { 104 | argscheck.checkArgs('sa', methodPrefix + "#setTags", arguments) 105 | var operation = { "operationType": "set", "group": tagGroup, "tags": tags } 106 | operations.push(operation) 107 | return editor 108 | } 109 | 110 | editor.apply = function (success, failure) { 111 | argscheck.checkArgs('FF', methodPrefix + "#apply", arguments) 112 | perform(nativeMethod, operations, success, failure) 113 | operations = [] 114 | return editor 115 | } 116 | 117 | return editor 118 | } 119 | 120 | function ScopedSubscriptionListEditor(methodPrefix, nativeMethod) { 121 | var operations = [] 122 | var editor = {} 123 | 124 | editor.subscribe = function (listId, scope) { 125 | argscheck.checkArgs('ss', methodPrefix + "#subscribe", arguments) 126 | var operation = { "action": "subscribe", "listId": listId, "scope": scope } 127 | operations.push(operation) 128 | return editor 129 | } 130 | 131 | editor.unsubscribe = function (listId, scope) { 132 | argscheck.checkArgs('ss', methodPrefix + "#unsubscribe", arguments) 133 | var operation = { "action": "unsubscribe", "listId": listId, "scope": scope } 134 | operations.push(operation) 135 | return editor 136 | } 137 | 138 | editor.apply = function (success, failure) { 139 | argscheck.checkArgs('FF', methodPrefix + "#apply", arguments) 140 | perform(nativeMethod, operations, success, failure) 141 | operations = [] 142 | return editor 143 | } 144 | 145 | return editor 146 | } 147 | 148 | function SubscriptionListEditor(methodPrefix, nativeMethod) { 149 | // Store the raw operations and let the SDK combine them 150 | var operations = [] 151 | var editor = {} 152 | 153 | editor.subscribe = function (listId) { 154 | argscheck.checkArgs('s', methodPrefix + "#subscribe", arguments) 155 | var operation = { "action": "subscribe", "listId": listId } 156 | operations.push(operation) 157 | return editor 158 | } 159 | 160 | editor.unsubscribe = function (listId) { 161 | argscheck.checkArgs('s', methodPrefix + "#unsubscribe", arguments) 162 | var operation = { "action": "unsubscribe", "listId": listId } 163 | operations.push(operation) 164 | return editor 165 | } 166 | 167 | editor.apply = function (success, failure) { 168 | argscheck.checkArgs('FF', methodPrefix + "#apply", arguments) 169 | perform(nativeMethod, operations, success, failure) 170 | operations = [] 171 | return editor 172 | } 173 | 174 | return editor 175 | } 176 | 177 | function AttributesEditor(methodPrefix, nativeMethod) { 178 | var operations = []; 179 | var editor = {}; 180 | 181 | editor.setAttribute = function (name, value) { 182 | argscheck.checkArgs('s*', methodPrefix + "#setAttribute", arguments) 183 | 184 | var operation = { "action": "set", "value": value, "key": name } 185 | 186 | if (typeof value === "string") { 187 | operation["type"] = "string" 188 | } else if (typeof value === "number") { 189 | operation["type"] = "number" 190 | } else if (typeof value === "boolean") { 191 | // No boolean attribute type. Convert value to string. 192 | operation["type"] = "string" 193 | operation["value"] = value.toString(); 194 | } else if (value instanceof Date) { 195 | // JavaScript's date type doesn't pass through the JS to native bridge. Dates are instead serialized as milliseconds since epoch. 196 | operation["type"] = "date" 197 | operation["value"] = value.getTime() 198 | } else { 199 | throw ("Unsupported attribute type: " + typeof value) 200 | } 201 | 202 | operations.push(operation) 203 | 204 | return editor 205 | } 206 | 207 | editor.setJsonAttribute = function( 208 | name, 209 | instanceId, 210 | json, 211 | expiration 212 | ) { 213 | argscheck.checkArgs('ss*', methodPrefix + "#setJsonAttribute", arguments) 214 | var operation = { 215 | "action": 'set', 216 | "value": json, 217 | "key": name, 218 | "instance_id": instanceId, 219 | "type": 'json' 220 | } 221 | 222 | if (expiration != null) { 223 | operation["expiration_milliseconds"] = expiration.getTime() 224 | } 225 | 226 | operations.push(operation) 227 | return editor 228 | } 229 | 230 | editor.removeJsonAttribute = function (name, instanceId) { 231 | argscheck.checkArgs('ss', methodPrefix + "#removeJsonAttribute", arguments) 232 | var operation = { "action": "remove", "key": name, "instance_id": instanceId } 233 | operations.push(operation) 234 | return editor 235 | } 236 | 237 | editor.removeAttribute = function (name) { 238 | argscheck.checkArgs('s', methodPrefix + "#removeAttribute", arguments) 239 | var operation = { "action": "remove", "key": name } 240 | operations.push(operation) 241 | return editor 242 | } 243 | 244 | editor.apply = function (success, failure) { 245 | argscheck.checkArgs('FF', methodPrefix + "#apply", arguments) 246 | perform(nativeMethod, operations, success, failure) 247 | operations = [] 248 | return editor 249 | } 250 | 251 | return editor 252 | } 253 | 254 | 255 | airship.takeOff = function (config, success, failure) { 256 | argscheck.checkArgs("*FF", "Airship.takeOff", arguments); 257 | perform("takeOff", config, success, failure) 258 | } 259 | 260 | airship.isFlying = function (success, failure) { 261 | argscheck.checkArgs("fF", "Airship.isFlying", arguments); 262 | perform("isFlying", null, success, failure) 263 | } 264 | 265 | airship.onDeepLink = function (callback) { 266 | argscheck.checkArgs('F', 'Airship.onDeepLink', arguments) 267 | return registerListener("airship.event.deep_link_received", callback) 268 | } 269 | 270 | // Channel 271 | 272 | airship.channel.getChannelId = function (success, failure) { 273 | argscheck.checkArgs('fF', 'Airship.channel.getChannelId', arguments) 274 | perform("channel#getChannelId", null, success, failure) 275 | } 276 | 277 | airship.channel.waitForChannelId = function (success, failure) { 278 | argscheck.checkArgs('fF', 'Airship.channel.waitForChannelId', arguments) 279 | perform("channel#waitForChannelId", null, success, failure) 280 | } 281 | 282 | airship.channel.getSubscriptionLists = function (success, failure) { 283 | argscheck.checkArgs('fF', 'Airship.channel.getSubscriptionLists', arguments) 284 | perform("channel#getSubscriptionLists", null, success, failure) 285 | } 286 | 287 | airship.channel.getTags = function (success, failure) { 288 | argscheck.checkArgs('fF', 'Airship.channel.getTags', arguments) 289 | perform("channel#getTags", null, success, failure) 290 | } 291 | 292 | 293 | airship.channel.editTags = function () { 294 | return new TagEditor('Airship.channel.editTags', 'channel#editTags') 295 | } 296 | 297 | airship.channel.editTagGroups = function () { 298 | return new TagGroupEditor('Airship.channel.editTagGroups', 'channel#editTagGroups') 299 | } 300 | 301 | airship.channel.editAttributes = function () { 302 | return new AttributesEditor('Airship.channel.editAttributes', 'channel#editAttributes') 303 | } 304 | 305 | airship.channel.editSubscriptionLists = function () { 306 | return new SubscriptionListEditor('Airship.channel.editSubscriptionLists', 'channel#editSubscriptionLists') 307 | } 308 | 309 | airship.channel.onChannelCreated = function (callback) { 310 | argscheck.checkArgs('F', 'Airship.channel.channel_created', arguments) 311 | return registerListener("airship.event.channel_created", callback) 312 | } 313 | 314 | airship.channel.enableChannelCreation = function (success, failure) { 315 | argscheck.checkArgs('FF', 'Airship.channel.enableChannelCreation', arguments) 316 | perform("channel#enableChannelCreation", null, success, failure) 317 | } 318 | 319 | // Contact 320 | 321 | airship.contact.getNamedUserId = function (success, failure) { 322 | argscheck.checkArgs('fF', 'Airship.contact.getNamedUserId', arguments) 323 | perform("contact#getNamedUserId", null, success, failure) 324 | } 325 | 326 | airship.contact.identify = function (namedUserId, success, failure) { 327 | argscheck.checkArgs('SFF', 'Airship.contact.identify', arguments) 328 | perform("contact#identify", namedUserId, success, failure) 329 | } 330 | 331 | airship.contact.reset = function (success, failure) { 332 | argscheck.checkArgs('FF', 'Airship.contact.reset', arguments) 333 | perform("contact#reset", null, success, failure) 334 | } 335 | 336 | airship.contact.notifyRemoteLogin = function (success, failure) { 337 | argscheck.checkArgs('FF', 'Airship.contact.notifyRemoteLogin', arguments) 338 | perform("contact#notifyRemoteLogin", null, success, failure) 339 | } 340 | 341 | airship.contact.getSubscriptionLists = function (success, failure) { 342 | argscheck.checkArgs('fF', 'Airship.contact.getSubscriptionLists', arguments) 343 | perform("contact#getSubscriptionLists", null, success, failure) 344 | } 345 | 346 | airship.contact.editTagGroups = function () { 347 | return new TagGroupEditor('Airship.contact.editTagGroups', 'contact#editTagGroups') 348 | } 349 | 350 | airship.contact.editAttributes = function () { 351 | return new AttributesEditor('Airship.contact.editAttributes', 'contact#editAttributes') 352 | } 353 | 354 | airship.contact.editSubscriptionLists = function () { 355 | return new ScopedSubscriptionListEditor('Airship.contact.editSubscriptionLists', 'contact#editSubscriptionLists') 356 | } 357 | 358 | // Push 359 | 360 | airship.push.enableUserNotifications = function (success, failure) { 361 | argscheck.checkArgs('fF', 'Airship.push.enableUserNotifications', arguments) 362 | perform("push#enableUserNotifications", null, success, failure) 363 | } 364 | 365 | airship.push.isUserNotificationsEnabled = function (success, failure) { 366 | argscheck.checkArgs('fF', 'Airship.push.isUserNotificationsEnabled', arguments) 367 | perform("push#isUserNotificationsEnabled", null, success, failure) 368 | } 369 | 370 | airship.push.setUserNotificationsEnabled = function (enabled, success, failure) { 371 | argscheck.checkArgs('*FF', 'Airship.push.setUserNotificationsEnabled', arguments) 372 | perform("push#setUserNotificationsEnabled", !!enabled, success, failure) 373 | } 374 | 375 | airship.push.getNotificationStatus = function (success, failure) { 376 | argscheck.checkArgs('fF', 'Airship.push.getNotificationStatus', arguments) 377 | perform("push#getNotificationStatus", null, success, failure) 378 | } 379 | 380 | airship.push.getPushToken = function (success, failure) { 381 | argscheck.checkArgs('fF', 'Airship.push.getPushToken', arguments) 382 | perform("push#getPushToken", null, success, failure) 383 | } 384 | 385 | airship.push.clearNotifications = function (success, failure) { 386 | argscheck.checkArgs('FF', 'Airship.push.clearNotifications', arguments) 387 | perform("push#clearNotifications", null, success, failure) 388 | } 389 | 390 | airship.push.clearNotification = function (id, success, failure) { 391 | argscheck.checkArgs('sFF', 'Airship.push.clearNotification', arguments) 392 | perform("push#clearNotification", id, success, failure) 393 | } 394 | 395 | airship.push.getActiveNotifications = function (success, failure) { 396 | argscheck.checkArgs('fF', 'Airship.push.getActiveNotifications', arguments) 397 | perform("push#getActiveNotifications", null, success, failure) 398 | } 399 | 400 | airship.push.onNotificationStatusChanged = function (callback) { 401 | argscheck.checkArgs('F', 'Airship.push.onNotificationStatusChanged', arguments) 402 | return registerListener("airship.event.notification_status_changed", callback) 403 | } 404 | 405 | airship.push.onPushTokenReceived = function (callback) { 406 | argscheck.checkArgs('F', 'Airship.push.onPushTokenReceived', arguments) 407 | return registerListener("airship.event.push_token_received", callback) 408 | } 409 | 410 | airship.push.onPushReceived = function (callback) { 411 | argscheck.checkArgs('F', 'Airship.push.onPushReceived', arguments) 412 | return registerListener("airship.event.push_received", callback) 413 | } 414 | 415 | airship.push.onNotificationResponse = function (callback) { 416 | argscheck.checkArgs('F', 'Airship.push.onNotificationResponse', arguments) 417 | return registerListener("airship.event.notification_response", callback) 418 | } 419 | 420 | // Push Android 421 | 422 | airship.push.android.setForegroundNotificationsEnabled = function (enabled, success, failure) { 423 | argscheck.checkArgs('*FF', 'Airship.push.android.setForegroundNotificationsEnabled', arguments) 424 | perform("push#android#setForegroundNotificationsEnabled", !!enabled, success, failure) 425 | } 426 | 427 | airship.push.android.isForegroundNotificationsEnabled = function (success, failure) { 428 | argscheck.checkArgs('fF', 'Airship.push.android.isForegroundNotificationsEnabled', arguments) 429 | perform("push#android#isForegroundNotificationsEnabled", null, success, failure) 430 | } 431 | 432 | airship.push.android.isNotificationChannelEnabled = function (channel, success, failure) { 433 | argscheck.checkArgs('sfF', 'Airship.push.android.isNotificationChannelEnabled', arguments) 434 | perform("push#android#isNotificationChannelEnabled", channel, success, failure) 435 | } 436 | 437 | airship.push.android.setNotificationConfig = function (config, success, failure) { 438 | argscheck.checkArgs('*FF', 'Airship.push.android.isNotificationChannelEnabled', arguments) 439 | perform("push#android#setNotificationConfig", config, success, failure) 440 | } 441 | 442 | // Push iOS 443 | 444 | airship.push.ios.setQuietTimeEnabled = function(enabled, success, failure) { 445 | argscheck.checkArgs('*FF', 'Airship.push.ios.setQuietTimeEnabled', arguments) 446 | perform("push#ios#setQuietTimeEnabled", !!enabled, success, failure) 447 | } 448 | 449 | airship.push.ios.isQuietTimeEnabled = function(success, failure) { 450 | argscheck.checkArgs('fF', 'Airship.push.ios.isQuietTimeEnabled', arguments) 451 | perform("push#ios#isQuietTimeEnabled", null, success, failure) 452 | } 453 | 454 | airship.push.ios.setQuietTime =function(quietTime, success, failure) { 455 | argscheck.checkArgs('*FF', 'Airship.push.ios.setQuietTime', arguments) 456 | perform("push#ios#setQuietTime", quietTime, success, failure) 457 | } 458 | 459 | airship.push.ios.getQuietTime = function(success, failure) { 460 | argscheck.checkArgs('fF', 'Airship.push.ios.getQuietTime', arguments) 461 | perform("push#ios#getQuietTime", null, success, failure) 462 | } 463 | 464 | airship.push.ios.isAutobadgeEnabled = function (success, failure) { 465 | argscheck.checkArgs('fF', 'Airship.push.ios.isAutobadgeEnabled', arguments) 466 | perform("push#ios#isAutobadgeEnabled", null, success, failure) 467 | } 468 | 469 | airship.push.ios.setAutobadgeEnabled = function (enabled, success, failure) { 470 | argscheck.checkArgs('*FF', 'Airship.push.ios.setAutobadgeEnabled', arguments) 471 | perform("push#ios#setAutobadgeEnabled", !!enabled, success, failure) 472 | } 473 | 474 | airship.push.ios.setForegroundPresentationOptions = function (options, success, failure) { 475 | argscheck.checkArgs('*FF', 'Airship.push.ios.setForegroundPresentationOptions', arguments) 476 | perform("push#ios#setForegroundPresentationOptions", options, success, failure) 477 | } 478 | 479 | airship.push.ios.setNotificationOptions = function (options, success, failure) { 480 | argscheck.checkArgs('*FF', 'Airship.push.ios.setNotificationOptions', arguments) 481 | perform("push#ios#setNotificationOptions", options, success, failure) 482 | } 483 | 484 | airship.push.ios.setBadgeNumber = function (badge, success, failure) { 485 | argscheck.checkArgs('nFF', 'Airship.push.ios.setBadgeNumber', arguments) 486 | perform("push#ios#setBadgeNumber", badge, success, failure) 487 | } 488 | 489 | airship.push.ios.getBadgeNumber = function (success, failure) { 490 | argscheck.checkArgs('fF', 'Airship.push.ios.getBadgeNumber', arguments) 491 | perform("push#ios#getBadgeNumber", null, success, failure) 492 | } 493 | 494 | airship.push.ios.getAuthorizedNotificationSettings = function (success, failure) { 495 | argscheck.checkArgs('fF', 'Airship.push.ios.getAuthorizedNotificationSettings', arguments) 496 | perform("push#ios#getAuthorizedNotificationSettings", null, success, failure) 497 | } 498 | 499 | airship.push.ios.getAuthorizedNotificationStatus = function (success, failure) { 500 | argscheck.checkArgs('fF', 'Airship.push.ios.getAuthorizedNotificationStatus', arguments) 501 | perform("push#ios#getAuthorizedNotificationStatus", null, success, failure) 502 | } 503 | 504 | airship.push.ios.resetBadge = function (success, failure) { 505 | argscheck.checkArgs('*FF', 'Airship.push.ios.resetBadge', arguments) 506 | perform("push#ios#resetBadge", null, success, failure) 507 | } 508 | 509 | airship.push.ios.onAuthorizedSettingsChanged = function (callback) { 510 | argscheck.checkArgs('F', 'Airship.push.ios.onAuthorizedSettingsChanged', arguments) 511 | return registerListener("airship.event.ios_authorized_notification_settings_changed", callback) 512 | } 513 | 514 | 515 | // Privacy Manager 516 | 517 | airship.privacyManager.isFeaturesEnabled = function (features, success, failure) { 518 | argscheck.checkArgs('afF', 'Airship.push.isFeaturesEnabled', arguments) 519 | perform("privacyManager#isFeaturesEnabled", features, success, failure) 520 | } 521 | 522 | airship.privacyManager.setEnabledFeatures = function (features, success, failure) { 523 | argscheck.checkArgs('aFF', 'Airship.push.setEnabledFeatures', arguments) 524 | perform("privacyManager#setEnabledFeatures", features, success, failure) 525 | } 526 | 527 | airship.privacyManager.enableFeatures = function (features, success, failure) { 528 | argscheck.checkArgs('aFF', 'Airship.push.enableFeatures', arguments) 529 | perform("privacyManager#enableFeatures", features, success, failure) 530 | } 531 | 532 | airship.privacyManager.disableFeatures = function (features, success, failure) { 533 | argscheck.checkArgs('aFF', 'Airship.push.disableFeatures', arguments) 534 | perform("privacyManager#disableFeatures", features, success, failure) 535 | } 536 | 537 | airship.privacyManager.getEnabledFeatures = function (success, failure) { 538 | argscheck.checkArgs('fF', 'Airship.push.getEnabledFeatures', arguments) 539 | perform("privacyManager#getEnabledFeatures", null, success, failure) 540 | } 541 | 542 | // Message Center 543 | 544 | airship.messageCenter.getUnreadCount = function (success, failure) { 545 | argscheck.checkArgs('fF', 'Airship.messageCenter.getUnreadCount', arguments) 546 | perform("messageCenter#getUnreadCount", null, success, failure) 547 | } 548 | 549 | airship.messageCenter.getMessages = function (success, failure) { 550 | argscheck.checkArgs('fF', 'Airship.messageCenter.getMessages', arguments) 551 | perform("messageCenter#getMessages", null, success, failure) 552 | } 553 | 554 | airship.messageCenter.markMessageRead = function (messageId, success, failure) { 555 | argscheck.checkArgs('sFF', 'Airship.messageCenter.markMessageRead', arguments) 556 | perform("messageCenter#markMessageRead", messageId, success, failure) 557 | } 558 | 559 | airship.messageCenter.deleteMessage = function (messageId, success, failure) { 560 | argscheck.checkArgs('sFF', 'Airship.messageCenter.deleteMessage', arguments) 561 | perform("messageCenter#deleteMessage", messageId, success, failure) 562 | } 563 | 564 | airship.messageCenter.dismiss = function (success, failure) { 565 | argscheck.checkArgs('FF', 'Airship.messageCenter.dismiss', arguments) 566 | perform("messageCenter#dismiss", null, success, failure) 567 | } 568 | 569 | airship.messageCenter.display = function (messageId, success, failure) { 570 | argscheck.checkArgs('SFF', 'Airship.messageCenter.display', arguments) 571 | perform("messageCenter#display", messageId, success, failure) 572 | } 573 | 574 | airship.messageCenter.showMessageView = function (messageId, success, failure) { 575 | argscheck.checkArgs('sFF', 'Airship.messageCenter.showMessageView', arguments) 576 | perform("messageCenter#showMessageView", messageId, success, failure) 577 | } 578 | 579 | airship.messageCenter.refreshMessages = function (success, failure) { 580 | argscheck.checkArgs('FF', 'Airship.messageCenter.refreshMessages', arguments) 581 | perform("messageCenter#refreshMessages", null, success, failure) 582 | } 583 | 584 | airship.messageCenter.setAutoLaunchDefaultMessageCenter = function (autoLaunch, success, failure) { 585 | argscheck.checkArgs('*FF', 'Airship.messageCenter.setAutoLaunchDefaultMessageCenter', arguments) 586 | perform("messageCenter#setAutoLaunchDefaultMessageCenter", !!autoLaunch, success, failure) 587 | } 588 | 589 | airship.messageCenter.onUpdated = function (callback) { 590 | argscheck.checkArgs('F', 'Airship.messageCenter.onUpdated', arguments) 591 | return registerListener("airship.event.message_center_updated", callback) 592 | } 593 | 594 | airship.messageCenter.onDisplay = function (callback) { 595 | argscheck.checkArgs('F', 'Airship.messageCenter.onDisplay', arguments) 596 | return registerListener("airship.event.display_message_center", callback) 597 | } 598 | 599 | 600 | // Preference Center 601 | 602 | airship.preferenceCenter.display = function (preferenceCenterId, success, failure) { 603 | argscheck.checkArgs('sFF', 'Airship.preferenceCenter.display', arguments) 604 | perform("preferenceCenter#display", preferenceCenterId, success, failure) 605 | } 606 | 607 | airship.preferenceCenter.getConfig = function (preferenceCenterId, success, failure) { 608 | argscheck.checkArgs('sfF', 'Airship.preferenceCenter.getConfig', arguments) 609 | perform("preferenceCenter#getConfig", preferenceCenterId, success, failure) 610 | } 611 | 612 | airship.preferenceCenter.setAutoLaunchDefaultPreferenceCenter = function (preferenceCenterId, autoLaunch, success, failure) { 613 | argscheck.checkArgs('s*FF', 'Airship.preferenceCenter.setAutoLaunchDefaultPreferenceCenter', arguments) 614 | perform("preferenceCenter#setAutoLaunchDefaultPreferenceCenter", [preferenceCenterId, !!autoLaunch], success, failure) 615 | } 616 | 617 | airship.preferenceCenter.onDisplay = function (callback) { 618 | argscheck.checkArgs('F', 'Airship.preferenceCenter.onDisplay', arguments) 619 | return registerListener("airship.event.display_preference_center", callback) 620 | } 621 | 622 | // Analytics 623 | 624 | airship.analytics.trackScreen = function (screen, success, failure) { 625 | argscheck.checkArgs('SFF', 'Airship.analytics.trackScreen', arguments) 626 | perform("analytics#trackScreen", screen, success, failure) 627 | } 628 | 629 | airship.analytics.associateIdentifier = function (key, value, success, failure) { 630 | argscheck.checkArgs('sSFF', 'Airship.analytics.associateIdentifier', arguments) 631 | perform("analytics#associateIdentifier", [key, value], success, failure) 632 | } 633 | 634 | airship.analytics.addCustomEvent = function (event, success, failure) { 635 | argscheck.checkArgs('*FF', 'Airship.analytics.addCustomEvent', arguments) 636 | perform("analytics#addCustomEvent", event, success, failure) 637 | } 638 | 639 | // Actions 640 | 641 | airship.actions.run = function (name, value, success, failure) { 642 | argscheck.checkArgs('s*FF', 'Airship.actions.run', arguments) 643 | perform("actions#run", [name, value], success, failure) 644 | } 645 | 646 | /// Feature Flags 647 | 648 | airship.featureFlagManager.flag = function (name, success, failure) { 649 | argscheck.checkArgs('sfF', 'Airship.featureFlagManager.flag', arguments) 650 | perform("featureFlagManager#flag", name, success, failure) 651 | } 652 | 653 | airship.featureFlagManager.trackInteraction = function (flag, success, failure) { 654 | argscheck.checkArgs('*FF', 'Airship.featureFlagManager.trackInteraction', arguments) 655 | perform("featureFlagManager#trackInteraction", flag, success, failure) 656 | } 657 | 658 | /// In App 659 | 660 | airship.inApp.setPaused = function (paused, success, failure) { 661 | argscheck.checkArgs('sFF', 'Airship.inApp.setPaused', arguments) 662 | perform("inApp#setPaused", !!paused, success, failure) 663 | } 664 | 665 | 666 | airship.inApp.isPaused = function (success, failure) { 667 | argscheck.checkArgs('fF', 'Airship.inApp.isPaused', arguments) 668 | perform("inApp#isPaused", null, success, failure) 669 | } 670 | 671 | airship.inApp.setDisplayInterval = function (interval, success, failure) { 672 | argscheck.checkArgs('nFF', 'Airship.inApp.setDisplayInterval', arguments) 673 | perform("inApp#setDisplayInterval", interval, success, failure) 674 | } 675 | 676 | airship.privacyManager.getDisplayInterval = function (success, failure) { 677 | argscheck.checkArgs('fF', 'Airship.inApp.getDisplayInterval', arguments) 678 | perform("inApp#getDisplayInterval", null, success, failure) 679 | } 680 | 681 | /// Locale 682 | 683 | airship.locale.setLocaleOverride = function (locale, success, failure) { 684 | argscheck.checkArgs('sFF', 'Airship.inApp.setLocaleOverride', arguments) 685 | perform("locale#setLocaleOverride", locale, success, failure) 686 | } 687 | 688 | 689 | airship.locale.clearLocaleOverride = function (success, failure) { 690 | argscheck.checkArgs('FF', 'Airship.inApp.clearLocaleOverride', arguments) 691 | perform("locale#clearLocaleOverride", null, success, failure) 692 | } 693 | 694 | airship.locale.getLocale = function (success, failure) { 695 | argscheck.checkArgs('fF', 'Airship.locale.getLocale', arguments) 696 | perform("locale#getLocale", null, success, failure) 697 | } 698 | 699 | module.exports = airship; --------------------------------------------------------------------------------