@pairlobby/local-server 0.0.0-stage → 0.21.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.
Files changed (47) hide show
  1. package/LICENSE +93 -0
  2. package/README.md +4 -2
  3. package/dist/backup.d.ts +79 -0
  4. package/dist/backup.d.ts.map +1 -0
  5. package/dist/backup.js +184 -0
  6. package/dist/backup.js.map +1 -0
  7. package/dist/check.d.ts +12 -0
  8. package/dist/check.d.ts.map +1 -0
  9. package/dist/check.js +102 -0
  10. package/dist/check.js.map +1 -0
  11. package/dist/index.d.ts +10 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +10 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/network.d.ts +23 -0
  16. package/dist/network.d.ts.map +1 -0
  17. package/dist/network.js +143 -0
  18. package/dist/network.js.map +1 -0
  19. package/dist/relay-main.d.ts +3 -0
  20. package/dist/relay-main.d.ts.map +1 -0
  21. package/dist/relay-main.js +141 -0
  22. package/dist/relay-main.js.map +1 -0
  23. package/dist/request-log.d.ts +38 -0
  24. package/dist/request-log.d.ts.map +1 -0
  25. package/dist/request-log.js +37 -0
  26. package/dist/request-log.js.map +1 -0
  27. package/dist/s3.d.ts +37 -0
  28. package/dist/s3.d.ts.map +1 -0
  29. package/dist/s3.js +140 -0
  30. package/dist/s3.js.map +1 -0
  31. package/dist/schema.d.ts +3 -0
  32. package/dist/schema.d.ts.map +1 -0
  33. package/dist/schema.js +98 -0
  34. package/dist/schema.js.map +1 -0
  35. package/dist/server.d.ts +60 -0
  36. package/dist/server.d.ts.map +1 -0
  37. package/dist/server.js +268 -0
  38. package/dist/server.js.map +1 -0
  39. package/dist/settings.d.ts +60 -0
  40. package/dist/settings.d.ts.map +1 -0
  41. package/dist/settings.js +172 -0
  42. package/dist/settings.js.map +1 -0
  43. package/dist/sqlite-store.d.ts +57 -0
  44. package/dist/sqlite-store.d.ts.map +1 -0
  45. package/dist/sqlite-store.js +331 -0
  46. package/dist/sqlite-store.js.map +1 -0
  47. package/package.json +43 -4
package/dist/schema.js ADDED
@@ -0,0 +1,98 @@
1
+ //! SQLite schema. Records are stored as JSON with only the columns lookups need
2
+ //! indexed, so a protocol field added later is an additive change rather than a
3
+ //! migration of every row.
4
+ export const SCHEMA_VERSION = 1;
5
+ export const SCHEMA = `
6
+ CREATE TABLE IF NOT EXISTS meta (
7
+ key TEXT PRIMARY KEY,
8
+ value TEXT NOT NULL
9
+ ) STRICT;
10
+
11
+ CREATE TABLE IF NOT EXISTS rooms (
12
+ room_id TEXT PRIMARY KEY,
13
+ earliest_seq INTEGER NOT NULL DEFAULT 1,
14
+ body TEXT NOT NULL
15
+ ) STRICT;
16
+ CREATE INDEX IF NOT EXISTS rooms_by_auto_close ON rooms (json_extract(body, '$.autoCloseAt')) WHERE json_extract(body, '$.lifecycle') = 'open' AND json_extract(body, '$.autoCloseAt') IS NOT NULL;
17
+
18
+ CREATE TABLE IF NOT EXISTS participants (
19
+ participant_id TEXT PRIMARY KEY,
20
+ room_id TEXT NOT NULL,
21
+ credential_hash TEXT NOT NULL,
22
+ body TEXT NOT NULL
23
+ ) STRICT;
24
+ CREATE INDEX IF NOT EXISTS participants_by_room ON participants (room_id);
25
+ CREATE UNIQUE INDEX IF NOT EXISTS participants_by_credential ON participants (room_id, credential_hash);
26
+
27
+ CREATE TABLE IF NOT EXISTS events (
28
+ room_id TEXT NOT NULL,
29
+ seq INTEGER NOT NULL,
30
+ body TEXT NOT NULL,
31
+ PRIMARY KEY (room_id, seq)
32
+ ) STRICT;
33
+
34
+ CREATE TABLE IF NOT EXISTS idempotency (
35
+ room_id TEXT NOT NULL,
36
+ key TEXT NOT NULL,
37
+ request_digest TEXT NOT NULL,
38
+ seq INTEGER NOT NULL,
39
+ PRIMARY KEY (room_id, key)
40
+ ) STRICT;
41
+
42
+ CREATE TABLE IF NOT EXISTS handovers (
43
+ handover_id TEXT PRIMARY KEY,
44
+ room_id TEXT NOT NULL,
45
+ body TEXT NOT NULL
46
+ ) STRICT;
47
+ CREATE INDEX IF NOT EXISTS handovers_by_room ON handovers (room_id);
48
+
49
+ CREATE TABLE IF NOT EXISTS controls (
50
+ room_id TEXT NOT NULL,
51
+ target_participant_id TEXT NOT NULL,
52
+ body TEXT NOT NULL,
53
+ PRIMARY KEY (room_id, target_participant_id)
54
+ ) STRICT;
55
+
56
+ CREATE TABLE IF NOT EXISTS invites (
57
+ digest TEXT PRIMARY KEY,
58
+ room_id TEXT NOT NULL,
59
+ state TEXT NOT NULL,
60
+ bound_attempt_id TEXT,
61
+ body TEXT NOT NULL
62
+ ) STRICT;
63
+ CREATE INDEX IF NOT EXISTS invites_by_room ON invites (room_id);
64
+
65
+ CREATE TABLE IF NOT EXISTS message_requests (
66
+ event_id TEXT PRIMARY KEY,
67
+ room_id TEXT NOT NULL,
68
+ seq INTEGER NOT NULL,
69
+ received_at INTEGER,
70
+ response_event_id TEXT,
71
+ requires_reply INTEGER NOT NULL,
72
+ body TEXT NOT NULL
73
+ ) STRICT;
74
+ CREATE INDEX IF NOT EXISTS message_requests_pending ON message_requests(room_id,seq) WHERE requires_reply=1 AND response_event_id IS NULL;
75
+ CREATE INDEX IF NOT EXISTS message_requests_group ON message_requests(room_id,json_extract(body,'$.conversationId'));
76
+
77
+ CREATE TABLE IF NOT EXISTS files (
78
+ file_id TEXT PRIMARY KEY,
79
+ room_id TEXT NOT NULL,
80
+ size INTEGER NOT NULL,
81
+ created_at INTEGER NOT NULL,
82
+ event_id TEXT,
83
+ body TEXT NOT NULL
84
+ ) STRICT;
85
+ CREATE INDEX IF NOT EXISTS files_by_room ON files (room_id, created_at);
86
+
87
+ CREATE TABLE IF NOT EXISTS file_chunks (
88
+ file_id TEXT NOT NULL,
89
+ idx INTEGER NOT NULL,
90
+ bytes BLOB NOT NULL,
91
+ PRIMARY KEY (file_id, idx)
92
+ ) STRICT;
93
+
94
+ CREATE INDEX IF NOT EXISTS events_message_id ON events(room_id,json_extract(body,'$.eventId'));
95
+ CREATE INDEX IF NOT EXISTS events_reply_to ON events(room_id,json_extract(body,'$.replyTo')) WHERE json_extract(body,'$.type')='message';
96
+ CREATE INDEX IF NOT EXISTS events_receipt_to ON events(room_id,json_extract(body,'$.payload.eventId')) WHERE json_extract(body,'$.type')='message.received';
97
+ `;
98
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,gFAAgF;AAChF,2BAA2B;AAE3B,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC,MAAM,CAAC,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4FrB,CAAC"}
@@ -0,0 +1,60 @@
1
+ import type { AdmissionLimits } from '@pairlobby/server-core';
2
+ import type { RoomPolicy } from '@pairlobby/protocol';
3
+ import type { BackupScheduleOptions, RestoreResult } from './backup.js';
4
+ import type { RequestLogOptions } from './request-log.js';
5
+ export interface ServeOptions {
6
+ host?: string;
7
+ port?: number;
8
+ dataFile: string;
9
+ /** Origins a browser page may call from. Empty means no browser may call this server at all. */
10
+ allowedOrigins?: string[];
11
+ /** Address to advertise in invitations when the server sits behind Tailscale or another proxy. */
12
+ publicUrl?: string;
13
+ /** Who may connect: anyone who can reach the address (default), or only this device and its Tailscale peers. */
14
+ peers?: 'any' | 'tailscale';
15
+ /** More names this server answers to beyond its own addresses and hostname, such as Tailscale MagicDNS names. */
16
+ hostnames?: string[];
17
+ /** Exact `Host` values to accept as well, such as an internal DNS name a load balancer forwards unchanged. */
18
+ allowedHosts?: string[];
19
+ /** Announce this relay on the local network over mDNS, so `pairlobby join <code>` there can find it. */
20
+ advertise?: boolean;
21
+ /** Reported by `GET /v1/server`, so a client can tell which build this relay runs. */
22
+ version?: string;
23
+ /** When set, creating a room needs this token; joining with an invite never does. Read it from the environment, never a command line. */
24
+ createToken?: string;
25
+ /**
26
+ * The header a load balancer in front of this relay puts the caller's address in, such as
27
+ * `x-forwarded-for`. Only set it when every request comes through that proxy: a caller
28
+ * reaching the relay directly could write the header themselves.
29
+ */
30
+ trustedProxyHeader?: string;
31
+ /** Rate limits on creating rooms and redeeming invites; callers on this device are exempt unless the relay is behind a proxy. */
32
+ limits?: Partial<AdmissionLimits>;
33
+ /** Caps for rooms created from now on, over the built-in defaults; rooms that already exist keep theirs. */
34
+ roomLimits?: Partial<RoomPolicy>;
35
+ /** One JSON line per request for a log collector; absent logs nothing. */
36
+ requestLog?: RequestLogOptions;
37
+ /** Copies the database while it runs and once more on close; on a disk with no database, the newest copy is restored first unless `restore` is false. */
38
+ backup?: BackupScheduleOptions & {
39
+ restore?: boolean;
40
+ };
41
+ }
42
+ export interface RunningServer {
43
+ url: string;
44
+ /** Addresses other devices can join through; empty when the server only listens on this device. */
45
+ shareUrls: string[];
46
+ /** Whether `pairlobby join <code>` on another device can find this server without --server. */
47
+ discoverable: boolean;
48
+ host: string;
49
+ port: number;
50
+ dataFile: string;
51
+ /** What happened to the backup on start, when backups are on. */
52
+ restore?: RestoreResult;
53
+ close(): Promise<void>;
54
+ }
55
+ /** The port a relay listens on unless told otherwise, and the one other devices try when searching a tailnet. */
56
+ export declare const DEFAULT_PORT = 8790;
57
+ export declare function startServer(options: ServeOptions): Promise<RunningServer>;
58
+ /** Whether a connection from `address` may reach a relay limited to `peers`. */
59
+ export declare function peerAllowed(peers: 'any' | 'tailscale', address: string): boolean;
60
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,wBAAwB,CAAC;AAE5D,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,qBAAqB,CAAC;AAKpD,OAAO,KAAK,EAAiB,qBAAqB,EAAE,aAAa,EAAC,MAAM,aAAa,CAAC;AACtF,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAGxD,MAAM,WAAW,YAAY;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,gGAAgG;IAChG,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gHAAgH;IAChH,KAAK,CAAC,EAAE,KAAK,GAAG,WAAW,CAAC;IAC5B,iHAAiH;IACjH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,8GAA8G;IAC9G,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,wGAAwG;IACxG,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yIAAyI;IACzI,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iIAAiI;IACjI,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAClC,4GAA4G;IAC5G,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IACjC,0EAA0E;IAC1E,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,yJAAyJ;IACzJ,MAAM,CAAC,EAAE,qBAAqB,GAAG;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAC,CAAC;CACxD;AAED,MAAM,WAAW,aAAa;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,mGAAmG;IACnG,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,+FAA+F;IAC/F,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAYD,iHAAiH;AACjH,eAAO,MAAM,YAAY,OAAO,CAAC;AAEjC,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAQ/E;AAoJD,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAEhF"}
package/dist/server.js ADDED
@@ -0,0 +1,268 @@
1
+ //! The local relay. Bridges `node:http` to the shared Web-standard router, so
2
+ //! local and hosted operation run the same request handling.
3
+ import { createServer as createHttpServer } from 'node:http';
4
+ import { mkdirSync } from 'node:fs';
5
+ import { hostname, networkInterfaces } from 'node:os';
6
+ import { dirname } from 'node:path';
7
+ import { RoomService, createAdmission, createRouter, memoryRateLimiter, proxiedAddress } from '@pairlobby/server-core';
8
+ import { ABSOLUTE_MAX_FILE_BYTES, DEFAULT_ROOM_POLICY } from '@pairlobby/protocol';
9
+ import { advertiseRelay, isLocalNetworkAddress, isLoopbackAddress, isTailscaleAddress } from './network.js';
10
+ import { REQUEST_ID_HEADER, requestIdFor, requestLogLine, shouldLog } from './request-log.js';
11
+ import { restoreLatest, startBackups } from './backup.js';
12
+ import { SqliteRoomStore } from './sqlite-store.js';
13
+ /** Non-file endpoints retain the smaller bound; the file-upload route may use the explicit file ceiling. */
14
+ const DEFAULT_MAX_REQUEST_BYTES = 32 * 1024 * 1024;
15
+ class RequestBodyTooLarge extends Error {
16
+ }
17
+ function requestBodyLimit(incoming) {
18
+ const path = (incoming.url ?? '').split('?', 1)[0];
19
+ return incoming.method === 'POST' && /^\/v1\/rooms\/[^/]+\/files$/.test(path ?? '') ? ABSOLUTE_MAX_FILE_BYTES : DEFAULT_MAX_REQUEST_BYTES;
20
+ }
21
+ /** The port a relay listens on unless told otherwise, and the one other devices try when searching a tailnet. */
22
+ export const DEFAULT_PORT = 8790;
23
+ export async function startServer(options) {
24
+ mkdirSync(dirname(options.dataFile), { recursive: true });
25
+ const backup = options.backup;
26
+ // Starting empty while the rooms sit in an unreachable backup would bury them under new copies.
27
+ const restore = backup && backup.restore !== false ? await restoreLatest({ target: backup.target, dataFile: options.dataFile, ...(backup.log ? { log: backup.log } : {}) }).catch((error) => {
28
+ throw new Error(`could not restore the rooms from ${backup.target.description}: ${error instanceof Error ? error.message : String(error)}. Fix access to it, or start with --no-restore to begin empty.`);
29
+ }) : undefined;
30
+ return listen(options, restore);
31
+ }
32
+ function listen(options, restore) {
33
+ const host = options.host ?? '127.0.0.1';
34
+ const port = options.port ?? DEFAULT_PORT;
35
+ const store = new SqliteRoomStore(options.dataFile);
36
+ const service = new RoomService(store, undefined, undefined, { ...DEFAULT_ROOM_POLICY, ...options.roomLimits });
37
+ // Loopback is not authentication: a page in the user's browser can reach it too.
38
+ // Host is pinned to defeat DNS rebinding, and an unexpected Origin is refused.
39
+ // A server listening on every interface also answers to this machine's own
40
+ // addresses and names; a rebinding attack needs a name the attacker controls.
41
+ const allowedHosts = [...(options.allowedHosts ?? [])];
42
+ if (options.publicUrl) {
43
+ allowedHosts.push(new URL(options.publicUrl).host);
44
+ }
45
+ const wildcard = isWildcard(host);
46
+ let boundPort = port;
47
+ const tailscaleOnly = options.peers === 'tailscale';
48
+ const extraNames = (options.hostnames ?? []).map((name) => name.toLowerCase());
49
+ const acceptHost = (value) => allowedHosts.includes(value) || (!isLoopback(host) && isOwnHost(value, boundPort, extraNames));
50
+ // Found by mDNS when advertised, or by a tailnet device trying the default port on this one's Tailscale address.
51
+ // Behind a proxy every caller arrives from the proxy's address, which looks local, so the
52
+ // socket can no longer vouch that a caller is on this network.
53
+ const behindProxy = Boolean(options.publicUrl || options.trustedProxyHeader);
54
+ const discoverable = () => !behindProxy && !isLoopback(host) && (options.advertise === true || (boundPort === DEFAULT_PORT && (wildcard || isTailscaleAddress(host))));
55
+ const serverInfo = () => ({ shareUrls: shareUrls(host, boundPort, options.publicUrl, tailscaleOnly), discoverable: discoverable(), ...(options.version ? { version: options.version } : {}) });
56
+ const inviteProbe = async (prefix) => store.hasInviteDigestPrefix(prefix);
57
+ // Where each request's connection came from, read off the socket: no header can claim it.
58
+ const peerAddresses = new WeakMap();
59
+ const peerIsLocal = (request) => !behindProxy && isLocalNetworkAddress(peerAddresses.get(request) ?? '');
60
+ const localRooms = async (name) => store.localJoinRooms(name);
61
+ const header = options.trustedProxyHeader;
62
+ const clientAddress = (request) => (header ? proxiedAddress(request, header) : undefined) ?? peerAddresses.get(request);
63
+ const exempt = (request) => !behindProxy && isLoopbackAddress(peerAddresses.get(request) ?? '');
64
+ const admission = createAdmission({ ...(options.createToken ? { createToken: options.createToken } : {}), limiter: memoryRateLimiter(), ...(options.limits ? { limits: options.limits } : {}), clientAddress, exempt });
65
+ const route = createRouter({ service, allowedOrigins: options.allowedOrigins ?? [], allowedHosts: acceptHost, serverInfo, inviteProbe, peerIsLocal, localRooms, admission });
66
+ // One local relay owns this store. Serialize mutating requests so two
67
+ // async service calls cannot both choose the same next event sequence.
68
+ let mutations = Promise.resolve();
69
+ // One timer for the whole relay, aimed at the earliest auto-close deadline. Sweeps
70
+ // join the write queue, and every write re-plans it, so a changed policy or a new
71
+ // message moves the wake-up instead of leaving a stale one to fire.
72
+ let wake;
73
+ let stopped = false;
74
+ const plan = (at) => {
75
+ clearTimeout(wake);
76
+ wake = undefined;
77
+ if (stopped || at === null) {
78
+ return;
79
+ }
80
+ wake = setTimeout(sweep, Math.min(Math.max(0, at - Date.now()), 2 ** 31 - 1));
81
+ wake.unref();
82
+ };
83
+ const sweep = () => {
84
+ mutations = mutations
85
+ .then(async () => {
86
+ const result = await service.closeDueRooms();
87
+ plan(result.more ? Date.now() : result.nextAt);
88
+ })
89
+ .catch(() => plan(Date.now() + 60_000));
90
+ };
91
+ const handle = (request) => {
92
+ const result = mutations.then(() => route(request));
93
+ if (!['GET', 'HEAD'].includes(request.method)) {
94
+ mutations = result.catch(() => { });
95
+ sweep();
96
+ }
97
+ return result;
98
+ };
99
+ sweep();
100
+ const health = JSON.stringify({ service: 'pairlobby-relay', ready: true, ...(options.version ? { version: options.version } : {}) });
101
+ const server = createHttpServer((incoming, outgoing) => {
102
+ // Answered before the Host check and the write queue: a load balancer probes by address, and a busy relay is still alive.
103
+ if (incoming.method === 'GET' && (incoming.url ?? '').split('?', 1)[0] === '/health') {
104
+ outgoing.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store' });
105
+ outgoing.end(health);
106
+ return;
107
+ }
108
+ void respond(handle, incoming, outgoing, `http://${incoming.headers.host ?? `${host}:${port}`}`, peerAddresses, options.requestLog);
109
+ });
110
+ // Enforced on the connection, before any request is read: the bind address alone
111
+ // cannot say "Tailscale only" when the Tailscale address may not exist yet at boot.
112
+ server.on('connection', (socket) => {
113
+ if (!peerAllowed(options.peers ?? 'any', socket.remoteAddress ?? '')) {
114
+ socket.destroy();
115
+ }
116
+ });
117
+ let stopAdvertising = () => { };
118
+ let backups = null;
119
+ return new Promise((resolve, reject) => {
120
+ server.once('error', reject);
121
+ server.listen(port, host, () => {
122
+ server.removeListener('error', reject);
123
+ // Port 0 asks the OS to choose, so the bound port is the only truthful one to report.
124
+ const address = server.address();
125
+ boundPort = typeof address === 'object' && address !== null ? address.port : port;
126
+ allowedHosts.push(`${host}:${boundPort}`, `localhost:${boundPort}`, `127.0.0.1:${boundPort}`);
127
+ if (options.advertise && !isLoopback(host)) {
128
+ stopAdvertising = advertiseRelay(boundPort);
129
+ }
130
+ backups = options.backup ? startBackups({ ...options.backup, source: store, dataFile: options.dataFile }) : null;
131
+ resolve({
132
+ url: options.publicUrl ?? `http://${wildcard ? '127.0.0.1' : host}:${boundPort}`,
133
+ shareUrls: shareUrls(host, boundPort, options.publicUrl, tailscaleOnly),
134
+ discoverable: discoverable(),
135
+ host,
136
+ port: boundPort,
137
+ dataFile: options.dataFile,
138
+ ...(restore ? { restore } : {}),
139
+ close: () => {
140
+ stopped = true;
141
+ clearTimeout(wake);
142
+ stopAdvertising();
143
+ return mutations.then(() => shutdown(server, store, backups));
144
+ }
145
+ });
146
+ });
147
+ });
148
+ }
149
+ function isWildcard(host) {
150
+ return host === '0.0.0.0' || host === '::';
151
+ }
152
+ function isLoopback(host) {
153
+ return ['127.0.0.1', 'localhost', '::1'].includes(host);
154
+ }
155
+ /** Names and addresses that belong to this machine right now; interfaces come and go with Wi-Fi and DHCP. */
156
+ function ownHostnames() {
157
+ const names = new Set();
158
+ for (const addresses of Object.values(networkInterfaces())) {
159
+ for (const address of addresses ?? []) {
160
+ names.add(address.family === 'IPv6' ? `[${address.address.toLowerCase()}]` : address.address);
161
+ }
162
+ }
163
+ const machine = hostname().toLowerCase();
164
+ names.add(machine);
165
+ names.add(machine.endsWith('.local') ? machine.slice(0, -'.local'.length) : `${machine}.local`);
166
+ return names;
167
+ }
168
+ /** Whether a connection from `address` may reach a relay limited to `peers`. */
169
+ export function peerAllowed(peers, address) {
170
+ return peers === 'any' || isLoopbackAddress(address) || isTailscaleAddress(address);
171
+ }
172
+ function isOwnHost(value, port, extraNames) {
173
+ let parsed;
174
+ try {
175
+ parsed = new URL(`http://${value}`);
176
+ }
177
+ catch {
178
+ return false;
179
+ }
180
+ const name = parsed.hostname.toLowerCase();
181
+ return (parsed.port === '' ? 80 : Number(parsed.port)) === port && (ownHostnames().has(name) || extraNames.includes(name));
182
+ }
183
+ function shareUrls(host, port, publicUrl, tailscaleOnly) {
184
+ if (publicUrl) {
185
+ return [publicUrl.replace(/\/+$/, '')];
186
+ }
187
+ if (isLoopback(host)) {
188
+ return [];
189
+ }
190
+ if (!isWildcard(host)) {
191
+ return [`http://${host.includes(':') ? `[${host}]` : host}:${port}`];
192
+ }
193
+ // IPv4 only: link-local IPv6 needs a zone id that another device cannot use as written.
194
+ const urls = [];
195
+ for (const addresses of Object.values(networkInterfaces())) {
196
+ for (const address of addresses ?? []) {
197
+ if (address.family === 'IPv4' && !address.internal && (!tailscaleOnly || isTailscaleAddress(address.address))) {
198
+ urls.push(`http://${address.address}:${port}`);
199
+ }
200
+ }
201
+ }
202
+ return urls;
203
+ }
204
+ async function respond(handle, incoming, outgoing, origin, peerAddresses, log) {
205
+ const started = Date.now();
206
+ const requestId = requestIdFor(typeof incoming.headers[REQUEST_ID_HEADER] === 'string' ? incoming.headers[REQUEST_ID_HEADER] : null);
207
+ const bodyLimit = requestBodyLimit(incoming);
208
+ const finish = (status, headers, body) => {
209
+ outgoing.writeHead(status, { ...headers, [REQUEST_ID_HEADER]: requestId });
210
+ outgoing.end(body);
211
+ const method = incoming.method ?? 'GET';
212
+ if (log && shouldLog(log.mode, method, status)) {
213
+ log.write(requestLogLine({ requestId, method, path: (incoming.url ?? '/').split('?', 1)[0], status, ms: Date.now() - started, at: new Date(started) }));
214
+ }
215
+ };
216
+ const tooLarge = () => finish(413, { 'content-type': 'application/json', connection: 'close' }, JSON.stringify({ error: { code: 'payload_too_large', message: 'this request is larger than the relay accepts' } }));
217
+ try {
218
+ if (Number(incoming.headers['content-length'] ?? 0) > bodyLimit) {
219
+ tooLarge();
220
+ return;
221
+ }
222
+ const request = await toRequest(incoming, origin, bodyLimit);
223
+ peerAddresses.set(request, incoming.socket.remoteAddress ?? '');
224
+ const response = await handle(request);
225
+ finish(response.status, Object.fromEntries(response.headers), response.body ? Buffer.from(await response.arrayBuffer()) : undefined);
226
+ }
227
+ catch (error) {
228
+ if (error instanceof RequestBodyTooLarge) {
229
+ tooLarge();
230
+ return;
231
+ }
232
+ finish(503, { 'content-type': 'application/json' }, JSON.stringify({ error: { code: 'server_unavailable', message: 'the server could not complete this request' } }));
233
+ }
234
+ }
235
+ async function toRequest(incoming, origin, bodyLimit) {
236
+ const chunks = [];
237
+ let received = 0;
238
+ for await (const chunk of incoming) {
239
+ received += chunk.byteLength;
240
+ if (received > bodyLimit) {
241
+ throw new RequestBodyTooLarge();
242
+ }
243
+ chunks.push(chunk);
244
+ }
245
+ const method = incoming.method ?? 'GET';
246
+ const headers = new Headers();
247
+ for (const [name, value] of Object.entries(incoming.headers)) {
248
+ if (value === undefined) {
249
+ continue;
250
+ }
251
+ headers.set(name, Array.isArray(value) ? value.join(', ') : value);
252
+ }
253
+ const hasBody = method !== 'GET' && method !== 'HEAD' && chunks.length > 0;
254
+ return new Request(new URL(incoming.url ?? '/', origin), { method, headers, ...(hasBody ? { body: Buffer.concat(chunks) } : {}) });
255
+ }
256
+ /** Stops taking requests, takes the last backup, then closes the database. */
257
+ function shutdown(server, store, backups) {
258
+ return new Promise((resolve) => {
259
+ server.close(() => {
260
+ void (backups?.stop() ?? Promise.resolve()).finally(() => {
261
+ store.close();
262
+ resolve();
263
+ });
264
+ });
265
+ server.closeAllConnections?.();
266
+ });
267
+ }
268
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import type { RoomPolicy } from '@pairlobby/protocol';
2
+ import type { AdmissionLimits } from '@pairlobby/server-core';
3
+ import type { RequestLogMode } from './request-log.js';
4
+ /** A setting the relay cannot start with; the message names the flag or variable that holds it. */
5
+ export declare class RelaySettingsError extends Error {
6
+ constructor(message: string);
7
+ }
8
+ export type RelayFlags = {
9
+ host?: string;
10
+ port?: string;
11
+ publicUrl?: string;
12
+ trustedProxyHeader?: string;
13
+ requestLog?: string;
14
+ backup?: string;
15
+ noRestore?: boolean;
16
+ };
17
+ export type RelayEnvironment = Record<string, string | undefined>;
18
+ export type RelaySettings = {
19
+ host?: string;
20
+ port?: number;
21
+ publicUrl?: string;
22
+ trustedProxyHeader?: string;
23
+ createToken?: string;
24
+ /** Exact Host values to accept beyond the relay's own, such as an internal DNS name. */
25
+ allowedHosts: string[];
26
+ limits: Partial<AdmissionLimits>;
27
+ /** Caps every new room gets; unset ones keep the built-in defaults. */
28
+ roomLimits: Partial<RoomLimits>;
29
+ requestLog?: RequestLogMode;
30
+ backup?: RelayBackupSettings;
31
+ };
32
+ /** Where and how often to copy the database. */
33
+ export type RelayBackupSettings = {
34
+ location: BackupLocation;
35
+ intervalMs: number;
36
+ keep: number;
37
+ restore: boolean;
38
+ };
39
+ export type BackupLocation = {
40
+ kind: 'folder';
41
+ path: string;
42
+ } | {
43
+ kind: 's3';
44
+ bucket: string;
45
+ prefix: string;
46
+ };
47
+ /** The per-room caps a relay operator may set. */
48
+ export type RoomLimits = Pick<RoomPolicy, 'maxParticipants' | 'maxRetainedEvents' | 'maxRetainedEventBytes' | 'maxEventPayloadBytes' | 'maxFileBytes' | 'maxRetainedFileBytes'>;
49
+ type Source = {
50
+ flag?: string;
51
+ variable: string;
52
+ };
53
+ /** Every variable `serve` reads, for help text and documentation. */
54
+ export declare const RELAY_VARIABLES: string[];
55
+ export declare function parsePublicUrl(value: string, source?: Source): string;
56
+ export declare function relaySettings(flags: RelayFlags, environment: RelayEnvironment): RelaySettings;
57
+ /** `s3://bucket/prefix` or an absolute folder path (`file://` allowed); null for anything else. */
58
+ export declare function parseBackupLocation(value: string): BackupLocation | null;
59
+ export {};
60
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,wBAAwB,CAAC;AAE5D,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAC;AAErD,mGAAmG;AACnG,qBAAa,kBAAmB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI9B;AAED,MAAM,MAAM,UAAU,GAAG;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAC,CAAC;AAEpK,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAElE,MAAM,MAAM,aAAa,GAAG;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wFAAwF;IACxF,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IACjC,uEAAuE;IACvE,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,MAAM,CAAC,EAAE,mBAAmB,CAAC;CAChC,CAAC;AAEF,gDAAgD;AAChD,MAAM,MAAM,mBAAmB,GAAG;IAAC,QAAQ,EAAE,cAAc,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAC,CAAC;AAEjH,MAAM,MAAM,cAAc,GAAG;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,GAAG;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAAC;AAE3G,kDAAkD;AAClD,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,iBAAiB,GAAG,mBAAmB,GAAG,uBAAuB,GAAG,sBAAsB,GAAG,cAAc,GAAG,sBAAsB,CAAC,CAAC;AAEhL,KAAK,MAAM,GAAG;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,CAAC;AAuBhD,qEAAqE;AACrE,eAAO,MAAM,eAAe,UAAgc,CAAC;AAW7d,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAA+D,GAAG,MAAM,CAW7H;AAoCD,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,GAAG,aAAa,CA+C7F;AAgCD,mGAAmG;AACnG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAQxE"}