@superrouteca/smap 1.0.0 → 1.0.1

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/CHANGELOG.md CHANGED
@@ -1,6 +1,25 @@
1
1
  # Changelog
2
2
 
3
- ## 1.0.0 (unreleased)
3
+ ## 1.0.1 — 2026-10-27
4
+
5
+ - Every map shows a small sMap badge (the sMap icon and name on a semi-transparent pill that reads on all five colour
6
+ schemes) in its bottom-right corner, left of the attribution (above it when the map is too narrow for both). It is
7
+ always shown and is not a link; the map data attribution stays visible. The badge is styled by `smap.css` and loads
8
+ nothing.
9
+ - The package description starts with "sMap"; the README starts with the sMap logo.
10
+ - The browser build is in the package: `dist/smap.js` (global `sMap`), `dist/smap.css` and `dist/smap-client.js`
11
+ (global `SMapClient`, types in `dist/smap-client.d.ts`). The map service no longer serves the SDK.
12
+ - A map reads from `https://tile.srsr.ca` unless `baseUrl` is given, wherever the script was loaded from.
13
+ - The Web SDK reports the package version (1.0.1).
14
+ - The bundled map renderer is upgraded to its version 6.16.0, which fixes a security advisory in how it cleans the map
15
+ data credit (GHSA-jrc7-96c5-q579). Maps now need WebGL 2, and the renderer's worker starts from a `blob:` URL
16
+ (`worker-src blob:` under a Content Security Policy). Feature properties that hold objects reach `popup` and
17
+ `onClick` as objects instead of JSON text.
18
+ - A `popup` may return a DOM node as well as an HTML string; an HTML string is inserted as it is (never sanitized), so
19
+ it must be trusted.
20
+ - The browser build no longer sets a renderer global beside `sMap`; use `sMap.engine`.
21
+
22
+ ## 1.0.0 — 2026-10-27
4
23
 
5
24
  - The Web SDK as an npm package: ES module and CommonJS entry points with types, the map renderer bundled, safe to
6
25
  import on a server.
package/README.md CHANGED
@@ -1,14 +1,21 @@
1
- # @superrouteca/smap
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Goodneo-Solutions/smap-sdk/main/assets/smap-logo-dark.svg">
3
+ <img src="https://raw.githubusercontent.com/Goodneo-Solutions/smap-sdk/main/assets/smap-logo.svg" alt="sMap" height="56">
4
+ </picture>
5
+
6
+ # sMap for the web: `@superrouteca/smap`
2
7
 
3
8
  Maps from Superroute's sMap service for the web: five colour schemes, labels in any language, your points, lines and
4
9
  areas drawn on the GPU (hundreds of thousands of points, clustered or not), 3D buildings and areas, and tokens that
5
10
  renew themselves. Also the JS client (no map view) for servers and workers: tokens, the nearest node, styles, tiles
6
11
  and raw reads.
7
12
 
8
- - Version 1.0.0, MIT licence (see `LICENSE` at the repository root)
13
+ - Version 1.0.1, MIT licence (see `LICENSE` at the repository root)
9
14
  - Works with bundlers (Vite, webpack, Next.js and others): ES module and CommonJS entry points with types
10
15
  - Nothing else to install: the map renderer is bundled in the package
11
16
  - Importing is safe on a server; a map is created in the browser only
17
+ - Browsers with WebGL 2 (every current browser). The renderer's worker starts from a `blob:` URL: a page with a
18
+ Content Security Policy allows `worker-src blob:`
12
19
 
13
20
  ## Installation
14
21
 
@@ -16,13 +23,17 @@ and raw reads.
16
23
  npm install @superrouteca/smap
17
24
  ```
18
25
 
19
- Without a bundler, the same SDK is served by the service:
26
+ Without a bundler, use the browser build in the package (also on the downloads page of the Superroute Open Platform
27
+ developer center): `dist/smap.js` sets the global `sMap`, `dist/smap.css` is its stylesheet, and `dist/smap-client.js`
28
+ (types in `dist/smap-client.d.ts`) is the JS client as `window.SMapClient`. Host the files with your page:
20
29
 
21
30
  ```html
22
- <link rel="stylesheet" href="https://tile.srsr.ca/sdk/v1/smap.css">
23
- <script src="https://tile.srsr.ca/sdk/v1/smap.js"></script>
31
+ <link rel="stylesheet" href="smap.css">
32
+ <script src="smap.js"></script>
24
33
  ```
25
34
 
35
+ The map reads from the public sMap service (`https://tile.srsr.ca`) unless `baseUrl` names another.
36
+
26
37
  ## A minimal map
27
38
 
28
39
  ```js
@@ -46,6 +57,9 @@ map.setBuildings3D(true);
46
57
  map.fitLayer('stops');
47
58
  ```
48
59
 
60
+ A `popup` option returns an HTML string, which is inserted as it is and never sanitized (escape any feature data you
61
+ put in it), or a DOM node.
62
+
49
63
  `setStyle()` and `setLanguage()` keep your layers. Other calls: `setData`, `setVisible`, `removeLayer`, `layers`,
50
64
  `flyTo`, `setProjection`, `on` / `off` / `once`, `remove`. `map.engine` is the renderer's own map for anything the
51
65
  SDK does not wrap, and `getEngine()` gives the renderer itself.
@@ -79,12 +93,15 @@ Runs wherever `fetch` exists (browsers, Node.js 18+, workers); elsewhere pass `f
79
93
 
80
94
  ## Data attribution
81
95
 
82
- The map data credit stays visible on every map, as the data licence requires.
96
+ The map data credit stays visible on every map, as the data licence requires. Every map shows a small sMap badge in
97
+ its bottom-right corner, left of the attribution (above it when the map is too narrow for both).
83
98
 
84
99
  ## Where the code comes from
85
100
 
86
- `dist/` is copied from the sMap service repository by `tools/sync-js.sh`; `dist/SOURCE.json` records the commit.
87
- It is never edited here. The type declarations in `types/` are kept in this repository.
101
+ The sources are in `src/` (the Web SDK `smap.js` and `smap.css`, the JS client `smap-client.js` and its types).
102
+ `npm run build` writes `dist/` from them and the rendering engine (an exact devDependency); `dist/SOURCE.json`
103
+ records the version, the engine and the hash of every file. `dist/` is committed and never edited by hand. The type
104
+ declarations of the map SDK are in `types/`.
88
105
 
89
106
  ## Tests
90
107
 
package/dist/SOURCE.json CHANGED
@@ -1,18 +1,17 @@
1
1
  {
2
- "repository": "sMap",
3
- "commit": "54f7117da908fd02d8df09c85a61777299263e06",
4
- "commitSubject": "Version 1.0.1 (was 1.1.0, unreleased)",
5
- "uncommittedSources": false,
6
2
  "sdkVersion": "1.0.1",
7
- "engine": "maplibre-gl 5.24.0",
3
+ "engine": "maplibre-gl 6.16.0",
8
4
  "files": {
9
- "smap.mjs": "33375b7d5167ed8aab6e9351cf1e5074200d8654f91d4a5f0aa2c680ada603b8",
10
- "smap.cjs": "b878b5a59c6422ed5d98e95848755e549f473574d5366b3babe0a11796825acc",
11
- "smap.css": "ab1e70d59ec40465bae7e7030da2f3ccf28133fd502e62bd598eefbadfd7a732",
12
- "smap-client.cjs": "fdc7ab0c8e53a2e941c5d28af51fb6c0508b2690d0beec5369a9fa94ceba0392",
13
- "smap-client.mjs": "d0bd9ce3a42484888f7627d548f0cd0ff85854c2f9919be867175aae33bf8929",
14
- "smap-client.d.cts": "a87a12d46ac61971a5c294afe4df49c481552aed65c7404537dacdc0afaff512",
15
- "smap-client.d.mts": "009474b826fb3016b0200cbf40a969ea42ba331c6ff37cfb363c61ce54084e28",
5
+ "smap.js": "b48a84d65d3937c122f3f67b6cead8b9348913e2447782cca2a3f7a6c5d48ae7",
6
+ "smap.css": "558e15e5dfb3676e90f4cdfdb277c58f78c3d8de5527c8df1707f6c572241668",
7
+ "smap-client.js": "8029946bec62ce4806e47e2324f3020b3d049b0a73bb98352cdd307474ae776c",
8
+ "smap-client.d.ts": "ad701612630a290b2dc0e49250ae25cea965ecb3a145c016bb462cd30f108cf7",
9
+ "smap.mjs": "b59530982e07703ce3e3ffeaea328f18a86855e99076e737ee41c4dbd186104d",
10
+ "smap.cjs": "22f7b7248f40d5436cb1cd4c865f06e1a38ab8f9beed96ec3828abf058549a71",
11
+ "smap-client.cjs": "9956892bea8d367c1d278de11c3850293ad7d963d74d9692ce7194f6deff6f15",
12
+ "smap-client.mjs": "176a70faf2b4ecb3f998e5823202097b2a9d1bce546c41f6411f2567e2f7e5ae",
13
+ "smap-client.d.cts": "80b96f5a907caf6295ca073816dc526e8d285f22670c809adaab5242a018e686",
14
+ "smap-client.d.mts": "a66834cab868a6a0eaf943c1b185a236916cb00ecf32a871f972ab3213eddeba",
16
15
  "vendor-licenses.txt": "ee5fc05a0677eaf69601d2c7db0d9ecd6cc27c3abc1d0733bc9ed34707cf8ef2"
17
16
  }
18
17
  }
@@ -1,4 +1,4 @@
1
- /* Generated by tools/sync-js.sh from the sMap repository (see SOURCE.json). Do not edit. */
1
+ /* Generated by js/build.mjs from js/src (see SOURCE.json). Do not edit. */
2
2
  /*! sMap JS client 1.0.1 — tokens, the nearest node, styles, tiles and raw reads, without a map view. */
3
3
  /*
4
4
  * Runs wherever `fetch` exists: browsers, Node.js 18+, workers; elsewhere (mini programs, older runtimes) pass your
@@ -1,4 +1,4 @@
1
- // Generated by tools/sync-js.sh. Do not edit.
1
+ // Generated by js/build.mjs. Do not edit.
2
2
  // sMap JS client — type declarations (UMD: `require('smap-client')`, `import SMapClient from …`, `window.SMapClient`).
3
3
  declare namespace SMapClient {
4
4
  interface Options {
@@ -1,4 +1,4 @@
1
- // Generated by tools/sync-js.sh. Do not edit.
1
+ // Generated by js/build.mjs. Do not edit.
2
2
  // sMap JS client — type declarations (UMD: `require('smap-client')`, `import SMapClient from …`, `window.SMapClient`).
3
3
  declare namespace SMapClient {
4
4
  interface Options {
@@ -0,0 +1,42 @@
1
+ // sMap JS client — type declarations (UMD: `require('smap-client')`, `import SMapClient from …`, `window.SMapClient`).
2
+ declare namespace SMapClient {
3
+ interface Options {
4
+ /** The public sMap address, e.g. https://tile.srsr.ca */
5
+ baseUrl: string;
6
+ token?: string;
7
+ getToken?: () => string | Promise<string>;
8
+ /** A map key (`smk_…`); with a token both are sent and the token is checked first. */
9
+ apiKey?: string;
10
+ /** Node URLs known in advance, used while baseUrl is down. */
11
+ nodes?: string[];
12
+ /** Read from the nearest node (default true). */
13
+ nearest?: boolean;
14
+ fetch?: typeof fetch;
15
+ /** Per request, default 15000. */
16
+ timeoutMs?: number;
17
+ }
18
+ type StyleFlavor = 'light' | 'white' | 'grayscale' | 'dark' | 'black';
19
+ interface SMapError extends Error {
20
+ status: number;
21
+ url: string;
22
+ }
23
+ }
24
+
25
+ declare class SMapClient {
26
+ constructor(options: SMapClient.Options);
27
+ static readonly version: string;
28
+ static tokenExpiry(token: string): number;
29
+ readonly baseUrl: string;
30
+ token(): Promise<string | null>;
31
+ nodes(): Promise<string[]>;
32
+ resetNodes(): void;
33
+ request(path: string, headers?: Record<string, string>): Promise<Response>;
34
+ style(flavor?: SMapClient.StyleFlavor, language?: string): Promise<Record<string, unknown>>;
35
+ tileJSON(source?: string): Promise<Record<string, unknown>>;
36
+ tile(source: string, z: number, x: number, y: number): Promise<Uint8Array | null>;
37
+ range(source: string, start: number, end: number): Promise<Uint8Array>;
38
+ glyphs(fontstack: string, range: string): Promise<Uint8Array>;
39
+ authorize(url: string): Promise<{ url: string; headers: Record<string, string> }>;
40
+ }
41
+
42
+ export = SMapClient;
@@ -0,0 +1,300 @@
1
+ /*! sMap JS client 1.0.1 — tokens, the nearest node, styles, tiles and raw reads, without a map view. */
2
+ /*
3
+ * Runs wherever `fetch` exists: browsers, Node.js 18+, workers; elsewhere (mini programs, older runtimes) pass your
4
+ * own `fetch`. UMD: `window.SMapClient`, `require('smap-client')`, or an AMD module.
5
+ *
6
+ * const client = new SMapClient({ baseUrl: 'https://tile.srsr.ca', getToken: () => myServer.mapToken() });
7
+ * const keyed = new SMapClient({ baseUrl: 'https://tile.srsr.ca', apiKey: 'smk_…' }); // or a map key
8
+ * const tile = await client.tile('basemap', 12, 1143, 1493); // Uint8Array (vector tile)
9
+ * const style = await client.style('light', 'en'); // style document
10
+ *
11
+ * Every request goes to the nearest node that answers (asked of baseUrl, remembered for an hour) and moves to the
12
+ * next one when a node fails.
13
+ */
14
+ (function (root, factory) {
15
+ if (typeof define === 'function' && define.amd) { define([], factory); }
16
+ else if (typeof module === 'object' && module.exports) { module.exports = factory(); }
17
+ else { root.SMapClient = factory(); }
18
+ }(typeof self !== 'undefined' ? self : this, function () {
19
+ 'use strict';
20
+
21
+ var VERSION = '1.0.1';
22
+ var NODE_TTL = 3600 * 1000; // the measured order
23
+ var KNOWN_TTL = 30 * 86400 * 1000; // the last node list received, used when none can be read
24
+
25
+ function unique(list) {
26
+ var seen = {};
27
+ return list.filter(function (u) { if (!u || seen[u]) { return false; } seen[u] = true; return true; });
28
+ }
29
+ var memory = {};
30
+
31
+ function store() {
32
+ try {
33
+ if (typeof localStorage !== 'undefined') { localStorage.getItem('smap'); return localStorage; }
34
+ } catch (e) { /* storage unavailable */ }
35
+ return { getItem: function (k) { return memory[k] || null; }, setItem: function (k, v) { memory[k] = v; } };
36
+ }
37
+
38
+ function decodeBase64Url(s) {
39
+ s = s.replace(/-/g, '+').replace(/_/g, '/');
40
+ while (s.length % 4) { s += '='; }
41
+ if (typeof atob === 'function') { return atob(s); }
42
+ return Buffer.from(s, 'base64').toString('binary');
43
+ }
44
+
45
+ /** Seconds since 1970 when a token expires; 0 when it cannot be read. */
46
+ function tokenExpiry(token) {
47
+ try { return JSON.parse(decodeBase64Url(String(token).split('.')[1])).exp || 0; } catch (e) { return 0; }
48
+ }
49
+
50
+ function SMapError(message, status, url) {
51
+ var e = new Error(message);
52
+ e.name = 'SMapError';
53
+ e.status = status || 0;
54
+ e.url = url || '';
55
+ return e;
56
+ }
57
+
58
+ /**
59
+ * @param {Object} options
60
+ * @param {string} options.baseUrl the public sMap address (e.g. https://tile.srsr.ca)
61
+ * @param {string} [options.token] a map token; or
62
+ * @param {function(): (string|Promise<string>)} [options.getToken] called at start and before each token expires
63
+ * @param {string} [options.apiKey] a map key (`smk_…`); with a token both are sent (token first)
64
+ * @param {string[]} [options.nodes] node URLs known in advance (used while baseUrl is down)
65
+ * @param {boolean} [options.nearest=true] read from the nearest node
66
+ * @param {function} [options.fetch] a fetch implementation (default: the global one)
67
+ * @param {number} [options.timeoutMs=15000] per request
68
+ */
69
+ function SMapClient(options) {
70
+ if (!(this instanceof SMapClient)) { return new SMapClient(options); }
71
+ options = options || {};
72
+ if (!options.baseUrl) { throw SMapError('sMap: baseUrl is required'); }
73
+ this.baseUrl = String(options.baseUrl).replace(/\/$/, '');
74
+ this._fetch = options.fetch || (typeof fetch === 'function' ? fetch.bind(typeof self !== 'undefined' ? self : globalThis) : null);
75
+ if (!this._fetch) { throw SMapError('sMap: no fetch available; pass options.fetch'); }
76
+ this._token = options.token || null;
77
+ this._getToken = options.getToken || null;
78
+ this._apiKey = options.apiKey || null;
79
+ this._nearest = options.nearest !== false;
80
+ this._seeds = (options.nodes || []).map(function (u) { return String(u).replace(/\/$/, ''); });
81
+ this._timeout = options.timeoutMs || 15000;
82
+ this._nodes = null; // ordered: nearest first
83
+ this._picking = null;
84
+ this._tokening = null;
85
+ }
86
+
87
+ SMapClient.version = VERSION;
88
+ SMapClient.tokenExpiry = tokenExpiry;
89
+
90
+ SMapClient.prototype._timed = function (url, init, ms) {
91
+ var ctrl = typeof AbortController !== 'undefined' ? new AbortController() : null;
92
+ var timer = ctrl ? setTimeout(function () { ctrl.abort(); }, ms) : null;
93
+ init = init || {};
94
+ if (ctrl) { init.signal = ctrl.signal; }
95
+ return this._fetch(url, init).then(function (res) { clearTimeout(timer); return res; }, function (e) { clearTimeout(timer); throw e; });
96
+ };
97
+
98
+ /** The token to send now, asking getToken again two minutes before it expires. */
99
+ SMapClient.prototype.token = function () {
100
+ var self = this;
101
+ var exp = this._token ? tokenExpiry(this._token) : 0;
102
+ var fresh = this._token && (!exp || exp - Date.now() / 1000 > 120);
103
+ if (fresh || !this._getToken) { return Promise.resolve(this._token); }
104
+ if (!this._tokening) {
105
+ this._tokening = Promise.resolve(this._getToken()).then(function (t) {
106
+ self._token = t;
107
+ self._tokening = null;
108
+ return t;
109
+ }, function (e) { self._tokening = null; throw e; });
110
+ }
111
+ return this._tokening;
112
+ };
113
+
114
+ /**
115
+ * The nodes in the order they are tried: nearest first, then the others; baseUrl alone when nearest is off or the
116
+ * list cannot be read.
117
+ *
118
+ * @returns {Promise<string[]>}
119
+ */
120
+ SMapClient.prototype.nodes = function () {
121
+ var self = this;
122
+ if (this._nodes) { return Promise.resolve(this._nodes); }
123
+ if (!this._nearest) { this._nodes = [this.baseUrl]; return Promise.resolve(this._nodes); }
124
+ if (this._picking) { return this._picking; }
125
+ var key = 'smap.nodes:' + this.baseUrl;
126
+ var storage = store();
127
+ var saved = null;
128
+ try { saved = JSON.parse(storage.getItem(key) || 'null'); } catch (e) { saved = null; }
129
+ if (saved && saved.urls && saved.urls.length && Date.now() - saved.at < NODE_TTL) {
130
+ this._nodes = saved.urls;
131
+ return Promise.resolve(this._nodes);
132
+ }
133
+ // Every node this client knows of: the kept list (30 days), the seed list, then baseUrl.
134
+ var kept = saved && saved.known && Date.now() - (saved.knownAt || 0) < KNOWN_TTL ? saved.known : [];
135
+ var known = unique([this.baseUrl].concat(this._seeds, kept));
136
+ this._picking = this._discover(known)
137
+ .then(function (list) {
138
+ var urls = list || unique(self._seeds.concat(kept));
139
+ if (urls.length === 0) { return { order: [self.baseUrl], known: null }; }
140
+ return self._measure(urls).then(function (order) { return { order: order, known: list }; });
141
+ })
142
+ .then(function (r) {
143
+ self._nodes = r.order;
144
+ self._picking = null;
145
+ // An order is remembered for the hour only when a node list was read; a guess made while nothing answered
146
+ // is tried again by the next client instead of being kept.
147
+ var record = { urls: r.known ? r.order : [], at: r.known ? Date.now() : 0, known: r.known || kept, knownAt: r.known ? Date.now() : (saved && saved.knownAt) || 0 };
148
+ try { storage.setItem(key, JSON.stringify(record)); } catch (e) { /* ignore */ }
149
+ return r.order;
150
+ });
151
+ return this._picking;
152
+ };
153
+
154
+ /** Asks each node in turn for the node list (any node answers the whole list); null when none answers. */
155
+ SMapClient.prototype._discover = function (candidates) {
156
+ var self = this;
157
+ function ask(i) {
158
+ if (i >= candidates.length) { return Promise.resolve(null); }
159
+ return self._timed(candidates[i] + '/v1/nodes', {}, 2000)
160
+ .then(function (res) { return res.ok ? res.json() : null; })
161
+ .then(function (body) {
162
+ var urls = ((body && body.nodes) || []).map(function (n) { return String(n.url).replace(/\/$/, ''); });
163
+ return urls.length ? urls : ask(i + 1);
164
+ }, function () { return ask(i + 1); });
165
+ }
166
+ return ask(0);
167
+ };
168
+
169
+ /** Times one small request to each node: fastest first, silent ones after, baseUrl last when not listed. */
170
+ SMapClient.prototype._measure = function (urls) {
171
+ var self = this;
172
+ return Promise.all(urls.map(function (url) {
173
+ var t0 = Date.now();
174
+ return self._timed(url + '/v1/ping', { cache: 'no-store' }, 1500)
175
+ .then(function (res) { return { url: url, ms: res.ok ? Date.now() - t0 : Infinity }; }, function () { return { url: url, ms: Infinity }; });
176
+ })).then(function (timed) {
177
+ timed.sort(function (a, b) { return a.ms - b.ms; });
178
+ var order = timed.map(function (t) { return t.url; });
179
+ if (order.indexOf(self.baseUrl) < 0) { order.push(self.baseUrl); }
180
+ return order;
181
+ });
182
+ };
183
+
184
+ /** Forgets the chosen node order, so the next request measures again. */
185
+ SMapClient.prototype.resetNodes = function () {
186
+ this._nodes = null;
187
+ var key = 'smap.nodes:' + this.baseUrl;
188
+ try {
189
+ var saved = JSON.parse(store().getItem(key) || 'null');
190
+ // The kept node list survives a reset; only the measured order is forgotten.
191
+ store().setItem(key, JSON.stringify(saved ? { urls: [], at: 0, known: saved.known, knownAt: saved.knownAt } : null));
192
+ } catch (e) { /* ignore */ }
193
+ };
194
+
195
+ /**
196
+ * GET `path` from the nearest node that answers, with the token; a node that fails (network error, 5xx) is moved
197
+ * to the end and the next one is tried. 4xx answers are final.
198
+ *
199
+ * @param {string} path starting with /
200
+ * @param {Object} [headers]
201
+ * @returns {Promise<Response>}
202
+ */
203
+ SMapClient.prototype.request = function (path, headers) {
204
+ var self = this;
205
+ return Promise.all([this.nodes(), this.token()]).then(function (r) {
206
+ var nodes = r[0].slice();
207
+ var token = r[1];
208
+ var last = null;
209
+ function attempt(i) {
210
+ if (i >= nodes.length) { throw last || SMapError('sMap: no node answered', 0, path); }
211
+ var h = {};
212
+ for (var k in (headers || {})) { if (Object.prototype.hasOwnProperty.call(headers, k)) { h[k] = headers[k]; } }
213
+ if (token) { h.Authorization = 'Bearer ' + token; }
214
+ if (self._apiKey) { h['X-SMap-Key'] = self._apiKey; }
215
+ var url = nodes[i] + path;
216
+ return self._timed(url, { headers: h }, self._timeout).then(function (res) {
217
+ if (res.status >= 500) {
218
+ last = SMapError('sMap: HTTP ' + res.status, res.status, url);
219
+ self._demote(nodes[i]);
220
+ return attempt(i + 1);
221
+ }
222
+ if (!res.ok && res.status !== 206) {
223
+ return res.text().then(function (text) { throw SMapError('sMap: ' + (text || ('HTTP ' + res.status)), res.status, url); });
224
+ }
225
+ return res;
226
+ }, function (e) {
227
+ last = SMapError('sMap: ' + (e && e.message ? e.message : 'network error'), 0, url);
228
+ self._demote(nodes[i]);
229
+ return attempt(i + 1);
230
+ });
231
+ }
232
+ return attempt(0);
233
+ });
234
+ };
235
+
236
+ SMapClient.prototype._demote = function (url) {
237
+ if (!this._nodes || this._nodes.length < 2) { return; }
238
+ var i = this._nodes.indexOf(url);
239
+ if (i >= 0) { this._nodes.splice(i, 1); this._nodes.push(url); }
240
+ };
241
+
242
+ function bytes(res) { return res.arrayBuffer().then(function (b) { return new Uint8Array(b); }); }
243
+
244
+ // A map key also goes in the query of styles and TileJSON: the service carries it into their tile URLs, so a
245
+ // renderer given the document reads its tiles with the key.
246
+ SMapClient.prototype._withKey = function (path) {
247
+ if (!this._apiKey) { return path; }
248
+ return path + (path.indexOf('?') >= 0 ? '&' : '?') + 'key=' + encodeURIComponent(this._apiKey);
249
+ };
250
+
251
+ /**
252
+ * A style document ('light', 'white', 'grayscale', 'dark', 'black') with labels in `language`.
253
+ * Its tile URLs point at the node that answered.
254
+ */
255
+ SMapClient.prototype.style = function (flavor, language) {
256
+ var q = language ? '?lang=' + encodeURIComponent(language) : '';
257
+ return this.request(this._withKey('/v1/styles/' + encodeURIComponent(flavor || 'light') + q)).then(function (r) { return r.json(); });
258
+ };
259
+
260
+ /** TileJSON of a source (bounds, zooms, layers, tile URL template). */
261
+ SMapClient.prototype.tileJSON = function (source) {
262
+ return this.request(this._withKey('/v1/tiles/' + encodeURIComponent(source || 'basemap'))).then(function (r) { return r.json(); });
263
+ };
264
+
265
+ /** One vector tile as bytes; null where the source has no tile (204 / 404). */
266
+ SMapClient.prototype.tile = function (source, z, x, y) {
267
+ var path = '/v1/tiles/' + encodeURIComponent(source) + '/' + (z | 0) + '/' + (x | 0) + '/' + (y | 0);
268
+ return this.request(path).then(function (r) { return r.status === 204 ? null : bytes(r); }, function (e) {
269
+ if (e && e.status === 404) { return null; }
270
+ throw e;
271
+ });
272
+ };
273
+
274
+ /** Raw bytes `[start, end]` (inclusive) of a source's archive, where your access allows raw reads. */
275
+ SMapClient.prototype.range = function (source, start, end) {
276
+ return this.request('/' + encodeURIComponent(source) + '.pmtiles', { Range: 'bytes=' + start + '-' + end }).then(bytes);
277
+ };
278
+
279
+ /** A font glyph range (`0-255.pbf` style), for renderers that load fonts themselves. */
280
+ SMapClient.prototype.glyphs = function (fontstack, range) {
281
+ return this.request('/v1/fonts/' + encodeURIComponent(fontstack) + '/' + range + '.pbf').then(bytes);
282
+ };
283
+
284
+ /**
285
+ * Headers and URL rewriting for a renderer that fetches by itself: returns the URL unchanged plus the
286
+ * Authorization (and map key) headers to add for sMap URLs; none for any other host.
287
+ */
288
+ SMapClient.prototype.authorize = function (url) {
289
+ var self = this;
290
+ return this.token().then(function (token) {
291
+ var ours = url.indexOf(self.baseUrl) === 0 || self._seeds.concat(self._nodes || []).some(function (n) { return url.indexOf(n) === 0; });
292
+ var headers = {};
293
+ if (ours && token) { headers.Authorization = 'Bearer ' + token; }
294
+ if (ours && self._apiKey) { headers['X-SMap-Key'] = self._apiKey; }
295
+ return { url: url, headers: headers };
296
+ });
297
+ };
298
+
299
+ return SMapClient;
300
+ }));
@@ -1,4 +1,4 @@
1
- /* Generated by tools/sync-js.sh from the sMap repository (see SOURCE.json). Do not edit. */
1
+ /* Generated by js/build.mjs from js/src (see SOURCE.json). Do not edit. */
2
2
  var smapClientModule = { exports: {} };
3
3
  (function (module, exports, define) {
4
4
  /*! sMap JS client 1.0.1 — tokens, the nearest node, styles, tiles and raw reads, without a map view. */