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 CHANGED
@@ -1,4 +1,4 @@
1
- # Cordova Hot Updates Plugin v2.1.2
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.1.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.1.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
@@ -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
- // Information methods
67
- - (void)getVersionInfo:(CDVInvokedUrlCommand*)command;
67
+ // Debug methods
68
+ - (void)getVersionInfo:(CDVInvokedUrlCommand*)command; // Get all version info for debugging
68
69
 
69
70
  @end
@@ -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.1.2
16
- * @date 2025-11-13
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:&copyError];
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]; // Сбрасываем canary для новой версии
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
- // Предотвращаем повторные перезагрузки при навигации между страницами (например, admin → index.html)
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
- // Используем loadFileURL:allowingReadAccessToURL: для правильных sandbox permissions
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:&copyError];
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
- // Проверка: previous = current (защита от цикла)
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
- @"error": @{
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
- @"error": @{
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
- @"error": @{
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
- @"error": @{
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
- @"error": @{
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
- @"error": @{
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
- @"error": @{
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
- @"error": @{
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
- // Копируем также в pending_update для автоустановки при следующем запуске (ТЗ п.7)
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
- @"error": @{
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
- @"error": @{
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
- @"error": @{
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]; // Сбрасываем canary
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
- // КРИТИЧЕСКИ ВАЖНО: Запускаем canary timer ПЕРЕД перезагрузкой
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
- // КРИТИЧЕСКИ ВАЖНО: Очищаем кэш WebView перед перезагрузкой
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
- messageAsString:@"Version required"];
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 - Information Methods
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";