feat(embeds): fallback for hung embeds and deleted X posts (#200)
CI / Build & Quality Checks (push) Successful in 3m24s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 10s
CI / Trigger Desktop Build (push) Successful in 9s
CI / Playwright smoke (e2e) (push) Successful in 9m57s

Probed how real providers fail before picking signals:
- X renders an EMPTY frame for a deleted/private/suspended post and says
  so only via postMessage `twttr.private.no_results`. The post embed now
  swaps to "This post isn't available…" with an "Open on X" link.
- A hung frame never fires `load`. After 20 s every player (media, rich
  posts, TikTok, Steam widget, X) overlays "This embed is taking too long
  to load" with Retry (remounts the iframe) and "Open on <site>". A late
  `load` clears it.
- Instagram and Bluesky show their own "removed / not found" page, and a
  refused request still fires `load` (browser error page), so neither needs
  or can use a guess. A missing height message is not treated as failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
Lotus CI
2026-09-26 11:54:16 -04:00
co-authored by Claude Opus 5.5
parent 1b5e6a37f5
commit ba5b1ffe7d
4 changed files with 277 additions and 52 deletions
+26
View File
@@ -32,6 +32,7 @@ import {
buildVideoEmbedUrl,
spotifyEmbedHeight,
parseMediaEmbed,
isTwitterNoResults,
} from './videoEmbed';
const HOST = 'chat.lotusguild.org';
@@ -403,3 +404,28 @@ test('parseMediaEmbed: Twitch embed carries the parent host', () => {
const clip = parseMediaEmbed('https://clips.twitch.tv/Slug', HOST);
assert.ok(clip?.embedUrl.startsWith('https://clips.twitch.tv/embed?clip=Slug'));
});
test('isTwitterNoResults: detects X reporting a missing post (string or parsed)', () => {
// Captured from platform.twitter.com/embed/Tweet.html?id=1 (no such post).
const msg = {
'twttr.embed': {
jsonrpc: '2.0',
method: 'twttr.private.no_results',
id: 'embed-0',
params: [{ data: { tweet_id: '1' } }],
},
};
assert.equal(isTwitterNoResults(msg), true);
assert.equal(isTwitterNoResults(JSON.stringify(msg)), true);
assert.equal(isTwitterNoResults({ 'twttr.embed': [msg['twttr.embed']] }), true);
});
test('isTwitterNoResults: ignores normal embed traffic and junk', () => {
const call = (method: string) => ({ 'twttr.embed': { jsonrpc: '2.0', method, params: [] } });
assert.equal(isTwitterNoResults(call('twttr.private.initialized')), false);
assert.equal(isTwitterNoResults(call('twttr.private.resize')), false);
assert.equal(isTwitterNoResults(call('twttr.private.rendered')), false);
assert.equal(isTwitterNoResults('not json'), false);
assert.equal(isTwitterNoResults(null), false);
assert.equal(isTwitterNoResults({ type: 'MEASURE' }), false);
});