diff --git a/CHANGELOG.md b/CHANGELOG.md index 2763d9e..17e3c3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.0-beta.4] - 2023-08-25 + +_If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md)._ + +### Added + +- Add a types package for the `bridge` module ([#6](https://github.com/hampoelz/Capacitor-NodeJS/issues/6)) +- Allow manual startup of the Node.js runtime, pass environment variables and arguments ([#8](https://github.com/hampoelz/Capacitor-NodeJS/issues/8)) + - Add `startMode` configuration + - Add `start()` method +- Add API to get a writeable data directory on each platform + +### Changed + +- **Breaking:** Remove return type of `send()` method +- Rewrite the `bridge` module in TypeScript ([#14](https://github.com/hampoelz/Capacitor-NodeJS/issues/14)) +- Start the Node.js runtime as child process on Electron ([#12](https://github.com/hampoelz/Capacitor-NodeJS/issues/12), [#15](https://github.com/hampoelz/Capacitor-NodeJS/issues/15)) +- Change loading mechanism of the `bridge` module to a built-in module ([#12](https://github.com/hampoelz/Capacitor-NodeJS/issues/12)) + ## [1.0.0-beta.3] - 2023-08-19 _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md)._ diff --git a/README.md b/README.md index a28b2ce..aa1f168 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,44 @@ # 📱 Capacitor NodeJS-Integration -➡ A full-fledged [Node.js](https://nodejs.org/) runtime for [Capacitor](https://capacitorjs.com) apps. +:arrow_right: A full-fledged [Node.js](https://nodejs.org/) runtime for [Capacitor](https://capacitorjs.com) apps. + +> [!NOTE] +> This project uses the [Node.js for Mobile Apps](https://github.com/nodejs-mobile/nodejs-mobile) toolkit to add Node.js support in Android and iOS + +> [!WARNING] +> **WIP - Work in Progress** + +**Table of contents** + +* [Install](#install) + + [Supported Platforms](#supported-platforms) +* [Examples](#examples) +* [Getting Started](#getting-started) + + [Basics](#basics) + + [Minimal example](#minimal-example) + + [Inter-Process Communication](#inter-process-communication) +* [Complex Projects](#complex-projects) + + [Custom starting point](#custom-starting-point) + + [Install Node.js Modules](#install-nodejs-modules) + + [Improve Node.js loading times](#improve-nodejs-loading-times) + + [Manual Node.js runtime start](#manual-nodejs-runtime-start) + + [Data storage](#data-storage) +* [Mobile Node.js APIs differences](#mobile-nodejs-apis-differences) +* [Configuration](#configuration) +* [API - Bridge module](#api---bridge-module) +* [API - Capacitor layer](#api---capacitor-layer) -> ℹī¸ This project uses the [Node.js for Mobile Apps](https://github.com/nodejs-mobile/nodejs-mobile) toolkit to add Node.js support in Android and IOS +## Install -> **⚠ WIP - Work in Progress ⚠** -> -> The project is part of my diploma thesis, an overview can be found at [hampoelz/HTL_Diplomarbeit](https://github.com/hampoelz/HTL_Diplomarbeit). -> -> ⏰ Planed Deadline: September 2023 -> -> Notes: -> - The project is still very unstable, if you have any problems or suggestions it would be nice if you create an issue. -> - When the project is stable it will be published on NPM. -> - Features like IOS support will be added in the future. -> - The node.js version used in this project depends on the [Node.js for Mobile Apps](https://github.com/nodejs-mobile/nodejs-mobile) toolkit. +**You've to use Capacitor v5 or newer. This project isn't compatible with lower versions of Capacitor.** + +```bash +npm install https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.4/capacitor-nodejs.tgz +npx cap sync +``` + +> [!NOTE] +> For now Android 32-bit x86 support is disabled since Capacitor-NodeJS v1.0.0-beta.2 _(based on node.js v16)_ as there is currently no support for it in the latest version of the nodejs-mobile core library. ### Supported Platforms @@ -24,90 +48,132 @@ - [x] Windows - [x] Linux - [x] macOS +- [ ] _Web (maybe in future with WebAssembly?)_ -## Install +## Examples -**You've to use Capacitor v5 or newer. This project isn't compatible with lower versions of Capacitor.** +Example projects can be found in the [hampoelz/Capacitor-NodeJS_Examples](https://github.com/hampoelz/Capacitor-NodeJS_Examples) repository. +Each example project is provided in a separate branch. -```bash -npm install https://github.com/hampoelz/capacitor-nodejs/releases/download/v1.0.0-beta.3/capacitor-nodejs.tgz -npx cap sync -``` +## Getting Started -> ❗ Important -> -> For now Android 32-bit x86 support is disabled in Capacitor-NodeJS v1.0.0-beta.2 _(based on node.js v16)_ as there is currently no support for it in the latest version of the nodejs-mobile core library. However, you can use Capacitor-NodeJS v1.0.0-beta.1 which is based on node.js v12. +This guide shows how to add a minimal Node.js project to a Capacitor application and communicate between these processes. -## Examples +### Basics -Several example projects can be found in the [hampoelz/Capacitor-NodeJS_Examples](https://github.com/hampoelz/Capacitor-NodeJS_Examples) repository. +In the example below uses the Vite build system. However, any build system can be used as long as the following criteria are met: -Currently there is one example project available. One that uses the vitejs framework, located in the [`example/vite`](https://github.com/hampoelz/Capacitor-NodeJS_Examples/tree/example/vite) branch. More is coming soon. +1. The Node.js project (to be executed by the engine) must be located in a subdirectory named `nodejs` _(or the path set via `nodeDir`)_ of the Capacitor `webDir`. +2. The Node.js project must have a starting point, this can either be a script named `index.js` or a package.json with a `main` field. -## Getting Started +> For example if the Node.js project needs to be compiled or bundled then this output should be located in the subdirectory of the Capacitor `webDir`. + +### Minimal example + +In this example the directory for the app's source files is named `src`, the directory for static assets is named `static`, +the directory for the compiled files is named `dist`, and the directory for the Node.js project is named `nodejs`. -To add a Node.js project to your app, the following steps are required: - -1. Create a new directory called `nodejs` inside your app's source/public directory _(this is usually the `src` folder or if you use a build system the `public` folder)_. The `nodejs` dir will serve as your Node.js project folder. _(modules can be installed later in this folder)_ -2. Create a `package.json` file in it as the starting point of the Node.js integration: - ```json - { - "name": "capacitor-node-project", - "version": "1.0.0", - "description": "node part of the project", - "main": "main.js", - "author": "hampoelz", - "license": "MIT", - "dependencies": { - "bridge": "file:../../node_modules/capacitor-nodejs/assets/builtin_modules/bridge" - } +So the configurations should contain at least the following values: + +**Vite Configurations:** + +```typescript +// in vite.config.js or vite.config.ts +{ + root: './src', + publicDir: '../static', + build: { + outDir: '../dist' } - ``` -3. Create the main script of the Node.js integration _(in this case `main.js`)_, which could look like this: - ```javascript - const { channel } = require('bridge'); - - channel.addListener('msg-from-capacitor', message => { - console.log('[node] Message from Capacitor code: ' + message); - channel.send("msg-from-nodejs", "Replying to this message: " + message, "And optionally add further args"); - }); - ``` -4. Run `npm install --install-links` in your newly created Node.js project folder. +} +``` -After that, the project structure should look something like this: +**Capacitor Configurations:** +```typescript +// in capacitor.config.json or capacitor.config.ts +{ + "webDir": 'dist', + "plugins": { + "CapacitorNodeJS": { + "nodeDir": "nodejs" + } + } +} ``` -my-capacitor-app/ -├── ... -├── src/ # app source directory -│ ├── ... -│ ├── nodejs/ # Node.js project directory -│ │ ├── node_modules/ -│ │ ├── main.js # main script of the Node.js integration -│ │ ├── package.json # starting point of the Node.js integration -│ ├── ... -│ ├── index.html -├── capacitor.config.json -├── package.json -├── README.md -└── ... + +
+ +To meet the criteria from above using Vite, just create a new directory called `nodejs` inside the `static` directory. +And create a new file called `index.js` in it as the starting point. + +> Vite will copy assets from the `static` directory to the root of the `dist` directory as-is. +> So the created `nodejs` project directory will be placed in the Capacitor `webdir` after build. + +
+ +The project structure should now look something like this: + +```diff + capacitor-app/ + ├── ... + ├── dist/ # Capacitor webdir + ├── src/ # app source directory ++ ├── static/ # static assets ++ │ ├── nodejs/ # Node.js project directory ++ │ │ ├── index.js # Node.js main script + ├── capacitor.config.json + ├── vite.config.ts + ├── ... ``` -Now you can communicate with the Node.js layer in your Capacitor app: +
+ +After building and syncing the project, the main script will be executed by the Node.js runtime when the app is launched. + +A guide for a more complex Node.js project can be found in the [Complex Projects](#complex-projects) section. + +### Inter-Process Communication + +A bridge module to communicate between the Capacitor layer and the Node.js process is built-in. + +Use the following code in a Node.js script to wait for messages from the Capacitor layer and send messages back: + +```javascript +const { channel } = require('bridge'); + +// Listens to "msg-from-capacitor" from the Capacitor layer. +channel.addListener('msg-from-capacitor', message => { + console.log('[Node.js] Message from Capacitor: ' + message); + + // Sends a message back to the Capacitor layer. + channel.send("msg-from-nodejs", `Replying to the message '${message}'.`, "And optionally add more arguments."); +}); +``` + +
+ +Now you can communicate with the Node.js process in the Capacitor app: ```typescript import { NodeJS } from 'capacitor-nodejs'; // Listens to "msg-from-nodejs" from the Node.js process. NodeJS.addListener('msg-from-nodejs', event => { - document.body.innerHTML = `

First argument: ${event.args[0]}
Second argument: ${event.args[1]}

` + document.body.innerHTML = ` +

+ Message from Capacitor
+ First argument: ${event.args[0]}
+ Second argument: ${event.args[1]} +

+ `; console.log(event); }); -// Wait for the Node.js process to initialize. +// Waits for the Node.js process to initialize. NodeJS.whenReady().then(() => { - // Send a message to the Node.js process. + // Sends a message to the Node.js process. NodeJS.send({ eventName: "msg-from-capacitor", args: [ "Hello from Capacitor!" ] @@ -116,16 +182,319 @@ NodeJS.whenReady().then(() => { }); ``` -> ❗ Important +A full API documentation can be found in the [API - Bridge module](#api---bridge-module) section. + +--- + +## Complex Projects + +**The examples in this guide are a continuation of the examples in the [Getting Started](#getting-started) guide.** + +### Custom starting point + +In the [Getting Started](#getting-started) guide, the default starting point `index.js` was used for the Node.js project. +However, you may want to rename the main script or move it to subdirectories for a better organized project. + +To change this starting point, add a file called `package.json` to the Node.js project, which describes the project more in detail. +Using the `main` field in this file, a custom starting point for the Node.js project can be specified. +This should be a module relative to the root of the Capacitor project folder. + +The package.json file could look like the following, if the `main` field is set to `./server.js`: + +```javascript +// static/nodejs/package.json +{ + "name": "capacitor-nodejs-project", + "version": "1.0.0", + "main": "./server.js" +} +``` + +The project structure should then change to something like this: + +```diff + capacitor-app/ + ├── ... + ├── dist/ + ├── src/ + ├── static/ + │ ├── nodejs/ # Node.js project directory +- │ │ ├── index.js # main script (old) ++ │ │ ├── server.js # main script (new) ++ │ │ ├── package.json # starting point + ├── capacitor.config.json + ├── vite.config.ts + ├── ... +``` + +### Install Node.js Modules + +To install Node.js modules, the project requires a `package.json` file. +See section [Custom starting point](#custom-starting-point) for more details. + +The modules have to be installed in the Node.js project directory in which the `package.json` file was created using the npm CLI. +After installing modules, rebuild and sync the Capacitor project so that the Node.js project with the newly installed modules is updated in the application. + +For convenience, a postinstall script can be added to the main `package.json` in the Capacitor project to automatically install the modules of the Node.js project: + +```json +// package.json +{ + "scripts": { + "postinstall": "cd static/nodejs/ && npm install" + }, + // other config options +} +``` + +> You may also want to add a gitignore file to ignore unnecessary files. +> To do this, create a new file called `.gitignore` in the Node.js project directory and copy the contents of [github/gitignore/Node.gitignore](https://github.com/github/gitignore/blob/main/Node.gitignore) into it. + +> [!IMPORTANT] +> If you are using the [`capacitor-community/electron`](https://github.com/capacitor-community/electron) plugin, packaging with the electron-builder may cause problems since it does not include the modules installed in the Node.js project by default. > -> If you use a build system for your app, make sure to add the Node.js project directory to you static assets. _(Or copy the nodejs dir to the output directory, for example by adding `cp -r src/nodejs dist/` to your build steps.)_ +> To fix this issue, add the configuration `"includeSubNodeModules": true` to the `electron-builder.config.json`. + +### Improve Node.js loading times + +The Node.js project can quickly grow very large when installing modules. +For projects that contain a large number of files, the load time can be reduced by decreasing the number of files. + +For this reason, it is recommended to use bunder tools such as [Rollup.js](https://rollupjs.org/). +In the following example, Rollup is used to bundle the Node.js project with all the modules used in a single file. + +To get started install Rollup and its plugins "commonjs", "node-resolve" and "json" into the root of the Capacitor project. +If Vite is used as build system, Rollup is already pre-installed and does not need to be installed: + +```bash +# Install Rollup (If Vite is used, this command is not needed) +npm i --save-dev rollup + +# Install Rollup Plugins +npm i --save-dev @rollup/plugin-commonjs @rollup/plugin-json @rollup/plugin-node-resolve +``` + +Since the Node.js project is now to be bundled, the project structure needs some changes. +The Node.js project should no longer be copied directly from Vite to the Capacitor webDir directory, instead it will be bundled with Rollup. + +This means that the Node.js project directory needs to be moved from the static assets to somewhere else. +For example to the root directory of the Capcitors project: + +```diff + capacitor-app/ + ├── ... + ├── dist/ + ├── src/ + ├── static/ +- │ ├── nodejs/ +- │ │ ├── node_modules/ +- │ │ ├── server.js +- │ │ ├── package.json +- │ │ ├── ... ++ ├── nodejs/ ++ │ ├── node_modules/ ++ │ ├── server.js ++ │ ├── package.json ++ │ ├── ... + ├── capacitor.config.json + ├── vite.config.ts + ├── ... +``` + +> Don't forget to update the new path to the project in the postinstall script, +> if one is used, as described in the [Installing Node.js modules](#install-nodejs-modules) section. + +After the restructuring of the project, Rollup can be configured. +Create a new file called `rollup.config.mjs` with the following content: + +```typescript +// rollup.config.mjs +import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; +import nodeResolve from '@rollup/plugin-node-resolve'; + +export default { + input: 'nodejs/server.js', + output: { + file: 'dist/nodejs/index.js', + format: 'cjs', + }, + external: ['bridge'], + plugins: [ + commonjs(), + json(), + nodeResolve({ + preferBuiltins: true, + }), + ], +}; +``` + +To add bundling of the Node.js project to the build steps, modify the main `package.json` in the root of the Capacitor project +and add `&& rollup -c rollup.config.mjs` to the `build` entry in the `scripts` object: + +```diff +# package.json +{ + "scripts": { +- "build": "vite build" ++ "build": "vite build && rollup -c rollup.config.mjs" + } +} +``` + +So the project structure should look something like this: + +```diff + capacitor-app/ + ├── ... + ├── dist/ + ├── src/ + ├── nodejs/ + │ ├── node_modules/ + │ ├── server.js + │ ├── package.json + │ ├── ... + ├── capacitor.config.json ++ ├── rollup.config.mjs + ├── vite.config.ts + ├── ... +``` + +After building and syncing the project, the Node.js runtime should start faster now. + +### Manual Node.js runtime start + +By default, the Node.js runtime starts automatically with application start. +However, this behavior may not be suitable for all projects. + +This behavior can be disabled globally via the `startMode` plugin configuration: + +```diff +# in capacitor.config.json or capacitor.config.ts +{ + "webDir": 'dist', + "plugins": { + "CapacitorNodeJS": { + "nodeDir": "nodejs", ++ "startMode": "manual", + }, + }, +} +``` + +Now the Node.js runtime has to be started manually with the `NodeJS.start()` command: + +```typescript +import { NodeJS } from 'capacitor-nodejs'; + +// Starts the Node.js engine. +NodeJS.start(); +``` + +Manually starting the Node.js runtime provides options to override the `nodeDir` configuration or even the path for the main script. + +In addition, arguments can be passed to the main script and environment variables for the Node.js runtime can be set: + +```typescript +import { NodeJS } from 'capacitor-nodejs'; + +// Options for starting the Node.js engine manually. +const options = { + args: [ "--option", "value" ], + env: { + "DB_HOST": "localhost", + "DB_USER": "myuser", + "DB_PASS": "mypassword" + } +} + +// Starts the Node.js engine with properties as set by the `options`. +NodeJS.start(options); +``` + +### Data storage + +Mobile platforms are different than the usual desktop platforms in that they require applications to write in specific sandboxed paths and don't have permissions to write elsewhere. + +The built-in bridge module provides an API to get a per-user application data directory on each platform: + +```javascript +const { getDataPath } = require('bridge'); + +// Get a path where data can be read and written. +const dataPath = getDataPath(); +``` + +> [!WARNING] > +> Do not use the Node.js project directory itself for data storage, it will be overwritten after each application update! -> ℹī¸ Information -> -> If you are using the [`capacitor-community/electron`](https://github.com/capacitor-community/electron) plugin, packaging with the electron-builder may cause problems since it does not include the modules from the nodejs project by default. -> -> To fix this issue, add `"includeSubNodeModules": true` to your `electron-builder.config.json`. +To get a path for temporary files, the node.js inbuilt method `os.tmpdir()` can be used: + +```javascript +const os = require('os'); + +// Get a path for temporary files. +const tmpPath = os.tmpdir(); +``` + +> [!WARNING] +> +> On Android, the files in the cache are kept until the system needs space, so it increases the application's disk space unless the developer manually deletes them. + +--- + +## Mobile Node.js APIs differences + +Not every API is supported on mobile devices. Mobile operating systems do not allow applications to call certain APIs that are expected to be available on other operating systems. + +### child_process module + +Mobile applications are expected to be a single process. +APIs that create new processes, such as `child_process.spawn()` or `child_process.fork()` will therefore run into permission issues. + +### file system (fs) module + +On mobile platforms, the current working directory is the root directory of the file system. +This can lead to unexpected behavior in code that assumes that the current working directory is set to the directory of the Node.js project. + +On Android creating hard links (`fs.link()` and `fs.linkSync()`) is not supported. + +### internationalization (intl) module + +The internationalization (`intl`) module is not available on current nodejs-mobile builds. + +### os module + +- `os.cpus()` may return inconsistent/unreliable results, since different OS versions will have different permissions for accessing CPU information. +- `os.homedir()` on mobile platforms there is no concept of user home directories. +- `os.platform()` can also return 'android' or 'ios', depending on the platform. + +On Android, the files in the cache (`os.tmpdir()`) are kept until the system needs space, so it increases the application's disk space unless the developer manually deletes them. + +### process module + +- `process.cwd()` is the root directory of the file system, instead of the start directory of the project. +- `process.exit()` is not allowed by the Apple App Store guildelines. +- `process.stdin` is not available. +- `process.platform` can also be 'android' or 'ios', depending on the platform. +- `process.versions` includes the 'mobile' key, containing the nodejs-mobile core library version. + +The following functions are only available on POSIX platforms, so they are unavailable on Android: + +- `process.getegid()` +- `process.geteuid()` +- `process.getgid()` +- `process.getgroups()` +- `process.getuid()` +- `process.setegid()` +- `process.seteuid()` +- `process.setgid()` +- `process.setgroups()` +- `process.setuid()` + +--- ## Configuration @@ -134,9 +503,10 @@ NodeJS.whenReady().then(() => { These config values are available: -| Prop | Type | Description | Default | Since | -| ------------- | ------------------- | ------------------------------------------------------------------------------ | --------------------- | ----- | -| **`nodeDir`** | string | Relative path of the integrated Node.js project based on the Capacitor webdir. | "nodejs" | 1.0.0 | +| Prop | Type | Description | Default | Since | +| --------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | ----- | +| **`nodeDir`** | string | Relative path of the integrated Node.js project based on the Capacitor webdir. | "nodejs" | 1.0.0 | +| **`startMode`** | 'auto' \| 'manual' | Startup mode of the Node.js engine. The following values are accepted: `auto`: The Node.js engine starts automatically when the application is launched. `manual`: The Node.js engine is started via the `NodeJS.start()` method. | "auto" | 1.0.0 | ### Examples @@ -146,7 +516,8 @@ In `capacitor.config.json`: { "plugins": { "CapacitorNodeJS": { - "nodeDir": "custom-nodejs" + "nodeDir": "custom-nodejs", + "startMode": "manual" } } } @@ -163,6 +534,7 @@ const config: CapacitorConfig = { plugins: { CapacitorNodeJS: { nodeDir: "custom-nodejs", + startMode: "manual", }, }, }; @@ -172,44 +544,33 @@ export default config; -If you change your `nodeDir` to `custom-nodejs`, then your project structure should look something like this: +--- -``` -my-capacitor-app/ -├── ... -├── src/ # app source directory -│ ├── ... -│ ├── custom-nodejs/ # the new Node.js project directory -│ │ ├── node_modules/ -│ │ ├── main.js -│ │ ├── package.json -│ ├── ... -│ ├── index.html -├── capacitor.config.json -├── package.json -├── README.md -└── ... -``` +## API - Bridge module -## Node Modules +The `bridge` module is built-in. It provides an API to communicate between the Capacitor layer and the Node.js process, as well as an API to get a per-user application data directory on each platform. -Node modules can be added to the project using npm. The Node modules have to be installed in the Node.js project folder in which the `package.json` file was created. +TypeScript declarations for this `bridge` module can be manually installed as dev-dependency. If needed, the types-only package can be found under `node_modules/capacitor-nodejs/assets/types/bridge` in the root of the Capacitor project. -Go to the Node.js project folder and proceed with the installation of the Node modules you want to add to your Node.js project. -Sync and rebuild your Capacitor project so that the newly added Node modules are added to the application. +-------------------- -On Android, the plugin extracts the project files and the Node modules from the APK assets in order to make them available to the Node.js for Mobile Apps engine. They are extracted from the APK and copied to a working folder (`context.getFilesDir().getAbsolutePath() + "/public/"` where `` is the Node.js project folder configured in the `capacitor.config.json` file. If there is no configuration, the `` can be omitted in the path) when the application is launched for the first time or a new version of the application has been installed. +* [`getDataPath()`](#getDataPath) -> ⚠ī¸ Warning -> -> Given the project folder will be overwritten after each application update, it should not be used for persistent data storage. +### getDataPath() -You may want to add a gitignore file to ignore unnecessary files. To do this, create a new file named `.gitignore` in the Node.js project folder and copy the contents of [github.com/github/gitignore/blob/master/Node.gitignore](https://github.com/github/gitignore/blob/master/Node.gitignore) into it. +```typescript +getDataPath: () => string +``` -## API - Node.js layer +Returns a path for a per-user application data directory on each platform, where data can be read and written. -The `channel` module is an [Event Emitter](https://nodejs.org/api/events.html#events_class_eventemitter). It provides a few methods so you can send messages from the Node.js process to the Capacitor layer. You can also receive replies from the Capacitor layer. To use this module, you need to add it to the dependencies of your Node.js project, as described in the [Getting Started](#getting-started) section. +**Since:** 1.0.0 + +-------------------- + + +The `channel` class of the `bridge` module is an [Event Emitter](https://nodejs.org/api/events.html#events_class_eventemitter). It provides a few methods to send messages from the Node.js process to the Capacitor layer, and to receive replies from the Capacitor layer. It has the following method to listen for events and send messages: @@ -351,14 +712,16 @@ Removes all listeners, or those of the specified `eventName`. -------------------- + ## API - Capacitor layer -The `NodeJS` module is the API you use in your Capacitor app. It provides a few methods so you can send messages from the Node.js layer and wait for them. +The `NodeJS` module is the API used in the Capacitor app. It provides a few methods to send messages from the Node.js layer and wait for them. It has the following methods: +* [`start(...)`](#start) * [`send(...)`](#send) * [`whenReady()`](#whenready) * [`addListener(string, ...)`](#addlistenerstring) @@ -372,6 +735,25 @@ It has the following methods: +### start(...) + +```typescript +start(options?: StartOptions) => Promise +``` + +Starts the Node.js engine with properties as set by the `options`. + +**Note:** This method is only available if the Node.js engine startup mode was set to `'manual'` via the plugin configuration. + +| Param | Type | +| ------------- | ----------------------------------------------------- | +| **`options`** | StartOptions | + +**Since:** 1.0.0 + +-------------------- + + ### send(...) ```typescript @@ -462,6 +844,23 @@ Removes all listeners, or those of the specified `eventName`, for this plugin. ### Interfaces +#### StartOptions + +An interface containing the options used when starting the Node.js engine manually. + +| Prop | Type | Description | Since | +| ------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | +| **`nodeDir`** | string | Relative path of the integrated Node.js project based on the Capacitor webdir. Defaults to the `nodeDir` field of the global plugin configuration. If the `nodeDir` config is not set, `nodejs` in the Capacitor webdir is used as Node.js project directory. | 1.0.0 | +| **`script`** | string | The primary entry point to the Node.js program. This should be a module relative to the root of the Node.js project folder. Defaults to the `main` field in the project's package.json. If the `main` field is not set, `index.js` in the project's root folder is used. | 1.0.0 | +| **`args`** | string[] | A list of string arguments. | 1.0.0 | +| **`env`** | NodeEnv | Environment key-value pairs. | 1.0.0 | + + +#### NodeEnv + +An interface that holds environment variables as string key-value pairs. + + #### ChannelPayloadData The payload data to send a message to the web page via `eventName`, diff --git a/UPGRADING.md b/UPGRADING.md index 55e97d4..5aa143a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,51 +1,68 @@ +## Capacitor-NodeJS v1.0.0-beta.4 + +Remove the `bridge` module from your Node.js project dependencies. It is now a built-in module on all platforms. + +```diff + { + "name": "capacitor-nodejs-project", + "version": "1.0.0", + "main": "./server.js" + "dependencies": { +- "bridge": "file:../../node_modules/capacitor-nodejs/assets/builtin_modules/bridge" + } + } +``` + ## Capacitor-NodeJS v1.0.0-beta.3 -1. Change the plugin name in your Capacitor configuration from `NodeJS` to `CapacitorNodeJS`. For example in `capacitor.config.json`: - ```diff +Change the plugin name in your Capacitor configuration from `NodeJS` to `CapacitorNodeJS`. +For example in `capacitor.config.json`: + +```diff { "plugins": { - - "NodeJS": { - + "CapacitorNodeJS": { - "nodeDir": "custom-nodejs" +- "NodeJS": { ++ "CapacitorNodeJS": { + "nodeDir": "custom-nodejs" + } } } } - ``` +``` ## Capacitor-NodeJS v1.0.0-beta.1 -1. Move your NodeJS project from you app's source directory to the subfolder `nodejs`. You can skip this step if you set a custom nodeDir in your capacitor configuration. Your project structure should then look something like this: - ``` - my-capacitor-app/ - ├── ... - ├── src/ # app source directory - │ ├── ... - │ ├── nodejs/ # NodeJS project directory - │ │ ├── node_modules/ - │ │ ├── main.js - │ │ ├── package.json - │ ├── ... - │ ├── index.html - ├── capacitor.config.json - ├── package.json - ├── README.md - └── ... - ``` +1. Move your NodeJS project from you app's Capacitor webDir to the subfolder `nodejs`. You can skip this step if you set a custom nodeDir in your capacitor configuration. Your project structure should then look something like this: + +``` +my-capacitor-app/ +├── ... +├── src/ # Capacitor webDir +│ ├── ... +│ ├── nodejs/ # Node.js project directory +│ │ ├── node_modules/ +│ │ ├── main.js +│ │ ├── package.json +│ ├── ... +│ ├── index.html +├── capacitor.config.json +├── package.json +├── README.md +└── ... +``` 2. Add `"bridge": "file:../../node_modules/capacitor-nodejs/assets/builtin_modules/bridge"` to `dependencies` in your NodeJS project's package.json file. The file should then look something like this: - ```json + +```diff { - "name": "capacitor-node-project", - "version": "1.0.0", - "description": "node part of the project", - "main": "main.js", - "author": "hampoelz", - "license": "MIT", - "dependencies": { - "bridge": "file:../../node_modules/capacitor-nodejs/assets/builtin_modules/bridge" - } + "name": "capacitor-nodejs-project", + "version": "1.0.0", + "main": "./server.js" + "dependencies": { ++ "bridge": "file:../../node_modules/capacitor-nodejs/assets/builtin_modules/bridge" + } } - ``` +``` 3. Run `npm install` in your NodeJS project folder to install the added bridge module. diff --git a/bridge/src/bridge.ts b/bridge/src/bridge.ts index 7373f48..2a23282 100644 --- a/bridge/src/bridge.ts +++ b/bridge/src/bridge.ts @@ -63,6 +63,13 @@ class Channel extends EventEmitter { }); } + /** + * Sends a message to the Capacitor layer via eventName, along with arguments. + * Arguments will be serialized with JSON. + * + * @param eventName The name of the event being send to. + * @param args The Array of arguments to send. + */ send(eventName: string, ...args: any[]) { if (eventName === undefined || eventName === '') { throw new Error("Required parameter 'eventName' was not specified"); @@ -87,12 +94,59 @@ class Channel extends EventEmitter { self.emit(eventName, ...args); }); } + + /** + * Listens to `eventName` and calls `listener(args...)` when a new message arrives from the Capacitor layer. + */ + override on(eventName: string, listener: (...args: any[]) => void): this { + return super.on(eventName, listener); + } + + /** + * Listens one time to `eventName` and calls `listener(args...)` when a new message + * arrives from the Capacitor layer, after which it is removed. + */ + override once(eventName: string, listener: (...args: any[]) => void): this { + return super.once(eventName, listener); + } + + /** + * Alias for `channel.on(eventName, listener)`. + */ + override addListener(eventName: string, listener: (...args: any[]) => void): this { + return super.once(eventName, listener); + } + + /** + * Removes the specified `listener` from the listener array for the specified `eventName`. + */ + override removeListener(eventName: string, listener: (...args: any[]) => void): this { + return super.removeListener(eventName, listener); + } + + /** + * Removes all listeners, or those of the specified `eventName`. + * + * @param eventName The name of the event all listeners will be removed from. + */ + override removeAllListeners(eventName?: string): this { + return super.removeAllListeners(eventName); + } } const appChannel = new Channel('APP_CHANNEL'); + +/** + * Provides a few methods to send messages from the Node.js process to the Capacitor layer, + * and to receive replies from the Capacitor layer. + */ const eventChannel = new Channel('EVENT_CHANNEL'); -function getDataDir(): string { +/** + * Returns a path for a per-user application data directory on each platform, + * where data can be read and written. + */ +function getDataPath(): string { const path = process.env['DATADIR']; if (!path) { throw new Error('Unable to get a directory for persistent data storage.'); @@ -100,4 +154,4 @@ function getDataDir(): string { return path; } -export { ChannelMessageCodec, appChannel, eventChannel, getDataDir }; +export { ChannelMessageCodec, appChannel, eventChannel, getDataPath }; diff --git a/bridge/src/index.ts b/bridge/src/index.ts index d77003d..c87c138 100644 --- a/bridge/src/index.ts +++ b/bridge/src/index.ts @@ -1,5 +1,5 @@ -import { appChannel, eventChannel, getDataDir, platform } from './bridge'; +import { appChannel, eventChannel, getDataPath } from './bridge'; appChannel.send('ready'); -export { eventChannel as channel, getDataDir as datadir, platform }; +export { eventChannel as channel, getDataPath }; diff --git a/package.json b/package.json index d300dbd..10f6e25 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "capacitor-nodejs", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "description": "A full-fledged Node.js runtime for Capacitor apps", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", diff --git a/src/NodeJS.ts b/src/NodeJS.ts index e08e5ba..d1e2a0c 100644 --- a/src/NodeJS.ts +++ b/src/NodeJS.ts @@ -2,10 +2,10 @@ import type { PluginListenerHandle } from '@capacitor/core'; import { Capacitor } from '@capacitor/core'; import type { - ChannelPayloadData, - ChannelCallbackData, - ChannelListenerCallback, - StartOptions + ChannelPayloadData, + ChannelCallbackData, + ChannelListenerCallback, + StartOptions } from './definitions'; import { CapacitorNodeJS } from './implementation'; @@ -13,6 +13,8 @@ export interface NodeJSInterface { /** * Starts the Node.js engine with properties as set by the `options`. * + * **Note:** This method is only available if the Node.js engine startup mode was set to `'manual'` via the plugin configuration. + * * @since 1.0.0 */ start(options?: StartOptions): Promise; diff --git a/src/definitions.ts b/src/definitions.ts index ffbc2e7..2fd428a 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -18,24 +18,19 @@ declare module '@capacitor/cli' { /** * Startup mode of the Node.js engine. * + * The following values are accepted: + * `auto`: The Node.js engine starts automatically when the application is launched. + * `manual`: The Node.js engine is started via the `NodeJS.start()` method. + * * @since 1.0.0 * @default "auto" * @example "manual" */ - startMode?: StartMode; + startMode?: 'auto' | 'manual'; }; } } -/** - * A string that represents the Node.js engine startup mode. - * - * The following values are accepted: - * - `auto`: The Node.js engine starts automatically when the application is launched. - * - `manual`: The Node.js engine is started via the `NodeJS.start()` method. - */ -export type StartMode = 'auto' | 'manual'; - /** * An interface containing the options used when starting the Node.js engine manually. */ @@ -52,7 +47,7 @@ export interface StartOptions { /** * The primary entry point to the Node.js program. - * This should be a module relative to the root of your Node.js project folder. + * This should be a module relative to the root of the Node.js project folder. * * Defaults to the `main` field in the project's package.json. If the `main` field * is not set, `index.js` in the project's root folder is used.