mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-22 21:52:28 +00:00
nodejs: load libsimplex at runtime per backend
This commit is contained in:
1 parent
a5ceeac000
commit
80182a7237
49 files changed
+549
-495
No files matched your search
@@ -2,7 +2,6 @@ node_modules/
|
||||
package-lock.json
|
||||
.vscode
|
||||
build/
|
||||
libs/
|
||||
dist/
|
||||
coverage/
|
||||
tmp/
|
||||
@@ -1,3 +1,2 @@
|
||||
libs/
|
||||
build/
|
||||
node_modules/
|
||||
@@ -62,31 +62,27 @@ There is an example with more options in [./examples/squaring-bot.ts](./examples
|
||||
|
||||
You can run it with: `npx ts-node ./examples/squaring-bot.ts`
|
||||
|
||||
## PostgreSQL backend
|
||||
## Native library
|
||||
|
||||
By default, the package uses SQLite. To use PostgreSQL instead:
|
||||
`libsimplex` is downloaded on first use into the user cache (`$XDG_CACHE_HOME/simplex-chat` or `~/.cache/simplex-chat` on Linux, `~/Library/Caches/simplex-chat` on macOS, `%LOCALAPPDATA%\simplex-chat` on Windows), shared with the Python library. To download it ahead of time, for example in a Dockerfile, run as the user that runs the app:
|
||||
|
||||
```bash
|
||||
npm install simplex-chat --simplex_backend=postgres
|
||||
npx simplex-chat install # sqlite (default)
|
||||
npx simplex-chat install --backend postgres # Linux x86_64 only
|
||||
```
|
||||
|
||||
Or persist the setting in `.npmrc`:
|
||||
Set `SIMPLEX_LIBS_DIR` to a directory with a local build of `libsimplex` and its dependencies to use it instead; it must be built for the backend in use.
|
||||
|
||||
```ini
|
||||
simplex_backend=postgres
|
||||
```
|
||||
`ChatApi.init` loads the library for `DbConfig.type`; the low-level `core` functions require `core.loadLibrary(backend)` first.
|
||||
|
||||
### Prerequisites (PostgreSQL)
|
||||
## PostgreSQL backend
|
||||
|
||||
`DbConfig.type` selects the backend; one backend per process.
|
||||
|
||||
- `libpq5` must be installed on the host system (`apt install libpq5` on Debian/Ubuntu)
|
||||
- PostgreSQL backend is only available for Linux x86_64
|
||||
- A PostgreSQL server accessible via connection string
|
||||
|
||||
### Passing PostgreSQL connection
|
||||
|
||||
The `DbConfig` type is a discriminated union — pick the variant that matches
|
||||
the backend you installed:
|
||||
|
||||
```ts
|
||||
// SQLite (default)
|
||||
dbOpts: {type: "sqlite", filePrefix: "./data/bot"}
|
||||
|
||||
@@ -15,36 +15,8 @@
|
||||
"msvs_settings": { "VCCLCompilerTool": { "ExceptionHandling": 1 } },
|
||||
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
|
||||
"conditions": [
|
||||
["OS=='mac'", {
|
||||
"libraries": [
|
||||
"-L<(module_root_dir)/libs",
|
||||
"-lsimplex"
|
||||
],
|
||||
"xcode_settings": {
|
||||
"OTHER_LDFLAGS": [
|
||||
"-Wl,-rpath,@loader_path/../../libs"
|
||||
]
|
||||
}
|
||||
}],
|
||||
["OS=='linux'", {
|
||||
"libraries": [
|
||||
"-L<(module_root_dir)/libs",
|
||||
"-lsimplex"
|
||||
],
|
||||
"ldflags": [
|
||||
"-Wl,-rpath,'$$ORIGIN'/../../libs"
|
||||
]
|
||||
}],
|
||||
["OS=='win'", {
|
||||
"libraries": [
|
||||
"<(module_root_dir)/libs/libsimplex.lib"
|
||||
],
|
||||
"copies": [{
|
||||
"destination": "<(PRODUCT_DIR)",
|
||||
"files": [
|
||||
"<(module_root_dir)/libs/*"
|
||||
]
|
||||
}]
|
||||
"libraries": [ "-ldl" ]
|
||||
}]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,15 +8,102 @@
|
||||
#include <thread>
|
||||
#include <system_error>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <unordered_map>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
#include "simplex.h"
|
||||
|
||||
namespace simplex {
|
||||
|
||||
using namespace Napi;
|
||||
|
||||
struct Library {
|
||||
std::string path;
|
||||
hs_init_with_rtsopts_fn hs_init_with_rtsopts = nullptr;
|
||||
hs_thread_done_fn hs_thread_done = nullptr;
|
||||
chat_migrate_init_fn chat_migrate_init = nullptr;
|
||||
chat_migrate_init_queue_fn chat_migrate_init_queue = nullptr;
|
||||
chat_close_store_fn chat_close_store = nullptr;
|
||||
chat_send_cmd_fn chat_send_cmd = nullptr;
|
||||
chat_recv_msg_wait_fn chat_recv_msg_wait = nullptr;
|
||||
chat_write_file_fn chat_write_file = nullptr;
|
||||
chat_read_file_fn chat_read_file = nullptr;
|
||||
chat_encrypt_file_fn chat_encrypt_file = nullptr;
|
||||
chat_decrypt_file_fn chat_decrypt_file = nullptr;
|
||||
};
|
||||
|
||||
static Library lib;
|
||||
static std::atomic<bool> loaded{false};
|
||||
static std::mutex load_mutex;
|
||||
|
||||
#ifdef _WIN32
|
||||
typedef HMODULE LibHandle;
|
||||
|
||||
static LibHandle OpenLibrary(const std::string& path) {
|
||||
int n = MultiByteToWideChar(CP_UTF8, 0, path.c_str(), -1, nullptr, 0);
|
||||
std::wstring wpath(n, L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, path.c_str(), -1, &wpath[0], n);
|
||||
// dependencies resolve from the libsimplex.dll, application and system dirs, not the cwd or PATH
|
||||
return LoadLibraryExW(wpath.c_str(), nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
|
||||
}
|
||||
|
||||
static void CloseLibrary(LibHandle h) {
|
||||
FreeLibrary(h);
|
||||
}
|
||||
|
||||
static void* FindSymbol(LibHandle h, const char* name) {
|
||||
return reinterpret_cast<void*>(GetProcAddress(h, name));
|
||||
}
|
||||
|
||||
static std::string LastLoadError() {
|
||||
DWORD code = GetLastError();
|
||||
char* msg = nullptr;
|
||||
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, code, 0, reinterpret_cast<LPSTR>(&msg), 0, nullptr);
|
||||
std::string err = msg ? msg : "error " + std::to_string(code);
|
||||
LocalFree(msg);
|
||||
// system messages end with CRLF
|
||||
err.erase(err.find_last_not_of("\r\n") + 1);
|
||||
return err;
|
||||
}
|
||||
#else
|
||||
typedef void* LibHandle;
|
||||
|
||||
static LibHandle OpenLibrary(const std::string& path) {
|
||||
return dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
|
||||
}
|
||||
|
||||
static void CloseLibrary(LibHandle h) {
|
||||
dlclose(h);
|
||||
}
|
||||
|
||||
static void* FindSymbol(LibHandle h, const char* name) {
|
||||
return dlsym(h, name);
|
||||
}
|
||||
|
||||
static std::string LastLoadError() {
|
||||
const char* err = dlerror();
|
||||
return err ? err : "unknown error";
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename F>
|
||||
static void Resolve(LibHandle h, const char* name, F& fn) {
|
||||
fn = reinterpret_cast<F>(FindSymbol(h, name));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
static void ResolveRequired(LibHandle h, const char* name, F& fn, std::string& missing) {
|
||||
Resolve(h, name, fn);
|
||||
if (fn == nullptr && missing.empty()) missing = name;
|
||||
}
|
||||
|
||||
void haskell_init() {
|
||||
#ifdef _WIN32
|
||||
// non-moving GC is broken on windows with GHC 9.4-9.6.3
|
||||
@@ -40,7 +127,57 @@ void haskell_init() {
|
||||
nullptr};
|
||||
#endif
|
||||
char **pargv = const_cast<char **>(argv);
|
||||
hs_init_with_rtsopts(&argc, &pargv);
|
||||
lib.hs_init_with_rtsopts(&argc, &pargv);
|
||||
}
|
||||
|
||||
static bool RequireLoaded(Env env) {
|
||||
if (loaded) return true;
|
||||
Error::New(env, "libsimplex is not loaded, call core.loadLibrary(backend) first").ThrowAsJavaScriptException();
|
||||
return false;
|
||||
}
|
||||
|
||||
Value Load(const CallbackInfo& args) {
|
||||
Env env = args.Env();
|
||||
if (args.Length() < 1 || !args[0].IsString()) {
|
||||
TypeError::New(env, "Expected string (libPath)").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
std::string path = args[0].As<String>().Utf8Value();
|
||||
// worker_threads may load concurrently; the Haskell runtime must be initialized once per process
|
||||
std::lock_guard<std::mutex> lock(load_mutex);
|
||||
if (loaded) {
|
||||
if (path != lib.path) Error::New(env, "libsimplex already loaded from " + lib.path).ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
LibHandle h = OpenLibrary(path);
|
||||
if (h == nullptr) {
|
||||
std::string err = LastLoadError();
|
||||
Error::New(env, "cannot load " + path + ": " + err).ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
Library l;
|
||||
l.path = path;
|
||||
std::string missing;
|
||||
ResolveRequired(h, "hs_init_with_rtsopts", l.hs_init_with_rtsopts, missing);
|
||||
ResolveRequired(h, "chat_migrate_init", l.chat_migrate_init, missing);
|
||||
ResolveRequired(h, "chat_close_store", l.chat_close_store, missing);
|
||||
ResolveRequired(h, "chat_send_cmd", l.chat_send_cmd, missing);
|
||||
ResolveRequired(h, "chat_recv_msg_wait", l.chat_recv_msg_wait, missing);
|
||||
ResolveRequired(h, "chat_write_file", l.chat_write_file, missing);
|
||||
ResolveRequired(h, "chat_read_file", l.chat_read_file, missing);
|
||||
ResolveRequired(h, "chat_encrypt_file", l.chat_encrypt_file, missing);
|
||||
ResolveRequired(h, "chat_decrypt_file", l.chat_decrypt_file, missing);
|
||||
if (!missing.empty()) {
|
||||
CloseLibrary(h);
|
||||
Error::New(env, path + " does not export " + missing).ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
Resolve(h, "chat_migrate_init_queue", l.chat_migrate_init_queue);
|
||||
Resolve(h, "hs_thread_done", l.hs_thread_done);
|
||||
lib = l;
|
||||
haskell_init();
|
||||
loaded = true;
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
class ResultAsyncWorker : public AsyncWorker {
|
||||
@@ -287,7 +424,7 @@ class Receiver {
|
||||
request = std::move(queue_.front());
|
||||
queue_.pop_front();
|
||||
}
|
||||
char* c_res = chat_recv_msg_wait(ctrl_, request.wait);
|
||||
char* c_res = lib.chat_recv_msg_wait(ctrl_, request.wait);
|
||||
napi_status status = Settle(request.deferred, [c_res](Napi::Env env, Promise::Deferred& deferred) {
|
||||
if (c_res == nullptr) {
|
||||
deferred.Reject(Error::New(env, "chat_recv_msg_wait failed").Value());
|
||||
@@ -312,7 +449,7 @@ class Receiver {
|
||||
}
|
||||
pending_->Tsfn().Release();
|
||||
// Each OS thread that enters Haskell keeps an RTS task record until it calls hs_thread_done.
|
||||
hs_thread_done();
|
||||
if (lib.hs_thread_done) lib.hs_thread_done();
|
||||
}
|
||||
|
||||
template <typename SettleFn>
|
||||
@@ -361,6 +498,7 @@ ResultAsyncWorker::ResultProcessor MigrateResultProcessor() {
|
||||
|
||||
Value ChatMigrateInit(const CallbackInfo& args) {
|
||||
Env env = args.Env();
|
||||
if (!RequireLoaded(env)) return env.Undefined();
|
||||
if (args.Length() < 3 || !args[0].IsString() || !args[1].IsString() || !args[2].IsString()) {
|
||||
TypeError::New(env, "Expected three string arguments").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
@@ -375,7 +513,7 @@ Value ChatMigrateInit(const CallbackInfo& args) {
|
||||
|
||||
auto execute_fn = [path, key, confirm](ResultAsyncWorker* worker) {
|
||||
chat_ctrl ctrl = nullptr;
|
||||
char* c_res = chat_migrate_init(path.c_str(), key.c_str(), confirm.c_str(), &ctrl);
|
||||
char* c_res = lib.chat_migrate_init(path.c_str(), key.c_str(), confirm.c_str(), &ctrl);
|
||||
worker->SetCtrl(reinterpret_cast<uintptr_t>(ctrl));
|
||||
HandleCResult(worker, c_res, "chat_migrate_init");
|
||||
};
|
||||
@@ -388,6 +526,11 @@ Value ChatMigrateInit(const CallbackInfo& args) {
|
||||
|
||||
Value ChatMigrateInitQueue(const CallbackInfo& args) {
|
||||
Env env = args.Env();
|
||||
if (!RequireLoaded(env)) return env.Undefined();
|
||||
if (lib.chat_migrate_init_queue == nullptr) {
|
||||
Error::New(env, "loaded libsimplex does not export chat_migrate_init_queue; queue size needs a newer libsimplex").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
if (args.Length() < 4 || !args[0].IsString() || !args[1].IsString() || !args[2].IsString() || !args[3].IsNumber()) {
|
||||
TypeError::New(env, "Expected three string arguments and number").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
@@ -408,7 +551,7 @@ Value ChatMigrateInitQueue(const CallbackInfo& args) {
|
||||
|
||||
auto execute_fn = [path, key, confirm, queue_size](ResultAsyncWorker* worker) {
|
||||
chat_ctrl ctrl = nullptr;
|
||||
char* c_res = chat_migrate_init_queue(path.c_str(), key.c_str(), confirm.c_str(), queue_size, &ctrl);
|
||||
char* c_res = lib.chat_migrate_init_queue(path.c_str(), key.c_str(), confirm.c_str(), queue_size, &ctrl);
|
||||
worker->SetCtrl(reinterpret_cast<uintptr_t>(ctrl));
|
||||
HandleCResult(worker, c_res, "chat_migrate_init_queue");
|
||||
};
|
||||
@@ -421,6 +564,7 @@ Value ChatMigrateInitQueue(const CallbackInfo& args) {
|
||||
|
||||
Value ChatCloseStore(const CallbackInfo& args) {
|
||||
Env env = args.Env();
|
||||
if (!RequireLoaded(env)) return env.Undefined();
|
||||
if (args.Length() < 1 || !args[0].IsBigInt()) {
|
||||
TypeError::New(env, "Expected bigint (ctrl)").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
@@ -436,7 +580,7 @@ Value ChatCloseStore(const CallbackInfo& args) {
|
||||
if (receiver) {
|
||||
receiver->Stop();
|
||||
}
|
||||
char* c_res = chat_close_store(ctrl);
|
||||
char* c_res = lib.chat_close_store(ctrl);
|
||||
HandleCResult(worker, c_res, "chat_close_store");
|
||||
};
|
||||
|
||||
@@ -448,6 +592,7 @@ Value ChatCloseStore(const CallbackInfo& args) {
|
||||
|
||||
Value ChatSendCmd(const CallbackInfo& args) {
|
||||
Env env = args.Env();
|
||||
if (!RequireLoaded(env)) return env.Undefined();
|
||||
if (args.Length() < 2 || !args[0].IsBigInt() || !args[1].IsString()) {
|
||||
TypeError::New(env, "Expected bigint (ctrl) and string (cmd)").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
@@ -460,7 +605,7 @@ Value ChatSendCmd(const CallbackInfo& args) {
|
||||
Promise promise = CreatePromiseAndCallback(env, cb);
|
||||
|
||||
auto execute_fn = [ctrl, cmd](ResultAsyncWorker* worker) {
|
||||
char* c_res = chat_send_cmd(ctrl, cmd.c_str());
|
||||
char* c_res = lib.chat_send_cmd(ctrl, cmd.c_str());
|
||||
HandleCResult(worker, c_res, "chat_send_cmd");
|
||||
};
|
||||
|
||||
@@ -472,6 +617,7 @@ Value ChatSendCmd(const CallbackInfo& args) {
|
||||
|
||||
Value ChatRecvMsgWait(const CallbackInfo& args) {
|
||||
Env env = args.Env();
|
||||
if (!RequireLoaded(env)) return env.Undefined();
|
||||
if (args.Length() < 2 || !args[0].IsBigInt() || !args[1].IsNumber()) {
|
||||
TypeError::New(env, "Expected bigint (ctrl), number (wait)").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
@@ -503,6 +649,7 @@ Value ChatRecvMsgWait(const CallbackInfo& args) {
|
||||
|
||||
Value ChatWriteFile(const CallbackInfo& args) {
|
||||
Env env = args.Env();
|
||||
if (!RequireLoaded(env)) return env.Undefined();
|
||||
if (args.Length() < 3 || !args[0].IsBigInt() || !args[1].IsString() || !(args[2].IsArrayBuffer() || args[2].IsTypedArray())) {
|
||||
TypeError::New(env, "Expected bigint (ctrl), string (path), ArrayBuffer or Uint8Array").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
@@ -530,7 +677,7 @@ Value ChatWriteFile(const CallbackInfo& args) {
|
||||
Promise promise = CreatePromiseAndCallback(env, cb);
|
||||
|
||||
auto execute_fn = [ctrl, path, data, len](ResultAsyncWorker* worker) {
|
||||
char* c_res = chat_write_file(ctrl, path.c_str(), data, static_cast<int>(len));
|
||||
char* c_res = lib.chat_write_file(ctrl, path.c_str(), data, static_cast<int>(len));
|
||||
HandleCResult(worker, c_res, "chat_write_file");
|
||||
};
|
||||
|
||||
@@ -543,6 +690,7 @@ Value ChatWriteFile(const CallbackInfo& args) {
|
||||
|
||||
Value ChatReadFile(const CallbackInfo& args) {
|
||||
Env env = args.Env();
|
||||
if (!RequireLoaded(env)) return env.Undefined();
|
||||
if (args.Length() < 3 || !args[0].IsString() || !args[1].IsString() || !args[2].IsString()) {
|
||||
TypeError::New(env, "Expected three strings (path, key, nonce)").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
@@ -556,7 +704,7 @@ Value ChatReadFile(const CallbackInfo& args) {
|
||||
Promise promise = CreatePromiseAndCallback(env, cb);
|
||||
|
||||
auto execute_fn = [path, key, nonce](BinaryAsyncWorker* worker) {
|
||||
char* buf = chat_read_file(path.c_str(), key.c_str(), nonce.c_str());
|
||||
char* buf = lib.chat_read_file(path.c_str(), key.c_str(), nonce.c_str());
|
||||
if (buf == nullptr) {
|
||||
worker->SetWorkerError("chat_read_file failed");
|
||||
return;
|
||||
@@ -586,6 +734,7 @@ Value ChatReadFile(const CallbackInfo& args) {
|
||||
|
||||
Value ChatEncryptFile(const CallbackInfo& args) {
|
||||
Env env = args.Env();
|
||||
if (!RequireLoaded(env)) return env.Undefined();
|
||||
if (args.Length() < 3 || !args[0].IsBigInt() || !args[1].IsString() || !args[2].IsString()) {
|
||||
TypeError::New(env, "Expected bigint (ctrl), two strings (fromPath, toPath)").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
@@ -599,7 +748,7 @@ Value ChatEncryptFile(const CallbackInfo& args) {
|
||||
Promise promise = CreatePromiseAndCallback(env, cb);
|
||||
|
||||
auto execute_fn = [ctrl, fromPath, toPath](ResultAsyncWorker* worker) {
|
||||
char* c_res = chat_encrypt_file(ctrl, fromPath.c_str(), toPath.c_str());
|
||||
char* c_res = lib.chat_encrypt_file(ctrl, fromPath.c_str(), toPath.c_str());
|
||||
HandleCResult(worker, c_res, "chat_encrypt_file");
|
||||
};
|
||||
|
||||
@@ -611,6 +760,7 @@ Value ChatEncryptFile(const CallbackInfo& args) {
|
||||
|
||||
Value ChatDecryptFile(const CallbackInfo& args) {
|
||||
Env env = args.Env();
|
||||
if (!RequireLoaded(env)) return env.Undefined();
|
||||
if (args.Length() < 4 || !args[0].IsString() || !args[1].IsString() || !args[2].IsString() || !args[3].IsString()) {
|
||||
TypeError::New(env, "Expected four strings (fromPath, key, nonce, toPath)").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
@@ -625,7 +775,7 @@ Value ChatDecryptFile(const CallbackInfo& args) {
|
||||
Promise promise = CreatePromiseAndCallback(env, cb);
|
||||
|
||||
auto execute_fn = [fromPath, key, nonce, toPath](ResultAsyncWorker* worker) {
|
||||
char* c_res = chat_decrypt_file(fromPath.c_str(), key.c_str(), nonce.c_str(), toPath.c_str());
|
||||
char* c_res = lib.chat_decrypt_file(fromPath.c_str(), key.c_str(), nonce.c_str(), toPath.c_str());
|
||||
HandleCResult(worker, c_res, "chat_decrypt_file");
|
||||
};
|
||||
|
||||
@@ -636,7 +786,7 @@ Value ChatDecryptFile(const CallbackInfo& args) {
|
||||
}
|
||||
|
||||
Object Init(Env env, Object exports) {
|
||||
haskell_init();
|
||||
exports.Set("load", Function::New(env, Load));
|
||||
auto* receivers = new Receivers();
|
||||
// Stopping all receivers before joining any bounds teardown by the longest in-flight receive.
|
||||
env.AddCleanupHook([receivers]() {
|
||||
|
||||
@@ -9,40 +9,30 @@
|
||||
#ifndef SimpleX_h
|
||||
#define SimpleX_h
|
||||
|
||||
extern "C" void hs_init(int argc, char **argv[]);
|
||||
extern "C" void hs_init_with_rtsopts(int * argc, char **argv[]);
|
||||
extern "C" void hs_thread_done(void);
|
||||
|
||||
typedef long* chat_ctrl;
|
||||
|
||||
typedef void (*hs_init_with_rtsopts_fn)(int *argc, char **argv[]);
|
||||
typedef void (*hs_thread_done_fn)(void);
|
||||
// the last parameter is used to return the pointer to chat controller
|
||||
extern "C" char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl);
|
||||
extern "C" char *chat_migrate_init_queue(const char *path, const char *key, const char *confirm, const int queueSize, chat_ctrl *ctrl);
|
||||
extern "C" char *chat_close_store(chat_ctrl ctrl);
|
||||
extern "C" char *chat_reopen_store(chat_ctrl ctrl);
|
||||
extern "C" char *chat_send_cmd(chat_ctrl ctrl, const char *cmd);
|
||||
extern "C" char *chat_recv_msg_wait(chat_ctrl ctrl, const int wait);
|
||||
extern "C" char *chat_parse_markdown(const char *str);
|
||||
extern "C" char *chat_parse_server(const char *str);
|
||||
extern "C" char *chat_password_hash(const char *pwd, const char *salt);
|
||||
extern "C" char *chat_valid_name(const char *name);
|
||||
extern "C" int chat_json_length(const char *str);
|
||||
extern "C" char *chat_encrypt_media(chat_ctrl ctrl, const char *key, const char *frame, const int len);
|
||||
extern "C" char *chat_decrypt_media(const char *key, const char *frame, const int len);
|
||||
typedef char *(*chat_migrate_init_fn)(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl);
|
||||
typedef char *(*chat_migrate_init_queue_fn)(const char *path, const char *key, const char *confirm, int queueSize, chat_ctrl *ctrl);
|
||||
typedef char *(*chat_close_store_fn)(chat_ctrl ctrl);
|
||||
typedef char *(*chat_send_cmd_fn)(chat_ctrl ctrl, const char *cmd);
|
||||
typedef char *(*chat_recv_msg_wait_fn)(chat_ctrl ctrl, int wait);
|
||||
|
||||
// chat_write_file returns null-terminated string with JSON of WriteFileResult
|
||||
extern "C" char *chat_write_file(chat_ctrl ctrl, const char *path, const char *data, const int len);
|
||||
typedef char *(*chat_write_file_fn)(chat_ctrl ctrl, const char *path, const char *data, int len);
|
||||
|
||||
// chat_read_file returns a buffer with:
|
||||
// result status (1 byte), then if
|
||||
// status == 0 (success): buffer length (uint32, 4 bytes), buffer of specified length.
|
||||
// status == 1 (error): null-terminated error message string.
|
||||
extern "C" char *chat_read_file(const char *path, const char *key, const char *nonce);
|
||||
typedef char *(*chat_read_file_fn)(const char *path, const char *key, const char *nonce);
|
||||
|
||||
// chat_encrypt_file returns null-terminated string with JSON of WriteFileResult
|
||||
extern "C" char *chat_encrypt_file(chat_ctrl ctrl, const char *fromPath, const char *toPath);
|
||||
typedef char *(*chat_encrypt_file_fn)(chat_ctrl ctrl, const char *fromPath, const char *toPath);
|
||||
|
||||
// chat_decrypt_file returns null-terminated string with the error message
|
||||
extern "C" char *chat_decrypt_file(const char *fromPath, const char *key, const char *nonce, const char *toPath);
|
||||
typedef char *(*chat_decrypt_file_fn)(const char *fromPath, const char *key, const char *nonce, const char *toPath);
|
||||
|
||||
#endif /* simplex_h */
|
||||
@@ -32,6 +32,7 @@ You are unlikely to ever need to use this module directly.
|
||||
|
||||
## Type Aliases
|
||||
|
||||
- [Backend](core.TypeAlias.Backend.md)
|
||||
- [DBMigrationError](core.TypeAlias.DBMigrationError.md)
|
||||
- [MigrationError](core.TypeAlias.MigrationError.md)
|
||||
- [MTRError](core.TypeAlias.MTRError.md)
|
||||
@@ -46,3 +47,4 @@ You are unlikely to ever need to use this module directly.
|
||||
- [chatRecvMsgWait](core.Function.chatRecvMsgWait.md)
|
||||
- [chatSendCmd](core.Function.chatSendCmd.md)
|
||||
- [chatWriteFile](core.Function.chatWriteFile.md)
|
||||
- [loadLibrary](core.Function.loadLibrary.md)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Class: ChatApi
|
||||
|
||||
Defined in: [src/api.ts:97](../src/api.ts#L97)
|
||||
Defined in: [src/api.ts:95](../src/api.ts#L95)
|
||||
|
||||
Main API class for interacting with the chat core library.
|
||||
|
||||
@@ -16,7 +16,7 @@ Main API class for interacting with the chat core library.
|
||||
|
||||
> `protected` **ctrl\_**: `bigint` \| `undefined`
|
||||
|
||||
Defined in: [src/api.ts:103](../src/api.ts#L103)
|
||||
Defined in: [src/api.ts:101](../src/api.ts#L101)
|
||||
|
||||
## Accessors
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/api.ts:103](../src/api.ts#L103)
|
||||
|
||||
> **get** **ctrl**(): `bigint`
|
||||
|
||||
Defined in: [src/api.ts:344](../src/api.ts#L344)
|
||||
Defined in: [src/api.ts:343](../src/api.ts#L343)
|
||||
|
||||
Chat controller reference
|
||||
|
||||
@@ -42,7 +42,7 @@ Chat controller reference
|
||||
|
||||
> **get** **initialized**(): `boolean`
|
||||
|
||||
Defined in: [src/api.ts:330](../src/api.ts#L330)
|
||||
Defined in: [src/api.ts:329](../src/api.ts#L329)
|
||||
|
||||
Chat controller is initialized
|
||||
|
||||
@@ -58,7 +58,7 @@ Chat controller is initialized
|
||||
|
||||
> **get** **started**(): `boolean`
|
||||
|
||||
Defined in: [src/api.ts:337](../src/api.ts#L337)
|
||||
Defined in: [src/api.ts:336](../src/api.ts#L336)
|
||||
|
||||
Chat controller is started
|
||||
|
||||
@@ -72,7 +72,7 @@ Chat controller is started
|
||||
|
||||
> **apiAcceptContactRequest**(`contactReqId`): `Promise`\<`Contact`\>
|
||||
|
||||
Defined in: [src/api.ts:750](../src/api.ts#L750)
|
||||
Defined in: [src/api.ts:749](../src/api.ts#L749)
|
||||
|
||||
Accept contact request.
|
||||
Network usage: interactive.
|
||||
@@ -93,7 +93,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiAcceptMember**(`groupId`, `groupMemberId`, `memberRole`): `Promise`\<`GroupMember`\>
|
||||
|
||||
Defined in: [src/api.ts:570](../src/api.ts#L570)
|
||||
Defined in: [src/api.ts:569](../src/api.ts#L569)
|
||||
|
||||
Accept group member. Requires Admin role.
|
||||
Network usage: background.
|
||||
@@ -122,7 +122,7 @@ Network usage: background.
|
||||
|
||||
> **apiAddMember**(`groupId`, `contactId`, `memberRole`): `Promise`\<`GroupMember`\>
|
||||
|
||||
Defined in: [src/api.ts:550](../src/api.ts#L550)
|
||||
Defined in: [src/api.ts:549](../src/api.ts#L549)
|
||||
|
||||
Add contact to group. Requires bot to have Admin role.
|
||||
Network usage: interactive.
|
||||
@@ -151,7 +151,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiBlockMembersForAll**(`groupId`, `groupMemberIds`, `blocked`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:590](../src/api.ts#L590)
|
||||
Defined in: [src/api.ts:589](../src/api.ts#L589)
|
||||
|
||||
Block members. Requires Moderator role.
|
||||
Network usage: background.
|
||||
@@ -180,7 +180,7 @@ Network usage: background.
|
||||
|
||||
> **apiCancelFile**(`fileId`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:540](../src/api.ts#L540)
|
||||
Defined in: [src/api.ts:539](../src/api.ts#L539)
|
||||
|
||||
Cancel file.
|
||||
Network usage: background.
|
||||
@@ -201,7 +201,7 @@ Network usage: background.
|
||||
|
||||
> **apiChatItemReaction**(`chatType`, `chatId`, `chatItemId`, `add`, `reaction`): `Promise`\<`ACIReaction`\>
|
||||
|
||||
Defined in: [src/api.ts:513](../src/api.ts#L513)
|
||||
Defined in: [src/api.ts:512](../src/api.ts#L512)
|
||||
|
||||
Add/remove message reaction.
|
||||
Network usage: background.
|
||||
@@ -238,7 +238,7 @@ Network usage: background.
|
||||
|
||||
> **apiConnect**(`userId`, `incognito`, `preparedLink`): `Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\>
|
||||
|
||||
Defined in: [src/api.ts:719](../src/api.ts#L719)
|
||||
Defined in: [src/api.ts:718](../src/api.ts#L718)
|
||||
|
||||
Connect via prepared SimpleX link. The link can be 1-time invitation link, contact address or group link
|
||||
Network usage: interactive.
|
||||
@@ -267,7 +267,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiConnectActiveUser**(`connLink`): `Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\>
|
||||
|
||||
Defined in: [src/api.ts:728](../src/api.ts#L728)
|
||||
Defined in: [src/api.ts:727](../src/api.ts#L727)
|
||||
|
||||
Connect via SimpleX link as string in the active user profile.
|
||||
Network usage: interactive.
|
||||
@@ -288,7 +288,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiConnectPlan**(`userId`, `connectionLink`): `Promise`\<\[`ConnectionPlan`, `CreatedConnLink`\]\>
|
||||
|
||||
Defined in: [src/api.ts:709](../src/api.ts#L709)
|
||||
Defined in: [src/api.ts:708](../src/api.ts#L708)
|
||||
|
||||
Determine SimpleX link type and if the bot is already connected via this link.
|
||||
Network usage: interactive.
|
||||
@@ -313,7 +313,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiCreateActiveUser**(`profile?`): `Promise`\<`User`\>
|
||||
|
||||
Defined in: [src/api.ts:887](../src/api.ts#L887)
|
||||
Defined in: [src/api.ts:886](../src/api.ts#L886)
|
||||
|
||||
Create new user profile
|
||||
Network usage: no.
|
||||
@@ -334,7 +334,7 @@ Network usage: no.
|
||||
|
||||
> **apiCreateGroupLink**(`groupId`, `memberRole`): `Promise`\<`string`\>
|
||||
|
||||
Defined in: [src/api.ts:650](../src/api.ts#L650)
|
||||
Defined in: [src/api.ts:649](../src/api.ts#L649)
|
||||
|
||||
Create group link.
|
||||
Network usage: interactive.
|
||||
@@ -359,7 +359,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiCreateLink**(`userId`): `Promise`\<`string`\>
|
||||
|
||||
Defined in: [src/api.ts:696](../src/api.ts#L696)
|
||||
Defined in: [src/api.ts:695](../src/api.ts#L695)
|
||||
|
||||
Create 1-time invitation link.
|
||||
Network usage: interactive.
|
||||
@@ -380,7 +380,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiCreateMemberContact**(`groupId`, `groupMemberId`): `Promise`\<`Contact`\>
|
||||
|
||||
Defined in: [src/api.ts:953](../src/api.ts#L953)
|
||||
Defined in: [src/api.ts:952](../src/api.ts#L952)
|
||||
|
||||
Create a direct message contact with a group member.
|
||||
Returns the created contact.
|
||||
@@ -406,7 +406,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiCreateUserAddress**(`userId`): `Promise`\<`CreatedConnLink`\>
|
||||
|
||||
Defined in: [src/api.ts:361](../src/api.ts#L361)
|
||||
Defined in: [src/api.ts:360](../src/api.ts#L360)
|
||||
|
||||
Create bot address.
|
||||
Network usage: interactive.
|
||||
@@ -427,7 +427,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiDeleteChat**(`chatType`, `chatId`, `deleteMode?`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:809](../src/api.ts#L809)
|
||||
Defined in: [src/api.ts:808](../src/api.ts#L808)
|
||||
|
||||
Delete chat.
|
||||
Network usage: background.
|
||||
@@ -456,7 +456,7 @@ Network usage: background.
|
||||
|
||||
> **apiDeleteChatItems**(`chatType`, `chatId`, `chatItemIds`, `deleteMode`): `Promise`\<`ChatItemDeletion`[]\>
|
||||
|
||||
Defined in: [src/api.ts:488](../src/api.ts#L488)
|
||||
Defined in: [src/api.ts:487](../src/api.ts#L487)
|
||||
|
||||
Delete message.
|
||||
Network usage: background.
|
||||
@@ -489,7 +489,7 @@ Network usage: background.
|
||||
|
||||
> **apiDeleteGroupLink**(`groupId`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:672](../src/api.ts#L672)
|
||||
Defined in: [src/api.ts:671](../src/api.ts#L671)
|
||||
|
||||
Delete group link.
|
||||
Network usage: background.
|
||||
@@ -510,7 +510,7 @@ Network usage: background.
|
||||
|
||||
> **apiDeleteMemberChatItem**(`groupId`, `chatItemIds`): `Promise`\<`ChatItemDeletion`[]\>
|
||||
|
||||
Defined in: [src/api.ts:503](../src/api.ts#L503)
|
||||
Defined in: [src/api.ts:502](../src/api.ts#L502)
|
||||
|
||||
Moderate message. Requires Moderator role (and higher than message author's).
|
||||
Network usage: background.
|
||||
@@ -535,7 +535,7 @@ Network usage: background.
|
||||
|
||||
> **apiDeleteUser**(`userId`, `delSMPQueues`, `viewPwd?`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:917](../src/api.ts#L917)
|
||||
Defined in: [src/api.ts:916](../src/api.ts#L916)
|
||||
|
||||
Delete user profile.
|
||||
Network usage: background.
|
||||
@@ -564,7 +564,7 @@ Network usage: background.
|
||||
|
||||
> **apiDeleteUserAddress**(`userId`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:371](../src/api.ts#L371)
|
||||
Defined in: [src/api.ts:370](../src/api.ts#L370)
|
||||
|
||||
Deletes a user address.
|
||||
Network usage: background.
|
||||
@@ -585,7 +585,7 @@ Network usage: background.
|
||||
|
||||
> **apiGetActiveUser**(): `Promise`\<`User` \| `undefined`\>
|
||||
|
||||
Defined in: [src/api.ts:867](../src/api.ts#L867)
|
||||
Defined in: [src/api.ts:866](../src/api.ts#L866)
|
||||
|
||||
Get active user profile
|
||||
Network usage: no.
|
||||
@@ -600,7 +600,7 @@ Network usage: no.
|
||||
|
||||
> **apiGetChat**(`chatType`, `chatId`, `count`): `Promise`\<`any`\>
|
||||
|
||||
Defined in: [src/api.ts:857](../src/api.ts#L857)
|
||||
Defined in: [src/api.ts:856](../src/api.ts#L856)
|
||||
|
||||
Get chat items.
|
||||
Network usage: no.
|
||||
@@ -629,7 +629,7 @@ Network usage: no.
|
||||
|
||||
> **apiGetChats**(`userId`, `pagination`, `query?`, `pendingConnections?`): `Promise`\<`AChat`[]\>
|
||||
|
||||
Defined in: [src/api.ts:794](../src/api.ts#L794)
|
||||
Defined in: [src/api.ts:793](../src/api.ts#L793)
|
||||
|
||||
Get chat previews (paginated).
|
||||
Network usage: no.
|
||||
@@ -666,7 +666,7 @@ on large databases.
|
||||
|
||||
> **apiGetGroupLink**(`groupId`): `Promise`\<`GroupLink`\>
|
||||
|
||||
Defined in: [src/api.ts:681](../src/api.ts#L681)
|
||||
Defined in: [src/api.ts:680](../src/api.ts#L680)
|
||||
|
||||
Get group link.
|
||||
Network usage: no.
|
||||
@@ -687,7 +687,7 @@ Network usage: no.
|
||||
|
||||
> **apiGetGroupLinkStr**(`groupId`): `Promise`\<`string`\>
|
||||
|
||||
Defined in: [src/api.ts:687](../src/api.ts#L687)
|
||||
Defined in: [src/api.ts:686](../src/api.ts#L686)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -705,7 +705,7 @@ Defined in: [src/api.ts:687](../src/api.ts#L687)
|
||||
|
||||
> **apiGetUserAddress**(`userId`): `Promise`\<`UserContactLink` \| `undefined`\>
|
||||
|
||||
Defined in: [src/api.ts:381](../src/api.ts#L381)
|
||||
Defined in: [src/api.ts:380](../src/api.ts#L380)
|
||||
|
||||
Get bot address and settings.
|
||||
Network usage: no.
|
||||
@@ -726,7 +726,7 @@ Network usage: no.
|
||||
|
||||
> **apiJoinGroup**(`groupId`): `Promise`\<`GroupInfo`\>
|
||||
|
||||
Defined in: [src/api.ts:560](../src/api.ts#L560)
|
||||
Defined in: [src/api.ts:559](../src/api.ts#L559)
|
||||
|
||||
Join group.
|
||||
Network usage: interactive.
|
||||
@@ -747,7 +747,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiLeaveGroup**(`groupId`): `Promise`\<`GroupInfo`\>
|
||||
|
||||
Defined in: [src/api.ts:610](../src/api.ts#L610)
|
||||
Defined in: [src/api.ts:609](../src/api.ts#L609)
|
||||
|
||||
Leave group.
|
||||
Network usage: background.
|
||||
@@ -768,7 +768,7 @@ Network usage: background.
|
||||
|
||||
> **apiListContacts**(`userId`): `Promise`\<`Contact`[]\>
|
||||
|
||||
Defined in: [src/api.ts:770](../src/api.ts#L770)
|
||||
Defined in: [src/api.ts:769](../src/api.ts#L769)
|
||||
|
||||
Get contacts.
|
||||
Network usage: no.
|
||||
@@ -789,7 +789,7 @@ Network usage: no.
|
||||
|
||||
> **apiListGroups**(`userId`, `contactId?`, `search?`): `Promise`\<`GroupInfo`[]\>
|
||||
|
||||
Defined in: [src/api.ts:780](../src/api.ts#L780)
|
||||
Defined in: [src/api.ts:779](../src/api.ts#L779)
|
||||
|
||||
Get groups.
|
||||
Network usage: no.
|
||||
@@ -818,7 +818,7 @@ Network usage: no.
|
||||
|
||||
> **apiListMembers**(`groupId`): `Promise`\<`GroupMember`[]\>
|
||||
|
||||
Defined in: [src/api.ts:620](../src/api.ts#L620)
|
||||
Defined in: [src/api.ts:619](../src/api.ts#L619)
|
||||
|
||||
Get group members.
|
||||
Network usage: no.
|
||||
@@ -839,7 +839,7 @@ Network usage: no.
|
||||
|
||||
> **apiListUsers**(): `Promise`\<`UserInfo`[]\>
|
||||
|
||||
Defined in: [src/api.ts:897](../src/api.ts#L897)
|
||||
Defined in: [src/api.ts:896](../src/api.ts#L896)
|
||||
|
||||
Get all user profiles
|
||||
Network usage: no.
|
||||
@@ -854,7 +854,7 @@ Network usage: no.
|
||||
|
||||
> **apiNewGroup**(`userId`, `groupProfile`): `Promise`\<`GroupInfo`\>
|
||||
|
||||
Defined in: [src/api.ts:630](../src/api.ts#L630)
|
||||
Defined in: [src/api.ts:629](../src/api.ts#L629)
|
||||
|
||||
Create group.
|
||||
Network usage: no.
|
||||
@@ -879,7 +879,7 @@ Network usage: no.
|
||||
|
||||
> **apiReceiveFile**(`fileId`): `Promise`\<`AChatItem`\>
|
||||
|
||||
Defined in: [src/api.ts:529](../src/api.ts#L529)
|
||||
Defined in: [src/api.ts:528](../src/api.ts#L528)
|
||||
|
||||
Receive file.
|
||||
Network usage: no.
|
||||
@@ -900,7 +900,7 @@ Network usage: no.
|
||||
|
||||
> **apiRejectContactRequest**(`contactReqId`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:760](../src/api.ts#L760)
|
||||
Defined in: [src/api.ts:759](../src/api.ts#L759)
|
||||
|
||||
Reject contact request. The user who sent the request is **not notified**.
|
||||
Network usage: no.
|
||||
@@ -921,7 +921,7 @@ Network usage: no.
|
||||
|
||||
> **apiRemoveMembers**(`groupId`, `memberIds`, `withMessages?`): `Promise`\<`GroupMember`[]\>
|
||||
|
||||
Defined in: [src/api.ts:600](../src/api.ts#L600)
|
||||
Defined in: [src/api.ts:599](../src/api.ts#L599)
|
||||
|
||||
Remove members. Requires Admin role.
|
||||
Network usage: background.
|
||||
@@ -950,7 +950,7 @@ Network usage: background.
|
||||
|
||||
> **apiSendMemberContactInvitation**(`contactId`, `message?`): `Promise`\<`Contact`\>
|
||||
|
||||
Defined in: [src/api.ts:964](../src/api.ts#L964)
|
||||
Defined in: [src/api.ts:963](../src/api.ts#L963)
|
||||
|
||||
Send a direct message invitation to a group member contact.
|
||||
The contact must have been created with [apiCreateMemberContact](#apicreatemembercontact).
|
||||
@@ -976,7 +976,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiSendMessages**(`chat`, `messages`, `liveMessage?`): `Promise`\<`AChatItem`[]\>
|
||||
|
||||
Defined in: [src/api.ts:432](../src/api.ts#L432)
|
||||
Defined in: [src/api.ts:431](../src/api.ts#L431)
|
||||
|
||||
Send messages.
|
||||
Network usage: background.
|
||||
@@ -1005,7 +1005,7 @@ Network usage: background.
|
||||
|
||||
> **apiSendTextMessage**(`chat`, `text`, `inReplyTo?`): `Promise`\<`AChatItem`[]\>
|
||||
|
||||
Defined in: [src/api.ts:455](../src/api.ts#L455)
|
||||
Defined in: [src/api.ts:454](../src/api.ts#L454)
|
||||
|
||||
Send text message.
|
||||
Network usage: background.
|
||||
@@ -1034,7 +1034,7 @@ Network usage: background.
|
||||
|
||||
> **apiSendTextReply**(`chatItem`, `text`): `Promise`\<`AChatItem`[]\>
|
||||
|
||||
Defined in: [src/api.ts:463](../src/api.ts#L463)
|
||||
Defined in: [src/api.ts:462](../src/api.ts#L462)
|
||||
|
||||
Send text message in reply to received message.
|
||||
Network usage: background.
|
||||
@@ -1059,7 +1059,7 @@ Network usage: background.
|
||||
|
||||
> **apiSetActiveUser**(`userId`, `viewPwd?`): `Promise`\<`User`\>
|
||||
|
||||
Defined in: [src/api.ts:907](../src/api.ts#L907)
|
||||
Defined in: [src/api.ts:906](../src/api.ts#L906)
|
||||
|
||||
Set active user profile
|
||||
Network usage: no.
|
||||
@@ -1084,7 +1084,7 @@ Network usage: no.
|
||||
|
||||
> **apiSetAddressSettings**(`userId`, `__namedParameters`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:415](../src/api.ts#L415)
|
||||
Defined in: [src/api.ts:414](../src/api.ts#L414)
|
||||
|
||||
Set bot address settings.
|
||||
Network usage: interactive.
|
||||
@@ -1109,7 +1109,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiSetAutoAcceptMemberContacts**(`userId`, `onOff`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:846](../src/api.ts#L846)
|
||||
Defined in: [src/api.ts:845](../src/api.ts#L845)
|
||||
|
||||
Set auto-accept member contacts.
|
||||
Network usage: no.
|
||||
@@ -1134,7 +1134,7 @@ Network usage: no.
|
||||
|
||||
> **apiSetContactCustomData**(`contactId`, `customData?`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:836](../src/api.ts#L836)
|
||||
Defined in: [src/api.ts:835](../src/api.ts#L835)
|
||||
|
||||
Set contact custom data.
|
||||
Network usage: no.
|
||||
@@ -1159,7 +1159,7 @@ Network usage: no.
|
||||
|
||||
> **apiSetContactPrefs**(`contactId`, `preferences`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:943](../src/api.ts#L943)
|
||||
Defined in: [src/api.ts:942](../src/api.ts#L942)
|
||||
|
||||
Configure chat preference overrides for the contact.
|
||||
Network usage: background.
|
||||
@@ -1184,7 +1184,7 @@ Network usage: background.
|
||||
|
||||
> **apiSetGroupCustomData**(`groupId`, `customData?`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:826](../src/api.ts#L826)
|
||||
Defined in: [src/api.ts:825](../src/api.ts#L825)
|
||||
|
||||
Set group custom data.
|
||||
Network usage: no.
|
||||
@@ -1209,7 +1209,7 @@ Network usage: no.
|
||||
|
||||
> **apiSetGroupLinkMemberRole**(`groupId`, `memberRole`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:663](../src/api.ts#L663)
|
||||
Defined in: [src/api.ts:662](../src/api.ts#L662)
|
||||
|
||||
Set member role for group link.
|
||||
Network usage: no.
|
||||
@@ -1234,7 +1234,7 @@ Network usage: no.
|
||||
|
||||
> **apiSetMembersRole**(`groupId`, `groupMemberIds`, `memberRole`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:580](../src/api.ts#L580)
|
||||
Defined in: [src/api.ts:579](../src/api.ts#L579)
|
||||
|
||||
Set members role. Requires Admin role.
|
||||
Network usage: background.
|
||||
@@ -1263,7 +1263,7 @@ Network usage: background.
|
||||
|
||||
> **apiSetProfileAddress**(`userId`, `enable`): `Promise`\<`UserProfileUpdateSummary`\>
|
||||
|
||||
Defined in: [src/api.ts:399](../src/api.ts#L399)
|
||||
Defined in: [src/api.ts:398](../src/api.ts#L398)
|
||||
|
||||
Add address to bot profile.
|
||||
Network usage: interactive.
|
||||
@@ -1288,7 +1288,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiUpdateChatItem**(`chatType`, `chatId`, `chatItemId`, `msgContent`, `liveMessage`): `Promise`\<`ChatItem`\>
|
||||
|
||||
Defined in: [src/api.ts:471](../src/api.ts#L471)
|
||||
Defined in: [src/api.ts:470](../src/api.ts#L470)
|
||||
|
||||
Update message.
|
||||
Network usage: background.
|
||||
@@ -1325,7 +1325,7 @@ Network usage: background.
|
||||
|
||||
> **apiUpdateGroupProfile**(`groupId`, `groupProfile`): `Promise`\<`GroupInfo`\>
|
||||
|
||||
Defined in: [src/api.ts:640](../src/api.ts#L640)
|
||||
Defined in: [src/api.ts:639](../src/api.ts#L639)
|
||||
|
||||
Update group profile.
|
||||
Network usage: background.
|
||||
@@ -1350,7 +1350,7 @@ Network usage: background.
|
||||
|
||||
> **apiUpdateProfile**(`userId`, `profile`): `Promise`\<`UserProfileUpdateSummary` \| `undefined`\>
|
||||
|
||||
Defined in: [src/api.ts:927](../src/api.ts#L927)
|
||||
Defined in: [src/api.ts:926](../src/api.ts#L926)
|
||||
|
||||
Update user profile.
|
||||
Network usage: background.
|
||||
@@ -1375,7 +1375,7 @@ Network usage: background.
|
||||
|
||||
> **close**(): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:158](../src/api.ts#L158)
|
||||
Defined in: [src/api.ts:157](../src/api.ts#L157)
|
||||
|
||||
Stop chat controller and close chat database.
|
||||
The database is not closed if stopping fails.
|
||||
@@ -1391,7 +1391,7 @@ Usually doesn't need to be called in chat bots.
|
||||
|
||||
> **off**\<`K`\>(`event`, `subscriber?`): `void`
|
||||
|
||||
Defined in: [src/api.ts:302](../src/api.ts#L302)
|
||||
Defined in: [src/api.ts:301](../src/api.ts#L301)
|
||||
|
||||
Unsubscribe all or a specific handler from a specific event.
|
||||
|
||||
@@ -1425,7 +1425,7 @@ An optional subscriber function for the event.
|
||||
|
||||
> **offAny**(`receiver?`): `void`
|
||||
|
||||
Defined in: [src/api.ts:318](../src/api.ts#L318)
|
||||
Defined in: [src/api.ts:317](../src/api.ts#L317)
|
||||
|
||||
Unsubscribe all or a specific handler from any events.
|
||||
|
||||
@@ -1449,7 +1449,7 @@ An optional subscriber function for the event.
|
||||
|
||||
> **on**\<`K`\>(`subscribers`): `void`
|
||||
|
||||
Defined in: [src/api.ts:212](../src/api.ts#L212)
|
||||
Defined in: [src/api.ts:211](../src/api.ts#L211)
|
||||
|
||||
Subscribe multiple event handlers at once.
|
||||
|
||||
@@ -1479,7 +1479,7 @@ If the same function is subscribed to event.
|
||||
|
||||
> **on**\<`K`\>(`event`, `subscriber`): `void`
|
||||
|
||||
Defined in: [src/api.ts:220](../src/api.ts#L220)
|
||||
Defined in: [src/api.ts:219](../src/api.ts#L219)
|
||||
|
||||
Subscribe a handler to a specific event.
|
||||
|
||||
@@ -1517,7 +1517,7 @@ If the same function is subscribed to event.
|
||||
|
||||
> **onAny**(`receiver`): `void`
|
||||
|
||||
Defined in: [src/api.ts:243](../src/api.ts#L243)
|
||||
Defined in: [src/api.ts:242](../src/api.ts#L242)
|
||||
|
||||
Subscribe a handler to any event.
|
||||
|
||||
@@ -1543,7 +1543,7 @@ If the same function is subscribed to event.
|
||||
|
||||
> **once**\<`K`\>(`event`, `subscriber`): `void`
|
||||
|
||||
Defined in: [src/api.ts:254](../src/api.ts#L254)
|
||||
Defined in: [src/api.ts:253](../src/api.ts#L253)
|
||||
|
||||
Subscribe a handler to a specific event to be delivered one time.
|
||||
|
||||
@@ -1581,7 +1581,7 @@ If the same function is subscribed to event.
|
||||
|
||||
> **recvChatEvent**(`wait?`): `Promise`\<`ChatEvent` \| `undefined`\>
|
||||
|
||||
Defined in: [src/api.ts:353](../src/api.ts#L353)
|
||||
Defined in: [src/api.ts:352](../src/api.ts#L352)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -1599,7 +1599,7 @@ Defined in: [src/api.ts:353](../src/api.ts#L353)
|
||||
|
||||
> **sendChatCmd**(`cmd`): `Promise`\<`ChatResponse`\>
|
||||
|
||||
Defined in: [src/api.ts:349](../src/api.ts#L349)
|
||||
Defined in: [src/api.ts:348](../src/api.ts#L348)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -1617,7 +1617,7 @@ Defined in: [src/api.ts:349](../src/api.ts#L349)
|
||||
|
||||
> **startChat**(): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:124](../src/api.ts#L124)
|
||||
Defined in: [src/api.ts:123](../src/api.ts#L123)
|
||||
|
||||
Start chat controller. Must be called with the existing user profile.
|
||||
|
||||
@@ -1631,7 +1631,7 @@ Start chat controller. Must be called with the existing user profile.
|
||||
|
||||
> **stopChat**(): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:147](../src/api.ts#L147)
|
||||
Defined in: [src/api.ts:146](../src/api.ts#L146)
|
||||
|
||||
Stop chat controller.
|
||||
`close` calls it before closing the database.
|
||||
@@ -1649,7 +1649,7 @@ Usually doesn't need to be called in chat bots.
|
||||
|
||||
> **wait**\<`K`\>(`event`): `Promise`\<`ChatEvent` & `object`\>
|
||||
|
||||
Defined in: [src/api.ts:262](../src/api.ts#L262)
|
||||
Defined in: [src/api.ts:261](../src/api.ts#L261)
|
||||
|
||||
Waits for specific event, with an optional predicate.
|
||||
Returns `undefined` on timeout if specified.
|
||||
@@ -1674,7 +1674,7 @@ Returns `undefined` on timeout if specified.
|
||||
|
||||
> **wait**\<`K`\>(`event`, `predicate`): `Promise`\<`ChatEvent` & `object`\>
|
||||
|
||||
Defined in: [src/api.ts:263](../src/api.ts#L263)
|
||||
Defined in: [src/api.ts:262](../src/api.ts#L262)
|
||||
|
||||
Waits for specific event, with an optional predicate.
|
||||
Returns `undefined` on timeout if specified.
|
||||
@@ -1703,7 +1703,7 @@ Returns `undefined` on timeout if specified.
|
||||
|
||||
> **wait**\<`K`\>(`event`, `timeout`): `Promise`\<ChatEvent & \{ type: K; \} \| `undefined`\>
|
||||
|
||||
Defined in: [src/api.ts:264](../src/api.ts#L264)
|
||||
Defined in: [src/api.ts:263](../src/api.ts#L263)
|
||||
|
||||
Waits for specific event, with an optional predicate.
|
||||
Returns `undefined` on timeout if specified.
|
||||
@@ -1732,7 +1732,7 @@ Returns `undefined` on timeout if specified.
|
||||
|
||||
> **wait**\<`K`\>(`event`, `predicate`, `timeout`): `Promise`\<ChatEvent & \{ type: K; \} \| `undefined`\>
|
||||
|
||||
Defined in: [src/api.ts:265](../src/api.ts#L265)
|
||||
Defined in: [src/api.ts:264](../src/api.ts#L264)
|
||||
|
||||
Waits for specific event, with an optional predicate.
|
||||
Returns `undefined` on timeout if specified.
|
||||
@@ -1767,9 +1767,9 @@ Returns `undefined` on timeout if specified.
|
||||
|
||||
> `static` **init**(`db`, `confirm?`, `queueSize?`): `Promise`\<`ChatApi`\>
|
||||
|
||||
Defined in: [src/api.ts:111](../src/api.ts#L111)
|
||||
Defined in: [src/api.ts:109](../src/api.ts#L109)
|
||||
|
||||
Initializes the ChatApi.
|
||||
Initializes the ChatApi, loading libsimplex for `db.type` (downloaded on first use).
|
||||
|
||||
#### Parameters
|
||||
|
||||
|
||||
@@ -8,12 +8,10 @@
|
||||
|
||||
> **DbConfig** = \{ `encryptionKey?`: `string`; `filePrefix`: `string`; `type`: `"sqlite"`; \} \| \{ `connectionString`: `string`; `schemaPrefix?`: `string`; `type`: `"postgres"`; \}
|
||||
|
||||
Defined in: [src/api.ts:65](../src/api.ts#L65)
|
||||
Defined in: [src/api.ts:63](../src/api.ts#L63)
|
||||
|
||||
Database configuration. The native library is built against exactly one
|
||||
backend (see `simplex_backend` / `SIMPLEX_BACKEND` at install time); this
|
||||
type makes the caller state which one they are targeting so field names
|
||||
can't lie about their meaning.
|
||||
Database configuration. `type` selects the libsimplex backend loaded by
|
||||
`ChatApi.init`; one backend per process.
|
||||
|
||||
## Union Members
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Class: ChatAPIError
|
||||
|
||||
Defined in: [src/core.ts:95](../src/core.ts#L95)
|
||||
Defined in: [src/core.ts:116](../src/core.ts#L116)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:95](../src/core.ts#L95)
|
||||
|
||||
> **new ChatAPIError**(`message`, `chatError?`): `ChatAPIError`
|
||||
|
||||
Defined in: [src/core.ts:96](../src/core.ts#L96)
|
||||
Defined in: [src/core.ts:117](../src/core.ts#L117)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -44,7 +44,7 @@ Defined in: [src/core.ts:96](../src/core.ts#L96)
|
||||
|
||||
> **chatError**: `ChatError` \| `undefined` = `undefined`
|
||||
|
||||
Defined in: [src/core.ts:96](../src/core.ts#L96)
|
||||
Defined in: [src/core.ts:117](../src/core.ts#L117)
|
||||
|
||||
***
|
||||
|
||||
@@ -52,7 +52,7 @@ Defined in: [src/core.ts:96](../src/core.ts#L96)
|
||||
|
||||
> **message**: `string`
|
||||
|
||||
Defined in: [src/core.ts:96](../src/core.ts#L96)
|
||||
Defined in: [src/core.ts:117](../src/core.ts#L117)
|
||||
|
||||
#### Inherited from
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Class: ChatInitError
|
||||
|
||||
Defined in: [src/core.ts:119](../src/core.ts#L119)
|
||||
Defined in: [src/core.ts:140](../src/core.ts#L140)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:119](../src/core.ts#L119)
|
||||
|
||||
> **new ChatInitError**(`message`, `dbMigrationError`): `ChatInitError`
|
||||
|
||||
Defined in: [src/core.ts:120](../src/core.ts#L120)
|
||||
Defined in: [src/core.ts:141](../src/core.ts#L141)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -44,7 +44,7 @@ Defined in: [src/core.ts:120](../src/core.ts#L120)
|
||||
|
||||
> **dbMigrationError**: [`DBMigrationError`](core.TypeAlias.DBMigrationError.md)
|
||||
|
||||
Defined in: [src/core.ts:120](../src/core.ts#L120)
|
||||
Defined in: [src/core.ts:141](../src/core.ts#L141)
|
||||
|
||||
***
|
||||
|
||||
@@ -52,7 +52,7 @@ Defined in: [src/core.ts:120](../src/core.ts#L120)
|
||||
|
||||
> **message**: `string`
|
||||
|
||||
Defined in: [src/core.ts:120](../src/core.ts#L120)
|
||||
Defined in: [src/core.ts:141](../src/core.ts#L141)
|
||||
|
||||
#### Inherited from
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: ErrorMigration
|
||||
|
||||
Defined in: [src/core.ts:152](../src/core.ts#L152)
|
||||
Defined in: [src/core.ts:173](../src/core.ts#L173)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:152](../src/core.ts#L152)
|
||||
|
||||
> **dbFile**: `string`
|
||||
|
||||
Defined in: [src/core.ts:154](../src/core.ts#L154)
|
||||
Defined in: [src/core.ts:175](../src/core.ts#L175)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:154](../src/core.ts#L154)
|
||||
|
||||
> **migrationError**: [`MigrationError`](core.TypeAlias.MigrationError.md)
|
||||
|
||||
Defined in: [src/core.ts:155](../src/core.ts#L155)
|
||||
Defined in: [src/core.ts:176](../src/core.ts#L176)
|
||||
|
||||
***
|
||||
|
||||
@@ -34,7 +34,7 @@ Defined in: [src/core.ts:155](../src/core.ts#L155)
|
||||
|
||||
> **type**: `"errorMigration"`
|
||||
|
||||
Defined in: [src/core.ts:153](../src/core.ts#L153)
|
||||
Defined in: [src/core.ts:174](../src/core.ts#L174)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: ErrorNotADatabase
|
||||
|
||||
Defined in: [src/core.ts:147](../src/core.ts#L147)
|
||||
Defined in: [src/core.ts:168](../src/core.ts#L168)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:147](../src/core.ts#L147)
|
||||
|
||||
> **dbFile**: `string`
|
||||
|
||||
Defined in: [src/core.ts:149](../src/core.ts#L149)
|
||||
Defined in: [src/core.ts:170](../src/core.ts#L170)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:149](../src/core.ts#L149)
|
||||
|
||||
> **type**: `"errorNotADatabase"`
|
||||
|
||||
Defined in: [src/core.ts:148](../src/core.ts#L148)
|
||||
Defined in: [src/core.ts:169](../src/core.ts#L169)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: ErrorSQL
|
||||
|
||||
Defined in: [src/core.ts:158](../src/core.ts#L158)
|
||||
Defined in: [src/core.ts:179](../src/core.ts#L179)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:158](../src/core.ts#L158)
|
||||
|
||||
> **dbFile**: `string`
|
||||
|
||||
Defined in: [src/core.ts:160](../src/core.ts#L160)
|
||||
Defined in: [src/core.ts:181](../src/core.ts#L181)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:160](../src/core.ts#L160)
|
||||
|
||||
> **migrationSQLError**: `string`
|
||||
|
||||
Defined in: [src/core.ts:161](../src/core.ts#L161)
|
||||
Defined in: [src/core.ts:182](../src/core.ts#L182)
|
||||
|
||||
***
|
||||
|
||||
@@ -34,7 +34,7 @@ Defined in: [src/core.ts:161](../src/core.ts#L161)
|
||||
|
||||
> **type**: `"errorSQL"`
|
||||
|
||||
Defined in: [src/core.ts:159](../src/core.ts#L159)
|
||||
Defined in: [src/core.ts:180](../src/core.ts#L180)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: InvalidConfirmation
|
||||
|
||||
Defined in: [src/core.ts:139](../src/core.ts#L139)
|
||||
Defined in: [src/core.ts:160](../src/core.ts#L160)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:139](../src/core.ts#L139)
|
||||
|
||||
> **type**: `"invalidConfirmation"`
|
||||
|
||||
Defined in: [src/core.ts:140](../src/core.ts#L140)
|
||||
Defined in: [src/core.ts:161](../src/core.ts#L161)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: InvalidQueueSize
|
||||
|
||||
Defined in: [src/core.ts:143](../src/core.ts#L143)
|
||||
Defined in: [src/core.ts:164](../src/core.ts#L164)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:143](../src/core.ts#L143)
|
||||
|
||||
> **type**: `"invalidQueueSize"`
|
||||
|
||||
Defined in: [src/core.ts:144](../src/core.ts#L144)
|
||||
Defined in: [src/core.ts:165](../src/core.ts#L165)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **Tag** = `"invalidConfirmation"` \| `"invalidQueueSize"` \| `"errorNotADatabase"` \| `"errorMigration"` \| `"errorSQL"`
|
||||
|
||||
Defined in: [src/core.ts:133](../src/core.ts#L133)
|
||||
Defined in: [src/core.ts:154](../src/core.ts#L154)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Enumeration: MigrationConfirmation
|
||||
|
||||
Defined in: [src/core.ts:104](../src/core.ts#L104)
|
||||
Defined in: [src/core.ts:125](../src/core.ts#L125)
|
||||
|
||||
Migration confirmation mode
|
||||
|
||||
@@ -16,7 +16,7 @@ Migration confirmation mode
|
||||
|
||||
> **Console**: `"console"`
|
||||
|
||||
Defined in: [src/core.ts:107](../src/core.ts#L107)
|
||||
Defined in: [src/core.ts:128](../src/core.ts#L128)
|
||||
|
||||
***
|
||||
|
||||
@@ -24,7 +24,7 @@ Defined in: [src/core.ts:107](../src/core.ts#L107)
|
||||
|
||||
> **Error**: `"error"`
|
||||
|
||||
Defined in: [src/core.ts:108](../src/core.ts#L108)
|
||||
Defined in: [src/core.ts:129](../src/core.ts#L129)
|
||||
|
||||
***
|
||||
|
||||
@@ -32,7 +32,7 @@ Defined in: [src/core.ts:108](../src/core.ts#L108)
|
||||
|
||||
> **YesUp**: `"yesUp"`
|
||||
|
||||
Defined in: [src/core.ts:105](../src/core.ts#L105)
|
||||
Defined in: [src/core.ts:126](../src/core.ts#L126)
|
||||
|
||||
***
|
||||
|
||||
@@ -40,4 +40,4 @@ Defined in: [src/core.ts:105](../src/core.ts#L105)
|
||||
|
||||
> **YesUpDown**: `"yesUpDown"`
|
||||
|
||||
Defined in: [src/core.ts:106](../src/core.ts#L106)
|
||||
Defined in: [src/core.ts:127](../src/core.ts#L127)
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatCloseStore**(`ctrl`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/core.ts:20](../src/core.ts#L20)
|
||||
Defined in: [src/core.ts:41](../src/core.ts#L41)
|
||||
|
||||
Close chat store
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatDecryptFile**(`fromPath`, `__namedParameters`, `toPath`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/core.ts:76](../src/core.ts#L76)
|
||||
Defined in: [src/core.ts:97](../src/core.ts#L97)
|
||||
|
||||
Decrypt file
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatEncryptFile**(`ctrl`, `fromPath`, `toPath`): `Promise`\<[`CryptoArgs`](core.Interface.CryptoArgs.md)\>
|
||||
|
||||
Defined in: [src/core.ts:68](../src/core.ts#L68)
|
||||
Defined in: [src/core.ts:89](../src/core.ts#L89)
|
||||
|
||||
Encrypt file
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatMigrateInit**(`dbPath`, `dbKey`, `confirm`, `queueSize?`): `Promise`\<`bigint`\>
|
||||
|
||||
Defined in: [src/core.ts:8](../src/core.ts#L8)
|
||||
Defined in: [src/core.ts:29](../src/core.ts#L29)
|
||||
|
||||
Initialize chat controller
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatReadFile**(`path`, `__namedParameters`): `Promise`\<`Buffer`\<`ArrayBufferLike`\>\>
|
||||
|
||||
Defined in: [src/core.ts:61](../src/core.ts#L61)
|
||||
Defined in: [src/core.ts:82](../src/core.ts#L82)
|
||||
|
||||
Read buffer from encrypted file
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatRecvMsgWait**(`ctrl`, `wait`): `Promise`\<`ChatEvent` \| `undefined`\>
|
||||
|
||||
Defined in: [src/core.ts:40](../src/core.ts#L40)
|
||||
Defined in: [src/core.ts:61](../src/core.ts#L61)
|
||||
|
||||
Receive chat event
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatSendCmd**(`ctrl`, `cmd`): `Promise`\<`ChatResponse`\>
|
||||
|
||||
Defined in: [src/core.ts:28](../src/core.ts#L28)
|
||||
Defined in: [src/core.ts:49](../src/core.ts#L49)
|
||||
|
||||
Send chat command as string
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatWriteFile**(`ctrl`, `path`, `buffer`): `Promise`\<[`CryptoArgs`](core.Interface.CryptoArgs.md)\>
|
||||
|
||||
Defined in: [src/core.ts:53](../src/core.ts#L53)
|
||||
Defined in: [src/core.ts:74](../src/core.ts#L74)
|
||||
|
||||
Write buffer to encrypted file
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
[**simplex-chat**](README.md)
|
||||
|
||||
***
|
||||
|
||||
[simplex-chat](README.md) / [core](Namespace.core.md) / loadLibrary
|
||||
|
||||
# Function: loadLibrary()
|
||||
|
||||
> **loadLibrary**(`backend`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/core.ts:13](../src/core.ts#L13)
|
||||
|
||||
Resolve (downloading on first use) and load libsimplex for the backend.
|
||||
One libsimplex per process: the Haskell runtime is initialized once, and another backend is rejected.
|
||||
|
||||
## Parameters
|
||||
|
||||
### backend
|
||||
|
||||
[`Backend`](core.TypeAlias.Backend.md)
|
||||
|
||||
## Returns
|
||||
|
||||
`Promise`\<`void`\>
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: APIResult\<R\>
|
||||
|
||||
Defined in: [src/core.ts:90](../src/core.ts#L90)
|
||||
Defined in: [src/core.ts:111](../src/core.ts#L111)
|
||||
|
||||
## Type Parameters
|
||||
|
||||
@@ -20,7 +20,7 @@ Defined in: [src/core.ts:90](../src/core.ts#L90)
|
||||
|
||||
> `optional` **error?**: `ChatError`
|
||||
|
||||
Defined in: [src/core.ts:92](../src/core.ts#L92)
|
||||
Defined in: [src/core.ts:113](../src/core.ts#L113)
|
||||
|
||||
***
|
||||
|
||||
@@ -28,4 +28,4 @@ Defined in: [src/core.ts:92](../src/core.ts#L92)
|
||||
|
||||
> `optional` **result?**: `R`
|
||||
|
||||
Defined in: [src/core.ts:91](../src/core.ts#L91)
|
||||
Defined in: [src/core.ts:112](../src/core.ts#L112)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: CryptoArgs
|
||||
|
||||
Defined in: [src/core.ts:114](../src/core.ts#L114)
|
||||
Defined in: [src/core.ts:135](../src/core.ts#L135)
|
||||
|
||||
File encryption key and nonce
|
||||
|
||||
@@ -16,7 +16,7 @@ File encryption key and nonce
|
||||
|
||||
> **fileKey**: `string`
|
||||
|
||||
Defined in: [src/core.ts:115](../src/core.ts#L115)
|
||||
Defined in: [src/core.ts:136](../src/core.ts#L136)
|
||||
|
||||
***
|
||||
|
||||
@@ -24,4 +24,4 @@ Defined in: [src/core.ts:115](../src/core.ts#L115)
|
||||
|
||||
> **fileNonce**: `string`
|
||||
|
||||
Defined in: [src/core.ts:116](../src/core.ts#L116)
|
||||
Defined in: [src/core.ts:137](../src/core.ts#L137)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: UpMigration
|
||||
|
||||
Defined in: [src/core.ts:193](../src/core.ts#L193)
|
||||
Defined in: [src/core.ts:214](../src/core.ts#L214)
|
||||
|
||||
## Properties
|
||||
|
||||
@@ -14,7 +14,7 @@ Defined in: [src/core.ts:193](../src/core.ts#L193)
|
||||
|
||||
> **upName**: `string`
|
||||
|
||||
Defined in: [src/core.ts:194](../src/core.ts#L194)
|
||||
Defined in: [src/core.ts:215](../src/core.ts#L215)
|
||||
|
||||
***
|
||||
|
||||
@@ -22,4 +22,4 @@ Defined in: [src/core.ts:194](../src/core.ts#L194)
|
||||
|
||||
> **withDown**: `boolean`
|
||||
|
||||
Defined in: [src/core.ts:195](../src/core.ts#L195)
|
||||
Defined in: [src/core.ts:216](../src/core.ts#L216)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MTREDifferent
|
||||
|
||||
Defined in: [src/core.ts:214](../src/core.ts#L214)
|
||||
Defined in: [src/core.ts:235](../src/core.ts#L235)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:214](../src/core.ts#L214)
|
||||
|
||||
> **downMigrations**: `string`[]
|
||||
|
||||
Defined in: [src/core.ts:216](../src/core.ts#L216)
|
||||
Defined in: [src/core.ts:237](../src/core.ts#L237)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:216](../src/core.ts#L216)
|
||||
|
||||
> **type**: `"different"`
|
||||
|
||||
Defined in: [src/core.ts:215](../src/core.ts#L215)
|
||||
Defined in: [src/core.ts:236](../src/core.ts#L236)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MTRENoDown
|
||||
|
||||
Defined in: [src/core.ts:209](../src/core.ts#L209)
|
||||
Defined in: [src/core.ts:230](../src/core.ts#L230)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:209](../src/core.ts#L209)
|
||||
|
||||
> **dbMigrations**: `string`[]
|
||||
|
||||
Defined in: [src/core.ts:211](../src/core.ts#L211)
|
||||
Defined in: [src/core.ts:232](../src/core.ts#L232)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:211](../src/core.ts#L211)
|
||||
|
||||
> **type**: `"noDown"`
|
||||
|
||||
Defined in: [src/core.ts:210](../src/core.ts#L210)
|
||||
Defined in: [src/core.ts:231](../src/core.ts#L231)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **Tag** = `"noDown"` \| `"different"`
|
||||
|
||||
Defined in: [src/core.ts:203](../src/core.ts#L203)
|
||||
Defined in: [src/core.ts:224](../src/core.ts#L224)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MEDowngrade
|
||||
|
||||
Defined in: [src/core.ts:182](../src/core.ts#L182)
|
||||
Defined in: [src/core.ts:203](../src/core.ts#L203)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:182](../src/core.ts#L182)
|
||||
|
||||
> **downMigrations**: `string`[]
|
||||
|
||||
Defined in: [src/core.ts:184](../src/core.ts#L184)
|
||||
Defined in: [src/core.ts:205](../src/core.ts#L205)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:184](../src/core.ts#L184)
|
||||
|
||||
> **type**: `"downgrade"`
|
||||
|
||||
Defined in: [src/core.ts:183](../src/core.ts#L183)
|
||||
Defined in: [src/core.ts:204](../src/core.ts#L204)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MEUpgrade
|
||||
|
||||
Defined in: [src/core.ts:177](../src/core.ts#L177)
|
||||
Defined in: [src/core.ts:198](../src/core.ts#L198)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:177](../src/core.ts#L177)
|
||||
|
||||
> **type**: `"upgrade"`
|
||||
|
||||
Defined in: [src/core.ts:178](../src/core.ts#L178)
|
||||
Defined in: [src/core.ts:199](../src/core.ts#L199)
|
||||
|
||||
#### Overrides
|
||||
|
||||
@@ -30,4 +30,4 @@ Defined in: [src/core.ts:178](../src/core.ts#L178)
|
||||
|
||||
> **upMigrations**: [`UpMigration`](core.Interface.UpMigration.md)[]
|
||||
|
||||
Defined in: [src/core.ts:179](../src/core.ts#L179)
|
||||
Defined in: [src/core.ts:200](../src/core.ts#L200)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MigrationError
|
||||
|
||||
Defined in: [src/core.ts:187](../src/core.ts#L187)
|
||||
Defined in: [src/core.ts:208](../src/core.ts#L208)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:187](../src/core.ts#L187)
|
||||
|
||||
> **mtrError**: [`MTRError`](core.TypeAlias.MTRError.md)
|
||||
|
||||
Defined in: [src/core.ts:189](../src/core.ts#L189)
|
||||
Defined in: [src/core.ts:210](../src/core.ts#L210)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:189](../src/core.ts#L189)
|
||||
|
||||
> **type**: `"migrationError"`
|
||||
|
||||
Defined in: [src/core.ts:188](../src/core.ts#L188)
|
||||
Defined in: [src/core.ts:209](../src/core.ts#L209)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **Tag** = `"upgrade"` \| `"downgrade"` \| `"migrationError"`
|
||||
|
||||
Defined in: [src/core.ts:171](../src/core.ts#L171)
|
||||
Defined in: [src/core.ts:192](../src/core.ts#L192)
|
||||
@@ -0,0 +1,11 @@
|
||||
[**simplex-chat**](README.md)
|
||||
|
||||
***
|
||||
|
||||
[simplex-chat](README.md) / [core](Namespace.core.md) / Backend
|
||||
|
||||
# Type Alias: Backend
|
||||
|
||||
> **Backend** = `"sqlite"` \| `"postgres"`
|
||||
|
||||
Defined in: [src/core.ts:5](../src/core.ts#L5)
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **DBMigrationError** = [`InvalidConfirmation`](core.DBMigrationError.Interface.InvalidConfirmation.md) \| [`InvalidQueueSize`](core.DBMigrationError.Interface.InvalidQueueSize.md) \| [`ErrorNotADatabase`](core.DBMigrationError.Interface.ErrorNotADatabase.md) \| [`ErrorMigration`](core.DBMigrationError.Interface.ErrorMigration.md) \| [`ErrorSQL`](core.DBMigrationError.Interface.ErrorSQL.md)
|
||||
|
||||
Defined in: [src/core.ts:125](../src/core.ts#L125)
|
||||
Defined in: [src/core.ts:146](../src/core.ts#L146)
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **MTRError** = [`MTRENoDown`](core.MTRError.Interface.MTRENoDown.md) \| [`MTREDifferent`](core.MTRError.Interface.MTREDifferent.md)
|
||||
|
||||
Defined in: [src/core.ts:198](../src/core.ts#L198)
|
||||
Defined in: [src/core.ts:219](../src/core.ts#L219)
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **MigrationError** = [`MEUpgrade`](core.MigrationError.Interface.MEUpgrade.md) \| [`MEDowngrade`](core.MigrationError.Interface.MEDowngrade.md) \| [`MigrationError`](core.MigrationError.Interface.MigrationError.md)
|
||||
|
||||
Defined in: [src/core.ts:165](../src/core.ts#L165)
|
||||
Defined in: [src/core.ts:186](../src/core.ts#L186)
|
||||
@@ -16,10 +16,9 @@
|
||||
"examples"
|
||||
],
|
||||
"scripts": {
|
||||
"preinstall": "node src/download-libs.js",
|
||||
"install": "node-gyp configure; node-gyp rebuild --release",
|
||||
"install-tools": "npm install -g node-gyp",
|
||||
"configure": "node-gyp configure; mkdir libs 2> /dev/null | true",
|
||||
"configure": "node-gyp configure",
|
||||
"build": "node-gyp rebuild && tsc && cp ./src/simplex.* ./dist",
|
||||
"run": "node src/index.js",
|
||||
"build-run": "node-gyp build && node src/index.js",
|
||||
|
||||
@@ -57,10 +57,8 @@ interface EventSubscriber<K extends CEvt.Tag> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Database configuration. The native library is built against exactly one
|
||||
* backend (see `simplex_backend` / `SIMPLEX_BACKEND` at install time); this
|
||||
* type makes the caller state which one they are targeting so field names
|
||||
* can't lie about their meaning.
|
||||
* Database configuration. `type` selects the libsimplex backend loaded by
|
||||
* `ChatApi.init`; one backend per process.
|
||||
*/
|
||||
export type DbConfig =
|
||||
| {
|
||||
@@ -103,7 +101,7 @@ export class ChatApi {
|
||||
private constructor(protected ctrl_: bigint | undefined) {}
|
||||
|
||||
/**
|
||||
* Initializes the ChatApi.
|
||||
* Initializes the ChatApi, loading libsimplex for `db.type` (downloaded on first use).
|
||||
* @param {DbConfig} db - Database configuration (sqlite or postgres).
|
||||
* @param {core.MigrationConfirmation} [confirm=core.MigrationConfirmation.YesUp] - Migration confirmation mode.
|
||||
* @param {number} [queueSize] - Size of internal queues, the core default is used when omitted.
|
||||
@@ -114,6 +112,7 @@ export class ChatApi {
|
||||
queueSize?: number
|
||||
): Promise<ChatApi> {
|
||||
const [path, key] = dbConfigToMigrateArgs(db)
|
||||
await core.loadLibrary(db.type)
|
||||
const ctrl = await core.chatMigrateInit(path, key, confirm, queueSize)
|
||||
return new ChatApi(ctrl)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
import {ChatEvent, ChatResponse, T} from "@simplex-chat/types"
|
||||
import * as libs from "./libs"
|
||||
import * as simplex from "./simplex"
|
||||
|
||||
export type Backend = "sqlite" | "postgres"
|
||||
|
||||
let loading: {backend: Backend, promise: Promise<void>} | undefined
|
||||
|
||||
/**
|
||||
* Resolve (downloading on first use) and load libsimplex for the backend.
|
||||
* One libsimplex per process: the Haskell runtime is initialized once, and another backend is rejected.
|
||||
*/
|
||||
export function loadLibrary(backend: Backend): Promise<void> {
|
||||
if (loading) {
|
||||
if (loading.backend === backend) return loading.promise
|
||||
return Promise.reject(new Error(`libsimplex already loaded with backend=${loading.backend}; cannot switch to ${backend} in the same process`))
|
||||
}
|
||||
const promise = libs.resolveLibsDir(backend).then(dir => simplex.load(libs.libPath(dir)))
|
||||
loading = {backend, promise}
|
||||
// a failed download or load can be retried
|
||||
promise.catch(() => { loading = undefined })
|
||||
return promise
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize chat controller
|
||||
* @param {number} [queueSize] - Size of internal queues, the core default is used when omitted.
|
||||
|
||||
@@ -1,266 +0,0 @@
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const extract = require('extract-zip');
|
||||
|
||||
const GITHUB_REPO = 'simplex-chat/simplex-chat-libs';
|
||||
const RELEASE_TAG = 'v7.1.0-beta.4';
|
||||
const BACKEND = (process.env.SIMPLEX_BACKEND || process.env.npm_config_simplex_backend || 'sqlite').toLowerCase();
|
||||
// A locally built libsimplex, copied into libs/ rather than loaded in place:
|
||||
// the addon's RUNPATH is $ORIGIN/../../libs.
|
||||
const LIBS_DIR_OVERRIDE = process.env.SIMPLEX_LIBS_DIR || process.env.npm_config_simplex_libs_dir;
|
||||
const LIB_NAMES = ['libsimplex.so', 'libsimplex.dylib', 'libsimplex.dll'];
|
||||
|
||||
if (BACKEND !== 'sqlite' && BACKEND !== 'postgres') {
|
||||
console.error(`✗ Invalid SIMPLEX_BACKEND: "${BACKEND}". Must be "sqlite" or "postgres".`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (BACKEND === 'postgres' && (process.platform !== 'linux' || process.arch !== 'x64')) {
|
||||
console.error(`✗ SIMPLEX_BACKEND=postgres is only supported on Linux x86_64.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const ROOT_DIR = process.cwd(); // Root of the package being installed
|
||||
const LIBS_DIR = path.join(ROOT_DIR, 'libs')
|
||||
const INSTALLED_FILE = path.join(LIBS_DIR, 'installed.txt');
|
||||
|
||||
// Detect platform and architecture
|
||||
function getPlatformInfo() {
|
||||
const platform = process.platform;
|
||||
const arch = process.arch;
|
||||
|
||||
let platformName;
|
||||
let archName;
|
||||
|
||||
if (platform === 'linux') {
|
||||
platformName = 'linux';
|
||||
} else if (platform === 'darwin') {
|
||||
platformName = 'macos';
|
||||
} else if (platform === 'win32') {
|
||||
platformName = 'windows';
|
||||
} else {
|
||||
throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
|
||||
if (arch === 'x64') {
|
||||
archName = 'x86_64';
|
||||
} else if (arch === 'arm64') {
|
||||
archName = 'aarch64';
|
||||
} else {
|
||||
throw new Error(`Unsupported architecture: ${arch}`);
|
||||
}
|
||||
|
||||
return { platformName, archName };
|
||||
}
|
||||
|
||||
// Cleanup on libs version mismatch
|
||||
function cleanLibsDirectory() {
|
||||
if (fs.existsSync(LIBS_DIR)) {
|
||||
console.log('Cleaning old libraries...');
|
||||
fs.rmSync(LIBS_DIR, { recursive: true, force: true });
|
||||
fs.mkdirSync(LIBS_DIR, { recursive: true });
|
||||
console.log('✓ Old libraries removed');
|
||||
}
|
||||
}
|
||||
|
||||
// Check if libraries are already installed with the correct version
|
||||
function isAlreadyInstalled() {
|
||||
if (!fs.existsSync(INSTALLED_FILE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const installedVersion = fs.readFileSync(INSTALLED_FILE, 'utf-8').trim();
|
||||
const expectedVersion = `${RELEASE_TAG}:${BACKEND}`;
|
||||
if (installedVersion === expectedVersion) {
|
||||
console.log(`✓ Libraries version ${RELEASE_TAG}:${BACKEND} already installed`);
|
||||
return true;
|
||||
} else {
|
||||
console.log(`Version mismatch: installed ${installedVersion}, need ${expectedVersion}`);
|
||||
cleanLibsDirectory();
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`Could not read installed.txt: ${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// No version check: the files behind SIMPLEX_LIBS_DIR change on every rebuild.
|
||||
function installFromOverride() {
|
||||
if (!fs.existsSync(LIBS_DIR_OVERRIDE)) {
|
||||
throw new Error(`SIMPLEX_LIBS_DIR does not exist: ${LIBS_DIR_OVERRIDE}`);
|
||||
}
|
||||
const lib = LIB_NAMES.find((name) => fs.existsSync(path.join(LIBS_DIR_OVERRIDE, name)));
|
||||
if (!lib) {
|
||||
throw new Error(`No ${LIB_NAMES.join(' / ')} in SIMPLEX_LIBS_DIR: ${LIBS_DIR_OVERRIDE}`);
|
||||
}
|
||||
console.log(`Using libraries from SIMPLEX_LIBS_DIR: ${LIBS_DIR_OVERRIDE}`);
|
||||
cleanLibsDirectory();
|
||||
copyDirSync(LIBS_DIR_OVERRIDE, LIBS_DIR);
|
||||
// Not a release tag: a later install without the variable sees a mismatch
|
||||
// and replaces these files with the released ones.
|
||||
fs.writeFileSync(INSTALLED_FILE, `${LIBS_DIR_OVERRIDE}:${BACKEND}`, 'utf-8');
|
||||
console.log(`✓ Installed ${lib} and its runtime libraries from ${LIBS_DIR_OVERRIDE}`);
|
||||
}
|
||||
|
||||
async function install() {
|
||||
try {
|
||||
if (LIBS_DIR_OVERRIDE) {
|
||||
installFromOverride();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if already installed
|
||||
if (isAlreadyInstalled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { platformName, archName } = getPlatformInfo();
|
||||
const repoName = GITHUB_REPO.split('/')[1];
|
||||
const backendSuffix = BACKEND === 'postgres' ? '-postgres' : '';
|
||||
const zipFilename = `${repoName}-${platformName}-${archName}${backendSuffix}.zip`;
|
||||
const ZIP_URL = `https://github.com/${GITHUB_REPO}/releases/download/${RELEASE_TAG}/${zipFilename}`;
|
||||
const ZIP_PATH = path.join(ROOT_DIR, zipFilename);
|
||||
const TEMP_EXTRACT_DIR = path.join(ROOT_DIR, '.temp-extract');
|
||||
|
||||
console.log(`Detected: ${platformName} ${archName}`);
|
||||
console.log(`Backend: ${BACKEND}`);
|
||||
console.log(`Downloading: ${zipFilename}`);
|
||||
|
||||
// Create libs directory
|
||||
if (!fs.existsSync(LIBS_DIR)) {
|
||||
fs.mkdirSync(LIBS_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// Download zip with error handling
|
||||
await downloadFile(ZIP_URL, ZIP_PATH);
|
||||
|
||||
// Extract to temporary directory
|
||||
console.log('Extracting to temporary directory...');
|
||||
if (!fs.existsSync(TEMP_EXTRACT_DIR)) {
|
||||
fs.mkdirSync(TEMP_EXTRACT_DIR, { recursive: true });
|
||||
}
|
||||
await extract(ZIP_PATH, { dir: TEMP_EXTRACT_DIR });
|
||||
|
||||
// Move libs folder contents to final location
|
||||
console.log('Moving libraries to libs/...');
|
||||
const libsSourcePath = path.join(TEMP_EXTRACT_DIR, 'libs');
|
||||
|
||||
if (fs.existsSync(libsSourcePath)) {
|
||||
// Copy all files from libs folder to LIBS_DIR
|
||||
const files = fs.readdirSync(libsSourcePath);
|
||||
files.forEach(file => {
|
||||
const src = path.join(libsSourcePath, file);
|
||||
const dest = path.join(LIBS_DIR, file);
|
||||
|
||||
if (fs.statSync(src).isDirectory()) {
|
||||
copyDirSync(src, dest);
|
||||
} else {
|
||||
fs.copyFileSync(src, dest);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new Error('libs folder not found in zip archive');
|
||||
}
|
||||
|
||||
// Write installed.txt with version
|
||||
fs.writeFileSync(INSTALLED_FILE, `${RELEASE_TAG}:${BACKEND}`, 'utf-8');
|
||||
console.log(`✓ Wrote version ${RELEASE_TAG}:${BACKEND} to installed.txt`);
|
||||
|
||||
// Cleanup
|
||||
fs.rmSync(TEMP_EXTRACT_DIR, { recursive: true, force: true });
|
||||
fs.unlinkSync(ZIP_PATH);
|
||||
console.log('✓ Installation complete');
|
||||
} catch (err) {
|
||||
console.error('✗ Failed:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to recursively copy directories
|
||||
function copyDirSync(src, dest) {
|
||||
if (!fs.existsSync(dest)) {
|
||||
fs.mkdirSync(dest, { recursive: true });
|
||||
}
|
||||
const files = fs.readdirSync(src);
|
||||
files.forEach(file => {
|
||||
const srcFile = path.join(src, file);
|
||||
const destFile = path.join(dest, file);
|
||||
if (fs.statSync(srcFile).isDirectory()) {
|
||||
copyDirSync(srcFile, destFile);
|
||||
} else {
|
||||
fs.copyFileSync(srcFile, destFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function downloadFile(url, dest) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const file = fs.createWriteStream(dest);
|
||||
|
||||
https.get(url, { headers: { 'User-Agent': 'Node.js' } }, (response) => {
|
||||
// Handle redirects
|
||||
if (response.statusCode === 302 || response.statusCode === 301) {
|
||||
file.destroy();
|
||||
fs.unlink(dest, () => {});
|
||||
return downloadFile(response.headers.location, dest)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
}
|
||||
|
||||
// Handle 404
|
||||
if (response.statusCode === 404) {
|
||||
file.destroy();
|
||||
fs.unlink(dest, () => {});
|
||||
reject(new Error(
|
||||
`Release artifact not found (404). Check:\n` +
|
||||
` - Repository exists: ${url.split('/releases')[0]}\n` +
|
||||
` - Release tag exists: ${RELEASE_TAG}\n` +
|
||||
` - Artifact filename is correct`
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle 403
|
||||
if (response.statusCode === 403) {
|
||||
file.destroy();
|
||||
fs.unlink(dest, () => {});
|
||||
reject(new Error(
|
||||
`Access denied (403). The repository may be private.\n` +
|
||||
`Set GITHUB_TOKEN environment variable for private repos.`
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle other HTTP errors
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
file.destroy();
|
||||
fs.unlink(dest, () => {});
|
||||
reject(new Error(
|
||||
`HTTP ${response.statusCode}: Failed to download from ${url}`
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
response.pipe(file);
|
||||
|
||||
file.on('finish', () => {
|
||||
file.close();
|
||||
resolve();
|
||||
});
|
||||
|
||||
file.on('error', (err) => {
|
||||
fs.unlink(dest, () => {});
|
||||
reject(new Error(`File write error: ${err.message}`));
|
||||
});
|
||||
}).on('error', (err) => {
|
||||
file.destroy();
|
||||
fs.unlink(dest, () => {});
|
||||
reject(new Error(`Download error: ${err.message}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
install();
|
||||
@@ -1,5 +1,6 @@
|
||||
// These functions are defined in CPP add-on ../cpp/simplex.cc
|
||||
|
||||
export function load(libPath: string): void
|
||||
export function chat_migrate_init(dbPath: string, dbKey: string, confirm: string): Promise<[bigint, string]>
|
||||
export function chat_migrate_init_queue(dbPath: string, dbKey: string, confirm: string, queueSize: number): Promise<[bigint, string]>
|
||||
export function chat_close_store(ctrl: bigint): Promise<string>
|
||||
|
||||
@@ -5,6 +5,7 @@ import * as core from "../src/core"
|
||||
const user = {userId: 1} as T.User
|
||||
|
||||
async function chatWithResponse(response: object): Promise<api.ChatApi> {
|
||||
jest.spyOn(core, "loadLibrary").mockResolvedValue()
|
||||
jest.spyOn(core, "chatMigrateInit").mockResolvedValue(BigInt(1))
|
||||
jest.spyOn(core, "chatSendCmd").mockResolvedValue(response as ChatResponse)
|
||||
return api.ChatApi.init({type: "sqlite", filePrefix: "unused"})
|
||||
@@ -38,6 +39,7 @@ describe("documented success responses", () => {
|
||||
|
||||
describe("startChat lifecycle", () => {
|
||||
function chatWithResponses(...responses: object[]): Promise<api.ChatApi> {
|
||||
jest.spyOn(core, "loadLibrary").mockResolvedValue()
|
||||
jest.spyOn(core, "chatMigrateInit").mockResolvedValue(BigInt(1))
|
||||
jest.spyOn(core, "chatRecvMsgWait").mockImplementation(() => new Promise(resolve => setTimeout(() => resolve(undefined), 10)))
|
||||
const send = jest.spyOn(core, "chatSendCmd")
|
||||
@@ -100,3 +102,24 @@ describe("startChat lifecycle", () => {
|
||||
expect(core.chatRecvMsgWait).toHaveBeenCalledWith(BigInt(1), 500_000)
|
||||
})
|
||||
})
|
||||
|
||||
describe("ChatApi.init", () => {
|
||||
it("loads the library for the configured backend before opening the database", async () => {
|
||||
let loaded!: () => void
|
||||
const load = jest.spyOn(core, "loadLibrary").mockReturnValue(new Promise(resolve => { loaded = resolve }))
|
||||
const migrate = jest.spyOn(core, "chatMigrateInit").mockResolvedValue(BigInt(1))
|
||||
const init = api.ChatApi.init({type: "postgres", connectionString: "postgres://unused"})
|
||||
await new Promise(setImmediate)
|
||||
expect(load).toHaveBeenCalledWith("postgres")
|
||||
expect(migrate).not.toHaveBeenCalled()
|
||||
loaded()
|
||||
await init
|
||||
expect(migrate).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("rejects an invalid config before loading the library", async () => {
|
||||
const load = jest.spyOn(core, "loadLibrary").mockResolvedValue()
|
||||
await expect(api.ChatApi.init({type: "mysql"} as unknown as api.DbConfig)).rejects.toThrow('Invalid DbConfig: {"type":"mysql"}')
|
||||
expect(load).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -2,8 +2,16 @@ import {execFile, spawnSync} from "child_process";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import {core} from "../src/index";
|
||||
import * as libs from "../src/libs";
|
||||
import * as simplex from "../src/simplex";
|
||||
|
||||
describe("Core tests", () => {
|
||||
let libPath: string;
|
||||
// the first run downloads libsimplex
|
||||
beforeAll(async () => {
|
||||
libPath = libs.libPath(await libs.resolveLibsDir("sqlite"));
|
||||
await core.loadLibrary("sqlite");
|
||||
}, 300000);
|
||||
const tmpDir = "./tests/tmp";
|
||||
const dbPath = path.join(tmpDir, "simplex_v1");
|
||||
|
||||
@@ -191,10 +199,42 @@ describe("Core tests", () => {
|
||||
expect(await receives).toEqual([{event: undefined}, {error: "chat receiver stopped"}]);
|
||||
}, 10000);
|
||||
|
||||
it("should accept loading libsimplex again from the same path", () => {
|
||||
expect(() => simplex.load(libPath)).not.toThrow();
|
||||
});
|
||||
|
||||
it("should refuse to load libsimplex from another path", () => {
|
||||
expect(() => simplex.load(path.resolve("other", "libsimplex.so"))).toThrow(`libsimplex already loaded from ${libPath}`);
|
||||
});
|
||||
|
||||
it("should load libsimplex after a failed load and in several workers", () => {
|
||||
const addon = path.resolve(__dirname, "..", "build", "Release", "simplex.node");
|
||||
const childDbPath = path.resolve(tmpDir, "simplex_workers");
|
||||
const script = `
|
||||
const {Worker} = require("worker_threads");
|
||||
const simplex = require(${JSON.stringify(addon)});
|
||||
try { simplex.load(${JSON.stringify(addon)}) } catch (e) { console.log(e.message.endsWith("does not export hs_init_with_rtsopts")) }
|
||||
const load = ${JSON.stringify(`require(${JSON.stringify(addon)}).load(${JSON.stringify(libPath)})`)};
|
||||
const workers = Array.from({length: 4}, () => new Worker(load, {eval: true}));
|
||||
Promise.all(workers.map((w) => new Promise((resolve, reject) => w.on("exit", resolve).on("error", reject))))
|
||||
.then(async (codes) => {
|
||||
simplex.load(${JSON.stringify(libPath)});
|
||||
const [ctrl] = await simplex.chat_migrate_init(${JSON.stringify(childDbPath)}, "key", "yesUp");
|
||||
await simplex.chat_send_cmd(ctrl, "/_stop");
|
||||
console.log(JSON.stringify(codes), await simplex.chat_close_store(ctrl) === "");
|
||||
});
|
||||
`;
|
||||
const child = spawnSync(process.execPath, ["-e", script], {timeout: 30000, encoding: "utf8"});
|
||||
if (child.status !== 0 || child.signal !== null) console.log("child stderr:", child.stderr);
|
||||
expect({status: child.status, signal: child.signal, stdout: child.stdout.trim()})
|
||||
.toEqual({status: 0, signal: null, stdout: "true\n[0,0,0,0] true"});
|
||||
}, 35000);
|
||||
|
||||
it("should let the process exit while a receiver is idle", () => {
|
||||
const childDbPath = path.resolve(tmpDir, "simplex_child");
|
||||
const script = `
|
||||
const simplex = require("./build/Release/simplex.node");
|
||||
simplex.load(${JSON.stringify(libPath)});
|
||||
simplex.chat_migrate_init(${JSON.stringify(childDbPath)}, "key", "yesUp")
|
||||
.then(([ctrl]) => simplex.chat_recv_msg_wait(ctrl, 1))
|
||||
.then((res) => console.log("received " + JSON.stringify(res)));
|
||||
@@ -209,6 +249,7 @@ describe("Core tests", () => {
|
||||
const script = `
|
||||
const fs = require("fs"), path = require("path");
|
||||
const simplex = require("./build/Release/simplex.node");
|
||||
simplex.load(${JSON.stringify(libPath)});
|
||||
(async () => {
|
||||
for (let i = 0; i < 40; i++) {
|
||||
const dir = fs.mkdtempSync(path.join(${JSON.stringify(path.resolve(tmpDir))}, "close-"));
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import {spawnSync} from "child_process"
|
||||
import * as path from "path"
|
||||
|
||||
jest.mock("../src/simplex", () => ({load: jest.fn()}))
|
||||
jest.mock("../src/libs", () => ({libPath: jest.requireActual("../src/libs").libPath, resolveLibsDir: jest.fn()}))
|
||||
|
||||
const PACKAGE_DIR = path.join(__dirname, "..")
|
||||
const ADDON = path.join(PACKAGE_DIR, "build", "Release", "simplex.node")
|
||||
const CHILD_TIMEOUT_MS = 10000
|
||||
const LIBS_DIR = path.resolve("libs")
|
||||
const SWITCH_ERROR = "libsimplex already loaded with backend=sqlite; cannot switch to postgres in the same process"
|
||||
const {libPath} = jest.requireActual<typeof import("../src/libs")>("../src/libs")
|
||||
|
||||
function runAddon(script: string): string {
|
||||
const r = spawnSync(process.execPath, ["-e", `const simplex = require(${JSON.stringify(ADDON)});\n${script}`], {encoding: "utf8", timeout: CHILD_TIMEOUT_MS})
|
||||
return `${r.stdout}${r.stderr}status=${r.status} signal=${r.signal}`
|
||||
}
|
||||
|
||||
function freshCore(): {core: typeof import("../src/core"), resolveLibsDir: jest.Mock, load: jest.Mock} {
|
||||
let modules!: ReturnType<typeof freshCore>
|
||||
jest.isolateModules(() => {
|
||||
modules = {
|
||||
core: require("../src/core"),
|
||||
resolveLibsDir: require("../src/libs").resolveLibsDir,
|
||||
load: require("../src/simplex").load,
|
||||
}
|
||||
})
|
||||
return modules
|
||||
}
|
||||
|
||||
describe("native load", () => {
|
||||
it("requires load before every FFI call", () => {
|
||||
const out = runAddon(`
|
||||
for (const name of Object.keys(simplex).filter(n => n !== "load").sort()) {
|
||||
try { simplex[name](); console.log(name + ": no error") } catch (e) { console.log(name + ": " + e.message) }
|
||||
}`)
|
||||
const bindings = ["chat_close_store", "chat_decrypt_file", "chat_encrypt_file", "chat_migrate_init", "chat_migrate_init_queue",
|
||||
"chat_read_file", "chat_recv_msg_wait", "chat_send_cmd", "chat_write_file"]
|
||||
const expected = bindings.map(name => `${name}: libsimplex is not loaded, call core.loadLibrary(backend) first\n`).join("")
|
||||
expect(out).toBe(`${expected}status=0 signal=null`)
|
||||
})
|
||||
|
||||
it("reports the path of a library that cannot be opened", () => {
|
||||
const out = runAddon(`try { simplex.load("/nonexistent/libsimplex.so") } catch (e) { console.log(e.message) }`)
|
||||
expect(out).toContain("cannot load /nonexistent/libsimplex.so")
|
||||
})
|
||||
|
||||
it("rejects a non-string library path", () => {
|
||||
const out = runAddon(`try { simplex.load(1) } catch (e) { console.log(e.name + ": " + e.message) }`)
|
||||
expect(out).toContain("TypeError: Expected string (libPath)")
|
||||
})
|
||||
|
||||
it("rejects a library without the chat exports", () => {
|
||||
const out = runAddon(`try { simplex.load(${JSON.stringify(ADDON)}) } catch (e) { console.log(e.message) }`)
|
||||
expect(out).toContain(`${ADDON} does not export hs_init_with_rtsopts`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("loadLibrary", () => {
|
||||
it("shares one load between concurrent calls", async () => {
|
||||
const {core, resolveLibsDir, load} = freshCore()
|
||||
resolveLibsDir.mockResolvedValue(LIBS_DIR)
|
||||
const first = core.loadLibrary("sqlite")
|
||||
const second = core.loadLibrary("sqlite")
|
||||
expect(second).toBe(first)
|
||||
await Promise.all([first, second])
|
||||
expect(resolveLibsDir).toHaveBeenCalledTimes(1)
|
||||
expect(load.mock.calls).toEqual([[libPath(LIBS_DIR)]])
|
||||
})
|
||||
|
||||
it("refuses to switch backend while the first load is in progress", async () => {
|
||||
const {core, resolveLibsDir} = freshCore()
|
||||
resolveLibsDir.mockResolvedValue(LIBS_DIR)
|
||||
const first = core.loadLibrary("sqlite")
|
||||
await expect(core.loadLibrary("postgres")).rejects.toThrow(SWITCH_ERROR)
|
||||
await first
|
||||
})
|
||||
|
||||
it("keeps a completed load for the process", async () => {
|
||||
const {core, resolveLibsDir, load} = freshCore()
|
||||
resolveLibsDir.mockResolvedValue(LIBS_DIR)
|
||||
await core.loadLibrary("sqlite")
|
||||
await core.loadLibrary("sqlite")
|
||||
await expect(core.loadLibrary("postgres")).rejects.toThrow(SWITCH_ERROR)
|
||||
expect(resolveLibsDir).toHaveBeenCalledTimes(1)
|
||||
expect(load).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("retries after a failed load", async () => {
|
||||
const {core, resolveLibsDir, load} = freshCore()
|
||||
resolveLibsDir.mockRejectedValueOnce(new Error("HTTP 503")).mockResolvedValue(LIBS_DIR)
|
||||
await expect(core.loadLibrary("sqlite")).rejects.toThrow("HTTP 503")
|
||||
await expect(core.loadLibrary("postgres")).resolves.toBeUndefined()
|
||||
expect(resolveLibsDir.mock.calls).toEqual([["sqlite"], ["postgres"]])
|
||||
expect(load).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user