diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a4aa83..9b563b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,12 +46,30 @@ if(MSVC) add_compile_definitions(NOMINMAX) endif() + + add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC}) -target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_JS_INC} "include/" "${CMAKE_CURRENT_SOURCE_DIR}/node_modules/node-addon-api" "${CMAKE_CURRENT_SOURCE_DIR}/node_modules" "${CMAKE_CURRENT_SOURCE_DIR}/node_modules/node-api-headers/include") + +# Mark the node-addon-api headers as system so the -Werror=switch-enum does not apply to them +target_include_directories(${PROJECT_NAME} + SYSTEM PRIVATE + ${CMAKE_JS_INC} + ${CMAKE_CURRENT_SOURCE_DIR}/node_modules/node-addon-api + ${CMAKE_CURRENT_SOURCE_DIR}/node_modules + ${CMAKE_CURRENT_SOURCE_DIR}/node_modules/node-api-headers/include +) + +target_include_directories(${PROJECT_NAME} PRIVATE "include/" ) set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_JS_LIB} ${LIBSESSION_STATIC_BUNDLE_LIBS}) + +if(UNIX AND NOT APPLE) + target_compile_options(${PROJECT_NAME} PRIVATE -Werror=switch-enum) +endif() + + if(MSVC AND CMAKE_JS_NODELIB_DEF AND CMAKE_JS_NODELIB_TARGET) # Generate node.lib execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS}) diff --git a/include/pro/pro.hpp b/include/pro/pro.hpp index 72722cf..8859e39 100644 --- a/include/pro/pro.hpp +++ b/include/pro/pro.hpp @@ -5,22 +5,78 @@ #include -#include "../../node_modules/node-addon-api/napi.h" -#include "../meta/meta_base_wrapper.hpp" -#include "../utilities.hpp" -#include "./types.hpp" #include "meta/meta_base_wrapper.hpp" +#include "pro/types.hpp" #include "session/pro_backend.h" #include "session/pro_backend.hpp" #include "session/session_protocol.h" #include "session/session_protocol.hpp" +#include "utilities.hpp" namespace session::nodeapi { -std::string_view ProBackendEnumToString(SESSION_PRO_BACKEND_PAYMENT_PROVIDER v); -std::string_view ProBackendEnumToString(SESSION_PRO_BACKEND_PAYMENT_STATUS v); -std::string_view ProBackendEnumToString(SESSION_PRO_BACKEND_PLAN v); -std::string_view ProBackendEnumToString(SESSION_PRO_BACKEND_USER_PRO_STATUS v); -std::string_view ProBackendEnumToString(SESSION_PRO_BACKEND_GET_PRO_STATUS_ERROR_REPORT v); + +std::string_view proBackendEnumToString(SESSION_PRO_BACKEND_PAYMENT_PROVIDER v); +std::string_view proBackendEnumToString(SESSION_PRO_BACKEND_PAYMENT_STATUS v); +std::string_view proBackendEnumToString(SESSION_PRO_BACKEND_PLAN v); +std::string_view proBackendEnumToString(SESSION_PRO_BACKEND_USER_PRO_STATUS v); +std::string_view proBackendEnumToString(SESSION_PRO_BACKEND_GET_PRO_STATUS_ERROR_REPORT v); +std::string_view proBackendEnumToString(session::ProFeaturesForMsgStatus v); + +template +Napi::Value toJsOrNullIfErrors( + const Napi::Env& env, const T& value, const std::vector& errors) { + return errors.empty() ? toJs(env, value) : env.Null(); +} + +template <> +struct toJs_impl { + auto operator()(const Napi::Env& env, pro_backend::ProRevocationItem i) const { + + auto obj = Napi::Object::New(env); + obj["genIndexHashB64"] = toJs(env, to_base64(i.gen_index_hash)); + obj["expiryUnixTsMs"] = toJs(env, i.expiry_unix_ts); + + return obj; + } +}; + +template <> +struct toJs_impl { + auto operator()(const Napi::Env& env, pro_backend::ProPaymentItem p) const { + + auto obj = Napi::Object::New(env); + obj["status"] = toJs(env, proBackendEnumToString(p.status)); + obj["plan"] = toJs(env, proBackendEnumToString(p.plan)); + obj["paymentProvider"] = toJs(env, proBackendEnumToString(p.payment_provider)); + + obj["autoRenewing"] = toJs(env, p.auto_renewing); + obj["unredeemedTsMs"] = toJs(env, p.unredeemed_unix_ts); + obj["redeemedTsMs"] = toJs(env, p.redeemed_unix_ts); + obj["expiryTsMs"] = toJs(env, p.expiry_unix_ts); + obj["gracePeriodDurationMs"] = toJs(env, p.grace_period_duration_ms); + obj["platformRefundExpiryTsMs"] = toJs(env, p.platform_refund_expiry_unix_ts); + obj["revokedTsMs"] = toJs(env, p.revoked_unix_ts); + + obj["googlePaymentToken"] = toJs(env, p.google_payment_token); + obj["appleOriginalTxId"] = toJs(env, p.apple_original_tx_id); + obj["appleTxId"] = toJs(env, p.apple_tx_id); + obj["appleWebLineOrderId"] = toJs(env, p.apple_web_line_order_id); + + return obj; + } +}; + +template <> +struct toJs_impl { + auto operator()(const Napi::Env& env, pro_backend::ResponseHeader r) const { + + auto obj = Napi::Object::New(env); + obj["status"] = toJs(env, r.status); + obj["errors"] = toJs(env, r.errors); + + return obj; + } +}; class ProWrapper : public Napi::ObjectWrap { @@ -40,30 +96,36 @@ class ProWrapper : public Napi::ObjectWrap { "proFeaturesForMessage", static_cast( napi_writable | napi_configurable)), + + // Pro requests StaticMethod<&ProWrapper::proProofRequestBody>( "proProofRequestBody", static_cast( napi_writable | napi_configurable)), - StaticMethod<&ProWrapper::proProofResponseBody>( - "proProofResponseBody", - static_cast( - napi_writable | napi_configurable)), StaticMethod<&ProWrapper::proRevocationsRequestBody>( "proRevocationsRequestBody", static_cast( napi_writable | napi_configurable)), - StaticMethod<&ProWrapper::proRevocationsResponseBody>( - "proRevocationsResponseBody", - static_cast( - napi_writable | napi_configurable)), StaticMethod<&ProWrapper::proStatusRequestBody>( "proStatusRequestBody", static_cast( napi_writable | napi_configurable)), - StaticMethod<&ProWrapper::proStatusResponseBody>( - "proStatusResponseBody", - static_cast( - napi_writable | napi_configurable)), + + // Note: those are not plugged in for now as we do this parsing through zod + // on desktop. + // Pro responses parsing + // StaticMethod<&ProWrapper::proProofParseResponse>( + // "proProofParseResponse", + // static_cast( + // napi_writable | napi_configurable)), + // StaticMethod<&ProWrapper::proRevocationsParseResponse>( + // "proRevocationsParseResponse", + // static_cast( + // napi_writable | napi_configurable)), + // StaticMethod<&ProWrapper::proStatusParseResponse>( + // "proStatusParseResponse", + // static_cast( + // napi_writable | napi_configurable)), }); } @@ -113,7 +175,7 @@ class ProWrapper : public Napi::ObjectWrap { auto obj = Napi::Object::New(env); - obj["success"] = toJs(env, pro_features_msg.success); + obj["status"] = toJs(env, proBackendEnumToString(pro_features_msg.status)); obj["error"] = pro_features_msg.error.size() ? toJs(env, pro_features_msg.error) : env.Null(); obj["codepointCount"] = toJs(env, pro_features_msg.codepoint_count); @@ -128,8 +190,8 @@ class ProWrapper : public Napi::ObjectWrap { // we expect arguments that match: // first: { // "requestVersion": number, - // "masterPrivkey": Uint8Array, - // "rotatingPrivkey": Uint8Array, + // "masterPrivKeyHex": string, + // "rotatingPrivKeyHex": string, // "unixTsMs": number, // } @@ -138,39 +200,42 @@ class ProWrapper : public Napi::ObjectWrap { auto env = info.Env(); auto first = info[0].As(); - if (first.IsEmpty()) throw std::invalid_argument("proProofRequestBody first received empty"); assertIsNumber(first.Get("requestVersion"), "proProofRequestBody.requestVersion"); + Napi::Number requestVersion = first.Get("requestVersion").As(); assertIsNumber(first.Get("unixTsMs"), "proProofRequestBody.unixTsMs"); - auto requestVersion = first.Get("requestVersion").As(); auto unix_ts_ms = toCppSysMs(first.Get("unixTsMs"), "proProofRequestBody.unixTsMs"); - assertIsUInt8Array(first.Get("masterPrivkey"), "proProofRequestBody.masterPrivkey"); - assertIsUInt8Array(first.Get("rotatingPrivkey"), "proProofRequestBody.rotatingPrivkey"); + assertIsString(first.Get("masterPrivKeyHex"), "proProofRequestBody.masterPrivKeyHex"); + assertIsString( + first.Get("rotatingPrivKeyHex"), "proProofRequestBody.rotatingPrivKeyHex"); - auto master_privkey_js = first.Get("masterPrivkey"); - auto rotating_privkey_js = first.Get("rotatingPrivkey"); - auto master_privkey = - toCppBuffer(master_privkey_js, "proProofRequestBody.masterPrivkey"); - auto rotating_privkey = - toCppBuffer(rotating_privkey_js, "proProofRequestBody.rotatingPrivkey"); + auto master_privkey_js = first.Get("masterPrivKeyHex"); + auto rotating_privkey_js = first.Get("rotatingPrivKeyHex"); + std::string master_privkey = + toCppString(master_privkey_js, "proProofRequestBody.masterPrivKeyHex"); + std::string rotating_privkey = + toCppString(rotating_privkey_js, "proProofRequestBody.rotatingPrivKeyHex"); + + assert_length(master_privkey, 64, "masterPrivKeyHex"); + assert_length(rotating_privkey, 64, "rotatingPrivkey"); - assert_length(master_privkey, 64, "master_privkey"); - assert_length(rotating_privkey, 64, "rotating_prevkey"); + auto master_privkey_decoded = from_hex(master_privkey); + auto rotating_privkey_decoded = from_hex(rotating_privkey); - auto json = pro_backend::GetProProofRequest::build_to_json( + std::string json = pro_backend::GetProProofRequest::build_to_json( static_cast(requestVersion.Int32Value()), - master_privkey, - rotating_privkey, + to_span(master_privkey_decoded), + to_span(rotating_privkey_decoded), unix_ts_ms); return json; }); }; - static Napi::Value proProofResponseBody(const Napi::CallbackInfo& info) { + static Napi::Value proProofParseResponse(const Napi::CallbackInfo& info) { return wrapResult(info, [&] { // we expect arguments that match: // first: { @@ -184,17 +249,14 @@ class ProWrapper : public Napi::ObjectWrap { auto first = info[0].As(); if (first.IsEmpty()) - throw std::invalid_argument("proProofResponseBody first received empty"); + throw std::invalid_argument("proProofParseResponse first received empty"); - assertIsString(first.Get("json"), "proProofResponseBody.jsonStr"); - auto json_str = toCppString(first.Get("json"), "proProofResponseBody.jsonStr"); - auto json = pro_backend::AddProPaymentOrGetProProofResponse::parse(json_str); + assertIsString(first.Get("json"), "proProofParseResponse.json"); + auto json_str = toCppString(first.Get("json"), "proProofParseResponse.json"); + auto parsed = pro_backend::AddProPaymentOrGetProProofResponse::parse(json_str); - auto obj = Napi::Object::New(env); - - obj["status"] = toJs(env, json.status); - obj["errors"] = toJs(env, json.errors); - obj["proof"] = json.errors.empty() ? toJs(env, json.proof) : env.Null(); + auto obj = toJs(env, static_cast(parsed)); + obj["proof"] = toJsOrNullIfErrors(env, parsed.proof, parsed.errors); return obj; }); @@ -227,13 +289,11 @@ class ProWrapper : public Napi::ObjectWrap { .ticket = ticket.Uint32Value(), }; - auto json = revocationsRequest.to_json(); - - return json; + return revocationsRequest.to_json(); }); }; - static Napi::Value proRevocationsResponseBody(const Napi::CallbackInfo& info) { + static Napi::Value proRevocationsParseResponse(const Napi::CallbackInfo& info) { return wrapResult(info, [&] { // we expect arguments that match: // first: { @@ -247,19 +307,16 @@ class ProWrapper : public Napi::ObjectWrap { auto first = info[0].As(); if (first.IsEmpty()) - throw std::invalid_argument("proRevocationsResponseBody first received empty"); + throw std::invalid_argument("proRevocationsParseResponse first received empty"); - assertIsString(first.Get("json"), "proRevocationsResponseBody.jsonStr"); - auto json_str = toCppString(first.Get("json"), "proRevocationsResponseBody.jsonStr"); - auto json = pro_backend::GetProRevocationsResponse::parse(json_str); + assertIsString(first.Get("json"), "proRevocationsParseResponse.json"); + auto json_str = toCppString(first.Get("json"), "proRevocationsParseResponse.json"); + auto parsed = pro_backend::GetProRevocationsResponse::parse(json_str); - auto obj = Napi::Object::New(env); - - obj["status"] = toJs(env, json.status); - obj["errors"] = toJs(env, json.errors); - obj["ticket"] = json.errors.empty() ? toJs(env, json.ticket) : env.Null(); - // FIXME: implement - // obj["items"] = json.errors.empty() ? toJs(env, json.items) : env.Null(); + auto obj = toJs(env, static_cast(parsed)); + // if error is set, the body might not be parsable so don't try to use it + obj["ticket"] = parsed.errors.size() ? env.Null() : toJs(env, parsed.ticket); + obj["items"] = parsed.errors.size() ? env.Null() : toJs(env, parsed.items); return obj; }); @@ -270,7 +327,7 @@ class ProWrapper : public Napi::ObjectWrap { // we expect arguments that match: // first: { // "requestVersion": number, - // "masterPrivkey": Uint8Array, + // "masterPrivKeyHex": string, // "unixTsMs": number, // "withPaymentHistory": boolean, // } @@ -292,17 +349,17 @@ class ProWrapper : public Napi::ObjectWrap { auto unix_ts_ms = toCppSysMs(first.Get("unixTsMs"), "proStatusRequestBody.unixTsMs"); auto withPaymentHistory = toCppBoolean( first.Get("withPaymentHistory"), "proStatusRequestBody.withPaymentHistory"); - assertIsUInt8Array(first.Get("masterPrivkey"), "proStatusRequestBody.masterPrivkey"); + assertIsString(first.Get("masterPrivKeyHex"), "proStatusRequestBody.masterPrivKeyHex"); - auto master_privkey_js = first.Get("masterPrivkey"); + auto master_privkey_js = first.Get("masterPrivKeyHex"); auto master_privkey = - toCppBuffer(master_privkey_js, "proStatusRequestBody.masterPrivkey"); + toCppString(master_privkey_js, "proStatusRequestBody.masterPrivKeyHex"); - assert_length(master_privkey, 64, "master_privkey"); + assert_length(master_privkey, 64, "proStatusRequestBody.masterPrivKeyHex"); auto json = pro_backend::GetProStatusRequest::build_to_json( static_cast(requestVersion.Int32Value()), - master_privkey, + to_span(from_hex(master_privkey)), unix_ts_ms, withPaymentHistory); @@ -310,7 +367,7 @@ class ProWrapper : public Napi::ObjectWrap { }); }; - static Napi::Value proStatusResponseBody(const Napi::CallbackInfo& info) { + static Napi::Value proStatusParseResponse(const Napi::CallbackInfo& info) { return wrapResult(info, [&] { // we expect arguments that match: // first: { @@ -324,30 +381,25 @@ class ProWrapper : public Napi::ObjectWrap { auto first = info[0].As(); if (first.IsEmpty()) - throw std::invalid_argument("proStatusResponseBody first received empty"); + throw std::invalid_argument("proStatusParseResponse first received empty"); - assertIsString(first.Get("json"), "proStatusResponseBody.jsonStr"); - auto json_str = toCppString(first.Get("json"), "proStatusResponseBody.jsonStr"); - auto json = pro_backend::GetProStatusResponse::parse(json_str); + assertIsString(first.Get("json"), "proStatusParseResponse.json"); + auto json_str = toCppString(first.Get("json"), "proStatusParseResponse.json"); + auto parsed = pro_backend::GetProStatusResponse::parse(json_str); - auto obj = Napi::Object::New(env); + auto obj = toJs(env, static_cast(parsed)); + + obj["items"] = toJsOrNullIfErrors(env, parsed.items, parsed.errors); + obj["userStatus"] = toJsOrNullIfErrors( + env, proBackendEnumToString(parsed.user_status), parsed.errors); + + obj["errorReport"] = toJsOrNullIfErrors( + env, proBackendEnumToString(parsed.error_report), parsed.errors); - obj["status"] = toJs(env, json.status); - obj["errors"] = toJs(env, json.errors); - // FIXME: implement - // obj["items"] = json.errors.empty() ? toJs(env, json.items) : env.Null(); - obj["userStatus"] = json.errors.empty() - ? toJs(env, ProBackendEnumToString(json.user_status)) - : env.Null(); - obj["errorReport"] = json.errors.empty() - ? toJs(env, ProBackendEnumToString(json.error_report)) - : env.Null(); - obj["autoRenewing"] = json.errors.empty() ? toJs(env, json.auto_renewing) : env.Null(); - - // FIXME: implement - // obj["expiryUnixTsMs"] = json.errors.empty() ? toJs(env, json.expiry_unix_ts_ms) : - // env.Null(); obj["gracePeriodDurationMs"] = json.errors.empty() ? toJs(env, - // json.grace_period_duration_ms) : env.Null(); + obj["autoRenewing"] = toJsOrNullIfErrors(env, parsed.auto_renewing, parsed.errors); + obj["expiryTsMs"] = toJsOrNullIfErrors(env, parsed.expiry_unix_ts_ms, parsed.errors); + obj["gracePeriodMs"] = + toJsOrNullIfErrors(env, parsed.grace_period_duration_ms, parsed.errors); return obj; }); diff --git a/include/utilities.hpp b/include/utilities.hpp index ae4f5e1..ef617bf 100644 --- a/include/utilities.hpp +++ b/include/utilities.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -23,6 +24,12 @@ namespace session::nodeapi { using namespace std::literals; +#ifdef _MSC_VER +#define UNREACHABLE() __assume(0) +#else +#define UNREACHABLE() __builtin_unreachable() +#endif + inline auto cat = oxen::log::Cat("nodeapi"); static void checkOrThrow(bool condition, const char* msg) { @@ -239,6 +246,21 @@ struct toJs_impl { } }; +template <> +struct toJs_impl { + auto operator()(const Napi::Env& env, std::chrono::milliseconds t) const { + return Napi::Number::New(env, t.count()); + } +}; + +template <> +struct toJs_impl> { + auto operator()( + const Napi::Env& env, std::chrono::sys_time t) const { + return Napi::Number::New(env, t.time_since_epoch().count()); + } +}; + // Returns {"url": "...", "key": buffer} object; both values will be Null if the pic is not set. template <> @@ -383,7 +405,7 @@ template std::array from_hex_to_array(std::string x) { std::string as_hex = oxenc::from_hex(x); if (as_hex.size() != N) { - throw std::invalid_argument(std::format( + throw std::invalid_argument(fmt::format( "from_hex_to_array: Decoded hex size mismatch: expected {}, got {}", N, as_hex.size())); @@ -410,7 +432,7 @@ concept HasSize = requires(T t) { template void assert_length(const T& x, size_t n, std::string_view base_identifier) { if (x.size() != n) { - throw std::invalid_argument(std::format( + throw std::invalid_argument(fmt::format( "assert_length: expected {}, got {} for {}", n, x.size(), base_identifier)); } } diff --git a/libsession-util b/libsession-util index 5fd7882..32bda61 160000 --- a/libsession-util +++ b/libsession-util @@ -1 +1 @@ -Subproject commit 5fd78825b79dbc9dbabd06cd64ae37e845d95904 +Subproject commit 32bda61d0a108841c773a02271ad8dd377eba3c7 diff --git a/src/pro/pro.cpp b/src/pro/pro.cpp index 0490e4a..abd510c 100644 --- a/src/pro/pro.cpp +++ b/src/pro/pro.cpp @@ -1,15 +1,16 @@ #include "pro/pro.hpp" namespace session::nodeapi { + std::string_view ProBackendEnumToString(SESSION_PRO_BACKEND_PAYMENT_PROVIDER v) { switch (v) { case SESSION_PRO_BACKEND_PAYMENT_PROVIDER_NIL: return "NIL"; case SESSION_PRO_BACKEND_PAYMENT_PROVIDER_GOOGLE_PLAY_STORE: return "GOOGLE_PLAY_STORE"; case SESSION_PRO_BACKEND_PAYMENT_PROVIDER_IOS_APP_STORE: return "IOS_APP_STORE"; case SESSION_PRO_BACKEND_PAYMENT_PROVIDER_COUNT: - // TODO: do we want to assert this cant happen? or should it be allowed? - return "Count"; + throw std::invalid_argument("SESSION_PRO_BACKEND_PAYMENT_PROVIDER_COUNT"); } + UNREACHABLE(); } std::string_view ProBackendEnumToString(SESSION_PRO_BACKEND_PAYMENT_STATUS v) { @@ -20,32 +21,32 @@ std::string_view ProBackendEnumToString(SESSION_PRO_BACKEND_PAYMENT_STATUS v) { case SESSION_PRO_BACKEND_PAYMENT_STATUS_EXPIRED: return "EXPIRED"; case SESSION_PRO_BACKEND_PAYMENT_STATUS_REFUNDED: return "REFUNDED"; case SESSION_PRO_BACKEND_PAYMENT_STATUS_COUNT: - // TODO: do we want to assert this cant happen? or should it be allowed? - return "COUNT"; + throw std::invalid_argument("SESSION_PRO_BACKEND_PAYMENT_STATUS_COUNT"); } + UNREACHABLE(); } -std::string_view ProBackendEnumToString(SESSION_PRO_BACKEND_PLAN v) { +std::string_view proBackendEnumToString(SESSION_PRO_BACKEND_PLAN v) { switch (v) { case SESSION_PRO_BACKEND_PLAN_NIL: return "NIL"; case SESSION_PRO_BACKEND_PLAN_ONE_MONTH: return "ONE_MONTH"; case SESSION_PRO_BACKEND_PLAN_THREE_MONTHS: return "THREE_MONTHS"; case SESSION_PRO_BACKEND_PLAN_TWELVE_MONTHS: return "TWELVE_MONTHS"; case SESSION_PRO_BACKEND_PLAN_COUNT: - // TODO: do we want to assert this cant happen? or should it be allowed? - return "COUNT"; + throw std::invalid_argument("SESSION_PRO_BACKEND_PLAN_COUNT"); } + UNREACHABLE(); } -std::string_view ProBackendEnumToString(SESSION_PRO_BACKEND_USER_PRO_STATUS v) { +std::string_view proBackendEnumToString(SESSION_PRO_BACKEND_USER_PRO_STATUS v) { switch (v) { case SESSION_PRO_BACKEND_USER_PRO_STATUS_NEVER_BEEN_PRO: return "NEVER_BEEN_PRO"; case SESSION_PRO_BACKEND_USER_PRO_STATUS_ACTIVE: return "ACTIVE"; case SESSION_PRO_BACKEND_USER_PRO_STATUS_EXPIRED: return "EXPIRED"; case SESSION_PRO_BACKEND_USER_PRO_STATUS_COUNT: - // TODO: do we want to assert this cant happen? or should it be allowed? - return "COUNT"; + throw std::invalid_argument("SESSION_PRO_BACKEND_USER_PRO_STATUS_COUNT"); } + UNREACHABLE(); } std::string_view ProBackendEnumToString(SESSION_PRO_BACKEND_GET_PRO_STATUS_ERROR_REPORT v) { @@ -53,9 +54,56 @@ std::string_view ProBackendEnumToString(SESSION_PRO_BACKEND_GET_PRO_STATUS_ERROR case SESSION_PRO_BACKEND_GET_PRO_STATUS_ERROR_REPORT_SUCCESS: return "SUCCESS"; case SESSION_PRO_BACKEND_GET_PRO_STATUS_ERROR_REPORT_GENERIC_ERROR: return "GENERIC_ERROR"; case SESSION_PRO_BACKEND_GET_PRO_STATUS_ERROR_REPORT_COUNT: - // TODO: do we want to assert this cant happen? or should it be allowed? - return "COUNT"; + throw std::invalid_argument("SESSION_PRO_BACKEND_GET_PRO_STATUS_ERROR_REPORT_COUNT"); + } + UNREACHABLE(); +} + +std::string_view proBackendEnumPlanToString(SESSION_PRO_BACKEND_PLAN v) { + switch (v) { + case SESSION_PRO_BACKEND_PLAN_NIL: return "NIL"; + case SESSION_PRO_BACKEND_PLAN_ONE_MONTH: return "ONE_MONTH"; + case SESSION_PRO_BACKEND_PLAN_THREE_MONTHS: return "THREE_MONTHS"; + case SESSION_PRO_BACKEND_PLAN_TWELVE_MONTHS: return "TWELVE_MONTHS"; + case SESSION_PRO_BACKEND_PLAN_COUNT: + throw std::invalid_argument("SESSION_PRO_BACKEND_PLAN_COUNT"); + } + UNREACHABLE(); +} + +std::string_view proBackendEnumPaymentProviderToString(SESSION_PRO_BACKEND_PAYMENT_PROVIDER v) { + switch (v) { + // Note: we want those to map ProOriginatingPlatform keys + case SESSION_PRO_BACKEND_PAYMENT_PROVIDER_NIL: return "Nil"; + case SESSION_PRO_BACKEND_PAYMENT_PROVIDER_GOOGLE_PLAY_STORE: return "GooglePlayStore"; + case SESSION_PRO_BACKEND_PAYMENT_PROVIDER_IOS_APP_STORE: return "iOSAppStore"; + case SESSION_PRO_BACKEND_PAYMENT_PROVIDER_COUNT: + throw std::invalid_argument("SESSION_PRO_BACKEND_PAYMENT_PROVIDER_COUNT"); + } + UNREACHABLE(); +} + +std::string_view proBackendEnumPaymentStatusToString(SESSION_PRO_BACKEND_PAYMENT_STATUS v) { + switch (v) { + case SESSION_PRO_BACKEND_PAYMENT_STATUS_NIL: return "NIL"; + case SESSION_PRO_BACKEND_PAYMENT_STATUS_UNREDEEMED: return "UNREDEEMED"; + case SESSION_PRO_BACKEND_PAYMENT_STATUS_REDEEMED: return "REDEEMED"; + case SESSION_PRO_BACKEND_PAYMENT_STATUS_EXPIRED: return "EXPIRED"; + case SESSION_PRO_BACKEND_PAYMENT_STATUS_REFUNDED: return "REFUNDED"; + case SESSION_PRO_BACKEND_PAYMENT_STATUS_COUNT: + throw std::invalid_argument("SESSION_PRO_BACKEND_PAYMENT_STATUS_COUNT"); } + UNREACHABLE(); } +std::string_view proBackendEnumToString(session::ProFeaturesForMsgStatus v) { + switch (v) { + case session::ProFeaturesForMsgStatus::Success: return "SUCCESS"; + case session::ProFeaturesForMsgStatus::UTFDecodingError: return "UTF_DECODING_ERROR"; + case session::ProFeaturesForMsgStatus::ExceedsCharacterLimit: + return "EXCEEDS_CHARACTER_LIMIT"; + } + UNREACHABLE(); } + +} // namespace session::nodeapi diff --git a/src/user_config.cpp b/src/user_config.cpp index 6f0ed29..aafe90b 100644 --- a/src/user_config.cpp +++ b/src/user_config.cpp @@ -281,8 +281,7 @@ Napi::Value UserConfigWrapper::generateProMasterKey(const Napi::CallbackInfo& in assert_length(ed25519_seed_cpp_hex, 64, "generateProMasterKey"); auto converted = from_hex_to_vector(ed25519_seed_cpp_hex); - auto pro_master_key_hex = - session::ed25519::ed25519_pro_key_pair_for_ed25519_seed(converted); + auto pro_master_key_hex = session::ed25519::ed25519_pro_privkey_for_ed25519_seed(converted); auto obj = Napi::Object::New(info.Env()); obj["proMasterKey"] = toJs(info.Env(), pro_master_key_hex); diff --git a/types/pro/pro.d.ts b/types/pro/pro.d.ts index b531c6b..951c29c 100644 --- a/types/pro/pro.d.ts +++ b/types/pro/pro.d.ts @@ -1,4 +1,4 @@ -/// +/// ; declare module 'libsession_util_nodejs' { type WithProRotatingEd25519PrivKey = { proRotatingEd25519PrivKey: string | null }; @@ -14,10 +14,16 @@ declare module 'libsession_util_nodejs' { type ProFeature = '10K_CHARACTER_LIMIT' | 'PRO_BADGE' | 'ANIMATED_AVATAR'; type ProFeatures = Array; type WithProFeatures = { proFeatures: ProFeatures }; + type WithGenIndexHash = { genIndexHashB64: string }; - type ProProof = { + type WithRequestVersion = { requestVersion: number }; + + type WithUnixTsMs = { + unixTsMs: number; + }; + + type ProProof = WithGenIndexHash & { version: number; - genIndexHashB64: string; /** * HexString, 64 chars */ @@ -25,18 +31,21 @@ declare module 'libsession_util_nodejs' { expiryMs: number; }; - type ProConfig = { + type WithRotatingPrivKeyHex = { /** * 64 bytes, 128 chars */ rotatingPrivKeyHex: string; + }; + + type ProConfig = WithRotatingPrivKeyHex & { proProof: ProProof; }; - type WithProBackendResponse = { - status: number; - errors: Array; - } + // type WithProBackendResponse = { + // status: number; + // errors: Array; + // }; // Must match session-desktop export enum ProOriginatingPlatform { @@ -70,6 +79,89 @@ declare module 'libsession_util_nodejs' { support_url: string; }; + type ProRevocationItem = WithGenIndexHash & { + expiryUnixTsMs: number; + }; + + type WithMasterPrivKeyHex = { masterPrivKeyHex: string }; + + type ProPaymentItem = { + /** + * Describes the current status of the consumption of the payment for Session Pro entitlement + * The status should be used to determine which timestamps should be used. + + * For example, a payment can be in a redeemed state whilst also have a refunded timestamp set + * if the payment was refunded and then the refund was reversed. We preserve all timestamps for + * book-keeping purposes. + */ + status: 'NIL' | 'UNREDEEMED' | 'REDEEMED' | 'EXPIRED' | 'REFUNDED'; + /** + * Session Pro product/plan item that was purchased + */ + plan: 'NIL' | 'ONE_MONTH' | 'THREE_MONTHS' | 'TWELVE_MONTHS'; + /** + * Store front that this particular payment came from + */ + paymentProvider: ProOriginatingPlatform; + /** + * Flag indicating whether or not this payment will automatically bill itself at the end of the + billing cycle. + */ + autoRenewing: boolean; + /** + * Unix timestamp of when the payment was witnessed by the Pro Backend. Always set + */ + unredeemedTsMs: number; + /** + * Unix timestamp of when the payment was redeemed. 0 if not activated + */ + redeemedTsMs: number; + /** + * Unix timestamp of when the payment was expiry. 0 if not activated + */ + expiryTsMs: number; + /** + * Duration of the grace period, e.g. when the payment provider will start to attempt to renew + * the Session Pro subscription. During the period between + * [expiry_unix_ts, expiry_unix_ts + grace_period_duration_ms] the user continues to have + * entitlement to Session Pro. This value is only applicable if `auto_renewing` is `true`. + */ + gracePeriodDurationMs: number; + /** + * Unix deadline timestamp of when the user is able to refund the subscription via the payment + provider. + * Thereafter the user must initiate a refund manually via Session support. + */ + platformRefundExpiryTsMs: number; + /** + * Unix timestamp of when the payment was revoked or refunded. 0 if not applicable. + */ + revokedTsMs: number; + /** + * When payment provider is set to Google Play Store, this is the platform-specific purchase + token. + * This information should be considered as confidential and stored appropriately. + */ + googlePaymentToken: string | null; + /** + * When payment provider is set to iOS App Store, this is the platform-specific original + transaction ID. + * This information should be considered as confidential and stored appropriately. + */ + appleOriginalTxId: string | null; + /** + * When payment provider is set to iOS App Store, this is the platform-specific transaction ID + * This information should be considered as confidential and stored appropriately. + */ + appleTxId: string | null; + /** + * When payment provider is set to iOS App Store, this is the platform-specific web line order + * ID. + * This information should be considered as confidential and stored appropriately. + */ + appleWebLineOrderId: string | null; + }; + type ProWrapper = { proFeaturesForMessage: (args: { utf16: string; @@ -78,65 +170,62 @@ declare module 'libsession_util_nodejs' { * If provided (here) as an input, it will be ignored. */ proFeatures: ProFeatures; - }) => WithProFeatures & { success: boolean; error: string | null; codepointCount: number }; - proProofRequestBody: (args: { - requestVersion: number, - masterPrivkey: Uint8Array, - rotatingPrivkey: Uint8Array, - unixTsMs: number, - } - ) => string; - - proProofResponseBody: (args: { - json: string, - } - ) => WithProBackendResponse & { proof: ProProof | null }; - - proRevocationsRequestBody: (args: { - requestVersion: number, - ticket: number, - } + }) => WithProFeatures & { + status: 'SUCCESS' | 'UTF_DECODING_ERROR' | 'EXCEEDS_CHARACTER_LIMIT'; + }; + proProofRequestBody: ( + args: WithMasterPrivKeyHex & WithRequestVersion & WithUnixTsMs & WithRotatingPrivKeyHex ) => string; - proRevocationsResponseBody: (args: { - json: string, - } - ) => WithProBackendResponse & { - ticket: number | null; - items: Array - }; + // proProofParseResponse: (args: { + // json: string; + // }) => WithProBackendResponse & { proof: ProProof | null }; - proStatusRequestBody: (args: { - requestVersion: number, - masterPrivkey: Uint8Array, - unixTsMs: number, - withPaymentHistory: boolean, - } + /** + * @param version: Request version. The latest accepted version is 0 + * @param ticket: 4-byte monotonic integer for the caller's revocation list iteration. Set to 0 if unknown; otherwise, use the latest known `ticket` from a prior `GetProRevocationsResponse` to allow + the Session Pro Backend to omit the revocation list if it has not changed. + * @returns the stringified body to include in the request + */ + proRevocationsRequestBody: (args: WithRequestVersion & { ticket: number }) => string; + + // proRevocationsParseResponse: (args: { json: string }) => WithProBackendResponse & { + // ticket: number | null; + // items: Array | null; + // }; + + proStatusRequestBody: ( + args: WithMasterPrivKeyHex & + WithRequestVersion & + WithUnixTsMs & { + withPaymentHistory: boolean; + } ) => string; - proStatusResponseBody: (args: { - json: string, - } - ) => WithProBackendResponse & { - ticket: number | null; - items: Array; - userStatus: number; - errorReport: number; - autoRenewing: boolean; - expiryUnixTsMs: number; - gracePeriodDurationMs: number; - }; - + // proStatusParseResponse: (args: { json: string }) => WithProBackendResponse & { + // ticket: number | null; + // items: Array; + // userStatus: number; + // errorReport: number; + // autoRenewing: boolean; + // expiryUnixTsMs: number; + // gracePeriodDurationMs: number; + // }; }; export type ProActionsCalls = MakeWrapperActionCalls; - /** * To be used inside the web worker only (calls are synchronous and won't work asynchronously) */ export class ProWrapperNode { public static proFeaturesForMessage: ProWrapper['proFeaturesForMessage']; + public static proProofRequestBody: ProWrapper['proProofRequestBody']; + public static proRevocationsRequestBody: ProWrapper['proRevocationsRequestBody']; + public static proStatusRequestBody: ProWrapper['proStatusRequestBody']; + // public static proProofParseResponse: ProWrapper['proProofParseResponse']; + // public static proRevocationsParseResponse: ProWrapper['proRevocationsParseResponse']; + // public static proStatusParseResponse: ProWrapper['proStatusParseResponse']; } /** @@ -144,5 +233,12 @@ declare module 'libsession_util_nodejs' { * You should never need to import them in Session directly * You will need to add an entry here if you add a new function */ - export type ProActionsType = MakeActionCall; + export type ProActionsType = + | MakeActionCall + | MakeActionCall + | MakeActionCall + | MakeActionCall; + // | MakeActionCall + // | MakeActionCall + // | MakeActionCall }