@comapeo/map-server 1.0.0-pre.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/README.md +610 -0
- package/dist/context.d.ts +46 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +181 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +112 -0
- package/dist/lib/constants.d.ts +7 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +6 -0
- package/dist/lib/download-request.d.ts +17 -0
- package/dist/lib/download-request.d.ts.map +1 -0
- package/dist/lib/download-request.js +113 -0
- package/dist/lib/errors.d.ts +88 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +158 -0
- package/dist/lib/event-stream-response.d.ts +17 -0
- package/dist/lib/event-stream-response.d.ts.map +1 -0
- package/dist/lib/event-stream-response.js +39 -0
- package/dist/lib/event-target.d.ts +9 -0
- package/dist/lib/event-target.d.ts.map +1 -0
- package/dist/lib/event-target.js +4 -0
- package/dist/lib/fetch-api.d.ts +3 -0
- package/dist/lib/fetch-api.d.ts.map +1 -0
- package/dist/lib/fetch-api.js +16 -0
- package/dist/lib/map-share.d.ts +52 -0
- package/dist/lib/map-share.d.ts.map +1 -0
- package/dist/lib/map-share.js +142 -0
- package/dist/lib/secret-stream-fetch.d.ts +7 -0
- package/dist/lib/secret-stream-fetch.d.ts.map +1 -0
- package/dist/lib/secret-stream-fetch.js +34 -0
- package/dist/lib/self-evicting-map.d.ts +16 -0
- package/dist/lib/self-evicting-map.d.ts.map +1 -0
- package/dist/lib/self-evicting-map.js +29 -0
- package/dist/lib/state-update-event.d.ts +8 -0
- package/dist/lib/state-update-event.d.ts.map +1 -0
- package/dist/lib/state-update-event.js +10 -0
- package/dist/lib/utils.d.ts +32 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +96 -0
- package/dist/middlewares/localhost-only.d.ts +11 -0
- package/dist/middlewares/localhost-only.d.ts.map +1 -0
- package/dist/middlewares/localhost-only.js +10 -0
- package/dist/middlewares/parse-request.d.ts +11 -0
- package/dist/middlewares/parse-request.d.ts.map +1 -0
- package/dist/middlewares/parse-request.js +25 -0
- package/dist/routes/downloads.d.ts +15 -0
- package/dist/routes/downloads.d.ts.map +1 -0
- package/dist/routes/downloads.js +60 -0
- package/dist/routes/map-shares.d.ts +19 -0
- package/dist/routes/map-shares.d.ts.map +1 -0
- package/dist/routes/map-shares.js +192 -0
- package/dist/routes/maps.d.ts +6 -0
- package/dist/routes/maps.d.ts.map +1 -0
- package/dist/routes/maps.js +118 -0
- package/dist/routes/root.d.ts +6 -0
- package/dist/routes/root.d.ts.map +1 -0
- package/dist/routes/root.js +29 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +96 -0
- package/node_modules/@envelop/instrumentation/LICENSE +21 -0
- package/node_modules/@envelop/instrumentation/README.md +30 -0
- package/node_modules/@envelop/instrumentation/cjs/index.js +5 -0
- package/node_modules/@envelop/instrumentation/cjs/instrumentation.js +89 -0
- package/node_modules/@envelop/instrumentation/cjs/package.json +1 -0
- package/node_modules/@envelop/instrumentation/esm/index.js +2 -0
- package/node_modules/@envelop/instrumentation/esm/instrumentation.js +82 -0
- package/node_modules/@envelop/instrumentation/package.json +57 -0
- package/node_modules/@envelop/instrumentation/typings/index.d.cts +1 -0
- package/node_modules/@envelop/instrumentation/typings/index.d.ts +1 -0
- package/node_modules/@envelop/instrumentation/typings/instrumentation.d.cts +44 -0
- package/node_modules/@envelop/instrumentation/typings/instrumentation.d.ts +44 -0
- package/node_modules/@whatwg-node/disposablestack/cjs/AsyncDisposableStack.js +73 -0
- package/node_modules/@whatwg-node/disposablestack/cjs/DisposableStack.js +62 -0
- package/node_modules/@whatwg-node/disposablestack/cjs/SupressedError.js +16 -0
- package/node_modules/@whatwg-node/disposablestack/cjs/index.js +11 -0
- package/node_modules/@whatwg-node/disposablestack/cjs/package.json +1 -0
- package/node_modules/@whatwg-node/disposablestack/cjs/symbols.js +20 -0
- package/node_modules/@whatwg-node/disposablestack/cjs/utils.js +11 -0
- package/node_modules/@whatwg-node/disposablestack/esm/AsyncDisposableStack.js +69 -0
- package/node_modules/@whatwg-node/disposablestack/esm/DisposableStack.js +58 -0
- package/node_modules/@whatwg-node/disposablestack/esm/SupressedError.js +12 -0
- package/node_modules/@whatwg-node/disposablestack/esm/index.js +7 -0
- package/node_modules/@whatwg-node/disposablestack/esm/symbols.js +16 -0
- package/node_modules/@whatwg-node/disposablestack/esm/utils.js +7 -0
- package/node_modules/@whatwg-node/disposablestack/package.json +44 -0
- package/node_modules/@whatwg-node/disposablestack/typings/AsyncDisposableStack.d.cts +15 -0
- package/node_modules/@whatwg-node/disposablestack/typings/AsyncDisposableStack.d.ts +15 -0
- package/node_modules/@whatwg-node/disposablestack/typings/DisposableStack.d.cts +14 -0
- package/node_modules/@whatwg-node/disposablestack/typings/DisposableStack.d.ts +14 -0
- package/node_modules/@whatwg-node/disposablestack/typings/SupressedError.d.cts +5 -0
- package/node_modules/@whatwg-node/disposablestack/typings/SupressedError.d.ts +5 -0
- package/node_modules/@whatwg-node/disposablestack/typings/index.d.cts +4 -0
- package/node_modules/@whatwg-node/disposablestack/typings/index.d.ts +4 -0
- package/node_modules/@whatwg-node/disposablestack/typings/symbols.d.cts +5 -0
- package/node_modules/@whatwg-node/disposablestack/typings/symbols.d.ts +5 -0
- package/node_modules/@whatwg-node/disposablestack/typings/utils.d.cts +2 -0
- package/node_modules/@whatwg-node/disposablestack/typings/utils.d.ts +2 -0
- package/node_modules/@whatwg-node/promise-helpers/cjs/index.js +270 -0
- package/node_modules/@whatwg-node/promise-helpers/cjs/package.json +1 -0
- package/node_modules/@whatwg-node/promise-helpers/esm/index.js +257 -0
- package/node_modules/@whatwg-node/promise-helpers/package.json +43 -0
- package/node_modules/@whatwg-node/promise-helpers/typings/index.d.cts +31 -0
- package/node_modules/@whatwg-node/promise-helpers/typings/index.d.ts +31 -0
- package/node_modules/@whatwg-node/server/README.md +590 -0
- package/node_modules/@whatwg-node/server/cjs/createServerAdapter.js +368 -0
- package/node_modules/@whatwg-node/server/cjs/index.js +17 -0
- package/node_modules/@whatwg-node/server/cjs/package.json +1 -0
- package/node_modules/@whatwg-node/server/cjs/plugins/types.js +0 -0
- package/node_modules/@whatwg-node/server/cjs/plugins/useContentEncoding.js +73 -0
- package/node_modules/@whatwg-node/server/cjs/plugins/useCors.js +124 -0
- package/node_modules/@whatwg-node/server/cjs/plugins/useErrorHandling.js +52 -0
- package/node_modules/@whatwg-node/server/cjs/types.js +0 -0
- package/node_modules/@whatwg-node/server/cjs/utils.js +599 -0
- package/node_modules/@whatwg-node/server/cjs/uwebsockets.js +241 -0
- package/node_modules/@whatwg-node/server/esm/createServerAdapter.js +365 -0
- package/node_modules/@whatwg-node/server/esm/index.js +11 -0
- package/node_modules/@whatwg-node/server/esm/plugins/types.js +0 -0
- package/node_modules/@whatwg-node/server/esm/plugins/useContentEncoding.js +70 -0
- package/node_modules/@whatwg-node/server/esm/plugins/useCors.js +120 -0
- package/node_modules/@whatwg-node/server/esm/plugins/useErrorHandling.js +46 -0
- package/node_modules/@whatwg-node/server/esm/types.js +0 -0
- package/node_modules/@whatwg-node/server/esm/utils.js +588 -0
- package/node_modules/@whatwg-node/server/esm/uwebsockets.js +234 -0
- package/node_modules/@whatwg-node/server/package.json +46 -0
- package/node_modules/@whatwg-node/server/typings/createServerAdapter.d.cts +19 -0
- package/node_modules/@whatwg-node/server/typings/createServerAdapter.d.ts +19 -0
- package/node_modules/@whatwg-node/server/typings/index.d.cts +11 -0
- package/node_modules/@whatwg-node/server/typings/index.d.ts +11 -0
- package/node_modules/@whatwg-node/server/typings/plugins/types.d.cts +76 -0
- package/node_modules/@whatwg-node/server/typings/plugins/types.d.ts +76 -0
- package/node_modules/@whatwg-node/server/typings/plugins/useContentEncoding.d.cts +2 -0
- package/node_modules/@whatwg-node/server/typings/plugins/useContentEncoding.d.ts +2 -0
- package/node_modules/@whatwg-node/server/typings/plugins/useCors.d.cts +14 -0
- package/node_modules/@whatwg-node/server/typings/plugins/useCors.d.ts +14 -0
- package/node_modules/@whatwg-node/server/typings/plugins/useErrorHandling.d.cts +13 -0
- package/node_modules/@whatwg-node/server/typings/plugins/useErrorHandling.d.ts +13 -0
- package/node_modules/@whatwg-node/server/typings/types.d.cts +100 -0
- package/node_modules/@whatwg-node/server/typings/types.d.ts +100 -0
- package/node_modules/@whatwg-node/server/typings/utils.d.cts +42 -0
- package/node_modules/@whatwg-node/server/typings/utils.d.ts +42 -0
- package/node_modules/@whatwg-node/server/typings/uwebsockets.d.cts +32 -0
- package/node_modules/@whatwg-node/server/typings/uwebsockets.d.ts +32 -0
- package/node_modules/tslib/CopyrightNotice.txt +15 -0
- package/node_modules/tslib/LICENSE.txt +12 -0
- package/node_modules/tslib/README.md +164 -0
- package/node_modules/tslib/SECURITY.md +41 -0
- package/node_modules/tslib/modules/index.d.ts +38 -0
- package/node_modules/tslib/modules/index.js +70 -0
- package/node_modules/tslib/modules/package.json +3 -0
- package/node_modules/tslib/package.json +47 -0
- package/node_modules/tslib/tslib.d.ts +460 -0
- package/node_modules/tslib/tslib.es6.html +1 -0
- package/node_modules/tslib/tslib.es6.js +402 -0
- package/node_modules/tslib/tslib.es6.mjs +401 -0
- package/node_modules/tslib/tslib.html +1 -0
- package/node_modules/tslib/tslib.js +484 -0
- package/package.json +87 -0
- package/src/context.ts +203 -0
- package/src/index.ts +193 -0
- package/src/lib/constants.ts +6 -0
- package/src/lib/download-request.ts +142 -0
- package/src/lib/errors.ts +187 -0
- package/src/lib/event-stream-response.ts +57 -0
- package/src/lib/event-target.ts +11 -0
- package/src/lib/fetch-api.ts +18 -0
- package/src/lib/map-share.ts +185 -0
- package/src/lib/secret-stream-fetch.ts +42 -0
- package/src/lib/self-evicting-map.ts +35 -0
- package/src/lib/state-update-event.ts +14 -0
- package/src/lib/utils.ts +110 -0
- package/src/middlewares/localhost-only.ts +16 -0
- package/src/middlewares/parse-request.ts +34 -0
- package/src/routes/downloads.ts +92 -0
- package/src/routes/map-shares.ts +246 -0
- package/src/routes/maps.ts +146 -0
- package/src/routes/root.ts +37 -0
- package/src/types.ts +152 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { randomBytes } from 'crypto';
|
|
3
|
+
import z32 from 'z32';
|
|
4
|
+
/**
|
|
5
|
+
* If the argument is an `Error` instance, return its `code` property if it is a string.
|
|
6
|
+
* Otherwise, returns `undefined`.
|
|
7
|
+
*
|
|
8
|
+
* @param {unknown} maybeError
|
|
9
|
+
* @returns {undefined | string}
|
|
10
|
+
* @example
|
|
11
|
+
* try {
|
|
12
|
+
* // do something
|
|
13
|
+
* } catch (err) {
|
|
14
|
+
* console.error(getErrorCode(err))
|
|
15
|
+
* }
|
|
16
|
+
*/
|
|
17
|
+
export function getErrorCode(maybeError) {
|
|
18
|
+
if (maybeError instanceof Error &&
|
|
19
|
+
'code' in maybeError &&
|
|
20
|
+
typeof maybeError.code === 'string') {
|
|
21
|
+
return maybeError.code;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
export function noop() { }
|
|
26
|
+
export function generateId() {
|
|
27
|
+
return z32.encode(randomBytes(8));
|
|
28
|
+
}
|
|
29
|
+
export function getOrInsert(map, key, value) {
|
|
30
|
+
if (map.has(key)) {
|
|
31
|
+
return map.get(key);
|
|
32
|
+
}
|
|
33
|
+
map.set(key, value);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
export function timingSafeEqual(a, b) {
|
|
37
|
+
const aBuf = Buffer.from(a);
|
|
38
|
+
const bBuf = Buffer.from(b);
|
|
39
|
+
if (aBuf.length !== bBuf.length) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return crypto.timingSafeEqual(aBuf, bBuf);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Returns a bbox that is the smallest bounding box that contains all the input bboxes.
|
|
46
|
+
*
|
|
47
|
+
* @param bboxes
|
|
48
|
+
* @returns Bounding Box [w, s, e, n] of all input bboxes
|
|
49
|
+
*/
|
|
50
|
+
export function unionBBox(bboxes) {
|
|
51
|
+
let [w, s, e, n] = bboxes[0];
|
|
52
|
+
for (let i = 1; i < bboxes.length; i++) {
|
|
53
|
+
const [w1, s1, e1, n1] = bboxes[i];
|
|
54
|
+
w = Math.min(w, w1);
|
|
55
|
+
s = Math.min(s, s1);
|
|
56
|
+
e = Math.max(e, e1);
|
|
57
|
+
n = Math.max(n, n1);
|
|
58
|
+
}
|
|
59
|
+
return [w, s, e, n];
|
|
60
|
+
}
|
|
61
|
+
export function getStyleBbox(style) {
|
|
62
|
+
const sourceBboxes = [];
|
|
63
|
+
for (const source of Object.values(style.sources)) {
|
|
64
|
+
if (!('bounds' in source))
|
|
65
|
+
continue;
|
|
66
|
+
sourceBboxes.push(source.bounds);
|
|
67
|
+
}
|
|
68
|
+
if (!isNonEmptyArray(sourceBboxes)) {
|
|
69
|
+
return [-180, -85.0511, 180, 85.0511];
|
|
70
|
+
}
|
|
71
|
+
return unionBBox(sourceBboxes);
|
|
72
|
+
}
|
|
73
|
+
export function getStyleMaxZoom(style) {
|
|
74
|
+
let maxzoom = -1;
|
|
75
|
+
for (const source of Object.values(style.sources)) {
|
|
76
|
+
if (!('maxzoom' in source))
|
|
77
|
+
continue;
|
|
78
|
+
maxzoom = Math.max(maxzoom, source.maxzoom ?? -1);
|
|
79
|
+
}
|
|
80
|
+
return maxzoom === -1 ? 22 : maxzoom;
|
|
81
|
+
}
|
|
82
|
+
export function getStyleMinZoom(style) {
|
|
83
|
+
let minzoom = 99;
|
|
84
|
+
for (const source of Object.values(style.sources)) {
|
|
85
|
+
if (!('minzoom' in source))
|
|
86
|
+
continue;
|
|
87
|
+
minzoom = Math.min(minzoom, source.minzoom ?? 99);
|
|
88
|
+
}
|
|
89
|
+
return minzoom === 99 ? 0 : minzoom;
|
|
90
|
+
}
|
|
91
|
+
function isNonEmptyArray(arr) {
|
|
92
|
+
return arr.length > 0;
|
|
93
|
+
}
|
|
94
|
+
export function addTrailingSlash(url) {
|
|
95
|
+
return url.endsWith('/') ? url : url + '/';
|
|
96
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type IRequestStrict, type RequestHandler } from 'itty-router';
|
|
2
|
+
/**
|
|
3
|
+
* Middleware to restrict access to localhost only. The localhost listener must
|
|
4
|
+
* pass { isLocalhost: true } in the context.
|
|
5
|
+
*/
|
|
6
|
+
export declare const localhostOnly: RequestHandler<IRequestStrict, [
|
|
7
|
+
{
|
|
8
|
+
isLocalhost: boolean;
|
|
9
|
+
}
|
|
10
|
+
]>;
|
|
11
|
+
//# sourceMappingURL=localhost-only.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localhost-only.d.ts","sourceRoot":"","sources":["../../src/middlewares/localhost-only.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAA;AAItE;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,cAAc,CACzC,cAAc,EACd;IAAC;QAAE,WAAW,EAAE,OAAO,CAAA;KAAE;CAAC,CAK1B,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { errors } from '../lib/errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Middleware to restrict access to localhost only. The localhost listener must
|
|
4
|
+
* pass { isLocalhost: true } in the context.
|
|
5
|
+
*/
|
|
6
|
+
export const localhostOnly = async (_, { isLocalhost }) => {
|
|
7
|
+
if (!isLocalhost) {
|
|
8
|
+
throw new errors.FORBIDDEN();
|
|
9
|
+
}
|
|
10
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type RequestHandler, type IRequestStrict, type IRequest } from 'itty-router';
|
|
2
|
+
import { Type as T, type StaticType } from 'typebox';
|
|
3
|
+
/**
|
|
4
|
+
* A small helper to create middleware that parses and validates the request
|
|
5
|
+
* body against the given schema. Downstream handlers can access the type-safe
|
|
6
|
+
* parsed body via `request.parsed`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const parseRequest: <TSchema extends T.TSchema, TRequest extends IRequest = IRequestStrict>(schema: TSchema) => RequestHandler<TRequest & {
|
|
9
|
+
parsed: StaticType<[], "Decode", {}, {}, TSchema>;
|
|
10
|
+
}>;
|
|
11
|
+
//# sourceMappingURL=parse-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-request.d.ts","sourceRoot":"","sources":["../../src/middlewares/parse-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAA;AACrF,OAAO,EAAE,IAAI,IAAI,CAAC,EAAE,KAAK,UAAU,EAAE,MAAM,SAAS,CAAA;AAKpD;;;;GAIG;AACH,eAAO,MAAM,YAAY,GACxB,OAAO,SAAS,CAAC,CAAC,OAAO,EACzB,QAAQ,SAAS,QAAQ,GAAG,cAAc,EAE1C,QAAQ,OAAO,KACb,cAAc,CAChB,QAAQ,GAAG;IAAE,MAAM,EAAE,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;CAAE,CAgBhE,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Compile } from 'typebox/compile';
|
|
2
|
+
import { errors } from '../lib/errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* A small helper to create middleware that parses and validates the request
|
|
5
|
+
* body against the given schema. Downstream handlers can access the type-safe
|
|
6
|
+
* parsed body via `request.parsed`.
|
|
7
|
+
*/
|
|
8
|
+
export const parseRequest = (schema) => {
|
|
9
|
+
const C = Compile(schema);
|
|
10
|
+
return async (request) => {
|
|
11
|
+
try {
|
|
12
|
+
const json = await request.json();
|
|
13
|
+
// Use Check to validate without type coercion
|
|
14
|
+
if (!C.Check(json)) {
|
|
15
|
+
throw new errors.INVALID_REQUEST();
|
|
16
|
+
}
|
|
17
|
+
request.parsed = json;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if ('status' in error)
|
|
21
|
+
throw error;
|
|
22
|
+
throw new errors.INVALID_REQUEST();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Type as T, type Static } from 'typebox';
|
|
2
|
+
import type { Context } from '../context.js';
|
|
3
|
+
import { type RouterExternal } from '../types.js';
|
|
4
|
+
declare const DownloadCreateRequest: T.TObject<{
|
|
5
|
+
senderDeviceId: T.TString;
|
|
6
|
+
mapShareUrls: T.TArray<T.TString>;
|
|
7
|
+
shareId: T.TString;
|
|
8
|
+
estimatedSizeBytes: T.TNumber;
|
|
9
|
+
}>;
|
|
10
|
+
export type DownloadCreateParams = Static<typeof DownloadCreateRequest>;
|
|
11
|
+
export declare function DownloadsRouter({ base }: {
|
|
12
|
+
base: string;
|
|
13
|
+
}, ctx: Context): RouterExternal;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=downloads.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"downloads.d.ts","sourceRoot":"","sources":["../../src/routes/downloads.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,IAAI,CAAC,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAA;AAEhD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAQ5C,OAAO,EAIN,KAAK,cAAc,EACnB,MAAM,aAAa,CAAA;AAEpB,QAAA,MAAM,qBAAqB;;;;;EAQzB,CAAA;AAEF,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAA;AAEvE,wBAAgB,eAAe,CAC9B,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAC1B,GAAG,EAAE,OAAO,GACV,cAAc,CA0DhB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { IttyRouter } from 'itty-router';
|
|
2
|
+
import { Type as T } from 'typebox';
|
|
3
|
+
import { CUSTOM_MAP_ID } from '../lib/constants.js';
|
|
4
|
+
import { DownloadRequest } from '../lib/download-request.js';
|
|
5
|
+
import { errors } from '../lib/errors.js';
|
|
6
|
+
import { createEventStreamResponse } from '../lib/event-stream-response.js';
|
|
7
|
+
import { SelfEvictingTimeoutMap } from '../lib/self-evicting-map.js';
|
|
8
|
+
import { addTrailingSlash } from '../lib/utils.js';
|
|
9
|
+
import { parseRequest } from '../middlewares/parse-request.js';
|
|
10
|
+
import { MapShareUrls, EstimatedSizeBytes, ShareId, } from '../types.js';
|
|
11
|
+
const DownloadCreateRequest = T.Object({
|
|
12
|
+
senderDeviceId: T.String({
|
|
13
|
+
minLength: 1,
|
|
14
|
+
description: 'The ID of the device that is sending the map share',
|
|
15
|
+
}),
|
|
16
|
+
mapShareUrls: MapShareUrls,
|
|
17
|
+
shareId: ShareId,
|
|
18
|
+
estimatedSizeBytes: EstimatedSizeBytes,
|
|
19
|
+
});
|
|
20
|
+
export function DownloadsRouter({ base }, ctx) {
|
|
21
|
+
const downloads = new SelfEvictingTimeoutMap();
|
|
22
|
+
const router = IttyRouter({ base });
|
|
23
|
+
router.post('/', parseRequest(DownloadCreateRequest), async (request) => {
|
|
24
|
+
const writable = ctx.createMapWritableStream(CUSTOM_MAP_ID);
|
|
25
|
+
const download = new DownloadRequest(writable, request.parsed, ctx.getKeyPair());
|
|
26
|
+
downloads.set(download.state.downloadId, download);
|
|
27
|
+
return Response.json(download.state, {
|
|
28
|
+
status: 201,
|
|
29
|
+
headers: {
|
|
30
|
+
Location: new URL(download.state.downloadId, addTrailingSlash(request.url)).href,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
router.get('/', () => {
|
|
35
|
+
return Array.from(downloads.values()).map((d) => d.state);
|
|
36
|
+
});
|
|
37
|
+
router.get('/:downloadId', async (request) => {
|
|
38
|
+
return getDownload(request.params.downloadId).state;
|
|
39
|
+
});
|
|
40
|
+
router.get('/:downloadId/events', async (request) => {
|
|
41
|
+
const download = getDownload(request.params.downloadId);
|
|
42
|
+
return createEventStreamResponse(download, { signal: request.signal });
|
|
43
|
+
});
|
|
44
|
+
router.post('/:downloadId/abort', async (request) => {
|
|
45
|
+
const download = getDownload(request.params.downloadId);
|
|
46
|
+
if (download.state.status !== 'downloading') {
|
|
47
|
+
throw new errors.ABORT_NOT_DOWNLOADING(`Cannot abort: download status is '${download.state.status}'`);
|
|
48
|
+
}
|
|
49
|
+
download.cancel();
|
|
50
|
+
return new Response(null, { status: 204 });
|
|
51
|
+
});
|
|
52
|
+
return router;
|
|
53
|
+
function getDownload(downloadId) {
|
|
54
|
+
const download = downloads.get(downloadId);
|
|
55
|
+
if (!download) {
|
|
56
|
+
throw new errors.DOWNLOAD_NOT_FOUND(`Download ID not found: ${downloadId}`);
|
|
57
|
+
}
|
|
58
|
+
return download;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Type as T, type Static } from 'typebox';
|
|
2
|
+
import type { Context } from '../context.js';
|
|
3
|
+
import { type RouterExternal } from '../types.js';
|
|
4
|
+
declare const MapShareCreateRequest: T.TObject<{
|
|
5
|
+
mapId: T.TString;
|
|
6
|
+
receiverDeviceId: T.TString;
|
|
7
|
+
}>;
|
|
8
|
+
export type MapShareCreateParams = Static<typeof MapShareCreateRequest>;
|
|
9
|
+
declare const LocalMapShareDeclineRequest: T.TObject<{
|
|
10
|
+
reason: T.TUnion<[T.TLiteral<"disk_full">, T.TLiteral<"user_rejected">, T.TString]>;
|
|
11
|
+
mapShareUrls: T.TArray<T.TString>;
|
|
12
|
+
senderDeviceId: T.TString;
|
|
13
|
+
}>;
|
|
14
|
+
export type MapShareDeclineParams = Static<typeof LocalMapShareDeclineRequest>;
|
|
15
|
+
export declare function MapSharesRouter({ base }: {
|
|
16
|
+
base: string;
|
|
17
|
+
}, ctx: Context): RouterExternal;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=map-shares.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map-shares.d.ts","sourceRoot":"","sources":["../../src/routes/map-shares.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,IAAI,IAAI,CAAC,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAA;AAIhD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAQ5C,OAAO,EAKN,KAAK,cAAc,EACnB,MAAM,aAAa,CAAA;AAEpB,QAAA,MAAM,qBAAqB;;;EAGzB,CAAA;AAEF,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAA;AAEvE,QAAA,MAAM,2BAA2B;;;;EAO/B,CAAA;AAEF,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,OAAO,2BAA2B,CAAC,CAAA;AAW9E,wBAAgB,eAAe,CAC9B,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAC1B,GAAG,EAAE,OAAO,GACV,cAAc,CAuKhB"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import { IttyRouter } from 'itty-router';
|
|
3
|
+
import { fetch as secretStreamFetch, Agent as SecretStreamAgent, } from 'secret-stream-http';
|
|
4
|
+
import { Type as T } from 'typebox';
|
|
5
|
+
import { Compile } from 'typebox/compile';
|
|
6
|
+
import z32 from 'z32';
|
|
7
|
+
import { errors, StatusError } from '../lib/errors.js';
|
|
8
|
+
import { createEventStreamResponse } from '../lib/event-stream-response.js';
|
|
9
|
+
import { MapShare } from '../lib/map-share.js';
|
|
10
|
+
import { SelfEvictingTimeoutMap } from '../lib/self-evicting-map.js';
|
|
11
|
+
import { addTrailingSlash, timingSafeEqual } from '../lib/utils.js';
|
|
12
|
+
import { localhostOnly } from '../middlewares/localhost-only.js';
|
|
13
|
+
import { parseRequest } from '../middlewares/parse-request.js';
|
|
14
|
+
import { MapShareUrls, MapShareDeclineReason, } from '../types.js';
|
|
15
|
+
const MapShareCreateRequest = T.Object({
|
|
16
|
+
mapId: T.String({ minLength: 1 }),
|
|
17
|
+
receiverDeviceId: T.String({ minLength: 1 }),
|
|
18
|
+
});
|
|
19
|
+
const LocalMapShareDeclineRequest = T.Object({
|
|
20
|
+
reason: MapShareDeclineReason,
|
|
21
|
+
mapShareUrls: MapShareUrls,
|
|
22
|
+
senderDeviceId: T.String({
|
|
23
|
+
minLength: 1,
|
|
24
|
+
description: 'The ID of the device that is sending the map share',
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
const RemoteMapShareDeclineRequest = T.Object({
|
|
28
|
+
reason: MapShareDeclineReason,
|
|
29
|
+
});
|
|
30
|
+
const CompiledLocalMapShareDeclineRequest = Compile(LocalMapShareDeclineRequest);
|
|
31
|
+
const CompiledRemoteMapShareDeclineRequest = Compile(RemoteMapShareDeclineRequest);
|
|
32
|
+
export function MapSharesRouter({ base }, ctx) {
|
|
33
|
+
const mapShares = new SelfEvictingTimeoutMap();
|
|
34
|
+
const router = IttyRouter({ base });
|
|
35
|
+
// These routes are only accessible from localhost (local API)
|
|
36
|
+
router.post('/', localhostOnly, parseRequest(MapShareCreateRequest), async (request) => {
|
|
37
|
+
const { mapId, receiverDeviceId } = request.parsed;
|
|
38
|
+
const mapInfo = await ctx.getMapInfo(mapId);
|
|
39
|
+
const mapShare = new MapShare({
|
|
40
|
+
...mapInfo,
|
|
41
|
+
receiverDeviceId,
|
|
42
|
+
baseUrls: getRemoteBaseUrls(request.url, await ctx.getRemotePort()),
|
|
43
|
+
});
|
|
44
|
+
mapShares.set(mapShare.shareId, mapShare);
|
|
45
|
+
return Response.json(mapShare.state, {
|
|
46
|
+
status: 201,
|
|
47
|
+
headers: {
|
|
48
|
+
Location: new URL(mapShare.shareId, addTrailingSlash(request.url))
|
|
49
|
+
.href,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
router.get('/', localhostOnly, () => {
|
|
54
|
+
return Array.from(mapShares.values()).map((ms) => ms.state);
|
|
55
|
+
});
|
|
56
|
+
router.get('/:shareId/events', localhostOnly, async (request) => {
|
|
57
|
+
const mapShare = getMapShare(request.params.shareId);
|
|
58
|
+
return createEventStreamResponse(mapShare, { signal: request.signal });
|
|
59
|
+
});
|
|
60
|
+
router.post('/:shareId/cancel', localhostOnly, async (request) => {
|
|
61
|
+
const mapShare = getMapShare(request.params.shareId);
|
|
62
|
+
mapShare.cancel();
|
|
63
|
+
return new Response(null, { status: 204 });
|
|
64
|
+
});
|
|
65
|
+
// These routes can be accessed by a remote peer, but the peer deviceId must
|
|
66
|
+
// match the receiverDeviceId on the map share
|
|
67
|
+
const validateRemoteDeviceId = async (request, { remoteDeviceId, isLocalhost }) => {
|
|
68
|
+
if (isLocalhost)
|
|
69
|
+
return;
|
|
70
|
+
if (!remoteDeviceId) {
|
|
71
|
+
throw new errors.FORBIDDEN();
|
|
72
|
+
}
|
|
73
|
+
const mapShare = getMapShare(request.params.shareId);
|
|
74
|
+
if (!timingSafeEqual(remoteDeviceId, mapShare.state.receiverDeviceId)) {
|
|
75
|
+
throw new errors.FORBIDDEN();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
router.all('/:shareId', validateRemoteDeviceId);
|
|
79
|
+
router.all('/:shareId/*', validateRemoteDeviceId);
|
|
80
|
+
router.get('/:shareId', async (request) => {
|
|
81
|
+
return getMapShare(request.params.shareId).state;
|
|
82
|
+
});
|
|
83
|
+
router.get('/:shareId/download', async (request) => {
|
|
84
|
+
const mapShare = getMapShare(request.params.shareId);
|
|
85
|
+
const stream = ctx.createMapReadableStream(mapShare.state.mapId);
|
|
86
|
+
return mapShare.downloadResponse(stream);
|
|
87
|
+
});
|
|
88
|
+
const localDeclineHandler = async (request) => {
|
|
89
|
+
let parsedBody;
|
|
90
|
+
try {
|
|
91
|
+
const json = await request.json();
|
|
92
|
+
if (!CompiledLocalMapShareDeclineRequest.Check(json)) {
|
|
93
|
+
throw new errors.INVALID_REQUEST();
|
|
94
|
+
}
|
|
95
|
+
parsedBody = json;
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
if ('status' in err)
|
|
99
|
+
throw err;
|
|
100
|
+
throw new errors.INVALID_REQUEST();
|
|
101
|
+
}
|
|
102
|
+
const { senderDeviceId, mapShareUrls, reason } = parsedBody;
|
|
103
|
+
const remotePublicKey = z32.decode(senderDeviceId);
|
|
104
|
+
const keyPair = ctx.getKeyPair();
|
|
105
|
+
let response;
|
|
106
|
+
// The sharer could have multiple IPs for different network interfaces, and
|
|
107
|
+
// not all of them may be on the same network as us, so try each URL until
|
|
108
|
+
// one works
|
|
109
|
+
for (const mapShareUrl of mapShareUrls) {
|
|
110
|
+
const url = new URL('decline', addTrailingSlash(mapShareUrl));
|
|
111
|
+
try {
|
|
112
|
+
response = (await secretStreamFetch(url, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
body: JSON.stringify({ reason }),
|
|
115
|
+
signal: request.signal,
|
|
116
|
+
dispatcher: new SecretStreamAgent({ remotePublicKey, keyPair }),
|
|
117
|
+
})); // Subtle difference bewteen Undici fetch Response and whatwg Response
|
|
118
|
+
break; // Exit loop on successful fetch
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
122
|
+
throw error; // Handle abort in caller
|
|
123
|
+
}
|
|
124
|
+
// Otherwise, try the next URL
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!response) {
|
|
128
|
+
throw new errors.DECLINE_CANNOT_CONNECT();
|
|
129
|
+
}
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
// pass through error from sender
|
|
132
|
+
throw new StatusError(response.status, await response.json());
|
|
133
|
+
}
|
|
134
|
+
return new Response(null, { status: 204 });
|
|
135
|
+
};
|
|
136
|
+
const remoteDeclineHandler = async (request) => {
|
|
137
|
+
let parsedBody;
|
|
138
|
+
try {
|
|
139
|
+
const json = await request.json();
|
|
140
|
+
if (!CompiledRemoteMapShareDeclineRequest.Check(json)) {
|
|
141
|
+
throw new errors.INVALID_REQUEST();
|
|
142
|
+
}
|
|
143
|
+
parsedBody = json;
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
if ('status' in err)
|
|
147
|
+
throw err;
|
|
148
|
+
throw new errors.INVALID_REQUEST();
|
|
149
|
+
}
|
|
150
|
+
const { reason } = parsedBody;
|
|
151
|
+
const mapShare = getMapShare(request.params.shareId);
|
|
152
|
+
mapShare.decline(reason);
|
|
153
|
+
return new Response(null, { status: 204 });
|
|
154
|
+
};
|
|
155
|
+
router.post('/:shareId/decline', async (request, { isLocalhost }) => {
|
|
156
|
+
if (isLocalhost) {
|
|
157
|
+
return localDeclineHandler(request);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
return remoteDeclineHandler(request);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
return router;
|
|
164
|
+
function getMapShare(shareId) {
|
|
165
|
+
const mapShare = mapShares.get(shareId);
|
|
166
|
+
if (!mapShare) {
|
|
167
|
+
throw new errors.MAP_SHARE_NOT_FOUND(`Map share ID not found: ${shareId}`);
|
|
168
|
+
}
|
|
169
|
+
return mapShare;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get the base URLs for downloads for all non-internal IPv4 addresses of the machine
|
|
174
|
+
*/
|
|
175
|
+
function getRemoteBaseUrls(requestUrl, remotePort) {
|
|
176
|
+
requestUrl = addTrailingSlash(requestUrl);
|
|
177
|
+
const interfaces = os.networkInterfaces();
|
|
178
|
+
const baseUrls = [];
|
|
179
|
+
for (const iface of Object.values(interfaces)) {
|
|
180
|
+
if (!iface)
|
|
181
|
+
continue;
|
|
182
|
+
for (const addr of iface) {
|
|
183
|
+
if (addr.family === 'IPv4' && !addr.internal) {
|
|
184
|
+
const url = new URL(requestUrl);
|
|
185
|
+
url.hostname = addr.address;
|
|
186
|
+
url.port = remotePort.toString();
|
|
187
|
+
baseUrls.push(url.toString());
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return baseUrls;
|
|
192
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { IRequestStrict } from 'itty-router';
|
|
2
|
+
import type { Context } from '../context.js';
|
|
3
|
+
export declare function MapsRouter({ base }: {
|
|
4
|
+
base?: string | undefined;
|
|
5
|
+
}, ctx: Context): import("itty-router").IttyRouterType<IRequestStrict, any[], any>;
|
|
6
|
+
//# sourceMappingURL=maps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maps.d.ts","sourceRoot":"","sources":["../../src/routes/maps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAmC,MAAM,aAAa,CAAA;AAI7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAe5C,wBAAgB,UAAU,CAAC,EAAE,IAAU,EAAE;;CAAA,EAAE,GAAG,EAAE,OAAO,oEA8HtD"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { IttyRouter } from 'itty-router';
|
|
2
|
+
import Mutex from 'p-mutex';
|
|
3
|
+
import { createServer as createSmpServer } from 'styled-map-package/server';
|
|
4
|
+
import { CUSTOM_MAP_ID, DEFAULT_MAP_ID, FALLBACK_MAP_ID, } from '../lib/constants.js';
|
|
5
|
+
import { errors } from '../lib/errors.js';
|
|
6
|
+
import { addTrailingSlash, noop } from '../lib/utils.js';
|
|
7
|
+
export function MapsRouter({ base = '/' }, ctx) {
|
|
8
|
+
base = addTrailingSlash(base);
|
|
9
|
+
const uploadMutexes = new Map();
|
|
10
|
+
const smpServer = createSmpServer({
|
|
11
|
+
base: `${base}:mapId/`,
|
|
12
|
+
});
|
|
13
|
+
const router = IttyRouter({ base });
|
|
14
|
+
router.get(`/:mapId/info`, async (request) => {
|
|
15
|
+
const info = await ctx.getMapInfo(request.params.mapId);
|
|
16
|
+
return {
|
|
17
|
+
created: info.mapCreated,
|
|
18
|
+
size: info.estimatedSizeBytes,
|
|
19
|
+
name: info.mapName,
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
const uploadHandler = async (request) => {
|
|
23
|
+
const writable = ctx.createMapWritableStream(request.params.mapId);
|
|
24
|
+
if (!request.body) {
|
|
25
|
+
throw new errors.INVALID_REQUEST('Request body is required');
|
|
26
|
+
}
|
|
27
|
+
await request.body.pipeTo(writable);
|
|
28
|
+
};
|
|
29
|
+
router.put('/:mapId', async (request) => {
|
|
30
|
+
// Only allow uploading to the custom map ID for now
|
|
31
|
+
if (request.params.mapId === DEFAULT_MAP_ID ||
|
|
32
|
+
request.params.mapId === FALLBACK_MAP_ID) {
|
|
33
|
+
throw new errors.FORBIDDEN(`Uploading to map ID "${request.params.mapId}" is not allowed`);
|
|
34
|
+
}
|
|
35
|
+
else if (request.params.mapId !== CUSTOM_MAP_ID) {
|
|
36
|
+
throw new errors.MAP_NOT_FOUND(`Map not found: ${request.params.mapId}`);
|
|
37
|
+
}
|
|
38
|
+
if (!request.body) {
|
|
39
|
+
throw new errors.INVALID_REQUEST('Request body is required');
|
|
40
|
+
}
|
|
41
|
+
// Get or create a mutex for this mapId to ensure sequential uploads
|
|
42
|
+
let mutex = uploadMutexes.get(request.params.mapId);
|
|
43
|
+
if (!mutex) {
|
|
44
|
+
mutex = new Mutex();
|
|
45
|
+
uploadMutexes.set(request.params.mapId, mutex);
|
|
46
|
+
}
|
|
47
|
+
await mutex.withLock(() => uploadHandler(request));
|
|
48
|
+
return new Response(null, { status: 200 });
|
|
49
|
+
});
|
|
50
|
+
router.delete('/:mapId', async (request) => {
|
|
51
|
+
// Only allow deleting the custom map ID
|
|
52
|
+
if (request.params.mapId === DEFAULT_MAP_ID ||
|
|
53
|
+
request.params.mapId === FALLBACK_MAP_ID) {
|
|
54
|
+
throw new errors.FORBIDDEN(`Deleting the map ID "${request.params.mapId}" is not allowed`);
|
|
55
|
+
}
|
|
56
|
+
else if (request.params.mapId !== CUSTOM_MAP_ID) {
|
|
57
|
+
throw new errors.MAP_NOT_FOUND(`Map not found: ${request.params.mapId}`);
|
|
58
|
+
}
|
|
59
|
+
// Use mutex to wait for any active uploads to complete before deleting
|
|
60
|
+
let mutex = uploadMutexes.get(request.params.mapId);
|
|
61
|
+
if (!mutex) {
|
|
62
|
+
mutex = new Mutex();
|
|
63
|
+
uploadMutexes.set(request.params.mapId, mutex);
|
|
64
|
+
}
|
|
65
|
+
await mutex.withLock(() => ctx.deleteMap(request.params.mapId));
|
|
66
|
+
return new Response(null, { status: 204 });
|
|
67
|
+
});
|
|
68
|
+
router.all(`/:mapId/*`, async (request) => {
|
|
69
|
+
if (request.params.mapId === DEFAULT_MAP_ID) {
|
|
70
|
+
return defaultMapHandler(request);
|
|
71
|
+
}
|
|
72
|
+
// Get the reader first - this throws MAP_NOT_FOUND for unknown map IDs
|
|
73
|
+
const reader = await ctx.getReader(request.params.mapId);
|
|
74
|
+
try {
|
|
75
|
+
return await smpServer.fetch(request, reader);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
// Convert generic 404 from smpServer to RESOURCE_NOT_FOUND
|
|
79
|
+
if (err instanceof Error && 'status' in err && err.status === 404) {
|
|
80
|
+
throw new errors.RESOURCE_NOT_FOUND();
|
|
81
|
+
}
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
// Special handler for the default map ID that tries to serve a custom map
|
|
86
|
+
// if available, otherwise falls back to the online style or bundled fallback
|
|
87
|
+
const defaultMapHandler = async (request) => {
|
|
88
|
+
const defaultOnlineStyleUrl = ctx.getDefaultOnlineStyleUrl();
|
|
89
|
+
const styleUrls = [
|
|
90
|
+
new URL(`../${CUSTOM_MAP_ID}/style.json`, request.url),
|
|
91
|
+
defaultOnlineStyleUrl,
|
|
92
|
+
new URL(`../${FALLBACK_MAP_ID}/style.json`, request.url),
|
|
93
|
+
];
|
|
94
|
+
for (const url of styleUrls) {
|
|
95
|
+
let response;
|
|
96
|
+
if (url === defaultOnlineStyleUrl) {
|
|
97
|
+
response = await fetch(url).catch(noop);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// No need to go through the networking stack for local requests
|
|
101
|
+
response = await router.fetch(new Request(url)).catch(noop);
|
|
102
|
+
}
|
|
103
|
+
response?.body?.cancel(); // Close the connection
|
|
104
|
+
if (response && response.ok) {
|
|
105
|
+
return new Response(null, {
|
|
106
|
+
status: 302,
|
|
107
|
+
headers: {
|
|
108
|
+
location: url.toString(),
|
|
109
|
+
'access-control-allow-origin': '*',
|
|
110
|
+
'cache-control': 'no-cache',
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
throw new errors.MAP_NOT_FOUND('No available map style found');
|
|
116
|
+
};
|
|
117
|
+
return router;
|
|
118
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../../src/routes/root.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAG5C,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAS/D,wBAAgB,UAAU,CAAC,EAAE,IAAU,EAAE;;CAAA,EAAE,GAAG,EAAE,OAAO,GAAG,cAAc,CAsBvE"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { json, Router } from 'itty-router';
|
|
2
|
+
import { error } from '../lib/errors.js';
|
|
3
|
+
import { localhostOnly } from '../middlewares/localhost-only.js';
|
|
4
|
+
import { DownloadsRouter } from './downloads.js';
|
|
5
|
+
import { MapSharesRouter } from './map-shares.js';
|
|
6
|
+
import { MapsRouter } from './maps.js';
|
|
7
|
+
const MAPS_BASE = '/maps/';
|
|
8
|
+
const MAP_SHARES_BASE = '/mapShares/';
|
|
9
|
+
const DOWNLOADS_BASE = '/downloads/';
|
|
10
|
+
export function RootRouter({ base = '/' }, ctx) {
|
|
11
|
+
const router = Router({
|
|
12
|
+
base,
|
|
13
|
+
// The `error` handler will send a response with the status code from any
|
|
14
|
+
// thrown StatusError, or a 500 for any other errors.
|
|
15
|
+
catch: (err) => error(err),
|
|
16
|
+
// Sends a 404 response for any requests that don't match a route, and for
|
|
17
|
+
// any request handlers that return JSON will send a JSON response.
|
|
18
|
+
finally: [(response) => response ?? error(404), json],
|
|
19
|
+
});
|
|
20
|
+
const mapsRouter = MapsRouter({ base: MAPS_BASE }, ctx);
|
|
21
|
+
const downloadsRouter = DownloadsRouter({ base: DOWNLOADS_BASE }, ctx);
|
|
22
|
+
const mapSharesRouter = MapSharesRouter({ base: MAP_SHARES_BASE }, ctx);
|
|
23
|
+
router.all(`${MAPS_BASE}*`, localhostOnly, mapsRouter.fetch);
|
|
24
|
+
router.all(`${DOWNLOADS_BASE}*`, localhostOnly, downloadsRouter.fetch);
|
|
25
|
+
// Some map share routes are remote-accessible - localhostOnly is applied in
|
|
26
|
+
// the map shares router where needed
|
|
27
|
+
router.all(`${MAP_SHARES_BASE}*`, mapSharesRouter.fetch);
|
|
28
|
+
return router;
|
|
29
|
+
}
|