From edccdd8ff0b2a36a2489bfdeb49a79f0b7ed67f4 Mon Sep 17 00:00:00 2001 From: Huzaif Mushtaq Mir Date: Wed, 24 Dec 2025 11:23:47 +0530 Subject: [PATCH 1/3] refactor: use ThreadSafeFunction for callbacks --- src/native/CallbackWrapper.h | 98 +++++++++++++++++++++++++++--------- src/native/main.cpp | 57 +++++++++++++++------ 2 files changed, 115 insertions(+), 40 deletions(-) diff --git a/src/native/CallbackWrapper.h b/src/native/CallbackWrapper.h index 235e01e..54ccff7 100644 --- a/src/native/CallbackWrapper.h +++ b/src/native/CallbackWrapper.h @@ -1,3 +1,6 @@ +#ifndef CALLBACK_WRAPPER_H +#define CALLBACK_WRAPPER_H + #ifndef SIZE_MAX #define SIZE_MAX ((size_t)(-1)) #endif @@ -10,51 +13,98 @@ typedef std::wstring STRING; typedef std::string STRING; #endif -class CallbackWrapper : public Napi::AsyncWorker +struct CallbackData { -public: - CallbackWrapper(Napi::Function &callback) : Napi::AsyncWorker(callback) - { - } uint32_t status; + virtual ~CallbackData() = default; +}; -private: - void Execute() +struct StatusCallbackData : public CallbackData +{ + StatusCallbackData(uint32_t s) { + status = s; } +}; + +struct ReleaseUpdateCallbackData : public CallbackData +{ + STRING releaseJson; + void *userData; - void OnOK() + ReleaseUpdateCallbackData(uint32_t s, const STRING &json, void *ud) + : releaseJson(json), userData(ud) { - Napi::HandleScope scope(Env()); - Callback().Call({Napi::Number::New(Env(), status)}); + status = s; } }; -class ReleaseUpdateCallbackWrapper : public Napi::AsyncWorker +class CallbackWrapper { public: - ReleaseUpdateCallbackWrapper(Napi::Function &callback) : Napi::AsyncWorker(callback) + CallbackWrapper(Napi::Env env, Napi::Function callback) { + tsfn = Napi::ThreadSafeFunction::New( + env, + callback, + "LexActivatorCallback", + 0, // Unlimited queue + 1 // Initial thread count + ); + } + + ~CallbackWrapper() + { + tsfn.Abort(); + } + + void Call(uint32_t status) + { + auto *data = new StatusCallbackData(status); + napi_status napiStatus = tsfn.NonBlockingCall(data, InvokeStatusCallback); + if (napiStatus != napi_ok) + { + delete data; + } + } + + void Call(uint32_t status, const STRING &releaseJson, void *userData) + { + auto *data = new ReleaseUpdateCallbackData(status, releaseJson, userData); + napi_status napiStatus = tsfn.NonBlockingCall(data, InvokeReleaseUpdateCallback); + if (napiStatus != napi_ok) + { + delete data; + } } - uint32_t status; - STRING releaseJson; - void* userData; private: - void Execute() + Napi::ThreadSafeFunction tsfn; + + static void InvokeStatusCallback(Napi::Env env, Napi::Function jsCallback, StatusCallbackData *data) { + if (env != nullptr && jsCallback != nullptr && data != nullptr) + { + Napi::HandleScope scope(env); + jsCallback.Call({Napi::Number::New(env, data->status)}); + } + delete data; } - void OnOK() + static void InvokeReleaseUpdateCallback(Napi::Env env, Napi::Function jsCallback, ReleaseUpdateCallbackData *data) { - Napi::HandleScope scope(Env()); + if (env != nullptr && jsCallback != nullptr && data != nullptr) + { + Napi::HandleScope scope(env); #ifdef _WIN32 - // convert STRING to utf16 encoded string - const wchar_t *releaseJsonPtr = releaseJson.c_str(); - const char16_t *utf16StringPtr = reinterpret_cast(releaseJsonPtr); - Callback().Call({Napi::Number::New(Env(), status), Napi::String::New(Env(), utf16StringPtr), Napi::External::New(Env(), userData)}); + const wchar_t *releaseJsonPtr = data->releaseJson.c_str(); + const char16_t *utf16StringPtr = reinterpret_cast(releaseJsonPtr); + jsCallback.Call({Napi::Number::New(env, data->status), Napi::String::New(env, utf16StringPtr),Napi::External::New(env, data->userData)}); #else - Callback().Call({Napi::Number::New(Env(), status), Napi::String::New(Env(), releaseJson), Napi::External::New(Env(), userData)}); + jsCallback.Call({Napi::Number::New(env, data->status), Napi::String::New(env, data->releaseJson), Napi::External::New(env, data->userData)}); #endif + } + delete data; } -}; \ No newline at end of file +}; +#endif // CALLBACK_WRAPPER_H diff --git a/src/native/main.cpp b/src/native/main.cpp index 2620cb6..3c491ef 100644 --- a/src/native/main.cpp +++ b/src/native/main.cpp @@ -23,7 +23,7 @@ const char *MISSING_LICENSE_KEY = "License key not set"; map LicenseCallbacks; map ReleaseCallbacks; -map ReleaseUpdateCallbacks; +map ReleaseUpdateCallbacks; STRING toEncodedString(Napi::String input) @@ -44,8 +44,11 @@ void licenseCallback(uint32_t status) { return; } - LicenseCallbacks[STRING(licenseKey)]->status = status; - LicenseCallbacks[STRING(licenseKey)]->Queue(); + auto it = LicenseCallbacks.find(STRING(licenseKey)); + if (it != LicenseCallbacks.end() && it->second != nullptr) + { + it->second->Call(status); + } } void softwareReleaseUpdateCallback(uint32_t status) @@ -55,21 +58,25 @@ void softwareReleaseUpdateCallback(uint32_t status) { return; } - ReleaseCallbacks[STRING(licenseKey)]->status = status; - ReleaseCallbacks[STRING(licenseKey)]->Queue(); + auto it = ReleaseCallbacks.find(STRING(licenseKey)); + if (it != ReleaseCallbacks.end() && it->second != nullptr) + { + it->second->Call(status); + } } void releaseUpdateCallback(uint32_t status, CONST_CHARTYPE releaseJson, void* userData) -{ +{ CHARTYPE licenseKey[256]; if (GetLicenseKey(licenseKey, 256) != LA_OK) { return; } - ReleaseUpdateCallbacks[STRING(licenseKey)]->status = status; - ReleaseUpdateCallbacks[STRING(licenseKey)]->releaseJson.assign(releaseJson); - ReleaseUpdateCallbacks[STRING(licenseKey)]->userData = NULL; - ReleaseUpdateCallbacks[STRING(licenseKey)]->Queue(); + auto it = ReleaseUpdateCallbacks.find(STRING(licenseKey)); + if (it != ReleaseUpdateCallbacks.end() && it->second != nullptr) + { + it->second->Call(status, STRING(releaseJson), userData); + } } Napi::Value setProductFile(const Napi::CallbackInfo &info) @@ -257,8 +264,14 @@ Napi::Value setLicenseCallback(const Napi::CallbackInfo &info) Napi::Error::New(env, MISSING_LICENSE_KEY).ThrowAsJavaScriptException(); return env.Null(); } - LicenseCallbacks[STRING(licenseKey)] = new CallbackWrapper(callback); - LicenseCallbacks[STRING(licenseKey)]->SuppressDestruct(); + STRING key(licenseKey); + // Clean up existing callback if present + auto it = LicenseCallbacks.find(key); + if (it != LicenseCallbacks.end() && it->second != nullptr) + { + delete it->second; + } + LicenseCallbacks[key] = new CallbackWrapper(env, callback); return Napi::Number::New(env, SetLicenseCallback(licenseCallback)); } @@ -1355,8 +1368,14 @@ Napi::Value checkReleaseUpdate(const Napi::CallbackInfo &info) Napi::Error::New(env, MISSING_LICENSE_KEY).ThrowAsJavaScriptException(); return env.Null(); } - ReleaseUpdateCallbacks[STRING(licenseKey)] = new ReleaseUpdateCallbackWrapper(callback); - ReleaseUpdateCallbacks[STRING(licenseKey)]->SuppressDestruct(); + STRING key(licenseKey); + // Clean up existing callback if present + auto it = ReleaseUpdateCallbacks.find(key); + if (it != ReleaseUpdateCallbacks.end() && it->second != nullptr) + { + delete it->second; + } + ReleaseUpdateCallbacks[key] = new CallbackWrapper(env, callback); return Napi::Number::New(env, CheckReleaseUpdateInternal(releaseUpdateCallback, arg1, NULL)); } @@ -1399,8 +1418,14 @@ Napi::Value checkForReleaseUpdate(const Napi::CallbackInfo &info) Napi::Error::New(env, MISSING_LICENSE_KEY).ThrowAsJavaScriptException(); return env.Null(); } - ReleaseCallbacks[STRING(licenseKey)] = new CallbackWrapper(callback); - ReleaseCallbacks[STRING(licenseKey)]->SuppressDestruct(); + STRING key(licenseKey); + // Clean up existing callback if present + auto it = ReleaseCallbacks.find(key); + if (it != ReleaseCallbacks.end() && it->second != nullptr) + { + delete it->second; + } + ReleaseCallbacks[key] = new CallbackWrapper(env, callback); return Napi::Number::New(env, CheckForReleaseUpdate(arg0.c_str(), arg1.c_str(), arg2.c_str(), softwareReleaseUpdateCallback)); } From 11a413a9ee6be32364d694939dd12e7b2af65608 Mon Sep 17 00:00:00 2001 From: Huzaif Mushtaq Mir Date: Wed, 24 Dec 2025 12:16:37 +0530 Subject: [PATCH 2/3] feat: add mutex to callback registration --- src/native/main.cpp | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/native/main.cpp b/src/native/main.cpp index 3c491ef..61824fa 100644 --- a/src/native/main.cpp +++ b/src/native/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include using namespace ::std; @@ -25,6 +26,7 @@ map LicenseCallbacks; map ReleaseCallbacks; map ReleaseUpdateCallbacks; +std::mutex callbackMutex; STRING toEncodedString(Napi::String input) { @@ -266,12 +268,15 @@ Napi::Value setLicenseCallback(const Napi::CallbackInfo &info) } STRING key(licenseKey); // Clean up existing callback if present - auto it = LicenseCallbacks.find(key); - if (it != LicenseCallbacks.end() && it->second != nullptr) { - delete it->second; + std::lock_guard lock(callbackMutex); + auto it = LicenseCallbacks.find(key); + if (it != LicenseCallbacks.end() && it->second != nullptr) + { + delete it->second; + } + LicenseCallbacks[key] = new CallbackWrapper(env, callback); } - LicenseCallbacks[key] = new CallbackWrapper(env, callback); return Napi::Number::New(env, SetLicenseCallback(licenseCallback)); } @@ -1370,12 +1375,15 @@ Napi::Value checkReleaseUpdate(const Napi::CallbackInfo &info) } STRING key(licenseKey); // Clean up existing callback if present - auto it = ReleaseUpdateCallbacks.find(key); - if (it != ReleaseUpdateCallbacks.end() && it->second != nullptr) { - delete it->second; + std::lock_guard lock(callbackMutex); + auto it = ReleaseUpdateCallbacks.find(key); + if (it != ReleaseUpdateCallbacks.end() && it->second != nullptr) + { + delete it->second; + } + ReleaseUpdateCallbacks[key] = new CallbackWrapper(env, callback); } - ReleaseUpdateCallbacks[key] = new CallbackWrapper(env, callback); return Napi::Number::New(env, CheckReleaseUpdateInternal(releaseUpdateCallback, arg1, NULL)); } @@ -1420,12 +1428,15 @@ Napi::Value checkForReleaseUpdate(const Napi::CallbackInfo &info) } STRING key(licenseKey); // Clean up existing callback if present - auto it = ReleaseCallbacks.find(key); - if (it != ReleaseCallbacks.end() && it->second != nullptr) { - delete it->second; + std::lock_guard lock(callbackMutex); + auto it = ReleaseCallbacks.find(key); + if (it != ReleaseCallbacks.end() && it->second != nullptr) + { + delete it->second; + } + ReleaseCallbacks[key] = new CallbackWrapper(env, callback); } - ReleaseCallbacks[key] = new CallbackWrapper(env, callback); return Napi::Number::New(env, CheckForReleaseUpdate(arg0.c_str(), arg1.c_str(), arg2.c_str(), softwareReleaseUpdateCallback)); } From 3f7d0f7fe7c511781d8a9ee817381ab234dda49d Mon Sep 17 00:00:00 2001 From: Huzaif Mushtaq Mir Date: Wed, 24 Dec 2025 12:49:35 +0530 Subject: [PATCH 3/3] fix: add mutex before map reads --- src/native/CallbackWrapper.h | 1 + src/native/main.cpp | 27 ++++++++++++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/native/CallbackWrapper.h b/src/native/CallbackWrapper.h index 54ccff7..36656ac 100644 --- a/src/native/CallbackWrapper.h +++ b/src/native/CallbackWrapper.h @@ -56,6 +56,7 @@ class CallbackWrapper ~CallbackWrapper() { tsfn.Abort(); + tsfn.Release(); } void Call(uint32_t status) diff --git a/src/native/main.cpp b/src/native/main.cpp index 61824fa..a2d1693 100644 --- a/src/native/main.cpp +++ b/src/native/main.cpp @@ -46,10 +46,13 @@ void licenseCallback(uint32_t status) { return; } - auto it = LicenseCallbacks.find(STRING(licenseKey)); - if (it != LicenseCallbacks.end() && it->second != nullptr) { - it->second->Call(status); + std::lock_guard lock(callbackMutex); + auto it = LicenseCallbacks.find(STRING(licenseKey)); + if (it != LicenseCallbacks.end() && it->second != nullptr) + { + it->second->Call(status); + } } } @@ -60,10 +63,13 @@ void softwareReleaseUpdateCallback(uint32_t status) { return; } - auto it = ReleaseCallbacks.find(STRING(licenseKey)); - if (it != ReleaseCallbacks.end() && it->second != nullptr) { - it->second->Call(status); + std::lock_guard lock(callbackMutex); + auto it = ReleaseCallbacks.find(STRING(licenseKey)); + if (it != ReleaseCallbacks.end() && it->second != nullptr) + { + it->second->Call(status); + } } } @@ -74,10 +80,13 @@ void releaseUpdateCallback(uint32_t status, CONST_CHARTYPE releaseJson, void* us { return; } - auto it = ReleaseUpdateCallbacks.find(STRING(licenseKey)); - if (it != ReleaseUpdateCallbacks.end() && it->second != nullptr) { - it->second->Call(status, STRING(releaseJson), userData); + std::lock_guard lock(callbackMutex); + auto it = ReleaseUpdateCallbacks.find(STRING(licenseKey)); + if (it != ReleaseUpdateCallbacks.end() && it->second != nullptr) + { + it->second->Call(status, STRING(releaseJson), userData); + } } }