@aguacerowx/react-native 0.0.50 → 0.0.52
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/src/main/java/com/aguacerowx/reactnative/SatelliteLayerView.java +11 -3
- package/android/src/main/java/com/aguacerowx/reactnative/WeatherFrameProcessorModule.java +315 -275
- package/ios/SatelliteLayerView.swift +11 -4
- package/ios/WeatherFrameProcessorModule.swift +222 -188
- package/lib/commonjs/WeatherLayerManager.js +112 -48
- package/lib/commonjs/WeatherLayerManager.js.map +1 -1
- package/lib/commonjs/aguaceroCoreDebugHooks.js +144 -0
- package/lib/commonjs/aguaceroCoreDebugHooks.js.map +1 -0
- package/lib/commonjs/aguaceroRnDebug.js +358 -0
- package/lib/commonjs/aguaceroRnDebug.js.map +1 -0
- package/lib/commonjs/gridCdnAuth.js +64 -0
- package/lib/commonjs/gridCdnAuth.js.map +1 -0
- package/lib/commonjs/index.js +50 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/nexrad/nexradAndroidController.js +38 -25
- package/lib/commonjs/nexrad/nexradAndroidController.js.map +1 -1
- package/lib/commonjs/nexrad/nexradDiag.js +31 -25
- package/lib/commonjs/nexrad/nexradDiag.js.map +1 -1
- package/lib/commonjs/satellite/satelliteAndroidController.js +24 -15
- package/lib/commonjs/satellite/satelliteAndroidController.js.map +1 -1
- package/lib/module/WeatherLayerManager.js +112 -48
- package/lib/module/WeatherLayerManager.js.map +1 -1
- package/lib/module/aguaceroCoreDebugHooks.js +136 -0
- package/lib/module/aguaceroCoreDebugHooks.js.map +1 -0
- package/lib/module/aguaceroRnDebug.js +341 -0
- package/lib/module/aguaceroRnDebug.js.map +1 -0
- package/lib/module/gridCdnAuth.js +56 -0
- package/lib/module/gridCdnAuth.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/nexrad/nexradAndroidController.js +38 -25
- package/lib/module/nexrad/nexradAndroidController.js.map +1 -1
- package/lib/module/nexrad/nexradDiag.js +31 -25
- package/lib/module/nexrad/nexradDiag.js.map +1 -1
- package/lib/module/satellite/satelliteAndroidController.js +24 -15
- package/lib/module/satellite/satelliteAndroidController.js.map +1 -1
- package/lib/typescript/WeatherLayerManager.d.ts.map +1 -1
- package/lib/typescript/aguaceroCoreDebugHooks.d.ts +10 -0
- package/lib/typescript/aguaceroCoreDebugHooks.d.ts.map +1 -0
- package/lib/typescript/aguaceroRnDebug.d.ts +97 -0
- package/lib/typescript/aguaceroRnDebug.d.ts.map +1 -0
- package/lib/typescript/gridCdnAuth.d.ts +24 -0
- package/lib/typescript/gridCdnAuth.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/nexrad/nexradAndroidController.d.ts.map +1 -1
- package/lib/typescript/nexrad/nexradDiag.d.ts.map +1 -1
- package/lib/typescript/satellite/satelliteAndroidController.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/WeatherLayerManager.js +2024 -1947
- package/src/aguaceroCoreDebugHooks.js +142 -0
- package/src/aguaceroRnDebug.js +335 -0
- package/src/gridCdnAuth.js +56 -0
- package/src/index.js +19 -7
- package/src/nexrad/nexradAndroidController.js +1078 -1068
- package/src/nexrad/nexradDiag.js +150 -144
- package/src/satellite/satelliteAndroidController.js +245 -236
|
@@ -381,10 +381,17 @@ public final class SatelliteLayerView: UIView {
|
|
|
381
381
|
if !bundleId.isEmpty {
|
|
382
382
|
req.setValue(bundleId, forHTTPHeaderField: "x-app-identifier")
|
|
383
383
|
}
|
|
384
|
-
// Match WeatherFrameProcessorModule + AguaceroCore grid fetch (CloudFront
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
384
|
+
// Match WeatherFrameProcessorModule + AguaceroCore grid fetch (CloudFront returns 403 without Origin on RN).
|
|
385
|
+
var gridOrigin = gridRequestSiteOrigin.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
386
|
+
if gridOrigin.isEmpty {
|
|
387
|
+
gridOrigin = "https://localhost"
|
|
388
|
+
}
|
|
389
|
+
while gridOrigin.hasSuffix("/") {
|
|
390
|
+
gridOrigin.removeLast()
|
|
391
|
+
}
|
|
392
|
+
if !gridOrigin.isEmpty {
|
|
393
|
+
req.setValue(gridOrigin, forHTTPHeaderField: "Origin")
|
|
394
|
+
req.setValue("\(gridOrigin)/", forHTTPHeaderField: "Referer")
|
|
388
395
|
}
|
|
389
396
|
|
|
390
397
|
let sem = DispatchSemaphore(value: 0)
|
|
@@ -1,189 +1,223 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import React
|
|
3
|
-
|
|
4
|
-
@objc(WeatherFrameProcessorModule)
|
|
5
|
-
class WeatherFrameProcessorModule: NSObject {
|
|
6
|
-
|
|
7
|
-
private var currentRunToken = 0
|
|
8
|
-
private let session = URLSession(configuration: .default)
|
|
9
|
-
|
|
10
|
-
// MARK: - Performance Helper
|
|
11
|
-
private func getMemoryUsage() -> String {
|
|
12
|
-
var taskInfo = mach_task_basic_info()
|
|
13
|
-
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size)/4
|
|
14
|
-
let kerr: kern_return_t = withUnsafeMutablePointer(to: &taskInfo) {
|
|
15
|
-
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
|
|
16
|
-
task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
if kerr == KERN_SUCCESS {
|
|
20
|
-
let usedMB = Float(taskInfo.resident_size) / 1024.0 / 1024.0
|
|
21
|
-
return String(format: "%.2f MB", usedMB)
|
|
22
|
-
}
|
|
23
|
-
return "Unknown"
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
@objc(cancelAllFrames)
|
|
27
|
-
func cancelAllFrames() {
|
|
28
|
-
currentRunToken += 1
|
|
29
|
-
print("🔍 [WeatherFrameProcessor] cancelAllFrames → new runToken=\(currentRunToken) (in-flight fetches will reject with E_CANCELLED). RAM: \(getMemoryUsage())")
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
@objc(processFrame:resolver:rejecter:)
|
|
33
|
-
func processFrame(options: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
34
|
-
let taskToken = self.currentRunToken
|
|
35
|
-
let startTime = CFAbsoluteTimeGetCurrent()
|
|
36
|
-
|
|
37
|
-
guard let urlString = options["url"] as? String, let url = URL(string: urlString) else {
|
|
38
|
-
print("🔍 [WeatherFrameProcessor] processFrame REJECT invalid url options=\(options.keys.sorted())")
|
|
39
|
-
reject("INVALID_URL", "The provided URL is not valid.", nil)
|
|
40
|
-
return
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Extract filename for log identification
|
|
44
|
-
let fileId = url.lastPathComponent
|
|
45
|
-
print("🔍 [WeatherFrameProcessor] processFrame START fileId=\(fileId) host=\(url.host ?? "?") runToken=\(taskToken) RAM=\(getMemoryUsage())")
|
|
46
|
-
|
|
47
|
-
let apiKey = options["apiKey"] as? String ?? ""
|
|
48
|
-
let bundleId = options["bundleId"] as? String
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if let
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
let
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
let
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
]
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
1
|
+
import Foundation
|
|
2
|
+
import React
|
|
3
|
+
|
|
4
|
+
@objc(WeatherFrameProcessorModule)
|
|
5
|
+
class WeatherFrameProcessorModule: NSObject {
|
|
6
|
+
|
|
7
|
+
private var currentRunToken = 0
|
|
8
|
+
private let session = URLSession(configuration: .default)
|
|
9
|
+
|
|
10
|
+
// MARK: - Performance Helper
|
|
11
|
+
private func getMemoryUsage() -> String {
|
|
12
|
+
var taskInfo = mach_task_basic_info()
|
|
13
|
+
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size)/4
|
|
14
|
+
let kerr: kern_return_t = withUnsafeMutablePointer(to: &taskInfo) {
|
|
15
|
+
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
|
|
16
|
+
task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if kerr == KERN_SUCCESS {
|
|
20
|
+
let usedMB = Float(taskInfo.resident_size) / 1024.0 / 1024.0
|
|
21
|
+
return String(format: "%.2f MB", usedMB)
|
|
22
|
+
}
|
|
23
|
+
return "Unknown"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@objc(cancelAllFrames)
|
|
27
|
+
func cancelAllFrames() {
|
|
28
|
+
currentRunToken += 1
|
|
29
|
+
print("🔍 [WeatherFrameProcessor] cancelAllFrames → new runToken=\(currentRunToken) (in-flight fetches will reject with E_CANCELLED). RAM: \(getMemoryUsage())")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@objc(processFrame:resolver:rejecter:)
|
|
33
|
+
func processFrame(options: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
34
|
+
let taskToken = self.currentRunToken
|
|
35
|
+
let startTime = CFAbsoluteTimeGetCurrent()
|
|
36
|
+
|
|
37
|
+
guard let urlString = options["url"] as? String, let url = URL(string: urlString) else {
|
|
38
|
+
print("🔍 [WeatherFrameProcessor] processFrame REJECT invalid url options=\(options.keys.sorted())")
|
|
39
|
+
reject("INVALID_URL", "The provided URL is not valid.", nil)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Extract filename for log identification
|
|
44
|
+
let fileId = url.lastPathComponent
|
|
45
|
+
print("🔍 [WeatherFrameProcessor] processFrame START fileId=\(fileId) host=\(url.host ?? "?") runToken=\(taskToken) RAM=\(getMemoryUsage())")
|
|
46
|
+
|
|
47
|
+
let apiKey = options["apiKey"] as? String ?? ""
|
|
48
|
+
let bundleId = options["bundleId"] as? String
|
|
49
|
+
let debug = (options["debug"] as? Bool) == true
|
|
50
|
+
let gridOriginOpt = options["gridRequestSiteOrigin"] as? String
|
|
51
|
+
let fallbackGridOrigin = "https://localhost"
|
|
52
|
+
|
|
53
|
+
var request = URLRequest(url: url)
|
|
54
|
+
request.setValue(apiKey, forHTTPHeaderField: "x-api-key")
|
|
55
|
+
if let bundleId = bundleId, !bundleId.isEmpty {
|
|
56
|
+
request.setValue(bundleId, forHTTPHeaderField: "x-app-identifier")
|
|
57
|
+
}
|
|
58
|
+
var originCandidate = (gridOriginOpt ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
59
|
+
if originCandidate.isEmpty {
|
|
60
|
+
originCandidate = fallbackGridOrigin
|
|
61
|
+
}
|
|
62
|
+
var origin = originCandidate
|
|
63
|
+
while origin.hasSuffix("/") {
|
|
64
|
+
origin.removeLast()
|
|
65
|
+
}
|
|
66
|
+
if !origin.isEmpty {
|
|
67
|
+
request.setValue(origin, forHTTPHeaderField: "Origin")
|
|
68
|
+
request.setValue("\(origin)/", forHTTPHeaderField: "Referer")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if debug {
|
|
72
|
+
let redacted = urlString.replacingOccurrences(
|
|
73
|
+
of: #"([?&])apiKey=[^&]*"#,
|
|
74
|
+
with: "$1apiKey=(redacted)",
|
|
75
|
+
options: .regularExpression
|
|
76
|
+
)
|
|
77
|
+
let bundleStr = (bundleId?.isEmpty == false) ? bundleId! : "(none)"
|
|
78
|
+
let originStr = (gridOriginOpt?.isEmpty == false) ? gridOriginOpt! : "(none)"
|
|
79
|
+
print("[AguaceroRN][debug][FrameProcessor] REQUEST url=\(redacted) apiKey.len=\(apiKey.count) bundleId=\(bundleStr) gridOrigin=\(originStr)")
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let task = session.dataTask(with: request) { data, response, error in
|
|
83
|
+
let fetchDuration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000
|
|
84
|
+
|
|
85
|
+
// Must reject so JS `await processFrame` does not hang forever (otherwise WeatherLayerManager
|
|
86
|
+
// leaves hasPreloadedRef stuck and grid data never loads after cancelAllFrames).
|
|
87
|
+
let finishCancelled = {
|
|
88
|
+
print("⚡️ [PERF] [WeatherFrameProcessor] CANCELLED: \(fileId) after \(String(format: "%.2f", fetchDuration))ms")
|
|
89
|
+
DispatchQueue.main.async {
|
|
90
|
+
reject("E_CANCELLED", "Weather frame fetch superseded", nil)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if self.currentRunToken != taskToken {
|
|
95
|
+
print("🔍 [WeatherFrameProcessor] \(fileId) aborted: runToken mismatch (task=\(taskToken) current=\(self.currentRunToken)) → E_CANCELLED")
|
|
96
|
+
finishCancelled()
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if let error = error {
|
|
101
|
+
print("🔍 [WeatherFrameProcessor] \(fileId) NETWORK_ERROR: \(error.localizedDescription)")
|
|
102
|
+
reject("NETWORK_ERROR", "Failed to fetch frame data: \(error.localizedDescription)", error)
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
|
|
107
|
+
let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1
|
|
108
|
+
if debug {
|
|
109
|
+
let redacted = urlString.replacingOccurrences(
|
|
110
|
+
of: #"([?&])apiKey=[^&]*"#,
|
|
111
|
+
with: "$1apiKey=(redacted)",
|
|
112
|
+
options: .regularExpression
|
|
113
|
+
)
|
|
114
|
+
var bodySnippet = ""
|
|
115
|
+
if let data = data, let text = String(data: data, encoding: .utf8) {
|
|
116
|
+
bodySnippet = text.count > 512 ? String(text.prefix(512)) + "…" : text
|
|
117
|
+
}
|
|
118
|
+
let bundleStr = (bundleId?.isEmpty == false) ? bundleId! : "(none)"
|
|
119
|
+
let originStr = (gridOriginOpt?.isEmpty == false) ? gridOriginOpt! : "(none)"
|
|
120
|
+
print("[AguaceroRN][debug][FrameProcessor] HTTP_ERROR status=\(statusCode) url=\(redacted) apiKey.len=\(apiKey.count) bundleId=\(bundleStr) gridOrigin=\(originStr) body=\(bodySnippet)")
|
|
121
|
+
if statusCode == 403 {
|
|
122
|
+
print("[AguaceroRN][debug][FrameProcessor] 403 hint: verify API key, allowlisted bundleId (x-app-identifier), and gridRequestSiteOrigin (Origin/Referer) match your web app.")
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
print("🔍 [WeatherFrameProcessor] \(fileId) HTTP_ERROR status=\(statusCode)")
|
|
126
|
+
}
|
|
127
|
+
reject("HTTP_ERROR", "HTTP request failed with status code: \(statusCode)", nil)
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
guard let data = data else {
|
|
132
|
+
print("🔍 [WeatherFrameProcessor] \(fileId) NO_DATA")
|
|
133
|
+
reject("NO_DATA", "The server returned no data.", nil)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
print("⚡️ [PERF] [WeatherFrameProcessor] NETWORK DONE: \(fileId) | Time: \(String(format: "%.2f", fetchDuration))ms | Size: \(data.count / 1024)KB | RAM: \(self.getMemoryUsage())")
|
|
138
|
+
|
|
139
|
+
do {
|
|
140
|
+
// 1. JSON Parsing
|
|
141
|
+
let jsonStart = CFAbsoluteTimeGetCurrent()
|
|
142
|
+
guard let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
|
|
143
|
+
print("🔍 [WeatherFrameProcessor] \(fileId) INVALID_RESPONSE: top-level JSON not object")
|
|
144
|
+
reject("INVALID_RESPONSE", "The API response format was invalid JSON.", nil)
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
let jsonTime = (CFAbsoluteTimeGetCurrent() - jsonStart) * 1000
|
|
148
|
+
|
|
149
|
+
guard let b64CompressedData = jsonResponse["data"] as? String,
|
|
150
|
+
let encoding = jsonResponse["encoding"] as? [String: Any],
|
|
151
|
+
let scale = encoding["scale"] as? NSNumber,
|
|
152
|
+
let offset = encoding["offset"] as? NSNumber,
|
|
153
|
+
let missing = encoding["missing_quantized"] as? NSNumber else {
|
|
154
|
+
let encKeys = (jsonResponse["encoding"] as? [String: Any])?.keys.sorted() ?? []
|
|
155
|
+
print("🔍 [WeatherFrameProcessor] \(fileId) INVALID_RESPONSE: keys=\(Array(jsonResponse.keys).sorted()) encodingKeys=\(encKeys)")
|
|
156
|
+
reject("INVALID_RESPONSE", "The API response format was invalid structure.", nil)
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 2. Base64 Decoding
|
|
161
|
+
let decodeStart = CFAbsoluteTimeGetCurrent()
|
|
162
|
+
guard let compressedData = Data(base64Encoded: b64CompressedData) else {
|
|
163
|
+
print("🔍 [WeatherFrameProcessor] \(fileId) DECODING_ERROR: base64 decode failed (b64 len=\(b64CompressedData.count))")
|
|
164
|
+
reject("DECODING_ERROR", "Failed to decode base64 data string.", nil)
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
let decodeTime = (CFAbsoluteTimeGetCurrent() - decodeStart) * 1000
|
|
168
|
+
|
|
169
|
+
// 3. Disk Write
|
|
170
|
+
let writeStart = CFAbsoluteTimeGetCurrent()
|
|
171
|
+
let cacheDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
|
|
172
|
+
let fileName = "frame_\(urlString.hashValue).zst"
|
|
173
|
+
let dataFile = cacheDir.appendingPathComponent(fileName)
|
|
174
|
+
|
|
175
|
+
try compressedData.write(to: dataFile)
|
|
176
|
+
let writeTime = (CFAbsoluteTimeGetCurrent() - writeStart) * 1000
|
|
177
|
+
|
|
178
|
+
var responseMap: [String: Any] = [
|
|
179
|
+
"filePath": dataFile.path,
|
|
180
|
+
"scale": scale,
|
|
181
|
+
"offset": offset,
|
|
182
|
+
"missing": missing,
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
if let scaleType = encoding["scale_type"] as? String {
|
|
186
|
+
responseMap["scaleType"] = scaleType
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let totalTime = (CFAbsoluteTimeGetCurrent() - startTime) * 1000
|
|
190
|
+
|
|
191
|
+
print("""
|
|
192
|
+
⚡️ [PERF] [WeatherFrameProcessor] FINISHED: \(fileId)
|
|
193
|
+
- Total: \(String(format: "%.2f", totalTime))ms
|
|
194
|
+
- JSON: \(String(format: "%.2f", jsonTime))ms
|
|
195
|
+
- B64 Decode: \(String(format: "%.2f", decodeTime))ms
|
|
196
|
+
- Disk Write: \(String(format: "%.2f", writeTime))ms
|
|
197
|
+
- RAM End: \(self.getMemoryUsage())
|
|
198
|
+
""")
|
|
199
|
+
|
|
200
|
+
if self.currentRunToken != taskToken {
|
|
201
|
+
print("🔍 [WeatherFrameProcessor] \(fileId) discarded after disk write: runToken mismatch before resolve")
|
|
202
|
+
finishCancelled()
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
DispatchQueue.main.async {
|
|
206
|
+
print("🔍 [WeatherFrameProcessor] \(fileId) RESOLVE ok path=\(dataFile.path)")
|
|
207
|
+
resolve(responseMap)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
} catch {
|
|
211
|
+
print("🔍 [WeatherFrameProcessor] \(fileId) PROCESSING_ERROR: \(error.localizedDescription)")
|
|
212
|
+
reject("PROCESSING_ERROR", "Failed to process the frame data: \(error.localizedDescription)", error)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
print("🔍 [WeatherFrameProcessor] \(fileId) URLSession.dataTask.resume() (runToken=\(taskToken))")
|
|
216
|
+
task.resume()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@objc
|
|
220
|
+
static func requiresMainQueueSetup() -> Bool {
|
|
221
|
+
return false
|
|
222
|
+
}
|
|
189
223
|
}
|