Skip to content

Commit 27b28eb

Browse files
steveseguinactions-user
authored andcommitted
**fix(overlays): deduplicate cross-transport feed messages across server2-capable pages**
Extend the `isDuplicateTransportDelivery` guard from the dock to all overlay pages that consume the channel-4 feed in server2 mode. This prevents duplicate rendering of messages arriving via both websocket relay and the VDO.Ninja bridge, addressing issues with additive API delivery and legacy `&v` parameters in saved URLs. **Key changes:** - Added the deduplication guard to the top of each page's feed processing funnel (e.g., `processInput`, `processData`, `addMessageToOverlay`). - Guard remains inactive without server params (`server`, `server2`, `server3`), preserving local demo/preview behavior. - Commands, callbacks, and idless meta events (e.g., without `id`) bypass deduplication. **Pages intentionally excluded:** - `cohost`: Socket and bridge transports are mutually exclusive. - `multi-alerts`: Already uses 2.5s signature dedupe. - `tipjar`: Has contribution-specific dedupe. - `confetti`: Handles only idless waitlist state. - `featured`: Does not consume the channel-4 feed. - `simple_api_client`: Serves as a diagnostic raw feed. **Additional fix:** - Resolved `emotes.html` receiving the feed twice with `&server2` alone, as both legacy and extension-relay sockets joined channel 4. This change builds on commit 77b9ad9 (dock dedupe) and targets pages from the games, scripts, and themes areas (e.g., `battle.html`, `content.html`, `credits.html`, `emotes.html`, `events.html`). [auto-enhanced]
1 parent 77b9ad9 commit 27b28eb

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)