API JavaScript
Usa funciones de Android desde el JavaScript de tu app: consulta el dispositivo, guarda datos, comparte texto o muestra notificaciones, programa alarmas, abre enlaces o usa el portapapeles. Las llamadas devuelven promesas. Dentro del APK se ejecutan de forma nativa en Android; en un navegador y con koru dev las mismas llamadas usan implementaciones web, así que puedes desarrollar sin un dispositivo.
En esta página
Antes de llamar la API
koru init activa device, storage y app; la plantilla basic activa además vibration y share. Para usar otro plugin, añade su ID y reconstruye:
"plugins": ["device", "storage", "app", "vibration", "share", "notifications", "alarms", "browser", "screen", "clipboard"]
Es un campo para añadir a tu koru.json, no un archivo completo. Activar vibration añade android.permission.VIBRATE; notifications añade android.permission.POST_NOTIFICATIONS; alarms añade POST_NOTIFICATIONS y RECEIVE_BOOT_COMPLETED. browser, screen y clipboard no necesitan permisos. En el HTML empaquetado importa el módulo generado:
<script type="module">
import { device } from "/__koru/core.mjs";
document.querySelector("#model").textContent = (await device.getInfo()).model;
</script>
Añade a la página un elemento con id="model". Koru también inyecta /__koru/core.js, que expone window.Koru. El módulo exporta device, app, storage, vibration, share, notifications, alarms, browser, screen, clipboard, invoke, platform e isNative. Con un bundler como Vite, importa en su lugar la copia que escribe koru js (./koru/core.mjs); se comporta igual. Todos los métodos devuelven una promesa. Maneja los errores para mostrar algo útil:
try {
const info = await device.getInfo();
console.log(info.model);
} catch (error) {
console.error(error.code, error.message);
}
TypeScript y soporte del editor
El módulo incluye declaraciones de tipos. Los proyectos de koru create ya vienen configurados:
- basic:
types/koru.d.mtsy unjsconfig.jsonque asocia/__koru/core.mjsa ese archivo, para que VS Code ytsc --checkJscompruebenwww/app.js. - vite:
src/koru/core.d.mtsestá junto acore.mjsy se detecta automáticamente.
En tu propio proyecto TypeScript, ejecuta koru js src/koru o asocia la ruta servida en tsconfig.json:
{
"compilerOptions": {
"moduleResolution": "bundler",
"paths": { "/__koru/core.mjs": ["./types/koru.d.mts"] }
}
}
Los tipos cubren cada método, la forma de cada resultado, CallOptions y los códigos de KoruError:
import { storage, type KoruError } from "/__koru/core.mjs";
try {
const theme: string | null = await storage.get("theme");
} catch (error) {
console.warn((error as KoruError).code);
}
Android o navegador
platform vale "android" dentro del APK y "web" en cualquier otro sitio. isNative() devuelve platform === "android". Úsalos en los pocos lugares donde el comportamiento deba cambiar:
import { isNative, share } from "/__koru/core.mjs";
const label = isNative() ? "Compartir" : "Copiar enlace";
En el navegador, device.getInfo() devuelve model: "Web browser" y sdk: 0; storage usa localStorage con el prefijo koru:; share.text usa la Web Share API o copia al portapapeles; las notificaciones usan la API Notification del navegador; vibration.vibrate usa navigator.vibrate donde existe. La validación de argumentos y los códigos de error son los mismos que en Android. Un navegador sin equivalente rechaza con UNAVAILABLE. Con koru dev, las llamadas a plugins no activados en koru.json se rechazan con METHOD_NOT_FOUND, como en el APK.
En una página remota cargada mediante server.bridge, la app añade KoruApp/1 al user agent, así que platform es "android" desde el principio y las llamadas esperan a que la página termine de cargar. Las páginas remotas fuera de bridge reciben koru-none: core.js informa "web" y usa las implementaciones web. Esas páginas deben cargar core.js ellas mismas. Consulta Contenido remoto y apps híbridas.
Índice de métodos
| Plugin | Métodos | ¿Activo con init? |
|---|---|---|
| Dispositivo | getInfo() |
Sí |
| App | getInfo() |
Sí |
| Almacenamiento | get(), set(), remove(), clear() |
Sí |
| Vibración | vibrate() |
No |
| Compartir | text() |
No |
| Notificaciones | permission(), requestPermission(), show(), cancel() |
No |
| Alarmas | schedule(), cancel(), list() |
No |
| Navegador | open() |
No |
| Pantalla | keepAwake() |
No |
| Portapapeles | writeText(), readText() |
No |
| Puente directo | invoke() |
Disponible en páginas del APK |
Cada acordeón se abre por separado. Los ejemplos presuponen importaciones desde /__koru/core.mjs y funcionan dentro del APK.
Dispositivo y aplicación
device.getInfo()Consulta el dispositivo Android
Firma: device.getInfo(options?) → Promise<DeviceInfo>. Sin parámetros nativos ni permisos.
Resultado: { model: string, manufacturer: string, osVersion: string, sdk: number, platform: "android" }. Los valores vienen de los campos Build de Android y cambian según el dispositivo; sdk es su nivel de API.
import { device } from "/__koru/core.mjs";
const info = await device.getInfo();
console.log(
`${info.manufacturer} ${info.model} · Android ${info.osVersion} (API ${info.sdk})`,
);
options admite timeout y signal, explicados en invoke(). Un plugin desactivado rechaza con METHOD_NOT_FOUND; el canal y el tiempo de espera también pueden producir errores.
app.getInfo()Consulta la identidad de la app
Firma: app.getInfo(options?) → Promise<AppInfo>. Sin parámetros nativos ni permisos.
Resultado: { appId: string, name: string, version: string, versionCode: number }, tomado del koru.json con el que se generó el APK.
import { app } from "/__koru/core.mjs";
const info = await app.getInfo();
document.querySelector("#version").textContent = `${info.name} ${info.version}`;
El objeto describe la app empaquetada. No consulta una tienda ni versiones disponibles.
Almacenamiento privado
Storage usa SharedPreferences privado de la app. Las claves tienen 1–128 caracteres: A–Z, a–z, 0–9, _, ., : y -. Los valores son strings. Los datos persisten entre arranques y actualizaciones firmadas con la misma identidad. Se eliminan al desinstalar. No pongas secretos en una página web ni supongas que este almacenamiento está cifrado.
storage.get(key)Lee un string o null
Firma: storage.get(key: string, options?) → Promise<string | null>.
Argumentos: key debe ser una clave válida. Resultado: el string guardado, o null si nunca existió o fue eliminado.
import { storage } from "/__koru/core.mjs";
const theme = (await storage.get("theme")) ?? "system";
console.log(theme);
Una clave ausente, de tipo incorrecto o inválida rechaza con INVALID_REQUEST. Este wrapper extrae por ti el valor del objeto nativo { value }.
storage.set(key, value)Guarda un string
Firma: storage.set(key: string, value: string, options?) → Promise<{}>.
Argumentos: clave válida y valor string. Android confirma el guardado antes de resolver la promesa. Resultado: objeto vacío {}.
import { storage } from "/__koru/core.mjs";
await storage.set("theme", "dark");
await storage.set("visits", String(3)); // convierte números a string
Tipos incorrectos rechazan con INVALID_REQUEST; un guardado fallido, con STORAGE_ERROR.
storage.remove(key)Elimina una clave
Firma: storage.remove(key: string, options?) → Promise<{}>. Resultado: objeto vacío una vez confirmada la eliminación. Eliminar una clave inexistente es válido.
import { storage } from "/__koru/core.mjs";
await storage.remove("theme");
console.log(await storage.get("theme")); // null
Una clave inválida rechaza con INVALID_REQUEST; un guardado fallido, con STORAGE_ERROR.
storage.clear()Elimina todas las claves koru
Firma: storage.clear(options?) → Promise<{}>. Elimina todas las claves del espacio privado de koru para esta app y resuelve con {}. No borra los assets web empaquetados ni otros datos Android de la app.
import { storage } from "/__koru/core.mjs";
await storage.clear();
Un guardado fallido rechaza con STORAGE_ERROR. Úsalo cuando el usuario realmente quiera borrar todas las claves.
Acciones del dispositivo
vibration.vibrate(duration?)Vibra durante unos milisegundos
Configuración: activa vibration en koru.json; koru añade android.permission.VIBRATE al APK.
Firma: vibration.vibrate(duration = 100, options?) → Promise<{}>. duration debe ser entero entre 1 y 10.000 milisegundos. La promesa resuelve con {} al iniciar la acción Android; no espera a que termine la vibración.
import { vibration } from "/__koru/core.mjs";
await vibration.vibrate(150);
Duraciones inválidas rechazan con INVALID_REQUEST. Un fallo de hardware o Android puede rechazar con NATIVE_ERROR; un dispositivo sin vibrador no ofrece respuesta táctil.
Notificaciones locales
Activa notifications en koru.json y reconstruye. Koru añade android.permission.POST_NOTIFICATIONS. Android 13/API 33+ puede pedir una decisión durante la ejecución; las versiones compatibles anteriores usan el ajuste de notificaciones del sistema. El diálogo de API 33+ no se ha probado en la matriz registrada de API 30. Las notificaciones son locales e inmediatas y usan el canal koru-local; esta API no programa alarmas futuras ni push remoto.
notifications.permission()Consulta el permiso actual
Firma: notifications.permission(options?) → Promise<{ status: "granted" | "denied" }>.
import { notifications } from "/__koru/core.mjs";
const { status } = await notifications.permission();
console.log(status); // "granted" o "denied"
Lee el permiso y el ajuste actuales de Android. No muestra un diálogo. Un plugin desactivado rechaza con METHOD_NOT_FOUND.
notifications.requestPermission()Pide acceso a notificaciones
Firma: notifications.requestPermission(options?) → Promise<{ status: "granted" | "denied" }>.
En Android 13/API 33+, si está denegado abre el diálogo del permiso. En Android 8–12/API 26–32, abre los ajustes de notificación de la app y resuelve al regresar. Si ya está concedido, resuelve de inmediato. El timeout predeterminado del wrapper es 120.000 ms para este método; options.timeout puede cambiarlo hasta ese límite. Solo puede haber una solicitud pendiente.
import { notifications } from "/__koru/core.mjs";
const { status } = await notifications.requestPermission();
if (status === "granted") console.log("Notificaciones activas");
BUSY indica otra solicitud pendiente; TIMEOUT, que no llegó respuesta a tiempo. denied es un resultado normal, no un error lanzado.
notifications.show({ id, title, body })Publica ahora una notificación
Firma: notifications.show({ id: number, title: string, body: string }, options?) → Promise<{}>.
id es un identificador entero; usa el mismo para reemplazar o cancelar una notificación. El valor nativo predeterminado es 1 si no pasas id, pero especificarlo aclara la cancelación posterior. title y body son strings obligatorios. La promesa resuelve con {} tras publicar, no cuando el usuario abre la notificación.
import { notifications } from "/__koru/core.mjs";
const { status } = await notifications.permission();
if (status === "granted") {
await notifications.show({
id: 7,
title: "Listo",
body: "La exportación terminó.",
});
}
Si las notificaciones están desactivadas o falta el permiso en Android 13+, rechaza con PERMISSION_DENIED. Tipos incorrectos rechazan con INVALID_REQUEST.
notifications.cancel(id)Retira una notificación
Firma: notifications.cancel(id: number, options?) → Promise<{}>. Usa el mismo ID entero de show; resuelve con {} después de pedirle a Android que la cancele. Cancelar un ID ausente es válido. No abre un diálogo de permiso.
import { notifications } from "/__koru/core.mjs";
await notifications.cancel(7);
Un ID que no sea entero rechaza con INVALID_REQUEST. Un fallo nativo Android puede rechazar con NATIVE_ERROR.
Alarmas programadas
Activa alarms (y, para Android 13+, notifications) en koru.json y reconstruye. Koru añade android.permission.POST_NOTIFICATIONS y android.permission.RECEIVE_BOOT_COMPLETED. Estos plugins se probaron en Chromium con koru dev (versiones web) y su bytecode pasa el verificador de koru build; no se han probado en un dispositivo Android.
alarms.schedule({ id, title, body, at })Muestra una notificación más tarde
Firma: alarms.schedule({ id, title, body, at }, options?) → Promise<{}>; alarms.cancel(id) → Promise<{}>; alarms.list() → Promise<string[]>, los IDs aún pendientes. at es un Date o milisegundos desde epoch. Programar un id que ya existe reemplaza la alarma anterior.
import { alarms, notifications } from "/__koru/core.mjs";
await notifications.requestPermission(); // necesario en Android 13+
await alarms.schedule({
id: "te",
title: "Té",
body: "Tu té está listo.",
at: Date.now() + 60_000,
});
console.log(await alarms.list()); // ["te"]
await alarms.cancel("te");
En Android koru usa AlarmManager.setAndAllowWhileIdle. La alarma es inexacta: puede llegar unos minutos tarde, sobre todo en reposo o en Doze. No se usan alarmas exactas porque requieren un permiso que el usuario concede en Ajustes. Las alarmas se guardan y se reprograman tras reiniciar el móvil o actualizar la app (BOOT_COMPLETED, MY_PACKAGE_REPLACED); las que vencieron mientras estaba apagado se muestran al arrancar. Al tocar la notificación se abre la app. Para mostrarse en Android 13+ hace falta el permiso de notificaciones: activa también notifications y llama a notifications.requestPermission().
En un navegador y con koru dev, las alarmas solo se disparan mientras la página está abierta (setTimeout más Notification si hay permiso). Si falta title, body o at, rechaza con INVALID_REQUEST.
Abrir enlaces en otras apps
Activa browser. Sin permisos.
browser.open(url)Abre un enlace en otra app
Firma: browser.open(url: string, options?) → Promise<{}>. Abre el enlace en otra app: un navegador, el correo, el teléfono, etc.
import { browser } from "/__koru/core.mjs";
await browser.open("https://koru.js.org");
await browser.open("mailto:hola@example.com");
Esquemas permitidos: https, http, mailto, tel, sms, geo y market. Cualquier otro rechaza con INVALID_REQUEST. Si ninguna app puede abrir el enlace, rechaza con NATIVE_ERROR. En un navegador usa window.open. Con server en koru.json, los enlaces externos ya se abren así solos; consulta Contenido remoto.
Mantener la pantalla encendida
Activa screen. Sin permisos.
screen.keepAwake(enabled = true)Evita que la pantalla se apague
Firma: screen.keepAwake(enabled = true, options?) → Promise<{}>. Mantiene la pantalla encendida mientras la app está visible (FLAG_KEEP_SCREEN_ON); keepAwake(false) la deja apagarse de nuevo.
import { screen } from "/__koru/core.mjs";
await screen.keepAwake(); // p. ej. mientras muestras una receta
await screen.keepAwake(false);
En un navegador usa la Screen Wake Lock API si existe; si no, rechaza con UNAVAILABLE.
Portapapeles
Activa clipboard. Sin permisos.
clipboard.writeText(text)Copia y pega texto
Firma: clipboard.writeText(text: string, options?) → Promise<{}>; clipboard.readText(options?) → Promise<string | null>, que es null si el portapapeles está vacío.
import { clipboard } from "/__koru/core.mjs";
await clipboard.writeText("hola");
const text = await clipboard.readText(); // "hola", o null si está vacío
Desde Android 10 solo la app en primer plano puede leer el portapapeles. En un navegador usa navigator.clipboard, que puede pedir permiso o rechazar con UNAVAILABLE.
Puente directo y errores
invoke(method, params?, options?)Llama directamente a un handler nativo
Firma: invoke(method: string, params = {}, options = {}) → Promise<object>. Es la función exportada por /__koru/core.mjs; window.Koru.invoke es el equivalente global. El método es un string como "device.info"; params debe ser un objeto simple serializable a JSON. Úsalo para un plugin personalizado o si necesitas el resultado nativo sin transformar.
import { invoke } from "/__koru/core.mjs";
const info = await invoke("device.info", {});
const raw = await invoke("storage.get", { key: "theme" });
console.log(info.model, raw.value); // raw.value es string o null
options.timeout es un número finito de milisegundos entre 1 y 120.000; por defecto 10.000. El temporizador también incluye la espera del canal nativo. options.signal admite AbortSignal; cancelar rechaza con CANCELLED. La cancelación impide una petición que aún no se envió, pero no deshace una acción nativa ya iniciada. Máximo 256 llamadas pendientes. El JSON serializado de una petición debe medir menos de 65.536 caracteres.
Resultado: el objeto devuelto por el handler nativo. Las acciones integradas devuelven {}; el wrapper storage.get() convierte el resultado bruto { value } en string o null. Los handlers personalizados definen su objeto de resultado.
Los errores rechazan con un Error cuyo code y message puedes mostrar o registrar. INVALID_REQUEST significa método o argumentos inválidos; METHOD_NOT_FOUND, método o plugin no disponible; STORAGE_ERROR, fallo de guardado; PERMISSION_DENIED, permiso o ajuste de notificaciones desactivado; NATIVE_ERROR, excepción Android; UNAVAILABLE, el navegador no tiene equivalente (solo implementaciones web). BUSY, TIMEOUT, CANCELLED, CHANNEL_ERROR, CHANNEL_REPLACED, INVALID_RESPONSE y PAGE_CLOSED describen el ciclo de vida del puente. Un timeout o cancelación no demuestra que una acción nativa ya iniciada se haya revertido.
Amplía tu app
Para añadir funciones propias, sigue la guía paso a paso Escribe un plugin o consulta el adaptador nativo y la referencia de plugins personalizados.