simplex-chat 7.1.0-beta.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 +10 -108
- 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 +39 -21
- 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
|
}
|