API پل جاوااسکریپت
اطلاعات دستگاه و اپلیکیشن
این چه گوشیای است، کدام ساخت است، یک شناسهی پایدار برای هر نصب، و APIهای استاندارد موقعیت مکانی و دوربین.
getUserMedia استاندارد وب#
navigator.mediaDevices.getUserMedia — camera and microphone
نمونه
API استاندارد وب برای دوربین و میکروفون داخل اپلیکیشن کار میکند. اپلیکیشن درخواست مجوز خود Android را نشان میدهد و جریان را به صفحه میدهد.
مقدار بازگشتی: یک Promise از MediaStream، یا یک رد (NotAllowedError وقتی کاربر رد کرده یا گزینه هنگام ساخت خاموش بوده، NotFoundError وقتی چنین دستگاهی نیست). پیشنیاز: Camera و/یا Mic در مرحله 4 (Access). وقتی از ZIP یا صفحهی HTML میسازید و کدتان getUserMedia({ video: … }) یا { audio: … } را فراخوانی میکند (یا از <input capture>، BarcodeDetector، SpeechRecognition استفاده میکند)، ساخت فراخوانی را پیدا میکند و مجوز مناسب را برایتان اضافه میکند؛ { video: false } دوربین را اضافه نمیکند. برای ساخت از وبسایت (URL)، خودتان آنها را تیک بزنید.
دوربین پشت را در یک عنصر video نشان دهید.
<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);
}نکتهها: وقتی دوربین و میکروفون را با هم درخواست میکنید، اپلیکیشن هر دو را یکجا از کاربر میخواهد. اگر گزینه هنگام ساخت خاموش بوده، درخواست بدون هیچ پرسشی رد میشود؛ دوباره با گزینهی روشن بسازید. همیشه پس از پایان کار، trackها را متوقف کنید، وگرنه چراغ دوربین/میکروفون روشن میماند. ویدیوی DRM (Widevine) بدون هیچ گزینهای مجاز است. دوربین و میکروفون حساس هستند: آنها را در فرم Play Data safety اعلام کنید.
IdleDetector استاندارد وب#
new IdleDetector() — Idle Detection
نمونه
API استاندارد Idle Detection: بفهمید کاربر کی استفاده از گوشی را متوقف میکند (مدتی بدون ورودی) یا صفحه را قفل میکند و کی برمیگردد؛ برای وضعیت «غایب»، توقف یک جلسهی زنده یا قفلکردن یک گاوصندوق.
مقدار بازگشتی: 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;
}
}نکتهها: در اپلیکیشن، «ورودی» یعنی ورودی کاربر به همین اپلیکیشن وقتی در پیشزمینه است؛ وقتی اپلیکیشن دیگری در پیشزمینه و صفحه روشن است، کاربر فعال حساب میشود (یک اپلیکیشن ورودی اپلیکیشنهای دیگر را نمیبیند؛ Chrome در Android هم همینطور پاسخ میدهد)؛ وقتی صفحه خاموش یا قفل است، زمان بیکاری از آخرین لمس حساب میشود. مجوز به خاطر سپرده میشود و navigator.permissions.query({name:'idle-detection'}) به آن پاسخ میدهد. انتقالهای داخلی: __idleState، __idlePermission؛ تغییرات قفل/بازکردن بهصورت appmint:idle میرسند. اپلیکیشنهای ساختهشده با هوش مصنوعی از watchIdle().
TextDetector استاندارد وب#
new TextDetector().detect(image) — text recognition (OCR)
نمونه
متن داخل یک تصویر را روی خود گوشی میخواند، مثل مبلغ رسید، شمارهی سریال یا کارت ویزیت، با شناساگر ML Kit گوگل. شکل آن همان TextDetector مربوط به Shape Detection API است که 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 را مجاز کنند (بوم آلوده SecurityError را رد میکند). انتقالهای داخلی: __ocrAvailable، __ocrRecognize (بهصورت appmint:ocr پاسخ داده میشود). اپلیکیشنهای ساختهشده با هوش مصنوعی از 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}` از ورودی خود اپلیکیشن و keyguard.
نمونه
انتقال داخلی پشت 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
وضعیت Permissions API برای یک نام مجوز W3C، از مجوزهای واقعی Android: "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".
مقدار بازگشتی: بلافاصله یک رشته. پیشنیاز: هیچ.
نسخه را در صفحهی درباره نشان دهید.
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() -> اپراتور/سیمکارت، به مجوز 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() -> اپراتور/سیمکارت، به مجوز Phone نیاز دارد WebToApk.getInstallId() -> UUID پایدار برای هر نصب
بهجای توضیح جداگانه، در گروهش توضیح داده شده است: Device / app information.
نمونه
یک شناسهی تصادفی میدهد که برای این نصب اپلیکیشن شما ثابت میماند.
مقدار بازگشتی: بلافاصله یک رشته (یک UUID مانند "3f2b…-…"). پیشنیاز: هیچ.
از آن بهعنوان کلید دستگاه استفاده کنید برای سرور خودتان یا بررسی «یک آزمایش رایگان برای هر دستگاه».
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() })
});شناسه را با هدر به سرور بفرستید و آن را از 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');
}نکتهها: شناسه در اولین استفاده ساخته میشود و در حافظهی اپلیکیشن میماند. پس از بهروزرسانی اپلیکیشن ثابت میماند. وقتی کاربر اپلیکیشن را حذف کند یا دادههایش را پاک کند، تغییر میکند. این شناسهی سختافزاری نیست؛ Android به اپلیکیشنها IMEI یا سریال نمیدهد. همین مقدار در getDeviceInfo() هم بهصورت installId.
getPhoneIdentity پل#
window.WebToApk.getPhoneIdentity(): String
WebToApk.getDeviceInfo() -> رشتهی JSON، بدون نیاز به مجوز WebToApk.getPhoneIdentity() -> اپراتور/سیمکارت، به مجوز Phone نیاز دارد WebToApk.getInstallId() -> UUID پایدار برای هر نصب
بهجای توضیح جداگانه، در گروهش توضیح داده شده است: Device / app information.
نمونه
اطلاعات اپراتور موبایل و سیمکارت را میدهد: نام اپراتور، کشور سیمکارت، کد اپراتور، نوع گوشی و وضعیت سیمکارت.
مقدار بازگشتی: بلافاصله یک رشته 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"
}کد تماس را از کشور سیمکارت انتخاب کنید برای فرم ثبتنام.
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 آنها را از همهی اپلیکیشنها پنهان میکند؛ برای شناسهی پایدار از 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 هم میگیرد و روی تلویزیون صفحه حلقهی فوکوس واضح میگیرد و لینکها، دکمهها، فیلدها و عناصر متصلشده با 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); }صفحهی اصلی متفاوتی برای تلویزیون انتخاب کنید و چیدمان گوشی را پیشفرض نگه دارید.
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 مرحلهی جداگانهای است: در Play Console اپلیکیشن را در فرمفکتور Android TV ثبت کنید (Release → Advanced settings → Form factors) و یک اسکرینشات تلویزیون اضافه کنید.