

1/1/1970
Paano Ko Ginawang Gumana ang Foony sa Likod ng mga Proxy
Kumusta! Matagal ko nang alam na ang mga web proxy ay nagdudulot ng compatibility issues sa mga website. Pero ang suporta ng Foony sa mga proxy ay kilala sa kasamaan, at medyo nakakalito ang paglutas ng proxy compatibility ng Foony.
Hindi rin ito isang "Foony gumagamit ng exotic APIs" na isyu (kahit na oo nga). Ito ay kombinasyon ng:
- Mga proxy na agresibong nagre-rewrite ng strings sa mga lugar kung saan talagang hindi nila dapat.
- Mga proxy na iba ang trato sa pangunahing site domain kumpara sa "ibang" domains (CDNs, asset hosts, atbp.).
- At ang malupit na realidad na may mga proxy na talagang hindi makakapag-suporta sa mga modernong web apps (HTTPS correctness, WebSockets, atbp.).
Hindi kami gumagana sa lahat ng proxy, pero ngayon ay gumagana na kami sa croxyproxy at proxyorb, na siyang layunin.
Sa baba ay ipinapaliwanag ko kung ano ang nasira, bakit ito nasira, at ang mga ayos na talagang importante.
Pass 1: Mga valid pero sirang Three.js shaders
Ang sintomas
Nang sinubukan ko ang croxyproxy, hindi ako makapaglaro ng 8 Ball Pool o anumang iba pang three.js laro ng Foony. Tuloy-tuloy ang shader compilation failure sa Three.js na may mga error tulad ng:
- "Shader Error 1282 - VALIDATE_STATUS false"
Halos walang silbi ang mensaheng iyon. Karaniwan, ibig sabihin nito ay "invalid ang shader mo, good luck." Galing. Kung iniisip mo kung bakit lagi akong gumagamit ng unique error messages para sa bawat error sa Foony, ito ang dahilan. Tumutulong ito para malaman kung saan ang problema sa halip na "sira ang code, ayusin mo."
Pero bakit nasisira ang mga perpektong valid na three.js shaders? Anong nangyayari?
Ang totoong dahilan: mga proxy na nagsisira sa layout(location = N)
Nag-eemit ang Three.js ng GLSL na may layout qualifiers tulad ng:
layout(location = 0) in vec3 position;
Sinusubukan ng ilang proxy na i-rewrite ang anumang mukhang JavaScript location API sa pamamagitan ng naive global string replacement. Masama na yun sa JS, pero ginagawa rin nila ito sa loob ng shader source strings. Siguro masyadong mahal sa kanila ang AST parsing.
Kaya ang shader source ay nasira at naging ganito:
layout(__cpLocation = 0) in vec3 position;
Kailangang location ang identifier doon. Anumang iba ay invalid GLSL, at tinatanggihan ito ng compiler. (Layout Qualifiers in GLSL)
Problema lang ito ng Three.js sa diwa na dynamically nag-generate ng shaders ang Three.js, at pinapasa namin ito sa WebGL sa runtime. Ang totoong bug ay ang rewriting strategy ng proxy.
Bakit hindi ko "inayos ang proxy"
Ang naive na approach ay hanapin ang location replacement string ng croxyproxy, __cpLocation, at palitan ito ng location. Pero, gumagamit ang ibang proxy ng iba't ibang replacement names. Iba ang gamit ng iba, gaya ng __cpLocation, ang iba ay gumagamit ng ibang weird identifiers. Kaya ang hardcoding ng fix gaya ng "palitan ang __cpLocation pabalik sa location" ay marupok.
Kailangan ko:
- Generic na fix (walang hardcoding ng proxy identifiers).
- Fix na gumagana kahit nire-rewrite ng proxy ang salitang
locationsa JavaScript ko.
Ang base64 trick: itago ang salitang location sa proxy
Kung nire-rewrite ng proxy ang bawat literal na location na nakikita nito, ang pinakasimpleng paraan ay huwag na lang gumamit ng location. Madali lang. Nakita ko na ang mga ganitong trick noon sa Lua nang i-reverse-engineer ko ang guide protection system ng RestedXP (kung tama ang naaalala ko, ino-obfuscate nila ang paggamit nila ng BNGetInfo, e.g. _G("\x42\x4E\x47\x65\x74\x49\x6E\x66\x6F")).
Gumagana rin ang trick na ito sa JavaScript, syempre. Sa client/index.html, dini-decode ko ang sumusunod sa runtime:
// Because these proxies try to replace every `location`, we use a base64 encoded string.
const suffix = 'pb24=';
const locStr = atob('bG9jYXR' + suffix); // "location"
const loc = window[locStr]; // window.location
Ang atob() na yun ay nangyayari pagkatapos nang mag-rewrite na ang proxy ng HTML/JS, kaya hindi nito "ma-pre-corrupt" ang string. Hinati ko ang string sa dalawa para mas mahirap matukoy, at ginagamit ko ang 'atob' dahil pwede, pero ang String.fromCharCode o hex-escaping window['\x6c\x6f\x63\x61\x74\x69\x6f\x6e'] ay maaaring gumana rin.
Ang sirang shader pattern ay laging structurally pareho:
layout(<something> = <number>)
Kaya ito ay ipinapares ko nang generically at pinapalitan ang <something> ng tamang identifier:
source.replace(/layout\s*\(\s*[^=)]+\s*=\s*(\d+)\s*\)/g, 'layout(' + locStr + ' = $1)');
Ang WebGL hook: i-patch ang shaderSource (WebGL1 + WebGL2)
Dahil tinatawag ng Three.js ang gl.shaderSource(shader, source), pini-patch ko ang shaderSource mismo:
const originalShaderSource = WebGLRenderingContext.prototype.shaderSource;
Object.defineProperty(WebGLRenderingContext.prototype, 'shaderSource', {
value: function (shader, source) {
return originalShaderSource.call(this, shader, fixCorruptedShaderSource(source));
},
writable: true,
configurable: true,
});
At ginagamit ko ang parehong patch sa WebGL2RenderingContext kung mayroon nito.
Nang nailagay na yun, nawala ang shader compilation errors. Sa puntong ito, gumagana na ang croxyproxy pero hindi pa rin ang proxyorb. Bakit?! Hindi ba dapat pareho ang gana niyan?
Pass 2: Ang pangalawang problema (mga domain) at kung bakit ang pag-alis ng foony.io ay nagpadali sa lahat
Historically, dalawang domain ang ginagamit ng Foony, kahit sa nakalipas na buwan:
foony.compara sa pangunahing sitefoony.iopara sa static assets
Ang orihinal na dahilan ay practical: ang pag-serve ng assets mula sa cookie-less domain ay umiiwas sa cookie header upload bloat sa bawat static file request. Maganda ito, pero hindi kasing kailangan gaya ng iniisip mo dahil ang HTTP/2 ay gumagamit ng HPACK para bawasan ang bytes na ipinapadala sa wire para sa headers.
Valid optimization ito sa normal browsing.
Sa likod ng mga proxy, naging malaking sanhi ito ng pagkasira. At gusto ng userbase ng Foony ang mga proxy. buntong-hininga
Iba ang trato ng mga proxy sa "pangunahing site" kumpara sa "ibang sites"
Maraming proxy ang naka-optimize para sa "i-proxy ang isang page / domain na ito." Matagumpay nilang mai-load ang main HTML, mag-inject ng scripts, mag-register ng sariling ServiceWorker, atbp.
Pero kapag nagsimula ang app na kunin ang assets mula sa ibang origin (gaya ng foony.io), papasok ka sa lahat ng masayang pagkasira:
- ServiceWorker interception failures tulad ng:
- "ServiceWorker intercepted the request and encountered an unexpected error"
- "Loading failed for the module with source"
- Query params na kinakailangan ng proxy infrastructure (at madaling aksidenteng maalis).
- Mga asset request na nawawalan ng internal routing metadata ng proxy.
- Mga kakaibang proxy na pinapalitan ang buong request ng
generic-php-slug.php?someQueryParam=hugeEncodedString(oo, hindi ko na inabala na suportahan yun).
Ang internal mechanisms ng mga proxy na ito ay nakadepende sa query params / URL encoding, at marupok talaga sila.
Isa sa mga halimbawa ay isang asset URL tulad ng:
https://<proxy-ip>/assets/firebase-<hash>.js?__pot=aHR0cHM6Ly9mb29ueS5jb20
Ang ?__pot=... na yun ay sariling routing/state ng proxy na nagsasabi sa proxy kung aling domain ang request. Kung tatanggalin mo ito, hindi ma-resolve ng mga proxy ang request nang tama, at mauuwi ka sa ServiceWorker error path.
"Resource swapping" sa rescue (at kung bakit naging komplikado nang mabilis)
Sa isang punto, sinubukan ko ang isang workaround: i-detect ang "naka-proxy kami," tapos i-swap ang anumang foony.io resource URLs sa kasalukuyang origin para makita ng proxy ang lahat bilang same-origin.
Mukhang makatuwiran iyon, at gumana ito sa croxyproxy, pero nagdagdag ito ng maraming complexity:
- Kailangan mong palitan ang mga
linkatscripttag na nasa HTML na. - Kailangan mo ng
MutationObserverpara hawakan ang mga dynamically-injected na tag (modulepreload, stylesheet, atbp.). - Kailangan mong pangalagaan ang query parameters ng proxy, o sisirain mo ang routing nila. At iba't ibang proxy ay iba ang ginagawa nito. Dahil siyempre.
- At kailangan mo pa ring panatilihin ang logic na generic (walang proxy-specific globals) para hindi maging bloated dumpster-fire ang code.
Dito rin lumitaw ulit ang "base64 trick": kahit sa sarili kong JavaScript, kailangan kong mag-ingat sa literal na string na location dahil maaaring i-rewrite ito ng proxy.
Pag-reverse-engineer sa injected script ng CroxyProxy
Sa puntong ito, naging curious ako: ano ba talaga ang ginagawa ng proxy sa page ko? Nag-i-inject ba sila ng sariling ads? May mas masama?
Ang client-side script ng CroxyProxy ay heavily obfuscated.
(new Function(new TextDecoder('utf-8').decode(new Uint8Array((atob('NjY3NTZlN...')).match(/.{1,2}/g).map(b => parseInt(b, 16))))))();
Na kapag pinatakbo, nagiging:
function a0_0x5ebf(_0x213dc9,_0x1c49b6){var _0x4aa7c1=a0_0x4274();return a0_0x5ebf=function(_0x159600,_0x51d898){_0x159600=...
Batay dito, mukhang gumagamit ang croxyproxy ng Obfuscator.io para sa obfuscation na ito. Salamat at madali itong i-deobfuscate gamit ang webcrack.
Ito ay nagreresulta sa mas readable na JavaScript:
((_0x15ca2c, _0x489eaa) => {
if (typeof module == "object" && module.exports) {
module.exports = _0x489eaa(require("./punycode"), require("./IPv6"), require("./SecondLevelDomains"));
} else if (typeof define == "function" && define.amd) {
define(["./punycode", "./IPv6", "./SecondLevelDomains"], _0x489eaa);
} else {
_0x15ca2c.URI = _0x489eaa(_0x15ca2c.punycode, _0x15ca2c.IPv6, _0x15ca2c.SecondLevelDomains, _0x15ca2c);
}
})(this, function (_0x724467, _0x275183, _0x219d84, _0x2dcc2c) {
var _0x114c1e = _0x2dcc2c && _0x2dcc2c.URI;
function _0x5a9187(_0x2fd4ea, _0x3bd460) {
var _0x23a83f = arguments.length >= 1;
if (!(this instanceof _0x5a9187)) {
Maganda. Ngayon makikita natin kung ano ang ginagawa nito. At... mukhang okay naman. Sa palagay ko ang obfuscation ay para tulungan ang prevention ng detection ng proxy. Halos.
Mayroon tayong ad / UI injection:
Bi(_0x308e2f) {
console.log("Ads: " + _0x331b11.showAds);
console.log("Ad codes: " + !!_0x331b11.adsJson);
console.log("Adblock: " + _0x308e2f);
_0x34fa18.document.body.insertAdjacentHTML("afterbegin", _0x331b11.modal);
if (_0x331b11.header) {
if (_0x331b11.header) {
this.Ri(_0x308e2f);
}
[...document.querySelectorAll("#__cpsHeader a")].forEach(_0xcce595 => {
_0xcce595.addEventListener("click", function (_0x3ef5f1) {
if (this.target === "_blank") {
_0x34fa18.open(this.href, "_blank").focus();
} else {
_0x34fa18.location.href = this.href;
}
_0x3ef5f1.stopImmediatePropagation();
_0x3ef5f1.preventDefault();
}, true);
});
}
return this;
}
May iba pang mga lugar, pero ang totoo lang nagpapakita lang ito ng ads, kasama ang pop-under style ads. Gumagamit din ito ng FuckAdBlock.
Ang totoong replacement sa strings, gayunpaman, ay nangyayari sa server-side. At sino ang nakakaalam kung anong lahat ang ginagawa niyan.
Sa anumang paraan, talagang hindi ka dapat gumagamit ng web proxies kung pinapahalagahan mo ang seguridad ng iyong account. Kung kailangan mo, iwasan ang paglalagay ng anumang PII / account / impormasyon sa pagbili.
"Resource swapping" sa basurahan
Napagdesisyunan ko na ang complexity mula sa resource swapping, kasama ang complexity sa ibang bahagi ng code para sa suporta sa foony.io, ay hindi sulit sa maliit na network savings ng magagandang, cookieless requests. Nakakakita rin kami ng hindi maipaliwanag na pagbaba sa aming gameplay conversions mula nang magkaroon kami ng foony.io, kaya pinaghihinalaan kong may iba pang isyu sa foony.io na hindi namin alam.
Kaya inalis ko ang foony.io. Sa ngayon, kahit man lang.
Nang tinanggal ko ang foony.io CDN logic at nag-standardize ng lahat sa foony.com, naging dramatikong simple ang proxy support:
- Same-origin asset loads.
- Mas kaunting "special cases" na ipapaliwanag sa proxy ServiceWorker.
- Mas kaunting rewriting.
- Hindi gaanong marupok na code.
Sa madaling salita, ang pag-alis ng foony.io ay isang architectural simplification na nagbawas ng surface area para sa kakaibang proxy behavior.
Pass 3: Ano ang gumagana, ano ang hindi, at bakit
Mga kumpirmadong gumaganang proxy
Sa puntong ito, gumagana ang Foony sa likod ng:
- croxyproxy
- proxyorb
Ilang ibang proxy ay malamang gumagana. Pusta ko, karamihan ay hindi pa rin. Pero kahit ang mga importanteng ginagamit ng mga tao para maglaro ng games ay mukhang gumagana.
Bakit hindi "lahat ng proxy"?
May mga proxy na talagang hindi makakapag-suporta ng modernong multiplayer web app. Mga halimbawa:
- Mga proxy na hindi maayos ang suporta sa HTTPS.
- Mga proxy na sumisira o nag-blo-block ng WebSockets (gumagamit ang Foony ng real-time networking). Technically puwede mong i-workaround ito, pero magdadagdag ito ng complexity.
- Mga proxy na may masyadong maraming restrictions sa cross-origin requests, headers, o ServiceWorkers.
Mga pangunahing aral
Ang web proxies ay napaka insecure
Sila ay middleware na:
- nagre-rewrite ng HTML
- nagre-rewrite ng JavaScript
- minsan nag-i-inject ng ServiceWorker
- at madalas nakadepende sa query params / URL encoding para sa pag-route ng requests
- maaaring maglaro sa mga page mo sa iba't ibang paraan
Nagulat ako kung gaano kalalim ang pumapasok ng ilang proxy: nagre-rewrite sila ng shader source strings, comments, at Diyos lang ang nakakaalam kung ano pa.
Minsan ang pinakamahusay na ayos ay architectural
Nagawang ma-render ulit ng WebGL patch ang games, pero ang pag-alis ng multi-domain CDN strategy ang nagpa-manatiling stable ng proxy support.
Magandang paalala ito: matalinong optimizations ay maaaring perpektong makatuwiran hanggang sa magkasalubong sila sa hostile middleware. O sa browser extensions ng user. O sa Safari. O sa language settings. O sa accessibility features. O sa solar flares. O sa kahit ano, talaga.
Konklusyon
Gumagana na ang Foony sa likod ng mga importanteng proxy (croxyproxy at proxyorb), nang hindi ginagawa ang codebase na proxy-specific na gulo:
- Generic na Three.js shader fix (walang proxy-specific identifiers).
- Mas simpleng domain strategy (foony.com saanman).