pmtiles-swarm 0.13.1 → 0.14.0
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 +29 -0
- package/package.json +1 -1
- package/src/api.js +32 -0
- package/src/web/index.html +16 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,35 @@
|
|
|
7
7
|
### 🐞 Bug fixes
|
|
8
8
|
- _...Add new stuff here..._
|
|
9
9
|
|
|
10
|
+
## 0.14.0
|
|
11
|
+
### ✨ Features and improvements
|
|
12
|
+
- **The Categories page hands you the URL a style should actually use.** A new **For a style** row
|
|
13
|
+
gives the category's TileJSON URL with a magnet in its fragment, which is the string worth
|
|
14
|
+
copying — the plain TileJSON row is still there for anything that only speaks HTTP.
|
|
15
|
+
|
|
16
|
+
Where a publisher is announcing the category over the DHT, the fragment carries the **mutable**
|
|
17
|
+
magnet (`xs=urn:btpk:…&s=<category>`), which is the form a category needs: it names the category
|
|
18
|
+
rather than a build, so it does not go stale on the next one while the URL keeps following it.
|
|
19
|
+
Without a publisher it falls back to the newest build's own magnet, which pins that build but
|
|
20
|
+
still beats a blank map on the only occasion it is read at all.
|
|
21
|
+
|
|
22
|
+
Also on `GET /api/categories` as `endpoints.styleUrl`, and `null` for a category whose newest
|
|
23
|
+
archive is not PMTiles — the same rule the tile endpoints already follow.
|
|
24
|
+
|
|
25
|
+
The magnet was already available on an individual archive, but a category URL is what a style
|
|
26
|
+
points at, and that is the page where it was missing.
|
|
27
|
+
|
|
28
|
+
### 🐞 Bug fixes
|
|
29
|
+
- **CI and the release workflow install with `--ignore-scripts`.** The 0.14.0 release failed in
|
|
30
|
+
`npm ci` because `node-datachannel` — WebTorrent's WebRTC binary, four levels down the tree —
|
|
31
|
+
found no prebuilt binary for the runner and crashed trying to build from source. The same
|
|
32
|
+
lockfile had published minutes earlier, so it was the download rather than an incompatibility.
|
|
33
|
+
|
|
34
|
+
Nothing in CI needs that binary: the suite passes without it, and `npm publish` ships source
|
|
35
|
+
rather than `node_modules`, so install scripts have no bearing on what is published. A
|
|
36
|
+
third-party binary download should not be able to block a release. Whether WebRTC works on a
|
|
37
|
+
given machine is still checked where it matters, after installing on the host.
|
|
38
|
+
|
|
10
39
|
## 0.13.1
|
|
11
40
|
### 🐞 Bug fixes
|
|
12
41
|
- **A node with a secondary engine no longer takes a quarter of an hour to start listening.**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmtiles-swarm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "BitTorrent distribution for PMTiles map archives: create torrents, watch folders, publish and subscribe to RSS feeds, and seed through qBittorrent or an embedded client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/api.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
isPublicSurface,
|
|
15
15
|
} from './auth.js';
|
|
16
16
|
import { normalizeCategories } from './catalog.js';
|
|
17
|
+
import { mutableMagnet } from './mutable.js';
|
|
17
18
|
import { guessKind } from './library.js';
|
|
18
19
|
import { QBittorrentEngine } from './engines/qbittorrent.js';
|
|
19
20
|
import { RESTART_REQUIRED, redactConfig, saveConfig } from './config.js';
|
|
@@ -60,6 +61,25 @@ function route(handler) {
|
|
|
60
61
|
* @param {object} deps.config - Resolved configuration.
|
|
61
62
|
* @returns {import('express').Express} - The configured app.
|
|
62
63
|
*/
|
|
64
|
+
/**
|
|
65
|
+
* A TileJSON URL carrying a magnet in its fragment.
|
|
66
|
+
* @param {string} category - Which category.
|
|
67
|
+
* @param {object} newest - Its newest entry.
|
|
68
|
+
* @param {string} base - Public base URL.
|
|
69
|
+
* @returns {string} - The URL a style should use.
|
|
70
|
+
*/
|
|
71
|
+
function styleUrlFor(category, newest, base) {
|
|
72
|
+
const url = `${base}/latest/${category}/tiles.json`;
|
|
73
|
+
const magnet = newest?.mutable?.publicKey
|
|
74
|
+
? mutableMagnet(newest.mutable.publicKey, {
|
|
75
|
+
salt: newest.mutable.salt ?? category,
|
|
76
|
+
name: newest.name,
|
|
77
|
+
webSeeds: newest.webSeeds,
|
|
78
|
+
})
|
|
79
|
+
: newest?.magnet;
|
|
80
|
+
return magnet ? `${url}#${magnet}` : url;
|
|
81
|
+
}
|
|
82
|
+
|
|
63
83
|
export function createApp({
|
|
64
84
|
library,
|
|
65
85
|
catalog,
|
|
@@ -1703,6 +1723,18 @@ export function createApp({
|
|
|
1703
1723
|
servable,
|
|
1704
1724
|
endpoints: {
|
|
1705
1725
|
tileJson: servable ? `${base}/latest/${category}/tiles.json` : null,
|
|
1726
|
+
// The same URL with a magnet in the fragment, which is what a
|
|
1727
|
+
// style should carry. A fragment is never sent in a request, so
|
|
1728
|
+
// an ordinary client fetches the TileJSON and ignores it, while
|
|
1729
|
+
// a swarm-aware one has the magnet before it makes any call --
|
|
1730
|
+
// and can therefore still start when this server cannot answer.
|
|
1731
|
+
//
|
|
1732
|
+
// The mutable magnet where there is one, because a category is
|
|
1733
|
+
// precisely where an infohash goes stale: it names the category
|
|
1734
|
+
// and resolves the current build over the DHT. Otherwise the
|
|
1735
|
+
// newest build's own magnet, which pins that build but still
|
|
1736
|
+
// beats a blank map when the fallback is needed at all.
|
|
1737
|
+
styleUrl: servable ? styleUrlFor(category, newest, base) : null,
|
|
1706
1738
|
torrent: `${base}/latest/${category}/archive.torrent`,
|
|
1707
1739
|
magnet: `${base}/latest/${category}/magnet`,
|
|
1708
1740
|
feed: `${base}/feed/${category}.xml`,
|
package/src/web/index.html
CHANGED
|
@@ -2356,6 +2356,7 @@
|
|
|
2356
2356
|
const newest = entry.newest;
|
|
2357
2357
|
const rows = [
|
|
2358
2358
|
['TileJSON', entry.endpoints.tileJson],
|
|
2359
|
+
['For a style', entry.endpoints.styleUrl],
|
|
2359
2360
|
['Torrent', entry.endpoints.torrent],
|
|
2360
2361
|
['Magnet', entry.endpoints.magnet],
|
|
2361
2362
|
['Feed', entry.endpoints.feed],
|
|
@@ -2384,7 +2385,20 @@
|
|
|
2384
2385
|
<td style="width:8rem">${label}</td>
|
|
2385
2386
|
<td>${
|
|
2386
2387
|
url
|
|
2387
|
-
? `<code>${escapeHtml(
|
|
2388
|
+
? `<code>${escapeHtml(
|
|
2389
|
+
url.length > 96 ? `${url.slice(0, 96)}…` : url,
|
|
2390
|
+
)}</code>${
|
|
2391
|
+
label === 'For a style'
|
|
2392
|
+
? `<div class="sub">the magnet rides in the fragment, which is
|
|
2393
|
+
never sent to the server — an ordinary client fetches the
|
|
2394
|
+
TileJSON and ignores it, a swarm-aware one reads it before
|
|
2395
|
+
making any call${
|
|
2396
|
+
url.includes('xs=urn:btpk')
|
|
2397
|
+
? ' and follows the category rather than this build'
|
|
2398
|
+
: ''
|
|
2399
|
+
}</div>`
|
|
2400
|
+
: ''
|
|
2401
|
+
}`
|
|
2388
2402
|
: '<span class="sub">not a PMTiles archive, so there is no tile endpoint</span>'
|
|
2389
2403
|
}</td>
|
|
2390
2404
|
<td style="width:11rem">${
|
|
@@ -2406,6 +2420,7 @@
|
|
|
2406
2420
|
const [index, row] = button.dataset.copy.split('-').map(Number);
|
|
2407
2421
|
const url = [
|
|
2408
2422
|
categories[index].endpoints.tileJson,
|
|
2423
|
+
categories[index].endpoints.styleUrl,
|
|
2409
2424
|
categories[index].endpoints.torrent,
|
|
2410
2425
|
categories[index].endpoints.magnet,
|
|
2411
2426
|
categories[index].endpoints.feed,
|