pinomad 0.9.1 → 0.11.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 +11 -9
- package/dist/{chunk-v414b5pp.js → chunk-44pnts98.js} +2 -2
- package/dist/{chunk-nj5gz6qh.js → chunk-4dcs8sbj.js} +11 -9
- package/dist/{chunk-n8cz7skh.js → chunk-67gq282t.js} +53 -32
- package/dist/{chunk-azarj0f4.js → chunk-6d16c2fv.js} +21 -11
- package/dist/{chunk-cx8jgpdt.js → chunk-9h5vawnm.js} +1040 -79
- package/dist/{chunk-029mmh7d.js → chunk-as6pyf00.js} +3 -3
- package/dist/chunk-b457q0tm.js +70 -0
- package/dist/chunk-etf7ef97.js +55 -0
- package/dist/chunk-greb3dp6.js +22 -0
- package/dist/chunk-gtdpjqa3.js +317 -0
- package/dist/chunk-h5q2y57m.js +20 -0
- package/dist/{chunk-4sepcfpy.js → chunk-jt34g8st.js} +2 -2
- package/dist/chunk-n1m1tjrk.js +214 -0
- package/dist/{chunk-3p1eysm9.js → chunk-ncbqa8jk.js} +5 -4
- package/dist/{chunk-sygeb9qb.js → chunk-pqe7q1s7.js} +1 -1
- package/dist/{chunk-fqgvf8v0.js → chunk-sd5fkxjj.js} +1 -1
- package/dist/{chunk-5gp9svzs.js → chunk-t91zt5f0.js} +4 -53
- package/dist/{chunk-vax4zspw.js → chunk-tj57aqav.js} +3 -3
- package/dist/{chunk-32g3m2h0.js → chunk-vvj9b1gc.js} +29 -24
- package/dist/{chunk-jgd38rna.js → chunk-xqycs43g.js} +171 -171
- package/dist/{chunk-khcpv54k.js → chunk-y127fe1a.js} +59 -4
- package/dist/pinomad.js +18 -12
- package/package.json +1 -1
- package/web/assets/index-BhHrKpoo.js +395 -0
- package/web/index.html +1 -1
- package/dist/chunk-3gcpzpjq.js +0 -16
- package/dist/chunk-n58m5hgh.js +0 -170
- package/web/assets/index-1g60bhv2.js +0 -395
|
@@ -2,7 +2,176 @@ import {
|
|
|
2
2
|
HOST_SETTING_KEYS,
|
|
3
3
|
isQueueMode,
|
|
4
4
|
isThinkingLevel
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-pqe7q1s7.js";
|
|
6
|
+
|
|
7
|
+
// packages/protocol/src/rtc.ts
|
|
8
|
+
function rtcAddressScope(address) {
|
|
9
|
+
const ip = parseRtcAddress(address);
|
|
10
|
+
if (ip === undefined)
|
|
11
|
+
return "unknown";
|
|
12
|
+
if (ip.family === 4)
|
|
13
|
+
return ipv4Scope(ip.octets);
|
|
14
|
+
const octets = mappedIpv4(ip.segments);
|
|
15
|
+
return octets === undefined ? ipv6Scope(ip.segments) : ipv4Scope(octets);
|
|
16
|
+
}
|
|
17
|
+
function rtcCandidateEnd(info) {
|
|
18
|
+
return {
|
|
19
|
+
...info.type === undefined ? {} : { type: info.type },
|
|
20
|
+
...info.address === undefined ? {} : { address: info.address },
|
|
21
|
+
scope: rtcAddressScope(info.address)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function rtcSelectedPair(local, remote) {
|
|
25
|
+
return { local: rtcCandidateEnd(local), remote: rtcCandidateEnd(remote) };
|
|
26
|
+
}
|
|
27
|
+
function parseRtcAddress(address) {
|
|
28
|
+
if (address === undefined)
|
|
29
|
+
return;
|
|
30
|
+
const text = address.trim();
|
|
31
|
+
if (text === "")
|
|
32
|
+
return;
|
|
33
|
+
let host = text;
|
|
34
|
+
if (text.startsWith("[")) {
|
|
35
|
+
const close = text.indexOf("]");
|
|
36
|
+
if (close === -1)
|
|
37
|
+
return;
|
|
38
|
+
const tail = text.slice(close + 1);
|
|
39
|
+
if (tail !== "" && !/^:\d+$/.test(tail))
|
|
40
|
+
return;
|
|
41
|
+
host = text.slice(1, close);
|
|
42
|
+
} else if (text.split(":").length === 2) {
|
|
43
|
+
const [candidate, port] = text.split(":");
|
|
44
|
+
if (/^\d+$/.test(port))
|
|
45
|
+
host = candidate;
|
|
46
|
+
}
|
|
47
|
+
const zone = host.indexOf("%");
|
|
48
|
+
if (zone !== -1)
|
|
49
|
+
host = host.slice(0, zone);
|
|
50
|
+
const ipv4 = parseIpv4(host);
|
|
51
|
+
if (ipv4 !== undefined)
|
|
52
|
+
return { family: 4, octets: ipv4 };
|
|
53
|
+
const ipv6 = parseIpv6(host);
|
|
54
|
+
return ipv6 === undefined ? undefined : { family: 6, segments: ipv6 };
|
|
55
|
+
}
|
|
56
|
+
function parseIpv4(text) {
|
|
57
|
+
const parts = text.split(".");
|
|
58
|
+
if (parts.length !== 4)
|
|
59
|
+
return;
|
|
60
|
+
const octets = [];
|
|
61
|
+
for (const part of parts) {
|
|
62
|
+
if (!/^(?:0|[1-9]\d{0,2})$/.test(part))
|
|
63
|
+
return;
|
|
64
|
+
const value = Number(part);
|
|
65
|
+
if (value > 255)
|
|
66
|
+
return;
|
|
67
|
+
octets.push(value);
|
|
68
|
+
}
|
|
69
|
+
return octets;
|
|
70
|
+
}
|
|
71
|
+
function parseIpv6(text) {
|
|
72
|
+
const halves = text.split("::");
|
|
73
|
+
if (halves.length > 2)
|
|
74
|
+
return;
|
|
75
|
+
const parsePart = (part, allowIpv4) => {
|
|
76
|
+
if (part === "")
|
|
77
|
+
return [];
|
|
78
|
+
const tokens = part.split(":");
|
|
79
|
+
const groups = [];
|
|
80
|
+
for (let index = 0;index < tokens.length; index++) {
|
|
81
|
+
const token = tokens[index];
|
|
82
|
+
if (allowIpv4 && index === tokens.length - 1 && token.includes(".")) {
|
|
83
|
+
const v4 = parseIpv4(token);
|
|
84
|
+
if (v4 === undefined)
|
|
85
|
+
return;
|
|
86
|
+
groups.push(v4[0] << 8 | v4[1], v4[2] << 8 | v4[3]);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(token))
|
|
90
|
+
return;
|
|
91
|
+
groups.push(Number.parseInt(token, 16));
|
|
92
|
+
}
|
|
93
|
+
return groups;
|
|
94
|
+
};
|
|
95
|
+
if (halves.length === 1) {
|
|
96
|
+
const groups = parsePart(text, true);
|
|
97
|
+
return groups?.length === 8 ? groups : undefined;
|
|
98
|
+
}
|
|
99
|
+
const left = parsePart(halves[0], false);
|
|
100
|
+
const right = parsePart(halves[1], true);
|
|
101
|
+
if (left === undefined || right === undefined)
|
|
102
|
+
return;
|
|
103
|
+
const zeros = 8 - left.length - right.length;
|
|
104
|
+
if (zeros < 1)
|
|
105
|
+
return;
|
|
106
|
+
return [...left, ...new Array(zeros).fill(0), ...right];
|
|
107
|
+
}
|
|
108
|
+
function mappedIpv4(segments) {
|
|
109
|
+
if (!segments.slice(0, 5).every((group) => group === 0) || segments[5] !== 65535)
|
|
110
|
+
return;
|
|
111
|
+
return [segments[6] >> 8, segments[6] & 255, segments[7] >> 8, segments[7] & 255];
|
|
112
|
+
}
|
|
113
|
+
function ipv4Scope(octets) {
|
|
114
|
+
const [a, b] = octets;
|
|
115
|
+
if (a === 127)
|
|
116
|
+
return "loopback";
|
|
117
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
118
|
+
return "tailnet";
|
|
119
|
+
if (a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254) {
|
|
120
|
+
return "lan";
|
|
121
|
+
}
|
|
122
|
+
return "public";
|
|
123
|
+
}
|
|
124
|
+
function ipv6Scope(segments) {
|
|
125
|
+
if (segments[0] === 64890 && segments[1] === 4444 && segments[2] === 41440)
|
|
126
|
+
return "tailnet";
|
|
127
|
+
if ((segments[0] & 65024) === 64512 || (segments[0] & 65472) === 65152)
|
|
128
|
+
return "lan";
|
|
129
|
+
if (segments.slice(0, 7).every((group) => group === 0) && segments[7] === 1)
|
|
130
|
+
return "loopback";
|
|
131
|
+
return "public";
|
|
132
|
+
}
|
|
133
|
+
var RTC_DEFAULT_STUN = ["stun:stun.cloudflare.com:3478"];
|
|
134
|
+
var RTC_DATA_CHANNEL_LABEL = "pinomad";
|
|
135
|
+
var RTC_MAX_RECORD_BYTES = 65535;
|
|
136
|
+
var RTC_DEFAULT_MAX_MESSAGE_SIZE = 65536;
|
|
137
|
+
var RTC_MIN_MAX_MESSAGE_SIZE = RTC_MAX_RECORD_BYTES;
|
|
138
|
+
function isRtcHello(value) {
|
|
139
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
140
|
+
return false;
|
|
141
|
+
const hello = value;
|
|
142
|
+
return Array.isArray(hello.stun) && hello.stun.every((server) => typeof server === "string" && server.length > 0) && hello.signalOnly === true;
|
|
143
|
+
}
|
|
144
|
+
function rtcCandidateType(candidate) {
|
|
145
|
+
const parts = candidate.trim().split(/\s+/);
|
|
146
|
+
const at = parts.indexOf("typ");
|
|
147
|
+
return at === -1 ? undefined : parts[at + 1];
|
|
148
|
+
}
|
|
149
|
+
function sdpCandidates(sdp) {
|
|
150
|
+
return sdp.split(/\r?\n/).filter((line) => line.startsWith("a=candidate:")).map((line) => line.slice("a=".length));
|
|
151
|
+
}
|
|
152
|
+
function withRtcCandidates(sdp, candidates) {
|
|
153
|
+
const lines = sdp.split(/\r?\n/).filter((line) => line !== "" && !line.startsWith("a=candidate:") && line !== "a=end-of-candidates");
|
|
154
|
+
const unique = [...new Set(candidates.map((candidate) => candidate.replace(/^a=/, "")))];
|
|
155
|
+
return [...lines, ...unique.map((candidate) => `a=${candidate}`), "a=end-of-candidates", ""].join(`\r
|
|
156
|
+
`);
|
|
157
|
+
}
|
|
158
|
+
function parseRtcMaxMessageSize(sdp) {
|
|
159
|
+
const line = sdp.split(/\r?\n/).find((entry) => entry.startsWith("a=max-message-size:"));
|
|
160
|
+
if (line === undefined)
|
|
161
|
+
return { kind: "bounded", bytes: RTC_DEFAULT_MAX_MESSAGE_SIZE };
|
|
162
|
+
const raw = line.slice("a=max-message-size:".length).trim();
|
|
163
|
+
if (!/^\d+$/.test(raw))
|
|
164
|
+
return { kind: "invalid", reason: `malformed a=max-message-size: ${raw}` };
|
|
165
|
+
const bytes = Number(raw);
|
|
166
|
+
if (!Number.isSafeInteger(bytes))
|
|
167
|
+
return { kind: "invalid", reason: `a=max-message-size is not a safe integer: ${raw}` };
|
|
168
|
+
if (bytes === 0)
|
|
169
|
+
return { kind: "unlimited" };
|
|
170
|
+
if (bytes < RTC_MIN_MAX_MESSAGE_SIZE) {
|
|
171
|
+
return { kind: "invalid", reason: `a=max-message-size ${bytes} cannot carry a ${RTC_MIN_MAX_MESSAGE_SIZE}-byte record` };
|
|
172
|
+
}
|
|
173
|
+
return { kind: "bounded", bytes };
|
|
174
|
+
}
|
|
6
175
|
|
|
7
176
|
// packages/protocol/src/auth.ts
|
|
8
177
|
var CUSTOM_PROVIDER_APIS = ["openai-completions", "openai-responses", "anthropic-messages", "google-generative-ai"];
|
|
@@ -218,175 +387,6 @@ function nonempty(value) {
|
|
|
218
387
|
return typeof value === "string" && value.length > 0;
|
|
219
388
|
}
|
|
220
389
|
|
|
221
|
-
// packages/protocol/src/rtc.ts
|
|
222
|
-
function rtcAddressScope(address) {
|
|
223
|
-
const ip = parseRtcAddress(address);
|
|
224
|
-
if (ip === undefined)
|
|
225
|
-
return "unknown";
|
|
226
|
-
if (ip.family === 4)
|
|
227
|
-
return ipv4Scope(ip.octets);
|
|
228
|
-
const octets = mappedIpv4(ip.segments);
|
|
229
|
-
return octets === undefined ? ipv6Scope(ip.segments) : ipv4Scope(octets);
|
|
230
|
-
}
|
|
231
|
-
function rtcCandidateEnd(info) {
|
|
232
|
-
return {
|
|
233
|
-
...info.type === undefined ? {} : { type: info.type },
|
|
234
|
-
...info.address === undefined ? {} : { address: info.address },
|
|
235
|
-
scope: rtcAddressScope(info.address)
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
function rtcSelectedPair(local, remote) {
|
|
239
|
-
return { local: rtcCandidateEnd(local), remote: rtcCandidateEnd(remote) };
|
|
240
|
-
}
|
|
241
|
-
function parseRtcAddress(address) {
|
|
242
|
-
if (address === undefined)
|
|
243
|
-
return;
|
|
244
|
-
const text = address.trim();
|
|
245
|
-
if (text === "")
|
|
246
|
-
return;
|
|
247
|
-
let host = text;
|
|
248
|
-
if (text.startsWith("[")) {
|
|
249
|
-
const close = text.indexOf("]");
|
|
250
|
-
if (close === -1)
|
|
251
|
-
return;
|
|
252
|
-
const tail = text.slice(close + 1);
|
|
253
|
-
if (tail !== "" && !/^:\d+$/.test(tail))
|
|
254
|
-
return;
|
|
255
|
-
host = text.slice(1, close);
|
|
256
|
-
} else if (text.split(":").length === 2) {
|
|
257
|
-
const [candidate, port] = text.split(":");
|
|
258
|
-
if (/^\d+$/.test(port))
|
|
259
|
-
host = candidate;
|
|
260
|
-
}
|
|
261
|
-
const zone = host.indexOf("%");
|
|
262
|
-
if (zone !== -1)
|
|
263
|
-
host = host.slice(0, zone);
|
|
264
|
-
const ipv4 = parseIpv4(host);
|
|
265
|
-
if (ipv4 !== undefined)
|
|
266
|
-
return { family: 4, octets: ipv4 };
|
|
267
|
-
const ipv6 = parseIpv6(host);
|
|
268
|
-
return ipv6 === undefined ? undefined : { family: 6, segments: ipv6 };
|
|
269
|
-
}
|
|
270
|
-
function parseIpv4(text) {
|
|
271
|
-
const parts = text.split(".");
|
|
272
|
-
if (parts.length !== 4)
|
|
273
|
-
return;
|
|
274
|
-
const octets = [];
|
|
275
|
-
for (const part of parts) {
|
|
276
|
-
if (!/^(?:0|[1-9]\d{0,2})$/.test(part))
|
|
277
|
-
return;
|
|
278
|
-
const value = Number(part);
|
|
279
|
-
if (value > 255)
|
|
280
|
-
return;
|
|
281
|
-
octets.push(value);
|
|
282
|
-
}
|
|
283
|
-
return octets;
|
|
284
|
-
}
|
|
285
|
-
function parseIpv6(text) {
|
|
286
|
-
const halves = text.split("::");
|
|
287
|
-
if (halves.length > 2)
|
|
288
|
-
return;
|
|
289
|
-
const parsePart = (part, allowIpv4) => {
|
|
290
|
-
if (part === "")
|
|
291
|
-
return [];
|
|
292
|
-
const tokens = part.split(":");
|
|
293
|
-
const groups = [];
|
|
294
|
-
for (let index = 0;index < tokens.length; index++) {
|
|
295
|
-
const token = tokens[index];
|
|
296
|
-
if (allowIpv4 && index === tokens.length - 1 && token.includes(".")) {
|
|
297
|
-
const v4 = parseIpv4(token);
|
|
298
|
-
if (v4 === undefined)
|
|
299
|
-
return;
|
|
300
|
-
groups.push(v4[0] << 8 | v4[1], v4[2] << 8 | v4[3]);
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
if (!/^[0-9a-fA-F]{1,4}$/.test(token))
|
|
304
|
-
return;
|
|
305
|
-
groups.push(Number.parseInt(token, 16));
|
|
306
|
-
}
|
|
307
|
-
return groups;
|
|
308
|
-
};
|
|
309
|
-
if (halves.length === 1) {
|
|
310
|
-
const groups = parsePart(text, true);
|
|
311
|
-
return groups?.length === 8 ? groups : undefined;
|
|
312
|
-
}
|
|
313
|
-
const left = parsePart(halves[0], false);
|
|
314
|
-
const right = parsePart(halves[1], true);
|
|
315
|
-
if (left === undefined || right === undefined)
|
|
316
|
-
return;
|
|
317
|
-
const zeros = 8 - left.length - right.length;
|
|
318
|
-
if (zeros < 1)
|
|
319
|
-
return;
|
|
320
|
-
return [...left, ...new Array(zeros).fill(0), ...right];
|
|
321
|
-
}
|
|
322
|
-
function mappedIpv4(segments) {
|
|
323
|
-
if (!segments.slice(0, 5).every((group) => group === 0) || segments[5] !== 65535)
|
|
324
|
-
return;
|
|
325
|
-
return [segments[6] >> 8, segments[6] & 255, segments[7] >> 8, segments[7] & 255];
|
|
326
|
-
}
|
|
327
|
-
function ipv4Scope(octets) {
|
|
328
|
-
const [a, b] = octets;
|
|
329
|
-
if (a === 127)
|
|
330
|
-
return "loopback";
|
|
331
|
-
if (a === 100 && b >= 64 && b <= 127)
|
|
332
|
-
return "tailnet";
|
|
333
|
-
if (a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254) {
|
|
334
|
-
return "lan";
|
|
335
|
-
}
|
|
336
|
-
return "public";
|
|
337
|
-
}
|
|
338
|
-
function ipv6Scope(segments) {
|
|
339
|
-
if (segments[0] === 64890 && segments[1] === 4444 && segments[2] === 41440)
|
|
340
|
-
return "tailnet";
|
|
341
|
-
if ((segments[0] & 65024) === 64512 || (segments[0] & 65472) === 65152)
|
|
342
|
-
return "lan";
|
|
343
|
-
if (segments.slice(0, 7).every((group) => group === 0) && segments[7] === 1)
|
|
344
|
-
return "loopback";
|
|
345
|
-
return "public";
|
|
346
|
-
}
|
|
347
|
-
var RTC_DEFAULT_STUN = ["stun:stun.cloudflare.com:3478"];
|
|
348
|
-
var RTC_DATA_CHANNEL_LABEL = "pinomad";
|
|
349
|
-
var RTC_MAX_RECORD_BYTES = 65535;
|
|
350
|
-
var RTC_DEFAULT_MAX_MESSAGE_SIZE = 65536;
|
|
351
|
-
var RTC_MIN_MAX_MESSAGE_SIZE = RTC_MAX_RECORD_BYTES;
|
|
352
|
-
function isRtcHello(value) {
|
|
353
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
354
|
-
return false;
|
|
355
|
-
const hello = value;
|
|
356
|
-
return Array.isArray(hello.stun) && hello.stun.every((server) => typeof server === "string" && server.length > 0) && hello.signalOnly === true;
|
|
357
|
-
}
|
|
358
|
-
function rtcCandidateType(candidate) {
|
|
359
|
-
const parts = candidate.trim().split(/\s+/);
|
|
360
|
-
const at = parts.indexOf("typ");
|
|
361
|
-
return at === -1 ? undefined : parts[at + 1];
|
|
362
|
-
}
|
|
363
|
-
function sdpCandidates(sdp) {
|
|
364
|
-
return sdp.split(/\r?\n/).filter((line) => line.startsWith("a=candidate:")).map((line) => line.slice("a=".length));
|
|
365
|
-
}
|
|
366
|
-
function withRtcCandidates(sdp, candidates) {
|
|
367
|
-
const lines = sdp.split(/\r?\n/).filter((line) => line !== "" && !line.startsWith("a=candidate:") && line !== "a=end-of-candidates");
|
|
368
|
-
const unique = [...new Set(candidates.map((candidate) => candidate.replace(/^a=/, "")))];
|
|
369
|
-
return [...lines, ...unique.map((candidate) => `a=${candidate}`), "a=end-of-candidates", ""].join(`\r
|
|
370
|
-
`);
|
|
371
|
-
}
|
|
372
|
-
function parseRtcMaxMessageSize(sdp) {
|
|
373
|
-
const line = sdp.split(/\r?\n/).find((entry) => entry.startsWith("a=max-message-size:"));
|
|
374
|
-
if (line === undefined)
|
|
375
|
-
return { kind: "bounded", bytes: RTC_DEFAULT_MAX_MESSAGE_SIZE };
|
|
376
|
-
const raw = line.slice("a=max-message-size:".length).trim();
|
|
377
|
-
if (!/^\d+$/.test(raw))
|
|
378
|
-
return { kind: "invalid", reason: `malformed a=max-message-size: ${raw}` };
|
|
379
|
-
const bytes = Number(raw);
|
|
380
|
-
if (!Number.isSafeInteger(bytes))
|
|
381
|
-
return { kind: "invalid", reason: `a=max-message-size is not a safe integer: ${raw}` };
|
|
382
|
-
if (bytes === 0)
|
|
383
|
-
return { kind: "unlimited" };
|
|
384
|
-
if (bytes < RTC_MIN_MAX_MESSAGE_SIZE) {
|
|
385
|
-
return { kind: "invalid", reason: `a=max-message-size ${bytes} cannot carry a ${RTC_MIN_MAX_MESSAGE_SIZE}-byte record` };
|
|
386
|
-
}
|
|
387
|
-
return { kind: "bounded", bytes };
|
|
388
|
-
}
|
|
389
|
-
|
|
390
390
|
// packages/protocol/src/attachments.ts
|
|
391
391
|
var MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
|
|
392
392
|
var MAX_ATTACHMENTS_PER_MESSAGE = 10;
|
|
@@ -619,4 +619,4 @@ function homeOf(index, summaries, id) {
|
|
|
619
619
|
return;
|
|
620
620
|
}
|
|
621
621
|
|
|
622
|
-
export {
|
|
622
|
+
export { rtcSelectedPair, RTC_DEFAULT_STUN, RTC_DATA_CHANNEL_LABEL, RTC_MAX_RECORD_BYTES, isRtcHello, rtcCandidateType, sdpCandidates, withRtcCandidates, parseRtcMaxMessageSize, isCustomProviderApi, HOST_STREAM_NAMES, hostStreams, conversationStream, docStream, UNAUTHORIZED_CLOSE_CODE, INVALID_FRAME_CLOSE_CODE, PROTOCOL, hostSupportsCall, hostSupportsFeature, parseClientFrame, MAX_ATTACHMENTS_PER_MESSAGE, MAX_PENDING_ATTACHMENTS, MAX_PENDING_ATTACHMENT_BYTES, ATTACHMENT_TTL_MS, MAX_ATTACHMENT_NAME_LENGTH, isAttachmentId, base64DecodedLength, attachmentBeginProblem, attachmentChunkProblem, attachmentBlock, parseAttachmentBlock, organize, checkoutOf, homeOf };
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
fromBase64Url,
|
|
4
4
|
parseEndpointSet,
|
|
5
5
|
toBase64Url
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-pqe7q1s7.js";
|
|
7
7
|
|
|
8
8
|
// packages/protocol/src/relay.ts
|
|
9
9
|
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
@@ -12,8 +12,11 @@ var RELAY_CLOSE = {
|
|
|
12
12
|
hostNotAllowed: 4603,
|
|
13
13
|
hostOffline: 4604,
|
|
14
14
|
unknownConnection: 4605,
|
|
15
|
+
handshakeTimeout: 4608,
|
|
15
16
|
replaced: 4609,
|
|
16
|
-
acceptTimeout: 4610
|
|
17
|
+
acceptTimeout: 4610,
|
|
18
|
+
tooManyConnections: 4629,
|
|
19
|
+
trafficLimit: 4630
|
|
17
20
|
};
|
|
18
21
|
function encodeRelayFrame(frame) {
|
|
19
22
|
return JSON.stringify(frame);
|
|
@@ -21,6 +24,7 @@ function encodeRelayFrame(frame) {
|
|
|
21
24
|
var isString = (value) => typeof value === "string";
|
|
22
25
|
var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
23
26
|
var isBoundedString = (value, max = 64) => isString(value) && value.length > 0 && value.length <= max;
|
|
27
|
+
var isDeviceKey = (value) => isBoundedString(value) && /^[A-Za-z0-9_-]+$/.test(value);
|
|
24
28
|
function decodeJson(text) {
|
|
25
29
|
try {
|
|
26
30
|
return JSON.parse(text);
|
|
@@ -45,6 +49,10 @@ function decodeRelayToHost(text) {
|
|
|
45
49
|
if (!isBoundedString(value.requestId))
|
|
46
50
|
throw new Error("malformed pairing-request");
|
|
47
51
|
return { t: "pairing-request", requestId: value.requestId };
|
|
52
|
+
case "revoke-device":
|
|
53
|
+
if (!isDeviceKey(value.publicKey))
|
|
54
|
+
throw new Error("malformed revoke-device");
|
|
55
|
+
return { t: "revoke-device", publicKey: value.publicKey };
|
|
48
56
|
case "push-result":
|
|
49
57
|
return decodePushResult(value);
|
|
50
58
|
case "joined":
|
|
@@ -140,6 +148,14 @@ function decodeHostToRelay(text) {
|
|
|
140
148
|
if (!isBoundedString(value.name, HOST_NAME_MAX_UNITS))
|
|
141
149
|
throw new Error("malformed name");
|
|
142
150
|
return { t: "name", name: value.name };
|
|
151
|
+
case "device-admitted":
|
|
152
|
+
if (!isBoundedString(value.requestId) || !isDeviceKey(value.publicKey))
|
|
153
|
+
throw new Error("malformed device-admitted");
|
|
154
|
+
return { t: "device-admitted", requestId: value.requestId, publicKey: value.publicKey };
|
|
155
|
+
case "device-revoked":
|
|
156
|
+
if (!isDeviceKey(value.publicKey))
|
|
157
|
+
throw new Error("malformed device-revoked");
|
|
158
|
+
return { t: "device-revoked", publicKey: value.publicKey };
|
|
143
159
|
default:
|
|
144
160
|
throw new Error("unknown host frame");
|
|
145
161
|
}
|
|
@@ -234,6 +250,7 @@ function verifyRelayChallengeV2(signature, args) {
|
|
|
234
250
|
// apps/host/src/relay-link.ts
|
|
235
251
|
import { randomBytes } from "node:crypto";
|
|
236
252
|
import { WebSocket } from "ws";
|
|
253
|
+
var MAX_UNSENT_REPORTS = 64;
|
|
237
254
|
function relayWsBase(origin) {
|
|
238
255
|
const normalized = origin.replace(/\/+$/, "");
|
|
239
256
|
if (normalized.startsWith("https://"))
|
|
@@ -257,6 +274,10 @@ function startRelayLink(options) {
|
|
|
257
274
|
const minDelay = options.reconnectDelayMs?.min ?? 1000;
|
|
258
275
|
const maxDelay = options.reconnectDelayMs?.max ?? 30000;
|
|
259
276
|
const maxPendingAccepts = options.maxPendingAccepts ?? 16;
|
|
277
|
+
const features = [
|
|
278
|
+
...options.accountOffer === undefined ? [] : ["pairing"],
|
|
279
|
+
...options.deviceReports === undefined ? [] : ["device-reports"]
|
|
280
|
+
];
|
|
260
281
|
let state = { status: "connecting" };
|
|
261
282
|
let stopped = false;
|
|
262
283
|
let control;
|
|
@@ -266,6 +287,8 @@ function startRelayLink(options) {
|
|
|
266
287
|
let pushEnvironments;
|
|
267
288
|
let reportedName;
|
|
268
289
|
let nameDeclared = false;
|
|
290
|
+
let reportsDeclared = false;
|
|
291
|
+
const unsentReports = [];
|
|
269
292
|
const pushes = new Map;
|
|
270
293
|
function setState(next) {
|
|
271
294
|
const changed = state.status !== next.status || state.status === "rejected" && next.status === "rejected" && state.reason !== next.reason;
|
|
@@ -302,6 +325,7 @@ function startRelayLink(options) {
|
|
|
302
325
|
control = socket;
|
|
303
326
|
pushEnvironments = undefined;
|
|
304
327
|
nameDeclared = false;
|
|
328
|
+
reportsDeclared = false;
|
|
305
329
|
reportedName = undefined;
|
|
306
330
|
watch(socket);
|
|
307
331
|
socket.on("message", (data, isBinary) => onControlMessage(socket, data, isBinary));
|
|
@@ -336,7 +360,7 @@ function startRelayLink(options) {
|
|
|
336
360
|
signature: signRelayChallenge(options.signingKey.secretKey, challenge),
|
|
337
361
|
signatureV2: signRelayChallengeV2(options.signingKey.secretKey, { ...challenge, noisePublicKey }),
|
|
338
362
|
noisePublicKey,
|
|
339
|
-
...
|
|
363
|
+
...features.length === 0 ? {} : { features },
|
|
340
364
|
...reportedName === undefined ? {} : { name: reportedName }
|
|
341
365
|
}));
|
|
342
366
|
break;
|
|
@@ -345,6 +369,12 @@ function startRelayLink(options) {
|
|
|
345
369
|
delay = minDelay;
|
|
346
370
|
pushEnvironments = frame.push;
|
|
347
371
|
nameDeclared = frame.features?.includes("name") === true;
|
|
372
|
+
reportsDeclared = options.deviceReports !== undefined && frame.features?.includes("device-reports") === true;
|
|
373
|
+
if (reportsDeclared) {
|
|
374
|
+
for (const report of unsentReports.splice(0)) {
|
|
375
|
+
socket.send(encodeRelayFrame({ t: "device-admitted", ...report }));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
348
378
|
const current = options.hostName?.value;
|
|
349
379
|
if (nameDeclared && current !== undefined && current !== reportedName) {
|
|
350
380
|
reportedName = current;
|
|
@@ -371,7 +401,7 @@ function startRelayLink(options) {
|
|
|
371
401
|
refused("unavailable");
|
|
372
402
|
break;
|
|
373
403
|
}
|
|
374
|
-
accountOffer().then((result) => {
|
|
404
|
+
accountOffer(frame.requestId).then((result) => {
|
|
375
405
|
if (socket !== control || socket.readyState !== WebSocket.OPEN)
|
|
376
406
|
return;
|
|
377
407
|
const checked = result.ok && result.endpointSet !== undefined ? parseEndpointSet(result.endpointSet) : undefined;
|
|
@@ -393,6 +423,18 @@ function startRelayLink(options) {
|
|
|
393
423
|
});
|
|
394
424
|
break;
|
|
395
425
|
}
|
|
426
|
+
case "revoke-device": {
|
|
427
|
+
const reports = options.deviceReports;
|
|
428
|
+
if (reports === undefined)
|
|
429
|
+
break;
|
|
430
|
+
const { publicKey } = frame;
|
|
431
|
+
reports.revoke(publicKey).then(() => {
|
|
432
|
+
if (socket === control && socket.readyState === WebSocket.OPEN) {
|
|
433
|
+
socket.send(encodeRelayFrame({ t: "device-revoked", publicKey }));
|
|
434
|
+
}
|
|
435
|
+
}, () => {});
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
396
438
|
case "incoming": {
|
|
397
439
|
if (pendingAccepts >= maxPendingAccepts)
|
|
398
440
|
break;
|
|
@@ -415,6 +457,7 @@ function startRelayLink(options) {
|
|
|
415
457
|
control = undefined;
|
|
416
458
|
pushEnvironments = undefined;
|
|
417
459
|
nameDeclared = false;
|
|
460
|
+
reportsDeclared = false;
|
|
418
461
|
reportedName = undefined;
|
|
419
462
|
for (const [requestId, request] of pushes) {
|
|
420
463
|
if (request.socket === socket) {
|
|
@@ -478,6 +521,18 @@ function startRelayLink(options) {
|
|
|
478
521
|
socket.send(encodeRelayFrame({ t: "push", requestId, ...request }));
|
|
479
522
|
});
|
|
480
523
|
},
|
|
524
|
+
reportAdmitted(requestId, publicKey) {
|
|
525
|
+
if (options.deviceReports === undefined)
|
|
526
|
+
return;
|
|
527
|
+
const socket = control;
|
|
528
|
+
if (reportsDeclared && socket !== undefined && socket.readyState === WebSocket.OPEN) {
|
|
529
|
+
socket.send(encodeRelayFrame({ t: "device-admitted", requestId, publicKey }));
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
unsentReports.push({ requestId, publicKey });
|
|
533
|
+
if (unsentReports.length > MAX_UNSENT_REPORTS)
|
|
534
|
+
unsentReports.shift();
|
|
535
|
+
},
|
|
481
536
|
close() {
|
|
482
537
|
stopped = true;
|
|
483
538
|
if (retryTimer !== undefined)
|
package/dist/pinomad.js
CHANGED
|
@@ -5,39 +5,45 @@ import {
|
|
|
5
5
|
} from "./chunk-kzbzacz9.js";
|
|
6
6
|
import {
|
|
7
7
|
packagedVersion
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-sd5fkxjj.js";
|
|
9
9
|
import {
|
|
10
10
|
__require
|
|
11
11
|
} from "./chunk-df32avw6.js";
|
|
12
12
|
|
|
13
13
|
// apps/cli/src/pinomad.ts
|
|
14
14
|
var COMMANDS = {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
up: () => import("./chunk-n1m1tjrk.js"),
|
|
16
|
+
status: () => import("./chunk-b457q0tm.js"),
|
|
17
|
+
host: () => import("./chunk-67gq282t.js"),
|
|
18
|
+
server: () => import("./chunk-9h5vawnm.js"),
|
|
19
|
+
relay: () => import("./chunk-9h5vawnm.js"),
|
|
20
|
+
service: () => import("./chunk-h5q2y57m.js"),
|
|
21
|
+
upgrade: () => import("./chunk-6d16c2fv.js"),
|
|
22
|
+
link: () => import("./chunk-as6pyf00.js"),
|
|
23
|
+
pair: () => import("./chunk-tj57aqav.js"),
|
|
24
|
+
join: () => import("./chunk-greb3dp6.js"),
|
|
25
|
+
leave: () => import("./chunk-4dcs8sbj.js")
|
|
24
26
|
};
|
|
25
27
|
var USAGE = `pinomad — PiNomad host, server, and utilities
|
|
26
28
|
|
|
27
29
|
usage: pinomad <command> [args…]
|
|
28
30
|
|
|
31
|
+
up [--server] [--lan] [--yes] set this host up: account, LAN, service
|
|
32
|
+
up --origin URL --remote-port N server to join (app.pinomad.si), LAN port (7422)
|
|
33
|
+
up --no-server|--no-lan [--data-dir D] turn a path off; with no flags, up asks
|
|
34
|
+
status [--data-dir D] joined server and account, LAN, service, host
|
|
29
35
|
host [--data-dir DIR] [--port 7420] [--project DIR]… run the host in the foreground
|
|
30
36
|
host --remote-port PORT [--public-url URL] remote access, LAN (ADR-0008)
|
|
31
37
|
host --relay URL remote access via relay (ADR-0015)
|
|
32
38
|
host --faux "answer" [--faux-tps 40] no real model, for tests
|
|
33
39
|
server --public-origin O [--data-dir D] [--trust-proxy] run the server
|
|
34
|
-
server passwd [--data-dir D] [--user U]
|
|
40
|
+
server passwd [--data-dir D] [--user U] reset an account's password (stdin)
|
|
35
41
|
relay … old name of server
|
|
36
42
|
service install|uninstall|status|restart|logs user-level host service
|
|
37
43
|
upgrade [--force] [address flags] upgrade the package and restart the service
|
|
38
44
|
link [--data-dir D|--url U …] print the browser link
|
|
39
45
|
pair [--data-dir D|--url U …] print a pairing QR
|
|
40
|
-
join <origin>
|
|
46
|
+
join <origin> [token] [--data-dir D] join this host to a server account
|
|
41
47
|
leave [--data-dir D] leave the server account
|
|
42
48
|
--version, -v print the version
|
|
43
49
|
`;
|