ساخت APK رایگان

API پل جاوااسکریپت

Contacts

خواندن، جست‌وجو، انتخاب و نوشتن؛ انتخابگر اصلاً به مجوز نیاز ندارد.

navigator.contacts استاندارد وب#

navigator.contacts.select(properties, options) — Contact Picker

نمونه

API استاندارد Contact Picker: انتخابگر مخاطب خود گوشی باز می‌شود و صفحه فقط شخص انتخاب‌شده را می‌گیرد، بدون مجوز مخاطبان.

مقدار بازگشتی: یک Promise از آرایه: [{ name: [string], tel: [string] }]، یا [] وقتی کاربر انتخابگر را می‌بندد. پیش‌نیاز: چیزی برای روشن‌کردن نیست؛ آن را از یک لمس فراخوانی کنید (حرکت کاربر لازم است، وگرنه SecurityError).

document.getElementById('pick').addEventListener('click', async () => {
  if (!('contacts' in navigator)) { showManualEntry(); return; }
  const props = await navigator.contacts.getProperties();   // ['name', 'tel']
  const [person] = await navigator.contacts.select(props, { multiple: false });
  if (!person) return;                                       // closed without choosing
  document.getElementById('name').value = person.name[0] || '';
  document.getElementById('phone').value = person.tel[0] || '';
});

کادر «ارسال به» را فقط با شماره پر کنید و برای هر حالتی که انتخابگر می‌تواند رد کند، پیامی نشان دهید:

document.getElementById('choose-number').addEventListener('click', async () => {
  if (!('contacts' in navigator)) return;
  try {
    const [person] = await navigator.contacts.select(['tel']);
    if (person && person.tel.length) document.getElementById('to').value = person.tel[0];
  } catch (err) {
    if (err.name === 'InvalidStateError') showMessage('The contact picker is already open or not available.');
    else if (err.name === 'SecurityError') showMessage('Tap the button to choose a contact.');
    else showMessage('Could not pick a contact: ' + err.message);
  }
});

فهرست مهمانان را با یک لمس در هر بار بسازید (هر بار یک نفر) و شماره‌ای را که از قبل در فهرست هست نادیده بگیرید:

const guests = [];

document.getElementById('add-guest').addEventListener('click', async () => {
  if (!('contacts' in navigator)) { showManualEntry(); return; }
  const [person] = await navigator.contacts.select(['name', 'tel']);
  if (!person || !person.tel.length) return;   // closed, or no number

  const number = person.tel[0];
  if (guests.some((g) => g.number === number)) { showMessage('Already invited'); return; }
  guests.push({ name: person.name[0] || number, number });
  renderGuests(guests);
});

نکته‌ها: اپلیکیشن از name و tel پشتیبانی می‌کند، یعنی شماره‌ای که کاربر لمس کرده. email، address و icon به مجوز کامل مخاطبان نیاز دارند، بنابراین ارائه نمی‌شوند و درخواست آن‌ها با TypeError رد می‌شود. در هر انتخاب یک نفر (multiple مجاز است اما انتخابگر Android یک نفر برمی‌گرداند). فقط در صفحه‌ی اصلی کار می‌کند. برای فهرست‌کردن یا جست‌وجوی همه‌ی مخاطبان از WebToApk.listContacts استفاده کنید (به گزینه‌ی Contacts نیاز دارد).

addContact پل#

window.WebToApk.addContact(requestId: String, contactJson: String)

انتخابگر سیستم (بدون نیاز به مجوز) و نوشتن مخاطب جدید (WRITE_CONTACTS). بخش خواندن، یعنی فهرست‌کردن و جست‌وجو، در گروه Contacts بالا قرار دارد.

به‌جای توضیح جداگانه، در گروهش توضیح داده شده است: Contacts: pick and add.

نمونه

مخاطب جدید را مستقیماً در دفترچه‌ی تلفن گوشی ذخیره می‌کند، بدون باز کردن برنامه‌ی مخاطبان.

مقدار بازگشتی: فعلاً چیزی برنمی‌گردد. پاسخ از طریق رویداد appmint:contacts (و window.onAppMintContacts) می‌رسد: در صورت موفقیت با detail = { requestId, ok: true, id: '123' }، وقتی ذخیره شکست خورد با { requestId, ok: false, id: null }، یا با { requestId, error }. پیش‌نیاز: هنگام ساخت، در مرحله 4 (Access) گزینه Edit Contacts را روشن کنید؛ در اولین فراخوانی از کاربر مجوز خواسته می‌شود.

یک مشتری را ذخیره کنید. مخاطب به‌صورت رشته‌ی JSON داده می‌شود.

function saveCustomer(name, phone, email) {
  if (!(window.WebToApk && window.WebToApk.addContact)) return;
  var id = 'add-' + Date.now();

  window.addEventListener('appmint:contacts', function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;
    window.removeEventListener('appmint:contacts', handler);

    if (e.detail.error === 'not_enabled') { showMessage('Turn on Edit Contacts when you build'); return; }
    if (e.detail.error) { showMessage('Not saved: ' + e.detail.error); return; }
    showMessage(e.detail.ok ? 'Saved to contacts' : 'Could not save the contact');
  });

  WebToApk.addContact(id, JSON.stringify({
    displayName: name,
    phones: [{ number: phone, type: 'mobile' }],   // type: mobile | home | work
    emails: [email]
  }));
}

شکل کوتاه. مقدارهای تکی هم پذیرفته می‌شوند: { name: 'Ali', phone: '+911234567890', email: '[email protected]' }.

دکمه‌ی «ذخیره در مخاطبان» در صفحه‌ی کارت ویزیت، با شکل کوتاه و یک Promise:

function addOne(contact) {
  return new Promise(function (resolve, reject) {
    var id = 'card-' + Date.now() + '-' + Math.random().toString(36).slice(2);
    window.addEventListener('appmint:contacts', function handler(e) {
      if (!e.detail || e.detail.requestId !== id) return;
      window.removeEventListener('appmint:contacts', handler);
      if (e.detail.error) reject(new Error(e.detail.error));
      else if (!e.detail.ok) reject(new Error('save_failed'));
      else resolve(e.detail.id);
    });
    WebToApk.addContact(id, JSON.stringify(contact));
  });
}

document.getElementById('save-card').addEventListener('click', function () {
  if (!(window.WebToApk && window.WebToApk.addContact)) return;
  addOne({ name: 'Priya Sharma', phone: '+919876543210', email: '[email protected]' })
    .then(function () { showMessage('Priya is in your contacts now'); })
    .catch(function (err) {
      showMessage(err.message === 'permission_denied' ? 'Allow Contacts to save this card.' : 'Not saved: ' + err.message);
    });
});

فهرست یک تیم را نفر به نفر وارد کنید، با شماره‌های محل کار و خانه، و گزارش دهید چند نفر ذخیره شدند:

async function importTeam(team) {   // [{ name, work, home, email }]
  if (!(window.WebToApk && window.WebToApk.addContact)) return;
  var saved = 0;
  for (var i = 0; i < team.length; i++) {
    var m = team[i];
    try {
      await addOne({   // addOne() from the example above
        displayName: m.name,
        phones: [{ number: m.work, type: 'work' }, { number: m.home, type: 'home' }],
        emails: m.email ? [m.email] : []
      });
      saved++;
    } catch (err) {
      if (err.message === 'not_enabled' || err.message === 'permission_denied') break;   // the rest would fail too
    }
  }
  showMessage(saved + ' of ' + team.length + ' saved');
}

نکته‌ها: خطاها: not_enabled، permission_denied. مقدار id برگشتی همان شناسه‌ی raw-contact جدید است. type تلفن غیر از home یا work به‌صورت mobile ذخیره می‌شود. مخاطب روی دستگاه ذخیره می‌شود (بدون حساب). نوشتن مخاطبان مجوز حساسی است: فهرست شما در Play Store باید آن را در فرم Data safety و در سیاست حفظ حریم خصوصی اعلام کند.

getContact پل#

window.WebToApk.getContact(requestId: String, contactId: String)

همه‌چیز ناهمگام است: خواندن با کرسر می‌تواند هزاران ردیف باشد و بازگشت مسدودکننده‌ی @JavascriptInterface آن را روی ترد جاوااسکریپت اجرا می‌کند و باعث ANR می‌شود. نتایج به‌صورت `appmint:contacts` / window.onAppMintContacts({requestId,...}) می‌رسند. requestId: یک رشته بدهید ('pick-' + Date.now()). یک عدد خام هم کار می‌کند؛ پل آن را از طریق double تبدیل می‌کند ("1.787491234567E12") و هر پاسخ دوباره آن را به حالت استاندارد برمی‌گرداند (BridgeRequestId)، بنابراین `res.requestId === Date.now()` در هر دو حالت درست از آب درمی‌آید. WebToApk.listContacts(id, limit, offset) WebToApk.searchContacts(id, query, limit) WebToApk.getContact(id, contactId) WebToApk.pickContact(id) <- بدون نیاز به مجوز (انتخابگر سیستم) WebToApk.addContact(id, json) <- به مجوز نوشتن مخاطبان نیاز دارد

به‌جای توضیح جداگانه، در گروهش توضیح داده شده است: Contacts.

نمونه

یک مخاطب را دوباره با id آن می‌خواند (همان id که از listContacts یا searchContacts).

مقدار بازگشتی: فعلاً چیزی برنمی‌گردد. پاسخ از طریق رویداد appmint:contacts (و window.onAppMintContacts) با detail = { requestId, contact: { id, displayName, photoUri, phones: [{ number, type }], emails: [...] } } می‌رسد. وقتی آن مخاطب دیگر وجود نداشته باشد، contact برابر null است. در صورت شکست: { requestId, error }. پیش‌نیاز: هنگام ساخت، در مرحله 4 (Access) گزینه Contacts را روشن کنید.

یک مخاطب ذخیره‌شده را دوباره بخوانید؛ به‌جای شنونده رویداد از هوک صفحه استفاده کنید.

var pendingId = '';

window.onAppMintContacts = function (detail) {
  if (detail.requestId !== pendingId) return;
  if (detail.error) { showMessage('Could not read the contact: ' + detail.error); return; }
  if (!detail.contact) { showMessage('This contact was deleted.'); return; }

  var c = detail.contact;
  document.getElementById('name').textContent = c.displayName;
  document.getElementById('phones').textContent =
    c.phones.map(function (p) { return p.type + ': ' + p.number; }).join(', ');
  document.getElementById('emails').textContent = c.emails.join(', ');
};

function openContact(contactId) {
  if (!(window.WebToApk && window.WebToApk.getContact)) return;
  pendingId = 'one-' + Date.now();
  WebToApk.getContact(pendingId, String(contactId));
}

فهرست ذخیره‌شده‌ی شناسه‌های مخاطبان مورد علاقه را تازه کنید و آن‌هایی را که از آن زمان از گوشی حذف شده‌اند کنار بگذارید:

function getOne(contactId) {
  return new Promise(function (resolve, reject) {
    var id = 'fav-' + contactId + '-' + Date.now();
    window.addEventListener('appmint:contacts', function handler(e) {
      if (!e.detail || e.detail.requestId !== id) return;
      window.removeEventListener('appmint:contacts', handler);
      if (e.detail.error) reject(new Error(e.detail.error)); else resolve(e.detail.contact);
    });
    WebToApk.getContact(id, String(contactId));
  });
}

async function loadFavourites() {
  if (!(window.WebToApk && window.WebToApk.getContact)) return;
  var ids = JSON.parse(localStorage.getItem('favIds') || '[]');
  var people = [];
  for (var i = 0; i < ids.length; i++) {
    var c = await getOne(ids[i]);
    if (c) people.push(c);   // null = deleted from the phone
  }
  localStorage.setItem('favIds', JSON.stringify(people.map(function (c) { return c.id; })));
  renderFavourites(people);
}

بررسی کنید که شماره‌ای که اپلیکیشن شما برای یک مشتری ذخیره کرده هنوز در کارت مخاطب او هست یا نه:

function checkStoredNumber(customer) {   // { contactId, phone }
  if (!(window.WebToApk && window.WebToApk.getContact)) return;
  var id = 'check-' + Date.now();

  window.addEventListener('appmint:contacts', function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;
    window.removeEventListener('appmint:contacts', handler);
    if (e.detail.error) { showMessage('Could not check: ' + e.detail.error); return; }
    if (!e.detail.contact) { showMessage('This customer is no longer in your contacts.'); return; }

    var digits = function (s) { return String(s).replace(/\D/g, ''); };
    var still = e.detail.contact.phones.some(function (p) {
      return digits(p.number).slice(-10) === digits(customer.phone).slice(-10);
    });
    if (!still) showMessage('The number for ' + e.detail.contact.displayName + ' has changed.');
  });

  WebToApk.getContact(id, String(customer.contactId));
}

نکته‌ها: window.onAppMintContacts هر پاسخ مربوط به مخاطبان را دریافت می‌کند (فهرست، جست‌وجو، انتخاب، افزودن)، پس همیشه requestId را بررسی کنید. می‌توانید از آن یا addEventListener('appmint:contacts', ...) استفاده کنید؛ هر دو فعال می‌شوند. خطاها: not_enabled، permission_denied، foreign_origin. مقدار id از pickContact شناسه‌ی یک ردیف تلفن است، نه شناسه‌ی مخاطب، بنابراین اینجا کار نمی‌کند. مخاطبان داده‌ی شخصی هستند: فهرست شما در Play Store باید آن را در فرم Data safety و در سیاست حفظ حریم خصوصی اعلام کند.

listContacts پل#

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

همه‌چیز ناهمگام است: خواندن با کرسر می‌تواند هزاران ردیف باشد و بازگشت مسدودکننده‌ی @JavascriptInterface آن را روی ترد جاوااسکریپت اجرا می‌کند و باعث ANR می‌شود. نتایج به‌صورت `appmint:contacts` / window.onAppMintContacts({requestId,...}) می‌رسند. requestId: یک رشته بدهید ('pick-' + Date.now()). یک عدد خام هم کار می‌کند؛ پل آن را از طریق double تبدیل می‌کند ("1.787491234567E12") و هر پاسخ دوباره آن را به حالت استاندارد برمی‌گرداند (BridgeRequestId)، بنابراین `res.requestId === Date.now()` در هر دو حالت درست از آب درمی‌آید. WebToApk.listContacts(id, limit, offset) WebToApk.searchContacts(id, query, limit) WebToApk.getContact(id, contactId) WebToApk.pickContact(id) <- بدون نیاز به مجوز (انتخابگر سیستم) WebToApk.addContact(id, json) <- به مجوز نوشتن مخاطبان نیاز دارد

به‌جای توضیح جداگانه، در گروهش توضیح داده شده است: Contacts.

نمونه

دفترچه‌ی تلفن گوشی را صفحه به صفحه و به ترتیب الفبایی نام می‌خواند.

مقدار بازگشتی: فعلاً چیزی برنمی‌گردد. پاسخ بعداً از طریق رویداد appmint:contacts (و window.onAppMintContacts) با detail = { requestId, contacts: [{ id, displayName, photoUri, phones: [{ number, type }], emails: ['[email protected]'] }] } یا { requestId, error } می‌رسد. پیش‌نیاز: هنگام ساخت، در مرحله 4 (Access) گزینه Contacts را روشن کنید؛ در اولین فراخوانی از کاربر مجوز خواسته می‌شود.

100 مخاطب اول را بارگذاری کنید. پاسخ را با requestId.

function loadContacts() {
  if (!(window.WebToApk && window.WebToApk.listContacts)) {
    showMessage('Contacts work only inside the app.');
    return;
  }
  var id = 'contacts-' + Date.now();

  window.addEventListener('appmint:contacts', function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;   // another call's answer
    window.removeEventListener('appmint:contacts', handler);

    if (e.detail.error) { showMessage('Could not read contacts: ' + e.detail.error); return; }

    var list = document.getElementById('list');
    list.innerHTML = '';
    e.detail.contacts.forEach(function (c) {
      var li = document.createElement('li');
      var num = c.phones.length ? c.phones[0].number + ' (' + c.phones[0].type + ')' : '';
      li.textContent = c.displayName + ' ' + num;
      list.appendChild(li);
    });
  });

  WebToApk.listContacts(id, 100, 0);   // limit 100, start at 0
}

صفحه‌ی بعد. عددی را که از قبل دارید به‌عنوان offset: WebToApk.listContacts(id, 100, 100).

کل دفترچه‌ی تلفن را در صفحه‌های 500 تایی بارگذاری کنید تا وقتی یک صفحه‌ی کوتاه نشان دهد به انتها رسیده‌اید:

function listPage(limit, offset) {
  return new Promise(function (resolve, reject) {
    var id = 'page-' + offset + '-' + Date.now();
    window.addEventListener('appmint:contacts', function handler(e) {
      if (!e.detail || e.detail.requestId !== id) return;
      window.removeEventListener('appmint:contacts', handler);
      if (e.detail.error) reject(new Error(e.detail.error));
      else resolve(e.detail.contacts);
    });
    WebToApk.listContacts(id, limit, offset);
  });
}

async function loadEveryContact() {
  if (!(window.WebToApk && window.WebToApk.listContacts)) return [];
  var all = [];
  while (true) {
    var page = await listPage(500, all.length);
    all = all.concat(page);
    if (page.length < 500) return all;   // last page
  }
}

فهرست «دعوت از دوستان» را فقط از شماره‌های موبایل بسازید و وقتی خواندن رد شد، به کاربر بگویید چه کند:

function showInviteList() {
  if (!(window.WebToApk && window.WebToApk.listContacts)) return;
  var id = 'invite-' + Date.now();

  window.addEventListener('appmint:contacts', function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;
    window.removeEventListener('appmint:contacts', handler);

    if (e.detail.error === 'permission_denied') { showMessage('Allow Contacts to invite friends.'); return; }
    if (e.detail.error) { showMessage('Contacts are not available: ' + e.detail.error); return; }

    var mobiles = [];
    e.detail.contacts.forEach(function (c) {
      c.phones.forEach(function (p) {
        if (p.type === 'mobile') mobiles.push({ name: c.displayName, number: p.number });
      });
    });
    renderInviteList(mobiles);   // your own list UI
  });

  WebToApk.listContacts(id, 500, 0);
}

نکته‌ها: مقدار limit بین 1 تا 2000 محدود می‌شود. خطاها: not_enabled (گزینه Contacts هنگام ساخت خاموش بوده)، permission_denied (کاربر «نه» گفته)، foreign_origin (از صفحه‌ای فراخوانی شده که متعلق به اپلیکیشن خودتان نیست). type تلفن یکی از این‌هاست: home، mobile، work، main، work_fax، home_fax یا other. photoUri یک نشانی content:// است، نه نشانی تصویری که صفحه بتواند بارگذاری کند. اگر می‌خواهید کاربر فقط یک نفر را انتخاب کند، به‌جای آن از pickContact استفاده کنید؛ این روش به هیچ مجوزی نیاز ندارد. مخاطبان داده‌ی شخصی هستند: فهرست شما در Play Store باید آن را در فرم Data safety و در سیاست حفظ حریم خصوصی اعلام کند.

pickContact پل#

window.WebToApk.pickContact(requestId: String)

انتخاب‌گر مخاطب سیستم - هیچ مجوزی نمی‌خواهد و هر وقت صفحه فقط می‌خواهد کاربر یک نفر را انتخاب کند، انتخاب درست است. از جدول PHONE انتخاب می‌کند، نه جدول مخاطب: انتخاب‌گر فقط به ردیفی که برمی‌گرداند دسترسی خواندن می‌دهد، پس انتخاب یک ردیف تلفن همان چیزی است که شماره را بدون READ_CONTACTS قابل‌خواندن می‌کند. انتخاب یک مخاطب خالی ردیفی با نام و بدون شماره برمی‌گرداند، و خواندن شماره از آن به همان مجوزی نیاز دارد که این متد برای دورزدنش وجود دارد.

نمونه

انتخابگر مخاطب خود Android را باز می‌کند تا کاربر یک شماره‌ی تلفن انتخاب کند. به مجوز نیازی نیست.

مقدار بازگشتی: فعلاً چیزی برنمی‌گردد. پاسخ از طریق رویداد appmint:contacts (و window.onAppMintContacts) با detail = { requestId, contact: { id, displayName, photoUri, phones: [{ number, type: 'picked' }], emails: [] } } می‌رسد. اگر کاربر انتخابگر را ببندد: { requestId, error: 'cancelled' }. پیش‌نیاز: هیچ؛ نه گزینه‌ی ساخت و نه درخواست مجوز.

فرم را با شخص انتخاب‌شده پر کنید.

function chooseContact() {
  if (!(window.WebToApk && window.WebToApk.pickContact)) {
    showMessage('Open this page in the app to pick a contact.');
    return;
  }
  var id = 'pick-' + Date.now();

  window.addEventListener('appmint:contacts', function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;
    window.removeEventListener('appmint:contacts', handler);

    if (e.detail.error === 'cancelled') return;              // user closed the picker
    if (e.detail.error) { showMessage('Could not open contacts'); return; }
    if (!e.detail.contact) { showMessage('Could not read that contact'); return; }

    var c = e.detail.contact;
    document.getElementById('name').value = c.displayName;
    document.getElementById('phone').value = c.phones.length ? c.phones[0].number : '';
  });

  WebToApk.pickContact(id);
}

فهرست کوتاهی از مخاطبان اضطراری نگه دارید. نام و شماره را ذخیره کنید، نه id، تا فهرست به دفترچه‌ی تلفن وابسته نباشد:

function pickOne() {
  return new Promise(function (resolve, reject) {
    var id = 'sos-' + Date.now();
    window.addEventListener('appmint:contacts', function handler(e) {
      if (!e.detail || e.detail.requestId !== id) return;
      window.removeEventListener('appmint:contacts', handler);
      if (e.detail.error === 'cancelled') resolve(null);
      else if (e.detail.error) reject(new Error(e.detail.error));
      else resolve(e.detail.contact);
    });
    WebToApk.pickContact(id);
  });
}

document.getElementById('add-sos').addEventListener('click', async function () {
  if (!(window.WebToApk && window.WebToApk.pickContact)) return;
  var c = await pickOne();
  if (!c || !c.phones.length) return;   // closed, or nothing readable
  var list = JSON.parse(localStorage.getItem('sos') || '[]');
  list.push({ name: c.displayName, number: c.phones[0].number });
  localStorage.setItem('sos', JSON.stringify(list));
  renderSosList(list);
});

وقتی انتخابگر باز است دکمه را غیرفعال کنید و وقتی گوشی برنامه‌ی مخاطبان ندارد، فیلدهای تایپی را نشان دهید:

var pickBtn = document.getElementById('pick');

pickBtn.addEventListener('click', function () {
  if (!(window.WebToApk && window.WebToApk.pickContact)) { showManualEntry(); return; }
  var id = 'recipient-' + Date.now();
  pickBtn.disabled = true;   // one picker at a time

  window.addEventListener('appmint:contacts', function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;
    window.removeEventListener('appmint:contacts', handler);
    pickBtn.disabled = false;

    if (e.detail.error === 'no_picker') { showManualEntry(); return; }
    if (e.detail.error) return;   // 'cancelled'
    if (e.detail.contact && e.detail.contact.phones.length) {
      document.getElementById('to').value = e.detail.contact.phones[0].number;
    }
  });

  WebToApk.pickContact(id);
});

نکته‌ها: نام و فقط ONE شماره‌ای را که کاربر لمس کرده می‌گیرید؛ هرگز ایمیل‌ها و بقیه‌ی دفترچه را نمی‌گیرید. emails همیشه خالی است. خطاها: cancelled، no_picker (گوشی برنامه‌ی مخاطبان ندارد). اگر خواندن پس از انتخاب شکست بخورد، contact برابر null است. چون بدون انتخاب کاربر چیزی خوانده نمی‌شود، این روش مناسب Play برای گرفتن شماره‌ی تلفن است؛ وقتی فقط یک نفر لازم دارید آن را به listContacts ترجیح دهید. برای اپلیکیشن‌های ساخته‌شده با هوش مصنوعی و واردشده از Google AI Studio، ساخت کدتان را اسکن می‌کند؛ فراخوانی pickContact( گزینه‌ی Contacts را تیک نمی‌زند، فقط listContacts، searchContacts و getContact این کار را می‌کنند، پس انتخابگر مجوز خواندن مخاطبان اضافه نمی‌کند.

searchContacts پل#

window.WebToApk.searchContacts(requestId: String, query: String, limit: Int)

همه‌چیز ناهمگام است: خواندن با کرسر می‌تواند هزاران ردیف باشد و بازگشت مسدودکننده‌ی @JavascriptInterface آن را روی ترد جاوااسکریپت اجرا می‌کند و باعث ANR می‌شود. نتایج به‌صورت `appmint:contacts` / window.onAppMintContacts({requestId,...}) می‌رسند. requestId: یک رشته بدهید ('pick-' + Date.now()). یک عدد خام هم کار می‌کند؛ پل آن را از طریق double تبدیل می‌کند ("1.787491234567E12") و هر پاسخ دوباره آن را به حالت استاندارد برمی‌گرداند (BridgeRequestId)، بنابراین `res.requestId === Date.now()` در هر دو حالت درست از آب درمی‌آید. WebToApk.listContacts(id, limit, offset) WebToApk.searchContacts(id, query, limit) WebToApk.getContact(id, contactId) WebToApk.pickContact(id) <- بدون نیاز به مجوز (انتخابگر سیستم) WebToApk.addContact(id, json) <- به مجوز نوشتن مخاطبان نیاز دارد

به‌جای توضیح جداگانه، در گروهش توضیح داده شده است: Contacts.

نمونه

مخاطبانی را که نامشان با کلمه‌ی جست‌وجو مطابقت دارد پیدا می‌کند، بدون بارگذاری کل دفترچه‌ی تلفن.

مقدار بازگشتی: فعلاً چیزی برنمی‌گردد. پاسخ از طریق رویداد appmint:contacts (و window.onAppMintContacts) با detail = { requestId, contacts: [...] } می‌رسد، که همان شکل مخاطبِ listContacts است، یا { requestId, error }. پیش‌نیاز: هنگام ساخت، در مرحله 4 (Access) گزینه Contacts را روشن کنید؛ در اولین فراخوانی از کاربر مجوز خواسته می‌شود.

همزمان با تایپ کاربر جست‌وجو کنید. فقط پاسخ آخرین جست‌وجو نشان داده می‌شود.

var lastSearchId = '';

function searchPeople(word) {
  if (!(window.WebToApk && window.WebToApk.searchContacts)) return;
  lastSearchId = 'search-' + Date.now();
  WebToApk.searchContacts(lastSearchId, word, 20);   // at most 20 results
}

window.addEventListener('appmint:contacts', function (e) {
  if (!e.detail || e.detail.requestId !== lastSearchId) return;   // old or other request
  if (e.detail.error) { showMessage('Search failed: ' + e.detail.error); return; }

  var names = e.detail.contacts.map(function (c) {
    return c.displayName + (c.phones.length ? ' - ' + c.phones[0].number : '');
  });
  document.getElementById('results').textContent = names.join('\n') || 'No match';
});

document.getElementById('q').addEventListener('input', function (e) {
  searchPeople(e.target.value);
});

جست‌وجو را در یک Promise بپیچید و صبر کنید تا کاربر تایپ را متوقف کند، تا تایپ سریع برای هر کلید یک جست‌وجو شروع نکند:

function searchOnce(word, limit) {
  return new Promise(function (resolve, reject) {
    var id = 'find-' + Date.now() + '-' + Math.random().toString(36).slice(2);
    window.addEventListener('appmint:contacts', function handler(e) {
      if (!e.detail || e.detail.requestId !== id) return;
      window.removeEventListener('appmint:contacts', handler);
      if (e.detail.error) reject(new Error(e.detail.error)); else resolve(e.detail.contacts);
    });
    WebToApk.searchContacts(id, word, limit);
  });
}

var timer = null;
document.getElementById('q').addEventListener('input', function (e) {
  clearTimeout(timer);
  var word = e.target.value.trim();
  timer = setTimeout(function () {
    if (!(window.WebToApk && window.WebToApk.searchContacts) || !word) return;
    searchOnce(word, 10)
      .then(function (people) { renderSuggestions(people); })
      .catch(function (err) { showMessage('Search failed: ' + err.message); });
  }, 300);
});

وقتی نام تایپ‌شده با یک نفر که ایمیل دارد مطابقت می‌کند، کادر ایمیل را از دفترچه‌ی تلفن پر کنید:

function suggestEmail(name) {
  if (!(window.WebToApk && window.WebToApk.searchContacts) || name.length < 2) return;
  var id = 'email-' + Date.now();

  window.addEventListener('appmint:contacts', function handler(e) {
    if (!e.detail || e.detail.requestId !== id) return;
    window.removeEventListener('appmint:contacts', handler);
    if (e.detail.error) return;   // leave the box for the user to type

    var withEmail = e.detail.contacts.filter(function (c) { return c.emails.length > 0; });
    if (withEmail.length === 1) {
      document.getElementById('email').value = withEmail[0].emails[0];
    }
  });

  WebToApk.searchContacts(id, name, 5);
}

document.getElementById('name').addEventListener('change', function (e) {
  suggestEmail(e.target.value.trim());
});

نکته‌ها: کلمه‌ی جست‌وجوی خالی مخاطبان را از ابتدای فهرست برمی‌گرداند (مثل listContacts). limit بین 1 تا 2000 محدود می‌شود و offset وجود ندارد. خطاها: not_enabled، permission_denied، foreign_origin. مخاطبان داده‌ی شخصی هستند: فهرست شما در Play Store باید آن را در فرم Data safety و در سیاست حفظ حریم خصوصی اعلام کند.