cordova-plugin-hot-updates 2.1.2 → 2.2.0
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 +61 -42
- package/package.json +2 -2
- package/plugin.xml +5 -1
- package/src/ios/HotUpdates+Helpers.h +23 -0
- package/src/ios/HotUpdates+Helpers.m +24 -0
- package/src/ios/HotUpdates.h +4 -3
- package/src/ios/HotUpdates.m +46 -212
- package/src/ios/HotUpdatesConstants.h +45 -0
- package/src/ios/HotUpdatesConstants.m +46 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Cordova Hot Updates Plugin v2.
|
|
1
|
+
# Cordova Hot Updates Plugin v2.2.0
|
|
2
2
|
|
|
3
3
|
Frontend-controlled manual hot updates for Cordova iOS applications using WebView Reload approach.
|
|
4
4
|
|
|
@@ -85,6 +85,66 @@ function downloadAndInstall(url, version) {
|
|
|
85
85
|
}
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
## Error Handling
|
|
89
|
+
|
|
90
|
+
All errors are returned in a unified format for programmatic handling:
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
// Error format
|
|
94
|
+
callback({
|
|
95
|
+
error: {
|
|
96
|
+
code: "ERROR_CODE", // Code for programmatic handling
|
|
97
|
+
message: "Detailed message" // Detailed message for logs
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Success result
|
|
102
|
+
callback(null)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Error Codes
|
|
106
|
+
|
|
107
|
+
#### getUpdate() errors:
|
|
108
|
+
- `UPDATE_DATA_REQUIRED` - Missing updateData parameter
|
|
109
|
+
- `URL_REQUIRED` - Missing url parameter
|
|
110
|
+
- `DOWNLOAD_IN_PROGRESS` - Download already in progress
|
|
111
|
+
- `DOWNLOAD_FAILED` - Network download error (message contains details)
|
|
112
|
+
- `HTTP_ERROR` - HTTP status != 200 (message contains status code)
|
|
113
|
+
- `TEMP_DIR_ERROR` - Error creating temporary directory
|
|
114
|
+
- `EXTRACTION_FAILED` - Error extracting ZIP archive
|
|
115
|
+
- `WWW_NOT_FOUND` - www folder not found in archive
|
|
116
|
+
|
|
117
|
+
#### forceUpdate() errors:
|
|
118
|
+
- `NO_UPDATE_READY` - getUpdate() not called first
|
|
119
|
+
- `UPDATE_FILES_NOT_FOUND` - Downloaded update files not found
|
|
120
|
+
- `INSTALL_FAILED` - Error copying files (message contains details)
|
|
121
|
+
|
|
122
|
+
#### canary() errors:
|
|
123
|
+
- `VERSION_REQUIRED` - Missing version parameter
|
|
124
|
+
|
|
125
|
+
### Error Handling Example
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
window.hotUpdate.getUpdate({url: 'http://...'}, function(error) {
|
|
129
|
+
if (error) {
|
|
130
|
+
console.error('[HotUpdates]', error.code, ':', error.message);
|
|
131
|
+
|
|
132
|
+
switch(error.code) {
|
|
133
|
+
case 'HTTP_ERROR':
|
|
134
|
+
// Handle HTTP errors
|
|
135
|
+
break;
|
|
136
|
+
case 'DOWNLOAD_FAILED':
|
|
137
|
+
// Handle network errors
|
|
138
|
+
break;
|
|
139
|
+
default:
|
|
140
|
+
console.error('Unknown error:', error);
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
console.log('Update downloaded successfully');
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
88
148
|
## API Reference
|
|
89
149
|
|
|
90
150
|
All API methods are available via `window.hotUpdate` after the `deviceready` event.
|
|
@@ -402,47 +462,6 @@ document.addEventListener('deviceready', function() {
|
|
|
402
462
|
}, false);
|
|
403
463
|
```
|
|
404
464
|
|
|
405
|
-
## Migration from v1.0.0
|
|
406
|
-
|
|
407
|
-
**Removed methods:**
|
|
408
|
-
- `getCurrentVersion()` - Manage in JS
|
|
409
|
-
- `getPendingUpdateInfo()` - Not needed
|
|
410
|
-
- `checkForUpdates()` - Frontend controls
|
|
411
|
-
- `downloadUpdate()` - Use `getUpdate()`
|
|
412
|
-
- `installUpdate()` - Use `forceUpdate()`
|
|
413
|
-
|
|
414
|
-
**New API:**
|
|
415
|
-
- `window.hotUpdate.getUpdate({url, version?}, callback)`
|
|
416
|
-
- `window.hotUpdate.forceUpdate(callback)`
|
|
417
|
-
- `window.hotUpdate.canary(version, callback)`
|
|
418
|
-
- `window.hotUpdate.getIgnoreList(callback)`
|
|
419
|
-
|
|
420
|
-
**Changes:**
|
|
421
|
-
- API via `window.hotUpdate` (not `window.HotUpdates`)
|
|
422
|
-
- Callback signature: `callback(error)` pattern
|
|
423
|
-
- No automatic background checking
|
|
424
|
-
|
|
425
|
-
## Changelog
|
|
426
|
-
|
|
427
|
-
### v2.1.2 (2025-11-13)
|
|
428
|
-
|
|
429
|
-
**Breaking Changes:**
|
|
430
|
-
- Changed API from `window.HotUpdates` to `window.hotUpdate`
|
|
431
|
-
- Removed automatic update checking
|
|
432
|
-
- Simplified to 4 methods: `getUpdate`, `forceUpdate`, `canary`, `getIgnoreList`
|
|
433
|
-
|
|
434
|
-
**New Features:**
|
|
435
|
-
- Frontend-controlled manual updates
|
|
436
|
-
- Two-step update flow
|
|
437
|
-
- 20-second canary timer
|
|
438
|
-
- IgnoreList system
|
|
439
|
-
- Auto-install on next launch
|
|
440
|
-
- WebView cache clearing
|
|
441
|
-
|
|
442
|
-
### v1.0.0
|
|
443
|
-
|
|
444
|
-
- Initial release
|
|
445
|
-
|
|
446
465
|
## License
|
|
447
466
|
|
|
448
467
|
Custom Non-Commercial License - See [LICENSE](LICENSE) file
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cordova-plugin-hot-updates",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
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": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
8
|
"prepublishOnly": "npm run verify",
|
|
9
|
-
"verify": "node -e \"console.log('Verifying package structure...'); const fs = require('fs'); ['www/HotUpdates.js', 'src/ios/HotUpdates.h', 'src/ios/HotUpdates.m', 'plugin.xml', 'LICENSE', 'README.md'].forEach(f => { if (!fs.existsSync(f)) throw new Error('Missing required file: ' + f); }); console.log('✓ All required files present');\"",
|
|
9
|
+
"verify": "node -e \"console.log('Verifying package structure...'); const fs = require('fs'); ['www/HotUpdates.js', 'src/ios/HotUpdates.h', 'src/ios/HotUpdates.m', 'src/ios/HotUpdatesConstants.h', 'src/ios/HotUpdatesConstants.m', 'src/ios/HotUpdates+Helpers.h', 'src/ios/HotUpdates+Helpers.m', 'plugin.xml', 'LICENSE', 'README.md'].forEach(f => { if (!fs.existsSync(f)) throw new Error('Missing required file: ' + f); }); console.log('✓ All required files present');\"",
|
|
10
10
|
"pack-test": "npm pack && echo '\n✓ Package created. Test installation with: cordova plugin add ./cordova-plugin-hot-updates-*.tgz'",
|
|
11
11
|
"preversion": "npm run verify",
|
|
12
12
|
"postversion": "git push && git push --tags"
|
package/plugin.xml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<?xml version='1.0' encoding='utf-8'?>
|
|
2
2
|
<plugin id="cordova-plugin-hot-updates"
|
|
3
|
-
version="2.
|
|
3
|
+
version="2.2.0"
|
|
4
4
|
xmlns="http://apache.org/cordova/ns/plugins/1.0"
|
|
5
5
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
6
6
|
|
|
@@ -40,6 +40,10 @@
|
|
|
40
40
|
<!-- Native iOS files -->
|
|
41
41
|
<source-file src="src/ios/HotUpdates.h" />
|
|
42
42
|
<source-file src="src/ios/HotUpdates.m" />
|
|
43
|
+
<source-file src="src/ios/HotUpdatesConstants.h" />
|
|
44
|
+
<source-file src="src/ios/HotUpdatesConstants.m" />
|
|
45
|
+
<source-file src="src/ios/HotUpdates+Helpers.h" />
|
|
46
|
+
<source-file src="src/ios/HotUpdates+Helpers.m" />
|
|
43
47
|
|
|
44
48
|
<!-- Required frameworks -->
|
|
45
49
|
<framework src="Foundation.framework" />
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* @file HotUpdates+Helpers.h
|
|
3
|
+
* @brief Helper methods for Hot Updates Plugin
|
|
4
|
+
* @details Category extension providing utility methods for error handling
|
|
5
|
+
* @version 2.2.0
|
|
6
|
+
* @date 2025-11-13
|
|
7
|
+
* @author Mustafin Vladimir
|
|
8
|
+
* @copyright Copyright (c) 2025. All rights reserved.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#import "HotUpdates.h"
|
|
12
|
+
|
|
13
|
+
@interface HotUpdates (Helpers)
|
|
14
|
+
|
|
15
|
+
/*!
|
|
16
|
+
* @brief Create error dictionary for JavaScript callback
|
|
17
|
+
* @param code Error code (e.g., "URL_REQUIRED")
|
|
18
|
+
* @param message Detailed message for logs
|
|
19
|
+
* @return Dictionary with error structure {error: {code: "...", message: "..."}}
|
|
20
|
+
*/
|
|
21
|
+
- (NSDictionary*)createError:(NSString*)code message:(NSString*)message;
|
|
22
|
+
|
|
23
|
+
@end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* @file HotUpdates+Helpers.m
|
|
3
|
+
* @brief Implementation of helper methods for Hot Updates Plugin
|
|
4
|
+
* @details Provides utility methods for error handling and response formatting
|
|
5
|
+
* @version 2.2.0
|
|
6
|
+
* @date 2025-11-13
|
|
7
|
+
* @author Mustafin Vladimir
|
|
8
|
+
* @copyright Copyright (c) 2025. All rights reserved.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#import "HotUpdates+Helpers.h"
|
|
12
|
+
|
|
13
|
+
@implementation HotUpdates (Helpers)
|
|
14
|
+
|
|
15
|
+
- (NSDictionary*)createError:(NSString*)code message:(NSString*)message {
|
|
16
|
+
return @{
|
|
17
|
+
@"error": @{
|
|
18
|
+
@"code": code,
|
|
19
|
+
@"message": message
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@end
|
package/src/ios/HotUpdates.h
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
#import <UIKit/UIKit.h>
|
|
21
21
|
#import <Cordova/CDVPlugin.h>
|
|
22
|
+
#import "HotUpdatesConstants.h"
|
|
23
|
+
|
|
22
24
|
@interface HotUpdates : CDVPlugin
|
|
23
25
|
{
|
|
24
26
|
NSString *documentsPath;
|
|
@@ -61,9 +63,8 @@
|
|
|
61
63
|
- (void)getUpdate:(CDVInvokedUrlCommand*)command; // Download update
|
|
62
64
|
- (void)forceUpdate:(CDVInvokedUrlCommand*)command; // Install downloaded update
|
|
63
65
|
- (void)canary:(CDVInvokedUrlCommand*)command; // Confirm successful load
|
|
64
|
-
- (void)rollback:(CDVInvokedUrlCommand*)command; // Rollback to previous version
|
|
65
66
|
|
|
66
|
-
//
|
|
67
|
-
- (void)getVersionInfo:(CDVInvokedUrlCommand*)command;
|
|
67
|
+
// Debug methods
|
|
68
|
+
- (void)getVersionInfo:(CDVInvokedUrlCommand*)command; // Get all version info for debugging
|
|
68
69
|
|
|
69
70
|
@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.
|
|
16
|
-
* @date 2025-11-
|
|
15
|
+
* @version 2.2.0
|
|
16
|
+
* @date 2025-11-03
|
|
17
17
|
* @author Mustafin Vladimir
|
|
18
18
|
* @copyright Copyright (c) 2025. All rights reserved.
|
|
19
19
|
*/
|
|
@@ -21,27 +21,10 @@
|
|
|
21
21
|
#import <Cordova/CDV.h>
|
|
22
22
|
#import <Cordova/CDVViewController.h>
|
|
23
23
|
#import "HotUpdates.h"
|
|
24
|
+
#import "HotUpdates+Helpers.h"
|
|
25
|
+
#import "HotUpdatesConstants.h"
|
|
24
26
|
#import <SSZipArchive/SSZipArchive.h>
|
|
25
27
|
|
|
26
|
-
// Storage keys
|
|
27
|
-
static NSString * const kInstalledVersion = @"hot_updates_installed_version";
|
|
28
|
-
static NSString * const kPendingVersion = @"hot_updates_pending_version";
|
|
29
|
-
static NSString * const kHasPending = @"hot_updates_has_pending";
|
|
30
|
-
static NSString * const kPreviousVersion = @"hot_updates_previous_version";
|
|
31
|
-
static NSString * const kIgnoreList = @"hot_updates_ignore_list";
|
|
32
|
-
static NSString * const kCanaryVersion = @"hot_updates_canary_version";
|
|
33
|
-
static NSString * const kDownloadInProgress = @"hot_updates_download_in_progress";
|
|
34
|
-
|
|
35
|
-
// Constants for v2.1.0
|
|
36
|
-
static NSString * const kPendingUpdateURL = @"hot_updates_pending_update_url";
|
|
37
|
-
static NSString * const kPendingUpdateReady = @"hot_updates_pending_ready";
|
|
38
|
-
|
|
39
|
-
// Directory names
|
|
40
|
-
static NSString * const kWWWDirName = @"www";
|
|
41
|
-
static NSString * const kPreviousWWWDirName = @"www_previous";
|
|
42
|
-
static NSString * const kBackupWWWDirName = @"www_backup";
|
|
43
|
-
static NSString * const kPendingUpdateDirName = @"pending_update";
|
|
44
|
-
|
|
45
28
|
// Флаг для предотвращения повторных перезагрузок при навигации внутри WebView
|
|
46
29
|
static BOOL hasPerformedInitialReload = NO;
|
|
47
30
|
|
|
@@ -65,24 +48,18 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
65
48
|
- (void)pluginInitialize {
|
|
66
49
|
[super pluginInitialize];
|
|
67
50
|
|
|
68
|
-
// Получаем пути к директориям
|
|
69
51
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
|
70
52
|
documentsPath = [paths objectAtIndex:0];
|
|
71
53
|
wwwPath = [documentsPath stringByAppendingPathComponent:kWWWDirName];
|
|
72
54
|
previousVersionPath = [documentsPath stringByAppendingPathComponent:kPreviousWWWDirName];
|
|
73
55
|
|
|
74
|
-
// Загружаем конфигурацию
|
|
75
56
|
[self loadConfiguration];
|
|
76
|
-
|
|
77
|
-
// Загрузка ignoreList
|
|
78
57
|
[self loadIgnoreList];
|
|
79
58
|
|
|
80
|
-
// Инициализация переменных
|
|
81
59
|
isUpdateReadyToInstall = NO;
|
|
82
60
|
pendingUpdateURL = nil;
|
|
83
61
|
pendingUpdateVersion = nil;
|
|
84
62
|
|
|
85
|
-
// Загружаем информацию о pending update если есть
|
|
86
63
|
pendingUpdateURL = [[NSUserDefaults standardUserDefaults] stringForKey:kPendingUpdateURL];
|
|
87
64
|
isUpdateReadyToInstall = [[NSUserDefaults standardUserDefaults] boolForKey:kPendingUpdateReady];
|
|
88
65
|
if (isUpdateReadyToInstall) {
|
|
@@ -95,21 +72,14 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
95
72
|
NSLog(@"[HotUpdates] Documents www path: %@", wwwPath);
|
|
96
73
|
NSLog(@"[HotUpdates] Ignore list: %@", ignoreList);
|
|
97
74
|
|
|
98
|
-
// 1. Проверяем и устанавливаем pending updates
|
|
99
75
|
[self checkAndInstallPendingUpdate];
|
|
100
|
-
|
|
101
|
-
// 2. Создаем папку www если её нет (копируем из bundle)
|
|
102
76
|
[self initializeWWWFolder];
|
|
103
|
-
|
|
104
|
-
// 3. Переключаем WebView на обновленный контент и перезагружаем
|
|
105
77
|
[self switchToUpdatedContentWithReload];
|
|
106
78
|
|
|
107
|
-
// 4. Запускаем canary timer на 20 секунд
|
|
108
79
|
NSString *currentVersion = [[NSUserDefaults standardUserDefaults] stringForKey:kInstalledVersion];
|
|
109
80
|
if (currentVersion) {
|
|
110
81
|
NSString *canaryVersion = [[NSUserDefaults standardUserDefaults] stringForKey:kCanaryVersion];
|
|
111
82
|
|
|
112
|
-
// Если canary еще не был вызван для текущей версии
|
|
113
83
|
if (!canaryVersion || ![canaryVersion isEqualToString:currentVersion]) {
|
|
114
84
|
NSLog(@"[HotUpdates] Starting canary timer (20 seconds) for version %@", currentVersion);
|
|
115
85
|
|
|
@@ -127,7 +97,6 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
127
97
|
}
|
|
128
98
|
|
|
129
99
|
- (void)loadConfiguration {
|
|
130
|
-
// Получаем версию bundle приложения
|
|
131
100
|
appBundleVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
|
|
132
101
|
if (!appBundleVersion) {
|
|
133
102
|
appBundleVersion = @"1.0.0";
|
|
@@ -146,14 +115,9 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
146
115
|
NSString *pendingVersion = [[NSUserDefaults standardUserDefaults] stringForKey:kPendingVersion];
|
|
147
116
|
NSString *installedVersion = [[NSUserDefaults standardUserDefaults] stringForKey:kInstalledVersion];
|
|
148
117
|
|
|
149
|
-
// ТЗ п.7: Если пользователь проигнорировал попап, обновление устанавливается автоматически при следующем запуске
|
|
150
118
|
if (hasPendingUpdate && pendingVersion) {
|
|
151
|
-
// ВАЖНО (ТЗ): НЕ проверяем ignoreList - JS сам решил загрузить эту версию
|
|
152
|
-
// Если версия скачана (через getUpdate), значит JS одобрил её установку
|
|
153
|
-
|
|
154
119
|
NSLog(@"[HotUpdates] Installing pending update %@ to Documents/www (auto-install on launch)", pendingVersion);
|
|
155
120
|
|
|
156
|
-
// НОВОЕ: Создаем резервную копию текущей версии перед установкой
|
|
157
121
|
[self backupCurrentVersion];
|
|
158
122
|
|
|
159
123
|
NSString *pendingUpdatePath = [documentsPath stringByAppendingPathComponent:kPendingUpdateDirName];
|
|
@@ -161,24 +125,20 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
161
125
|
NSString *documentsWwwPath = [documentsPath stringByAppendingPathComponent:kWWWDirName];
|
|
162
126
|
|
|
163
127
|
if ([[NSFileManager defaultManager] fileExistsAtPath:pendingWwwPath]) {
|
|
164
|
-
// Удаляем старую Documents/www
|
|
165
128
|
if ([[NSFileManager defaultManager] fileExistsAtPath:documentsWwwPath]) {
|
|
166
129
|
[[NSFileManager defaultManager] removeItemAtPath:documentsWwwPath error:nil];
|
|
167
130
|
}
|
|
168
131
|
|
|
169
|
-
// Копируем pending_update/www в Documents/www
|
|
170
132
|
NSError *copyError = nil;
|
|
171
133
|
BOOL copySuccess = [[NSFileManager defaultManager] copyItemAtPath:pendingWwwPath toPath:documentsWwwPath error:©Error];
|
|
172
134
|
|
|
173
135
|
if (copySuccess) {
|
|
174
|
-
// Помечаем как установленный
|
|
175
136
|
[[NSUserDefaults standardUserDefaults] setObject:pendingVersion forKey:kInstalledVersion];
|
|
176
137
|
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:kHasPending];
|
|
177
138
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kPendingVersion];
|
|
178
|
-
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kCanaryVersion];
|
|
139
|
+
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kCanaryVersion];
|
|
179
140
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
180
141
|
|
|
181
|
-
// Очищаем pending_update папку
|
|
182
142
|
[[NSFileManager defaultManager] removeItemAtPath:pendingUpdatePath error:nil];
|
|
183
143
|
|
|
184
144
|
NSLog(@"[HotUpdates] Update %@ installed successfully (canary timer will start)", pendingVersion);
|
|
@@ -195,7 +155,7 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
195
155
|
* Uses static flag to prevent reload on every page navigation (only once per app launch)
|
|
196
156
|
*/
|
|
197
157
|
- (void)switchToUpdatedContentWithReload {
|
|
198
|
-
// Предотвращаем повторные перезагрузки при навигации между страницами
|
|
158
|
+
// Предотвращаем повторные перезагрузки при навигации между страницами
|
|
199
159
|
if (hasPerformedInitialReload) {
|
|
200
160
|
NSLog(@"[HotUpdates] Initial reload already performed, skipping");
|
|
201
161
|
return;
|
|
@@ -207,19 +167,15 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
207
167
|
NSString *documentsWwwPath = [documentsPath stringByAppendingPathComponent:kWWWDirName];
|
|
208
168
|
NSString *indexPath = [documentsWwwPath stringByAppendingPathComponent:@"index.html"];
|
|
209
169
|
|
|
210
|
-
// Проверяем, что файлы действительно существуют
|
|
211
170
|
if ([[NSFileManager defaultManager] fileExistsAtPath:indexPath]) {
|
|
212
171
|
NSLog(@"[HotUpdates] Using WebView reload approach");
|
|
213
172
|
NSLog(@"[HotUpdates] Found installed update version: %@", installedVersion);
|
|
214
173
|
|
|
215
|
-
// Устанавливаем новый путь
|
|
216
174
|
((CDVViewController *)self.viewController).wwwFolderName = documentsWwwPath;
|
|
217
175
|
NSLog(@"[HotUpdates] Changed wwwFolderName to: %@", documentsWwwPath);
|
|
218
176
|
|
|
219
|
-
// Принудительно перезагружаем WebView для применения нового пути
|
|
220
177
|
[self reloadWebView];
|
|
221
178
|
|
|
222
|
-
// Устанавливаем флаг, чтобы больше не перезагружать при навигации
|
|
223
179
|
hasPerformedInitialReload = YES;
|
|
224
180
|
|
|
225
181
|
NSLog(@"[HotUpdates] WebView reloaded with updated content (version: %@)", installedVersion);
|
|
@@ -228,7 +184,6 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
228
184
|
}
|
|
229
185
|
} else {
|
|
230
186
|
NSLog(@"[HotUpdates] No installed updates, using bundle www");
|
|
231
|
-
// Устанавливаем флаг даже если нет обновлений, чтобы не проверять постоянно
|
|
232
187
|
hasPerformedInitialReload = YES;
|
|
233
188
|
}
|
|
234
189
|
}
|
|
@@ -256,11 +211,9 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
256
211
|
}
|
|
257
212
|
|
|
258
213
|
- (void)reloadWebView {
|
|
259
|
-
// Приводим к типу CDVViewController для доступа к webViewEngine
|
|
260
214
|
if ([self.viewController isKindOfClass:[CDVViewController class]]) {
|
|
261
215
|
CDVViewController *cdvViewController = (CDVViewController *)self.viewController;
|
|
262
216
|
|
|
263
|
-
// Строим новый URL для обновленного контента
|
|
264
217
|
NSString *documentsWwwPath = [documentsPath stringByAppendingPathComponent:kWWWDirName];
|
|
265
218
|
NSString *indexPath = [documentsWwwPath stringByAppendingPathComponent:@"index.html"];
|
|
266
219
|
NSURL *fileURL = [NSURL fileURLWithPath:indexPath];
|
|
@@ -270,13 +223,11 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
270
223
|
|
|
271
224
|
id webViewEngine = cdvViewController.webViewEngine;
|
|
272
225
|
if (webViewEngine && [webViewEngine respondsToSelector:@selector(engineWebView)]) {
|
|
273
|
-
// Получаем WKWebView
|
|
274
226
|
WKWebView *webView = [webViewEngine performSelector:@selector(engineWebView)];
|
|
275
227
|
|
|
276
228
|
if (webView && [webView isKindOfClass:[WKWebView class]]) {
|
|
277
|
-
//
|
|
229
|
+
// loadFileURL:allowingReadAccessToURL: правильно настраивает sandbox permissions для локальных файлов
|
|
278
230
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
279
|
-
// Этот метод правильно настраивает sandbox для локальных файлов
|
|
280
231
|
[webView loadFileURL:fileURL allowingReadAccessToURL:allowReadAccessToURL];
|
|
281
232
|
NSLog(@"[HotUpdates] WebView loadFileURL executed with sandbox permissions");
|
|
282
233
|
});
|
|
@@ -294,12 +245,10 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
294
245
|
|
|
295
246
|
- (void)initializeWWWFolder {
|
|
296
247
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
297
|
-
|
|
298
|
-
// Проверяем, существует ли папка www в Documents
|
|
248
|
+
|
|
299
249
|
if (![fileManager fileExistsAtPath:wwwPath]) {
|
|
300
250
|
NSLog(@"[HotUpdates] WWW folder not found in Documents. Creating and copying from bundle...");
|
|
301
|
-
|
|
302
|
-
// Копируем содержимое www из bundle в Documents
|
|
251
|
+
|
|
303
252
|
NSString *bundleWWWPath = [[NSBundle mainBundle] pathForResource:@"www" ofType:nil];
|
|
304
253
|
if (bundleWWWPath) {
|
|
305
254
|
NSError *error;
|
|
@@ -323,7 +272,6 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
323
272
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
324
273
|
NSError *error = nil;
|
|
325
274
|
|
|
326
|
-
// Создаем папку назначения если её нет
|
|
327
275
|
if (![fileManager fileExistsAtPath:destination]) {
|
|
328
276
|
[fileManager createDirectoryAtPath:destination withIntermediateDirectories:YES attributes:nil error:&error];
|
|
329
277
|
if (error) {
|
|
@@ -332,24 +280,19 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
332
280
|
}
|
|
333
281
|
}
|
|
334
282
|
|
|
335
|
-
// Распаковка ZIP архива с SSZipArchive
|
|
336
283
|
NSLog(@"[HotUpdates] Extracting ZIP archive using SSZipArchive library");
|
|
337
284
|
|
|
338
|
-
// Простая проверка файла
|
|
339
285
|
if (![[NSFileManager defaultManager] fileExistsAtPath:zipPath]) {
|
|
340
286
|
NSLog(@"[HotUpdates] ZIP file does not exist: %@", zipPath);
|
|
341
287
|
return NO;
|
|
342
288
|
}
|
|
343
|
-
|
|
344
|
-
// Создаем временную папку для распаковки
|
|
289
|
+
|
|
345
290
|
NSString *tempExtractPath = [destination stringByAppendingPathComponent:@"temp_extract"];
|
|
346
|
-
|
|
347
|
-
// Удаляем существующую временную папку
|
|
291
|
+
|
|
348
292
|
if ([[NSFileManager defaultManager] fileExistsAtPath:tempExtractPath]) {
|
|
349
293
|
[[NSFileManager defaultManager] removeItemAtPath:tempExtractPath error:nil];
|
|
350
294
|
}
|
|
351
|
-
|
|
352
|
-
// Создаем временную папку
|
|
295
|
+
|
|
353
296
|
if (![[NSFileManager defaultManager] createDirectoryAtPath:tempExtractPath withIntermediateDirectories:YES attributes:nil error:&error]) {
|
|
354
297
|
NSLog(@"[HotUpdates] Failed to create temp extraction folder: %@", error.localizedDescription);
|
|
355
298
|
return NO;
|
|
@@ -357,17 +300,15 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
357
300
|
|
|
358
301
|
NSLog(@"[HotUpdates] Extracting to temp location: %@", tempExtractPath);
|
|
359
302
|
|
|
360
|
-
// Распаковываем ZIP архив
|
|
361
303
|
BOOL extractSuccess = [SSZipArchive unzipFileAtPath:zipPath toDestination:tempExtractPath];
|
|
362
304
|
|
|
363
305
|
if (extractSuccess) {
|
|
364
306
|
NSLog(@"[HotUpdates] ZIP extraction successful");
|
|
365
307
|
|
|
366
|
-
// Проверяем содержимое распакованного архива
|
|
367
308
|
NSArray *extractedContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:tempExtractPath error:nil];
|
|
368
309
|
NSLog(@"[HotUpdates] Extracted contents: %@", extractedContents);
|
|
369
|
-
|
|
370
|
-
// Ищем папку www
|
|
310
|
+
|
|
311
|
+
// Ищем папку www (может быть вложенной)
|
|
371
312
|
NSString *wwwSourcePath = nil;
|
|
372
313
|
for (NSString *item in extractedContents) {
|
|
373
314
|
NSString *itemPath = [tempExtractPath stringByAppendingPathComponent:item];
|
|
@@ -377,7 +318,6 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
377
318
|
wwwSourcePath = itemPath;
|
|
378
319
|
break;
|
|
379
320
|
}
|
|
380
|
-
// Проверяем, есть ли www внутри папки
|
|
381
321
|
NSString *nestedWwwPath = [itemPath stringByAppendingPathComponent:@"www"];
|
|
382
322
|
if ([[NSFileManager defaultManager] fileExistsAtPath:nestedWwwPath]) {
|
|
383
323
|
wwwSourcePath = nestedWwwPath;
|
|
@@ -385,26 +325,22 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
385
325
|
}
|
|
386
326
|
}
|
|
387
327
|
}
|
|
388
|
-
|
|
328
|
+
|
|
389
329
|
if (wwwSourcePath) {
|
|
390
330
|
NSLog(@"[HotUpdates] Found www folder at: %@", wwwSourcePath);
|
|
391
331
|
|
|
392
|
-
// Копируем www папку в финальное место
|
|
393
332
|
NSString *finalWwwPath = [destination stringByAppendingPathComponent:@"www"];
|
|
394
333
|
|
|
395
|
-
// Удаляем существующую папку www если есть
|
|
396
334
|
if ([[NSFileManager defaultManager] fileExistsAtPath:finalWwwPath]) {
|
|
397
335
|
[[NSFileManager defaultManager] removeItemAtPath:finalWwwPath error:nil];
|
|
398
336
|
}
|
|
399
337
|
|
|
400
|
-
// Копируем новую www папку
|
|
401
338
|
NSError *copyError = nil;
|
|
402
339
|
BOOL copySuccess = [[NSFileManager defaultManager] copyItemAtPath:wwwSourcePath toPath:finalWwwPath error:©Error];
|
|
403
340
|
|
|
404
341
|
if (copySuccess) {
|
|
405
342
|
NSLog(@"[HotUpdates] www folder copied successfully to: %@", finalWwwPath);
|
|
406
343
|
|
|
407
|
-
// Очищаем временную папку
|
|
408
344
|
[[NSFileManager defaultManager] removeItemAtPath:tempExtractPath error:nil];
|
|
409
345
|
|
|
410
346
|
NSLog(@"[HotUpdates] ZIP extraction completed successfully");
|
|
@@ -417,11 +353,9 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
417
353
|
NSLog(@"[HotUpdates] Available contents: %@", extractedContents);
|
|
418
354
|
}
|
|
419
355
|
|
|
420
|
-
// Очищаем временную папку при ошибке
|
|
421
356
|
[[NSFileManager defaultManager] removeItemAtPath:tempExtractPath error:nil];
|
|
422
357
|
} else {
|
|
423
358
|
NSLog(@"[HotUpdates] Failed to extract ZIP archive");
|
|
424
|
-
// Очищаем временную папку при ошибке
|
|
425
359
|
[[NSFileManager defaultManager] removeItemAtPath:tempExtractPath error:nil];
|
|
426
360
|
}
|
|
427
361
|
|
|
@@ -471,7 +405,6 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
471
405
|
NSString *currentVersion = [[NSUserDefaults standardUserDefaults] stringForKey:kInstalledVersion];
|
|
472
406
|
NSString *previousVersion = [self getPreviousVersion];
|
|
473
407
|
|
|
474
|
-
// Если некуда откатываться (свежая установка из Store) - ничего не делаем
|
|
475
408
|
if (!previousVersion || previousVersion.length == 0) {
|
|
476
409
|
NSLog(@"[HotUpdates] Fresh install from Store, rollback not possible");
|
|
477
410
|
return;
|
|
@@ -479,21 +412,17 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
479
412
|
|
|
480
413
|
NSLog(@"[HotUpdates] Version %@ considered faulty, performing rollback", currentVersion);
|
|
481
414
|
|
|
482
|
-
// Добавляем в ignoreList
|
|
483
415
|
if (currentVersion) {
|
|
484
416
|
[self addVersionToIgnoreList:currentVersion];
|
|
485
417
|
}
|
|
486
418
|
|
|
487
|
-
// Выполняем rollback
|
|
488
419
|
BOOL rollbackSuccess = [self rollbackToPreviousVersion];
|
|
489
420
|
|
|
490
421
|
if (rollbackSuccess) {
|
|
491
422
|
NSLog(@"[HotUpdates] Automatic rollback completed successfully");
|
|
492
423
|
|
|
493
|
-
// Сбрасываем флаг для разрешения перезагрузки после rollback
|
|
494
424
|
hasPerformedInitialReload = NO;
|
|
495
425
|
|
|
496
|
-
// Очищаем кэш и перезагружаем WebView
|
|
497
426
|
[self clearWebViewCache];
|
|
498
427
|
[self reloadWebView];
|
|
499
428
|
} else {
|
|
@@ -549,26 +478,23 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
549
478
|
|
|
550
479
|
NSLog(@"[HotUpdates] Rollback: %@ -> %@", currentVersion ?: @"bundle", previousVersion ?: @"nil");
|
|
551
480
|
|
|
552
|
-
// Проверка: нет previousVersion
|
|
553
481
|
if (!previousVersion || previousVersion.length == 0) {
|
|
554
482
|
NSLog(@"[HotUpdates] Rollback failed: no previous version");
|
|
555
483
|
return NO;
|
|
556
484
|
}
|
|
557
485
|
|
|
558
|
-
// Проверка: папка не существует
|
|
559
486
|
if (![fileManager fileExistsAtPath:previousVersionPath]) {
|
|
560
487
|
NSLog(@"[HotUpdates] Rollback failed: previous version folder not found");
|
|
561
488
|
return NO;
|
|
562
489
|
}
|
|
563
490
|
|
|
564
|
-
//
|
|
491
|
+
// Защита от цикла rollback
|
|
565
492
|
NSString *effectiveCurrentVersion = currentVersion ?: appBundleVersion;
|
|
566
493
|
if ([previousVersion isEqualToString:effectiveCurrentVersion]) {
|
|
567
494
|
NSLog(@"[HotUpdates] Rollback failed: cannot rollback to same version");
|
|
568
495
|
return NO;
|
|
569
496
|
}
|
|
570
497
|
|
|
571
|
-
// Создаем временную резервную копию текущей
|
|
572
498
|
NSString *tempBackupPath = [documentsPath stringByAppendingPathComponent:kBackupWWWDirName];
|
|
573
499
|
if ([fileManager fileExistsAtPath:tempBackupPath]) {
|
|
574
500
|
[fileManager removeItemAtPath:tempBackupPath error:nil];
|
|
@@ -576,7 +502,6 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
576
502
|
|
|
577
503
|
NSError *error = nil;
|
|
578
504
|
|
|
579
|
-
// Бэкапим текущую версию
|
|
580
505
|
if ([fileManager fileExistsAtPath:wwwPath]) {
|
|
581
506
|
[fileManager moveItemAtPath:wwwPath toPath:tempBackupPath error:&error];
|
|
582
507
|
if (error) {
|
|
@@ -585,23 +510,19 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
585
510
|
}
|
|
586
511
|
}
|
|
587
512
|
|
|
588
|
-
// Копируем предыдущую версию
|
|
589
513
|
BOOL success = [fileManager copyItemAtPath:previousVersionPath
|
|
590
514
|
toPath:wwwPath
|
|
591
515
|
error:&error];
|
|
592
516
|
|
|
593
517
|
if (success) {
|
|
594
|
-
// Обновляем метаданные (previousVersion очищается для предотвращения циклов)
|
|
595
518
|
[[NSUserDefaults standardUserDefaults] setObject:previousVersion forKey:kInstalledVersion];
|
|
596
519
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kPreviousVersion];
|
|
597
520
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
598
521
|
|
|
599
|
-
// Очищаем временный бэкап
|
|
600
522
|
[fileManager removeItemAtPath:tempBackupPath error:nil];
|
|
601
523
|
|
|
602
524
|
NSLog(@"[HotUpdates] Rollback successful: %@ -> %@", currentVersion, previousVersion);
|
|
603
525
|
|
|
604
|
-
// Добавляем проблемную версию в ignoreList
|
|
605
526
|
if (currentVersion) {
|
|
606
527
|
[self addVersionToIgnoreList:currentVersion];
|
|
607
528
|
}
|
|
@@ -610,7 +531,6 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
610
531
|
} else {
|
|
611
532
|
NSLog(@"[HotUpdates] Rollback failed: %@", error.localizedDescription);
|
|
612
533
|
|
|
613
|
-
// Восстанавливаем текущую версию
|
|
614
534
|
if ([fileManager fileExistsAtPath:tempBackupPath]) {
|
|
615
535
|
[fileManager moveItemAtPath:tempBackupPath toPath:wwwPath error:nil];
|
|
616
536
|
}
|
|
@@ -626,11 +546,8 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
626
546
|
|
|
627
547
|
if (!updateData) {
|
|
628
548
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
629
|
-
messageAsDictionary
|
|
630
|
-
|
|
631
|
-
@"message": @"Update data required"
|
|
632
|
-
}
|
|
633
|
-
}];
|
|
549
|
+
messageAsDictionary:[self createError:kErrorUpdateDataRequired
|
|
550
|
+
message:@"Update data required"]];
|
|
634
551
|
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
635
552
|
return;
|
|
636
553
|
}
|
|
@@ -639,16 +556,12 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
639
556
|
|
|
640
557
|
if (!downloadURL) {
|
|
641
558
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
642
|
-
messageAsDictionary
|
|
643
|
-
|
|
644
|
-
@"message": @"URL required"
|
|
645
|
-
}
|
|
646
|
-
}];
|
|
559
|
+
messageAsDictionary:[self createError:kErrorURLRequired
|
|
560
|
+
message:@"URL is required"]];
|
|
647
561
|
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
648
562
|
return;
|
|
649
563
|
}
|
|
650
564
|
|
|
651
|
-
// Опциональная версия (для автоустановки при следующем запуске)
|
|
652
565
|
NSString *updateVersion = [updateData objectForKey:@"version"];
|
|
653
566
|
if (!updateVersion) {
|
|
654
567
|
updateVersion = @"pending";
|
|
@@ -657,50 +570,38 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
657
570
|
NSLog(@"[HotUpdates] getUpdate() called - downloading update from: %@", downloadURL);
|
|
658
571
|
NSLog(@"[HotUpdates] Version: %@", updateVersion);
|
|
659
572
|
|
|
660
|
-
// ВАЖНО (ТЗ): НЕ проверяем ignoreList - JS сам контролирует что загружать
|
|
661
|
-
|
|
662
|
-
// 1. Проверяем, не установлена ли уже эта версия
|
|
663
573
|
NSString *installedVersion = [[NSUserDefaults standardUserDefaults] stringForKey:kInstalledVersion];
|
|
664
574
|
if (installedVersion && [installedVersion isEqualToString:updateVersion]) {
|
|
665
575
|
NSLog(@"[HotUpdates] Version %@ already installed, skipping download", updateVersion);
|
|
666
|
-
// Возвращаем SUCCESS - версия уже установлена
|
|
667
576
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
|
668
577
|
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
669
578
|
return;
|
|
670
579
|
}
|
|
671
580
|
|
|
672
|
-
// 2. Проверяем, не скачана ли уже эта версия (hasPending + та же версия)
|
|
673
581
|
BOOL hasPending = [[NSUserDefaults standardUserDefaults] boolForKey:kHasPending];
|
|
674
582
|
NSString *existingPendingVersion = [[NSUserDefaults standardUserDefaults] stringForKey:kPendingVersion];
|
|
675
583
|
|
|
676
584
|
if (hasPending && existingPendingVersion && [existingPendingVersion isEqualToString:updateVersion]) {
|
|
677
585
|
NSLog(@"[HotUpdates] Version %@ already downloaded, skipping re-download", updateVersion);
|
|
678
|
-
// Возвращаем SUCCESS - версия уже скачана, повторная загрузка не нужна
|
|
679
586
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
|
680
587
|
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
681
588
|
return;
|
|
682
589
|
}
|
|
683
590
|
|
|
684
|
-
// 3. Проверяем, не скачивается ли уже обновление
|
|
685
591
|
if (isDownloadingUpdate) {
|
|
686
592
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
687
|
-
messageAsDictionary
|
|
688
|
-
|
|
689
|
-
@"message": @"Download already in progress"
|
|
690
|
-
}
|
|
691
|
-
}];
|
|
593
|
+
messageAsDictionary:[self createError:kErrorDownloadInProgress
|
|
594
|
+
message:@"Download already in progress"]];
|
|
692
595
|
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
693
596
|
return;
|
|
694
597
|
}
|
|
695
598
|
|
|
696
|
-
// Сохраняем URL и версию для последующей установки и автоустановки
|
|
697
599
|
pendingUpdateURL = downloadURL;
|
|
698
600
|
pendingUpdateVersion = updateVersion;
|
|
699
601
|
[[NSUserDefaults standardUserDefaults] setObject:downloadURL forKey:kPendingUpdateURL];
|
|
700
602
|
[[NSUserDefaults standardUserDefaults] setObject:updateVersion forKey:kPendingVersion];
|
|
701
603
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
702
604
|
|
|
703
|
-
// Запускаем загрузку
|
|
704
605
|
[self downloadUpdateOnly:downloadURL callbackId:command.callbackId];
|
|
705
606
|
}
|
|
706
607
|
|
|
@@ -729,11 +630,8 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
729
630
|
NSLog(@"[HotUpdates] Download failed: %@", error.localizedDescription);
|
|
730
631
|
|
|
731
632
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
732
|
-
messageAsDictionary
|
|
733
|
-
|
|
734
|
-
@"message": error.localizedDescription
|
|
735
|
-
}
|
|
736
|
-
}];
|
|
633
|
+
messageAsDictionary:[self createError:kErrorDownloadFailed
|
|
634
|
+
message:[NSString stringWithFormat:@"Download failed: %@", error.localizedDescription]]];
|
|
737
635
|
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
|
738
636
|
return;
|
|
739
637
|
}
|
|
@@ -743,11 +641,8 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
743
641
|
NSLog(@"[HotUpdates] Download failed: HTTP %ld", (long)httpResponse.statusCode);
|
|
744
642
|
|
|
745
643
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
746
|
-
messageAsDictionary
|
|
747
|
-
|
|
748
|
-
@"message": [NSString stringWithFormat:@"HTTP %ld", (long)httpResponse.statusCode]
|
|
749
|
-
}
|
|
750
|
-
}];
|
|
644
|
+
messageAsDictionary:[self createError:kErrorHTTPError
|
|
645
|
+
message:[NSString stringWithFormat:@"HTTP error: %ld", (long)httpResponse.statusCode]]];
|
|
751
646
|
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
|
752
647
|
return;
|
|
753
648
|
}
|
|
@@ -767,15 +662,12 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
767
662
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
768
663
|
NSError *error;
|
|
769
664
|
|
|
770
|
-
// Создаем временную папку для распаковки
|
|
771
665
|
NSString *tempUpdatePath = [documentsPath stringByAppendingPathComponent:@"temp_downloaded_update"];
|
|
772
666
|
|
|
773
|
-
// Удаляем старую временную папку
|
|
774
667
|
if ([fileManager fileExistsAtPath:tempUpdatePath]) {
|
|
775
668
|
[fileManager removeItemAtPath:tempUpdatePath error:nil];
|
|
776
669
|
}
|
|
777
670
|
|
|
778
|
-
// Создаем новую временную папку
|
|
779
671
|
[fileManager createDirectoryAtPath:tempUpdatePath
|
|
780
672
|
withIntermediateDirectories:YES
|
|
781
673
|
attributes:nil
|
|
@@ -784,16 +676,12 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
784
676
|
if (error) {
|
|
785
677
|
NSLog(@"[HotUpdates] Error creating temp directory: %@", error);
|
|
786
678
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
787
|
-
messageAsDictionary
|
|
788
|
-
|
|
789
|
-
@"message": @"Cannot create temp directory"
|
|
790
|
-
}
|
|
791
|
-
}];
|
|
679
|
+
messageAsDictionary:[self createError:kErrorTempDirError
|
|
680
|
+
message:@"Cannot create temp directory"]];
|
|
792
681
|
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
|
793
682
|
return;
|
|
794
683
|
}
|
|
795
684
|
|
|
796
|
-
// Распаковываем обновление
|
|
797
685
|
BOOL unzipSuccess = [self unzipFile:updateLocation.path toDestination:tempUpdatePath];
|
|
798
686
|
|
|
799
687
|
if (!unzipSuccess) {
|
|
@@ -801,63 +689,49 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
801
689
|
[fileManager removeItemAtPath:tempUpdatePath error:nil];
|
|
802
690
|
|
|
803
691
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
804
|
-
messageAsDictionary
|
|
805
|
-
|
|
806
|
-
@"message": @"Failed to extract update package"
|
|
807
|
-
}
|
|
808
|
-
}];
|
|
692
|
+
messageAsDictionary:[self createError:kErrorExtractionFailed
|
|
693
|
+
message:@"Failed to extract update package"]];
|
|
809
694
|
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
|
810
695
|
return;
|
|
811
696
|
}
|
|
812
697
|
|
|
813
|
-
// Проверяем наличие www папки
|
|
814
698
|
NSString *tempWwwPath = [tempUpdatePath stringByAppendingPathComponent:kWWWDirName];
|
|
815
699
|
if (![fileManager fileExistsAtPath:tempWwwPath]) {
|
|
816
700
|
NSLog(@"[HotUpdates] www folder not found in update package");
|
|
817
701
|
[fileManager removeItemAtPath:tempUpdatePath error:nil];
|
|
818
702
|
|
|
819
703
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
820
|
-
messageAsDictionary
|
|
821
|
-
|
|
822
|
-
@"message": @"www folder not found in package"
|
|
823
|
-
}
|
|
824
|
-
}];
|
|
704
|
+
messageAsDictionary:[self createError:kErrorWWWNotFound
|
|
705
|
+
message:@"www folder not found in package"]];
|
|
825
706
|
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
|
826
707
|
return;
|
|
827
708
|
}
|
|
828
709
|
|
|
829
|
-
// Копируем
|
|
710
|
+
// Копируем в pending_update для автоустановки при следующем запуске
|
|
830
711
|
NSString *pendingPath = [documentsPath stringByAppendingPathComponent:kPendingUpdateDirName];
|
|
831
712
|
|
|
832
|
-
// Удаляем старую pending_update папку
|
|
833
713
|
if ([fileManager fileExistsAtPath:pendingPath]) {
|
|
834
714
|
[fileManager removeItemAtPath:pendingPath error:nil];
|
|
835
715
|
}
|
|
836
716
|
|
|
837
|
-
// Копируем temp_downloaded_update → pending_update
|
|
838
717
|
BOOL copySuccess = [fileManager copyItemAtPath:tempUpdatePath
|
|
839
718
|
toPath:pendingPath
|
|
840
719
|
error:&error];
|
|
841
720
|
|
|
842
721
|
if (!copySuccess) {
|
|
843
722
|
NSLog(@"[HotUpdates] Failed to copy to pending_update: %@", error);
|
|
844
|
-
// Не критично - forceUpdate всё равно сработает из temp_downloaded_update
|
|
845
723
|
} else {
|
|
846
724
|
NSLog(@"[HotUpdates] Copied to pending_update for auto-install on next launch");
|
|
847
725
|
}
|
|
848
726
|
|
|
849
|
-
// Помечаем обновление как готовое к установке (для forceUpdate)
|
|
850
727
|
isUpdateReadyToInstall = YES;
|
|
851
728
|
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kPendingUpdateReady];
|
|
852
|
-
|
|
853
|
-
// Устанавливаем флаг для автоустановки при следующем запуске (ТЗ п.7)
|
|
854
729
|
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kHasPending];
|
|
855
730
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
856
731
|
|
|
857
732
|
NSLog(@"[HotUpdates] Update downloaded and ready to install");
|
|
858
733
|
NSLog(@"[HotUpdates] If user ignores popup, update will install automatically on next launch");
|
|
859
734
|
|
|
860
|
-
// Возвращаем успех (callback без ошибки) - ТЗ: возвращаем null при успехе
|
|
861
735
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
|
862
736
|
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
|
863
737
|
}
|
|
@@ -867,16 +741,10 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
867
741
|
- (void)forceUpdate:(CDVInvokedUrlCommand*)command {
|
|
868
742
|
NSLog(@"[HotUpdates] forceUpdate() called - installing downloaded update");
|
|
869
743
|
|
|
870
|
-
// ВАЖНО: Не проверяем ignoreList - это контролирует JS
|
|
871
|
-
|
|
872
|
-
// Проверяем, что обновление было скачано
|
|
873
744
|
if (!isUpdateReadyToInstall) {
|
|
874
745
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
875
|
-
messageAsDictionary
|
|
876
|
-
|
|
877
|
-
@"message": @"No update ready to install. Call getUpdate() first."
|
|
878
|
-
}
|
|
879
|
-
}];
|
|
746
|
+
messageAsDictionary:[self createError:kErrorNoUpdateReady
|
|
747
|
+
message:@"No update ready to install"]];
|
|
880
748
|
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
881
749
|
return;
|
|
882
750
|
}
|
|
@@ -884,47 +752,36 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
884
752
|
NSString *tempUpdatePath = [documentsPath stringByAppendingPathComponent:@"temp_downloaded_update"];
|
|
885
753
|
NSString *tempWwwPath = [tempUpdatePath stringByAppendingPathComponent:kWWWDirName];
|
|
886
754
|
|
|
887
|
-
// Проверяем наличие скачанных файлов
|
|
888
755
|
if (![[NSFileManager defaultManager] fileExistsAtPath:tempWwwPath]) {
|
|
889
756
|
NSLog(@"[HotUpdates] Downloaded update files not found");
|
|
890
757
|
|
|
891
758
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
892
|
-
messageAsDictionary
|
|
893
|
-
|
|
894
|
-
@"message": @"Downloaded update files not found"
|
|
895
|
-
}
|
|
896
|
-
}];
|
|
759
|
+
messageAsDictionary:[self createError:kErrorUpdateFilesNotFound
|
|
760
|
+
message:@"Downloaded update files not found"]];
|
|
897
761
|
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
898
762
|
return;
|
|
899
763
|
}
|
|
900
764
|
|
|
901
|
-
// Устанавливаем обновление
|
|
902
765
|
[self installDownloadedUpdate:tempWwwPath callbackId:command.callbackId];
|
|
903
766
|
}
|
|
904
767
|
|
|
905
768
|
- (void)installDownloadedUpdate:(NSString*)tempWwwPath callbackId:(NSString*)callbackId {
|
|
906
769
|
NSLog(@"[HotUpdates] Installing update");
|
|
907
770
|
|
|
908
|
-
// Определяем версию ДО установки
|
|
909
771
|
NSString *versionToInstall = [[NSUserDefaults standardUserDefaults] stringForKey:kPendingVersion];
|
|
910
772
|
if (!versionToInstall) {
|
|
911
773
|
versionToInstall = @"unknown";
|
|
912
774
|
}
|
|
913
775
|
|
|
914
|
-
// ВАЖНО (ТЗ): НЕ проверяем ignoreList - JS сам контролирует что устанавливать
|
|
915
|
-
|
|
916
776
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
917
777
|
NSError *error;
|
|
918
778
|
|
|
919
|
-
// ВАЖНО: Создаем резервную копию текущей версии
|
|
920
779
|
[self backupCurrentVersion];
|
|
921
780
|
|
|
922
|
-
// Удаляем текущую www
|
|
923
781
|
if ([fileManager fileExistsAtPath:wwwPath]) {
|
|
924
782
|
[fileManager removeItemAtPath:wwwPath error:nil];
|
|
925
783
|
}
|
|
926
784
|
|
|
927
|
-
// Копируем новую версию
|
|
928
785
|
BOOL copySuccess = [fileManager copyItemAtPath:tempWwwPath
|
|
929
786
|
toPath:wwwPath
|
|
930
787
|
error:&error];
|
|
@@ -933,67 +790,53 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
933
790
|
NSLog(@"[HotUpdates] Failed to install update: %@", error);
|
|
934
791
|
|
|
935
792
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
936
|
-
messageAsDictionary
|
|
937
|
-
|
|
938
|
-
@"message": error.localizedDescription
|
|
939
|
-
}
|
|
940
|
-
}];
|
|
793
|
+
messageAsDictionary:[self createError:kErrorInstallFailed
|
|
794
|
+
message:[NSString stringWithFormat:@"Install failed: %@", error.localizedDescription]]];
|
|
941
795
|
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
|
942
796
|
return;
|
|
943
797
|
}
|
|
944
798
|
|
|
945
|
-
// Используем версию определенную ранее
|
|
946
799
|
NSString *newVersion = versionToInstall;
|
|
947
800
|
|
|
948
|
-
// Обновляем метаданные
|
|
949
801
|
[[NSUserDefaults standardUserDefaults] setObject:newVersion forKey:kInstalledVersion];
|
|
950
802
|
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:kPendingUpdateReady];
|
|
951
|
-
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:kHasPending];
|
|
803
|
+
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:kHasPending];
|
|
952
804
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kPendingUpdateURL];
|
|
953
805
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kPendingVersion];
|
|
954
|
-
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kCanaryVersion];
|
|
806
|
+
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kCanaryVersion];
|
|
955
807
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
956
808
|
|
|
957
|
-
// Очищаем временные папки
|
|
958
809
|
NSString *tempUpdatePath = [documentsPath stringByAppendingPathComponent:@"temp_downloaded_update"];
|
|
959
810
|
NSString *pendingPath = [documentsPath stringByAppendingPathComponent:kPendingUpdateDirName];
|
|
960
811
|
[fileManager removeItemAtPath:tempUpdatePath error:nil];
|
|
961
812
|
[fileManager removeItemAtPath:pendingPath error:nil];
|
|
962
813
|
|
|
963
|
-
// Сбрасываем флаги
|
|
964
814
|
isUpdateReadyToInstall = NO;
|
|
965
815
|
pendingUpdateURL = nil;
|
|
966
816
|
|
|
967
817
|
NSLog(@"[HotUpdates] Update installed successfully");
|
|
968
818
|
|
|
969
|
-
// Возвращаем успех ПЕРЕД перезагрузкой - ТЗ: возвращаем null при успехе
|
|
970
819
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
|
971
820
|
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
|
972
821
|
|
|
973
|
-
//
|
|
974
|
-
// После reloadWebView pluginInitialize НЕ вызывается, поэтому таймер нужно запустить вручную
|
|
822
|
+
// После reloadWebView pluginInitialize НЕ вызывается, поэтому canary timer запускаем вручную
|
|
975
823
|
NSLog(@"[HotUpdates] Starting canary timer (20 seconds) for version %@", newVersion);
|
|
976
824
|
|
|
977
|
-
// Останавливаем предыдущий таймер если был
|
|
978
825
|
if (canaryTimer && [canaryTimer isValid]) {
|
|
979
826
|
[canaryTimer invalidate];
|
|
980
827
|
}
|
|
981
828
|
|
|
982
|
-
// Запускаем новый таймер на 20 секунд
|
|
983
829
|
canaryTimer = [NSTimer scheduledTimerWithTimeInterval:20.0
|
|
984
830
|
target:self
|
|
985
831
|
selector:@selector(canaryTimeout)
|
|
986
832
|
userInfo:nil
|
|
987
833
|
repeats:NO];
|
|
988
834
|
|
|
989
|
-
// Сбрасываем флаг для разрешения перезагрузки после установки обновления
|
|
990
835
|
hasPerformedInitialReload = NO;
|
|
991
836
|
|
|
992
|
-
//
|
|
993
|
-
// Без этого может загрузиться старая закэшированная версия
|
|
837
|
+
// Очищаем кэш WebView перед перезагрузкой, иначе может загрузиться старая версия
|
|
994
838
|
[self clearWebViewCache];
|
|
995
839
|
|
|
996
|
-
// Перезагружаем WebView с новым контентом
|
|
997
840
|
[self reloadWebView];
|
|
998
841
|
}
|
|
999
842
|
|
|
@@ -1004,7 +847,8 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
1004
847
|
|
|
1005
848
|
if (!canaryVersion || canaryVersion.length == 0) {
|
|
1006
849
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
1007
|
-
|
|
850
|
+
messageAsDictionary:[self createError:kErrorVersionRequired
|
|
851
|
+
message:@"Version is required"]];
|
|
1008
852
|
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
1009
853
|
return;
|
|
1010
854
|
}
|
|
@@ -1031,7 +875,7 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
1031
875
|
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
1032
876
|
}
|
|
1033
877
|
|
|
1034
|
-
#pragma mark -
|
|
878
|
+
#pragma mark - Debug Methods
|
|
1035
879
|
|
|
1036
880
|
- (void)getVersionInfo:(CDVInvokedUrlCommand*)command {
|
|
1037
881
|
NSString *installedVersion = [[NSUserDefaults standardUserDefaults] stringForKey:kInstalledVersion];
|
|
@@ -1055,14 +899,4 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
1055
899
|
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
1056
900
|
}
|
|
1057
901
|
|
|
1058
|
-
- (void)checkForUpdates:(CDVInvokedUrlCommand*)command {
|
|
1059
|
-
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
1060
|
-
messageAsDictionary:@{
|
|
1061
|
-
@"error": @{
|
|
1062
|
-
@"message": @"checkForUpdates() removed in v2.1.0. Use fetch() in JS to check your server for updates, then call getUpdate({url}) to download."
|
|
1063
|
-
}
|
|
1064
|
-
}];
|
|
1065
|
-
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
902
|
@end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* @file HotUpdatesConstants.h
|
|
3
|
+
* @brief Constants for Hot Updates Plugin
|
|
4
|
+
* @details Defines error codes, storage keys, and directory names
|
|
5
|
+
* @version 2.2.0
|
|
6
|
+
* @date 2025-11-13
|
|
7
|
+
* @author Mustafin Vladimir
|
|
8
|
+
* @copyright Copyright (c) 2025. All rights reserved.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#import <Foundation/Foundation.h>
|
|
12
|
+
|
|
13
|
+
#pragma mark - Error Codes
|
|
14
|
+
|
|
15
|
+
extern NSString * const kErrorUpdateDataRequired;
|
|
16
|
+
extern NSString * const kErrorURLRequired;
|
|
17
|
+
extern NSString * const kErrorDownloadInProgress;
|
|
18
|
+
extern NSString * const kErrorDownloadFailed;
|
|
19
|
+
extern NSString * const kErrorHTTPError;
|
|
20
|
+
extern NSString * const kErrorTempDirError;
|
|
21
|
+
extern NSString * const kErrorExtractionFailed;
|
|
22
|
+
extern NSString * const kErrorWWWNotFound;
|
|
23
|
+
extern NSString * const kErrorNoUpdateReady;
|
|
24
|
+
extern NSString * const kErrorUpdateFilesNotFound;
|
|
25
|
+
extern NSString * const kErrorInstallFailed;
|
|
26
|
+
extern NSString * const kErrorVersionRequired;
|
|
27
|
+
|
|
28
|
+
#pragma mark - Storage Keys
|
|
29
|
+
|
|
30
|
+
extern NSString * const kInstalledVersion;
|
|
31
|
+
extern NSString * const kPendingVersion;
|
|
32
|
+
extern NSString * const kHasPending;
|
|
33
|
+
extern NSString * const kPreviousVersion;
|
|
34
|
+
extern NSString * const kIgnoreList;
|
|
35
|
+
extern NSString * const kCanaryVersion;
|
|
36
|
+
extern NSString * const kDownloadInProgress;
|
|
37
|
+
extern NSString * const kPendingUpdateURL;
|
|
38
|
+
extern NSString * const kPendingUpdateReady;
|
|
39
|
+
|
|
40
|
+
#pragma mark - Directory Names
|
|
41
|
+
|
|
42
|
+
extern NSString * const kWWWDirName;
|
|
43
|
+
extern NSString * const kPreviousWWWDirName;
|
|
44
|
+
extern NSString * const kBackupWWWDirName;
|
|
45
|
+
extern NSString * const kPendingUpdateDirName;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* @file HotUpdatesConstants.m
|
|
3
|
+
* @brief Implementation of constants for Hot Updates Plugin
|
|
4
|
+
* @details Defines all constant values used throughout the plugin
|
|
5
|
+
* @version 2.2.0
|
|
6
|
+
* @date 2025-11-13
|
|
7
|
+
* @author Mustafin Vladimir
|
|
8
|
+
* @copyright Copyright (c) 2025. All rights reserved.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#import <Foundation/Foundation.h>
|
|
12
|
+
#import "HotUpdatesConstants.h"
|
|
13
|
+
|
|
14
|
+
#pragma mark - Error Codes
|
|
15
|
+
|
|
16
|
+
NSString * const kErrorUpdateDataRequired = @"UPDATE_DATA_REQUIRED";
|
|
17
|
+
NSString * const kErrorURLRequired = @"URL_REQUIRED";
|
|
18
|
+
NSString * const kErrorDownloadInProgress = @"DOWNLOAD_IN_PROGRESS";
|
|
19
|
+
NSString * const kErrorDownloadFailed = @"DOWNLOAD_FAILED";
|
|
20
|
+
NSString * const kErrorHTTPError = @"HTTP_ERROR";
|
|
21
|
+
NSString * const kErrorTempDirError = @"TEMP_DIR_ERROR";
|
|
22
|
+
NSString * const kErrorExtractionFailed = @"EXTRACTION_FAILED";
|
|
23
|
+
NSString * const kErrorWWWNotFound = @"WWW_NOT_FOUND";
|
|
24
|
+
NSString * const kErrorNoUpdateReady = @"NO_UPDATE_READY";
|
|
25
|
+
NSString * const kErrorUpdateFilesNotFound = @"UPDATE_FILES_NOT_FOUND";
|
|
26
|
+
NSString * const kErrorInstallFailed = @"INSTALL_FAILED";
|
|
27
|
+
NSString * const kErrorVersionRequired = @"VERSION_REQUIRED";
|
|
28
|
+
|
|
29
|
+
#pragma mark - Storage Keys
|
|
30
|
+
|
|
31
|
+
NSString * const kInstalledVersion = @"hot_updates_installed_version";
|
|
32
|
+
NSString * const kPendingVersion = @"hot_updates_pending_version";
|
|
33
|
+
NSString * const kHasPending = @"hot_updates_has_pending";
|
|
34
|
+
NSString * const kPreviousVersion = @"hot_updates_previous_version";
|
|
35
|
+
NSString * const kIgnoreList = @"hot_updates_ignore_list";
|
|
36
|
+
NSString * const kCanaryVersion = @"hot_updates_canary_version";
|
|
37
|
+
NSString * const kDownloadInProgress = @"hot_updates_download_in_progress";
|
|
38
|
+
NSString * const kPendingUpdateURL = @"hot_updates_pending_update_url";
|
|
39
|
+
NSString * const kPendingUpdateReady = @"hot_updates_pending_ready";
|
|
40
|
+
|
|
41
|
+
#pragma mark - Directory Names
|
|
42
|
+
|
|
43
|
+
NSString * const kWWWDirName = @"www";
|
|
44
|
+
NSString * const kPreviousWWWDirName = @"www_previous";
|
|
45
|
+
NSString * const kBackupWWWDirName = @"www_backup";
|
|
46
|
+
NSString * const kPendingUpdateDirName = @"pending_update";
|