API del bridge JavaScript
Contatti
Lettura, ricerca, selezione e scrittura: il selettore non richiede alcuna autorizzazione.
addContact bridge#
window.WebToApk.addContact(requestId: String, contactJson: String)
Il selettore di sistema (nessuna autorizzazione necessaria) e la scrittura di un nuovo contatto (WRITE_CONTACTS). La parte di lettura, cioè elenco e ricerca, si trova nel gruppo Contatti qui sopra.
Descritto dal suo gruppo, Contatti: seleziona e aggiungi, invece che da solo.
Esempio
Salva un nuovo contatto direttamente nella rubrica del telefono, senza aprire l'app Contatti.
Restituisce: niente per ora. La risposta è l'evento appmint:contacts (e window.onAppMintContacts) con detail = { requestId, ok: true, id: '123' } in caso di successo, { requestId, ok: false, id: null } quando il salvataggio non è riuscito, oppure { requestId, error }. Richiede: attiva Modifica contatti nel Passaggio 4 (Accesso) quando crei l'app; all'utente viene chiesta l'autorizzazione alla prima chiamata.
Salva un cliente. Il contatto viene passato come stringa 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]
}));
}Forma breve. Sono accettati anche i valori singoli: { name: 'Ali', phone: '+911234567890', email: '[email protected]' }.
Un pulsante "Salva nei contatti" in una pagina di biglietto da visita, con la forma breve e una 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);
});
});Importa l'elenco di un team una persona alla volta, con i numeri di lavoro e di casa, e indica quanti sono stati salvati:
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');
}Note: Errori: not_enabled, permission_denied. Il id restituito è il nuovo id del raw-contact. Un type di telefono diverso da home o work viene salvato come mobile. Il contatto viene salvato sul dispositivo (nessun account). La scrittura dei contatti è un'autorizzazione sensibile: la tua scheda su Play Store deve dichiararla nel modulo Sicurezza dei dati e nell'informativa sulla privacy.
getContact bridge#
window.WebToApk.getContact(requestId: String, contactId: String)
Tutto asincrono: la lettura con cursore può riguardare migliaia di righe, e un ritorno bloccante di @JavascriptInterface la eseguirebbe sul thread JS, causando un ANR. I risultati arrivano come `appmint:contacts` / window.onAppMintContacts({requestId,...}). requestId: passa una STRINGA ('pick-' + Date.now()). Funziona anche un NUMERO grezzo: il bridge lo converte tramite un double ("1.787491234567E12") e ogni risposta lo riporta alla forma canonica (BridgeRequestId), quindi `res.requestId === Date.now()` con confronto stretto risulta vero in entrambi i casi. WebToApk.listContacts(id, limit, offset) WebToApk.searchContacts(id, query, limit) WebToApk.getContact(id, contactId) WebToApk.pickContact(id) <- NESSUNA autorizzazione necessaria (selettore di sistema) WebToApk.addContact(id, json) <- richiede l'autorizzazione di scrittura dei contatti
Descritto dal suo gruppo, Contatti, invece che da solo.
Esempio
Rilegge un contatto tramite il suo id (il id ottenuto da listContacts o searchContacts).
Restituisce: niente per ora. La risposta è l'evento appmint:contacts (e window.onAppMintContacts) con detail = { requestId, contact: { id, displayName, photoUri, phones: [{ number, type }], emails: [...] } }. contact è null quando quel contatto non esiste più. In caso di errore: { requestId, error }. Richiede: attiva Contatti nel Passaggio 4 (Accesso) quando crei l'app.
Aggiorna un contatto salvato usando l'hook della pagina invece di un listener di eventi.
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));
}Aggiorna un elenco salvato di id di contatti preferiti ed elimina quelli cancellati nel frattempo dal telefono:
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);
}Controlla se il numero che la tua app ha salvato per un cliente è ancora nella sua scheda contatto:
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));
}Note: window.onAppMintContacts riceve ogni risposta dei contatti (elenco, ricerca, selezione, aggiunta), quindi controlla sempre requestId. Puoi usare questo o addEventListener('appmint:contacts', ...): scattano entrambi. Errori: not_enabled, permission_denied, foreign_origin. Il id di pickContact è l'id di una riga di telefono, non l'id di un contatto, quindi qui non funziona. I contatti sono dati personali: la tua scheda su Play Store deve dichiararli nel modulo Sicurezza dei dati e nell'informativa sulla privacy.
listContacts bridge#
window.WebToApk.listContacts(requestId: String, limit: Int, offset: Int)
Tutto asincrono: la lettura con cursore può riguardare migliaia di righe, e un ritorno bloccante di @JavascriptInterface la eseguirebbe sul thread JS, causando un ANR. I risultati arrivano come `appmint:contacts` / window.onAppMintContacts({requestId,...}). requestId: passa una STRINGA ('pick-' + Date.now()). Funziona anche un NUMERO grezzo: il bridge lo converte tramite un double ("1.787491234567E12") e ogni risposta lo riporta alla forma canonica (BridgeRequestId), quindi `res.requestId === Date.now()` con confronto stretto risulta vero in entrambi i casi. WebToApk.listContacts(id, limit, offset) WebToApk.searchContacts(id, query, limit) WebToApk.getContact(id, contactId) WebToApk.pickContact(id) <- NESSUNA autorizzazione necessaria (selettore di sistema) WebToApk.addContact(id, json) <- richiede l'autorizzazione di scrittura dei contatti
Descritto dal suo gruppo, Contatti, invece che da solo.
Esempio
Legge la rubrica del telefono, pagina per pagina, in ordine alfabetico per nome.
Restituisce: niente per ora. La risposta arriva piĂą tardi come evento appmint:contacts (e window.onAppMintContacts) con detail = { requestId, contacts: [{ id, displayName, photoUri, phones: [{ number, type }], emails: ['[email protected]'] }] }, oppure { requestId, error }. Richiede: attiva Contatti nel Passaggio 4 (Accesso) quando crei l'app; all'utente viene chiesta l'autorizzazione alla prima chiamata.
Carica i primi 100 contatti. Abbina la risposta sul tuo 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
}Pagina successiva. Passa il numero che hai giĂ come offset: WebToApk.listContacts(id, 100, 100).
Carica l'intera rubrica a pagine di 500 finché una pagina più corta ti dice che sei arrivato alla fine:
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
}
}Crea un elenco "Invita amici" solo dai numeri di cellulare e di' all'utente cosa fare quando la lettura viene rifiutata:
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);
}Note: limit è limitato tra 1 e 2000. Errori: not_enabled (Contatti era disattivato durante la creazione), permission_denied (l'utente ha detto No), foreign_origin (chiamata da una pagina che non appartiene alla tua app). Il type del telefono è home, mobile, work, main, work_fax, home_fax o other. photoUri è un indirizzo content://, non un URL di immagine che una pagina possa caricare. Per lasciare che l'utente scelga una sola persona, usa invece pickContact: non richiede autorizzazioni. I contatti sono dati personali: la tua scheda su Play Store deve dichiararli nel modulo Sicurezza dei dati e nell'informativa sulla privacy.
pickContact bridge#
window.WebToApk.pickContact(requestId: String)
Selettore contatti di sistema: non richiede ALCUNA autorizzazione ed è la scelta giusta ogni volta che la pagina vuole solo che l'utente scelga una persona. Sceglie dalla tabella PHONE, non dalla tabella dei contatti: il selettore concede l'accesso in lettura solo alla riga che restituisce, quindi scegliere una riga di telefono è ciò che rende leggibile il numero senza READ_CONTACTS. Scegliere un contatto semplice restituirebbe una riga con un nome e nessun numero, e leggere il numero da essa richiederebbe l'autorizzazione che questo metodo esiste per evitare.
Esempio
Apre il selettore di contatti di Android così l'utente sceglie un numero di telefono. Non serve alcuna autorizzazione.
Restituisce: niente per ora. La risposta è l'evento appmint:contacts (e window.onAppMintContacts) con detail = { requestId, contact: { id, displayName, photoUri, phones: [{ number, type: 'picked' }], emails: [] } }. Se l'utente chiude il selettore: { requestId, error: 'cancelled' }. Richiede: niente: nessuna opzione di build, nessuna richiesta di autorizzazione.
Compila un modulo con la persona scelta.
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);
}Tieni un breve elenco di contatti di emergenza. Salva il nome e il numero, non il id, così l'elenco non dipende dalla rubrica:
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);
});Disattiva il pulsante mentre il selettore è aperto e mostra i campi da compilare a mano quando il telefono non ha un'app contatti:
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);
});Note: Ricevi il nome e l'UNICO numero toccato dall'utente, mai le email, mai il resto della rubrica. emails è sempre vuoto. Errori: cancelled, no_picker (nessuna app contatti sul telefono). Se la lettura fallisce dopo una selezione, contact è null. Poiché nulla viene letto senza la scelta dell'utente, è il modo adatto a Play per ottenere un numero di telefono; preferiscilo a listContacts quando ti serve una sola persona. Per le app create con l'AI e le importazioni da Google AI Studio, la build analizza il tuo codice; una chiamata a pickContact( non attiva Contatti: lo fanno solo listContacts, searchContacts e getContact, quindi il selettore non aggiunge alcuna autorizzazione di lettura dei contatti.
searchContacts bridge#
window.WebToApk.searchContacts(requestId: String, query: String, limit: Int)
Tutto asincrono: la lettura con cursore può riguardare migliaia di righe, e un ritorno bloccante di @JavascriptInterface la eseguirebbe sul thread JS, causando un ANR. I risultati arrivano come `appmint:contacts` / window.onAppMintContacts({requestId,...}). requestId: passa una STRINGA ('pick-' + Date.now()). Funziona anche un NUMERO grezzo: il bridge lo converte tramite un double ("1.787491234567E12") e ogni risposta lo riporta alla forma canonica (BridgeRequestId), quindi `res.requestId === Date.now()` con confronto stretto risulta vero in entrambi i casi. WebToApk.listContacts(id, limit, offset) WebToApk.searchContacts(id, query, limit) WebToApk.getContact(id, contactId) WebToApk.pickContact(id) <- NESSUNA autorizzazione necessaria (selettore di sistema) WebToApk.addContact(id, json) <- richiede l'autorizzazione di scrittura dei contatti
Descritto dal suo gruppo, Contatti, invece che da solo.
Esempio
Trova i contatti il cui nome corrisponde a una parola di ricerca, senza caricare l'intera rubrica.
Restituisce: niente per ora. La risposta è l'evento appmint:contacts (e window.onAppMintContacts) con detail = { requestId, contacts: [...] } (la stessa forma di contatto di listContacts) oppure { requestId, error }. Richiede: attiva Contatti nel Passaggio 4 (Accesso) quando crei l'app; all'utente viene chiesta l'autorizzazione alla prima chiamata.
Cerca mentre l'utente scrive. Viene mostrata solo la risposta all'ultima ricerca.
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);
});Racchiudi la ricerca in una Promise e aspetta che l'utente smetta di scrivere, così una digitazione veloce non avvia una ricerca a ogni tasto:
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);
});Compila il campo email dalla rubrica quando il nome digitato corrisponde a una sola persona che ha un'email:
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());
});Note: Una parola di ricerca vuota restituisce i contatti dall'inizio dell'elenco (come listContacts). limit è limitato tra 1 e 2000 e non c'è offset. Errori: not_enabled, permission_denied, foreign_origin. I contatti sono dati personali: la tua scheda su Play Store deve dichiararli nel modulo Sicurezza dei dati e nell'informativa sulla privacy.