@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,781 @@
1
+ import CryptoKit
2
+ import Foundation
3
+ import ImageIO
4
+ import SDWebImage
5
+ import SDWebImageSVGCoder
6
+ import SDWebImageWebPCoder
7
+ import UIKit
8
+
9
+ enum OneKeyImageSafetyViolation: LocalizedError, Equatable, Sendable {
10
+ case encodedDataTooLarge
11
+ case dataURIPayloadTooLarge
12
+ case staticDimensionsTooLarge
13
+ case animatedDimensionsTooLarge
14
+ case animatedEncodedDataTooLarge
15
+ case animatedFrameCountTooLarge
16
+ case animatedDurationTooLong
17
+
18
+ var errorDescription: String? {
19
+ switch self {
20
+ case .encodedDataTooLarge:
21
+ return "Image encoded data exceeds 32 MiB"
22
+ case .dataURIPayloadTooLarge:
23
+ return "Image data URI payload exceeds 8 MiB"
24
+ case .staticDimensionsTooLarge:
25
+ return "Static image dimensions exceed the safety limit"
26
+ case .animatedDimensionsTooLarge:
27
+ return "Animated image dimensions exceed the safety limit"
28
+ case .animatedEncodedDataTooLarge:
29
+ return "Animated image encoded data exceeds 16 MiB"
30
+ case .animatedFrameCountTooLarge:
31
+ return "Animated image frame count exceeds 1000"
32
+ case .animatedDurationTooLong:
33
+ return "Animated image duration exceeds 60 seconds"
34
+ }
35
+ }
36
+ }
37
+
38
+ struct OneKeyImageMetadata: Equatable, Sendable {
39
+ let width: Int?
40
+ let height: Int?
41
+ let frameCount: Int?
42
+ let duration: TimeInterval?
43
+
44
+ var isAnimated: Bool { (frameCount ?? 1) > 1 }
45
+ }
46
+
47
+ struct OneKeyImageHeadersIdentity: Hashable, Sendable {
48
+ private struct Field: Hashable, Sendable {
49
+ let name: String
50
+ let value: String
51
+ }
52
+
53
+ private let fields: [Field]
54
+
55
+ init(headers: [String: String]?) {
56
+ fields = (headers ?? [:])
57
+ .map { Field(name: $0.key.lowercased(), value: $0.value) }
58
+ .sorted {
59
+ if $0.name == $1.name { return $0.value < $1.value }
60
+ return $0.name < $1.name
61
+ }
62
+ }
63
+
64
+ var isEmpty: Bool { fields.isEmpty }
65
+
66
+ var cacheKeyComponent: String? {
67
+ guard !fields.isEmpty else { return nil }
68
+ let canonicalValue = fields.map {
69
+ "\($0.name.utf8.count):\($0.name)\($0.value.utf8.count):\($0.value)"
70
+ }.joined()
71
+ let digest = SHA256.hash(data: Data(canonicalValue.utf8))
72
+ return digest.map { String(format: "%02x", $0) }.joined()
73
+ }
74
+ }
75
+
76
+ private struct OneKeyImageSafetyFlightIdentity: Hashable, Sendable {
77
+ let url: URL
78
+ let headers: OneKeyImageHeadersIdentity
79
+ }
80
+
81
+ final class OneKeyImageSafetyTracker: @unchecked Sendable {
82
+ private let lock = NSLock()
83
+ private var storedViolation: OneKeyImageSafetyViolation?
84
+
85
+ var violation: OneKeyImageSafetyViolation? {
86
+ lock.lock()
87
+ defer { lock.unlock() }
88
+ return storedViolation
89
+ }
90
+
91
+ @discardableResult
92
+ func record(_ violation: OneKeyImageSafetyViolation) -> Bool {
93
+ lock.lock()
94
+ defer { lock.unlock() }
95
+ guard storedViolation == nil else { return false }
96
+ storedViolation = violation
97
+ return true
98
+ }
99
+
100
+ @discardableResult
101
+ func inspectReceivedByteCount(_ byteCount: Int64) -> Bool {
102
+ guard byteCount > Int64(OneKeyImageSafetyPolicy.maximumEncodedBytes) else { return false }
103
+ record(.encodedDataTooLarge)
104
+ return true
105
+ }
106
+ }
107
+
108
+ final class OneKeyImageSafetyFlightHandle: @unchecked Sendable {
109
+ let tracker: OneKeyImageSafetyTracker
110
+ var manager: SDWebImageManager { managerLease.manager }
111
+
112
+ private let coordinator: OneKeyImageSafetyFlightCoordinator
113
+ private let identity: OneKeyImageSafetyFlightIdentity
114
+ private let managerLease: OneKeyImageManagerLease
115
+ private let lock = NSLock()
116
+ private var finished = false
117
+
118
+ fileprivate init(
119
+ coordinator: OneKeyImageSafetyFlightCoordinator,
120
+ identity: OneKeyImageSafetyFlightIdentity,
121
+ tracker: OneKeyImageSafetyTracker,
122
+ managerLease: OneKeyImageManagerLease
123
+ ) {
124
+ self.coordinator = coordinator
125
+ self.identity = identity
126
+ self.tracker = tracker
127
+ self.managerLease = managerLease
128
+ }
129
+
130
+ func finish() {
131
+ lock.lock()
132
+ guard !finished else {
133
+ lock.unlock()
134
+ return
135
+ }
136
+ finished = true
137
+ lock.unlock()
138
+ coordinator.release(identity: identity, tracker: tracker)
139
+ managerLease.finish()
140
+ }
141
+
142
+ deinit {
143
+ finish()
144
+ }
145
+ }
146
+
147
+ final class OneKeyImageSafetyFlightCoordinator: @unchecked Sendable {
148
+ static let shared = OneKeyImageSafetyFlightCoordinator()
149
+
150
+ private struct Flight {
151
+ let tracker: OneKeyImageSafetyTracker
152
+ var referenceCount: Int
153
+ }
154
+
155
+ private let lock = NSLock()
156
+ private let managerPool: OneKeyImageManagerPool
157
+ private var flights: [OneKeyImageSafetyFlightIdentity: Flight] = [:]
158
+
159
+ init(managerPool: OneKeyImageManagerPool = .shared) {
160
+ self.managerPool = managerPool
161
+ }
162
+
163
+ func acquire(
164
+ url: URL,
165
+ headers: [String: String]? = nil
166
+ ) -> OneKeyImageSafetyFlightHandle {
167
+ let headersIdentity = OneKeyImageHeadersIdentity(headers: headers)
168
+ let identity = OneKeyImageSafetyFlightIdentity(url: url, headers: headersIdentity)
169
+ let managerLease = managerPool.acquire(identity: headersIdentity)
170
+ lock.lock()
171
+ let tracker: OneKeyImageSafetyTracker
172
+ if var flight = flights[identity] {
173
+ flight.referenceCount += 1
174
+ flights[identity] = flight
175
+ tracker = flight.tracker
176
+ } else {
177
+ tracker = OneKeyImageSafetyTracker()
178
+ flights[identity] = Flight(tracker: tracker, referenceCount: 1)
179
+ }
180
+ lock.unlock()
181
+ return OneKeyImageSafetyFlightHandle(
182
+ coordinator: self,
183
+ identity: identity,
184
+ tracker: tracker,
185
+ managerLease: managerLease
186
+ )
187
+ }
188
+
189
+ fileprivate func release(
190
+ identity: OneKeyImageSafetyFlightIdentity,
191
+ tracker: OneKeyImageSafetyTracker
192
+ ) {
193
+ lock.lock()
194
+ defer { lock.unlock() }
195
+ guard var flight = flights[identity], flight.tracker === tracker else { return }
196
+ if flight.referenceCount == 1 {
197
+ flights.removeValue(forKey: identity)
198
+ } else {
199
+ flight.referenceCount -= 1
200
+ flights[identity] = flight
201
+ }
202
+ }
203
+
204
+ var activeFlightCount: Int {
205
+ lock.lock()
206
+ defer { lock.unlock() }
207
+ return flights.count
208
+ }
209
+ }
210
+
211
+ enum OneKeyImageSafetyPolicy {
212
+ static let maximumEncodedBytes = 32 * 1024 * 1024
213
+ static let maximumDataURIDecodedBytes = 8 * 1024 * 1024
214
+ static let maximumAnimatedEncodedBytes = 16 * 1024 * 1024
215
+ static let maximumStaticPixelArea = 100_000_000
216
+ static let maximumStaticSide = 32_768
217
+ static let maximumAnimatedPixelArea = 16_000_000
218
+ static let maximumAnimatedSide = 8_192
219
+ static let maximumAnimatedFrameCount = 1_000
220
+ static let maximumAnimatedDuration: TimeInterval = 60
221
+ static let maximumAnimatedBufferBytes: UInt = 16 * 1024 * 1024
222
+
223
+ static func preflight(source: String) -> OneKeyImageSafetyViolation? {
224
+ guard source.range(of: "data:", options: [.anchored, .caseInsensitive]) != nil,
225
+ let comma = source.firstIndex(of: ",")
226
+ else {
227
+ return nil
228
+ }
229
+ let header = source[..<comma]
230
+ let payload = source[source.index(after: comma)...]
231
+ let payloadByteCount = payload.utf8.count
232
+ let isBase64 = header.range(of: ";base64", options: .caseInsensitive) != nil
233
+ let decodedByteCount =
234
+ isBase64 ? base64DecodedByteCount(payload) : payloadByteCount
235
+ guard let decodedByteCount else {
236
+ // Invalid base64 is a normal corrupt-data failure. The raw character
237
+ // ceiling still guarantees the decoder cannot allocate over 8 MiB.
238
+ let maximumEncodedLength = ((maximumDataURIDecodedBytes + 2) / 3) * 4
239
+ return payloadByteCount > maximumEncodedLength ? .dataURIPayloadTooLarge : nil
240
+ }
241
+ return decodedByteCount > maximumDataURIDecodedBytes ? .dataURIPayloadTooLarge : nil
242
+ }
243
+
244
+ static func preflight(url: URL) -> OneKeyImageSafetyViolation? {
245
+ guard url.isFileURL,
246
+ let values = try? url.resourceValues(forKeys: [.fileSizeKey, .totalFileAllocatedSizeKey])
247
+ else {
248
+ return nil
249
+ }
250
+ let byteCount = max(values.fileSize ?? 0, values.totalFileAllocatedSize ?? 0)
251
+ return byteCount > maximumEncodedBytes ? .encodedDataTooLarge : nil
252
+ }
253
+
254
+ static func violation(
255
+ encodedByteCount: Int,
256
+ metadata: OneKeyImageMetadata?
257
+ ) -> OneKeyImageSafetyViolation? {
258
+ guard encodedByteCount <= maximumEncodedBytes else { return .encodedDataTooLarge }
259
+ guard let metadata else { return nil }
260
+
261
+ if metadata.isAnimated {
262
+ if encodedByteCount > maximumAnimatedEncodedBytes {
263
+ return .animatedEncodedDataTooLarge
264
+ }
265
+ if let frameCount = metadata.frameCount,
266
+ frameCount > maximumAnimatedFrameCount
267
+ {
268
+ return .animatedFrameCountTooLarge
269
+ }
270
+ if let duration = metadata.duration,
271
+ !duration.isFinite || duration > maximumAnimatedDuration
272
+ {
273
+ return .animatedDurationTooLong
274
+ }
275
+
276
+ if let width = metadata.width,
277
+ let height = metadata.height,
278
+ width > 0,
279
+ height > 0,
280
+ width > maximumAnimatedSide || height > maximumAnimatedSide
281
+ || Double(width) * Double(height) > Double(maximumAnimatedPixelArea)
282
+ {
283
+ return .animatedDimensionsTooLarge
284
+ }
285
+ } else if let width = metadata.width,
286
+ let height = metadata.height,
287
+ width > 0,
288
+ height > 0,
289
+ width > maximumStaticSide || height > maximumStaticSide
290
+ || Double(width) * Double(height) > Double(maximumStaticPixelArea)
291
+ {
292
+ return .staticDimensionsTooLarge
293
+ }
294
+ return nil
295
+ }
296
+
297
+ static func violation(for data: Data) -> OneKeyImageSafetyViolation? {
298
+ violation(encodedByteCount: data.count, metadata: metadata(from: data))
299
+ }
300
+
301
+ static func violation(for image: UIImage) -> OneKeyImageSafetyViolation? {
302
+ let pixelWidth = image.cgImage?.width ?? Int(ceil(image.size.width * image.scale))
303
+ let pixelHeight = image.cgImage?.height ?? Int(ceil(image.size.height * image.scale))
304
+ if let animatedImage = image as? SDAnimatedImage {
305
+ let frameCount = Int(animatedImage.animatedImageFrameCount)
306
+ let encodedByteCount = animatedImage.animatedImageData?.count ?? 0
307
+ var duration: TimeInterval = 0
308
+ if frameCount <= maximumAnimatedFrameCount {
309
+ for index in 0..<frameCount {
310
+ duration += animatedImage.animatedImageDuration(at: UInt(index))
311
+ if duration > maximumAnimatedDuration { break }
312
+ }
313
+ }
314
+ return violation(
315
+ encodedByteCount: encodedByteCount,
316
+ metadata: OneKeyImageMetadata(
317
+ width: pixelWidth,
318
+ height: pixelHeight,
319
+ frameCount: frameCount,
320
+ duration: duration
321
+ )
322
+ )
323
+ }
324
+ let frameCount = image.images?.count
325
+ return violation(
326
+ encodedByteCount: 0,
327
+ metadata: OneKeyImageMetadata(
328
+ width: pixelWidth,
329
+ height: pixelHeight,
330
+ frameCount: frameCount,
331
+ duration: frameCount == nil ? nil : image.duration
332
+ )
333
+ )
334
+ }
335
+
336
+ static func metadata(from data: Data) -> OneKeyImageMetadata? {
337
+ guard !data.isEmpty,
338
+ let source = CGImageSourceCreateWithData(
339
+ data as CFData,
340
+ [kCGImageSourceShouldCache: false] as CFDictionary
341
+ ),
342
+ CGImageSourceGetCount(source) > 0,
343
+ let firstProperties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as NSDictionary?
344
+ else {
345
+ return nil
346
+ }
347
+ let sourceProperties = CGImageSourceCopyProperties(source, nil) as NSDictionary?
348
+ var dimensions = maximumDimensions(
349
+ width: (sourceProperties?[kCGImagePropertyPixelWidth] as? NSNumber)?.intValue,
350
+ height: (sourceProperties?[kCGImagePropertyPixelHeight] as? NSNumber)?.intValue,
351
+ candidateWidth: (firstProperties[kCGImagePropertyPixelWidth] as? NSNumber)?.intValue,
352
+ candidateHeight: (firstProperties[kCGImagePropertyPixelHeight] as? NSNumber)?.intValue
353
+ )
354
+ let frameCount = CGImageSourceGetCount(source)
355
+ var duration: TimeInterval?
356
+ if frameCount <= maximumAnimatedFrameCount {
357
+ var total: TimeInterval = 0
358
+ var hasDuration = false
359
+ for index in 0..<frameCount {
360
+ guard
361
+ let properties =
362
+ CGImageSourceCopyPropertiesAtIndex(source, index, nil) as NSDictionary?
363
+ else {
364
+ continue
365
+ }
366
+ dimensions = maximumDimensions(
367
+ width: dimensions.width,
368
+ height: dimensions.height,
369
+ candidateWidth: (properties[kCGImagePropertyPixelWidth] as? NSNumber)?.intValue,
370
+ candidateHeight: (properties[kCGImagePropertyPixelHeight] as? NSNumber)?.intValue
371
+ )
372
+ if frameCount > 1, let frameDuration = frameDuration(from: properties) {
373
+ hasDuration = true
374
+ total += frameDuration
375
+ if !total.isFinite || total > maximumAnimatedDuration { break }
376
+ }
377
+ }
378
+ duration = hasDuration ? total : nil
379
+ }
380
+ return OneKeyImageMetadata(
381
+ width: dimensions.width,
382
+ height: dimensions.height,
383
+ frameCount: frameCount,
384
+ duration: duration
385
+ )
386
+ }
387
+
388
+ static func maximumDimensions(
389
+ width: Int?,
390
+ height: Int?,
391
+ candidateWidth: Int?,
392
+ candidateHeight: Int?
393
+ ) -> (width: Int?, height: Int?) {
394
+ (
395
+ [width, candidateWidth].compactMap { $0 }.max(),
396
+ [height, candidateHeight].compactMap { $0 }.max()
397
+ )
398
+ }
399
+
400
+ private static func base64DecodedByteCount(_ payload: Substring) -> Int? {
401
+ var characterCount = 0
402
+ var paddingCount = 0
403
+ var sawPadding = false
404
+ for byte in payload.utf8 {
405
+ if byte == 9 || byte == 10 || byte == 13 || byte == 32 { continue }
406
+ if byte == 61 {
407
+ sawPadding = true
408
+ paddingCount += 1
409
+ guard paddingCount <= 2 else { return nil }
410
+ } else {
411
+ guard !sawPadding,
412
+ (byte >= 65 && byte <= 90)
413
+ || (byte >= 97 && byte <= 122)
414
+ || (byte >= 48 && byte <= 57)
415
+ || byte == 43
416
+ || byte == 47
417
+ else {
418
+ return nil
419
+ }
420
+ }
421
+ characterCount += 1
422
+ }
423
+ guard characterCount > 0 else { return 0 }
424
+ let remainder = characterCount % 4
425
+ guard remainder != 1, paddingCount == 0 || remainder == 0 else { return nil }
426
+ let unpaddedBytes =
427
+ (characterCount / 4) * 3
428
+ + (remainder == 2 ? 1 : remainder == 3 ? 2 : 0)
429
+ return unpaddedBytes - paddingCount
430
+ }
431
+
432
+ private static func frameDuration(from properties: NSDictionary) -> TimeInterval? {
433
+ if let dictionary = properties[kCGImagePropertyGIFDictionary] as? NSDictionary {
434
+ return duration(
435
+ in: dictionary,
436
+ unclampedKey: kCGImagePropertyGIFUnclampedDelayTime,
437
+ clampedKey: kCGImagePropertyGIFDelayTime
438
+ )
439
+ }
440
+ if let dictionary = properties[kCGImagePropertyPNGDictionary] as? NSDictionary {
441
+ return duration(
442
+ in: dictionary,
443
+ unclampedKey: kCGImagePropertyAPNGUnclampedDelayTime,
444
+ clampedKey: kCGImagePropertyAPNGDelayTime
445
+ )
446
+ }
447
+ if let dictionary = properties[kCGImagePropertyWebPDictionary] as? NSDictionary {
448
+ return duration(
449
+ in: dictionary,
450
+ unclampedKey: kCGImagePropertyWebPUnclampedDelayTime,
451
+ clampedKey: kCGImagePropertyWebPDelayTime
452
+ )
453
+ }
454
+ return nil
455
+ }
456
+
457
+ private static func duration(
458
+ in dictionary: NSDictionary,
459
+ unclampedKey: CFString,
460
+ clampedKey: CFString
461
+ ) -> TimeInterval? {
462
+ let unclamped = (dictionary[unclampedKey] as? NSNumber)?.doubleValue
463
+ if let unclamped, !unclamped.isFinite || unclamped > 0 { return unclamped }
464
+ let clamped = (dictionary[clampedKey] as? NSNumber)?.doubleValue
465
+ return clamped.flatMap { !($0.isFinite) || $0 > 0 ? $0 : nil }
466
+ }
467
+ }
468
+
469
+ enum OneKeyImageCoderRegistry {
470
+ private static let svgCoder = SDImageSVGCoder.shared
471
+ private static let webPCoder = SDImageWebPCoder.shared
472
+
473
+ static let coder: SDImageCodersManager = {
474
+ // Keep OneKey's decoder set independent from Expo's global registrations.
475
+ // SDImageCodersManager starts with ImageIO, GIF and APNG coders.
476
+ let manager = SDImageCodersManager()
477
+ manager.addCoder(svgCoder)
478
+ manager.addCoder(webPCoder)
479
+ return manager
480
+ }()
481
+
482
+ private static let globalRegistration: Void = {
483
+ // SDAnimatedImage resolves its animated coder through the global manager,
484
+ // even when a request-local coder is provided in the SDWebImage context.
485
+ let global = SDImageCodersManager.shared
486
+ let isAlreadyRegistered = (global.coders ?? []).contains {
487
+ ($0 as AnyObject) === webPCoder
488
+ }
489
+ if !isAlreadyRegistered {
490
+ global.addCoder(webPCoder)
491
+ }
492
+ }()
493
+
494
+ static func ensureWebPRegistered() {
495
+ _ = globalRegistration
496
+ }
497
+ }
498
+
499
+ enum OneKeyImagePipeline {
500
+ static let downloader = SDWebImageDownloader(config: nil)
501
+ static let manager = SDWebImageManager(cache: SDImageCache.shared, loader: downloader)
502
+
503
+ static func makeIsolatedManager() -> SDWebImageManager {
504
+ SDWebImageManager(
505
+ cache: SDImageCache.shared,
506
+ loader: SDWebImageDownloader(config: nil)
507
+ )
508
+ }
509
+ }
510
+
511
+ final class OneKeyImageManagerLease: @unchecked Sendable {
512
+ let manager: SDWebImageManager
513
+
514
+ private let pool: OneKeyImageManagerPool?
515
+ private let identity: OneKeyImageHeadersIdentity
516
+ private let lock = NSLock()
517
+ private var finished = false
518
+
519
+ fileprivate init(
520
+ manager: SDWebImageManager,
521
+ pool: OneKeyImageManagerPool?,
522
+ identity: OneKeyImageHeadersIdentity
523
+ ) {
524
+ self.manager = manager
525
+ self.pool = pool
526
+ self.identity = identity
527
+ }
528
+
529
+ func finish() {
530
+ lock.lock()
531
+ guard !finished else {
532
+ lock.unlock()
533
+ return
534
+ }
535
+ finished = true
536
+ lock.unlock()
537
+ pool?.release(identity: identity, manager: manager)
538
+ }
539
+
540
+ deinit {
541
+ finish()
542
+ }
543
+ }
544
+
545
+ final class OneKeyImageManagerPool: @unchecked Sendable {
546
+ static let shared = OneKeyImageManagerPool()
547
+
548
+ private struct Entry {
549
+ let manager: SDWebImageManager
550
+ var referenceCount: Int
551
+ }
552
+
553
+ private let defaultManager: SDWebImageManager
554
+ private let lock = NSLock()
555
+ private var entries: [OneKeyImageHeadersIdentity: Entry] = [:]
556
+
557
+ init(defaultManager: SDWebImageManager = OneKeyImagePipeline.manager) {
558
+ self.defaultManager = defaultManager
559
+ }
560
+
561
+ func acquire(identity: OneKeyImageHeadersIdentity) -> OneKeyImageManagerLease {
562
+ guard !identity.isEmpty else {
563
+ return OneKeyImageManagerLease(manager: defaultManager, pool: nil, identity: identity)
564
+ }
565
+
566
+ lock.lock()
567
+ let manager: SDWebImageManager
568
+ if var entry = entries[identity] {
569
+ entry.referenceCount += 1
570
+ entries[identity] = entry
571
+ manager = entry.manager
572
+ } else {
573
+ manager = OneKeyImagePipeline.makeIsolatedManager()
574
+ entries[identity] = Entry(manager: manager, referenceCount: 1)
575
+ }
576
+ lock.unlock()
577
+ return OneKeyImageManagerLease(manager: manager, pool: self, identity: identity)
578
+ }
579
+
580
+ fileprivate func release(
581
+ identity: OneKeyImageHeadersIdentity,
582
+ manager: SDWebImageManager
583
+ ) {
584
+ lock.lock()
585
+ defer { lock.unlock() }
586
+ guard var entry = entries[identity], entry.manager === manager else { return }
587
+ if entry.referenceCount == 1 {
588
+ entries.removeValue(forKey: identity)
589
+ } else {
590
+ entry.referenceCount -= 1
591
+ entries[identity] = entry
592
+ }
593
+ }
594
+
595
+ var activeManagerCount: Int {
596
+ lock.lock()
597
+ defer { lock.unlock() }
598
+ return entries.count
599
+ }
600
+ }
601
+
602
+ enum OneKeyImageDecodeSizing {
603
+ static let maximumDecodedBytes = 16 * 1024 * 1024
604
+ static let bytesPerPixel = 4
605
+ static let maximumPixelArea = CGFloat(maximumDecodedBytes / bytesPerPixel)
606
+ static let defaultPreloadPixelSize = CGSize(width: 2_048, height: 2_048)
607
+
608
+ static func logicalViewSize(width: Double?, height: Double?) -> CGSize? {
609
+ let validWidth = width.flatMap { value -> CGFloat? in
610
+ guard value.isFinite, value > 0 else { return nil }
611
+ return CGFloat(value)
612
+ }
613
+ let validHeight = height.flatMap { value -> CGFloat? in
614
+ guard value.isFinite, value > 0 else { return nil }
615
+ return CGFloat(value)
616
+ }
617
+ switch (validWidth, validHeight) {
618
+ case (let width?, let height?): return CGSize(width: width, height: height)
619
+ case (let width?, nil): return CGSize(width: width, height: width)
620
+ case (nil, let height?): return CGSize(width: height, height: height)
621
+ case (nil, nil): return nil
622
+ }
623
+ }
624
+
625
+ static func thumbnailPixelSize(
626
+ viewSize: CGSize,
627
+ scale: CGFloat,
628
+ contentFit: OneKeyImageContentFit
629
+ ) -> CGSize? {
630
+ guard viewSize.width.isFinite,
631
+ viewSize.height.isFinite,
632
+ viewSize.width > 0,
633
+ viewSize.height > 0
634
+ else {
635
+ return nil
636
+ }
637
+ let pixelScale = min(max(scale.isFinite ? scale : 1, 1), 3)
638
+ let requestedSize: CGSize
639
+ switch contentFit {
640
+ case .cover:
641
+ // The source aspect ratio is unknown before decode. A square based on the
642
+ // longest edge avoids under-decoding the common cover case.
643
+ let edge = ceil(max(viewSize.width, viewSize.height) * pixelScale)
644
+ requestedSize = CGSize(width: edge, height: edge)
645
+ case .contain, .fill, .center:
646
+ requestedSize = CGSize(
647
+ width: ceil(viewSize.width * pixelScale),
648
+ height: ceil(viewSize.height * pixelScale)
649
+ )
650
+ }
651
+ return cappedToMaximumArea(requestedSize)
652
+ }
653
+
654
+ private static func cappedToMaximumArea(_ size: CGSize) -> CGSize {
655
+ let area = size.width * size.height
656
+ guard area > maximumPixelArea else { return size }
657
+ let ratio = sqrt(maximumPixelArea / area)
658
+ return CGSize(
659
+ width: max(floor(size.width * ratio), 1),
660
+ height: max(floor(size.height * ratio), 1)
661
+ )
662
+ }
663
+ }
664
+
665
+ enum OneKeyImageRequestContext {
666
+ static let safetyCacheGeneration = "onekey-image-safety-v2"
667
+ static let renderOptions: SDWebImage.SDWebImageOptions = [.retryFailed]
668
+ static let preloadOptions: SDWebImage.SDWebImageOptions = [
669
+ .retryFailed, .lowPriority,
670
+ ]
671
+
672
+ static var baseContext: [SDWebImageContextOption: Any] {
673
+ OneKeyImageCoderRegistry.ensureWebPRegistered()
674
+ return [
675
+ .imageCoder: OneKeyImageCoderRegistry.coder,
676
+ .animatedImageClass: SDAnimatedImage.self,
677
+ .customManager: OneKeyImagePipeline.manager,
678
+ ]
679
+ }
680
+
681
+ static func headers(from json: String?) -> [String: String]? {
682
+ guard let json,
683
+ let data = json.data(using: .utf8),
684
+ let value = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
685
+ else {
686
+ return nil
687
+ }
688
+ var headers: [String: String] = [:]
689
+ for (key, rawValue) in value {
690
+ if let string = rawValue as? String { headers[key] = string }
691
+ }
692
+ return headers.isEmpty ? nil : headers
693
+ }
694
+
695
+ static func make(
696
+ headersJson: String?,
697
+ cachePolicy: OneKeyImageCachePolicy,
698
+ thumbnailPixelSize: CGSize?,
699
+ safetyTracker: OneKeyImageSafetyTracker?,
700
+ manager: SDWebImageManager
701
+ ) -> [SDWebImageContextOption: Any] {
702
+ var context = baseContext
703
+ context[.customManager] = manager
704
+ if let safetyTracker {
705
+ for (key, value) in transportGuards(tracker: safetyTracker) {
706
+ context[key] = value
707
+ }
708
+ }
709
+ let headers = headers(from: headersJson)
710
+ let headersIdentity = OneKeyImageHeadersIdentity(headers: headers)
711
+ if let headers {
712
+ context[.downloadRequestModifier] = SDWebImageDownloaderRequestModifier(headers: headers)
713
+ }
714
+ context[.cacheKeyFilter] = SDWebImageCacheKeyFilter { url in
715
+ filteredCacheKey(
716
+ for: url,
717
+ headersIdentity: headersIdentity
718
+ )
719
+ }
720
+ if let thumbnailPixelSize {
721
+ for (key, value) in thumbnailOptions(pixelSize: thumbnailPixelSize) {
722
+ context[key] = value
723
+ }
724
+ }
725
+
726
+ let cacheType = cacheType(for: cachePolicy)
727
+ context[.queryCacheType] = cacheType.rawValue
728
+ context[.storeCacheType] = cacheType.rawValue
729
+ context[.originalQueryCacheType] = cacheType.rawValue
730
+ context[.originalStoreCacheType] = cacheType.rawValue
731
+ return context
732
+ }
733
+
734
+ static func transportGuards(
735
+ tracker: OneKeyImageSafetyTracker
736
+ ) -> [SDWebImageContextOption: Any] {
737
+ [
738
+ .downloadResponseModifier: SDWebImageDownloaderResponseModifier { response in
739
+ guard !tracker.inspectReceivedByteCount(response.expectedContentLength) else {
740
+ return nil
741
+ }
742
+ return response
743
+ },
744
+ .downloadDecryptor: SDWebImageDownloaderDecryptor { data, _ in
745
+ if let violation = OneKeyImageSafetyPolicy.violation(for: data) {
746
+ tracker.record(violation)
747
+ return nil
748
+ }
749
+ return data
750
+ },
751
+ ]
752
+ }
753
+
754
+ static func filteredCacheKey(
755
+ for url: URL,
756
+ headersIdentity: OneKeyImageHeadersIdentity = OneKeyImageHeadersIdentity(headers: nil)
757
+ ) -> String {
758
+ var identity = url.absoluteString
759
+ if let headersComponent = headersIdentity.cacheKeyComponent {
760
+ identity += "|headers:\(headersComponent)"
761
+ }
762
+ return "\(identity)|\(safetyCacheGeneration)"
763
+ }
764
+
765
+ static func thumbnailOptions(pixelSize: CGSize) -> [SDWebImageContextOption: Any] {
766
+ guard pixelSize.width > 0, pixelSize.height > 0 else { return [:] }
767
+ return [
768
+ .imageThumbnailPixelSize: NSValue(cgSize: pixelSize),
769
+ .imagePreserveAspectRatio: true,
770
+ ]
771
+ }
772
+
773
+ static func cacheType(for policy: OneKeyImageCachePolicy) -> SDImageCacheType {
774
+ switch policy {
775
+ case .memoryDisk: return .all
776
+ case .memory: return .memory
777
+ case .disk: return .disk
778
+ case .none: return .none
779
+ }
780
+ }
781
+ }