vibedate 0.7.0 → 0.7.2
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/cli.js +397 -57
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -387,30 +387,60 @@ function uninstallDaemonService(opts) {
|
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
// src/pairing.ts
|
|
390
|
+
var MAX_BUFFERED = 100;
|
|
390
391
|
function createPairing() {
|
|
391
392
|
const queue = [];
|
|
392
393
|
const matchCbs = /* @__PURE__ */ new Set();
|
|
394
|
+
const msgCbs = /* @__PURE__ */ new Set();
|
|
395
|
+
const queuedCbs = /* @__PURE__ */ new Set();
|
|
396
|
+
const buffers = /* @__PURE__ */ new Map();
|
|
393
397
|
let current;
|
|
394
|
-
const
|
|
398
|
+
const emitMatch = (link) => {
|
|
395
399
|
for (const cb of matchCbs) cb(link);
|
|
396
400
|
};
|
|
401
|
+
const deliver = (from, m) => {
|
|
402
|
+
for (const cb of msgCbs) cb(from, m);
|
|
403
|
+
};
|
|
404
|
+
const notifyQueued = (from, n) => {
|
|
405
|
+
for (const cb of queuedCbs) cb(from, n);
|
|
406
|
+
};
|
|
407
|
+
const flush = (link) => {
|
|
408
|
+
const buf = buffers.get(link);
|
|
409
|
+
if (buf !== void 0 && buf.length > 0) {
|
|
410
|
+
for (const m of buf) deliver(link.hello.handle, m);
|
|
411
|
+
buffers.set(link, []);
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
const setCurrent = (link) => {
|
|
415
|
+
current = link;
|
|
416
|
+
if (link !== void 0) flush(link);
|
|
417
|
+
emitMatch(link);
|
|
418
|
+
};
|
|
397
419
|
const watch = (link) => {
|
|
398
420
|
link.onClose(() => {
|
|
421
|
+
buffers.delete(link);
|
|
399
422
|
if (current === link) {
|
|
400
|
-
current = void 0;
|
|
401
423
|
const nextUp = queue.shift();
|
|
402
|
-
|
|
403
|
-
current = nextUp;
|
|
404
|
-
emit(current);
|
|
405
|
-
} else {
|
|
406
|
-
emit(void 0);
|
|
407
|
-
}
|
|
424
|
+
setCurrent(nextUp);
|
|
408
425
|
} else {
|
|
409
426
|
const idx = queue.indexOf(link);
|
|
410
427
|
if (idx >= 0) queue.splice(idx, 1);
|
|
411
428
|
}
|
|
412
429
|
});
|
|
413
430
|
};
|
|
431
|
+
const bindMessages = (link) => {
|
|
432
|
+
link.onMessage((m) => {
|
|
433
|
+
if (link === current) {
|
|
434
|
+
deliver(link.hello.handle, m);
|
|
435
|
+
} else {
|
|
436
|
+
const buf = buffers.get(link) ?? [];
|
|
437
|
+
buf.push(m);
|
|
438
|
+
if (buf.length > MAX_BUFFERED) buf.shift();
|
|
439
|
+
buffers.set(link, buf);
|
|
440
|
+
notifyQueued(link.hello.handle, buf.length);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
};
|
|
414
444
|
return {
|
|
415
445
|
get available() {
|
|
416
446
|
return queue.length;
|
|
@@ -420,9 +450,9 @@ function createPairing() {
|
|
|
420
450
|
},
|
|
421
451
|
add(link) {
|
|
422
452
|
watch(link);
|
|
453
|
+
bindMessages(link);
|
|
423
454
|
if (current === void 0) {
|
|
424
|
-
|
|
425
|
-
emit(link);
|
|
455
|
+
setCurrent(link);
|
|
426
456
|
} else {
|
|
427
457
|
queue.push(link);
|
|
428
458
|
}
|
|
@@ -433,10 +463,7 @@ function createPairing() {
|
|
|
433
463
|
current = void 0;
|
|
434
464
|
}
|
|
435
465
|
const nextUp = queue.shift();
|
|
436
|
-
|
|
437
|
-
current = nextUp;
|
|
438
|
-
}
|
|
439
|
-
emit(current);
|
|
466
|
+
setCurrent(nextUp);
|
|
440
467
|
return current;
|
|
441
468
|
},
|
|
442
469
|
open(handle2) {
|
|
@@ -447,12 +474,17 @@ function createPairing() {
|
|
|
447
474
|
current.close();
|
|
448
475
|
current = void 0;
|
|
449
476
|
}
|
|
450
|
-
|
|
451
|
-
emit(current);
|
|
477
|
+
setCurrent(link);
|
|
452
478
|
return current;
|
|
453
479
|
},
|
|
454
480
|
onMatch(cb) {
|
|
455
481
|
matchCbs.add(cb);
|
|
482
|
+
},
|
|
483
|
+
onMessage(cb) {
|
|
484
|
+
msgCbs.add(cb);
|
|
485
|
+
},
|
|
486
|
+
onQueued(cb) {
|
|
487
|
+
queuedCbs.add(cb);
|
|
456
488
|
}
|
|
457
489
|
};
|
|
458
490
|
}
|
|
@@ -914,6 +946,56 @@ var webAppHtml = `<!DOCTYPE html>
|
|
|
914
946
|
.incoming-call p{ color: var(--muted); font-size: .85rem; margin: 0 0 18px; }
|
|
915
947
|
.incoming-call .ic-actions{ display: flex; gap: 10px; }
|
|
916
948
|
|
|
949
|
+
/* ---- room full-mesh group video ---- */
|
|
950
|
+
.room-panel{
|
|
951
|
+
position: fixed; right: 18px; bottom: 18px; z-index: 45;
|
|
952
|
+
background: linear-gradient(180deg, var(--bg-card), var(--bg-card-2));
|
|
953
|
+
border: 1px solid var(--border-2); border-radius: 16px; box-shadow: var(--shadow-2);
|
|
954
|
+
padding: 12px 14px; min-width: 240px; max-width: 280px; display: none;
|
|
955
|
+
}
|
|
956
|
+
.room-panel.is-open{ display: block; animation: rise var(--dur-med) var(--ease-out); }
|
|
957
|
+
.room-panel .rp-title{ font-size: .78rem; font-weight: 800; margin-bottom: 6px; display:flex; align-items:center; gap:6px; }
|
|
958
|
+
.room-panel .rp-meta{ font-size: .7rem; color: var(--muted-2); margin-bottom: 10px; line-height: 1.4; word-break: break-all; }
|
|
959
|
+
.room-panel .rp-actions{ display:flex; gap: 8px; }
|
|
960
|
+
.room-panel .rp-btn{
|
|
961
|
+
border: 0; border-radius: 9px; padding: 8px 14px; font-weight: 700; font-size: .78rem;
|
|
962
|
+
background: linear-gradient(180deg, var(--coral), var(--coral-dim)); color: #2a1109;
|
|
963
|
+
flex: 1;
|
|
964
|
+
}
|
|
965
|
+
.room-panel .rp-btn:disabled{ opacity: .4; cursor: not-allowed; }
|
|
966
|
+
.room-panel .rp-btn.is-leave{ background: var(--danger); color: #2a0a0c; }
|
|
967
|
+
.room-video{
|
|
968
|
+
position: fixed; inset: 0; z-index: 68;
|
|
969
|
+
display: none; flex-direction: column; align-items: stretch; justify-content: flex-start;
|
|
970
|
+
background: rgba(12,7,15,.86); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
|
|
971
|
+
padding: 18px 18px 80px;
|
|
972
|
+
}
|
|
973
|
+
.room-video.is-open{ display: flex; }
|
|
974
|
+
.room-video .rv-head{
|
|
975
|
+
display:flex; align-items:center; justify-content: space-between; gap: 12px;
|
|
976
|
+
margin-bottom: 14px; color: var(--fg); font-weight: 800; font-size: .9rem;
|
|
977
|
+
}
|
|
978
|
+
.room-video .rv-grid{
|
|
979
|
+
flex: 1; display: grid; gap: 12px; min-height: 0;
|
|
980
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
981
|
+
align-content: start;
|
|
982
|
+
}
|
|
983
|
+
.room-video .rv-tile{
|
|
984
|
+
display:flex; flex-direction: column; gap: 6px; min-width: 0;
|
|
985
|
+
}
|
|
986
|
+
.room-video .rv-tile video{
|
|
987
|
+
width: 100%; aspect-ratio: 4 / 3; background: #000; border-radius: 14px;
|
|
988
|
+
border: 1px solid var(--border-2); object-fit: cover;
|
|
989
|
+
}
|
|
990
|
+
.room-video .rv-tile .rv-label{
|
|
991
|
+
font-size: .75rem; color: var(--muted); font-weight: 600; word-break: break-all;
|
|
992
|
+
}
|
|
993
|
+
.room-video .rv-leave{
|
|
994
|
+
position: absolute; bottom: 24px; left: 50%; transform: translateX(-50%);
|
|
995
|
+
border: 0; border-radius: 999px; padding: 12px 22px; font-weight: 800; font-size: .9rem;
|
|
996
|
+
background: var(--danger); color: #2a0a0c;
|
|
997
|
+
}
|
|
998
|
+
|
|
917
999
|
/* ---- live text chat (browser <-> PeerLink over /live/message) ---- */
|
|
918
1000
|
.live-actions{ display: flex; gap: 6px; flex-shrink: 0; }
|
|
919
1001
|
.live-row .cbtn{
|
|
@@ -1151,6 +1233,24 @@ var webAppHtml = `<!DOCTYPE html>
|
|
|
1151
1233
|
<button class="vhangup" id="hangupBtn" type="button">Hang up</button>
|
|
1152
1234
|
</div>
|
|
1153
1235
|
|
|
1236
|
+
<aside class="room-panel" id="roomPanel" aria-label="Room">
|
|
1237
|
+
<div class="rp-title"><span class="lp-dot" aria-hidden="true"></span> Room</div>
|
|
1238
|
+
<div class="rp-meta" id="roomMeta">not in a room</div>
|
|
1239
|
+
<div class="rp-actions">
|
|
1240
|
+
<button class="rp-btn" id="roomJoinBtn" type="button">Join video</button>
|
|
1241
|
+
<button class="rp-btn is-leave" id="roomLeaveBtn" type="button" disabled>Leave</button>
|
|
1242
|
+
</div>
|
|
1243
|
+
</aside>
|
|
1244
|
+
|
|
1245
|
+
<div class="room-video" id="roomVideo" role="dialog" aria-modal="true" aria-label="Room video">
|
|
1246
|
+
<div class="rv-head">
|
|
1247
|
+
<span id="roomVideoTitle">Room video</span>
|
|
1248
|
+
<span id="roomVideoCount" style="font-weight:600;color:var(--muted);font-size:.78rem"></span>
|
|
1249
|
+
</div>
|
|
1250
|
+
<div class="rv-grid" id="roomVideoGrid"></div>
|
|
1251
|
+
<button class="rv-leave" id="roomVideoLeaveBtn" type="button">Leave video</button>
|
|
1252
|
+
</div>
|
|
1253
|
+
|
|
1154
1254
|
<script>
|
|
1155
1255
|
(function(){
|
|
1156
1256
|
"use strict";
|
|
@@ -1816,6 +1916,252 @@ var webAppHtml = `<!DOCTYPE html>
|
|
|
1816
1916
|
}
|
|
1817
1917
|
refreshPeers();
|
|
1818
1918
|
setInterval(refreshPeers, 4000);
|
|
1919
|
+
|
|
1920
|
+
/* ---- Room full-mesh group video ----------------------------------------
|
|
1921
|
+
* When vibedate open --room <name> is attached, /api/room exposes the
|
|
1922
|
+
* roster. Each browser grabs getUserMedia once and opens one
|
|
1923
|
+
* RTCPeerConnection to every other member. Deterministic offerer election
|
|
1924
|
+
* (self < other lexicographic) avoids glare. Signaling rides the local
|
|
1925
|
+
* server's merged mailbox: GET /room/signal?handle=SELF returns
|
|
1926
|
+
* { frame: { from, rtc } }, POST /room/signal { handle: TARGET, frame }
|
|
1927
|
+
* relays one rtc-* frame to that member. */
|
|
1928
|
+
var roomPanel = document.getElementById("roomPanel");
|
|
1929
|
+
var roomMeta = document.getElementById("roomMeta");
|
|
1930
|
+
var roomJoinBtn = document.getElementById("roomJoinBtn");
|
|
1931
|
+
var roomLeaveBtn = document.getElementById("roomLeaveBtn");
|
|
1932
|
+
var roomVideo = document.getElementById("roomVideo");
|
|
1933
|
+
var roomVideoGrid = document.getElementById("roomVideoGrid");
|
|
1934
|
+
var roomVideoTitle = document.getElementById("roomVideoTitle");
|
|
1935
|
+
var roomVideoCount = document.getElementById("roomVideoCount");
|
|
1936
|
+
var roomVideoLeaveBtn = document.getElementById("roomVideoLeaveBtn");
|
|
1937
|
+
|
|
1938
|
+
var roomVid = { localStream: null, peers: {}, self: null, room: null, joined: false, polling: false };
|
|
1939
|
+
var roomLastRoster = [];
|
|
1940
|
+
|
|
1941
|
+
function roomPostSignal(handle, frame){
|
|
1942
|
+
return fetch("/room/signal", {
|
|
1943
|
+
method: "POST",
|
|
1944
|
+
headers: { "content-type": "application/json" },
|
|
1945
|
+
body: JSON.stringify({ handle: handle, frame: frame })
|
|
1946
|
+
}).catch(function(){ /* best effort */ });
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
function roomFetchSignal(selfHandle, timeoutMs){
|
|
1950
|
+
var ctrl = new AbortController();
|
|
1951
|
+
var t = setTimeout(function(){ ctrl.abort(); }, timeoutMs);
|
|
1952
|
+
return fetch("/room/signal?handle=" + encodeURIComponent(selfHandle), { signal: ctrl.signal })
|
|
1953
|
+
.then(function(r){ clearTimeout(t); return r; })
|
|
1954
|
+
.catch(function(e){ clearTimeout(t); throw e; });
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
function makeRoomTile(label, muted){
|
|
1958
|
+
var tile = document.createElement("div");
|
|
1959
|
+
tile.className = "rv-tile";
|
|
1960
|
+
var vid = document.createElement("video");
|
|
1961
|
+
vid.autoplay = true;
|
|
1962
|
+
vid.playsInline = true;
|
|
1963
|
+
if (muted) vid.muted = true;
|
|
1964
|
+
var lab = document.createElement("div");
|
|
1965
|
+
lab.className = "rv-label";
|
|
1966
|
+
lab.textContent = label;
|
|
1967
|
+
tile.appendChild(vid);
|
|
1968
|
+
tile.appendChild(lab);
|
|
1969
|
+
roomVideoGrid.appendChild(tile);
|
|
1970
|
+
return vid;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
function updateRoomCount(){
|
|
1974
|
+
var n = 0;
|
|
1975
|
+
if (roomVid.localStream) n++;
|
|
1976
|
+
for (var k in roomVid.peers) if (Object.prototype.hasOwnProperty.call(roomVid.peers, k)) n++;
|
|
1977
|
+
roomVideoCount.textContent = n ? (n + " tile" + (n === 1 ? "" : "s")) : "";
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
function connectPeer(handle, role){
|
|
1981
|
+
if (!roomVid.localStream || !roomVid.self) return null;
|
|
1982
|
+
if (roomVid.peers[handle]) return roomVid.peers[handle];
|
|
1983
|
+
var pc = new RTCPeerConnection(rtcConfig());
|
|
1984
|
+
var videoEl = makeRoomTile(handle, false);
|
|
1985
|
+
var peer = { pc: pc, videoEl: videoEl, role: role };
|
|
1986
|
+
roomVid.peers[handle] = peer;
|
|
1987
|
+
updateRoomCount();
|
|
1988
|
+
|
|
1989
|
+
roomVid.localStream.getTracks().forEach(function(t){ pc.addTrack(t, roomVid.localStream); });
|
|
1990
|
+
pc.onicecandidate = function(e){
|
|
1991
|
+
// Trickle ICE to this peer. Empty candidate = end-of-candidates marker.
|
|
1992
|
+
roomPostSignal(handle, {
|
|
1993
|
+
t: "rtc-ice",
|
|
1994
|
+
candidate: e.candidate && e.candidate.candidate ? e.candidate.candidate : ""
|
|
1995
|
+
});
|
|
1996
|
+
};
|
|
1997
|
+
pc.ontrack = function(e){
|
|
1998
|
+
if (e.streams && e.streams[0]) videoEl.srcObject = e.streams[0];
|
|
1999
|
+
};
|
|
2000
|
+
pc.onconnectionstatechange = function(){
|
|
2001
|
+
// Quiet \u2014 closed peers stay in the grid until Leave clears everything.
|
|
2002
|
+
};
|
|
2003
|
+
|
|
2004
|
+
if (role === "offerer") {
|
|
2005
|
+
(async function(){
|
|
2006
|
+
try {
|
|
2007
|
+
var offer = await pc.createOffer();
|
|
2008
|
+
await pc.setLocalDescription(offer);
|
|
2009
|
+
await roomPostSignal(handle, { t: "rtc-offer", sdp: offer.sdp });
|
|
2010
|
+
} catch(e){ /* offer failed \u2014 peer will retry on next roster refresh if still present */ }
|
|
2011
|
+
})();
|
|
2012
|
+
}
|
|
2013
|
+
return peer;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
async function handleRoomSignal(from, rtc){
|
|
2017
|
+
if (!rtc || !from || from === roomVid.self) return;
|
|
2018
|
+
if (rtc.t === "rtc-offer") {
|
|
2019
|
+
var peer = roomVid.peers[from];
|
|
2020
|
+
if (!peer) peer = connectPeer(from, "answerer");
|
|
2021
|
+
if (!peer) return;
|
|
2022
|
+
try {
|
|
2023
|
+
await peer.pc.setRemoteDescription({ type: "offer", sdp: rtc.sdp });
|
|
2024
|
+
var answer = await peer.pc.createAnswer();
|
|
2025
|
+
await peer.pc.setLocalDescription(answer);
|
|
2026
|
+
await roomPostSignal(from, { t: "rtc-answer", sdp: answer.sdp });
|
|
2027
|
+
} catch(e){}
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
if (rtc.t === "rtc-answer") {
|
|
2031
|
+
var pAns = roomVid.peers[from];
|
|
2032
|
+
if (!pAns) return;
|
|
2033
|
+
try { await pAns.pc.setRemoteDescription({ type: "answer", sdp: rtc.sdp }); } catch(e){}
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
if (rtc.t === "rtc-ice") {
|
|
2037
|
+
var pIce = roomVid.peers[from];
|
|
2038
|
+
if (!pIce) return;
|
|
2039
|
+
// Skip empty candidate (end-of-candidates) \u2014 browsers tolerate either way.
|
|
2040
|
+
if (!rtc.candidate) return;
|
|
2041
|
+
try { await pIce.pc.addIceCandidate({ candidate: rtc.candidate }); } catch(e){}
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
async function roomSignalLoop(){
|
|
2046
|
+
if (roomVid.polling) return;
|
|
2047
|
+
roomVid.polling = true;
|
|
2048
|
+
while (roomVid.joined && roomVid.self) {
|
|
2049
|
+
var res;
|
|
2050
|
+
try { res = await roomFetchSignal(roomVid.self, 5000); }
|
|
2051
|
+
catch(e){ await sleep(400); continue; }
|
|
2052
|
+
if (!res || res.status !== 200) { await sleep(400); continue; }
|
|
2053
|
+
var data;
|
|
2054
|
+
try { data = await res.json(); } catch(e){ continue; }
|
|
2055
|
+
var f = data && data.frame;
|
|
2056
|
+
if (!f) continue; // timeout empty
|
|
2057
|
+
// Sender-routed shape: { from, rtc } (or a nested frame.frame fallback).
|
|
2058
|
+
var from = f.from;
|
|
2059
|
+
var rtc = f.rtc || f.frame || f;
|
|
2060
|
+
if (from && rtc && rtc.t) await handleRoomSignal(from, rtc);
|
|
2061
|
+
}
|
|
2062
|
+
roomVid.polling = false;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
function meshWithMembers(members){
|
|
2066
|
+
if (!roomVid.joined || !roomVid.self || !roomVid.localStream) return;
|
|
2067
|
+
var self = roomVid.self;
|
|
2068
|
+
var seen = {};
|
|
2069
|
+
(members || []).forEach(function(m){
|
|
2070
|
+
var h = m && m.handle;
|
|
2071
|
+
if (!h || h === self) return;
|
|
2072
|
+
seen[h] = true;
|
|
2073
|
+
if (roomVid.peers[h]) return;
|
|
2074
|
+
// Deterministic glare avoidance: lower handle is the offerer.
|
|
2075
|
+
if (self < h) connectPeer(h, "offerer");
|
|
2076
|
+
// else wait for their offer via the long-poll loop
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
async function joinRoomVideo(){
|
|
2081
|
+
if (roomVid.joined) return;
|
|
2082
|
+
if (!roomVid.self || !roomVid.room) return;
|
|
2083
|
+
roomJoinBtn.disabled = true;
|
|
2084
|
+
try {
|
|
2085
|
+
roomVid.localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
|
|
2086
|
+
} catch(e){
|
|
2087
|
+
roomJoinBtn.disabled = false;
|
|
2088
|
+
roomMeta.textContent = (roomVid.room || "room") + " \xB7 camera/mic blocked";
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
roomVid.joined = true;
|
|
2092
|
+
roomVid.peers = {};
|
|
2093
|
+
roomVideoGrid.innerHTML = "";
|
|
2094
|
+
var localEl = makeRoomTile((roomVid.self || "you") + " (you)", true);
|
|
2095
|
+
localEl.srcObject = roomVid.localStream;
|
|
2096
|
+
roomVideo.classList.add("is-open");
|
|
2097
|
+
roomVideoTitle.textContent = "Room \xB7 " + roomVid.room;
|
|
2098
|
+
roomLeaveBtn.disabled = false;
|
|
2099
|
+
updateRoomCount();
|
|
2100
|
+
roomSignalLoop();
|
|
2101
|
+
// Seed mesh against the latest roster, then refresh once more for races.
|
|
2102
|
+
meshWithMembers(roomLastRoster);
|
|
2103
|
+
try {
|
|
2104
|
+
var res = await fetch("/api/room");
|
|
2105
|
+
if (res.status === 200) {
|
|
2106
|
+
var data = await res.json();
|
|
2107
|
+
var members = (data && data.members) || [];
|
|
2108
|
+
roomLastRoster = members;
|
|
2109
|
+
meshWithMembers(members);
|
|
2110
|
+
}
|
|
2111
|
+
} catch(e){}
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
function leaveRoomVideo(){
|
|
2115
|
+
roomVid.joined = false;
|
|
2116
|
+
for (var h in roomVid.peers) {
|
|
2117
|
+
if (!Object.prototype.hasOwnProperty.call(roomVid.peers, h)) continue;
|
|
2118
|
+
try { roomVid.peers[h].pc.close(); } catch(e){}
|
|
2119
|
+
}
|
|
2120
|
+
roomVid.peers = {};
|
|
2121
|
+
if (roomVid.localStream) {
|
|
2122
|
+
roomVid.localStream.getTracks().forEach(function(t){ try { t.stop(); } catch(e){} });
|
|
2123
|
+
roomVid.localStream = null;
|
|
2124
|
+
}
|
|
2125
|
+
roomVideoGrid.innerHTML = "";
|
|
2126
|
+
roomVideo.classList.remove("is-open");
|
|
2127
|
+
roomLeaveBtn.disabled = true;
|
|
2128
|
+
roomJoinBtn.disabled = !roomVid.room;
|
|
2129
|
+
updateRoomCount();
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
roomJoinBtn.addEventListener("click", function(){ joinRoomVideo().catch(function(){ leaveRoomVideo(); }); });
|
|
2133
|
+
roomLeaveBtn.addEventListener("click", leaveRoomVideo);
|
|
2134
|
+
roomVideoLeaveBtn.addEventListener("click", leaveRoomVideo);
|
|
2135
|
+
|
|
2136
|
+
async function refreshRoom(){
|
|
2137
|
+
try {
|
|
2138
|
+
var res = await fetch("/api/room");
|
|
2139
|
+
if (res.status !== 200) return;
|
|
2140
|
+
var data = await res.json();
|
|
2141
|
+
var roomName = data && data.room;
|
|
2142
|
+
if (!roomName) {
|
|
2143
|
+
if (roomVid.joined) leaveRoomVideo();
|
|
2144
|
+
roomVid.room = null;
|
|
2145
|
+
roomVid.self = null;
|
|
2146
|
+
roomLastRoster = [];
|
|
2147
|
+
roomPanel.classList.remove("is-open");
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
roomVid.room = roomName;
|
|
2151
|
+
roomVid.self = data.self || null;
|
|
2152
|
+
roomLastRoster = (data.members) || [];
|
|
2153
|
+
var n = roomLastRoster.length;
|
|
2154
|
+
roomMeta.textContent = roomName +
|
|
2155
|
+
(roomVid.self ? " \xB7 you " + roomVid.self : "") +
|
|
2156
|
+
" \xB7 " + n + " other" + (n === 1 ? "" : "s");
|
|
2157
|
+
roomPanel.classList.add("is-open");
|
|
2158
|
+
roomJoinBtn.disabled = roomVid.joined || !roomVid.self;
|
|
2159
|
+
roomLeaveBtn.disabled = !roomVid.joined;
|
|
2160
|
+
if (roomVid.joined) meshWithMembers(roomLastRoster);
|
|
2161
|
+
} catch(e){}
|
|
2162
|
+
}
|
|
2163
|
+
refreshRoom();
|
|
2164
|
+
setInterval(refreshRoom, 4000);
|
|
1819
2165
|
})();
|
|
1820
2166
|
</script>
|
|
1821
2167
|
</body>
|
|
@@ -1898,8 +2244,8 @@ function createRoomBridge(name) {
|
|
|
1898
2244
|
let session;
|
|
1899
2245
|
const messageQueue = [];
|
|
1900
2246
|
const messageWaiters = [];
|
|
1901
|
-
const
|
|
1902
|
-
const signalWaiters =
|
|
2247
|
+
const signalQueue = [];
|
|
2248
|
+
const signalWaiters = [];
|
|
1903
2249
|
const drainMessage = (m) => {
|
|
1904
2250
|
const safe = { ...m, text: sanitizePeerText(m.text) };
|
|
1905
2251
|
const waiter = messageWaiters.shift();
|
|
@@ -1913,23 +2259,15 @@ function createRoomBridge(name) {
|
|
|
1913
2259
|
}
|
|
1914
2260
|
};
|
|
1915
2261
|
const drainSignal = (from, frame) => {
|
|
1916
|
-
const
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
w(frame);
|
|
1922
|
-
return;
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
let box = signalBoxes.get(from);
|
|
1926
|
-
if (box === void 0) {
|
|
1927
|
-
box = [];
|
|
1928
|
-
signalBoxes.set(from, box);
|
|
2262
|
+
const tagged = { from, rtc: frame };
|
|
2263
|
+
const waiter = signalWaiters.shift();
|
|
2264
|
+
if (waiter !== void 0) {
|
|
2265
|
+
waiter(tagged);
|
|
2266
|
+
return;
|
|
1929
2267
|
}
|
|
1930
|
-
|
|
1931
|
-
if (
|
|
1932
|
-
|
|
2268
|
+
signalQueue.push(tagged);
|
|
2269
|
+
if (signalQueue.length > MAX_QUEUED_ROOM_SIGNALS) {
|
|
2270
|
+
signalQueue.splice(0, signalQueue.length - MAX_QUEUED_ROOM_SIGNALS);
|
|
1933
2271
|
}
|
|
1934
2272
|
};
|
|
1935
2273
|
return {
|
|
@@ -1972,27 +2310,17 @@ function createRoomBridge(name) {
|
|
|
1972
2310
|
sendSignal(handle2, frame) {
|
|
1973
2311
|
session?.sendSignal(handle2, frame);
|
|
1974
2312
|
},
|
|
1975
|
-
async pollSignal(
|
|
1976
|
-
|
|
1977
|
-
if (box !== void 0 && box.length > 0) return box.shift() ?? null;
|
|
2313
|
+
async pollSignal(_handle, timeoutMs) {
|
|
2314
|
+
if (signalQueue.length > 0) return signalQueue.shift() ?? null;
|
|
1978
2315
|
return new Promise((resolve) => {
|
|
1979
2316
|
const timer = setTimeout(() => {
|
|
1980
|
-
const
|
|
1981
|
-
if (
|
|
1982
|
-
const idx = waiters2.indexOf(resolve);
|
|
1983
|
-
if (idx >= 0) waiters2.splice(idx, 1);
|
|
1984
|
-
if (waiters2.length === 0) signalWaiters.delete(handle2);
|
|
1985
|
-
}
|
|
2317
|
+
const idx = signalWaiters.indexOf(resolve);
|
|
2318
|
+
if (idx >= 0) signalWaiters.splice(idx, 1);
|
|
1986
2319
|
resolve(null);
|
|
1987
2320
|
}, timeoutMs);
|
|
1988
|
-
|
|
1989
|
-
if (waiters === void 0) {
|
|
1990
|
-
waiters = [];
|
|
1991
|
-
signalWaiters.set(handle2, waiters);
|
|
1992
|
-
}
|
|
1993
|
-
waiters.push((f) => {
|
|
2321
|
+
signalWaiters.push((s) => {
|
|
1994
2322
|
clearTimeout(timer);
|
|
1995
|
-
resolve(
|
|
2323
|
+
resolve(s);
|
|
1996
2324
|
});
|
|
1997
2325
|
});
|
|
1998
2326
|
}
|
|
@@ -2312,7 +2640,7 @@ async function handle(req, res, opts) {
|
|
|
2312
2640
|
}
|
|
2313
2641
|
|
|
2314
2642
|
// src/cli.ts
|
|
2315
|
-
var VERSION = "0.7.
|
|
2643
|
+
var VERSION = "0.7.2";
|
|
2316
2644
|
function parsePort(raw) {
|
|
2317
2645
|
const n = Number(raw);
|
|
2318
2646
|
if (!Number.isInteger(n) || n < 1 || n > 65535) return void 0;
|
|
@@ -2788,10 +3116,16 @@ async function cmdLiveViaRelay(profile, to) {
|
|
|
2788
3116
|
` \xB7 relayed to ${sanitizePeerText(l.hello.handle)} (${l.hello.league}${qual} \xB7 ${l.hello.harness}) ${usageMark(l.hello)}${idMark(l.hello)}
|
|
2789
3117
|
`
|
|
2790
3118
|
);
|
|
2791
|
-
|
|
2792
|
-
|
|
3119
|
+
});
|
|
3120
|
+
pairing.onMessage((from, m) => {
|
|
3121
|
+
process2.stdout.write(` <${sanitizePeerText(from)}> ${sanitizePeerText(m.text)}
|
|
2793
3122
|
`);
|
|
2794
|
-
|
|
3123
|
+
});
|
|
3124
|
+
pairing.onQueued((from, n) => {
|
|
3125
|
+
process2.stdout.write(
|
|
3126
|
+
` \xB7 ${sanitizePeerText(from)} sent a message (${n} queued) \u2014 /open ${sanitizePeerText(from)} to read
|
|
3127
|
+
`
|
|
3128
|
+
);
|
|
2795
3129
|
});
|
|
2796
3130
|
pairing.add(link);
|
|
2797
3131
|
process2.stdout.write(" type to chat \xB7 /quit\n");
|
|
@@ -2870,10 +3204,16 @@ async function cmdLive(dating, any, to, keepAlive, viaRelay) {
|
|
|
2870
3204
|
` \xB7 matched ${sanitizePeerText(link.hello.handle)} (${link.hello.league}${qual} \xB7 ${link.hello.harness}) ${usageMark(link.hello)}${idMark(link.hello)}
|
|
2871
3205
|
`
|
|
2872
3206
|
);
|
|
2873
|
-
|
|
2874
|
-
|
|
3207
|
+
});
|
|
3208
|
+
pairing.onMessage((from, m) => {
|
|
3209
|
+
process2.stdout.write(` <${sanitizePeerText(from)}> ${sanitizePeerText(m.text)}
|
|
2875
3210
|
`);
|
|
2876
|
-
|
|
3211
|
+
});
|
|
3212
|
+
pairing.onQueued((from, n) => {
|
|
3213
|
+
process2.stdout.write(
|
|
3214
|
+
` \xB7 ${sanitizePeerText(from)} sent a message (${n} queued) \u2014 /open ${sanitizePeerText(from)} to read
|
|
3215
|
+
`
|
|
3216
|
+
);
|
|
2877
3217
|
});
|
|
2878
3218
|
const { topics, acceptLeague } = discoveryScope(profile.league, any);
|
|
2879
3219
|
const session = await startDiscovery({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibedate",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
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",
|