@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
|
@@ -299,9 +299,17 @@ public class SatelliteLayerView extends FrameLayout {
|
|
|
299
299
|
reqBuilder.header("x-app-identifier", bundleId);
|
|
300
300
|
}
|
|
301
301
|
// Match WeatherFrameProcessorModule + AguaceroCore grid fetch (CloudFront allowlists on RN).
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
302
|
+
String gridOrigin = gridRequestSiteOrigin;
|
|
303
|
+
if (gridOrigin == null || gridOrigin.trim().isEmpty()) {
|
|
304
|
+
gridOrigin = "https://localhost";
|
|
305
|
+
}
|
|
306
|
+
gridOrigin = gridOrigin.trim();
|
|
307
|
+
while (gridOrigin.endsWith("/")) {
|
|
308
|
+
gridOrigin = gridOrigin.substring(0, gridOrigin.length() - 1);
|
|
309
|
+
}
|
|
310
|
+
if (!gridOrigin.isEmpty()) {
|
|
311
|
+
reqBuilder.header("Origin", gridOrigin);
|
|
312
|
+
reqBuilder.header("Referer", gridOrigin + "/");
|
|
305
313
|
}
|
|
306
314
|
Request req = reqBuilder.build();
|
|
307
315
|
Call call = HTTP.newCall(req);
|
|
@@ -1,276 +1,316 @@
|
|
|
1
|
-
package com.aguacerowx.reactnative;
|
|
2
|
-
|
|
3
|
-
import androidx.annotation.NonNull;
|
|
4
|
-
import android.util.Base64;
|
|
5
|
-
import android.util.Log;
|
|
6
|
-
|
|
7
|
-
import com.facebook.react.bridge.Arguments;
|
|
8
|
-
import com.facebook.react.bridge.Promise;
|
|
9
|
-
import com.facebook.react.bridge.ReactApplicationContext;
|
|
10
|
-
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
11
|
-
import com.facebook.react.bridge.ReactMethod;
|
|
12
|
-
import com.facebook.react.bridge.ReadableMap;
|
|
13
|
-
import com.facebook.react.bridge.WritableMap;
|
|
14
|
-
|
|
15
|
-
import org.json.JSONObject;
|
|
16
|
-
|
|
17
|
-
import java.io.File;
|
|
18
|
-
import java.io.FileOutputStream;
|
|
19
|
-
import java.io.IOException;
|
|
20
|
-
import java.util.Collections;
|
|
21
|
-
import java.util.Set;
|
|
22
|
-
import java.util.concurrent.ConcurrentHashMap;
|
|
23
|
-
import java.util.concurrent.ExecutorService;
|
|
24
|
-
import java.util.concurrent.Executors;
|
|
25
|
-
import java.util.concurrent.Semaphore;
|
|
26
|
-
import java.util.concurrent.TimeUnit;
|
|
27
|
-
import java.util.concurrent.atomic.AtomicInteger;
|
|
28
|
-
|
|
29
|
-
import okhttp3.Call;
|
|
30
|
-
import okhttp3.Callback;
|
|
31
|
-
import okhttp3.ConnectionPool;
|
|
32
|
-
import okhttp3.Dispatcher;
|
|
33
|
-
import okhttp3.OkHttpClient;
|
|
34
|
-
import okhttp3.Request;
|
|
35
|
-
import okhttp3.Response;
|
|
36
|
-
import okhttp3.ResponseBody;
|
|
37
|
-
|
|
38
|
-
public class WeatherFrameProcessorModule extends ReactContextBaseJavaModule {
|
|
39
|
-
|
|
40
|
-
private static final String TAG = "AguaceroWX_Perf";
|
|
41
|
-
|
|
42
|
-
private final
|
|
43
|
-
private final
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
private final
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
dispatcher
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
.
|
|
69
|
-
.
|
|
70
|
-
.
|
|
71
|
-
.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
semaphore
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
final
|
|
101
|
-
|
|
102
|
-
final String
|
|
103
|
-
final String
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
if (currentRunToken.get() != taskToken) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
promise.reject("
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
1
|
+
package com.aguacerowx.reactnative;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import android.util.Base64;
|
|
5
|
+
import android.util.Log;
|
|
6
|
+
|
|
7
|
+
import com.facebook.react.bridge.Arguments;
|
|
8
|
+
import com.facebook.react.bridge.Promise;
|
|
9
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
10
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
11
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
12
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
13
|
+
import com.facebook.react.bridge.WritableMap;
|
|
14
|
+
|
|
15
|
+
import org.json.JSONObject;
|
|
16
|
+
|
|
17
|
+
import java.io.File;
|
|
18
|
+
import java.io.FileOutputStream;
|
|
19
|
+
import java.io.IOException;
|
|
20
|
+
import java.util.Collections;
|
|
21
|
+
import java.util.Set;
|
|
22
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
23
|
+
import java.util.concurrent.ExecutorService;
|
|
24
|
+
import java.util.concurrent.Executors;
|
|
25
|
+
import java.util.concurrent.Semaphore;
|
|
26
|
+
import java.util.concurrent.TimeUnit;
|
|
27
|
+
import java.util.concurrent.atomic.AtomicInteger;
|
|
28
|
+
|
|
29
|
+
import okhttp3.Call;
|
|
30
|
+
import okhttp3.Callback;
|
|
31
|
+
import okhttp3.ConnectionPool;
|
|
32
|
+
import okhttp3.Dispatcher;
|
|
33
|
+
import okhttp3.OkHttpClient;
|
|
34
|
+
import okhttp3.Request;
|
|
35
|
+
import okhttp3.Response;
|
|
36
|
+
import okhttp3.ResponseBody;
|
|
37
|
+
|
|
38
|
+
public class WeatherFrameProcessorModule extends ReactContextBaseJavaModule {
|
|
39
|
+
|
|
40
|
+
private static final String TAG = "AguaceroWX_Perf";
|
|
41
|
+
/** CloudFront returns 403 without Origin; RN has no browser default. */
|
|
42
|
+
private static final String FALLBACK_GRID_REQUEST_SITE_ORIGIN = "https://localhost";
|
|
43
|
+
private final AtomicInteger currentRunToken = new AtomicInteger(0);
|
|
44
|
+
private final ReactApplicationContext reactContext;
|
|
45
|
+
private final OkHttpClient httpClient;
|
|
46
|
+
|
|
47
|
+
// FIX 1: Use an ExecutorService instead of "new Thread()".
|
|
48
|
+
// CachedThreadPool reuses threads and scales down when idle.
|
|
49
|
+
private final ExecutorService taskExecutor = Executors.newCachedThreadPool();
|
|
50
|
+
|
|
51
|
+
// FIX 2: Reduce concurrency to 4.
|
|
52
|
+
// 6 concurrent JSON parsings + Base64 decodings causes GC thrashing on mobile.
|
|
53
|
+
// 4 is the "sweet spot" for throughput vs memory.
|
|
54
|
+
private static final int MAX_CONCURRENT = 4;
|
|
55
|
+
private final Semaphore semaphore = new Semaphore(MAX_CONCURRENT, true);
|
|
56
|
+
|
|
57
|
+
private final Set<Call> activeCalls = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
|
58
|
+
|
|
59
|
+
WeatherFrameProcessorModule(ReactApplicationContext context) {
|
|
60
|
+
super(context);
|
|
61
|
+
this.reactContext = context;
|
|
62
|
+
|
|
63
|
+
Dispatcher dispatcher = new Dispatcher();
|
|
64
|
+
dispatcher.setMaxRequests(64);
|
|
65
|
+
dispatcher.setMaxRequestsPerHost(32);
|
|
66
|
+
|
|
67
|
+
this.httpClient = new OkHttpClient.Builder()
|
|
68
|
+
.dispatcher(dispatcher)
|
|
69
|
+
.connectionPool(new ConnectionPool(MAX_CONCURRENT, 30, TimeUnit.SECONDS))
|
|
70
|
+
.connectTimeout(15, TimeUnit.SECONDS)
|
|
71
|
+
.readTimeout(30, TimeUnit.SECONDS)
|
|
72
|
+
.writeTimeout(15, TimeUnit.SECONDS)
|
|
73
|
+
.build();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@ReactMethod
|
|
77
|
+
public void cancelAllFrames() {
|
|
78
|
+
currentRunToken.incrementAndGet();
|
|
79
|
+
|
|
80
|
+
for (Call call : activeCalls) {
|
|
81
|
+
if (!call.isCanceled()) {
|
|
82
|
+
call.cancel();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
activeCalls.clear();
|
|
86
|
+
|
|
87
|
+
// Release all semaphore permits to unblock the Executor queue
|
|
88
|
+
semaphore.drainPermits();
|
|
89
|
+
semaphore.release(MAX_CONCURRENT);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@NonNull
|
|
93
|
+
@Override
|
|
94
|
+
public String getName() {
|
|
95
|
+
return "WeatherFrameProcessorModule";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@ReactMethod
|
|
99
|
+
public void processFrame(ReadableMap options, Promise promise) {
|
|
100
|
+
final int taskToken = currentRunToken.get();
|
|
101
|
+
|
|
102
|
+
final String urlString = options.getString("url");
|
|
103
|
+
final String apiKey = options.getString("apiKey");
|
|
104
|
+
final String bundleId = options.hasKey("bundleId") ? options.getString("bundleId") : null;
|
|
105
|
+
final String gridRequestSiteOrigin =
|
|
106
|
+
options.hasKey("gridRequestSiteOrigin") && !options.isNull("gridRequestSiteOrigin")
|
|
107
|
+
? options.getString("gridRequestSiteOrigin")
|
|
108
|
+
: null;
|
|
109
|
+
final boolean debug =
|
|
110
|
+
options.hasKey("debug") && !options.isNull("debug") && options.getBoolean("debug");
|
|
111
|
+
|
|
112
|
+
// FIX 3: Submit to Executor instead of spinning up a raw OS thread
|
|
113
|
+
taskExecutor.execute(() -> {
|
|
114
|
+
try {
|
|
115
|
+
if (currentRunToken.get() != taskToken) {
|
|
116
|
+
promise.reject("E_CANCELLED", "Weather frame fetch superseded");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// This blocks the Executor thread (which is fine, it's a pool)
|
|
121
|
+
// until a slot is available
|
|
122
|
+
semaphore.acquire();
|
|
123
|
+
|
|
124
|
+
if (currentRunToken.get() != taskToken) {
|
|
125
|
+
semaphore.release();
|
|
126
|
+
promise.reject("E_CANCELLED", "Weather frame fetch superseded");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
Request.Builder requestBuilder = new Request.Builder()
|
|
131
|
+
.url(urlString)
|
|
132
|
+
.header("x-api-key", apiKey);
|
|
133
|
+
if (bundleId != null && !bundleId.isEmpty()) {
|
|
134
|
+
requestBuilder.header("x-app-identifier", bundleId);
|
|
135
|
+
}
|
|
136
|
+
String originSource = gridRequestSiteOrigin;
|
|
137
|
+
if (originSource == null || originSource.trim().isEmpty()) {
|
|
138
|
+
originSource = FALLBACK_GRID_REQUEST_SITE_ORIGIN;
|
|
139
|
+
}
|
|
140
|
+
String origin = originSource.trim();
|
|
141
|
+
while (origin.endsWith("/")) {
|
|
142
|
+
origin = origin.substring(0, origin.length() - 1);
|
|
143
|
+
}
|
|
144
|
+
if (!origin.isEmpty()) {
|
|
145
|
+
requestBuilder.header("Origin", origin);
|
|
146
|
+
requestBuilder.header("Referer", origin + "/");
|
|
147
|
+
}
|
|
148
|
+
Request request = requestBuilder.build();
|
|
149
|
+
|
|
150
|
+
if (debug) {
|
|
151
|
+
String redactedUrl = urlString.replaceAll("([?&])apiKey=[^&]*", "$1apiKey=(redacted)");
|
|
152
|
+
Log.w("AguaceroRN", "[debug][FrameProcessor] REQUEST "
|
|
153
|
+
+ "method=GET url=" + redactedUrl
|
|
154
|
+
+ " apiKey.len=" + (apiKey != null ? apiKey.length() : 0)
|
|
155
|
+
+ " bundleId=" + (bundleId != null && !bundleId.isEmpty() ? bundleId : "(none)")
|
|
156
|
+
+ " gridOrigin=" + (gridRequestSiteOrigin != null ? gridRequestSiteOrigin : "(none)")
|
|
157
|
+
+ " headers=[x-api-key, "
|
|
158
|
+
+ (bundleId != null && !bundleId.isEmpty() ? "x-app-identifier, " : "")
|
|
159
|
+
+ (gridRequestSiteOrigin != null ? "Origin, Referer" : "no Origin/Referer")
|
|
160
|
+
+ "]");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
Call call = httpClient.newCall(request);
|
|
164
|
+
activeCalls.add(call);
|
|
165
|
+
|
|
166
|
+
call.enqueue(new Callback() {
|
|
167
|
+
@Override
|
|
168
|
+
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
|
169
|
+
activeCalls.remove(call);
|
|
170
|
+
semaphore.release(); // IMPORTANT: Release immediately on fail
|
|
171
|
+
|
|
172
|
+
if (currentRunToken.get() != taskToken) {
|
|
173
|
+
promise.reject("E_CANCELLED", "Weather frame fetch superseded");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!call.isCanceled()) {
|
|
177
|
+
Log.e(TAG, "[FrameProcessor] Network failure: " + e.getMessage());
|
|
178
|
+
promise.reject("NETWORK_ERROR", e.getMessage(), e);
|
|
179
|
+
} else {
|
|
180
|
+
promise.reject("E_CANCELLED", "Weather frame fetch superseded");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@Override
|
|
185
|
+
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
|
186
|
+
activeCalls.remove(call);
|
|
187
|
+
// NOTE: We hold the semaphore until processing is done to prevent
|
|
188
|
+
// CPU saturation from JSON parsing.
|
|
189
|
+
|
|
190
|
+
// Check token immediately
|
|
191
|
+
if (currentRunToken.get() != taskToken) {
|
|
192
|
+
response.close();
|
|
193
|
+
semaphore.release();
|
|
194
|
+
promise.reject("E_CANCELLED", "Weather frame fetch superseded");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
FileOutputStream fos = null;
|
|
199
|
+
try {
|
|
200
|
+
if (!response.isSuccessful()) {
|
|
201
|
+
if (debug) {
|
|
202
|
+
String bodyPeek = "";
|
|
203
|
+
try {
|
|
204
|
+
ResponseBody peekBody = response.peekBody(512);
|
|
205
|
+
if (peekBody != null) {
|
|
206
|
+
bodyPeek = peekBody.string();
|
|
207
|
+
}
|
|
208
|
+
} catch (Exception ignored) {
|
|
209
|
+
bodyPeek = "(unreadable)";
|
|
210
|
+
}
|
|
211
|
+
String redactedUrl = urlString.replaceAll("([?&])apiKey=[^&]*", "$1apiKey=(redacted)");
|
|
212
|
+
Log.e("AguaceroRN", "[debug][FrameProcessor] HTTP_ERROR code=" + response.code()
|
|
213
|
+
+ " url=" + redactedUrl
|
|
214
|
+
+ " apiKey.len=" + (apiKey != null ? apiKey.length() : 0)
|
|
215
|
+
+ " bundleId=" + (bundleId != null && !bundleId.isEmpty() ? bundleId : "(none)")
|
|
216
|
+
+ " gridOrigin=" + (gridRequestSiteOrigin != null ? gridRequestSiteOrigin : "(none)")
|
|
217
|
+
+ " body=" + bodyPeek);
|
|
218
|
+
if (response.code() == 403) {
|
|
219
|
+
Log.e("AguaceroRN", "[debug][FrameProcessor] 403 hint: verify API key, allowlisted bundleId (x-app-identifier), and gridRequestSiteOrigin (Origin/Referer) match your web app.");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
promise.reject("HTTP_ERROR", "HTTP " + response.code());
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
ResponseBody body = response.body();
|
|
227
|
+
if (body == null) {
|
|
228
|
+
promise.reject("NO_DATA", "Response body was null");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
byte[] bodyBytes = body.bytes();
|
|
233
|
+
|
|
234
|
+
if (currentRunToken.get() != taskToken) {
|
|
235
|
+
promise.reject("E_CANCELLED", "Weather frame fetch superseded");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// JSON parse
|
|
240
|
+
String jsonString = new String(bodyBytes);
|
|
241
|
+
bodyBytes = null; // FIX 4: Nullify immediately to help GC
|
|
242
|
+
|
|
243
|
+
JSONObject jsonResponse = new JSONObject(jsonString);
|
|
244
|
+
jsonString = null; // FIX 4: Nullify immediately
|
|
245
|
+
|
|
246
|
+
if (!jsonResponse.has("data") || !jsonResponse.has("encoding")) {
|
|
247
|
+
promise.reject("INVALID_RESPONSE", "Invalid API response structure");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
String b64CompressedData = jsonResponse.getString("data");
|
|
252
|
+
JSONObject encoding = jsonResponse.getJSONObject("encoding");
|
|
253
|
+
|
|
254
|
+
// Don't need the huge JSON object anymore
|
|
255
|
+
jsonResponse = null;
|
|
256
|
+
|
|
257
|
+
// Base64 decode
|
|
258
|
+
byte[] compressedData = Base64.decode(b64CompressedData, Base64.DEFAULT);
|
|
259
|
+
b64CompressedData = null; // FIX 4: Nullify immediately
|
|
260
|
+
|
|
261
|
+
if (currentRunToken.get() != taskToken) {
|
|
262
|
+
promise.reject("E_CANCELLED", "Weather frame fetch superseded");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Disk write
|
|
267
|
+
File cacheDir = reactContext.getCacheDir();
|
|
268
|
+
// Use a unique name based on hash
|
|
269
|
+
String fileName = "frame_" + urlString.hashCode() + ".zst";
|
|
270
|
+
File dataFile = new File(cacheDir, fileName);
|
|
271
|
+
|
|
272
|
+
fos = new FileOutputStream(dataFile);
|
|
273
|
+
fos.write(compressedData);
|
|
274
|
+
fos.flush();
|
|
275
|
+
|
|
276
|
+
WritableMap responseMap = Arguments.createMap();
|
|
277
|
+
responseMap.putString("filePath", dataFile.getAbsolutePath());
|
|
278
|
+
responseMap.putDouble("scale", encoding.getDouble("scale"));
|
|
279
|
+
responseMap.putDouble("offset", encoding.getDouble("offset"));
|
|
280
|
+
responseMap.putDouble("missing", encoding.getDouble("missing_quantized"));
|
|
281
|
+
if (encoding.has("scale_type")) {
|
|
282
|
+
responseMap.putString("scaleType", encoding.getString("scale_type"));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (currentRunToken.get() != taskToken) {
|
|
286
|
+
promise.reject("E_CANCELLED", "Weather frame fetch superseded");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
promise.resolve(responseMap);
|
|
291
|
+
|
|
292
|
+
} catch (Exception e) {
|
|
293
|
+
if (currentRunToken.get() == taskToken) {
|
|
294
|
+
Log.e(TAG, "[FrameProcessor] Error: " + e.getMessage(), e);
|
|
295
|
+
promise.reject("PROCESSING_ERROR", e.getMessage(), e);
|
|
296
|
+
}
|
|
297
|
+
} finally {
|
|
298
|
+
// FIX 5: Ensure resources are closed and semaphore is released
|
|
299
|
+
if (fos != null) {
|
|
300
|
+
try { fos.close(); } catch (IOException ex) { /* ignore */ }
|
|
301
|
+
}
|
|
302
|
+
response.close();
|
|
303
|
+
semaphore.release();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
} catch (InterruptedException e) {
|
|
309
|
+
Thread.currentThread().interrupt();
|
|
310
|
+
// If interrupted while waiting for semaphore, just exit
|
|
311
|
+
} catch (Exception e) {
|
|
312
|
+
promise.reject("EXECUTION_ERROR", e.getMessage());
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
276
316
|
}
|