|
40 | 40 | var overlayRequestTimer = null; |
41 | 41 | var overlayRequestStartedAt = 0; |
42 | 42 | var overlayResponseReceived = false; |
| 43 | + var recentMetaPayloads = {}; |
| 44 | + var sockets = []; |
| 45 | + var hasServerMode = params.has("server") || params.has("server2") || params.has("server3"); |
43 | 46 |
|
44 | 47 | function showMissingOverlayError() { |
45 | 48 | console.error("[AI Overlay] No saved AI overlay found for overlay=" + (requestedOverlay || "active") + ". Open aiprompt.html, save the overlay, then reload this page."); |
|
54 | 57 |
|
55 | 58 | function forwardToOverlay(payload) { |
56 | 59 | if (!payload) return; |
| 60 | + if (!shouldForwardPayload(payload)) return; |
57 | 61 | if (!overlayReady || !overlayFrame || !overlayFrame.contentWindow) { |
58 | 62 | pendingPayloads.push(payload); |
59 | 63 | if (pendingPayloads.length > 100) pendingPayloads.shift(); |
|
62 | 66 | overlayFrame.contentWindow.postMessage({ dataReceived: { overlayNinja: payload } }, "*"); |
63 | 67 | } |
64 | 68 |
|
| 69 | + function shouldForwardPayload(payload) { |
| 70 | + if (!payload || typeof payload !== "object" || !Object.prototype.hasOwnProperty.call(payload, "meta")) return true; |
| 71 | + var key = ""; |
| 72 | + try { |
| 73 | + key = JSON.stringify(payload); |
| 74 | + } catch (e) { |
| 75 | + return true; |
| 76 | + } |
| 77 | + var now = Date.now(); |
| 78 | + if (recentMetaPayloads[key] && now - recentMetaPayloads[key] < 500) return false; |
| 79 | + recentMetaPayloads[key] = now; |
| 80 | + for (var existing in recentMetaPayloads) { |
| 81 | + if (now - recentMetaPayloads[existing] > 2000) delete recentMetaPayloads[existing]; |
| 82 | + } |
| 83 | + return true; |
| 84 | + } |
| 85 | + |
65 | 86 | function loadHtml(html) { |
66 | 87 | if (loaded || !html) return; |
67 | 88 | loaded = true; |
|
102 | 123 | } |
103 | 124 | } |
104 | 125 |
|
105 | | - function requestExtensionOverlays() { |
106 | | - if (!roomID) return false; |
107 | | - bridgeFrame = document.createElement("iframe"); |
108 | | - bridgeFrame.id = "bridgeFrame"; |
109 | | - bridgeFrame.dataset.bridgeLabel = params.get("label") || "dock"; |
110 | | - bridgeFrame.onload = function () { |
| 126 | + function createBridgeFrame(label) { |
| 127 | + var frame = document.createElement("iframe"); |
| 128 | + frame.id = "bridgeFrame"; |
| 129 | + frame.dataset.bridgeLabel = label; |
| 130 | + frame.onload = function () { |
111 | 131 | startOverlayRequestRetries(); |
112 | 132 | }; |
113 | | - bridgeFrame.src = "https://vdo.socialstream.ninja/?ln&password=" + encodeURIComponent(password) + |
114 | | - "&salt=vdo.ninja¬mobile&solo&label=" + encodeURIComponent(bridgeFrame.dataset.bridgeLabel) + "&view=" + encodeURIComponent(roomID) + |
| 133 | + frame.src = "https://vdo.socialstream.ninja/?ln&password=" + encodeURIComponent(password) + |
| 134 | + "&salt=vdo.ninja¬mobile&solo&label=" + encodeURIComponent(label) + "&view=" + encodeURIComponent(roomID) + |
115 | 135 | "&novideo&noaudio&cleanoutput&room=" + encodeURIComponent(roomID); |
116 | | - bridgeFrame.style.cssText = "width:0;height:0;position:fixed;left:-100px;top:-100px;"; |
117 | | - document.body.appendChild(bridgeFrame); |
| 136 | + frame.style.cssText = "width:0;height:0;position:fixed;left:-100px;top:-100px;"; |
| 137 | + document.body.appendChild(frame); |
| 138 | + return frame; |
| 139 | + } |
| 140 | + |
| 141 | + function requestExtensionOverlays() { |
| 142 | + if (!roomID) return false; |
| 143 | + bridgeFrame = createBridgeFrame("aioverlay"); |
118 | 144 | return true; |
119 | 145 | } |
120 | 146 |
|
| 147 | + function processIncomingData(raw) { |
| 148 | + if (!raw || typeof raw !== "object") return; |
| 149 | + if (raw.dataReceived && raw.dataReceived.overlayNinja) { |
| 150 | + forwardToOverlay(raw.dataReceived.overlayNinja); |
| 151 | + return; |
| 152 | + } |
| 153 | + if (raw.overlayNinja) { |
| 154 | + forwardToOverlay(raw.overlayNinja); |
| 155 | + return; |
| 156 | + } |
| 157 | + forwardToOverlay(raw); |
| 158 | + } |
| 159 | + |
| 160 | + function connectSocket(serverURL, socketRoomID, outChannel, inChannel) { |
| 161 | + if (!serverURL || !socketRoomID) return; |
| 162 | + var connection = { |
| 163 | + socket: null, |
| 164 | + attempts: 1, |
| 165 | + timer: null, |
| 166 | + closed: false |
| 167 | + }; |
| 168 | + sockets.push(connection); |
| 169 | + |
| 170 | + function scheduleReconnect() { |
| 171 | + if (connection.closed) return; |
| 172 | + var delay = Math.min(12000, 200 * connection.attempts); |
| 173 | + if (connection.timer) clearTimeout(connection.timer); |
| 174 | + connection.timer = setTimeout(function () { |
| 175 | + connection.attempts += 1; |
| 176 | + connectNow(); |
| 177 | + }, delay); |
| 178 | + } |
| 179 | + |
| 180 | + function connectNow() { |
| 181 | + if (connection.closed) return; |
| 182 | + try { |
| 183 | + connection.socket = new WebSocket(serverURL); |
| 184 | + } catch (e) { |
| 185 | + scheduleReconnect(); |
| 186 | + return; |
| 187 | + } |
| 188 | + |
| 189 | + connection.socket.onopen = function () { |
| 190 | + connection.attempts = 1; |
| 191 | + try { |
| 192 | + connection.socket.send(JSON.stringify({ |
| 193 | + join: socketRoomID, |
| 194 | + out: outChannel, |
| 195 | + in: inChannel |
| 196 | + })); |
| 197 | + } catch (e) {} |
| 198 | + }; |
| 199 | + |
| 200 | + connection.socket.onmessage = function (event) { |
| 201 | + if (!event || !event.data) return; |
| 202 | + try { |
| 203 | + var parsed = JSON.parse(event.data); |
| 204 | + processIncomingData(parsed); |
| 205 | + if (parsed && parsed.get) { |
| 206 | + connection.socket.send(JSON.stringify({ |
| 207 | + callback: { |
| 208 | + get: parsed.get, |
| 209 | + result: true |
| 210 | + } |
| 211 | + })); |
| 212 | + } |
| 213 | + } catch (e) {} |
| 214 | + }; |
| 215 | + |
| 216 | + connection.socket.onerror = function () { |
| 217 | + try { |
| 218 | + connection.socket.close(); |
| 219 | + } catch (e) {} |
| 220 | + }; |
| 221 | + |
| 222 | + connection.socket.onclose = function () { |
| 223 | + scheduleReconnect(); |
| 224 | + }; |
| 225 | + } |
| 226 | + |
| 227 | + connectNow(); |
| 228 | + } |
| 229 | + |
| 230 | + function connectServerModes() { |
| 231 | + var socketRoomID = (roomID || "").split(",")[0]; |
| 232 | + if (!socketRoomID) return; |
| 233 | + if (params.has("server")) { |
| 234 | + var apiServerURL = params.get("server") || (params.has("localserver") ? "ws://127.0.0.1:3000" : "wss://io.socialstream.ninja/api"); |
| 235 | + connectSocket(apiServerURL, socketRoomID, 2, 1); |
| 236 | + } |
| 237 | + if (params.has("server2") || params.has("server3")) { |
| 238 | + var extensionServerURL = params.has("server2") |
| 239 | + ? (params.get("server2") || "wss://io.socialstream.ninja/extension") |
| 240 | + : (params.get("server3") || "wss://io.socialstream.ninja/extension"); |
| 241 | + connectSocket(extensionServerURL, socketRoomID, 3, 4); |
| 242 | + } |
| 243 | + } |
| 244 | + |
121 | 245 | function postOverlayRequest() { |
122 | 246 | if (!bridgeFrame || !bridgeFrame.contentWindow || loaded || overlayResponseReceived) return; |
123 | 247 | bridgeFrame.contentWindow.postMessage({ |
|
168 | 292 | } |
169 | 293 | } |
170 | 294 |
|
| 295 | + function isBridgeSource(source) { |
| 296 | + return !!(bridgeFrame && source === bridgeFrame.contentWindow); |
| 297 | + } |
| 298 | + |
171 | 299 | window.addEventListener("message", function (event) { |
172 | | - if (bridgeFrame && event.source !== bridgeFrame.contentWindow) return; |
| 300 | + if (bridgeFrame && !isBridgeSource(event.source)) return; |
173 | 301 | var message = event.data || {}; |
174 | 302 | if (message.action && !loaded && !overlayResponseReceived) postOverlayRequest(); |
175 | 303 | var payload = resolveBridgeChunk(message.dataReceived && message.dataReceived.overlayNinja); |
|
182 | 310 | } |
183 | 311 | return; |
184 | 312 | } |
| 313 | + if (hasServerMode && isBridgeSource(event.source)) return; |
185 | 314 | forwardToOverlay(payload); |
186 | 315 | }); |
187 | 316 |
|
| 317 | + window.addEventListener("beforeunload", function () { |
| 318 | + for (var i = 0; i < sockets.length; i++) { |
| 319 | + var connection = sockets[i]; |
| 320 | + if (connection.timer) clearTimeout(connection.timer); |
| 321 | + connection.closed = true; |
| 322 | + if (connection.socket) { |
| 323 | + try { |
| 324 | + connection.socket.onclose = null; |
| 325 | + connection.socket.close(); |
| 326 | + } catch (e) {} |
| 327 | + } |
| 328 | + } |
| 329 | + }); |
| 330 | + |
188 | 331 | var loadedLocal = loadFromLocalState(); |
| 332 | + connectServerModes(); |
189 | 333 | if (requestExtensionOverlays()) { |
190 | 334 | setTimeout(function () { |
191 | 335 | if (!loaded) console.warn("[AI Overlay] Waiting for saved AI overlays from the extension..."); |
|
0 commit comments