Создать APK бесплатно

JavaScript bridge API

Телефон, SMS и журнал звонков

Совершение звонка, отправка или создание сообщения и чтение истории. Всё это требует разрешений, и Play спрашивает о них.

composeSms мост#

window.WebToApk.composeSms(phoneNumber: String, message: String): Boolean

Открывает приложение SMS пользователя с заполненным сообщением. Не требует разрешения и не может быть отклонено Play — правильный выбор по умолчанию для всего неавтоматического. Возвращает false, если на телефоне нет приложения SMS (или его не удалось открыть).

Пример

Открывает приложение SMS пользователя с уже введённым номером и текстом. Отправить нажимает пользователь.

Возвращает: true, когда приложение SMS открылось, false, когда на телефоне нет приложения SMS или его не удалось открыть (синхронно). Требуется: ничего — ни переключателя в сборке, ни разрешения.

Кнопка «Напишите нам».

document.getElementById('textUs').addEventListener('click', function () {
  if (window.WebToApk && window.WebToApk.composeSms) {
    var ok = WebToApk.composeSms('+911234567890', 'Hello, I need help with my order');
    if (!ok) showMessage('Could not open the SMS app.');
  } else {
    // In a normal browser the standard sms: link does the same job.
    location.href = 'sms:+911234567890?body=' + encodeURIComponent('Hello, I need help with my order');
  }
});

Кнопка «Напомнить» в каждой строке записи, которая подставляет напоминание на номер этого клиента:

document.getElementById('appointments').addEventListener('click', function (e) {
  var row = e.target.closest('[data-phone]');
  if (!row || !(window.WebToApk && typeof window.WebToApk.composeSms === 'function')) return;
  var when = new Date(Number(row.getAttribute('data-time')));
  var text = 'Hi ' + row.getAttribute('data-name') + ', a reminder of your appointment on ' +
             when.toLocaleDateString() + ' at ' +
             when.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) + '.';
  if (!WebToApk.composeSms(row.getAttribute('data-phone'), text)) {
    showMessage('This phone has no SMS app.');
  }
});

Форма «Отправить по SMS»: когда на телефоне нет приложения SMS (false), скопируйте текст, чтобы пользователь мог вставить его в другом месте:

document.getElementById('smsForm').addEventListener('submit', function (e) {
  e.preventDefault();
  var number = document.getElementById('to').value.trim();
  var text = document.getElementById('message').value;
  if (!number || !text) return;

  var opened = window.WebToApk && typeof window.WebToApk.composeSms === 'function' &&
               WebToApk.composeSms(number, text);
  if (!opened && navigator.clipboard) {
    navigator.clipboard.writeText(text).then(function () {
      showMessage('No SMS app here. The message was copied instead.');
    });
  }
});

Примечания: Ничего не отправляется, пока пользователь не нажмёт «отправить» в своём приложении SMS, поэтому сам этот метод не требует разрешения на SMS. Используйте его для каждого сообщения, которое пользователь пишет или одобряет; используйте sendSms только когда приложению действительно нужно отправлять само. Для приложений, созданных ИИ, и импортов из Google AI Studio сборка сканирует ваш код; вызов composeSms( не отмечает SMS (чтение и отправка) — это делают только sendSms и listSms — поэтому кнопка «Напишите нам» не добавляет ограниченных разрешений Play.

listCallLog мост#

window.WebToApk.listCallLog(requestId: String, limit: Int, offset: Int, sinceMillis: String)

WebToApk.listCallLog(id, limit, offset, sinceMillis) Доставляется как `appmint:calllog` / window.onAppMintCallLog.

Описано в своей группе, Журнал звонков, а не отдельно.

Пример

Читает историю звонков телефона, сначала самые новые.

Возвращает: пока ничего. Ответом служит событие appmint:calllog (и window.onAppMintCallLog) с detail = { requestId, calls: [{ number, name, type, date, duration }] } или { requestId, error }. date — миллисекунды с 1970 года; duration — секунды. Требуется: включите Журнал звонков (чтение) на шаге 4 (Доступ) при сборке; при первом вызове пользователь получит запрос на разрешение.

Покажите последние 50 звонков.

function loadCalls() {
  if (!(window.WebToApk && window.WebToApk.listCallLog)) return;
  var id = 'calls-' + Date.now();

  window.addEventListener('appmint:calllog', function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;
    window.removeEventListener('appmint:calllog', handler);
    if (e.detail.error) { showMessage('Call log: ' + e.detail.error); return; }

    e.detail.calls.forEach(function (c) {
      // c.type: incoming | outgoing | missed | voicemail | rejected | blocked | other
      addRow((c.name || c.number) + ' - ' + c.type + ' - ' +
             new Date(c.date).toLocaleString() + ' - ' + c.duration + 's');
    });
  });

  WebToApk.listCallLog(id, 50, 0, '0');   // limit, offset, since (string)
}

Только звонки за последние 24 часа, с помощью хука страницы. sinceMillis — это СТРОКА, потому что число слишком велико для int моста.

window.onAppMintCallLog = function (detail) {
  if (detail.requestId !== 'today') return;
  if (detail.error) return;
  document.getElementById('count').textContent = detail.calls.length + ' calls today';
};

if (window.WebToApk && window.WebToApk.listCallLog) {
  WebToApk.listCallLog('today', 500, 0, String(Date.now() - 24 * 60 * 60 * 1000));
}

Экран истории звонков с кнопкой «Загрузить ещё», получающий по 25 звонков с помощью offset:

var PAGE = 25, loaded = 0;

function loadMoreCalls() {
  if (!(window.WebToApk && typeof window.WebToApk.listCallLog === 'function')) return;
  var id = 'page-' + loaded;

  window.addEventListener('appmint:calllog', function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;
    window.removeEventListener('appmint:calllog', handler);
    if (e.detail.error) { showMessage('Call log: ' + e.detail.error); return; }

    e.detail.calls.forEach(function (c) { addRow(c.name || c.number, c.type, c.date); });
    loaded += e.detail.calls.length;
    document.getElementById('more').hidden = e.detail.calls.length < PAGE;   // last page
  });

  WebToApk.listCallLog(id, PAGE, loaded, '0');   // skip the calls already shown
}

document.getElementById('more').addEventListener('click', loadMoreCalls);
loadMoreCalls();

Примечания: limit ограничивается диапазоном 1–2000. Ошибки: not_enabled, permission_denied, foreign_origin. Журнал звонков — ограниченное разрешение Google Play: вам нужно подать одобренную декларацию о разрешениях в Play Console, иначе приложение будет отклонено.

listSms мост#

window.WebToApk.listSms(requestId: String, box: String, limit: Int, offset: Int)

WebToApk.composeSms(number, message) <- БЕЗ разрешения, открывает приложение SMS WebToApk.sendSms(id, number, message, wantDelivery) WebToApk.listSms(id, "inbox"|"sent"|"draft", limit, offset) Результаты отправки: `appmint:sms` / window.onAppMintSms {requestId, status:"sent"|"delivered"|"failed", ok, reason?} Входящие сообщения: `appmint:sms-received` / window.onAppMintSmsReceived.

Описано в своей группе, SMS, а не отдельно.

Пример

Читает SMS-сообщения из входящих, отправленных или черновиков телефона, сначала самые новые. В этом файле также показано, как получать новые сообщения по мере поступления.

Возвращает: пока ничего. Ответом служит событие appmint:sms (и window.onAppMintSms) с detail = { requestId, messages: [{ id, address, body, date, read, box }] }. При ошибке: { requestId, status: 'failed', ok: false, reason }. Требуется: включите SMS (чтение и отправка) на шаге 4 (Доступ) при сборке; при первом вызове пользователь получит запрос на разрешение.

Прочитайте последние 30 входящих сообщений.

function loadInbox() {
  if (!(window.WebToApk && window.WebToApk.listSms)) return;
  var id = 'inbox-' + Date.now();

  window.addEventListener('appmint:sms', function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;
    window.removeEventListener('appmint:sms', handler);
    if (e.detail.status === 'failed') { showMessage('Cannot read SMS: ' + e.detail.reason); return; }

    e.detail.messages.forEach(function (m) {
      addRow(m.address + ': ' + m.body + (m.read ? '' : ' (new)') +
             ' - ' + new Date(m.date).toLocaleString());
    });
  });

  WebToApk.listSms(id, 'inbox', 30, 0);   // box: 'inbox' | 'sent' | 'draft'
}

Новые сообщения по мере поступления (appmint:sms-received, а также window.onAppMintSmsReceived). Сообщения, пришедшие, пока приложение закрыто, сохраняются (последние 50) и передаются вашей странице при следующем открытии приложения.

window.addEventListener('appmint:sms-received', function (e) {
  // e.detail = { address: '+91...', body: 'Your OTP is 4821', date: 1737000000000 }
  var code = (e.detail.body.match(/\b\d{4,6}\b/) || [])[0];
  if (code) document.getElementById('otp').value = code;
});

// or, as a page hook:
window.onAppMintSmsReceived = function (sms) {
  console.log('SMS from ' + sms.address);
};

Трекер расходов, который просматривает последние 200 входящих сообщений в поисках списаний банка и суммирует суммы:

function scanSpending() {
  if (!(window.WebToApk && typeof window.WebToApk.listSms === 'function')) return;
  var id = 'spend-' + Date.now();

  window.addEventListener('appmint:sms', function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;
    window.removeEventListener('appmint:sms', handler);
    if (e.detail.status === 'failed') { showMessage('Cannot read SMS: ' + e.detail.reason); return; }

    var total = 0;
    e.detail.messages.forEach(function (m) {
      if (!/debited/i.test(m.body)) return;
      var amount = m.body.match(/(?:Rs\.?|INR)\s*([\d,]+(?:\.\d{1,2})?)/i);
      if (amount) total += parseFloat(amount[1].replace(/,/g, ''));
    });
    document.getElementById('spent').textContent = 'Spent: Rs ' + total.toFixed(2);
  });

  WebToApk.listSms(id, 'inbox', 200, 0);
}

Примечания: limit ограничивается диапазоном 1–500. Любое неизвестное имя ящика читает входящие. Значения reason: not_enabled, permission_denied, foreign_origin. Полученные сообщения по-прежнему доходят и до обычного приложения SMS пользователя. SMS — ограниченное разрешение Google Play: вам нужно подать одобренную декларацию о разрешениях в Play Console (обычно подходят только приложения SMS по умолчанию), иначе приложение будет отклонено.

makePhoneCall мост#

window.WebToApk.makePhoneCall(phoneNumber: String)

Звонит на [phoneNumber]. — Разрешение «Телефон» ВКЛЮЧЕНО в сборке: звонок совершается напрямую. В первый раз Android спрашивает у пользователя разрешение «Телефон»; если он отказывает, открывается номеронабиратель с введённым номером, чтобы пользователь всё равно мог позвонить сам. — Разрешение «Телефон» ВЫКЛЮЧЕНО: открывается номеронабиратель с введённым номером (ему разрешение не нужно), и пользователь нажимает «Позвонить». — Страница, не принадлежащая приложению (внешний сайт, открытый в приложении), всегда получает номеронабиратель, а не прямой звонок. Обычная ссылка `tel:` в вашем HTML тоже открывает номеронабиратель и часто этого достаточно.

Пример

Начинает телефонный звонок на номер или открывает номеронабиратель с введённым номером.

Возвращает: ничего. Требуется: ничего для открытия номеронабирателя. Включите Телефон на шаге 4 (Доступ) при сборке, если звонок должен начинаться сразу.

Кнопка «Позвонить в магазин».

document.getElementById('callShop').addEventListener('click', function () {
  if (window.WebToApk && window.WebToApk.makePhoneCall) {
    WebToApk.makePhoneCall('+911234567890');
  }
});

Переключатель не нужен: обычная ссылка tel: открывает номеронабиратель с номером — и в приложении, и в браузере. Часто этого достаточно.

<a href="tel:+911234567890">Call the shop</a>

Один обработчик на весь справочник сотрудников, где каждая строка хранит свой номер в атрибуте data-phone:

document.getElementById('staffList').addEventListener('click', function (e) {
  var btn = e.target.closest('[data-phone]');
  if (!btn) return;
  var number = btn.getAttribute('data-phone').replace(/[^\d+]/g, '');   // keep digits and +
  if (!number) return;
  if (window.WebToApk && typeof window.WebToApk.makePhoneCall === 'function') {
    WebToApk.makePhoneCall(number);
  } else {
    location.href = 'tel:' + number;   // a normal browser
  }
});

Примечания: Если Телефон выключен, открывается номеронабиратель с номером, и пользователь нажимает вызов. Если Телефон включён, звонок начинается сразу; в первый раз Android спрашивает у пользователя разрешение на звонки, а если он отказывает, вместо этого открывается номеронабиратель, чтобы он всё равно мог позвонить. Страница, не принадлежащая приложению (внешний сайт, открытый внутри приложения), всегда получает номеронабиратель. Передавайте номер обычной строкой. Ничего не сообщается обратно — вы не можете узнать, был ли звонок совершён. Переключатель «Телефон» добавляет разрешение CALL_PHONE в ваше описание в Play; укажите его в форме безопасности данных и включайте, только если оно нужно.

sendSms мост#

window.WebToApk.sendSms(

WebToApk.composeSms(number, message) <- БЕЗ разрешения, открывает приложение SMS WebToApk.sendSms(id, number, message, wantDelivery) WebToApk.listSms(id, "inbox"|"sent"|"draft", limit, offset) Результаты отправки: `appmint:sms` / window.onAppMintSms {requestId, status:"sent"|"delivered"|"failed", ok, reason?} Входящие сообщения: `appmint:sms-received` / window.onAppMintSmsReceived.

Описано в своей группе, SMS, а не отдельно.

Пример

Отправляет SMS прямо из приложения, не открывая приложение SMS, и сообщает, что произошло на самом деле.

Возвращает: пока ничего. Ответом служит событие appmint:sms (и window.onAppMintSms) с detail = { requestId, status: 'sent' | 'delivered' | 'failed', ok, reason? }. С отчётом о доставке при успехе вы получаете ДВА ответа: sent, затем delivered. Требуется: включите SMS (чтение и отправка) на шаге 4 (Доступ) при сборке; при первом вызове пользователь получит запрос на разрешение.

Отправьте код и следите за его статусом. Не удаляйте слушатель после первого ответа, если вы запросили отчёт о доставке.

function sendCode(phone, code) {
  if (!(window.WebToApk && window.WebToApk.sendSms)) {
    showMessage('Sending SMS works only inside the app.');
    return;
  }
  var id = 'sms-' + Date.now();

  function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;
    var d = e.detail;
    if (d.status === 'sent' && d.reason === 'no_delivery_report') {
      showStatus('Sent (the network gave no delivery report)');
      window.removeEventListener('appmint:sms', handler);
    } else if (d.status === 'sent') {
      showStatus('Sent');
    } else if (d.status === 'delivered') {
      showStatus('Delivered');
      window.removeEventListener('appmint:sms', handler);
    } else {                                   // 'failed'
      showStatus('Failed: ' + d.reason);
      window.removeEventListener('appmint:sms', handler);
    }
  }
  window.addEventListener('appmint:sms', handler);

  WebToApk.sendSms(id, phone, 'Your code is ' + code, true);   // true = delivery report
}

Без отчёта о доставке. Передайте false последним значением: вы получите ровно один ответ, sent или failed.

window.onAppMintSms = function (d) {
  if (d.requestId === 'alert-1') showStatus(d.ok ? 'Alert sent' : 'Failed: ' + d.reason);
};
if (window.WebToApk && window.WebToApk.sendSms) {
  WebToApk.sendSms('alert-1', '+911234567890', 'Gate opened at ' + new Date().toLocaleTimeString(), false);
}

То же уведомление короткому списку номеров, одному за другим, с сохранением каждого результата (и остановкой, когда SMS выключены или отклонены, потому что все остальные номера завершились бы так же):

function sendToAll(numbers, text, done) {
  if (!(window.WebToApk && typeof window.WebToApk.sendSms === 'function')) { done(null); return; }
  var results = [], i = 0;

  function next() {
    if (i >= numbers.length) { done(results); return; }
    var number = numbers[i++];
    var id = 'notice-' + Date.now() + '-' + i;

    window.addEventListener('appmint:sms', function handler(e) {
      if (!e.detail || e.detail.requestId !== id) return;
      window.removeEventListener('appmint:sms', handler);
      results.push({ number: number, ok: e.detail.ok, reason: e.detail.reason });
      var r = e.detail.reason;
      if (r === 'not_enabled' || r === 'permission_denied' || r === 'foreign_origin') done(results);
      else next();
    });
    WebToApk.sendSms(id, number, text, false);   // one answer per number
  }
  next();
}

sendToAll(['+911234567890', '+919876543210'], 'Practice moved to 6 pm today.', function (results) {
  if (!results) return showMessage('Sending SMS works only inside the app.');
  showStatus(results.filter(function (r) { return r.ok; }).length + ' of ' + results.length + ' sent');
});

Примечания: Длинный текст разбивается на части и отправляется как одно сообщение. Если отчёт о доставке не пришёл в течение 60 секунд, вы получаете status: 'sent' с reason: 'no_delivery_report' — сообщение всё равно было отправлено. Значения reason: not_enabled, permission_denied, foreign_origin, invalid_arguments (пустой номер или текст), no_service, radio_off, generic_failure, null_pdu, not_delivered и другие. Используйте строковый requestId. Каждое SMS может стоить пользователю денег. SMS — ограниченное разрешение Google Play: вам нужно подать одобренную декларацию о разрешениях в Play Console (обычно подходят только приложения SMS по умолчанию), поэтому предпочитайте composeSms, когда пользователь может сам нажать «отправить».