JavaScript ब्रिज API
डिवाइस और ऐप की जानकारी
यह कौन-सा फ़ोन है, कौन-सा बिल्ड है, हर इंस्टॉल की स्थिर पहचान, और मानक लोकेशन व कैमरा API।
getUserMedia वेब स्टैंडर्ड#
navigator.mediaDevices.getUserMedia — camera and microphone
उदाहरण
मानक वेब कैमरा और माइक्रोफ़ोन API ऐप के अंदर काम करती है। ऐप Android का अपना अनुमति प्रॉम्प्ट दिखाता है और स्ट्रीम पेज को दे देता है।
लौटाता है: एक MediaStream का Promise, या रिजेक्शन (NotAllowedError जब उपयोगकर्ता ने मना किया या बिल्ड के समय स्विच बंद था, NotFoundError जब ऐसा कोई डिवाइस न हो)। ज़रूरत: चरण 4 (Access) में Camera और/या Mic। जब आप ZIP या HTML पेज से बिल्ड करते हैं और आपका कोड getUserMedia({ video: … }) या { audio: … } कॉल करता है (या <input capture>, BarcodeDetector, SpeechRecognition इस्तेमाल करता है), तो बिल्ड वह कॉल ढूँढकर आपके लिए सही अनुमति जोड़ देता है; { video: false } Camera नहीं जोड़ता। वेबसाइट (URL) बिल्ड में उन्हें खुद टिक करें।
वीडियो एलिमेंट में पीछे का कैमरा दिखाएँ।
<video id="preview" autoplay playsinline muted></video>
<button id="startCam">Start camera</button>
<script>
document.getElementById('startCam').addEventListener('click', async function () {
if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
showMessage('Camera is not available here.');
return;
}
try {
var stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' }, // 'user' = front camera
audio: false
});
document.getElementById('preview').srcObject = stream;
} catch (err) {
if (err.name === 'NotAllowedError') showMessage('Camera permission was refused.');
else showMessage('Camera error: ' + err.name);
}
});
</script>सिर्फ़ माइक्रोफ़ोन से वॉइस नोट रिकॉर्ड करें।
async function recordVoice(seconds) {
var stream;
try {
stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
} catch (err) {
showMessage('Microphone permission was refused.');
return;
}
var chunks = [];
var rec = new MediaRecorder(stream);
rec.ondataavailable = function (e) { chunks.push(e.data); };
rec.onstop = function () {
stream.getTracks().forEach(function (t) { t.stop(); }); // turn the mic off
var blob = new Blob(chunks, { type: rec.mimeType });
document.getElementById('player').src = URL.createObjectURL(blob);
};
rec.start();
setTimeout(function () { rec.stop(); }, seconds * 1000);
}कैमरा स्ट्रीम से एक फ़ोटो लें और कैमरा छोड़ दें।
async function snapshot() {
if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) return null;
let stream;
try {
stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' } });
} catch (err) {
showMessage(err.name === 'NotFoundError' ? 'No camera on this device.' : 'Camera permission was refused.');
return null;
}
const video = document.createElement('video');
video.srcObject = stream;
await video.play();
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth; canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0);
stream.getTracks().forEach(function (t) { t.stop(); });
return canvas.toDataURL('image/jpeg', 0.9);
}ध्यान दें: जब आप कैमरा और माइक साथ में माँगते हैं, तो ऐप यूज़र से दोनों एक साथ माँगता है। अगर बिल्ड के समय स्विच बंद था, तो रिक्वेस्ट बिना कोई प्रॉम्प्ट दिखाए मना हो जाती है - स्विच चालू करके दोबारा बिल्ड करें। काम पूरा होने पर हमेशा ट्रैक बंद करें, वरना कैमरा/माइक की लाइट जलती रहती है। DRM वीडियो (Widevine) बिना किसी स्विच के अनुमत है। कैमरा और माइक्रोफ़ोन संवेदनशील हैं: इन्हें अपने Play Data safety फ़ॉर्म में बताएँ।
IdleDetector वेब स्टैंडर्ड#
new IdleDetector() — Idle Detection
उदाहरण
मानक Idle Detection API: जानें कि उपयोगकर्ता ने फ़ोन इस्तेमाल करना कब बंद किया (कुछ देर कोई इनपुट नहीं) या स्क्रीन कब लॉक की, और वह कब लौटा - "दूर" स्थिति दिखाने, लाइव सेशन रोकने या वॉल्ट लॉक करने के लिए।
लौटाता है: IdleDetector.requestPermission() → 'granted' या 'denied' (टैप चाहिए); detector.start({threshold, signal}) → Promise (अनुमति के बिना NotAllowedError से रिजेक्ट, 60000 ms से कम पर TypeError से); फिर userState ('active'/'idle'), screenState ('locked'/'unlocked') और एक change इवेंट। ज़रूरत: चालू करने के लिए कुछ नहीं और कोई Android अनुमति नहीं।
document.getElementById('away').addEventListener('click', async () => {
if (!('IdleDetector' in window)) return;
if ((await IdleDetector.requestPermission()) !== 'granted') return;
const ac = new AbortController();
const idle = new IdleDetector();
idle.addEventListener('change', () => {
setStatus(idle.userState === 'idle' || idle.screenState === 'locked' ? 'Away' : 'Online');
});
await idle.start({ threshold: 5 * 60000, signal: ac.signal });
stopButton.onclick = () => ac.abort();
});अगले लॉन्च पर नए टैप के बिना फिर से देखना शुरू करें, जब उपयोगकर्ता पहले ही हाँ कह चुका हो:
async function watchAwayIfAllowed() {
if (!('IdleDetector' in window)) return;
const perm = await navigator.permissions.query({ name: 'idle-detection' });
if (perm.state !== 'granted') return; // 'prompt' until requestPermission() ran from a tap
const idle = new IdleDetector();
idle.onchange = () => {
if (idle.screenState === 'locked') pauseLiveSession();
else if (idle.userState === 'active') resumeLiveSession();
};
await idle.start({ threshold: 60000 }); // 1 minute is the smallest threshold
}
watchAwayIfAllowed();उपयोगकर्ता को ऑटो-लॉक की देरी बदलने दें: चल रहे डिटेक्टर को रोकें, नया शुरू करें, और असली त्रुटियाँ बताएँ:
let vaultWatch = null;
async function setAutoLock(minutes) {
if (vaultWatch) vaultWatch.abort(); // aborting stops the old detector
vaultWatch = new AbortController();
const idle = new IdleDetector();
idle.addEventListener('change', () => {
if (idle.userState === 'idle' || idle.screenState === 'locked') lockVault();
});
try {
await idle.start({ threshold: minutes * 60000, signal: vaultWatch.signal });
} catch (err) {
if (err.name === 'NotAllowedError') showMessage('Tap "Enable auto-lock" first.');
else if (err instanceof TypeError) showMessage('Pick at least 1 minute.');
else throw err;
}
}ध्यान दें: ऐप में "इनपुट" का मतलब है इसी ऐप के सामने रहते हुए उपयोगकर्ता का इनपुट; जब स्क्रीन चालू हो और कोई दूसरा ऐप सामने हो, तो उपयोगकर्ता सक्रिय माना जाता है (कोई ऐप दूसरे ऐप्स का इनपुट नहीं देख सकता - Android पर Chrome भी यही जवाब देता है); स्क्रीन बंद या लॉक होने के बाद, निष्क्रिय समय आख़िरी टच से गिना जाता है। अनुमति याद रखी जाती है, और navigator.permissions.query({name:'idle-detection'}) उसका जवाब देता है। अंदरूनी ट्रांसपोर्ट: __idleState, __idlePermission; लॉक/अनलॉक के बदलाव appmint:idle के रूप में आते हैं। AI से बने ऐप इस्तेमाल करते हैं watchIdle().
TextDetector वेब स्टैंडर्ड#
new TextDetector().detect(image) — text recognition (OCR)
उदाहरण
फ़ोन पर ही किसी तस्वीर का टेक्स्ट पढ़ता है - रसीद का कुल, सीरियल नंबर, बिज़नेस कार्ड - Google के ML Kit रिकग्नाइज़र से। इसका रूप Shape Detection API का TextDetector है, जिसे Chrome एक फ़्लैग के पीछे देता है।
लौटाता है: ऐरे का Promise, हर लाइन के लिए एक एंट्री: इमेज के पिक्सेल में { rawValue, boundingBox, cornerPoints }। Google Play services के बिना NotSupportedError से रिजेक्ट होता है और पहचान फ़ेल होने पर OperationError से। ज़रूरत: चालू करने के लिए कुछ नहीं और कोई अनुमति नहीं (कैमरा से पढ़ने के लिए <input type="file" accept="image/*" capture> या कैमरा स्ट्रीम से फ़ोटो लें)।
document.getElementById('photo').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file || !('TextDetector' in window)) return;
status.textContent = 'Reading…';
try {
const lines = await new TextDetector().detect(file); // a Blob, <img>, <canvas>, <video> or ImageBitmap
const text = lines.map((l) => l.rawValue).join('\n');
const total = /total\s*[:$€£]?\s*([\d.,]+)/i.exec(text);
status.textContent = total ? 'Total: ' + total[1] : text;
} catch (err) {
status.textContent = 'Could not read the text (' + err.name + ')';
}
});लाइव कैमरा प्रीव्यू से सीरियल नंबर पढ़ें (चल रहा <video> जैसा है वैसा दिया जाता है, उसका मौजूदा फ़्रेम पढ़ा जाता है):
const video = document.querySelector('video');
async function startPreview() {
video.srcObject = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
await video.play();
}
document.getElementById('read-serial').addEventListener('click', async () => {
if (!('TextDetector' in window)) return;
const lines = await new TextDetector().detect(video);
const hit = lines.map((l) => l.rawValue).find((t) => /S\/?N[:\s]*[A-Z0-9-]{6,}/i.test(t));
serialField.value = hit ? hit.replace(/^.*S\/?N[:\s]*/i, '') : '';
if (!hit) showMessage('No serial number in view — move closer and try again.');
});boundingBox (इमेज पिक्सेल) की मदद से फ़ोटो के ऊपर पहचानी गई हर लाइन की आउटलाइन बनाएँ:
async function outlineText(img, canvas) {
await img.decode(); // detect() rejects InvalidStateError before it has loaded
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const g = canvas.getContext('2d');
g.drawImage(img, 0, 0);
const lines = await new TextDetector().detect(img);
g.strokeStyle = '#ff3b30';
g.lineWidth = 3;
for (const { boundingBox: b, rawValue } of lines) {
g.strokeRect(b.x, b.y, b.width, b.height);
console.log(rawValue);
}
return lines.length;
}जहाँ टेक्स्ट पहचान उपलब्ध नहीं (Google Play services नहीं हैं) वहाँ स्कैन बटन छिपाएँ, और बिज़नेस कार्ड से संपर्क की जानकारी निकालें:
const scanBtn = document.getElementById('scan-card');
scanBtn.hidden = !('TextDetector' in window);
document.getElementById('card-photo').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
const text = (await new TextDetector().detect(file)).map((l) => l.rawValue).join('\n');
fillContact({
email: (text.match(/[\w.+-]+@[\w-]+\.[\w.]+/) || [''])[0],
phone: (text.match(/\+?\d[\d\s().-]{7,}\d/) || [''])[0],
});
});ध्यान दें: सिर्फ़ लैटिन लिपियाँ (अंग्रेज़ी, स्पेनिश, फ़्रेंच, जर्मन, इंडोनेशियाई, वियतनामी, तुर्की और अन्य); चीनी, देवनागरी, जापानी, कोरियाई और अरबी पहचानी नहीं जातीं। मॉडल Google Play services से आता है: आपका कोड इसे इस्तेमाल करे तो बिल्ड इंस्टॉल के समय इसे माँगता है, वरना नए फ़ोन पर पहली कॉल कुछ सेकंड रुकती है जब तक यह डाउनलोड होता है। दूसरी साइट की इमेज में CORS की अनुमति होनी चाहिए (दूषित canvas SecurityError को रिजेक्ट कर देता है)। अंदरूनी ट्रांसपोर्ट: __ocrAvailable, __ocrRecognize (जवाब appmint:ocr के रूप में आता है)। AI से बने ऐप इस्तेमाल करते हैं recognizeText().
__idlePermission ब्रिज#
window.WebToApk.__idlePermission(request: Boolean): String
IdleDetector.requestPermission() ([request] true, उपयोगकर्ता के जेस्चर के दौरान कॉल किया गया) अनुमति दर्ज करता है; false सिर्फ़ उसे पढ़ता है। "granted" | "prompt".
उदाहरण
IdleDetector.requestPermission() और 'idle-detection' अनुमति की स्थिति के पीछे का अंदरूनी ट्रांसपोर्ट। पेज मानक API इस्तेमाल करते हैं; इसे कभी सीधे कॉल न करें - __ वाले तरीके बिना सूचना के बदल सकते हैं।
लौटाता है: 'granted' या 'prompt'। ज़रूरत: अनुरोध के लिए उपयोगकर्ता का जेस्चर।
पब्लिक API इस्तेमाल करें:
button.onclick = async () => {
const state = await IdleDetector.requestPermission();
console.log(state); // 'granted'
};पहले जाँच लें कि फ़ीचर मौजूद है, और मना किए जाने को "अनुमति नहीं है" मानें:
async function askIdlePermission() {
if (!('IdleDetector' in window)) return false;
try {
return (await IdleDetector.requestPermission()) === 'granted';
} catch (err) {
return false;
}
}टैप पर अनुमति माँगें, फिर पहचान शुरू करें:
startBtn.addEventListener('click', async () => {
if (!(await askIdlePermission())) { showMessage('Idle detection was not allowed.'); return; }
const idle = new IdleDetector();
idle.addEventListener('change', () => console.log(idle.userState));
await idle.start({ threshold: 60000 });
});ध्यान दें: ऐप शेल (IdleAndRelatedApps.kt) में लागू किया गया है।
__idleState ब्रिज#
window.WebToApk.__idleState(): String
IdleDetector: ऐप के अपने इनपुट और कीगार्ड से `{idleMs, locked}`।
उदाहरण
IdleDetector के पीछे का अंदरूनी ट्रांसपोर्ट: उपयोगकर्ता ने आख़िरी बार इनपुट कब दिया और स्क्रीन लॉक है या नहीं। पेज मानक API इस्तेमाल करते हैं; इसे कभी सीधे कॉल न करें - __ वाले तरीके बिना सूचना के बदल सकते हैं।
लौटाता है: JSON {idleMs, locked}। स्क्रीन और ऐप के बदलाव उसी रूप में appmint:idle इवेंट के रूप में भेजे जाते हैं। ज़रूरत: कुछ नहीं।
पब्लिक API इस्तेमाल करें:
const idle = new IdleDetector();
idle.onchange = () => console.log(idle.userState, idle.screenState);
await idle.start({ threshold: 60000 });स्क्रीन बंद होने पर AbortController से देखना बंद करें:
const ctl = new AbortController();
const idle = new IdleDetector();
idle.addEventListener('change', () => {
if (idle.userState === 'idle') showAwayBanner();
});
await idle.start({ threshold: 60000, signal: ctl.signal });
// later: ctl.abort();स्क्रीन लॉक होने पर ऐप को लॉक करें:
if ('IdleDetector' in window && (await IdleDetector.requestPermission()) === 'granted') {
const d = new IdleDetector();
d.onchange = () => { if (d.screenState === 'locked') showPinScreen(); };
await d.start({ threshold: 60000 });
}ध्यान दें: ऐप शेल (IdleAndRelatedApps.kt) में लागू किया गया है।
__permissionState ब्रिज#
window.WebToApk.__permissionState(name: String): String
Android के असली अनुमति-स्वीकृतियों से, किसी W3C अनुमति नाम की Permissions API स्थिति: "granted" | "denied" | "prompt", या "" जब शेल उस नाम का जवाब नहीं देता (तब पेज की क्वेरी WebView के अपने इम्प्लीमेंटेशन के पास जाती है)।
उदाहरण
navigator.permissions.query({ name }) के पीछे का आंतरिक ट्रांसपोर्ट। पेज स्टैंडर्ड API इस्तेमाल करते हैं; इसे कभी सीधे कॉल न करें - __ मेथड बिना सूचना के बदल सकते हैं।
लौटाता है: 'granted', 'denied', 'prompt', या '' उस नाम के लिए जिसका जवाब ऐप नहीं देता (तब क्वेरी WebView की अपनी जगह जाती है)। ज़रूरत: चालू करने के लिए कुछ नहीं।
पब्लिक API इस्तेमाल करें:
const status = await navigator.permissions.query({ name: 'camera' });
showCameraHint(status.state); // 'granted' | 'denied' | 'prompt'
status.addEventListener('change', () => showCameraHint(status.state));एक साथ कई अनुमतियाँ जाँचें:
const names = ['camera', 'microphone', 'geolocation', 'notifications'];
const states = await Promise.all(names.map((name) =>
navigator.permissions.query({ name }).then((s) => s.state, () => 'unknown')));
names.forEach((name, i) => console.log(name, states[i]));तभी माँगें जब स्थिति denied न हो, और जब हो तब बताएँ कि क्या करना है:
async function ensureLocation() {
const s = await navigator.permissions.query({ name: 'geolocation' });
if (s.state === 'denied') { showMessage('Turn location on in the phone settings.'); return null; }
return new Promise((ok, fail) => navigator.geolocation.getCurrentPosition(ok, fail));
}ध्यान दें: इनका जवाब देता है: camera, microphone, geolocation, notifications, screen-wake-lock, clipboard-read, clipboard-write। जिस क्षमता को बिल्ड ने बंद रखा (या जिसकी अनुमति मैनिफ़ेस्ट में नहीं है) वह denied होती है। लाइव स्थितियाँ appmint:permissions-changed (हर अनुमति डायलॉग के बाद भेजा जाता है) पर और पेज दोबारा दिखने पर फिर से पढ़ी जाती हैं।
getAppName ब्रिज#
window.WebToApk.getAppName(): String
क्रिएटर ने विज़ार्ड में जो ऐप का नाम चुना - लॉन्चर जो दिखाता है।
उदाहरण
बिल्ड विज़ार्ड में आपका चुना हुआ ऐप का नाम बताता है - लॉन्चर आइकन के नीचे वाला नाम।
लौटाता है: तुरंत एक स्ट्रिंग। ज़रूरत: कुछ नहीं।
टाइटल और शेयर टेक्स्ट में ऐप का असली नाम इस्तेमाल करें।
var appName = (window.WebToApk && window.WebToApk.getAppName)
? WebToApk.getAppName()
: document.title; // in a browser: the page title
document.getElementById('welcome').textContent = 'Welcome to ' + appName;
document.getElementById('shareBtn').addEventListener('click', function () {
if (navigator.share) navigator.share({ text: 'Try ' + appName + '!' });
});ऐप का नाम पेज टाइटल और हेडिंग में रखें:
if (window.WebToApk && WebToApk.getAppName) {
const name = WebToApk.getAppName();
document.title = name;
document.querySelector('h1').textContent = name;
}ऐसा सपोर्ट ईमेल बनाएँ जिसमें ऐप और उसका वर्ज़न लिखा हो:
function supportMailto() {
const b = window.WebToApk;
const name = b && b.getAppName ? b.getAppName() : 'our app';
const version = b && b.getAppVersion ? b.getAppVersion() : 'web';
const subject = encodeURIComponent('Help with ' + name + ' ' + version);
return 'mailto:[email protected]?subject=' + subject;
}
document.getElementById('contact').href = supportMailto();ध्यान दें: यह विज़ार्ड वाला नाम है, पेज का <title> नहीं। यह चलते समय नहीं बदलता। वर्ज़न के लिए इसका इस्तेमाल करें getAppVersion().
getAppVersion ब्रिज#
window.WebToApk.getAppVersion(): String
इंस्टॉल किए गए पैकेज का versionName, जैसे "1.4.2" - वह स्ट्रिंग जो क्रिएटर बिल्ड करते समय तय करता है, versionCode नहीं। अगर पैकेज पढ़ा न जा सके तो "1.0" लौटाता है, ताकि पेज बिना null जाँच के इसे प्रिंट कर सके।
उदाहरण
इंस्टॉल किए गए ऐप का वर्ज़न नाम बताता है, उदाहरण के लिए "1.4.2".
लौटाता है: तुरंत एक स्ट्रिंग। ज़रूरत: कुछ नहीं।
About स्क्रीन में वर्ज़न दिखाएँ।
var version = (window.WebToApk && window.WebToApk.getAppVersion)
? WebToApk.getAppVersion()
: 'web';
document.getElementById('about').textContent = 'Version ' + version;इंस्टॉल किया वर्ज़न पुराना हो तो अपडेट के लिए कहें:
function isOlder(a, b) { // "1.4.2" < "1.10.0"
const x = a.split('.').map(Number), y = b.split('.').map(Number);
for (let i = 0; i < Math.max(x.length, y.length); i++) {
if ((x[i] || 0) !== (y[i] || 0)) return (x[i] || 0) < (y[i] || 0);
}
return false;
}
if (window.WebToApk && WebToApk.getAppVersion && isOlder(WebToApk.getAppVersion(), '1.5.0')) {
document.getElementById('update-banner').hidden = false;
}बग रिपोर्ट में getDeviceInfo() से वर्ज़न और वर्ज़न कोड जोड़ें:
function reportInfo() {
const b = window.WebToApk;
if (!(b && b.getAppVersion)) return 'web';
let text = 'v' + b.getAppVersion();
if (b.getDeviceInfo) {
const info = JSON.parse(b.getDeviceInfo());
text += ' (build ' + info.app.versionCode + ')';
}
return text;
}
document.getElementById('report').value = reportInfo();ध्यान दें: यह वर्ज़न का NAME है जो आपने बिल्ड करते समय तय किया, वर्ज़न कोड नहीं। अगर पैकेज पढ़ा न जा सके तो यह "1.0" लौटाता है, इसलिए कभी खाली नहीं होता। वर्ज़न कोड और इंस्टॉल की तारीखें getDeviceInfo() में हैं, इसके तहत app.
getDeviceInfo ब्रिज#
window.WebToApk.getDeviceInfo(): String
WebToApk.getDeviceInfo() -> JSON स्ट्रिंग, कोई अनुमति ज़रूरी नहीं WebToApk.getPhoneIdentity() -> कैरियर/SIM, Phone अनुमति चाहिए WebToApk.getInstallId() -> हर इंस्टॉल का स्थिर UUID
अलग से नहीं, बल्कि अपने समूह Device / app information के साथ बताया गया है।
उदाहरण
फ़ोन, Android, स्क्रीन, आपके ऐप और मौजूदा स्थिति (बैटरी, मेमोरी, नेटवर्क) की जानकारी एक ही कॉल में देता है।
लौटाता है: तुरंत एक JSON स्ट्रिंग - इस्तेमाल करें JSON.parse। टॉप-लेवल कुंजियाँ: android, hardware, screen, app, runtime, installId। ज़रूरत: कुछ नहीं - कोई अनुमति नहीं।
फ़ोन की जानकारी दिखाएँ।
function showPhoneInfo() {
if (!(window.WebToApk && window.WebToApk.getDeviceInfo)) {
document.getElementById('out').textContent = navigator.userAgent; // browser: only the user agent
return;
}
var info = JSON.parse(WebToApk.getDeviceInfo());
document.getElementById('out').innerHTML =
'Android: ' + info.android.release + ' (SDK ' + info.android.sdkInt + ')<br>' +
'Version ID: ' + info.android.buildId + '<br>' +
'Build number: ' + info.android.incremental + '<br>' +
'Phone: ' + info.hardware.manufacturer + ' ' + info.hardware.model + '<br>' +
'Screen: ' + info.screen.widthPx + 'x' + info.screen.heightPx + ' @' + info.screen.refreshRate + 'Hz<br>' +
'Battery: ' + info.runtime.batteryLevel + '%' + (info.runtime.charging ? ' (charging)' : '') + '<br>' +
'Network: ' + info.runtime.networkType + '<br>' +
'App: ' + info.app.versionName + ' (' + info.app.versionCode + ')<br>' +
'WebView: ' + info.app.webViewPackage;
}सारी कुंजियाँ:
{
"android": { "release": "14", "sdkInt": 34, "codename": "REL", "buildId": "UP1A.231005.007", "incremental": "R.1a2b3c", "display": "…", "fingerprint": "…", "securityPatch": "2026-08-05" },
"hardware": { "manufacturer": "OPPO", "brand": "OPPO", "model": "CPH2269", "device": "…", "product": "…", "board": "…", "hardware": "…", "abis": ["arm64-v8a"], "socManufacturer": "…", "socModel": "…", "isEmulator": false },
"screen": { "widthPx": 720, "heightPx": 1600, "density": 2, "densityDpi": 320, "refreshRate": 90, "nightMode": true },
"app": { "packageName": "com.example.app", "versionName": "1.0", "versionCode": 1, "targetSdk": 35, "firstInstallTime": 1737000000000, "lastUpdateTime": 1737000000000, "installer": "com.android.vending", "webViewPackage": "com.google.android.webview 131.0" },
"runtime": { "totalMemoryBytes": 4000000000, "availableMemoryBytes": 1500000000, "lowMemory": false, "storageFreeBytes": 20000000000, "storageTotalBytes": 64000000000, "batteryLevel": 80, "charging": false, "locale": "en-IN", "timezone": "Asia/Kolkata", "networkType": "wifi" },
"installId": "3f2b8c1e-…"
}कम स्टोरेज या कम मेमोरी वाले फ़ोन पर चेतावनी दें, बड़े डाउनलोड से पहले।
function canDownload(bytesNeeded) {
if (!(window.WebToApk && WebToApk.getDeviceInfo)) return true; // browser: let the download try
const r = JSON.parse(WebToApk.getDeviceInfo()).runtime;
if (r.storageFreeBytes < bytesNeeded * 2) { showMessage('Not enough free storage.'); return false; }
if (r.lowMemory) showMessage('The phone is low on memory; this may be slow.');
return true;
}ध्यान दें: socManufacturer / socModel सिर्फ़ Android 12+ पर मिलते हैं। networkType इनमें से एक है: wifi, cellular, ethernet, other, none या unknown। installer Play इंस्टॉल के लिए com.android.vending है और साइड-लोड के लिए खाली। मान उसी पल पढ़े जाते हैं जब आप कॉल करते हैं - बैटरी या नेटवर्क ताज़ा करने के लिए दोबारा कॉल करें। IMEI या सीरियल नंबर नहीं मिलता: Android सभी ऐप्स के लिए इन्हें रोकता है; इस्तेमाल करें getInstallId().
getInstallId ब्रिज#
window.WebToApk.getInstallId(): String
WebToApk.getDeviceInfo() -> JSON स्ट्रिंग, कोई अनुमति ज़रूरी नहीं WebToApk.getPhoneIdentity() -> कैरियर/SIM, Phone अनुमति चाहिए WebToApk.getInstallId() -> हर इंस्टॉल का स्थिर UUID
अलग से नहीं, बल्कि अपने समूह Device / app information के साथ बताया गया है।
उदाहरण
एक रैंडम ID देता है जो आपके ऐप के इस इंस्टॉल के लिए वही रहती है।
लौटाता है: तुरंत एक स्ट्रिंग (जैसे "3f2b…-…" जैसा UUID)। ज़रूरत: कुछ नहीं।
इसे डिवाइस कुंजी की तरह इस्तेमाल करें अपने सर्वर के लिए या "हर डिवाइस पर एक मुफ़्त ट्रायल" की जाँच के लिए।
function deviceKey() {
if (window.WebToApk && window.WebToApk.getInstallId) {
return WebToApk.getInstallId();
}
// In a browser: keep your own random ID in localStorage (per browser, cleared with site data).
var k = localStorage.getItem('installId');
if (!k) {
k = 'w-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10);
localStorage.setItem('installId', k);
}
return k;
}
fetch('https://api.example.com/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ device: deviceKey() })
});ID को हेडर के साथ अपने सर्वर पर भेजें और उसे URL से बाहर रखें।
async function apiGet(path) {
const id = (window.WebToApk && WebToApk.getInstallId) ? WebToApk.getInstallId() : null;
const res = await fetch('https://api.example.com' + path, {
headers: id ? { 'X-Install-Id': id } : {}
});
return res.json();
}इसे क्रैश रिपोर्ट के साथ जोड़ें ताकि एक इंस्टॉल की समस्याएँ खोजी जा सकें।
if (window.WebToApk && WebToApk.setCrashUserId && WebToApk.getInstallId) {
// returns false when Crash reports are off or the id has other characters
const ok = WebToApk.setCrashUserId(WebToApk.getInstallId());
if (!ok) console.log('crash user id not set');
}ध्यान दें: ID पहली बार इस्तेमाल पर बनती है और ऐप के स्टोरेज में रखी जाती है। ऐप अपडेट के बाद भी यह वही रहती है। यूज़र के ऐप अनइंस्टॉल करने या उसका डेटा साफ़ करने पर यह बदल जाती है। यह हार्डवेयर ID नहीं है - Android ऐप को IMEI या सीरियल नहीं देता। यही वैल्यू getDeviceInfo() में भी है installId.
getPhoneIdentity ब्रिज#
window.WebToApk.getPhoneIdentity(): String
WebToApk.getDeviceInfo() -> JSON स्ट्रिंग, कोई अनुमति ज़रूरी नहीं WebToApk.getPhoneIdentity() -> कैरियर/SIM, Phone अनुमति चाहिए WebToApk.getInstallId() -> हर इंस्टॉल का स्थिर UUID
अलग से नहीं, बल्कि अपने समूह Device / app information के साथ बताया गया है।
उदाहरण
मोबाइल कैरियर और SIM की जानकारी देता है: कैरियर का नाम, SIM का देश, ऑपरेटर कोड, फ़ोन का प्रकार और SIM की स्थिति।
लौटाता है: तुरंत एक JSON स्ट्रिंग - इस्तेमाल करें JSON.parse: { carrierName, simCountryIso, networkCountryIso, simOperator, phoneType, simState, hardwareIdAvailable: false }, या { error }। ज़रूरत: बिल्ड करते समय चरण 4 (Access) में Phone चालू करें; वरना यह लौटाता है { "error": "not_enabled" }.
कैरियर दिखाएँ और देश कोड चुनें।
function showCarrier() {
if (!(window.WebToApk && window.WebToApk.getPhoneIdentity)) return;
var p = JSON.parse(WebToApk.getPhoneIdentity());
if (p.error === 'not_enabled') { showMessage('Turn on Phone when you build the app'); return; }
if (p.error) { showMessage('Could not read SIM info: ' + p.error); return; }
if (p.simState !== 'ready') {
showMessage('No SIM ready (' + p.simState + ')'); // absent | pin_required | network_locked | unknown
return;
}
document.getElementById('carrier').textContent = p.carrierName; // e.g. "Jio 4G"
document.getElementById('country').value = p.simCountryIso.toUpperCase(); // e.g. "IN"
}साइन-अप फ़ॉर्म के लिए SIM वाले देश का कॉलिंग कोड चुनें।
const DIAL = { IN: '+91', US: '+1', GB: '+44', DE: '+49' };
function defaultDialCode() {
if (!(window.WebToApk && WebToApk.getPhoneIdentity)) return '+1';
const p = JSON.parse(WebToApk.getPhoneIdentity());
if (p.error || p.simState !== 'ready') return '+1';
return DIAL[p.simCountryIso.toUpperCase()] || '+1';
}
document.getElementById('dial').value = defaultDialCode();सिर्फ़ Wi-Fi वाले टैबलेट और फ़ोन में फ़र्क करें इससे phoneType.
if (window.WebToApk && WebToApk.getPhoneIdentity) {
const p = JSON.parse(WebToApk.getPhoneIdentity());
if (!p.error && p.phoneType === 'none') {
document.getElementById('sms-login').hidden = true; // no cellular radio: hide SMS sign-in
}
}ध्यान दें: phoneType है gsm, cdma, sip या none (सिर्फ़ Wi-Fi वाले टैबलेट)। simOperator MCC+MNC कोड है (जैसे "40445")। कोई IMEI, MEID या सीरियल नहीं है - hardwareIdAvailable हमेशा false होता है, क्योंकि Android 10 से Android इन्हें हर ऐप से छिपाता है; स्थिर id के लिए getInstallId() इस्तेमाल करें। Phone स्विच आपकी Play लिस्टिंग में संवेदनशील अनुमति जोड़ता है; Data safety फ़ॉर्म में बताएँ कि आप इसे किसलिए इस्तेमाल करते हैं।
isTv ब्रिज#
window.WebToApk.isTv(): Boolean
जब ऐप टेलीविज़न (Android TV, Google TV, Fire TV) पर चलता है तो True।
उदाहरण
पेज को बताता है कि ऐप टेलीविज़न (Android TV, Google TV, Fire TV) पर चल रहा है या नहीं, ताकि वह रिमोट के अनुकूल लेआउट दिखा सके।
लौटाता है: true या false (तुरंत)। ज़रूरत: कुछ नहीं। विज़ार्ड में Android TV support चालू होने पर ऐप को TV लॉन्चर में TV बैनर भी मिलता है, और TV पर पेज को साफ़ फ़ोकस रिंग मिलती है और लिंक, बटन, फ़ील्ड तथा onclick या बटन रोल वाले एलिमेंट रिमोट के ऐरो से पहुँचने योग्य हो जाते हैं; OK फ़ोकस वाले एलिमेंट को क्लिक करता है और Back फ़ोन की तरह काम करता है।
var onTv = !!(window.WebToApk && typeof window.WebToApk.isTv === 'function' && window.WebToApk.isTv());
if (onTv) {
document.documentElement.classList.add('tv'); /* bigger text, no hover-only menus */
document.querySelector('#play').focus(); /* start the remote on the main action */
}html.tv body { font-size: 22px; }
html.tv :focus-visible { transform: scale(1.05); }TV के लिए अलग होम स्क्रीन चुनें और फ़ोन वाला लेआउट डिफ़ॉल्ट रखें।
function isTv() {
return !!(window.WebToApk && typeof WebToApk.isTv === 'function' && WebToApk.isTv());
}
history.replaceState({}, '', isTv() ? '/tv-home' : '/home');
if (isTv()) document.querySelectorAll('.touch-only').forEach(function (el) { el.hidden = true; });ध्यान दें: Android TV एमुलेटर और उसके D-pad से टेस्ट करें। Google Play पर TV में प्रकाशित करना एक अलग चरण है: Play Console में ऐप को Android TV फ़ॉर्म फ़ैक्टर में शामिल करें (Release → Advanced settings → Form factors) और TV का एक स्क्रीनशॉट जोड़ें।