├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── ionic-issue-bot.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs └── interfaces │ ├── callbackfunction.md │ ├── checkforupdateresponse.md │ ├── iappinfo.md │ ├── icurrentconfig.md │ ├── ideployconfig.md │ ├── ideploypluginapi.md │ ├── ipluginbaseapi.md │ ├── isnapshotinfo.md │ ├── isyncoptions.md │ └── window.md ├── hooks └── beforePrepare.js ├── package.json ├── plugin.xml ├── scripts ├── apply-changes.sh ├── create-local-app.sh ├── docs.sh ├── ng-prepare.sh └── update-plugin-version-code.sh ├── src ├── android │ ├── IonicCordovaCommon.java │ └── cordovapluginionic.gradle ├── browser │ └── IonicCordovaCommon.js └── ios │ ├── IonicCordovaCommon.h │ └── IonicCordovaCommon.m ├── tests └── test_common.ts ├── tsconfig.json ├── tsconfig.ng.json ├── tslint.json └── www ├── IonicCordova.ts ├── common.ts ├── definitions.ts ├── guards.ts ├── index.ts └── ngx └── index.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | insert_final_newline = true 4 | 5 | [*.{js,jsx,ts,tsx,json,scss}] 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **cordova plugin ls output:** 8 | ``` 9 | cordova plugin ls output here 10 | ``` 11 | 12 | **ionic info output:** 13 | ``` 14 | ionic info output here 15 | ``` 16 | 17 | **Describe the bug** 18 | A clear and concise description of what the bug is. 19 | 20 | **To Reproduce** 21 | Steps to reproduce the behavior: 22 | 1. Go to '...' 23 | 2. Click on '....' 24 | 3. Scroll down to '....' 25 | 4. See error 26 | 27 | **Expected behavior** 28 | A clear and concise description of what you expected to happen. 29 | 30 | **Screenshots** 31 | If applicable, add screenshots to help explain your problem. 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/ionic-issue-bot.yml: -------------------------------------------------------------------------------- 1 | triage: 2 | label: triage 3 | dryRun: false 4 | 5 | closeAndLock: 6 | labels: 7 | - label: "ionitron: support" 8 | message: > 9 | Thanks for the issue! This issue appears to be a support request. We 10 | use this issue tracker exclusively for bug reports and feature 11 | requests. For support questions, please see our [Support 12 | Page](https://ionicframework.com/support). 13 | Thank you for using Ionic! 14 | - label: "ionitron: pro" 15 | message: > 16 | Thanks for the issue! This issue appears to be related to our 17 | commercial products. We use this issue tracker exclusively for bug 18 | reports and feature requests. Please [open a support 19 | ticket](https://ionicframework.com/support/request) and we'll be happy 20 | to assist you. 21 | Thank you for using Ionic! 22 | - label: "ionitron: missing template" 23 | message: > 24 | Thanks for the issue! It appears that you have not filled out the 25 | provided issue template. We use this issue template in order to gather 26 | more information and further assist you. Please create a new issue and 27 | ensure the template is fully filled out. 28 | Thank you for using Ionic! 29 | - label: "ionitron: old major version" 30 | message: > 31 | Thanks for the issue! This issue appears to be associated with an old 32 | version of the cordova-plugin-ionic. Please update to the latest version. 33 | If the issue is relevant and if it persists after updating to the latest 34 | version, please create a new issue. Thank you for using Ionic! 35 | 36 | close: true 37 | lock: true 38 | dryRun: false 39 | 40 | stale: 41 | days: 180 42 | maxIssuesPerRun: 100 43 | exemptLabels: 44 | - triage 45 | exemptProjects: true 46 | exemptMilestones: true 47 | label: "ionitron: stale issue" 48 | message: > 49 | Thanks for the issue! This issue is being closed due to inactivity. If this 50 | is still an issue with the latest version of Ionic, please create a new 51 | issue and ensure the template is fully filled out. 52 | 53 | 54 | Thank you for using Ionic! 55 | close: true 56 | lock: true 57 | dryRun: false 58 | 59 | wrongRepo: 60 | repos: 61 | - label: "ionitron: framework" 62 | repo: ionic 63 | message: > 64 | Thanks for the issue! We use this issue tracker exclusively for bug 65 | reports and feature requests for cordova-plugin-ionic. It appears that this 66 | issue is associated with the Ionic Framework. I am moving this issue to 67 | the Ionic Framework repository. Please track this issue over there. 68 | Thank you for using Ionic! 69 | - label: "ionitron: capacitor" 70 | repo: capacitor 71 | message: > 72 | Thanks for the issue! We use this issue tracker exclusively for bug 73 | reports and feature requests for cordova-plugin-ionic. It appears that this 74 | issue is associated with Capacitor. I am moving this issue to the 75 | Capacitor repository. Please track this issue over there. 76 | Thank you for using Ionic! 77 | - label: "ionitron: ionic-native" 78 | repo: ionic-native 79 | message: > 80 | reports and feature requests for the Ionic CLI. It appears that this 81 | reports and feature requests for cordova-plugin-ionic. It appears that this 82 | issue is associated with Ionic Native. I am moving this issue to the 83 | Ionic Native repository. Please track this issue over there. 84 | Thank you for using Ionic! 85 | - label: "ionitron: ionic-site" 86 | repo: ionic-site 87 | message: > 88 | Thanks for the issue! We use this issue tracker exclusively for bug 89 | reports and feature requests for cordova-plugin-ionic. It appears that this 90 | issue is associated with the Ionic website. I am moving this issue to 91 | the Ionic website repository. Please track this issue over there. 92 | Thank you for using Ionic! 93 | close: true 94 | lock: true 95 | dryRun: false 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | dist/ 4 | tmp/ 5 | .idea/ 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | tmp/ 4 | scripts/ 5 | apply-changes.sh 6 | create-local-app.sh 7 | bump-versions.sh 8 | docs/ 9 | tests/ 10 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | - repo: git://github.com/awebdeveloper/pre-commit-tslint/ 2 | sha: 'v0.0.2' # Use the sha or tag you want to point at 3 | hooks: 4 | - id: tslint 5 | exclude: 'tests' 6 | additional_dependencies: ['tslint@5.9.1', 'tslint-ionic-rules@0.0.14', 'typescript@2.6.2'] 7 | args: ['--config', 'tslint.json', '--project', 'tsconfig.json', '--fix'] 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ====== 3 | ## 5.5.3 4 | * Fixed iOS "no space left on device" 5 | 6 | ## 5.5.2 7 | * Fixed iOS error 28 – no space left on device 8 | 9 | ## 5.5.1 10 | * Updated iOS live update logic to properly handle non-200 status code responses from the update server. 11 | 12 | ## 5.5.0 13 | * Removed no longer used cordova-plugin-whitelist dependency 14 | * Updated gradle dependencies to use implementation rather than compile 15 | 16 | ## 5.4.8 17 | * Fixed background update mode to handle a scenario in which a live update for an older binary is applied to a new binary 18 | 19 | ## 5.4.7 20 | * fix(ng): Angular Ivy support ([#236](https://github.com/ionic-team/cordova-plugin-ionic/pull/236)) 21 | 22 | ## 5.4.6 23 | * fix(ios): create intermediate directories for downloads ([#231](https://github.com/ionic-team/cordova-plugin-ionic/pull/231)) 24 | 25 | ## 5.4.5 26 | * fix(android): make sure parent folders exist on file creation ([#226](https://github.com/ionic-team/cordova-plugin-ionic/pull/226)) 27 | 28 | ## 5.4.4 29 | * Fix issue where too many network requests at once could fire and cause performance issues. 30 | 31 | ## 5.4.3 32 | * Fix issue where types caused incompatability with Ionic v3 (Typescript 2.x) 33 | 34 | ## 5.4.0 35 | * Remove cordova-plugin-file as dependency to fix ([#213](https://github.com/ionic-team/cordova-plugin-ionic/issues/213)) 36 | 37 | ## 5.3.1 38 | * Add cordova-plugin-whitelist dependency ([#215](https://github.com/ionic-team/cordova-plugin-ionic/pull/215)) 39 | 40 | ## 5.3.0 41 | * Added an 'incompatibleUpdateAvailable' property to the 'CheckForUpdateResponse' ([#204](https://github.com/ionic-team/cordova-plugin-ionic/pull/204)) 42 | * 'ConfigurationInfo' now contains the 'BuildId' in addition to the 'SnapshotId' ([#204](https://github.com/ionic-team/cordova-plugin-ionic/pull/204)) 43 | 44 | ## 5.2.9 45 | * Get dataDirectory from getAppInfo function ([#197](https://github.com/ionic-team/cordova-plugin-ionic/pull/197)) 46 | * Add proxy for browser platform to support it ([#199](https://github.com/ionic-team/cordova-plugin-ionic/pull/199)) 47 | 48 | ## 5.2.8 49 | * Fix Type Error in IDeployConfig ([#196](https://github.com/ionic-team/cordova-plugin-ionic/pull/196)) 50 | 51 | ## 5.2.7 52 | * Change hook to run before_prepare and make it async ([#178](https://github.com/ionic-team/cordova-plugin-ionic/pull/178)) 53 | * Fixed bug where the a new binary update would load an older cached version of the app ([#179)](https://github.com/ionic-team/cordova-plugin-ionic/issues/179)) 54 | 55 | ## 5.2.6 56 | * Check for Capacitor and switch folder ([#164](https://github.com/ionic-team/cordova-plugin-ionic/pull/164)) 57 | * Remove unused import ([#163](https://github.com/ionic-team/cordova-plugin-ionic/pull/163)) 58 | * Delay device ready until pro checks are done ([#161](https://github.com/ionic-team/cordova-plugin-ionic/pull/161)) 59 | 60 | ## 5.2.5 61 | * Fix bug where binaryVersionName and binaryVersionCode are not returned from getConfiguation call 62 | * Fix bug where downloadUpdate progress call back would go from 0 to 50 rather than 100 ([#156](https://github.com/ionic-team/cordova-plugin-ionic/pull/156])) 63 | * Check if the device is online before checking for updates ([#154](https://github.com/ionic-team/cordova-plugin-ionic/pull/154)) 64 | 65 | ## 5.2.4 66 | * update check device resp to be accurate ([#148](https://github.com/ionic-team/cordova-plugin-ionic/pull/148)) 67 | 68 | ## 5.2.3 69 | 70 | * Fixed bug with AndroidManifest.xml syntax for real since our release script kept breaking it 71 | 72 | ## 5.2.2 73 | 74 | * Fixed bug with AndroidManifest.xml syntax 75 | 76 | ## 5.2.1 77 | 78 | * Add ACCESS_NETWORK_STATE permission to make navigator.onLine work on android 79 | 80 | ## 5.2.0 81 | 82 | * Added `DisableDeploy` Cordova preference allowing disabling of the plugin 83 | * Requires `cordova-plugin-ionic-webview@^2.1.4` for `DisableDeploy` support to work correctly 84 | 85 | ## 5.1.6 86 | 87 | * Fixed a bug with none update method strategy that could cause background updates upon resume of the app from background 88 | 89 | ## 5.0.6 90 | 91 | * Fixed a bug with version rebulds that could make some initial redirects take up to 15 seconds. 92 | 93 | ## 5.0.5 94 | 95 | * Rebuild a deploy directory in the case where the binary version has changed since the update was downloaded. 96 | 97 | # 5.0.0 98 | 99 | * Release! 100 | * Misc. bugfixes from rc3 101 | 102 | ## 5.0.0-rc.3 103 | 104 | * Improved dev tools 105 | 106 | ## 5.0.0-rc.2 107 | 108 | * Disable certain features if browser `fetch` is unavailable 109 | * Update some API methods for coherent returns 110 | 111 | ## 5.0.0-rc.1 112 | 113 | * Removed the switch statement in Android Native code to support older Java platforms 114 | 115 | ## 5.0.0-rc.0 116 | 117 | * Removed the deprecated API, to be added to version 4.2.0 118 | 119 | ## 5.0.0-alpha.0 120 | 121 | * Rewrote the plugin in Typescript. 122 | * Added support for application file manifests. 123 | * Added full support for partial update downloads, greatly decreasing network bandwidth. 124 | * **Deprecated old plugin API** in favor of modern promise-based API using async/await. Existing methods are still available, but may be removed in the future. 125 | 126 | ## 4.1.7 127 | 128 | * Fix a redirect bug in iOS that would give the `background` update method inconsistent behavior 129 | 130 | ## 4.1.6 131 | 132 | * Fix redirect bug in extract when version already exists (PR #82) 133 | 134 | ## 4.1.5 135 | 136 | * Fix UUID storage bug on iOS (PR #79) 137 | 138 | ## 4.1.4 139 | 140 | * Fix `checkAndApply` bug on Android (PR #77) 141 | 142 | ## 4.1.3 143 | 144 | * Fix broken release (4.1.2) 145 | 146 | ## 4.1.2 147 | 148 | * Handle `partial` flag from Pro API in `check-device` endpoint. 149 | 150 | ## 4.1.1 151 | 152 | * Send plugin version to Ionic Pro when checking for updates. 153 | 154 | ## 4.1.0 155 | 156 | * Added support for partial downloads. 157 | 158 | ## 4.0.1 159 | 160 | * Fixed a bug where `deleteVersion` would errorwhen called. (PR #63) 161 | 162 | # 4.0.0 163 | 164 | * Removed some extraneous plugin result calls. 165 | * **BREAKING** Unified all API functions to return `true` on success. 166 | 167 | ## 3.1.3 168 | 169 | * Fixed a bug where `ng-cordova` could potentially be overwritten when a deploy is applied. 170 | * Update no-zip branch 171 | 172 | ## 3.1.2 173 | 174 | * Fixed the extract callback value 175 | * Fixed a bug where the splashscreen would show for long periods while using the `background` update method on Android 176 | 177 | ## 3.1.1 178 | 179 | * Fixed another issue with the cordova.js regex. 180 | 181 | ## 3.1.0 182 | 183 | * Added a `WARN_DEBUG` flag to allow bypass of the debug dialog. (PR $49) 184 | * Fixed a bug where minified script tags could be overwritten. 185 | * Fixed a bug where redirect could error incorrectly on Android. 186 | 187 | # 3.0.0 188 | 189 | * Updated Cordova Splashscreen dependency (PR #41) 190 | * Fixed the callback responses from the `download` and `extract` functions to reflect the docs. 191 | * Store updates to plugin config make via the `init` methods in preferences. 192 | 193 | ## 2.0.4 194 | 195 | * Added a supported platforms note (PR #33) 196 | * Added correct callback calld for initialize and redirect methods (PR #20) 197 | 198 | ## 2.0.3 199 | 200 | * Fixed a bug where the splashscreen could hang in some cases when dismissing the debug dialog within the automatic update methods on Android. 201 | 202 | ## 2.0.2 203 | 204 | * Fixed a bug where the splashscreen could hang in some cases when using the `background` update method on Android. 205 | 206 | ## 2.0.1 207 | 208 | * Fixed a bug with the splashscreen dependency definition 209 | 210 | # 2.0.0 211 | 212 | * **BREAKING** Refactored the deploy plugin API to take a config object at `init`, but no longer needs app ID's/channels in individual calls. 213 | * Fixed a bug where the splashscreen was hiding before the deploy had finished on iOS. 214 | * Fixed an iOS bug where redirects were failing as a result of a regex comparison. 215 | * Fixed `auto` update method in Android to properly show the splash screen. 216 | * Streamlined the way debug builds are handled. The plugin will now ask before each redirect away from the bundled version, allowing easier local development. 217 | 218 | ## 1.1.9 219 | 220 | * Track channel. 221 | * Add ability to clear debug dialog. 222 | 223 | ## 1.1.8 224 | 225 | * Hooked up the `MAX_STORE` variable. 226 | 227 | ## 1.1.7 228 | 229 | * When the app is a `DEBUG` build, the deploy feature will show a prompt and ask whether to apply updates 230 | 231 | ## 1.1.6 232 | 233 | * Fixed a bug with `auto` mode when versions were already present. 234 | * Added `MAX_STORE` flag for future use. 235 | * Changed default behavior to `background` downloads. 236 | 237 | ## 1.1.5 238 | 239 | * Added this changelog and updated the README 240 | 241 | ## 1.1.4 242 | 243 | * Added background download flags and changed `AUTO_UPDATE` config to `UPDATE_METHOD` 244 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016-present Drifty Co. 2 | http://drifty.com/ 3 | 4 | MIT License 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ionic Live Update SDK 2 | ====== 3 | 4 | ## Documentation 5 | 6 | Documentation can be found [here](https://ionicframework.com/docs/appflow/deploy/intro) 7 | 8 | ## Support 9 | 10 | If you need support please use our [customer support portal](https://ionic.zendesk.com/hc/en-us). 11 | -------------------------------------------------------------------------------- /docs/interfaces/callbackfunction.md: -------------------------------------------------------------------------------- 1 | [Cordova Plugin Ionic](../../README.md) 2 | 3 | # Interface: CallbackFunction 4 | 5 | A callback function to handle the result. 6 | 7 | ## Type parameters 8 | #### T 9 | ## Hierarchy 10 | 11 | **CallbackFunction** 12 | 13 | ## Callable 14 | ▸ **__call**(result?: *[T]()*): `void` 15 | 16 | A callback function to handle the result. 17 | 18 | **Parameters:** 19 | 20 | | Name | Type | 21 | | ------ | ------ | 22 | | `Optional` result | [T]() | 23 | 24 | **Returns:** `void` 25 | 26 | ## Index 27 | 28 | --- 29 | 30 | -------------------------------------------------------------------------------- /docs/interfaces/checkforupdateresponse.md: -------------------------------------------------------------------------------- 1 | [Cordova Plugin Ionic](../../README.md) 2 | 3 | # Interface: CheckForUpdateResponse 4 | 5 | The response object describing if an update is available. 6 | 7 | ## Hierarchy 8 | 9 | **CheckForUpdateResponse** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [available](checkforupdateresponse.md#available) 16 | * [build](checkforupdateresponse.md#build) 17 | * [compatible](checkforupdateresponse.md#compatible) 18 | * [incompatibleUpdateAvailable](checkforupdateresponse.md#incompatibleupdateavailable) 19 | * [partial](checkforupdateresponse.md#partial) 20 | * [snapshot](checkforupdateresponse.md#snapshot) 21 | * [url](checkforupdateresponse.md#url) 22 | 23 | --- 24 | 25 | ## Properties 26 | 27 | 28 | 29 | ### available 30 | 31 | **● available**: *`boolean`* 32 | 33 | Whether or not an update is available. 34 | 35 | ___ 36 | 37 | 38 | ### `` build 39 | 40 | **● build**: *`undefined` \| `string`* 41 | 42 | The id of the build if available. 43 | 44 | ___ 45 | 46 | 47 | ### compatible 48 | 49 | **● compatible**: *`boolean`* 50 | 51 | Equivalent to available since v5 this can be ignored in favor of available 52 | *__deprecated__*: 53 | 54 | ___ 55 | 56 | 57 | ### `` incompatibleUpdateAvailable 58 | 59 | **● incompatibleUpdateAvailable**: *`undefined` \| `true` \| `false`* 60 | 61 | Whether or not there is an update available that is not compatible with this device. 62 | 63 | ___ 64 | 65 | 66 | ### partial 67 | 68 | **● partial**: *`false`* 69 | 70 | Legacy indicator of whether the update is a partial one. This will always be false and can be ignored 71 | *__deprecated__*: 72 | 73 | ___ 74 | 75 | 76 | ### `` snapshot 77 | 78 | **● snapshot**: *`undefined` \| `string`* 79 | 80 | The id of the snapshot if available. 81 | 82 | ___ 83 | 84 | 85 | ### `` url 86 | 87 | **● url**: *`undefined` \| `string`* 88 | 89 | The url to fetch the manifest of files in the update. 90 | 91 | ___ 92 | 93 | -------------------------------------------------------------------------------- /docs/interfaces/iappinfo.md: -------------------------------------------------------------------------------- 1 | [Cordova Plugin Ionic](../../README.md) 2 | 3 | # Interface: IAppInfo 4 | 5 | Information about the application. 6 | 7 | ## Hierarchy 8 | 9 | **IAppInfo** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [binaryVersionCode](iappinfo.md#binaryversioncode) 16 | * [binaryVersionName](iappinfo.md#binaryversionname) 17 | * [bundleName](iappinfo.md#bundlename) 18 | * [bundleVersion](iappinfo.md#bundleversion) 19 | * [dataDirectory](iappinfo.md#datadirectory) 20 | * [device](iappinfo.md#device) 21 | * [platform](iappinfo.md#platform) 22 | * [platformVersion](iappinfo.md#platformversion) 23 | * [version](iappinfo.md#version) 24 | 25 | --- 26 | 27 | ## Properties 28 | 29 | 30 | 31 | ### binaryVersionCode 32 | 33 | **● binaryVersionCode**: *`string` \| `number`* 34 | 35 | The versionCode on Android or CFBundleVersion on iOS this should be changed every time you do a new build debug or otherwise. 36 | 37 | ___ 38 | 39 | 40 | ### binaryVersionName 41 | 42 | **● binaryVersionName**: *`string`* 43 | 44 | The versionName on Android or CFBundleShortVersionString on iOS this is the end user readable version listed on the stores. 45 | 46 | ___ 47 | 48 | 49 | ### bundleName 50 | 51 | **● bundleName**: *`string`* 52 | 53 | The bundle name. 54 | 55 | ___ 56 | 57 | 58 | ### bundleVersion 59 | 60 | **● bundleVersion**: *`string`* 61 | 62 | *__deprecated__*: The versionName on Android or CFBundleShortVersionString on iOS this is the end user readable version listed on the stores. 63 | 64 | ___ 65 | 66 | 67 | ### dataDirectory 68 | 69 | **● dataDirectory**: *`string`* 70 | 71 | Directory where the snapshots are stored 72 | 73 | ___ 74 | 75 | 76 | ### device 77 | 78 | **● device**: *`string`* 79 | 80 | A generated device ID (NOT a native device ID) 81 | 82 | ___ 83 | 84 | 85 | ### platform 86 | 87 | **● platform**: *"ios" \| "android"* 88 | 89 | The platform that the app is currently installed on. 90 | 91 | ___ 92 | 93 | 94 | ### platformVersion 95 | 96 | **● platformVersion**: *`string`* 97 | 98 | The version of the native platform. 99 | 100 | ___ 101 | 102 | 103 | ### version 104 | 105 | **● version**: *`string`* 106 | 107 | *__deprecated__*: The versionCode on Android or CFBundleVersion on iOS this should be changed every time you do a new build debug or otherwise. 108 | 109 | ___ 110 | 111 | -------------------------------------------------------------------------------- /docs/interfaces/icurrentconfig.md: -------------------------------------------------------------------------------- 1 | [Cordova Plugin Ionic](../../README.md) 2 | 3 | # Interface: ICurrentConfig 4 | 5 | The current configuration for the deploy plugin on the device. 6 | 7 | ## Hierarchy 8 | 9 | **ICurrentConfig** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [appId](icurrentconfig.md#appid) 16 | * [binaryVersion](icurrentconfig.md#binaryversion) 17 | * [binaryVersionCode](icurrentconfig.md#binaryversioncode) 18 | * [binaryVersionName](icurrentconfig.md#binaryversionname) 19 | * [channel](icurrentconfig.md#channel) 20 | * [currentBuildId](icurrentconfig.md#currentbuildid) 21 | * [currentVersionId](icurrentconfig.md#currentversionid) 22 | * [disabled](icurrentconfig.md#disabled) 23 | * [host](icurrentconfig.md#host) 24 | * [maxVersions](icurrentconfig.md#maxversions) 25 | * [minBackgroundDuration](icurrentconfig.md#minbackgroundduration) 26 | * [updateMethod](icurrentconfig.md#updatemethod) 27 | 28 | --- 29 | 30 | ## Properties 31 | 32 | 33 | 34 | ### appId 35 | 36 | **● appId**: *`string`* 37 | 38 | The [Ionic Pro](https://ionicframework.com/docs/pro/) app id. 39 | 40 | ___ 41 | 42 | 43 | ### binaryVersion 44 | 45 | **● binaryVersion**: *`string`* 46 | 47 | *__deprecated__*: The binary version of the native bundle versionName on Android or CFBundleShortVersionString on iOS deprecated in favor of versionName 48 | 49 | ___ 50 | 51 | 52 | ### binaryVersionCode 53 | 54 | **● binaryVersionCode**: *`string`* 55 | 56 | The build version code of the native bundle versionCode on Android or CFBundleVersion on iOS 57 | 58 | ___ 59 | 60 | 61 | ### binaryVersionName 62 | 63 | **● binaryVersionName**: *`string`* 64 | 65 | The binary version of the native bundle versionName on Android or CFBundleShortVersionString on iOS 66 | 67 | ___ 68 | 69 | 70 | ### channel 71 | 72 | **● channel**: *`string`* 73 | 74 | The [channel](https://ionicframework.com/docs/pro/deploy/channels) that the plugin should listen for updates on. 75 | 76 | ___ 77 | 78 | 79 | ### `` currentBuildId 80 | 81 | **● currentBuildId**: *`undefined` \| `string`* 82 | 83 | The id of the currently applied build or undefined if none is applied. 84 | 85 | ___ 86 | 87 | 88 | ### `` currentVersionId 89 | 90 | **● currentVersionId**: *`undefined` \| `string`* 91 | 92 | The id of the currently applied updated or undefined if none is applied. 93 | 94 | ___ 95 | 96 | 97 | ### disabled 98 | 99 | **● disabled**: *`boolean`* 100 | 101 | Whether the user disabled deploy updates or not. 102 | 103 | ___ 104 | 105 | 106 | ### host 107 | 108 | **● host**: *`string`* 109 | 110 | The host API the plugin is configured to check for updates from. 111 | 112 | ___ 113 | 114 | 115 | ### maxVersions 116 | 117 | **● maxVersions**: *`number`* 118 | 119 | The maximum number of updates to be stored locally on the device. 120 | 121 | ___ 122 | 123 | 124 | ### minBackgroundDuration 125 | 126 | **● minBackgroundDuration**: *`number`* 127 | 128 | The number of seconds the app needs to be in the background before the plugin considers it closed for the purposes of fetching and applying a new update. 129 | 130 | ___ 131 | 132 | 133 | ### updateMethod 134 | 135 | **● updateMethod**: *"none" \| "auto" \| "background"* 136 | 137 | The currently configured updateMethod for the plugin. 138 | 139 | ___ 140 | 141 | -------------------------------------------------------------------------------- /docs/interfaces/ideployconfig.md: -------------------------------------------------------------------------------- 1 | [Cordova Plugin Ionic](../../README.md) 2 | 3 | # Interface: IDeployConfig 4 | 5 | The configuration for the deploy plugin on the device. 6 | 7 | ## Hierarchy 8 | 9 | **IDeployConfig** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [appId](ideployconfig.md#appid) 16 | * [channel](ideployconfig.md#channel) 17 | * [debug](ideployconfig.md#debug) 18 | * [host](ideployconfig.md#host) 19 | * [maxVersions](ideployconfig.md#maxversions) 20 | * [minBackgroundDuration](ideployconfig.md#minbackgroundduration) 21 | * [updateMethod](ideployconfig.md#updatemethod) 22 | 23 | --- 24 | 25 | ## Properties 26 | 27 | 28 | 29 | ### `` appId 30 | 31 | **● appId**: *`undefined` \| `string`* 32 | 33 | The [Ionic Pro](https://ionicframework.com/docs/pro/) app id. 34 | 35 | ___ 36 | 37 | 38 | ### `` channel 39 | 40 | **● channel**: *`undefined` \| `string`* 41 | 42 | The [channel](https://ionicframework.com/docs/pro/deploy/channels) that the plugin should listen for updates on. 43 | 44 | ___ 45 | 46 | 47 | ### `` debug 48 | 49 | **● debug**: *`undefined` \| `true` \| `false`* 50 | 51 | whether or not the app should in debug mode 52 | 53 | ___ 54 | 55 | 56 | ### `` host 57 | 58 | **● host**: *`undefined` \| `string`* 59 | 60 | *__ignore__*: 61 | 62 | ___ 63 | 64 | 65 | ### `` maxVersions 66 | 67 | **● maxVersions**: *`undefined` \| `number`* 68 | 69 | The number of previous updates to be cached on the device 70 | 71 | ___ 72 | 73 | 74 | ### `` minBackgroundDuration 75 | 76 | **● minBackgroundDuration**: *`undefined` \| `number`* 77 | 78 | The number of seconds the app should be in the background for before the plugin considers it closed and checks for an updated on resume of the app. 79 | 80 | ___ 81 | 82 | 83 | ### `` updateMethod 84 | 85 | **● updateMethod**: *"none" \| "auto" \| "background"* 86 | 87 | The update method the app should use when checking for available updates 88 | 89 | ___ 90 | 91 | -------------------------------------------------------------------------------- /docs/interfaces/ideploypluginapi.md: -------------------------------------------------------------------------------- 1 | [Cordova Plugin Ionic](../../README.md) 2 | 3 | # Interface: IDeployPluginAPI 4 | 5 | The Public API for the deploy Plugin 6 | 7 | ## Hierarchy 8 | 9 | **IDeployPluginAPI** 10 | 11 | ## Index 12 | 13 | ### Methods 14 | 15 | * [checkForUpdate](ideploypluginapi.md#checkforupdate) 16 | * [configure](ideploypluginapi.md#configure) 17 | * [deleteVersionById](ideploypluginapi.md#deleteversionbyid) 18 | * [downloadUpdate](ideploypluginapi.md#downloadupdate) 19 | * [extractUpdate](ideploypluginapi.md#extractupdate) 20 | * [getAvailableVersions](ideploypluginapi.md#getavailableversions) 21 | * [getConfiguration](ideploypluginapi.md#getconfiguration) 22 | * [getCurrentVersion](ideploypluginapi.md#getcurrentversion) 23 | * [reloadApp](ideploypluginapi.md#reloadapp) 24 | * [sync](ideploypluginapi.md#sync) 25 | 26 | --- 27 | 28 | ## Methods 29 | 30 | 31 | 32 | ### checkForUpdate 33 | 34 | ▸ **checkForUpdate**(): `Promise`<[CheckForUpdateResponse](checkforupdateresponse.md)> 35 | 36 | *__description__*: Check for available updates for the currently configured app id and channel. 37 | 38 | *__since__*: v5.0.0 39 | 40 | **Returns:** `Promise`<[CheckForUpdateResponse](checkforupdateresponse.md)> 41 | A response describing an update if one is available. 42 | 43 | ___ 44 | 45 | 46 | ### configure 47 | 48 | ▸ **configure**(config: *[IDeployConfig](ideployconfig.md)*): `Promise`<`void`> 49 | 50 | *__description__*: Update the default configuration for the plugin on the current device. The new configuration will be persisted across app close and binary updates. 51 | 52 | *__since__*: v5.0.0 53 | 54 | **Parameters:** 55 | 56 | | Name | Type | Description | 57 | | ------ | ------ | ------ | 58 | | config | [IDeployConfig](ideployconfig.md) | The new configuration for the plugin on this device. | 59 | 60 | **Returns:** `Promise`<`void`> 61 | 62 | ___ 63 | 64 | 65 | ### deleteVersionById 66 | 67 | ▸ **deleteVersionById**(versionId: *`string`*): `Promise`<`boolean`> 68 | 69 | *__description__*: Remove the files specific to a snapshot from the device. 70 | 71 | **Parameters:** 72 | 73 | | Name | Type | 74 | | ------ | ------ | 75 | | versionId | `string` | 76 | 77 | **Returns:** `Promise`<`boolean`> 78 | true if the update was deleted. 79 | 80 | ___ 81 | 82 | 83 | ### downloadUpdate 84 | 85 | ▸ **downloadUpdate**(progress?: *[CallbackFunction](callbackfunction.md)<`number`>*): `Promise`<`boolean`> 86 | 87 | *__description__*: Download the new files from an available update found by the checkForUpdate method and prepare the update. 88 | 89 | *__since__*: v5.0.0 90 | 91 | **Parameters:** 92 | 93 | | Name | Type | Description | 94 | | ------ | ------ | ------ | 95 | | `Optional` progress | [CallbackFunction](callbackfunction.md)<`number`> | A progress callback function which will be called with a number representing the percent of completion of the download and prepare. | 96 | 97 | **Returns:** `Promise`<`boolean`> 98 | true if the download succeeded 99 | 100 | ___ 101 | 102 | 103 | ### extractUpdate 104 | 105 | ▸ **extractUpdate**(progress?: *[CallbackFunction](callbackfunction.md)<`number`>*): `Promise`<`boolean`> 106 | 107 | *__description__*: Extract a downloaded bundle of updated files. 108 | 109 | *__since__*: v5.0.0 110 | 111 | **Parameters:** 112 | 113 | | Name | Type | Description | 114 | | ------ | ------ | ------ | 115 | | `Optional` progress | [CallbackFunction](callbackfunction.md)<`number`> | A progress callback function which will be called with a number representing the percent of completion of the extract. | 116 | 117 | **Returns:** `Promise`<`boolean`> 118 | true if the extract succeeded 119 | 120 | ___ 121 | 122 | 123 | ### getAvailableVersions 124 | 125 | ▸ **getAvailableVersions**(): `Promise`<[ISnapshotInfo](isnapshotinfo.md)[]> 126 | 127 | *__description__*: Get a list of the snapshots available on the device. 128 | 129 | *__since__*: v5.0.0 130 | 131 | **Returns:** `Promise`<[ISnapshotInfo](isnapshotinfo.md)[]> 132 | a list of available updates. 133 | 134 | ___ 135 | 136 | 137 | ### getConfiguration 138 | 139 | ▸ **getConfiguration**(): `Promise`<[ICurrentConfig](icurrentconfig.md)> 140 | 141 | *__description__*: Get the current configuration for the plugin on the current device. 142 | 143 | *__since__*: v5.0.0 144 | 145 | **Returns:** `Promise`<[ICurrentConfig](icurrentconfig.md)> 146 | The current configuration of the plugin. 147 | 148 | ___ 149 | 150 | 151 | ### getCurrentVersion 152 | 153 | ▸ **getCurrentVersion**(): `Promise`<[ISnapshotInfo](isnapshotinfo.md) \| `undefined`> 154 | 155 | *__description__*: Get info about the currently deployed update or undefined if none are applied. 156 | 157 | *__since__*: v5.0.0 158 | 159 | **Returns:** `Promise`<[ISnapshotInfo](isnapshotinfo.md) \| `undefined`> 160 | The info about the currently applied update or undefined if none is applied. 161 | 162 | ___ 163 | 164 | 165 | ### reloadApp 166 | 167 | ▸ **reloadApp**(): `Promise`<`boolean`> 168 | 169 | *__description__*: Reload the app if a more recent version of the app is available. 170 | 171 | *__since__*: v5.0.0 172 | 173 | **Returns:** `Promise`<`boolean`> 174 | true if the reload succeeded 175 | 176 | ___ 177 | 178 | 179 | ### sync 180 | 181 | ▸ **sync**(syncOptions: *[ISyncOptions](isyncoptions.md)*): `Promise`<[ISnapshotInfo](isnapshotinfo.md) \| `undefined`> 182 | 183 | *__description__*: Check for an update, download it, and apply it in one step. 184 | 185 | *__since__*: v5.0.0 186 | 187 | **Parameters:** 188 | 189 | | Name | Type | Description | 190 | | ------ | ------ | ------ | 191 | | syncOptions | [ISyncOptions](isyncoptions.md) | (Optional) Application update overrides. | 192 | 193 | **Returns:** `Promise`<[ISnapshotInfo](isnapshotinfo.md) \| `undefined`> 194 | The info about the currently applied update or undefined if none is applied. 195 | 196 | ___ 197 | 198 | -------------------------------------------------------------------------------- /docs/interfaces/ipluginbaseapi.md: -------------------------------------------------------------------------------- 1 | [Cordova Plugin Ionic](../../README.md) 2 | 3 | # Interface: IPluginBaseAPI 4 | 5 | The IonicCordova Plugin API 6 | 7 | ## Hierarchy 8 | 9 | **IPluginBaseAPI** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [deploy](ipluginbaseapi.md#deploy) 16 | 17 | ### Methods 18 | 19 | * [getAppDetails](ipluginbaseapi.md#getappdetails) 20 | * [getAppInfo](ipluginbaseapi.md#getappinfo) 21 | 22 | --- 23 | 24 | ## Properties 25 | 26 | 27 | 28 | ### deploy 29 | 30 | **● deploy**: *[IDeployPluginAPI](ideploypluginapi.md)* 31 | 32 | An instance of the Ionic Deploy Plugin API 33 | 34 | ___ 35 | 36 | ## Methods 37 | 38 | 39 | 40 | ### getAppDetails 41 | 42 | ▸ **getAppDetails**(): `Promise`<[IAppInfo](iappinfo.md)> 43 | 44 | *__description__*: Get info about the current app. 45 | 46 | **Returns:** `Promise`<[IAppInfo](iappinfo.md)> 47 | 48 | ___ 49 | 50 | 51 | ### getAppInfo 52 | 53 | ▸ **getAppInfo**(success: *`Function`*, failure: *`Function`*): `void` 54 | 55 | **Parameters:** 56 | 57 | | Name | Type | Description | 58 | | ------ | ------ | ------ | 59 | | success | `Function` | \- | 60 | | failure | `Function` | | 61 | 62 | **Returns:** `void` 63 | 64 | ___ 65 | 66 | -------------------------------------------------------------------------------- /docs/interfaces/isnapshotinfo.md: -------------------------------------------------------------------------------- 1 | [Cordova Plugin Ionic](../../README.md) 2 | 3 | # Interface: ISnapshotInfo 4 | 5 | Information about a snapshot 6 | 7 | ## Hierarchy 8 | 9 | **ISnapshotInfo** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [binaryVersion](isnapshotinfo.md#binaryversion) 16 | * [binaryVersionCode](isnapshotinfo.md#binaryversioncode) 17 | * [binaryVersionName](isnapshotinfo.md#binaryversionname) 18 | * [binary_version](isnapshotinfo.md#binary_version) 19 | * [buildId](isnapshotinfo.md#buildid) 20 | * [channel](isnapshotinfo.md#channel) 21 | * [deploy_uuid](isnapshotinfo.md#deploy_uuid) 22 | * [versionId](isnapshotinfo.md#versionid) 23 | 24 | --- 25 | 26 | ## Properties 27 | 28 | 29 | 30 | ### binaryVersion 31 | 32 | **● binaryVersion**: *`string`* 33 | 34 | *__deprecated__*: The binary version the snapshot was downloaded for. The versionName on Android or CFBundleShortVersionString on iOS this is the end user readable version listed on the stores. 35 | 36 | ___ 37 | 38 | 39 | ### binaryVersionCode 40 | 41 | **● binaryVersionCode**: *`string`* 42 | 43 | The binary version build code the snapshot was downloaded for. The versionCode on Android or CFBundleVersion on iOS this should be changed every time you do a new build debug or otherwise. 44 | 45 | ___ 46 | 47 | 48 | ### binaryVersionName 49 | 50 | **● binaryVersionName**: *`string`* 51 | 52 | The binary version name the snapshot was downloaded for. The versionName on Android or CFBundleShortVersionString on iOS this is the end user readable version listed on the stores. 53 | 54 | ___ 55 | 56 | 57 | ### binary_version 58 | 59 | **● binary_version**: *`string`* 60 | 61 | *__deprecated__*: in favor of [binaryVersion](#binaryversion) 62 | 63 | The binary version the snapshot was downloaded for. 64 | 65 | ___ 66 | 67 | 68 | ### buildId 69 | 70 | **● buildId**: *`string`* 71 | 72 | The id for the snapshot. 73 | 74 | ___ 75 | 76 | 77 | ### channel 78 | 79 | **● channel**: *`string`* 80 | 81 | The channel that the snapshot was downloaded for.. 82 | 83 | ___ 84 | 85 | 86 | ### deploy_uuid 87 | 88 | **● deploy_uuid**: *`string`* 89 | 90 | *__deprecated__*: in favor of [versionId](#versionid) 91 | 92 | The id for the snapshot. 93 | 94 | ___ 95 | 96 | 97 | ### versionId 98 | 99 | **● versionId**: *`string`* 100 | 101 | The id for the snapshot. 102 | 103 | ___ 104 | 105 | -------------------------------------------------------------------------------- /docs/interfaces/isyncoptions.md: -------------------------------------------------------------------------------- 1 | [Cordova Plugin Ionic](../../README.md) 2 | 3 | # Interface: ISyncOptions 4 | 5 | Configuration options for the call to `sync` 6 | 7 | ## Hierarchy 8 | 9 | **ISyncOptions** 10 | 11 | ## Index 12 | 13 | ### Properties 14 | 15 | * [updateMethod](isyncoptions.md#updatemethod) 16 | 17 | --- 18 | 19 | ## Properties 20 | 21 | 22 | 23 | ### `` updateMethod 24 | 25 | **● updateMethod**: *"background" \| "auto"* 26 | 27 | Whether the update should be applied immediately or on the next app start. 28 | 29 | ___ 30 | 31 | -------------------------------------------------------------------------------- /docs/interfaces/window.md: -------------------------------------------------------------------------------- 1 | [Cordova Plugin Ionic](../../README.md) 2 | 3 | # Interface: Window 4 | 5 | ## Hierarchy 6 | 7 | **Window** 8 | 9 | ## Index 10 | 11 | ### Properties 12 | 13 | * [IonicCordova](window.md#ioniccordova) 14 | 15 | --- 16 | 17 | ## Properties 18 | 19 | 20 | 21 | ### IonicCordova 22 | 23 | **● IonicCordova**: *[IPluginBaseAPI](ipluginbaseapi.md)* 24 | 25 | ___ 26 | 27 | -------------------------------------------------------------------------------- /hooks/beforePrepare.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').exec; 2 | 3 | module.exports = function(ctx) { 4 | return new Promise((resolve, reject) => { 5 | exec( 6 | 'ionic --version --no-interactive', 7 | { cwd: ctx.opts.projectRoot }, 8 | ionicVersionOutput.bind({}, ctx.opts.projectRoot, resolve, reject) 9 | ) 10 | }); 11 | }; 12 | 13 | function ionicVersionOutput(rootDir, resolve, reject, err, version, stderr) { 14 | if(err) { 15 | console.error('There was an error checking your version of the Ionic CLI are you sure you have it installed?'); 16 | console.log(err); 17 | console.log(stderr); 18 | reject(); 19 | } 20 | const versionInfo = version.split('.'); 21 | let majorVersion = undefined; 22 | while (versionInfo.length && isNaN(majorVersion)) { 23 | majorVersion = versionInfo.shift(); 24 | } 25 | if (isNaN(majorVersion)) { 26 | console.error('There was an error checking your version of the Ionic CLI are you sure you have it installed?'); 27 | reject(); 28 | } 29 | else if (majorVersion < 4) { 30 | console.error(`You are running version ${majorVersion} of the Ionic CLI. Version 4 or greater is required for this plugin.`); 31 | reject(); 32 | } 33 | 34 | console.log('Generating initial manifest for Ionic Deploy...'); 35 | exec( 36 | 'ionic deploy manifest --no-interactive', 37 | { cwd: rootDir }, 38 | ionicManifestOutput.bind({}, resolve, reject) 39 | ) 40 | } 41 | 42 | function ionicManifestOutput(resolve, reject, err, version, stderr) { 43 | if(err) { 44 | console.error('There was an error generating the initial manifest of files for the deploy plugin.'); 45 | reject() 46 | } 47 | resolve(); 48 | console.log('Ionic Deploy initial manifest successfully generated.'); 49 | } 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-ionic", 3 | "version": "5.5.3", 4 | "cordova": { 5 | "id": "cordova-plugin-ionic", 6 | "platforms": [ 7 | "android", 8 | "ios" 9 | ] 10 | }, 11 | "main": "dist/index.js", 12 | "types": "dist/index.d.ts", 13 | "description": "Ionic Cordova SDK", 14 | "scripts": { 15 | "apply-dev": "npm run build && ./scripts/apply-changes.sh", 16 | "create-dev": "./scripts/create-local-app.sh", 17 | "clean": "rimraf dist", 18 | "lint": "tslint --config tslint.json --project tsconfig.json", 19 | "precommit": "npm run lint", 20 | "watch": "tsc -w", 21 | "watch-dev": "watch 'npm run apply-dev' ./www", 22 | "build": "npm run clean && tsc && ngc -p tsconfig.ng.json && ./scripts/ng-prepare.sh", 23 | "sync-plugin-xml": "sync-cordova-xml2 package.json plugin.xml --output=plugin.xml", 24 | "version": "npm run sync-plugin-xml && git add plugin.xml && ./scripts/update-plugin-version-code.sh", 25 | "prepublishOnly": "npm run build", 26 | "test": "echo 'We should really get unit tests running'", 27 | "release": "npm run build && np --any-branch", 28 | "docs": "./scripts/docs.sh" 29 | }, 30 | "repository": "https://github.com/ionic-team/cordova-plugin-ionic.git", 31 | "issue": "https://github.com/ionic-team/cordova-plugin-ionic/issues", 32 | "bugs": { 33 | "url": "https://github.com/ionic-team/cordova-plugin-ionic/issues" 34 | }, 35 | "keywords": [ 36 | "ionic", 37 | "cordova", 38 | "deploy", 39 | "liveupdates", 40 | "mobile", 41 | "hybrid", 42 | "ecosystem:cordova", 43 | "cordova-android", 44 | "cordova-ios" 45 | ], 46 | "author": "Ionic", 47 | "contributors": [ 48 | { 49 | "name": "Max Lynch", 50 | "email": "max@ionic.io" 51 | }, 52 | { 53 | "name": "William Pelrine", 54 | "email": "rudy@ionic.io" 55 | } 56 | ], 57 | "license": "MIT", 58 | "devDependencies": { 59 | "@angular/compiler": "^9.1.1", 60 | "@angular/compiler-cli": "^9.1.1", 61 | "@angular/core": "^7.2.15", 62 | "@types/cordova": "0.0.34", 63 | "jest": "^22.4.3", 64 | "np": "^3.0.4", 65 | "rimraf": "^2.6.2", 66 | "sync-cordova-xml2": "0.0.2", 67 | "ts-jest": "^22.4.2", 68 | "tslint": "^5.9.1", 69 | "tslint-ionic-rules": "0.0.14", 70 | "typedoc": "^0.11.1", 71 | "typedoc-plugin-markdown": "^1.1.6", 72 | "watch": "^1.0.2" 73 | }, 74 | "jest": { 75 | "globals": { 76 | "ts-jest": { 77 | "tsConfigFile": "tsconfig.json" 78 | } 79 | }, 80 | "moduleFileExtensions": [ 81 | "ts", 82 | "js" 83 | ], 84 | "transform": { 85 | ".(ts)": "./node_modules/ts-jest/preprocessor.js" 86 | }, 87 | "testRegex": "tests/.*\\.(ts|js)$" 88 | }, 89 | "dependencies": { 90 | "typescript": "3.8.3" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | cordova-plugin-ionic 4 | Ionic Cordova SDK 5 | MIT 6 | ionic,cordova,deploy,liveupdates,mobile,hybrid,ecosystem:cordova,cordova-android,cordova-ios 7 | https://github.com/ionic-team/cordova-plugin-ionic.git 8 | https://github.com/ionic-team/cordova-plugin-ionic/issues 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | $APP_ID 33 | 34 | 35 | $CHANNEL_NAME 36 | 37 | 38 | $UPDATE_API 39 | 40 | 41 | $UPDATE_METHOD 42 | 43 | 44 | $MAX_STORE 45 | 46 | 47 | $MIN_BACKGROUND_DURATION 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | $APP_ID 65 | $CHANNEL_NAME 66 | $UPDATE_API 67 | $UPDATE_METHOD 68 | $MAX_STORE 69 | $MIN_BACKGROUND_DURATION 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | Ionic 81 | 82 | -------------------------------------------------------------------------------- /scripts/apply-changes.sh: -------------------------------------------------------------------------------- 1 | # local manual "watch" script 2 | set -o errexit 3 | 4 | # Script vars 5 | APP_PATH=${IONIC_APP_NAME:-../testapp} 6 | 7 | # Sync to iOS 8 | cp ./dist/* ${APP_PATH}/platforms/ios/www/plugins/cordova-plugin-ionic/dist/ 9 | sed -i '' 's/"use strict"/cordova.define("cordova-plugin-ionic.common", function(require, exports, module) {"use strict"/' ${APP_PATH}/platforms/ios/www/plugins/cordova-plugin-ionic/dist/common.js 10 | sed -i '' 's/"use strict"/cordova.define("cordova-plugin-ionic.guards", function(require, exports, module) {"use strict"/' ${APP_PATH}/platforms/ios/www/plugins/cordova-plugin-ionic/dist/guards.js 11 | echo '});' >> ${APP_PATH}/platforms/ios/www/plugins/cordova-plugin-ionic/dist/common.js 12 | echo '});' >> ${APP_PATH}/platforms/ios/www/plugins/cordova-plugin-ionic/dist/guards.js 13 | 14 | # Sync to Android 15 | cp ./dist/* ${APP_PATH}/platforms/android/platform_www/plugins/cordova-plugin-ionic/dist/ 16 | sed -i '' 's/"use strict"/cordova.define("cordova-plugin-ionic.common", function(require, exports, module) {"use strict"/' ${APP_PATH}/platforms/android/platform_www/plugins/cordova-plugin-ionic/dist/common.js 17 | sed -i '' 's/"use strict"/cordova.define("cordova-plugin-ionic.guards", function(require, exports, module) {"use strict"/' ${APP_PATH}/platforms/android/platform_www/plugins/cordova-plugin-ionic/dist/guards.js 18 | echo '});' >> ${APP_PATH}/platforms/android/platform_www/plugins/cordova-plugin-ionic/dist/common.js 19 | echo '});' >> ${APP_PATH}/platforms/android/platform_www/plugins/cordova-plugin-ionic/dist/guards.js 20 | -------------------------------------------------------------------------------- /scripts/create-local-app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit 3 | set -o nounset 4 | 5 | # Stop CLI prompts and init config vars 6 | APP_ID=${IONIC_APP_ID:-2de70dab} 7 | APP_NAME=${IONIC_APP_NAME:-testapp} 8 | CHANNEL=${IONIC_CHANNEL:-Master} 9 | CI=1 10 | UPDATE_METHOD=${IONIC_UPDATE_METHOD:-auto} 11 | BACKGROUND_DURATION=${IONIC_BACKGROUND_DURATION:-1} 12 | 13 | # Build the plugin ts 14 | npm run build 15 | 16 | # Create a blank ionic app cd 17 | cd .. 18 | ionic start ${APP_NAME} blank --type=ionic-angular --cordova 19 | cd ${APP_NAME} 20 | npm run build 21 | 22 | # Add cordova platform and install the plugin 23 | cordova platform add ios@latest 24 | cordova platform add android@latest 25 | cordova plugin add ../cordova-plugin-ionic --save \ 26 | --variable MIN_BACKGROUND_DURATION="$BACKGROUND_DURATION" \ 27 | --variable APP_ID="${APP_ID}" \ 28 | --variable CHANNEL_NAME="${CHANNEL}" \ 29 | --variable UPDATE_METHOD="${UPDATE_METHOD}" --link 30 | cordova prepare ios 31 | cordova prepare android 32 | -------------------------------------------------------------------------------- /scripts/docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit 3 | rm -rf docs 4 | npx typedoc --theme markdown --mdEngine github --name "Cordova Plugin Ionic" --readme none --hideGenerator --out docs/ --includeDeclarations --excludeExternals --mdHideSources --mode file types/IonicCordova.d.ts 5 | rm -rf docs/README.md 6 | rm -rf docs/modules 7 | ls -d -1 docs/interfaces/* | xargs sed -i '' 's/README\.md.*\.md/..\/README.md/g' 8 | -------------------------------------------------------------------------------- /scripts/ng-prepare.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | cat<dist/ngx/package.json 4 | { 5 | "name": "cordova-plugin-ionic", 6 | "main": "ngx/index.js", 7 | "module": "ngx/index.js", 8 | "typings": "ngx/index.d.ts" 9 | } 10 | EOF 11 | -------------------------------------------------------------------------------- /scripts/update-plugin-version-code.sh: -------------------------------------------------------------------------------- 1 | # Releases a new version to npm 2 | set -f errexit 3 | 4 | # Script vars 5 | VERSION=$(npm list cordova-plugin-ionic | grep "@" | cut -d "@" -f 2 | cut -d " " -f 1); 6 | echo "updating PLUGIN_VERSION in www/common.ts to $VERSION"; 7 | # Update common.ts 8 | sed -E -i "" "s/^ public PLUGIN_VERSION = '[0-9a-zA-Z\.-]+';/ public PLUGIN_VERSION = '$VERSION';/g" www/common.ts 9 | git add www/common.ts 10 | 11 | echo "Remember to update the changelog..." 12 | -------------------------------------------------------------------------------- /src/android/IonicCordovaCommon.java: -------------------------------------------------------------------------------- 1 | package com.ionicframework.common; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.content.res.AssetManager; 6 | import android.net.Uri; 7 | 8 | import org.apache.commons.io.FileUtils; 9 | import org.apache.cordova.CordovaWebView; 10 | import org.apache.cordova.CallbackContext; 11 | import org.apache.cordova.CordovaPlugin; 12 | import org.apache.cordova.CordovaInterface; 13 | import org.apache.cordova.PluginResult; 14 | import org.json.JSONArray; 15 | import org.json.JSONObject; 16 | import org.json.JSONException; 17 | 18 | import android.os.Environment; 19 | import android.util.Log; 20 | import android.app.Activity; 21 | import android.content.pm.PackageInfo; 22 | import android.os.Build; 23 | 24 | import java.io.DataInputStream; 25 | import java.io.File; 26 | import java.io.FileOutputStream; 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.io.OutputStream; 30 | import java.net.URL; 31 | import java.util.Iterator; 32 | import java.util.UUID; 33 | 34 | public class IonicCordovaCommon extends CordovaPlugin { 35 | public static final String TAG = "IonicCordovaCommon"; 36 | private static final String PREFS_KEY = "ionicDeploySavedPreferences"; 37 | private static final String CUSTOM_PREFS_KEY = "ionicDeployCustomPreferences"; 38 | private AssetManager assetManager; 39 | 40 | 41 | private SharedPreferences prefs; 42 | private String uuid; 43 | 44 | private interface FileOp { 45 | void run(final JSONArray args, final CallbackContext callbackContext) throws Exception; 46 | } 47 | 48 | /** 49 | * Sets the context of the Command. This can then be used to do things like 50 | * get file paths associated with the Activity. 51 | * 52 | * @param cordova The context of the main Activity. 53 | * @param webView The CordovaWebView Cordova is running in. 54 | */ 55 | public void initialize(CordovaInterface cordova, CordovaWebView webView) { 56 | super.initialize(cordova, webView); 57 | 58 | // Initialize shared preferences 59 | Context cxt = this.cordova.getActivity().getApplicationContext(); 60 | this.prefs = cxt.getSharedPreferences("com.ionic.common.preferences", Context.MODE_PRIVATE); 61 | assetManager = cordova.getContext().getAssets(); 62 | 63 | // Get or generate a plugin UUID 64 | this.uuid = this.prefs.getString("uuid", UUID.randomUUID().toString()); 65 | prefs.edit().putString("uuid", this.uuid).apply(); 66 | } 67 | 68 | private void threadhelper(final FileOp f, final JSONArray args, final CallbackContext callbackContext){ 69 | cordova.getThreadPool().execute(new Runnable() { 70 | public void run() { 71 | try { 72 | f.run(args, callbackContext); 73 | } catch ( Exception e) { 74 | callbackContext.error(e.getMessage()); 75 | } 76 | } 77 | }); 78 | } 79 | 80 | /** 81 | * Executes the request and returns PluginResult. 82 | * 83 | * @param action The action to execute. 84 | * @param args JSONArray of arguments for the plugin. 85 | * @param callbackContext The callback id used when calling back into JavaScript. 86 | * @return True if the action was valid, false if not. 87 | */ 88 | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { 89 | if (action.equals("getAppInfo")) { 90 | this.getAppInfo(callbackContext); 91 | } else if (action.equals("getPreferences")) { 92 | this.getPreferences(callbackContext); 93 | } else if (action.equals("setPreferences")) { 94 | this.setPreferences(callbackContext, args.getJSONObject(0)); 95 | } else if (action.equals("configure")){ 96 | this.configure(callbackContext, args.getJSONObject(0)); 97 | } else if (action.equals("copyTo")){ 98 | this.copyTo(callbackContext, args.getJSONObject(0)); 99 | } else if (action.equals("remove")){ 100 | this.remove(callbackContext, args.getJSONObject(0)); 101 | } else if (action.equals("downloadFile")){ 102 | threadhelper( new FileOp( ){ 103 | public void run(final JSONArray passedArgs, final CallbackContext cbcontext) throws JSONException { 104 | downloadFile(cbcontext, passedArgs.getJSONObject(0)); 105 | } 106 | }, args, callbackContext); 107 | 108 | } else { 109 | return false; 110 | } 111 | 112 | return true; 113 | } 114 | 115 | private File getDirectory(String directory) { 116 | Context c = cordova.getContext(); 117 | switch(directory) { 118 | case "DOCUMENTS": 119 | return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS); 120 | case "DATA": 121 | return c.getFilesDir(); 122 | case "CACHE": 123 | return c.getCacheDir(); 124 | case "EXTERNAL": 125 | return c.getExternalFilesDir(null); 126 | case "EXTERNAL_STORAGE": 127 | return Environment.getExternalStorageDirectory(); 128 | } 129 | return null; 130 | } 131 | 132 | private void copyAssets(String assetPath, String targetDir) throws IOException { 133 | String[] files = null; 134 | try { 135 | files = assetManager.list(assetPath); 136 | } catch (IOException e) { 137 | Log.e("tag", "Failed to get asset file list.", e); 138 | } 139 | if (files != null) for (String filename : files) { 140 | InputStream in = null; 141 | OutputStream out = null; 142 | try { 143 | if (assetManager.list(assetPath + "/" + filename).length > 0) { 144 | File newDir = new File(targetDir, filename); 145 | newDir.mkdir(); 146 | copyAssets(assetPath + "/" + filename, newDir.getPath()); 147 | continue; 148 | } 149 | in = assetManager.open(assetPath + "/" + filename); 150 | File destDir = new File(targetDir); 151 | if (!destDir.exists()) { 152 | destDir.mkdirs(); 153 | } 154 | File outFile = new File(targetDir, filename); 155 | out = new FileOutputStream(outFile); 156 | copyFile(in, out); 157 | } catch(IOException e) { 158 | Log.e("tag", "Failed to copy asset file: " + filename, e); 159 | } 160 | finally { 161 | if (in != null) { 162 | try { 163 | in.close(); 164 | } catch (IOException e) { 165 | // NOOP 166 | } 167 | } 168 | if (out != null) { 169 | try { 170 | out.close(); 171 | } catch (IOException e) { 172 | // NOOP 173 | } 174 | } 175 | } 176 | } 177 | } 178 | 179 | private void copyFile(InputStream in, OutputStream out) throws IOException { 180 | byte[] buffer = new byte[1024]; 181 | int read; 182 | while((read = in.read(buffer)) != -1){ 183 | out.write(buffer, 0, read); 184 | } 185 | } 186 | 187 | /** 188 | * copy a directory or file to another location 189 | * 190 | */ 191 | public void copyTo(CallbackContext callbackContext, JSONObject options) throws JSONException { 192 | Log.d(TAG, "copyTo called with " + options.toString()); 193 | PluginResult result; 194 | 195 | try { 196 | JSONObject source = options.getJSONObject("source"); 197 | String target = options.getString("target"); 198 | 199 | if (source.getString("directory").equals("APPLICATION")) { 200 | this.copyAssets(source.getString("path"), target); 201 | } else { 202 | File srcDir = this.getDirectory(source.getString("directory")); 203 | File srcFile = new File(srcDir.getPath() + "/" + source.getString("path")); 204 | 205 | if (!srcFile.exists()) { 206 | result = new PluginResult(PluginResult.Status.ERROR, "source file or directory does not exist"); 207 | result.setKeepCallback(false); 208 | callbackContext.sendPluginResult(result); 209 | return; 210 | } 211 | 212 | if (srcFile.isDirectory()) { 213 | FileUtils.copyDirectory(srcFile, new File(target)); 214 | } else { 215 | FileUtils.copyFile(srcFile, new File(target)); 216 | } 217 | } 218 | } catch (Exception e) { 219 | result = new PluginResult(PluginResult.Status.ERROR, e.getMessage()); 220 | result.setKeepCallback(false); 221 | callbackContext.sendPluginResult(result); 222 | return; 223 | } 224 | 225 | result = new PluginResult(PluginResult.Status.OK); 226 | result.setKeepCallback(false); 227 | callbackContext.sendPluginResult(result); 228 | } 229 | 230 | /** 231 | * recursively remove a directory or a file 232 | * 233 | */ 234 | public void remove(CallbackContext callbackContext, JSONObject options) throws JSONException { 235 | Log.d(TAG, "recursiveRemove called with " + options.toString()); 236 | String target = options.getString("target"); 237 | File dest = new File(target); 238 | final PluginResult result; 239 | 240 | if (!dest.exists()) { 241 | result = new PluginResult(PluginResult.Status.ERROR, "file or directory does not exist"); 242 | result.setKeepCallback(false); 243 | callbackContext.sendPluginResult(result); 244 | return; 245 | } 246 | 247 | try { 248 | FileUtils.forceDelete(dest); 249 | } catch (IOException e) { 250 | result = new PluginResult(PluginResult.Status.ERROR, e.getMessage()); 251 | result.setKeepCallback(false); 252 | callbackContext.sendPluginResult(result); 253 | return; 254 | } 255 | 256 | result = new PluginResult(PluginResult.Status.OK); 257 | result.setKeepCallback(false); 258 | callbackContext.sendPluginResult(result); 259 | } 260 | 261 | public void downloadFile(CallbackContext callbackContext, JSONObject options) throws JSONException { 262 | Log.d(TAG, "downloadFile called with " + options.toString()); 263 | String url = options.getString("url"); 264 | String dest = options.getString("target"); 265 | final PluginResult result; 266 | 267 | try { 268 | URL u = new URL(url); 269 | InputStream is = u.openStream(); 270 | 271 | DataInputStream dis = new DataInputStream(is); 272 | 273 | byte[] buffer = new byte[1024]; 274 | int length; 275 | 276 | File downFile = new File(dest); 277 | downFile.getParentFile().mkdirs(); 278 | downFile.createNewFile(); 279 | FileOutputStream fos = new FileOutputStream(downFile); 280 | while ((length = dis.read(buffer))>0) { 281 | fos.write(buffer, 0, length); 282 | } 283 | 284 | } catch (Exception e) { 285 | Log.e(TAG, "downloadFile error", e); 286 | result = new PluginResult(PluginResult.Status.ERROR, e.getMessage()); 287 | result.setKeepCallback(false); 288 | callbackContext.sendPluginResult(result); 289 | return; 290 | } 291 | result = new PluginResult(PluginResult.Status.OK); 292 | result.setKeepCallback(false); 293 | callbackContext.sendPluginResult(result); 294 | } 295 | 296 | /** 297 | * Get basic app information. Used for the Ionic monitoring service. 298 | * 299 | * @param callbackContext The callback id used when calling back into JavaScript. 300 | */ 301 | public void getAppInfo(CallbackContext callbackContext) throws JSONException { 302 | JSONObject j = new JSONObject(); 303 | 304 | try { 305 | PackageInfo pInfo = this.cordova.getActivity().getPackageManager().getPackageInfo(this.cordova.getActivity().getPackageName(), 0); 306 | String versionName = pInfo.versionName; 307 | String name = pInfo.packageName; 308 | int versionCode = pInfo.versionCode; 309 | String platformVersion = String.valueOf(Build.VERSION.RELEASE); 310 | 311 | j.put("platform", "android"); 312 | j.put("platformVersion", platformVersion); 313 | j.put("version", versionCode); 314 | j.put("binaryVersionCode", versionCode); 315 | j.put("bundleName", name); 316 | j.put("bundleVersion", versionName); 317 | j.put("binaryVersionName", versionName); 318 | j.put("device", this.uuid); 319 | j.put("dataDirectory", toDirUrl(cordova.getActivity().getFilesDir())); 320 | 321 | Log.d(TAG, "Got package info. Version: " + versionName + ", bundleName: " + name + ", versionCode: " + versionCode); 322 | final PluginResult result = new PluginResult(PluginResult.Status.OK, j); 323 | result.setKeepCallback(false); 324 | callbackContext.sendPluginResult(result); 325 | } catch(Exception ex) { 326 | Log.e(TAG, "Unable to get package info", ex); 327 | callbackContext.error(ex.toString()); 328 | } 329 | } 330 | 331 | /** 332 | * Grabs a string from the activity's resources. 333 | * 334 | * @param aString The name of the resource to retrieve 335 | * @return The string contents of the resource 336 | */ 337 | private String getStringResourceByName(String aString) { 338 | Activity activity = cordova.getActivity(); 339 | String packageName = activity.getPackageName(); 340 | int resId = activity.getResources().getIdentifier(aString, "string", packageName); 341 | return activity.getString(resId); 342 | } 343 | 344 | /** 345 | * Get saved prefs configured via code at runtime 346 | * 347 | */ 348 | public JSONObject getCustomConfig() throws JSONException { 349 | SharedPreferences prefs = this.cordova.getActivity().getApplicationContext().getSharedPreferences("com.ionic.deploy.preferences", Context.MODE_PRIVATE); 350 | String prefsString = prefs.getString(this.CUSTOM_PREFS_KEY, null); 351 | if (prefsString != null) { 352 | JSONObject customPrefs = new JSONObject(prefsString); 353 | return customPrefs; 354 | } 355 | return new JSONObject("{}"); 356 | } 357 | 358 | /** 359 | * Set saved prefs configured via code at runtime 360 | * 361 | */ 362 | public void configure(CallbackContext callbackContext, JSONObject newConfig) throws JSONException { 363 | Log.i(TAG, "Set custom config called with " + newConfig.toString()); 364 | SharedPreferences prefs = this.cordova.getActivity().getApplicationContext().getSharedPreferences("com.ionic.deploy.preferences", Context.MODE_PRIVATE); 365 | SharedPreferences.Editor editor = prefs.edit(); 366 | JSONObject storedConfig = this.getCustomConfig(); 367 | this.mergeObjects(storedConfig, newConfig); 368 | editor.putString(this.CUSTOM_PREFS_KEY, storedConfig.toString()); 369 | editor.commit(); 370 | Log.i(TAG, "config updated"); 371 | 372 | final PluginResult result = new PluginResult(PluginResult.Status.OK, storedConfig); 373 | result.setKeepCallback(false); 374 | callbackContext.sendPluginResult(result); 375 | } 376 | 377 | /** 378 | * Get cordova plugin preferences and state information. 379 | * 380 | * @param callbackContext The callback id used when calling back into JavaScript. 381 | */ 382 | public void getPreferences(CallbackContext callbackContext) throws JSONException { 383 | 384 | JSONObject nativePrefs = this.getNativeConfig(); 385 | JSONObject customPrefs = this.getCustomConfig(); 386 | 387 | // Check for prefs that have been saved before 388 | SharedPreferences prefs = this.cordova.getActivity().getApplicationContext().getSharedPreferences("com.ionic.deploy.preferences", Context.MODE_PRIVATE); 389 | String prefsString = prefs.getString(this.PREFS_KEY, null); 390 | if (prefsString != null) { 391 | JSONObject savedPrefs; 392 | Log.i(TAG, "Found saved prefs: " + prefsString); 393 | // grab the save prefs 394 | savedPrefs = new JSONObject(prefsString); 395 | 396 | // update with the lastest things from config.xml 397 | this.mergeObjects(savedPrefs, nativePrefs); 398 | 399 | // update with the lastest things from custom configuration 400 | this.mergeObjects(savedPrefs, customPrefs); 401 | 402 | final PluginResult result = new PluginResult(PluginResult.Status.OK, savedPrefs); 403 | result.setKeepCallback(false); 404 | callbackContext.sendPluginResult(result); 405 | return; 406 | } 407 | 408 | // no saved prefs were found 409 | try { 410 | nativePrefs.put("updates", new JSONObject("{}")); 411 | final PluginResult result = new PluginResult(PluginResult.Status.OK, nativePrefs); 412 | result.setKeepCallback(false); 413 | callbackContext.sendPluginResult(result); 414 | } catch(Exception ex) { 415 | Log.e(TAG, "Unable to get preferences", ex); 416 | callbackContext.error(ex.toString()); 417 | } 418 | } 419 | 420 | private JSONObject getNativeConfig() throws JSONException { 421 | JSONObject j = new JSONObject(); 422 | int maxV; 423 | int minBackgroundDuration; 424 | try { 425 | maxV = Integer.parseInt(getStringResourceByName("ionic_max_versions")); 426 | } catch(NumberFormatException e) { 427 | maxV = 2; 428 | } 429 | 430 | try { 431 | minBackgroundDuration = Integer.parseInt(getStringResourceByName("ionic_min_background_duration")); 432 | } catch(NumberFormatException e) { 433 | minBackgroundDuration = 30; 434 | } 435 | String versionName; 436 | int versionCode; 437 | try { 438 | PackageInfo pInfo = this.cordova.getActivity().getPackageManager().getPackageInfo(this.cordova.getActivity().getPackageName(), 0); 439 | versionName = pInfo.versionName; 440 | versionCode = pInfo.versionCode; 441 | } catch(Exception ex) { 442 | Log.e(TAG, "Unable to get package info", ex); 443 | versionName = "unknown"; 444 | versionCode = 0; 445 | } 446 | 447 | String appId = getStringResourceByName("ionic_app_id"); 448 | j.put("appId", appId); 449 | j.put("disabled", preferences.getBoolean("DisableDeploy", false)); 450 | j.put("channel", getStringResourceByName("ionic_channel_name")); 451 | j.put("host", getStringResourceByName("ionic_update_api")); 452 | j.put("updateMethod", getStringResourceByName("ionic_update_method")); 453 | j.put("maxVersions", maxV); 454 | j.put("minBackgroundDuration", minBackgroundDuration); 455 | j.put("binaryVersion", versionName); 456 | j.put("binaryVersionName", versionName); 457 | j.put("binaryVersionCode", versionCode); 458 | 459 | 460 | Log.d(TAG, "Got Native Prefs for AppID: " + appId); 461 | return j; 462 | } 463 | 464 | /** 465 | * Add any keys from obj2 into obj1 overwriting them if they exist 466 | */ 467 | private void mergeObjects(JSONObject obj1, JSONObject obj2) { 468 | Iterator it = obj2.keys(); 469 | while (it.hasNext()) { 470 | String key = (String)it.next(); 471 | try { 472 | obj1.putOpt(key, obj2.opt(key)); 473 | } catch (JSONException ex) { 474 | Log.d(TAG, "key didn't exist when merging object"); 475 | } 476 | } 477 | } 478 | 479 | /** 480 | * Set cordova plugin preferences and state information. 481 | * @param callbackContext The callback id used when calling back into JavaScript. 482 | * @param newPrefs 483 | */ 484 | public void setPreferences(CallbackContext callbackContext, JSONObject newPrefs) { 485 | Log.i(TAG, "Set preferences called with prefs" + newPrefs.toString()); 486 | SharedPreferences prefs = this.cordova.getActivity().getApplicationContext().getSharedPreferences("com.ionic.deploy.preferences", Context.MODE_PRIVATE); 487 | SharedPreferences.Editor editor = prefs.edit(); 488 | editor.putString(this.PREFS_KEY, newPrefs.toString()); 489 | editor.commit(); 490 | Log.i(TAG, "preferences updated"); 491 | final PluginResult result = new PluginResult(PluginResult.Status.OK, newPrefs); 492 | result.setKeepCallback(false); 493 | callbackContext.sendPluginResult(result); 494 | } 495 | 496 | private static String toDirUrl(File f) { 497 | return Uri.fromFile(f).toString() + '/'; 498 | } 499 | 500 | } 501 | -------------------------------------------------------------------------------- /src/android/cordovapluginionic.gradle: -------------------------------------------------------------------------------- 1 | repositories{ 2 | mavenCentral() 3 | } 4 | 5 | dependencies { 6 | implementation 'commons-io:commons-io:2.4' 7 | } 8 | 9 | android { 10 | packagingOptions { 11 | exclude 'META-INF/NOTICE' 12 | exclude 'META-INF/LICENSE' 13 | } 14 | } 15 | 16 | ext.postBuildExtras = { 17 | android { 18 | compileOptions { 19 | sourceCompatibility JavaVersion.VERSION_1_8 20 | targetCompatibility JavaVersion.VERSION_1_8 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/browser/IonicCordovaCommon.js: -------------------------------------------------------------------------------- 1 | function notSupported(win,fail) { 2 | console.log('IonicCordova is not supported on browser platform'); 3 | setTimeout(function(){ 4 | if (win) { 5 | win(); 6 | } 7 | },0); 8 | } 9 | 10 | function getPreferences(win,fail) { 11 | setTimeout(function(){ 12 | win({ 13 | minBackgroundDuration: 30, 14 | disabled: true 15 | }); 16 | },0); 17 | } 18 | 19 | module.exports = { 20 | getPreferences: getPreferences, 21 | getAppInfo: notSupported, 22 | setPreferences:notSupported, 23 | configure: notSupported 24 | }; 25 | 26 | require("cordova/exec/proxy").add("IonicCordovaCommon", module.exports); 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/ios/IonicCordovaCommon.h: -------------------------------------------------------------------------------- 1 | // 2 | // IonicCordovaCommon.h 3 | // IonicCordovaCommon 4 | // 5 | // Created by Ionic on 4/26/2018. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | #import 26 | 27 | @interface IonicCordovaCommon : CDVPlugin 28 | 29 | /** 30 | * Get basic app information. Used for the Ionic monitoring service. 31 | * 32 | * @param command 33 | * 34 | * The callback id used when calling back into JavaScript. 35 | */ 36 | - (void) getAppInfo:(CDVInvokedUrlCommand*)command; 37 | 38 | /** 39 | * Get cordova plugin preferences and state information. 40 | * 41 | * @param command 42 | * 43 | * The callback id used when calling back into JavaScript. 44 | */ 45 | - (void) getPreferences:(CDVInvokedUrlCommand *)command; 46 | 47 | /** 48 | * Set cordova plugin preferences and state information. 49 | * 50 | * @param command 51 | * 52 | * The callback id used when calling back into JavaScript. 53 | */ 54 | - (void) setPreferences:(CDVInvokedUrlCommand *)command; 55 | 56 | /** 57 | * Set cordova custom plugin preferences and state information. 58 | * 59 | * @param command 60 | * 61 | * The callback id used when calling back into JavaScript. 62 | */ 63 | - (void) configure:(CDVInvokedUrlCommand *)command; 64 | 65 | - (void) copyTo:(CDVInvokedUrlCommand *)command; 66 | 67 | - (void) remove:(CDVInvokedUrlCommand *)command; 68 | 69 | - (void) downloadFile:(CDVInvokedUrlCommand *)command; 70 | 71 | /** 72 | * Get cordova plugin native congiguration and state information (config.xml stuff) 73 | * 74 | */ 75 | - (NSMutableDictionary*) getNativeConfig; 76 | 77 | /** 78 | * Get cordova plugin custom congiguration overrides (things changed via configure method) 79 | * 80 | */ 81 | - (NSMutableDictionary*) getCustomConfig; 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /src/ios/IonicCordovaCommon.m: -------------------------------------------------------------------------------- 1 | #import "IonicCordovaCommon.h" 2 | #import 3 | #import 4 | 5 | 6 | @interface IonicCordovaCommon() 7 | 8 | @property Boolean revertToBase; 9 | @property NSString *baseIndexPath; 10 | 11 | @end 12 | 13 | @implementation IonicCordovaCommon 14 | 15 | - (void) pluginInitialize { 16 | NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; 17 | 18 | self.revertToBase = true; 19 | self.baseIndexPath = [[NSBundle mainBundle] pathForResource:@"www" ofType: nil]; 20 | 21 | if ([prefs stringForKey:@"uuid"] == nil) { 22 | [prefs setObject:[[NSUUID UUID] UUIDString] forKey:@"uuid"]; 23 | } 24 | [prefs synchronize]; 25 | } 26 | 27 | - (void) remove:(CDVInvokedUrlCommand*)command { 28 | NSDictionary *options = command.arguments[0]; 29 | NSString *path = options[@"target"]; 30 | NSLog(@"Got remove path: %@", path); 31 | NSError *removeError = nil; 32 | if (![[NSFileManager defaultManager] removeItemAtPath:path error:&removeError]) { 33 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: [removeError localizedDescription]] callbackId:command.callbackId]; 34 | return; 35 | } 36 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] callbackId:command.callbackId]; 37 | } 38 | 39 | - (void) copyTo:(CDVInvokedUrlCommand*)command { 40 | NSDictionary *options = command.arguments[0]; 41 | NSLog(@"Got copyTo: %@", options); 42 | NSString *srcDir = options[@"source"][@"directory"]; 43 | NSString *srcPath = options[@"source"][@"path"]; 44 | NSString *dest = options[@"target"]; 45 | 46 | if (![srcDir isEqualToString:@"APPLICATION"]) { 47 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: @"Only Application directory is supported"] callbackId:command.callbackId]; 48 | return; 49 | } 50 | NSMutableString *source = [NSMutableString stringWithString:[[NSBundle mainBundle] resourcePath]]; 51 | [source appendString:@"/"]; 52 | [source appendString:srcPath]; 53 | NSError *createDirError = nil; 54 | if (![[NSFileManager defaultManager] createDirectoryAtPath:dest withIntermediateDirectories:YES attributes:nil error:&createDirError]) { 55 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: [createDirError localizedDescription]] callbackId:command.callbackId]; 56 | return; 57 | } 58 | [[NSFileManager defaultManager] removeItemAtPath:dest error:nil]; 59 | NSError *copyError = nil; 60 | if (![[NSFileManager defaultManager] copyItemAtPath:source toPath:dest error:©Error]) { 61 | NSLog(@"Error copying files: %@", [copyError localizedDescription]); 62 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: [copyError localizedDescription]] callbackId:command.callbackId]; 63 | return; 64 | } 65 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] callbackId:command.callbackId]; 66 | } 67 | 68 | - (void) downloadFile:(CDVInvokedUrlCommand*)command { 69 | NSDictionary *options = command.arguments[0]; 70 | NSString *target = options[@"target"]; 71 | NSString *urlStr = options[@"url"]; 72 | NSLog(@"Got downloadFile: %@", options); 73 | 74 | NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlStr]]; 75 | [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 76 | if (error) { 77 | NSLog(@"Download Error:%@",error.description); 78 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: [error localizedDescription]] callbackId:command.callbackId]; 79 | } 80 | NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response; 81 | if (httpResponse.statusCode != 200) { 82 | NSString *errorMsg = [NSString stringWithFormat:@"HTTP response status code: %ld", httpResponse.statusCode]; 83 | NSLog(@"Download Error: %@", errorMsg); 84 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: errorMsg] callbackId:command.callbackId]; 85 | } 86 | if (data) { 87 | [[NSFileManager defaultManager] createDirectoryAtPath:[target stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil]; 88 | [data writeToFile:target atomically:YES]; 89 | NSLog(@"File is saved to %@", target); 90 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] callbackId:command.callbackId]; 91 | } 92 | }] resume]; 93 | } 94 | 95 | - (void) getAppInfo:(CDVInvokedUrlCommand*)command { 96 | NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; 97 | NSMutableDictionary *json = [[NSMutableDictionary alloc] init]; 98 | NSString* platformVersion = [[UIDevice currentDevice] systemVersion]; 99 | NSString* versionCode = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"]; 100 | NSString* bundleName = [[NSBundle mainBundle] infoDictionary][@"CFBundleIdentifier"]; 101 | NSString* versionName = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]; 102 | NSString* uuid = [prefs stringForKey:@"uuid"]; 103 | NSString *libPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; 104 | NSString * cordovaDataDirectory = [libPath stringByAppendingPathComponent:@"NoCloud"]; 105 | 106 | if (versionName == nil) { 107 | versionName = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"]; 108 | if (versionName == nil) { 109 | versionName = @""; 110 | } 111 | } 112 | 113 | json[@"platform"] = @"ios"; 114 | json[@"platformVersion"] = platformVersion; 115 | json[@"version"] = versionCode; 116 | json[@"binaryVersionCode"] = versionCode; 117 | json[@"bundleName"] = bundleName; 118 | json[@"bundleVersion"] = versionName; 119 | json[@"binaryVersionName"] = versionName; 120 | json[@"device"] = uuid; 121 | json[@"dataDirectory"] = [[NSURL fileURLWithPath:cordovaDataDirectory] absoluteString]; 122 | NSLog(@"Got app info: %@", json); 123 | 124 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:json] callbackId:command.callbackId]; 125 | 126 | } 127 | 128 | - (void) getPreferences:(CDVInvokedUrlCommand*)command { 129 | // Get updated preferences if available 130 | NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; 131 | NSDictionary *immutableStoredPrefs = [prefs objectForKey:@"ionicDeploySavedPreferences"]; 132 | NSMutableDictionary *savedPrefs = [immutableStoredPrefs mutableCopy]; 133 | NSMutableDictionary *nativeConfig = [self getNativeConfig]; 134 | NSMutableDictionary *customConfig = [self getCustomConfig]; 135 | 136 | if (savedPrefs!= nil) { 137 | 138 | NSLog(@"found some saved prefs doing precedence ops: %@", savedPrefs); 139 | // Merge with most up to date Native Settings 140 | [savedPrefs addEntriesFromDictionary:nativeConfig]; 141 | 142 | // Merge with any custom settings 143 | [savedPrefs addEntriesFromDictionary:customConfig]; 144 | 145 | NSLog(@"Returning saved prefs: %@", savedPrefs); 146 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: savedPrefs] callbackId:command.callbackId]; 147 | return; 148 | } 149 | 150 | // No saved prefs found get them all from config 151 | // Make sure to initialize empty updates object 152 | NSLog(@"initing updates key"); 153 | nativeConfig[@"updates"] = [[NSDictionary alloc] init]; 154 | NSLog(@"Initialized App Prefs: %@", nativeConfig); 155 | 156 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:nativeConfig] callbackId:command.callbackId]; 157 | } 158 | 159 | - (void) setPreferences:(CDVInvokedUrlCommand*)command { 160 | NSDictionary *json = command.arguments[0]; 161 | NSLog(@"Got prefs to save: %@", json); 162 | [[NSUserDefaults standardUserDefaults] setObject:json forKey:@"ionicDeploySavedPreferences"]; 163 | [[NSUserDefaults standardUserDefaults] synchronize]; 164 | 165 | [self getPreferences:command]; 166 | } 167 | 168 | - (NSMutableDictionary*) getNativeConfig { 169 | // Get preferences from cordova 170 | NSString *appId = [NSString stringWithFormat:@"%@", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"IonAppId"]]; 171 | NSNumber * disabled = [NSNumber numberWithBool:[[self.commandDelegate.settings objectForKey:[@"DisableDeploy" lowercaseString]] boolValue]]; 172 | NSString *host = [NSString stringWithFormat:@"%@", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"IonApi"]]; 173 | NSString *updateMethod = [NSString stringWithFormat:@"%@", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"IonUpdateMethod"]]; 174 | NSString *channel = [NSString stringWithFormat:@"%@", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"IonChannelName"]]; 175 | NSNumber *maxV = [NSNumber numberWithInt:[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"IonMaxVersions"] intValue]]; 176 | NSNumber *minBackgroundDuration = [NSNumber numberWithInt:[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"IonMinBackgroundDuration"] intValue]]; 177 | NSString* versionCode = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"]; 178 | NSString* versionName = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]; 179 | 180 | // Build the preferences json object 181 | NSMutableDictionary *json = [[NSMutableDictionary alloc] init]; 182 | json[@"appId"] = appId; 183 | json[@"disabled"] = disabled; 184 | json[@"channel"] = channel; 185 | json[@"host"] = host; 186 | json[@"updateMethod"] = updateMethod; 187 | json[@"maxVersions"] = maxV; 188 | json[@"minBackgroundDuration"] = minBackgroundDuration; 189 | json[@"binaryVersionCode"] = versionCode; 190 | json[@"binaryVersion"] = versionName; 191 | json[@"binaryVersionName"] = versionName; 192 | NSLog(@"Got Native app preferences: %@", json); 193 | return json; 194 | } 195 | 196 | - (NSMutableDictionary*) getCustomConfig { 197 | // Get custom preferences if available 198 | NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; 199 | NSDictionary *immutableConfig = [prefs objectForKey:@"ionicDeployCustomPreferences"]; 200 | NSMutableDictionary *customConfig = [immutableConfig mutableCopy]; 201 | if (customConfig!= nil) { 202 | NSLog(@"Found custom config: %@", customConfig); 203 | return customConfig; 204 | } 205 | NSLog(@"No custom config found"); 206 | NSMutableDictionary *json = [[NSMutableDictionary alloc] init]; 207 | return json; 208 | } 209 | 210 | - (void) configure:(CDVInvokedUrlCommand *)command { 211 | NSDictionary *newConfig = command.arguments[0]; 212 | NSLog(@"Got new config to save: %@", newConfig); 213 | NSMutableDictionary *storedConfig = [self getCustomConfig]; 214 | [storedConfig addEntriesFromDictionary:newConfig]; 215 | [[NSUserDefaults standardUserDefaults] setObject:storedConfig forKey:@"ionicDeployCustomPreferences"]; 216 | [[NSUserDefaults standardUserDefaults] synchronize]; 217 | 218 | [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:newConfig] callbackId:command.callbackId]; 219 | } 220 | 221 | @end 222 | -------------------------------------------------------------------------------- /tests/test_common.ts: -------------------------------------------------------------------------------- 1 | import * as IonicCordova from '../www/common'; 2 | 3 | let mockPluginAPI = { 4 | IonicDeploy: { 5 | }, 6 | IonicCordova: { 7 | } 8 | }; 9 | 10 | function mockCordova() { 11 | return {exec: execMock()}; 12 | }; 13 | 14 | function execMock() { 15 | return jest.fn( (success, error, pluginName, method, extras) => { 16 | return mockPluginAPI[pluginName][method](success, error); 17 | }); 18 | } 19 | 20 | function callbackMock(returnValue: any, succeed: boolean) { 21 | return jest.fn( (success, error) => { 22 | succeed ? success(returnValue) : error(returnValue); 23 | }); 24 | } 25 | 26 | const pluginConfig = { 27 | appId: 'myapp', 28 | disabled: false, 29 | host: 'https://myhost.com', 30 | channel: 'mychannel', 31 | updateMethod: 'auto', 32 | maxVersions: 5, 33 | currentVersionId: 'version1' 34 | }; 35 | 36 | describe('IonicCordova', () => { 37 | 38 | beforeEach( () => { 39 | global.cordova = mockCordova(); 40 | }); 41 | 42 | afterEach( () => { 43 | mockPluginAPI = { 44 | IonicDeploy: { 45 | }, 46 | IonicCordova: { 47 | } 48 | }; 49 | }); 50 | 51 | it('should have a deploy object', async () => { 52 | const pluginBase = IonicCordova; 53 | expect(pluginBase.deploy).toBeDefined(); 54 | }); 55 | 56 | describe('IonicDeploy', () => { 57 | 58 | afterEach( () => { 59 | mockPluginAPI = { 60 | IonicDeploy: { 61 | }, 62 | IonicCordova: { 63 | } 64 | }; 65 | }); 66 | 67 | it('should have a parent object', async () => { 68 | mockPluginAPI.IonicCordova.getPreferences = callbackMock(pluginConfig, true) 69 | const pluginBase = IonicCordova; 70 | expect(pluginBase.deploy._parent).toBeDefined(); 71 | }); 72 | 73 | it('should set preferences on successful init of deploy plugin', async () => { 74 | mockPluginAPI.IonicCordova.getPreferences = callbackMock(pluginConfig, true) 75 | const pluginBase = IonicCordova; 76 | expect(global.cordova.exec.mock.calls.length).toBe(1); 77 | expect(global.cordova.exec.mock.calls[0][2]).toBe('IonicCordova'); 78 | expect(global.cordova.exec.mock.calls[0][3]).toBe('getPreferences'); 79 | expect(await pluginBase.deploy._pluginConfig).toEqual(pluginConfig); 80 | }); 81 | 82 | it('should log returned error on failed initialization of deploy plugin', async () => { 83 | mockPluginAPI.IonicCordova.getPreferences = callbackMock('random failure', false) 84 | const pluginBase = IonicCordova; 85 | expect(global.cordova.exec.mock.calls.length).toBe(1); 86 | expect(global.cordova.exec.mock.calls[0][2]).toBe('IonicCordova'); 87 | expect(global.cordova.exec.mock.calls[0][3]).toBe('getPreferences'); 88 | expect(pluginBase.deploy._pluginConfig).rejects 89 | }); 90 | 91 | describe('init', () => { 92 | 93 | afterEach( () => { 94 | mockPluginAPI = { 95 | IonicDeploy: { 96 | }, 97 | IonicCordova: { 98 | } 99 | }; 100 | }); 101 | 102 | it('should call the failure function when passed a bad config', async done => { 103 | mockPluginAPI.IonicCordova.getPreferences = callbackMock(pluginConfig, true) 104 | const pluginBase = IonicCordova; 105 | const badConfig = { 106 | appId: 26 107 | }; 108 | const success = function(result) { 109 | expect(true).toEqual(false); 110 | done(); 111 | }; 112 | const failure = function(err) { 113 | expect(err).toBe('Invalid Config Object'); 114 | done(); 115 | }; 116 | expect(pluginBase.deploy.init(badConfig, success, failure)).toBe(undefined); 117 | } 118 | 119 | it('should update preferences when called', async done => { 120 | mockPluginAPI.IonicCordova.getPreferences = callbackMock(pluginConfig, true) 121 | const pluginBase = IonicCordova; 122 | expect(await pluginBase.deploy._pluginConfig).toEqual(pluginConfig); 123 | const newConfig = { 124 | appId: 'newappid', 125 | disabled: true, 126 | host: 'http://newhost.com', 127 | channel: 'newchannel', 128 | } 129 | mockPluginAPI.IonicDeploy.syncPreferences = callbackMock(undefined, true) 130 | 131 | 132 | const success = function(result) { 133 | expect(result).toBeUndefined(); 134 | done(); 135 | }; 136 | const failure = function(err) { 137 | expect(true).toEqual(false); 138 | done(); 139 | }; 140 | pluginBase.deploy.init(newConfig, success, failure); 141 | const expectedConfig = Object.assign({}, pluginConfig, newConfig); 142 | expect(await pluginBase.deploy._pluginConfig).toEqual(expectedConfig); 143 | 144 | expect(global.cordova.exec.mock.calls.length).toBe(2); 145 | expect(global.cordova.exec.mock.calls[0][2]).toBe('IonicCordova'); 146 | expect(global.cordova.exec.mock.calls[0][3]).toBe('getPreferences'); 147 | expect(global.cordova.exec.mock.calls[1][2]).toBe('IonicDeploy'); 148 | expect(global.cordova.exec.mock.calls[1][3]).toBe('syncPreferences'); 149 | }); 150 | 151 | it('should update preferences multiple times when called more than once', async done => { 152 | mockPluginAPI.IonicCordova.getPreferences = callbackMock(pluginConfig, true) 153 | const pluginBase = IonicCordova; 154 | expect(await pluginBase.deploy._pluginConfig).toEqual(pluginConfig); 155 | mockPluginAPI.IonicDeploy.syncPreferences = callbackMock(undefined, true) 156 | 157 | const success = function(result) { 158 | expect(result).toBeUndefined(); 159 | done(); 160 | }; 161 | const failure = function(err) { 162 | expect(true).toEqual(false); 163 | done(); 164 | }; 165 | let newConfig = { 166 | channel: 'newchannel', 167 | }; 168 | pluginBase.deploy.init(newConfig, success, failure); 169 | let expectedConfig = Object.assign({}, pluginConfig, newConfig); 170 | expect(await pluginBase.deploy._pluginConfig).toEqual(expectedConfig); 171 | 172 | newConfig = { 173 | channel: 'anotherchannel' 174 | }; 175 | 176 | expectedConfig = Object.assign({}, pluginConfig, newConfig); 177 | expect(await pluginBase.deploy._pluginConfig).toEqual(expectedConfig); 178 | 179 | expect(global.cordova.exec.mock.calls.length).toBe(3); 180 | expect(global.cordova.exec.mock.calls[0][2]).toBe('IonicCordova'); 181 | expect(global.cordova.exec.mock.calls[0][3]).toBe('getPreferences'); 182 | expect(global.cordova.exec.mock.calls[1][2]).toBe('IonicDeploy'); 183 | expect(global.cordova.exec.mock.calls[1][3]).toBe('syncPreferences'); 184 | expect(global.cordova.exec.mock.calls[2][2]).toBe('IonicDeploy'); 185 | expect(global.cordova.exec.mock.calls[2][3]).toBe('syncPreferences'); 186 | 187 | }); 188 | 189 | it('should call failure when initialization has failed to get preferences', async done => { 190 | mockPluginAPI.IonicCordova.getPreferences = callbackMock('some error', false) 191 | const pluginBase = IonicCordova; 192 | const newConfig = { 193 | appId: 'newappid', 194 | disabled: true, 195 | host: 'http://newhost.com', 196 | channel: 'newchannel', 197 | } 198 | 199 | const success = function(succ) { 200 | expect(true).toEqual(false); 201 | done(); 202 | }; 203 | const fail = function(err) { 204 | expect(err).toEqual('some error'); 205 | done(); 206 | }; 207 | await pluginBase.deploy.init(newConfig, success, fail); 208 | }); 209 | } 210 | 211 | describe('configure', () => { 212 | 213 | afterEach( () => { 214 | mockPluginAPI = { 215 | IonicDeploy: { 216 | }, 217 | IonicCordova: { 218 | } 219 | }; 220 | }); 221 | 222 | it('should throw when passed a bad config', async () => { 223 | mockPluginAPI.IonicCordova.getPreferences = callbackMock(pluginConfig, true) 224 | const pluginBase = IonicCordova; 225 | const badConfig = { 226 | appId: 26 227 | }; 228 | expect(pluginBase.deploy.configure(badConfig)).rejects.toThrow('Invalid Config Object'); 229 | } 230 | 231 | it('should update preferences when called', async () => { 232 | mockPluginAPI.IonicCordova.getPreferences = callbackMock(pluginConfig, true) 233 | const pluginBase = IonicCordova; 234 | expect(pluginBase.deploy._pluginConfig).resolves.toBe(pluginConfig); 235 | const newConfig = { 236 | appId: 'newappid', 237 | disabled: true, 238 | host: 'http://newhost.com', 239 | channel: 'newchannel', 240 | } 241 | mockPluginAPI.IonicDeploy.syncPreferences = callbackMock(undefined, true) 242 | expect(pluginBase.deploy.configure(newConfig)).resolves.toBe(undefined); 243 | const expectedConfig = Object.assign({}, pluginConfig, newConfig); 244 | expect(pluginBase.deploy._pluginConfig).resolves.toBe(expectedConfig); 245 | } 246 | 247 | it('should reject when initilization has failed to get preferences', async () => { 248 | mockPluginAPI.IonicCordova.getPreferences = callbackMock('some error', false) 249 | const pluginBase = IonicCordova; 250 | const newConfig = { 251 | appId: 'newappid', 252 | disabled: true, 253 | host: 'http://newhost.com', 254 | channel: 'newchannel', 255 | } 256 | expect(pluginBase.deploy.configure(newConfig)).rejects.toThrow('some error'); 257 | }); 258 | 259 | it('should update preferences multiple times when called more than once', async () => { 260 | 261 | mockPluginAPI.IonicCordova.getPreferences = callbackMock(pluginConfig, true) 262 | const pluginBase = IonicCordova; 263 | expect(pluginBase.deploy._pluginConfig).resolves.toBe(pluginConfig); 264 | 265 | let newConfig = { 266 | channel: 'channel1', 267 | } 268 | mockPluginAPI.IonicDeploy.syncPreferences = callbackMock(undefined, true) 269 | expect(pluginBase.deploy.configure(newConfig)).resolves.toBe(undefined); 270 | let expectedConfig = Object.assign({}, pluginConfig, newConfig); 271 | expect(pluginBase.deploy._pluginConfig).resolves.toBe(expectedConfig); 272 | newConfig = { 273 | channel: 'channel2' 274 | } 275 | 276 | expectedConfig = Object.assign({}, pluginConfig, newConfig); 277 | expect(pluginBase.deploy.configure(newConfig)).resolves.toBe(undefined); 278 | expect(pluginBase.deploy._pluginConfig).resolves.toBe(expectedConfig); 279 | } 280 | }); 281 | } 282 | }); 283 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowUnreachableCode": false, 4 | "noImplicitAny": true, 5 | "declaration": true, 6 | "experimentalDecorators": true, 7 | "importHelpers": false, 8 | "module": "commonjs", 9 | "outDir": "dist", 10 | "moduleResolution": "node", 11 | "noFallthroughCasesInSwitch": true, 12 | "noUnusedLocals": true, 13 | "pretty": true, 14 | "strict": true, 15 | "target": "es5", 16 | "lib": [ 17 | "es2015", 18 | "dom" 19 | ] 20 | }, 21 | "include": ["www/common.ts", "www/guards.ts", "www/index.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.ng.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "es2015", 5 | "outDir": "dist/ngx", 6 | "rootDir": "www" 7 | }, 8 | "include": ["www/ngx/index.ts"], 9 | "angularCompilerOptions": { 10 | "enableIvy": false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-ionic-rules", 3 | "rules": { 4 | "eofline": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /www/IonicCordova.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | IonicCordova: IPluginBaseAPI; 4 | } 5 | type IDeployPluginAPI = DeployPluginAPI; 6 | } 7 | 8 | /** 9 | * The Public API for the deploy Plugin 10 | */ 11 | export interface DeployPluginAPI { 12 | 13 | /** 14 | * @description Update the default configuration for the plugin on the current device. The new configuration will be persisted across app close and binary updates. 15 | * 16 | * @since v5.0.0 17 | * 18 | * @param config The new configuration for the plugin on this device. 19 | */ 20 | configure(config: IDeployConfig): Promise; 21 | 22 | /** 23 | * @description Get the current configuration for the plugin on the current device. 24 | * 25 | * @since v5.0.0 26 | * 27 | * @return The current configuration of the plugin. 28 | */ 29 | getConfiguration(): Promise; 30 | 31 | /** 32 | * @description Check for available updates for the currently configured app id and channel. 33 | * 34 | * @since v5.0.0 35 | * 36 | * @return A response describing an update if one is available. 37 | */ 38 | checkForUpdate(): Promise; 39 | 40 | /** 41 | * @description Download the new files from an available update found by the checkForUpdate method and prepare the update. 42 | * 43 | * @since v5.0.0 44 | * 45 | * @param progress A progress callback function which will be called with a number representing the percent of completion of the download and prepare. 46 | * 47 | * @return true if the download succeeded 48 | */ 49 | downloadUpdate(progress?: CallbackFunction): Promise; 50 | 51 | /** 52 | * @description Extract a downloaded bundle of updated files. 53 | * 54 | * @since v5.0.0 55 | * 56 | * @param progress A progress callback function which will be called with a number representing the percent of completion of the extract. 57 | * 58 | * @return true if the extract succeeded 59 | */ 60 | extractUpdate(progress?: CallbackFunction): Promise; 61 | 62 | /** 63 | * @description Reload the app if a more recent version of the app is available. 64 | * 65 | * @since v5.0.0 66 | * 67 | * @return true if the reload succeeded 68 | */ 69 | reloadApp(): Promise; 70 | 71 | /** 72 | * @description Check for an update, download it, and apply it in one step. 73 | * 74 | * @since v5.0.0 75 | * 76 | * @param syncOptions (Optional) Application update overrides. 77 | * 78 | * @param progress (Optional) A callback which will recieve progress updates 79 | * 80 | * @return The info about the currently applied update or undefined if none is applied. 81 | */ 82 | sync(syncOptions: ISyncOptions, progress?: CallbackFunction): Promise; 83 | 84 | /** 85 | * 86 | * @description Get info about the currently deployed update or undefined if none are applied. 87 | * 88 | * @since v5.0.0 89 | * 90 | * @return The info about the currently applied update or undefined if none is applied. 91 | */ 92 | getCurrentVersion(): Promise; 93 | 94 | /** 95 | * @description Get a list of the snapshots available on the device. 96 | * 97 | * @since v5.0.0 98 | * 99 | * @return a list of available updates. 100 | */ 101 | getAvailableVersions(): Promise; 102 | 103 | /** 104 | * @description Remove the files specific to a snapshot from the device. 105 | * 106 | * @param version The versionId 107 | * 108 | * @return true if the update was deleted. 109 | */ 110 | deleteVersionById(versionId: string): Promise; 111 | 112 | /** 113 | * @description Returns info specific to a snapshot from the device. 114 | * 115 | * @param version The versionId 116 | * 117 | * @return Returns info specific to a snapshot from the device. 118 | * 119 | */ 120 | getVersionById(versionId: string): Promise; 121 | } 122 | 123 | /** 124 | * The IonicCordova Plugin API 125 | */ 126 | export interface IPluginBaseAPI { 127 | /** 128 | * 129 | * @param success 130 | * @param failure 131 | */ 132 | getAppInfo(success: Function, failure: Function): void; 133 | 134 | /** 135 | * @description Get info about the current app. 136 | * 137 | */ 138 | getAppDetails(): Promise; 139 | 140 | /** 141 | * An instance of the Ionic Deploy Plugin API 142 | */ 143 | deploy: IDeployPluginAPI; 144 | } 145 | 146 | /** 147 | * The configuration for the deploy plugin on the device. 148 | */ 149 | export interface IDeployConfig { 150 | 151 | /** 152 | * The [Ionic Pro](https://ionicframework.com/docs/pro/) app id. 153 | */ 154 | appId?: string; 155 | 156 | /** 157 | * whether or not the app should in debug mode 158 | */ 159 | debug?: boolean; 160 | 161 | /** 162 | * @ignore 163 | */ 164 | host?: string; 165 | 166 | /** 167 | * The [channel](https://ionicframework.com/docs/pro/deploy/channels) that the plugin should listen for updates on. 168 | */ 169 | channel?: string; 170 | 171 | /** 172 | * The number of previous updates to be cached on the device 173 | */ 174 | maxVersions?: number; 175 | 176 | /** 177 | * The number of seconds the app should be in the background for before the plugin considers it closed 178 | * and checks for an updated on resume of the app. 179 | */ 180 | minBackgroundDuration?: number; 181 | 182 | /** 183 | * The update method the app should use when checking for available updates 184 | */ 185 | updateMethod?: 'none' | 'auto' | 'background'; 186 | } 187 | 188 | /** 189 | * The current configuration for the deploy plugin on the device. 190 | */ 191 | export interface ICurrentConfig { 192 | /** 193 | * The [Ionic Pro](https://ionicframework.com/docs/pro/) app id. 194 | */ 195 | appId: string; 196 | 197 | /** 198 | * The [channel](https://ionicframework.com/docs/pro/deploy/channels) that the plugin should listen for updates on. 199 | */ 200 | channel: string; 201 | 202 | /** 203 | * @deprecated 204 | * The binary version of the native bundle versionName on Android or CFBundleShortVersionString on iOS 205 | * deprecated in favor of versionName 206 | */ 207 | binaryVersion: string; 208 | 209 | /** 210 | * The binary version of the native bundle versionName on Android or CFBundleShortVersionString on iOS 211 | */ 212 | binaryVersionName: string; 213 | 214 | /** 215 | * The build version code of the native bundle versionCode on Android or CFBundleVersion on iOS 216 | */ 217 | binaryVersionCode: string; 218 | 219 | /** 220 | * Whether the user disabled deploy updates or not. 221 | */ 222 | disabled: boolean; 223 | 224 | /** 225 | * The host API the plugin is configured to check for updates from. 226 | */ 227 | host: string; 228 | 229 | /** 230 | * The currently configured updateMethod for the plugin. 231 | */ 232 | updateMethod: 'none' | 'auto' | 'background'; 233 | 234 | /** 235 | * The maximum number of updates to be stored locally on the device. 236 | */ 237 | maxVersions: number; 238 | 239 | /** 240 | * The number of seconds the app needs to be in the background before the plugin considers it 241 | * closed for the purposes of fetching and applying a new update. 242 | */ 243 | minBackgroundDuration: number; 244 | 245 | /** 246 | * The id of the currently applied updated or undefined if none is applied. 247 | */ 248 | currentVersionId?: string; 249 | 250 | /** 251 | * The id of the currently applied build or undefined if none is applied. 252 | */ 253 | currentBuildId?: string; 254 | } 255 | 256 | /** 257 | * Information about a snapshot 258 | */ 259 | export interface ISnapshotInfo { 260 | 261 | /** 262 | * @deprecated in favor of [versionId](#versionid) 263 | * 264 | * The id for the snapshot. 265 | */ 266 | deploy_uuid: string; 267 | 268 | /** 269 | * The id for the snapshot. 270 | */ 271 | versionId: string; 272 | 273 | /** 274 | * The id for the snapshot. 275 | */ 276 | buildId: string; 277 | 278 | /** 279 | * The channel that the snapshot was downloaded for.. 280 | */ 281 | channel: string; 282 | 283 | /** 284 | * @deprecated in favor of [binaryVersion](#binaryversion) 285 | * 286 | * The binary version the snapshot was downloaded for. 287 | */ 288 | binary_version: string; 289 | 290 | /** 291 | * @deprecated 292 | * The binary version the snapshot was downloaded for. 293 | * The versionName on Android or CFBundleShortVersionString on iOS this is the end user readable version listed on the stores. 294 | */ 295 | binaryVersion: string; 296 | 297 | /** 298 | * The binary version name the snapshot was downloaded for. 299 | * The versionName on Android or CFBundleShortVersionString on iOS this is the end user readable version listed on the stores. 300 | */ 301 | binaryVersionName: string; 302 | 303 | /** 304 | * The binary version build code the snapshot was downloaded for. 305 | * The versionCode on Android or CFBundleVersion on iOS this should be changed every time you do a new build debug or otherwise. 306 | */ 307 | binaryVersionCode: string; 308 | } 309 | 310 | /** 311 | * Configuration options for the call to `sync` 312 | */ 313 | export interface ISyncOptions { 314 | /** 315 | * Whether the update should be applied immediately or on the next app start. 316 | */ 317 | updateMethod?: 'background' | 'auto'; 318 | } 319 | 320 | /** 321 | * The response object describing if an update is available. 322 | */ 323 | export interface CheckForUpdateResponse { 324 | /** 325 | * Whether or not an update is available. 326 | */ 327 | available: boolean; 328 | 329 | /** 330 | * Equivalent to available since v5 this can be ignored in favor of available 331 | * @deprecated 332 | */ 333 | compatible: boolean; 334 | 335 | /** 336 | * Legacy indicator of whether the update is a partial one. This will always be false and can be ignored 337 | * @deprecated 338 | */ 339 | partial: false; 340 | 341 | /** 342 | * The id of the snapshot if available. 343 | */ 344 | snapshot?: string; 345 | 346 | /** 347 | * The id of the build if available. 348 | */ 349 | build?: string; 350 | 351 | /** 352 | * The url to fetch the manifest of files in the update. 353 | */ 354 | url?: string; 355 | 356 | /** 357 | * Whether or not there is an update available that is not compatible with this device. 358 | */ 359 | incompatibleUpdateAvailable?: boolean; 360 | } 361 | 362 | /** 363 | * Information about the application. 364 | */ 365 | export interface IAppInfo { 366 | 367 | /** 368 | * The platform that the app is currently installed on. 369 | */ 370 | platform: 'ios' | 'android'; 371 | 372 | /** 373 | * The version of the native platform. 374 | */ 375 | platformVersion: string; 376 | 377 | /** 378 | * @deprecated 379 | * The versionCode on Android or CFBundleVersion on iOS this should be changed every time you do a new build debug or otherwise. 380 | */ 381 | version: string; 382 | 383 | /** 384 | * The versionCode on Android or CFBundleVersion on iOS this should be changed every time you do a new build debug or otherwise. 385 | */ 386 | binaryVersionCode: string | number; 387 | 388 | /** 389 | * The bundle name. 390 | */ 391 | bundleName: string; 392 | 393 | /** 394 | * @deprecated 395 | * The versionName on Android or CFBundleShortVersionString on iOS this is the end user readable version listed on the stores. 396 | */ 397 | bundleVersion: string; 398 | 399 | /** 400 | * The versionName on Android or CFBundleShortVersionString on iOS this is the end user readable version listed on the stores. 401 | */ 402 | binaryVersionName: string; 403 | 404 | /** 405 | * A generated device ID (NOT a native device ID) 406 | */ 407 | device: string; 408 | 409 | /** 410 | * Directory where the snapshots are stored 411 | */ 412 | dataDirectory: string; 413 | 414 | /** 415 | * Directory where the application files are stored 416 | */ 417 | applicationDirectory: string; 418 | } 419 | 420 | /** 421 | * A callback function to handle the result. 422 | */ 423 | export interface CallbackFunction { (result?: T): void; } 424 | -------------------------------------------------------------------------------- /www/common.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { 4 | CallbackFunction, 5 | CheckForUpdateResponse, 6 | IAppInfo, 7 | ICurrentConfig, 8 | IDeployConfig, 9 | IPluginBaseAPI, 10 | ISnapshotInfo, 11 | ISyncOptions, 12 | } from './IonicCordova'; 13 | 14 | declare const cordova: Cordova; 15 | 16 | const channel = cordova.require('cordova/channel'); 17 | channel.createSticky('onIonicProReady'); 18 | channel.waitForInitialization('onIonicProReady'); 19 | 20 | declare const Ionic: any; 21 | declare const WEBVIEW_SERVER_URL: string; 22 | declare const Capacitor: any; 23 | 24 | enum UpdateMethod { 25 | BACKGROUND = 'background', 26 | AUTO = 'auto', 27 | NONE = 'none', 28 | } 29 | 30 | enum UpdateState { 31 | Available = 'available', 32 | Pending = 'pending', 33 | Ready = 'ready', 34 | } 35 | 36 | import { 37 | FetchManifestResp, IAvailableUpdate, 38 | ISavedPreferences, 39 | ManifestFileEntry, 40 | } from './definitions'; 41 | 42 | import { 43 | isPluginConfig 44 | } from './guards'; 45 | 46 | 47 | class Path { 48 | static join(...paths: string[]): string { 49 | let fullPath: string = paths.shift() || ''; 50 | for (const path of paths) { 51 | if (fullPath && fullPath.slice(-1) !== '/') { 52 | fullPath += '/'; 53 | } 54 | fullPath = path.slice(0, 1) !== '/' ? fullPath + path : fullPath + path.slice(1); 55 | } 56 | return fullPath; 57 | } 58 | } 59 | 60 | /** 61 | * LIVE UPDATE API 62 | * 63 | * The plugin API for the live updates feature. 64 | */ 65 | 66 | class IonicDeployImpl { 67 | 68 | private readonly appInfo: IAppInfo; 69 | private _savedPreferences: ISavedPreferences; 70 | private _fileManager: FileManager = new FileManager(); 71 | private SNAPSHOT_CACHE = 'ionic_built_snapshots'; 72 | private MANIFEST_FILE = 'pro-manifest.json'; 73 | public PLUGIN_VERSION = '5.5.3'; 74 | 75 | constructor(appInfo: IAppInfo, preferences: ISavedPreferences) { 76 | this.appInfo = appInfo; 77 | this._savedPreferences = preferences; 78 | } 79 | 80 | async _handleInitialPreferenceState() { 81 | // make sure we're not going to redirect to a stale version 82 | await this.cleanupStaleVersions(); 83 | const isOnline = navigator && navigator.onLine; 84 | if (!isOnline) { 85 | console.warn('The device appears to be offline. Loading last available version and skipping update checks.'); 86 | this.reloadApp(); 87 | return; 88 | } 89 | 90 | const updateMethod = this._savedPreferences.updateMethod; 91 | switch (updateMethod) { 92 | case UpdateMethod.AUTO: 93 | // NOTE: call sync with background as override to avoid sync 94 | // reloading the app and manually reload always once sync has 95 | // set the correct currentVersionId 96 | console.log('calling _sync'); 97 | try { 98 | await this.sync({updateMethod: UpdateMethod.BACKGROUND}); 99 | } catch (e) { 100 | console.warn(e); 101 | console.warn('Sync failed. Defaulting to last available version.'); 102 | } 103 | console.log('calling _reload'); 104 | await this.reloadApp(); 105 | console.log('done _reloading'); 106 | break; 107 | case UpdateMethod.NONE: 108 | this.reloadApp(); 109 | break; 110 | default: 111 | // NOTE: default anything that doesn't explicitly match to background updates 112 | await this.reloadApp(); 113 | try { 114 | this.sync({updateMethod: UpdateMethod.BACKGROUND}); 115 | } catch (e) { 116 | console.warn(e); 117 | console.warn('Background sync failed. Unable to check for new updates.'); 118 | } 119 | return; 120 | } 121 | } 122 | 123 | getSnapshotCacheDir(versionId: string): string { 124 | return new URL(Path.join(this.appInfo.dataDirectory, this.SNAPSHOT_CACHE, versionId)).pathname; 125 | } 126 | 127 | getBundledAppDir(): string { 128 | let folder = 'www'; 129 | if (typeof (Capacitor) !== 'undefined') { 130 | folder = 'public'; 131 | } 132 | return folder; 133 | } 134 | 135 | private async _savePrefs(prefs: ISavedPreferences): Promise { 136 | return new Promise(async (resolve, reject) => { 137 | try { 138 | cordova.exec(async (savedPrefs: ISavedPreferences) => { 139 | resolve(savedPrefs); 140 | }, reject, 'IonicCordovaCommon', 'setPreferences', [prefs]); 141 | } catch (e) { 142 | reject(e.message); 143 | } 144 | }); 145 | } 146 | 147 | async configure(config: IDeployConfig) { 148 | if (!isPluginConfig(config)) { 149 | throw new Error('Invalid Config Object'); 150 | } 151 | await new Promise((resolve, reject) => { 152 | cordova.exec(resolve, reject, 'IonicCordovaCommon', 'configure', [config]); 153 | }); 154 | Object.assign(this._savedPreferences, config); 155 | this._savePrefs(this._savedPreferences); 156 | } 157 | 158 | async checkForUpdate(): Promise { 159 | const isOnline = navigator && navigator.onLine; 160 | if (!isOnline) { 161 | throw new Error('The device is offline.'); 162 | } 163 | const prefs = this._savedPreferences; 164 | const appInfo = this.appInfo; 165 | const endpoint = `${prefs.host}/apps/${prefs.appId}/channels/check-device`; 166 | 167 | const device_details = { 168 | binary_version: prefs.binaryVersionName, 169 | device_id: appInfo.device || null, 170 | platform: appInfo.platform, 171 | platform_version: appInfo.platformVersion, 172 | snapshot: prefs.currentVersionId, 173 | build: prefs.currentBuildId 174 | }; 175 | 176 | const body = { 177 | channel_name: prefs.channel, 178 | app_id: prefs.appId, 179 | device: device_details, 180 | plugin_version: this.PLUGIN_VERSION, 181 | manifest: true 182 | }; 183 | 184 | const timeout = new Promise( (resolve, reject) => { 185 | setTimeout(reject, 5000, 'Request timed out. The device maybe offline.'); 186 | }); 187 | const request = fetch(endpoint, { 188 | method: 'POST', 189 | headers: new Headers({ 190 | 'Content-Type': 'application/json' 191 | }), 192 | body: JSON.stringify(body) 193 | }); 194 | 195 | const resp = await (Promise.race([timeout, request]) as Promise); 196 | 197 | let jsonResp; 198 | if (resp.status < 500) { 199 | jsonResp = await resp.json(); 200 | } 201 | if (resp.ok) { 202 | const checkForUpdateResp: CheckForUpdateResponse = jsonResp.data; 203 | if (checkForUpdateResp.available && checkForUpdateResp.url && checkForUpdateResp.snapshot && checkForUpdateResp.build) { 204 | prefs.availableUpdate = { 205 | binaryVersionCode: prefs.binaryVersionCode, 206 | binaryVersionName: prefs.binaryVersionName, 207 | channel: prefs.channel, 208 | state: UpdateState.Available, 209 | lastUsed: new Date().toISOString(), 210 | url: checkForUpdateResp.url, 211 | versionId: checkForUpdateResp.snapshot, 212 | buildId: checkForUpdateResp.build 213 | }; 214 | await this._savePrefs(prefs); 215 | } 216 | return checkForUpdateResp; 217 | } 218 | 219 | throw new Error(`Error Status ${resp.status}: ${jsonResp ? jsonResp.error.message : await resp.text()}`); 220 | } 221 | 222 | async downloadUpdate(progress?: CallbackFunction): Promise { 223 | const prefs = this._savedPreferences; 224 | if (prefs.availableUpdate && prefs.availableUpdate.state === UpdateState.Available) { 225 | const { fileBaseUrl, manifestJson } = await this._fetchManifest(prefs.availableUpdate.url); 226 | const diffedManifest = await this._diffManifests(manifestJson); 227 | await this.prepareUpdateDirectory(prefs.availableUpdate.versionId); 228 | await this._downloadFilesFromManifest(fileBaseUrl, diffedManifest, prefs.availableUpdate.versionId, progress); 229 | prefs.availableUpdate.state = UpdateState.Pending; 230 | await this._savePrefs(prefs); 231 | return true; 232 | } 233 | return false; 234 | } 235 | 236 | private async _downloadFilesFromManifest(baseUrl: string, manifest: ManifestFileEntry[], versionId: string, progress?: CallbackFunction) { 237 | console.log('Downloading update...'); 238 | let size = 0, downloaded = 0; 239 | manifest.forEach(i => { 240 | size += i.size; 241 | }); 242 | 243 | const beforeDownloadTimer = new Timer('downloadTimer'); 244 | const downloadFile = async (file: ManifestFileEntry) => { 245 | const base = new URL(baseUrl); 246 | const newUrl = new URL(file.href, baseUrl); 247 | newUrl.search = base.search; 248 | const filePath = Path.join(this.getSnapshotCacheDir(versionId), file.href); 249 | await this._fileManager.downloadAndWriteFile(newUrl.toString(), filePath); 250 | // Update progress 251 | downloaded += file.size; 252 | if (progress) { 253 | progress(Math.floor((downloaded / size) * 100)); 254 | } 255 | }; 256 | 257 | let downloads = []; 258 | let count = 0; 259 | console.log(`About to download ${manifest.length} new files for update.`); 260 | const maxBatch = 20; 261 | let numberBatches = Math.round(manifest.length / maxBatch); 262 | if (manifest.length % maxBatch !== 0) { 263 | numberBatches = numberBatches + 1; 264 | } 265 | for (const entry of manifest) { 266 | if (downloads.length >= maxBatch) { 267 | count++; 268 | await Promise.all(downloads); 269 | beforeDownloadTimer.diff(`downloaded batch ${count} of ${numberBatches} downloads. Done downloading ${count * maxBatch} of ${manifest.length} files`); 270 | downloads = []; 271 | } 272 | downloads.push(downloadFile(entry)); 273 | } 274 | if (downloads.length) { 275 | count++; 276 | await Promise.all(downloads); 277 | beforeDownloadTimer.diff(`downloaded batch ${count} of ${numberBatches} downloads. Done downloading all ${manifest.length} files`); 278 | } 279 | beforeDownloadTimer.end(`Downloaded ${manifest.length} files`); 280 | } 281 | 282 | private async _fetchManifest(url: string): Promise { 283 | const resp = await fetch(url, { 284 | method: 'GET', 285 | redirect: 'follow', 286 | }); 287 | return { 288 | fileBaseUrl: resp.url, 289 | manifestJson: await resp.json() 290 | }; 291 | } 292 | 293 | private async _diffManifests(newManifest: ManifestFileEntry[]) { 294 | try { 295 | const manifestResp = await fetch(`${WEBVIEW_SERVER_URL}/${this.MANIFEST_FILE}`); 296 | const bundledManifest: ManifestFileEntry[] = await manifestResp.json(); 297 | const bundleManifestStrings = bundledManifest.map(entry => JSON.stringify(entry)); 298 | return newManifest.filter(entry => bundleManifestStrings.indexOf(JSON.stringify(entry)) === -1); 299 | } catch (e) { 300 | return newManifest; 301 | } 302 | } 303 | 304 | private async prepareUpdateDirectory(versionId: string) { 305 | await this._cleanSnapshotDir(versionId); 306 | console.log('Cleaned version directory'); 307 | 308 | await this._copyBaseAppDir(versionId); 309 | console.log('Copied base app resources'); 310 | } 311 | 312 | async extractUpdate(progress?: CallbackFunction): Promise { 313 | const prefs = this._savedPreferences; 314 | if (!prefs.availableUpdate || prefs.availableUpdate.state !== UpdateState.Pending) { 315 | return false; 316 | } 317 | 318 | if (progress) { 319 | progress(100); 320 | } 321 | 322 | prefs.availableUpdate.state = UpdateState.Ready; 323 | prefs.updates[prefs.availableUpdate.versionId] = prefs.availableUpdate; 324 | await this._savePrefs(prefs); 325 | return true; 326 | } 327 | 328 | async reloadApp(): Promise { 329 | const prefs = this._savedPreferences; 330 | 331 | // Save the current update if it's ready 332 | if (prefs.availableUpdate && prefs.availableUpdate.state === UpdateState.Ready) { 333 | prefs.currentVersionId = prefs.availableUpdate.versionId; 334 | prefs.currentBuildId = prefs.availableUpdate.buildId; 335 | delete prefs.availableUpdate; 336 | await this._savePrefs(prefs); 337 | } 338 | 339 | // Is there a non-binary version deployed? 340 | if (prefs.currentVersionId) { 341 | // Are we already running the deployed version? 342 | if (await this._isRunningVersion(prefs.currentVersionId)) { 343 | console.log(`Already running version ${prefs.currentVersionId}`); 344 | await this._savePrefs(prefs); 345 | channel.onIonicProReady.fire(); 346 | Ionic.WebView.persistServerBasePath(); 347 | await this.cleanupVersions(); 348 | return false; 349 | } 350 | 351 | // Is the current version on the device? 352 | if (!(prefs.currentVersionId in prefs.updates)) { 353 | console.error(`Missing version ${prefs.currentVersionId}`); 354 | channel.onIonicProReady.fire(); 355 | return false; 356 | } 357 | 358 | // Reload the webview 359 | const newLocation = this.getSnapshotCacheDir(prefs.currentVersionId); 360 | Ionic.WebView.setServerBasePath(newLocation); 361 | return true; 362 | } 363 | 364 | channel.onIonicProReady.fire(); 365 | return false; 366 | } 367 | 368 | // compare an update to the current version using both name & code 369 | private isUpdateForCurrentBinary(update: IAvailableUpdate) { 370 | const currentVersionCode = this._savedPreferences.binaryVersionCode; 371 | const currentVersionName = this._savedPreferences.binaryVersionName; 372 | console.log(`Current: versionCode: ${currentVersionCode} versionName: ${currentVersionName}`); 373 | console.log(`update: versionCode: ${update.binaryVersionCode} versionName: ${update.binaryVersionName}`); 374 | return update.binaryVersionName === currentVersionName && update.binaryVersionCode === currentVersionCode; 375 | } 376 | 377 | private isUpdateCurrentlyInstalled(update: IAvailableUpdate) { 378 | return this._savedPreferences.currentVersionId === update.versionId; 379 | } 380 | 381 | private async cleanupStaleVersions() { 382 | const updates = this.getStoredUpdates(); 383 | const prefs = this._savedPreferences; 384 | 385 | for (const update of updates) { 386 | // Is the version built from a previous binary? 387 | if (!this.isUpdateForCurrentBinary(update) && !(await this._isRunningVersion(update.versionId))) { 388 | console.log( 389 | `Update ${update.versionId} was built for different binary version removing update from device` + 390 | `Update binaryVersionName: ${update.binaryVersionName}, Device binaryVersionName ${prefs.binaryVersionName}` + 391 | `Update binaryVersionCode: ${update.binaryVersionCode}, Device binaryVersionCode ${prefs.binaryVersionCode}` 392 | ); 393 | 394 | // This is no longer necessary for this function, but a previous version of the code 395 | // deleted `prefs.currentVersionId` near initialization so other code may rely on still 396 | // deleting `prefs.currentVersionId`. 397 | if (this.isUpdateCurrentlyInstalled(update)) { 398 | delete prefs.currentVersionId; 399 | } 400 | 401 | await this.deleteVersionById(update.versionId); 402 | } 403 | } 404 | } 405 | 406 | private async _isRunningVersion(versionId: string) { 407 | const currentPath = await this._getServerBasePath(); 408 | return currentPath.includes(versionId); 409 | } 410 | 411 | private async _getServerBasePath(): Promise { 412 | return new Promise( async (resolve, reject) => { 413 | try { 414 | Ionic.WebView.getServerBasePath(resolve); 415 | } catch (e) { 416 | reject(e); 417 | } 418 | }); 419 | } 420 | 421 | private async _cleanSnapshotDir(versionId: string) { 422 | const timer = new Timer('CleanSnapshotDir'); 423 | const snapshotDir = this.getSnapshotCacheDir(versionId); 424 | try { 425 | await this._fileManager.remove(snapshotDir); 426 | timer.end(); 427 | } catch (e) { 428 | console.log('No directory found for snapshot no need to delete'); 429 | timer.end(); 430 | } 431 | } 432 | 433 | private async _copyBaseAppDir(versionId: string) { 434 | const timer = new Timer('CopyBaseApp'); 435 | await this._fileManager.copyTo({ 436 | source: { 437 | path: this.getBundledAppDir(), 438 | directory: 'APPLICATION', 439 | }, 440 | target: this.getSnapshotCacheDir(versionId), 441 | }); 442 | timer.end(); 443 | } 444 | 445 | async getCurrentVersion(): Promise { 446 | const versionId = this._savedPreferences.currentVersionId; 447 | if (typeof versionId === 'string') { 448 | return this.getVersionById(versionId); 449 | } 450 | return; 451 | } 452 | 453 | async getVersionById(versionId: string): Promise { 454 | const update = this._savedPreferences.updates[versionId]; 455 | if (!update) { 456 | return; 457 | } 458 | return this._convertToSnapshotInfo(update); 459 | } 460 | 461 | private _convertToSnapshotInfo(update: IAvailableUpdate): ISnapshotInfo { 462 | return { 463 | deploy_uuid: update.versionId, 464 | versionId: update.versionId, 465 | buildId: update.buildId, 466 | channel: update.channel, 467 | binary_version: update.binaryVersionName, 468 | binaryVersion: update.binaryVersionName, 469 | binaryVersionCode: update.binaryVersionCode, 470 | binaryVersionName: update.binaryVersionName 471 | }; 472 | } 473 | 474 | async getAvailableVersions(): Promise { 475 | return Object.keys(this._savedPreferences.updates).map(k => this._convertToSnapshotInfo(this._savedPreferences.updates[k])); 476 | } 477 | 478 | async deleteVersionById(versionId: string): Promise { 479 | const prefs = this._savedPreferences; 480 | 481 | delete prefs.updates[versionId]; 482 | await this._savePrefs(prefs); 483 | 484 | // delete snapshot directory 485 | await this._cleanSnapshotDir(versionId); 486 | 487 | return true; 488 | } 489 | 490 | private getStoredUpdates() { 491 | // get an array of stored updates 492 | const prefs = this._savedPreferences; 493 | const updates = []; 494 | for (const versionId of Object.keys(prefs.updates)) { 495 | updates.push(prefs.updates[versionId]); 496 | } 497 | return updates; 498 | } 499 | 500 | private async cleanupVersions() { 501 | await this.cleanupStaleVersions(); 502 | 503 | const prefs = this._savedPreferences; 504 | // get updates which now have no stale versions 505 | // filter out the current running version 506 | // clean down to Max Updates stored 507 | 508 | const updatesToDelete = this.getStoredUpdates().filter((a) => !this.isUpdateCurrentlyInstalled(a)) 509 | .sort((a, b) => a.lastUsed.localeCompare(b.lastUsed)) 510 | .reverse() 511 | .slice(prefs.maxVersions); 512 | 513 | for (const update of updatesToDelete) { 514 | await this.deleteVersionById(update.versionId); 515 | } 516 | } 517 | 518 | async sync(syncOptions: ISyncOptions = {}, progress?: CallbackFunction): Promise { 519 | const prefs = this._savedPreferences; 520 | 521 | // TODO: Get API override if present? 522 | const updateMethod = syncOptions.updateMethod || prefs.updateMethod; 523 | 524 | const wrappedProgress = progress ? (complete?: number) => { 525 | progress(complete); 526 | } : undefined; 527 | 528 | await this.checkForUpdate(); 529 | 530 | if (prefs.availableUpdate) { 531 | if (prefs.availableUpdate.state === UpdateState.Available) { 532 | await this.downloadUpdate(wrappedProgress); 533 | } 534 | if (prefs.availableUpdate.state === UpdateState.Pending) { 535 | // ignore progress from this since it's trivial 536 | await this.extractUpdate(); 537 | } 538 | if (prefs.availableUpdate.state === UpdateState.Ready && updateMethod === UpdateMethod.AUTO) { 539 | await this.reloadApp(); 540 | } 541 | } 542 | 543 | if (prefs.currentVersionId && prefs.currentBuildId) { 544 | return { 545 | deploy_uuid: prefs.currentVersionId, 546 | versionId: prefs.currentVersionId, 547 | buildId: prefs.currentBuildId, 548 | channel: prefs.channel, 549 | binary_version: prefs.binaryVersionName, 550 | binaryVersion: prefs.binaryVersionName, 551 | binaryVersionCode: prefs.binaryVersionCode, 552 | binaryVersionName: prefs.binaryVersionName 553 | }; 554 | } 555 | return; 556 | } 557 | } 558 | 559 | class FileManager { 560 | async copyTo(options: { source: { directory: string; path: string; } , target: string}) { 561 | return new Promise( (resolve, reject) => { 562 | cordova.exec(resolve, reject, 'IonicCordovaCommon', 'copyTo', [options]); 563 | }); 564 | } 565 | 566 | async remove(path: string) { 567 | return new Promise( (resolve, reject) => { 568 | cordova.exec(resolve, reject, 'IonicCordovaCommon', 'remove', [{target: path}]); 569 | }); 570 | } 571 | 572 | async downloadAndWriteFile(url: string, path: string) { 573 | return new Promise( (resolve, reject) => { 574 | cordova.exec(resolve, reject, 'IonicCordovaCommon', 'downloadFile', [{url, target: path}]); 575 | }); 576 | } 577 | } 578 | 579 | 580 | 581 | class IonicDeploy implements IDeployPluginAPI { 582 | private parent: IPluginBaseAPI; 583 | private delegate: Promise; 584 | private fetchIsAvailable: boolean; 585 | private lastPause = 0; 586 | private minBackgroundDuration = 10; 587 | private disabled = false; 588 | 589 | constructor(parent: IPluginBaseAPI) { 590 | this.parent = parent; 591 | this.delegate = this.initialize(); 592 | this.fetchIsAvailable = typeof(fetch) === 'function'; 593 | document.addEventListener('deviceready', this.onLoad.bind(this)); 594 | } 595 | 596 | async initialize() { 597 | const preferences = await this._initPreferences(); 598 | this.minBackgroundDuration = preferences.minBackgroundDuration; 599 | this.disabled = preferences.disabled || !this.fetchIsAvailable; 600 | const appInfo = await this.parent.getAppDetails(); 601 | const delegate = new IonicDeployImpl(appInfo, preferences); 602 | // Only initialize start the plugin if fetch is available and DisableDeploy preference is false 603 | if (this.disabled) { 604 | let disabledMessage = 'cordova-plugin-ionic has been disabled.'; 605 | if (!this.fetchIsAvailable) { 606 | disabledMessage = 'Fetch is unavailable so ' + disabledMessage; 607 | } 608 | console.warn(disabledMessage); 609 | channel.onIonicProReady.fire(); 610 | } else { 611 | await delegate._handleInitialPreferenceState(); 612 | } 613 | 614 | return delegate; 615 | } 616 | 617 | async onLoad() { 618 | document.addEventListener('pause', this.onPause.bind(this)); 619 | document.addEventListener('resume', this.onResume.bind(this)); 620 | await this.onResume(); 621 | } 622 | 623 | async onPause() { 624 | this.lastPause = Date.now(); 625 | } 626 | 627 | async onResume() { 628 | if (!this.disabled && this.lastPause && this.minBackgroundDuration && Date.now() - this.lastPause > this.minBackgroundDuration * 1000) { 629 | await (await this.delegate)._handleInitialPreferenceState(); 630 | } 631 | } 632 | 633 | async _initPreferences(): Promise { 634 | return new Promise(async (resolve, reject) => { 635 | try { 636 | channel.onNativeReady.subscribe(async () => { 637 | // timeout to let browser proxy to init 638 | window.setTimeout(function () { 639 | cordova.exec(async (prefs: ISavedPreferences) => { 640 | resolve(prefs); 641 | }, reject, 'IonicCordovaCommon', 'getPreferences'); 642 | }, 0); 643 | }); 644 | } catch (e) { 645 | channel.onIonicProReady.fire(); 646 | reject(e.message); 647 | } 648 | }); 649 | } 650 | 651 | async checkForUpdate(): Promise { 652 | if (!this.disabled) { 653 | return (await this.delegate).checkForUpdate(); 654 | } 655 | return {available: false, compatible: false, partial: false}; 656 | } 657 | 658 | async configure(config: IDeployConfig): Promise { 659 | if (!this.disabled) return (await this.delegate).configure(config); 660 | } 661 | 662 | async getConfiguration(): Promise { 663 | return new Promise(async (resolve, reject) => { 664 | try { 665 | cordova.exec(async (prefs: ISavedPreferences) => { 666 | if (prefs.availableUpdate) { 667 | delete prefs.availableUpdate; 668 | } 669 | if (prefs.updates) { 670 | delete prefs.updates; 671 | } 672 | resolve(prefs); 673 | }, reject, 'IonicCordovaCommon', 'getPreferences'); 674 | } catch (e) { 675 | reject(e.message); 676 | } 677 | }); 678 | } 679 | 680 | async deleteVersionById(version: string): Promise { 681 | if (!this.disabled) return (await this.delegate).deleteVersionById(version); 682 | return true; 683 | } 684 | 685 | async downloadUpdate(progress?: CallbackFunction): Promise { 686 | if (!this.disabled) return (await this.delegate).downloadUpdate(progress); 687 | return false; 688 | } 689 | 690 | async extractUpdate(progress?: CallbackFunction): Promise { 691 | if (!this.disabled) return (await this.delegate).extractUpdate(progress); 692 | return false; 693 | } 694 | 695 | async getAvailableVersions(): Promise { 696 | if (!this.disabled) return (await this.delegate).getAvailableVersions(); 697 | return []; 698 | } 699 | 700 | async getCurrentVersion(): Promise { 701 | if (!this.disabled) return (await this.delegate).getCurrentVersion(); 702 | return; 703 | } 704 | 705 | async getVersionById(versionId: string): Promise { 706 | if (!this.disabled) return (await this.delegate).getVersionById(versionId); 707 | return; 708 | } 709 | 710 | async reloadApp(): Promise { 711 | if (!this.disabled) return (await this.delegate).reloadApp(); 712 | return false; 713 | } 714 | 715 | async sync(syncOptions: ISyncOptions = {}, progress?: CallbackFunction): Promise { 716 | if (!this.disabled) return (await this.delegate).sync(syncOptions, progress); 717 | return; 718 | } 719 | } 720 | 721 | 722 | /** 723 | * BASE API 724 | * 725 | * All features of the Ionic Cordova plugin are registered here, along with some low level error tracking features used 726 | * by the monitoring service. 727 | */ 728 | class IonicCordova implements IPluginBaseAPI { 729 | 730 | public deploy: IDeployPluginAPI; 731 | 732 | constructor() { 733 | this.deploy = new IonicDeploy(this); 734 | } 735 | 736 | 737 | getAppInfo(success: CallbackFunction, failure: CallbackFunction) { 738 | console.warn('This function has been deprecated in favor of IonicCordova.getAppDetails.'); 739 | this.getAppDetails().then( 740 | result => success(result), 741 | err => { 742 | typeof err === 'string' ? failure(err) : failure(err.message); 743 | } 744 | ); 745 | } 746 | 747 | async getAppDetails(): Promise { 748 | return new Promise( (resolve, reject) => { 749 | cordova.exec(resolve, reject, 'IonicCordovaCommon', 'getAppInfo'); 750 | }); 751 | } 752 | } 753 | 754 | class Timer { 755 | name: string; 756 | startTime: Date; 757 | lastTime: Date; 758 | constructor(name: string) { 759 | this.name = name; 760 | this.startTime = new Date(); 761 | this.lastTime = new Date(); 762 | console.log(`Starting IonicTimer ${this.name}`); 763 | } 764 | 765 | end(extraLog?: string) { 766 | console.log(`Finished IonicTimer ${this.name} in ${(new Date().getTime() - this.startTime.getTime()) / 1000} seconds.`); 767 | if (extraLog) { 768 | console.log(`IonicTimer extra ${extraLog}`); 769 | } 770 | } 771 | 772 | diff(message?: string) { 773 | console.log(`Message: ${message} Diff IonicTimer ${this.name} in ${(new Date().getTime() - this.lastTime.getTime()) / 1000} seconds.`); 774 | this.lastTime = new Date(); 775 | } 776 | } 777 | 778 | const instance = new IonicCordova(); 779 | export = instance; 780 | -------------------------------------------------------------------------------- /www/definitions.ts: -------------------------------------------------------------------------------- 1 | import { ICurrentConfig } from './IonicCordova'; 2 | 3 | export interface IAvailableUpdate { 4 | binaryVersionName: string; 5 | binaryVersionCode: string; 6 | channel: string; 7 | lastUsed: string; 8 | state: string; 9 | url: string; 10 | versionId: string; 11 | buildId: string; 12 | } 13 | 14 | export interface ISavedPreferences extends ICurrentConfig { 15 | availableUpdate?: IAvailableUpdate; 16 | updates: { [versionId: string]: IAvailableUpdate }; 17 | } 18 | 19 | export interface UpdateInfo { 20 | versionId: string; 21 | path: string; 22 | } 23 | 24 | export interface ManifestFileEntry { 25 | integrity: string; 26 | href: string; 27 | size: number; 28 | } 29 | 30 | export interface FetchManifestResp { 31 | manifestJson: ManifestFileEntry[]; 32 | fileBaseUrl: string; 33 | } 34 | -------------------------------------------------------------------------------- /www/guards.ts: -------------------------------------------------------------------------------- 1 | import { IDeployConfig } from './IonicCordova'; 2 | 3 | export function isPluginConfig(o: object): o is IDeployConfig { 4 | const obj = o; 5 | const allowedKeys = ['appId', 'channel', 'disabled', 'host', 'maxVersions', 'minBackgroundDuration', 'updateMethod']; 6 | if (!obj) return false; 7 | for (const key in obj) { 8 | if (allowedKeys.indexOf(key) === -1) { 9 | return false; 10 | } 11 | } 12 | return obj && 13 | (obj.appId === undefined || typeof obj.appId === 'string') && 14 | (obj.channel === undefined || typeof obj.channel === 'string') && 15 | (obj.debug === undefined || typeof obj.debug === 'string') && 16 | (obj.updateMethod === undefined || typeof obj.updateMethod === 'string') && 17 | (obj.maxVersions === undefined || typeof obj.maxVersions === 'number') && 18 | (obj.minBackgroundDuration === undefined || typeof obj.minBackgroundDuration === 'number') && 19 | (obj.host === undefined || typeof obj.host === 'string'); 20 | } 21 | -------------------------------------------------------------------------------- /www/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CallbackFunction, 3 | CheckForUpdateResponse, 4 | ICurrentConfig, 5 | IDeployConfig, 6 | ISnapshotInfo, 7 | ISyncOptions, 8 | } from './IonicCordova'; 9 | /** 10 | * @hidden 11 | */ 12 | const deviceready = new Promise((resolve, rejects) => { 13 | document.addEventListener('deviceready', () => { 14 | if (window.IonicCordova) { 15 | return resolve(window.IonicCordova.deploy); 16 | } 17 | return rejects('cordova-plugin-ionic not found. Are you sure you installed it?'); 18 | }); 19 | }); 20 | 21 | export class DeployClass implements IDeployPluginAPI { 22 | 23 | async configure(config: IDeployConfig) { 24 | const deploy = await deviceready; 25 | return deploy.configure(config); 26 | } 27 | 28 | async getConfiguration(): Promise { 29 | const deploy = await deviceready; 30 | return deploy.getConfiguration(); 31 | } 32 | 33 | async checkForUpdate(): Promise { 34 | const deploy = await deviceready; 35 | return deploy.checkForUpdate(); 36 | } 37 | 38 | async downloadUpdate(progress?: CallbackFunction) { 39 | const deploy = await deviceready; 40 | return deploy.downloadUpdate(progress); 41 | } 42 | 43 | async extractUpdate(progress?: CallbackFunction) { 44 | const deploy = await deviceready; 45 | return deploy.extractUpdate(progress); 46 | } 47 | 48 | async reloadApp() { 49 | const deploy = await deviceready; 50 | return deploy.reloadApp(); 51 | } 52 | 53 | async sync(options: ISyncOptions, progress?: CallbackFunction) { 54 | const deploy = await deviceready; 55 | return deploy.sync(options, progress); 56 | } 57 | 58 | async getCurrentVersion(): Promise { 59 | const deploy = await deviceready; 60 | return deploy.getCurrentVersion(); 61 | } 62 | 63 | async getAvailableVersions() { 64 | const deploy = await deviceready; 65 | return deploy.getAvailableVersions(); 66 | } 67 | 68 | async deleteVersionById(versionId: string) { 69 | const deploy = await deviceready; 70 | return deploy.deleteVersionById(versionId); 71 | } 72 | 73 | async getVersionById(versionId: string) { 74 | const deploy = await deviceready; 75 | return deploy.getVersionById(versionId); 76 | } 77 | } 78 | 79 | export const Deploy = new DeployClass(); -------------------------------------------------------------------------------- /www/ngx/index.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { DeployClass } from '../index'; 3 | 4 | @Injectable() 5 | export class Deploy extends DeployClass { } 6 | --------------------------------------------------------------------------------