Crea un plugin de Cordova para Electron
Post principal
Este post forma parte de este tutorial:
Si no has leido ese otro post, es posible que necesites leerlo antes de continuar con este. Aprenderás a:
- Preparar la configuración base del plugin
- Crear la interfaz de JavaScript
- Usar el plugin en una app de Cordova
Puedes ver el código fuente de este tutorial en GitHub:
https://github.com/adrian-bueno/multiplatform-cordova-plugin-example
Inicio
Este tutorial está escrito para
cordova-electron
4.0.0
Vamos a implementar en Node.js los 4 métodos que definimos en nuestra interfaz de JavaScript www/CordovaPluginExample.js
:
- greeting
- countdownTimer
- writeFile
- bitcoinCurrentPrice
Crea un nuevo directorio electron
dentro de src
.
Después crea los ficheros CordovaPluginExample.js
y package.json
dentro de electron
.
📁 cordova-plugin-example
└── 📁 src
└── 📁 electron
├── 📄 CordovaPluginExample.js
└── 📄 package.json
Añade la configuración mínima a este package.json
:
- name
- version: puedes poner y dejar siempre 0.0.0, no es necesario actualizar esta versión
- main: el fichero JavaScript principal
- cordova: Configuración específica de Cordova
- serviceName: El nombre del plugin que pusimos en
www/CordovaPluginExample.js
(PLUGIN_NAME)
- serviceName: El nombre del plugin que pusimos en
{
"name": "cordova-plugin-example",
"version": "0.0.0",
"main": "CordovaPluginExample.js",
"cordova": {
"serviceName": "CordovaPluginExample"
}
}
Electron usa mínimo 2 procesos: el proceso
main
y el procesorenderer
(podemos crear más procesos si queremos). El procesomain
es el proceso principal y es un programa de Node.js. Los procesosrenderer
se crean cada vez que abrimos unaBrowserWindow
y se encarga de renderizar la aplicación web (son procesos de Chromium). Basicamente, el procesomain
== Node.js, y el procesorenderer
== Chromium. Más informaciónEl código de
src/electron
se ejecuta en el procesomain
.
Dependencias externas
Vamos a usar axios (un cliente HTTP basado en promesas y que funciona tanto en Node.js como el navegador) para obtener el valor actual del Bitcoin de https://api.coindesk.com/v1/bpi/currentprice.json .
Para usar dependencias externas, tenemos que añadirlas a las dependencias de src/electron/package.json
:
{
"name": "cordova-plugin-example",
"version": "0.0.0",
"main": "CordovaPluginExample.js",
"cordova": {
"serviceName": "CordovaPluginExample"
},
"dependencies": {
"axios": "^0.26.0"
}
}
Configuración del plugin.xml
La configuración de Electron en el plugin.xml es la más simple de todas:
<!-- Electron -->
<platform name="electron">
<framework src="src/electron"/>
</platform>
No tenemos que especificar permisos ni ninguna otra cosa como en Android o iOS.
Estructura del código fuente de Electron
📁 cordova-plugin-example
├── 📁 src
│ ├── 📁 android
│ ├── 📁 ios
│ └── 📁 electron
│ ├── 📄 CordovaPluginExample.js
│ └── 📄 package.json
├── 📁 www
│ └── 📄 CordovaPluginExample.js
├── 📄 .gitignore
├── 📄 package.json
├── 📄 plugin.xml
└── 📄 README.md
Código fuente en Node.js
CordovaPluginExample.js
const fs = require("fs");
const path = require("path");
const os = require("os");
const axios = require("axios").default;
// Este es el ejemplo más sencillo.
// Devuelve el string "Hello {name}!"
// o "Hello!" si no se recibe ningún nombre.
function greeting(args) {
const [name] = args;
return name ? `Hello ${name}!` : "Hello!";
}
// Devuelve un número cada segundo, desde el valor
// del parámetro "seconds" hasta 0.
// NO ES POSIBLE IMPLEMENTARLO con cordova-electron 4.0.0+.
// cordova-electron 4.0.0+ no tiene implementada
// la funcionalidad de keepCallback que si está
// disponible en Android e iOS.
function countdownTimer(args) {
// const [seconds] = args;
throw "NOT_IMPLEMENTED";
}
// Escribe un fichero en la carpeta
// "Documents" del usuario
function writeFile(args) {
const [fileName, text] = args;
if (!fileName || !text) {
throw "BAD_ARGS";
}
try {
const dir = path.join(os.homedir(), "Documents");
fs.mkdirSync(dir, { recursive: true });
const filePath = path.join(dir, fileName);
fs.writeFileSync(filePath, text);
} catch (error) {
console.error(error);
throw "WRITE_ERROR"
}
}
// Este ejemplo sirve solo para mostrar como usar
// la dependencia que hemos añadido en el package.json,
// en este caso axios.
//
// Las funciones también pueden ser "async" y
// devolver promesas.
async function bitcoinCurrentPrice() {
try {
const response = await axios.get("https://api.coindesk.com/v1/bpi/currentprice.json")
return response.data;
} catch (error) {
console.error(error);
throw "REQUEST_ERROR";
}
}
// Exporta las funciones, usa los mismos
// nombres que en las acciones de los métodos
// de www/CordovaPluginExample.js (el 4º parámetro):
//
// exports.writeFile = function (successCallback, errorCallback, fileName, text) {
// exec(successCallback, errorCallback, PLUGIN_NAME, 'writeFile', [fileName, text]);
// };
module.exports = {
greeting,
countdownTimer,
writeFile,
bitcoinCurrentPrice,
};
Las funciones pueden devolver valores directamente o a través de una promesa.
Devolver varios valores (funcionalidad ‘keepCallback’)
Esta parte del tutorial no usa código oficial de cordova-electron
(al menos por ahora).
Recientemente tuve que crear un plugin para Android y Electron en el que necesitaba detectar cuando se conectaba/desconectaba un dispositivo USB. Por lo que necesitaba la funcionalidad de keepCallback
. Como esta funcionalidad no esta disponible por ahora en el código oficial de cordova-electron
, tuve que crear un fork e implementarlo. Podeís ver el fork aquí (rama ‘feature/keep-callback’). También hice un pull request que está esperando la validación de los desarrolladores oficiales de cordova-electron
. Si aprueban estos cambios o proponen otra implementación, actualizaré el tutorial.
Para usar este fork tenemos que reemplazar la versión 4.0.0
del package.json
de nuestra app de Cordova por github:adrian-bueno/cordova-electron#feature/keep-callback
.
Primero borra la plataforma de Electron:
npx cordova platform remove electron
Después vuelve a añadir la plataforma usando la URL del fork y la rama:
npx cordova platform add https://github.com/adrian-bueno/cordova-electron#feature/keep-callback
Deberías ver los siguiente en el package.json
:
{
...
"dependencies": {
"cordova-electron": "github:adrian-bueno/cordova-electron#feature/keep-callback"
}
...
}
Ahora si que podemos completar el código de la función countdownTimer
.
Para esta funcionalidad decidí usar el $
para identificar a las funciones que pueden devolver más de un valor a lo largo del tiempo. Al implementarlo de esta forma no se rompe la funcionalidad actual y los plugins existentes no dejarían de funcionar.
Tenemos que hacer los siguientes cambios:
Edita www/CordovaPluginExample.js
:
...
// Creamos una variable para detectar si estamos
// ejecutando el código en Electron
const runningInElectron = navigator.userAgent.indexOf("Electron") >= 0;
// Si estamos en Electron, la acción debe terminar con un $
// Para el resto de plataformas continuamos dejando el mismo nombre
exports.countdownTimer = function (successCallback, errorCallback, seconds) {
const action = runningInElectron ? "countdownTimer$" : "countdownTimer";
exec(successCallback, errorCallback, PLUGIN_NAME, action, [seconds]);
};
...
Edita src/electron/CordovaPluginExample.js
:
...
// Añade un $ al final del nombre de la función.
// Estas funciones tienen 3 parámetros:
// - callback OK
// - callback error
// - parámetros
//
// Las funciones de callback tienen
// la siguiente interfaz:
// - success(data: any, keepCallback: boolean): void
// - error(data: any): void
function countdownTimer$(success, error, args) {
const [seconds] = args;
let secondsLeft = seconds > 0 ? seconds : 10;
function startTimeout() {
const keepCallback = secondsLeft > 0;
success(secondsLeft, keepCallback);
if (keepCallback) {
secondsLeft--;
setTimeout(startTimeout, 1000);
}
}
startTimeout();
}
...
module.exports = {
greeting,
countdownTimer$, // <= añade el $
writeFile,
bitcoinCurrentPrice,
};