Crear APK gratis

API del puente de JavaScript

Compartir, portapapeles e imprimir

La hoja para compartir de Android, el portapapeles del sistema, el texto que otra app compartió con la tuya y el diálogo de impresión.

AppMint.pdf ayudante#

AppMint.pdf.fromPage(options) / fromHtml(html, options)

Ejemplo

Crea un PDF real (texto seleccionable, gráficos vectoriales) sin diálogo de impresión: fromPage guarda la página tal como está en pantalla, con el diseño de tu CSS @media print; fromHtml renderiza el HTML que le pases en una página invisible en la propia dirección de la app, de modo que las imágenes, hojas de estilo y fuentes relativas se cargan como en tu página; espera a las fuentes y a todas las imágenes y luego lo guarda. window.print() sigue abriendo el diálogo de impresión normal.

Devuelve: una Promise de {ok: true, fileName, pages, bytes, saved, uri?, base64?} o {ok: false, code, message}; code es busy (un PDF a la vez), bad-options, failed, too-large (base64 de más de 25 MB) o permission-denied (almacenamiento rechazado en Android 7-9). Requiere: nada: todas las compilaciones lo tienen. Guardar en Descargas no necesita permiso en Android 10+.

Opciones: fileName (se añade ".pdf"; por defecto: el título de la página), pageSize 'A4' (por defecto) | 'Letter' | 'Legal' | 'A3' | 'A5', landscape (true/false), margins en milímetros (un número o {top, right, bottom, left}; si se omite = tu propia regla @page), save 'downloads' (por defecto) | 'share' | 'base64'.

Guardar la página actual en Descargas:

async function saveInvoice() {
  if (!(window.AppMint && AppMint.pdf)) { window.print(); return; }   // in a browser
  var r = await AppMint.pdf.fromPage({ fileName: 'invoice-1042', pageSize: 'A4', margins: 12 });
  if (r.ok) console.log('Saved ' + r.fileName + ' (' + r.pages + ' pages)');
  else alert('Could not make the PDF: ' + r.message);
}

Un recibo creado desde HTML, directo a la hoja para compartir (las imágenes y fuentes se cargan antes de crear el PDF):

async function shareReceipt(order) {
  var html = '<style>body{font-family:sans-serif} td{padding:4px 8px}</style>' +
    '<img src="img/logo.png" width="120"><h1>Receipt #' + order.id + '</h1><table>' +
    order.items.map(function (i) { return '<tr><td>' + i.name + '</td><td>' + i.price + '</td></tr>'; }).join('') +
    '</table><p><b>Total ' + order.total + '</b></p>';
  var r = await AppMint.pdf.fromHtml(html, { fileName: 'receipt-' + order.id, pageSize: 'Letter', save: 'share' });
  if (!r.ok) console.warn(r.code, r.message);
}

Recupera los bytes para subirlos tú mismo:

async function uploadReport() {
  var r = await AppMint.pdf.fromPage({ landscape: true, margins: { top: 10, right: 8, bottom: 10, left: 8 }, save: 'base64' });
  if (!r.ok) return;
  var bin = atob(r.base64), buf = new Uint8Array(bin.length);
  for (var i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
  await fetch('/api/reports', { method: 'POST', headers: { 'Content-Type': 'application/pdf' }, body: buf });
}

Las llamadas directas reciben las opciones como texto JSON y un id de callback; usa mejor el helper anterior:

if (window.WebToApk && typeof window.WebToApk.pdfFromPage === 'function') {
  // AppMint.pdf.fromPage wraps: WebToApk.pdfFromPage(JSON.stringify(options), callbackId)
  // and AppMint.pdf.fromHtml wraps: WebToApk.pdfFromHtml(html, JSON.stringify(options), callbackId)
  AppMint.pdf.fromPage({ save: 'downloads' }).then(function (r) { console.log(r); });
}

Notas: Oculta botones y menús del PDF con @media print { .no-print { display: none } }. Un PDF descargado muestra «Descargado <name> · Abrir» como cualquier descarga. fromHtml ejecuta los scripts del HTML (aparecen los gráficos dibujados por script) pero no tiene acceso a las funciones nativas de la app. Las páginas más largas que una hoja se dividen como lo haría Chrome; usa break-before: page para controlar dónde.

navigator.clipboard estándar web#

navigator.clipboard.writeText / readText (and write / read)

Ejemplo

La API estándar del portapapeles, respondida por el portapapeles real del teléfono dentro de la app.

Devuelve: writeText(text) → una Promise que se resuelve cuando se copia y se rechaza con un NotAllowedError en caso contrario. readText() → una Promise con el texto del portapapeles ("" si está vacío). write([item]) / read() → Promises, como en el estándar. Requiere: nada. Se instala al inicio del documento, así que una comprobación hecha mientras tu página se analiza ya lo ve.

Copiar:

async function copyLink(link) {
  if (!(navigator.clipboard && navigator.clipboard.writeText)) { alert('Copy is not available'); return; }
  try {
    await navigator.clipboard.writeText(link);
    showMessage('Link copied');
  } catch (e) {
    showMessage('Could not copy');
  }
}

Pegar, desde un toque:

document.getElementById('paste').onclick = async function () {
  try {
    var text = await navigator.clipboard.readText();
    document.getElementById('input').value = text;
  } catch (e) {
    showMessage('Could not read the clipboard');
  }
};

Copia una imagen (por ejemplo un código QR dibujado en un canvas) con ClipboardItem:

async function copyCanvas(canvas) {
  if (!(navigator.clipboard && navigator.clipboard.write && window.ClipboardItem)) {
    alert('Copying images is not available here');
    return;
  }
  var blob = await new Promise(function (r) { canvas.toBlob(r, 'image/png'); });
  try {
    await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
    showMessage('Image copied');
  } catch (e) {
    showMessage('Could not copy the image');
  }
}

Notas: En la app, writeText/readText llaman a WebToApk.copyToClipboard y WebToApk.readClipboard. write() toma solo el primer ClipboardItem y admite text/plain, text/html y una imagen (hasta 16 MB; una imagen gana al texto en el mismo elemento). read() se resuelve a [item] con el texto, HTML o imagen del portapapeles ([] cuando está vacío). Solo las páginas propias de la app pueden leerlo: un sitio web ajeno abierto dentro de la app es rechazado (NotAllowedError). Léelo con un toque del usuario: Android 12+ muestra un aviso de «pegado desde el portapapeles». En un navegador el mismo código funciona con las reglas de permisos del propio navegador.

navigator.share estándar web#

navigator.share(data) / navigator.canShare(data)

Ejemplo

La API estándar Web Share, adaptada para funcionar dentro de la app: el texto y los enlaces abren la hoja para compartir de Android, y files se comparten como archivos reales (WhatsApp, Gmail, Drive…).

Devuelve: navigator.share → una Promise que se resuelve cuando se abre la hoja (no te dice qué app se eligió). navigator.canShare → true/false. Requiere: nada. Se instala al inicio del documento, así que una comprobación hecha mientras tu página se analiza ya lo ve.

Un texto y un enlace:

async function shareItem(item) {
  if (!navigator.share) { alert('Sharing is not available here'); return; }
  try {
    await navigator.share({ title: item.name, text: 'Look at this', url: item.link });
  } catch (e) { /* in a browser: AbortError when the user closes the sheet */ }
}

Archivos: comprueba primero con canShare:

async function sharePdf(blob) {
  var file = new File([blob], 'invoice.pdf', { type: 'application/pdf' });
  var data = { files: [file], title: 'Invoice', text: 'Invoice for March' };
  if (!(navigator.canShare && navigator.canShare(data))) {
    alert('File sharing is not available here');
    return;
  }
  try {
    await navigator.share(data);
  } catch (e) { console.log('Share failed', e); }
}

Varios archivos elegidos por el usuario: las fotos de un mismo tipo se comparten con ese tipo:

document.getElementById('photos').onchange = async function (e) {
  var files = Array.from(e.target.files);
  if (!files.length) return;
  var data = { files: files, title: 'Trip photos' };
  if (!(navigator.canShare && navigator.canShare(data))) {
    alert('File sharing is not available here');
    return;
  }
  try {
    await navigator.share(data);
  } catch (err) { console.log('Share failed', err); }
};

Notas: En la app title se convierte en el asunto, y text + url se envían como un solo texto. Con files, cada archivo se lee en la página y se envía como base64 (WebToApk.shareFiles), así que mantén los archivos en pocos MB. Si la hoja para compartir no puede abrirse, los archivos se guardan como descargas y la promesa se resuelve igualmente. canShare devuelve true para cualquier texto compartido. Las apps creadas con IA pueden usar share desde @/lib/appmintNative.

window.print estándar web#

window.print()

Ejemplo

La llamada de impresión estándar, adaptada para funcionar dentro de la app: abre la pantalla de impresión de Android, donde el usuario imprime en papel o guarda un PDF.

Devuelve: nada. Requiere: nada.

document.getElementById('print-receipt').onclick = function () {
  window.print();
};

Oculta los controles de la app en el papel con una hoja de estilos de impresión:

<style>
  @media print {
    nav, .toolbar, button { display: none !important; }
    body { background: #fff; color: #000; }
  }
</style>

Imprime una etiqueta de envío sin la página alrededor: un iframe srcdoc del mismo origen imprime solo su propio documento:

function esc(s) {
  return String(s).replace(/[&<>"]/g, function (c) {
    return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c];
  });
}
function printLabel(name, address) {
  var frame = document.createElement('iframe');
  frame.style.cssText = 'position:fixed;width:0;height:0;border:0';
  frame.srcdoc = '<title>Label</title><style>body{font:18px sans-serif;margin:24px}</style>' +
                 '<b>' + esc(name) + '</b><br>' + esc(address).replace(/\n/g, '<br>');
  frame.onload = function () {
    frame.contentWindow.print();
    setTimeout(function () { frame.remove(); }, 1000);
  };
  document.body.appendChild(frame);
}

Notas: window.print() imprime la página principal tal como está dibujada ahora. Un <iframe> del mismo origen (el iframe oculto que una biblioteca de impresión rellena con un recibo y al que llama iframe.contentWindow.print(), o una página en un iframe que llama a su propio window.print()) imprime el documento de ESE iframe (ver __printHtml). Un iframe de otro origen no se puede imprimir desde la página. El reemplazo existe antes del primer script de la página. beforeprint no se puede cancelar (en ningún navegador).

__clipboardRead puente#

window.WebToApk.__clipboardRead(): String

navigator.clipboard.read(): `{parts:[{type, text|base64}]}`. Mismo alcance que readClipboard() en esta shell (que no tiene control de origen).

Ejemplo

Transporte interno detrás de navigator.clipboard.read(). Las páginas usan la API estándar; nunca llames a esto directamente: los métodos __ pueden cambiar sin previo aviso.

Devuelve: {parts:[{type, text|base64}]}: el mismo alcance que readClipboard() en esta app. Requiere: nada que activar.

Usa la API pública:

canvas.toBlob(async (png) => {
  await navigator.clipboard.write([new ClipboardItem({ 'image/png': png })]);
});
const [item] = await navigator.clipboard.read();
if (item && item.types.includes('image/png')) preview(await item.getType('image/png'));

Pega texto con formato en un editor, conservando el formato cuando el portapapeles tiene HTML:

async function pasteRich(editor) {
  try {
    var items = await navigator.clipboard.read();
    if (!items.length) { showMessage('The clipboard is empty'); return; }
    var item = items[0];
    if (item.types.includes('text/html')) {
      editor.insertHtml(sanitize(await (await item.getType('text/html')).text()));
    } else if (item.types.includes('text/plain')) {
      editor.insertText(await (await item.getType('text/plain')).text());
    }
  } catch (e) {
    showMessage('Could not read the clipboard');
  }
}

Pega cualquier imagen copiada (PNG, JPEG, WebP…) como foto de perfil y distingue una lectura rechazada:

document.getElementById('paste-photo').onclick = async function () {
  try {
    var items = await navigator.clipboard.read();
    var item = items[0];
    var type = item && item.types.find(function (t) { return t.indexOf('image/') === 0; });
    if (!type) { showMessage('Copy a picture first'); return; }
    uploadAvatar(await item.getType(type));   // a Blob of that type
  } catch (e) {
    if (e.name === 'NotAllowedError') showMessage('This page cannot read the clipboard');
    else showMessage('Could not paste the picture');
  }
};

Notas: Android 12+ muestra su propio mensaje de «pegado desde tu portapapeles» cuando una app lee el portapapeles.

__clipboardWrite puente#

window.WebToApk.__clipboardWrite(partsJson: String): Boolean

navigator.clipboard.write(): [partsJson] es `[{type, text}|{type, base64}]` para un ClipboardItem: text/plain, text/html o una imagen que se copia como URI de contenido.

Ejemplo

Transporte interno detrás de navigator.clipboard.write([ClipboardItem]). Las páginas usan la API estándar; nunca llames a esto directamente: los métodos __ pueden cambiar sin previo aviso.

Devuelve: true cuando se colocó el contenido. Requiere: nada que activar.

Usa la API pública:

canvas.toBlob(async (png) => {
  await navigator.clipboard.write([new ClipboardItem({ 'image/png': png })]);
});
const [item] = await navigator.clipboard.read();
if (item && item.types.includes('image/png')) preview(await item.getType('image/png'));

Copia texto con formato y su versión sin formato en un solo elemento, para que los editores ricos reciban la tabla y los campos simples reciban el texto:

async function copyTable(table) {
  if (!(navigator.clipboard && navigator.clipboard.write && window.ClipboardItem)) return;
  try {
    await navigator.clipboard.write([new ClipboardItem({
      'text/html': new Blob([table.outerHTML], { type: 'text/html' }),
      'text/plain': new Blob([table.innerText], { type: 'text/plain' })
    })]);
    showMessage('Table copied');
  } catch (e) {
    showMessage('Could not copy (' + e.name + ')');   // NotAllowedError when the write fails
  }
}

Copia una imagen que muestra la página, como un código QR, para poder pegarla en un chat:

async function copyImage(img) {
  try {
    var blob = await (await fetch(img.src)).blob();   // an image/* type, up to 16 MB
    await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
    showMessage('Image copied');
  } catch (e) {
    showMessage('Could not copy the image');
  }
}

Notas: Texto, HTML y una imagen por elemento. La imagen se copia como URI de contenido mediante el FileProvider de la app, así que se pega en WhatsApp, Gmail o Keep.

__printHtml puente#

window.WebToApk.__printHtml(html: String, baseUrl: String, title: String)

Imprime [html] (el documento serializado de un iframe del mismo origen, enviado por el shim de `window.print()` ([PrintShim])) mediante un WebView fuera de pantalla en [baseUrl], porque el adaptador de impresión del WebView principal solo puede imprimir la página completa. [title] da nombre al trabajo de impresión («<App name> Document» cuando está en blanco).

Ejemplo

Transporte interno detrás de la impresión de un iframe: iframe.contentWindow.print() (y el propio window.print() de una página en un iframe) envía aquí el documento de ese iframe, y la app lo imprime mediante la pantalla de impresión de Android. Las páginas usan las llamadas estándar; nunca llames a esto directamente.

Devuelve: nada. Requiere: nada.

El patrón de recibo que esto hace posible: un iframe oculto relleno solo con el recibo:

function printReceipt(order) {
  var frame = document.createElement('iframe');
  frame.style.display = 'none';
  document.body.appendChild(frame);
  var doc = frame.contentWindow.document;
  doc.open();
  doc.write('<html><head><title>Receipt ' + order.id + '</title>' +
            '<style>body{font:14px sans-serif} td{padding:2px 8px}</style></head><body>' +
            '<h2>Receipt ' + order.id + '</h2><table>' +
            order.lines.map(function (l) { return '<tr><td>' + l.name + '</td><td>' + l.price + '</td></tr>'; }).join('') +
            '</table></body></html>');
  doc.close();
  frame.contentWindow.print();   // prints the RECEIPT, not the page behind it
  setTimeout(function () { frame.remove(); }, 1000);
}

Una página de ticket propia, cargada en un iframe oculto del mismo origen: se cargan su CSS e imágenes, y su <title> da nombre al trabajo de impresión:

function printTicket(id) {
  var frame = document.createElement('iframe');
  frame.style.cssText = 'position:fixed;width:0;height:0;border:0';
  frame.src = 'ticket.html?id=' + encodeURIComponent(id);
  frame.onload = function () {
    frame.contentWindow.print();   // prints ticket.html's DOM as it stands now
    setTimeout(function () { frame.remove(); }, 1000);
  };
  document.body.appendChild(frame);
}

Ajusta el documento del iframe justo antes de la captura con beforeprint en la ventana del iframe:

function printWithTimestamp(frame) {
  var w = frame.contentWindow;
  w.addEventListener('beforeprint', function () {
    var p = w.document.createElement('p');
    p.textContent = 'Printed ' + new Date().toLocaleString();
    w.document.body.appendChild(p);
  }, { once: true });
  w.print();   // the printed copy includes the line added above
}

Notas: Los argumentos son (html, baseUrl, title): el documento serializado del iframe, la dirección con la que se resuelven sus enlaces relativos (la dirección de la propia página para un iframe about:blank) y el nombre del trabajo de impresión (el <title> del iframe, o si no el de la página). La captura se toma cuando se llama a print(); los scripts dentro no se vuelven a ejecutar, y beforeprint / afterprint se activan en la ventana del iframe antes y después. Un iframe de otro origen no se puede imprimir desde la página.

clearSharedText puente#

window.WebToApk.clearSharedText()

Borra lo compartido guardado una vez que la página lo ha consumido.

Ejemplo

Olvida el texto o enlace que se compartió con la app, una vez que tu página lo ha usado.

Devuelve: nada. Requiere: nada (lo compartido en sí requiere Recibir contenido compartido desde otras apps con Texto y enlaces marcado).

Lee lo compartido guardado, úsalo y luego bórralo para que no se procese de nuevo tras recargar:

if (window.WebToApk && typeof window.WebToApk.getSharedText === 'function') {
  var raw = window.WebToApk.getSharedText();
  if (raw) {
    var shared = JSON.parse(raw);          // { text, subject, url, receivedAt }
    saveNote(shared.subject, shared.text);
    window.WebToApk.clearSharedText();     // getSharedText() now returns ""
  }
}

window.addEventListener('appmint:shared', function (e) {
  saveNote(e.detail.subject, e.detail.text);
  if (window.WebToApk && window.WebToApk.clearSharedText) window.WebToApk.clearSharedText();
});

Pregunta primero y borra lo compartido sea cual sea el botón que toque el usuario:

function askAboutShare(s) {
  var dialog = document.getElementById('share-dialog');
  document.getElementById('share-preview').textContent = s.text;
  dialog.hidden = false;
  function done() {
    dialog.hidden = true;
    if (window.WebToApk && typeof window.WebToApk.clearSharedText === 'function') {
      window.WebToApk.clearSharedText();
    }
  }
  document.getElementById('share-save').onclick = function () { saveNote(s.subject, s.text); done(); };
  document.getElementById('share-discard').onclick = done;
}

Bórralo solo cuando esté guardado de forma segura, para poder reintentar una subida fallida:

async function importShare() {
  if (!(window.WebToApk && typeof window.WebToApk.getSharedText === 'function')) return;
  var raw = window.WebToApk.getSharedText();
  if (!raw) return;
  var s = JSON.parse(raw);
  try {
    var res = await fetch('/api/bookmarks', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ url: s.url || s.text, title: s.subject })
    });
    if (!res.ok) throw new Error('HTTP ' + res.status);
    window.WebToApk.clearSharedText();
  } catch (e) {
    showMessage('Could not save the link — tap Retry');   // Retry calls importShare() again
  }
}

Notas: Borrar solo afecta a getSharedText(). Un nuevo contenido compartido reemplaza al guardado y activa appmint:shared de nuevo. Los archivos compartidos con la app se liberan por separado con AppMint.releaseOpenedFile().

copyToClipboard puente#

window.WebToApk.copyToClipboard(text: String): Boolean

Usa el envoltorio de promesa: const r = await AppMint.requestReview(); // {result, openedStore} const r = await AppMint.requestReview({ openStore: true }); // ficha de la tienda si no hay hoja r.result: 'shown' | 'not_from_play' | 'quota' | 'error'. Consulta RatePrompt.

Descrito por su grupo, La hoja de reseñas dentro de la app de Google Play, y no por sí mismo.

Ejemplo

Copia texto sin formato al portapapeles del teléfono. Las páginas normalmente usan el navigator.clipboard.writeText(text) estándar, que la shell redirige aquí.

Devuelve: true si tuvo éxito, false si falló (de forma síncrona). Requiere: nada.

Primero la API estándar:

document.getElementById('copy-btn').onclick = async function () {
  try {
    await navigator.clipboard.writeText('PROMO-2026');
    showMessage('Copied');
  } catch (e) {
    showMessage('Could not copy');
  }
};

La llamada en bruto:

if (window.WebToApk && typeof window.WebToApk.copyToClipboard === 'function') {
  var ok = window.WebToApk.copyToClipboard('PROMO-2026');
  showMessage(ok ? 'Copied' : 'Could not copy');
}

Un botón «Copiar» en cada bloque de código, usando la respuesta síncrona para actualizar la etiqueta:

document.querySelectorAll('pre').forEach(function (pre) {
  var btn = document.createElement('button');
  btn.textContent = 'Copy';
  btn.onclick = function () {
    var ok = !!(window.WebToApk && typeof window.WebToApk.copyToClipboard === 'function' &&
                window.WebToApk.copyToClipboard(pre.innerText));
    btn.textContent = ok ? 'Copied' : 'Copy failed';
    setTimeout(function () { btn.textContent = 'Copy'; }, 1500);
  };
  pre.parentNode.insertBefore(btn, pre);
});

Notas: Solo texto sin formato (sin imágenes ni HTML). Android 13+ muestra su propia pequeña vista previa de «Copiado», así que quizá no necesites tu propio mensaje ahí.

getSharedText puente#

window.WebToApk.getSharedText(): String

Texto o un enlace que otra app compartió CON esta («Compartir con <app>»; un video de YouTube compartido desde la app de YouTube llega como su enlace), como JSON `{text, subject, url, receivedAt}` o "" cuando no se compartió nada. Se conserva hasta que se lee, así que una app que se monta después de compartir aún lo ve; un contenido compartido que llega mientras la app está en ejecución también activa `appmint:shared`. Una foto o video compartido con un pie de foto entrega el archivo mediante AppMint.getOpenedFile() y el pie de foto aquí. Requiere la opción de la compilación «Recibir contenido compartido desde otras apps» con «Texto y enlaces» marcado, que registra la entrada en la hoja para compartir.

Ejemplo

Texto o un enlace que otra app compartió CON tu app («Compartir → tu app»), como cadena JSON, o "" cuando no se compartió nada.

Devuelve: una cadena JSON {text, subject, url, receivedAt} o "" (de forma síncrona). url es el primer enlace http(s):// encontrado en text (o ""); receivedAt es una hora en milisegundos. Requiere: Recibir contenido compartido desde otras apps activado al compilar, con Texto y enlaces marcado: eso pone tu app en el menú Compartir de otras apps.

Lo compartido se conserva hasta que lo borres, así que léelo al iniciar y escucha lo que llegue mientras la app está en ejecución:

function handleShare(s) {
  addBookmark(s.url || s.text, s.subject);
  if (window.WebToApk && window.WebToApk.clearSharedText) window.WebToApk.clearSharedText();
}

// 1. On start (the app may have been opened by the share).
if (window.WebToApk && typeof window.WebToApk.getSharedText === 'function') {
  var raw = window.WebToApk.getSharedText();
  if (raw) handleShare(JSON.parse(raw));
}

// 2. While running: detail has the same keys { text, subject, url, receivedAt }.
window.addEventListener('appmint:shared', function (e) {
  handleShare(e.detail);
});

Un hook global recibe el mismo objeto, si prefieres uno:

window.onAppMintShared = function (s) { handleShare(s); };

Abre un video de YouTube que se compartió con la app, usando el campo url:

function openSharedVideo() {
  if (!(window.WebToApk && typeof window.WebToApk.getSharedText === 'function')) return;
  var raw = window.WebToApk.getSharedText();
  if (!raw) return;
  var s = JSON.parse(raw);
  var m = /(?:youtu\.be\/|[?&]v=)([\w-]{11})/.exec(s.url || s.text);
  if (m) {
    showVideo(m[1]);
    window.WebToApk.clearSharedText();
  } else {
    showMessage('That share is not a video link');
  }
}

Notas: Un video de YouTube compartido desde la app de YouTube llega como su enlace. Una foto o video compartido CON un pie de foto entrega el archivo mediante AppMint.getOpenedFile() y el pie de foto aquí. Borra lo compartido después de usarlo, o lo volverás a ver en el siguiente inicio. Las apps creadas con IA pueden usar sharedText() / onShared() desde @/lib/appmintNative.

pdfFromHtml puente#

window.WebToApk.pdfFromHtml(html: String, optionsJson: String, callbackId: String)

Renderiza [html] fuera de pantalla en el origen de la app y lo guarda como PDF; usa `AppMint.pdf.fromHtml(html, options)`.

Ejemplo

Documentado junto con AppMint.pdf: el ejemplo de allí también muestra este.

Crea un PDF real (texto seleccionable, gráficos vectoriales) sin diálogo de impresión: fromPage guarda la página tal como está en pantalla, con el diseño de tu CSS @media print; fromHtml renderiza el HTML que le pases en una página invisible en la propia dirección de la app, de modo que las imágenes, hojas de estilo y fuentes relativas se cargan como en tu página; espera a las fuentes y a todas las imágenes y luego lo guarda. window.print() sigue abriendo el diálogo de impresión normal.

Devuelve: una Promise de {ok: true, fileName, pages, bytes, saved, uri?, base64?} o {ok: false, code, message}; code es busy (un PDF a la vez), bad-options, failed, too-large (base64 de más de 25 MB) o permission-denied (almacenamiento rechazado en Android 7-9). Requiere: nada: todas las compilaciones lo tienen. Guardar en Descargas no necesita permiso en Android 10+.

Opciones: fileName (se añade ".pdf"; por defecto: el título de la página), pageSize 'A4' (por defecto) | 'Letter' | 'Legal' | 'A3' | 'A5', landscape (true/false), margins en milímetros (un número o {top, right, bottom, left}; si se omite = tu propia regla @page), save 'downloads' (por defecto) | 'share' | 'base64'.

Guardar la página actual en Descargas:

async function saveInvoice() {
  if (!(window.AppMint && AppMint.pdf)) { window.print(); return; }   // in a browser
  var r = await AppMint.pdf.fromPage({ fileName: 'invoice-1042', pageSize: 'A4', margins: 12 });
  if (r.ok) console.log('Saved ' + r.fileName + ' (' + r.pages + ' pages)');
  else alert('Could not make the PDF: ' + r.message);
}

Un recibo creado desde HTML, directo a la hoja para compartir (las imágenes y fuentes se cargan antes de crear el PDF):

async function shareReceipt(order) {
  var html = '<style>body{font-family:sans-serif} td{padding:4px 8px}</style>' +
    '<img src="img/logo.png" width="120"><h1>Receipt #' + order.id + '</h1><table>' +
    order.items.map(function (i) { return '<tr><td>' + i.name + '</td><td>' + i.price + '</td></tr>'; }).join('') +
    '</table><p><b>Total ' + order.total + '</b></p>';
  var r = await AppMint.pdf.fromHtml(html, { fileName: 'receipt-' + order.id, pageSize: 'Letter', save: 'share' });
  if (!r.ok) console.warn(r.code, r.message);
}

Recupera los bytes para subirlos tú mismo:

async function uploadReport() {
  var r = await AppMint.pdf.fromPage({ landscape: true, margins: { top: 10, right: 8, bottom: 10, left: 8 }, save: 'base64' });
  if (!r.ok) return;
  var bin = atob(r.base64), buf = new Uint8Array(bin.length);
  for (var i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
  await fetch('/api/reports', { method: 'POST', headers: { 'Content-Type': 'application/pdf' }, body: buf });
}

Las llamadas directas reciben las opciones como texto JSON y un id de callback; usa mejor el helper anterior:

if (window.WebToApk && typeof window.WebToApk.pdfFromPage === 'function') {
  // AppMint.pdf.fromPage wraps: WebToApk.pdfFromPage(JSON.stringify(options), callbackId)
  // and AppMint.pdf.fromHtml wraps: WebToApk.pdfFromHtml(html, JSON.stringify(options), callbackId)
  AppMint.pdf.fromPage({ save: 'downloads' }).then(function (r) { console.log(r); });
}

Notas: Oculta botones y menús del PDF con @media print { .no-print { display: none } }. Un PDF descargado muestra «Descargado <name> · Abrir» como cualquier descarga. fromHtml ejecuta los scripts del HTML (aparecen los gráficos dibujados por script) pero no tiene acceso a las funciones nativas de la app. Las páginas más largas que una hoja se dividen como lo haría Chrome; usa break-before: page para controlar dónde.

pdfFromPage puente#

window.WebToApk.pdfFromPage(optionsJson: String, callbackId: String)

Guarda la página como PDF sin diálogo de impresión; usa `AppMint.pdf.fromPage(options)`. [optionsJson] `{fileName, pageSize, landscape, margins, save}`; resuelve [callbackId].

Ejemplo

Documentado junto con AppMint.pdf: el ejemplo de allí también muestra este.

Crea un PDF real (texto seleccionable, gráficos vectoriales) sin diálogo de impresión: fromPage guarda la página tal como está en pantalla, con el diseño de tu CSS @media print; fromHtml renderiza el HTML que le pases en una página invisible en la propia dirección de la app, de modo que las imágenes, hojas de estilo y fuentes relativas se cargan como en tu página; espera a las fuentes y a todas las imágenes y luego lo guarda. window.print() sigue abriendo el diálogo de impresión normal.

Devuelve: una Promise de {ok: true, fileName, pages, bytes, saved, uri?, base64?} o {ok: false, code, message}; code es busy (un PDF a la vez), bad-options, failed, too-large (base64 de más de 25 MB) o permission-denied (almacenamiento rechazado en Android 7-9). Requiere: nada: todas las compilaciones lo tienen. Guardar en Descargas no necesita permiso en Android 10+.

Opciones: fileName (se añade ".pdf"; por defecto: el título de la página), pageSize 'A4' (por defecto) | 'Letter' | 'Legal' | 'A3' | 'A5', landscape (true/false), margins en milímetros (un número o {top, right, bottom, left}; si se omite = tu propia regla @page), save 'downloads' (por defecto) | 'share' | 'base64'.

Guardar la página actual en Descargas:

async function saveInvoice() {
  if (!(window.AppMint && AppMint.pdf)) { window.print(); return; }   // in a browser
  var r = await AppMint.pdf.fromPage({ fileName: 'invoice-1042', pageSize: 'A4', margins: 12 });
  if (r.ok) console.log('Saved ' + r.fileName + ' (' + r.pages + ' pages)');
  else alert('Could not make the PDF: ' + r.message);
}

Un recibo creado desde HTML, directo a la hoja para compartir (las imágenes y fuentes se cargan antes de crear el PDF):

async function shareReceipt(order) {
  var html = '<style>body{font-family:sans-serif} td{padding:4px 8px}</style>' +
    '<img src="img/logo.png" width="120"><h1>Receipt #' + order.id + '</h1><table>' +
    order.items.map(function (i) { return '<tr><td>' + i.name + '</td><td>' + i.price + '</td></tr>'; }).join('') +
    '</table><p><b>Total ' + order.total + '</b></p>';
  var r = await AppMint.pdf.fromHtml(html, { fileName: 'receipt-' + order.id, pageSize: 'Letter', save: 'share' });
  if (!r.ok) console.warn(r.code, r.message);
}

Recupera los bytes para subirlos tú mismo:

async function uploadReport() {
  var r = await AppMint.pdf.fromPage({ landscape: true, margins: { top: 10, right: 8, bottom: 10, left: 8 }, save: 'base64' });
  if (!r.ok) return;
  var bin = atob(r.base64), buf = new Uint8Array(bin.length);
  for (var i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
  await fetch('/api/reports', { method: 'POST', headers: { 'Content-Type': 'application/pdf' }, body: buf });
}

Las llamadas directas reciben las opciones como texto JSON y un id de callback; usa mejor el helper anterior:

if (window.WebToApk && typeof window.WebToApk.pdfFromPage === 'function') {
  // AppMint.pdf.fromPage wraps: WebToApk.pdfFromPage(JSON.stringify(options), callbackId)
  // and AppMint.pdf.fromHtml wraps: WebToApk.pdfFromHtml(html, JSON.stringify(options), callbackId)
  AppMint.pdf.fromPage({ save: 'downloads' }).then(function (r) { console.log(r); });
}

Notas: Oculta botones y menús del PDF con @media print { .no-print { display: none } }. Un PDF descargado muestra «Descargado <name> · Abrir» como cualquier descarga. fromHtml ejecuta los scripts del HTML (aparecen los gráficos dibujados por script) pero no tiene acceso a las funciones nativas de la app. Las páginas más largas que una hoja se dividen como lo haría Chrome; usa break-before: page para controlar dónde.

print puente#

window.WebToApk.print()

Entrega la página tal como está ahora al diálogo de impresión de Android, que puede imprimir en papel o guardar un PDF. Nada que configurar: el WebView renderiza el documento.

Descrito por su grupo, Impresión, en lugar de por separado.

Ejemplo

Abre la pantalla de impresión de Android con la página tal como se ve ahora: imprime en papel o «Guardar como PDF». Las páginas normalmente usan el window.print() estándar, que la shell redirige aquí.

Devuelve: nada. Requiere: nada.

Primero la API estándar:

document.getElementById('print-btn').onclick = function () {
  window.print();   // in the app: the Android print screen
};

La llamada directa hace lo mismo:

if (window.WebToApk && typeof window.WebToApk.print === 'function') {
  window.WebToApk.print();
}

Imprime una factura de una lista marcándola para una hoja de estilos de impresión (deja la marca puesta: la pantalla de impresión maqueta la página después de que print() devuelva):

// CSS: @media print { .invoice:not(.print-me), nav, button { display: none !important; } }
function printInvoice(id) {
  document.querySelectorAll('.invoice.print-me').forEach(function (el) {
    el.classList.remove('print-me');
  });
  document.getElementById('invoice-' + id).classList.add('print-me');
  if (window.WebToApk && typeof window.WebToApk.print === 'function') {
    window.WebToApk.print();
  } else {
    window.print();   // a normal browser
  }
}

Notas: Imprime todo el WebView, la página principal. El propio print() de un iframe del mismo origen imprime solo ese iframe (ver window.print). Usa una hoja de estilos @media print para ocultar botones y menús. El trabajo se llama «<App name> Document». Los errores solo se registran.

readClipboard puente#

window.WebToApk.readClipboard(): String

Usa el envoltorio de promesa: const r = await AppMint.requestReview(); // {result, openedStore} const r = await AppMint.requestReview({ openStore: true }); // ficha de la tienda si no hay hoja r.result: 'shown' | 'not_from_play' | 'quota' | 'error'. Consulta RatePrompt.

Descrito por su grupo, La hoja de reseñas dentro de la app de Google Play, y no por sí mismo.

Ejemplo

Lee el texto que hay ahora en el portapapeles del teléfono. Las páginas normalmente usan el navigator.clipboard.readText() estándar, que la shell redirige aquí.

Devuelve: el texto, o "" cuando el portapapeles está vacío o no se puede leer (de forma síncrona). Requiere: nada.

Primero la API estándar, por ejemplo un botón «Pegar código»:

document.getElementById('paste-btn').onclick = async function () {
  try {
    var text = await navigator.clipboard.readText();
    if (text) document.getElementById('code').value = text.trim();
    else showMessage('The clipboard is empty');
  } catch (e) {
    showMessage('Could not read the clipboard');
  }
};

La llamada en bruto:

if (window.WebToApk && typeof window.WebToApk.readClipboard === 'function') {
  var text = window.WebToApk.readClipboard();
  if (text) document.getElementById('code').value = text;
}

Un botón «Pegar enlace» que extrae la primera dirección web de lo que se haya copiado:

document.getElementById('paste-link').onclick = function () {
  if (!(window.WebToApk && typeof window.WebToApk.readClipboard === 'function')) return;
  var text = window.WebToApk.readClipboard();
  var m = /https?:\/\/\S+/.exec(text);
  if (m) document.getElementById('link').value = m[0];
  else showMessage('There is no link on the clipboard');
};

Notas: Solo las páginas de tu propia app pueden leerlo: un sitio web ajeno abierto dentro de la app recibe "". Android solo permite que la app lea el portapapeles mientras está en pantalla, y Android 12+ muestra un aviso de «pegado desde el portapapeles»: léelo con un toque del usuario, no al cargar la página.

shareFiles puente#

window.WebToApk.shareFiles(filesJson: String, title: String, text: String): Boolean

Compartir archivos de forma realmente nativa para navigator.share({files:[...]}). El WebView de Android no tiene la API Web Share, y ACTION_SEND con text/plain no puede llevar un archivo, así que antes una imagen compartida solo se GUARDABA, no se compartía. filesJson es [{name, mimeType, base64}, ...]; escribimos cada uno en la caché, lo exponemos mediante el FileProvider de la app y lanzamos ACTION_SEND / ACTION_SEND_MULTIPLE para que se abra la hoja real de compartir de Android (WhatsApp, Gmail, …). Devuelve true si se abrió la hoja.

Ejemplo

Comparte uno o más archivos reales (una imagen, un PDF…) mediante la hoja para compartir de Android. Las páginas normalmente usan el navigator.share({ files: [...] }) estándar, que la shell redirige aquí.

Devuelve: true si se abrió la hoja para compartir, false en caso contrario (de forma síncrona). Requiere: nada.

Primero la API estándar:

async function shareCanvas(canvas) {
  var blob = await new Promise(function (r) { canvas.toBlob(r, 'image/png'); });
  var file = new File([blob], 'drawing.png', { type: 'image/png' });
  if (!(navigator.canShare && navigator.canShare({ files: [file] }))) {
    alert('File sharing is not available here');
    return;
  }
  try {
    await navigator.share({ files: [file], title: 'My drawing', text: 'Made in my app' });
  } catch (e) { /* in a browser: the user closed the sheet */ }
}

La llamada directa recibe una cadena JSON de [{name, mimeType, base64}] (base64 sin el prefijo data:), luego title y text:

function shareBase64Png(base64) {
  if (!(window.WebToApk && typeof window.WebToApk.shareFiles === 'function')) return false;
  var files = [{ name: 'receipt.png', mimeType: 'image/png', base64: base64 }];
  return window.WebToApk.shareFiles(JSON.stringify(files), 'Receipt', 'Your receipt');
}

Exporta una tabla como archivo CSV (el texto se codifica en UTF-8 antes de pasar a base64):

function shareCsv(rows) {
  if (!(window.WebToApk && typeof window.WebToApk.shareFiles === 'function')) return;
  var csv = rows.map(function (r) {
    return r.map(function (c) { return '"' + String(c).replace(/"/g, '""') + '"'; }).join(',');
  }).join('\n');
  var base64 = btoa(unescape(encodeURIComponent(csv)));
  var files = [{ name: 'expenses.csv', mimeType: 'text/csv', base64: base64 }];
  if (!window.WebToApk.shareFiles(JSON.stringify(files), 'Expenses', '')) {
    showMessage('Could not open the share sheet');
  }
}

Notas: Los archivos se escriben en la caché de la app y se comparten mediante su FileProvider. Los caracteres no seguros de name se convierten en _. Varios archivos de un mismo tipo se comparten con ese tipo; los tipos mezclados se comparten como */*. El archivo completo viaja como base64 por el puente, así que mantenlo en pocos MB. Cuando navigator.share({files}) no puede abrir la hoja, la shell guarda los archivos como descargas y la promesa se resuelve igualmente.

shareNative puente#

window.WebToApk.shareNative(title: String, text: String, url: String)

La hoja para compartir de Android, el portapapeles del sistema y el cierre de una ventana que abrió la página. Son los equivalentes de la web estándar (navigator.share, navigator.clipboard, window.close) respondidos de forma nativa para que se comporten igual dentro de la app.

Descrito por su grupo, Compartir, portapapeles y ventana, en lugar de por separado.

Ejemplo

Abre la hoja para compartir de Android con un título, texto y enlace. Las páginas normalmente usan el navigator.share({title, text, url}) estándar, que la shell redirige aquí.

Devuelve: nada (no puede decirte qué app eligió el usuario). Requiere: nada.

Primero la API estándar: también funciona en un navegador móvil:

document.getElementById('share-btn').onclick = async function () {
  if (!navigator.share) { alert('Sharing is not available here'); return; }
  try {
    await navigator.share({ title: 'My app', text: 'Look at this', url: 'https://mysite.com/item/42' });
  } catch (e) {
    // in a browser: AbortError when the user closes the sheet
  }
};

La llamada directa, los tres argumentos son cadenas (usa '' para omitir uno):

if (window.WebToApk && typeof window.WebToApk.shareNative === 'function') {
  window.WebToApk.shareNative('My app', 'Look at this', 'https://mysite.com/item/42');
}

Solo texto, sin enlace, por ejemplo la puntuación de un juego:

function shareScore(score) {
  if (!(window.WebToApk && typeof window.WebToApk.shareNative === 'function')) return;
  window.WebToApk.shareNative('My score', 'I scored ' + score + ' points. Can you beat it?', '');
}

Notas: El texto compartido es text + un salto de línea + url (o solo uno de ellos). title se convierte en el asunto del correo y en el encabezado de la hoja para compartir. Solo texto y enlaces; para compartir un archivo, usa navigator.share({files}) (ver shareFiles).

Los botones share: de los mensajes dentro de la app. Un botón cuyo enlace es share:<text> abre esta misma hoja para compartir con ese texto, sin código en la página. La plantilla Invitar amigos usa share:Try {app}: {store}; la app rellena su propio nombre y su dirección de Play. Tu página puede saber que ocurrió:

window.addEventListener('appmint:inapp', function (e) {
  var d = e.detail;
  if (d.type === 'clicked' && d.link && d.link.indexOf('share:') === 0) analytics('invite_shared', d.name);
});

Si dibujas tú mismo los mensajes dentro de la app (setInAppPresentation('none')), ejecuta un botón share: de la misma manera:

function runInAppButton(link) {
  if (link.indexOf('share:') === 0 && window.WebToApk && WebToApk.shareNative) {
    WebToApk.shareNative('', link.slice(6), '');   // text only
  }
}

Un enlace copy:<text> copia en lugar de compartir, por ejemplo un código de cupón de un mensaje promocional:

window.addEventListener('appmint:inapp', function (e) {
  var d = e.detail;
  if (d.type === 'clicked' && d.link === 'copy:SAVE20') showToast('Code SAVE20 copied');
});

Generado a partir del runtime de la app y sus archivos de ejemplo en cada compilación de la documentación. Léelo como Markdown · Todas las familias.

Comprobado con el puente publicado el 2026-10-22.