Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 12 additions & 138 deletions include/pro/pro.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <napi.h>
#include <oxenc/base64.h>
#include <oxenc/hex.h>

Expand Down Expand Up @@ -110,22 +111,6 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
"proStatusRequestBody",
static_cast<napi_property_attributes>(
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_property_attributes>(
// napi_writable | napi_configurable)),
// StaticMethod<&ProWrapper::proRevocationsParseResponse>(
// "proRevocationsParseResponse",
// static_cast<napi_property_attributes>(
// napi_writable | napi_configurable)),
// StaticMethod<&ProWrapper::proStatusParseResponse>(
// "proStatusParseResponse",
// static_cast<napi_property_attributes>(
// napi_writable | napi_configurable)),
});
}

Expand All @@ -135,7 +120,7 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
// we expect two arguments that match:
// first: {
// "utf16": string,
// "proFeatures": Array<ProFeature>,
// "proFeaturesBitset": bigint,
// }

assertInfoLength(info, 1);
Expand All @@ -147,27 +132,13 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
if (first.IsEmpty())
throw std::invalid_argument("proFeaturesForMessage first received empty");

assertIsArray(first.Get("proFeatures"), "proFeaturesForMessage.proFeatures");
auto proFeaturesJS = first.Get("proFeatures").As<Napi::Array>();
std::vector<std::string> proFeatures;
proFeatures.reserve(proFeaturesJS.Length());
for (uint32_t i = 0; i < proFeaturesJS.Length(); i++) {
auto itemValue = proFeaturesJS.Get(i);
assertIsString(itemValue, "proFeaturesForMessage.proFeatures.itemValue");
std::string item =
toCppString(itemValue, "proFeaturesForMessage.proFeatures.itemValue");
proFeatures.push_back(item);
}

SESSION_PROTOCOL_PRO_EXTRA_FEATURES flags = 0;
for (std::string& feature : proFeatures) {
// Note: 10K_CHARACTER_LIMIT cannot be requested by the caller
if (feature == "PRO_BADGE") {
flags |= SESSION_PROTOCOL_PRO_EXTRA_FEATURES_PRO_BADGE;
} else if (feature == "ANIMATED_AVATAR") {
flags |= SESSION_PROTOCOL_PRO_EXTRA_FEATURES_ANIMATED_AVATAR;
}
}
assertIsBigint(
first.Get("proFeaturesBitset"), "proFeaturesForMessage.proFeaturesBitset");

auto lossless = true;
SESSION_PROTOCOL_PRO_FEATURES flags =
first.Get("proFeaturesBitset").As<Napi::BigInt>().Uint64Value(&lossless);

assertIsString(first.Get("utf16"), "proFeaturesForMessage.utf16");
std::u16string utf16 = first.Get("utf16").As<Napi::String>().Utf16Value();
auto pro_features_msg =
Expand All @@ -179,7 +150,7 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
obj["error"] =
pro_features_msg.error.size() ? toJs(env, pro_features_msg.error) : env.Null();
obj["codepointCount"] = toJs(env, pro_features_msg.codepoint_count);
obj["proFeatures"] = proFeaturesToJs(env, pro_features_msg.features);
obj["proFeaturesBitset"] = proFeaturesToJsBitset(env, pro_features_msg.features);

return obj;
});
Expand Down Expand Up @@ -219,9 +190,6 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
std::string rotating_privkey =
toCppString(rotating_privkey_js, "proProofRequestBody.rotatingPrivKeyHex");

assert_length(master_privkey, 64, "masterPrivKeyHex");
assert_length(rotating_privkey, 64, "rotatingPrivkey");

auto master_privkey_decoded = from_hex(master_privkey);
auto rotating_privkey_decoded = from_hex(rotating_privkey);

Expand All @@ -235,33 +203,6 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
});
};

static Napi::Value proProofParseResponse(const Napi::CallbackInfo& info) {
return wrapResult(info, [&] {
// we expect arguments that match:
// first: {
// "json": string,
// }

assertInfoLength(info, 1);
assertIsObject(info[0]);
auto env = info.Env();

auto first = info[0].As<Napi::Object>();

if (first.IsEmpty())
throw std::invalid_argument("proProofParseResponse first received empty");

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 = toJs(env, static_cast<pro_backend::ResponseHeader>(parsed));
obj["proof"] = toJsOrNullIfErrors(env, parsed.proof, parsed.errors);

return obj;
});
};

static Napi::Value proRevocationsRequestBody(const Napi::CallbackInfo& info) {
return wrapResult(info, [&] {
// we expect arguments that match:
Expand Down Expand Up @@ -293,35 +234,6 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
});
};

static Napi::Value proRevocationsParseResponse(const Napi::CallbackInfo& info) {
return wrapResult(info, [&] {
// we expect arguments that match:
// first: {
// "json": string,
// }

assertInfoLength(info, 1);
assertIsObject(info[0]);
auto env = info.Env();

auto first = info[0].As<Napi::Object>();

if (first.IsEmpty())
throw std::invalid_argument("proRevocationsParseResponse first received empty");

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 = toJs(env, static_cast<pro_backend::ResponseHeader>(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;
});
};

static Napi::Value proStatusRequestBody(const Napi::CallbackInfo& info) {
return wrapResult(info, [&] {
// we expect arguments that match:
Expand Down Expand Up @@ -355,55 +267,17 @@ class ProWrapper : public Napi::ObjectWrap<ProWrapper> {
auto master_privkey =
toCppString(master_privkey_js, "proStatusRequestBody.masterPrivKeyHex");

assert_length(master_privkey, 64, "proStatusRequestBody.masterPrivKeyHex");
auto master_privkey_decoded = from_hex(master_privkey);

auto json = pro_backend::GetProStatusRequest::build_to_json(
static_cast<uint8_t>(requestVersion.Int32Value()),
to_span(from_hex(master_privkey)),
to_span(master_privkey_decoded),
unix_ts_ms,
withPaymentHistory);

return json;
});
};

static Napi::Value proStatusParseResponse(const Napi::CallbackInfo& info) {
return wrapResult(info, [&] {
// we expect arguments that match:
// first: {
// "json": string,
// }

assertInfoLength(info, 1);
assertIsObject(info[0]);
auto env = info.Env();

auto first = info[0].As<Napi::Object>();

if (first.IsEmpty())
throw std::invalid_argument("proStatusParseResponse first received empty");

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 = toJs(env, static_cast<pro_backend::ResponseHeader>(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["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;
});
};
};

}; // namespace session::nodeapi
3 changes: 2 additions & 1 deletion include/pro/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct toJs_impl<session::ProProof> {
obj["genIndexHashB64"] = toJs(env, oxenc::to_base64(pro_proof.gen_index_hash));
obj["rotatingPubkeyHex"] = toJs(env, oxenc::to_hex(pro_proof.rotating_pubkey));
obj["expiryMs"] = toJs(env, pro_proof.expiry_unix_ts.time_since_epoch().count());
obj["signatureHex"] = toJs(env, oxenc::to_hex(pro_proof.sig));

return obj;
}
Expand Down Expand Up @@ -77,7 +78,7 @@ struct toJs_impl<session::DecodedPro> {
: decoded_pro.status == ProStatus::Valid ? "Valid"
: "Expired");
obj["proProof"] = toJs(env, decoded_pro.proof);
obj["proFeatures"] = proFeaturesToJs(env, decoded_pro.features);
obj["proFeaturesBitset"] = proFeaturesToJsBitset(env, decoded_pro.features);

return obj;
}
Expand Down
9 changes: 9 additions & 0 deletions include/user_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <napi.h>

#include "base_config.hpp"
#include "session/config/pro.hpp"
#include "session/config/user_profile.hpp"
#include "utilities.hpp"

Expand All @@ -15,6 +16,10 @@ class UserConfigWrapper : public ConfigBaseImpl, public Napi::ObjectWrap<UserCon
explicit UserConfigWrapper(const Napi::CallbackInfo& info);

private:
// FIXME: wrap those in the extra data field of UserConfig instead
std::optional<config::ProConfig> pro_config;
int64_t pro_user_features = 0;

config::UserProfile& config{get_config<config::UserProfile>()};

Napi::Value getPriority(const Napi::CallbackInfo& info);
Expand All @@ -37,6 +42,10 @@ class UserConfigWrapper : public ConfigBaseImpl, public Napi::ObjectWrap<UserCon

Napi::Value getProConfig(const Napi::CallbackInfo& info);
void setProConfig(const Napi::CallbackInfo& info);
Napi::Value getProFeaturesBitset(const Napi::CallbackInfo& info);
void setProFeaturesBitset(const Napi::CallbackInfo& info);

Napi::Value generateProMasterKey(const Napi::CallbackInfo& info);
Napi::Value generateRotatingPrivKeyHex(const Napi::CallbackInfo& info);
};
}; // namespace session::nodeapi
28 changes: 18 additions & 10 deletions include/utilities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ void assertInfoMinLength(const Napi::CallbackInfo& info, const int minLength);

void assertIsStringOrNull(const Napi::Value& value, const std::string& identifier = "");
void assertIsNumber(const Napi::Value& value, const std::string& identifier);
void assertIsBigint(const Napi::Value& val, const std::string& identifier);
void assertIsArray(const Napi::Value& value, const std::string& identifier);
void assertIsObject(const Napi::Value& value);
void assertIsUInt8ArrayOrNull(const Napi::Value& value);
Expand Down Expand Up @@ -72,6 +73,12 @@ std::span<const unsigned char> toCppBufferView(Napi::Value x, const std::string&
std::vector<unsigned char> toCppBuffer(Napi::Value x, const std::string& identifier);

int64_t toCppInteger(Napi::Value x, const std::string& identifier, bool allowUndefined = false);

/**
* Same as toCppInteger, but for BigInt
*/
int64_t toCppIntegerB(Napi::Value x, const std::string& identifier, bool allowUndefined = false);

std::optional<int64_t> maybeNonemptyInt(Napi::Value x, const std::string& identifier);
std::optional<bool> maybeNonemptyBoolean(Napi::Value x, const std::string& identifier);
std::optional<std::chrono::sys_seconds> maybeNonemptySysSeconds(
Expand Down Expand Up @@ -394,7 +401,8 @@ Napi::Object decrypt_result_to_JS(

confirm_pushed_entry_t confirm_pushed_entry_from_JS(const Napi::Env& env, const Napi::Object& obj);

Napi::Object proFeaturesToJs(const Napi::Env& env, const SESSION_PROTOCOL_PRO_FEATURES bitset);
Napi::BigInt proFeaturesToJsBitset(
const Napi::Env& env, const SESSION_PROTOCOL_PRO_FEATURES bitset);

std::span<const uint8_t> from_hex_to_span(std::string_view x);

Expand All @@ -405,10 +413,11 @@ template <std::size_t N>
std::array<uint8_t, N> from_hex_to_array(std::string x) {
std::string as_hex = oxenc::from_hex(x);
if (as_hex.size() != N) {
throw std::invalid_argument(fmt::format(
"from_hex_to_array: Decoded hex size mismatch: expected {}, got {}",
N,
as_hex.size()));
throw std::invalid_argument(
fmt::format(
"from_hex_to_array: Decoded hex size mismatch: expected {}, got {}",
N,
as_hex.size()));
}

std::array<uint8_t, N> result;
Expand All @@ -424,16 +433,15 @@ std::vector<unsigned char> from_base64_to_vector(std::string_view x);
// Concept to match containers with a size() method
template <typename T>
concept HasSize = requires(T t) {
{
t.size()
} -> std::convertible_to<size_t>;
{t.size()}->std::convertible_to<size_t>;
};

template <HasSize T>
void assert_length(const T& x, size_t n, std::string_view base_identifier) {
if (x.size() != n) {
throw std::invalid_argument(fmt::format(
"assert_length: expected {}, got {} for {}", n, x.size(), base_identifier));
throw std::invalid_argument(
fmt::format(
"assert_length: expected {}, got {} for {}", n, x.size(), base_identifier));
}
}

Expand Down
Loading
Loading