background blurbackground mobile blur

1/1/1970

কীভাবে আমি Foony কে প্রক্সির আড়ালেও কাজ করালাম

হ্যালো! অনেক দিন ধরেই জানি যে web proxy গুলো প্রায়ই সাইটের সাথে compatibility নিয়ে ঝামেলা করে। কিন্তু প্রক্সির ভেতর Foony-র সাপোর্ট তো বরাবরই কুখ্যাতভাবে খারাপ ছিল, আর Foony-র proxy compatibility ঠিক করা বেশ ঝামেলার কাজ হয়ে দাঁড়িয়েছিল।

এটা কিন্তু সেই ধরনের সমস্যা না যে “Foony নাকি অদ্ভুত সব API ব্যবহার করে” (যদিও আমরা সত্যিই করি)। আসল ঝামেলাটা কয়েকটা জিনিস মিলিয়ে তৈরি হচ্ছিল:

  • প্রক্সি এমন সব জায়গায় string জোর করে বদলে দিচ্ছিল, যেখানে ওদের একদমই হাত দেওয়া উচিত না।
  • প্রক্সি মূল সাইটের ডোমেইনকে আবার “অন্যান্য” ডোমেইন (CDN, asset host ইত্যাদি) থেকে আলাদা ভাবে ব্যবহার করছিল।
  • আর কঠিন সত্যিটা হল, কিছু প্রক্সি আধুনিক web app ঠিকমতোই সাপোর্ট করতে পারে না (HTTPS ঠিকঠাক রাখা, WebSocket ইত্যাদি)।

প্রত্যেকটা প্রক্সির সাথে এখনো কাজ করি না, কিন্তু অন্তত croxyproxy আর proxyorb এ Foony এখন চলে, আর সেটাই ছিল টার্গেট।

নিচে পুরোটা খুলে বলছি, কোথায় কী ভাঙছিল, কেন ভাঙছিল, আর কোন ফিক্সগুলো আসলেই কাজে দিয়েছে।


ধাপ ১: একদম ঠিক, তবু নষ্ট Three.js shader গুলো

লক্ষণটা কী ছিল

croxyproxy দিয়ে চেষ্টা করলে আমি 8 Ball Pool খেলতেই পারছিলাম না, Foony-র আরও three.js গেম গুলোর কোনোটাই না। বারবার Three.js এ shader compilation failure টাইপের error পাচ্ছিলাম, যেমন:

  • “Shader Error 1282 - VALIDATE_STATUS false”

এই মেসেজটা প্রায় পুরোপুরিই অকাজের। সাধারণত এর মানে শুধু এই, “তোমার shader টা ভাঙা, এখন নিজের ভাগ্য নিজেই সামলাও।” বাহ। কখনো যদি ভাবো কেন Foony-তে প্রায় প্রতিটা error-এর জন্য আলাদা মেসেজ রাখি, তার কারণ এটাই। এতে ঠিক কোথায় সমস্যা হচ্ছে সেটা বোঝা যায়, শুধু এতটুকু না যে “কোড নষ্ট, গিয়ে ঠিক করো।”

কিন্তু একদম ঠিকঠাক Three.js shader গুলোই বা হঠাৎ ভাঙছিল কেন? গোলমালটা কোথায়?

আসল কারণ: প্রক্সি গিয়ে নষ্ট করে দিচ্ছিল layout(location = N)

Three.js যে GLSL কোড বানায়, তাতে এরকম layout qualifier থাকে:

layout(location = 0) in vec3 position;

কিছু প্রক্সি আবার যেকোনো জায়গায় location লেখা দেখলেই ভাবছে এটা নাকি JavaScript-এর location API, আর সোজা গ্লোবাল string replace চালিয়ে দিচ্ছে। JS-এর ভেতরেই এটা খারাপ আইডিয়া, আর মজার ব্যাপার হল ওরা shader-এর source string-এর ভেতরেও একই কাজ করছে। মনে হয় AST parse করা ওদের কাছে একটু বেশিই খরচের ব্যাপার।

ফলাফল, shader-এর সোর্স বদলে গিয়ে এমন কিছুর মতো দেখাচ্ছিল:

layout(__cpLocation = 0) in vec3 position;

ওই জায়গায় identifier টা অবশ্যই location হতে হবে। অন্য কিছু হলেই সেটা invalid GLSL, আর কম্পাইলার সোজা রিজেক্ট করে দেয়। (GLSL এ Layout Qualifier)

এটাকে কেবল একটু Three.js-এর সমস্যা বলা যায় এই কারণে যে Three.js ডায়নামিকভাবে shader বানায়, আর আমরা runtime এ সেই সোর্স WebGL-এ পাঠাই। আসল বাগটা কিন্তু প্রক্সি-র string রিরাইট করার এই আজব স্ট্র্যাটেজিতেই।

কেন আমি “প্রক্সি-টাই ঠিক করে ফেলি” পথটা নিলাম না

সবচেয়ে সোজা আইডিয়া হতে পারত, croxyproxy যে location কে কী দিয়ে বদলাচ্ছে, সেই __cpLocation স্ট্রিংটা খুঁজে নিয়ে আবার location করে দেওয়া। কিন্তু ভিন্ন ভিন্ন প্রক্সি আবার ভিন্ন নাম ব্যবহার করে। কেউ __cpLocation, কেউ একেবারে অন্য কোনও আজব আইডেন্টিফায়ার। তাই “__cpLocation কে সব জায়গায় আবার location করে দাও” টাইপ হার্ডকোডেড ফিক্স খুবই ভঙ্গুর।

আমার দরকার ছিল:

  • একেবারে জেনেরিক ফিক্স (কোনও প্রক্সি-স্পেসিফিক আইডেন্টিফায়ার হার্ডকোড না করে)।
  • এমন একটা ফিক্স, যেটা চালু থাকবে এমনকি যদি প্রক্সি আমার JavaScript-এর ভেতরের location শব্দটা পর্যন্ত পাল্টে দেয়।

base64 ট্রিক: location শব্দটা প্রক্সির চোখের আড়ালে রাখা

যদি প্রক্সি যেখানেই সরাসরি location লেখা পায় সেখানেই সেটা পাল্টে দেয়, তাহলে সবচেয়ে সহজ উপায় হল সরাসরি location লিখবই না। কাজ সোজা। এমন ট্রিক আমি আগেও দেখেছি, Lua তে, যখন RestedXP-এর গাইড প্রোটেকশন সিস্টেম রিভার্স ইঞ্জিনিয়ার করছিলাম (মনে থাকলে, ওরা BNGetInfo কল করা অবস্কিউর করতে _G("\x42\x4E\x47\x65\x74\x49\x6E\x66\x6F") টাইপ কাণ্ড করত)।

এই একই ট্রিক JavaScript-এ একদম ঠিকঠাক কাজ করে। client/index.html এ আমি runtime এ নিচের কোডটা ডিকোড করি:

// এই প্রক্সি গুলো যেহেতু যেখানে সেখানে `location` পাল্টে দেয়, তাই আমরা এখানে base64 এনকোড করা স্ট্রিং ব্যবহার করেছি।
const suffix = 'pb24=';
const locStr = atob('bG9jYXR' + suffix); // এটা "location"
const loc = window[locStr]; // এটা window.location

atob() কলটা প্রক্সি যখন নিজের মত করে HTML/JS রিরাইট করে ফেলে তার পর ঘটে, তাই আগে থেকেই সে এই স্ট্রিংটা নষ্ট করতে পারে না। আমি আবার স্ট্রিংটাকেও দুই টুকরো করেছি, যাতে ডিটেক্ট করা আরও কঠিন হয়। এখানে 'atob' ব্যবহার করেছি কারণ হাতে ছিল, তবে চাইলে String.fromCharCode বা window['\x6c\x6f\x63\x61\x74\x69\x6f\x6e'] টাইপ hex-escape দিয়েও করা যেত।

নষ্ট হওয়া shader-এর প্যাটার্নটা গঠনগত দিক থেকে সবসময় একই রকম হচ্ছিল:

layout(<something> = <number>)

তাই আমি জেনেরিকভাবে ওই প্যাটার্নটা match করে <something> জায়গায় সঠিক আইডেন্টিফায়ার বসিয়ে দিচ্ছি:

source.replace(/layout\s*\(\s*[^=)]+\s*=\s*(\d+)\s*\)/g, 'layout(' + locStr + ' = $1)');

WebGL হুক: shaderSource প্যাচ করা (WebGL1 + WebGL2)

Three.js যেহেতু ভেতরে gl.shaderSource(shader, source) কল করে, আমি সরাসরি shaderSource মেথডটাকেই প্যাচ করেছি:

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,
});

আর একই রকম প্যাচ আমি WebGL2RenderingContext এর জন্যও লাগিয়ে দিচ্ছি, যদি সেটা ব্রাউজারে থাকে।

এই প্যাচ বসানোর পর shader compilation error গুলো উধাও। এই পর্যন্ত এসে croxyproxy তে সব ঠিকঠাক চলছিল, কিন্তু proxyorb তখনো ভাঙা। কেন?! একই রকম কাজ তো হওয়ার কথা ছিল, তাই না?


ধাপ ২: ডোমেইন নিয়ে দ্বিতীয় সমস্যা, আর কেন foony.io সরিয়ে ফেলাই সবকিছু সহজ করে দিল

অনেক দিন ধরে, অন্তত গত এক মাস ধরে, Foony দুটো ডোমেইন ব্যবহার করছিল:

  • মূল সাইটের জন্য foony.com
  • static asset গুলোর জন্য foony.io

এর পেছনের আসল কারণটা ছিল একেবারে ব্যবহারিক। আলাদা, cookie-less ডোমেইন থেকে asset সার্ভ করলে, প্রতিটা static ফাইলের রিকোয়েস্টে মোটা মোটা cookie হেডার বার বার আপলোড করতে হয় না। দারুণ একটা অপ্টিমাইজেশন, কিন্তু HTTP/2 যেহেতু হেডার কমপ্রেস করতে HPACK ব্যবহার করে, তাই এটা তোমার ভাবার মতো এতটা দরকারও না।

সাধারণ ব্রাউজিং-এর ক্ষেত্রে এটা একদম বৈধ আর ঠিকঠাক অপ্টিমাইজেশন।

কিন্তু প্রক্সির আড়ালে গেলে, এটাই আবার ভাঙাচোরা হওয়ার বড় কারণ হয়ে দাঁড়াল। আর Foony-র ইউজারবেস তো প্রক্সি ছাড়া চলতেই চায় না। দীর্ঘশ্বাস

প্রক্সি “মূল সাইট” আর “অন্য সাইট” কে আলাদাভাবে দেখে

অনেক প্রক্সিই এমনভাবে বানানো, যেন “এই একটা পেজ / ডোমেইনকেই প্রক্সি করব”। মানে, মেইন HTML ঠিকঠাক লোড হবে, ওরা নিজেদের স্ক্রিপ্ট ইনজেক্ট করবে, নিজের একটি ServiceWorker রেজিস্টার করবে ইত্যাদি।

কিন্তু অ্যাপ যখন আবার একেবারে অন্য origin থেকে asset টানতে শুরু করে (ধরো foony.io), তখনই শুরু হয় মজার সব ব্রেকডান্স:

  • ServiceWorker interception টাইপ ফেইলিওর, যেমন:
    • “ServiceWorker intercepted the request and encountered an unexpected error”
    • “Loading failed for the module with source”
  • proxy ইন্টারনাল ইনফ্রাস্ট্রাকচারের জন্য কিছু query parameter বাধ্যতামূলক হওয়া (আর অসাবধানতায় সেগুলো খুব সহজেই কেটে ফেলা)।
  • asset রিকোয়েস্টে প্রক্সি-র নিজস্ব routing metadata হারিয়ে ফেলা।
  • কিছু অদ্ভুত প্রক্সি আবার পুরো রিকোয়েস্টটাই বদলে দিয়ে generic-php-slug.php?someQueryParam=hugeEncodedString এর মত করে ফেলে (হ্যাঁ, ওগুলোর জন্য আলাদা করে সাপোর্ট দেওয়ার চেষ্টা পর্যন্ত করিনি)।

এই প্রকার প্রক্সিগুলোর ইন্টারনাল মেকানিজম পুরোপুরি নির্ভর করে query param আর URL encoding-এর ওপর, আর এগুলো ভীষণ নাজুক।

উদাহরণ হিসেবে, একটা asset URL প্রায় এরকম দেখাচ্ছিল:

https://<proxy-ip>/assets/firebase-<hash>.js?__pot=aHR0cHM6Ly9mb29ueS5jb20

?__pot=... অংশটা প্রক্সি-র নিজের routing/state, যা দিয়ে সে বোঝে রিকোয়েস্টটা আসলে কোন ডোমেইনের জন্য। তুমি যদি এটা কেটে দাও, প্রক্সি আর রিকোয়েস্টটা ঠিকঠাক রিসলভ করতে পারে না, আর শেষমেশ ServiceWorker-এর error পাথেই চলে যেতে হয়।

“Resource swapping” নামের বাঁচানোর প্ল্যান (আর কেন এটা ঝটপট জটিল হয়ে গেল)

এক পর্যায়ে আমি একটা ওয়ার্কারাউন্ড ট্রাই করলাম: আগে ডিটেক্ট করি “আমরা প্রক্সির ভেতরে আছি”, তারপর সব foony.io রিসোর্স URL-কে বর্তমান origin-এ বদলে দিই, যেন প্রক্সি-র চোখে সবকিছুই same-origin মনে হয়।

কানে শুনতে বেশ যৌক্তিক, আর croxyproxy তে এটা কাজও করেছিল, কিন্তু বদলে অনেক বাড়তি জটিলতা নিয়ে এল:

  • HTML-এর ভেতরে আগে থেকে থাকা সব link আর script ট্যাগও তখন বদলে দিতে হয়।
  • ডায়নামিকভাবে ইনজেক্ট হওয়া ট্যাগ (modulepreload, stylesheet ইত্যাদি) ধরার জন্য আবার MutationObserver বসাতে হয়।
  • প্রক্সি-র query parameter গুলো অক্ষত রাখতে হয়, না হলে ওদের routing ভেঙে যায়। আর মজার ব্যাপার হল, ভিন্ন ভিন্ন প্রক্সি আবার এই কাজটাই ভিন্ন ভিন্নভাবে করে। অবশ্যই, আর কেমনই বা হবে।
  • আর এই সবের ভেতরেও লজিকটা আবার একেবারে জেনেরিক রাখতে হয় (কোনও proxy-specific global ধরে নিয়ে না), যেন পুরো কোডবেসটা শেষমেশ প্রক্সি-স্পেশাল ডাম্পস্টার-ফায়ারে না পরিণত হয়।

এখানেই আবার “base64 ট্রিক”-টা কাজে লাগল, কারণ নিজের JavaScript-এর ভেতরেও আমাকে সরাসরি location স্ট্রিং ব্যবহার করার সময় সাবধানে থাকতে হচ্ছিল, প্রক্সি যে কোন মুহূর্তে সেটাকে পাল্টে দিতে পারে।

CroxyProxy যে স্ক্রিপ্ট ইনজেক্ট করে, সেটার রিভার্স-ইঞ্জিনিয়ারিং

এই জায়গায় এসে কৌতূহলটা চেপে রাখা গেল না। আসলে প্রক্সি আমার পেজের সাথে কী করছে? শুধু নিজের ad ইনজেক্ট করছে, নাকি আরও ভয়ংকর কিছু?

CroxyProxy-র client-side স্ক্রিপ্টটা ভীষণভাবে অবস্কিউর করা।

(new Function(new TextDecoder('utf-8').decode(new Uint8Array((atob('NjY3NTZlN...')).match(/.{1,2}/g).map(b => parseInt(b, 16))))))();

ওটা চালালে এরকম কিছু বেরিয়ে আসে:

function a0_0x5ebf(_0x213dc9,_0x1c49b6){var _0x4aa7c1=a0_0x4274();return a0_0x5ebf=function(_0x159600,_0x51d898){_0x159600=...

এগুলো দেখে মনে হল croxyproxy ওদের কোড অবস্কিউর করার জন্য Obfuscator.io ব্যবহার করছে। আর ভালো খবর হল, এগুলোকে আবার পড়ার মত করার জন্য webcrack বেশ সহজেই কাজ করে।

ওগুলো দিয়ে পেটানোর পর 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)) {

মিষ্টি। এখন অন্তত দেখা যাচ্ছে কী হচ্ছে। আর... মোটের ওপর বেশ ঠিকঠাকই মনে হচ্ছে। আমার ধারণা, বেশির ভাগ অবস্কিউরেশনই কেবল প্রক্সি-টাকে ধরা পড়া থেকে বাঁচানোর জন্য। মোটামুটি

একটু-আধটু ad / UI ইনজেকশনও আছে, যেমন:

    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;
    }

আরও দু-এক জায়গায় আছে, কিন্তু মোট কথা হল ওরা নিজের ad দেখাচ্ছে, pop-under স্টাইলের ad-সহ। আর ওরা FuckAdBlock ও ব্যবহার করছে।

তবে আসল string replacement গুলো হচ্ছে সার্ভার-সাইডে। আর সেগুলো ভেতরে ভেতরে কী কী করছে, সেটা কে জানে

যাই হোক, তুমি যদি নিজের অ্যাকাউন্ট সিকিউরিটি নিয়ে বিন্দুমাত্র মাথা ঘামাও, তাহলে web proxy একদমই ব্যবহার না করাই ভালো। ব্যবহার করতেই হলে অন্তত কোনো ধরনের PII / অ্যাকাউন্ট / পেমেন্ট তথ্য সেখানে লিখো না।

"Resource swapping" কে শেষে ডাস্টবিনে পাঠানো

শেষমেশ ঠিক করলাম, resource swapping থেকে আসা বাড়তি জটিলতা, আর foony.io সাপোর্ট করতে গিয়ে কোডের অন্য অংশে জড়িয়ে যাওয়া ঝামেলাগুলো, সব মিলিয়ে সামান্য নেটওয়ার্ক সেভিংসটার জন্য একেবারেই মূল্যবান না। তার ওপর foony.io ব্যবহার শুরু করার পর থেকে আমরা গেমপ্লে কনভার্শনে এক ধরনের অদ্ভুত ড্রপও দেখছিলাম, তাই সন্দেহ হচ্ছিল foony.io নিয়ে আরও কিছু গোচর-অগোচর সমস্যা ছিল, যা আমরা জানতামই না।

তাই foony.io সরিয়ে দিলাম। অন্তত আপাতত।

foony.io CDN সংক্রান্ত সব লজিক ফেলে দিয়ে, সবকিছু যখন একদম foony.com এ স্ট্যান্ডার্ড করলাম, তখনই প্রক্সি সাপোর্ট অনেক সহজ হয়ে গেল:

  • asset সবই same-origin থেকে লোড হচ্ছে।
  • প্রক্সি ServiceWorker-কে বোঝানোর মতো “স্পেশাল কেস” অনেক কমে গেল।
  • কম রিরাইটিং।
  • কম ভঙ্গুর, বেশি টেকসই কোড।

সোজা কথায়, foony.io সরিয়ে দেওয়া মানে ছিল আর্কিটেকচারের এক দারুণ সরলীকরণ, যা নানা ধরনের আজব প্রক্সি-ব্যবহারের জন্য ভাঙার জায়গাগুলো কমিয়ে দিল।


ধাপ ৩: কী কী কাজ করে, কী করে না, আর কেন

যেগুলো কাজ করছে, এমন প্রক্সি

এই পর্যন্ত এসে Foony যে প্রক্সিগুলোর আড়ালেও ঠিকঠাক চলছে, সেগুলো হল:

  • croxyproxy
  • proxyorb

আরও কিছু প্রক্সি হয়তো কাজ করছে, ধারণা করা যায়। বেশির ভাগের ক্ষেত্রেই এখনো কাজ করবে না, সেটা নিয়েও বাজি ধরতে পারি। কিন্তু অন্তত যেগুলোর ওপর ভর করে মানুষজন গেম খেলতে চায়, সেগুলো বেশ ভালোই চলছে।

“সব প্রক্সি” কেন না?

কিছু প্রক্সি দিয়ে তো আধুনিক multiplayer web app চালানোই সম্ভব না। উদাহরণ হিসেবে:

  • যেসব প্রক্সি HTTPS ঠিকমতো সাপোর্টই করে না।
  • যেসব প্রক্সি WebSocket ভেঙে দেয় বা পুরোপুরি ব্লক করে (Foony কিন্তু real-time নেটওয়ার্কিং ব্যবহার করে)। টেকনিক্যালি চাইলে এরও কিছু ওয়ার্কারাউন্ড আছে, কিন্তু তাতে জটিলতা গুণিতকভাবে বাড়বে।
  • যেসব প্রক্সি cross-origin রিকোয়েস্ট, header, বা ServiceWorker নিয়ে এত বেশি বিধিনিষেধ দেয় যে অ্যাপটাই কাজ করা বন্ধ করে দেয়।

মূল শেখাগুলো

Web proxy গুলো আসলেই খুব অনিরাপদ

ওগুলো এমন এক ধরনের middleware, যেগুলো:

  • HTML আবার নিজের মত করে লিখে ফেলে
  • JavaScript-ও রিরাইট করে
  • কখনো কখনো নিজের ServiceWorker ইনজেক্ট করে
  • আর প্রায়ই query param / URL encoding-এর ওপর ভর করে রিকোয়েস্ট রাউট করে
  • আর তোমার পেজ নিয়ে যা খুশি তত রকম আলতু-ফালতু কাজ করতে পারে

কিছু প্রক্সি ঠিক কতটা গভীর পর্যন্ত যায় দেখে সত্যি অবাক হয়েছি, তারা shader source string, comment পর্যন্ত রিরাইট করে, আর আর কী কী করছে, সেটা একমাত্র ভগবানই জানেন।

অনেক সময় সেরা ফিক্সটা কোড নয়, আর্কিটেকচারে

WebGL প্যাচ গেমগুলোকে আবার রেন্ডার করতে সাহায্য করেছে, কিন্তু বহু-ডোমেইন CDN স্ট্র্যাটেজি বাদ দেওয়াই প্রক্সি সাপোর্টকে দীর্ঘমেয়াদে স্থিতিশীল রেখেছে।

এটা একটা দারুণ স্মরণ করিয়ে দেওয়া বিষয়: তুমি যত বুদ্ধিদীপ্ত অপ্টিমাইজেশনই করো না কেন, সেগুলো একদম যুক্তিযুক্ত থাকে, যতক্ষণ না ওগুলোর মুখোমুখি হয় কোনও hostile middleware এর সাথে। বা ইউজারের অদ্ভুত সব ব্রাউজার এক্সটেনশন। বা Safari। বা ভাষার সেটিংস। বা accessibility ফিচার। বা সোলার ফ্লেয়ার। মানে, প্রায় যেকোনো কিছুর সাথেই ধাক্কা খেতে পারে।


উপসংহার

এখন Foony সেই সব প্রক্সির আড়ালেও কাজ করছে, যেগুলো সত্যিই আমাদের জন্য গুরুত্বপূর্ণ (croxyproxy আর proxyorb), আর তাও আবার কোডবেসকে প্রক্সি-স্পেসিফিক জঞ্জালে না ডুবিয়ে:

  • একেবারে জেনেরিক Three.js shader ফিক্স (কোনও proxy-specific identifier হার্ডকোড না করে)।
  • অনেক বেশি সহজ ডোমেইন স্ট্র্যাটেজি (সব জায়গায়ই foony.com)।
8 Ball Pool online multiplayer billiards icon