Skip to content

Commit 957affe

Browse files
committed
fix(overlays): extend cross-transport feed dedupe to all server2-capable pages
Roll the dock's isDuplicateTransportDelivery guard out to every overlay page that consumes the channel-4 feed while the VDO.Ninja bridge can run concurrently, so additive API delivery and saved URLs with old &v params render each message once. The guard sits at the top of each page's single feed funnel (or at both transport entry points for hype, whose bridge handler is inline) and stays inert without server params. Commands, callbacks, and idless meta events are never deduplicated; local demo and preview payloads carry no id and pass through untouched. Pages left alone on purpose: cohost (socket and bridge are mutually exclusive), multi-alerts (2.5s signature dedupe), tipjar (contribution dedupe), confetti (idless waitlist state only), featured (no channel-4 feed), simple_api_client (diagnostic raw feed). Also fixes emotes.html receiving the feed twice with &server2 alone, as both its legacy and extension-relay sockets join channel 4.
1 parent 77b9ad9 commit 957affe

38 files changed

Lines changed: 1636 additions & 53 deletions

battle.html

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,49 @@
440440
}
441441
}
442442

443+
// Cross-transport dedupe: in server mode the same feed message can arrive over both
444+
// the websocket relay and the VDO.Ninja bridge; render only the first copy.
445+
var CROSS_TRANSPORT_DEDUPE_TTL_MS = 12000;
446+
var recentTransportDeliveries = new Map();
447+
function isDuplicateTransportDelivery(data) {
448+
if (!(urlParams.has("server") || urlParams.has("server2") || urlParams.has("server3"))) {
449+
return false;
450+
}
451+
if (!data || typeof data !== "object") {
452+
return false;
453+
}
454+
if (data.id === undefined || data.id === null) {
455+
return false;
456+
}
457+
if ("action" in data || "get" in data || "callback" in data || "mid" in data) {
458+
return false;
459+
}
460+
if (!(data.chatname || data.chatmessage || data.chatimg || data.hasDonation || data.membership || data.event || data.contentimg)) {
461+
return false;
462+
}
463+
var key =
464+
String(data.id) +
465+
"|" + (data.type || "") +
466+
"|" + (data.chatname || "") +
467+
"|" + String(data.chatmessage || "").slice(0, 100) +
468+
"|" + (data.hasDonation || "") +
469+
"|" + (data.event || "");
470+
var now = Date.now();
471+
recentTransportDeliveries.forEach(function (seenAt, seenKey) {
472+
if (now - seenAt > CROSS_TRANSPORT_DEDUPE_TTL_MS) {
473+
recentTransportDeliveries.delete(seenKey);
474+
}
475+
});
476+
if (recentTransportDeliveries.has(key)) {
477+
return true;
478+
}
479+
recentTransportDeliveries.set(key, now);
480+
return false;
481+
}
443482
function processInput(data){
483+
if (isDuplicateTransportDelivery(data)) {
484+
return;
485+
}
444486
if (data.startgame && hostControlled) {
445487
startGame();
446488
return;

content.html

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,49 @@
436436
document.documentElement.style.setProperty("--background-color", "#0000", "important");
437437
}
438438

439+
// Cross-transport dedupe: in server mode the same feed message can arrive over both
440+
// the websocket relay and the VDO.Ninja bridge; render only the first copy.
441+
var CROSS_TRANSPORT_DEDUPE_TTL_MS = 12000;
442+
var recentTransportDeliveries = new Map();
443+
function isDuplicateTransportDelivery(data) {
444+
if (!(urlParams.has("server") || urlParams.has("server2") || urlParams.has("server3"))) {
445+
return false;
446+
}
447+
if (!data || typeof data !== "object") {
448+
return false;
449+
}
450+
if (data.id === undefined || data.id === null) {
451+
return false;
452+
}
453+
if ("action" in data || "get" in data || "callback" in data || "mid" in data) {
454+
return false;
455+
}
456+
if (!(data.chatname || data.chatmessage || data.chatimg || data.hasDonation || data.membership || data.event || data.contentimg)) {
457+
return false;
458+
}
459+
var key =
460+
String(data.id) +
461+
"|" + (data.type || "") +
462+
"|" + (data.chatname || "") +
463+
"|" + String(data.chatmessage || "").slice(0, 100) +
464+
"|" + (data.hasDonation || "") +
465+
"|" + (data.event || "");
466+
var now = Date.now();
467+
recentTransportDeliveries.forEach(function (seenAt, seenKey) {
468+
if (now - seenAt > CROSS_TRANSPORT_DEDUPE_TTL_MS) {
469+
recentTransportDeliveries.delete(seenKey);
470+
}
471+
});
472+
if (recentTransportDeliveries.has(key)) {
473+
return true;
474+
}
475+
recentTransportDeliveries.set(key, now);
476+
return false;
477+
}
439478
function processInput(data){
479+
if (isDuplicateTransportDelivery(data)) {
480+
return;
481+
}
440482
if ("mid" in data){
441483
return;
442484
} else if ("pin" in data){

credits.html

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,49 @@ <h2 class="instructions-title">Stream Credits Setup</h2>
753753

754754
let previewMode = false;
755755

756+
// Cross-transport dedupe: in server mode the same feed message can arrive over both
757+
// the websocket relay and the VDO.Ninja bridge; render only the first copy.
758+
var CROSS_TRANSPORT_DEDUPE_TTL_MS = 12000;
759+
var recentTransportDeliveries = new Map();
760+
function isDuplicateTransportDelivery(data) {
761+
if (!(urlParams.has("server") || urlParams.has("server2") || urlParams.has("server3"))) {
762+
return false;
763+
}
764+
if (!data || typeof data !== "object") {
765+
return false;
766+
}
767+
if (data.id === undefined || data.id === null) {
768+
return false;
769+
}
770+
if ("action" in data || "get" in data || "callback" in data || "mid" in data) {
771+
return false;
772+
}
773+
if (!(data.chatname || data.chatmessage || data.chatimg || data.hasDonation || data.membership || data.event || data.contentimg)) {
774+
return false;
775+
}
776+
var key =
777+
String(data.id) +
778+
"|" + (data.type || "") +
779+
"|" + (data.chatname || "") +
780+
"|" + String(data.chatmessage || "").slice(0, 100) +
781+
"|" + (data.hasDonation || "") +
782+
"|" + (data.event || "");
783+
var now = Date.now();
784+
recentTransportDeliveries.forEach(function (seenAt, seenKey) {
785+
if (now - seenAt > CROSS_TRANSPORT_DEDUPE_TTL_MS) {
786+
recentTransportDeliveries.delete(seenKey);
787+
}
788+
});
789+
if (recentTransportDeliveries.has(key)) {
790+
return true;
791+
}
792+
recentTransportDeliveries.set(key, now);
793+
return false;
794+
}
756795
function processData(data) {
796+
if (isDuplicateTransportDelivery(data)) {
797+
return;
798+
}
757799
if (data.content) {
758800
data = data.content;
759801
}

emotes.html

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,49 @@
496496
document.documentElement.style.setProperty("--background-color", pageBgParam, "important");
497497
}
498498

499+
// Cross-transport dedupe: in server mode the same feed message can arrive over both
500+
// the websocket relay and the VDO.Ninja bridge; render only the first copy.
501+
var CROSS_TRANSPORT_DEDUPE_TTL_MS = 12000;
502+
var recentTransportDeliveries = new Map();
503+
function isDuplicateTransportDelivery(data) {
504+
if (!(urlParams.has("server") || urlParams.has("server2") || urlParams.has("server3"))) {
505+
return false;
506+
}
507+
if (!data || typeof data !== "object") {
508+
return false;
509+
}
510+
if (data.id === undefined || data.id === null) {
511+
return false;
512+
}
513+
if ("action" in data || "get" in data || "callback" in data || "mid" in data) {
514+
return false;
515+
}
516+
if (!(data.chatname || data.chatmessage || data.chatimg || data.hasDonation || data.membership || data.event || data.contentimg)) {
517+
return false;
518+
}
519+
var key =
520+
String(data.id) +
521+
"|" + (data.type || "") +
522+
"|" + (data.chatname || "") +
523+
"|" + String(data.chatmessage || "").slice(0, 100) +
524+
"|" + (data.hasDonation || "") +
525+
"|" + (data.event || "");
526+
var now = Date.now();
527+
recentTransportDeliveries.forEach(function (seenAt, seenKey) {
528+
if (now - seenAt > CROSS_TRANSPORT_DEDUPE_TTL_MS) {
529+
recentTransportDeliveries.delete(seenKey);
530+
}
531+
});
532+
if (recentTransportDeliveries.has(key)) {
533+
return true;
534+
}
535+
recentTransportDeliveries.set(key, now);
536+
return false;
537+
}
499538
function processInput(data){
539+
if (isDuplicateTransportDelivery(data)) {
540+
return;
541+
}
500542
if ("mid" in data){
501543
return;
502544
} else if ("pin" in data){

events.html

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1909,7 +1909,49 @@ <h1 class="small">Events Dashboard</h1>
19091909
};
19101910
}
19111911

1912+
// Cross-transport dedupe: in server mode the same feed message can arrive over both
1913+
// the websocket relay and the VDO.Ninja bridge; render only the first copy.
1914+
var CROSS_TRANSPORT_DEDUPE_TTL_MS = 12000;
1915+
var recentTransportDeliveries = new Map();
1916+
function isDuplicateTransportDelivery(data) {
1917+
if (!(urlParams.has("server") || urlParams.has("server2") || urlParams.has("server3"))) {
1918+
return false;
1919+
}
1920+
if (!data || typeof data !== "object") {
1921+
return false;
1922+
}
1923+
if (data.id === undefined || data.id === null) {
1924+
return false;
1925+
}
1926+
if ("action" in data || "get" in data || "callback" in data || "mid" in data) {
1927+
return false;
1928+
}
1929+
if (!(data.chatname || data.chatmessage || data.chatimg || data.hasDonation || data.membership || data.event || data.contentimg)) {
1930+
return false;
1931+
}
1932+
var key =
1933+
String(data.id) +
1934+
"|" + (data.type || "") +
1935+
"|" + (data.chatname || "") +
1936+
"|" + String(data.chatmessage || "").slice(0, 100) +
1937+
"|" + (data.hasDonation || "") +
1938+
"|" + (data.event || "");
1939+
var now = Date.now();
1940+
recentTransportDeliveries.forEach(function (seenAt, seenKey) {
1941+
if (now - seenAt > CROSS_TRANSPORT_DEDUPE_TTL_MS) {
1942+
recentTransportDeliveries.delete(seenKey);
1943+
}
1944+
});
1945+
if (recentTransportDeliveries.has(key)) {
1946+
return true;
1947+
}
1948+
recentTransportDeliveries.set(key, now);
1949+
return false;
1950+
}
19121951
function addMessageToOverlay(data) {
1952+
if (isDuplicateTransportDelivery(data)) {
1953+
return;
1954+
}
19131955
if (data && String(data.type || '').toLowerCase() === 'obs') {
19141956
return;
19151957
}

games.html

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,49 @@
465465
}
466466

467467
// Handle incoming messages (compatible with Social Stream format)
468+
// Cross-transport dedupe: in server mode the same feed message can arrive over both
469+
// the websocket relay and the VDO.Ninja bridge; render only the first copy.
470+
var CROSS_TRANSPORT_DEDUPE_TTL_MS = 12000;
471+
var recentTransportDeliveries = new Map();
472+
function isDuplicateTransportDelivery(data) {
473+
if (!(urlParams.has("server") || urlParams.has("server2") || urlParams.has("server3"))) {
474+
return false;
475+
}
476+
if (!data || typeof data !== "object") {
477+
return false;
478+
}
479+
if (data.id === undefined || data.id === null) {
480+
return false;
481+
}
482+
if ("action" in data || "get" in data || "callback" in data || "mid" in data) {
483+
return false;
484+
}
485+
if (!(data.chatname || data.chatmessage || data.chatimg || data.hasDonation || data.membership || data.event || data.contentimg)) {
486+
return false;
487+
}
488+
var key =
489+
String(data.id) +
490+
"|" + (data.type || "") +
491+
"|" + (data.chatname || "") +
492+
"|" + String(data.chatmessage || "").slice(0, 100) +
493+
"|" + (data.hasDonation || "") +
494+
"|" + (data.event || "");
495+
var now = Date.now();
496+
recentTransportDeliveries.forEach(function (seenAt, seenKey) {
497+
if (now - seenAt > CROSS_TRANSPORT_DEDUPE_TTL_MS) {
498+
recentTransportDeliveries.delete(seenKey);
499+
}
500+
});
501+
if (recentTransportDeliveries.has(key)) {
502+
return true;
503+
}
504+
recentTransportDeliveries.set(key, now);
505+
return false;
506+
}
468507
function processData(data) {
508+
if (isDuplicateTransportDelivery(data)) {
509+
return;
510+
}
469511
if (!gameState.isActive) return;
470512

471513
// Handle different data formats from Social Stream

games/chickenroyale.html

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1920,7 +1920,49 @@ <h4>🐔 Lobby <span id="roster-count">(0)</span></h4>
19201920
}
19211921

19221922
// ====================== Chat integration ======================
1923+
// Cross-transport dedupe: in server mode the same feed message can arrive over both
1924+
// the websocket relay and the VDO.Ninja bridge; render only the first copy.
1925+
var CROSS_TRANSPORT_DEDUPE_TTL_MS = 12000;
1926+
var recentTransportDeliveries = new Map();
1927+
function isDuplicateTransportDelivery(data) {
1928+
if (!(urlParams.has("server") || urlParams.has("server2") || urlParams.has("server3"))) {
1929+
return false;
1930+
}
1931+
if (!data || typeof data !== "object") {
1932+
return false;
1933+
}
1934+
if (data.id === undefined || data.id === null) {
1935+
return false;
1936+
}
1937+
if ("action" in data || "get" in data || "callback" in data || "mid" in data) {
1938+
return false;
1939+
}
1940+
if (!(data.chatname || data.chatmessage || data.chatimg || data.hasDonation || data.membership || data.event || data.contentimg)) {
1941+
return false;
1942+
}
1943+
var key =
1944+
String(data.id) +
1945+
"|" + (data.type || "") +
1946+
"|" + (data.chatname || "") +
1947+
"|" + String(data.chatmessage || "").slice(0, 100) +
1948+
"|" + (data.hasDonation || "") +
1949+
"|" + (data.event || "");
1950+
var now = Date.now();
1951+
recentTransportDeliveries.forEach(function (seenAt, seenKey) {
1952+
if (now - seenAt > CROSS_TRANSPORT_DEDUPE_TTL_MS) {
1953+
recentTransportDeliveries.delete(seenKey);
1954+
}
1955+
});
1956+
if (recentTransportDeliveries.has(key)) {
1957+
return true;
1958+
}
1959+
recentTransportDeliveries.set(key, now);
1960+
return false;
1961+
}
19231962
function processData(data) {
1963+
if (isDuplicateTransportDelivery(data)) {
1964+
return;
1965+
}
19241966
try {
19251967
if (data && data.content) data = data.content;
19261968
if (!data || typeof data !== 'object') return;

0 commit comments

Comments
 (0)