Skip to content

Commit 6d7bead

Browse files
committed
Add Gemini Live session resumption UI
1 parent 4870484 commit 6d7bead

63 files changed

Lines changed: 3274 additions & 648 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

actions.html

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,44 @@ <h2>Session ID Required</h2>
321321
})(window);
322322

323323
var urlParams = new URLSearchParams(window.location.search);
324+
var SERVER_EXCLUSIVE_TRANSPORT_VERSION = "3.52.0";
325+
// Transport migration guard: only pages with full server parity may skip the legacy bridge.
326+
var TRANSPORT_CAPABILITIES = {
327+
serverFeed: false,
328+
serverTargeted: false,
329+
upstreamCommands: false,
330+
customChannel: true,
331+
legacyBridgeRequired: true,
332+
legacyBridgeReason: "Flow Actions uses channel 6 and sendTargetP2P('actions') is still bridge-only."
333+
};
334+
function versionAtLeast(value, minimum) {
335+
var a = String(value || "").split(".");
336+
var b = String(minimum || "").split(".");
337+
var len = Math.max(a.length, b.length);
338+
for (var i = 0; i < len; i++) {
339+
var av = parseInt(a[i], 10) || 0;
340+
var bv = parseInt(b[i], 10) || 0;
341+
if (av > bv) return true;
342+
if (av < bv) return false;
343+
}
344+
return true;
345+
}
346+
// Only feed-carrying server params listed by this page may disable the bridge; server3 alone is command-only.
347+
function hasExclusiveServerTransport(caps) {
348+
var params = caps && typeof caps.exclusiveServerParams === "object" ? caps.exclusiveServerParams : { server2: true };
349+
return (params.server === true && urlParams.has("server")) ||
350+
(params.server2 !== false && urlParams.has("server2")) ||
351+
(params.server3 === true && urlParams.has("server2") && urlParams.has("server3"));
352+
}
353+
function useServerOnlyTransport() {
354+
var caps = (typeof TRANSPORT_CAPABILITIES === "object" && TRANSPORT_CAPABILITIES) ? TRANSPORT_CAPABILITIES : {};
355+
return hasExclusiveServerTransport(caps) &&
356+
versionAtLeast(urlParams.get("v"), SERVER_EXCLUSIVE_TRANSPORT_VERSION) &&
357+
caps.serverFeed === true &&
358+
caps.legacyBridgeRequired !== true &&
359+
caps.upstreamCommands !== true &&
360+
caps.customChannel !== true;
361+
}
324362
var iframe = null;
325363
var connectedPeers = {};
326364
var roomID = "test";
@@ -1242,18 +1280,19 @@ <h2>Session ID Required</h2>
12421280
setupSocket();
12431281
}
12441282

1245-
// Create iframe for receiving WebRTC data channel messages
1246-
iframe = document.createElement("iframe");
1247-
iframe.connectedPeers = connectedPeers;
1248-
iframe.src = "https://vdo.socialstream.ninja/?ln&salt=vdo.ninja&password=" + password +
1249-
"&solo&view=" + roomID + "&novideo&noaudio&label=actions&cleanoutput&room=" + roomID;
1250-
iframe.style.width = "0px";
1251-
iframe.style.height = "0px";
1252-
iframe.style.position = "fixed";
1253-
iframe.style.left = "-100px";
1254-
iframe.style.top = "-100px";
1255-
iframe.id = "frame1";
1256-
document.body.appendChild(iframe);
1283+
if (!useServerOnlyTransport()) {
1284+
iframe = document.createElement("iframe");
1285+
iframe.connectedPeers = connectedPeers;
1286+
iframe.src = "https://vdo.socialstream.ninja/?ln&salt=vdo.ninja&password=" + password +
1287+
"&solo&view=" + roomID + "&novideo&noaudio&label=actions&cleanoutput&room=" + roomID;
1288+
iframe.style.width = "0px";
1289+
iframe.style.height = "0px";
1290+
iframe.style.position = "fixed";
1291+
iframe.style.left = "-100px";
1292+
iframe.style.top = "-100px";
1293+
iframe.id = "frame1";
1294+
document.body.appendChild(iframe);
1295+
}
12571296

12581297
console.log("Actions page initialized with session ID:", roomID);
12591298

aioverlay.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@
160160

161161
function requestExtensionOverlays() {
162162
if (!roomID) return false;
163+
// This bridge fetches saved AI overlay HTML/control responses. In server mode, feed payloads from it are ignored below.
163164
bridgeFrame = createBridgeFrame("aioverlay");
164165
return true;
165166
}

aiprompt.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2332,6 +2332,7 @@ <h2>Send custom payload</h2>
23322332
var urlParams = new URLSearchParams(location.search);
23332333
bridgeRoomID = urlParams.get("session") || "";
23342334
bridgePassword = urlParams.get("password") || "false";
2335+
// This bridge is the editor's upstream AI/control path, not an overlay chat-feed receiver.
23352336
if (!bridgeRoomID) {
23362337
setStatus("No session in URL. You can still build, preview, and export overlays. Add ?session=YOUR_ID to ask the AI.", "warn");
23372338
return;

background.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10986,6 +10986,10 @@ async function openchat(target = null, force = false) {
1098610986
function sendDataP2P(data, UUID = false) {
1098710987
// function to send data to the DOCk via the VDO.Ninja API
1098810988

10989+
// TODO after 2026-09-01: make the server2 dock send additive instead of
10990+
// returning here. Overlay links generated from 3.52.0+ are prepared to use
10991+
// socket-only receive mode only when their TRANSPORT_CAPABILITIES opt in.
10992+
// Keep legacy sends for old/cached/custom overlays. Base reference: b26c7aeb.
1098910993
if (!UUID && settings.server2 && socketserverDock && socketserverDock.readyState === 1) {
1099010994
try {
1099110995
if (data.out) {

battle.html

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,44 @@
179179
})(window);
180180

181181
var urlParams = new URLSearchParams(window.location.search);
182+
var SERVER_EXCLUSIVE_TRANSPORT_VERSION = "3.52.0";
183+
// Transport migration guard: only pages with full server parity may skip the legacy bridge.
184+
var TRANSPORT_CAPABILITIES = {
185+
serverFeed: true,
186+
serverTargeted: false,
187+
upstreamCommands: false,
188+
customChannel: false,
189+
legacyBridgeRequired: false,
190+
exclusiveServerParams: { server: true, server2: true, server3: false }
191+
};
192+
function versionAtLeast(value, minimum) {
193+
var a = String(value || "").split(".");
194+
var b = String(minimum || "").split(".");
195+
var len = Math.max(a.length, b.length);
196+
for (var i = 0; i < len; i++) {
197+
var av = parseInt(a[i], 10) || 0;
198+
var bv = parseInt(b[i], 10) || 0;
199+
if (av > bv) return true;
200+
if (av < bv) return false;
201+
}
202+
return true;
203+
}
204+
// Only feed-carrying server params listed by this page may disable the bridge; server3 alone is command-only.
205+
function hasExclusiveServerTransport(caps) {
206+
var params = caps && typeof caps.exclusiveServerParams === "object" ? caps.exclusiveServerParams : { server2: true };
207+
return (params.server === true && urlParams.has("server")) ||
208+
(params.server2 !== false && urlParams.has("server2")) ||
209+
(params.server3 === true && urlParams.has("server2") && urlParams.has("server3"));
210+
}
211+
function useServerOnlyTransport() {
212+
var caps = (typeof TRANSPORT_CAPABILITIES === "object" && TRANSPORT_CAPABILITIES) ? TRANSPORT_CAPABILITIES : {};
213+
return hasExclusiveServerTransport(caps) &&
214+
versionAtLeast(urlParams.get("v"), SERVER_EXCLUSIVE_TRANSPORT_VERSION) &&
215+
caps.serverFeed === true &&
216+
caps.legacyBridgeRequired !== true &&
217+
caps.upstreamCommands !== true &&
218+
caps.customChannel !== true;
219+
}
182220

183221
try {
184222
if ((window.ninjafy || window.electronApi)){ // fix for electron dragging.
@@ -429,28 +467,31 @@
429467
}
430468
}
431469

432-
var iframe = document.createElement("iframe"); // we can leave it called "dock", as it will get all the same messages the dock gets. This could be optimized of course..
433-
iframe.src = "https://vdo.socialstream.ninja/?ln&salt=vdo.ninja&notmobile&notmobile&password="+encodeURIComponent(password)+lanonly+"&solo&view="+roomID+"&novideo&noaudio&label=dock&cleanoutput&room="+roomID; // view only connection (data two way of course)
434-
iframe.style.width = "0px";
435-
iframe.style.height = "0px";
436-
iframe.style.position = "fixed";
437-
iframe.style.left = "-100px";
438-
iframe.style.top = "-100px";
439-
iframe.id = "frame1"
440-
document.body.appendChild(iframe);
441-
442-
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
443-
var eventer = window[eventMethod];
444-
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
445-
446-
eventer(messageEvent, function (e) {
447-
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
448-
if (e.data && typeof e.data === "object" && e.data.dataReceived && typeof e.data.dataReceived === "object"){ // raw data
449-
if ("overlayNinja" in e.data.dataReceived){
450-
processInput(e.data.dataReceived.overlayNinja);
470+
var iframe = null;
471+
if (!useServerOnlyTransport()) {
472+
iframe = document.createElement("iframe"); // we can leave it called "dock", as it will get all the same messages the dock gets. This could be optimized of course..
473+
iframe.src = "https://vdo.socialstream.ninja/?ln&salt=vdo.ninja&notmobile&notmobile&password="+encodeURIComponent(password)+lanonly+"&solo&view="+roomID+"&novideo&noaudio&label=dock&cleanoutput&room="+roomID; // view only connection (data two way of course)
474+
iframe.style.width = "0px";
475+
iframe.style.height = "0px";
476+
iframe.style.position = "fixed";
477+
iframe.style.left = "-100px";
478+
iframe.style.top = "-100px";
479+
iframe.id = "frame1"
480+
document.body.appendChild(iframe);
481+
482+
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
483+
var eventer = window[eventMethod];
484+
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
485+
486+
eventer(messageEvent, function (e) {
487+
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes
488+
if (e.data && typeof e.data === "object" && e.data.dataReceived && typeof e.data.dataReceived === "object"){ // raw data
489+
if ("overlayNinja" in e.data.dataReceived){
490+
processInput(e.data.dataReceived.overlayNinja);
491+
}
451492
}
452-
}
453-
});
493+
});
494+
}
454495

455496

456497
var conCon = 1;

bot.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,9 +1912,10 @@
19121912
});
19131913
}
19141914

1915-
if (!socketserver) {
1916-
RecvDataWindow(); // only load the iframe if not connecting via wss
1917-
}
1915+
// Transport migration: this page already chooses socket OR iframe; do not start the iframe when a server socket is active.
1916+
if (!socketserver) {
1917+
RecvDataWindow(); // only load the iframe if not connecting via wss
1918+
}
19181919

19191920
try {
19201921
var fallbackImage = new Image(); // preload it.

cohost-overlay.html

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,44 @@
854854
<script>
855855
(function () {
856856
var urlParams = new URLSearchParams(location.search);
857+
var SERVER_EXCLUSIVE_TRANSPORT_VERSION = "3.52.0";
858+
// Transport migration guard: only pages with full server parity may skip the legacy bridge.
859+
var TRANSPORT_CAPABILITIES = {
860+
serverFeed: true,
861+
serverTargeted: true,
862+
upstreamCommands: false,
863+
customChannel: false,
864+
legacyBridgeRequired: false,
865+
exclusiveServerParams: { server: true, server2: true, server3: false }
866+
};
867+
function versionAtLeast(value, minimum) {
868+
var a = String(value || "").split(".");
869+
var b = String(minimum || "").split(".");
870+
var len = Math.max(a.length, b.length);
871+
for (var i = 0; i < len; i++) {
872+
var av = parseInt(a[i], 10) || 0;
873+
var bv = parseInt(b[i], 10) || 0;
874+
if (av > bv) return true;
875+
if (av < bv) return false;
876+
}
877+
return true;
878+
}
879+
// Only feed-carrying server params listed by this page may disable the bridge; server3 alone is command-only.
880+
function hasExclusiveServerTransport(caps) {
881+
var params = caps && typeof caps.exclusiveServerParams === "object" ? caps.exclusiveServerParams : { server2: true };
882+
return (params.server === true && urlParams.has("server")) ||
883+
(params.server2 !== false && urlParams.has("server2")) ||
884+
(params.server3 === true && urlParams.has("server2") && urlParams.has("server3"));
885+
}
886+
function useServerOnlyTransport() {
887+
var caps = (typeof TRANSPORT_CAPABILITIES === "object" && TRANSPORT_CAPABILITIES) ? TRANSPORT_CAPABILITIES : {};
888+
return hasExclusiveServerTransport(caps) &&
889+
versionAtLeast(urlParams.get("v"), SERVER_EXCLUSIVE_TRANSPORT_VERSION) &&
890+
caps.serverFeed === true &&
891+
caps.legacyBridgeRequired !== true &&
892+
caps.upstreamCommands !== true &&
893+
caps.customChannel !== true;
894+
}
857895
var sessionId = urlParams.get("session") || urlParams.get("room") || "";
858896
var password = urlParams.get("password") || "false";
859897
var label = urlParams.get("label") || "cohost-overlay";
@@ -1124,7 +1162,7 @@
11241162
}
11251163

11261164
function setupBridge() {
1127-
if (!sessionId || urlParams.has("preview")) return;
1165+
if (!sessionId || urlParams.has("preview") || useServerOnlyTransport()) return;
11281166
bridgeFrame = document.createElement("iframe");
11291167
bridgeFrame.src = "https://vdo.socialstream.ninja/?ln&salt=vdo.ninja&password=" + encodeURIComponent(password) + "&push&notmobile&label=" + encodeURIComponent(label) + "&view=" + encodeURIComponent(sessionId) + "&vd=0&ad=0&novideo&noaudio&autostart&cleanoutput&room=" + encodeURIComponent(sessionId);
11301168
bridgeFrame.style.cssText = "width:0;height:0;border:0;position:absolute;left:-200px;top:-200px;";

0 commit comments

Comments
 (0)