Build a Cordova plugin for Electron
Main post
This post forms part of this tutorial:
You might want to check that post first before continuing with this one. You will learn how to:
- Prepare the base plugin configuration
- Create the JavaScript interface
- Use the plugin in a Cordova app
You could find the source code of this tutorial on GitHub:
https://github.com/adrian-bueno/multiplatform-cordova-plugin-example.
Start
This tutorial is for
cordova-electron
4.0.0
We are going to implement in Node.js the 4 methods defined in our JavaScript interface www/CordovaPluginExample.js
:
- greeting
- countdownTimer
- writeFile
- bitcoinCurrentPrice
Create a new directory electron
inside src
.
Then create the files CordovaPluginExample.js
and package.json
inside electron
.
π cordova-plugin-example
βββ π src
βββ π electron
βββ π CordovaPluginExample.js
βββ π package.json
Then add the minimum configuration to this new package.json
:
- name
- version: you could just set 0.0.0, you don’t need to update this number
- main: the main JavaScript file
- cordova: Cordova specific configuration
- serviceName: The PLUGIN_NAME we used in
www/CordovaPluginExample.js
- serviceName: The PLUGIN_NAME we used in
{
"name": "cordova-plugin-example",
"version": "0.0.0",
"main": "CordovaPluginExample.js",
"cordova": {
"serviceName": "CordovaPluginExample"
}
}
Electron runs with at least 2 processes: the
main
process and therenderer
process. (We can spawn more process if needed). Themain
process runs in a Node.js environment. Aredenderer
process is spawned for every openedBrowserWindow
and is in charge of rendering the web content. Basically,main
process == Node.js,renderer
process == Chromium. More infoThe code in
src/electron
runs in themain
process.
External dependencies
We are going to use axios (a promise based HTTP client for the browser and Node.js) to obtain Bitcoin’s current price from https://api.coindesk.com/v1/bpi/currentprice.json .
To use external dependencies just add them to src/electron/package.json
:
{
"name": "cordova-plugin-example",
"version": "0.0.0",
"main": "CordovaPluginExample.js",
"cordova": {
"serviceName": "CordovaPluginExample"
},
"dependencies": {
"axios": "^0.26.0"
}
}
plugin.xml configuration
The plugin.xml configuration for Electron is pretty simple:
<!-- Electron -->
<platform name="electron">
<framework src="src/electron"/>
</platform>
That’s it. We don’t have to especify permissions or edit any other file, like in Android and iOS.
Electron source code structure
π cordova-plugin-example
βββ π src
β βββ π android
β βββ π ios
β βββ π electron
β βββ π CordovaPluginExample.js
β βββ π package.json
βββ π www
β βββ π CordovaPluginExample.js
βββ π .gitignore
βββ π package.json
βββ π plugin.xml
βββ π README.md
Node.js source code
CordovaPluginExample.js
const fs = require("fs");
const path = require("path");
const os = require("os");
const axios = require("axios").default;
// This is our simplest example.
// Returns the string "Hello {name}!"
// or "Hello!" if a name is not received.
function greeting(args) {
const [name] = args;
return name ? `Hello ${name}!` : "Hello!";
}
// Returns a number every second, from "seconds" parameter value to 0.
// NOT POSSIBLE TO IMPLEMENT IT WITH cordova-electron 4.0.0+.
// cordova-electron 4.0.0+ doesn't support the keepCallback
// functionality that is available in Android and iOS.
function countdownTimer(args) {
// const [seconds] = args;
throw "NOT_IMPLEMENTED";
}
// Writes a file in the user's Documents folder.
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"
}
}
// This example is just to show how can we use
// the external dependencies we declared previously,
// in this case, axios.
//
// Functions could also be async and return Promises.
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";
}
}
// Export the funtions, use the same names
// declared in www/CordovaPluginExample.js
// (in the 'action' parameter, the 4th one)
//
// exports.writeFile = function (successCallback, errorCallback, fileName, text) {
// exec(successCallback, errorCallback, PLUGIN_NAME, 'writeFile', [fileName, text]);
// };
module.exports = {
greeting,
countdownTimer,
writeFile,
bitcoinCurrentPrice,
};
Functions can return values directly or inside a Promise.
Return multiple values (‘keepCallback’ functionality)
This part of the tutorial doesn’t use oficial cordova-electron
code (at least for now).
I recently needed to build a plugin for Android and Electron in which I needed to detect when a USB device was plugged/unplugged. So I needed the keepCallback
functionality. Since this functionality is not available yet in the official cordova-electron
, I created a fork you could find here (branch ‘feature/keep-callback’). I also created a pull request that is waiting for validation from the official devs. I will update this tutorial if they approve this changes or add this functionality in another way.
To use this fork, just replace the version 4.0.0
from your Cordova app’s package.json
with github:adrian-bueno/cordova-electron#feature/keep-callback
.
Remove first cordova-electron
platform:
npx cordova platform remove electron
Then add this fork URL and branch as a platform:
npx cordova platform add https://github.com/adrian-bueno/cordova-electron#feature/keep-callback
Now you will see the following in your app’s package.json
:
{
...
"dependencies": {
"cordova-electron": "github:adrian-bueno/cordova-electron#feature/keep-callback"
}
...
}
Now we can implement the code for function countdownTimer
.
For this functionality I decided to use the $
character to identify functions that could return multiple values over time. By implementing it by this way there is no need to add any breaking changes for existing plugins.
We have to do the following changes:
Edit www/CordovaPluginExample.js
:
...
// Detect if the app is running in the Electron platform
const runningInElectron = navigator.userAgent.indexOf("Electron") >= 0;
// If the app is running in Electron, the action must end with a $
// For the rest of platforms we continue with the same action name
exports.countdownTimer = function (successCallback, errorCallback, seconds) {
const action = runningInElectron ? "countdownTimer$" : "countdownTimer";
exec(successCallback, errorCallback, PLUGIN_NAME, action, [seconds]);
};
...
Edit src/electron/CordovaPluginExample.js
:
...
// Add the $ to the function name.
// This functions now have 3 parameters:
// - success callback
// - error callback
// - args
//
// The callbacks have the following interfaces:
// - 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$, // <= add the $
writeFile,
bitcoinCurrentPrice,
};