signalk-siparu 0.2.17 → 0.2.19
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/NOTICE +19 -0
- package/package.json +2 -1
- package/plugin/data/places.json.gz +0 -0
- package/plugin/dist/access.d.ts +5 -0
- package/plugin/dist/access.js +12 -0
- package/plugin/dist/config-routes.d.ts +2 -2
- package/plugin/dist/config-routes.js +5 -12
- package/plugin/dist/index.js +2 -1
- package/plugin/dist/pairing.d.ts +5 -2
- package/plugin/dist/pairing.js +31 -2
- package/plugin/dist/places.d.ts +19 -0
- package/plugin/dist/places.js +161 -0
- package/plugin/dist/rest.js +8 -6
- package/plugin/dist/voyage-routes.js +2 -1
- package/plugin/dist/voyagelog.d.ts +4 -1
- package/plugin/dist/voyagelog.js +18 -3
- package/public/assets/{Logbook-LkN3bBHC.js → Logbook-LcBpVM_m.js} +1 -1
- package/public/assets/{Map-CP7gOG79.js → Map-BDJ0JNax.js} +1 -1
- package/public/assets/{Remote-epjGhN0y.js → Remote-DIjEANdD.js} +1 -1
- package/public/assets/{SecurityHelp-Nw42xMq9.js → SecurityHelp-3nLcQ6mU.js} +1 -1
- package/public/assets/{Voyage-CQcJtXWP.js → Voyage-9Ncyl9QV.js} +1 -1
- package/public/assets/{index-gLen3lD8.js → index-BeY0LLy7.js} +3 -3
- package/public/index.html +1 -1
package/NOTICE
CHANGED
|
@@ -40,3 +40,22 @@ Map data and assets served from the tile host (not bundled in this package):
|
|
|
40
40
|
OpenStreetMap contributors - ODbL 1.0 - https://www.openstreetmap.org/copyright
|
|
41
41
|
Protomaps basemaps - BSD-3-Clause - https://github.com/protomaps/basemaps
|
|
42
42
|
Noto Sans glyphs - SIL OFL 1.1 - https://github.com/protomaps/basemaps-assets
|
|
43
|
+
|
|
44
|
+
-------------------------------------------------------------------------------
|
|
45
|
+
GeoNames gazetteer - https://www.geonames.org
|
|
46
|
+
Creative Commons Attribution 4.0 - https://creativecommons.org/licenses/by/4.0/
|
|
47
|
+
|
|
48
|
+
plugin/data/places.json.gz is an extract of coastal places (harbours, bays and the
|
|
49
|
+
towns behind them) from the GeoNames dump, built by tools/build-places.mjs. The
|
|
50
|
+
plugin uses it aboard to name where a voyage began and ended.
|
|
51
|
+
Current file: built 2026-10-06 from the GeoNames dump of 2026-10-06 and the World
|
|
52
|
+
Port Index as published on 2026-10-06;
|
|
53
|
+
sha256 7ed46496ab48dde6ed2d6b07878cb7404f659aeb16475ea4e06a96993dc4ac72.
|
|
54
|
+
|
|
55
|
+
-------------------------------------------------------------------------------
|
|
56
|
+
World Port Index (Publication 150) - https://msi.nga.mil
|
|
57
|
+
National Geospatial-Intelligence Agency, United States. A work of the US government,
|
|
58
|
+
in the public domain.
|
|
59
|
+
|
|
60
|
+
Port names and positions from the index are folded into plugin/data/places.json.gz
|
|
61
|
+
where GeoNames does not carry the port under the same name.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signalk-siparu",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.19",
|
|
4
4
|
"description": "Kept aboard, proven ashore. Position, wind, depth and voyage history, recorded on the boat and readable from anywhere: sealed to the devices you name, signed on the way out. A read-only Signal K plugin with a built-in dashboard.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"signalk-node-server-plugin",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"main": "plugin/dist/index.js",
|
|
10
10
|
"files": [
|
|
11
11
|
"plugin/dist",
|
|
12
|
+
"plugin/data",
|
|
12
13
|
"public",
|
|
13
14
|
"NOTICE"
|
|
14
15
|
],
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readable = readable;
|
|
4
|
+
function readable(router) {
|
|
5
|
+
const access = router.access;
|
|
6
|
+
if (typeof access === 'function') {
|
|
7
|
+
const registrar = access.call(router, 'readonly');
|
|
8
|
+
if (registrar && typeof registrar.get === 'function')
|
|
9
|
+
return registrar;
|
|
10
|
+
}
|
|
11
|
+
return router;
|
|
12
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ServerAPI } from '@signalk/server-api';
|
|
2
2
|
import type { IRouter } from 'express';
|
|
3
|
+
import { allowConfigure } from './pairing';
|
|
3
4
|
interface ConfigDeps {
|
|
4
5
|
app: ServerAPI;
|
|
5
6
|
acceptOpenNetwork: () => boolean;
|
|
@@ -10,6 +11,5 @@ interface ConfigDeps {
|
|
|
10
11
|
};
|
|
11
12
|
restart: (configuration: object) => void;
|
|
12
13
|
}
|
|
13
|
-
export
|
|
14
|
+
export { allowConfigure };
|
|
14
15
|
export declare function registerConfigRoutes(router: IRouter, deps: ConfigDeps): void;
|
|
15
|
-
export {};
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.allowConfigure =
|
|
3
|
+
exports.allowConfigure = void 0;
|
|
4
4
|
exports.registerConfigRoutes = registerConfigRoutes;
|
|
5
5
|
const pairing_1 = require("./pairing");
|
|
6
|
+
Object.defineProperty(exports, "allowConfigure", { enumerable: true, get: function () { return pairing_1.allowConfigure; } });
|
|
6
7
|
const origin_guard_1 = require("./origin-guard");
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
const ss = app.securityStrategy;
|
|
10
|
-
return ss?.allowConfigure?.(req) === true;
|
|
11
|
-
}
|
|
12
|
-
catch {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
8
|
+
const access_1 = require("./access");
|
|
16
9
|
function readJsonBody(req) {
|
|
17
10
|
return new Promise((resolve) => {
|
|
18
11
|
const parsed = req.body;
|
|
@@ -41,7 +34,7 @@ function readJsonBody(req) {
|
|
|
41
34
|
}
|
|
42
35
|
function registerConfigRoutes(router, deps) {
|
|
43
36
|
const { app, acceptOpenNetwork, getConfig, fuelPathsView, restart } = deps;
|
|
44
|
-
router.get('/config/fuel-paths', (_req, res) => {
|
|
37
|
+
(0, access_1.readable)(router).get('/config/fuel-paths', (_req, res) => {
|
|
45
38
|
res.json(fuelPathsView());
|
|
46
39
|
});
|
|
47
40
|
router.post('/config/fuel-paths', (0, origin_guard_1.sameOrigin)((req, res) => {
|
|
@@ -51,7 +44,7 @@ function registerConfigRoutes(router, deps) {
|
|
|
51
44
|
return;
|
|
52
45
|
}
|
|
53
46
|
}
|
|
54
|
-
else if (!allowConfigure(app, req)) {
|
|
47
|
+
else if (!(0, pairing_1.allowConfigure)(app, req)) {
|
|
55
48
|
res.status(403).json({ error: 'admin_required' });
|
|
56
49
|
return;
|
|
57
50
|
}
|
package/plugin/dist/index.js
CHANGED
|
@@ -60,6 +60,7 @@ const approval_1 = require("./approval");
|
|
|
60
60
|
const keystore_1 = require("./keystore");
|
|
61
61
|
const keysync_1 = require("./keysync");
|
|
62
62
|
const sealer_1 = require("./sealer");
|
|
63
|
+
const places_1 = require("./places");
|
|
63
64
|
const voyagelog_1 = require("./voyagelog");
|
|
64
65
|
const phaselog_1 = require("./phaselog");
|
|
65
66
|
const PLUGIN_ID = 'siparu';
|
|
@@ -303,7 +304,7 @@ module.exports = (app) => {
|
|
|
303
304
|
rollups = ru;
|
|
304
305
|
const qs = new query_1.QueryService(st, ru);
|
|
305
306
|
query = qs;
|
|
306
|
-
const vl = new voyagelog_1.VoyageLog(st, opts, (msg) => app.debug(msg));
|
|
307
|
+
const vl = new voyagelog_1.VoyageLog(st, opts, (msg) => app.debug(msg), places_1.Gazetteer.lazy(places_1.PLACES_FILE, (msg) => app.debug(msg)));
|
|
307
308
|
voyages = vl;
|
|
308
309
|
const pl = new phaselog_1.PhaseLog(st, opts, (msg) => app.debug(msg));
|
|
309
310
|
phases = pl;
|
package/plugin/dist/pairing.d.ts
CHANGED
|
@@ -8,11 +8,11 @@ export type PairScreen = {
|
|
|
8
8
|
state: 'idle';
|
|
9
9
|
} | {
|
|
10
10
|
state: 'showing_code';
|
|
11
|
-
userCode: string;
|
|
11
|
+
userCode: string | null;
|
|
12
12
|
expiresAt: string;
|
|
13
13
|
} | {
|
|
14
14
|
state: 'awaiting_approval';
|
|
15
|
-
userCode: string;
|
|
15
|
+
userCode: string | null;
|
|
16
16
|
email: string | null;
|
|
17
17
|
expiresAt: string;
|
|
18
18
|
device?: {
|
|
@@ -38,6 +38,9 @@ export type PairStatus = PairScreen & {
|
|
|
38
38
|
};
|
|
39
39
|
export declare function securityOff(app: ServerAPI, req: unknown): boolean;
|
|
40
40
|
export declare function writeLocked(app: ServerAPI, req: unknown, acceptOpenNetwork: boolean): boolean;
|
|
41
|
+
export declare function allowConfigure(app: ServerAPI, req: unknown): boolean;
|
|
42
|
+
export declare function privileged(app: ServerAPI, req: unknown): boolean;
|
|
43
|
+
export declare function forTheCrew(screen: PairScreen): PairScreen;
|
|
41
44
|
export declare const WRITE_LOCKED_MESSAGE = "Signal K security is off, so Siparu will not pair, unpair or edit the log. Add an admin user in Signal K, or accept the open-network risk in the plugin settings.";
|
|
42
45
|
interface Deps {
|
|
43
46
|
app: ServerAPI;
|
package/plugin/dist/pairing.js
CHANGED
|
@@ -3,10 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RelayRefused = exports.WRITE_LOCKED_MESSAGE = void 0;
|
|
4
4
|
exports.securityOff = securityOff;
|
|
5
5
|
exports.writeLocked = writeLocked;
|
|
6
|
+
exports.allowConfigure = allowConfigure;
|
|
7
|
+
exports.privileged = privileged;
|
|
8
|
+
exports.forTheCrew = forTheCrew;
|
|
6
9
|
exports.maskEmail = maskEmail;
|
|
7
10
|
exports.registerPairRoutes = registerPairRoutes;
|
|
8
11
|
exports.retryPendingUnlinks = retryPendingUnlinks;
|
|
9
12
|
exports.__resetPairingState = __resetPairingState;
|
|
13
|
+
const access_1 = require("./access");
|
|
10
14
|
const fingerprint_1 = require("./fingerprint");
|
|
11
15
|
const origin_guard_1 = require("./origin-guard");
|
|
12
16
|
function securityOff(app, req) {
|
|
@@ -21,6 +25,30 @@ function securityOff(app, req) {
|
|
|
21
25
|
function writeLocked(app, req, acceptOpenNetwork) {
|
|
22
26
|
return securityOff(app, req) && !acceptOpenNetwork;
|
|
23
27
|
}
|
|
28
|
+
function allowConfigure(app, req) {
|
|
29
|
+
try {
|
|
30
|
+
const ss = app.securityStrategy;
|
|
31
|
+
return ss?.allowConfigure?.(req) === true;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function privileged(app, req) {
|
|
38
|
+
return securityOff(app, req) || allowConfigure(app, req);
|
|
39
|
+
}
|
|
40
|
+
function forTheCrew(screen) {
|
|
41
|
+
switch (screen.state) {
|
|
42
|
+
case 'showing_code':
|
|
43
|
+
return { state: 'showing_code', userCode: null, expiresAt: screen.expiresAt };
|
|
44
|
+
case 'awaiting_approval':
|
|
45
|
+
return { state: 'awaiting_approval', userCode: null, email: null, expiresAt: screen.expiresAt };
|
|
46
|
+
case 'paired':
|
|
47
|
+
return { ...screen, email: null };
|
|
48
|
+
default:
|
|
49
|
+
return screen;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
24
52
|
exports.WRITE_LOCKED_MESSAGE = 'Signal K security is off, so Siparu will not pair, unpair or edit the log. Add an admin user in Signal K, or accept the open-network risk in the plugin settings.';
|
|
25
53
|
let deviceCode = null;
|
|
26
54
|
let userCode = null;
|
|
@@ -110,8 +138,9 @@ function registerPairRoutes(router, deps) {
|
|
|
110
138
|
pairedAt: remote.pairedAt,
|
|
111
139
|
uplink: uplinkStatus() ?? undefined
|
|
112
140
|
});
|
|
113
|
-
router.get('/pair/status', (0, origin_guard_1.sameOrigin)((req, res) => {
|
|
114
|
-
const json = (
|
|
141
|
+
(0, access_1.readable)(router).get('/pair/status', (0, origin_guard_1.sameOrigin)((req, res) => {
|
|
142
|
+
const json = (full) => {
|
|
143
|
+
const screen = privileged(app, req) ? full : forTheCrew(full);
|
|
115
144
|
const status = { ...screen };
|
|
116
145
|
if (securityOff(app, req)) {
|
|
117
146
|
status.security_off = true;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type PlaceResolver = (lat: number | null, lon: number | null) => string | null;
|
|
2
|
+
export declare const PLACES_FILE: string;
|
|
3
|
+
export declare const PLACE_RADIUS_NM = 5;
|
|
4
|
+
export declare const WATER_HANDICAP_NM = 1;
|
|
5
|
+
export declare const HAMLET_HANDICAP_NM = 0.75;
|
|
6
|
+
export declare class Gazetteer {
|
|
7
|
+
private readonly lat;
|
|
8
|
+
private readonly lon;
|
|
9
|
+
private readonly kind;
|
|
10
|
+
private readonly names;
|
|
11
|
+
private constructor();
|
|
12
|
+
static parse(text: string): Gazetteer;
|
|
13
|
+
static fromFile(file: string, log?: (msg: string) => void): Gazetteer | null;
|
|
14
|
+
static lazy(file: string, log?: (msg: string) => void, withinNm?: number): PlaceResolver;
|
|
15
|
+
get size(): number;
|
|
16
|
+
nearest(lat: number, lon: number, withinNm?: number): string | null;
|
|
17
|
+
private firstAtOrAbove;
|
|
18
|
+
resolver(withinNm?: number): PlaceResolver;
|
|
19
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.Gazetteer = exports.HAMLET_HANDICAP_NM = exports.WATER_HANDICAP_NM = exports.PLACE_RADIUS_NM = exports.PLACES_FILE = void 0;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const node_zlib_1 = require("node:zlib");
|
|
40
|
+
const rollup_1 = require("./rollup");
|
|
41
|
+
const LAND = 0;
|
|
42
|
+
const WATER = 1;
|
|
43
|
+
const HAMLET = 2;
|
|
44
|
+
exports.PLACES_FILE = path.join(__dirname, '..', 'data', 'places.json.gz');
|
|
45
|
+
exports.PLACE_RADIUS_NM = 5;
|
|
46
|
+
exports.WATER_HANDICAP_NM = 1;
|
|
47
|
+
exports.HAMLET_HANDICAP_NM = 0.75;
|
|
48
|
+
function isRow(x) {
|
|
49
|
+
return (Array.isArray(x) &&
|
|
50
|
+
(x.length === 3 || (x.length === 4 && (x[3] === LAND || x[3] === WATER || x[3] === HAMLET))) &&
|
|
51
|
+
typeof x[0] === 'number' &&
|
|
52
|
+
typeof x[1] === 'number' &&
|
|
53
|
+
typeof x[2] === 'string' &&
|
|
54
|
+
x[0] >= -90 &&
|
|
55
|
+
x[0] <= 90 &&
|
|
56
|
+
x[1] >= -180 &&
|
|
57
|
+
x[1] <= 180 &&
|
|
58
|
+
x[2].length > 0);
|
|
59
|
+
}
|
|
60
|
+
class Gazetteer {
|
|
61
|
+
lat;
|
|
62
|
+
lon;
|
|
63
|
+
kind;
|
|
64
|
+
names;
|
|
65
|
+
constructor(lat, lon, kind, names) {
|
|
66
|
+
this.lat = lat;
|
|
67
|
+
this.lon = lon;
|
|
68
|
+
this.kind = kind;
|
|
69
|
+
this.names = names;
|
|
70
|
+
}
|
|
71
|
+
static parse(text) {
|
|
72
|
+
const parsed = JSON.parse(text);
|
|
73
|
+
const rows = Array.isArray(parsed.places) ? parsed.places.filter(isRow) : [];
|
|
74
|
+
rows.sort((a, b) => a[0] - b[0]);
|
|
75
|
+
const lat = new Float64Array(rows.length);
|
|
76
|
+
const lon = new Float64Array(rows.length);
|
|
77
|
+
const kind = new Uint8Array(rows.length);
|
|
78
|
+
const names = new Array(rows.length);
|
|
79
|
+
rows.forEach((r, i) => {
|
|
80
|
+
lat[i] = r[0];
|
|
81
|
+
lon[i] = r[1];
|
|
82
|
+
kind[i] = r[3] ?? 0;
|
|
83
|
+
names[i] = r[2];
|
|
84
|
+
});
|
|
85
|
+
return new Gazetteer(lat, lon, kind, names);
|
|
86
|
+
}
|
|
87
|
+
static fromFile(file, log) {
|
|
88
|
+
try {
|
|
89
|
+
const bytes = fs.readFileSync(file);
|
|
90
|
+
const text = bytes[0] === 0x1f && bytes[1] === 0x8b ? (0, node_zlib_1.gunzipSync)(bytes).toString('utf8') : bytes.toString('utf8');
|
|
91
|
+
const g = Gazetteer.parse(text);
|
|
92
|
+
if (g.size === 0) {
|
|
93
|
+
log?.(`places: ${file} holds no places; voyage ends stay positions`);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
log?.(`places: ${g.size} places loaded from ${file}`);
|
|
97
|
+
return g;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
log?.(`places: gazetteer not loaded from ${file}: ${err}`);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
static lazy(file, log, withinNm = exports.PLACE_RADIUS_NM) {
|
|
105
|
+
let loaded;
|
|
106
|
+
return (lat, lon) => {
|
|
107
|
+
if (lat === null || lon === null)
|
|
108
|
+
return null;
|
|
109
|
+
if (loaded === undefined)
|
|
110
|
+
loaded = Gazetteer.fromFile(file, log);
|
|
111
|
+
return loaded ? loaded.nearest(lat, lon, withinNm) : null;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
get size() {
|
|
115
|
+
return this.names.length;
|
|
116
|
+
}
|
|
117
|
+
nearest(lat, lon, withinNm = exports.PLACE_RADIUS_NM) {
|
|
118
|
+
const band = withinNm / 60;
|
|
119
|
+
const cosLat = Math.cos((lat * Math.PI) / 180);
|
|
120
|
+
const lonBand = cosLat > 1e-6 ? band / cosLat : 360;
|
|
121
|
+
let best = null;
|
|
122
|
+
let bestScore = Infinity;
|
|
123
|
+
for (let i = this.firstAtOrAbove(lat - band); i < this.names.length; i++) {
|
|
124
|
+
const rowLat = this.lat[i];
|
|
125
|
+
if (rowLat > lat + band)
|
|
126
|
+
break;
|
|
127
|
+
const rowLon = this.lon[i];
|
|
128
|
+
let dLon = Math.abs(rowLon - lon);
|
|
129
|
+
if (dLon > 180)
|
|
130
|
+
dLon = 360 - dLon;
|
|
131
|
+
if (dLon > lonBand)
|
|
132
|
+
continue;
|
|
133
|
+
const d = (0, rollup_1.haversineNm)(lat, lon, rowLat, rowLon);
|
|
134
|
+
if (d >= withinNm)
|
|
135
|
+
continue;
|
|
136
|
+
const k = this.kind[i];
|
|
137
|
+
const score = k === WATER ? d + exports.WATER_HANDICAP_NM : k === HAMLET ? d + exports.HAMLET_HANDICAP_NM : d;
|
|
138
|
+
if (score < bestScore) {
|
|
139
|
+
bestScore = score;
|
|
140
|
+
best = this.names[i];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return best;
|
|
144
|
+
}
|
|
145
|
+
firstAtOrAbove(lat) {
|
|
146
|
+
let lo = 0;
|
|
147
|
+
let hi = this.lat.length;
|
|
148
|
+
while (lo < hi) {
|
|
149
|
+
const mid = (lo + hi) >>> 1;
|
|
150
|
+
if (this.lat[mid] < lat)
|
|
151
|
+
lo = mid + 1;
|
|
152
|
+
else
|
|
153
|
+
hi = mid;
|
|
154
|
+
}
|
|
155
|
+
return lo;
|
|
156
|
+
}
|
|
157
|
+
resolver(withinNm = exports.PLACE_RADIUS_NM) {
|
|
158
|
+
return (lat, lon) => (lat === null || lon === null ? null : this.nearest(lat, lon, withinNm));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
exports.Gazetteer = Gazetteer;
|
package/plugin/dist/rest.js
CHANGED
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.setRestDeps = setRestDeps;
|
|
37
37
|
exports.registerRoutes = registerRoutes;
|
|
38
38
|
const nodePath = __importStar(require("node:path"));
|
|
39
|
+
const access_1 = require("./access");
|
|
39
40
|
const charts_1 = require("./charts");
|
|
40
41
|
const query_1 = require("./query");
|
|
41
42
|
let deps = null;
|
|
@@ -60,17 +61,18 @@ function intParam(req, name) {
|
|
|
60
61
|
return n;
|
|
61
62
|
}
|
|
62
63
|
function registerRoutes(router) {
|
|
63
|
-
|
|
64
|
+
const read = (0, access_1.readable)(router);
|
|
65
|
+
read.get('/live', (_req, res) => {
|
|
64
66
|
if (!deps)
|
|
65
67
|
return sendError(res, 503, 'NOT_STARTED', 'plugin is not started');
|
|
66
68
|
res.json(deps.live());
|
|
67
69
|
});
|
|
68
|
-
|
|
70
|
+
read.get('/inventory', (_req, res) => {
|
|
69
71
|
if (!deps)
|
|
70
72
|
return sendError(res, 503, 'NOT_STARTED', 'plugin is not started');
|
|
71
73
|
res.json(deps.inventory());
|
|
72
74
|
});
|
|
73
|
-
|
|
75
|
+
read.get('/snapshots', (req, res) => {
|
|
74
76
|
if (!deps)
|
|
75
77
|
return sendError(res, 503, 'NOT_STARTED', 'plugin is not started');
|
|
76
78
|
let q;
|
|
@@ -97,7 +99,7 @@ function registerRoutes(router) {
|
|
|
97
99
|
sendInternal(res, err);
|
|
98
100
|
});
|
|
99
101
|
});
|
|
100
|
-
|
|
102
|
+
read.get('/health', (_req, res) => {
|
|
101
103
|
if (!deps)
|
|
102
104
|
return sendError(res, 503, 'NOT_STARTED', 'plugin is not started');
|
|
103
105
|
deps
|
|
@@ -106,7 +108,7 @@ function registerRoutes(router) {
|
|
|
106
108
|
.catch((err) => sendInternal(res, err));
|
|
107
109
|
});
|
|
108
110
|
const asyncGet = (path, handler) => {
|
|
109
|
-
|
|
111
|
+
read.get(path, (req, res) => {
|
|
110
112
|
if (!deps)
|
|
111
113
|
return sendError(res, 503, 'NOT_STARTED', 'plugin is not started');
|
|
112
114
|
const d = deps;
|
|
@@ -144,7 +146,7 @@ function registerRoutes(router) {
|
|
|
144
146
|
return d.rollupHours(from, to);
|
|
145
147
|
});
|
|
146
148
|
asyncGet('/map-config', async (d) => d.mapConfig());
|
|
147
|
-
|
|
149
|
+
read.get('/charts/*', (req, res) => {
|
|
148
150
|
if (!deps)
|
|
149
151
|
return sendError(res, 503, 'NOT_STARTED', 'plugin is not started');
|
|
150
152
|
const rest = req.params['0'];
|
|
@@ -4,6 +4,7 @@ exports.registerVoyageEditRoutes = registerVoyageEditRoutes;
|
|
|
4
4
|
const pairing_1 = require("./pairing");
|
|
5
5
|
const config_routes_1 = require("./config-routes");
|
|
6
6
|
const origin_guard_1 = require("./origin-guard");
|
|
7
|
+
const access_1 = require("./access");
|
|
7
8
|
const STATUS = {
|
|
8
9
|
not_found: 404,
|
|
9
10
|
no_previous: 409,
|
|
@@ -12,7 +13,7 @@ const STATUS = {
|
|
|
12
13
|
};
|
|
13
14
|
function registerVoyageEditRoutes(router, deps) {
|
|
14
15
|
const { app, acceptOpenNetwork, edits, mergeWithPrevious, undoMerge } = deps;
|
|
15
|
-
router.get('/voyages/edits', (_req, res) => {
|
|
16
|
+
(0, access_1.readable)(router).get('/voyages/edits', (_req, res) => {
|
|
16
17
|
res.json(edits());
|
|
17
18
|
});
|
|
18
19
|
const edit = (name, run) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Snapshot, TrackPoint, Voyage, VoyageStatsResult } from './contract';
|
|
2
2
|
import { Options } from './config';
|
|
3
3
|
import { RollupEngine } from './rollup';
|
|
4
|
+
import { PlaceResolver } from './places';
|
|
4
5
|
import { Logger, Store } from './store';
|
|
5
6
|
export type EditResult = {
|
|
6
7
|
ok: true;
|
|
@@ -13,13 +14,15 @@ export declare class VoyageLog {
|
|
|
13
14
|
private store;
|
|
14
15
|
private opts;
|
|
15
16
|
private log;
|
|
17
|
+
private place;
|
|
16
18
|
private state;
|
|
17
19
|
private manual;
|
|
18
20
|
private window;
|
|
19
21
|
private chain;
|
|
20
22
|
private persistedRevision;
|
|
21
23
|
private filePath;
|
|
22
|
-
constructor(store: Store, opts: Options, log: Logger);
|
|
24
|
+
constructor(store: Store, opts: Options, log: Logger, place?: PlaceResolver);
|
|
25
|
+
private named;
|
|
23
26
|
private enqueue;
|
|
24
27
|
init(now: number): Promise<void>;
|
|
25
28
|
feed(snap: Snapshot): Promise<void>;
|
package/plugin/dist/voyagelog.js
CHANGED
|
@@ -45,18 +45,29 @@ class VoyageLog {
|
|
|
45
45
|
store;
|
|
46
46
|
opts;
|
|
47
47
|
log;
|
|
48
|
+
place;
|
|
48
49
|
state = { voyages: [], nextId: 1 };
|
|
49
50
|
manual = [];
|
|
50
51
|
window = [];
|
|
51
52
|
chain = Promise.resolve();
|
|
52
53
|
persistedRevision = '';
|
|
53
54
|
filePath;
|
|
54
|
-
constructor(store, opts, log) {
|
|
55
|
+
constructor(store, opts, log, place = () => null) {
|
|
55
56
|
this.store = store;
|
|
56
57
|
this.opts = opts;
|
|
57
58
|
this.log = log;
|
|
59
|
+
this.place = place;
|
|
58
60
|
this.filePath = path.join(store.rollupDir, 'voyages.json');
|
|
59
61
|
}
|
|
62
|
+
named(v) {
|
|
63
|
+
const start_port = v.start_port ?? (0, voyage_1.nearestPort)(v.start_lat, v.start_lon, this.opts.ports) ?? this.place(v.start_lat, v.start_lon);
|
|
64
|
+
const end_port = v.status === 'closed'
|
|
65
|
+
? (v.end_port ?? (0, voyage_1.nearestPort)(v.end_lat, v.end_lon, this.opts.ports) ?? this.place(v.end_lat, v.end_lon))
|
|
66
|
+
: v.end_port;
|
|
67
|
+
if (start_port === v.start_port && end_port === v.end_port)
|
|
68
|
+
return v;
|
|
69
|
+
return { ...v, start_port, end_port };
|
|
70
|
+
}
|
|
60
71
|
enqueue(job) {
|
|
61
72
|
const run = this.chain.then(job);
|
|
62
73
|
this.chain = run.then(() => undefined, (err) => this.log(`voyage error: ${err}`));
|
|
@@ -94,10 +105,14 @@ class VoyageLog {
|
|
|
94
105
|
});
|
|
95
106
|
}
|
|
96
107
|
list(limit) {
|
|
97
|
-
return [...this.state.voyages]
|
|
108
|
+
return [...this.state.voyages]
|
|
109
|
+
.sort((a, b) => b.start_ts - a.start_ts)
|
|
110
|
+
.slice(0, limit)
|
|
111
|
+
.map((v) => this.named(v));
|
|
98
112
|
}
|
|
99
113
|
current() {
|
|
100
|
-
|
|
114
|
+
const open = this.state.voyages.find((v) => v.status === 'open');
|
|
115
|
+
return open ? this.named(open) : null;
|
|
101
116
|
}
|
|
102
117
|
async track(voyageId, now) {
|
|
103
118
|
const v = this.state.voyages.find((x) => x.id === voyageId);
|