From ad20e57543dbf452d12322344b947a1f6e3e08a9 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 29 Oct 2015 18:12:24 -0700 Subject: [PATCH] Added sync sample and updated the documentation. --- README.md | 57 ++++++++- bin/www/codePush.js | 38 +++--- bin/www/syncStatus.js | 2 +- {sample => samples/advanced}/README.md | 4 +- samples/advanced/config.xml | 38 ++++++ .../advanced}/www/css/index.css | 0 {sample => samples/advanced}/www/img/logo.png | Bin {sample => samples/advanced}/www/index.html | 0 {sample => samples/advanced}/www/js/index.js | 0 samples/basic/README.md | 7 ++ {sample => samples/basic}/config.xml | 4 +- samples/basic/www/css/index.css | 115 ++++++++++++++++++ samples/basic/www/img/logo.png | Bin 0 -> 21814 bytes samples/basic/www/index.html | 50 ++++++++ samples/basic/www/js/index.js | 80 ++++++++++++ test/serverUtil.ts | 2 +- .../template/www/js/scenarioSyncWithRevert.js | 3 +- test/template/www/js/updateSync.js | 1 + test/test.ts | 20 +-- typings/codePush.d.ts | 24 +++- www/codePush.ts | 41 ++++--- www/syncStatus.ts | 2 +- 22 files changed, 423 insertions(+), 65 deletions(-) rename {sample => samples/advanced}/README.md (55%) create mode 100644 samples/advanced/config.xml rename {sample => samples/advanced}/www/css/index.css (100%) rename {sample => samples/advanced}/www/img/logo.png (100%) rename {sample => samples/advanced}/www/index.html (100%) rename {sample => samples/advanced}/www/js/index.js (100%) create mode 100644 samples/basic/README.md rename {sample => samples/basic}/config.xml (89%) create mode 100644 samples/basic/www/css/index.css create mode 100644 samples/basic/www/img/logo.png create mode 100644 samples/basic/www/index.html create mode 100644 samples/basic/www/js/index.js diff --git a/README.md b/README.md index 62561b25..d4fce25a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ A Cordova application's assets (HTML, JavaScript, CSS files and other resources) CodePush is here to simplify this process by allowing you to instantly update your application's assets without having to submit a new update to the store. We do this by packaging the application assets in a zip archive and sending it to the CodePush server. In the application, we install and persist the update. Then, since these are all web assets, the application will just reload from the updated package location. We store the update packages in the internal storage of the device. -For an easy way to get started, please see our [sample application](/sample) and our [getting started guide](#getting-started). +For an easy way to get started, please see our [sample applications](/samples) and our [getting started guide](#getting-started). ## Compiling sources & contributing @@ -31,10 +31,12 @@ The JavaScript code in this plugin is compiled from TypeScript. Please see [this - __[checkForUpdate](#codepushcheckforupdate)__: Checks the server for update packages. - __[notifyApplicationReady](#codepushnotifyapplicationready)__: Notifies the plugin that the update operation succeeded. - __[getCurrentPackage](#codepushgetcurrentpackage)__: Gets information about the currently applied package. +- __[sync](#codepushsync)__: Convenience function for installing updates in one call. ## Objects - __[LocalPackage](#localpackage)__: Contains information about a locally installed package. - __[RemotePackage](#remotepackage)__: Contains information about an update package available for download. +- __[SyncStatus](#syncStatus)__: Defines the possible result statuses of the [sync](#codepushsync) operation. ## Getting started - Add the plugin to your application. @@ -57,7 +59,7 @@ The JavaScript code in this plugin is compiled from TypeScript. Please see [this ```xml ``` -- You are now ready to use the plugin in the application code. See the sample app for an example and the methods description for more details. +- You are now ready to use the plugin in the application code. See the [sample applications](/samples) for a examples and the methods description for more details. ## Create an application update package You can create an update by simply zipping and deploying your platform's www folder. The [CodePush CLI](https://github.com/Microsoft/code-push/tree/master/cli) has a ```deploy``` command for this. @@ -167,6 +169,14 @@ Contains details about an update package that is available for download. ``` - __abortDownload(abortSuccess, abortError)__: Aborts the current download session, if any. +## SyncStatus +Defines the possible result statuses of the [sync](#codepushsync) operation. +### Properties +- __UP_TO_DATE__: The application is up to date. (number) +- __APPLY_SUCCESS__: An update is available, it has been downloaded, unzipped and copied to the deployment folder. After the completion of the callback invoked with SyncStatus.APPLY_SUCCESS, the application will be reloaded with the updated code and resources. +- __UPDATE_IGNORED__: An optional update is available, but the user declined to install it. The update was not downloaded. +- __ERROR__: An error happened during the sync operation. This might be an error while communicating with the server, downloading or unziping the update. The console logs should contain more information about what happened. No update has been applied in this case. + ## codePush.checkForUpdate Queries the CodePush server for updates. ```javascript @@ -276,3 +286,46 @@ var app = { } } ``` + +## codePush.sync +```javascript +codePush.sync(syncCallback, syncOptions); +``` +Convenience method for installing updates in one method call. +This method is provided for simplicity, and its behavior can be replicated by using window.codePush.checkForUpdate(), RemotePackage's download() and LocalPackage's apply() methods. +The algorithm of this method is the following: + - Checks for an update on the CodePush server. + - If an update is available + - If the update is mandatory and the alertMessage is set in options, the user will be informed that the application will be updated to the latest version. + The update package will then be downloaded and applied. + - If the update is not mandatory and the confirmMessage is set in options, the user will be asked if they want to update to the latest version. + If they decline, the syncCallback will be invoked with SyncStatus.UPDATE_IGNORED. + - Otherwise, the update package will be downloaded and applied with no user interaction. + - If no update is available on the server, the syncCallback will be invoked with the SyncStatus.UP_TO_DATE. + - If an error ocurrs during checking for update, downloading or applying it, the syncCallback will be invoked with the SyncStatus.ERROR. + +- __syncCallback__: Optional callback to be called with the status of the sync operation. The callback will be called only once, and the possible statuses are defined by the SyncStatus enum. +- __syncOptions__: Optional SyncOptions parameter configuring the behavior of the sync operation. + +### Example +```javascript +window.codePush.sync(function (syncStatus) { + switch (syncStatus) { + case SyncStatus.APPLY_SUCCESS: + console.log("The update was applied successfully. This is the last callback before the application is reloaded with the updated content."); + /* Don't continue app initialization, the application will refresh after this return. */ + return; + case SyncStatus.UP_TO_DATE: + app.displayMessage("The application is up to date."); + break; + case SyncStatus.UPDATE_IGNORED: + app.displayMessage("The user decided not to install the optional update."); + break; + case SyncStatus.ERROR: + app.displayMessage("An error ocurred while checking for updates"); + break; + } + + // continue application initialization +} +``` diff --git a/bin/www/codePush.js b/bin/www/codePush.js index 3b0b67c0..a1c6ec4c 100644 --- a/bin/www/codePush.js +++ b/bin/www/codePush.js @@ -89,13 +89,19 @@ var CodePush = (function () { syncOptions = this.getDefaultSyncOptions(); } if (syncOptions.mandatoryUpdateMessage) { - syncOptions.mandatoryUpdateContinueButtonLabel = syncOptions.mandatoryUpdateContinueButtonLabel || this.getDefaultSyncOptions().mandatoryUpdateContinueButtonLabel; - syncOptions.dialogTitle = syncOptions.dialogTitle || this.getDefaultSyncOptions().dialogTitle; + syncOptions.mandatoryContinueButtonLabel = syncOptions.mandatoryContinueButtonLabel || this.getDefaultSyncOptions().mandatoryContinueButtonLabel; + syncOptions.updateTitle = syncOptions.updateTitle || this.getDefaultSyncOptions().updateTitle; } if (syncOptions.optionalUpdateMessage) { - syncOptions.optionalUpdateConfirmButtonLabel = syncOptions.optionalUpdateConfirmButtonLabel || this.getDefaultSyncOptions().optionalUpdateConfirmButtonLabel; - syncOptions.optionalUpdateCancelButtonLabel = syncOptions.optionalUpdateCancelButtonLabel || this.getDefaultSyncOptions().optionalUpdateCancelButtonLabel; - syncOptions.dialogTitle = syncOptions.dialogTitle || this.getDefaultSyncOptions().dialogTitle; + syncOptions.optionalInstallButtonLabel = syncOptions.optionalInstallButtonLabel || this.getDefaultSyncOptions().optionalInstallButtonLabel; + syncOptions.optionalIgnoreButtonLabel = syncOptions.optionalIgnoreButtonLabel || this.getDefaultSyncOptions().optionalIgnoreButtonLabel; + syncOptions.updateTitle = syncOptions.updateTitle || this.getDefaultSyncOptions().updateTitle; + } + if (typeof syncOptions.ignoreFailedUpdates !== typeof true) { + syncOptions.ignoreFailedUpdates = true; + } + if (syncOptions.appendReleaseDescription && !syncOptions.descriptionPrefix) { + syncOptions.descriptionPrefix = this.getDefaultSyncOptions().descriptionPrefix; } window.codePush.notifyApplicationReady(); var onError = function (error) { @@ -117,9 +123,10 @@ var CodePush = (function () { } else { if (remotePackage.isMandatory && syncOptions.mandatoryUpdateMessage) { - navigator.notification.alert(syncOptions.mandatoryUpdateMessage, function () { downloadAndInstallUpdate(remotePackage); }, syncOptions.dialogTitle, syncOptions.mandatoryUpdateContinueButtonLabel); + var message = syncOptions.appendReleaseDescription ? syncOptions.mandatoryUpdateMessage + syncOptions.descriptionPrefix + remotePackage.description : syncOptions.mandatoryUpdateMessage; + navigator.notification.alert(message, function () { downloadAndInstallUpdate(remotePackage); }, syncOptions.updateTitle, syncOptions.mandatoryContinueButtonLabel); } - else if (!remotePackage.isMandatory && syncOptions && syncOptions.optionalUpdateMessage) { + else if (!remotePackage.isMandatory && syncOptions.optionalUpdateMessage) { var optionalUpdateCallback = function (buttonIndex) { switch (buttonIndex) { case 1: @@ -127,11 +134,12 @@ var CodePush = (function () { break; case 2: default: - syncCallback && syncCallback(SyncStatus.USER_DECLINED); + syncCallback && syncCallback(SyncStatus.UPDATE_IGNORED); break; } }; - navigator.notification.confirm(syncOptions.optionalUpdateMessage, optionalUpdateCallback, syncOptions.dialogTitle, [syncOptions.optionalUpdateConfirmButtonLabel, syncOptions.optionalUpdateCancelButtonLabel]); + var message = syncOptions.appendReleaseDescription ? syncOptions.optionalUpdateMessage + syncOptions.descriptionPrefix + remotePackage.description : syncOptions.optionalUpdateMessage; + navigator.notification.confirm(message, optionalUpdateCallback, syncOptions.updateTitle, [syncOptions.optionalInstallButtonLabel, syncOptions.optionalIgnoreButtonLabel]); } else { downloadAndInstallUpdate(remotePackage); @@ -143,14 +151,16 @@ var CodePush = (function () { CodePush.prototype.getDefaultSyncOptions = function () { if (!CodePush.DefaultSyncOptions) { CodePush.DefaultSyncOptions = { - dialogTitle: "Update", + updateTitle: "Update", mandatoryUpdateMessage: "You will be updated to the latest version.", - mandatoryUpdateContinueButtonLabel: "Continue", + mandatoryContinueButtonLabel: "Continue", optionalUpdateMessage: "An update is available. Would you like to install it?", - optionalUpdateConfirmButtonLabel: "Install", - optionalUpdateCancelButtonLabel: "Cancel", + optionalInstallButtonLabel: "Install", + optionalIgnoreButtonLabel: "Ignore", rollbackTimeout: 0, - ignoreFailedUpdates: true + ignoreFailedUpdates: true, + appendReleaseDescription: false, + descriptionPrefix: " Description: " }; } return CodePush.DefaultSyncOptions; diff --git a/bin/www/syncStatus.js b/bin/www/syncStatus.js index 52ade88b..3783ccda 100644 --- a/bin/www/syncStatus.js +++ b/bin/www/syncStatus.js @@ -14,7 +14,7 @@ var SyncStatus; (function (SyncStatus) { SyncStatus[SyncStatus["UP_TO_DATE"] = 0] = "UP_TO_DATE"; SyncStatus[SyncStatus["APPLY_SUCCESS"] = 1] = "APPLY_SUCCESS"; - SyncStatus[SyncStatus["USER_DECLINED"] = 2] = "USER_DECLINED"; + SyncStatus[SyncStatus["UPDATE_IGNORED"] = 2] = "UPDATE_IGNORED"; SyncStatus[SyncStatus["ERROR"] = 3] = "ERROR"; })(SyncStatus || (SyncStatus = {})); module.exports = SyncStatus; diff --git a/sample/README.md b/samples/advanced/README.md similarity index 55% rename from sample/README.md rename to samples/advanced/README.md index dc9904ef..b8422d4b 100644 --- a/sample/README.md +++ b/samples/advanced/README.md @@ -1,6 +1,6 @@ -# Cordova CodePush Sample App +# Cordova CodePush Sample App - Advanced -This is a sample application demonstrating one way you could integrate CodePush in your Cordova application. All the CodePush specific code is found in [index.js](/sample/www/js/index.js). The CodePush configuration is found in [config.xml](/sample/config.xml). +This is a sample application demonstrating a more advanced way you could integrate CodePush in your Cordova application. All the CodePush specific code is found in [index.js](/sample/www/js/index.js). The CodePush configuration is found in [config.xml](/sample/config.xml). When the application loads, on the `deviceready` event, we poll the CodePush server for an update. If an update is available, we prompt the user to install it. If the user approves it, the update is installed and the application is reloaded. diff --git a/samples/advanced/config.xml b/samples/advanced/config.xml new file mode 100644 index 00000000..e0a750fc --- /dev/null +++ b/samples/advanced/config.xml @@ -0,0 +1,38 @@ + + + CodePushAdvanced + + A sample Apache Cordova application that uses the CodePush service. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/www/css/index.css b/samples/advanced/www/css/index.css similarity index 100% rename from sample/www/css/index.css rename to samples/advanced/www/css/index.css diff --git a/sample/www/img/logo.png b/samples/advanced/www/img/logo.png similarity index 100% rename from sample/www/img/logo.png rename to samples/advanced/www/img/logo.png diff --git a/sample/www/index.html b/samples/advanced/www/index.html similarity index 100% rename from sample/www/index.html rename to samples/advanced/www/index.html diff --git a/sample/www/js/index.js b/samples/advanced/www/js/index.js similarity index 100% rename from sample/www/js/index.js rename to samples/advanced/www/js/index.js diff --git a/samples/basic/README.md b/samples/basic/README.md new file mode 100644 index 00000000..6c66384e --- /dev/null +++ b/samples/basic/README.md @@ -0,0 +1,7 @@ +# Cordova CodePush Sample App - Basic + +This is a sample application demonstrating the CodePush sync operation. All the CodePush specific code is found in [index.js](/sample/www/js/index.js). The CodePush configuration is found in [config.xml](/sample/config.xml). + +When the application loads, on the `deviceready` event, we invoke sync. This checks for an update, and if one is available, the user will be prompted to install it. Once the user accepts it, the update is installed and the application reloaded. See SyncOptions in our documentation for customizing the sync behavior. + +For more information on how to get started see our [Getting Started](https://github.com/Microsoft/cordova-plugin-code-push#getting-started) section. diff --git a/sample/config.xml b/samples/basic/config.xml similarity index 89% rename from sample/config.xml rename to samples/basic/config.xml index ae1496f1..fb67b947 100644 --- a/sample/config.xml +++ b/samples/basic/config.xml @@ -1,6 +1,6 @@ - - HelloCodePush + + CodePushBasic A sample Apache Cordova application that uses the CodePush service. diff --git a/samples/basic/www/css/index.css b/samples/basic/www/css/index.css new file mode 100644 index 00000000..51daa797 --- /dev/null +++ b/samples/basic/www/css/index.css @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +* { + -webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */ +} + +body { + -webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */ + -webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */ + -webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */ + background-color:#E4E4E4; + background-image:linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%); + background-image:-webkit-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%); + background-image:-ms-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%); + background-image:-webkit-gradient( + linear, + left top, + left bottom, + color-stop(0, #A7A7A7), + color-stop(0.51, #E4E4E4) + ); + background-attachment:fixed; + font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif; + font-size:12px; + height:100%; + margin:0px; + padding:0px; + text-transform:uppercase; + width:100%; +} + +/* Portrait layout (default) */ +.app { + background:url(../img/logo.png) no-repeat center top; /* 170px x 200px */ + position:absolute; /* position in the center of the screen */ + left:50%; + top:50%; + height:50px; /* text area height */ + width:225px; /* text area width */ + text-align:center; + padding:180px 0px 0px 0px; /* image height is 200px (bottom 20px are overlapped with text) */ + margin:-115px 0px 0px -112px; /* offset vertical: half of image height and text area height */ + /* offset horizontal: half of text area width */ +} + +/* Landscape layout (with min-width) */ +@media screen and (min-aspect-ratio: 1/1) and (min-width:400px) { + .app { + background-position:left center; + padding:75px 0px 75px 170px; /* padding-top + padding-bottom + text area = image height */ + margin:-90px 0px 0px -198px; /* offset vertical: half of image height */ + /* offset horizontal: half of image width and text area width */ + } +} + +h1 { + font-size:24px; + font-weight:normal; + margin:0px; + overflow:visible; + padding:0px; + text-align:center; +} + +.event { + border-radius:4px; + -webkit-border-radius:4px; + color:#FFFFFF; + font-size:12px; + margin:0px 30px; + padding:2px 0px; +} + +.event.listening { + background-color:#333333; + display:block; +} + +.event.received { + background-color:#4B946A; + display:none; +} + +@keyframes fade { + from { opacity: 1.0; } + 50% { opacity: 0.4; } + to { opacity: 1.0; } +} + +@-webkit-keyframes fade { + from { opacity: 1.0; } + 50% { opacity: 0.4; } + to { opacity: 1.0; } +} + +.blink { + animation:fade 3000ms infinite; + -webkit-animation:fade 3000ms infinite; +} diff --git a/samples/basic/www/img/logo.png b/samples/basic/www/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9519e7dd78adb6e44548c08510a7bf02442a7697 GIT binary patch literal 21814 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 + + + + + + + + + + Hello World + + +
+

Hello, CodePush (Version 1)

+ +
+ + + + diff --git a/samples/basic/www/js/index.js b/samples/basic/www/js/index.js new file mode 100644 index 00000000..b661fea7 --- /dev/null +++ b/samples/basic/www/js/index.js @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +var app = { + // Application Constructor + initialize: function () { + this.bindEvents(); + }, + // Bind Event Listeners + // + // Bind any events that are required on startup. Common events are: + // 'load', 'deviceready', 'offline', and 'online'. + bindEvents: function () { + document.addEventListener('deviceready', this.onDeviceReady, false); + }, + // deviceready Event Handler + // + // The scope of 'this' is the event. In order to call the 'receivedEvent' + // function, we must explicitly call 'app.receivedEvent(...);' + onDeviceReady: function () { + /* Invoke sync with the default options, which enables user interaction. + For customizing the sync behavior, see SyncOptions in the CodePush documentation. */ + window.codePush.sync(function (syncStatus) { + switch (syncStatus) { + case SyncStatus.APPLY_SUCCESS: + console.log("The update was applied successfully. This is the last callback before the application is reloaded with the updated content."); + /* Don't continue app initialization, the application will refresh after this return. */ + return; + case SyncStatus.UP_TO_DATE: + app.displayMessage("The application is up to date."); + break; + case SyncStatus.UPDATE_IGNORED: + app.displayMessage("The user decided not to install the optional update."); + break; + case SyncStatus.ERROR: + app.displayMessage("An error ocurred while checking for updates"); + break; + } + + // continue application initialization + app.receivedEvent('deviceready'); + }); + }, + // Update DOM on a Received Event + receivedEvent: function (id) { + var parentElement = document.getElementById(id); + var listeningElement = parentElement.querySelector('.listening'); + var receivedElement = parentElement.querySelector('.received'); + + listeningElement.setAttribute('style', 'display:none;'); + receivedElement.setAttribute('style', 'display:block;'); + + console.log('Received Event: ' + id); + }, + // Displays an alert dialog containing a message. + displayMessage: function (message) { + navigator.notification.alert( + message, + null, + 'CodePush', + 'OK'); + } +}; + +app.initialize(); \ No newline at end of file diff --git a/test/serverUtil.ts b/test/serverUtil.ts index a275b757..f27b1829 100644 --- a/test/serverUtil.ts +++ b/test/serverUtil.ts @@ -35,7 +35,7 @@ export class TestMessage { public static SYNC_UP_TO_DATE = 0; public static SYNC_APPLY_SUCCESS = 1; - public static SYNC_USER_DECLINED = 2; + public static SYNC_UPDATE_IGNORED = 2; public static SYNC_ERROR = 3; } diff --git a/test/template/www/js/scenarioSyncWithRevert.js b/test/template/www/js/scenarioSyncWithRevert.js index f09bbb0f..e24f2961 100644 --- a/test/template/www/js/scenarioSyncWithRevert.js +++ b/test/template/www/js/scenarioSyncWithRevert.js @@ -21,8 +21,7 @@ var app = { { mandatoryUpdateMessage: undefined, optionalUpdateMessage: undefined, - rollbackTimeout: 3000, - ignoreFailedUpdates: true + rollbackTimeout: 3000 }); }, sendTestMessage: function (message, args) { diff --git a/test/template/www/js/updateSync.js b/test/template/www/js/updateSync.js index 31a98f74..13e6da43 100644 --- a/test/template/www/js/updateSync.js +++ b/test/template/www/js/updateSync.js @@ -13,6 +13,7 @@ var app = { receivedDeviceReady: function () { document.getElementById("deviceready").innerText = "Device is ready (scenario - update sync)"; console.log('Received Event: deviceready'); + app.sendTestMessage("DEVICE_READY_AFTER_UPDATE"); /* invoke sync with UI options such that the update will not be installed */ window.codePush.sync(function (status) { app.sendTestMessage("SYNC_STATUS", [status]); diff --git a/test/test.ts b/test/test.ts index 06eeb5d4..51558644 100644 --- a/test/test.ts +++ b/test/test.ts @@ -536,31 +536,13 @@ describe("window.codePush", function() { mockResponse = { updateInfo: getMockResponse(true) }; - /* create an update */ - setupUpdateProject(UpdateNotifyApplicationReady, "Update 1 (good update)") - .then(projectManager.createUpdateArchive.bind(undefined, updatesDirectory, targetPlatform)) - .then((updatePath: string) => { - var deferred = Q.defer(); - mockUpdatePackagePath = updatePath; - testMessageCallback = verifyMessages([new su.AppMessage(su.TestMessage.SYNC_STATUS, [su.TestMessage.SYNC_APPLY_SUCCESS]), su.TestMessage.DEVICE_READY_AFTER_UPDATE, su.TestMessage.NOTIFY_APP_READY_SUCCESS, su.TestMessage.APPLICATION_NOT_REVERTED], deferred); - console.log("Running project..."); - projectManager.runPlatform(testRunDirectory, targetPlatform, true, targetEmulator); - return deferred.promise; - }) - .done(done, done); - }); - - it("sync should call notifyApplicationReady()", function(done) { - - mockResponse = { updateInfo: getMockResponse(false) }; - /* create an update */ setupUpdateProject(UpdateSync, "Update 1 (good update)") .then(projectManager.createUpdateArchive.bind(undefined, updatesDirectory, targetPlatform)) .then((updatePath: string) => { var deferred = Q.defer(); mockUpdatePackagePath = updatePath; - testMessageCallback = verifyMessages([new su.AppMessage(su.TestMessage.SYNC_STATUS, [su.TestMessage.SYNC_APPLY_SUCCESS]), su.TestMessage.APPLICATION_NOT_REVERTED], deferred); + testMessageCallback = verifyMessages([new su.AppMessage(su.TestMessage.SYNC_STATUS, [su.TestMessage.SYNC_APPLY_SUCCESS]), su.TestMessage.DEVICE_READY_AFTER_UPDATE, su.TestMessage.APPLICATION_NOT_REVERTED], deferred); console.log("Running project..."); projectManager.runPlatform(testRunDirectory, targetPlatform, true, targetEmulator); return deferred.promise; diff --git a/typings/codePush.d.ts b/typings/codePush.d.ts index 27de0a80..5b25f717 100644 --- a/typings/codePush.d.ts +++ b/typings/codePush.d.ts @@ -192,7 +192,7 @@ interface CodePushCordovaPlugin { * - If the update is mandatory and the alertMessage is set in options, the user will be informed that the application will be updated to the latest version. * The update package will then be downloaded and applied. * - If the update is not mandatory and the confirmMessage is set in options, the user will be asked if they want to update to the latest version. - * If they decline, the syncCallback will be invoked with SyncStatus.USER_DECLINED. + * If they decline, the syncCallback will be invoked with SyncStatus.UPDATE_IGNORED. * - Otherwise, the update package will be downloaded and applied with no user interaction. * - If no update is available on the server, or if a previously rolled back update is available and the ignoreFailedUpdates is set to true, the syncCallback will be invoked with the SyncStatus.UP_TO_DATE. * - If an error ocurrs during checking for update, downloading or applying it, the syncCallback will be invoked with the SyncStatus.ERROR. @@ -223,7 +223,7 @@ declare enum SyncStatus { /** * An optional update is available, but the user declined to install it. The update was not downloaded. */ - USER_DECLINED, + UPDATE_IGNORED, /** * An error happened during the sync operation. This might be an error while communicating with the server, downloading or unziping the update. @@ -263,22 +263,34 @@ interface SyncOptions { * The title of the dialog box used for interacting with the user in case of a mandatory or optional update. * This title will only be used if at least one of mandatoryUpdateMessage or optionalUpdateMessage options are set. */ - dialogTitle?: string; + updateTitle?: string; /** * The label of the confirmation button in case of an optional update. */ - optionalUpdateConfirmButtonLabel?: string; + optionalInstallButtonLabel?: string; /** * The label of the cancel button in case of an optional update. */ - optionalUpdateCancelButtonLabel?: string; + optionalIgnoreButtonLabel?: string; /** * The label of the continue button in case of a mandatory update. */ - mandatoryUpdateContinueButtonLabel?: string + mandatoryContinueButtonLabel?: string; + + /** + * Flag indicating if the update description provided by the CodePush server should be displayed + */ + appendReleaseDescription?: boolean; + + /** + * Optional prefix to add to the release description. + */ + descriptionPrefix?: string; + + } /** diff --git a/www/codePush.ts b/www/codePush.ts index 4591fb21..eb39464f 100644 --- a/www/codePush.ts +++ b/www/codePush.ts @@ -126,7 +126,7 @@ class CodePush implements CodePushCordovaPlugin { * - If the update is mandatory and the alertMessage is set in options, the user will be informed that the application will be updated to the latest version. * The update package will then be downloaded and applied. * - If the update is not mandatory and the confirmMessage is set in options, the user will be asked if they want to update to the latest version. - * If they decline, the syncCallback will be invoked with SyncStatus.USER_DECLINED. + * If they decline, the syncCallback will be invoked with SyncStatus.UPDATE_IGNORED. * - Otherwise, the update package will be downloaded and applied with no user interaction. * - If no update is available on the server, the syncCallback will be invoked with the SyncStatus.UP_TO_DATE. * - If an error ocurrs during checking for update, downloading or applying it, the syncCallback will be invoked with the SyncStatus.ERROR. @@ -145,13 +145,20 @@ class CodePush implements CodePushCordovaPlugin { /* Some options were specified */ if (syncOptions.mandatoryUpdateMessage) { - syncOptions.mandatoryUpdateContinueButtonLabel = syncOptions.mandatoryUpdateContinueButtonLabel || this.getDefaultSyncOptions().mandatoryUpdateContinueButtonLabel; - syncOptions.dialogTitle = syncOptions.dialogTitle || this.getDefaultSyncOptions().dialogTitle; + syncOptions.mandatoryContinueButtonLabel = syncOptions.mandatoryContinueButtonLabel || this.getDefaultSyncOptions().mandatoryContinueButtonLabel; + syncOptions.updateTitle = syncOptions.updateTitle || this.getDefaultSyncOptions().updateTitle; } if (syncOptions.optionalUpdateMessage) { - syncOptions.optionalUpdateConfirmButtonLabel = syncOptions.optionalUpdateConfirmButtonLabel || this.getDefaultSyncOptions().optionalUpdateConfirmButtonLabel; - syncOptions.optionalUpdateCancelButtonLabel = syncOptions.optionalUpdateCancelButtonLabel || this.getDefaultSyncOptions().optionalUpdateCancelButtonLabel; - syncOptions.dialogTitle = syncOptions.dialogTitle || this.getDefaultSyncOptions().dialogTitle; + syncOptions.optionalInstallButtonLabel = syncOptions.optionalInstallButtonLabel || this.getDefaultSyncOptions().optionalInstallButtonLabel; + syncOptions.optionalIgnoreButtonLabel = syncOptions.optionalIgnoreButtonLabel || this.getDefaultSyncOptions().optionalIgnoreButtonLabel; + syncOptions.updateTitle = syncOptions.updateTitle || this.getDefaultSyncOptions().updateTitle; + } + if (typeof syncOptions.ignoreFailedUpdates !== typeof true) { + /* Ignoring failed updates by default. */ + syncOptions.ignoreFailedUpdates = true; + } + if (syncOptions.appendReleaseDescription && !syncOptions.descriptionPrefix) { + syncOptions.descriptionPrefix = this.getDefaultSyncOptions().descriptionPrefix; } window.codePush.notifyApplicationReady(); @@ -179,8 +186,9 @@ class CodePush implements CodePushCordovaPlugin { } else { if (remotePackage.isMandatory && syncOptions.mandatoryUpdateMessage) { /* Alert user */ - navigator.notification.alert(syncOptions.mandatoryUpdateMessage, () => { downloadAndInstallUpdate(remotePackage); }, syncOptions.dialogTitle, syncOptions.mandatoryUpdateContinueButtonLabel); - } else if (!remotePackage.isMandatory && syncOptions && syncOptions.optionalUpdateMessage) { + var message = syncOptions.appendReleaseDescription ? syncOptions.mandatoryUpdateMessage + syncOptions.descriptionPrefix + remotePackage.description : syncOptions.mandatoryUpdateMessage; + navigator.notification.alert(message, () => { downloadAndInstallUpdate(remotePackage); }, syncOptions.updateTitle, syncOptions.mandatoryContinueButtonLabel); + } else if (!remotePackage.isMandatory && syncOptions.optionalUpdateMessage) { /* Confirm update with user */ var optionalUpdateCallback = (buttonIndex: number) => { switch (buttonIndex) { @@ -191,12 +199,13 @@ class CodePush implements CodePushCordovaPlugin { case 2: default: /* Cancel */ - syncCallback && syncCallback(SyncStatus.USER_DECLINED); + syncCallback && syncCallback(SyncStatus.UPDATE_IGNORED); break; } }; - navigator.notification.confirm(syncOptions.optionalUpdateMessage, optionalUpdateCallback, syncOptions.dialogTitle, [syncOptions.optionalUpdateConfirmButtonLabel, syncOptions.optionalUpdateCancelButtonLabel]); + var message = syncOptions.appendReleaseDescription ? syncOptions.optionalUpdateMessage + syncOptions.descriptionPrefix + remotePackage.description : syncOptions.optionalUpdateMessage; + navigator.notification.confirm(message, optionalUpdateCallback, syncOptions.updateTitle, [syncOptions.optionalInstallButtonLabel, syncOptions.optionalIgnoreButtonLabel]); } else { /* No user interaction */ downloadAndInstallUpdate(remotePackage); @@ -214,14 +223,16 @@ class CodePush implements CodePushCordovaPlugin { private getDefaultSyncOptions(): SyncOptions { if (!CodePush.DefaultSyncOptions) { CodePush.DefaultSyncOptions = { - dialogTitle: "Update", + updateTitle: "Update", mandatoryUpdateMessage: "You will be updated to the latest version.", - mandatoryUpdateContinueButtonLabel: "Continue", + mandatoryContinueButtonLabel: "Continue", optionalUpdateMessage: "An update is available. Would you like to install it?", - optionalUpdateConfirmButtonLabel: "Install", - optionalUpdateCancelButtonLabel: "Cancel", + optionalInstallButtonLabel: "Install", + optionalIgnoreButtonLabel: "Ignore", rollbackTimeout: 0, - ignoreFailedUpdates: true + ignoreFailedUpdates: true, + appendReleaseDescription: false, + descriptionPrefix: " Description: " }; } diff --git a/www/syncStatus.ts b/www/syncStatus.ts index 07b2734f..dfdd9084 100644 --- a/www/syncStatus.ts +++ b/www/syncStatus.ts @@ -20,7 +20,7 @@ enum SyncStatus { /** * An optional update is available, but the user declined to install it. The update was not downloaded. */ - USER_DECLINED, + UPDATE_IGNORED, /** * An error happened during the sync operation. This might be an error while communicating with the server, downloading or unziping the update.