cordova-plugin-hot-updates 2.2.1 → 2.2.3
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 +54 -1
- package/package.json +1 -1
- package/plugin.xml +1 -1
- package/src/ios/HotUpdates.h +8 -6
- package/src/ios/HotUpdates.m +192 -152
- package/src/ios/HotUpdatesConstants.h +1 -0
- package/src/ios/HotUpdatesConstants.m +1 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Cordova Hot Updates Plugin v2.2.
|
|
1
|
+
# Cordova Hot Updates Plugin v2.2.3
|
|
2
2
|
|
|
3
3
|
Frontend-controlled manual hot updates for Cordova iOS applications using WebView Reload approach.
|
|
4
4
|
|
|
@@ -14,6 +14,7 @@ This plugin enables **manual, JavaScript-controlled** web content updates for yo
|
|
|
14
14
|
- **Auto-Install on Launch**: If user ignores update prompt, it installs on next app launch
|
|
15
15
|
- **Canary System**: Automatic rollback if update fails to load (20-second timeout)
|
|
16
16
|
- **IgnoreList**: Tracks problematic versions (information only, does NOT block installation)
|
|
17
|
+
- **Version History** *(new in v2.2.3)*: Tracks successful versions for progressive data migrations
|
|
17
18
|
- **Instant Effect**: WebView Reload approach - no app restart needed
|
|
18
19
|
- **Cache Management**: Clears WKWebView cache (disk, memory, Service Worker) before reload
|
|
19
20
|
|
|
@@ -268,6 +269,58 @@ window.hotUpdate.getIgnoreList(function(result) {
|
|
|
268
269
|
|
|
269
270
|
---
|
|
270
271
|
|
|
272
|
+
### window.hotUpdate.getVersionHistory(callback)
|
|
273
|
+
|
|
274
|
+
Returns list of all successfully installed versions (excluding rolled back ones).
|
|
275
|
+
|
|
276
|
+
**New in v2.2.3** - Enables progressive data migrations.
|
|
277
|
+
|
|
278
|
+
When internal data structure changes, you may need to run migrations. This method returns the version history so your app can determine which migrations to run.
|
|
279
|
+
|
|
280
|
+
**Key behaviors:**
|
|
281
|
+
- **Automatically initialized** with `appBundleVersion` on first launch
|
|
282
|
+
- **Added to history** when update is successfully installed
|
|
283
|
+
- **Removed from history** when version is rolled back (failed canary)
|
|
284
|
+
- **Excludes ignoreList** - only contains successful versions
|
|
285
|
+
|
|
286
|
+
**Parameters:**
|
|
287
|
+
- `callback` (Function) - `callback(result)`
|
|
288
|
+
- `result`: `{versions: string[]}` - Array of successful version strings
|
|
289
|
+
|
|
290
|
+
**Example:**
|
|
291
|
+
```javascript
|
|
292
|
+
window.hotUpdate.getVersionHistory(function(result) {
|
|
293
|
+
console.log('Version history:', result.versions);
|
|
294
|
+
// Example: ["2.7.7", "2.7.8", "2.7.9"]
|
|
295
|
+
|
|
296
|
+
// Run migrations based on version progression
|
|
297
|
+
result.versions.forEach((version, index) => {
|
|
298
|
+
if (index > 0) {
|
|
299
|
+
const from = result.versions[index - 1];
|
|
300
|
+
const to = version;
|
|
301
|
+
runMigration(from, to);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Use Case:**
|
|
308
|
+
```javascript
|
|
309
|
+
// Check for missed critical versions
|
|
310
|
+
window.hotUpdate.getVersionHistory(function(result) {
|
|
311
|
+
const criticalVersions = ['2.8.0', '3.0.0']; // Versions with important migrations
|
|
312
|
+
const missed = criticalVersions.filter(v => !result.versions.includes(v));
|
|
313
|
+
|
|
314
|
+
if (missed.length > 0) {
|
|
315
|
+
console.warn('User skipped critical versions:', missed);
|
|
316
|
+
// Run all missed critical migrations
|
|
317
|
+
missed.forEach(v => runCriticalMigration(v));
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
271
324
|
### window.hotUpdate.getVersionInfo(callback)
|
|
272
325
|
|
|
273
326
|
Returns version information (debug method).
|
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.3",
|
|
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
package/src/ios/HotUpdates.h
CHANGED
|
@@ -29,16 +29,18 @@
|
|
|
29
29
|
|
|
30
30
|
// Settings
|
|
31
31
|
NSMutableArray *ignoreList; // Список игнорируемых версий (управляется только native)
|
|
32
|
+
NSMutableArray *versionHistory; // История успешно установленных версий (исключая откаченные)
|
|
32
33
|
NSString *previousVersionPath; // Путь к предыдущей версии
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
// JavaScript API methods (v2.
|
|
36
|
-
- (void)getUpdate:(CDVInvokedUrlCommand*)command;
|
|
37
|
-
- (void)forceUpdate:(CDVInvokedUrlCommand*)command;
|
|
38
|
-
- (void)canary:(CDVInvokedUrlCommand*)command;
|
|
39
|
-
- (void)getIgnoreList:(CDVInvokedUrlCommand*)command;
|
|
36
|
+
// JavaScript API methods (v2.2.2)
|
|
37
|
+
- (void)getUpdate:(CDVInvokedUrlCommand*)command; // Download update
|
|
38
|
+
- (void)forceUpdate:(CDVInvokedUrlCommand*)command; // Install downloaded update
|
|
39
|
+
- (void)canary:(CDVInvokedUrlCommand*)command; // Confirm successful load
|
|
40
|
+
- (void)getIgnoreList:(CDVInvokedUrlCommand*)command; // Get ignore list (JS reads only)
|
|
41
|
+
- (void)getVersionHistory:(CDVInvokedUrlCommand*)command; // Get version history (successful installs only)
|
|
40
42
|
|
|
41
43
|
// Debug method
|
|
42
|
-
- (void)getVersionInfo:(CDVInvokedUrlCommand*)command;
|
|
44
|
+
- (void)getVersionInfo:(CDVInvokedUrlCommand*)command; // Get all version info for debugging
|
|
43
45
|
|
|
44
46
|
@end
|
package/src/ios/HotUpdates.m
CHANGED
|
@@ -55,26 +55,20 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
55
55
|
|
|
56
56
|
[self loadConfiguration];
|
|
57
57
|
[self loadIgnoreList];
|
|
58
|
+
[self loadVersionHistory];
|
|
58
59
|
|
|
59
60
|
// Сбрасываем флаг загрузки (если приложение было убито во время загрузки)
|
|
60
61
|
isDownloadingUpdate = NO;
|
|
61
62
|
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:kDownloadInProgress];
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
pendingUpdateURL = nil;
|
|
65
|
-
pendingUpdateVersion = nil;
|
|
66
|
-
|
|
64
|
+
// Читаем состояние из UserDefaults
|
|
67
65
|
pendingUpdateURL = [[NSUserDefaults standardUserDefaults] stringForKey:kPendingUpdateURL];
|
|
68
66
|
isUpdateReadyToInstall = [[NSUserDefaults standardUserDefaults] boolForKey:kPendingUpdateReady];
|
|
69
67
|
if (isUpdateReadyToInstall) {
|
|
70
68
|
pendingUpdateVersion = [[NSUserDefaults standardUserDefaults] stringForKey:kPendingVersion];
|
|
71
|
-
NSLog(@"[HotUpdates] Found pending update ready to install: %@", pendingUpdateVersion);
|
|
72
69
|
}
|
|
73
70
|
|
|
74
|
-
NSLog(@"[HotUpdates]
|
|
75
|
-
NSLog(@"[HotUpdates] Bundle www path: %@", [[NSBundle mainBundle] pathForResource:kWWWDirName ofType:nil]);
|
|
76
|
-
NSLog(@"[HotUpdates] Documents www path: %@", wwwPath);
|
|
77
|
-
NSLog(@"[HotUpdates] Ignore list: %@", ignoreList);
|
|
71
|
+
NSLog(@"[HotUpdates] Initializing plugin...");
|
|
78
72
|
|
|
79
73
|
[self checkAndInstallPendingUpdate];
|
|
80
74
|
[self initializeWWWFolder];
|
|
@@ -85,15 +79,12 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
85
79
|
NSString *canaryVersion = [[NSUserDefaults standardUserDefaults] stringForKey:kCanaryVersion];
|
|
86
80
|
|
|
87
81
|
if (!canaryVersion || ![canaryVersion isEqualToString:currentVersion]) {
|
|
88
|
-
NSLog(@"[HotUpdates] Starting canary timer
|
|
89
|
-
|
|
82
|
+
NSLog(@"[HotUpdates] Starting canary timer for version %@", currentVersion);
|
|
90
83
|
[self startCanaryTimer];
|
|
91
|
-
} else {
|
|
92
|
-
NSLog(@"[HotUpdates] Canary already confirmed for version %@", currentVersion);
|
|
93
84
|
}
|
|
94
85
|
}
|
|
95
86
|
|
|
96
|
-
NSLog(@"[HotUpdates] Plugin initialized
|
|
87
|
+
NSLog(@"[HotUpdates] Plugin initialized (v%@)", appBundleVersion);
|
|
97
88
|
}
|
|
98
89
|
|
|
99
90
|
- (void)loadConfiguration {
|
|
@@ -101,9 +92,6 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
101
92
|
if (!appBundleVersion) {
|
|
102
93
|
appBundleVersion = @"1.0.0";
|
|
103
94
|
}
|
|
104
|
-
|
|
105
|
-
NSLog(@"[HotUpdates] Configuration loaded:");
|
|
106
|
-
NSLog(@" App bundle version: %@", appBundleVersion);
|
|
107
95
|
}
|
|
108
96
|
|
|
109
97
|
/*!
|
|
@@ -115,7 +103,7 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
115
103
|
NSString *pendingVersion = [[NSUserDefaults standardUserDefaults] stringForKey:kPendingVersion];
|
|
116
104
|
|
|
117
105
|
if (hasPendingUpdate && pendingVersion) {
|
|
118
|
-
NSLog(@"[HotUpdates]
|
|
106
|
+
NSLog(@"[HotUpdates] Auto-installing pending update: %@", pendingVersion);
|
|
119
107
|
|
|
120
108
|
[self backupCurrentVersion];
|
|
121
109
|
|
|
@@ -140,9 +128,18 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
140
128
|
|
|
141
129
|
[[NSFileManager defaultManager] removeItemAtPath:pendingUpdatePath error:nil];
|
|
142
130
|
|
|
143
|
-
|
|
131
|
+
// Добавляем версию в историю при успешной установке
|
|
132
|
+
[self addVersionToHistory:pendingVersion];
|
|
133
|
+
|
|
134
|
+
NSLog(@"[HotUpdates] Update %@ installed successfully", pendingVersion);
|
|
144
135
|
} else {
|
|
145
|
-
NSLog(@"[HotUpdates] Failed to install update: %@", copyError.localizedDescription);
|
|
136
|
+
NSLog(@"[HotUpdates] Failed to install pending update: %@", copyError.localizedDescription);
|
|
137
|
+
// Очищаем флаги чтобы не пытаться снова при следующем запуске
|
|
138
|
+
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:kHasPending];
|
|
139
|
+
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kPendingVersion];
|
|
140
|
+
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
141
|
+
// Удаляем битое обновление
|
|
142
|
+
[[NSFileManager defaultManager] removeItemAtPath:pendingUpdatePath error:nil];
|
|
146
143
|
}
|
|
147
144
|
}
|
|
148
145
|
}
|
|
@@ -156,7 +153,6 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
156
153
|
- (void)switchToUpdatedContentWithReload {
|
|
157
154
|
// Предотвращаем повторные перезагрузки при навигации между страницами
|
|
158
155
|
if (hasPerformedInitialReload) {
|
|
159
|
-
NSLog(@"[HotUpdates] Initial reload already performed, skipping");
|
|
160
156
|
return;
|
|
161
157
|
}
|
|
162
158
|
|
|
@@ -167,24 +163,19 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
167
163
|
NSString *indexPath = [documentsWwwPath stringByAppendingPathComponent:@"index.html"];
|
|
168
164
|
|
|
169
165
|
if ([[NSFileManager defaultManager] fileExistsAtPath:indexPath]) {
|
|
170
|
-
NSLog(@"[HotUpdates]
|
|
171
|
-
NSLog(@"[HotUpdates] Found installed update version: %@", installedVersion);
|
|
166
|
+
NSLog(@"[HotUpdates] Loading installed version: %@", installedVersion);
|
|
172
167
|
|
|
173
168
|
((CDVViewController *)self.viewController).wwwFolderName = documentsWwwPath;
|
|
174
|
-
NSLog(@"[HotUpdates] Changed wwwFolderName to: %@", documentsWwwPath);
|
|
175
|
-
|
|
176
169
|
hasPerformedInitialReload = YES;
|
|
177
170
|
|
|
178
171
|
// Очищаем кэш перед перезагрузкой, иначе может загрузиться старая версия
|
|
179
172
|
[self clearWebViewCacheWithCompletion:^{
|
|
180
173
|
[self reloadWebView];
|
|
181
|
-
NSLog(@"[HotUpdates] WebView reloaded with updated content (version: %@)", installedVersion);
|
|
182
174
|
}];
|
|
183
175
|
} else {
|
|
184
|
-
NSLog(@"[HotUpdates] Documents/www/index.html not found,
|
|
176
|
+
NSLog(@"[HotUpdates] WARNING: Documents/www/index.html not found, using bundle");
|
|
185
177
|
}
|
|
186
178
|
} else {
|
|
187
|
-
NSLog(@"[HotUpdates] No installed updates, using bundle www");
|
|
188
179
|
hasPerformedInitialReload = YES;
|
|
189
180
|
}
|
|
190
181
|
}
|
|
@@ -202,8 +193,6 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
202
193
|
* @param completion Block called after cache is cleared (on main thread)
|
|
203
194
|
*/
|
|
204
195
|
- (void)clearWebViewCacheWithCompletion:(void (^)(void))completion {
|
|
205
|
-
NSLog(@"[HotUpdates] Clearing WebView cache");
|
|
206
|
-
|
|
207
196
|
NSSet *websiteDataTypes = [NSSet setWithArray:@[
|
|
208
197
|
WKWebsiteDataTypeDiskCache,
|
|
209
198
|
WKWebsiteDataTypeMemoryCache,
|
|
@@ -215,7 +204,6 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
215
204
|
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes
|
|
216
205
|
modifiedSince:dateFrom
|
|
217
206
|
completionHandler:^{
|
|
218
|
-
NSLog(@"[HotUpdates] WebView cache cleared");
|
|
219
207
|
if (completion) {
|
|
220
208
|
dispatch_async(dispatch_get_main_queue(), completion);
|
|
221
209
|
}
|
|
@@ -231,26 +219,22 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
231
219
|
NSURL *fileURL = [NSURL fileURLWithPath:indexPath];
|
|
232
220
|
NSURL *allowReadAccessToURL = [NSURL fileURLWithPath:documentsWwwPath];
|
|
233
221
|
|
|
234
|
-
NSLog(@"[HotUpdates] Loading WebView with new URL: %@", fileURL.absoluteString);
|
|
235
|
-
|
|
236
222
|
id webViewEngine = cdvViewController.webViewEngine;
|
|
237
223
|
if (webViewEngine && [webViewEngine respondsToSelector:@selector(engineWebView)]) {
|
|
238
224
|
WKWebView *webView = [webViewEngine performSelector:@selector(engineWebView)];
|
|
239
225
|
|
|
240
226
|
if (webView && [webView isKindOfClass:[WKWebView class]]) {
|
|
241
|
-
// loadFileURL:allowingReadAccessToURL: правильно настраивает sandbox permissions для локальных файлов
|
|
242
227
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
243
228
|
[webView loadFileURL:fileURL allowingReadAccessToURL:allowReadAccessToURL];
|
|
244
|
-
NSLog(@"[HotUpdates] WebView loadFileURL executed with sandbox permissions");
|
|
245
229
|
});
|
|
246
230
|
} else {
|
|
247
|
-
NSLog(@"[HotUpdates] Could not access WKWebView for reload");
|
|
231
|
+
NSLog(@"[HotUpdates] ERROR: Could not access WKWebView for reload");
|
|
248
232
|
}
|
|
249
233
|
} else {
|
|
250
|
-
NSLog(@"[HotUpdates] WebView engine not available for reload");
|
|
234
|
+
NSLog(@"[HotUpdates] ERROR: WebView engine not available for reload");
|
|
251
235
|
}
|
|
252
236
|
} else {
|
|
253
|
-
NSLog(@"[HotUpdates] ViewController is not CDVViewController type");
|
|
237
|
+
NSLog(@"[HotUpdates] ERROR: ViewController is not CDVViewController type");
|
|
254
238
|
}
|
|
255
239
|
}
|
|
256
240
|
|
|
@@ -259,120 +243,126 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
259
243
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
260
244
|
|
|
261
245
|
if (![fileManager fileExistsAtPath:wwwPath]) {
|
|
262
|
-
NSLog(@"[HotUpdates] WWW folder not found in Documents. Creating and copying from bundle...");
|
|
263
|
-
|
|
264
246
|
NSString *bundleWWWPath = [[NSBundle mainBundle] pathForResource:@"www" ofType:nil];
|
|
265
247
|
if (bundleWWWPath) {
|
|
266
248
|
NSError *error;
|
|
267
249
|
[fileManager copyItemAtPath:bundleWWWPath toPath:wwwPath error:&error];
|
|
268
250
|
if (error) {
|
|
269
|
-
NSLog(@"[HotUpdates]
|
|
251
|
+
NSLog(@"[HotUpdates] ERROR: Failed to copy www folder: %@", error.localizedDescription);
|
|
270
252
|
} else {
|
|
271
|
-
NSLog(@"[HotUpdates]
|
|
253
|
+
NSLog(@"[HotUpdates] Initialized www folder from bundle");
|
|
272
254
|
}
|
|
273
255
|
} else {
|
|
274
|
-
NSLog(@"[HotUpdates]
|
|
256
|
+
NSLog(@"[HotUpdates] ERROR: Bundle www folder not found");
|
|
275
257
|
}
|
|
276
|
-
} else {
|
|
277
|
-
NSLog(@"[HotUpdates] WWW folder already exists in Documents");
|
|
278
258
|
}
|
|
279
259
|
}
|
|
280
260
|
|
|
281
261
|
- (BOOL)unzipFile:(NSString*)zipPath toDestination:(NSString*)destination {
|
|
282
|
-
NSLog(@"[HotUpdates] Unzipping %@ to %@", zipPath, destination);
|
|
283
|
-
|
|
284
262
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
285
263
|
NSError *error = nil;
|
|
286
264
|
|
|
265
|
+
// Проверяем существование ZIP файла
|
|
266
|
+
if (![fileManager fileExistsAtPath:zipPath]) {
|
|
267
|
+
NSLog(@"[HotUpdates] ERROR: ZIP file does not exist");
|
|
268
|
+
return NO;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Проверяем magic bytes (PK\x03\x04)
|
|
272
|
+
if (![self isValidZipFile:zipPath]) {
|
|
273
|
+
NSLog(@"[HotUpdates] ERROR: Invalid file format (not a ZIP archive)");
|
|
274
|
+
return NO;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
NSLog(@"[HotUpdates] Extracting update package...");
|
|
278
|
+
|
|
279
|
+
// Создаём директорию назначения если не существует
|
|
287
280
|
if (![fileManager fileExistsAtPath:destination]) {
|
|
288
281
|
[fileManager createDirectoryAtPath:destination withIntermediateDirectories:YES attributes:nil error:&error];
|
|
289
282
|
if (error) {
|
|
290
|
-
NSLog(@"[HotUpdates]
|
|
283
|
+
NSLog(@"[HotUpdates] ERROR: Failed to create destination directory: %@", error.localizedDescription);
|
|
291
284
|
return NO;
|
|
292
285
|
}
|
|
293
286
|
}
|
|
294
287
|
|
|
295
|
-
NSLog(@"[HotUpdates] Extracting ZIP archive using SSZipArchive library");
|
|
296
|
-
|
|
297
|
-
if (![[NSFileManager defaultManager] fileExistsAtPath:zipPath]) {
|
|
298
|
-
NSLog(@"[HotUpdates] ZIP file does not exist: %@", zipPath);
|
|
299
|
-
return NO;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
288
|
NSString *tempExtractPath = [destination stringByAppendingPathComponent:@"temp_extract"];
|
|
303
289
|
|
|
304
|
-
|
|
305
|
-
|
|
290
|
+
// Очищаем временную директорию
|
|
291
|
+
if ([fileManager fileExistsAtPath:tempExtractPath]) {
|
|
292
|
+
[fileManager removeItemAtPath:tempExtractPath error:nil];
|
|
306
293
|
}
|
|
307
294
|
|
|
308
|
-
if (![
|
|
309
|
-
NSLog(@"[HotUpdates] Failed to create temp
|
|
295
|
+
if (![fileManager createDirectoryAtPath:tempExtractPath withIntermediateDirectories:YES attributes:nil error:&error]) {
|
|
296
|
+
NSLog(@"[HotUpdates] ERROR: Failed to create temp directory: %@", error.localizedDescription);
|
|
310
297
|
return NO;
|
|
311
298
|
}
|
|
312
299
|
|
|
313
|
-
NSLog(@"[HotUpdates] Extracting to temp location: %@", tempExtractPath);
|
|
314
|
-
|
|
315
300
|
BOOL extractSuccess = [SSZipArchive unzipFileAtPath:zipPath toDestination:tempExtractPath];
|
|
316
301
|
|
|
317
|
-
if (extractSuccess) {
|
|
318
|
-
NSLog(@"[HotUpdates] ZIP
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
302
|
+
if (!extractSuccess) {
|
|
303
|
+
NSLog(@"[HotUpdates] ERROR: Failed to extract ZIP archive");
|
|
304
|
+
[fileManager removeItemAtPath:tempExtractPath error:nil];
|
|
305
|
+
return NO;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
NSArray *extractedContents = [fileManager contentsOfDirectoryAtPath:tempExtractPath error:nil];
|
|
309
|
+
|
|
310
|
+
// Ищем папку www (может быть вложенной)
|
|
311
|
+
NSString *wwwSourcePath = nil;
|
|
312
|
+
for (NSString *item in extractedContents) {
|
|
313
|
+
NSString *itemPath = [tempExtractPath stringByAppendingPathComponent:item];
|
|
314
|
+
BOOL isDirectory;
|
|
315
|
+
if ([fileManager fileExistsAtPath:itemPath isDirectory:&isDirectory] && isDirectory) {
|
|
316
|
+
if ([item isEqualToString:@"www"]) {
|
|
317
|
+
wwwSourcePath = itemPath;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
NSString *nestedWwwPath = [itemPath stringByAppendingPathComponent:@"www"];
|
|
321
|
+
if ([fileManager fileExistsAtPath:nestedWwwPath]) {
|
|
322
|
+
wwwSourcePath = nestedWwwPath;
|
|
323
|
+
break;
|
|
338
324
|
}
|
|
339
325
|
}
|
|
326
|
+
}
|
|
340
327
|
|
|
341
|
-
|
|
342
|
-
|
|
328
|
+
if (!wwwSourcePath) {
|
|
329
|
+
NSLog(@"[HotUpdates] ERROR: www folder not found in ZIP archive");
|
|
330
|
+
[fileManager removeItemAtPath:tempExtractPath error:nil];
|
|
331
|
+
return NO;
|
|
332
|
+
}
|
|
343
333
|
|
|
344
|
-
|
|
334
|
+
NSString *finalWwwPath = [destination stringByAppendingPathComponent:@"www"];
|
|
345
335
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
336
|
+
if ([fileManager fileExistsAtPath:finalWwwPath]) {
|
|
337
|
+
[fileManager removeItemAtPath:finalWwwPath error:nil];
|
|
338
|
+
}
|
|
349
339
|
|
|
350
|
-
|
|
351
|
-
|
|
340
|
+
NSError *copyError = nil;
|
|
341
|
+
BOOL copySuccess = [fileManager copyItemAtPath:wwwSourcePath toPath:finalWwwPath error:©Error];
|
|
352
342
|
|
|
353
|
-
|
|
354
|
-
NSLog(@"[HotUpdates] www folder copied successfully to: %@", finalWwwPath);
|
|
343
|
+
[fileManager removeItemAtPath:tempExtractPath error:nil];
|
|
355
344
|
|
|
356
|
-
|
|
345
|
+
if (!copySuccess) {
|
|
346
|
+
NSLog(@"[HotUpdates] ERROR: Failed to copy www folder: %@", copyError.localizedDescription);
|
|
347
|
+
return NO;
|
|
348
|
+
}
|
|
357
349
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
NSLog(@"[HotUpdates] Error copying www folder: %@", copyError.localizedDescription);
|
|
362
|
-
}
|
|
363
|
-
} else {
|
|
364
|
-
NSLog(@"[HotUpdates] www folder not found in ZIP archive");
|
|
365
|
-
NSLog(@"[HotUpdates] Available contents: %@", extractedContents);
|
|
366
|
-
}
|
|
350
|
+
NSLog(@"[HotUpdates] Extraction completed successfully");
|
|
351
|
+
return YES;
|
|
352
|
+
}
|
|
367
353
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
354
|
+
- (BOOL)isValidZipFile:(NSString*)filePath {
|
|
355
|
+
NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:filePath];
|
|
356
|
+
if (!file) return NO;
|
|
357
|
+
|
|
358
|
+
NSData *header = [file readDataOfLength:4];
|
|
359
|
+
[file closeFile];
|
|
373
360
|
|
|
374
|
-
|
|
375
|
-
|
|
361
|
+
if (header.length < 4) return NO;
|
|
362
|
+
|
|
363
|
+
const uint8_t *bytes = header.bytes;
|
|
364
|
+
// ZIP magic: PK\x03\x04 (0x504B0304)
|
|
365
|
+
return (bytes[0] == 0x50 && bytes[1] == 0x4B && bytes[2] == 0x03 && bytes[3] == 0x04);
|
|
376
366
|
}
|
|
377
367
|
|
|
378
368
|
#pragma mark - Settings Management
|
|
@@ -409,6 +399,58 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
409
399
|
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
410
400
|
}
|
|
411
401
|
|
|
402
|
+
#pragma mark - Version History Management
|
|
403
|
+
|
|
404
|
+
- (void)loadVersionHistory {
|
|
405
|
+
NSArray *savedHistory = [[NSUserDefaults standardUserDefaults] arrayForKey:kVersionHistory];
|
|
406
|
+
if (savedHistory) {
|
|
407
|
+
versionHistory = [savedHistory mutableCopy];
|
|
408
|
+
} else {
|
|
409
|
+
versionHistory = [NSMutableArray array];
|
|
410
|
+
// При первом запуске добавляем исходную версию приложения
|
|
411
|
+
if (appBundleVersion) {
|
|
412
|
+
[versionHistory addObject:appBundleVersion];
|
|
413
|
+
[self saveVersionHistory];
|
|
414
|
+
NSLog(@"[HotUpdates] Initial version history created with app version: %@", appBundleVersion);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
- (NSArray*)getVersionHistoryInternal {
|
|
420
|
+
return [versionHistory copy];
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
- (void)saveVersionHistory {
|
|
424
|
+
[[NSUserDefaults standardUserDefaults] setObject:versionHistory forKey:kVersionHistory];
|
|
425
|
+
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
- (void)addVersionToHistory:(NSString*)version {
|
|
429
|
+
if (version && ![versionHistory containsObject:version]) {
|
|
430
|
+
[versionHistory addObject:version];
|
|
431
|
+
[self saveVersionHistory];
|
|
432
|
+
NSLog(@"[HotUpdates] Added version %@ to version history", version);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
- (void)removeVersionFromHistory:(NSString*)version {
|
|
437
|
+
if (version && [versionHistory containsObject:version]) {
|
|
438
|
+
[versionHistory removeObject:version];
|
|
439
|
+
[self saveVersionHistory];
|
|
440
|
+
NSLog(@"[HotUpdates] Removed version %@ from version history", version);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
- (void)getVersionHistory:(CDVInvokedUrlCommand*)command {
|
|
445
|
+
NSArray *history = [self getVersionHistoryInternal];
|
|
446
|
+
|
|
447
|
+
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
|
|
448
|
+
messageAsDictionary:@{
|
|
449
|
+
@"versions": history
|
|
450
|
+
}];
|
|
451
|
+
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
|
452
|
+
}
|
|
453
|
+
|
|
412
454
|
#pragma mark - Canary Timer
|
|
413
455
|
|
|
414
456
|
/*!
|
|
@@ -557,6 +599,8 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
557
599
|
|
|
558
600
|
if (currentVersion) {
|
|
559
601
|
[self addVersionToIgnoreList:currentVersion];
|
|
602
|
+
// Удаляем откаченную версию из истории (она не прошла canary)
|
|
603
|
+
[self removeVersionFromHistory:currentVersion];
|
|
560
604
|
}
|
|
561
605
|
|
|
562
606
|
return YES;
|
|
@@ -603,8 +647,7 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
603
647
|
updateVersion = @"pending";
|
|
604
648
|
}
|
|
605
649
|
|
|
606
|
-
NSLog(@"[HotUpdates] getUpdate
|
|
607
|
-
NSLog(@"[HotUpdates] Version: %@", updateVersion);
|
|
650
|
+
NSLog(@"[HotUpdates] getUpdate: v%@ from %@", updateVersion, downloadURL);
|
|
608
651
|
|
|
609
652
|
NSString *installedVersion = [[NSUserDefaults standardUserDefaults] stringForKey:kInstalledVersion];
|
|
610
653
|
if (installedVersion && [installedVersion isEqualToString:updateVersion]) {
|
|
@@ -632,11 +675,9 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
632
675
|
return;
|
|
633
676
|
}
|
|
634
677
|
|
|
678
|
+
// Сохраняем только в память, в UserDefaults запишем после успешной загрузки
|
|
635
679
|
pendingUpdateURL = downloadURL;
|
|
636
680
|
pendingUpdateVersion = updateVersion;
|
|
637
|
-
[[NSUserDefaults standardUserDefaults] setObject:downloadURL forKey:kPendingUpdateURL];
|
|
638
|
-
[[NSUserDefaults standardUserDefaults] setObject:updateVersion forKey:kPendingVersion];
|
|
639
|
-
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
640
681
|
|
|
641
682
|
[self downloadUpdateOnly:downloadURL callbackId:command.callbackId];
|
|
642
683
|
}
|
|
@@ -650,7 +691,7 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
650
691
|
|
|
651
692
|
NSURL *url = [NSURL URLWithString:downloadURL];
|
|
652
693
|
if (!url) {
|
|
653
|
-
NSLog(@"[HotUpdates] Invalid URL
|
|
694
|
+
NSLog(@"[HotUpdates] ERROR: Invalid URL format");
|
|
654
695
|
isDownloadingUpdate = NO;
|
|
655
696
|
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:kDownloadInProgress];
|
|
656
697
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
@@ -678,7 +719,7 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
678
719
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
679
720
|
|
|
680
721
|
if (error) {
|
|
681
|
-
NSLog(@"[HotUpdates] Download failed: %@", error.localizedDescription);
|
|
722
|
+
NSLog(@"[HotUpdates] ERROR: Download failed: %@", error.localizedDescription);
|
|
682
723
|
|
|
683
724
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
684
725
|
messageAsDictionary:[self createError:kErrorDownloadFailed
|
|
@@ -689,7 +730,7 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
689
730
|
|
|
690
731
|
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
|
691
732
|
if (httpResponse.statusCode != 200) {
|
|
692
|
-
NSLog(@"[HotUpdates]
|
|
733
|
+
NSLog(@"[HotUpdates] ERROR: HTTP %ld", (long)httpResponse.statusCode);
|
|
693
734
|
|
|
694
735
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
695
736
|
messageAsDictionary:[self createError:kErrorHTTPError
|
|
@@ -698,9 +739,7 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
698
739
|
return;
|
|
699
740
|
}
|
|
700
741
|
|
|
701
|
-
NSLog(@"[HotUpdates] Download completed
|
|
702
|
-
|
|
703
|
-
// Сохраняем скачанное обновление во временную папку
|
|
742
|
+
NSLog(@"[HotUpdates] Download completed, verifying...");
|
|
704
743
|
[self saveDownloadedUpdate:location callbackId:callbackId];
|
|
705
744
|
}];
|
|
706
745
|
|
|
@@ -708,24 +747,25 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
708
747
|
}
|
|
709
748
|
|
|
710
749
|
- (void)saveDownloadedUpdate:(NSURL*)updateLocation callbackId:(NSString*)callbackId {
|
|
711
|
-
NSLog(@"[HotUpdates] Saving downloaded update");
|
|
712
|
-
|
|
713
750
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
714
751
|
NSError *error;
|
|
715
752
|
|
|
753
|
+
// Используем временную папку для новой загрузки (не трогаем существующую temp_downloaded_update)
|
|
754
|
+
NSString *newDownloadPath = [documentsPath stringByAppendingPathComponent:@"temp_new_download"];
|
|
716
755
|
NSString *tempUpdatePath = [documentsPath stringByAppendingPathComponent:@"temp_downloaded_update"];
|
|
717
756
|
|
|
718
|
-
|
|
719
|
-
|
|
757
|
+
// Очищаем только папку для новой загрузки
|
|
758
|
+
if ([fileManager fileExistsAtPath:newDownloadPath]) {
|
|
759
|
+
[fileManager removeItemAtPath:newDownloadPath error:nil];
|
|
720
760
|
}
|
|
721
761
|
|
|
722
|
-
[fileManager createDirectoryAtPath:
|
|
762
|
+
[fileManager createDirectoryAtPath:newDownloadPath
|
|
723
763
|
withIntermediateDirectories:YES
|
|
724
764
|
attributes:nil
|
|
725
765
|
error:&error];
|
|
726
766
|
|
|
727
767
|
if (error) {
|
|
728
|
-
NSLog(@"[HotUpdates]
|
|
768
|
+
NSLog(@"[HotUpdates] ERROR: Failed to create temp directory: %@", error.localizedDescription);
|
|
729
769
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
730
770
|
messageAsDictionary:[self createError:kErrorTempDirError
|
|
731
771
|
message:@"Cannot create temp directory"]];
|
|
@@ -733,11 +773,10 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
733
773
|
return;
|
|
734
774
|
}
|
|
735
775
|
|
|
736
|
-
BOOL unzipSuccess = [self unzipFile:updateLocation.path toDestination:
|
|
776
|
+
BOOL unzipSuccess = [self unzipFile:updateLocation.path toDestination:newDownloadPath];
|
|
737
777
|
|
|
738
778
|
if (!unzipSuccess) {
|
|
739
|
-
|
|
740
|
-
[fileManager removeItemAtPath:tempUpdatePath error:nil];
|
|
779
|
+
[fileManager removeItemAtPath:newDownloadPath error:nil];
|
|
741
780
|
|
|
742
781
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
743
782
|
messageAsDictionary:[self createError:kErrorExtractionFailed
|
|
@@ -746,10 +785,10 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
746
785
|
return;
|
|
747
786
|
}
|
|
748
787
|
|
|
749
|
-
NSString *
|
|
750
|
-
if (![fileManager fileExistsAtPath:
|
|
751
|
-
NSLog(@"[HotUpdates] www folder not found in update package");
|
|
752
|
-
[fileManager removeItemAtPath:
|
|
788
|
+
NSString *newWwwPath = [newDownloadPath stringByAppendingPathComponent:kWWWDirName];
|
|
789
|
+
if (![fileManager fileExistsAtPath:newWwwPath]) {
|
|
790
|
+
NSLog(@"[HotUpdates] ERROR: www folder not found in update package");
|
|
791
|
+
[fileManager removeItemAtPath:newDownloadPath error:nil];
|
|
753
792
|
|
|
754
793
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
755
794
|
messageAsDictionary:[self createError:kErrorWWWNotFound
|
|
@@ -758,6 +797,12 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
758
797
|
return;
|
|
759
798
|
}
|
|
760
799
|
|
|
800
|
+
// Успех! Теперь безопасно заменяем старую temp_downloaded_update на новую
|
|
801
|
+
if ([fileManager fileExistsAtPath:tempUpdatePath]) {
|
|
802
|
+
[fileManager removeItemAtPath:tempUpdatePath error:nil];
|
|
803
|
+
}
|
|
804
|
+
[fileManager moveItemAtPath:newDownloadPath toPath:tempUpdatePath error:nil];
|
|
805
|
+
|
|
761
806
|
// Копируем в pending_update для автоустановки при следующем запуске
|
|
762
807
|
NSString *pendingPath = [documentsPath stringByAppendingPathComponent:kPendingUpdateDirName];
|
|
763
808
|
|
|
@@ -770,18 +815,18 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
770
815
|
error:&error];
|
|
771
816
|
|
|
772
817
|
if (!copySuccess) {
|
|
773
|
-
NSLog(@"[HotUpdates] Failed to copy to pending_update: %@", error);
|
|
774
|
-
} else {
|
|
775
|
-
NSLog(@"[HotUpdates] Copied to pending_update for auto-install on next launch");
|
|
818
|
+
NSLog(@"[HotUpdates] WARNING: Failed to copy to pending_update (auto-install disabled): %@", error.localizedDescription);
|
|
776
819
|
}
|
|
777
820
|
|
|
778
821
|
isUpdateReadyToInstall = YES;
|
|
822
|
+
// Сохраняем URL и версию в UserDefaults только после успешной загрузки
|
|
823
|
+
[[NSUserDefaults standardUserDefaults] setObject:pendingUpdateURL forKey:kPendingUpdateURL];
|
|
824
|
+
[[NSUserDefaults standardUserDefaults] setObject:pendingUpdateVersion forKey:kPendingVersion];
|
|
779
825
|
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kPendingUpdateReady];
|
|
780
826
|
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kHasPending];
|
|
781
827
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
782
828
|
|
|
783
|
-
NSLog(@"[HotUpdates] Update
|
|
784
|
-
NSLog(@"[HotUpdates] If user ignores popup, update will install automatically on next launch");
|
|
829
|
+
NSLog(@"[HotUpdates] Update ready (v%@)", pendingUpdateVersion);
|
|
785
830
|
|
|
786
831
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
|
787
832
|
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
|
@@ -790,8 +835,6 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
790
835
|
#pragma mark - Force Update (Install Only)
|
|
791
836
|
|
|
792
837
|
- (void)forceUpdate:(CDVInvokedUrlCommand*)command {
|
|
793
|
-
NSLog(@"[HotUpdates] forceUpdate() called - installing downloaded update");
|
|
794
|
-
|
|
795
838
|
if (!isUpdateReadyToInstall) {
|
|
796
839
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
797
840
|
messageAsDictionary:[self createError:kErrorNoUpdateReady
|
|
@@ -804,7 +847,7 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
804
847
|
NSString *tempWwwPath = [tempUpdatePath stringByAppendingPathComponent:kWWWDirName];
|
|
805
848
|
|
|
806
849
|
if (![[NSFileManager defaultManager] fileExistsAtPath:tempWwwPath]) {
|
|
807
|
-
NSLog(@"[HotUpdates] Downloaded update files not found");
|
|
850
|
+
NSLog(@"[HotUpdates] ERROR: Downloaded update files not found");
|
|
808
851
|
|
|
809
852
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
810
853
|
messageAsDictionary:[self createError:kErrorUpdateFilesNotFound
|
|
@@ -817,13 +860,13 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
817
860
|
}
|
|
818
861
|
|
|
819
862
|
- (void)installDownloadedUpdate:(NSString*)tempWwwPath callbackId:(NSString*)callbackId {
|
|
820
|
-
NSLog(@"[HotUpdates] Installing update");
|
|
821
|
-
|
|
822
863
|
NSString *versionToInstall = [[NSUserDefaults standardUserDefaults] stringForKey:kPendingVersion];
|
|
823
864
|
if (!versionToInstall) {
|
|
824
865
|
versionToInstall = @"unknown";
|
|
825
866
|
}
|
|
826
867
|
|
|
868
|
+
NSLog(@"[HotUpdates] forceUpdate: installing v%@", versionToInstall);
|
|
869
|
+
|
|
827
870
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
828
871
|
NSError *error;
|
|
829
872
|
|
|
@@ -838,7 +881,7 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
838
881
|
error:&error];
|
|
839
882
|
|
|
840
883
|
if (!copySuccess) {
|
|
841
|
-
NSLog(@"[HotUpdates] Failed to install update: %@", error);
|
|
884
|
+
NSLog(@"[HotUpdates] ERROR: Failed to install update: %@", error.localizedDescription);
|
|
842
885
|
|
|
843
886
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
|
|
844
887
|
messageAsDictionary:[self createError:kErrorInstallFailed
|
|
@@ -866,19 +909,18 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
866
909
|
pendingUpdateURL = nil;
|
|
867
910
|
pendingUpdateVersion = nil;
|
|
868
911
|
|
|
912
|
+
// Добавляем версию в историю при успешной установке
|
|
913
|
+
[self addVersionToHistory:newVersion];
|
|
914
|
+
|
|
869
915
|
NSLog(@"[HotUpdates] Update installed successfully");
|
|
916
|
+
NSLog(@"[HotUpdates] Starting canary timer for version %@", newVersion);
|
|
870
917
|
|
|
871
918
|
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
|
872
919
|
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
|
873
920
|
|
|
874
|
-
// После reloadWebView pluginInitialize НЕ вызывается, поэтому canary timer запускаем вручную
|
|
875
|
-
NSLog(@"[HotUpdates] Starting canary timer (20 seconds) for version %@", newVersion);
|
|
876
|
-
|
|
877
921
|
[self startCanaryTimer];
|
|
878
|
-
|
|
879
922
|
hasPerformedInitialReload = NO;
|
|
880
923
|
|
|
881
|
-
// Очищаем кэш WebView перед перезагрузкой, иначе может загрузиться старая версия
|
|
882
924
|
[self clearWebViewCacheWithCompletion:^{
|
|
883
925
|
[self reloadWebView];
|
|
884
926
|
}];
|
|
@@ -901,8 +943,6 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
901
943
|
return;
|
|
902
944
|
}
|
|
903
945
|
|
|
904
|
-
NSLog(@"[HotUpdates] Canary called for version: %@", canaryVersion);
|
|
905
|
-
|
|
906
946
|
// Сохраняем canary версию
|
|
907
947
|
[[NSUserDefaults standardUserDefaults] setObject:canaryVersion forKey:kCanaryVersion];
|
|
908
948
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
@@ -911,7 +951,7 @@ static BOOL hasPerformedInitialReload = NO;
|
|
|
911
951
|
if (canaryTimer && [canaryTimer isValid]) {
|
|
912
952
|
[canaryTimer invalidate];
|
|
913
953
|
canaryTimer = nil;
|
|
914
|
-
NSLog(@"[HotUpdates] Canary
|
|
954
|
+
NSLog(@"[HotUpdates] Canary confirmed: v%@", canaryVersion);
|
|
915
955
|
}
|
|
916
956
|
|
|
917
957
|
// ТЗ: при успехе callback возвращает null
|
|
@@ -39,6 +39,7 @@ extern NSString * const kCanaryVersion;
|
|
|
39
39
|
extern NSString * const kDownloadInProgress;
|
|
40
40
|
extern NSString * const kPendingUpdateURL;
|
|
41
41
|
extern NSString * const kPendingUpdateReady;
|
|
42
|
+
extern NSString * const kVersionHistory;
|
|
42
43
|
|
|
43
44
|
#pragma mark - Directory Names
|
|
44
45
|
|
|
@@ -37,6 +37,7 @@ NSString * const kCanaryVersion = @"hot_updates_canary_version";
|
|
|
37
37
|
NSString * const kDownloadInProgress = @"hot_updates_download_in_progress";
|
|
38
38
|
NSString * const kPendingUpdateURL = @"hot_updates_pending_update_url";
|
|
39
39
|
NSString * const kPendingUpdateReady = @"hot_updates_pending_ready";
|
|
40
|
+
NSString * const kVersionHistory = @"hot_updates_version_history";
|
|
40
41
|
|
|
41
42
|
#pragma mark - Directory Names
|
|
42
43
|
|