diff --git a/.gitignore b/.gitignore index 645b55ec9d..e94707c2a1 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,6 @@ website/package-lock.json website/.cache website/test/stubs-layout-cache/_includes/*.js apps/android/app/release +packages/simplex-chat-node/build +packages/simplex-chat-node/.vscode +packages/simplex-chat-node/libs diff --git a/packages/simplex-chat-node/binding.gyp b/packages/simplex-chat-node/binding.gyp new file mode 100644 index 0000000000..e24863d7a8 --- /dev/null +++ b/packages/simplex-chat-node/binding.gyp @@ -0,0 +1,13 @@ +{ + "targets": [ + { + "target_name": "addon", + "sources": [ "cpp/simplex.cc" ], + "libraries": [ + "-L<(module_root_dir)/libs", + "-lHSsimplex-chat-6.2.0.5-inplace-ghc9.6.3", + "-L<(module_root_dir)/build/Release/" + ] + } + ] +} \ No newline at end of file diff --git a/packages/simplex-chat-node/cpp/simplex.cc b/packages/simplex-chat-node/cpp/simplex.cc new file mode 100644 index 0000000000..4066dd0798 --- /dev/null +++ b/packages/simplex-chat-node/cpp/simplex.cc @@ -0,0 +1,188 @@ +#include +#include + +#include +#include "simplex.h" + +namespace simplex +{ + + using v8::Array; + using v8::ArrayBuffer; + using v8::Context; + using v8::FunctionCallbackInfo; + using v8::Isolate; + using v8::Local; + using v8::Maybe; + using v8::Number; + using v8::Object; + using v8::String; + using v8::Value; + + void haskell_init() + { + int argc = 5; + char *argv[] = { + "simplex", + "+RTS", // requires `hs_init_with_rtsopts` + "-A64m", // chunk size for new allocations + "-H64m", // initial heap size + "-xn", // non-moving GC + 0}; + char **pargv = argv; + hs_init_with_rtsopts(&argc, &pargv); + } + + void ChatMigrateInit(const FunctionCallbackInfo &args) + { + Isolate *isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + std::string path(*(v8::String::Utf8Value(isolate, args[0]))); + std::string key(*(v8::String::Utf8Value(isolate, args[1]))); + std::string confirm(*(v8::String::Utf8Value(isolate, args[2]))); + + long *ctrl(0); + + std::string res = chat_migrate_init(path.c_str(), key.c_str(), confirm.c_str(), &ctrl); + std::stringstream ss; + ss << ctrl + << "\n" + << res; + // printf("ChatCtrl after init %d", cChatCtrl); + // v8::Local ctrl = v8::String::NewFromUtf8(isolate, cppChatCtrl.c_str(), v8::String::kNormalString); + + args.GetReturnValue().Set( + String::NewFromUtf8( + isolate, ss.str().c_str()) + .ToLocalChecked()); + } + + void ChatCloseStore(const FunctionCallbackInfo &args) + { + Isolate *isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + long *ctrl = (long *)((long)args[0].As()->Value()); + + args.GetReturnValue().Set( + String::NewFromUtf8( + isolate, chat_close_store(ctrl)) + .ToLocalChecked()); + } + + void ChatSendCmd(const FunctionCallbackInfo &args) + { + Isolate *isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + long *ctrl = (long *)((long)args[0].As()->Value()); + std::string cmd(*(v8::String::Utf8Value(isolate, args[1]))); + + args.GetReturnValue().Set( + String::NewFromUtf8( + isolate, chat_send_cmd(ctrl, cmd.c_str())) + .ToLocalChecked()); + } + + void ChatRecvMsgWait(const FunctionCallbackInfo &args) + { + Isolate *isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + long *ctrl = (long *)((long)args[0].As()->Value()); + long wait = ((long)args[1].As()->Value()); + + args.GetReturnValue().Set( + String::NewFromUtf8( + isolate, chat_recv_msg_wait(ctrl, wait)) + .ToLocalChecked()); + } + + void ChatWriteFile(const FunctionCallbackInfo &args) + { + Isolate *isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + long *ctrl = (long *)((long)args[0].As()->Value()); + std::string path(*(v8::String::Utf8Value(isolate, args[1]))); + + Local arr = args[2].As(); + char *buffer = (char *)arr->Data(); + int len(arr->ByteLength()); + // std::array address(*arr); + + // v8::Array a = *buffer; + // std::string data(*((isolate, args[1]))); + + args.GetReturnValue().Set( + String::NewFromUtf8( + isolate, chat_write_file(ctrl, path.c_str(), buffer, len)) + .ToLocalChecked()); + } + + void ChatReadFile(const FunctionCallbackInfo &args) + { + Isolate *isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + long *ctrl = (long *)((long)args[0].As()->Value()); + std::string path(*(v8::String::Utf8Value(isolate, args[1]))); + + Local arr = args[2].As(); + char *buffer = (char *)arr->Data(); + int len(arr->ByteLength()); + + args.GetReturnValue().Set( + String::NewFromUtf8( + isolate, chat_write_file(ctrl, path.c_str(), buffer, len)) + .ToLocalChecked()); + } + + void ChatEncryptFile(const FunctionCallbackInfo &args) + { + Isolate *isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + long *ctrl = (long *)((long)args[0].As()->Value()); + std::string fromPath(*(v8::String::Utf8Value(isolate, args[1]))); + std::string toPath(*(v8::String::Utf8Value(isolate, args[2]))); + + args.GetReturnValue().Set( + String::NewFromUtf8( + isolate, chat_encrypt_file(ctrl, fromPath.c_str(), toPath.c_str())) + .ToLocalChecked()); + } + + void ChatDecryptFile(const FunctionCallbackInfo &args) + { + Isolate *isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + std::string fromPath(*(v8::String::Utf8Value(isolate, args[0]))); + std::string key(*(v8::String::Utf8Value(isolate, args[1]))); + std::string nonce(*(v8::String::Utf8Value(isolate, args[2]))); + std::string toPath(*(v8::String::Utf8Value(isolate, args[3]))); + + args.GetReturnValue().Set( + String::NewFromUtf8( + isolate, chat_decrypt_file(fromPath.c_str(), key.c_str(), nonce.c_str(), toPath.c_str())) + .ToLocalChecked()); + } + + void Initialize(Local exports) + { + haskell_init(); + NODE_SET_METHOD(exports, "chat_migrate_init", ChatMigrateInit); + NODE_SET_METHOD(exports, "chat_close_store", ChatCloseStore); + NODE_SET_METHOD(exports, "chat_send_cmd", ChatSendCmd); + NODE_SET_METHOD(exports, "chat_recv_msg_wait", ChatRecvMsgWait); + NODE_SET_METHOD(exports, "chat_write_file", ChatWriteFile); + NODE_SET_METHOD(exports, "chat_read_file", ChatReadFile); + NODE_SET_METHOD(exports, "chat_encrypt_file", ChatEncryptFile); + NODE_SET_METHOD(exports, "chat_decrypt_file", ChatDecryptFile); + } + + NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) + +} // namespace simplex \ No newline at end of file diff --git a/packages/simplex-chat-node/cpp/simplex.h b/packages/simplex-chat-node/cpp/simplex.h new file mode 100644 index 0000000000..8e579626ed --- /dev/null +++ b/packages/simplex-chat-node/cpp/simplex.h @@ -0,0 +1,46 @@ +// +// simplex.h +// SimpleX +// +// Created by Evgeny on 30/05/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +#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[]); + +typedef long* chat_ctrl; + +// 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_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); + +// 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); + +// 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); + +// 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); + +// 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); + +#endif /* simplex_h */ \ No newline at end of file diff --git a/packages/simplex-chat-node/package.json b/packages/simplex-chat-node/package.json new file mode 100644 index 0000000000..86daf44c1f --- /dev/null +++ b/packages/simplex-chat-node/package.json @@ -0,0 +1,19 @@ +{ + "name": "simplexjs", + "version": "1.0.0", + "main": "src/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/simplex-chat/simplexjs.git" + }, + "author": "SimpleX team", + "license": "ISC", + "bugs": { + "url": "https://github.com/simplex-chat/simplexjs/issues" + }, + "homepage": "https://github.com/simplex-chat/simplexjs#readme", + "description": "" +} diff --git a/packages/simplex-chat-node/src/index.js b/packages/simplex-chat-node/src/index.js new file mode 100644 index 0000000000..150406b5e7 --- /dev/null +++ b/packages/simplex-chat-node/src/index.js @@ -0,0 +1,22 @@ +const addon = require('../build/Release/addon'); +const fs = require('fs'); + +const ctrlAndRes = addon.chat_migrate_init("db", "a", "yesUp") +const ctrl = Number(ctrlAndRes.split("\n")[0]) +const res = ctrlAndRes.split("\n")[1] +console.log("Migrate ctrl:", ctrl, "res:", res) +console.log(addon.chat_send_cmd(ctrl, "/v")) +// const wait = 15_000_000 +// console.log(ctrl, addon.chat_recv_msg_wait(ctrl, wait)) + +const path = "/tmp/write_file.txt" +const data = [0, 1, 2] +console.log(addon.chat_write_file(ctrl, path, new ArrayBuffer(data))) + + +fs.writeFileSync("/tmp/file_unencrypted.txt", "unencrypted") +const encRes = JSON.parse(addon.chat_encrypt_file(ctrl, "/tmp/file_unencrypted.txt", "/tmp/file_encrypted.txt")) +const key = encRes.cryptoArgs.fileKey +const nonce = encRes.cryptoArgs.fileNonce +console.log(encRes) +console.log(addon.chat_decrypt_file("/tmp/file_encrypted.txt", key, nonce, "/tmp/file_decrypted.txt"))