cronapp-cordova-ota-plugin 4.4.0-RC.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Cronapp Cordova OTA Plugin
2
+
3
+ Plugin Cordova para aplicar atualizacoes OTA de assets usando `cronapp-cordova-plugin-contentsync`.
4
+
5
+ ## Configuracao
6
+
7
+ O plugin e instalado como dependencia de `cordova-plugin-cronapp`. Por padrao, nada e executado ate que a aplicacao tenha `CRONAPP_OTA_UPDATE` configurada como `true` no `config.xml` principal do projeto e `window.hostApp` esteja preenchido.
8
+
9
+ ```xml
10
+ <preference name="CRONAPP_OTA_UPDATE" value="true" />
11
+ ```
12
+
13
+ O plugin inicia automaticamente no `deviceready`, aguarda 5 segundos e verifica o manifest em `window.hostApp + "/liveupdate/manifest.json"` quando OTA estiver habilitado. Depois disso, repete a verificacao a cada 20 minutos.
14
+
15
+ Se `window.hostApp` nao existir, ou `CRONAPP_OTA_UPDATE` estiver ausente ou diferente de `true`, o OTA nao executa nenhuma verificacao.
16
+
17
+ A versao atual e calculada a partir da versao salva da ultima atualizacao OTA ou da versao nativa do app, o que for maior.
18
+
19
+ ## Manifest
20
+
21
+ ```json
22
+ {
23
+ "url": "https://server.com.br/live-update/bundle-production-1.0.0-20260519-182452.zip",
24
+ "version": "1.0.1"
25
+ }
26
+ ```
27
+
28
+ Somente versoes semanticamente maiores que a versao atual armazenada sao recomendadas ao usuario.
@@ -0,0 +1,4 @@
1
+ {
2
+ "url": "https://s3.us-east-1.amazonaws.com/static.cronapp.io/www.zip",
3
+ "version": "1.0.2"
4
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "cronapp-cordova-ota-plugin",
3
+ "version": "4.4.0-RC.8",
4
+ "description": "Cronapp Cordova over-the-air update plugin.",
5
+ "main": "www/cronapp-ota.js",
6
+ "scripts": {
7
+ "build": "exit 0"
8
+ },
9
+ "cordova": {
10
+ "id": "cronapp-cordova-ota-plugin",
11
+ "platforms": [
12
+ "android",
13
+ "ios"
14
+ ]
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/CronApp/cronapp-framework"
19
+ },
20
+ "keywords": [
21
+ "cordova",
22
+ "ecosystem:cordova",
23
+ "cordova-android",
24
+ "cordova-ios",
25
+ "cronapp",
26
+ "ota",
27
+ "contentsync"
28
+ ],
29
+ "license": "MIT"
30
+ }
package/plugin.xml ADDED
@@ -0,0 +1,41 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cronapp-cordova-ota-plugin" version="4.4.0-RC.8">
3
+ <name>Cronapp OTA Plugin</name>
4
+ <description>Cronapp Cordova over-the-air update plugin.</description>
5
+ <license>MIT</license>
6
+ <keywords>cordova,cronapp,ota,contentsync,live-update</keywords>
7
+ <repo>https://github.com/CronApp/cronapp-framework</repo>
8
+ <issue>https://github.com/CronApp/cronapp-framework/issues</issue>
9
+
10
+ <dependency id="cronapp-cordova-plugin-contentsync" version="4.4.0-RC.8"/>
11
+ <dependency id="cordova-plugin-dialogs" version="^2.0.1"/>
12
+
13
+ <js-module name="CronappOTA" src="www/cronapp-ota.js">
14
+ <clobbers target="cordova.plugins.cronappOTA"/>
15
+ </js-module>
16
+
17
+ <js-module name="CronappOTABootstrap" src="www/cronapp-ota-bootstrap.js">
18
+ <runs/>
19
+ </js-module>
20
+
21
+ <platform name="android">
22
+ <config-file parent="/*" target="res/xml/config.xml">
23
+ <feature name="CronappOTA">
24
+ <param name="android-package" value="br.com.cronapp.cordova.ota.CronappOTA"/>
25
+ </feature>
26
+ </config-file>
27
+
28
+ <source-file src="src/android/br/com/cronapp/cordova/ota/CronappOTA.java" target-dir="src/br/com/cronapp/cordova/ota"/>
29
+ </platform>
30
+
31
+ <platform name="ios">
32
+ <config-file parent="/*" target="config.xml">
33
+ <feature name="CronappOTA">
34
+ <param name="ios-package" value="CronappOTA"/>
35
+ </feature>
36
+ </config-file>
37
+
38
+ <header-file src="src/ios/CronappOTA.h"/>
39
+ <source-file src="src/ios/CronappOTA.m"/>
40
+ </platform>
41
+ </plugin>
@@ -0,0 +1,84 @@
1
+ package br.com.cronapp.cordova.ota;
2
+
3
+ import android.content.Context;
4
+ import android.content.SharedPreferences;
5
+ import android.content.pm.PackageInfo;
6
+ import android.content.pm.PackageManager;
7
+
8
+ import org.apache.cordova.CallbackContext;
9
+ import org.apache.cordova.CordovaPlugin;
10
+ import org.json.JSONArray;
11
+ import org.json.JSONException;
12
+ import org.json.JSONObject;
13
+
14
+ public class CronappOTA extends CordovaPlugin {
15
+ private static final String PREFERENCES_NAME = "CronappOTA";
16
+
17
+ @Override
18
+ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
19
+ if ("getConfig".equals(action)) {
20
+ callbackContext.success(getConfig());
21
+ return true;
22
+ }
23
+
24
+ if ("getStoredVersion".equals(action)) {
25
+ callbackContext.success(getPreferences().getString(args.getString(0), ""));
26
+ return true;
27
+ }
28
+
29
+ if ("setStoredVersion".equals(action)) {
30
+ getPreferences().edit().putString(args.getString(0), args.getString(1)).apply();
31
+ callbackContext.success();
32
+ return true;
33
+ }
34
+
35
+ if ("clearStoredVersion".equals(action)) {
36
+ getPreferences().edit().remove(args.getString(0)).apply();
37
+ callbackContext.success();
38
+ return true;
39
+ }
40
+
41
+ return false;
42
+ }
43
+
44
+ private JSONObject getConfig() throws JSONException {
45
+ JSONObject config = new JSONObject();
46
+ config.put("otaUpdate", getPreference("CRONAPP_OTA_UPDATE"));
47
+ config.put("currentVersion", getAppVersion());
48
+ config.put("applicationId", getApplicationId());
49
+ return config;
50
+ }
51
+
52
+ private String getPreference(String key) {
53
+ return getPreference(key, "");
54
+ }
55
+
56
+ private String getPreference(String key, String defaultValue) {
57
+ String value = preferences.getString(key, null);
58
+
59
+ if (value == null) {
60
+ value = preferences.getString(key.toLowerCase(), null);
61
+ }
62
+
63
+ return value == null ? defaultValue : value;
64
+ }
65
+
66
+ private String getApplicationId() {
67
+ return cordova.getActivity().getPackageName();
68
+ }
69
+
70
+ private String getAppVersion() {
71
+ try {
72
+ PackageManager packageManager = cordova.getActivity().getPackageManager();
73
+ PackageInfo packageInfo = packageManager.getPackageInfo(getApplicationId(), 0);
74
+ return packageInfo.versionName == null ? "" : packageInfo.versionName;
75
+ } catch (PackageManager.NameNotFoundException e) {
76
+ return "";
77
+ }
78
+ }
79
+
80
+ private SharedPreferences getPreferences() {
81
+ Context context = cordova.getActivity().getApplicationContext();
82
+ return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
83
+ }
84
+ }
@@ -0,0 +1,10 @@
1
+ #import <Cordova/CDV.h>
2
+
3
+ @interface CronappOTA : CDVPlugin
4
+
5
+ - (void)getConfig:(CDVInvokedUrlCommand*)command;
6
+ - (void)getStoredVersion:(CDVInvokedUrlCommand*)command;
7
+ - (void)setStoredVersion:(CDVInvokedUrlCommand*)command;
8
+ - (void)clearStoredVersion:(CDVInvokedUrlCommand*)command;
9
+
10
+ @end
@@ -0,0 +1,81 @@
1
+ #import "CronappOTA.h"
2
+
3
+ static NSString *const CronappOTAPreferencesName = @"CronappOTA";
4
+
5
+ @implementation CronappOTA
6
+
7
+ - (void)getConfig:(CDVInvokedUrlCommand*)command
8
+ {
9
+ NSDictionary *config = @{
10
+ @"otaUpdate": [self getPreference:@"CRONAPP_OTA_UPDATE" defaultValue:@""],
11
+ @"currentVersion": [self appVersion],
12
+ @"applicationId": [self applicationId]
13
+ };
14
+
15
+ [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:config] callbackId:command.callbackId];
16
+ }
17
+
18
+ - (void)getStoredVersion:(CDVInvokedUrlCommand*)command
19
+ {
20
+ NSString *key = [self storageKeyFromCommand:command];
21
+ NSString *value = [[NSUserDefaults standardUserDefaults] stringForKey:[self namespacedKey:key]];
22
+
23
+ [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:value ?: @""] callbackId:command.callbackId];
24
+ }
25
+
26
+ - (void)setStoredVersion:(CDVInvokedUrlCommand*)command
27
+ {
28
+ NSString *key = [self storageKeyFromCommand:command];
29
+ NSString *value = [command argumentAtIndex:1 withDefault:@"" andClass:[NSString class]];
30
+
31
+ [[NSUserDefaults standardUserDefaults] setObject:value forKey:[self namespacedKey:key]];
32
+ [[NSUserDefaults standardUserDefaults] synchronize];
33
+ [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] callbackId:command.callbackId];
34
+ }
35
+
36
+ - (void)clearStoredVersion:(CDVInvokedUrlCommand*)command
37
+ {
38
+ NSString *key = [self storageKeyFromCommand:command];
39
+
40
+ [[NSUserDefaults standardUserDefaults] removeObjectForKey:[self namespacedKey:key]];
41
+ [[NSUserDefaults standardUserDefaults] synchronize];
42
+ [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] callbackId:command.callbackId];
43
+ }
44
+
45
+ - (NSString*)getPreference:(NSString*)key defaultValue:(NSString*)defaultValue
46
+ {
47
+ id value = [self.commandDelegate.settings objectForKey:[key lowercaseString]];
48
+
49
+ if (value == nil) {
50
+ value = [self.commandDelegate.settings objectForKey:key];
51
+ }
52
+
53
+ if (value == nil) {
54
+ return defaultValue;
55
+ }
56
+
57
+ return [NSString stringWithFormat:@"%@", value];
58
+ }
59
+
60
+ - (NSString*)applicationId
61
+ {
62
+ return [[NSBundle mainBundle] bundleIdentifier] ?: @"";
63
+ }
64
+
65
+ - (NSString*)appVersion
66
+ {
67
+ NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
68
+ return version ?: @"";
69
+ }
70
+
71
+ - (NSString*)storageKeyFromCommand:(CDVInvokedUrlCommand*)command
72
+ {
73
+ return [command argumentAtIndex:0 withDefault:@"cronapp_ota_version" andClass:[NSString class]];
74
+ }
75
+
76
+ - (NSString*)namespacedKey:(NSString*)key
77
+ {
78
+ return [NSString stringWithFormat:@"%@.%@", CronappOTAPreferencesName, key];
79
+ }
80
+
81
+ @end
@@ -0,0 +1,5 @@
1
+ var ota = require('cronapp-cordova-ota-plugin.CronappOTA');
2
+
3
+ document.addEventListener('deviceready', function () {
4
+ ota.autoStart();
5
+ }, false);
@@ -0,0 +1,448 @@
1
+ var exec = require('cordova/exec');
2
+
3
+ var SERVICE = 'CronappOTA';
4
+ var STORAGE_KEY = 'cronapp_ota_version';
5
+ var DEFAULT_INITIAL_DELAY = 5000;
6
+ var DEFAULT_CHECK_INTERVAL = 20 * 60 * 1000;
7
+
8
+ var options = {};
9
+ var timer = null;
10
+ var initialized = false;
11
+ var checking = false;
12
+ var applying = false;
13
+ var lastManifest = null;
14
+
15
+ function native(action, args) {
16
+ return new Promise(function (resolve, reject) {
17
+ exec(resolve, reject, SERVICE, action, args || []);
18
+ });
19
+ }
20
+
21
+ function isBlank(value) {
22
+ return value === undefined || value === null || String(value).trim() === '';
23
+ }
24
+
25
+ function isTrue(value) {
26
+ return value === true || String(value).trim().toLowerCase() === 'true';
27
+ }
28
+
29
+ function buildManifestUrl(hostApp) {
30
+ if (isBlank(hostApp)) {
31
+ return '';
32
+ }
33
+
34
+ return String(hostApp).trim().replace(/\/+$/, '') + '/liveupdate/manifest.json';
35
+ }
36
+
37
+ function cleanVersion(value) {
38
+ if (isBlank(value)) {
39
+ return null;
40
+ }
41
+
42
+ return String(value).trim().replace(/^v/i, '');
43
+ }
44
+
45
+ function parseSemver(value) {
46
+ var version = cleanVersion(value);
47
+ var match;
48
+
49
+ if (!version) {
50
+ return null;
51
+ }
52
+
53
+ match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
54
+ if (!match) {
55
+ return null;
56
+ }
57
+
58
+ return {
59
+ major: parseInt(match[1], 10),
60
+ minor: parseInt(match[2], 10),
61
+ patch: parseInt(match[3], 10),
62
+ prerelease: match[4] ? match[4].split('.') : []
63
+ };
64
+ }
65
+
66
+ function comparePrerelease(left, right) {
67
+ var max = Math.max(left.length, right.length);
68
+ var i;
69
+ var leftPart;
70
+ var rightPart;
71
+ var leftNumber;
72
+ var rightNumber;
73
+
74
+ if (!left.length && right.length) {
75
+ return 1;
76
+ }
77
+
78
+ if (left.length && !right.length) {
79
+ return -1;
80
+ }
81
+
82
+ for (i = 0; i < max; i += 1) {
83
+ leftPart = left[i];
84
+ rightPart = right[i];
85
+
86
+ if (leftPart === undefined) {
87
+ return -1;
88
+ }
89
+
90
+ if (rightPart === undefined) {
91
+ return 1;
92
+ }
93
+
94
+ leftNumber = /^[0-9]+$/.test(leftPart) ? parseInt(leftPart, 10) : null;
95
+ rightNumber = /^[0-9]+$/.test(rightPart) ? parseInt(rightPart, 10) : null;
96
+
97
+ if (leftNumber !== null && rightNumber !== null) {
98
+ if (leftNumber > rightNumber) {
99
+ return 1;
100
+ }
101
+ if (leftNumber < rightNumber) {
102
+ return -1;
103
+ }
104
+ } else if (leftNumber !== null) {
105
+ return -1;
106
+ } else if (rightNumber !== null) {
107
+ return 1;
108
+ } else if (leftPart > rightPart) {
109
+ return 1;
110
+ } else if (leftPart < rightPart) {
111
+ return -1;
112
+ }
113
+ }
114
+
115
+ return 0;
116
+ }
117
+
118
+ function compareSemver(left, right) {
119
+ var leftVersion = parseSemver(left);
120
+ var rightVersion = parseSemver(right);
121
+ var fields = ['major', 'minor', 'patch'];
122
+ var i;
123
+
124
+ if (!leftVersion || !rightVersion) {
125
+ return 0;
126
+ }
127
+
128
+ for (i = 0; i < fields.length; i += 1) {
129
+ if (leftVersion[fields[i]] > rightVersion[fields[i]]) {
130
+ return 1;
131
+ }
132
+ if (leftVersion[fields[i]] < rightVersion[fields[i]]) {
133
+ return -1;
134
+ }
135
+ }
136
+
137
+ return comparePrerelease(leftVersion.prerelease, rightVersion.prerelease);
138
+ }
139
+
140
+ function requestJson(url, headers) {
141
+ return new Promise(function (resolve, reject) {
142
+ var xhr = new XMLHttpRequest();
143
+ var finalUrl = url;
144
+ var separator = url.indexOf('?') === -1 ? '?' : '&';
145
+
146
+ finalUrl += separator + '_cronapp_ota=' + Date.now();
147
+ xhr.open('GET', finalUrl, true);
148
+ xhr.timeout = options.requestTimeout || 30000;
149
+
150
+ Object.keys(headers || {}).forEach(function (key) {
151
+ xhr.setRequestHeader(key, headers[key]);
152
+ });
153
+
154
+ xhr.onreadystatechange = function () {
155
+ var response;
156
+
157
+ if (xhr.readyState !== 4) {
158
+ return;
159
+ }
160
+
161
+ if (xhr.status < 200 || xhr.status >= 300) {
162
+ reject(new Error('Manifest request failed with status ' + xhr.status));
163
+ return;
164
+ }
165
+
166
+ try {
167
+ response = JSON.parse(xhr.responseText);
168
+ } catch (error) {
169
+ reject(error);
170
+ return;
171
+ }
172
+
173
+ resolve(response);
174
+ };
175
+
176
+ xhr.onerror = function () {
177
+ reject(new Error('Manifest request failed'));
178
+ };
179
+
180
+ xhr.ontimeout = function () {
181
+ reject(new Error('Manifest request timed out'));
182
+ };
183
+
184
+ xhr.send();
185
+ });
186
+ }
187
+
188
+ function confirmUpdate(version) {
189
+ return new Promise(function (resolve) {
190
+ var message = 'A versao ' + version + ' esta disponivel. Deseja implantar agora?';
191
+
192
+ if (navigator.notification && navigator.notification.confirm) {
193
+ navigator.notification.confirm(message, function (buttonIndex) {
194
+ resolve(buttonIndex === 1);
195
+ }, 'Atualizacao disponivel', ['Sim', 'Nao']);
196
+ return;
197
+ }
198
+
199
+ resolve(window.confirm(message));
200
+ });
201
+ }
202
+
203
+ function getCurrentVersion(config) {
204
+ var configuredVersion = cleanVersion(options.currentVersion) || cleanVersion(config.currentVersion);
205
+
206
+ if (!parseSemver(configuredVersion)) {
207
+ configuredVersion = null;
208
+ }
209
+
210
+ return native('getStoredVersion', [STORAGE_KEY]).then(function (storedVersion) {
211
+ storedVersion = cleanVersion(storedVersion);
212
+ if (!parseSemver(storedVersion)) {
213
+ storedVersion = null;
214
+ }
215
+
216
+ if (storedVersion && configuredVersion && compareSemver(configuredVersion, storedVersion) > 0) {
217
+ return configuredVersion;
218
+ }
219
+
220
+ return storedVersion || configuredVersion || '0.0.0';
221
+ }, function () {
222
+ return configuredVersion || '0.0.0';
223
+ });
224
+ }
225
+
226
+ function validateManifest(manifest, config) {
227
+ if (!manifest || isBlank(manifest.url) || isBlank(manifest.version)) {
228
+ throw new Error('Invalid OTA manifest');
229
+ }
230
+
231
+ if (!parseSemver(manifest.version)) {
232
+ throw new Error('Invalid OTA manifest version: ' + manifest.version);
233
+ }
234
+
235
+ if (!isBlank(config.applicationId) && !isBlank(manifest.applicationId) && config.applicationId !== manifest.applicationId) {
236
+ throw new Error('Manifest applicationId does not match this application');
237
+ }
238
+ }
239
+
240
+ function buildSyncUrl(manifest) {
241
+ var url = String(manifest.url);
242
+
243
+ if (options.appendVersionToBundleUrl === false) {
244
+ return url;
245
+ }
246
+
247
+ return url + (url.indexOf('?') === -1 ? '?' : '&') + '_cronapp_ota_version=' + encodeURIComponent(manifest.version);
248
+ }
249
+
250
+ function refreshApplication(localPath) {
251
+ var url = String(localPath || '');
252
+
253
+ if (url) {
254
+ url = url.replace(/^file:\/\//, '').replace(/\/$/, '');
255
+ url = 'file://' + url + '/index.html';
256
+ }
257
+
258
+ if (window.ContentSync && typeof window.ContentSync.loadUrl === 'function' && localPath) {
259
+ window.ContentSync.loadUrl(url, function () {}, function () {
260
+ window.location.reload();
261
+ });
262
+ return;
263
+ }
264
+
265
+ if (localPath) {
266
+ window.location.replace(url);
267
+ return;
268
+ }
269
+
270
+ window.location.reload();
271
+ }
272
+
273
+ function applyManifest(manifest) {
274
+ return new Promise(function (resolve, reject) {
275
+ var sync;
276
+
277
+ if (!window.ContentSync || typeof window.ContentSync.sync !== 'function') {
278
+ reject(new Error('ContentSync is not available'));
279
+ return;
280
+ }
281
+
282
+ if (applying) {
283
+ reject(new Error('OTA update is already being applied'));
284
+ return;
285
+ }
286
+
287
+ applying = true;
288
+ sync = window.ContentSync.sync({
289
+ src: buildSyncUrl(manifest),
290
+ id: options.contentSyncId || 'cronapp',
291
+ type: 'replace',
292
+ copyCordovaAssets: true,
293
+ headers: options.bundleHeaders || {}
294
+ });
295
+
296
+ sync.on('complete', function (data) {
297
+ native('setStoredVersion', [STORAGE_KEY, cleanVersion(manifest.version)]).then(function () {
298
+ applying = false;
299
+ refreshApplication(data && data.localPath);
300
+ resolve(data);
301
+ }, function (error) {
302
+ applying = false;
303
+ reject(error);
304
+ });
305
+ });
306
+
307
+ sync.on('error', function (error) {
308
+ applying = false;
309
+ reject(error);
310
+ });
311
+
312
+ sync.on('cancel', function () {
313
+ applying = false;
314
+ reject(new Error('OTA update was cancelled'));
315
+ });
316
+ });
317
+ }
318
+
319
+ function getConfig() {
320
+ return native('getConfig', []).then(function (config) {
321
+ config = config || {};
322
+ config.otaUpdate = options.otaUpdate !== undefined ? options.otaUpdate : config.otaUpdate;
323
+ config.manifestUrl = isTrue(config.otaUpdate) ? buildManifestUrl(window.hostApp) : '';
324
+ config.currentVersion = options.currentVersion || config.currentVersion;
325
+ config.applicationId = config.applicationId || '';
326
+ return config;
327
+ });
328
+ }
329
+
330
+ function scheduleNext() {
331
+ var interval = options.checkInterval || DEFAULT_CHECK_INTERVAL;
332
+
333
+ if (timer) {
334
+ clearTimeout(timer);
335
+ }
336
+
337
+ timer = setTimeout(function () {
338
+ module.exports.checkNow({ silent: true }).then(scheduleNext, scheduleNext);
339
+ }, interval);
340
+ }
341
+
342
+ module.exports.configure = function (newOptions) {
343
+ options = newOptions || {};
344
+ return module.exports;
345
+ };
346
+
347
+ module.exports.start = function () {
348
+ if (initialized) {
349
+ return Promise.resolve();
350
+ }
351
+
352
+ initialized = true;
353
+ return new Promise(function (resolve) {
354
+ setTimeout(function () {
355
+ module.exports.checkNow({ silent: true }).then(resolve, resolve);
356
+ scheduleNext();
357
+ }, options.initialDelay || DEFAULT_INITIAL_DELAY);
358
+ });
359
+ };
360
+
361
+ module.exports.stop = function () {
362
+ initialized = false;
363
+
364
+ if (timer) {
365
+ clearTimeout(timer);
366
+ timer = null;
367
+ }
368
+ };
369
+
370
+ module.exports.autoStart = function () {
371
+ return module.exports.start();
372
+ };
373
+
374
+ module.exports.checkNow = function (checkOptions) {
375
+ checkOptions = checkOptions || {};
376
+
377
+ if (checking || applying) {
378
+ return Promise.resolve(null);
379
+ }
380
+
381
+ checking = true;
382
+
383
+ return getConfig().then(function (config) {
384
+ if (isBlank(config.manifestUrl)) {
385
+ return null;
386
+ }
387
+
388
+ return requestJson(config.manifestUrl, options.manifestHeaders || {}).then(function (manifest) {
389
+ return getCurrentVersion(config).then(function (currentVersion) {
390
+ validateManifest(manifest, config);
391
+ lastManifest = manifest;
392
+
393
+ if (compareSemver(manifest.version, currentVersion) <= 0) {
394
+ return null;
395
+ }
396
+
397
+ if (checkOptions.prompt === false) {
398
+ return manifest;
399
+ }
400
+
401
+ return confirmUpdate(manifest.version).then(function (accepted) {
402
+ if (!accepted) {
403
+ return manifest;
404
+ }
405
+
406
+ return applyManifest(manifest);
407
+ });
408
+ });
409
+ });
410
+ }).then(function (result) {
411
+ checking = false;
412
+ return result;
413
+ }, function (error) {
414
+ checking = false;
415
+
416
+ if (!checkOptions.silent && window.console && console.warn) {
417
+ console.warn('Cronapp OTA check failed', error);
418
+ }
419
+
420
+ throw error;
421
+ });
422
+ };
423
+
424
+ module.exports.applyLastManifest = function () {
425
+ if (!lastManifest) {
426
+ return Promise.reject(new Error('No OTA manifest available'));
427
+ }
428
+
429
+ return applyManifest(lastManifest);
430
+ };
431
+
432
+ module.exports.getStoredVersion = function () {
433
+ return native('getStoredVersion', [STORAGE_KEY]);
434
+ };
435
+
436
+ module.exports.setStoredVersion = function (version) {
437
+ if (!parseSemver(version)) {
438
+ return Promise.reject(new Error('Invalid semver version: ' + version));
439
+ }
440
+
441
+ return native('setStoredVersion', [STORAGE_KEY, cleanVersion(version)]);
442
+ };
443
+
444
+ module.exports.clearStoredVersion = function () {
445
+ return native('clearStoredVersion', [STORAGE_KEY]);
446
+ };
447
+
448
+ module.exports.compareSemver = compareSemver;