@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.
Files changed (56) hide show
  1. package/android/src/main/java/com/aguacerowx/reactnative/SatelliteLayerView.java +11 -3
  2. package/android/src/main/java/com/aguacerowx/reactnative/WeatherFrameProcessorModule.java +315 -275
  3. package/ios/SatelliteLayerView.swift +11 -4
  4. package/ios/WeatherFrameProcessorModule.swift +222 -188
  5. package/lib/commonjs/WeatherLayerManager.js +112 -48
  6. package/lib/commonjs/WeatherLayerManager.js.map +1 -1
  7. package/lib/commonjs/aguaceroCoreDebugHooks.js +144 -0
  8. package/lib/commonjs/aguaceroCoreDebugHooks.js.map +1 -0
  9. package/lib/commonjs/aguaceroRnDebug.js +358 -0
  10. package/lib/commonjs/aguaceroRnDebug.js.map +1 -0
  11. package/lib/commonjs/gridCdnAuth.js +64 -0
  12. package/lib/commonjs/gridCdnAuth.js.map +1 -0
  13. package/lib/commonjs/index.js +50 -0
  14. package/lib/commonjs/index.js.map +1 -1
  15. package/lib/commonjs/nexrad/nexradAndroidController.js +38 -25
  16. package/lib/commonjs/nexrad/nexradAndroidController.js.map +1 -1
  17. package/lib/commonjs/nexrad/nexradDiag.js +31 -25
  18. package/lib/commonjs/nexrad/nexradDiag.js.map +1 -1
  19. package/lib/commonjs/satellite/satelliteAndroidController.js +24 -15
  20. package/lib/commonjs/satellite/satelliteAndroidController.js.map +1 -1
  21. package/lib/module/WeatherLayerManager.js +112 -48
  22. package/lib/module/WeatherLayerManager.js.map +1 -1
  23. package/lib/module/aguaceroCoreDebugHooks.js +136 -0
  24. package/lib/module/aguaceroCoreDebugHooks.js.map +1 -0
  25. package/lib/module/aguaceroRnDebug.js +341 -0
  26. package/lib/module/aguaceroRnDebug.js.map +1 -0
  27. package/lib/module/gridCdnAuth.js +56 -0
  28. package/lib/module/gridCdnAuth.js.map +1 -0
  29. package/lib/module/index.js +2 -0
  30. package/lib/module/index.js.map +1 -1
  31. package/lib/module/nexrad/nexradAndroidController.js +38 -25
  32. package/lib/module/nexrad/nexradAndroidController.js.map +1 -1
  33. package/lib/module/nexrad/nexradDiag.js +31 -25
  34. package/lib/module/nexrad/nexradDiag.js.map +1 -1
  35. package/lib/module/satellite/satelliteAndroidController.js +24 -15
  36. package/lib/module/satellite/satelliteAndroidController.js.map +1 -1
  37. package/lib/typescript/WeatherLayerManager.d.ts.map +1 -1
  38. package/lib/typescript/aguaceroCoreDebugHooks.d.ts +10 -0
  39. package/lib/typescript/aguaceroCoreDebugHooks.d.ts.map +1 -0
  40. package/lib/typescript/aguaceroRnDebug.d.ts +97 -0
  41. package/lib/typescript/aguaceroRnDebug.d.ts.map +1 -0
  42. package/lib/typescript/gridCdnAuth.d.ts +24 -0
  43. package/lib/typescript/gridCdnAuth.d.ts.map +1 -0
  44. package/lib/typescript/index.d.ts +2 -0
  45. package/lib/typescript/nexrad/nexradAndroidController.d.ts.map +1 -1
  46. package/lib/typescript/nexrad/nexradDiag.d.ts.map +1 -1
  47. package/lib/typescript/satellite/satelliteAndroidController.d.ts.map +1 -1
  48. package/package.json +1 -1
  49. package/src/WeatherLayerManager.js +2024 -1947
  50. package/src/aguaceroCoreDebugHooks.js +142 -0
  51. package/src/aguaceroRnDebug.js +335 -0
  52. package/src/gridCdnAuth.js +56 -0
  53. package/src/index.js +19 -7
  54. package/src/nexrad/nexradAndroidController.js +1078 -1068
  55. package/src/nexrad/nexradDiag.js +150 -144
  56. package/src/satellite/satelliteAndroidController.js +245 -236
@@ -14,6 +14,9 @@ import { SatelliteAndroidController } from './satellite/satelliteAndroidControll
14
14
  import NwsAlertsOverlay from './nws/NwsAlertsOverlay';
15
15
  import { mapRegistry } from './MapRegistry';
16
16
  import { satBridgeWarn } from './satelliteBridgeDiag';
17
+ import { augmentProcessFrameOptionsForDebug, aguaceroDebugWarn, configureAguaceroRnDebug, getAguaceroAuthDiagnosticSnapshot, isAguaceroRnDebugEnabled } from './aguaceroRnDebug';
18
+ import { installAguaceroCoreDebugHooks, logProcessFrameAuthMismatch } from './aguaceroCoreDebugHooks';
19
+ import { resolveGridRequestSiteOrigin } from './gridCdnAuth';
17
20
  const NEXRAD_NATIVE = Platform.OS === 'android' || Platform.OS === 'ios';
18
21
  const SATELLITE_NATIVE = Platform.OS === 'android' || Platform.OS === 'ios';
19
22
  satBridgeWarn('SDK fingerprint', {
@@ -22,9 +25,9 @@ satBridgeWarn('SDK fingerprint', {
22
25
  note: 'If you never see sat-bridge logs, the app bundle is not loading this WeatherLayerManager build.'
23
26
  });
24
27
 
25
- /**
26
- * Same filtering as {@link AguaceroCore#_getFilteredMrmsTimestampsForVariable} for older cores
27
- * where that helper (or {@code setMRMSDurationValue}) is missing from the prototype chain.
28
+ /**
29
+ * Same filtering as {@link AguaceroCore#_getFilteredMrmsTimestampsForVariable} for older cores
30
+ * where that helper (or {@code setMRMSDurationValue}) is missing from the prototype chain.
28
31
  */
29
32
  function getFilteredMrmsTimestampsCompat(core, variable) {
30
33
  const raw = core.mrmsStatus?.[variable];
@@ -39,9 +42,9 @@ function getFilteredMrmsTimestampsCompat(core, variable) {
39
42
  return list;
40
43
  }
41
44
 
42
- /**
43
- * Older npm installs may resolve an {@link AguaceroCore} without {@code setMRMSDurationValue} /
44
- * {@code setNexradDurationValue}; mirror those methods here using the same logic as the SDK.
45
+ /**
46
+ * Older npm installs may resolve an {@link AguaceroCore} without {@code setMRMSDurationValue} /
47
+ * {@code setNexradDurationValue}; mirror those methods here using the same logic as the SDK.
45
48
  */
46
49
  async function applyMrmsDurationValue(core, value) {
47
50
  if (typeof core.setMRMSDurationValue === 'function') {
@@ -106,12 +109,12 @@ const {
106
109
  InspectorModule
107
110
  } = NativeModules;
108
111
 
109
- /**
110
- * `state:change` payloads can briefly have {@code isNexrad} before {@code nexradSite} / {@code nexradTimestamp}
111
- * are populated; {@code core.state} is updated first. Merge so readouts match the active radar.
112
- *
113
- * @param {object | null} emitted
114
- * @param {object | null} coreState
112
+ /**
113
+ * `state:change` payloads can briefly have {@code isNexrad} before {@code nexradSite} / {@code nexradTimestamp}
114
+ * are populated; {@code core.state} is updated first. Merge so readouts match the active radar.
115
+ *
116
+ * @param {object | null} emitted
117
+ * @param {object | null} coreState
115
118
  */
116
119
  function mergeNexradEmittedWithCore(emitted, coreState) {
117
120
  if (!emitted || !coreState) return null;
@@ -126,10 +129,10 @@ function mergeNexradEmittedWithCore(emitted, coreState) {
126
129
  };
127
130
  }
128
131
 
129
- /**
130
- * Compact timeline identity for deduping {@code state:change}: extending the NEXRAD/satellite window
131
- * often keeps the same selected unix — without this, {@link WeatherLayerManager} short-circuits and
132
- * never calls native {@code sync} / preload with the expanded frame list.
132
+ /**
133
+ * Compact timeline identity for deduping {@code state:change}: extending the NEXRAD/satellite window
134
+ * often keeps the same selected unix — without this, {@link WeatherLayerManager} short-circuits and
135
+ * never calls native {@code sync} / preload with the expanded frame list.
133
136
  */
134
137
  function nexradObsTimelineSig(state) {
135
138
  const arr = [...(state?.availableNexradTimestamps || [])].map(Number).filter(t => Number.isFinite(t)).sort((a, b) => a - b);
@@ -142,8 +145,9 @@ function satelliteObsTimelineSig(state) {
142
145
  return `${keys.length}:${keys[0]}:${keys[keys.length - 1]}`;
143
146
  }
144
147
 
145
- /** True in __DEV__, or set `globalThis.__AGUACERO_WX_GRID_DEBUG__ = true` in the app entry for release builds. */
148
+ /** True when {@link WeatherLayerManager} `debug` / {@link configureAguaceroRnDebug}, or legacy `__AGUACERO_WX_GRID_DEBUG__`. */
146
149
  function wxGridDebugEnabled() {
150
+ if (isAguaceroRnDebugEnabled()) return true;
147
151
  try {
148
152
  if (typeof __DEV__ !== 'undefined' && __DEV__) return true;
149
153
  return Boolean(typeof globalThis !== 'undefined' && globalThis.__AGUACERO_WX_GRID_DEBUG__);
@@ -169,37 +173,34 @@ function wxGridWarn(tag, detail) {
169
173
  }
170
174
  }
171
175
 
172
- /**
173
- * Native {@code processFrame} must mirror browser/AguaceroCore grid auth: encoded {@code apiKey}
174
- * in the query string and optional {@code Origin} / {@code Referer} (many CloudFront setups require them).
175
- *
176
- * @param {string} baseGridUrl
177
- * @param {string} resourcePath
178
- * @param {string} apiKey
179
- * @param {string | null | undefined} bundleId
180
- * @param {string | undefined} gridRequestSiteOrigin - e.g. production web origin your edge allowlists (no trailing slash)
176
+ /**
177
+ * Native {@code processFrame} must mirror browser/AguaceroCore grid auth: encoded {@code apiKey}
178
+ * in the query string and optional {@code Origin} / {@code Referer} (many CloudFront setups require them).
179
+ *
180
+ * @param {string} baseGridUrl
181
+ * @param {string} resourcePath
182
+ * @param {string} apiKey
183
+ * @param {string | null | undefined} bundleId
184
+ * @param {string | undefined} gridRequestSiteOrigin - prop or core origin (see {@link resolveGridRequestSiteOrigin})
185
+ * @param {import('@aguacerowx/javascript-sdk').AguaceroCore | null | undefined} core
181
186
  */
182
- function buildGridFrameProcessOptions(baseGridUrl, resourcePath, apiKey, bundleId, gridRequestSiteOrigin) {
183
- const url = `${baseGridUrl}${resourcePath}?apiKey=${encodeURIComponent(apiKey)}`;
187
+ function buildGridFrameProcessOptions(baseGridUrl, resourcePath, apiKey, bundleId, gridRequestSiteOrigin, core) {
188
+ const trimmedKey = typeof apiKey === 'string' ? apiKey.trim() : apiKey;
189
+ const url = `${baseGridUrl}${resourcePath}?apiKey=${encodeURIComponent(trimmedKey || '')}`;
184
190
  const options = {
185
191
  url,
186
- apiKey,
192
+ apiKey: trimmedKey,
187
193
  bundleId
188
194
  };
189
- if (typeof gridRequestSiteOrigin === 'string') {
190
- let origin = gridRequestSiteOrigin.trim();
191
- while (origin.endsWith('/')) {
192
- origin = origin.slice(0, -1);
193
- }
194
- if (origin.length > 0) {
195
- options.gridRequestSiteOrigin = origin;
196
- }
195
+ const origin = resolveGridRequestSiteOrigin(gridRequestSiteOrigin, core);
196
+ if (origin) {
197
+ options.gridRequestSiteOrigin = origin;
197
198
  }
198
199
  return options;
199
200
  }
200
201
 
201
- /**
202
- * A helper function to generate the raw RGBA byte buffer for the colormap texture.
202
+ /**
203
+ * A helper function to generate the raw RGBA byte buffer for the colormap texture.
203
204
  */
204
205
  const _generateColormapBytes = colormap => {
205
206
  const width = 256;
@@ -261,8 +262,15 @@ export const WeatherLayerManager = /*#__PURE__*/forwardRef((props, ref) => {
261
262
  onNwsAlertClick,
262
263
  /** Same semantics as mapsgl / AguaceroCore: Origin + Referer for grid CDN (required by many CloudFront rules). */
263
264
  gridRequestSiteOrigin,
265
+ /** When true, logs auth/HTTP diagnostics under `[AguaceroRN][debug]` (Metro / Logcat / Xcode). */
266
+ debug = false,
264
267
  ...restProps
265
268
  } = props;
269
+ useEffect(() => {
270
+ configureAguaceroRnDebug({
271
+ enabled: Boolean(debug)
272
+ });
273
+ }, [debug]);
266
274
  const context = useContext(AguaceroContext);
267
275
 
268
276
  // Create the core here instead of getting it from context
@@ -277,6 +285,24 @@ export const WeatherLayerManager = /*#__PURE__*/forwardRef((props, ref) => {
277
285
  },
278
286
  autoRefresh: false // <-- add this
279
287
  }), [apiKey, gridRequestSiteOrigin]);
288
+ useEffect(() => {
289
+ if (!core) return;
290
+ installAguaceroCoreDebugHooks(core, {
291
+ gridRequestSiteOriginProp: gridRequestSiteOrigin ?? null,
292
+ debugProp: Boolean(debug)
293
+ });
294
+ if (isAguaceroRnDebugEnabled() && !apiKey) {
295
+ aguaceroDebugWarn('WeatherLayerManager.missingApiKey', {
296
+ hint: 'apiKey prop is empty — all CDN requests will fail or return 403'
297
+ });
298
+ }
299
+ if (isAguaceroRnDebugEnabled() && apiKey && !gridRequestSiteOrigin) {
300
+ aguaceroDebugWarn('WeatherLayerManager.missingGridOrigin', {
301
+ hint: 'gridRequestSiteOrigin is not set — CloudFront often returns 403 without Origin/Referer on React Native',
302
+ snapshot: getAguaceroAuthDiagnosticSnapshot(core)
303
+ });
304
+ }
305
+ }, [core, debug, gridRequestSiteOrigin, apiKey]);
280
306
  const [watchesWarningsOptions, setWatchesWarningsOptions] = useState(() => ({
281
307
  alertInteractionEnabled: true,
282
308
  ...(watchesWarningsProp ?? {})
@@ -380,6 +406,8 @@ export const WeatherLayerManager = /*#__PURE__*/forwardRef((props, ref) => {
380
406
  // Track if we've done the initial load
381
407
  const hasInitialLoad = useRef(false);
382
408
  const hasPreloadedRef = useRef(false);
409
+ /** Bumped on {@code cancelAllFrames} / full reload so stale {@code processFrame} results are ignored. */
410
+ const preloadGenerationRef = useRef(0);
383
411
 
384
412
  // Track the last state we processed to avoid redundant updates
385
413
  const lastProcessedState = useRef(null);
@@ -460,9 +488,9 @@ export const WeatherLayerManager = /*#__PURE__*/forwardRef((props, ref) => {
460
488
  refreshData: () => {
461
489
  _checkForUpdates();
462
490
  },
463
- /**
464
- * NWS watches/warnings (native map, iOS + Android): same options as mapsgl {@link WeatherLayerManager#configureWatchesWarnings}.
465
- * @param {object} partial
491
+ /**
492
+ * NWS watches/warnings (native map, iOS + Android): same options as mapsgl {@link WeatherLayerManager#configureWatchesWarnings}.
493
+ * @param {object} partial
466
494
  */
467
495
  configureWatchesWarnings: partial => {
468
496
  setWatchesWarningsOptions(prev => ({
@@ -526,6 +554,8 @@ export const WeatherLayerManager = /*#__PURE__*/forwardRef((props, ref) => {
526
554
 
527
555
  // Only mark as "has preloaded" after validation passes
528
556
  hasPreloadedRef.current = true;
557
+ const generation = preloadGenerationRef.current;
558
+ const isStaleGeneration = () => generation !== preloadGenerationRef.current;
529
559
  wxGridVerbose('preloadStart', {
530
560
  isMRMS,
531
561
  model,
@@ -642,7 +672,11 @@ export const WeatherLayerManager = /*#__PURE__*/forwardRef((props, ref) => {
642
672
  } else {
643
673
  resourcePath = `/grids/${model}/${date}/${run}/${currentFrame}/${variable}/0`;
644
674
  }
645
- const options = buildGridFrameProcessOptions(core.baseGridUrl, resourcePath, core.apiKey, core.bundleId, gridRequestSiteOrigin);
675
+ const options = augmentProcessFrameOptionsForDebug(buildGridFrameProcessOptions(core.baseGridUrl, resourcePath, core.apiKey, core.bundleId, gridRequestSiteOrigin, core), core);
676
+ logProcessFrameAuthMismatch(core, options, {
677
+ phase: 'preloadCurrent',
678
+ currentCacheKey
679
+ });
646
680
  try {
647
681
  wxGridVerbose('processFrameRequest', {
648
682
  currentCacheKey,
@@ -650,6 +684,13 @@ export const WeatherLayerManager = /*#__PURE__*/forwardRef((props, ref) => {
650
684
  frame: currentFrame
651
685
  });
652
686
  const result = await WeatherFrameProcessorModule.processFrame(options);
687
+ if (isStaleGeneration()) {
688
+ wxGridVerbose('preloadCurrentFrameStale', {
689
+ currentCacheKey,
690
+ generation
691
+ });
692
+ return;
693
+ }
653
694
  if (!result || !result.filePath) {
654
695
  hasPreloadedRef.current = false;
655
696
  wxGridWarn('preloadAbort', {
@@ -747,12 +788,20 @@ export const WeatherLayerManager = /*#__PURE__*/forwardRef((props, ref) => {
747
788
  const cancelled = e && (e.code === 'E_CANCELLED' || e?.userInfo?.code === 'E_CANCELLED' || typeof e?.message === 'string' && e.message.includes('superseded'));
748
789
  if (!cancelled) {
749
790
  hasPreloadedRef.current = false;
750
- wxGridWarn('preloadProcessFrameError', {
791
+ const errDetail = {
751
792
  currentCacheKey,
752
793
  code: e?.code,
753
794
  message: e?.message,
754
795
  userInfo: e?.userInfo
755
- });
796
+ };
797
+ wxGridWarn('preloadProcessFrameError', errDetail);
798
+ if (isAguaceroRnDebugEnabled()) {
799
+ aguaceroDebugWarn('preloadProcessFrameError', {
800
+ ...errDetail,
801
+ auth: getAguaceroAuthDiagnosticSnapshot(core),
802
+ is403: e?.code === 'HTTP_ERROR' && typeof e?.message === 'string' && e.message.includes('403')
803
+ });
804
+ }
756
805
  } else {
757
806
  wxGridVerbose('preloadProcessFrameCancelled', {
758
807
  currentCacheKey
@@ -777,8 +826,15 @@ export const WeatherLayerManager = /*#__PURE__*/forwardRef((props, ref) => {
777
826
  } else {
778
827
  resourcePath = `/grids/${model}/${date}/${run}/${frame}/${variable}/0`;
779
828
  }
780
- const options = buildGridFrameProcessOptions(core.baseGridUrl, resourcePath, core.apiKey, core.bundleId, gridRequestSiteOrigin);
829
+ const options = augmentProcessFrameOptionsForDebug(buildGridFrameProcessOptions(core.baseGridUrl, resourcePath, core.apiKey, core.bundleId, gridRequestSiteOrigin, core), core);
830
+ logProcessFrameAuthMismatch(core, options, {
831
+ phase: 'preloadBackground',
832
+ cacheKey
833
+ });
781
834
  WeatherFrameProcessorModule.processFrame(options).then(result => {
835
+ if (isStaleGeneration()) {
836
+ return;
837
+ }
782
838
  if (!result || !result.filePath) {
783
839
  return;
784
840
  }
@@ -1166,7 +1222,11 @@ export const WeatherLayerManager = /*#__PURE__*/forwardRef((props, ref) => {
1166
1222
  m = (frameDate.getUTCMonth() + 1).toString().padStart(2, '0'),
1167
1223
  d = frameDate.getUTCDate().toString().padStart(2, '0');
1168
1224
  const resourcePath = `/grids/mrms/${y}${m}${d}/${frame}/0/${currentVariable}/0`;
1169
- const options = buildGridFrameProcessOptions(core.baseGridUrl, resourcePath, core.apiKey, core.bundleId, gridRequestSiteOrigin);
1225
+ const options = augmentProcessFrameOptionsForDebug(buildGridFrameProcessOptions(core.baseGridUrl, resourcePath, core.apiKey, core.bundleId, gridRequestSiteOrigin, core), core);
1226
+ logProcessFrameAuthMismatch(core, options, {
1227
+ phase: 'mrmsRefresh',
1228
+ cacheKey
1229
+ });
1170
1230
  WeatherFrameProcessorModule.processFrame(options).then(result => {
1171
1231
  if (!result || !result.filePath) return;
1172
1232
  const frameData = {
@@ -1431,6 +1491,7 @@ export const WeatherLayerManager = /*#__PURE__*/forwardRef((props, ref) => {
1431
1491
  }
1432
1492
  }
1433
1493
  hasPreloadedRef.current = false;
1494
+ preloadGenerationRef.current += 1;
1434
1495
  preloadedDataCache.current.clear();
1435
1496
  cachedGeometry.current = null;
1436
1497
  cachedColormap.current = null;
@@ -1438,7 +1499,8 @@ export const WeatherLayerManager = /*#__PURE__*/forwardRef((props, ref) => {
1438
1499
  WeatherFrameProcessorModule.cancelAllFrames();
1439
1500
  wxGridVerbose('cancelAllFrames', {
1440
1501
  reason: 'needsFullLoad',
1441
- variable: newState.variable
1502
+ variable: newState.variable,
1503
+ generation: preloadGenerationRef.current
1442
1504
  });
1443
1505
  if (!newState.variable) {
1444
1506
  previousStateRef.current = newState;
@@ -1460,6 +1522,8 @@ export const WeatherLayerManager = /*#__PURE__*/forwardRef((props, ref) => {
1460
1522
  mrmsTimestamp: newState.mrmsTimestamp,
1461
1523
  forecastHour: newState.forecastHour
1462
1524
  });
1525
+ hasPreloadedRef.current = false;
1526
+ void preloadAllFramesToDisk(newState);
1463
1527
  }
1464
1528
  if (success && newState.opacity !== renderProps.opacity) {
1465
1529
  setRenderProps(prev => ({