@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
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Per-core hooks for {@link AguaceroCore} when RN debug is enabled.
3
+ */
4
+ import {
5
+ aguaceroDebug,
6
+ aguaceroDebugWarn,
7
+ getAguaceroAuthDiagnosticSnapshot,
8
+ isAguaceroRnDebugEnabled,
9
+ redactApiKeyFromUrl,
10
+ } from './aguaceroRnDebug';
11
+
12
+ const HOOK_FLAG = '__aguaceroRnDebugHooksInstalled';
13
+
14
+ /**
15
+ * @param {import('@aguacerowx/javascript-sdk').AguaceroCore} core
16
+ * @param {Record<string, unknown>} [extra]
17
+ */
18
+ export function installAguaceroCoreDebugHooks(core, extra = {}) {
19
+ if (!core || !isAguaceroRnDebugEnabled() || core[HOOK_FLAG]) {
20
+ return;
21
+ }
22
+ core[HOOK_FLAG] = true;
23
+
24
+ aguaceroDebug('core.created', getAguaceroAuthDiagnosticSnapshot(core, extra));
25
+
26
+ if (typeof core.initialize === 'function' && !core.__aguaceroOrigInitialize) {
27
+ core.__aguaceroOrigInitialize = core.initialize.bind(core);
28
+ core.initialize = async function aguaceroDebugInitialize(options) {
29
+ aguaceroDebug('core.initialize.start', {
30
+ stateMode: {
31
+ isMRMS: core.state?.isMRMS,
32
+ isSatellite: core.state?.isSatellite,
33
+ isNexrad: core.state?.isNexrad,
34
+ model: core.state?.model,
35
+ variable: core.state?.variable,
36
+ },
37
+ });
38
+ try {
39
+ await core.__aguaceroOrigInitialize(options);
40
+ aguaceroDebug('core.initialize.done', {
41
+ hasModelStatus: Boolean(core.modelStatus),
42
+ hasMrmsStatus: Boolean(core.mrmsStatus),
43
+ hasSatelliteListing: Boolean(core.satelliteListing),
44
+ mrmsVariableCount: core.mrmsStatus ? Object.keys(core.mrmsStatus).length : 0,
45
+ });
46
+ } catch (err) {
47
+ aguaceroDebugWarn('core.initialize.error', { message: err?.message || String(err) });
48
+ throw err;
49
+ }
50
+ };
51
+ }
52
+
53
+ if (typeof core._loadGridData === 'function' && !core.__aguaceroOrigLoadGridData) {
54
+ core.__aguaceroOrigLoadGridData = core._loadGridData.bind(core);
55
+ core._loadGridData = async function aguaceroDebugLoadGridData(state) {
56
+ const tag = 'core._loadGridData';
57
+ aguaceroDebug(`${tag}.start`, {
58
+ isMRMS: state?.isMRMS,
59
+ model: state?.model,
60
+ variable: state?.variable,
61
+ mrmsTimestamp: state?.mrmsTimestamp,
62
+ forecastHour: state?.forecastHour,
63
+ auth: getAguaceroAuthDiagnosticSnapshot(core),
64
+ });
65
+ const result = await core.__aguaceroOrigLoadGridData(state);
66
+ if (result == null) {
67
+ aguaceroDebugWarn(`${tag}.null`, {
68
+ hint: 'Grid load returned null — often failed fetch (check fetch.httpError above) or missing apiKey',
69
+ });
70
+ } else {
71
+ aguaceroDebug(`${tag}.ok`, {
72
+ hasData: Boolean(result?.data),
73
+ encodingKeys: result?.encoding ? Object.keys(result.encoding) : [],
74
+ });
75
+ }
76
+ return result;
77
+ };
78
+ }
79
+
80
+ if (typeof core.refreshNexradTimes === 'function' && !core.__aguaceroOrigRefreshNexradTimes) {
81
+ core.__aguaceroOrigRefreshNexradTimes = core.refreshNexradTimes.bind(core);
82
+ core.refreshNexradTimes = async function aguaceroDebugRefreshNexradTimes() {
83
+ aguaceroDebug('core.refreshNexradTimes.start', {
84
+ site: core.state?.nexradSite,
85
+ product: core.state?.nexradProduct,
86
+ dataSource: core.state?.nexradDataSource,
87
+ });
88
+ try {
89
+ const out = await core.__aguaceroOrigRefreshNexradTimes();
90
+ aguaceroDebug('core.refreshNexradTimes.done', {
91
+ timesCount: out?.unixTimes?.length ?? 0,
92
+ });
93
+ return out;
94
+ } catch (err) {
95
+ aguaceroDebugWarn('core.refreshNexradTimes.error', { message: err?.message || String(err) });
96
+ throw err;
97
+ }
98
+ };
99
+ }
100
+
101
+ if (typeof core.setState === 'function' && !core.__aguaceroOrigSetState) {
102
+ core.__aguaceroOrigSetState = core.setState.bind(core);
103
+ core.setState = async function aguaceroDebugSetState(patch) {
104
+ const modeChange =
105
+ patch &&
106
+ ('isMRMS' in patch || 'isSatellite' in patch || 'isNexrad' in patch || 'variable' in patch);
107
+ if (modeChange) {
108
+ aguaceroDebug('core.setState.modePatch', {
109
+ keys: Object.keys(patch || {}),
110
+ patch: { ...patch, apiKey: undefined },
111
+ });
112
+ }
113
+ return core.__aguaceroOrigSetState(patch);
114
+ };
115
+ }
116
+ }
117
+
118
+ /**
119
+ * @param {import('@aguacerowx/javascript-sdk').AguaceroCore} core
120
+ */
121
+ export function logProcessFrameAuthMismatch(core, options, context = {}) {
122
+ if (!isAguaceroRnDebugEnabled() || !core || !options) return;
123
+ const mismatches = [];
124
+ if (options.apiKey && core.apiKey && options.apiKey !== core.apiKey) {
125
+ mismatches.push('processFrame.apiKey !== core.apiKey');
126
+ }
127
+ if (options.bundleId !== undefined && options.bundleId !== core.bundleId) {
128
+ mismatches.push('processFrame.bundleId !== core.bundleId');
129
+ }
130
+ const optOrigin = options.gridRequestSiteOrigin || '';
131
+ const coreOrigin = core.gridRequestSiteOrigin || '';
132
+ if (String(optOrigin) !== String(coreOrigin)) {
133
+ mismatches.push('processFrame.gridRequestSiteOrigin !== core.gridRequestSiteOrigin');
134
+ }
135
+ if (mismatches.length > 0) {
136
+ aguaceroDebugWarn('auth.mismatch', {
137
+ mismatches,
138
+ processFrameUrl: redactApiKeyFromUrl(options.url),
139
+ ...context,
140
+ });
141
+ }
142
+ }
@@ -0,0 +1,335 @@
1
+ /**
2
+ * React Native SDK auth / HTTP diagnostics.
3
+ *
4
+ * Enable in your app (pick one):
5
+ *
6
+ * 1. **Recommended** — pass `debug={true}` on {@link WeatherLayerManager}:
7
+ * ```jsx
8
+ * <WeatherLayerManager apiKey={key} debug gridRequestSiteOrigin="https://your-allowed-origin.com" />
9
+ * ```
10
+ *
11
+ * 2. **Global** — before rendering weather (works in release builds):
12
+ * ```js
13
+ * import { configureAguaceroRnDebug } from '@aguacerowx/react-native';
14
+ * configureAguaceroRnDebug({ enabled: true });
15
+ * ```
16
+ * or: `globalThis.__AGUACERO_DEBUG__ = true` in your entry file.
17
+ *
18
+ * Logs use the prefix `[AguaceroRN][debug]` (Metro, Xcode, Logcat).
19
+ * API keys are never printed in full — only length, fingerprint, and whitespace hints.
20
+ */
21
+
22
+ import { Platform } from 'react-native';
23
+ import { resolveGridRequestSiteOrigin } from './gridCdnAuth';
24
+
25
+ const LOG_PREFIX = '[AguaceroRN][debug]';
26
+
27
+ /** @type {boolean | null} */
28
+ let _explicitEnabled = null;
29
+
30
+ let _fetchLoggerInstalled = false;
31
+ let _fetchSeq = 0;
32
+
33
+ const AGUACERO_URL_MARKERS = [
34
+ 'cloudfront.net',
35
+ 'lambda-url.us-east-2.on.aws',
36
+ 'amazonaws.com',
37
+ 'noaa.gov',
38
+ ];
39
+
40
+ /**
41
+ * @param {boolean} enabled
42
+ */
43
+ export function setAguaceroRnDebugEnabled(enabled) {
44
+ configureAguaceroRnDebug({ enabled: Boolean(enabled) });
45
+ }
46
+
47
+ /**
48
+ * @param {{ enabled?: boolean }} opts
49
+ */
50
+ export function configureAguaceroRnDebug(opts = {}) {
51
+ if (opts && typeof opts.enabled === 'boolean') {
52
+ _explicitEnabled = opts.enabled;
53
+ }
54
+ const on = isAguaceroRnDebugEnabled();
55
+ try {
56
+ if (typeof globalThis !== 'undefined') {
57
+ globalThis.__AGUACERO_DEBUG__ = on;
58
+ globalThis.__AGUACERO_WX_GRID_DEBUG__ = on;
59
+ globalThis.__AGUACERO_NEXRAD_DEBUG__ = on;
60
+ }
61
+ } catch {
62
+ /* ignore */
63
+ }
64
+ if (on) {
65
+ installGlobalFetchLogger();
66
+ aguaceroDebug('debug.enabled', {
67
+ platform: Platform.OS,
68
+ explicitFlag: _explicitEnabled,
69
+ globalFlag: safeGlobalDebugFlag(),
70
+ });
71
+ }
72
+ }
73
+
74
+ /**
75
+ * True when `debug={true}`, {@link configureAguaceroRnDebug}, or `globalThis.__AGUACERO_DEBUG__ === true`.
76
+ * @returns {boolean}
77
+ */
78
+ export function isAguaceroRnDebugEnabled() {
79
+ if (_explicitEnabled === true) return true;
80
+ if (_explicitEnabled === false) {
81
+ try {
82
+ return typeof globalThis !== 'undefined' && globalThis.__AGUACERO_DEBUG__ === true;
83
+ } catch {
84
+ return false;
85
+ }
86
+ }
87
+ try {
88
+ if (typeof __DEV__ !== 'undefined' && __DEV__) return false;
89
+ } catch {
90
+ /* ignore */
91
+ }
92
+ try {
93
+ return typeof globalThis !== 'undefined' && globalThis.__AGUACERO_DEBUG__ === true;
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+
99
+ function safeGlobalDebugFlag() {
100
+ try {
101
+ return typeof globalThis !== 'undefined' ? globalThis.__AGUACERO_DEBUG__ === true : false;
102
+ } catch {
103
+ return false;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * @param {string | null | undefined} secret
109
+ * @returns {Record<string, unknown>}
110
+ */
111
+ export function describeSecret(secret) {
112
+ if (secret == null || secret === '') {
113
+ return { present: false, length: 0 };
114
+ }
115
+ const s = String(secret);
116
+ const trimmed = s.trim();
117
+ return {
118
+ present: true,
119
+ length: s.length,
120
+ trimmedLength: trimmed.length,
121
+ hasLeadingWhitespace: s.length > 0 && s !== trimmed && /^\s/.test(s),
122
+ hasTrailingWhitespace: s.length > 0 && s !== trimmed && /\s$/.test(s),
123
+ hasInternalWhitespace: /\s/.test(trimmed) && trimmed.indexOf(' ') >= 0,
124
+ fingerprint: fingerprintSecret(trimmed || s),
125
+ looksLikePlaceholder: /^(your[-_]?)?api[-_]?key|xxx+|test+$/i.test(trimmed),
126
+ };
127
+ }
128
+
129
+ /**
130
+ * @param {string} s
131
+ * @returns {string}
132
+ */
133
+ export function fingerprintSecret(s) {
134
+ if (!s) return '(empty)';
135
+ if (s.length <= 8) return `len${s.length}`;
136
+ return `${s.slice(0, 4)}…${s.slice(-4)} (len=${s.length})`;
137
+ }
138
+
139
+ /**
140
+ * @param {string} u
141
+ * @returns {string}
142
+ */
143
+ export function redactApiKeyFromUrl(u) {
144
+ if (!u || typeof u !== 'string') return String(u);
145
+ return u.replace(/([?&])apiKey=[^&]*/gi, '$1apiKey=(redacted)');
146
+ }
147
+
148
+ /**
149
+ * @param {string} url
150
+ * @returns {boolean}
151
+ */
152
+ export function shouldLogAguaceroUrl(url) {
153
+ if (!url || typeof url !== 'string') return false;
154
+ const lower = url.toLowerCase();
155
+ return AGUACERO_URL_MARKERS.some((m) => lower.includes(m));
156
+ }
157
+
158
+ /**
159
+ * @param {string} tag
160
+ * @param {Record<string, unknown> | undefined} detail
161
+ */
162
+ export function aguaceroDebug(tag, detail) {
163
+ if (!isAguaceroRnDebugEnabled()) return;
164
+ if (detail !== undefined) {
165
+ console.warn(`${LOG_PREFIX}[${tag}]`, detail);
166
+ } else {
167
+ console.warn(`${LOG_PREFIX}[${tag}]`);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Auth / HTTP failures: logged when debug is on (same prefix).
173
+ * @param {string} tag
174
+ * @param {Record<string, unknown> | undefined} detail
175
+ */
176
+ export function aguaceroDebugWarn(tag, detail) {
177
+ if (!isAguaceroRnDebugEnabled()) return;
178
+ if (detail !== undefined) {
179
+ console.warn(`${LOG_PREFIX}[WARN][${tag}]`, detail);
180
+ } else {
181
+ console.warn(`${LOG_PREFIX}[WARN][${tag}]`);
182
+ }
183
+ }
184
+
185
+ /**
186
+ * @param {import('@aguacerowx/javascript-sdk').AguaceroCore | { apiKey?: string; bundleId?: string | null; gridRequestSiteOrigin?: string | null; baseGridUrl?: string; isReactNative?: boolean }} core
187
+ * @param {Record<string, unknown>} [extra]
188
+ */
189
+ export function getAguaceroAuthDiagnosticSnapshot(core, extra = {}) {
190
+ const apiKey = core?.apiKey;
191
+ const bundleId = core?.bundleId;
192
+ const origin = core?.gridRequestSiteOrigin;
193
+ return {
194
+ platform: Platform.OS,
195
+ isReactNative: Boolean(core?.isReactNative),
196
+ baseGridUrl: core?.baseGridUrl ?? null,
197
+ apiKey: describeSecret(apiKey),
198
+ bundleId: bundleId
199
+ ? { present: true, value: String(bundleId), length: String(bundleId).length }
200
+ : { present: false, hint: 'Install react-native-device-info for x-app-identifier on CDN requests' },
201
+ gridRequestSiteOrigin: origin
202
+ ? { present: true, value: String(origin), length: String(origin).length }
203
+ : {
204
+ present: false,
205
+ hint: 'Pass gridRequestSiteOrigin on WeatherLayerManager (RN falls back to https://localhost if omitted)',
206
+ },
207
+ willSendAppIdentifier: Boolean(bundleId && core?.isReactNative),
208
+ willSendOriginHeaders: Boolean(origin && String(origin).trim()),
209
+ ...extra,
210
+ };
211
+ }
212
+
213
+ /**
214
+ * @param {Record<string, unknown>} options - {@link buildGridFrameProcessOptions} output
215
+ * @param {import('@aguacerowx/javascript-sdk').AguaceroCore} [core]
216
+ * @returns {Record<string, unknown>}
217
+ */
218
+ export function augmentProcessFrameOptionsForDebug(options, core) {
219
+ const out = { ...options };
220
+ if (!out.gridRequestSiteOrigin && core) {
221
+ const origin = resolveGridRequestSiteOrigin(undefined, core);
222
+ if (origin) {
223
+ out.gridRequestSiteOrigin = origin;
224
+ }
225
+ }
226
+ if (!isAguaceroRnDebugEnabled()) {
227
+ return out;
228
+ }
229
+ out.debug = true;
230
+ aguaceroDebug('processFrame.options', {
231
+ url: redactApiKeyFromUrl(out.url),
232
+ hasApiKeyInQuery: typeof out.url === 'string' && /[?&]apiKey=/i.test(out.url),
233
+ apiKey: describeSecret(out.apiKey),
234
+ bundleId: out.bundleId ? { present: true, value: String(out.bundleId) } : { present: false },
235
+ gridRequestSiteOrigin: out.gridRequestSiteOrigin ?? null,
236
+ coreSnapshot: core ? getAguaceroAuthDiagnosticSnapshot(core) : undefined,
237
+ });
238
+ return out;
239
+ }
240
+
241
+ /**
242
+ * @param {string | Request} input
243
+ * @param {RequestInit | undefined} init
244
+ */
245
+ function summarizeFetchRequest(input, init) {
246
+ const url = typeof input === 'string' ? input : input?.url;
247
+ const headers = new Headers(
248
+ (typeof input !== 'string' && input?.headers) || init?.headers || undefined,
249
+ );
250
+ const headerRecord = {};
251
+ headers.forEach((v, k) => {
252
+ const lk = k.toLowerCase();
253
+ if (lk === 'x-api-key' || lk === 'authorization') {
254
+ headerRecord[k] = describeSecret(v);
255
+ } else {
256
+ headerRecord[k] = v;
257
+ }
258
+ });
259
+ return {
260
+ url: redactApiKeyFromUrl(url || ''),
261
+ method: init?.method || (typeof input !== 'string' ? input?.method : undefined) || 'GET',
262
+ headers: headerRecord,
263
+ hasApiKeyQuery: typeof url === 'string' && /[?&]apiKey=/i.test(url),
264
+ };
265
+ }
266
+
267
+ /**
268
+ * Patches `global.fetch` once to log Aguacero CDN / API traffic when debug is enabled.
269
+ */
270
+ export function installGlobalFetchLogger() {
271
+ if (_fetchLoggerInstalled || !isAguaceroRnDebugEnabled()) return;
272
+ if (typeof globalThis === 'undefined' || typeof globalThis.fetch !== 'function') return;
273
+
274
+ const originalFetch = globalThis.fetch.bind(globalThis);
275
+ _fetchLoggerInstalled = true;
276
+
277
+ globalThis.fetch = async function aguaceroInstrumentedFetch(input, init) {
278
+ const url = typeof input === 'string' ? input : input?.url;
279
+ const shouldLog = shouldLogAguaceroUrl(url || '');
280
+ const reqId = shouldLog ? `f${++_fetchSeq}` : null;
281
+ const started = Date.now();
282
+
283
+ if (shouldLog) {
284
+ aguaceroDebug('fetch.start', { reqId, ...summarizeFetchRequest(input, init) });
285
+ }
286
+
287
+ try {
288
+ const response = await originalFetch(input, init);
289
+ if (shouldLog) {
290
+ const elapsedMs = Date.now() - started;
291
+ const base = {
292
+ reqId,
293
+ status: response.status,
294
+ statusText: response.statusText,
295
+ ok: response.ok,
296
+ elapsedMs,
297
+ url: redactApiKeyFromUrl(url || ''),
298
+ };
299
+ if (!response.ok) {
300
+ let bodySnippet = null;
301
+ try {
302
+ const clone = response.clone();
303
+ const text = await clone.text();
304
+ bodySnippet = text.length > 600 ? `${text.slice(0, 600)}…` : text;
305
+ } catch {
306
+ bodySnippet = '(could not read body)';
307
+ }
308
+ aguaceroDebugWarn('fetch.httpError', {
309
+ ...base,
310
+ bodySnippet,
311
+ hint403:
312
+ response.status === 403
313
+ ? '403 usually means: invalid/disabled API key, bundleId not allowlisted (x-app-identifier), or missing/wrong gridRequestSiteOrigin (Origin/Referer). Compare snapshot at core.created / processFrame.options.'
314
+ : undefined,
315
+ });
316
+ } else {
317
+ aguaceroDebug('fetch.ok', base);
318
+ }
319
+ }
320
+ return response;
321
+ } catch (err) {
322
+ if (shouldLog) {
323
+ aguaceroDebugWarn('fetch.throw', {
324
+ reqId,
325
+ message: err?.message || String(err),
326
+ elapsedMs: Date.now() - started,
327
+ url: redactApiKeyFromUrl(url || ''),
328
+ });
329
+ }
330
+ throw err;
331
+ }
332
+ };
333
+
334
+ aguaceroDebug('fetch.hookInstalled', { platform: Platform.OS });
335
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * CloudFront grid CDN auth helpers for React Native.
3
+ *
4
+ * The CDN returns HTTP 403 when {@code Origin} is missing (even if {@code x-api-key} and
5
+ * {@code apiKey} query are valid). Browsers set Origin automatically; RN native HTTP does not.
6
+ */
7
+
8
+ /** Fallback when no prop/core/global origin is configured (any origin satisfies the CDN). */
9
+ export const RN_DEFAULT_GRID_REQUEST_SITE_ORIGIN = 'https://localhost';
10
+
11
+ /**
12
+ * @returns {string}
13
+ */
14
+ export function readGlobalGridRequestSiteOrigin() {
15
+ try {
16
+ const g = globalThis.__AGUACERO_GRID_REQUEST_SITE_ORIGIN__;
17
+ if (typeof g === 'string' && g.trim()) {
18
+ return g.trim().replace(/\/+$/, '');
19
+ }
20
+ } catch {
21
+ /* ignore */
22
+ }
23
+ return '';
24
+ }
25
+
26
+ /**
27
+ * Resolve the site origin used for {@code Origin} / {@code Referer} on grid CDN requests.
28
+ *
29
+ * @param {string | undefined | null} propOrigin - {@link WeatherLayerManager} `gridRequestSiteOrigin` prop
30
+ * @param {{ gridRequestSiteOrigin?: string | null; isReactNative?: boolean } | null | undefined} core
31
+ * @returns {string | null} Normalized origin without trailing slash, or null on web when unset
32
+ */
33
+ export function resolveGridRequestSiteOrigin(propOrigin, core) {
34
+ const normalize = (s) => {
35
+ let o = String(s).trim();
36
+ while (o.endsWith('/')) {
37
+ o = o.slice(0, -1);
38
+ }
39
+ return o;
40
+ };
41
+
42
+ if (typeof propOrigin === 'string' && propOrigin.trim()) {
43
+ return normalize(propOrigin);
44
+ }
45
+ if (typeof core?.gridRequestSiteOrigin === 'string' && core.gridRequestSiteOrigin.trim()) {
46
+ return normalize(core.gridRequestSiteOrigin);
47
+ }
48
+ const fromGlobal = readGlobalGridRequestSiteOrigin();
49
+ if (fromGlobal) {
50
+ return fromGlobal;
51
+ }
52
+ if (core?.isReactNative) {
53
+ return RN_DEFAULT_GRID_REQUEST_SITE_ORIGIN;
54
+ }
55
+ return null;
56
+ }
package/src/index.js CHANGED
@@ -1,7 +1,19 @@
1
- export { MapManager } from './MapManager';
2
- export { WeatherLayerManager } from './WeatherLayerManager';
3
- export { default as GridRenderLayer } from './GridRenderLayerNativeComponent';
4
- export {
5
- AGUACERO_NEXRAD_MAP_LAYER_ID,
6
- AGUACERO_SATELLITE_MAP_LAYER_ID,
7
- } from './nws/nwsAndroidConstants';
1
+ export { MapManager } from './MapManager';
2
+ export { WeatherLayerManager } from './WeatherLayerManager';
3
+ export {
4
+ configureAguaceroRnDebug,
5
+ setAguaceroRnDebugEnabled,
6
+ isAguaceroRnDebugEnabled,
7
+ getAguaceroAuthDiagnosticSnapshot,
8
+ aguaceroDebug,
9
+ aguaceroDebugWarn,
10
+ } from './aguaceroRnDebug';
11
+ export {
12
+ resolveGridRequestSiteOrigin,
13
+ RN_DEFAULT_GRID_REQUEST_SITE_ORIGIN,
14
+ } from './gridCdnAuth';
15
+ export { default as GridRenderLayer } from './GridRenderLayerNativeComponent';
16
+ export {
17
+ AGUACERO_NEXRAD_MAP_LAYER_ID,
18
+ AGUACERO_SATELLITE_MAP_LAYER_ID,
19
+ } from './nws/nwsAndroidConstants';