🕵️ SicherheitslückenCVE-2026-58728 | Google Android ARM64 MMU mmu.h ARM64_TLBI race condition(15.09.2026 um 21:40 Uhr)
🔧 ProgrammierungEnforce GitHub Advanced Security configurations(15.09.2026 um 21:31 Uhr)
🔧 ProgrammierungHow i give my coding agent a map of the repo with Empryo(15.09.2026 um 21:54 Uhr)
🔧 ProgrammierungReef Connects Agent Feedback, Learning and Versioned Delivery(15.09.2026 um 21:55 Uhr)
🔧 ProgrammierungUAJY Handbook RAG Chatbot Splits FAISS Search From Gemini(15.09.2026 um 21:55 Uhr)
🔧 ProgrammierungKnowledge Base For AI Agents: What It Must Do(15.09.2026 um 22:00 Uhr)
🕵️ SicherheitslückenCVE-2026-58728 | Google Android ARM64 MMU mmu.h ARM64_TLBI race condition(15.09.2026 um 21:40 Uhr)
🔧 ProgrammierungEnforce GitHub Advanced Security configurations(15.09.2026 um 21:31 Uhr)
🔧 ProgrammierungHow i give my coding agent a map of the repo with Empryo(15.09.2026 um 21:54 Uhr)
🔧 ProgrammierungReef Connects Agent Feedback, Learning and Versioned Delivery(15.09.2026 um 21:55 Uhr)
🔧 ProgrammierungUAJY Handbook RAG Chatbot Splits FAISS Search From Gemini(15.09.2026 um 21:55 Uhr)
🔧 ProgrammierungKnowledge Base For AI Agents: What It Must Do(15.09.2026 um 22:00 Uhr)

🔧 Programmierung 🕛 vor 1 Monat 6 Min Lesezeit
0

Saving a tweet as a PDF is harder than it looks (CORS, tokens, and Unicode)

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

I wanted a simple thing: paste a Twitter/X post link, get a clean PDF of it. Text, author, images, the whole card. No screenshots stitched together.



It sounded like a one-afternoon project. It was not. Here are the four walls I hit, and how I got past each one. If you ever try to read tweet data from the browser, this will save you a day.






Wall 1: you cannot read a tweet from the browser



The obvious first attempt: fetch the tweet from the syndication endpoint that X's own embed widgets use.




CODE
const id = "1719049883789729890";
const url = `https://cdn.syndication.twimg.com/tweet-result?id=${id}&lang=en&token=${token}`;
const res = await fetch(url); // TypeError: Failed to fetch






Dead on arrival. The response carries this header:




CODE
Access-Control-Allow-Origin: https://platform.twitter.com






So the endpoint only allows requests from X's own embed origin. No other website can call it from the browser, CORS blocks it. The old conversation endpoints (/timeline/conversation/<id>.json) now return 200 with an empty body, so those are gone too. And the official API needs a paid key.



There is no pure client-side path. If you want tweet data on your own site, something on your server has to fetch it.






The fix: a tiny server-side proxy



CORS is a browser rule. Server to server, it does not apply. So the browser asks my server, and my server asks X. My stack is WordPress, so this is a small REST endpoint (any backend works the same way).




CODE
register_rest_route('myplugin/v1', '/tweet', [
'methods' => 'GET',
'permission_callback' => '__return_true',
'callback' => function ($req) {
$id = preg_replace('/[^0-9]/', '', $req->get_param('id'));
$token = preg_replace('/[^A-Za-z0-9]/', '', $req->get_param('token'));
$url = "https://cdn.syndication.twimg.com/tweet-result?id=$id&lang=en&token=$token";

$r = wp_remote_get($url, [
'timeout' => 15,
'headers' => ['User-Agent' => 'Mozilla/5.0 ... Chrome/124 Safari/537.36'],
]);
// ...normalize and return JSON
},
]);






One caveat worth knowing: send a real browser User-Agent. Without it the endpoint gets moody.






Wall 2: the mysterious token



That endpoint needs a token query param. It is not an API key. It is a value derived from the tweet ID, and X's own embed code computes it client-side. The formula floating around the web (used by Vercel's react-tweet) is:




CODE
function token(id) {
return ((Number(id) / 1e15) * Math.PI)
.toString(36)
.replace(/(0+|\.)/g, "");
}






I compute this in the browser (it is trivial and exact there) and pass it to my endpoint, so the PHP side never has to reimplement JavaScript's float-to-base36. Clean division of labor.



Now the browser flow is:




CODE
const id = parseIdFromUrl(url);
const r = await fetch(`/wp-json/myplugin/v1/tweet?id=${id}&token=${token(id)}`);
const post = await r.json(); // { name, handle, avatar, text, images, likes, replies, quoted }









Wall 3: images taint the canvas



To put images into a PDF, most tools rasterize through a <canvas>. But drawing a cross-origin image onto a canvas taints it, and toDataURL() throws a security error. Twitter's pbs.twimg.com sometimes cooperates with crossOrigin, sometimes not.



Simplest reliable fix: let the server fetch the image bytes too and hand them back as base64 data URLs. Data URLs are same-origin by definition, so no taint, ever.




CODE
$bin = wp_remote_retrieve_body(wp_remote_get($imgUrl, [...]));
$dataUrl = 'data:' . $mime . ';base64,' . base64_encode($bin);






Yes, base64 is about 33% bigger than the binary. For a handful of tweet images, who cares. It just works, on every browser, every time.






Wall 4: jsPDF breaks on Hindi and emoji



This one bit hardest. I drew the whole tweet card with snapshots the preview element, and because the images are already base64 data URLs, there is no CORS drama.




CODE
const canvas = await html2canvas(cardEl, { backgroundColor: "#fff", scale: 2, useCORS: true });
canvas.toBlob(blob => download(blob, "post.png"), "image/png");









Lessons




  1. If a third party locks CORS to their own origin, no client-side trick beats it. Proxy through your server or stop.

  2. Compute fiddly values (like that base36 token) on whichever side has the right primitives, then pass them along. Do not port float math across languages for no reason.

  3. Base64 data URLs are the boring, reliable answer to canvas tainting.

  4. jsPDF is Latin-only out of the box. For real i18n, render text on a canvas and embed it as an image.



The finished thing does what I wanted: paste a link, get a clean PDF or image, threads and quote tweets included, every language intact, all in the browser with a thin server helper.



If you want to see it in action: Twitter/X to PDF.



Have you fought the syndication endpoint or jsPDF's font limits before? I would love to hear how you handled it.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
2 Quellen
IT Security News Hourly Summary 2026-09-15 21h : 4 posts
1 Quelle
Bypassing inference bottlenecks: Accelerating complex AI search with Retrieve-for-Train
1 Quelle
What’s next for CISA’s CDM program that gives cybersecurity tools to federal agencies