@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 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
@@ -0,0 +1,6 @@
1
+ //@ts-check
2
+
3
+ export const PATH_EXECUTABLE_NAME = 'deltachat-rpc-server'
4
+
5
+ export const ENV_VAR_NAME = "DELTA_CHAT_RPC_SERVER"
6
+ export const SKIP_SEARCH_IN_PATH = "DELTA_CHAT_SKIP_PATH"
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
+ }