vibedate 0.8.3 → 0.8.5
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/dist/{chunk-3FJDVN72.js → chunk-KIFZHCWP.js} +228 -47
- package/dist/{chunk-XUWNLMOY.js → chunk-VHO7JWSS.js} +22 -3
- package/dist/cli.js +70 -24
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +2 -2
- package/dist/{room-DpZhNvuu.d.ts → room-B-0DreC0.d.ts} +13 -1
- package/package.json +4 -2
|
@@ -18,13 +18,18 @@ import { makeEvent, notify as vibeCoreNotify } from "@pooriaarab/vibe-core";
|
|
|
18
18
|
var MAX_TEXT_LEN = 4e3;
|
|
19
19
|
var MAX_ID_LEN = 64;
|
|
20
20
|
var MAX_B64_CHUNK_LEN = 16 * 1024;
|
|
21
|
+
var MAX_BINARY_CHUNK_BYTES = 64 * 1024;
|
|
21
22
|
var MAX_MEDIA_SIZE = 25 * 1024 * 1024;
|
|
22
23
|
var MAX_MIME_LEN = 128;
|
|
23
24
|
var MAX_NAME_LEN = 256;
|
|
24
25
|
var MAX_SDP_LEN = 64 * 1024;
|
|
25
26
|
var MAX_CANDIDATE_LEN = 4 * 1024;
|
|
26
27
|
var MAX_FRAME_LEN = Math.max(MAX_B64_CHUNK_LEN, 2 * MAX_SDP_LEN) + 2048;
|
|
28
|
+
var BIN_MEDIA_CHUNK_TAG = 1;
|
|
27
29
|
function serializeFrame(f) {
|
|
30
|
+
if (f.t === "media-chunk" && "data" in f) {
|
|
31
|
+
throw new Error("binary media-chunk must use serializeBinaryMediaChunk, not serializeFrame");
|
|
32
|
+
}
|
|
28
33
|
return JSON.stringify(f);
|
|
29
34
|
}
|
|
30
35
|
function parseFrame(raw) {
|
|
@@ -123,6 +128,107 @@ function parseFrame(raw) {
|
|
|
123
128
|
return null;
|
|
124
129
|
}
|
|
125
130
|
}
|
|
131
|
+
function serializeBinaryMediaChunk(parts) {
|
|
132
|
+
const { id, seq, data } = parts;
|
|
133
|
+
if (typeof id !== "string" || id.length === 0 || id.length > MAX_ID_LEN) {
|
|
134
|
+
throw new Error(`invalid media chunk id length: ${typeof id === "string" ? id.length : "?"}`);
|
|
135
|
+
}
|
|
136
|
+
if (typeof seq !== "number" || !Number.isFinite(seq) || !Number.isInteger(seq) || seq < 0) {
|
|
137
|
+
throw new Error(`invalid media chunk seq: ${String(seq)}`);
|
|
138
|
+
}
|
|
139
|
+
if (!Buffer.isBuffer(data) || data.length === 0 || data.length > MAX_BINARY_CHUNK_BYTES) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`invalid media chunk payload length: ${Buffer.isBuffer(data) ? data.length : "n/a"}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const hdr = Buffer.from(JSON.stringify({ id, seq }), "utf8");
|
|
145
|
+
if (hdr.length > 65535) throw new Error(`media chunk header too large: ${hdr.length}`);
|
|
146
|
+
const out = Buffer.allocUnsafe(1 + 2 + 4 + hdr.length + data.length);
|
|
147
|
+
let o = 0;
|
|
148
|
+
out[o++] = BIN_MEDIA_CHUNK_TAG;
|
|
149
|
+
out.writeUInt16BE(hdr.length, o);
|
|
150
|
+
o += 2;
|
|
151
|
+
out.writeUInt32BE(data.length, o);
|
|
152
|
+
o += 4;
|
|
153
|
+
hdr.copy(out, o);
|
|
154
|
+
o += hdr.length;
|
|
155
|
+
data.copy(out, o);
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
function tryParseBinaryMediaChunk(buf) {
|
|
159
|
+
if (buf.length === 0) return { frame: null, bytesConsumed: 0 };
|
|
160
|
+
if (buf[0] !== BIN_MEDIA_CHUNK_TAG) return { frame: null, bytesConsumed: 0 };
|
|
161
|
+
if (buf.length < 7) return { frame: null, bytesConsumed: 0 };
|
|
162
|
+
const hdrLen = buf.readUInt16BE(1);
|
|
163
|
+
const payloadLen = buf.readUInt32BE(3);
|
|
164
|
+
const MAX_HDR_LEN = 512;
|
|
165
|
+
if (hdrLen === 0 || hdrLen > MAX_HDR_LEN || payloadLen === 0 || payloadLen > MAX_BINARY_CHUNK_BYTES) {
|
|
166
|
+
return { frame: null, bytesConsumed: 1 };
|
|
167
|
+
}
|
|
168
|
+
const total = 7 + hdrLen + payloadLen;
|
|
169
|
+
if (buf.length < total) return { frame: null, bytesConsumed: 0 };
|
|
170
|
+
const hdrBuf = buf.subarray(7, 7 + hdrLen);
|
|
171
|
+
let hdrObj;
|
|
172
|
+
try {
|
|
173
|
+
hdrObj = JSON.parse(hdrBuf.toString("utf8"));
|
|
174
|
+
} catch {
|
|
175
|
+
return { frame: null, bytesConsumed: 1 };
|
|
176
|
+
}
|
|
177
|
+
if (typeof hdrObj !== "object" || hdrObj === null || Array.isArray(hdrObj)) {
|
|
178
|
+
return { frame: null, bytesConsumed: 1 };
|
|
179
|
+
}
|
|
180
|
+
const r = hdrObj;
|
|
181
|
+
const id = r["id"];
|
|
182
|
+
const seq = r["seq"];
|
|
183
|
+
if (typeof id !== "string" || id.length === 0 || id.length > MAX_ID_LEN) {
|
|
184
|
+
return { frame: null, bytesConsumed: 1 };
|
|
185
|
+
}
|
|
186
|
+
if (typeof seq !== "number" || !Number.isFinite(seq) || !Number.isInteger(seq) || seq < 0) {
|
|
187
|
+
return { frame: null, bytesConsumed: 1 };
|
|
188
|
+
}
|
|
189
|
+
const data = Buffer.from(buf.subarray(7 + hdrLen, 7 + hdrLen + payloadLen));
|
|
190
|
+
return {
|
|
191
|
+
frame: { t: "media-chunk", id, seq, data },
|
|
192
|
+
bytesConsumed: total
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function pullFramesFromBuffer(buf) {
|
|
196
|
+
const frames = [];
|
|
197
|
+
let offset = 0;
|
|
198
|
+
while (offset < buf.length) {
|
|
199
|
+
const first = buf[offset];
|
|
200
|
+
if (first === BIN_MEDIA_CHUNK_TAG) {
|
|
201
|
+
const slice = offset === 0 ? buf : buf.subarray(offset);
|
|
202
|
+
const { frame: frame2, bytesConsumed } = tryParseBinaryMediaChunk(slice);
|
|
203
|
+
if (bytesConsumed === 0) {
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
if (frame2 !== null) frames.push(frame2);
|
|
207
|
+
offset += bytesConsumed;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const nlRel = buf.indexOf(10, offset);
|
|
211
|
+
if (nlRel < 0) {
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
const lineBuf = buf.subarray(offset, nlRel);
|
|
215
|
+
offset = nlRel + 1;
|
|
216
|
+
if (lineBuf.length === 0) continue;
|
|
217
|
+
let onlyWs = true;
|
|
218
|
+
for (let i = 0; i < lineBuf.length; i++) {
|
|
219
|
+
const c = lineBuf[i];
|
|
220
|
+
if (c !== 32 && c !== 9 && c !== 13) {
|
|
221
|
+
onlyWs = false;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (onlyWs) continue;
|
|
226
|
+
const frame = parseFrame(lineBuf);
|
|
227
|
+
if (frame !== null) frames.push(frame);
|
|
228
|
+
}
|
|
229
|
+
const rest = offset === 0 ? buf : buf.subarray(offset);
|
|
230
|
+
return { frames, rest: rest.length === 0 ? Buffer.alloc(0) : Buffer.from(rest) };
|
|
231
|
+
}
|
|
126
232
|
|
|
127
233
|
// src/identity.ts
|
|
128
234
|
import {
|
|
@@ -137,10 +243,15 @@ import { chmodSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writ
|
|
|
137
243
|
import path2 from "path";
|
|
138
244
|
|
|
139
245
|
// src/state.ts
|
|
140
|
-
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
246
|
+
import { mkdirSync, readFileSync, rmSync, renameSync, writeFileSync } from "fs";
|
|
141
247
|
import os from "os";
|
|
142
248
|
import path from "path";
|
|
143
249
|
import { createConsentLedger } from "@pooriaarab/vibe-core";
|
|
250
|
+
function writeJsonAtomic(filePath, obj) {
|
|
251
|
+
const tmpPath = `${filePath}.tmp`;
|
|
252
|
+
writeFileSync(tmpPath, JSON.stringify(obj, null, 2) + "\n", "utf8");
|
|
253
|
+
renameSync(tmpPath, filePath);
|
|
254
|
+
}
|
|
144
255
|
var CONSENT_SCOPE = "share:league";
|
|
145
256
|
var LIVE_CONSENT_SCOPE = "share:live";
|
|
146
257
|
function defaultStateDir() {
|
|
@@ -162,7 +273,7 @@ var FileConsentStore = class {
|
|
|
162
273
|
}
|
|
163
274
|
save(grants) {
|
|
164
275
|
mkdirSync(path.dirname(this.file), { recursive: true });
|
|
165
|
-
|
|
276
|
+
writeJsonAtomic(this.file, { grants });
|
|
166
277
|
}
|
|
167
278
|
};
|
|
168
279
|
function createLedger(dir = defaultStateDir()) {
|
|
@@ -184,7 +295,7 @@ function connectProfile(snapshot, handle, dir = defaultStateDir()) {
|
|
|
184
295
|
connectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
185
296
|
};
|
|
186
297
|
mkdirSync(dir, { recursive: true });
|
|
187
|
-
|
|
298
|
+
writeJsonAtomic(profilePath(dir), state);
|
|
188
299
|
return state;
|
|
189
300
|
}
|
|
190
301
|
function loadProfile(dir = defaultStateDir()) {
|
|
@@ -245,14 +356,10 @@ function saveHandle(handle, dir = defaultStateDir()) {
|
|
|
245
356
|
);
|
|
246
357
|
}
|
|
247
358
|
mkdirSync(dir, { recursive: true });
|
|
248
|
-
|
|
359
|
+
writeJsonAtomic(handleFilePath(dir), { handle: canonical });
|
|
249
360
|
const existing = loadProfile(dir);
|
|
250
361
|
if (existing !== null) {
|
|
251
|
-
|
|
252
|
-
profilePath(dir),
|
|
253
|
-
JSON.stringify({ ...existing, handle: canonical }, null, 2) + "\n",
|
|
254
|
-
"utf8"
|
|
255
|
-
);
|
|
362
|
+
writeJsonAtomic(profilePath(dir), { ...existing, handle: canonical });
|
|
256
363
|
}
|
|
257
364
|
return canonical;
|
|
258
365
|
}
|
|
@@ -270,7 +377,7 @@ function blocklistPath(dir) {
|
|
|
270
377
|
}
|
|
271
378
|
function persistBlocklist(blocked, dir) {
|
|
272
379
|
mkdirSync(dir, { recursive: true });
|
|
273
|
-
|
|
380
|
+
writeJsonAtomic(blocklistPath(dir), { blocked });
|
|
274
381
|
}
|
|
275
382
|
function loadBlocklist(dir = defaultStateDir()) {
|
|
276
383
|
try {
|
|
@@ -442,7 +549,7 @@ import { readFile } from "fs/promises";
|
|
|
442
549
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
443
550
|
import os2 from "os";
|
|
444
551
|
import path3 from "path";
|
|
445
|
-
var DEFAULT_CHUNK_BYTES =
|
|
552
|
+
var DEFAULT_CHUNK_BYTES = MAX_BINARY_CHUNK_BYTES;
|
|
446
553
|
var MIME_BY_EXT = {
|
|
447
554
|
".png": "image/png",
|
|
448
555
|
".jpg": "image/jpeg",
|
|
@@ -455,11 +562,32 @@ var MIME_BY_EXT = {
|
|
|
455
562
|
function inferMime(name) {
|
|
456
563
|
return MIME_BY_EXT[path3.extname(name).toLowerCase()] ?? "application/octet-stream";
|
|
457
564
|
}
|
|
458
|
-
function
|
|
459
|
-
return new Promise((resolve) => {
|
|
460
|
-
const ok = socket.write(
|
|
565
|
+
function writeBytes(socket, data) {
|
|
566
|
+
return new Promise((resolve, reject) => {
|
|
567
|
+
const ok = socket.write(data);
|
|
461
568
|
if (ok) resolve();
|
|
462
|
-
else
|
|
569
|
+
else {
|
|
570
|
+
const onDrain = () => {
|
|
571
|
+
cleanup();
|
|
572
|
+
resolve();
|
|
573
|
+
};
|
|
574
|
+
const onClose = () => {
|
|
575
|
+
cleanup();
|
|
576
|
+
reject(new Error("Socket closed before drain"));
|
|
577
|
+
};
|
|
578
|
+
const onError = (err) => {
|
|
579
|
+
cleanup();
|
|
580
|
+
reject(err);
|
|
581
|
+
};
|
|
582
|
+
const cleanup = () => {
|
|
583
|
+
socket.removeListener("drain", onDrain);
|
|
584
|
+
socket.removeListener("close", onClose);
|
|
585
|
+
socket.removeListener("error", onError);
|
|
586
|
+
};
|
|
587
|
+
socket.once("drain", onDrain);
|
|
588
|
+
socket.once("close", onClose);
|
|
589
|
+
socket.once("error", onError);
|
|
590
|
+
}
|
|
463
591
|
});
|
|
464
592
|
}
|
|
465
593
|
async function sendMedia(opts) {
|
|
@@ -471,15 +599,23 @@ async function sendMedia(opts) {
|
|
|
471
599
|
}
|
|
472
600
|
if (mime.length > MAX_MIME_LEN) throw new Error(`mime too long: ${mime.length} > ${MAX_MIME_LEN}`);
|
|
473
601
|
if (name.length > MAX_NAME_LEN) throw new Error(`name too long: ${name.length} > ${MAX_NAME_LEN}`);
|
|
474
|
-
await
|
|
475
|
-
const chunkBytes =
|
|
602
|
+
await writeBytes(socket, serializeFrame({ t: "media-start", id, mime, size, name }) + "\n");
|
|
603
|
+
const chunkBytes = Math.min(
|
|
604
|
+
opts.chunkBytes ?? DEFAULT_CHUNK_BYTES,
|
|
605
|
+
opts.legacyJson ? Math.floor(16 * 1024 * 3 / 4) : MAX_BINARY_CHUNK_BYTES
|
|
606
|
+
);
|
|
476
607
|
let seq = 0;
|
|
477
608
|
for (let off = 0; off < size; off += chunkBytes) {
|
|
478
|
-
const
|
|
479
|
-
|
|
609
|
+
const slice = data.subarray(off, off + chunkBytes);
|
|
610
|
+
if (opts.legacyJson) {
|
|
611
|
+
const b64 = slice.toString("base64");
|
|
612
|
+
await writeBytes(socket, serializeFrame({ t: "media-chunk", id, seq, b64 }) + "\n");
|
|
613
|
+
} else {
|
|
614
|
+
await writeBytes(socket, serializeBinaryMediaChunk({ id, seq, data: slice }));
|
|
615
|
+
}
|
|
480
616
|
seq++;
|
|
481
617
|
}
|
|
482
|
-
await
|
|
618
|
+
await writeBytes(socket, serializeFrame({ t: "media-end", id }) + "\n");
|
|
483
619
|
return { id, size };
|
|
484
620
|
}
|
|
485
621
|
async function sendMediaFile(opts) {
|
|
@@ -492,7 +628,8 @@ async function sendMediaFile(opts) {
|
|
|
492
628
|
mime,
|
|
493
629
|
name,
|
|
494
630
|
id: opts.id,
|
|
495
|
-
chunkBytes: opts.chunkBytes
|
|
631
|
+
chunkBytes: opts.chunkBytes,
|
|
632
|
+
legacyJson: opts.legacyJson
|
|
496
633
|
});
|
|
497
634
|
}
|
|
498
635
|
function safeName(id, name) {
|
|
@@ -533,9 +670,20 @@ var MediaReceiver = class {
|
|
|
533
670
|
return;
|
|
534
671
|
}
|
|
535
672
|
let bytes;
|
|
536
|
-
|
|
537
|
-
bytes =
|
|
538
|
-
|
|
673
|
+
if ("data" in frame && Buffer.isBuffer(frame.data)) {
|
|
674
|
+
bytes = frame.data;
|
|
675
|
+
if (bytes.length === 0 || bytes.length > MAX_BINARY_CHUNK_BYTES) {
|
|
676
|
+
this.abort(frame.id);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
} else if ("b64" in frame && typeof frame.b64 === "string") {
|
|
680
|
+
try {
|
|
681
|
+
bytes = Buffer.from(frame.b64, "base64");
|
|
682
|
+
} catch {
|
|
683
|
+
this.abort(frame.id);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
} else {
|
|
539
687
|
this.abort(frame.id);
|
|
540
688
|
return;
|
|
541
689
|
}
|
|
@@ -558,7 +706,9 @@ var MediaReceiver = class {
|
|
|
558
706
|
const filePath = path3.join(this.opts.tmpDir ?? os2.tmpdir(), safeName(frame.id, tx.name));
|
|
559
707
|
try {
|
|
560
708
|
writeFileSync3(filePath, buf);
|
|
561
|
-
} catch {
|
|
709
|
+
} catch (err) {
|
|
710
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
711
|
+
this.onMedia({ mime: tx.mime, name: tx.name, path: filePath, size: tx.received, error });
|
|
562
712
|
return;
|
|
563
713
|
}
|
|
564
714
|
this.onMedia({ mime: tx.mime, name: tx.name, path: filePath, size: tx.received });
|
|
@@ -576,7 +726,7 @@ function createPeerLink(socket, hello, initialBuffer = "", linkOpts = {}) {
|
|
|
576
726
|
const mediaCbs = /* @__PURE__ */ new Set();
|
|
577
727
|
const signalCbs = /* @__PURE__ */ new Set();
|
|
578
728
|
const closeCbs = /* @__PURE__ */ new Set();
|
|
579
|
-
let buf = initialBuffer;
|
|
729
|
+
let buf = typeof initialBuffer === "string" ? Buffer.from(initialBuffer, "utf8") : Buffer.from(initialBuffer);
|
|
580
730
|
let closed = false;
|
|
581
731
|
let mediaReceiver;
|
|
582
732
|
const ensureMediaReceiver = () => {
|
|
@@ -624,19 +774,15 @@ function createPeerLink(socket, hello, initialBuffer = "", linkOpts = {}) {
|
|
|
624
774
|
}
|
|
625
775
|
};
|
|
626
776
|
const pump = () => {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
if (line.trim() === "") continue;
|
|
632
|
-
const frame = parseFrame(line);
|
|
633
|
-
if (frame === null) continue;
|
|
634
|
-
dispatch(frame);
|
|
635
|
-
}
|
|
777
|
+
if (buf.length === 0) return;
|
|
778
|
+
const { frames, rest } = pullFramesFromBuffer(buf);
|
|
779
|
+
buf = rest;
|
|
780
|
+
for (const frame of frames) dispatch(frame);
|
|
636
781
|
};
|
|
637
782
|
pump();
|
|
638
783
|
socket.on("data", (chunk) => {
|
|
639
|
-
|
|
784
|
+
const bytes = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
785
|
+
buf = buf.length === 0 ? Buffer.from(bytes) : Buffer.concat([buf, bytes]);
|
|
640
786
|
pump();
|
|
641
787
|
});
|
|
642
788
|
socket.on("end", () => {
|
|
@@ -659,6 +805,9 @@ function createPeerLink(socket, hello, initialBuffer = "", linkOpts = {}) {
|
|
|
659
805
|
});
|
|
660
806
|
return {
|
|
661
807
|
hello,
|
|
808
|
+
get closed() {
|
|
809
|
+
return closed;
|
|
810
|
+
},
|
|
662
811
|
send(text) {
|
|
663
812
|
if (closed) return;
|
|
664
813
|
const frame = { t: "msg", id: randomUUID2(), text, at: Date.now() };
|
|
@@ -863,15 +1012,18 @@ async function startDiscovery(opts) {
|
|
|
863
1012
|
...hello.sig !== void 0 ? { sig: hello.sig } : {}
|
|
864
1013
|
}) + "\n"
|
|
865
1014
|
);
|
|
866
|
-
let buf =
|
|
1015
|
+
let buf = Buffer.alloc(0);
|
|
867
1016
|
let handedOff = false;
|
|
868
1017
|
const onData = (chunk) => {
|
|
869
1018
|
if (handedOff) return;
|
|
870
|
-
buf
|
|
1019
|
+
buf = buf.length === 0 ? Buffer.from(chunk) : Buffer.concat([buf, chunk]);
|
|
871
1020
|
let nl;
|
|
872
|
-
while ((nl = buf.indexOf(
|
|
873
|
-
|
|
874
|
-
|
|
1021
|
+
while ((nl = buf.indexOf(
|
|
1022
|
+
10
|
|
1023
|
+
/* \n */
|
|
1024
|
+
)) >= 0) {
|
|
1025
|
+
const line = buf.subarray(0, nl).toString("utf8");
|
|
1026
|
+
buf = buf.subarray(nl + 1);
|
|
875
1027
|
if (line.trim() === "") continue;
|
|
876
1028
|
const frame = parseFrame(line);
|
|
877
1029
|
if (frame === null) continue;
|
|
@@ -917,7 +1069,7 @@ async function startDiscovery(opts) {
|
|
|
917
1069
|
});
|
|
918
1070
|
onLink(link);
|
|
919
1071
|
}
|
|
920
|
-
buf =
|
|
1072
|
+
buf = Buffer.alloc(0);
|
|
921
1073
|
return;
|
|
922
1074
|
}
|
|
923
1075
|
};
|
|
@@ -1067,6 +1219,14 @@ async function createNostrRelayLink(opts) {
|
|
|
1067
1219
|
return;
|
|
1068
1220
|
}
|
|
1069
1221
|
peerNostrPubkey = event.pubkey;
|
|
1222
|
+
try {
|
|
1223
|
+
const parsed = JSON.parse(plaintext);
|
|
1224
|
+
if (parsed && typeof parsed === "object" && "__vibe_rtc" in parsed) {
|
|
1225
|
+
for (const cb of signalCbs) cb(parsed.__vibe_rtc);
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
} catch {
|
|
1229
|
+
}
|
|
1070
1230
|
const msg = { id: event.id, text: plaintext, at: event.created_at * 1e3 };
|
|
1071
1231
|
for (const cb of messageCbs) cb(msg);
|
|
1072
1232
|
return;
|
|
@@ -1075,6 +1235,9 @@ async function createNostrRelayLink(opts) {
|
|
|
1075
1235
|
publishPresence();
|
|
1076
1236
|
return {
|
|
1077
1237
|
hello,
|
|
1238
|
+
get closed() {
|
|
1239
|
+
return closed;
|
|
1240
|
+
},
|
|
1078
1241
|
send(text) {
|
|
1079
1242
|
if (closed) return;
|
|
1080
1243
|
if (peerNostrPubkey === void 0) {
|
|
@@ -1088,9 +1251,14 @@ async function createNostrRelayLink(opts) {
|
|
|
1088
1251
|
async sendMedia() {
|
|
1089
1252
|
return { id: "", size: 0 };
|
|
1090
1253
|
},
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1254
|
+
sendSignal(frame) {
|
|
1255
|
+
if (closed) return;
|
|
1256
|
+
const payload = JSON.stringify({ __vibe_rtc: frame });
|
|
1257
|
+
if (peerNostrPubkey === void 0) {
|
|
1258
|
+
pending.push(payload);
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
sendEncrypted(payload, peerNostrPubkey);
|
|
1094
1262
|
},
|
|
1095
1263
|
onMessage(cb) {
|
|
1096
1264
|
messageCbs.add(cb);
|
|
@@ -1163,6 +1331,7 @@ var ROOM_TOPIC_PREFIX = "vibedate-room:";
|
|
|
1163
1331
|
function roomTopic(name) {
|
|
1164
1332
|
return createHash3("sha256").update(`${ROOM_TOPIC_PREFIX}${name}`, "utf8").digest();
|
|
1165
1333
|
}
|
|
1334
|
+
var MAX_ROOM = 32;
|
|
1166
1335
|
async function startRoom(opts) {
|
|
1167
1336
|
const topic = roomTopic(opts.room);
|
|
1168
1337
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -1186,7 +1355,19 @@ async function startRoom(opts) {
|
|
|
1186
1355
|
...opts.stateDir === void 0 ? {} : { stateDir: opts.stateDir },
|
|
1187
1356
|
...opts.notify === void 0 ? {} : { notify: opts.notify },
|
|
1188
1357
|
onLink: (link) => {
|
|
1358
|
+
const isSelf = link.hello.pubkey !== void 0 && opts.hello.pubkey !== void 0 ? link.hello.pubkey === opts.hello.pubkey : link.hello.handle === opts.hello.handle;
|
|
1359
|
+
if (isSelf) {
|
|
1360
|
+
link.close();
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1189
1363
|
const handle = link.hello.handle;
|
|
1364
|
+
if (!entries.has(handle) && entries.size >= MAX_ROOM) {
|
|
1365
|
+
link.send(`Room is full (${MAX_ROOM} members max). Try again later.`);
|
|
1366
|
+
link.close();
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
const cur = entries.get(handle);
|
|
1370
|
+
if (cur) cur.link.close();
|
|
1190
1371
|
entries.set(handle, { hello: link.hello, link });
|
|
1191
1372
|
memberHellos.set(handle, link.hello);
|
|
1192
1373
|
fireRoster();
|
|
@@ -1197,8 +1378,8 @@ async function startRoom(opts) {
|
|
|
1197
1378
|
for (const cb of signalCbs) cb(handle, frame);
|
|
1198
1379
|
});
|
|
1199
1380
|
link.onClose(() => {
|
|
1200
|
-
const
|
|
1201
|
-
if (
|
|
1381
|
+
const cur2 = entries.get(handle);
|
|
1382
|
+
if (cur2 && cur2.link === link) {
|
|
1202
1383
|
entries.delete(handle);
|
|
1203
1384
|
memberHellos.delete(handle);
|
|
1204
1385
|
fireRoster();
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
signHelloClaims,
|
|
30
30
|
startDiscovery,
|
|
31
31
|
startRoom
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-KIFZHCWP.js";
|
|
33
33
|
|
|
34
34
|
// src/mcp.ts
|
|
35
35
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -14890,6 +14890,7 @@ function uninstallDaemonService(opts) {
|
|
|
14890
14890
|
|
|
14891
14891
|
// src/pairing.ts
|
|
14892
14892
|
var MAX_BUFFERED = 100;
|
|
14893
|
+
var MAX_QUEUE = 100;
|
|
14893
14894
|
function createPairing() {
|
|
14894
14895
|
const queue = [];
|
|
14895
14896
|
const matchCbs = /* @__PURE__ */ new Set();
|
|
@@ -14923,7 +14924,14 @@ function createPairing() {
|
|
|
14923
14924
|
link.onClose(() => {
|
|
14924
14925
|
buffers.delete(link);
|
|
14925
14926
|
if (current === link) {
|
|
14926
|
-
|
|
14927
|
+
let nextUp;
|
|
14928
|
+
while (queue.length > 0) {
|
|
14929
|
+
const cand = queue.shift();
|
|
14930
|
+
if (!cand.closed) {
|
|
14931
|
+
nextUp = cand;
|
|
14932
|
+
break;
|
|
14933
|
+
}
|
|
14934
|
+
}
|
|
14927
14935
|
setCurrent(nextUp);
|
|
14928
14936
|
} else {
|
|
14929
14937
|
const idx = queue.indexOf(link);
|
|
@@ -14980,6 +14988,10 @@ function createPairing() {
|
|
|
14980
14988
|
setCurrent(link);
|
|
14981
14989
|
} else {
|
|
14982
14990
|
queue.push(link);
|
|
14991
|
+
if (queue.length > MAX_QUEUE) {
|
|
14992
|
+
const oldest = queue.shift();
|
|
14993
|
+
oldest.close();
|
|
14994
|
+
}
|
|
14983
14995
|
}
|
|
14984
14996
|
},
|
|
14985
14997
|
next() {
|
|
@@ -14987,7 +14999,14 @@ function createPairing() {
|
|
|
14987
14999
|
current.close();
|
|
14988
15000
|
current = void 0;
|
|
14989
15001
|
}
|
|
14990
|
-
|
|
15002
|
+
let nextUp;
|
|
15003
|
+
while (queue.length > 0) {
|
|
15004
|
+
const cand = queue.shift();
|
|
15005
|
+
if (!cand.closed) {
|
|
15006
|
+
nextUp = cand;
|
|
15007
|
+
break;
|
|
15008
|
+
}
|
|
15009
|
+
}
|
|
14991
15010
|
setCurrent(nextUp);
|
|
14992
15011
|
return current;
|
|
14993
15012
|
},
|
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
stopDaemon,
|
|
11
11
|
uninstallDaemonService,
|
|
12
12
|
writeDaemonState
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-VHO7JWSS.js";
|
|
14
14
|
import {
|
|
15
15
|
CANDIDATES,
|
|
16
16
|
LIVE_NOTICE,
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
signHelloClaims,
|
|
47
47
|
startDiscovery,
|
|
48
48
|
startRoom
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-KIFZHCWP.js";
|
|
50
50
|
|
|
51
51
|
// src/cli.ts
|
|
52
52
|
import readline from "readline";
|
|
@@ -1389,9 +1389,8 @@ var webAppHtml = `<!DOCTYPE html>
|
|
|
1389
1389
|
var idleIdx = 0;
|
|
1390
1390
|
|
|
1391
1391
|
function rtcConfig(){
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
return { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] };
|
|
1392
|
+
var extra = (window.__VIBE_ICE__ && window.__VIBE_ICE__.iceServers) || [];
|
|
1393
|
+
return { iceServers: [{ urls: "stun:stun.l.google.com:19302" }].concat(extra) };
|
|
1395
1394
|
}
|
|
1396
1395
|
function sleep(ms){ return new Promise(function(r){ setTimeout(r, ms); }); }
|
|
1397
1396
|
|
|
@@ -2228,10 +2227,11 @@ function createLiveBridge() {
|
|
|
2228
2227
|
link.onMedia((m) => {
|
|
2229
2228
|
const mb = boxes.get(handle2);
|
|
2230
2229
|
if (!mb) {
|
|
2231
|
-
fs.unlink(m.path, () => {
|
|
2230
|
+
if (!m.error) fs.unlink(m.path, () => {
|
|
2232
2231
|
});
|
|
2233
2232
|
return;
|
|
2234
2233
|
}
|
|
2234
|
+
if (m.error) return;
|
|
2235
2235
|
mb.media.push({ ...m, name: sanitizePeerText(m.name), mime: sanitizePeerText(m.mime) });
|
|
2236
2236
|
if (mb.media.length > MAX_QUEUED_MESSAGES) {
|
|
2237
2237
|
const dropped = mb.media.splice(0, mb.media.length - MAX_QUEUED_MESSAGES);
|
|
@@ -2459,7 +2459,18 @@ async function handle(req, res, opts) {
|
|
|
2459
2459
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
2460
2460
|
const pathname = url.pathname;
|
|
2461
2461
|
if (req.method === "GET" && pathname === "/") {
|
|
2462
|
-
|
|
2462
|
+
const turnUrl = process.env["VIBEDATE_TURN_URL"];
|
|
2463
|
+
let html = webAppHtml;
|
|
2464
|
+
if (turnUrl) {
|
|
2465
|
+
const turnUser = process.env["VIBEDATE_TURN_USER"];
|
|
2466
|
+
const turnCred = process.env["VIBEDATE_TURN_CRED"];
|
|
2467
|
+
const server = { urls: turnUrl };
|
|
2468
|
+
if (turnUser) server.username = turnUser;
|
|
2469
|
+
if (turnCred) server.credential = turnCred;
|
|
2470
|
+
const script = `<script>window.__VIBE_ICE__={iceServers:${JSON.stringify([server]).replace(/</g, "\\u003c")}};</script>`;
|
|
2471
|
+
html = html.replace("<script>", script + "\n<script>");
|
|
2472
|
+
}
|
|
2473
|
+
send(res, 200, "text/html; charset=utf-8", html);
|
|
2463
2474
|
return;
|
|
2464
2475
|
}
|
|
2465
2476
|
if (req.method === "GET" && pathname === "/api/state") {
|
|
@@ -2784,7 +2795,7 @@ async function handle(req, res, opts) {
|
|
|
2784
2795
|
}
|
|
2785
2796
|
|
|
2786
2797
|
// src/cli.ts
|
|
2787
|
-
var VERSION = "0.8.
|
|
2798
|
+
var VERSION = "0.8.5";
|
|
2788
2799
|
function parsePort(raw) {
|
|
2789
2800
|
const n = Number(raw);
|
|
2790
2801
|
if (!Number.isInteger(n) || n < 1 || n > 65535) return void 0;
|
|
@@ -3136,7 +3147,7 @@ async function cmdDiscover(live, any, viaRelay) {
|
|
|
3136
3147
|
);
|
|
3137
3148
|
return 0;
|
|
3138
3149
|
}
|
|
3139
|
-
async function cmdOpen(port, any, room) {
|
|
3150
|
+
async function cmdOpen(port, any, room, viaRelay, to) {
|
|
3140
3151
|
const profile = loadProfile();
|
|
3141
3152
|
let live;
|
|
3142
3153
|
let roomBridge;
|
|
@@ -3167,17 +3178,45 @@ async function cmdOpen(port, any, room) {
|
|
|
3167
3178
|
}).catch(() => {
|
|
3168
3179
|
});
|
|
3169
3180
|
} else if (live) {
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
+
if (viaRelay && to !== void 0) {
|
|
3182
|
+
const target = normalizeHandle(to);
|
|
3183
|
+
const peer = target ? loadPeers().find((p) => sameHandle(p.handle, target) && typeof p.pubkey === "string") : void 0;
|
|
3184
|
+
if (peer && peer.pubkey) {
|
|
3185
|
+
const peerPubkey = peer.pubkey;
|
|
3186
|
+
void (async () => {
|
|
3187
|
+
try {
|
|
3188
|
+
const myNostr = await loadOrCreateNostrKey();
|
|
3189
|
+
const transport = await createNostrPoolTransport();
|
|
3190
|
+
const identity = loadOrCreateIdentity();
|
|
3191
|
+
const link = await createNostrRelayLink({
|
|
3192
|
+
myNostr,
|
|
3193
|
+
myEd25519Hex: identity.publicKeyHex,
|
|
3194
|
+
peerEd25519Hex: peerPubkey,
|
|
3195
|
+
hello: peer,
|
|
3196
|
+
transport
|
|
3197
|
+
});
|
|
3198
|
+
live.addLink(link);
|
|
3199
|
+
} catch {
|
|
3200
|
+
}
|
|
3201
|
+
})();
|
|
3202
|
+
} else {
|
|
3203
|
+
process2.stderr.write(`
|
|
3204
|
+
warning: --via-relay peer ${to} not found or lacks identity pubkey.
|
|
3205
|
+
`);
|
|
3206
|
+
}
|
|
3207
|
+
} else {
|
|
3208
|
+
const { topics, acceptLeague } = discoveryScope(profile.league, any);
|
|
3209
|
+
void startDiscovery({
|
|
3210
|
+
hello,
|
|
3211
|
+
topics,
|
|
3212
|
+
acceptLeague,
|
|
3213
|
+
isBlocked: blockedChecker(),
|
|
3214
|
+
onLink: (link) => live.addLink(link)
|
|
3215
|
+
}).then((s) => {
|
|
3216
|
+
session = s;
|
|
3217
|
+
}).catch(() => {
|
|
3218
|
+
});
|
|
3219
|
+
}
|
|
3181
3220
|
}
|
|
3182
3221
|
}
|
|
3183
3222
|
process2.stdout.write(`
|
|
@@ -3390,10 +3429,17 @@ async function cmdLive(dating, any, to, keepAlive, viaRelay) {
|
|
|
3390
3429
|
`);
|
|
3391
3430
|
}
|
|
3392
3431
|
link.onMedia((m) => {
|
|
3393
|
-
|
|
3394
|
-
|
|
3432
|
+
if (m.error) {
|
|
3433
|
+
process2.stdout.write(
|
|
3434
|
+
` \u{1F4CE} <${sanitizePeerText(link.hello.handle)}> sent ${sanitizePeerText(m.name)} \u2014 FAILED to save: ${m.error.message}
|
|
3395
3435
|
`
|
|
3396
|
-
|
|
3436
|
+
);
|
|
3437
|
+
} else {
|
|
3438
|
+
process2.stdout.write(
|
|
3439
|
+
` \u{1F4CE} <${sanitizePeerText(link.hello.handle)}> sent ${sanitizePeerText(m.name)} \u2014 saved to ${m.path}
|
|
3440
|
+
`
|
|
3441
|
+
);
|
|
3442
|
+
}
|
|
3397
3443
|
});
|
|
3398
3444
|
pairing.add(link);
|
|
3399
3445
|
}
|
|
@@ -3884,7 +3930,7 @@ async function main(argv) {
|
|
|
3884
3930
|
case "discover":
|
|
3885
3931
|
return cmdDiscover(parsed.live, parsed.any, parsed.viaRelay);
|
|
3886
3932
|
case "open":
|
|
3887
|
-
return cmdOpen(parsed.port, parsed.any, parsed.room);
|
|
3933
|
+
return cmdOpen(parsed.port, parsed.any, parsed.room, parsed.viaRelay, parsed.to);
|
|
3888
3934
|
case "live":
|
|
3889
3935
|
return cmdLive(parsed.dating, parsed.any, parsed.to, parsed.keepAlive, parsed.viaRelay);
|
|
3890
3936
|
case "find":
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UsageSnapshot, UsageSource, Harness, HarnessUsageOptions } from '@pooriaarab/vibe-core';
|
|
2
2
|
export { Harness, UsageSnapshot, UsageSource, createConsentLedger } from '@pooriaarab/vibe-core';
|
|
3
|
-
import { P as PeerHello, a as PeerLink } from './room-
|
|
4
|
-
export { D as DiscoveryOptions, b as DiscoverySession, L as LIVE_NOTICE, R as ROOM_TOPIC_PREFIX, c as RoomMember, d as RoomMessage, e as RoomOptions, f as RoomSession, S as StoredPeer, T as TOPIC_PREFIX, l as leagueTopic, g as loadPeers, p as parseHandshake, r as recordPeer, h as recordPeerMessage, i as roomTopic, s as serializeHandshake, j as startDiscovery, k as startRoom } from './room-
|
|
3
|
+
import { P as PeerHello, a as PeerLink } from './room-B-0DreC0.js';
|
|
4
|
+
export { D as DiscoveryOptions, b as DiscoverySession, L as LIVE_NOTICE, R as ROOM_TOPIC_PREFIX, c as RoomMember, d as RoomMessage, e as RoomOptions, f as RoomSession, S as StoredPeer, T as TOPIC_PREFIX, l as leagueTopic, g as loadPeers, p as parseHandshake, r as recordPeer, h as recordPeerMessage, i as roomTopic, s as serializeHandshake, j as startDiscovery, k as startRoom } from './room-B-0DreC0.js';
|
|
5
5
|
import { KeyObject } from 'node:crypto';
|
|
6
6
|
|
|
7
7
|
/**
|
package/dist/index.js
CHANGED
package/dist/mcp.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { a as PeerLink, P as PeerHello, f as RoomSession, b as DiscoverySession, d as RoomMessage } from './room-
|
|
2
|
+
import { a as PeerLink, P as PeerHello, f as RoomSession, b as DiscoverySession, d as RoomMessage } from './room-B-0DreC0.js';
|
|
3
3
|
import '@pooriaarab/vibe-core';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/mcp.js
CHANGED
|
@@ -24,11 +24,20 @@ type Frame = {
|
|
|
24
24
|
mime: string;
|
|
25
25
|
size: number;
|
|
26
26
|
name: string;
|
|
27
|
-
}
|
|
27
|
+
}
|
|
28
|
+
/** Legacy JSON media-chunk (base64). Prefer binary wire for new sends. */
|
|
29
|
+
| {
|
|
28
30
|
t: 'media-chunk';
|
|
29
31
|
id: string;
|
|
30
32
|
seq: number;
|
|
31
33
|
b64: string;
|
|
34
|
+
}
|
|
35
|
+
/** Binary-path media-chunk: raw bytes, no base64. Produced by the binary framer. */
|
|
36
|
+
| {
|
|
37
|
+
t: 'media-chunk';
|
|
38
|
+
id: string;
|
|
39
|
+
seq: number;
|
|
40
|
+
data: Buffer;
|
|
32
41
|
} | {
|
|
33
42
|
t: 'media-end';
|
|
34
43
|
id: string;
|
|
@@ -56,11 +65,14 @@ interface ReceivedMedia {
|
|
|
56
65
|
/** Temp file path holding the reassembled bytes. */
|
|
57
66
|
readonly path: string;
|
|
58
67
|
readonly size: number;
|
|
68
|
+
readonly error?: Error;
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
interface PeerLink {
|
|
62
72
|
/** The validated identity of the remote peer (from the hello handshake). */
|
|
63
73
|
readonly hello: PeerHello;
|
|
74
|
+
/** Whether the link has been closed. */
|
|
75
|
+
readonly closed: boolean;
|
|
64
76
|
/** Send a line of text as a `msg` frame. */
|
|
65
77
|
send(text: string): void;
|
|
66
78
|
/** Read a file from disk and send it as a chunked media transfer. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibedate",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"description": "Dating by tokens — matched by usage league across agentic CLIs. Raw usage stays local; only your league is shared. CLI + local web app + MCP. Local-first.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"build": "tsup src/index.ts src/cli.ts src/mcp.ts --format esm --dts --clean",
|
|
26
26
|
"typecheck": "tsc --noEmit",
|
|
27
27
|
"test": "vitest run",
|
|
28
|
-
"test:watch": "vitest"
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"bench": "tsx bench/run.ts"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
"@types/node": "^20.11.0",
|
|
38
39
|
"hyperdht": "^6.33.0",
|
|
39
40
|
"tsup": "^8.0.0",
|
|
41
|
+
"tsx": "^4.23.1",
|
|
40
42
|
"typescript": "^5.4.0",
|
|
41
43
|
"vitest": "^1.6.0",
|
|
42
44
|
"werift": "^0.24.1"
|