simplex-chat 0.2.1 → 6.5.0-beta.10
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 +108 -21
- package/binding.gyp +50 -0
- package/cpp/simplex.cc +426 -0
- package/cpp/simplex.h +46 -0
- package/dist/api.d.ts +423 -0
- package/dist/api.js +857 -0
- package/dist/api.js.map +1 -0
- package/dist/bot.d.ts +29 -0
- package/dist/bot.js +189 -0
- package/dist/bot.js.map +1 -0
- package/dist/core.d.ts +128 -0
- package/dist/core.js +121 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +19 -2
- package/dist/index.js +20 -4
- package/dist/index.js.map +1 -1
- package/dist/simplex.d.ts +10 -0
- package/dist/simplex.js +1 -0
- package/dist/util.d.ts +15 -0
- package/dist/util.js +91 -0
- package/dist/util.js.map +1 -0
- package/docs/Namespace.api.md +33 -0
- package/docs/Namespace.bot.md +23 -0
- package/docs/Namespace.core.md +48 -0
- package/docs/Namespace.util.md +25 -0
- package/docs/README.md +12 -0
- package/docs/api.Class.ChatApi.md +1752 -0
- package/docs/api.Class.ChatCommandError.md +205 -0
- package/docs/api.Enumeration.ConnReqType.md +27 -0
- package/docs/api.Interface.BotAddressSettings.md +60 -0
- package/docs/api.TypeAlias.DbConfig.md +64 -0
- package/docs/api.TypeAlias.EventSubscriberFunc.md +27 -0
- package/docs/api.TypeAlias.EventSubscribers.md +11 -0
- package/docs/api.Variable.defaultBotAddressSettings.md +11 -0
- package/docs/bot.Function.run.md +21 -0
- package/docs/bot.Interface.BotConfig.md +75 -0
- package/docs/bot.Interface.BotOptions.md +81 -0
- package/docs/bot.TypeAlias.BotDbOpts.md +17 -0
- package/docs/core.Class.ChatAPIError.md +205 -0
- package/docs/core.Class.ChatInitError.md +205 -0
- package/docs/core.DBMigrationError.Interface.ErrorMigration.md +41 -0
- package/docs/core.DBMigrationError.Interface.ErrorNotADatabase.md +33 -0
- package/docs/core.DBMigrationError.Interface.ErrorSQL.md +41 -0
- package/docs/core.DBMigrationError.Interface.InvalidConfirmation.md +25 -0
- package/docs/core.DBMigrationError.TypeAlias.Tag.md +11 -0
- package/docs/core.Enumeration.MigrationConfirmation.md +43 -0
- package/docs/core.Function.chatCloseStore.md +23 -0
- package/docs/core.Function.chatDecryptFile.md +31 -0
- package/docs/core.Function.chatEncryptFile.md +31 -0
- package/docs/core.Function.chatMigrateInit.md +31 -0
- package/docs/core.Function.chatReadFile.md +27 -0
- package/docs/core.Function.chatRecvMsgWait.md +27 -0
- package/docs/core.Function.chatSendCmd.md +27 -0
- package/docs/core.Function.chatWriteFile.md +31 -0
- package/docs/core.Interface.APIResult.md +31 -0
- package/docs/core.Interface.CryptoArgs.md +27 -0
- package/docs/core.Interface.UpMigration.md +25 -0
- package/docs/core.MTRError.Interface.MTREDifferent.md +33 -0
- package/docs/core.MTRError.Interface.MTRENoDown.md +33 -0
- package/docs/core.MTRError.TypeAlias.Tag.md +11 -0
- package/docs/core.MigrationError.Interface.MEDowngrade.md +33 -0
- package/docs/core.MigrationError.Interface.MEUpgrade.md +33 -0
- package/docs/core.MigrationError.Interface.MigrationError.md +33 -0
- package/docs/core.MigrationError.TypeAlias.Tag.md +11 -0
- package/docs/core.Namespace.DBMigrationError.md +18 -0
- package/docs/core.Namespace.MTRError.md +16 -0
- package/docs/core.Namespace.MigrationError.md +17 -0
- package/docs/core.TypeAlias.DBMigrationError.md +11 -0
- package/docs/core.TypeAlias.MTRError.md +11 -0
- package/docs/core.TypeAlias.MigrationError.md +11 -0
- package/docs/util.Function.botAddressSettings.md +21 -0
- package/docs/util.Function.chatInfoName.md +21 -0
- package/docs/util.Function.chatInfoRef.md +21 -0
- package/docs/util.Function.ciBotCommand.md +21 -0
- package/docs/util.Function.ciContentText.md +21 -0
- package/docs/util.Function.contactAddressStr.md +21 -0
- package/docs/util.Function.fromLocalProfile.md +21 -0
- package/docs/util.Function.reactionText.md +21 -0
- package/docs/util.Function.senderName.md +25 -0
- package/docs/util.Interface.BotCommand.md +25 -0
- package/examples/squaring-bot-pg.js +21 -0
- package/examples/squaring-bot-readme.js +17 -0
- package/examples/squaring-bot.ts +42 -0
- package/package.json +34 -37
- package/src/api.ts +939 -0
- package/src/bot.ts +214 -0
- package/src/core.ts +210 -0
- package/src/download-libs.js +239 -0
- package/src/index.ts +22 -0
- package/src/simplex.d.ts +10 -0
- package/src/simplex.js +1 -0
- package/src/util.ts +92 -0
- package/tsconfig.json +23 -0
- package/dist/client.d.ts +0 -76
- package/dist/client.js +0 -329
- package/dist/client.js.map +0 -1
- package/dist/command.d.ts +0 -412
- package/dist/command.js +0 -186
- package/dist/command.js.map +0 -1
- package/dist/index-web.d.ts +0 -1
- package/dist/index-web.js +0 -6
- package/dist/index-web.js.map +0 -1
- package/dist/index.bundle.js +0 -11
- package/dist/queue.d.ts +0 -24
- package/dist/queue.js +0 -77
- package/dist/queue.js.map +0 -1
- package/dist/response.d.ts +0 -755
- package/dist/response.js +0 -21
- package/dist/response.js.map +0 -1
- package/dist/test.html +0 -7
- package/dist/transport.d.ts +0 -51
- package/dist/transport.js +0 -131
- package/dist/transport.js.map +0 -1
- package/dist/websocket.d.ts +0 -8
- package/dist/websocket.js +0 -4
- package/dist/websocket.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,42 +1,129 @@
|
|
|
1
|
-
# SimpleX Chat
|
|
1
|
+
# SimpleX Chat Node.js library
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This library replaced now deprecated [SimpleX Chat WebRTC TypeScript client](https://www.npmjs.com/package/@simplex-chat/webrtc-client).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
## Use cases
|
|
6
|
+
|
|
7
|
+
- chat bots: you can implement any logic of connecting with and communicating with SimpleX Chat users. Using chat groups a chat bot can connect SimpleX Chat users with each other.
|
|
8
|
+
- control of the equipment: e.g. servers or home automation. SimpleX Chat provides secure and authorised connections, so this is more secure than using rest APIs.
|
|
9
|
+
- any scenarios of scripted message sending.
|
|
10
|
+
- chat and chat-based interfaces.
|
|
11
|
+
|
|
12
|
+
Please share your use cases and implementations.
|
|
13
|
+
|
|
14
|
+
## Quick start: a simple bot
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
npm i simplex-chat@6.5.0-beta.10
|
|
7
18
|
```
|
|
8
19
|
|
|
9
|
-
|
|
20
|
+
Simple bot that replies with squares of numbers you send to it:
|
|
10
21
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
```javascript
|
|
23
|
+
(async () => {
|
|
24
|
+
const {bot} = await import("simplex-chat")
|
|
25
|
+
// if you are running from this GitHub repo:
|
|
26
|
+
// const {bot} = await import("../dist/index.js")
|
|
27
|
+
const [chat, _user, _address] = await bot.run({
|
|
28
|
+
profile: {displayName: "Squaring bot example", fullName: ""},
|
|
29
|
+
dbOpts: {type: "sqlite", filePrefix: "./squaring_bot"},
|
|
30
|
+
options: {
|
|
31
|
+
addressSettings: {welcomeMessage: "Send a number, I will square it.",
|
|
32
|
+
},
|
|
33
|
+
onMessage: async (ci, content) => {
|
|
34
|
+
const n = +content.text
|
|
35
|
+
const reply = typeof n === "number" && !isNaN(n)
|
|
36
|
+
? `${n} * ${n} = ${n * n}`
|
|
37
|
+
: `this is not a number`
|
|
38
|
+
await chat.apiSendTextReply(ci, reply)
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
})()
|
|
42
|
+
```
|
|
16
43
|
|
|
17
|
-
|
|
44
|
+
If you installed this package as dependency, you can run this example with:
|
|
18
45
|
|
|
19
|
-
|
|
20
|
-
-
|
|
46
|
+
```sh
|
|
47
|
+
node ./node_modules/simplex-chat/examples/squaring-bot-readme.js
|
|
48
|
+
```
|
|
21
49
|
|
|
22
|
-
|
|
50
|
+
If you run it on Mac, the first time it will take 20-30 seconds for MacOS to verify the library.
|
|
23
51
|
|
|
24
|
-
|
|
52
|
+
If you cloned this repository, you can:
|
|
25
53
|
|
|
26
54
|
```
|
|
27
|
-
|
|
55
|
+
cd ./packages/simplex-chat-nodejs
|
|
56
|
+
npm install
|
|
28
57
|
npm run build
|
|
58
|
+
node ./examples/squaring-bot-readme.js
|
|
29
59
|
```
|
|
30
60
|
|
|
31
|
-
|
|
61
|
+
There is an example with more options in [./examples/squaring-bot.ts](./examples/squaring-bot.ts).
|
|
62
|
+
|
|
63
|
+
You can run it with: `npx ts-node ./examples/squaring-bot.ts`
|
|
64
|
+
|
|
65
|
+
## PostgreSQL backend
|
|
32
66
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
67
|
+
By default, the package uses SQLite. To use PostgreSQL instead:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm install simplex-chat --simplex_backend=postgres
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Or persist the setting in `.npmrc`:
|
|
74
|
+
|
|
75
|
+
```ini
|
|
76
|
+
simplex_backend=postgres
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Prerequisites (PostgreSQL)
|
|
80
|
+
|
|
81
|
+
- `libpq5` must be installed on the host system (`apt install libpq5` on Debian/Ubuntu)
|
|
82
|
+
- PostgreSQL backend is only available for Linux x86_64
|
|
83
|
+
- A PostgreSQL server accessible via connection string
|
|
84
|
+
|
|
85
|
+
### Passing PostgreSQL connection
|
|
86
|
+
|
|
87
|
+
The `DbConfig` type is a discriminated union — pick the variant that matches
|
|
88
|
+
the backend you installed:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
// SQLite (default)
|
|
92
|
+
dbOpts: {type: "sqlite", filePrefix: "./data/bot"}
|
|
93
|
+
// optional: encryptionKey: "<sqlcipher-key>"
|
|
94
|
+
|
|
95
|
+
// PostgreSQL
|
|
96
|
+
dbOpts: {
|
|
97
|
+
type: "postgres",
|
|
98
|
+
connectionString: "postgres://user:pass@host/db",
|
|
99
|
+
// schemaPrefix: "bot", // optional — defaults to "simplex_v1"
|
|
100
|
+
}
|
|
101
|
+
```
|
|
36
102
|
|
|
37
103
|
## Documentation
|
|
38
104
|
|
|
39
|
-
|
|
105
|
+
The library docs are [here](./docs/README.md).
|
|
106
|
+
|
|
107
|
+
Library provides these modules:
|
|
108
|
+
- [bot](./docs/Namespace.bot.md): a simple declarative API to run a chat-bot with a single function call. It automates creating and updating of the bot profile, address and bot commands shown in the app UI.
|
|
109
|
+
- [api](./docs/Namespace.api.md): an API to send chat commands and receive chat events to/from chat core. You need to use it in bot event handlers, and for any other use cases.
|
|
110
|
+
- [core](./docs/Namespace.core.md): a low level API to the core library - the same that is used in desktop clients. You are unlikely to ever need to use this module directly.
|
|
111
|
+
- [util](./docs/Namespace.util.md): useful functions for chat events and types.
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
This library uses [@simplex-chat/types](https://www.npmjs.com/package/@simplex-chat/types) package with auto-generated [bot API types](../../bots/api/README.md).
|
|
115
|
+
|
|
116
|
+
## Supported chat functions
|
|
117
|
+
|
|
118
|
+
Library provides types and functions to:
|
|
119
|
+
|
|
120
|
+
- create and change user profile (although, in most cases you can do it manually, via SimpleX Chat terminal app).
|
|
121
|
+
- create and accept invitations or connect with the contacts.
|
|
122
|
+
- create and manage long-term user address, accepting connection requests automatically.
|
|
123
|
+
- send, receive, delete and update messages, and add message reactions.
|
|
124
|
+
- create, join and manage group.
|
|
125
|
+
- send and receive files.
|
|
126
|
+
- etc.
|
|
40
127
|
|
|
41
128
|
## License
|
|
42
129
|
|
package/binding.gyp
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"targets": [
|
|
3
|
+
{
|
|
4
|
+
"target_name": "simplex",
|
|
5
|
+
"sources": [ "cpp/simplex.cc" ],
|
|
6
|
+
"include_dirs": [
|
|
7
|
+
"<!@(node -p \"require('node-addon-api').include\")"
|
|
8
|
+
],
|
|
9
|
+
"dependencies": [
|
|
10
|
+
"<!(node -p \"require('node-addon-api').gyp\")"
|
|
11
|
+
],
|
|
12
|
+
"cflags!": [ "-fno-exceptions" ],
|
|
13
|
+
"cflags_cc!": [ "-fno-exceptions" ],
|
|
14
|
+
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
|
|
15
|
+
"conditions": [
|
|
16
|
+
["OS=='mac'", {
|
|
17
|
+
"libraries": [
|
|
18
|
+
"-L<(module_root_dir)/libs",
|
|
19
|
+
"-lsimplex"
|
|
20
|
+
],
|
|
21
|
+
"xcode_settings": {
|
|
22
|
+
"OTHER_LDFLAGS": [
|
|
23
|
+
"-Wl,-rpath,@loader_path/../../libs"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}],
|
|
27
|
+
["OS=='linux'", {
|
|
28
|
+
"libraries": [
|
|
29
|
+
"-L<(module_root_dir)/libs",
|
|
30
|
+
"-lsimplex"
|
|
31
|
+
],
|
|
32
|
+
"ldflags": [
|
|
33
|
+
"-Wl,-rpath,'$$ORIGIN'/../../libs"
|
|
34
|
+
]
|
|
35
|
+
}],
|
|
36
|
+
["OS=='win'", {
|
|
37
|
+
"libraries": [
|
|
38
|
+
"<(module_root_dir)/libs/libsimplex.lib"
|
|
39
|
+
],
|
|
40
|
+
"copies": [{
|
|
41
|
+
"destination": "<(PRODUCT_DIR)",
|
|
42
|
+
"files": [
|
|
43
|
+
"<(module_root_dir)/libs/*"
|
|
44
|
+
]
|
|
45
|
+
}]
|
|
46
|
+
}]
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
package/cpp/simplex.cc
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
#include <napi.h>
|
|
2
|
+
#include <sstream>
|
|
3
|
+
#include <string>
|
|
4
|
+
#include <functional>
|
|
5
|
+
#include <cstdlib>
|
|
6
|
+
#include "simplex.h"
|
|
7
|
+
|
|
8
|
+
namespace simplex {
|
|
9
|
+
|
|
10
|
+
using namespace Napi;
|
|
11
|
+
|
|
12
|
+
void haskell_init() {
|
|
13
|
+
#ifdef _WIN32
|
|
14
|
+
// non-moving GC is broken on windows with GHC 9.4-9.6.3
|
|
15
|
+
int argc = 5;
|
|
16
|
+
const char *argv[] = {
|
|
17
|
+
"simplex",
|
|
18
|
+
"+RTS", // requires `hs_init_with_rtsopts`
|
|
19
|
+
"-A64m", // chunk size for new allocations
|
|
20
|
+
"-H64m", // initial heap size
|
|
21
|
+
"--install-signal-handlers=no",
|
|
22
|
+
nullptr};
|
|
23
|
+
#else
|
|
24
|
+
int argc = 6;
|
|
25
|
+
const char *argv[] = {
|
|
26
|
+
"simplex",
|
|
27
|
+
"+RTS", // requires `hs_init_with_rtsopts`
|
|
28
|
+
"-A64m", // chunk size for new allocations
|
|
29
|
+
"-H64m", // initial heap size
|
|
30
|
+
"-xn", // non-moving GC
|
|
31
|
+
"--install-signal-handlers=no",
|
|
32
|
+
nullptr};
|
|
33
|
+
#endif
|
|
34
|
+
char **pargv = const_cast<char **>(argv);
|
|
35
|
+
hs_init_with_rtsopts(&argc, &pargv);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class ResultAsyncWorker : public AsyncWorker {
|
|
39
|
+
public:
|
|
40
|
+
using ExecuteFn = std::function<void(ResultAsyncWorker*)>;
|
|
41
|
+
using ResultProcessor = std::function<void(ResultAsyncWorker*, Napi::Env)>;
|
|
42
|
+
|
|
43
|
+
ResultAsyncWorker(Function& callback, ExecuteFn execute_fn, ResultProcessor result_processor = nullptr)
|
|
44
|
+
: AsyncWorker(callback), execute_fn_(std::move(execute_fn)), result_processor_(std::move(result_processor)) {}
|
|
45
|
+
|
|
46
|
+
void Execute() override {
|
|
47
|
+
execute_fn_(this);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
void OnOK() override {
|
|
51
|
+
HandleScope scope(Env());
|
|
52
|
+
if (result_processor_) {
|
|
53
|
+
result_processor_(this, Env());
|
|
54
|
+
} else {
|
|
55
|
+
Callback().Call({Env().Null(), String::New(Env(), result_)});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
void OnError(const Error& e) override {
|
|
60
|
+
HandleScope scope(Env());
|
|
61
|
+
Callback().Call({e.Value(), Env().Undefined()});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void SetResult(std::string result) {
|
|
65
|
+
result_ = std::move(result);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
void SetWorkerError(const std::string& msg) {
|
|
69
|
+
SetError(msg);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const std::string& GetStringResult() const {
|
|
73
|
+
return result_;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
void SetCtrl(uintptr_t ctrl) {
|
|
77
|
+
ctrl_ = ctrl;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
uintptr_t GetCtrl() const {
|
|
81
|
+
return ctrl_;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
protected:
|
|
85
|
+
std::string result_;
|
|
86
|
+
uintptr_t ctrl_ = 0;
|
|
87
|
+
|
|
88
|
+
private:
|
|
89
|
+
ExecuteFn execute_fn_;
|
|
90
|
+
ResultProcessor result_processor_;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
class BinaryAsyncWorker : public AsyncWorker {
|
|
94
|
+
public:
|
|
95
|
+
using ExecuteFn = std::function<void(BinaryAsyncWorker*)>;
|
|
96
|
+
|
|
97
|
+
BinaryAsyncWorker(Function& callback, ExecuteFn execute_fn)
|
|
98
|
+
: AsyncWorker(callback), execute_fn_(std::move(execute_fn)) {}
|
|
99
|
+
|
|
100
|
+
void Execute() override {
|
|
101
|
+
execute_fn_(this);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
void OnOK() override {
|
|
105
|
+
HandleScope scope(Env());
|
|
106
|
+
if (original_buf == nullptr || binary_len == 0) {
|
|
107
|
+
Callback().Call({Env().Null(), Env().Undefined()});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
char* data_ptr = original_buf + 5;
|
|
111
|
+
auto finalizer = [](Napi::Env env, char* finalize_data, char* orig) {
|
|
112
|
+
free(orig);
|
|
113
|
+
};
|
|
114
|
+
Napi::Buffer<char> buffer = Napi::Buffer<char>::New(Env(), data_ptr, binary_len, finalizer, original_buf);
|
|
115
|
+
Callback().Call({Env().Null(), buffer});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
void OnError(const Error& e) override {
|
|
119
|
+
HandleScope scope(Env());
|
|
120
|
+
Callback().Call({e.Value(), Env().Undefined()});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
void SetWorkerError(const std::string& msg) {
|
|
124
|
+
SetError(msg);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
char* original_buf = nullptr;
|
|
128
|
+
size_t binary_len = 0;
|
|
129
|
+
|
|
130
|
+
private:
|
|
131
|
+
ExecuteFn execute_fn_;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Helper for converting chat_ctrl pointer to BigInt
|
|
135
|
+
Napi::BigInt ToChatCtrlBigInt(Napi::Env env, uintptr_t ctrl) {
|
|
136
|
+
return Napi::BigInt::New(env, static_cast<uint64_t>(ctrl));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Helper for converting BigInt to chat_ctrl pointer
|
|
140
|
+
chat_ctrl FromChatCtrlBigInt(const Napi::Value& value) {
|
|
141
|
+
Napi::Env env = value.Env();
|
|
142
|
+
if (!value.IsBigInt()) {
|
|
143
|
+
Napi::TypeError::New(env, "Expected BigInt for ctrl").ThrowAsJavaScriptException();
|
|
144
|
+
return nullptr;
|
|
145
|
+
}
|
|
146
|
+
Napi::BigInt big = value.As<Napi::BigInt>();
|
|
147
|
+
bool lossless;
|
|
148
|
+
uint64_t val = big.Uint64Value(&lossless);
|
|
149
|
+
if (!lossless) {
|
|
150
|
+
Napi::TypeError::New(env, "BigInt too large for ctrl").ThrowAsJavaScriptException();
|
|
151
|
+
return nullptr;
|
|
152
|
+
}
|
|
153
|
+
return reinterpret_cast<chat_ctrl>(val);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Helper for handling common C result patterns (no empty check)
|
|
157
|
+
void HandleCResult(ResultAsyncWorker* worker, char* c_res, const std::string& func_name) {
|
|
158
|
+
if (c_res == nullptr) {
|
|
159
|
+
worker->SetWorkerError(func_name + " failed");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
std::string res = c_res;
|
|
163
|
+
free(c_res);
|
|
164
|
+
worker->SetResult(res);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
Napi::Promise CreatePromiseAndCallback(Env env, Function& cb_out) {
|
|
168
|
+
Promise::Deferred deferred = Promise::Deferred::New(env);
|
|
169
|
+
cb_out = Function::New(env, [deferred](const CallbackInfo& args) {
|
|
170
|
+
if (!args[0].IsNull() && !args[0].IsUndefined()) {
|
|
171
|
+
deferred.Reject(args[0]);
|
|
172
|
+
} else {
|
|
173
|
+
deferred.Resolve(args[1]);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
return deferred.Promise();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Common result processors
|
|
180
|
+
ResultAsyncWorker::ResultProcessor MigrateResultProcessor() {
|
|
181
|
+
return [](ResultAsyncWorker* worker, Napi::Env env) {
|
|
182
|
+
Napi::Array arr = Napi::Array::New(env, 2);
|
|
183
|
+
arr.Set(0u, ToChatCtrlBigInt(env, worker->GetCtrl()));
|
|
184
|
+
arr.Set(1u, Napi::String::New(env, worker->GetStringResult()));
|
|
185
|
+
worker->Callback().Call({env.Null(), arr});
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Refactored functions using common patterns
|
|
190
|
+
|
|
191
|
+
Value ChatMigrateInit(const CallbackInfo& args) {
|
|
192
|
+
Env env = args.Env();
|
|
193
|
+
if (args.Length() < 3 || !args[0].IsString() || !args[1].IsString() || !args[2].IsString()) {
|
|
194
|
+
TypeError::New(env, "Expected three string arguments").ThrowAsJavaScriptException();
|
|
195
|
+
return env.Undefined();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
std::string path = args[0].As<String>().Utf8Value();
|
|
199
|
+
std::string key = args[1].As<String>().Utf8Value();
|
|
200
|
+
std::string confirm = args[2].As<String>().Utf8Value();
|
|
201
|
+
|
|
202
|
+
Function cb;
|
|
203
|
+
Promise promise = CreatePromiseAndCallback(env, cb);
|
|
204
|
+
|
|
205
|
+
auto execute_fn = [path, key, confirm](ResultAsyncWorker* worker) {
|
|
206
|
+
chat_ctrl ctrl = nullptr;
|
|
207
|
+
char* c_res = chat_migrate_init(path.c_str(), key.c_str(), confirm.c_str(), &ctrl);
|
|
208
|
+
worker->SetCtrl(reinterpret_cast<uintptr_t>(ctrl));
|
|
209
|
+
HandleCResult(worker, c_res, "chat_migrate_init");
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn), MigrateResultProcessor());
|
|
213
|
+
worker->Queue();
|
|
214
|
+
|
|
215
|
+
return promise;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
Value ChatCloseStore(const CallbackInfo& args) {
|
|
219
|
+
Env env = args.Env();
|
|
220
|
+
if (args.Length() < 1 || !args[0].IsBigInt()) {
|
|
221
|
+
TypeError::New(env, "Expected bigint (ctrl)").ThrowAsJavaScriptException();
|
|
222
|
+
return env.Undefined();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
chat_ctrl ctrl = FromChatCtrlBigInt(args[0]);
|
|
226
|
+
|
|
227
|
+
Function cb;
|
|
228
|
+
Promise promise = CreatePromiseAndCallback(env, cb);
|
|
229
|
+
|
|
230
|
+
auto execute_fn = [ctrl](ResultAsyncWorker* worker) {
|
|
231
|
+
char* c_res = chat_close_store(ctrl);
|
|
232
|
+
HandleCResult(worker, c_res, "chat_close_store");
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn));
|
|
236
|
+
worker->Queue();
|
|
237
|
+
|
|
238
|
+
return promise;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
Value ChatSendCmd(const CallbackInfo& args) {
|
|
242
|
+
Env env = args.Env();
|
|
243
|
+
if (args.Length() < 2 || !args[0].IsBigInt() || !args[1].IsString()) {
|
|
244
|
+
TypeError::New(env, "Expected bigint (ctrl) and string (cmd)").ThrowAsJavaScriptException();
|
|
245
|
+
return env.Undefined();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
chat_ctrl ctrl = FromChatCtrlBigInt(args[0]);
|
|
249
|
+
std::string cmd = args[1].As<String>().Utf8Value();
|
|
250
|
+
|
|
251
|
+
Function cb;
|
|
252
|
+
Promise promise = CreatePromiseAndCallback(env, cb);
|
|
253
|
+
|
|
254
|
+
auto execute_fn = [ctrl, cmd](ResultAsyncWorker* worker) {
|
|
255
|
+
char* c_res = chat_send_cmd(ctrl, cmd.c_str());
|
|
256
|
+
HandleCResult(worker, c_res, "chat_send_cmd");
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn));
|
|
260
|
+
worker->Queue();
|
|
261
|
+
|
|
262
|
+
return promise;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
Value ChatRecvMsgWait(const CallbackInfo& args) {
|
|
266
|
+
Env env = args.Env();
|
|
267
|
+
if (args.Length() < 2 || !args[0].IsBigInt() || !args[1].IsNumber()) {
|
|
268
|
+
TypeError::New(env, "Expected bigint (ctrl), number (wait)").ThrowAsJavaScriptException();
|
|
269
|
+
return env.Undefined();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
chat_ctrl ctrl = FromChatCtrlBigInt(args[0]);
|
|
273
|
+
int wait = static_cast<int>(args[1].As<Number>().Int32Value());
|
|
274
|
+
|
|
275
|
+
Function cb;
|
|
276
|
+
Promise promise = CreatePromiseAndCallback(env, cb);
|
|
277
|
+
|
|
278
|
+
auto execute_fn = [ctrl, wait](ResultAsyncWorker* worker) {
|
|
279
|
+
char* c_res = chat_recv_msg_wait(ctrl, wait);
|
|
280
|
+
HandleCResult(worker, c_res, "chat_recv_msg_wait");
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn));
|
|
284
|
+
worker->Queue();
|
|
285
|
+
|
|
286
|
+
return promise;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
Value ChatWriteFile(const CallbackInfo& args) {
|
|
290
|
+
Env env = args.Env();
|
|
291
|
+
if (args.Length() < 3 || !args[0].IsBigInt() || !args[1].IsString() || !args[2].IsArrayBuffer()) {
|
|
292
|
+
TypeError::New(env, "Expected bigint (ctrl), string (path), ArrayBuffer").ThrowAsJavaScriptException();
|
|
293
|
+
return env.Undefined();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
chat_ctrl ctrl = FromChatCtrlBigInt(args[0]);
|
|
297
|
+
std::string path = args[1].As<String>().Utf8Value();
|
|
298
|
+
ArrayBuffer ab = args[2].As<ArrayBuffer>();
|
|
299
|
+
char* data = static_cast<char*>(ab.Data());
|
|
300
|
+
size_t len = ab.ByteLength();
|
|
301
|
+
|
|
302
|
+
Function cb;
|
|
303
|
+
Promise promise = CreatePromiseAndCallback(env, cb);
|
|
304
|
+
|
|
305
|
+
auto execute_fn = [ctrl, path, ab, data, len](ResultAsyncWorker* worker) {
|
|
306
|
+
(void)ab; // to keep ArrayBuffer alive
|
|
307
|
+
char* c_res = chat_write_file(ctrl, path.c_str(), data, static_cast<int>(len));
|
|
308
|
+
HandleCResult(worker, c_res, "chat_write_file");
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn));
|
|
312
|
+
worker->Queue();
|
|
313
|
+
|
|
314
|
+
return promise;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
Value ChatReadFile(const CallbackInfo& args) {
|
|
318
|
+
Env env = args.Env();
|
|
319
|
+
if (args.Length() < 3 || !args[0].IsString() || !args[1].IsString() || !args[2].IsString()) {
|
|
320
|
+
TypeError::New(env, "Expected three strings (path, key, nonce)").ThrowAsJavaScriptException();
|
|
321
|
+
return env.Undefined();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
std::string path = args[0].As<String>().Utf8Value();
|
|
325
|
+
std::string key = args[1].As<String>().Utf8Value();
|
|
326
|
+
std::string nonce = args[2].As<String>().Utf8Value();
|
|
327
|
+
|
|
328
|
+
Function cb;
|
|
329
|
+
Promise promise = CreatePromiseAndCallback(env, cb);
|
|
330
|
+
|
|
331
|
+
auto execute_fn = [path, key, nonce](BinaryAsyncWorker* worker) {
|
|
332
|
+
char* buf = chat_read_file(path.c_str(), key.c_str(), nonce.c_str());
|
|
333
|
+
if (buf == nullptr) {
|
|
334
|
+
worker->SetWorkerError("chat_read_file failed");
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
char status = buf[0];
|
|
338
|
+
if (status == 1) {
|
|
339
|
+
std::string err = buf + 1;
|
|
340
|
+
free(buf);
|
|
341
|
+
worker->SetWorkerError(err);
|
|
342
|
+
return;
|
|
343
|
+
} else if (status == 0) {
|
|
344
|
+
uint32_t len = *(uint32_t*)(buf + 1);
|
|
345
|
+
worker->original_buf = buf;
|
|
346
|
+
worker->binary_len = len;
|
|
347
|
+
} else {
|
|
348
|
+
free(buf);
|
|
349
|
+
worker->SetWorkerError("Unexpected status from chat_read_file");
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
BinaryAsyncWorker* worker = new BinaryAsyncWorker(cb, std::move(execute_fn));
|
|
355
|
+
worker->Queue();
|
|
356
|
+
|
|
357
|
+
return promise;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
Value ChatEncryptFile(const CallbackInfo& args) {
|
|
361
|
+
Env env = args.Env();
|
|
362
|
+
if (args.Length() < 3 || !args[0].IsBigInt() || !args[1].IsString() || !args[2].IsString()) {
|
|
363
|
+
TypeError::New(env, "Expected bigint (ctrl), two strings (fromPath, toPath)").ThrowAsJavaScriptException();
|
|
364
|
+
return env.Undefined();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
chat_ctrl ctrl = FromChatCtrlBigInt(args[0]);
|
|
368
|
+
std::string fromPath = args[1].As<String>().Utf8Value();
|
|
369
|
+
std::string toPath = args[2].As<String>().Utf8Value();
|
|
370
|
+
|
|
371
|
+
Function cb;
|
|
372
|
+
Promise promise = CreatePromiseAndCallback(env, cb);
|
|
373
|
+
|
|
374
|
+
auto execute_fn = [ctrl, fromPath, toPath](ResultAsyncWorker* worker) {
|
|
375
|
+
char* c_res = chat_encrypt_file(ctrl, fromPath.c_str(), toPath.c_str());
|
|
376
|
+
HandleCResult(worker, c_res, "chat_encrypt_file");
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn));
|
|
380
|
+
worker->Queue();
|
|
381
|
+
|
|
382
|
+
return promise;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
Value ChatDecryptFile(const CallbackInfo& args) {
|
|
386
|
+
Env env = args.Env();
|
|
387
|
+
if (args.Length() < 4 || !args[0].IsString() || !args[1].IsString() || !args[2].IsString() || !args[3].IsString()) {
|
|
388
|
+
TypeError::New(env, "Expected four strings (fromPath, key, nonce, toPath)").ThrowAsJavaScriptException();
|
|
389
|
+
return env.Undefined();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
std::string fromPath = args[0].As<String>().Utf8Value();
|
|
393
|
+
std::string key = args[1].As<String>().Utf8Value();
|
|
394
|
+
std::string nonce = args[2].As<String>().Utf8Value();
|
|
395
|
+
std::string toPath = args[3].As<String>().Utf8Value();
|
|
396
|
+
|
|
397
|
+
Function cb;
|
|
398
|
+
Promise promise = CreatePromiseAndCallback(env, cb);
|
|
399
|
+
|
|
400
|
+
auto execute_fn = [fromPath, key, nonce, toPath](ResultAsyncWorker* worker) {
|
|
401
|
+
char* c_res = chat_decrypt_file(fromPath.c_str(), key.c_str(), nonce.c_str(), toPath.c_str());
|
|
402
|
+
HandleCResult(worker, c_res, "chat_decrypt_file");
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn));
|
|
406
|
+
worker->Queue();
|
|
407
|
+
|
|
408
|
+
return promise;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
Object Init(Env env, Object exports) {
|
|
412
|
+
haskell_init();
|
|
413
|
+
exports.Set("chat_migrate_init", Function::New(env, ChatMigrateInit));
|
|
414
|
+
exports.Set("chat_close_store", Function::New(env, ChatCloseStore));
|
|
415
|
+
exports.Set("chat_send_cmd", Function::New(env, ChatSendCmd));
|
|
416
|
+
exports.Set("chat_recv_msg_wait", Function::New(env, ChatRecvMsgWait));
|
|
417
|
+
exports.Set("chat_write_file", Function::New(env, ChatWriteFile));
|
|
418
|
+
exports.Set("chat_read_file", Function::New(env, ChatReadFile));
|
|
419
|
+
exports.Set("chat_encrypt_file", Function::New(env, ChatEncryptFile));
|
|
420
|
+
exports.Set("chat_decrypt_file", Function::New(env, ChatDecryptFile));
|
|
421
|
+
return exports;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
NODE_API_MODULE(simplex, Init)
|
|
425
|
+
|
|
426
|
+
}
|
package/cpp/simplex.h
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//
|
|
2
|
+
// simplex.h
|
|
3
|
+
// SimpleX
|
|
4
|
+
//
|
|
5
|
+
// Created by Evgeny on 30/05/2022.
|
|
6
|
+
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
#ifndef SimpleX_h
|
|
10
|
+
#define SimpleX_h
|
|
11
|
+
|
|
12
|
+
extern "C" void hs_init(int argc, char **argv[]);
|
|
13
|
+
extern "C" void hs_init_with_rtsopts(int * argc, char **argv[]);
|
|
14
|
+
|
|
15
|
+
typedef long* chat_ctrl;
|
|
16
|
+
|
|
17
|
+
// the last parameter is used to return the pointer to chat controller
|
|
18
|
+
extern "C" char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl);
|
|
19
|
+
extern "C" char *chat_close_store(chat_ctrl ctrl);
|
|
20
|
+
extern "C" char *chat_reopen_store(chat_ctrl ctrl);
|
|
21
|
+
extern "C" char *chat_send_cmd(chat_ctrl ctrl, const char *cmd);
|
|
22
|
+
extern "C" char *chat_recv_msg_wait(chat_ctrl ctrl, const int wait);
|
|
23
|
+
extern "C" char *chat_parse_markdown(const char *str);
|
|
24
|
+
extern "C" char *chat_parse_server(const char *str);
|
|
25
|
+
extern "C" char *chat_password_hash(const char *pwd, const char *salt);
|
|
26
|
+
extern "C" char *chat_valid_name(const char *name);
|
|
27
|
+
extern "C" int chat_json_length(const char *str);
|
|
28
|
+
extern "C" char *chat_encrypt_media(chat_ctrl ctrl, const char *key, const char *frame, const int len);
|
|
29
|
+
extern "C" char *chat_decrypt_media(const char *key, const char *frame, const int len);
|
|
30
|
+
|
|
31
|
+
// chat_write_file returns null-terminated string with JSON of WriteFileResult
|
|
32
|
+
extern "C" char *chat_write_file(chat_ctrl ctrl, const char *path, const char *data, const int len);
|
|
33
|
+
|
|
34
|
+
// chat_read_file returns a buffer with:
|
|
35
|
+
// result status (1 byte), then if
|
|
36
|
+
// status == 0 (success): buffer length (uint32, 4 bytes), buffer of specified length.
|
|
37
|
+
// status == 1 (error): null-terminated error message string.
|
|
38
|
+
extern "C" char *chat_read_file(const char *path, const char *key, const char *nonce);
|
|
39
|
+
|
|
40
|
+
// chat_encrypt_file returns null-terminated string with JSON of WriteFileResult
|
|
41
|
+
extern "C" char *chat_encrypt_file(chat_ctrl ctrl, const char *fromPath, const char *toPath);
|
|
42
|
+
|
|
43
|
+
// chat_decrypt_file returns null-terminated string with the error message
|
|
44
|
+
extern "C" char *chat_decrypt_file(const char *fromPath, const char *key, const char *nonce, const char *toPath);
|
|
45
|
+
|
|
46
|
+
#endif /* simplex_h */
|