ऐसे रनटाइम का अनुकरण जो मना करता है: ब्राउज़र को WeChat जैसा व्यवहार करने के लिए हम कैसे सैंडबॉक्स करते हैं
लेखक: Oleg Sidorkin, Cinevva के CTO और सह-संस्थापक

हमने हाल ही में अपने Game Creator में WeChat मिनी गेम मोड जारी किया है: यह एक बिल्ड प्रोफ़ाइल है, जो AI द्वारा बनाए गए गेम को वेब प्लेटफ़ॉर्म के उस उपसमुच्चय तक सीमित रखती है जिसे WeChat के रनटाइम पर पोर्ट किया जा सकता है। यह प्रोफ़ाइल जनरेशन नियमों का एक समूह है। कोई DOM UI नहीं, कोई Pointer Lock नहीं, केवल mp3 ऑडियो और कोई डायनेमिक कोड नहीं। मॉडल इनका पालन उसी तरह करता है जैसे मॉडल नियमों का पालन करते हैं, यानी: आम तौर पर।
जब हर उल्लंघन ब्राउज़र में अदृश्य हो और एक्सपोर्ट के बाद घातक साबित हो, तब “आम तौर पर” पर्याप्त नहीं है। HTML हेल्थ बार वाला गेम हमारे प्रीव्यू में बिल्कुल ठीक चलता है, लेकिन WeChat पर खाली स्क्रीन दिखाता है, क्योंकि WeChat मिनी गेम में DOM होता ही नहीं। इसलिए हमने एक ऐसी चीज़ बनाई जो इन नियमों को सुझावों से बदलकर भौतिक नियम बना देती है: एक स्ट्रिक्ट-मोड सैंडबॉक्स, जो ब्राउज़र को उन कामों से इनकार करने पर मजबूर करता है जिन्हें WeChat नहीं कर सकता। यह पोस्ट बताती है कि वह कैसे काम करता है।
मुख्य निर्णय: उपस्थिति का नहीं, अनुपस्थिति का अनुकरण करें
स्वाभाविक तरीका WeChat का अनुकरण करना होता: wx.createCanvas, wx.onTouchStart, wx.setStorageSync को लागू करना और गेम को अनुकरण किए गए wx.* इंटरफ़ेस पर चलाना। हमने इसका उल्टा किया, और इसका कारण हमारी एक्सपोर्ट पाइपलाइन की संरचना है।
WeChat मोड में गेम अब भी ब्राउज़र API के लिए लिखे जाते हैं। पैकेजिंग के समय एक अडैप्टर—समुदाय में मानक weapp-adapter तरीके वाला—उन ब्राउज़र API को wx.* पर मैप करता है। इसका अर्थ है कि हमारे गेम कभी सीधे wx.* को कॉल नहीं करते, इसलिए wx एमुलेटर उन कोड पाथ का परीक्षण करता जो मौजूद ही नहीं हैं। पोर्ट को वास्तव में ब्राउज़र में wx.* की अनुपस्थिति नहीं तोड़ती। उसे ऐसे ब्राउज़र API की उपस्थिति तोड़ती है जिनका WeChat में कोई समकक्ष नहीं है और जिन्हें जनरेशन के दौरान चुपचाप इस्तेमाल कर लिया जाता है। document.createElement('div')। requestPointerLock()। IndexedDB। ऐसी .ogg फ़ाइल जो Chrome में आसानी से डिकोड होती है, लेकिन WeChat iOS पर कभी नहीं होगी।
इसलिए सैंडबॉक्स अनुपस्थिति का अनुकरण करता है। WeChat में जो कुछ भी उपलब्ध नहीं है, उसे प्रीव्यू में हटा दिया जाता है, पॉइज़न कर दिया जाता है या चिह्नित कर दिया जाता है, और गेम अपने पहले फ़्रेम से ही दोनों प्लेटफ़ॉर्म के साझा दायरे में विकसित होता है।
यह कहाँ रहता है: एक सर्विस वर्कर जो पहले से मौजूद था
हमारे एडिटर प्रीव्यू में फ़ाइलें परोसने का एक असामान्य तरीका है, जिसने इसे लगभग बिना अतिरिक्त लागत के संभव बना दिया। एडिटर में गेम नेटवर्क से सर्व नहीं किए जाते। एक सर्विस वर्कर /game/* अनुरोधों को इंटरसेप्ट करता है और IndexedDB से फ़ाइलें परोसता है, जिससे प्रीव्यू iframe को नेटवर्क राउंड ट्रिप के बिना तुरंत रीलोड मिलता है। वह वर्कर पहले से ही हर गेम पेज में दो वर्चुअल फ़ाइलें इंजेक्ट करता है: एक त्रुटि-कैप्चर स्क्रिप्ट, जो कंसोल आउटपुट और क्रैश को एडिटर तक भेजती है, और एक लाइव सीन इंस्पेक्टर।
सैंडबॉक्स तीसरी वर्चुअल फ़ाइल _wechat-strict.js है, जिसे कैप्चर स्क्रिप्ट के तुरंत बाद और किसी भी गेम कोड के चलने से पहले पेज के <head> में इंजेक्ट किया जाता है। क्रम दो कारणों से अत्यंत महत्वपूर्ण है। इसे कैप्चर के बाद चलना चाहिए, ताकि इसके console.error कॉल पहले से हुक किए जा चुके हों और एडिटर तक पहुँचें; और गेम मॉड्यूल से पहले चलना चाहिए, ताकि गेम कोड की पहली पंक्ति निष्पादित होने तक पॉइज़न सक्रिय हो चुके हों।
सक्रियण एक पंक्ति की तरकीब है। क्रिएटर का WeChat टॉगल localStorage में सहेजा जाता है और प्रीव्यू iframe एडिटर के समान ओरिजिन पर है, इसलिए हार्नेस सीधे फ़्लैग पढ़ लेता है:
try {
if (localStorage.getItem('cinevva-gc-profile') !== 'wechat-minigame') return;
} catch (e) { return; }हर गैर-WeChat गेम के लिए यह स्क्रिप्ट केवल एक स्ट्रिंग तुलना और समयपूर्व वापसी है। कोई बिल्ड फ़्लैग नहीं, कोई सर्वर राउंड ट्रिप नहीं और सर्विस वर्कर के साथ सिंक्रोनाइज़ करने के लिए कोई स्टेट नहीं।
पॉइज़न सूची और दो-स्तरीय वर्गीकरण
हर उल्लंघन के लिए एक जैसी प्रतिक्रिया उचित नहीं होती, इसलिए हार्नेस दो स्तरों में अंतर करता है। जो API WeChat पर मौजूद ही नहीं हैं, वे त्रुटि फेंकती हैं, क्योंकि एक्सपोर्ट के बाद भी यही होता है और जनरेशन के दौरान क्रैश होना समीक्षा के समय होने वाले क्रैश का ईमानदार पूर्वावलोकन है। जो चीज़ें मौजूद हैं, लेकिन बाद में टूटती हैं, उन्हें व्यवहार बदले बिना स्पष्ट कंसोल त्रुटियों के रूप में चिह्नित किया जाता है, क्योंकि उन्हें ब्लॉक करने से गेम की वास्तविक स्थिति उस व्यक्ति से छिप जाती जो उस पर काम कर रहा है।
त्रुटि फेंकने वाले स्तर में शामिल हैं: createElement और createElementNS दोनों के माध्यम से किसी भी HTML UI एलिमेंट का निर्माण (Three.js अपना canvas NS वाले रूप से बनाता है, इसलिए दोनों रास्तों पर एक जैसा गेट चाहिए), eval, new Function, requestPointerLock और सर्विस वर्कर पंजीकरण। हर फेंकी गई त्रुटि के संदेश में उसका समाधान भी होता है:
throw violate("document.createElement('" + tag + "') — WeChat मिनी गेम में DOM नहीं होता। " +
"UI को canvas पर बनाएँ (ऑफ़स्क्रीन 2D canvas -> स्क्रीन-स्पेस क्वाड पर THREE.CanvasTexture) " +
"और टैप की हिट-टेस्टिंग स्वयं करें।");चिह्नित करने वाले स्तर में शामिल हैं: indexedDB को पढ़ना (undefined लौटाता है, बिल्कुल WeChat की तरह, साथ ही localStorage इस्तेमाल करने का निर्देश देने वाली त्रुटि), ऐसे किसी भी प्रारूप का ऑडियो जिसे WeChat iOS डिकोड नहीं कर सकता (तीन जगह जाँच की जाती है: Audio कंस्ट्रक्टर, मीडिया एलिमेंट प्रोटोटाइप का src सेटर और फ़ेच किए गए URL), गेम के अपने ओरिजिन या हमारे एसेट CDN के अलावा किसी भी ओरिजिन को किए गए नेटवर्क अनुरोध (WeChat पर इनके लिए श्वेतसूचीबद्ध, ICP-पंजीकृत डोमेन चाहिए), और Tone.js की कोई भी उपस्थिति।
एक पोस्ट-लोड ऑडिट भी है, जो पेज के स्थिर होने के कुछ समय बाद चलता है: यह <body> में मौजूद एलिमेंट देखता है और ऐसे HTML एलिमेंट की रिपोर्ट करता है जो डायनेमिक रूप से बनाए जाने के बजाय गेम के अपने मार्कअप से आए हों। स्टैटिक HUD createElement गेट से बच निकलते हैं, इसलिए केवल वह गेट पर्याप्त नहीं है।
हर रिपोर्ट संदेश के आधार पर डुप्लिकेट हटाती है और प्रति सत्र अधिकतम पचास रिपोर्ट तक सीमित रहती है। ऐसा इनवर्टेड-लूप बग जो हर फ़्रेम में एक div बनाता है, केवल एक त्रुटि पैदा करता है, न कि संदेशों की ऐसी बाढ़ जो उपयोगी संकेत को डुबो दे।
वे चार अपवाद जिनसे हमने सबसे अधिक सीखा
सैंडबॉक्स बनाना मुख्यतः यह तय करना है कि किसे पॉइज़न नहीं करना है, और हर अपवाद हमारे अपने टूल के विकास के दौरान टूटने से सामने आया।
हमारा डीबगर eval पर चलता है। क्रिएटर का execute_js टूल, जिसका इस्तेमाल AI लाइव गेम की जाँच करने के लिए करता है, कैप्चर स्क्रिप्ट के संदेश हैंडलर के माध्यम से कोड का मूल्यांकन करता है, जो eval कॉल करता है। eval को सीधे पॉइज़न करने से AI की अपनी आँखें बंद हो जातीं। समाधान: पॉइज़न करने से पहले हार्नेस वास्तविक फ़ंक्शन को एक नॉन-एन्यूमरेबल प्रॉपर्टी में सुरक्षित रखता है, और कैप्चर स्क्रिप्ट का हैंडलर ज़रूरत पड़ने पर उसका उपयोग करता है:
Object.defineProperty(window, '__cinevvaRealEval', { value: window.eval, enumerable: false });
window.eval = function () { throw violate('WeChat मिनी गेम में eval() प्रतिबंधित है।'); };
// संदेश प्राप्त होने के समय कैप्चर स्क्रिप्ट:
returnValue = (window.__cinevvaRealEval || eval)(e.data.code);eval का उपयोग करने की कोशिश करने वाला गेम कोड अब भी विफल होता है। डीबगर नहीं।
रिकॉर्डर भी एलिमेंट बनाता है। हमारा रील रिकॉर्डर गेम iframe में एक <script> के रूप में इंजेक्ट किया जाता है, जिसे iframe के अपने document.createElement से बनाया जाता है, और वह सिंथेटिक <a> क्लिक के माध्यम से तैयार वीडियो डाउनलोड करता है। उन टैग को ब्लॉक करने से रिकॉर्डिंग केवल और ठीक WeChat मोड में टूट जाती। इसलिए script की अनुमति रहती है और a त्रुटि फेंकने के बजाय चेतावनी देता है। वास्तविक लिंक वाला गेम फिर भी चिह्नित होता है और टूलिंग भी काम करती रहती है।
कीबोर्ड इवेंट बने रहते हैं। AI एक ऐसे टूल से नियंत्रणों की पुष्टि करता है जो कृत्रिम की-प्रेस बनाता है और फिर पढ़ता है कि गेम स्टेट कैसे बदला। फ़ोन का अनुकरण करने के लिए कीबोर्ड इनपुट दबाने से वह नियंत्रण-पुष्टि लूप नष्ट हो जाता जो उलटे कैमरे वाले बग पकड़ता है। टच-फ़र्स्ट व्यवहार सैंडबॉक्स से नहीं, बल्कि जनरेशन नियमों और प्रोफ़ाइल की कंट्रोल स्कीम से लागू किया जाता है।
WebGL2 बना रहता है, और इसका कारण नीचे एक अलग खंड का हकदार है। प्रारंभिक पॉइज़न सूची ने प्रोफ़ाइल द्वारा माँगी गई “WebGL1-सुरक्षित” रेंडरिंग को बाध्य करने के लिए getContext('webgl2') को ब्लॉक किया था। लेकिन Three.js ने r163 में WebGL1 का समर्थन पूरी तरह हटा दिया, और हम r181 पिन करते हैं: WebGL2 को ब्लॉक करने से इस मोड का हर 3D गेम खाली दिखाई देता। प्रोफ़ाइल का रेंडरिंग नियम असंगत था और प्लेटफ़ॉर्म की पुरानी समझ पर लिखा गया था। सैंडबॉक्स ऑडिट ने स्वयं सैंडबॉक्स के विनिर्देश का ऑडिट किया, और उस सूत्र को खींचने पर हमें कुछ महत्वपूर्ण पता चला।
WebGL2 के प्रश्न का उचित उत्तर
कॉन्टेक्स्ट निर्माण को अछूता छोड़ने से स्वाभाविक अगला प्रश्न उठा: यदि हमारे इंजन को WebGL2 चाहिए, तो क्या गेम ऐसे WeChat डिवाइस पर टूटेंगे जिनमें केवल WebGL1 है? इसकी जाँच ने हमें प्लेटफ़ॉर्म की न्यूनतम रेंडरिंग क्षमता की अब तक की सबसे स्पष्ट तस्वीर दी, इसलिए स्रोतों सहित विवरण यहाँ है।
Android पर WeChat का मिनी गेम रनटाइम किसी भी आधुनिक बेस लाइब्रेरी में WebGL2 देता है और अंतर्निहित GLES3 हार्डवेयर व्यावहारिक रूप से सर्वव्यापी है। असली कहानी iOS की है। WebGL2 समर्थन और हाई-परफ़ॉर्मेंस+ मोड पर WeChat के अपने इंजीनियरिंग दस्तावेज़ स्पष्ट करते हैं कि iPhone पर WebGL2 ठीक से केवल हाई-परफ़ॉर्मेंस+ रनटाइम में उपलब्ध है, जिसके लिए हाल का WeChat क्लाइंट (8.0.45 या उससे नया, iOS 14 पर इससे भी नया) और व्यावहारिक रूप से iOS 15.5 या नया चाहिए। Tencent के अपने वर्णन के अनुसार सामान्य हाई-परफ़ॉर्मेंस मोड WebGL2 को “अधिक समस्याओं के साथ” चलाता है, और सामान्य iOS रनटाइम में यह बिल्कुल उपलब्ध नहीं है।
वास्तव में खतरनाक हिस्सा विफलता का स्वरूप है। असमर्थित परिवेशों में getContext('webgl2') null के बजाय ऐसा truthy लेकिन टूटा हुआ कॉन्टेक्स्ट लौटा सकता है—ऐसा व्यवहार जिसे डेवलपर Tencent से सीधे ठीक करने को कह चुके हैं। लौटाए गए मान पर भरोसा करने वाला गेम वहाँ साफ़ ढंग से विफल नहीं होता। वह विकृत ग्राफ़िक्स या बिना किसी त्रुटि के काली स्क्रीन दिखाता है।
इसलिए हम इसे तीन स्तरों पर संभालते हैं। सैंडबॉक्स WebGL2 कॉन्टेक्स्ट निर्माण को अछूता छोड़ता है, क्योंकि उसे पॉइज़न करना हमारे अपने इंजन के विरुद्ध काम करता। जनरेशन प्रोफ़ाइल अब हर गेम में एक बूट गेट अनिवार्य करती है: रेंडरर निर्माण को try/catch में लपेटना, फिर एक कार्यात्मक जाँच (typeof gl.createVertexArray === 'function', केवल WebGL2 में उपलब्ध ऐसी क्षमता जो झूठा कॉन्टेक्स्ट नहीं देगा), और विफलता पर खाली स्क्रीन के बजाय canvas पर बनाया गया विनम्र “कृपया WeChat अपडेट करें” संदेश। Unity से रूपांतरित मिनी गेम भी इसी पैटर्न का उपयोग करते हैं, इसीलिए वास्तविक उपयोग में आपको काली स्क्रीन के बजाय अपग्रेड करने के संकेत दिखाई देते हैं। और जब एक्सपोर्टर तैयार होगा, तो वह game.json में हाई-परफ़ॉर्मेंस+ मोड पिन करेगा, ताकि सक्षम पाथ डिफ़ॉल्ट हो।
WebGL2 की न्यूनतम आवश्यकता से कितने दर्शक छूटते हैं? लगभग iOS 15.5 से पुराने संस्करण या 8.0.45 से पुराने WeChat क्लाइंट इस्तेमाल करने वाले उपयोगकर्ता—2026 में कम एकल-अंकीय प्रतिशत, लेकिन पुराने डिवाइस में केंद्रित, जो कुछ गेम शैलियों के लिए दूसरों से अधिक मायने रखता है। यदि वास्तविक वितरण डेटा कभी दिखाता है कि उस अंतिम हिस्से तक पहुँचना सार्थक है, तो हमारे पास एक सस्ता वैकल्पिक रास्ता सुरक्षित है: WeChat प्रोफ़ाइल को three r162 पर पिन करना, जो WebGL1-सक्षम अंतिम रिलीज़ है। प्रोफ़ाइल वाले गेम केवल स्थिर कोर API का उपयोग करते हैं, इसलिए डाउनग्रेड केवल एक पंक्ति का import map बदलाव है, जिसे हमने जानबूझकर तब तक सुरक्षित रखा है जब तक डेटा इसकी माँग न करे।
मॉडल के साथ लूप पूरा करना
यही वह हिस्सा है जो AI-प्रथम उत्पाद के लिए इसे बनाने योग्य बनाता है। हर बिल्ड के बाद क्रिएटर का एजेंट एक टूल कॉल करता है, जो गेम का कंसोल पढ़ता है और बूट पूरा होने की प्रतीक्षा करता है। कैप्चर स्क्रिप्ट console.error को उस स्ट्रीम में भेजती है। इसलिए [wechat-strict] उल्लंघन ऐसी चेतावनी नहीं है जिसे कोई इंसान स्क्रॉल करते हुए अनदेखा कर दे। वह ठीक उसी चैनल में पहुँचता है जिसे मॉडल बिल्ड पूरा घोषित करने से पहले पहले ही जाँचता है, उसी चरण में, और संदेश में समाधान भी स्पष्ट लिखा होता है।
जनरेशन प्रोफ़ाइल एक निर्देश से अंतिम कमी पूरी करती है: हर [wechat-strict] संदेश को बिल्ड रोकने वाला बग मानें, उसका मूल कारण ठीक करें और हार्नेस को पहचानने या बायपास करने की कोशिश कभी न करें। जो गेम केवल सैंडबॉक्स बंद होने पर चलता है, वह एक्सपोर्ट के बाद विफल होगा, और मॉडल को यही बात स्पष्ट रूप से बताई जाती है।
व्यावहारिक रूप से सैंडबॉक्स एक अस्पष्ट अनुपालन समस्या (“क्या मॉडल ने सभी चौदह नियमों का पालन किया?”) को उस डीबगिंग लूप में बदल देता है जिसमें सिस्टम पहले से अच्छा है (“कंसोल में त्रुटि दिख रही है, उसे ठीक करो”)।
ब्राउज़र किसका अनुकरण नहीं कर सकता
अब ईमानदारी वाला हिस्सा। यह सैंडबॉक्स API-सतह के उल्लंघन पकड़ता है, जो हमारे अनुमान में पोर्ट विफल होने के अधिकांश कारण हैं। यह इन चीज़ों को नहीं पकड़ सकता: वास्तविक फ़ोन पर प्रदर्शन, JIT के बिना JavaScript चलाता iOS, WeChat के WebGL कार्यान्वयन की विचित्रताएँ या फ़ाइल एक्सटेंशन से आगे की कोडेक व्यवहार भिन्नताएँ। इनके लिए वास्तविक रनटाइम चाहिए।
इसलिए सैंडबॉक्स तीन स्तरों में पहला है। दूसरा स्तर, जब हमारा एक्सपोर्टर WeChat DevTools प्रोजेक्ट बनाने लगेगा, आधिकारिक सिम्युलेटर होगा जिसे DevTools CLI और miniprogram-automator के माध्यम से हेडलेस रूप से चलाया जाएगा: एक्सपोर्ट किए गए पैकेज को बूट करें, पुष्टि करें कि पहला फ़्रेम रेंडर होता है और एक टैप स्क्रिप्ट करें। तीसरा स्तर आधिकारिक डिवाइस पाथ है—प्रीव्यू QR कोड और भौतिक फ़ोन पर रिमोट डीबगिंग—जहाँ वे सच्चाइयाँ सामने आती हैं जिन्हें कोई अन्य माध्यम नहीं दिखा सकता। हर अगला स्तर पिछले से धीमा और अधिक विश्वसनीय है, और प्रत्येक का काम अगले स्तर तक जाने की आवश्यकता को दुर्लभ बनाना है।
यदि आप यह मोड आज़माना चाहते हैं, तो Game Creator में “WeChat मिनी गेम मोड” टॉगल इस्तेमाल करें। इससे जुड़े बाज़ार और नियामकीय संदर्भ के लिए WeChat के पचास करोड़ खिलाड़ियों तक पहुँचने की हमारी व्यावहारिक मार्गदर्शिका देखें।
संदर्भ
- WeChat आधिकारिक दस्तावेज़: मिनी गेम्स में JavaScript समर्थन
- WeChat आधिकारिक दस्तावेज़: अडैप्टर लेयर
- WeChat आधिकारिक दस्तावेज़: high-performance+ मोड
- WeChat इंजीनियरिंग दस्तावेज़: मिनी गेम्स में WebGL2 रेंडरिंग समर्थन
- WeChat इंजीनियरिंग दस्तावेज़: iOS के high-performance और high-performance+ मोड
- WeChat डेवलपर समुदाय: खराब webgl2 कॉन्टेक्स्ट को null लौटाना चाहिए
- WeChat आधिकारिक दस्तावेज़: miniprogram-automator
- Three.js r163 रिलीज़ नोट्स (WebGL1 समर्थन हटाया गया)