Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve jsonrpc js docs #6168

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
45 changes: 35 additions & 10 deletions deltachat-jsonrpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ This crate provides a [JSON-RPC 2.0](https://www.jsonrpc.org/specification) inte

The JSON-RPC API is exposed in two fashions:

* A executable that exposes the JSON-RPC API through a [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) server running on localhost.
* The JSON-RPC API can also be called through the [C FFI](../deltachat-ffi). The C FFI needs to be built with the `jsonrpc` feature. It will then expose the functions `dc_jsonrpc_init`, `dc_jsonrpc_request`, `dc_jsonrpc_next_response` and `dc_jsonrpc_unref`. See the docs in the [header file](../deltachat-ffi/deltachat.h) for details.
- A executable that exposes the JSON-RPC API through a [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) server running on localhost.
- The JSON-RPC API can also be called through the [C FFI](../deltachat-ffi). The C FFI needs to be built with the `jsonrpc` feature. It will then expose the functions `dc_jsonrpc_init`, `dc_jsonrpc_request`, `dc_jsonrpc_next_response` and `dc_jsonrpc_unref`. See the docs in the [header file](../deltachat-ffi/deltachat.h) for details.

We also include a JavaScript and TypeScript client for the JSON-RPC API. The source for this is in the [`typescript`](typescript) folder. The client can easily be used with the WebSocket server to build DeltaChat apps for web browsers or Node.js. See the [examples](typescript/example) for details.

Expand All @@ -24,16 +24,17 @@ If you want to use the server in a production setup, first build it in release m
```sh
cargo build --features webserver --release
```

You will then find the `deltachat-jsonrpc-server` executable in your `target/release` folder.

The executable currently does not support any command-line arguments. By default, once started it will accept WebSocket connections on `ws://localhost:20808/ws`. It will store the persistent configuration and databases in a `./accounts` folder relative to the directory from where it is started.

The server can be configured with environment variables:

|variable|default|description|
|-|-|-|
|`DC_PORT`|`20808`|port to listen on|
|`DC_ACCOUNTS_PATH`|`./accounts`|path to storage directory|
| variable | default | description |
| ------------------ | ------------ | ------------------------- |
| `DC_PORT` | `20808` | port to listen on |
| `DC_ACCOUNTS_PATH` | `./accounts` | path to storage directory |

If you are targeting other architectures (like KaiOS or Android), the webserver binary can be cross-compiled easily with [rust-cross](https://github.com/cross-rs/cross):

Expand All @@ -43,30 +44,53 @@ cross build --features=webserver --target armv7-linux-androideabi --release

#### Using the TypeScript/JavaScript client

The package includes a JavaScript/TypeScript client which is partially auto-generated through the JSON-RPC library used by this crate ([yerpc](https://github.com/Frando/yerpc/)). Find the source in the [`typescript`](typescript) folder.
The package includes a JavaScript/TypeScript client which is partially auto-generated through the JSON-RPC library used by this crate ([yerpc](https://github.com/Frando/yerpc/)). Find the source in the [`typescript`](typescript) folder.

To use it locally, first install the dependencies and compile the TypeScript code to JavaScript:

```sh
cd typescript
npm install
npm run build
```

The JavaScript client is not yet published on NPM (but will likely be soon). Currently, it is recommended to vendor the bundled build. After running `npm run build` as documented above, there will be a file `dist/deltachat.bundle.js`. This is an ESM module containing all dependencies. Copy this file to your project and import the DeltaChat class.
The package is also published on npm under the name [`@deltachat/jsonrpc-client`](https://www.npmjs.com/package/@deltachat/jsonrpc-client).

###### Usage

Stdio server (recommended):

```typescript
import { startDeltaChat } from "@deltachat/stdio-rpc-server";
import { C } from "@deltachat/jsonrpc-client";

const dc = await startDeltaChat("deltachat-data");
console.log(await dc.rpc.getSystemInfo());
const accounts = await dc.rpc.getAllAccounts();
console.log("accounts", accounts);
dc.close();
```

Websocket:

```typescript
import { DeltaChat } from './deltachat.bundle.js'
import { WebsocketDeltaChat as DeltaChat } from '@deltachat/jsonrpc-client''=

const dc = new DeltaChat('ws://localhost:20808/ws')
console.log(await dc.rpc.getSystemInfo());
const accounts = await dc.rpc.getAllAccounts()
console.log('accounts', accounts)
```

##### Generate TypeScript/JavaScript documentation

A script is included to build autogenerated documentation, which includes all RPC methods:

```sh
cd typescript
npm run docs
```

Then open the [`typescript/docs`](typescript/docs) folder in a web browser.

## Development
Expand All @@ -81,6 +105,7 @@ npm run build
npm run example:build
npm run example:start
```

Then, open [`http://localhost:8080/example.html`](http://localhost:8080/example.html) in a web browser.

Run `npm run example:dev` to live-rebuild the example app when files changes.
Expand All @@ -104,7 +129,7 @@ cd typescript
npm run test
```

This will build the `deltachat-jsonrpc-server` binary and then run a test suite against the WebSocket server.
This will build the `deltachat-rpc-server` binary and then run a test suite against the deltachat-rpc-server (stdio).

The test suite includes some tests that need online connectivity and a way to create test email accounts. To run these tests, talk to DeltaChat developers to get a token for the `testrun.org` service, or use a local instance of [`mailadm`](https://github.com/deltachat/docker-mailadm).

Expand Down
188 changes: 188 additions & 0 deletions deltachat-jsonrpc/typescript/Readme.md
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will be shown as start page for https://js.jsonrpc.delta.chat

Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# @deltachat/jsonrpc-client

This package is a client for the jsonrpc server.

> If you are looking for the functions in the documentation, they are under [`RawClient`](https://js.jsonrpc.delta.chat/classes/RawClient.html).

### Important Terms

- [delta chat core](https://github.com/deltachat/deltachat-core-rust/) the heart of all Delta Chat clients. Handels all the heavy lifting (email, encryption, ...) and provides an easy api for bots and clients (`getChatlist`, `getChat`, `getContact`, ...).
- [jsonrpc](https://www.jsonrpc.org/specification) is a json based protocol
for applications to speak to each other by [remote procedure calls](https://en.wikipedia.org/wiki/Remote_procedure_call) (short RPC),
which basically means that the client can call methods on the server by sending a json messages.
- [`deltachat-rpc-server`](https://github.com/deltachat/deltachat-core-rust/tree/main/deltachat-rpc-server) provides the jsonrpc api over stdio (stdin/stdout)
- [`@deltachat/stdio-rpc-server`](https://www.npmjs.com/package/@deltachat/stdio-rpc-server) is an easy way to install `deltachat-rpc-server` from npm and use it from nodejs.

#### Transport

You need to connect this client to an instance of deltachat core via a transport.

Currently there are 2 transports available:

- (recomended) `StdioTransport` usable from `StdioDeltaChat` - speak to `deltachat-rpc-server` directly
- `WebsocketTransport` usable from `WebsocketDeltaChat`

You can also make your own transport, for example deltachat desktop uses a custom transport that sends the json messages over electron ipc.
Just implement your transport based on the `Transport` interface - look at how the [stdio transport is implemented](https://github.com/deltachat/deltachat-core-rust/blob/7121675d226e69fd85d0194d4b9c4442e4dd8299/deltachat-jsonrpc/typescript/src/client.ts#L113) for an example, it's not hard.

## Usage

> The **minimum** nodejs version for `@deltachat/stdio-rpc-server` is `16`

```
npm i @deltachat/stdio-rpc-server @deltachat/jsonrpc-client
```

```js
import { startDeltaChat } from "@deltachat/stdio-rpc-server";
// Import constants you might need later
import { C } from "@deltachat/jsonrpc-client";

async function main() {
const dc = await startDeltaChat("deltachat-data");
console.log(await dc.rpc.getSystemInfo());
dc.close();
}
main();
```

For a more complete example refer to <https://github.com/deltachat-bot/echo/tree/master/nodejs_stdio_jsonrpc>.

### Listening for events

```ts
dc.on("Info", (accountId, { msg }) =>
console.info(accountId, "[core:info]", msg)
);
// Or get an event emitter for only one account
const emitter = dc.getContextEvents(accountId);
emitter.on("IncomingMsg", async ({ chatId, msgId }) => {
const message = await dc.rpc.getMessage(accountId, msgId);
console.log("got message in chat " + chatId + " : ", message.text);
});
```

### Getting Started

This section describes how to handle the Delta Chat core library over the jsonrpc bindings.
For general information about Delta Chat itself,
see <https://delta.chat> and <https://github.com/deltachat>.

Let's start.

First of all, you have to start the deltachat-rpc-server process.

```js
import { startDeltaChat } from "@deltachat/stdio-rpc-server";
const dc = await startDeltaChat("deltachat-data");
```

Then we have to create an Account (also called Context or profile) that is bound to a database.
The database is a normal SQLite file with a "blob directory" beside it.
But these details are handled by deltachat's account manager.
So you just have to tell the account manager to create a new account:

```js
const accountId = await dc.rpc.addAccount();
```

After that, register event listeners so you can see what core is doing:
Intenally `@deltachat/jsonrpc-client` implments a loop that waits for new events and then emits them to javascript land.
```js
dc.on("Info", (accountId, { msg }) =>
console.info(accountId, "[core:info]", msg)
);
```

Now you can **configure the account:**
```js
// use some real test credentials here
await dc.rpc.setConfig(accountId, "addr", "[email protected]")
await dc.rpc.setConfig(accountId, "mail_pw", "***")
// you can also set multiple config options in one call
await dc.rpc.batchSetConfig(accountId, {
"addr": "[email protected]",
"mail_pw": "***"
})

// after setting the credentials attempt to login
await dc.rpc.configure(accountId)
```

`configure()` returns a promise that is rejected on error (with await is is thrown).
The configuration itself may take a while. You can monitor it's progress like this:
```js
dc.on("ConfigureProgress", (accountId, { progress, comment }) => {
console.log(accountId, "ConfigureProgress", progress, comment);
});
// make sure to register this event handler before calling `dc.rpc.configure()`
```

The configuration result is saved in the database.
On subsequent starts it is not needed to call `dc.rpc.configure(accountId)`
(you can check this using `dc.rpc.isConfigured(accountId)`).

On a successfully configuration delta chat core automatically connects to the server, however subsequent starts you **need to do that manually** by calling `dc.rpc.startIo(accountId)` or `dc.rpc.startIoForAllAccounts()`.

```js
if (!await dc.rpc.isConfigured(accountId)) {
// use some real test credentials here
await dc.rpc.batchSetConfig(accountId, {
"addr": "[email protected]",
"mail_pw": "***"
})
await dc.rpc.configure(accountId)
} else {
await dc.rpc.startIo(accountId)
}
```

Now you can **send the first message:**

```js
const contactId = await dc.rpc.createContact(accountId, "[email protected]", null /* optional name */)
const chatId = await dc.rpc.createChatByContactId(accountId, contactId)

await dc.rpc.miscSendTextMessage(accountId, chatId, "Hi, here is my first message!")
```

`dc.rpc.miscSendTextMessage()` returns immediately;
the sending itself is done in the background.
If you check the testing address (bob),
you should receive a normal e-mail.
Answer this e-mail in any e-mail program with "Got it!",
and the IO you started above will **receive the message**.

You can then **list all messages** of a chat as follows:

```js
let i = 0;
for (const msgId of await exp.rpc.getMessageIds(120, 12, false, false)) {
i++;
console.log(`Message: ${i}`, (await dc.rpc.getMessage(120, msgId)).text);
}
```

This will output the following two lines:
```
Message 1: Hi, here is my first message!
Message 2: Got it!
```

<!-- TODO: ### Clean shutdown? - seems to be more advanced to call async functions on exit, also is this needed in this usecase? -->

## Further information

- `@deltachat/stdio-rpc-server`
- [package on npm](https://www.npmjs.com/package/@deltachat/stdio-rpc-server)
- [source code on github](https://github.com/deltachat/deltachat-core-rust/tree/main/deltachat-rpc-server/npm-package)
- [use `@deltachat/stdio-rpc-server` on an usuported platform](https://github.com/deltachat/deltachat-core-rust/tree/main/deltachat-rpc-server/npm-package#how-to-use-on-an-unsupported-platform)
- The issue-tracker for the core library is here: <https://github.com/deltachat/deltachat-core-rust/issues>

If you need further assistance,
please do not hesitate to contact us
through the channels shown at https://delta.chat/en/contribute

Please keep in mind, that your derived work
must respect the Mozilla Public License 2.0 of deltachat-rpc-server
and the respective licenses of the libraries deltachat-rpc-server links with.
2 changes: 1 addition & 1 deletion deltachat-jsonrpc/typescript/example/example.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DcEvent, DeltaChat } from "../deltachat.js";
import { DcEvent, WebsocketDeltaChat as DeltaChat } from "../deltachat.js";

var SELECTED_ACCOUNT = 0;

Expand Down
6 changes: 3 additions & 3 deletions deltachat-jsonrpc/typescript/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { RawClient } from "../generated/client.js";
import { WebsocketTransport, BaseTransport, Request } from "yerpc";
import { TinyEmitter } from "@deltachat/tiny-emitter";

type Events = { ALL: (accountId: number, event: EventType) => void } & {
export type Events = { ALL: (accountId: number, event: EventType) => void } & {
[Property in EventType["kind"]]: (
accountId: number,
event: Extract<EventType, { kind: Property }>
) => void;
};

type ContextEvents = { ALL: (event: EventType) => void } & {
export type ContextEvents = { ALL: (event: EventType) => void } & {
[Property in EventType["kind"]]: (
event: Extract<EventType, { kind: Property }>
) => void;
Expand Down Expand Up @@ -83,7 +83,7 @@ export const DEFAULT_OPTS: Opts = {
url: "ws://localhost:20808/ws",
startEventLoop: true,
};
export class DeltaChat extends BaseDeltaChat<WebsocketTransport> {
export class WebsocketDeltaChat extends BaseDeltaChat<WebsocketTransport> {
opts: Opts;
close() {
this.transport.close();
Expand Down
42 changes: 34 additions & 8 deletions deltachat-rpc-server/npm-package/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,46 @@ import { startDeltaChat } from "@deltachat/stdio-rpc-server";
import { C } from "@deltachat/jsonrpc-client";

async function main() {
const dc = await startDeltaChat("deltachat-data");
console.log(await dc.rpc.getSystemInfo());
dc.close()
const dc = await startDeltaChat("deltachat-data");
console.log(await dc.rpc.getSystemInfo());
dc.close();
}
main()
main();
```

For a more complete example refer to https://github.com/deltachat-bot/echo/pull/69/files (TODO change link when pr is merged).
For a more complete example refer to https://github.com/deltachat-bot/echo/tree/master/nodejs_stdio_jsonrpc.

## How to use on an unsupported platform

<!-- todo instructions, will uses an env var for pointing to `deltachat-rpc-server` binary -->
You need to have rust installed to compile deltachat core for your platform and cpu architecture.
<https://rustup.rs/> is the recommended way to install rust.
Also your system probably needs more than 4gb ram to compile core, alternatively your could try to build the debug build, that might take less ram to build.

<!-- todo copy parts from https://github.com/deltachat/deltachat-desktop/blob/7045c6f549e4b9d5caa0709d5bd314bbd9fd53db/docs/UPDATE_CORE.md -->
1. clone the core repo, right next to your project folder: `git clone [email protected]:deltachat/deltachat-core-rust.git`
2. go into your core checkout and run `git pull` and `git checkout <version>` to point it to the correct version (needs to be the same version the `@deltachat/jsonrpc-client` package has)
3. run `cargo build --release --package deltachat-rpc-server --bin deltachat-rpc-server`

Then you have 2 options:

### point to deltachat-rpc-server via direct path:

```sh
# start your app with the DELTA_CHAT_RPC_SERVER env var
DELTA_CHAT_RPC_SERVER="../deltachat-core-rust/target/release/deltachat-rpc-server" node myapp.js
```

### install deltachat-rpc-server in your $PATH:

```sh
# use this to install to ~/.cargo/bin
cargo install --release --package deltachat-rpc-server --bin deltachat-rpc-server
# or manually move deltachat-core-rust/target/release/deltachat-rpc-server
# to a location that is included in your $PATH Environment variable.
```

```js
startDeltaChat("data-dir", { takeVersionFromPATH: true });
```

## How does it work when you install it

Expand All @@ -46,7 +72,7 @@ references:
When you import this package it searches for the rpc server in the following locations and order:

1. `DELTA_CHAT_RPC_SERVER` environment variable
2. use the PATH when `{takeVersionFromPATH: true}` is supplied in the options.
2. use the PATH when `{takeVersionFromPATH: true}` is supplied in the options.
3. prebuilds in npm packages

so by default it uses the prebuilds.
Expand Down
Loading
Loading