cordova-plugin-hot-updates 2.2.0 → 2.2.1
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 +32 -6
- package/package.json +1 -1
- package/plugin.xml +1 -1
- package/src/ios/HotUpdates+Helpers.h +1 -1
- package/src/ios/HotUpdates+Helpers.m +1 -1
- package/src/ios/HotUpdates.h +3 -29
- package/src/ios/HotUpdates.m +85 -41
- package/src/ios/HotUpdatesConstants.h +9 -3
- package/src/ios/HotUpdatesConstants.m +1 -1
- package/www/HotUpdates.js +109 -116
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Cordova Hot Updates Plugin v2.2.
|
|
1
|
+
# Cordova Hot Updates Plugin v2.2.1
|
|
2
2
|
|
|
3
3
|
Frontend-controlled manual hot updates for Cordova iOS applications using WebView Reload approach.
|
|
4
4
|
|
|
@@ -125,11 +125,11 @@ callback(null)
|
|
|
125
125
|
### Error Handling Example
|
|
126
126
|
|
|
127
127
|
```javascript
|
|
128
|
-
window.hotUpdate.getUpdate({url: 'http://...'}, function(
|
|
129
|
-
if (error) {
|
|
130
|
-
console.error('[HotUpdates]', error.code, ':', error.message);
|
|
128
|
+
window.hotUpdate.getUpdate({url: 'http://...'}, function(result) {
|
|
129
|
+
if (result && result.error) {
|
|
130
|
+
console.error('[HotUpdates]', result.error.code, ':', result.error.message);
|
|
131
131
|
|
|
132
|
-
switch(error.code) {
|
|
132
|
+
switch(result.error.code) {
|
|
133
133
|
case 'HTTP_ERROR':
|
|
134
134
|
// Handle HTTP errors
|
|
135
135
|
break;
|
|
@@ -137,7 +137,7 @@ window.hotUpdate.getUpdate({url: 'http://...'}, function(error) {
|
|
|
137
137
|
// Handle network errors
|
|
138
138
|
break;
|
|
139
139
|
default:
|
|
140
|
-
console.error('Unknown error:', error);
|
|
140
|
+
console.error('Unknown error:', result.error);
|
|
141
141
|
}
|
|
142
142
|
} else {
|
|
143
143
|
console.log('Update downloaded successfully');
|
|
@@ -268,6 +268,32 @@ window.hotUpdate.getIgnoreList(function(result) {
|
|
|
268
268
|
|
|
269
269
|
---
|
|
270
270
|
|
|
271
|
+
### window.hotUpdate.getVersionInfo(callback)
|
|
272
|
+
|
|
273
|
+
Returns version information (debug method).
|
|
274
|
+
|
|
275
|
+
**Parameters:**
|
|
276
|
+
- `callback` (Function) - `callback(info)`
|
|
277
|
+
- `info.appBundleVersion` (string) - Native app version from Info.plist
|
|
278
|
+
- `info.installedVersion` (string|null) - Current hot update version
|
|
279
|
+
- `info.previousVersion` (string|null) - Last working version (for rollback)
|
|
280
|
+
- `info.canaryVersion` (string|null) - Version confirmed by canary
|
|
281
|
+
- `info.pendingVersion` (string|null) - Version pending installation
|
|
282
|
+
- `info.hasPendingUpdate` (boolean) - Whether pending update exists
|
|
283
|
+
- `info.ignoreList` (string[]) - Array of problematic versions
|
|
284
|
+
|
|
285
|
+
**Example:**
|
|
286
|
+
```javascript
|
|
287
|
+
window.hotUpdate.getVersionInfo(function(info) {
|
|
288
|
+
console.log('App version:', info.appBundleVersion);
|
|
289
|
+
console.log('Installed:', info.installedVersion);
|
|
290
|
+
console.log('Previous:', info.previousVersion);
|
|
291
|
+
console.log('Pending:', info.hasPendingUpdate ? info.pendingVersion : 'none');
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
271
297
|
## Complete Update Flow
|
|
272
298
|
|
|
273
299
|
```javascript
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cordova-plugin-hot-updates",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "Frontend-controlled manual hot updates for Cordova iOS apps using WebView Reload approach. Manual updates only, JavaScript controls all decisions.",
|
|
5
5
|
"main": "www/HotUpdates.js",
|
|
6
6
|
"scripts": {
|
package/plugin.xml
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @file HotUpdates+Helpers.h
|
|
3
3
|
* @brief Helper methods for Hot Updates Plugin
|
|
4
4
|
* @details Category extension providing utility methods for error handling
|
|
5
|
-
* @version 2.
|
|
5
|
+
* @version 2.1.0
|
|
6
6
|
* @date 2025-11-13
|
|
7
7
|
* @author Mustafin Vladimir
|
|
8
8
|
* @copyright Copyright (c) 2025. All rights reserved.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @file HotUpdates+Helpers.m
|
|
3
3
|
* @brief Implementation of helper methods for Hot Updates Plugin
|
|
4
4
|
* @details Provides utility methods for error handling and response formatting
|
|
5
|
-
* @version 2.
|
|
5
|
+
* @version 2.1.0
|
|
6
6
|
* @date 2025-11-13
|
|
7
7
|
* @author Mustafin Vladimir
|
|
8
8
|
* @copyright Copyright (c) 2025. All rights reserved.
|
package/src/ios/HotUpdates.h
CHANGED
|
@@ -32,39 +32,13 @@
|
|
|
32
32
|
NSString *previousVersionPath; // Путь к предыдущей версии
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
//
|
|
36
|
-
- (void)pluginInitialize;
|
|
37
|
-
- (void)loadConfiguration;
|
|
38
|
-
- (void)initializeWWWFolder;
|
|
39
|
-
- (void)checkAndInstallPendingUpdate;
|
|
40
|
-
- (void)switchToUpdatedContentWithReload;
|
|
41
|
-
- (void)reloadWebView;
|
|
42
|
-
|
|
43
|
-
// Update management methods (internal)
|
|
44
|
-
- (void)installPendingUpdate:(NSString*)newVersion;
|
|
45
|
-
- (BOOL)unzipFile:(NSString *)zipPath toDestination:(NSString *)destinationPath;
|
|
46
|
-
|
|
47
|
-
// Version comparison utilities
|
|
48
|
-
- (NSComparisonResult)compareVersion:(NSString*)version1 withVersion:(NSString*)version2;
|
|
49
|
-
|
|
50
|
-
// JavaScript callable methods (minimal set for debugging)
|
|
51
|
-
- (void)getCurrentVersion:(CDVInvokedUrlCommand*)command;
|
|
52
|
-
- (void)getPendingUpdateInfo:(CDVInvokedUrlCommand*)command;
|
|
53
|
-
|
|
54
|
-
// Ignore List management (JS can only read, native controls)
|
|
55
|
-
- (void)getIgnoreList:(CDVInvokedUrlCommand*)command;
|
|
56
|
-
|
|
57
|
-
// Debug methods (for manual testing only)
|
|
58
|
-
- (void)addToIgnoreList:(CDVInvokedUrlCommand*)command;
|
|
59
|
-
- (void)removeFromIgnoreList:(CDVInvokedUrlCommand*)command;
|
|
60
|
-
- (void)clearIgnoreList:(CDVInvokedUrlCommand*)command;
|
|
61
|
-
|
|
62
|
-
// Update methods (v2.1.0 - manual updates only)
|
|
35
|
+
// JavaScript API methods (v2.1.0)
|
|
63
36
|
- (void)getUpdate:(CDVInvokedUrlCommand*)command; // Download update
|
|
64
37
|
- (void)forceUpdate:(CDVInvokedUrlCommand*)command; // Install downloaded update
|
|
65
38
|
- (void)canary:(CDVInvokedUrlCommand*)command; // Confirm successful load
|
|
39
|
+
- (void)getIgnoreList:(CDVInvokedUrlCommand*)command; // Get ignore list (JS reads only)
|
|
66
40
|
|
|
67
|
-
// Debug
|
|
41
|
+
// Debug method
|
|
68
42
|
- (void)getVersionInfo:(CDVInvokedUrlCommand*)command; // Get all version info for debugging
|
|
69
43
|
|
|
70
44
|
@end
|
package/src/ios/HotUpdates.m
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* - IgnoreList for tracking problematic versions
|
|
13
13
|
* - Auto-install pending updates on next app launch
|
|
14
14
|
*
|
|
15
|
-
* @version 2.2
|
|
16
|
-
* @date 2025-11-
|
|
15
|
+
* @version 2.1.2
|
|
16
|
+
* @date 2025-11-26
|
|
17
17
|
* @author Mustafin Vladimir
|
|
18
18
|
* @copyright Copyright (c) 2025. All rights reserved.
|
|
19
19
|
*/
|
|
@@ -56,6 +56,10 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
56
56
|
[self loadConfiguration];
|
|
57
57
|
[self loadIgnoreList];
|
|
58
58
|
|
|
59
|
+
// Сбрасываем флаг загрузки (если приложение было убито во время загрузки)
|
|
60
|
+
isDownloadingUpdate = NO;
|
|
61
|
+
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:kDownloadInProgress];
|
|
62
|
+
|
|
59
63
|
isUpdateReadyToInstall = NO;
|
|
60
64
|
pendingUpdateURL = nil;
|
|
61
65
|
pendingUpdateVersion = nil;
|
|
@@ -83,11 +87,7 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
83
87
|
if (!canaryVersion || ![canaryVersion isEqualToString:currentVersion]) {
|
|
84
88
|
NSLog(@"[HotUpdates] Starting canary timer (20 seconds) for version %@", currentVersion);
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
target:self
|
|
88
|
-
selector:@selector(canaryTimeout)
|
|
89
|
-
userInfo:nil
|
|
90
|
-
repeats:NO];
|
|
90
|
+
[self startCanaryTimer];
|
|
91
91
|
} else {
|
|
92
92
|
NSLog(@"[HotUpdates] Canary already confirmed for version %@", currentVersion);
|
|
93
93
|
}
|
|
@@ -113,7 +113,6 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
113
113
|
- (void)checkAndInstallPendingUpdate {
|
|
114
114
|
BOOL hasPendingUpdate = [[NSUserDefaults standardUserDefaults] boolForKey:kHasPending];
|
|
115
115
|
NSString *pendingVersion = [[NSUserDefaults standardUserDefaults] stringForKey:kPendingVersion];
|
|
116
|
-
NSString *installedVersion = [[NSUserDefaults standardUserDefaults] stringForKey:kInstalledVersion];
|
|
117
116
|
|
|
118
117
|
if (hasPendingUpdate && pendingVersion) {
|
|
119
118
|
NSLog(@"[HotUpdates] Installing pending update %@ to Documents/www (auto-install on launch)", pendingVersion);
|
|
@@ -174,11 +173,13 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
174
173
|
((CDVViewController *)self.viewController).wwwFolderName = documentsWwwPath;
|
|
175
174
|
NSLog(@"[HotUpdates] Changed wwwFolderName to: %@", documentsWwwPath);
|
|
176
175
|
|
|
177
|
-
[self reloadWebView];
|
|
178
|
-
|
|
179
176
|
hasPerformedInitialReload = YES;
|
|
180
177
|
|
|
181
|
-
|
|
178
|
+
// Очищаем кэш перед перезагрузкой, иначе может загрузиться старая версия
|
|
179
|
+
[self clearWebViewCacheWithCompletion:^{
|
|
180
|
+
[self reloadWebView];
|
|
181
|
+
NSLog(@"[HotUpdates] WebView reloaded with updated content (version: %@)", installedVersion);
|
|
182
|
+
}];
|
|
182
183
|
} else {
|
|
183
184
|
NSLog(@"[HotUpdates] Documents/www/index.html not found, keeping bundle www");
|
|
184
185
|
}
|
|
@@ -189,10 +190,18 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
189
190
|
}
|
|
190
191
|
|
|
191
192
|
/*!
|
|
192
|
-
* @brief
|
|
193
|
-
* @details
|
|
193
|
+
* @brief Clear WebView cache
|
|
194
|
+
* @details Clears disk cache, memory cache, offline storage and service workers
|
|
194
195
|
*/
|
|
195
196
|
- (void)clearWebViewCache {
|
|
197
|
+
[self clearWebViewCacheWithCompletion:nil];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/*!
|
|
201
|
+
* @brief Clear WebView cache with completion handler
|
|
202
|
+
* @param completion Block called after cache is cleared (on main thread)
|
|
203
|
+
*/
|
|
204
|
+
- (void)clearWebViewCacheWithCompletion:(void (^)(void))completion {
|
|
196
205
|
NSLog(@"[HotUpdates] Clearing WebView cache");
|
|
197
206
|
|
|
198
207
|
NSSet *websiteDataTypes = [NSSet setWithArray:@[
|
|
@@ -207,6 +216,9 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
207
216
|
modifiedSince:dateFrom
|
|
208
217
|
completionHandler:^{
|
|
209
218
|
NSLog(@"[HotUpdates] WebView cache cleared");
|
|
219
|
+
if (completion) {
|
|
220
|
+
dispatch_async(dispatch_get_main_queue(), completion);
|
|
221
|
+
}
|
|
210
222
|
}];
|
|
211
223
|
}
|
|
212
224
|
|
|
@@ -397,6 +409,28 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
397
409
|
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
398
410
|
}
|
|
399
411
|
|
|
412
|
+
#pragma mark - Canary Timer
|
|
413
|
+
|
|
414
|
+
/*!
|
|
415
|
+
* @brief Start canary timer with weak self to prevent retain cycle
|
|
416
|
+
* @details Uses block-based timer (iOS 10+) with weak reference
|
|
417
|
+
*/
|
|
418
|
+
- (void)startCanaryTimer {
|
|
419
|
+
// Инвалидируем предыдущий таймер если есть
|
|
420
|
+
if (canaryTimer && [canaryTimer isValid]) {
|
|
421
|
+
[canaryTimer invalidate];
|
|
422
|
+
canaryTimer = nil;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Используем weak self для предотвращения retain cycle
|
|
426
|
+
__weak __typeof__(self) weakSelf = self;
|
|
427
|
+
canaryTimer = [NSTimer scheduledTimerWithTimeInterval:20.0
|
|
428
|
+
repeats:NO
|
|
429
|
+
block:^(NSTimer * _Nonnull timer) {
|
|
430
|
+
[weakSelf canaryTimeout];
|
|
431
|
+
}];
|
|
432
|
+
}
|
|
433
|
+
|
|
400
434
|
#pragma mark - Canary Timeout Handler
|
|
401
435
|
|
|
402
436
|
- (void)canaryTimeout {
|
|
@@ -412,10 +446,7 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
412
446
|
|
|
413
447
|
NSLog(@"[HotUpdates] Version %@ considered faulty, performing rollback", currentVersion);
|
|
414
448
|
|
|
415
|
-
|
|
416
|
-
[self addVersionToIgnoreList:currentVersion];
|
|
417
|
-
}
|
|
418
|
-
|
|
449
|
+
// Примечание: версия добавляется в ignoreList внутри rollbackToPreviousVersion
|
|
419
450
|
BOOL rollbackSuccess = [self rollbackToPreviousVersion];
|
|
420
451
|
|
|
421
452
|
if (rollbackSuccess) {
|
|
@@ -423,8 +454,9 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
423
454
|
|
|
424
455
|
hasPerformedInitialReload = NO;
|
|
425
456
|
|
|
426
|
-
[self
|
|
427
|
-
|
|
457
|
+
[self clearWebViewCacheWithCompletion:^{
|
|
458
|
+
[self reloadWebView];
|
|
459
|
+
}];
|
|
428
460
|
} else {
|
|
429
461
|
NSLog(@"[HotUpdates] Automatic rollback failed");
|
|
430
462
|
}
|
|
@@ -542,7 +574,11 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
542
574
|
#pragma mark - Get Update (Download Only)
|
|
543
575
|
|
|
544
576
|
- (void)getUpdate:(CDVInvokedUrlCommand*)command {
|
|
545
|
-
|
|
577
|
+
// Безопасное получение первого аргумента
|
|
578
|
+
NSDictionary *updateData = nil;
|
|
579
|
+
if (command.arguments.count > 0 && [command.arguments[0] isKindOfClass:[NSDictionary class]]) {
|
|
580
|
+
updateData = command.arguments[0];
|
|
581
|
+
}
|
|
546
582
|
|
|
547
583
|
if (!updateData) {
|
|
548
584
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
@@ -613,15 +649,30 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
613
649
|
NSLog(@"[HotUpdates] Starting download");
|
|
614
650
|
|
|
615
651
|
NSURL *url = [NSURL URLWithString:downloadURL];
|
|
652
|
+
if (!url) {
|
|
653
|
+
NSLog(@"[HotUpdates] Invalid URL: %@", downloadURL);
|
|
654
|
+
isDownloadingUpdate = NO;
|
|
655
|
+
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:kDownloadInProgress];
|
|
656
|
+
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
657
|
+
|
|
658
|
+
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
659
|
+
messageAsDictionary:[self createError:kErrorURLRequired
|
|
660
|
+
message:@"Invalid URL format"]];
|
|
661
|
+
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
616
664
|
|
|
617
665
|
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
|
618
|
-
config.timeoutIntervalForRequest =
|
|
619
|
-
config.timeoutIntervalForResource =
|
|
666
|
+
config.timeoutIntervalForRequest = 30.0; // ТЗ: 30-60 секунд
|
|
667
|
+
config.timeoutIntervalForResource = 60.0; // ТЗ: максимум 60 секунд на всю загрузку
|
|
620
668
|
|
|
621
669
|
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
|
|
622
670
|
|
|
623
671
|
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url
|
|
624
672
|
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
|
|
673
|
+
// Инвалидируем сессию для предотвращения утечки памяти
|
|
674
|
+
[session finishTasksAndInvalidate];
|
|
675
|
+
|
|
625
676
|
self->isDownloadingUpdate = NO;
|
|
626
677
|
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:kDownloadInProgress];
|
|
627
678
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
@@ -813,6 +864,7 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
813
864
|
|
|
814
865
|
isUpdateReadyToInstall = NO;
|
|
815
866
|
pendingUpdateURL = nil;
|
|
867
|
+
pendingUpdateVersion = nil;
|
|
816
868
|
|
|
817
869
|
NSLog(@"[HotUpdates] Update installed successfully");
|
|
818
870
|
|
|
@@ -822,28 +874,24 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
822
874
|
// После reloadWebView pluginInitialize НЕ вызывается, поэтому canary timer запускаем вручную
|
|
823
875
|
NSLog(@"[HotUpdates] Starting canary timer (20 seconds) for version %@", newVersion);
|
|
824
876
|
|
|
825
|
-
|
|
826
|
-
[canaryTimer invalidate];
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
canaryTimer = [NSTimer scheduledTimerWithTimeInterval:20.0
|
|
830
|
-
target:self
|
|
831
|
-
selector:@selector(canaryTimeout)
|
|
832
|
-
userInfo:nil
|
|
833
|
-
repeats:NO];
|
|
877
|
+
[self startCanaryTimer];
|
|
834
878
|
|
|
835
879
|
hasPerformedInitialReload = NO;
|
|
836
880
|
|
|
837
881
|
// Очищаем кэш WebView перед перезагрузкой, иначе может загрузиться старая версия
|
|
838
|
-
[self
|
|
839
|
-
|
|
840
|
-
|
|
882
|
+
[self clearWebViewCacheWithCompletion:^{
|
|
883
|
+
[self reloadWebView];
|
|
884
|
+
}];
|
|
841
885
|
}
|
|
842
886
|
|
|
843
887
|
#pragma mark - Canary
|
|
844
888
|
|
|
845
889
|
- (void)canary:(CDVInvokedUrlCommand*)command {
|
|
846
|
-
|
|
890
|
+
// Безопасное получение первого аргумента
|
|
891
|
+
NSString *canaryVersion = nil;
|
|
892
|
+
if (command.arguments.count > 0 && [command.arguments[0] isKindOfClass:[NSString class]]) {
|
|
893
|
+
canaryVersion = command.arguments[0];
|
|
894
|
+
}
|
|
847
895
|
|
|
848
896
|
if (!canaryVersion || canaryVersion.length == 0) {
|
|
849
897
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
@@ -866,12 +914,8 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
866
914
|
NSLog(@"[HotUpdates] Canary timer stopped - JS confirmed bundle is working");
|
|
867
915
|
}
|
|
868
916
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
@"success": @YES,
|
|
872
|
-
@"canaryVersion": canaryVersion,
|
|
873
|
-
@"message": @"Canary version confirmed"
|
|
874
|
-
}];
|
|
917
|
+
// ТЗ: при успехе callback возвращает null
|
|
918
|
+
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
|
875
919
|
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
876
920
|
}
|
|
877
921
|
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* @file HotUpdatesConstants.h
|
|
3
3
|
* @brief Constants for Hot Updates Plugin
|
|
4
|
-
* @details
|
|
5
|
-
* @version 2.
|
|
4
|
+
* @details Contains error codes, storage keys, and directory names used by the plugin
|
|
5
|
+
* @version 2.1.0
|
|
6
6
|
* @date 2025-11-13
|
|
7
7
|
* @author Mustafin Vladimir
|
|
8
8
|
* @copyright Copyright (c) 2025. All rights reserved.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
#
|
|
11
|
+
#ifndef HotUpdatesConstants_h
|
|
12
|
+
#define HotUpdatesConstants_h
|
|
12
13
|
|
|
13
14
|
#pragma mark - Error Codes
|
|
14
15
|
|
|
16
|
+
// Error codes returned to JavaScript
|
|
15
17
|
extern NSString * const kErrorUpdateDataRequired;
|
|
16
18
|
extern NSString * const kErrorURLRequired;
|
|
17
19
|
extern NSString * const kErrorDownloadInProgress;
|
|
@@ -27,6 +29,7 @@ extern NSString * const kErrorVersionRequired;
|
|
|
27
29
|
|
|
28
30
|
#pragma mark - Storage Keys
|
|
29
31
|
|
|
32
|
+
// NSUserDefaults keys
|
|
30
33
|
extern NSString * const kInstalledVersion;
|
|
31
34
|
extern NSString * const kPendingVersion;
|
|
32
35
|
extern NSString * const kHasPending;
|
|
@@ -39,7 +42,10 @@ extern NSString * const kPendingUpdateReady;
|
|
|
39
42
|
|
|
40
43
|
#pragma mark - Directory Names
|
|
41
44
|
|
|
45
|
+
// Directory names
|
|
42
46
|
extern NSString * const kWWWDirName;
|
|
43
47
|
extern NSString * const kPreviousWWWDirName;
|
|
44
48
|
extern NSString * const kBackupWWWDirName;
|
|
45
49
|
extern NSString * const kPendingUpdateDirName;
|
|
50
|
+
|
|
51
|
+
#endif /* HotUpdatesConstants_h */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @file HotUpdatesConstants.m
|
|
3
3
|
* @brief Implementation of constants for Hot Updates Plugin
|
|
4
4
|
* @details Defines all constant values used throughout the plugin
|
|
5
|
-
* @version 2.
|
|
5
|
+
* @version 2.1.0
|
|
6
6
|
* @date 2025-11-13
|
|
7
7
|
* @author Mustafin Vladimir
|
|
8
8
|
* @copyright Copyright (c) 2025. All rights reserved.
|
package/www/HotUpdates.js
CHANGED
|
@@ -1,73 +1,80 @@
|
|
|
1
1
|
var exec = require('cordova/exec');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Cordova Hot Updates Plugin v2.1
|
|
4
|
+
* Cordova Hot Updates Plugin v2.2.1
|
|
5
5
|
* Frontend-controlled manual hot updates for iOS
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* using the WebView Reload approach. All update decisions are controlled
|
|
9
|
-
* by JavaScript - the native plugin only executes commands.
|
|
10
|
-
*
|
|
11
|
-
* Features:
|
|
12
|
-
* - Frontend-controlled manual updates (no automatic checking)
|
|
13
|
-
* - Two-step update flow: getUpdate() downloads, forceUpdate() installs
|
|
14
|
-
* - Automatic rollback with 20-second canary timer
|
|
15
|
-
* - IgnoreList system for tracking problematic versions (information only)
|
|
16
|
-
* - Auto-install pending updates on next app launch
|
|
17
|
-
* - WebView reload approach for instant updates without app restart
|
|
18
|
-
* - No App Store approval required for web content updates
|
|
19
|
-
*
|
|
20
|
-
* @version 2.1.2
|
|
7
|
+
* @version 2.2.1
|
|
21
8
|
* @author Mustafin Vladimir
|
|
22
9
|
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Error codes returned by the plugin
|
|
13
|
+
* @readonly
|
|
14
|
+
* @enum {string}
|
|
15
|
+
*/
|
|
16
|
+
var ErrorCodes = {
|
|
17
|
+
// getUpdate errors
|
|
18
|
+
UPDATE_DATA_REQUIRED: 'UPDATE_DATA_REQUIRED',
|
|
19
|
+
URL_REQUIRED: 'URL_REQUIRED',
|
|
20
|
+
DOWNLOAD_IN_PROGRESS: 'DOWNLOAD_IN_PROGRESS',
|
|
21
|
+
DOWNLOAD_FAILED: 'DOWNLOAD_FAILED',
|
|
22
|
+
HTTP_ERROR: 'HTTP_ERROR',
|
|
23
|
+
TEMP_DIR_ERROR: 'TEMP_DIR_ERROR',
|
|
24
|
+
EXTRACTION_FAILED: 'EXTRACTION_FAILED',
|
|
25
|
+
WWW_NOT_FOUND: 'WWW_NOT_FOUND',
|
|
26
|
+
// forceUpdate errors
|
|
27
|
+
NO_UPDATE_READY: 'NO_UPDATE_READY',
|
|
28
|
+
UPDATE_FILES_NOT_FOUND: 'UPDATE_FILES_NOT_FOUND',
|
|
29
|
+
INSTALL_FAILED: 'INSTALL_FAILED',
|
|
30
|
+
// canary errors
|
|
31
|
+
VERSION_REQUIRED: 'VERSION_REQUIRED'
|
|
32
|
+
};
|
|
33
|
+
|
|
23
34
|
var HotUpdates = {
|
|
24
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Error codes enum
|
|
38
|
+
* @type {Object}
|
|
39
|
+
*/
|
|
40
|
+
ErrorCodes: ErrorCodes,
|
|
41
|
+
|
|
25
42
|
/**
|
|
26
43
|
* Download update from server
|
|
27
44
|
*
|
|
28
|
-
* Downloads update ZIP archive from the provided URL and saves to two locations:
|
|
29
|
-
* - temp_downloaded_update (for immediate installation via forceUpdate)
|
|
30
|
-
* - pending_update (for auto-installation on next app launch)
|
|
31
|
-
*
|
|
32
|
-
* If the specified version is already downloaded, returns success without re-downloading.
|
|
33
|
-
* Does NOT check ignoreList - JavaScript controls all installation decisions.
|
|
34
|
-
*
|
|
35
45
|
* @param {Object} options - Update options
|
|
36
46
|
* @param {string} options.url - URL to download ZIP archive (required)
|
|
37
47
|
* @param {string} [options.version] - Version string (optional)
|
|
38
|
-
* @param {Function} callback - Callback
|
|
39
|
-
* -
|
|
40
|
-
* -
|
|
48
|
+
* @param {Function} callback - Callback(error)
|
|
49
|
+
* - null on success
|
|
50
|
+
* - {error: {code: string, message: string}} on error
|
|
41
51
|
*
|
|
42
52
|
* @example
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* }, function(error) {
|
|
47
|
-
* if (error) {
|
|
48
|
-
* console.error('Download failed:', error);
|
|
49
|
-
* } else {
|
|
50
|
-
* console.log('Update downloaded successfully');
|
|
51
|
-
* // Can now call forceUpdate() to install immediately
|
|
52
|
-
* // Or user can ignore and it will auto-install on next launch
|
|
53
|
-
* }
|
|
53
|
+
* hotUpdate.getUpdate({url: 'https://server.com/update.zip', version: '2.0.0'}, function(err) {
|
|
54
|
+
* if (err) console.error(err.error.code, err.error.message);
|
|
55
|
+
* else console.log('Downloaded');
|
|
54
56
|
* });
|
|
55
57
|
*/
|
|
56
58
|
getUpdate: function(options, callback) {
|
|
57
|
-
if (!options
|
|
59
|
+
if (!options) {
|
|
60
|
+
if (callback) {
|
|
61
|
+
callback({error: {code: ErrorCodes.UPDATE_DATA_REQUIRED, message: 'Update data required'}});
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!options.url) {
|
|
58
67
|
if (callback) {
|
|
59
|
-
callback({error: {message: 'URL is required'}});
|
|
68
|
+
callback({error: {code: ErrorCodes.URL_REQUIRED, message: 'URL is required'}});
|
|
60
69
|
}
|
|
61
70
|
return;
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
exec(
|
|
65
74
|
function() {
|
|
66
|
-
// Success
|
|
67
75
|
if (callback) callback(null);
|
|
68
76
|
},
|
|
69
77
|
function(error) {
|
|
70
|
-
// Error
|
|
71
78
|
if (callback) callback({error: error});
|
|
72
79
|
},
|
|
73
80
|
'HotUpdates',
|
|
@@ -79,42 +86,22 @@ var HotUpdates = {
|
|
|
79
86
|
/**
|
|
80
87
|
* Install downloaded update immediately
|
|
81
88
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* 2. Copy downloaded update to Documents/www
|
|
86
|
-
* 3. Clear WebView cache (disk, memory, Service Worker)
|
|
87
|
-
* 4. Reload WebView
|
|
88
|
-
* 5. Start 20-second canary timer
|
|
89
|
-
*
|
|
90
|
-
* IMPORTANT: JavaScript MUST call canary(version) within 20 seconds
|
|
91
|
-
* after reload to confirm successful bundle load. Otherwise automatic
|
|
92
|
-
* rollback will occur.
|
|
93
|
-
*
|
|
94
|
-
* Does NOT check ignoreList - JavaScript decides what to install.
|
|
95
|
-
*
|
|
96
|
-
* @param {Function} callback - Callback function
|
|
97
|
-
* - Called with null on success (before WebView reload)
|
|
98
|
-
* - Called with {error: {message?: string}} on error
|
|
89
|
+
* @param {Function} callback - Callback(error)
|
|
90
|
+
* - null on success (WebView will reload)
|
|
91
|
+
* - {error: {code: string, message: string}} on error
|
|
99
92
|
*
|
|
100
93
|
* @example
|
|
101
|
-
*
|
|
102
|
-
* if (error)
|
|
103
|
-
*
|
|
104
|
-
* } else {
|
|
105
|
-
* console.log('Update installing, WebView will reload...');
|
|
106
|
-
* // After reload, MUST call canary() within 20 seconds!
|
|
107
|
-
* }
|
|
94
|
+
* hotUpdate.forceUpdate(function(err) {
|
|
95
|
+
* if (err) console.error(err.error.code);
|
|
96
|
+
* // WebView reloads, call canary() within 20 sec
|
|
108
97
|
* });
|
|
109
98
|
*/
|
|
110
99
|
forceUpdate: function(callback) {
|
|
111
100
|
exec(
|
|
112
101
|
function() {
|
|
113
|
-
// Success
|
|
114
102
|
if (callback) callback(null);
|
|
115
103
|
},
|
|
116
104
|
function(error) {
|
|
117
|
-
// Error
|
|
118
105
|
if (callback) callback({error: error});
|
|
119
106
|
},
|
|
120
107
|
'HotUpdates',
|
|
@@ -124,36 +111,30 @@ var HotUpdates = {
|
|
|
124
111
|
},
|
|
125
112
|
|
|
126
113
|
/**
|
|
127
|
-
* Confirm successful bundle load (
|
|
128
|
-
*
|
|
129
|
-
* MUST be called within 20 seconds after forceUpdate() to confirm
|
|
130
|
-
* that the new bundle loaded successfully. This stops the canary timer
|
|
131
|
-
* and prevents automatic rollback.
|
|
132
|
-
*
|
|
133
|
-
* If not called within 20 seconds:
|
|
134
|
-
* - Automatic rollback to previous version
|
|
135
|
-
* - Failed version added to ignoreList
|
|
136
|
-
* - WebView reloaded with previous version
|
|
137
|
-
*
|
|
138
|
-
* Call this immediately after your app initialization completes.
|
|
114
|
+
* Confirm successful bundle load (MUST call within 20 sec after forceUpdate)
|
|
139
115
|
*
|
|
140
|
-
* @param {string} version -
|
|
141
|
-
* @param {Function} [callback] - Optional callback
|
|
116
|
+
* @param {string} version - Current version
|
|
117
|
+
* @param {Function} [callback] - Optional callback
|
|
142
118
|
*
|
|
143
119
|
* @example
|
|
144
|
-
* // Call as early as possible after app loads
|
|
145
120
|
* document.addEventListener('deviceready', function() {
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
* }, false);
|
|
121
|
+
* hotUpdate.canary('2.0.0');
|
|
122
|
+
* });
|
|
149
123
|
*/
|
|
150
124
|
canary: function(version, callback) {
|
|
125
|
+
if (!version) {
|
|
126
|
+
if (callback) {
|
|
127
|
+
callback({error: {code: ErrorCodes.VERSION_REQUIRED, message: 'Version is required'}});
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
151
132
|
exec(
|
|
152
133
|
function() {
|
|
153
|
-
if (callback) callback();
|
|
134
|
+
if (callback) callback(null);
|
|
154
135
|
},
|
|
155
|
-
function() {
|
|
156
|
-
if (callback) callback();
|
|
136
|
+
function(error) {
|
|
137
|
+
if (callback) callback({error: error});
|
|
157
138
|
},
|
|
158
139
|
'HotUpdates',
|
|
159
140
|
'canary',
|
|
@@ -162,50 +143,62 @@ var HotUpdates = {
|
|
|
162
143
|
},
|
|
163
144
|
|
|
164
145
|
/**
|
|
165
|
-
* Get list of problematic versions
|
|
146
|
+
* Get list of problematic versions
|
|
166
147
|
*
|
|
167
|
-
*
|
|
168
|
-
* This is an INFORMATION-ONLY system - native does NOT block installation
|
|
169
|
-
* of versions in this list.
|
|
170
|
-
*
|
|
171
|
-
* JavaScript should read this list and decide whether to skip downloading/
|
|
172
|
-
* installing these versions. If JS decides to install a version from the
|
|
173
|
-
* ignoreList, that's allowed (per TS requirements).
|
|
174
|
-
*
|
|
175
|
-
* Native automatically adds versions to this list when rollback occurs.
|
|
176
|
-
* JavaScript cannot modify the list (no add/remove/clear methods per TS v2.1.0).
|
|
177
|
-
*
|
|
178
|
-
* @param {Function} callback - Callback function
|
|
179
|
-
* - Called with {versions: string[]} - Array of problematic version strings
|
|
148
|
+
* @param {Function} callback - Callback({versions: string[]})
|
|
180
149
|
*
|
|
181
150
|
* @example
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
* // JavaScript decides what to do with this information
|
|
187
|
-
* var shouldSkip = result.versions.includes(availableVersion);
|
|
188
|
-
* if (shouldSkip) {
|
|
189
|
-
* console.log('Skipping known problematic version');
|
|
190
|
-
* } else {
|
|
191
|
-
* // Download and install
|
|
151
|
+
* hotUpdate.getIgnoreList(function(result) {
|
|
152
|
+
* if (result.versions.includes(newVersion)) {
|
|
153
|
+
* console.log('Version is blacklisted');
|
|
192
154
|
* }
|
|
193
155
|
* });
|
|
194
156
|
*/
|
|
195
157
|
getIgnoreList: function(callback) {
|
|
196
158
|
exec(
|
|
197
|
-
function(
|
|
198
|
-
|
|
199
|
-
if (callback) callback({versions: versions || []});
|
|
159
|
+
function(result) {
|
|
160
|
+
if (callback) callback(result || {versions: []});
|
|
200
161
|
},
|
|
201
|
-
function(
|
|
202
|
-
// Error - return empty list
|
|
162
|
+
function() {
|
|
203
163
|
if (callback) callback({versions: []});
|
|
204
164
|
},
|
|
205
165
|
'HotUpdates',
|
|
206
166
|
'getIgnoreList',
|
|
207
167
|
[]
|
|
208
168
|
);
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get version info (debug method)
|
|
173
|
+
*
|
|
174
|
+
* @param {Function} callback - Callback with version info
|
|
175
|
+
* {
|
|
176
|
+
* appBundleVersion: string,
|
|
177
|
+
* installedVersion: string|null,
|
|
178
|
+
* previousVersion: string|null,
|
|
179
|
+
* canaryVersion: string|null,
|
|
180
|
+
* pendingVersion: string|null,
|
|
181
|
+
* hasPendingUpdate: boolean,
|
|
182
|
+
* ignoreList: string[]
|
|
183
|
+
* }
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* hotUpdate.getVersionInfo(function(info) {
|
|
187
|
+
* console.log('Current:', info.installedVersion || info.appBundleVersion);
|
|
188
|
+
* });
|
|
189
|
+
*/
|
|
190
|
+
getVersionInfo: function(callback) {
|
|
191
|
+
exec(
|
|
192
|
+
function(info) {
|
|
193
|
+
if (callback) callback(info);
|
|
194
|
+
},
|
|
195
|
+
function(error) {
|
|
196
|
+
if (callback) callback({error: error});
|
|
197
|
+
},
|
|
198
|
+
'HotUpdates',
|
|
199
|
+
'getVersionInfo',
|
|
200
|
+
[]
|
|
201
|
+
);
|
|
209
202
|
}
|
|
210
203
|
};
|
|
211
204
|
|