@onekeyfe/react-native-image 3.0.98

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.
Files changed (186) hide show
  1. package/LICENSE +21 -0
  2. package/OneKeyImage.podspec +32 -0
  3. package/README.md +88 -0
  4. package/android/CMakeLists.txt +12 -0
  5. package/android/build.gradle +85 -0
  6. package/android/consumer-rules.pro +10 -0
  7. package/android/gradle.properties +5 -0
  8. package/android/src/main/AndroidManifest.xml +1 -0
  9. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  10. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImage.kt +616 -0
  11. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageCache.kt +131 -0
  12. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageDecodeDimensions.kt +169 -0
  13. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageGlideRegistry.kt +285 -0
  14. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageModel.kt +68 -0
  15. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImagePackage.kt +28 -0
  16. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyImageSafety.kt +1120 -0
  17. package/android/src/main/java/com/margelo/nitro/onekeyimage/OneKeyTosUrl.kt +93 -0
  18. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyAnimationTimelineInspectorTest.kt +210 -0
  19. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageCacheTypeTest.kt +16 -0
  20. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageDecodeDimensionsTest.kt +70 -0
  21. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageModelTest.kt +65 -0
  22. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageRequestSignatureTest.kt +33 -0
  23. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyImageSafetyTest.kt +346 -0
  24. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyResourceCleanupTest.kt +43 -0
  25. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyTerminalEventPairTest.kt +38 -0
  26. package/android/src/test/java/com/margelo/nitro/onekeyimage/OneKeyTosUrlTest.kt +82 -0
  27. package/ios/OneKeyImage.swift +636 -0
  28. package/ios/OneKeyImageCache.swift +215 -0
  29. package/ios/OneKeyImageRequestContext.swift +781 -0
  30. package/ios/OneKeyTosURL.swift +69 -0
  31. package/ios/tests/OneKeyImageInfrastructureTests.swift +555 -0
  32. package/ios/tests/OneKeyTosURLTests.swift +142 -0
  33. package/lib/module/OneKeyImage.nitro.js +30 -0
  34. package/lib/module/index.js +223 -0
  35. package/lib/module/package.json +1 -0
  36. package/lib/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  37. package/lib/nitrogen/generated/android/c++/JFunc_void_double_double_OneKeyImageCacheType.hpp +77 -0
  38. package/lib/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  39. package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.cpp +130 -0
  40. package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.hpp +66 -0
  41. package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.cpp +255 -0
  42. package/lib/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.hpp +93 -0
  43. package/lib/nitrogen/generated/android/c++/JOneKeyImageCachePolicy.hpp +64 -0
  44. package/lib/nitrogen/generated/android/c++/JOneKeyImageCacheType.hpp +61 -0
  45. package/lib/nitrogen/generated/android/c++/JOneKeyImageContentFit.hpp +64 -0
  46. package/lib/nitrogen/generated/android/c++/JOneKeyImageLoadingStrategy.hpp +61 -0
  47. package/lib/nitrogen/generated/android/c++/JOneKeyImagePreloadSource.hpp +88 -0
  48. package/lib/nitrogen/generated/android/c++/JOneKeyImageVariant.hpp +64 -0
  49. package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.cpp +144 -0
  50. package/lib/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.hpp +54 -0
  51. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void.kt +78 -0
  52. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_double_double_OneKeyImageCacheType.kt +78 -0
  53. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_std__string.kt +78 -0
  54. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageCacheSpec.kt +69 -0
  55. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageSpec.kt +189 -0
  56. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCachePolicy.kt +25 -0
  57. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCacheType.kt +24 -0
  58. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageContentFit.kt +25 -0
  59. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageLoadingStrategy.kt +24 -0
  60. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImagePreloadSource.kt +86 -0
  61. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageVariant.kt +25 -0
  62. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/onekeyimageOnLoad.kt +35 -0
  63. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageManager.kt +96 -0
  64. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageStateUpdater.kt +23 -0
  65. package/lib/nitrogen/generated/android/onekeyimage+autolinking.cmake +85 -0
  66. package/lib/nitrogen/generated/android/onekeyimage+autolinking.gradle +27 -0
  67. package/lib/nitrogen/generated/android/onekeyimageOnLoad.cpp +78 -0
  68. package/lib/nitrogen/generated/android/onekeyimageOnLoad.hpp +34 -0
  69. package/lib/nitrogen/generated/ios/OneKeyImage+autolinking.rb +62 -0
  70. package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.cpp +90 -0
  71. package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.hpp +405 -0
  72. package/lib/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Umbrella.hpp +71 -0
  73. package/lib/nitrogen/generated/ios/OneKeyImageAutolinking.mm +41 -0
  74. package/lib/nitrogen/generated/ios/OneKeyImageAutolinking.swift +38 -0
  75. package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.cpp +11 -0
  76. package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.hpp +114 -0
  77. package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.cpp +11 -0
  78. package/lib/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.hpp +206 -0
  79. package/lib/nitrogen/generated/ios/c++/views/HybridOneKeyImageComponent.mm +228 -0
  80. package/lib/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  81. package/lib/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  82. package/lib/nitrogen/generated/ios/swift/Func_void_double_double_OneKeyImageCacheType.swift +46 -0
  83. package/lib/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  84. package/lib/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  85. package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec.swift +58 -0
  86. package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec_cxx.swift +202 -0
  87. package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageSpec.swift +70 -0
  88. package/lib/nitrogen/generated/ios/swift/HybridOneKeyImageSpec_cxx.swift +539 -0
  89. package/lib/nitrogen/generated/ios/swift/OneKeyImageCachePolicy.swift +48 -0
  90. package/lib/nitrogen/generated/ios/swift/OneKeyImageCacheType.swift +44 -0
  91. package/lib/nitrogen/generated/ios/swift/OneKeyImageContentFit.swift +48 -0
  92. package/lib/nitrogen/generated/ios/swift/OneKeyImageLoadingStrategy.swift +44 -0
  93. package/lib/nitrogen/generated/ios/swift/OneKeyImagePreloadSource.swift +148 -0
  94. package/lib/nitrogen/generated/ios/swift/OneKeyImageVariant.swift +48 -0
  95. package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.cpp +24 -0
  96. package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.hpp +68 -0
  97. package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.cpp +52 -0
  98. package/lib/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.hpp +108 -0
  99. package/lib/nitrogen/generated/shared/c++/OneKeyImageCachePolicy.hpp +84 -0
  100. package/lib/nitrogen/generated/shared/c++/OneKeyImageCacheType.hpp +80 -0
  101. package/lib/nitrogen/generated/shared/c++/OneKeyImageContentFit.hpp +84 -0
  102. package/lib/nitrogen/generated/shared/c++/OneKeyImageLoadingStrategy.hpp +80 -0
  103. package/lib/nitrogen/generated/shared/c++/OneKeyImagePreloadSource.hpp +114 -0
  104. package/lib/nitrogen/generated/shared/c++/OneKeyImageVariant.hpp +84 -0
  105. package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.cpp +62 -0
  106. package/lib/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.hpp +132 -0
  107. package/lib/nitrogen/generated/shared/json/OneKeyImageConfig.json +24 -0
  108. package/lib/typescript/package.json +1 -0
  109. package/lib/typescript/src/OneKeyImage.nitro.d.ts +67 -0
  110. package/lib/typescript/src/index.d.ts +55 -0
  111. package/nitro.json +31 -0
  112. package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  113. package/nitrogen/generated/android/c++/JFunc_void_double_double_OneKeyImageCacheType.hpp +77 -0
  114. package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  115. package/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.cpp +130 -0
  116. package/nitrogen/generated/android/c++/JHybridOneKeyImageCacheSpec.hpp +66 -0
  117. package/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.cpp +255 -0
  118. package/nitrogen/generated/android/c++/JHybridOneKeyImageSpec.hpp +93 -0
  119. package/nitrogen/generated/android/c++/JOneKeyImageCachePolicy.hpp +64 -0
  120. package/nitrogen/generated/android/c++/JOneKeyImageCacheType.hpp +61 -0
  121. package/nitrogen/generated/android/c++/JOneKeyImageContentFit.hpp +64 -0
  122. package/nitrogen/generated/android/c++/JOneKeyImageLoadingStrategy.hpp +61 -0
  123. package/nitrogen/generated/android/c++/JOneKeyImagePreloadSource.hpp +88 -0
  124. package/nitrogen/generated/android/c++/JOneKeyImageVariant.hpp +64 -0
  125. package/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.cpp +144 -0
  126. package/nitrogen/generated/android/c++/views/JHybridOneKeyImageStateUpdater.hpp +54 -0
  127. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void.kt +78 -0
  128. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_double_double_OneKeyImageCacheType.kt +78 -0
  129. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/Func_void_std__string.kt +78 -0
  130. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageCacheSpec.kt +69 -0
  131. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/HybridOneKeyImageSpec.kt +189 -0
  132. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCachePolicy.kt +25 -0
  133. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageCacheType.kt +24 -0
  134. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageContentFit.kt +25 -0
  135. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageLoadingStrategy.kt +24 -0
  136. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImagePreloadSource.kt +86 -0
  137. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/OneKeyImageVariant.kt +25 -0
  138. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/onekeyimageOnLoad.kt +35 -0
  139. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageManager.kt +96 -0
  140. package/nitrogen/generated/android/kotlin/com/margelo/nitro/onekeyimage/views/HybridOneKeyImageStateUpdater.kt +23 -0
  141. package/nitrogen/generated/android/onekeyimage+autolinking.cmake +85 -0
  142. package/nitrogen/generated/android/onekeyimage+autolinking.gradle +27 -0
  143. package/nitrogen/generated/android/onekeyimageOnLoad.cpp +78 -0
  144. package/nitrogen/generated/android/onekeyimageOnLoad.hpp +34 -0
  145. package/nitrogen/generated/ios/OneKeyImage+autolinking.rb +62 -0
  146. package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.cpp +90 -0
  147. package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Bridge.hpp +405 -0
  148. package/nitrogen/generated/ios/OneKeyImage-Swift-Cxx-Umbrella.hpp +71 -0
  149. package/nitrogen/generated/ios/OneKeyImageAutolinking.mm +41 -0
  150. package/nitrogen/generated/ios/OneKeyImageAutolinking.swift +38 -0
  151. package/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.cpp +11 -0
  152. package/nitrogen/generated/ios/c++/HybridOneKeyImageCacheSpecSwift.hpp +114 -0
  153. package/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.cpp +11 -0
  154. package/nitrogen/generated/ios/c++/HybridOneKeyImageSpecSwift.hpp +206 -0
  155. package/nitrogen/generated/ios/c++/views/HybridOneKeyImageComponent.mm +228 -0
  156. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  157. package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  158. package/nitrogen/generated/ios/swift/Func_void_double_double_OneKeyImageCacheType.swift +46 -0
  159. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  160. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  161. package/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec.swift +58 -0
  162. package/nitrogen/generated/ios/swift/HybridOneKeyImageCacheSpec_cxx.swift +202 -0
  163. package/nitrogen/generated/ios/swift/HybridOneKeyImageSpec.swift +70 -0
  164. package/nitrogen/generated/ios/swift/HybridOneKeyImageSpec_cxx.swift +539 -0
  165. package/nitrogen/generated/ios/swift/OneKeyImageCachePolicy.swift +48 -0
  166. package/nitrogen/generated/ios/swift/OneKeyImageCacheType.swift +44 -0
  167. package/nitrogen/generated/ios/swift/OneKeyImageContentFit.swift +48 -0
  168. package/nitrogen/generated/ios/swift/OneKeyImageLoadingStrategy.swift +44 -0
  169. package/nitrogen/generated/ios/swift/OneKeyImagePreloadSource.swift +148 -0
  170. package/nitrogen/generated/ios/swift/OneKeyImageVariant.swift +48 -0
  171. package/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.cpp +24 -0
  172. package/nitrogen/generated/shared/c++/HybridOneKeyImageCacheSpec.hpp +68 -0
  173. package/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.cpp +52 -0
  174. package/nitrogen/generated/shared/c++/HybridOneKeyImageSpec.hpp +108 -0
  175. package/nitrogen/generated/shared/c++/OneKeyImageCachePolicy.hpp +84 -0
  176. package/nitrogen/generated/shared/c++/OneKeyImageCacheType.hpp +80 -0
  177. package/nitrogen/generated/shared/c++/OneKeyImageContentFit.hpp +84 -0
  178. package/nitrogen/generated/shared/c++/OneKeyImageLoadingStrategy.hpp +80 -0
  179. package/nitrogen/generated/shared/c++/OneKeyImagePreloadSource.hpp +114 -0
  180. package/nitrogen/generated/shared/c++/OneKeyImageVariant.hpp +84 -0
  181. package/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.cpp +62 -0
  182. package/nitrogen/generated/shared/c++/views/HybridOneKeyImageComponent.hpp +132 -0
  183. package/nitrogen/generated/shared/json/OneKeyImageConfig.json +24 -0
  184. package/package.json +128 -0
  185. package/src/OneKeyImage.nitro.ts +86 -0
  186. package/src/index.tsx +376 -0
@@ -0,0 +1,69 @@
1
+ import Foundation
2
+
3
+ enum OneKeyTosURL {
4
+ static let widthBuckets = [
5
+ 32, 40, 48, 64, 96, 128, 160, 200, 256, 320, 480, 640, 960, 1280,
6
+ ]
7
+
8
+ static func optimized(
9
+ rawURL: URL,
10
+ displaySize: CGFloat,
11
+ scale: CGFloat,
12
+ overscan: Double,
13
+ hasCustomIdentity: Bool
14
+ ) -> URL {
15
+ guard !hasCustomIdentity,
16
+ displaySize.isFinite,
17
+ displaySize > 0,
18
+ let host = rawURL.host?.lowercased(),
19
+ isAllowedHost(host),
20
+ !isUnsupportedPath(rawURL.path),
21
+ var components = URLComponents(url: rawURL, resolvingAgainstBaseURL: false),
22
+ !hasProtectedQuery(components.queryItems ?? [])
23
+ else {
24
+ return rawURL
25
+ }
26
+
27
+ let normalizedScale = scale.isFinite ? min(max(scale, 1), 3) : 1
28
+ let normalizedOverscan = overscan.isFinite ? max(overscan, 1) : 1
29
+ let requestedPixels = ceil(displaySize * normalizedScale * normalizedOverscan)
30
+ let bucket: Int
31
+ if !requestedPixels.isFinite || requestedPixels >= CGFloat(widthBuckets.last!) {
32
+ bucket = widthBuckets.last!
33
+ } else {
34
+ let requested = Int(requestedPixels)
35
+ bucket = widthBuckets.first(where: { $0 >= requested }) ?? widthBuckets.last!
36
+ }
37
+ var queryItems = components.queryItems ?? []
38
+ queryItems.append(URLQueryItem(name: "x-tos-process", value: "image/resize,w_\(bucket)"))
39
+ components.queryItems = queryItems
40
+ return components.url ?? rawURL
41
+ }
42
+
43
+ private static func isAllowedHost(_ host: String) -> Bool {
44
+ [
45
+ "app-assets.onekey.so",
46
+ "common.onekey-asset.com",
47
+ "asset.onekey-asset.com",
48
+ "uni.onekey-asset.com",
49
+ "uni-test.onekey-asset.com",
50
+ ].contains(host)
51
+ }
52
+
53
+ private static func isUnsupportedPath(_ path: String) -> Bool {
54
+ let ext = URL(fileURLWithPath: path).pathExtension.lowercased()
55
+ return ["svg", "mp4", "webm", "m4v", "mov", "avi"].contains(ext)
56
+ }
57
+
58
+ private static func hasProtectedQuery(_ items: [URLQueryItem]) -> Bool {
59
+ let protectedNames = [
60
+ "expires", "policy", "signature", "token", "auth_key", "accesskeyid",
61
+ "ossaccesskeyid", "security-token",
62
+ ]
63
+ return items.contains { item in
64
+ let name = item.name.lowercased()
65
+ return ["x-amz-", "x-oss-", "x-tos-"].contains(where: name.hasPrefix)
66
+ || protectedNames.contains(name)
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,555 @@
1
+ import SDWebImage
2
+ import SDWebImageSVGCoder
3
+ import SDWebImageWebPCoder
4
+ import XCTest
5
+
6
+ @testable import OneKeyImage
7
+
8
+ final class OneKeyImageInfrastructureTests: XCTestCase {
9
+ func testRenderAndPreloadDoNotUseSharedCookieStorage() {
10
+ XCTAssertTrue(OneKeyImageRequestContext.renderOptions.contains(.retryFailed))
11
+ XCTAssertFalse(OneKeyImageRequestContext.renderOptions.contains(.handleCookies))
12
+ XCTAssertFalse(OneKeyImageRequestContext.renderOptions.contains(.lowPriority))
13
+ XCTAssertTrue(OneKeyImageRequestContext.preloadOptions.contains(.retryFailed))
14
+ XCTAssertTrue(OneKeyImageRequestContext.preloadOptions.contains(.lowPriority))
15
+ XCTAssertFalse(OneKeyImageRequestContext.preloadOptions.contains(.handleCookies))
16
+ }
17
+
18
+ func testExplicitCookieHeadersHaveDistinctPrivateIdentities() throws {
19
+ let url = try XCTUnwrap(URL(string: "https://example.com/private.png"))
20
+ let first = OneKeyImageHeadersIdentity(headers: ["Cookie": "session=first"])
21
+ let second = OneKeyImageHeadersIdentity(headers: ["cookie": "session=second"])
22
+ let firstKey = OneKeyImageRequestContext.filteredCacheKey(
23
+ for: url,
24
+ headersIdentity: first
25
+ )
26
+ let secondKey = OneKeyImageRequestContext.filteredCacheKey(
27
+ for: url,
28
+ headersIdentity: second
29
+ )
30
+
31
+ XCTAssertNotEqual(first, second)
32
+ XCTAssertNotEqual(firstKey, secondKey)
33
+ XCTAssertFalse(firstKey.contains("session=first"))
34
+ XCTAssertFalse(secondKey.contains("session=second"))
35
+ }
36
+
37
+ func testOneKeyUsesDedicatedManagerAndDownloader() {
38
+ XCTAssertFalse(OneKeyImagePipeline.manager === SDWebImageManager.shared)
39
+ XCTAssertFalse(OneKeyImagePipeline.downloader === SDWebImageDownloader.shared)
40
+ XCTAssertTrue(
41
+ (OneKeyImagePipeline.manager.imageCache as AnyObject) === SDImageCache.shared
42
+ )
43
+ XCTAssertTrue(
44
+ (OneKeyImagePipeline.manager.imageLoader as AnyObject) === OneKeyImagePipeline.downloader
45
+ )
46
+
47
+ let customManager =
48
+ OneKeyImageRequestContext.baseContext[SDWebImageContextOption.customManager]
49
+ as? SDWebImageManager
50
+ XCTAssertTrue(
51
+ customManager === OneKeyImagePipeline.manager
52
+ )
53
+ }
54
+
55
+ func testURLAndHeaderIdentityIsolateCachedResponses() throws {
56
+ let url = try XCTUnwrap(URL(string: "https://example.com/private.png"))
57
+ let firstHeaders = ["Authorization": "Bearer first", "X-Tenant": "one"]
58
+ let equivalentHeaders = ["x-tenant": "one", "authorization": "Bearer first"]
59
+ let otherHeaders = ["Authorization": "Bearer second", "X-Tenant": "one"]
60
+ let firstIdentity = OneKeyImageHeadersIdentity(headers: firstHeaders)
61
+ let equivalentIdentity = OneKeyImageHeadersIdentity(headers: equivalentHeaders)
62
+ let otherIdentity = OneKeyImageHeadersIdentity(headers: otherHeaders)
63
+
64
+ XCTAssertEqual(firstIdentity, equivalentIdentity)
65
+ XCTAssertNotEqual(firstIdentity, otherIdentity)
66
+ XCTAssertEqual(firstIdentity.cacheKeyComponent, equivalentIdentity.cacheKeyComponent)
67
+ XCTAssertNotEqual(firstIdentity.cacheKeyComponent, otherIdentity.cacheKeyComponent)
68
+ XCTAssertEqual(firstIdentity.cacheKeyComponent?.count, 64)
69
+ XCTAssertFalse(firstIdentity.cacheKeyComponent?.contains("Bearer") ?? true)
70
+
71
+ let firstKey = OneKeyImageRequestContext.filteredCacheKey(
72
+ for: url,
73
+ headersIdentity: firstIdentity
74
+ )
75
+ let equivalentKey = OneKeyImageRequestContext.filteredCacheKey(
76
+ for: url,
77
+ headersIdentity: equivalentIdentity
78
+ )
79
+ let otherKey = OneKeyImageRequestContext.filteredCacheKey(
80
+ for: url,
81
+ headersIdentity: otherIdentity
82
+ )
83
+ XCTAssertEqual(firstKey, equivalentKey)
84
+ XCTAssertNotEqual(firstKey, otherKey)
85
+ XCTAssertFalse(firstKey.contains("Bearer first"))
86
+ XCTAssertFalse(otherKey.contains("Bearer second"))
87
+ }
88
+
89
+ func testHeaderManagerPoolReusesIsolatesAndReclaimsManagers() {
90
+ let pool = OneKeyImageManagerPool()
91
+ let emptyIdentity = OneKeyImageHeadersIdentity(headers: nil)
92
+ let firstIdentity = OneKeyImageHeadersIdentity(
93
+ headers: ["Authorization": "Bearer first"]
94
+ )
95
+ let equivalentIdentity = OneKeyImageHeadersIdentity(
96
+ headers: ["authorization": "Bearer first"]
97
+ )
98
+ let otherIdentity = OneKeyImageHeadersIdentity(
99
+ headers: ["Authorization": "Bearer second"]
100
+ )
101
+
102
+ let empty = pool.acquire(identity: emptyIdentity)
103
+ let first = pool.acquire(identity: firstIdentity)
104
+ let equivalent = pool.acquire(identity: equivalentIdentity)
105
+ let other = pool.acquire(identity: otherIdentity)
106
+
107
+ XCTAssertTrue(empty.manager === OneKeyImagePipeline.manager)
108
+ XCTAssertTrue(first.manager === equivalent.manager)
109
+ XCTAssertFalse(first.manager === other.manager)
110
+ XCTAssertFalse(first.manager === OneKeyImagePipeline.manager)
111
+ XCTAssertFalse(
112
+ (first.manager.imageLoader as AnyObject) === (other.manager.imageLoader as AnyObject)
113
+ )
114
+ XCTAssertTrue((first.manager.imageCache as AnyObject) === SDImageCache.shared)
115
+ XCTAssertTrue((other.manager.imageCache as AnyObject) === SDImageCache.shared)
116
+ XCTAssertEqual(pool.activeManagerCount, 2)
117
+
118
+ first.finish()
119
+ first.finish()
120
+ XCTAssertEqual(pool.activeManagerCount, 2)
121
+ equivalent.finish()
122
+ XCTAssertEqual(pool.activeManagerCount, 1)
123
+ other.finish()
124
+ XCTAssertEqual(pool.activeManagerCount, 0)
125
+ empty.finish()
126
+
127
+ let next = pool.acquire(identity: firstIdentity)
128
+ XCTAssertFalse(next.manager === first.manager)
129
+ XCTAssertEqual(pool.activeManagerCount, 1)
130
+ next.finish()
131
+ XCTAssertEqual(pool.activeManagerCount, 0)
132
+ }
133
+
134
+ func testSafetyFlightsAreSeparatedByHeaderIdentity() throws {
135
+ let pool = OneKeyImageManagerPool()
136
+ let coordinator = OneKeyImageSafetyFlightCoordinator(managerPool: pool)
137
+ let url = try XCTUnwrap(URL(string: "https://example.com/private.png"))
138
+ let first = coordinator.acquire(
139
+ url: url,
140
+ headers: ["Authorization": "Bearer first"]
141
+ )
142
+ let equivalent = coordinator.acquire(
143
+ url: url,
144
+ headers: ["authorization": "Bearer first"]
145
+ )
146
+ let other = coordinator.acquire(
147
+ url: url,
148
+ headers: ["Authorization": "Bearer second"]
149
+ )
150
+
151
+ XCTAssertTrue(first.tracker === equivalent.tracker)
152
+ XCTAssertTrue(first.manager === equivalent.manager)
153
+ XCTAssertFalse(first.tracker === other.tracker)
154
+ XCTAssertFalse(first.manager === other.manager)
155
+ XCTAssertEqual(coordinator.activeFlightCount, 2)
156
+ XCTAssertEqual(pool.activeManagerCount, 2)
157
+
158
+ first.finish()
159
+ equivalent.finish()
160
+ other.finish()
161
+ XCTAssertEqual(coordinator.activeFlightCount, 0)
162
+ XCTAssertEqual(pool.activeManagerCount, 0)
163
+ }
164
+
165
+ func testConcurrentURLFlightSharesViolationAndReleasesIdempotently() throws {
166
+ let coordinator = OneKeyImageSafetyFlightCoordinator()
167
+ let url = try XCTUnwrap(URL(string: "https://example.com/shared.png"))
168
+ let otherURL = try XCTUnwrap(URL(string: "https://example.com/other.png"))
169
+ let first = coordinator.acquire(url: url)
170
+ let second = coordinator.acquire(url: url)
171
+ let other = coordinator.acquire(url: otherURL)
172
+
173
+ XCTAssertTrue(first.tracker === second.tracker)
174
+ XCTAssertFalse(first.tracker === other.tracker)
175
+ XCTAssertEqual(coordinator.activeFlightCount, 2)
176
+ first.tracker.record(.encodedDataTooLarge)
177
+ XCTAssertEqual(second.tracker.violation, .encodedDataTooLarge)
178
+
179
+ first.finish()
180
+ first.finish()
181
+ XCTAssertEqual(coordinator.activeFlightCount, 2)
182
+ second.finish()
183
+ XCTAssertEqual(coordinator.activeFlightCount, 1)
184
+ other.finish()
185
+ XCTAssertEqual(coordinator.activeFlightCount, 0)
186
+
187
+ let next = coordinator.acquire(url: url)
188
+ XCTAssertFalse(next.tracker === first.tracker)
189
+ XCTAssertNil(next.tracker.violation)
190
+ next.finish()
191
+ }
192
+
193
+ func testStaleRequestGenerationCannotContinue() {
194
+ XCTAssertTrue(HybridOneKeyImage.isCurrentRequestGeneration(4, current: 4))
195
+ XCTAssertFalse(HybridOneKeyImage.isCurrentRequestGeneration(4, current: 5))
196
+ XCTAssertTrue(
197
+ HybridOneKeyImage.canFinishTerminal(4, current: 4, terminalGeneration: nil)
198
+ )
199
+ XCTAssertFalse(
200
+ HybridOneKeyImage.canFinishTerminal(4, current: 5, terminalGeneration: nil)
201
+ )
202
+ XCTAssertFalse(
203
+ HybridOneKeyImage.canFinishTerminal(4, current: 4, terminalGeneration: 4)
204
+ )
205
+
206
+ var events: [String] = []
207
+ HybridOneKeyImage.deliverTerminalCallbacks(
208
+ primary: { events.append("primary-reentered") },
209
+ onLoadEnd: { events.append("end") }
210
+ )
211
+ XCTAssertEqual(events, ["primary-reentered", "end"])
212
+ }
213
+
214
+ func testEveryCachePolicyMapsToExpectedSDCacheType() {
215
+ let cases: [(OneKeyImageCachePolicy, SDImageCacheType)] = [
216
+ (.memoryDisk, .all),
217
+ (.memory, .memory),
218
+ (.disk, .disk),
219
+ (.none, .none),
220
+ ]
221
+
222
+ for (policy, expected) in cases {
223
+ XCTAssertEqual(
224
+ OneKeyImageRequestContext.cacheType(for: policy),
225
+ expected,
226
+ policy.stringValue
227
+ )
228
+ }
229
+ }
230
+
231
+ func testThumbnailUsesSafetyGenerationAndSDWebImageDerivedKey() throws {
232
+ let size = CGSize(width: 120, height: 192)
233
+ let url = try XCTUnwrap(URL(string: "https://example.com/image.png"))
234
+ XCTAssertEqual(
235
+ OneKeyImageRequestContext.filteredCacheKey(for: url),
236
+ "https://example.com/image.png|onekey-image-safety-v2"
237
+ )
238
+ let thumbnailOptions = OneKeyImageRequestContext.thumbnailOptions(pixelSize: size)
239
+ XCTAssertEqual(
240
+ (thumbnailOptions[.imageThumbnailPixelSize] as? NSValue)?.cgSizeValue,
241
+ size
242
+ )
243
+ XCTAssertEqual(thumbnailOptions[.imagePreserveAspectRatio] as? Bool, true)
244
+ }
245
+
246
+ func testDecoderSetExplicitlyIncludesSVGAndWebPWithoutAVIF() {
247
+ OneKeyImageCoderRegistry.ensureWebPRegistered()
248
+ let names = (OneKeyImageCoderRegistry.coder.coders ?? []).map {
249
+ String(describing: type(of: $0))
250
+ }
251
+ XCTAssertTrue(names.contains("SDImageWebPCoder"))
252
+ XCTAssertTrue(names.contains("SDImageSVGCoder"))
253
+ XCTAssertFalse(names.contains { $0.localizedCaseInsensitiveContains("AVIF") })
254
+ XCTAssertTrue(
255
+ (OneKeyImageCoderRegistry.coder.coders ?? []).contains {
256
+ ($0 as AnyObject) === SDImageSVGCoder.shared
257
+ })
258
+ XCTAssertTrue(
259
+ (SDImageCodersManager.shared.coders ?? []).contains {
260
+ ($0 as AnyObject) === SDImageWebPCoder.shared
261
+ })
262
+ }
263
+
264
+ func testNonCoverDecodeUsesRectangularTarget() throws {
265
+ let size = try XCTUnwrap(
266
+ OneKeyImageDecodeSizing.thumbnailPixelSize(
267
+ viewSize: CGSize(width: 40, height: 64),
268
+ scale: 3,
269
+ contentFit: .contain
270
+ )
271
+ )
272
+ XCTAssertEqual(size, CGSize(width: 120, height: 192))
273
+ }
274
+
275
+ func testCoverAndPreloadUseLongestEdge() throws {
276
+ let logicalSize = try XCTUnwrap(
277
+ OneKeyImageDecodeSizing.logicalViewSize(width: 40, height: 64)
278
+ )
279
+ let size = try XCTUnwrap(
280
+ OneKeyImageDecodeSizing.thumbnailPixelSize(
281
+ viewSize: logicalSize,
282
+ scale: 3,
283
+ contentFit: .cover
284
+ )
285
+ )
286
+ XCTAssertEqual(size, CGSize(width: 192, height: 192))
287
+ }
288
+
289
+ func testDecodeTargetNeverExceedsSixteenMegabytes() throws {
290
+ let size = try XCTUnwrap(
291
+ OneKeyImageDecodeSizing.thumbnailPixelSize(
292
+ viewSize: CGSize(width: 5_000, height: 4_000),
293
+ scale: 3,
294
+ contentFit: .contain
295
+ )
296
+ )
297
+ XCTAssertLessThanOrEqual(
298
+ size.width * size.height * CGFloat(OneKeyImageDecodeSizing.bytesPerPixel),
299
+ CGFloat(OneKeyImageDecodeSizing.maximumDecodedBytes)
300
+ )
301
+ XCTAssertEqual(size.width / size.height, 1.25, accuracy: 0.001)
302
+ }
303
+
304
+ func testInvalidOrMissingPreloadDimensionsAreNormalizedConservatively() {
305
+ XCTAssertNil(OneKeyImageDecodeSizing.logicalViewSize(width: 0, height: nil))
306
+ XCTAssertEqual(
307
+ OneKeyImageDecodeSizing.logicalViewSize(width: nil, height: 64),
308
+ CGSize(width: 64, height: 64)
309
+ )
310
+ }
311
+
312
+ func testNoSizePreloadUsesCappedDecodeTarget() {
313
+ XCTAssertEqual(HybridOneKeyImageCache.maximumConcurrentPreloads, 4)
314
+ XCTAssertEqual(
315
+ OneKeyImageDecodeSizing.defaultPreloadPixelSize,
316
+ CGSize(width: 2_048, height: 2_048)
317
+ )
318
+ XCTAssertEqual(
319
+ OneKeyImageDecodeSizing.defaultPreloadPixelSize.width
320
+ * OneKeyImageDecodeSizing.defaultPreloadPixelSize.height
321
+ * CGFloat(OneKeyImageDecodeSizing.bytesPerPixel),
322
+ CGFloat(OneKeyImageDecodeSizing.maximumDecodedBytes)
323
+ )
324
+ }
325
+
326
+ func testEncodedAndMetadataSafetyLimits() {
327
+ XCTAssertEqual(
328
+ OneKeyImageSafetyPolicy.violation(
329
+ encodedByteCount: OneKeyImageSafetyPolicy.maximumEncodedBytes + 1,
330
+ metadata: nil
331
+ ),
332
+ .encodedDataTooLarge
333
+ )
334
+ XCTAssertEqual(
335
+ OneKeyImageSafetyPolicy.violation(
336
+ encodedByteCount: 1,
337
+ metadata: OneKeyImageMetadata(
338
+ width: OneKeyImageSafetyPolicy.maximumStaticSide + 1,
339
+ height: 1,
340
+ frameCount: 1,
341
+ duration: nil
342
+ )
343
+ ),
344
+ .staticDimensionsTooLarge
345
+ )
346
+ XCTAssertEqual(
347
+ OneKeyImageSafetyPolicy.violation(
348
+ encodedByteCount: OneKeyImageSafetyPolicy.maximumAnimatedEncodedBytes + 1,
349
+ metadata: OneKeyImageMetadata(
350
+ width: 1,
351
+ height: 1,
352
+ frameCount: 2,
353
+ duration: 1
354
+ )
355
+ ),
356
+ .animatedEncodedDataTooLarge
357
+ )
358
+ XCTAssertEqual(
359
+ OneKeyImageSafetyPolicy.violation(
360
+ encodedByteCount: 1,
361
+ metadata: OneKeyImageMetadata(
362
+ width: OneKeyImageSafetyPolicy.maximumAnimatedSide + 1,
363
+ height: 1,
364
+ frameCount: 2,
365
+ duration: 1
366
+ )
367
+ ),
368
+ .animatedDimensionsTooLarge
369
+ )
370
+ XCTAssertEqual(
371
+ OneKeyImageSafetyPolicy.violation(
372
+ encodedByteCount: 1,
373
+ metadata: OneKeyImageMetadata(
374
+ width: 1,
375
+ height: 1,
376
+ frameCount: OneKeyImageSafetyPolicy.maximumAnimatedFrameCount + 1,
377
+ duration: nil
378
+ )
379
+ ),
380
+ .animatedFrameCountTooLarge
381
+ )
382
+ XCTAssertEqual(
383
+ OneKeyImageSafetyPolicy.violation(
384
+ encodedByteCount: 1,
385
+ metadata: OneKeyImageMetadata(
386
+ width: 1,
387
+ height: 1,
388
+ frameCount: 2,
389
+ duration: OneKeyImageSafetyPolicy.maximumAnimatedDuration + 0.001
390
+ )
391
+ ),
392
+ .animatedDurationTooLong
393
+ )
394
+ XCTAssertEqual(
395
+ OneKeyImageSafetyPolicy.violation(
396
+ encodedByteCount: 1,
397
+ metadata: OneKeyImageMetadata(
398
+ width: 1,
399
+ height: 1,
400
+ frameCount: 2,
401
+ duration: .nan
402
+ )
403
+ ),
404
+ .animatedDurationTooLong
405
+ )
406
+ XCTAssertEqual(
407
+ OneKeyImageSafetyPolicy.violation(
408
+ encodedByteCount: 1,
409
+ metadata: OneKeyImageMetadata(
410
+ width: 1,
411
+ height: 1,
412
+ frameCount: 2,
413
+ duration: .infinity
414
+ )
415
+ ),
416
+ .animatedDurationTooLong
417
+ )
418
+ }
419
+
420
+ func testAnimatedLimitsDoNotRequireDimensions() {
421
+ let dimensionlessMetadata = { (frameCount: Int, duration: TimeInterval?) in
422
+ OneKeyImageMetadata(
423
+ width: nil,
424
+ height: nil,
425
+ frameCount: frameCount,
426
+ duration: duration
427
+ )
428
+ }
429
+
430
+ XCTAssertEqual(
431
+ OneKeyImageSafetyPolicy.violation(
432
+ encodedByteCount: OneKeyImageSafetyPolicy.maximumAnimatedEncodedBytes + 1,
433
+ metadata: dimensionlessMetadata(2, 1)
434
+ ),
435
+ .animatedEncodedDataTooLarge
436
+ )
437
+ XCTAssertEqual(
438
+ OneKeyImageSafetyPolicy.violation(
439
+ encodedByteCount: 1,
440
+ metadata: dimensionlessMetadata(
441
+ OneKeyImageSafetyPolicy.maximumAnimatedFrameCount + 1,
442
+ nil
443
+ )
444
+ ),
445
+ .animatedFrameCountTooLarge
446
+ )
447
+ XCTAssertEqual(
448
+ OneKeyImageSafetyPolicy.violation(
449
+ encodedByteCount: 1,
450
+ metadata: dimensionlessMetadata(2, .nan)
451
+ ),
452
+ .animatedDurationTooLong
453
+ )
454
+ XCTAssertEqual(
455
+ OneKeyImageSafetyPolicy.violation(
456
+ encodedByteCount: 1,
457
+ metadata: dimensionlessMetadata(2, .infinity)
458
+ ),
459
+ .animatedDurationTooLong
460
+ )
461
+ }
462
+
463
+ func testMetadataDimensionsIncludeLargerLaterFrames() {
464
+ let dimensions = OneKeyImageSafetyPolicy.maximumDimensions(
465
+ width: 100,
466
+ height: 200,
467
+ candidateWidth: 9_000,
468
+ candidateHeight: 300
469
+ )
470
+ XCTAssertEqual(dimensions.width, 9_000)
471
+ XCTAssertEqual(dimensions.height, 300)
472
+ }
473
+
474
+ func testSafetyLimitsAcceptExactBoundaries() {
475
+ XCTAssertNil(
476
+ OneKeyImageSafetyPolicy.violation(
477
+ encodedByteCount: OneKeyImageSafetyPolicy.maximumAnimatedEncodedBytes,
478
+ metadata: OneKeyImageMetadata(
479
+ width: 4_000,
480
+ height: 4_000,
481
+ frameCount: OneKeyImageSafetyPolicy.maximumAnimatedFrameCount,
482
+ duration: OneKeyImageSafetyPolicy.maximumAnimatedDuration
483
+ )
484
+ )
485
+ )
486
+ XCTAssertNil(
487
+ OneKeyImageSafetyPolicy.violation(
488
+ encodedByteCount: OneKeyImageSafetyPolicy.maximumEncodedBytes,
489
+ metadata: OneKeyImageMetadata(
490
+ width: 10_000,
491
+ height: 10_000,
492
+ frameCount: 1,
493
+ duration: nil
494
+ )
495
+ )
496
+ )
497
+ }
498
+
499
+ func testDataURIPayloadCalculationAndCorruptDataClassification() {
500
+ XCTAssertNil(OneKeyImageSafetyPolicy.preflight(source: "data:image/png;base64,TWE="))
501
+ XCTAssertEqual(
502
+ OneKeyImageSafetyPolicy.preflight(
503
+ source: "data:text/plain,\(String(repeating: "a", count: 8 * 1024 * 1024 + 1))"
504
+ ),
505
+ .dataURIPayloadTooLarge
506
+ )
507
+ XCTAssertNil(OneKeyImageSafetyPolicy.violation(for: Data("not an image".utf8)))
508
+ }
509
+
510
+ func testReceivedByteTrackerRejectsEveryOversizedObservation() {
511
+ let tracker = OneKeyImageSafetyTracker()
512
+ XCTAssertFalse(
513
+ tracker.inspectReceivedByteCount(Int64(OneKeyImageSafetyPolicy.maximumEncodedBytes))
514
+ )
515
+ XCTAssertTrue(
516
+ tracker.inspectReceivedByteCount(Int64(OneKeyImageSafetyPolicy.maximumEncodedBytes + 1))
517
+ )
518
+ XCTAssertTrue(
519
+ tracker.inspectReceivedByteCount(Int64(OneKeyImageSafetyPolicy.maximumEncodedBytes + 2))
520
+ )
521
+ XCTAssertEqual(tracker.violation, .encodedDataTooLarge)
522
+ }
523
+
524
+ func testSafetyContextInstallsTransportGuards() {
525
+ let tracker = OneKeyImageSafetyTracker()
526
+ let context = OneKeyImageRequestContext.transportGuards(tracker: tracker)
527
+ XCTAssertNotNil(context[.downloadResponseModifier] as? SDWebImageDownloaderResponseModifier)
528
+ XCTAssertNotNil(context[.downloadDecryptor] as? SDWebImageDownloaderDecryptor)
529
+ }
530
+
531
+ func testSafetyFailureNeverRetriesRawTosURL() {
532
+ XCTAssertTrue(
533
+ HybridOneKeyImage.shouldRetryRaw(
534
+ mayFallbackToRaw: true,
535
+ safetyViolation: nil
536
+ )
537
+ )
538
+ XCTAssertFalse(
539
+ HybridOneKeyImage.shouldRetryRaw(
540
+ mayFallbackToRaw: true,
541
+ safetyViolation: .encodedDataTooLarge
542
+ )
543
+ )
544
+ }
545
+
546
+ func testAnimatedViewUsesBoundedFrameBuffer() throws {
547
+ let image = HybridOneKeyImage()
548
+ let imageView = try XCTUnwrap(image.view as? SDAnimatedImageView)
549
+ XCTAssertEqual(
550
+ imageView.maxBufferSize,
551
+ OneKeyImageSafetyPolicy.maximumAnimatedBufferBytes
552
+ )
553
+ XCTAssertTrue(imageView.clearBufferWhenStopped)
554
+ }
555
+ }