@aguacerowx/mapsgl 0.0.54 → 0.0.55

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aguacerowx/mapsgl",
3
- "version": "0.0.54",
3
+ "version": "0.0.55",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -10,6 +10,7 @@
10
10
  "scripts": {
11
11
  "prepublishOnly": "npm run bundle-nexrad",
12
12
  "bundle-nexrad": "esbuild src/nexrad/radarDecode.worker.ts --bundle --format=esm --platform=browser --outfile=src/nexrad/radarDecode.worker.bundled.js && esbuild src/nexrad/radarArchiveCore.ts --bundle --format=esm --platform=browser --outfile=src/nexrad/radarArchiveCore.bundled.js --external:@aguacerowx/javascript-sdk && esbuild src/nexrad/MapboxRadarLayer.ts --bundle --format=esm --platform=browser --outfile=src/nexrad/MapboxRadarLayer.bundled.js --external:mapbox-gl --external:@aguacerowx/javascript-sdk && esbuild src/nexrad/nexradCrossSectionSampleAtLatLon.ts --format=esm --platform=browser --outfile=src/nexrad/nexradCrossSectionSampleAtLatLon.bundled.js && esbuild src/nexrad/radarFrameGpuMatch.ts --format=esm --platform=browser --outfile=src/nexrad/radarFrameGpuMatch.bundled.js && esbuild src/nexrad/nexradMapboxFrameOpts.ts --bundle --format=esm --platform=browser --outfile=src/nexrad/nexradMapboxFrameOpts.bundled.js --external:@aguacerowx/javascript-sdk",
13
+ "bundle-nexrad-rn": "node scripts/bundle-nexrad-rn.mjs",
13
14
  "gen:nws-key": "esbuild ../../../aguacero-frontend/src/components/WarningsMenu/nwsWarningCustomizationKey.ts --bundle --format=esm --platform=neutral --outfile=src/nwsWarningCustomizationKey.gen.js"
14
15
  },
15
16
  "files": [
@@ -21,7 +22,7 @@
21
22
  "esbuild": "^0.21.5"
22
23
  },
23
24
  "dependencies": {
24
- "@aguacerowx/javascript-sdk": "^0.0.24",
25
+ "@aguacerowx/javascript-sdk": "^0.0.25",
25
26
  "buffer": "^6.0.3",
26
27
  "fzstd": "^0.1.1",
27
28
  "mapbox-gl": "^3.4.0",
@@ -3,7 +3,13 @@
3
3
  * Preloads all timestamps on the active timeline (MRMS/satellite-style) so scrubbing does not trigger loads.
4
4
  */
5
5
  import { getUnitConversionFunction, getDefaultRadarTilt } from '@aguacerowx/javascript-sdk';
6
- import { fetchAndParseArchive, objectKeyToUrl, setNexradArchiveApiKey } from './nexrad/radarArchiveCore.bundled.js';
6
+ import {
7
+ fetchAndParseArchive,
8
+ objectKeyToUrl,
9
+ setNexradArchiveApiKey,
10
+ setNexradArchiveBundleId,
11
+ setNexradSitesFetchAuth,
12
+ } from './nexrad/radarArchiveCore.bundled.js';
7
13
  import { MapboxRadarLayer } from './nexrad/MapboxRadarLayer.bundled.js';
8
14
  import { nexradBinGroupIdForKey, variableToNexradGroup } from '@aguacerowx/javascript-sdk';
9
15
  import { sampleNexradFrameAtLatLon } from './nexrad/nexradCrossSectionSampleAtLatLon.bundled.js';
@@ -268,6 +274,8 @@ export class NexradWeatherController {
268
274
  this._frameCache.clear();
269
275
 
270
276
  setNexradArchiveApiKey(this.core.apiKey || '');
277
+ setNexradArchiveBundleId(this.core.bundleId || '');
278
+ setNexradSitesFetchAuth(this.core.apiKey || '', this.core.bundleId || '');
271
279
 
272
280
  const times = [...(state.availableNexradTimestamps || [])]
273
281
  .map(Number)
@@ -353,6 +361,8 @@ export class NexradWeatherController {
353
361
  }
354
362
 
355
363
  setNexradArchiveApiKey(this.core.apiKey || '');
364
+ setNexradArchiveBundleId(this.core.bundleId || '');
365
+ setNexradSitesFetchAuth(this.core.apiKey || '', this.core.bundleId || '');
356
366
 
357
367
  const unix = Number(state.nexradTimestamp);
358
368
  const p = this._buildFetchParamsForUnix(state, unix);
@@ -842,14 +842,6 @@ export class NwsWatchesWarningsOverlay {
842
842
 
843
843
  async _initialLoad() {
844
844
  if (this._destroyed || !this.options.enabled) return;
845
- try {
846
- const baseline = await this._fetchBaseline();
847
- if (baseline) {
848
- this._workingFc = baseline;
849
- }
850
- } catch (err) {
851
- console.warn('[NwsWatchesWarningsOverlay] Baseline fetch failed:', err);
852
- }
853
845
 
854
846
  const paintWhenReady = () => {
855
847
  this._ensureLayers();
@@ -862,6 +854,18 @@ export class NwsWatchesWarningsOverlay {
862
854
  paintWhenReady();
863
855
  }
864
856
 
857
+ void this._fetchBaseline()
858
+ .then((baseline) => {
859
+ if (this._destroyed || !this.options.enabled) return;
860
+ if (baseline) {
861
+ this._workingFc = baseline;
862
+ }
863
+ this._setWorkingData(this._workingFc);
864
+ })
865
+ .catch((err) => {
866
+ console.warn('[NwsWatchesWarningsOverlay] Baseline fetch failed:', err);
867
+ });
868
+
865
869
  this._connectSse();
866
870
  }
867
871
 
@@ -1,4 +1,6 @@
1
1
  import type { NexradSite } from './PreprocessedSweepParser.js';
2
+ import nexradSitesDefault from './nexradSitesDefault.json';
3
+ import { nexradArchiveDiag, redactApiKeyFromUrl } from './nexradArchiveDiag.js';
2
4
 
3
5
  type NexradSitesPayload = {
4
6
  sites?: NexradSite[];
@@ -13,22 +15,98 @@ export function setNexradSitesJsonUrl(url: string) {
13
15
  sitesUrl = url || sitesUrl;
14
16
  }
15
17
 
18
+ /**
19
+ * When sitesUrl is HTTPS (e.g. CloudFront), use the same auth as AguaceroCore grid fetches:
20
+ * `?apiKey=`, `x-api-key`, and `x-app-identifier` on React Native when bundle id is set.
21
+ */
22
+ let SITES_FETCH_API_KEY = '';
23
+ let SITES_FETCH_BUNDLE_ID = '';
24
+ export function setNexradSitesFetchAuth(apiKey: string, bundleId?: string) {
25
+ SITES_FETCH_API_KEY = apiKey || '';
26
+ if (bundleId !== undefined) {
27
+ SITES_FETCH_BUNDLE_ID = bundleId || '';
28
+ }
29
+ }
30
+
16
31
  let nexradSitesPayloadPromise: Promise<NexradSitesPayload> | null = null;
17
32
 
33
+ function sitesFetchUrl(): string {
34
+ if (!sitesUrl.startsWith('http') || !SITES_FETCH_API_KEY) {
35
+ return sitesUrl;
36
+ }
37
+ const sep = sitesUrl.includes('?') ? '&' : '?';
38
+ return `${sitesUrl}${sep}apiKey=${SITES_FETCH_API_KEY}`;
39
+ }
40
+
41
+ function sitesFetchHeaders(): Record<string, string> | undefined {
42
+ if (!SITES_FETCH_API_KEY) {
43
+ return undefined;
44
+ }
45
+ const headers: Record<string, string> = {
46
+ 'x-api-key': SITES_FETCH_API_KEY,
47
+ };
48
+ const nav = (globalThis as { navigator?: { product?: string } }).navigator;
49
+ if (nav?.product === 'ReactNative' && SITES_FETCH_BUNDLE_ID) {
50
+ headers['x-app-identifier'] = SITES_FETCH_BUNDLE_ID;
51
+ }
52
+ return headers;
53
+ }
54
+
55
+ function embeddedSitesPayload(): NexradSitesPayload {
56
+ return nexradSitesDefault as NexradSitesPayload;
57
+ }
58
+
18
59
  export function loadNexradSitesPayload(): Promise<NexradSitesPayload> {
19
60
  if (nexradSitesPayloadPromise) {
20
61
  return nexradSitesPayloadPromise;
21
62
  }
22
- nexradSitesPayloadPromise = fetch(sitesUrl)
23
- .then((response) => {
24
- if (!response.ok) throw new Error(`nexrad.json fetch failed: HTTP ${response.status}`);
25
- return response.json() as Promise<NexradSitesPayload>;
26
- })
27
- .catch((error) => {
63
+ nexradSitesPayloadPromise = (async (): Promise<NexradSitesPayload> => {
64
+ const url = sitesFetchUrl();
65
+ const headers = sitesFetchHeaders();
66
+ const headerKeys = headers ? Object.keys(headers).join(',') : '';
67
+ nexradArchiveDiag('sites.fetch.start', {
68
+ canonicalUrl: sitesUrl,
69
+ fetchUrl: redactApiKeyFromUrl(url),
70
+ hasApiKeyQuery: url.includes('apiKey='),
71
+ headerKeys: headerKeys || '(none)',
72
+ apiKeyLen: SITES_FETCH_API_KEY.length,
73
+ bundleIdLen: SITES_FETCH_BUNDLE_ID.length,
74
+ });
75
+ try {
76
+ const response = await fetch(url, headers ? { headers } : undefined);
77
+ if (response.ok) {
78
+ nexradArchiveDiag('sites.fetch.ok', {
79
+ status: response.status,
80
+ canonicalUrl: sitesUrl,
81
+ });
82
+ return (await response.json()) as NexradSitesPayload;
83
+ }
84
+ if (sitesUrl.startsWith('https://')) {
85
+ console.warn(
86
+ `[mapsgl] nexrad.json HTTP ${response.status} for ${sitesUrl} — using embedded site list`,
87
+ );
88
+ nexradArchiveDiag('sites.fetch.fallbackEmbedded', {
89
+ httpStatus: response.status,
90
+ reason: 'HTTPS non-OK — CloudFront may not host /data/nexrad.json; embedded list used',
91
+ canonicalUrl: sitesUrl,
92
+ });
93
+ return embeddedSitesPayload();
94
+ }
95
+ throw new Error(`nexrad.json fetch failed: HTTP ${response.status}`);
96
+ } catch (error) {
97
+ if (sitesUrl.startsWith('https://')) {
98
+ console.warn('[mapsgl] nexrad.json load failed — using embedded site list:', error);
99
+ nexradArchiveDiag('sites.fetch.catchFallbackEmbedded', {
100
+ message: error instanceof Error ? error.message : String(error),
101
+ canonicalUrl: sitesUrl,
102
+ });
103
+ return embeddedSitesPayload();
104
+ }
28
105
  console.error('[mapsgl] Could not load nexrad.json:', error);
29
106
  nexradSitesPayloadPromise = null;
30
107
  throw error;
31
- });
108
+ }
109
+ })();
32
110
  return nexradSitesPayloadPromise;
33
111
  }
34
112
 
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Verbose NEXRAD archive pipeline logs for React Native (Hermes / Logcat).
3
+ * Filter: {@code [Aguacero][NEXRAD][archive]}
4
+ *
5
+ * Omits raw API keys; use lengths / booleans only.
6
+ */
7
+ export type NexradArchiveDiagDetail = Record<string, string | number | boolean | null | undefined>;
8
+
9
+ export function redactApiKeyFromUrl(u: string): string {
10
+ return u.replace(/([?&])apiKey=[^&]*/gi, '$1apiKey=(redacted)');
11
+ }
12
+
13
+ function isReactNative(): boolean {
14
+ const nav = (globalThis as { navigator?: { product?: string } }).navigator;
15
+ return nav?.product === 'ReactNative';
16
+ }
17
+
18
+ /** Logs only on React Native to avoid noisy web consoles (mapsgl path is already observable in DevTools). */
19
+ export function nexradArchiveDiag(phase: string, detail?: NexradArchiveDiagDetail): void {
20
+ if (!isReactNative()) return;
21
+ if (detail !== undefined) {
22
+ console.warn(`[Aguacero][NEXRAD][archive] ${phase}`, detail);
23
+ } else {
24
+ console.warn(`[Aguacero][NEXRAD][archive] ${phase}`);
25
+ }
26
+ }
@@ -221,7 +221,8 @@ function nexradLevel3S3ProductForSiteTilt(siteId, radarVariable, tilt) {
221
221
  return products[idx];
222
222
  }
223
223
  function mapboxFrameUploadOptionsForNexradState(state) {
224
- if (!state || state.nexradDataSource !== "level3") return void 0;
224
+ if (!state) return { geometryLayoutKey: "canonical" };
225
+ if (state.nexradDataSource !== "level3") return { geometryLayoutKey: "canonical" };
225
226
  const site = state.nexradSite;
226
227
  const radarVar = (state.nexradProduct || "REF").toUpperCase();
227
228
  if (nexradLevel3UsesTiltIndexedS3Products(radarVar) && site) {
@@ -235,7 +236,7 @@ function mapboxFrameUploadOptionsForNexradState(state) {
235
236
  const mnemonic = nexradLevel3S3VelocityProductForSiteTilt(site, tilt);
236
237
  return { geometryLayoutKey: `${site}|${radarVar}|${mnemonic}` };
237
238
  }
238
- return { geometryCacheKeysRayBoundaries: true };
239
+ return { geometryLayoutKey: "canonical" };
239
240
  }
240
241
  export {
241
242
  mapboxFrameUploadOptionsForNexradState,
@@ -104,7 +104,8 @@ type NexradStateForMapbox = {
104
104
  export function mapboxFrameUploadOptionsForNexradState(
105
105
  state: NexradStateForMapbox | null | undefined,
106
106
  ): MapboxRadarFrameUploadOptions | undefined {
107
- if (!state || state.nexradDataSource !== 'level3') return undefined;
107
+ if (!state) return { geometryLayoutKey: 'canonical' };
108
+ if (state.nexradDataSource !== 'level3') return { geometryLayoutKey: 'canonical' };
108
109
  const site = state.nexradSite;
109
110
  const radarVar = (state.nexradProduct || 'REF').toUpperCase();
110
111
 
@@ -121,5 +122,5 @@ export function mapboxFrameUploadOptionsForNexradState(
121
122
  return { geometryLayoutKey: `${site}|${radarVar}|${mnemonic}` };
122
123
  }
123
124
 
124
- return { geometryCacheKeysRayBoundaries: true };
125
+ return { geometryLayoutKey: 'canonical' };
125
126
  }