From b64c6f0f9fc70c678f14283b64722b2d97220fed Mon Sep 17 00:00:00 2001 From: sankeer28 Date: Mon, 20 Jul 2026 18:27:34 -0400 Subject: [PATCH 1/9] fix(404Anime): stop UpdateData from awaiting the page-bridge read inline UpdateData awaited execInPage/getPageVariable on every tick, which fires more often than that round trip completes. Calls backed up and the extension logged 'UpdateData execution is too slow' while skipping most updates, so Discord activity never refreshed for real users. Poll the bridge on our own 1s interval instead and have UpdateData read the cached result synchronously. --- websites/0-9/404Anime/presence.ts | 35 ++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/websites/0-9/404Anime/presence.ts b/websites/0-9/404Anime/presence.ts index e353640024dc..bd3181a62b22 100644 --- a/websites/0-9/404Anime/presence.ts +++ b/websites/0-9/404Anime/presence.ts @@ -52,7 +52,7 @@ function getVideoFallback(): Pick< } } -async function getBridgeData(): Promise { +async function fetchBridgeData(): Promise { if (supports(presence, 'execInPage')) { try { const data = await presence.execInPage({ @@ -79,6 +79,35 @@ async function getBridgeData(): Promise { } } +// UpdateData fires far more often than the execInPage/getPageVariable round +// trip can complete, so awaiting it inline on every tick backs up and the +// extension starts skipping updates ("UpdateData execution is too slow"). +// Poll the bridge on our own slower cadence instead, and let UpdateData +// read the cached result synchronously — decouples the update tick rate +// from bridge latency entirely. +let bridgeCache: Anime404PremidPresence | null = null +let bridgePollInFlight = false + +function pollBridgeData(): void { + if (bridgePollInFlight) + return + bridgePollInFlight = true + fetchBridgeData() + .then((data) => { + bridgeCache = data + }) + .finally(() => { + bridgePollInFlight = false + }) +} + +pollBridgeData() +setInterval(pollBridgeData, 1000) + +function getBridgeData(): Anime404PremidPresence | null { + return bridgeCache +} + function applyPlayback( presenceData: PresenceData, currentTime = 0, @@ -163,8 +192,8 @@ function getBrowsingState(pathname: string): string { return 'Exploring 404Anime' } -presence.on('UpdateData', async () => { - const bridge = await getBridgeData() +presence.on('UpdateData', () => { + const bridge = getBridgeData() const fallback = getVideoFallback() const data = { ...fallback, From 754685e124d26e2d42446790a565b8076a4c4956 Mon Sep 17 00:00:00 2001 From: sankeer28 Date: Wed, 22 Jul 2026 18:49:38 -0400 Subject: [PATCH 2/9] fix(404Anime): read presence data from the DOM instead of an async bridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix (poll + cache) worked around execInPage's round-trip latency without removing it. Every other activity in this repo reads its data straight from the page's DOM, which is synchronous — no cross-realm message-passing needed. Switch to that pattern: the site now renders a hidden #s-anime-premid element with the presence data as data-* attributes, and UpdateData reads it directly with no async call at all, removing the "UpdateData execution is too slow" risk entirely instead of just reducing how often it's hit. --- websites/0-9/404Anime/presence.ts | 75 ++++++++++--------------------- 1 file changed, 23 insertions(+), 52 deletions(-) diff --git a/websites/0-9/404Anime/presence.ts b/websites/0-9/404Anime/presence.ts index bd3181a62b22..3be792c4b8d5 100644 --- a/websites/0-9/404Anime/presence.ts +++ b/websites/0-9/404Anime/presence.ts @@ -1,4 +1,4 @@ -import { ActivityType, Assets, getTimestamps, supports } from 'premid' +import { ActivityType, Assets, getTimestamps } from 'premid' const presence = new Presence({ clientId: '503557087041683458', @@ -52,60 +52,31 @@ function getVideoFallback(): Pick< } } -async function fetchBridgeData(): Promise { - if (supports(presence, 'execInPage')) { - try { - const data = await presence.execInPage({ - get: '__SANIME_PREMID__', - }) - - if (data?.animeTitle || data?.episode) - return data - } - catch { - // Fall back to the older variable reader below. - } - } - - try { - const page = await presence.getPageVariable<{ - __SANIME_PREMID__?: Anime404PremidPresence | null - }>('__SANIME_PREMID__') - - return page.__SANIME_PREMID__ ?? null - } - catch { +function getBridgeData(): Anime404PremidPresence | null { + const el = document.getElementById('s-anime-premid') + if (!el) return null - } -} - -// UpdateData fires far more often than the execInPage/getPageVariable round -// trip can complete, so awaiting it inline on every tick backs up and the -// extension starts skipping updates ("UpdateData execution is too slow"). -// Poll the bridge on our own slower cadence instead, and let UpdateData -// read the cached result synchronously — decouples the update tick rate -// from bridge latency entirely. -let bridgeCache: Anime404PremidPresence | null = null -let bridgePollInFlight = false - -function pollBridgeData(): void { - if (bridgePollInFlight) - return - bridgePollInFlight = true - fetchBridgeData() - .then((data) => { - bridgeCache = data - }) - .finally(() => { - bridgePollInFlight = false - }) -} -pollBridgeData() -setInterval(pollBridgeData, 1000) + const d = el.dataset + if (!d.animeTitle && !d.episode) + return null -function getBridgeData(): Anime404PremidPresence | null { - return bridgeCache + return { + page: d.page, + mediaType: d.mediaType as Anime404PremidPresence['mediaType'], + animeId: d.animeId ? Number(d.animeId) : undefined, + animeTitle: d.animeTitle, + episode: d.episode ? Number(d.episode) : undefined, + season: d.season ? Number(d.season) : undefined, + episodeTitle: d.episodeTitle ?? null, + audio: d.audio as Anime404PremidPresence['audio'], + server: d.server, + cover: d.cover ?? null, + url: d.url, + currentTime: d.currentTime ? Number(d.currentTime) : undefined, + duration: d.duration ? Number(d.duration) : undefined, + paused: d.paused === 'true', + } } function applyPlayback( From 4ebb7ae0a39687ee2c60728ee05020d9dc019e8a Mon Sep 17 00:00:00 2001 From: sankeer28 Date: Thu, 23 Jul 2026 17:22:12 -0400 Subject: [PATCH 3/9] chore(404Anime): bump version to 1.0.2 --- websites/0-9/404Anime/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/websites/0-9/404Anime/metadata.json b/websites/0-9/404Anime/metadata.json index 2a53e5282ebe..22e48ddd3e31 100644 --- a/websites/0-9/404Anime/metadata.json +++ b/websites/0-9/404Anime/metadata.json @@ -11,7 +11,7 @@ }, "url": "404anime.ca", "regExp": "^https?[:][/][/]404anime[.]ca[/]", - "version": "1.0.1", + "version": "1.0.2", "logo": "https://cdn.rcd.gg/PreMiD/websites/0-9/404Anime/assets/logo.png", "thumbnail": "https://cdn.rcd.gg/PreMiD/websites/0-9/404Anime/assets/thumbnail.png", "color": "#facc15", From 09981fca647bf1f93db5da2463c37a0d551626bb Mon Sep 17 00:00:00 2001 From: sankeer28 Date: Fri, 24 Jul 2026 17:10:41 -0400 Subject: [PATCH 4/9] fix(404Anime): skip redundant setActivity calls when presence data is unchanged --- websites/0-9/404Anime/presence.ts | 33 ++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/websites/0-9/404Anime/presence.ts b/websites/0-9/404Anime/presence.ts index 3be792c4b8d5..a80013c6beb1 100644 --- a/websites/0-9/404Anime/presence.ts +++ b/websites/0-9/404Anime/presence.ts @@ -163,6 +163,34 @@ function getBrowsingState(pathname: string): string { return 'Exploring 404Anime' } +// Skip resending identical presence data every tick — UpdateData fires +// repeatedly even when nothing changed (e.g. idling on a browse page, or a +// video ticking forward by sub-second amounts each poll). Discord's client +// ticks elapsed/remaining time forward on its own once a timestamp is set, so +// re-sending on every call isn't needed; only a real change (episode, pause +// state, page, or the viewer seeking more than a few seconds) should push an +// update to the desktop app. +let lastSent: PresenceData | null = null +const TIMESTAMP_TOLERANCE_SEC = 3 + +function activityChanged(next: PresenceData, prev: PresenceData | null): boolean { + if (!prev) + return true + + const fields: (keyof PresenceData)[] = ['details', 'state', 'largeImageKey', 'largeImageText', 'smallImageKey', 'smallImageText'] + if (fields.some(key => next[key] !== prev[key])) + return true + + if ((next.startTimestamp == null) !== (prev.startTimestamp == null)) + return true + if ((next.endTimestamp == null) !== (prev.endTimestamp == null)) + return true + + const startDiff = Math.abs(Number(next.startTimestamp ?? 0) - Number(prev.startTimestamp ?? 0)) + const endDiff = Math.abs(Number(next.endTimestamp ?? 0) - Number(prev.endTimestamp ?? 0)) + return startDiff > TIMESTAMP_TOLERANCE_SEC || endDiff > TIMESTAMP_TOLERANCE_SEC +} + presence.on('UpdateData', () => { const bridge = getBridgeData() const fallback = getVideoFallback() @@ -220,5 +248,8 @@ presence.on('UpdateData', () => { presenceData.smallImageText = 'Browsing' } - presence.setActivity(presenceData) + if (activityChanged(presenceData, lastSent)) { + lastSent = presenceData + presence.setActivity(presenceData) + } }) From 135e606aa9b3c665871ef80c58e465830f3fdc56 Mon Sep 17 00:00:00 2001 From: sankeer28 Date: Fri, 24 Jul 2026 17:16:20 -0400 Subject: [PATCH 5/9] fix(404Anime): trim caching diff to essential fields --- websites/0-9/404Anime/presence.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/websites/0-9/404Anime/presence.ts b/websites/0-9/404Anime/presence.ts index a80013c6beb1..41146e568d1b 100644 --- a/websites/0-9/404Anime/presence.ts +++ b/websites/0-9/404Anime/presence.ts @@ -163,13 +163,6 @@ function getBrowsingState(pathname: string): string { return 'Exploring 404Anime' } -// Skip resending identical presence data every tick — UpdateData fires -// repeatedly even when nothing changed (e.g. idling on a browse page, or a -// video ticking forward by sub-second amounts each poll). Discord's client -// ticks elapsed/remaining time forward on its own once a timestamp is set, so -// re-sending on every call isn't needed; only a real change (episode, pause -// state, page, or the viewer seeking more than a few seconds) should push an -// update to the desktop app. let lastSent: PresenceData | null = null const TIMESTAMP_TOLERANCE_SEC = 3 From 08437ef4a8d12f8c68a59a6c40881707e593eadf Mon Sep 17 00:00:00 2001 From: sankeer28 Date: Mon, 27 Jul 2026 17:22:26 -0400 Subject: [PATCH 6/9] fix(404Anime): read data with execInPage again, cached behind change signals Restores the execInPage read and caches it. The cache is refreshed only on a URL change or when the page rewrites its `` signature, which covers what is playing but not the playback position. Position is read off the