simplex-chat 7.0.2 → 7.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/binding.gyp +2 -0
- package/cpp/simplex.cc +265 -29
- package/cpp/simplex.h +2 -0
- package/dist/api.d.ts +3 -64
- package/dist/api.js +8 -106
- package/dist/api.js.map +1 -1
- package/dist/bot.d.ts +4 -2
- package/dist/bot.js +1 -1
- package/dist/bot.js.map +1 -1
- package/dist/util.js +1 -1
- package/dist/util.js.map +1 -1
- package/docs/Namespace.bot.md +1 -0
- package/docs/api.Class.ChatApi.md +122 -78
- package/docs/bot.Function.run.md +1 -1
- package/docs/bot.Function.subscribeChatItems.md +27 -0
- package/docs/bot.Interface.BotConfig.md +7 -7
- package/docs/bot.Interface.BotOptions.md +10 -10
- package/docs/bot.TypeAlias.BotDbOpts.md +4 -0
- package/docs/core.Class.ChatAPIError.md +4 -4
- package/docs/core.Class.ChatInitError.md +4 -4
- package/docs/core.DBMigrationError.Interface.ErrorMigration.md +4 -4
- package/docs/core.DBMigrationError.Interface.ErrorNotADatabase.md +3 -3
- package/docs/core.DBMigrationError.Interface.ErrorSQL.md +4 -4
- package/docs/core.DBMigrationError.Interface.InvalidConfirmation.md +2 -2
- package/docs/core.DBMigrationError.Interface.InvalidQueueSize.md +25 -0
- package/docs/core.DBMigrationError.TypeAlias.Tag.md +2 -2
- package/docs/core.Enumeration.MigrationConfirmation.md +5 -5
- package/docs/core.Function.chatCloseStore.md +1 -1
- package/docs/core.Function.chatDecryptFile.md +1 -1
- package/docs/core.Function.chatEncryptFile.md +1 -1
- package/docs/core.Function.chatMigrateInit.md +8 -2
- package/docs/core.Function.chatReadFile.md +3 -3
- package/docs/core.Function.chatRecvMsgWait.md +1 -1
- package/docs/core.Function.chatSendCmd.md +1 -1
- package/docs/core.Function.chatWriteFile.md +2 -2
- package/docs/core.Interface.APIResult.md +3 -3
- package/docs/core.Interface.CryptoArgs.md +3 -3
- package/docs/core.Interface.UpMigration.md +3 -3
- package/docs/core.MTRError.Interface.MTREDifferent.md +3 -3
- package/docs/core.MTRError.Interface.MTRENoDown.md +10 -10
- package/docs/core.MTRError.TypeAlias.Tag.md +1 -1
- package/docs/core.MigrationError.Interface.MEDowngrade.md +3 -3
- package/docs/core.MigrationError.Interface.MEUpgrade.md +4 -4
- package/docs/core.MigrationError.Interface.MigrationError.md +3 -3
- package/docs/core.MigrationError.TypeAlias.Tag.md +1 -1
- package/docs/core.Namespace.DBMigrationError.md +1 -0
- package/docs/core.TypeAlias.DBMigrationError.md +2 -2
- package/docs/core.TypeAlias.MTRError.md +1 -1
- package/docs/core.TypeAlias.MigrationError.md +1 -1
- package/package.json +2 -2
- package/src/api.ts +40 -22
- package/src/bot.ts +17 -30
- package/src/core.ts +15 -7
- package/src/download-libs.js +28 -1
- package/src/simplex.d.ts +3 -2
- package/src/util.ts +1 -1
package/binding.gyp
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
],
|
|
12
12
|
"cflags!": [ "-fno-exceptions" ],
|
|
13
13
|
"cflags_cc!": [ "-fno-exceptions" ],
|
|
14
|
+
"xcode_settings": { "GCC_ENABLE_CPP_EXCEPTIONS": "YES" },
|
|
15
|
+
"msvs_settings": { "VCCLCompilerTool": { "ExceptionHandling": 1 } },
|
|
14
16
|
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
|
|
15
17
|
"conditions": [
|
|
16
18
|
["OS=='mac'", {
|
package/cpp/simplex.cc
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
#include <string>
|
|
4
4
|
#include <functional>
|
|
5
5
|
#include <cstdlib>
|
|
6
|
+
#include <climits>
|
|
7
|
+
#include <memory>
|
|
8
|
+
#include <thread>
|
|
9
|
+
#include <system_error>
|
|
10
|
+
#include <mutex>
|
|
11
|
+
#include <condition_variable>
|
|
12
|
+
#include <deque>
|
|
13
|
+
#include <unordered_map>
|
|
6
14
|
#include "simplex.h"
|
|
7
15
|
|
|
8
16
|
namespace simplex {
|
|
@@ -81,6 +89,11 @@ class ResultAsyncWorker : public AsyncWorker {
|
|
|
81
89
|
return ctrl_;
|
|
82
90
|
}
|
|
83
91
|
|
|
92
|
+
// the worker thread reads this object's memory until the worker completes
|
|
93
|
+
void KeepAlive(Object obj) {
|
|
94
|
+
keep_alive_ = Persistent(obj);
|
|
95
|
+
}
|
|
96
|
+
|
|
84
97
|
protected:
|
|
85
98
|
std::string result_;
|
|
86
99
|
uintptr_t ctrl_ = 0;
|
|
@@ -88,6 +101,7 @@ class ResultAsyncWorker : public AsyncWorker {
|
|
|
88
101
|
private:
|
|
89
102
|
ExecuteFn execute_fn_;
|
|
90
103
|
ResultProcessor result_processor_;
|
|
104
|
+
ObjectReference keep_alive_;
|
|
91
105
|
};
|
|
92
106
|
|
|
93
107
|
class BinaryAsyncWorker : public AsyncWorker {
|
|
@@ -97,21 +111,25 @@ class BinaryAsyncWorker : public AsyncWorker {
|
|
|
97
111
|
BinaryAsyncWorker(Function& callback, ExecuteFn execute_fn)
|
|
98
112
|
: AsyncWorker(callback), execute_fn_(std::move(execute_fn)) {}
|
|
99
113
|
|
|
114
|
+
~BinaryAsyncWorker() {
|
|
115
|
+
free(original_buf);
|
|
116
|
+
}
|
|
117
|
+
|
|
100
118
|
void Execute() override {
|
|
101
119
|
execute_fn_(this);
|
|
102
120
|
}
|
|
103
121
|
|
|
104
122
|
void OnOK() override {
|
|
105
123
|
HandleScope scope(Env());
|
|
106
|
-
|
|
107
|
-
|
|
124
|
+
char* buf = original_buf;
|
|
125
|
+
original_buf = nullptr;
|
|
126
|
+
if (binary_len == 0) {
|
|
127
|
+
free(buf);
|
|
128
|
+
Callback().Call({Env().Null(), Buffer<char>::New(Env(), 0)});
|
|
108
129
|
return;
|
|
109
130
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
free(orig);
|
|
113
|
-
};
|
|
114
|
-
Napi::Buffer<char> buffer = Napi::Buffer<char>::New(Env(), data_ptr, binary_len, finalizer, original_buf);
|
|
131
|
+
// Copies when the runtime forbids external buffers (Electron); the finalizer then runs immediately.
|
|
132
|
+
Buffer<char> buffer = Buffer<char>::NewOrCopy(Env(), buf + 5, binary_len, [](Napi::Env, char*, char* orig) { free(orig); }, buf);
|
|
115
133
|
Callback().Call({Env().Null(), buffer});
|
|
116
134
|
}
|
|
117
135
|
|
|
@@ -176,6 +194,159 @@ Napi::Promise CreatePromiseAndCallback(Env env, Function& cb_out) {
|
|
|
176
194
|
return deferred.Promise();
|
|
177
195
|
}
|
|
178
196
|
|
|
197
|
+
const char* const RECEIVER_STOPPED = "chat receiver stopped";
|
|
198
|
+
|
|
199
|
+
struct RecvRequest {
|
|
200
|
+
int wait = 0;
|
|
201
|
+
std::shared_ptr<Promise::Deferred> deferred;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// Holds the event loop open only while receives are pending; used only on the JS main thread.
|
|
205
|
+
class PendingReceives {
|
|
206
|
+
public:
|
|
207
|
+
explicit PendingReceives(ThreadSafeFunction tsfn) : tsfn_(tsfn) {}
|
|
208
|
+
|
|
209
|
+
const ThreadSafeFunction& Tsfn() const {
|
|
210
|
+
return tsfn_;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
void Add(Napi::Env env) {
|
|
214
|
+
if (count_++ == 0) tsfn_.Ref(env);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
void Remove(Napi::Env env) {
|
|
218
|
+
if (--count_ == 0) tsfn_.Unref(env);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private:
|
|
222
|
+
ThreadSafeFunction tsfn_;
|
|
223
|
+
size_t count_ = 0;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// A blocking receive would hold a libuv pool thread for up to `wait`, stalling fs, dns and crypto.
|
|
227
|
+
class Receiver {
|
|
228
|
+
public:
|
|
229
|
+
// Returns nullptr with a pending JS exception if the TSFN cannot be created, throws std::system_error if the thread cannot start.
|
|
230
|
+
static std::shared_ptr<Receiver> Start(Napi::Env env, chat_ctrl ctrl) {
|
|
231
|
+
ThreadSafeFunction tsfn = ThreadSafeFunction::New(env, Function::New(env, [](const CallbackInfo&) {}), "chat_recv_msg_wait", 0, 1);
|
|
232
|
+
if (env.IsExceptionPending()) {
|
|
233
|
+
return nullptr;
|
|
234
|
+
}
|
|
235
|
+
auto receiver = std::make_shared<Receiver>(ctrl, tsfn);
|
|
236
|
+
try {
|
|
237
|
+
receiver->thread_ = std::thread(&Receiver::Run, receiver.get());
|
|
238
|
+
} catch (const std::system_error&) {
|
|
239
|
+
tsfn.Release();
|
|
240
|
+
throw;
|
|
241
|
+
}
|
|
242
|
+
return receiver;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
Receiver(chat_ctrl ctrl, ThreadSafeFunction tsfn) : ctrl_(ctrl), pending_(std::make_shared<PendingReceives>(tsfn)) {}
|
|
246
|
+
|
|
247
|
+
Receiver(const Receiver&) = delete;
|
|
248
|
+
Receiver& operator=(const Receiver&) = delete;
|
|
249
|
+
|
|
250
|
+
~Receiver() {
|
|
251
|
+
Stop();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
void Enqueue(Napi::Env env, RecvRequest request) {
|
|
255
|
+
pending_->Add(env);
|
|
256
|
+
{
|
|
257
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
258
|
+
queue_.push_back(std::move(request));
|
|
259
|
+
}
|
|
260
|
+
cv_.notify_one();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
void RequestStop() {
|
|
264
|
+
{
|
|
265
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
266
|
+
stop_ = true;
|
|
267
|
+
}
|
|
268
|
+
cv_.notify_one();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Waits for the receive in progress, so it must not run on the JS main thread outside env teardown.
|
|
272
|
+
void Stop() {
|
|
273
|
+
RequestStop();
|
|
274
|
+
if (thread_.joinable()) {
|
|
275
|
+
thread_.join();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private:
|
|
280
|
+
void Run() {
|
|
281
|
+
for (;;) {
|
|
282
|
+
RecvRequest request;
|
|
283
|
+
{
|
|
284
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
285
|
+
cv_.wait(lock, [this] { return stop_ || !queue_.empty(); });
|
|
286
|
+
if (stop_) break;
|
|
287
|
+
request = std::move(queue_.front());
|
|
288
|
+
queue_.pop_front();
|
|
289
|
+
}
|
|
290
|
+
char* c_res = chat_recv_msg_wait(ctrl_, request.wait);
|
|
291
|
+
napi_status status = Settle(request.deferred, [c_res](Napi::Env env, Promise::Deferred& deferred) {
|
|
292
|
+
if (c_res == nullptr) {
|
|
293
|
+
deferred.Reject(Error::New(env, "chat_recv_msg_wait failed").Value());
|
|
294
|
+
} else {
|
|
295
|
+
deferred.Resolve(String::New(env, c_res));
|
|
296
|
+
free(c_res);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
if (status != napi_ok) {
|
|
300
|
+
free(c_res);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
std::deque<RecvRequest> unserved;
|
|
304
|
+
{
|
|
305
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
306
|
+
unserved.swap(queue_);
|
|
307
|
+
}
|
|
308
|
+
for (RecvRequest& request : unserved) {
|
|
309
|
+
Settle(request.deferred, [](Napi::Env env, Promise::Deferred& deferred) {
|
|
310
|
+
deferred.Reject(Error::New(env, RECEIVER_STOPPED).Value());
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
pending_->Tsfn().Release();
|
|
314
|
+
// Each OS thread that enters Haskell keeps an RTS task record until it calls hs_thread_done.
|
|
315
|
+
hs_thread_done();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
template <typename SettleFn>
|
|
319
|
+
napi_status Settle(std::shared_ptr<Promise::Deferred> deferred, SettleFn settle) {
|
|
320
|
+
// The callback may run after this Receiver is destroyed, so it owns the pending count.
|
|
321
|
+
std::shared_ptr<PendingReceives> pending = pending_;
|
|
322
|
+
return pending->Tsfn().BlockingCall([pending, deferred, settle](Napi::Env env, Function) {
|
|
323
|
+
settle(env, *deferred);
|
|
324
|
+
pending->Remove(env);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const chat_ctrl ctrl_;
|
|
329
|
+
const std::shared_ptr<PendingReceives> pending_;
|
|
330
|
+
std::mutex mutex_;
|
|
331
|
+
std::condition_variable cv_;
|
|
332
|
+
std::deque<RecvRequest> queue_;
|
|
333
|
+
bool stop_ = false;
|
|
334
|
+
std::thread thread_;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// Keyed by chat_ctrl, accessed only on the JS main thread.
|
|
338
|
+
using Receivers = std::unordered_map<uintptr_t, std::shared_ptr<Receiver>>;
|
|
339
|
+
|
|
340
|
+
std::shared_ptr<Receiver> TakeReceiver(Receivers& receivers, chat_ctrl ctrl) {
|
|
341
|
+
auto it = receivers.find(reinterpret_cast<uintptr_t>(ctrl));
|
|
342
|
+
if (it == receivers.end()) {
|
|
343
|
+
return nullptr;
|
|
344
|
+
}
|
|
345
|
+
std::shared_ptr<Receiver> receiver = std::move(it->second);
|
|
346
|
+
receivers.erase(it);
|
|
347
|
+
return receiver;
|
|
348
|
+
}
|
|
349
|
+
|
|
179
350
|
// Common result processors
|
|
180
351
|
ResultAsyncWorker::ResultProcessor MigrateResultProcessor() {
|
|
181
352
|
return [](ResultAsyncWorker* worker, Napi::Env env) {
|
|
@@ -215,6 +386,39 @@ Value ChatMigrateInit(const CallbackInfo& args) {
|
|
|
215
386
|
return promise;
|
|
216
387
|
}
|
|
217
388
|
|
|
389
|
+
Value ChatMigrateInitQueue(const CallbackInfo& args) {
|
|
390
|
+
Env env = args.Env();
|
|
391
|
+
if (args.Length() < 4 || !args[0].IsString() || !args[1].IsString() || !args[2].IsString() || !args[3].IsNumber()) {
|
|
392
|
+
TypeError::New(env, "Expected three string arguments and number").ThrowAsJavaScriptException();
|
|
393
|
+
return env.Undefined();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
std::string path = args[0].As<String>().Utf8Value();
|
|
397
|
+
std::string key = args[1].As<String>().Utf8Value();
|
|
398
|
+
std::string confirm = args[2].As<String>().Utf8Value();
|
|
399
|
+
Number queue_size_arg = args[3].As<Number>();
|
|
400
|
+
int queue_size = queue_size_arg.Int32Value();
|
|
401
|
+
if (static_cast<double>(queue_size) != queue_size_arg.DoubleValue()) {
|
|
402
|
+
RangeError::New(env, "Expected 32-bit integer queue size").ThrowAsJavaScriptException();
|
|
403
|
+
return env.Undefined();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
Function cb;
|
|
407
|
+
Promise promise = CreatePromiseAndCallback(env, cb);
|
|
408
|
+
|
|
409
|
+
auto execute_fn = [path, key, confirm, queue_size](ResultAsyncWorker* worker) {
|
|
410
|
+
chat_ctrl ctrl = nullptr;
|
|
411
|
+
char* c_res = chat_migrate_init_queue(path.c_str(), key.c_str(), confirm.c_str(), queue_size, &ctrl);
|
|
412
|
+
worker->SetCtrl(reinterpret_cast<uintptr_t>(ctrl));
|
|
413
|
+
HandleCResult(worker, c_res, "chat_migrate_init_queue");
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn), MigrateResultProcessor());
|
|
417
|
+
worker->Queue();
|
|
418
|
+
|
|
419
|
+
return promise;
|
|
420
|
+
}
|
|
421
|
+
|
|
218
422
|
Value ChatCloseStore(const CallbackInfo& args) {
|
|
219
423
|
Env env = args.Env();
|
|
220
424
|
if (args.Length() < 1 || !args[0].IsBigInt()) {
|
|
@@ -223,11 +427,15 @@ Value ChatCloseStore(const CallbackInfo& args) {
|
|
|
223
427
|
}
|
|
224
428
|
|
|
225
429
|
chat_ctrl ctrl = FromChatCtrlBigInt(args[0]);
|
|
430
|
+
std::shared_ptr<Receiver> receiver = TakeReceiver(*static_cast<Receivers*>(args.Data()), ctrl);
|
|
226
431
|
|
|
227
432
|
Function cb;
|
|
228
433
|
Promise promise = CreatePromiseAndCallback(env, cb);
|
|
229
434
|
|
|
230
|
-
auto execute_fn = [ctrl](ResultAsyncWorker* worker) {
|
|
435
|
+
auto execute_fn = [ctrl, receiver](ResultAsyncWorker* worker) {
|
|
436
|
+
if (receiver) {
|
|
437
|
+
receiver->Stop();
|
|
438
|
+
}
|
|
231
439
|
char* c_res = chat_close_store(ctrl);
|
|
232
440
|
HandleCResult(worker, c_res, "chat_close_store");
|
|
233
441
|
};
|
|
@@ -271,44 +479,63 @@ Value ChatRecvMsgWait(const CallbackInfo& args) {
|
|
|
271
479
|
|
|
272
480
|
chat_ctrl ctrl = FromChatCtrlBigInt(args[0]);
|
|
273
481
|
int wait = static_cast<int>(args[1].As<Number>().Int32Value());
|
|
482
|
+
Receivers& receivers = *static_cast<Receivers*>(args.Data());
|
|
483
|
+
|
|
484
|
+
auto deferred = std::make_shared<Promise::Deferred>(Promise::Deferred::New(env));
|
|
485
|
+
auto it = receivers.find(reinterpret_cast<uintptr_t>(ctrl));
|
|
486
|
+
if (it == receivers.end()) {
|
|
487
|
+
std::shared_ptr<Receiver> receiver;
|
|
488
|
+
try {
|
|
489
|
+
receiver = Receiver::Start(env, ctrl);
|
|
490
|
+
} catch (const std::system_error& e) {
|
|
491
|
+
deferred->Reject(Error::New(env, e.what()).Value());
|
|
492
|
+
return deferred->Promise();
|
|
493
|
+
}
|
|
494
|
+
if (!receiver) {
|
|
495
|
+
return env.Undefined();
|
|
496
|
+
}
|
|
497
|
+
it = receivers.emplace(reinterpret_cast<uintptr_t>(ctrl), std::move(receiver)).first;
|
|
498
|
+
}
|
|
499
|
+
it->second->Enqueue(env, {wait, deferred});
|
|
274
500
|
|
|
275
|
-
|
|
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;
|
|
501
|
+
return deferred->Promise();
|
|
287
502
|
}
|
|
288
503
|
|
|
289
504
|
Value ChatWriteFile(const CallbackInfo& args) {
|
|
290
505
|
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();
|
|
506
|
+
if (args.Length() < 3 || !args[0].IsBigInt() || !args[1].IsString() || !(args[2].IsArrayBuffer() || args[2].IsTypedArray())) {
|
|
507
|
+
TypeError::New(env, "Expected bigint (ctrl), string (path), ArrayBuffer or Uint8Array").ThrowAsJavaScriptException();
|
|
293
508
|
return env.Undefined();
|
|
294
509
|
}
|
|
295
510
|
|
|
296
511
|
chat_ctrl ctrl = FromChatCtrlBigInt(args[0]);
|
|
297
512
|
std::string path = args[1].As<String>().Utf8Value();
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
513
|
+
char* data;
|
|
514
|
+
size_t len;
|
|
515
|
+
if (args[2].IsArrayBuffer()) {
|
|
516
|
+
ArrayBuffer ab = args[2].As<ArrayBuffer>();
|
|
517
|
+
data = static_cast<char*>(ab.Data());
|
|
518
|
+
len = ab.ByteLength();
|
|
519
|
+
} else {
|
|
520
|
+
TypedArray view = args[2].As<TypedArray>();
|
|
521
|
+
data = static_cast<char*>(view.ArrayBuffer().Data()) + view.ByteOffset();
|
|
522
|
+
len = view.ByteLength();
|
|
523
|
+
}
|
|
524
|
+
if (len > static_cast<size_t>(INT_MAX)) {
|
|
525
|
+
RangeError::New(env, "Buffer is too large").ThrowAsJavaScriptException();
|
|
526
|
+
return env.Undefined();
|
|
527
|
+
}
|
|
301
528
|
|
|
302
529
|
Function cb;
|
|
303
530
|
Promise promise = CreatePromiseAndCallback(env, cb);
|
|
304
531
|
|
|
305
|
-
auto execute_fn = [ctrl, path,
|
|
306
|
-
(void)ab; // to keep ArrayBuffer alive
|
|
532
|
+
auto execute_fn = [ctrl, path, data, len](ResultAsyncWorker* worker) {
|
|
307
533
|
char* c_res = chat_write_file(ctrl, path.c_str(), data, static_cast<int>(len));
|
|
308
534
|
HandleCResult(worker, c_res, "chat_write_file");
|
|
309
535
|
};
|
|
310
536
|
|
|
311
537
|
ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn));
|
|
538
|
+
worker->KeepAlive(args[2].As<Object>());
|
|
312
539
|
worker->Queue();
|
|
313
540
|
|
|
314
541
|
return promise;
|
|
@@ -410,10 +637,19 @@ Value ChatDecryptFile(const CallbackInfo& args) {
|
|
|
410
637
|
|
|
411
638
|
Object Init(Env env, Object exports) {
|
|
412
639
|
haskell_init();
|
|
640
|
+
auto* receivers = new Receivers();
|
|
641
|
+
// Stopping all receivers before joining any bounds teardown by the longest in-flight receive.
|
|
642
|
+
env.AddCleanupHook([receivers]() {
|
|
643
|
+
for (auto& entry : *receivers) {
|
|
644
|
+
entry.second->RequestStop();
|
|
645
|
+
}
|
|
646
|
+
delete receivers;
|
|
647
|
+
});
|
|
413
648
|
exports.Set("chat_migrate_init", Function::New(env, ChatMigrateInit));
|
|
414
|
-
exports.Set("
|
|
649
|
+
exports.Set("chat_migrate_init_queue", Function::New(env, ChatMigrateInitQueue));
|
|
650
|
+
exports.Set("chat_close_store", Function::New(env, ChatCloseStore, "chat_close_store", receivers));
|
|
415
651
|
exports.Set("chat_send_cmd", Function::New(env, ChatSendCmd));
|
|
416
|
-
exports.Set("chat_recv_msg_wait", Function::New(env, ChatRecvMsgWait));
|
|
652
|
+
exports.Set("chat_recv_msg_wait", Function::New(env, ChatRecvMsgWait, "chat_recv_msg_wait", receivers));
|
|
417
653
|
exports.Set("chat_write_file", Function::New(env, ChatWriteFile));
|
|
418
654
|
exports.Set("chat_read_file", Function::New(env, ChatReadFile));
|
|
419
655
|
exports.Set("chat_encrypt_file", Function::New(env, ChatEncryptFile));
|
package/cpp/simplex.h
CHANGED
|
@@ -11,11 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
extern "C" void hs_init(int argc, char **argv[]);
|
|
13
13
|
extern "C" void hs_init_with_rtsopts(int * argc, char **argv[]);
|
|
14
|
+
extern "C" void hs_thread_done(void);
|
|
14
15
|
|
|
15
16
|
typedef long* chat_ctrl;
|
|
16
17
|
|
|
17
18
|
// the last parameter is used to return the pointer to chat controller
|
|
18
19
|
extern "C" char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl);
|
|
20
|
+
extern "C" char *chat_migrate_init_queue(const char *path, const char *key, const char *confirm, const int queueSize, chat_ctrl *ctrl);
|
|
19
21
|
extern "C" char *chat_close_store(chat_ctrl ctrl);
|
|
20
22
|
extern "C" char *chat_reopen_store(chat_ctrl ctrl);
|
|
21
23
|
extern "C" char *chat_send_cmd(chat_ctrl ctrl, const char *cmd);
|
package/dist/api.d.ts
CHANGED
|
@@ -41,27 +41,6 @@ export type EventSubscriberFunc<K extends CEvt.Tag> = (event: ChatEvent & {
|
|
|
41
41
|
export type EventSubscribers = {
|
|
42
42
|
[K in CEvt.Tag]?: EventSubscriberFunc<K>;
|
|
43
43
|
};
|
|
44
|
-
/**
|
|
45
|
-
* Database configuration. The native library is built against exactly one
|
|
46
|
-
* backend (see `simplex_backend` / `SIMPLEX_BACKEND` at install time); this
|
|
47
|
-
* type makes the caller state which one they are targeting so field names
|
|
48
|
-
* can't lie about their meaning.
|
|
49
|
-
*/
|
|
50
|
-
export type DbConfig = {
|
|
51
|
-
/** SQLite backend (default). */
|
|
52
|
-
type: "sqlite";
|
|
53
|
-
/** File prefix — two schema files are named `<prefix>_chat.db` and `<prefix>_agent.db`. */
|
|
54
|
-
filePrefix: string;
|
|
55
|
-
/** Optional SQLCipher encryption key. Empty/omitted = unencrypted. */
|
|
56
|
-
encryptionKey?: string;
|
|
57
|
-
} | {
|
|
58
|
-
/** PostgreSQL backend (Linux x86_64 only, libpq5 required). */
|
|
59
|
-
type: "postgres";
|
|
60
|
-
/** Schema prefix used to namespace tables. Defaults to `"simplex_v1"` when omitted. */
|
|
61
|
-
schemaPrefix?: string;
|
|
62
|
-
/** PostgreSQL connection string (e.g. `postgres://user:pass@host/db`). */
|
|
63
|
-
connectionString: string;
|
|
64
|
-
};
|
|
65
44
|
/**
|
|
66
45
|
* Main API class for interacting with the chat core library.
|
|
67
46
|
*/
|
|
@@ -74,10 +53,11 @@ export declare class ChatApi {
|
|
|
74
53
|
private constructor();
|
|
75
54
|
/**
|
|
76
55
|
* Initializes the ChatApi.
|
|
77
|
-
* @param {
|
|
56
|
+
* @param {string} dbFilePrefix - File prefix for the database files.
|
|
57
|
+
* @param {string} [dbKey=""] - Database encryption key.
|
|
78
58
|
* @param {core.MigrationConfirmation} [confirm=core.MigrationConfirmation.YesUp] - Migration confirmation mode.
|
|
79
59
|
*/
|
|
80
|
-
static init(
|
|
60
|
+
static init(dbFilePrefix: string, dbKey?: string, confirm?: core.MigrationConfirmation): Promise<ChatApi>;
|
|
81
61
|
/**
|
|
82
62
|
* Start chat controller. Must be called with the existing user profile.
|
|
83
63
|
*/
|
|
@@ -348,40 +328,11 @@ export declare class ChatApi {
|
|
|
348
328
|
* Network usage: no.
|
|
349
329
|
*/
|
|
350
330
|
apiListGroups(userId: number, contactId?: number, search?: string): Promise<T.GroupInfo[]>;
|
|
351
|
-
/**
|
|
352
|
-
* Get chat previews (paginated).
|
|
353
|
-
* Network usage: no.
|
|
354
|
-
*
|
|
355
|
-
* Prefer this over apiListContacts / apiListGroups for any scan: those
|
|
356
|
-
* methods load every record into memory in a single response and will fail
|
|
357
|
-
* on large databases.
|
|
358
|
-
*/
|
|
359
|
-
apiGetChats(userId: number, pagination: T.PaginationByTime, query?: T.ChatListQuery, pendingConnections?: boolean): Promise<T.AChat[]>;
|
|
360
331
|
/**
|
|
361
332
|
* Delete chat.
|
|
362
333
|
* Network usage: background.
|
|
363
334
|
*/
|
|
364
335
|
apiDeleteChat(chatType: T.ChatType, chatId: number, deleteMode?: T.ChatDeleteMode): Promise<void>;
|
|
365
|
-
/**
|
|
366
|
-
* Set group custom data.
|
|
367
|
-
* Network usage: no.
|
|
368
|
-
*/
|
|
369
|
-
apiSetGroupCustomData(groupId: number, customData?: object): Promise<void>;
|
|
370
|
-
/**
|
|
371
|
-
* Set contact custom data.
|
|
372
|
-
* Network usage: no.
|
|
373
|
-
*/
|
|
374
|
-
apiSetContactCustomData(contactId: number, customData?: object): Promise<void>;
|
|
375
|
-
/**
|
|
376
|
-
* Set auto-accept member contacts.
|
|
377
|
-
* Network usage: no.
|
|
378
|
-
*/
|
|
379
|
-
apiSetAutoAcceptMemberContacts(userId: number, onOff: boolean): Promise<void>;
|
|
380
|
-
/**
|
|
381
|
-
* Get chat items.
|
|
382
|
-
* Network usage: no.
|
|
383
|
-
*/
|
|
384
|
-
apiGetChat(chatType: T.ChatType, chatId: number, count: number): Promise<any>;
|
|
385
336
|
/**
|
|
386
337
|
* Get active user profile
|
|
387
338
|
* Network usage: no.
|
|
@@ -417,16 +368,4 @@ export declare class ChatApi {
|
|
|
417
368
|
* Network usage: background.
|
|
418
369
|
*/
|
|
419
370
|
apiSetContactPrefs(contactId: number, preferences: T.Preferences): Promise<void>;
|
|
420
|
-
/**
|
|
421
|
-
* Create a direct message contact with a group member.
|
|
422
|
-
* Returns the created contact.
|
|
423
|
-
* Network usage: interactive.
|
|
424
|
-
*/
|
|
425
|
-
apiCreateMemberContact(groupId: number, groupMemberId: number): Promise<T.Contact>;
|
|
426
|
-
/**
|
|
427
|
-
* Send a direct message invitation to a group member contact.
|
|
428
|
-
* The contact must have been created with {@link apiCreateMemberContact}.
|
|
429
|
-
* Network usage: interactive.
|
|
430
|
-
*/
|
|
431
|
-
apiSendMemberContactInvitation(contactId: number, message?: T.MsgContent | string): Promise<T.Contact>;
|
|
432
371
|
}
|
package/dist/api.js
CHANGED
|
@@ -26,17 +26,6 @@ exports.defaultBotAddressSettings = {
|
|
|
26
26
|
welcomeMessage: undefined,
|
|
27
27
|
businessAddress: false
|
|
28
28
|
};
|
|
29
|
-
function dbConfigToMigrateArgs(db) {
|
|
30
|
-
var _a, _b;
|
|
31
|
-
switch (db.type) {
|
|
32
|
-
case "sqlite":
|
|
33
|
-
return [db.filePrefix, (_a = db.encryptionKey) !== null && _a !== void 0 ? _a : ""];
|
|
34
|
-
case "postgres":
|
|
35
|
-
return [(_b = db.schemaPrefix) !== null && _b !== void 0 ? _b : "", db.connectionString];
|
|
36
|
-
default:
|
|
37
|
-
throw new Error(`Invalid DbConfig: ${JSON.stringify(db)}`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
29
|
/**
|
|
41
30
|
* Main API class for interacting with the chat core library.
|
|
42
31
|
*/
|
|
@@ -50,12 +39,12 @@ class ChatApi {
|
|
|
50
39
|
}
|
|
51
40
|
/**
|
|
52
41
|
* Initializes the ChatApi.
|
|
53
|
-
* @param {
|
|
42
|
+
* @param {string} dbFilePrefix - File prefix for the database files.
|
|
43
|
+
* @param {string} [dbKey=""] - Database encryption key.
|
|
54
44
|
* @param {core.MigrationConfirmation} [confirm=core.MigrationConfirmation.YesUp] - Migration confirmation mode.
|
|
55
45
|
*/
|
|
56
|
-
static async init(
|
|
57
|
-
const
|
|
58
|
-
const ctrl = await core.chatMigrateInit(path, key, confirm);
|
|
46
|
+
static async init(dbFilePrefix, dbKey = "", confirm = core.MigrationConfirmation.YesUp) {
|
|
47
|
+
const ctrl = await core.chatMigrateInit(dbFilePrefix, dbKey, confirm);
|
|
59
48
|
return new ChatApi(ctrl);
|
|
60
49
|
}
|
|
61
50
|
/**
|
|
@@ -357,8 +346,7 @@ class ChatApi {
|
|
|
357
346
|
const r = await this.sendChatCmd(types_1.CC.APISendMessages.cmdString({
|
|
358
347
|
sendRef,
|
|
359
348
|
composedMessages: messages,
|
|
360
|
-
liveMessage
|
|
361
|
-
signMessages: false
|
|
349
|
+
liveMessage
|
|
362
350
|
}));
|
|
363
351
|
if (r.type === "newChatItems")
|
|
364
352
|
return r.chatItems;
|
|
@@ -604,7 +592,7 @@ class ChatApi {
|
|
|
604
592
|
* Network usage: interactive.
|
|
605
593
|
*/
|
|
606
594
|
async apiConnectPlan(userId, connectionLink) {
|
|
607
|
-
const r = await this.sendChatCmd(types_1.CC.APIConnectPlan.cmdString({ userId,
|
|
595
|
+
const r = await this.sendChatCmd(types_1.CC.APIConnectPlan.cmdString({ userId, connectionLink }));
|
|
608
596
|
if (r.type === "connectionPlan")
|
|
609
597
|
return [r.connectionPlan, r.connLink];
|
|
610
598
|
throw new ChatCommandError("error getting connect plan", r);
|
|
@@ -622,7 +610,7 @@ class ChatApi {
|
|
|
622
610
|
* Network usage: interactive.
|
|
623
611
|
*/
|
|
624
612
|
async apiConnectActiveUser(connLink) {
|
|
625
|
-
const r = await this.sendChatCmd(types_1.CC.Connect.cmdString({ incognito: false,
|
|
613
|
+
const r = await this.sendChatCmd(types_1.CC.Connect.cmdString({ incognito: false, connLink_: connLink }));
|
|
626
614
|
return this.handleConnectResult(r);
|
|
627
615
|
}
|
|
628
616
|
handleConnectResult(r) {
|
|
@@ -677,20 +665,6 @@ class ChatApi {
|
|
|
677
665
|
return r.groups;
|
|
678
666
|
throw new ChatCommandError("error listing groups", r);
|
|
679
667
|
}
|
|
680
|
-
/**
|
|
681
|
-
* Get chat previews (paginated).
|
|
682
|
-
* Network usage: no.
|
|
683
|
-
*
|
|
684
|
-
* Prefer this over apiListContacts / apiListGroups for any scan: those
|
|
685
|
-
* methods load every record into memory in a single response and will fail
|
|
686
|
-
* on large databases.
|
|
687
|
-
*/
|
|
688
|
-
async apiGetChats(userId, pagination, query = { type: "filters", favorite: false, unread: false }, pendingConnections = false) {
|
|
689
|
-
const r = await this.sendChatCmd(types_1.CC.APIGetChats.cmdString({ userId, pendingConnections, pagination, query }));
|
|
690
|
-
if (r.type === "apiChats")
|
|
691
|
-
return r.chats;
|
|
692
|
-
throw new ChatCommandError("error getting chats", r);
|
|
693
|
-
}
|
|
694
668
|
/**
|
|
695
669
|
* Delete chat.
|
|
696
670
|
* Network usage: background.
|
|
@@ -709,47 +683,6 @@ class ChatApi {
|
|
|
709
683
|
}
|
|
710
684
|
throw new ChatCommandError("error deleting chat", r);
|
|
711
685
|
}
|
|
712
|
-
/**
|
|
713
|
-
* Set group custom data.
|
|
714
|
-
* Network usage: no.
|
|
715
|
-
*/
|
|
716
|
-
async apiSetGroupCustomData(groupId, customData) {
|
|
717
|
-
const r = await this.sendChatCmd(types_1.CC.APISetGroupCustomData.cmdString({ groupId, customData }));
|
|
718
|
-
if (r.type === "cmdOk")
|
|
719
|
-
return;
|
|
720
|
-
throw new ChatCommandError("error setting group custom data", r);
|
|
721
|
-
}
|
|
722
|
-
/**
|
|
723
|
-
* Set contact custom data.
|
|
724
|
-
* Network usage: no.
|
|
725
|
-
*/
|
|
726
|
-
async apiSetContactCustomData(contactId, customData) {
|
|
727
|
-
const r = await this.sendChatCmd(types_1.CC.APISetContactCustomData.cmdString({ contactId, customData }));
|
|
728
|
-
if (r.type === "cmdOk")
|
|
729
|
-
return;
|
|
730
|
-
throw new ChatCommandError("error setting contact custom data", r);
|
|
731
|
-
}
|
|
732
|
-
/**
|
|
733
|
-
* Set auto-accept member contacts.
|
|
734
|
-
* Network usage: no.
|
|
735
|
-
*/
|
|
736
|
-
async apiSetAutoAcceptMemberContacts(userId, onOff) {
|
|
737
|
-
const r = await this.sendChatCmd(types_1.CC.APISetUserAutoAcceptMemberContacts.cmdString({ userId, onOff }));
|
|
738
|
-
if (r.type === "cmdOk")
|
|
739
|
-
return;
|
|
740
|
-
throw new ChatCommandError("error setting auto-accept member contacts", r);
|
|
741
|
-
}
|
|
742
|
-
/**
|
|
743
|
-
* Get chat items.
|
|
744
|
-
* Network usage: no.
|
|
745
|
-
*/
|
|
746
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
747
|
-
async apiGetChat(chatType, chatId, count) {
|
|
748
|
-
const r = await this.sendChatCmd(`/_get chat ${types_1.T.ChatType.cmdString(chatType)}${chatId} count=${count}`);
|
|
749
|
-
if (r.type === "apiChat")
|
|
750
|
-
return r.chat;
|
|
751
|
-
throw new ChatCommandError("error getting chat", r);
|
|
752
|
-
}
|
|
753
686
|
/**
|
|
754
687
|
* Get active user profile
|
|
755
688
|
* Network usage: no.
|
|
@@ -777,7 +710,7 @@ class ChatApi {
|
|
|
777
710
|
* Network usage: no.
|
|
778
711
|
*/
|
|
779
712
|
async apiCreateActiveUser(profile) {
|
|
780
|
-
const r = await this.sendChatCmd(types_1.CC.CreateActiveUser.cmdString({ newUser: { profile, pastTimestamp: false
|
|
713
|
+
const r = await this.sendChatCmd(types_1.CC.CreateActiveUser.cmdString({ newUser: { profile, pastTimestamp: false } }));
|
|
781
714
|
if (r.type === "activeUser")
|
|
782
715
|
return r.user;
|
|
783
716
|
throw new ChatCommandError("unexpected response", r);
|
|
@@ -836,37 +769,6 @@ class ChatApi {
|
|
|
836
769
|
if (r.type !== "contactPrefsUpdated")
|
|
837
770
|
throw new ChatCommandError("error setting contact prefs", r);
|
|
838
771
|
}
|
|
839
|
-
/**
|
|
840
|
-
* Create a direct message contact with a group member.
|
|
841
|
-
* Returns the created contact.
|
|
842
|
-
* Network usage: interactive.
|
|
843
|
-
*/
|
|
844
|
-
async apiCreateMemberContact(groupId, groupMemberId) {
|
|
845
|
-
const r = await this.sendChatCmd(`/_create member contact #${groupId} ${groupMemberId}`);
|
|
846
|
-
if (r.type === "newMemberContact")
|
|
847
|
-
return r.contact;
|
|
848
|
-
throw new ChatCommandError("error creating member contact", r);
|
|
849
|
-
}
|
|
850
|
-
/**
|
|
851
|
-
* Send a direct message invitation to a group member contact.
|
|
852
|
-
* The contact must have been created with {@link apiCreateMemberContact}.
|
|
853
|
-
* Network usage: interactive.
|
|
854
|
-
*/
|
|
855
|
-
async apiSendMemberContactInvitation(contactId, message) {
|
|
856
|
-
let cmd = `/_invite member contact @${contactId}`;
|
|
857
|
-
if (message !== undefined) {
|
|
858
|
-
if (typeof message === "string") {
|
|
859
|
-
cmd += ` text ${message}`;
|
|
860
|
-
}
|
|
861
|
-
else {
|
|
862
|
-
cmd += ` json ${JSON.stringify(message)}`;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
const r = await this.sendChatCmd(cmd);
|
|
866
|
-
if (r.type === "newMemberContactSentInv")
|
|
867
|
-
return r.contact;
|
|
868
|
-
throw new ChatCommandError("error sending member contact invitation", r);
|
|
869
|
-
}
|
|
870
772
|
}
|
|
871
773
|
exports.ChatApi = ChatApi;
|
|
872
774
|
//# sourceMappingURL=api.js.map
|