@deltachat/stdio-rpc-server 1.137.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +77 -0
- package/index.d.ts +39 -0
- package/index.js +159 -0
- package/package.json +26 -0
- package/src/const.js +6 -0
- package/src/errors.js +41 -0
package/Readme.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
## npm package for deltachat-rpc-server
|
|
2
|
+
|
|
3
|
+
This is the successor of `deltachat-node`,
|
|
4
|
+
it does not use NAPI bindings but instead uses stdio executables
|
|
5
|
+
to let you talk to core over jsonrpc over stdio.
|
|
6
|
+
This simplifies cross-compilation and even reduces binary size (no CFFI layer and no NAPI layer).
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
> The **minimum** nodejs version for this package is `20.11`
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
npm i @deltachat/stdio-rpc-server @deltachat/jsonrpc-client
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
import { startDeltaChat } from "@deltachat/stdio-rpc-server";
|
|
18
|
+
import { C } from "@deltachat/jsonrpc-client";
|
|
19
|
+
|
|
20
|
+
async function main() {
|
|
21
|
+
const dc = await startDeltaChat("deltachat-data");
|
|
22
|
+
console.log(await dc.rpc.getSystemInfo());
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For a more complete example refer to https://github.com/deltachat-bot/echo/pull/69/files (TODO change link when pr is merged).
|
|
27
|
+
|
|
28
|
+
## How to use on an unsupported platform
|
|
29
|
+
|
|
30
|
+
<!-- todo instructions, will uses an env var for pointing to `deltachat-rpx-server` binary -->
|
|
31
|
+
|
|
32
|
+
<!-- todo copy parts from https://github.com/deltachat/deltachat-desktop/blob/7045c6f549e4b9d5caa0709d5bd314bbd9fd53db/docs/UPDATE_CORE.md -->
|
|
33
|
+
|
|
34
|
+
## How does it work when you install it
|
|
35
|
+
|
|
36
|
+
NPM automatically installs platform dependent optional dependencies when `os` and `cpu` fields are set correctly.
|
|
37
|
+
|
|
38
|
+
references:
|
|
39
|
+
|
|
40
|
+
- https://napi.rs/docs/deep-dive/release#3-the-native-addon-for-different-platforms-is-distributed-through-different-npm-packages, [webarchive version](https://web.archive.org/web/20240309234250/https://napi.rs/docs/deep-dive/release#3-the-native-addon-for-different-platforms-is-distributed-through-different-npm-packages)
|
|
41
|
+
- https://docs.npmjs.com/cli/v6/configuring-npm/package-json#cpu
|
|
42
|
+
- https://docs.npmjs.com/cli/v6/configuring-npm/package-json#os
|
|
43
|
+
|
|
44
|
+
When you import this package it searches for the rpc server in the following locations and order:
|
|
45
|
+
|
|
46
|
+
1. `DELTA_CHAT_RPC_SERVER` environment variable
|
|
47
|
+
2. in PATH
|
|
48
|
+
- unless `DELTA_CHAT_SKIP_PATH=1` is specified
|
|
49
|
+
- searches in .cargo/bin directory first
|
|
50
|
+
- but there an additional version check is performed
|
|
51
|
+
3. prebuilds in npm packages
|
|
52
|
+
|
|
53
|
+
## How do you built this package in CI
|
|
54
|
+
|
|
55
|
+
- To build platform packages, run the `build_platform_package.py` script:
|
|
56
|
+
```
|
|
57
|
+
python3 build_platform_package.py <cargo-target>
|
|
58
|
+
# example
|
|
59
|
+
python3 build_platform_package.py x86_64-apple-darwin
|
|
60
|
+
```
|
|
61
|
+
- Then pass it as an artifact to the last CI action that publishes the main package.
|
|
62
|
+
- upload all packages from `deltachat-rpc-server/npm-package/platform_package`.
|
|
63
|
+
- then publish `deltachat-rpc-server/npm-package`,
|
|
64
|
+
- this will run `update_optional_dependencie_and_version.js` (in the `prepack` script),
|
|
65
|
+
which puts all platform packages into `optionalDependencies` and updates the `version` in `package.json`
|
|
66
|
+
|
|
67
|
+
## How to build a version you can use localy on your host machine for development
|
|
68
|
+
|
|
69
|
+
You can not install the npm packet from the previous section locally, unless you have a local npm registry set up where you upload it too. This is why we have seperate scripts for making it work for local installation.
|
|
70
|
+
|
|
71
|
+
- If you just need your host platform run `python scripts/make_local_dev_version.py`
|
|
72
|
+
- note: this clears the `platform_package` folder
|
|
73
|
+
- (advanced) If you need more than one platform for local install you can just run `node scripts/update_optional_dependencies_and_version.js` after building multiple plaftorms with `build_platform_package.py`
|
|
74
|
+
|
|
75
|
+
## Thanks to nlnet
|
|
76
|
+
|
|
77
|
+
The initial work on this package was funded by nlnet as part of the [Delta Tauri](https://nlnet.nl/project/DeltaTauri/) Project.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { StdioDeltaChat } from "@deltachat/jsonrpc-client";
|
|
2
|
+
|
|
3
|
+
export interface SearchOptions {
|
|
4
|
+
/** whether to disable looking for deltachat-rpc-server inside of $PATH */
|
|
5
|
+
skipSearchInPath: boolean;
|
|
6
|
+
|
|
7
|
+
/** whether to disable the DELTA_CHAT_RPC_SERVER environment variable */
|
|
8
|
+
disableEnvPath: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @returns absolute path to deltachat-rpc-server binary
|
|
14
|
+
* @throws when it is not found
|
|
15
|
+
*/
|
|
16
|
+
export function getRPCServerPath(
|
|
17
|
+
options?: Partial<SearchOptions>
|
|
18
|
+
): Promise<string>;
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export type DeltaChatOverJsonRpcServer = StdioDeltaChat & {
|
|
23
|
+
shutdown: () => Promise<void>;
|
|
24
|
+
readonly pathToServerBinary: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
*
|
|
30
|
+
* @param directory directory for accounts folder
|
|
31
|
+
* @param options
|
|
32
|
+
*/
|
|
33
|
+
export function startDeltaChat(directory: string, options?: Partial<SearchOptions> ): Promise<DeltaChatOverJsonRpcServer>
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
export namespace FnTypes {
|
|
37
|
+
export type getRPCServerPath = typeof getRPCServerPath
|
|
38
|
+
export type startDeltaChat = typeof startDeltaChat
|
|
39
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
import { execFile, spawn } from "node:child_process";
|
|
3
|
+
import { stat, readdir } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import { join, basename } from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import {
|
|
9
|
+
ENV_VAR_NAME,
|
|
10
|
+
PATH_EXECUTABLE_NAME,
|
|
11
|
+
SKIP_SEARCH_IN_PATH,
|
|
12
|
+
} from "./src/const.js";
|
|
13
|
+
import {
|
|
14
|
+
ENV_VAR_LOCATION_NOT_FOUND,
|
|
15
|
+
FAILED_TO_START_SERVER_EXECUTABLE,
|
|
16
|
+
NPM_NOT_FOUND_SUPPORTED_PLATFORM_ERROR,
|
|
17
|
+
NPM_NOT_FOUND_UNSUPPORTED_PLATFORM_ERROR,
|
|
18
|
+
} from "./src/errors.js";
|
|
19
|
+
|
|
20
|
+
// Because this is not compiled by typescript, esm needs this stuff (` with { type: "json" };`,
|
|
21
|
+
// nodejs still complains about it being experimental, but deno also uses it, so treefit bets taht it will become standard)
|
|
22
|
+
import package_json from "./package.json" with { type: "json" };
|
|
23
|
+
import { createRequire } from "node:module";
|
|
24
|
+
|
|
25
|
+
// exports
|
|
26
|
+
// - [ ] a raw starter that has a stdin/out handle thingie like desktop uses
|
|
27
|
+
// - [X] a function that already wraps the stdio handle from above into the deltachat jsonrpc bindings
|
|
28
|
+
|
|
29
|
+
function findRPCServerInNodeModules() {
|
|
30
|
+
const arch = os.arch();
|
|
31
|
+
const operating_system = process.platform;
|
|
32
|
+
const package_name = `@deltachat/stdio-rpc-server-${operating_system}-${arch}`;
|
|
33
|
+
try {
|
|
34
|
+
const { resolve } = createRequire(import.meta.url);
|
|
35
|
+
return resolve(package_name);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.debug("findRpcServerInNodeModules", error);
|
|
38
|
+
if (Object.keys(package_json.optionalDependencies).includes(package_name)) {
|
|
39
|
+
throw new Error(NPM_NOT_FOUND_SUPPORTED_PLATFORM_ERROR(package_name));
|
|
40
|
+
} else {
|
|
41
|
+
throw new Error(NPM_NOT_FOUND_UNSUPPORTED_PLATFORM_ERROR());
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @type {import("./index").FnTypes.getRPCServerPath} */
|
|
47
|
+
export async function getRPCServerPath(
|
|
48
|
+
options = { skipSearchInPath: false, disableEnvPath: false }
|
|
49
|
+
) {
|
|
50
|
+
// @TODO: improve confusing naming of these options
|
|
51
|
+
const { skipSearchInPath, disableEnvPath } = options;
|
|
52
|
+
// 1. check if it is set as env var
|
|
53
|
+
if (process.env[ENV_VAR_NAME] && !disableEnvPath) {
|
|
54
|
+
try {
|
|
55
|
+
if (!(await stat(process.env[ENV_VAR_NAME])).isFile()) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`expected ${ENV_VAR_NAME} to point to the deltachat-rpc-server executable`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw new Error(ENV_VAR_LOCATION_NOT_FOUND());
|
|
62
|
+
}
|
|
63
|
+
return process.env[ENV_VAR_NAME];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 2. check if it can be found in PATH
|
|
67
|
+
if (!process.env[SKIP_SEARCH_IN_PATH] && !skipSearchInPath) {
|
|
68
|
+
const path_dirs = process.env["PATH"].split(/:|;/);
|
|
69
|
+
// check cargo dir first
|
|
70
|
+
const cargo_dirs = path_dirs.filter((p) => p.endsWith(".cargo/bin"));
|
|
71
|
+
const findExecutable = async (directory) => {
|
|
72
|
+
const files = await readdir(directory);
|
|
73
|
+
const file = files.find((p) =>
|
|
74
|
+
basename(p).includes(PATH_EXECUTABLE_NAME)
|
|
75
|
+
);
|
|
76
|
+
if (file) {
|
|
77
|
+
return join(directory, file);
|
|
78
|
+
} else {
|
|
79
|
+
throw null;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const executable_search = // TODO make code simpler to read
|
|
83
|
+
(await Promise.allSettled(cargo_dirs.map(findExecutable))).find(
|
|
84
|
+
({ status }) => status === "fulfilled"
|
|
85
|
+
) ||
|
|
86
|
+
(await Promise.allSettled(path_dirs.map(findExecutable))).find(
|
|
87
|
+
({ status }) => status === "fulfilled"
|
|
88
|
+
);
|
|
89
|
+
// TODO maybe we could the system do this stuff automatically
|
|
90
|
+
// by just trying to execute it and then use "which" (unix) or "where" (windows) to get the path to the executable
|
|
91
|
+
if (executable_search.status === "fulfilled") {
|
|
92
|
+
const executable = executable_search.value;
|
|
93
|
+
// test if it is the right version
|
|
94
|
+
try {
|
|
95
|
+
// for some unknown reason it is in stderr and not in stdout
|
|
96
|
+
const { stderr } = await promisify(execFile)(executable, ["--version"]);
|
|
97
|
+
const version = stderr.slice(0, stderr.indexOf("\n"));
|
|
98
|
+
if (package_json.version !== version) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`version mismatch: (npm package: ${package_json.version}) (installed ${PATH_EXECUTABLE_NAME} version: ${version})`
|
|
101
|
+
);
|
|
102
|
+
} else {
|
|
103
|
+
return executable;
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(
|
|
107
|
+
"Found executable in PATH, but there was an error: " + error
|
|
108
|
+
);
|
|
109
|
+
console.error("So falling back to using prebuild...");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// 3. check for prebuilds
|
|
114
|
+
|
|
115
|
+
return findRPCServerInNodeModules();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
import { StdioDeltaChat } from "@deltachat/jsonrpc-client";
|
|
119
|
+
|
|
120
|
+
/** @type {import("./index").FnTypes.startDeltaChat} */
|
|
121
|
+
export async function startDeltaChat(directory, options) {
|
|
122
|
+
const pathToServerBinary = await getRPCServerPath(options);
|
|
123
|
+
const server = spawn(pathToServerBinary, {
|
|
124
|
+
env: {
|
|
125
|
+
RUST_LOG: process.env.RUST_LOG || "info",
|
|
126
|
+
DC_ACCOUNTS_PATH: directory,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
server.on("error", (err) => {
|
|
131
|
+
throw new Error(FAILED_TO_START_SERVER_EXECUTABLE(pathToServerBinary, err));
|
|
132
|
+
});
|
|
133
|
+
let shouldClose = false;
|
|
134
|
+
|
|
135
|
+
server.on("exit", () => {
|
|
136
|
+
if (shouldClose) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
throw new Error("Server quit");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
server.stderr.pipe(process.stderr);
|
|
143
|
+
|
|
144
|
+
/** @type {import('./index').DeltaChatOverJsonRpcServer} */
|
|
145
|
+
//@ts-expect-error
|
|
146
|
+
const dc = new StdioDeltaChat(server.stdin, server.stdout, true);
|
|
147
|
+
|
|
148
|
+
dc.shutdown = async () => {
|
|
149
|
+
shouldClose = true;
|
|
150
|
+
if (!server.kill()) {
|
|
151
|
+
console.log("server termination failed");
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
//@ts-expect-error
|
|
156
|
+
dc.pathToServerBinary = pathToServerBinary
|
|
157
|
+
|
|
158
|
+
return dc;
|
|
159
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"name": "@deltachat/stdio-rpc-server",
|
|
4
|
+
"version": "1.137.4",
|
|
5
|
+
"license": "MPL-2.0",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"prepack": "node scripts/update_optional_dependencies_and_version.js"
|
|
10
|
+
},
|
|
11
|
+
"optionalDependencies": {
|
|
12
|
+
"@deltachat/stdio-rpc-server-darwin-arm64": "1.137.4",
|
|
13
|
+
"@deltachat/stdio-rpc-server-android-arm64": "1.137.4",
|
|
14
|
+
"@deltachat/stdio-rpc-server-linux-arm64": "1.137.4",
|
|
15
|
+
"@deltachat/stdio-rpc-server-linux-arm": "1.137.4",
|
|
16
|
+
"@deltachat/stdio-rpc-server-android-arm": "1.137.4",
|
|
17
|
+
"@deltachat/stdio-rpc-server-win32-i32": "1.137.4",
|
|
18
|
+
"@deltachat/stdio-rpc-server-linux-i32": "1.137.4",
|
|
19
|
+
"@deltachat/stdio-rpc-server-darwin-x64": "1.137.4",
|
|
20
|
+
"@deltachat/stdio-rpc-server-win32-x64": "1.137.4",
|
|
21
|
+
"@deltachat/stdio-rpc-server-linux-x64": "1.137.4"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@deltachat/jsonrpc-client": "*"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/const.js
ADDED
package/src/errors.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
import { ENV_VAR_NAME } from "./const.js";
|
|
3
|
+
|
|
4
|
+
const cargoInstallCommand =
|
|
5
|
+
"cargo install --git https://github.com/deltachat/deltachat-core-rust deltachat-rpc-server";
|
|
6
|
+
|
|
7
|
+
export function NPM_NOT_FOUND_SUPPORTED_PLATFORM_ERROR(package_name) {
|
|
8
|
+
return `deltachat-rpc-server not found:
|
|
9
|
+
|
|
10
|
+
- Install it with "npm i ${package_name}"
|
|
11
|
+
- or download/compile deltachat-rpc-server for your platform and
|
|
12
|
+
- either put it into your PATH (for example with "${cargoInstallCommand}")
|
|
13
|
+
- or set the "${ENV_VAR_NAME}" env var to the path to deltachat-rpc-server"`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function NPM_NOT_FOUND_UNSUPPORTED_PLATFORM_ERROR() {
|
|
17
|
+
return `deltachat-rpc-server not found:
|
|
18
|
+
|
|
19
|
+
Unfortunately no prebuild is available for your system, so you need to provide deltachat-rpc-server yourself.
|
|
20
|
+
|
|
21
|
+
- Download or Compile deltachat-rpc-server for your platform and
|
|
22
|
+
- either put it into your PATH (for example with "${cargoInstallCommand}")
|
|
23
|
+
- or set the "${ENV_VAR_NAME}" env var to the path to deltachat-rpc-server"`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function ENV_VAR_LOCATION_NOT_FOUND(error) {
|
|
27
|
+
return `deltachat-rpc-server not found in ${ENV_VAR_NAME}:
|
|
28
|
+
|
|
29
|
+
Error: ${error}
|
|
30
|
+
|
|
31
|
+
Content of ${ENV_VAR_NAME}: "${process.env[ENV_VAR_NAME]}"`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function FAILED_TO_START_SERVER_EXECUTABLE(pathToServerBinary, error) {
|
|
35
|
+
return `Failed to start server executable at '${pathToServerBinary}',
|
|
36
|
+
|
|
37
|
+
Error: ${error}
|
|
38
|
+
|
|
39
|
+
Make sure the deltachat-rpc-server binary exists at this location
|
|
40
|
+
and you can start it with \`${pathToServerBinary} --version\``;
|
|
41
|
+
}
|