@hot-updater/react-native 1.0.0-rc.3 → 1.0.0-rc.30
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/android/proguard-rules.pro +17 -2
- package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +954 -837
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +15 -11
- package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +43 -31
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +37 -54
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
- package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
- package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
- package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
- package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
- package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +127 -50
- package/android/src/main/java/com/hotupdater/PreviousProcessExit.kt +89 -0
- package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
- package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
- package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
- package/android/src/newarch/HotUpdater.kt +7 -9
- package/android/src/newarch/HotUpdaterModule.kt +109 -84
- package/android/src/oldarch/HotUpdater.kt +7 -9
- package/android/src/oldarch/HotUpdaterModule.kt +98 -74
- package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
- package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
- package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +950 -1112
- package/ios/HotUpdater/Internal/BundleMetadata.swift +8 -53
- package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
- package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
- package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
- package/ios/HotUpdater/Internal/HotUpdater.mm +13 -45
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +190 -73
- package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
- package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
- package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
- package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
- package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
- package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
- package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
- package/lib/commonjs/appReady.js +111 -0
- package/lib/commonjs/appReady.js.map +1 -0
- package/lib/commonjs/checkForUpdate.js +124 -69
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/clientPlugin.js +13 -0
- package/lib/commonjs/clientPlugin.js.map +1 -0
- package/lib/commonjs/error.js +4 -13
- package/lib/commonjs/error.js.map +1 -1
- package/lib/commonjs/fetchJSON.js +16 -27
- package/lib/commonjs/fetchJSON.js.map +1 -1
- package/lib/commonjs/fetchUpdateResponse.js +54 -0
- package/lib/commonjs/fetchUpdateResponse.js.map +1 -0
- package/lib/commonjs/httpClient.js +63 -94
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/index.js +71 -18
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +78 -60
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/pluginHost.js +44 -0
- package/lib/commonjs/pluginHost.js.map +1 -0
- package/lib/commonjs/releaseCatalogCache.js +36 -87
- package/lib/commonjs/releaseCatalogCache.js.map +1 -1
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/sdkVersion.js.map +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/store.js +3 -10
- package/lib/commonjs/store.js.map +1 -1
- package/lib/commonjs/updateError.js +132 -0
- package/lib/commonjs/updateError.js.map +1 -0
- package/lib/commonjs/wrap.js +43 -26
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/module/appReady.js +103 -0
- package/lib/module/appReady.js.map +1 -0
- package/lib/module/checkForUpdate.js +117 -64
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/clientPlugin.js +4 -0
- package/lib/module/clientPlugin.js.map +1 -0
- package/lib/module/error.js +4 -13
- package/lib/module/error.js.map +1 -1
- package/lib/module/fetchJSON.js +14 -26
- package/lib/module/fetchJSON.js.map +1 -1
- package/lib/module/fetchUpdateResponse.js +49 -0
- package/lib/module/fetchUpdateResponse.js.map +1 -0
- package/lib/module/httpClient.js +62 -93
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/index.js +58 -20
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +71 -53
- package/lib/module/native.js.map +1 -1
- package/lib/module/pluginHost.js +35 -0
- package/lib/module/pluginHost.js.map +1 -0
- package/lib/module/releaseCatalogCache.js +32 -82
- package/lib/module/releaseCatalogCache.js.map +1 -1
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/sdkVersion.js.map +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/store.js +3 -10
- package/lib/module/store.js.map +1 -1
- package/lib/module/updateError.js +126 -0
- package/lib/module/updateError.js.map +1 -0
- package/lib/module/wrap.js +43 -26
- package/lib/module/wrap.js.map +1 -1
- package/lib/typescript/commonjs/appReady.d.ts +23 -0
- package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +16 -4
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
- package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
- package/lib/typescript/commonjs/error.d.ts +4 -13
- package/lib/typescript/commonjs/error.d.ts.map +1 -1
- package/lib/typescript/commonjs/fetchJSON.d.ts +5 -5
- package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/commonjs/fetchUpdateResponse.d.ts +14 -0
- package/lib/typescript/commonjs/fetchUpdateResponse.d.ts.map +1 -0
- package/lib/typescript/commonjs/httpClient.d.ts +4 -33
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -8
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +31 -22
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/pluginHost.d.ts +4 -0
- package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts +8 -14
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +42 -47
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/store.d.ts +1 -3
- package/lib/typescript/commonjs/store.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +1 -1
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/updateError.d.ts +26 -0
- package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +6 -7
- package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
- package/lib/typescript/module/appReady.d.ts +23 -0
- package/lib/typescript/module/appReady.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +16 -4
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/clientPlugin.d.ts +2 -0
- package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
- package/lib/typescript/module/error.d.ts +4 -13
- package/lib/typescript/module/error.d.ts.map +1 -1
- package/lib/typescript/module/fetchJSON.d.ts +5 -5
- package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/module/fetchUpdateResponse.d.ts +14 -0
- package/lib/typescript/module/fetchUpdateResponse.d.ts.map +1 -0
- package/lib/typescript/module/httpClient.d.ts +4 -33
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -8
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +31 -22
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/pluginHost.d.ts +4 -0
- package/lib/typescript/module/pluginHost.d.ts.map +1 -0
- package/lib/typescript/module/releaseCatalogCache.d.ts +8 -14
- package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +42 -47
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/store.d.ts +1 -3
- package/lib/typescript/module/store.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +1 -1
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/updateError.d.ts +26 -0
- package/lib/typescript/module/updateError.d.ts.map +1 -0
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/lib/typescript/module/wrap.types.d.ts +6 -7
- package/lib/typescript/module/wrap.types.d.ts.map +1 -1
- package/package.json +7 -4
- package/src/appReady.ts +149 -0
- package/src/checkForUpdate.ts +151 -88
- package/src/clientPlugin.ts +18 -0
- package/src/error.ts +4 -14
- package/src/fetchJSON.ts +14 -30
- package/src/fetchUpdateResponse.ts +54 -0
- package/src/httpClient.ts +88 -159
- package/src/index.ts +90 -24
- package/src/native.ts +106 -102
- package/src/pluginHost.ts +45 -0
- package/src/releaseCatalogCache.ts +43 -172
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +41 -48
- package/src/store.ts +2 -23
- package/src/types.ts +1 -1
- package/src/updateError.ts +153 -0
- package/src/wrap.tsx +57 -26
- package/src/wrap.types.ts +6 -7
- package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
- package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
- package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
- package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
- package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
- package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
- package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
- package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
- package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
- package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
- package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
- package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
- package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
- package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
- package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
- package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
- package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
- package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
- package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
- package/lib/commonjs/catalogCacheNative.spec.js +0 -31
- package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -615
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
- package/lib/commonjs/httpClient.spec.js +0 -102
- package/lib/commonjs/httpClient.spec.js.map +0 -1
- package/lib/commonjs/index.spec.js +0 -302
- package/lib/commonjs/index.spec.js.map +0 -1
- package/lib/commonjs/iosInterop.spec.js +0 -18
- package/lib/commonjs/iosInterop.spec.js.map +0 -1
- package/lib/commonjs/native.spec.js +0 -883
- package/lib/commonjs/native.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.js +0 -133
- package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
- package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
- package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
- package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
- package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
- package/lib/commonjs/store.notification.spec.js +0 -76
- package/lib/commonjs/store.notification.spec.js.map +0 -1
- package/lib/commonjs/store.spec.js +0 -316
- package/lib/commonjs/store.spec.js.map +0 -1
- package/lib/commonjs/wrap.spec.js +0 -329
- package/lib/commonjs/wrap.spec.js.map +0 -1
- package/lib/module/catalogCacheNative.spec.js +0 -31
- package/lib/module/catalogCacheNative.spec.js.map +0 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -614
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
- package/lib/module/httpClient.spec.js +0 -102
- package/lib/module/httpClient.spec.js.map +0 -1
- package/lib/module/index.spec.js +0 -301
- package/lib/module/index.spec.js.map +0 -1
- package/lib/module/iosInterop.spec.js +0 -18
- package/lib/module/iosInterop.spec.js.map +0 -1
- package/lib/module/native.spec.js +0 -882
- package/lib/module/native.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
- package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.js +0 -128
- package/lib/module/notifyAppReadyInsights.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.spec.js +0 -245
- package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
- package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
- package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/module/releaseCatalogCache.spec.js +0 -195
- package/lib/module/releaseCatalogCache.spec.js.map +0 -1
- package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
- package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
- package/lib/module/store.notification.spec.js +0 -77
- package/lib/module/store.notification.spec.js.map +0 -1
- package/lib/module/store.spec.js +0 -315
- package/lib/module/store.spec.js.map +0 -1
- package/lib/module/wrap.spec.js +0 -330
- package/lib/module/wrap.spec.js.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
- package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
- package/src/catalogCacheNative.spec.ts +0 -63
- package/src/checkForUpdate.releaseCatalog.spec.ts +0 -611
- package/src/httpClient.spec.ts +0 -132
- package/src/index.spec.ts +0 -337
- package/src/iosInterop.spec.ts +0 -30
- package/src/native.spec.ts +0 -945
- package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
- package/src/notifyAppReadyInsights.spec.ts +0 -243
- package/src/notifyAppReadyInsights.test-utils.ts +0 -31
- package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
- package/src/notifyAppReadyInsights.ts +0 -194
- package/src/releaseCatalogCache.spec.ts +0 -282
- package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
- package/src/store.notification.spec.ts +0 -92
- package/src/store.spec.ts +0 -372
- package/src/wrap.spec.ts +0 -406
|
@@ -26,6 +26,16 @@ class HotUpdaterModule internal constructor(
|
|
|
26
26
|
|
|
27
27
|
override fun getName(): String = NAME
|
|
28
28
|
|
|
29
|
+
private fun readSafeInteger(
|
|
30
|
+
map: ReadableMap,
|
|
31
|
+
key: String,
|
|
32
|
+
): Long? {
|
|
33
|
+
if (!map.hasKey(key) || map.isNull(key)) return null
|
|
34
|
+
val value = runCatching { map.getDouble(key) }.getOrNull() ?: return null
|
|
35
|
+
if (!value.isFinite() || value % 1.0 != 0.0) return null
|
|
36
|
+
return value.toLong().takeIf { it in 0..9_007_199_254_740_991L }
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
override fun onCatalystInstanceDestroy() {
|
|
30
40
|
super.onCatalystInstanceDestroy()
|
|
31
41
|
// Cancel all ongoing coroutines when module is destroyed
|
|
@@ -37,12 +47,12 @@ class HotUpdaterModule internal constructor(
|
|
|
37
47
|
*/
|
|
38
48
|
private fun getInstance(): HotUpdaterImpl = HotUpdater.getInstance(mReactApplicationContext)
|
|
39
49
|
|
|
40
|
-
private fun
|
|
41
|
-
if (!params.hasKey("
|
|
50
|
+
private fun parseAssets(params: ReadableMap): Map<String, ChangedAssetDescriptor>? {
|
|
51
|
+
if (!params.hasKey("assets") || params.isNull("assets")) {
|
|
42
52
|
return null
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
val changedAssetsMap = params.getMap("
|
|
55
|
+
val changedAssetsMap = params.getMap("assets") ?: return null
|
|
46
56
|
val parsedAssets = linkedMapOf<String, ChangedAssetDescriptor>()
|
|
47
57
|
val iterator = changedAssetsMap.keySetIterator()
|
|
48
58
|
|
|
@@ -58,6 +68,7 @@ class HotUpdaterModule internal constructor(
|
|
|
58
68
|
val baseFileHash = patchMap.getString("baseFileHash")
|
|
59
69
|
val patchFileHash = patchMap.getString("patchFileHash")
|
|
60
70
|
val patchUrl = patchMap.getString("patchUrl")
|
|
71
|
+
val byteSize = readSafeInteger(patchMap, "byteSize")
|
|
61
72
|
|
|
62
73
|
if (
|
|
63
74
|
algorithm != null &&
|
|
@@ -72,6 +83,7 @@ class HotUpdaterModule internal constructor(
|
|
|
72
83
|
baseFileHash = baseFileHash,
|
|
73
84
|
patchFileHash = patchFileHash,
|
|
74
85
|
patchUrl = patchUrl,
|
|
86
|
+
byteSize = byteSize,
|
|
75
87
|
)
|
|
76
88
|
} else {
|
|
77
89
|
null
|
|
@@ -166,90 +178,105 @@ class HotUpdaterModule internal constructor(
|
|
|
166
178
|
try {
|
|
167
179
|
val bundleId = params.getString("bundleId")
|
|
168
180
|
if (bundleId == null || bundleId.isEmpty()) {
|
|
169
|
-
promise.
|
|
181
|
+
promise.rejectUpdateBundle(HotUpdaterException.missingBundleId())
|
|
170
182
|
return@launch
|
|
171
183
|
}
|
|
172
184
|
|
|
173
|
-
val
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (
|
|
185
|
+
val manifestUrl = params.getString("manifestUrl")
|
|
186
|
+
val manifestFileHash = params.getString("manifestFileHash")
|
|
187
|
+
val assets = parseAssets(params)
|
|
188
|
+
if (manifestUrl.isNullOrEmpty() || manifestFileHash.isNullOrEmpty() || assets == null) {
|
|
189
|
+
promise.rejectUpdateBundle(HotUpdaterException.invalidManifestParams())
|
|
190
|
+
return@launch
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
java.net.URL(manifestUrl)
|
|
194
|
+
} catch (e: java.net.MalformedURLException) {
|
|
195
|
+
promise.rejectUpdateBundle(
|
|
196
|
+
HotUpdaterException.invalidFileUrl("manifestUrl", manifestUrl, UpdateFailureResource.MANIFEST),
|
|
197
|
+
)
|
|
198
|
+
return@launch
|
|
199
|
+
}
|
|
200
|
+
val channel = params.getString("channel")
|
|
201
|
+
val archiveUrl =
|
|
202
|
+
if (params.hasKey("archiveUrl") && !params.isNull("archiveUrl")) {
|
|
203
|
+
params.getString("archiveUrl")
|
|
204
|
+
} else {
|
|
205
|
+
null
|
|
206
|
+
}
|
|
207
|
+
if (archiveUrl != null) {
|
|
177
208
|
try {
|
|
178
|
-
java.net.URL(
|
|
209
|
+
java.net.URL(archiveUrl)
|
|
179
210
|
} catch (e: java.net.MalformedURLException) {
|
|
180
|
-
promise.
|
|
211
|
+
promise.rejectUpdateBundle(
|
|
212
|
+
HotUpdaterException.invalidFileUrl("archiveUrl", archiveUrl, UpdateFailureResource.ARCHIVE),
|
|
213
|
+
)
|
|
181
214
|
return@launch
|
|
182
215
|
}
|
|
183
216
|
}
|
|
184
|
-
|
|
185
|
-
val fileHash = params.getString("fileHash")
|
|
186
|
-
val manifestUrl = params.getString("manifestUrl")
|
|
187
|
-
val manifestFileHash = params.getString("manifestFileHash")
|
|
188
|
-
val changedAssets = parseChangedAssets(params)
|
|
189
|
-
val channel = params.getString("channel")
|
|
190
217
|
val selection = parseSelection(params)
|
|
191
218
|
|
|
192
219
|
val impl = getInstance()
|
|
193
220
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
221
|
+
val result =
|
|
222
|
+
impl.updateBundle(
|
|
223
|
+
bundleId,
|
|
224
|
+
manifestUrl,
|
|
225
|
+
manifestFileHash,
|
|
226
|
+
assets,
|
|
227
|
+
channel,
|
|
228
|
+
selection,
|
|
229
|
+
archiveUrl,
|
|
230
|
+
) { progress ->
|
|
231
|
+
// Post to Main thread for React Native event emission
|
|
232
|
+
Handler(Looper.getMainLooper()).post {
|
|
233
|
+
try {
|
|
234
|
+
val progressParams =
|
|
235
|
+
Arguments.createMap().apply {
|
|
236
|
+
putDouble("progress", progress.progress)
|
|
237
|
+
putString("artifactType", progress.artifactType)
|
|
238
|
+
progress.details?.let { details ->
|
|
239
|
+
val files = Arguments.createArray()
|
|
240
|
+
details.files.forEach { file ->
|
|
241
|
+
files.pushMap(
|
|
242
|
+
Arguments.createMap().apply {
|
|
243
|
+
putString("path", file.path)
|
|
244
|
+
putString("downloadPath", file.downloadPath)
|
|
245
|
+
putString("status", file.status)
|
|
246
|
+
putDouble("progress", file.progress)
|
|
247
|
+
putInt("order", file.order)
|
|
248
|
+
file.downloadedBytes?.let { putDouble("downloadedBytes", it.toDouble()) }
|
|
249
|
+
file.totalBytes?.let { putDouble("totalBytes", it.toDouble()) }
|
|
250
|
+
},
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
putMap(
|
|
255
|
+
"details",
|
|
217
256
|
Arguments.createMap().apply {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
putDouble("progress", file.progress)
|
|
222
|
-
putInt("order", file.order)
|
|
223
|
-
file.downloadedBytes?.let { putDouble("downloadedBytes", it.toDouble()) }
|
|
224
|
-
file.totalBytes?.let { putDouble("totalBytes", it.toDouble()) }
|
|
257
|
+
putInt("totalFilesCount", details.totalFilesCount)
|
|
258
|
+
putInt("completedFilesCount", details.completedFilesCount)
|
|
259
|
+
putArray("files", files)
|
|
225
260
|
},
|
|
226
261
|
)
|
|
227
262
|
}
|
|
228
|
-
|
|
229
|
-
putMap(
|
|
230
|
-
"details",
|
|
231
|
-
Arguments.createMap().apply {
|
|
232
|
-
putInt("totalFilesCount", details.totalFilesCount)
|
|
233
|
-
putInt("completedFilesCount", details.completedFilesCount)
|
|
234
|
-
putArray("files", files)
|
|
235
|
-
},
|
|
236
|
-
)
|
|
237
263
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
264
|
+
|
|
265
|
+
this@HotUpdaterModule
|
|
266
|
+
.mReactApplicationContext
|
|
267
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
268
|
+
?.emit("onProgress", progressParams)
|
|
269
|
+
} catch (e: Exception) {
|
|
270
|
+
Log.w("HotUpdater", "Failed to emit progress (bridge may be unavailable): ${e.message}")
|
|
271
|
+
}
|
|
246
272
|
}
|
|
247
273
|
}
|
|
248
|
-
|
|
249
|
-
promise.resolve(true)
|
|
274
|
+
promise.resolve(result.toWritableMap())
|
|
250
275
|
} catch (e: HotUpdaterException) {
|
|
251
|
-
promise.
|
|
276
|
+
promise.rejectUpdateBundle(e)
|
|
252
277
|
} catch (e: Exception) {
|
|
278
|
+
// Not an update failure (a stale Release selection, for example),
|
|
279
|
+
// so the rejection carries no classification.
|
|
253
280
|
promise.reject("UNKNOWN_ERROR", e.message ?: "An unknown error occurred")
|
|
254
281
|
}
|
|
255
282
|
}
|
|
@@ -414,17 +441,14 @@ class HotUpdaterModule internal constructor(
|
|
|
414
441
|
override fun getInstallId(): String = getInstance().getInstallId()
|
|
415
442
|
|
|
416
443
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
417
|
-
override fun
|
|
418
|
-
|
|
419
|
-
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
420
|
-
override fun getUsername(): String? = getInstance().getUsername()
|
|
444
|
+
override fun getStorageItem(key: String): String? = getInstance().getStorageItem(key)
|
|
421
445
|
|
|
422
446
|
@ReactMethod
|
|
423
|
-
override fun
|
|
424
|
-
|
|
425
|
-
|
|
447
|
+
override fun setStorageItem(
|
|
448
|
+
key: String,
|
|
449
|
+
value: String?,
|
|
426
450
|
) {
|
|
427
|
-
getInstance().
|
|
451
|
+
getInstance().setStorageItem(key, value)
|
|
428
452
|
}
|
|
429
453
|
|
|
430
454
|
@ReactMethod
|
|
@@ -62,13 +62,11 @@ abstract class HotUpdaterSpec internal constructor(
|
|
|
62
62
|
|
|
63
63
|
abstract fun getInstallId(): String
|
|
64
64
|
|
|
65
|
-
abstract fun
|
|
65
|
+
abstract fun getStorageItem(key: String): String?
|
|
66
66
|
|
|
67
|
-
abstract fun
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
userId: String?,
|
|
71
|
-
username: String?,
|
|
67
|
+
abstract fun setStorageItem(
|
|
68
|
+
key: String,
|
|
69
|
+
value: String?,
|
|
72
70
|
)
|
|
73
71
|
|
|
74
72
|
abstract fun resetChannel(promise: Promise)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import Compression
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
enum BrotliFileDecompressor {
|
|
5
|
+
private static let bufferSize = 64 * 1024
|
|
6
|
+
|
|
7
|
+
static func decompress(
|
|
8
|
+
from sourcePath: String,
|
|
9
|
+
to outputPath: String,
|
|
10
|
+
expectedOutputByteSize: Int64? = nil
|
|
11
|
+
) throws {
|
|
12
|
+
if let expectedOutputByteSize, expectedOutputByteSize < 0 {
|
|
13
|
+
throw error(6, "Invalid expected Brotli output size")
|
|
14
|
+
}
|
|
15
|
+
let fileManager = FileManager.default
|
|
16
|
+
let parentDirectory = (outputPath as NSString).deletingLastPathComponent
|
|
17
|
+
try fileManager.createDirectory(
|
|
18
|
+
atPath: parentDirectory,
|
|
19
|
+
withIntermediateDirectories: true
|
|
20
|
+
)
|
|
21
|
+
if fileManager.fileExists(atPath: outputPath) {
|
|
22
|
+
try fileManager.removeItem(atPath: outputPath)
|
|
23
|
+
}
|
|
24
|
+
guard fileManager.createFile(atPath: outputPath, contents: nil) else {
|
|
25
|
+
throw error(1, "Failed to create Brotli output file")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let input = try FileHandle(forReadingFrom: URL(fileURLWithPath: sourcePath))
|
|
29
|
+
let output = try FileHandle(forWritingTo: URL(fileURLWithPath: outputPath))
|
|
30
|
+
defer {
|
|
31
|
+
try? input.close()
|
|
32
|
+
try? output.close()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var stream = compression_stream(
|
|
36
|
+
dst_ptr: UnsafeMutablePointer<UInt8>(bitPattern: 1)!,
|
|
37
|
+
dst_size: 0,
|
|
38
|
+
src_ptr: UnsafePointer<UInt8>(bitPattern: 1)!,
|
|
39
|
+
src_size: 0,
|
|
40
|
+
state: nil
|
|
41
|
+
)
|
|
42
|
+
guard compression_stream_init(
|
|
43
|
+
&stream,
|
|
44
|
+
COMPRESSION_STREAM_DECODE,
|
|
45
|
+
COMPRESSION_BROTLI
|
|
46
|
+
) == COMPRESSION_STATUS_OK else {
|
|
47
|
+
throw error(2, "Failed to initialize Brotli decompression")
|
|
48
|
+
}
|
|
49
|
+
defer { compression_stream_destroy(&stream) }
|
|
50
|
+
|
|
51
|
+
let outputBuffer = UnsafeMutablePointer<UInt8>.allocate(
|
|
52
|
+
capacity: bufferSize
|
|
53
|
+
)
|
|
54
|
+
defer { outputBuffer.deallocate() }
|
|
55
|
+
|
|
56
|
+
var reachedEnd = false
|
|
57
|
+
var totalOutputByteSize: UInt64 = 0
|
|
58
|
+
let maximumOutputByteSize = expectedOutputByteSize.map(UInt64.init)
|
|
59
|
+
while !reachedEnd {
|
|
60
|
+
let chunk = try FileUtilities.readUpToCount(
|
|
61
|
+
from: input,
|
|
62
|
+
count: bufferSize
|
|
63
|
+
) ?? Data()
|
|
64
|
+
let status: compression_status
|
|
65
|
+
if chunk.isEmpty {
|
|
66
|
+
stream.src_ptr = UnsafePointer<UInt8>(bitPattern: 1)!
|
|
67
|
+
stream.src_size = 0
|
|
68
|
+
status = try process(
|
|
69
|
+
&stream,
|
|
70
|
+
output: output,
|
|
71
|
+
outputBuffer: outputBuffer,
|
|
72
|
+
finalize: true,
|
|
73
|
+
totalOutputByteSize: &totalOutputByteSize,
|
|
74
|
+
maximumOutputByteSize: maximumOutputByteSize
|
|
75
|
+
)
|
|
76
|
+
} else {
|
|
77
|
+
status = try chunk.withUnsafeBytes { bytes in
|
|
78
|
+
guard let source = bytes.baseAddress?
|
|
79
|
+
.assumingMemoryBound(to: UInt8.self) else {
|
|
80
|
+
return COMPRESSION_STATUS_OK
|
|
81
|
+
}
|
|
82
|
+
stream.src_ptr = source
|
|
83
|
+
stream.src_size = chunk.count
|
|
84
|
+
return try process(
|
|
85
|
+
&stream,
|
|
86
|
+
output: output,
|
|
87
|
+
outputBuffer: outputBuffer,
|
|
88
|
+
finalize: false,
|
|
89
|
+
totalOutputByteSize: &totalOutputByteSize,
|
|
90
|
+
maximumOutputByteSize: maximumOutputByteSize
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if status == COMPRESSION_STATUS_END {
|
|
96
|
+
guard stream.src_size == 0 else {
|
|
97
|
+
throw error(7, "Brotli stream contains trailing compressed bytes")
|
|
98
|
+
}
|
|
99
|
+
let trailing = try FileUtilities.readUpToCount(from: input, count: 1)
|
|
100
|
+
guard trailing?.isEmpty != false else {
|
|
101
|
+
throw error(7, "Brotli stream contains trailing compressed bytes")
|
|
102
|
+
}
|
|
103
|
+
reachedEnd = true
|
|
104
|
+
} else if chunk.isEmpty {
|
|
105
|
+
throw error(3, "Brotli input ended before the stream completed")
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if let maximumOutputByteSize,
|
|
110
|
+
totalOutputByteSize != maximumOutputByteSize {
|
|
111
|
+
throw error(
|
|
112
|
+
8,
|
|
113
|
+
"Brotli output size mismatch: expected \(maximumOutputByteSize), got \(totalOutputByteSize)"
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private static func process(
|
|
119
|
+
_ stream: inout compression_stream,
|
|
120
|
+
output: FileHandle,
|
|
121
|
+
outputBuffer: UnsafeMutablePointer<UInt8>,
|
|
122
|
+
finalize: Bool,
|
|
123
|
+
totalOutputByteSize: inout UInt64,
|
|
124
|
+
maximumOutputByteSize: UInt64?
|
|
125
|
+
) throws -> compression_status {
|
|
126
|
+
let flags = finalize ? Int32(COMPRESSION_STREAM_FINALIZE.rawValue) : 0
|
|
127
|
+
var status = COMPRESSION_STATUS_OK
|
|
128
|
+
repeat {
|
|
129
|
+
let previousSourceSize = stream.src_size
|
|
130
|
+
stream.dst_ptr = outputBuffer
|
|
131
|
+
stream.dst_size = bufferSize
|
|
132
|
+
status = compression_stream_process(&stream, flags)
|
|
133
|
+
|
|
134
|
+
guard status == COMPRESSION_STATUS_OK ||
|
|
135
|
+
status == COMPRESSION_STATUS_END else {
|
|
136
|
+
throw error(4, "Brotli decompression failed")
|
|
137
|
+
}
|
|
138
|
+
let produced = bufferSize - stream.dst_size
|
|
139
|
+
if produced > 0 {
|
|
140
|
+
let (nextOutputByteSize, overflowed) = totalOutputByteSize
|
|
141
|
+
.addingReportingOverflow(UInt64(produced))
|
|
142
|
+
guard !overflowed,
|
|
143
|
+
maximumOutputByteSize.map({ nextOutputByteSize <= $0 }) ?? true else {
|
|
144
|
+
throw error(6, "Brotli output exceeds expected size")
|
|
145
|
+
}
|
|
146
|
+
output.write(Data(bytes: outputBuffer, count: produced))
|
|
147
|
+
totalOutputByteSize = nextOutputByteSize
|
|
148
|
+
}
|
|
149
|
+
if finalize,
|
|
150
|
+
status == COMPRESSION_STATUS_OK,
|
|
151
|
+
produced == 0,
|
|
152
|
+
previousSourceSize == stream.src_size {
|
|
153
|
+
throw error(5, "Brotli decompression stalled")
|
|
154
|
+
}
|
|
155
|
+
} while stream.src_size > 0 || stream.dst_size == 0 ||
|
|
156
|
+
(finalize && status == COMPRESSION_STATUS_OK)
|
|
157
|
+
return status
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private static func error(_ code: Int, _ message: String) -> NSError {
|
|
161
|
+
NSError(
|
|
162
|
+
domain: "BrotliFileDecompressor",
|
|
163
|
+
code: code,
|
|
164
|
+
userInfo: [NSLocalizedDescriptionKey: message]
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
protocol BuiltInAssetResolver: AnyObject {
|
|
4
|
+
func use(bundle: Bundle)
|
|
5
|
+
func copyIfMatches(assetPath: String, expectedHash: String, destination: String) -> Bool
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
final class IOSBuiltInAssetResolver: BuiltInAssetResolver {
|
|
9
|
+
private struct CachedIndex: Codable {
|
|
10
|
+
let schemaVersion: Int
|
|
11
|
+
let packageIdentity: String
|
|
12
|
+
var locators: [String: String]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private let cacheURL: URL
|
|
16
|
+
private let lock = NSLock()
|
|
17
|
+
private var bundle: Bundle = .main
|
|
18
|
+
|
|
19
|
+
init(cacheURL: URL) {
|
|
20
|
+
self.cacheURL = cacheURL
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func use(bundle: Bundle) {
|
|
24
|
+
lock.lock()
|
|
25
|
+
self.bundle = bundle
|
|
26
|
+
lock.unlock()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func copyIfMatches(
|
|
30
|
+
assetPath: String,
|
|
31
|
+
expectedHash: String,
|
|
32
|
+
destination: String
|
|
33
|
+
) -> Bool {
|
|
34
|
+
lock.lock()
|
|
35
|
+
defer { lock.unlock() }
|
|
36
|
+
|
|
37
|
+
guard FileUtilities.normalizedRelativePath(from: assetPath) == assetPath else {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
let packageIdentity = packageIdentity(for: bundle)
|
|
41
|
+
var index = readIndex(packageIdentity: packageIdentity)
|
|
42
|
+
|
|
43
|
+
if let cachedLocator = index.locators[assetPath],
|
|
44
|
+
copyLocatorIfMatches(
|
|
45
|
+
cachedLocator,
|
|
46
|
+
expectedHash: expectedHash,
|
|
47
|
+
destination: destination
|
|
48
|
+
) {
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
guard let locator = resolveLocator(assetPath: assetPath),
|
|
53
|
+
copyLocatorIfMatches(
|
|
54
|
+
locator,
|
|
55
|
+
expectedHash: expectedHash,
|
|
56
|
+
destination: destination
|
|
57
|
+
) else {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
index.locators[assetPath] = locator
|
|
62
|
+
writeIndex(index)
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private func resolveLocator(assetPath: String) -> String? {
|
|
67
|
+
if assetPath == "index.ios.bundle" {
|
|
68
|
+
guard let bundleURL = bundle.url(forResource: "main", withExtension: "jsbundle"),
|
|
69
|
+
let relativePath = relativePath(of: bundleURL, inside: bundle.bundleURL) else {
|
|
70
|
+
return nil
|
|
71
|
+
}
|
|
72
|
+
return "bundle:\(relativePath)"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
guard let resourceURL = bundle.resourceURL else { return nil }
|
|
76
|
+
let sourceURL = resourceURL.appendingPathComponent(assetPath).standardizedFileURL
|
|
77
|
+
guard relativePath(of: sourceURL, inside: resourceURL) == assetPath,
|
|
78
|
+
isRegularFile(sourceURL) else {
|
|
79
|
+
return nil
|
|
80
|
+
}
|
|
81
|
+
return "resource:\(assetPath)"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private func copyLocatorIfMatches(
|
|
85
|
+
_ locator: String,
|
|
86
|
+
expectedHash: String,
|
|
87
|
+
destination: String
|
|
88
|
+
) -> Bool {
|
|
89
|
+
guard let sourceURL = sourceURL(for: locator), isRegularFile(sourceURL) else {
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
let destinationURL = URL(fileURLWithPath: destination)
|
|
93
|
+
let temporaryURL = destinationURL
|
|
94
|
+
.deletingLastPathComponent()
|
|
95
|
+
.appendingPathComponent("\(destinationURL.lastPathComponent).builtin.tmp")
|
|
96
|
+
do {
|
|
97
|
+
try FileManager.default.createDirectory(
|
|
98
|
+
at: destinationURL.deletingLastPathComponent(),
|
|
99
|
+
withIntermediateDirectories: true
|
|
100
|
+
)
|
|
101
|
+
try? FileManager.default.removeItem(at: temporaryURL)
|
|
102
|
+
try FileManager.default.copyItem(at: sourceURL, to: temporaryURL)
|
|
103
|
+
guard HashUtils.verifyHash(fileURL: temporaryURL, expectedHash: expectedHash) else {
|
|
104
|
+
try? FileManager.default.removeItem(at: temporaryURL)
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
try? FileManager.default.removeItem(at: destinationURL)
|
|
108
|
+
try FileManager.default.moveItem(at: temporaryURL, to: destinationURL)
|
|
109
|
+
return true
|
|
110
|
+
} catch {
|
|
111
|
+
try? FileManager.default.removeItem(at: temporaryURL)
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private func sourceURL(for locator: String) -> URL? {
|
|
117
|
+
if locator.hasPrefix("bundle:") {
|
|
118
|
+
let relativePath = String(locator.dropFirst("bundle:".count))
|
|
119
|
+
let sourceURL = bundle.bundleURL.appendingPathComponent(relativePath).standardizedFileURL
|
|
120
|
+
return self.relativePath(of: sourceURL, inside: bundle.bundleURL) == relativePath
|
|
121
|
+
? sourceURL
|
|
122
|
+
: nil
|
|
123
|
+
}
|
|
124
|
+
if locator.hasPrefix("resource:"), let resourceURL = bundle.resourceURL {
|
|
125
|
+
let relativePath = String(locator.dropFirst("resource:".count))
|
|
126
|
+
let sourceURL = resourceURL.appendingPathComponent(relativePath).standardizedFileURL
|
|
127
|
+
return self.relativePath(of: sourceURL, inside: resourceURL) == relativePath
|
|
128
|
+
? sourceURL
|
|
129
|
+
: nil
|
|
130
|
+
}
|
|
131
|
+
return nil
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private func relativePath(of fileURL: URL, inside directoryURL: URL) -> String? {
|
|
135
|
+
let directory = directoryURL.standardizedFileURL.resolvingSymlinksInPath().path
|
|
136
|
+
let file = fileURL.standardizedFileURL.resolvingSymlinksInPath().path
|
|
137
|
+
let prefix = directory.hasSuffix("/") ? directory : "\(directory)/"
|
|
138
|
+
guard file.hasPrefix(prefix) else { return nil }
|
|
139
|
+
return String(file.dropFirst(prefix.count))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private func isRegularFile(_ url: URL) -> Bool {
|
|
143
|
+
(try? url.resourceValues(forKeys: [.isRegularFileKey]).isRegularFile) == true
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private func packageIdentity(for bundle: Bundle) -> String {
|
|
147
|
+
let values = try? bundle.bundleURL.resourceValues(forKeys: [.contentModificationDateKey, .fileSizeKey])
|
|
148
|
+
let bundleIdentifier = bundle.bundleIdentifier ?? ""
|
|
149
|
+
let shortVersion = bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? ""
|
|
150
|
+
let bundleVersion = bundle.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? ""
|
|
151
|
+
let bundlePath = bundle.bundleURL.standardizedFileURL.path
|
|
152
|
+
let fileSize = String(values?.fileSize ?? -1)
|
|
153
|
+
let modificationTime = String(values?.contentModificationDate?.timeIntervalSince1970 ?? -1)
|
|
154
|
+
|
|
155
|
+
return [
|
|
156
|
+
bundleIdentifier,
|
|
157
|
+
shortVersion,
|
|
158
|
+
bundleVersion,
|
|
159
|
+
bundlePath,
|
|
160
|
+
fileSize,
|
|
161
|
+
modificationTime,
|
|
162
|
+
].joined(separator: "|")
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private func readIndex(packageIdentity: String) -> CachedIndex {
|
|
166
|
+
if let data = try? Data(contentsOf: cacheURL),
|
|
167
|
+
let index = try? JSONDecoder().decode(CachedIndex.self, from: data),
|
|
168
|
+
index.schemaVersion == Self.schemaVersion,
|
|
169
|
+
index.packageIdentity == packageIdentity {
|
|
170
|
+
return index
|
|
171
|
+
}
|
|
172
|
+
return CachedIndex(
|
|
173
|
+
schemaVersion: Self.schemaVersion,
|
|
174
|
+
packageIdentity: packageIdentity,
|
|
175
|
+
locators: [:]
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private func writeIndex(_ index: CachedIndex) {
|
|
180
|
+
do {
|
|
181
|
+
try FileManager.default.createDirectory(
|
|
182
|
+
at: cacheURL.deletingLastPathComponent(),
|
|
183
|
+
withIntermediateDirectories: true
|
|
184
|
+
)
|
|
185
|
+
let temporaryURL = cacheURL.appendingPathExtension("tmp")
|
|
186
|
+
let data = try JSONEncoder().encode(index)
|
|
187
|
+
try data.write(to: temporaryURL, options: .atomic)
|
|
188
|
+
try? FileManager.default.removeItem(at: cacheURL)
|
|
189
|
+
try FileManager.default.moveItem(at: temporaryURL, to: cacheURL)
|
|
190
|
+
} catch {
|
|
191
|
+
// Cache persistence must never block a valid install.
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private static let schemaVersion = 1
|
|
196
|
+
}
|