aviation-chart 2.0.33 → 2.0.34

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/dist/nav-sdk.d.ts CHANGED
@@ -964,6 +964,7 @@ declare class MapSDK implements IMapSDK {
964
964
  private _chartApiUrl;
965
965
  private _apiVersion;
966
966
  private _tileCipher;
967
+ private _stopAMMVersioning;
967
968
  readonly ready: Promise<void>;
968
969
  private _resolveReady;
969
970
  private _rejectReady;
@@ -9810,7 +9810,8 @@ class TileCipher {
9810
9810
  const blob = b64ToBytes(wrapped);
9811
9811
  return aesGcmDecrypt(this.customerKeyRaw, blob.slice(0, IV_LEN), blob.slice(IV_LEN));
9812
9812
  }
9813
- async decrypt(realUrl, envelope) {
9813
+ async decrypt(realUrl, envelope, signal) {
9814
+ signal?.throwIfAborted();
9814
9815
  const m = realUrl.match(/\/tiles\/e\/([^/]+)\//);
9815
9816
  const period = m?.[1];
9816
9817
  if (!period)
@@ -9825,13 +9826,103 @@ class TileCipher {
9825
9826
  const iv = bytes.slice(HEADER_LEN, HEADER_LEN + IV_LEN);
9826
9827
  const ct = bytes.slice(HEADER_LEN + IV_LEN);
9827
9828
  const key = await this.periodKey(period);
9829
+ signal?.throwIfAborted();
9828
9830
  let plain = await aesGcmDecrypt(key, iv, ct, aad);
9831
+ signal?.throwIfAborted();
9829
9832
  if (flags & FLAG_GZIP)
9830
9833
  plain = await gunzip(plain);
9834
+ signal?.throwIfAborted();
9831
9835
  return plain.buffer.slice(plain.byteOffset, plain.byteOffset + plain.byteLength);
9832
9836
  }
9833
9837
  }
9834
9838
 
9839
+ function createTileProtocol(cipher, fetchImpl = fetch) {
9840
+ return async (params, controller) => {
9841
+ const signal = controller.signal;
9842
+ signal.throwIfAborted();
9843
+ const url = String(params.url).replace(/^kunyu:\/\//, "");
9844
+ const response = await fetchImpl(url, {
9845
+ signal,
9846
+ ...(params.headers ? { headers: params.headers } : {}),
9847
+ ...(params.credentials ? { credentials: params.credentials } : {}),
9848
+ });
9849
+ if (!response.ok)
9850
+ throw new Error(`tile ${response.status}`);
9851
+ const envelope = await response.arrayBuffer();
9852
+ signal.throwIfAborted();
9853
+ const data = envelope.byteLength ? await cipher.decrypt(url, envelope, signal) : new ArrayBuffer(0);
9854
+ signal.throwIfAborted();
9855
+ return {
9856
+ data,
9857
+ cacheControl: response.headers.get("Cache-Control"),
9858
+ expires: response.headers.get("Expires"),
9859
+ };
9860
+ };
9861
+ }
9862
+
9863
+ function watchAMMVersion(map, fetchImpl = fetch, intervalMs = 60000) {
9864
+ let stopped = false;
9865
+ let pending;
9866
+ let appliedSource;
9867
+ let appliedTiles = "";
9868
+ let appliedVersion = "";
9869
+ async function refresh() {
9870
+ if (stopped || pending)
9871
+ return;
9872
+ const source = map.getSource("amm");
9873
+ const definition = map.getStyle()?.sources["amm"];
9874
+ const tiles = definition?.tiles;
9875
+ const template = tiles?.[0];
9876
+ if (!source?.setTiles || !tiles || !template)
9877
+ return;
9878
+ const position = template.indexOf("/amm/{z}/{x}/{y}.pbf");
9879
+ if (position < 0 || template.startsWith("pmtiles:"))
9880
+ return;
9881
+ const controller = new AbortController();
9882
+ pending = controller;
9883
+ const timeout = setTimeout(() => controller.abort(), 10000);
9884
+ try {
9885
+ const response = await fetchImpl(template.slice(0, position) + "/amm/manifest", {
9886
+ cache: "no-store", signal: controller.signal,
9887
+ });
9888
+ if (!response.ok)
9889
+ return;
9890
+ const manifest = await response.json();
9891
+ if (stopped || controller.signal.aborted || map.getSource("amm") !== source ||
9892
+ !manifest?.available || typeof manifest.version !== "string" || !manifest.version)
9893
+ return;
9894
+ if (source === appliedSource && manifest.version === appliedVersion && JSON.stringify(tiles) === appliedTiles)
9895
+ return;
9896
+ const next = tiles.map(tile => {
9897
+ const [path, query = ""] = tile.split("?");
9898
+ const params = new URLSearchParams(query);
9899
+ params.set("v", manifest.version);
9900
+ return path + "?" + params.toString();
9901
+ });
9902
+ source.setTiles(next);
9903
+ appliedSource = source;
9904
+ appliedTiles = JSON.stringify(next);
9905
+ appliedVersion = manifest.version;
9906
+ }
9907
+ catch {
9908
+ }
9909
+ finally {
9910
+ clearTimeout(timeout);
9911
+ pending = undefined;
9912
+ }
9913
+ }
9914
+ const timer = setInterval(() => { void refresh(); }, intervalMs);
9915
+ const stop = () => {
9916
+ stopped = true;
9917
+ clearInterval(timer);
9918
+ pending?.abort();
9919
+ map.off("remove", stop);
9920
+ };
9921
+ map.on("remove", stop);
9922
+ void refresh();
9923
+ return stop;
9924
+ }
9925
+
9835
9926
  // ICAO aviation chart layer styles — token-driven multi-theme.
9836
9927
  //
9837
9928
  // 单一 buildLayers(T):图层结构只写一份(以 light 为准);凡随主题变化的值
@@ -11118,18 +11209,7 @@ class MapSDK {
11118
11209
  if (options.licenseKey) {
11119
11210
  try {
11120
11211
  this._tileCipher = new TileCipher(this._chartApiUrl, this._apiVersion, options.licenseKey);
11121
- const cipher = this._tileCipher;
11122
- _mlGlExports.addProtocol("kunyu", async (params) => {
11123
- const realUrl = String(params.url).replace(/^kunyu:\/\//, "");
11124
- const resp = await fetch(realUrl, { signal: params.signal });
11125
- if (!resp.ok)
11126
- throw new Error(`tile ${resp.status}`);
11127
- const buf = await resp.arrayBuffer();
11128
- if (buf.byteLength === 0)
11129
- return { data: null };
11130
- const data = await cipher.decrypt(realUrl, buf);
11131
- return { data };
11132
- });
11212
+ _mlGlExports.addProtocol("kunyu", createTileProtocol(this._tileCipher));
11133
11213
  }
11134
11214
  catch (e) {
11135
11215
  this._tileCipher = undefined;
@@ -11176,6 +11256,8 @@ class MapSDK {
11176
11256
  this.initializeChartFeatures();
11177
11257
  if (!options.style) {
11178
11258
  this.initializeAviationLayers();
11259
+ this._stopAMMVersioning?.();
11260
+ this._stopAMMVersioning = watchAMMVersion(this.map);
11179
11261
  }
11180
11262
  this.map.fire("loadComplete", { notam: this.notam, adsb: this.adsb });
11181
11263
  this.map.fire("aviation:ready", { notam: this.notam, adsb: this.adsb });
@@ -11769,6 +11851,8 @@ class MapSDK {
11769
11851
  destroy() {
11770
11852
  if (this.isDestroyed)
11771
11853
  return;
11854
+ this._stopAMMVersioning?.();
11855
+ this._stopAMMVersioning = undefined;
11772
11856
  this.globalConfig.setMapReinitCallback(null);
11773
11857
  this.pluginManager.destroy();
11774
11858
  this.performanceManager.destroy();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aviation-chart",
3
- "version": "2.0.33",
3
+ "version": "2.0.34",
4
4
  "description": "KunYu aviation map SDK",
5
5
  "main": "dist/nav-sdk.esm.js",
6
6
  "module": "dist/nav-sdk.esm.js",
@@ -19,6 +19,7 @@
19
19
  "dist/sdk.style.css"
20
20
  ],
21
21
  "scripts": {
22
+ "test:unit": "node --test tests/*.test.mjs",
22
23
  "dev": "vite",
23
24
  "build:dev": "rollup -c --environment NODE_ENV:development && node scripts/version-dist.mjs",
24
25
  "build:prod": "rollup -c --environment NODE_ENV:production && node scripts/version-dist.mjs",