From b7a686271cdd489680a7c2ba95a98225e3f78999 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 3 Sep 2025 11:02:34 +0200 Subject: [PATCH 1/5] feat: add timestamp to track when a profile was updated so we can always show the correct one --- include/contacts_config.hpp | 1 + include/groups/meta_group_wrapper.hpp | 1 + include/user_config.hpp | 4 +++- include/utilities.hpp | 9 ++++++++ libsession-util | 2 +- package.json | 2 +- src/contacts_config.cpp | 31 +++++++++++++++++++++++++++ src/groups/meta_group_wrapper.cpp | 21 ++++++++++++++++++ src/user_config.cpp | 26 ++++++++++++++++++++-- src/utilities.cpp | 12 +++++++++++ types/blinding/blinding.d.ts | 1 - types/groups/groupmembers.d.ts | 3 +++ types/groups/metagroup.d.ts | 2 ++ types/user/contacts.d.ts | 13 ++++++++++- types/user/userconfig.d.ts | 20 ++++++++++++++--- 15 files changed, 138 insertions(+), 10 deletions(-) diff --git a/include/contacts_config.hpp b/include/contacts_config.hpp index 7074da4..9741e2b 100644 --- a/include/contacts_config.hpp +++ b/include/contacts_config.hpp @@ -20,6 +20,7 @@ class ContactsConfigWrapper : public ConfigBaseImpl, Napi::Value get(const Napi::CallbackInfo& info); Napi::Value getAll(const Napi::CallbackInfo& info); void set(const Napi::CallbackInfo& info); + Napi::Value setProfileUpdatedSeconds(const Napi::CallbackInfo& info); Napi::Value erase(const Napi::CallbackInfo& info); }; diff --git a/include/groups/meta_group_wrapper.hpp b/include/groups/meta_group_wrapper.hpp index 9b53ec0..c194e8e 100644 --- a/include/groups/meta_group_wrapper.hpp +++ b/include/groups/meta_group_wrapper.hpp @@ -72,6 +72,7 @@ class MetaGroupWrapper : public Napi::ObjectWrap { void memberSetPromotionFailed(const Napi::CallbackInfo& info); void memberSetPromotionAccepted(const Napi::CallbackInfo& info); void memberSetProfilePicture(const Napi::CallbackInfo& info); + void memberSetProfileUpdatedSeconds(const Napi::CallbackInfo& info); Napi::Value memberResetAllSendingState(const Napi::CallbackInfo& info); void memberSetSupplement(const Napi::CallbackInfo& info); Napi::Value memberEraseAndRekey(const Napi::CallbackInfo& info); diff --git a/include/user_config.hpp b/include/user_config.hpp index 271fd1e..d853a86 100644 --- a/include/user_config.hpp +++ b/include/user_config.hpp @@ -23,7 +23,9 @@ class UserConfigWrapper : public ConfigBaseImpl, public Napi::ObjectWrap toCppBuffer(Napi::Value x, const std::string& identif int64_t toCppInteger(Napi::Value x, const std::string& identifier, bool allowUndefined = false); std::optional maybeNonemptyInt(Napi::Value x, const std::string& identifier); std::optional maybeNonemptyBoolean(Napi::Value x, const std::string& identifier); +std::optional maybeNonemptySysSeconds( + Napi::Value x, const std::string& identifier); bool toCppBoolean(Napi::Value x, const std::string& identifier); @@ -109,6 +111,13 @@ struct toJs_impl { } }; +template <> +struct toJs_impl { + auto operator()(const Napi::Env& env, std::chrono::sys_seconds t) const { + return Napi::Number::New(env, t.time_since_epoch().count()); + } +}; + template struct toJs_impl>> { auto operator()(const Napi::Env& env, T n) const { return Napi::Number::New(env, n); } diff --git a/libsession-util b/libsession-util index 95b9fe7..9f25429 160000 --- a/libsession-util +++ b/libsession-util @@ -1 +1 @@ -Subproject commit 95b9fe74d1c419487f6e69458db1ebc2bb824a2a +Subproject commit 9f25429935a6c6dffb26c82d9eb258ad1873a80f diff --git a/package.json b/package.json index cc4c388..f37bb27 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "main": "index.js", "name": "libsession_util_nodejs", "description": "Wrappers for the Session Util Library", - "version": "0.5.5", + "version": "0.5.6", "license": "GPL-3.0", "author": { "name": "Oxen Project", diff --git a/src/contacts_config.cpp b/src/contacts_config.cpp index 19c9593..6d4a1df 100644 --- a/src/contacts_config.cpp +++ b/src/contacts_config.cpp @@ -44,6 +44,7 @@ struct toJs_impl { obj["nickname"] = toJs(env, maybe_string(contact.nickname)); obj["approved"] = toJs(env, contact.approved); obj["approvedMe"] = toJs(env, contact.approved_me); + obj["profileUpdatedSeconds"] = toJs(env, contact.profile_updated); obj["blocked"] = toJs(env, contact.blocked); obj["priority"] = toJs(env, contact.priority); obj["createdAtSeconds"] = toJs(env, contact.created); @@ -64,7 +65,11 @@ void ContactsConfigWrapper::Init(Napi::Env env, Napi::Object exports) { InstanceMethod("get", &ContactsConfigWrapper::get), InstanceMethod("getAll", &ContactsConfigWrapper::getAll), InstanceMethod("set", &ContactsConfigWrapper::set), + InstanceMethod( + "setProfileUpdatedSeconds", + &ContactsConfigWrapper::setProfileUpdatedSeconds), InstanceMethod("erase", &ContactsConfigWrapper::erase), + }); } @@ -150,6 +155,32 @@ void ContactsConfigWrapper::set(const Napi::CallbackInfo& info) { }); } +Napi::Value ContactsConfigWrapper::setProfileUpdatedSeconds(const Napi::CallbackInfo& info) { + return wrapResult(info, [&] { + assertInfoLength(info, 1); + + auto arg = info[0]; + assertIsObject(arg); + auto obj = arg.As(); + + if (obj.IsEmpty()) + throw std::invalid_argument("setProfileUpdatedSeconds received empty"); + + auto sessionID = toCppString(obj.Get("id"), "contacts.setProfileUpdatedSeconds, id"); + auto contact = config.get(sessionID); + + auto seconds = maybeNonemptySysSeconds( + obj.Get("profileUpdatedSeconds"), + "contacts.setProfileUpdatedSeconds, profileUpdatedSeconds"); + if (contact && seconds) { + config.set_profile_updated(sessionID, seconds.value()); + config.set(*contact); + return true; + } + return false; + }); +} + /** ============================== * ERASERS * ============================== */ diff --git a/src/groups/meta_group_wrapper.cpp b/src/groups/meta_group_wrapper.cpp index 77b703f..078ab0b 100644 --- a/src/groups/meta_group_wrapper.cpp +++ b/src/groups/meta_group_wrapper.cpp @@ -17,6 +17,7 @@ Napi::Object member_to_js(const Napi::Env& env, const member& info, const member obj["pubkeyHex"] = toJs(env, info.session_id); obj["name"] = toJs(env, info.name); obj["profilePicture"] = toJs(env, info.profile_picture); + obj["profileUpdatedSeconds"] = toJs(env, info.profile_updated); obj["supplement"] = toJs(env, info.supplement); switch (status) { @@ -131,6 +132,9 @@ void MetaGroupWrapper::Init(Napi::Env env, Napi::Object exports) { &MetaGroupWrapper::memberSetPromotionAccepted), InstanceMethod( "memberSetProfilePicture", &MetaGroupWrapper::memberSetProfilePicture), + InstanceMethod( + "memberSetProfileUpdatedSeconds", + &MetaGroupWrapper::memberSetProfileUpdatedSeconds), InstanceMethod( "memberResetAllSendingState", &MetaGroupWrapper::memberResetAllSendingState), @@ -669,6 +673,23 @@ void MetaGroupWrapper::memberSetProfilePicture(const Napi::CallbackInfo& info) { }); } +void MetaGroupWrapper::memberSetProfileUpdatedSeconds(const Napi::CallbackInfo& info) { + wrapExceptions(info, [&] { + assertInfoLength(info, 2); + assertIsString(info[0]); + assertIsObject(info[1]); + + auto pubkeyHex = toCppString(info[0], "memberSetProfiUpdatedSeconds"); + auto updatedAtSeconds = maybeNonemptySysSeconds(info[1], "memberSetProfiUpdatedSeconds"); + + auto m = this->meta_group->members->get(pubkeyHex); + if (m) { + m->profile_updated = updatedAtSeconds.value(); + this->meta_group->members->set(*m); + } + }); +} + Napi::Value MetaGroupWrapper::memberResetAllSendingState(const Napi::CallbackInfo& info) { return wrapResult(info, [&] { bool changed = false; diff --git a/src/user_config.cpp b/src/user_config.cpp index 4f1937c..c24c45a 100644 --- a/src/user_config.cpp +++ b/src/user_config.cpp @@ -20,10 +20,15 @@ void UserConfigWrapper::Init(Napi::Env env, Napi::Object exports) { InstanceMethod("getPriority", &UserConfigWrapper::getPriority), InstanceMethod("getName", &UserConfigWrapper::getName), InstanceMethod("getProfilePic", &UserConfigWrapper::getProfilePic), + InstanceMethod( + "getProfileUpdatedSeconds", + &UserConfigWrapper::getProfileUpdatedSeconds), + InstanceMethod( + "setReuploadProfilePic", &UserConfigWrapper::setReuploadProfilePic), InstanceMethod("setPriority", &UserConfigWrapper::setPriority), InstanceMethod("setName", &UserConfigWrapper::setName), InstanceMethod("setNameTruncated", &UserConfigWrapper::setNameTruncated), - InstanceMethod("setProfilePic", &UserConfigWrapper::setProfilePic), + InstanceMethod("setNewProfilePic", &UserConfigWrapper::setNewProfilePic), InstanceMethod( "getEnableBlindedMsgRequest", &UserConfigWrapper::getEnableBlindedMsgRequest), @@ -107,7 +112,7 @@ void UserConfigWrapper::setNameTruncated(const Napi::CallbackInfo& info) { }); } -void UserConfigWrapper::setProfilePic(const Napi::CallbackInfo& info) { +void UserConfigWrapper::setNewProfilePic(const Napi::CallbackInfo& info) { return wrapExceptions(info, [&] { assertInfoLength(info, 1); auto profile_pic_obj = info[0]; @@ -119,6 +124,23 @@ void UserConfigWrapper::setProfilePic(const Napi::CallbackInfo& info) { }); } +void UserConfigWrapper::setReuploadProfilePic(const Napi::CallbackInfo& info) { + assertInfoLength(info, 1); + auto profile_pic_obj = info[0]; + + if (!profile_pic_obj.IsNull() && !profile_pic_obj.IsUndefined()) + assertIsObject(profile_pic_obj); + + config.set_reupload_profile_pic(profile_pic_from_object(profile_pic_obj)); +} + +Napi::Value UserConfigWrapper::getProfileUpdatedSeconds(const Napi::CallbackInfo& info) { + return wrapResult(info, [&] { + auto env = info.Env(); + return config.get_profile_updated(); + }); +} + Napi::Value UserConfigWrapper::getEnableBlindedMsgRequest(const Napi::CallbackInfo& info) { return wrapResult(info, [&] { auto env = info.Env(); diff --git a/src/utilities.cpp b/src/utilities.cpp index 089aab0..61d88d8 100644 --- a/src/utilities.cpp +++ b/src/utilities.cpp @@ -138,6 +138,18 @@ std::optional maybeNonemptyInt(Napi::Value x, const std::string& identi throw std::invalid_argument{"maybeNonemptyInt with invalid type, called from " + identifier}; } +std::optional maybeNonemptySysSeconds(Napi::Value x, const std::string& identifier) { + if (x.IsNull() || x.IsUndefined()) + return std::nullopt; + + if (x.IsNumber()) { + auto num = x.As().Int64Value(); + return std::chrono::sys_seconds{std::chrono::seconds{num}}; + } + + throw std::invalid_argument{"maybeNonemptyTime with invalid type, called from " + identifier}; +} + std::optional maybeNonemptyBoolean(Napi::Value x, const std::string& identifier) { if (x.IsNull() || x.IsUndefined()) return std::nullopt; diff --git a/types/blinding/blinding.d.ts b/types/blinding/blinding.d.ts index 7ab01f0..fcad107 100644 --- a/types/blinding/blinding.d.ts +++ b/types/blinding/blinding.d.ts @@ -34,7 +34,6 @@ declare module 'libsession_util_nodejs' { */ export class BlindingWrapperNode { public static blindVersionPubkey: BlindingWrapper['blindVersionPubkey']; - public static blindVersionSignRequest: BlindingWrapper['blindVersionSignRequest']; public static blindVersionSign: BlindingWrapper['blindVersionSign']; } diff --git a/types/groups/groupmembers.d.ts b/types/groups/groupmembers.d.ts index 6cbe676..4c7bbd0 100644 --- a/types/groups/groupmembers.d.ts +++ b/types/groups/groupmembers.d.ts @@ -61,6 +61,8 @@ declare module 'libsession_util_nodejs' { */ supplement: boolean; + profileUpdatedSeconds: number; + /** * True if the member is scheduled to get the keys (`.admin` field of libsession). * This is equivalent of memberStatus being one of: @@ -116,6 +118,7 @@ declare module 'libsession_util_nodejs' { memberSetPromotionAccepted: (pubkeyHex: PubkeyType) => void; memberSetProfilePicture: (pubkeyHex: PubkeyType, profilePicture: ProfilePicture) => void; + memberSetProfileUpdatedSeconds: (pubkeyHex: PubkeyType, profileUpdatedSeconds: number) => void; memberResetAllSendingState: () => boolean; memberSetSupplement: (pubkeyHex: PubkeyType) => void; membersMarkPendingRemoval: (members: Array, withMessages: boolean) => void; diff --git a/types/groups/metagroup.d.ts b/types/groups/metagroup.d.ts index 1231955..765c8e4 100644 --- a/types/groups/metagroup.d.ts +++ b/types/groups/metagroup.d.ts @@ -108,6 +108,7 @@ declare module 'libsession_util_nodejs' { public memberEraseAndRekey: MetaGroupWrapper['memberEraseAndRekey']; public membersMarkPendingRemoval: MetaGroupWrapper['membersMarkPendingRemoval']; public memberSetProfilePicture: MetaGroupWrapper['memberSetProfilePicture']; + public memberSetProfileUpdatedSeconds: MetaGroupWrapper['memberSetProfileUpdatedSeconds']; public memberResetAllSendingState: MetaGroupWrapper['memberResetAllSendingState']; public memberSetSupplement: MetaGroupWrapper['memberSetSupplement']; @@ -159,6 +160,7 @@ declare module 'libsession_util_nodejs' { | MakeActionCall | MakeActionCall | MakeActionCall + | MakeActionCall | MakeActionCall | MakeActionCall diff --git a/types/user/contacts.d.ts b/types/user/contacts.d.ts index 8fb8164..9095d02 100644 --- a/types/user/contacts.d.ts +++ b/types/user/contacts.d.ts @@ -12,6 +12,7 @@ declare module 'libsession_util_nodejs' { free: () => void; get: (pubkeyHex: string) => ContactInfo | null; set: (contact: ContactInfoSet) => void; + setProfileUpdatedSeconds: (pubkeyHex: string, profileUpdatedSeconds: number) => void; getAll: () => Array; erase: (pubkeyHex: string) => void; }; @@ -29,7 +30,10 @@ declare module 'libsession_util_nodejs' { name?: string; nickname?: string; profilePicture?: ProfilePicture; - createdAtSeconds: number; // can only be set the first time a contact is created, a new change won't override the value in the wrapper. + /** + * Can only be set the first time a contact is created, a new change won't override the value in the wrapper. + */ + createdAtSeconds: number; expirationMode?: DisappearingMessageConversationModeType; expirationTimerSeconds?: number; }; @@ -44,12 +48,18 @@ declare module 'libsession_util_nodejs' { approved: boolean; approvedMe: boolean; blocked: boolean; + /** + * This should be bumped whenever the contact profile is updated through + * `setProfileUpdatedSeconds`. + */ + profileUpdatedSeconds: number; }; export class ContactsConfigWrapperNode extends BaseConfigWrapperNode { constructor(secretKey: Uint8Array, dump: Uint8Array | null); public get: ContactsWrapper['get']; public set: ContactsWrapper['set']; + public setProfileUpdatedSeconds: ContactsWrapper['setProfileUpdatedSeconds']; public getAll: ContactsWrapper['getAll']; public erase: ContactsWrapper['erase']; } @@ -59,6 +69,7 @@ declare module 'libsession_util_nodejs' { | MakeActionCall | MakeActionCall | MakeActionCall + | MakeActionCall | MakeActionCall | MakeActionCall; } diff --git a/types/user/userconfig.d.ts b/types/user/userconfig.d.ts index 95ac407..78331e5 100644 --- a/types/user/userconfig.d.ts +++ b/types/user/userconfig.d.ts @@ -17,7 +17,17 @@ declare module 'libsession_util_nodejs' { setPriority: (priority: number) => void; setName: (name: string) => void; setNameTruncated: (name: string) => void; - setProfilePic: (pic: ProfilePicture) => void; + /** + * Call this when uploading a new profile picture (i.e. not an auto reupload) + */ + setNewProfilePic: (pic: ProfilePicture) => void; + /** + * Call this when reuploading a the previous profile picture + */ + setReuploadProfilePic: (pic: ProfilePicture) => void; + + getProfileUpdatedSeconds: () => number; + setEnableBlindedMsgRequest: (msgRequest: boolean) => void; getEnableBlindedMsgRequest: () => boolean | undefined; setNoteToSelfExpiry: (expirySeconds: number) => void; @@ -40,7 +50,9 @@ declare module 'libsession_util_nodejs' { public setPriority: UserConfigWrapper['setPriority']; public setName: UserConfigWrapper['setName']; public setNameTruncated: UserConfigWrapper['setNameTruncated']; - public setProfilePic: UserConfigWrapper['setProfilePic']; + public setNewProfilePic: UserConfigWrapper['setNewProfilePic']; + public setReuploadProfilePic: UserConfigWrapper['setReuploadProfilePic']; + public getProfileUpdatedSeconds: UserConfigWrapper['getProfileUpdatedSeconds']; public getEnableBlindedMsgRequest: UserConfigWrapper['getEnableBlindedMsgRequest']; public setEnableBlindedMsgRequest: UserConfigWrapper['setEnableBlindedMsgRequest']; public getNoteToSelfExpiry: UserConfigWrapper['getNoteToSelfExpiry']; @@ -61,7 +73,9 @@ declare module 'libsession_util_nodejs' { | MakeActionCall | MakeActionCall | MakeActionCall - | MakeActionCall + | MakeActionCall + | MakeActionCall + | MakeActionCall | MakeActionCall | MakeActionCall | MakeActionCall From 86dc87edd59255b305c469df0fa06eaac0344df2 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 5 Sep 2025 07:14:44 +0200 Subject: [PATCH 2/5] feat: add tracking of auto reuploaded/newly uploaded profilepic --- include/profile_pic.hpp | 16 ----------- include/user_config.hpp | 2 ++ include/utilities.hpp | 36 +++++++++++++++++++----- libsession-util | 2 +- src/user_config.cpp | 56 ++++++++++++++++++++++++++++++++++++++ src/utilities.cpp | 50 ++++++++++++++++++++++++++++++++-- types/user/userconfig.d.ts | 20 ++++++++++++++ 7 files changed, 155 insertions(+), 27 deletions(-) diff --git a/include/profile_pic.hpp b/include/profile_pic.hpp index c959571..e1b2c67 100644 --- a/include/profile_pic.hpp +++ b/include/profile_pic.hpp @@ -5,22 +5,6 @@ namespace session::nodeapi { -// Returns {"url": "...", "key": buffer} object; both values will be Null if the pic is not set. - -template <> -struct toJs_impl { - Napi::Object operator()(const Napi::Env& env, const config::profile_pic& pic) { - auto obj = Napi::Object::New(env); - if (pic) { - obj["url"] = toJs(env, pic.url); - obj["key"] = toJs(env, pic.key); - } else { - obj["url"] = env.Null(); - obj["key"] = env.Null(); - } - return obj; - } -}; // Constructs a profile_pic from a Napi::Value which must be either Null or an Object; if an // Object then it *must* contain "url" (string or null) and "key" (uint8array of size 32 or diff --git a/include/user_config.hpp b/include/user_config.hpp index d853a86..0d1285b 100644 --- a/include/user_config.hpp +++ b/include/user_config.hpp @@ -24,6 +24,8 @@ class UserConfigWrapper : public ConfigBaseImpl, public Napi::ObjectWrap #include "session/config/namespaces.hpp" +#include "session/config/profile_pic.hpp" #include "session/types.hpp" #include "utilities.hpp" @@ -111,13 +112,6 @@ struct toJs_impl { } }; -template <> -struct toJs_impl { - auto operator()(const Napi::Env& env, std::chrono::sys_seconds t) const { - return Napi::Number::New(env, t.time_since_epoch().count()); - } -}; - template struct toJs_impl>> { auto operator()(const Napi::Env& env, T n) const { return Napi::Number::New(env, n); } @@ -185,6 +179,30 @@ struct toJs_impl> { } }; +template <> +struct toJs_impl { + auto operator()(const Napi::Env& env, std::chrono::sys_seconds 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 <> +struct toJs_impl { + auto operator()(const Napi::Env& env, const config::profile_pic& pic) const { + auto obj = Napi::Object::New(env); + if (pic) { + obj["url"] = toJs(env, pic.url); + obj["key"] = toJs(env, pic.key); + } else { + obj["url"] = env.Null(); + obj["key"] = env.Null(); + } + return obj; + } +}; + // Helper for various "get_all" functions that copy [it...end) into a Napi::Array via toJs(). // Throws a Napi::Error on any exception. template @@ -271,6 +289,10 @@ std::string printable(std::span x); * Keep the current priority if a wrapper */ int64_t toPriority(Napi::Value x, int64_t currentPriority); +int64_t toPriority(int64_t newPriority, int64_t currentPriority); + +std::optional maybeNonemptyProfilePic( + Napi::Value x, const std::string& identifier); int64_t unix_timestamp_now(); diff --git a/libsession-util b/libsession-util index 9f25429..700915c 160000 --- a/libsession-util +++ b/libsession-util @@ -1 +1 @@ -Subproject commit 9f25429935a6c6dffb26c82d9eb258ad1873a80f +Subproject commit 700915ce0912c0c2a2c9c521b1c2f80a8c8a44e7 diff --git a/src/user_config.cpp b/src/user_config.cpp index c24c45a..ce6e6c7 100644 --- a/src/user_config.cpp +++ b/src/user_config.cpp @@ -28,6 +28,7 @@ void UserConfigWrapper::Init(Napi::Env env, Napi::Object exports) { InstanceMethod("setPriority", &UserConfigWrapper::setPriority), InstanceMethod("setName", &UserConfigWrapper::setName), InstanceMethod("setNameTruncated", &UserConfigWrapper::setNameTruncated), + InstanceMethod("setUserConfig", &UserConfigWrapper::setUserConfig), InstanceMethod("setNewProfilePic", &UserConfigWrapper::setNewProfilePic), InstanceMethod( "getEnableBlindedMsgRequest", @@ -74,6 +75,61 @@ Napi::Value UserConfigWrapper::getProfilePic(const Napi::CallbackInfo& info) { }); } +void UserConfigWrapper::setUserConfig(const Napi::CallbackInfo& info) { + return wrapExceptions(info, [&] { + auto env = info.Env(); + assertInfoLength(info, 1); + auto configObj = info[0]; + assertIsObject(configObj); + + auto obj = configObj.As(); + if (obj.IsEmpty()) { + return; + } + // Handle priority field + if (auto priority = maybeNonemptyInt( + obj.Get("priority"), "UserConfigWrapper::setUserConfig - priority")) { + auto new_priority = toPriority(priority.value(), config.get_nts_priority()); + config.set_nts_priority(new_priority); + } + + // Handle nameTruncated field + if (auto new_name = maybeNonemptyString( + obj.Get("name"), "UserConfigWrapper::setUserConfig - name")) { + config.set_name_truncated(*new_name); // truncates silently if too long + } + + // Handle newProfilePic field + if (auto newProfilePic = maybeNonemptyProfilePic( + obj.Get("newProfilePic"), "UserConfigWrapper::setUserConfig - newProfilePic")) { + config.set_profile_pic(*newProfilePic); + } + + // Handle reuploadProfilePic field + if (auto reuploadProfilePic = maybeNonemptyProfilePic( + obj.Get("reuploadProfilePic"), + "UserConfigWrapper::setUserConfig - reuploadProfilePic")) { + config.set_reupload_profile_pic(*reuploadProfilePic); + } + + // Handle enableBlindedMsgRequest field + if (auto blindedMsgReqCpp = maybeNonemptyBoolean( + obj.Get("enableBlindedMsgRequest"), + "UserConfigWrapper::setUserConfig - enableBlindedMsgRequest")) { + config.set_blinded_msgreqs(*blindedMsgReqCpp); + } + + // Handle noteToSelfExpiry field + if (auto new_nts_expiry_seconds = maybeNonemptyInt( + obj.Get("noteToSelfExpirySeconds"), + "UserConfigWrapper::setUserConfig - noteToSelfExpiry")) { + auto expiry = obj.Get("noteToSelfExpiry"); + + config.set_nts_expiry(std::chrono::seconds{*new_nts_expiry_seconds}); + } + }); +} + void UserConfigWrapper::setPriority(const Napi::CallbackInfo& info) { return wrapExceptions(info, [&] { auto env = info.Env(); diff --git a/src/utilities.cpp b/src/utilities.cpp index 61d88d8..3a5df49 100644 --- a/src/utilities.cpp +++ b/src/utilities.cpp @@ -5,6 +5,7 @@ #include #include "session/config/namespaces.hpp" +#include "session/config/profile_pic.hpp" #include "session/util.hpp" namespace session::nodeapi { @@ -138,7 +139,8 @@ std::optional maybeNonemptyInt(Napi::Value x, const std::string& identi throw std::invalid_argument{"maybeNonemptyInt with invalid type, called from " + identifier}; } -std::optional maybeNonemptySysSeconds(Napi::Value x, const std::string& identifier) { +std::optional maybeNonemptySysSeconds( + Napi::Value x, const std::string& identifier) { if (x.IsNull() || x.IsUndefined()) return std::nullopt; @@ -150,6 +152,39 @@ std::optional maybeNonemptySysSeconds(Napi::Value x, c throw std::invalid_argument{"maybeNonemptyTime with invalid type, called from " + identifier}; } +std::optional maybeNonemptyProfilePic( + Napi::Value x, const std::string& identifier) { + if (x.IsNull() || x.IsUndefined()) + return std::nullopt; + /** + * We want to allow url and key to be empty strings here, + * as this means to remove the profile pic altogether. + * However, if they are undefined/null, we want to return nullopt, as no changes should be + * made. + */ + + if (!x.IsObject()) { + throw std::invalid_argument{ + "maybeNonemptyProfilePic with invalid type, called from " + identifier}; + } + auto obj = x.As(); + if (obj.IsEmpty()) { + return std::nullopt; + } + + auto url = maybeNonemptyString(obj.Get("url"), "maybeNonemptyProfilePic url"); + auto key = maybeNonemptyBuffer(obj.Get("key"), "maybeNonemptyProfilePic key"); + + if (!url || !key) { + // when the `x` obj is provided (i.e. not null), it should have those 2 fields set. + // They can be empty (meaning to remove the profile pic), but not undefined/null, as the + // object itself should have been undefined/null + throw new std::invalid_argument{"maybeNonemptyProfilePic with invalid input"}; + } + return session::config::profile_pic{*url, *key}; + +} // namespace session::nodeapi + std::optional maybeNonemptyBoolean(Napi::Value x, const std::string& identifier) { if (x.IsNull() || x.IsUndefined()) return std::nullopt; @@ -202,6 +237,16 @@ int64_t toPriority(Napi::Value x, int64_t currentPriority) { return newPriority; } +int64_t toPriority(int64_t newPriority, int64_t currentPriority) { + if (newPriority > 0) + // keep the existing priority if it is already set + return std::max(currentPriority, 1); + + // newPriority being < 0 means that that conversation is hidden (and so + // unpinned) + return newPriority; +} + int64_t unix_timestamp_now() { using namespace std::chrono; return duration_cast(system_clock::now().time_since_epoch()).count(); @@ -261,5 +306,4 @@ confirm_pushed_entry_t confirm_pushed_entry_from_JS(const Napi::Env& env, const confirm_pushed_entry_t confirmed_pushed_entry{seqno, hashes}; return confirmed_pushed_entry; } - -} // namespace session::nodeapi +} // namespace session::nodeapi \ No newline at end of file diff --git a/types/user/userconfig.d.ts b/types/user/userconfig.d.ts index 78331e5..582e09f 100644 --- a/types/user/userconfig.d.ts +++ b/types/user/userconfig.d.ts @@ -17,6 +17,24 @@ declare module 'libsession_util_nodejs' { setPriority: (priority: number) => void; setName: (name: string) => void; setNameTruncated: (name: string) => void; + /** + * + * Batch set user config fields. + * Set a field to null to not change it + * for example, + * - set newProfilePic to null to not change the profile picture + * - set newProfilePic to url: null, key: null to erase it + * + * `name` set to null means to not change it, but `name` set to `''` means to erase it + */ + setUserConfig: (details: { + priority: number | null; + name: string | null; + newProfilePic: ProfilePicture | null; + reuploadProfilePic: ProfilePicture | null; + enableBlindedMsgRequest: boolean | null; + noteToSelfExpiry: number | null; + }) => void; /** * Call this when uploading a new profile picture (i.e. not an auto reupload) */ @@ -52,6 +70,7 @@ declare module 'libsession_util_nodejs' { public setNameTruncated: UserConfigWrapper['setNameTruncated']; public setNewProfilePic: UserConfigWrapper['setNewProfilePic']; public setReuploadProfilePic: UserConfigWrapper['setReuploadProfilePic']; + public setUserConfig: UserConfigWrapper['setUserConfig']; public getProfileUpdatedSeconds: UserConfigWrapper['getProfileUpdatedSeconds']; public getEnableBlindedMsgRequest: UserConfigWrapper['getEnableBlindedMsgRequest']; public setEnableBlindedMsgRequest: UserConfigWrapper['setEnableBlindedMsgRequest']; @@ -75,6 +94,7 @@ declare module 'libsession_util_nodejs' { | MakeActionCall | MakeActionCall | MakeActionCall + | MakeActionCall | MakeActionCall | MakeActionCall | MakeActionCall From d46e344b1d4f1f03a270dc788f44c2e65a24ffb1 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 5 Sep 2025 11:14:10 +0200 Subject: [PATCH 3/5] feat: expose function to get profileUrl+key in hex as a url fragment --- include/user_config.hpp | 2 ++ src/user_config.cpp | 15 +++++++++++++++ types/user/userconfig.d.ts | 9 +++++++++ 3 files changed, 26 insertions(+) diff --git a/include/user_config.hpp b/include/user_config.hpp index 0d1285b..e8aa921 100644 --- a/include/user_config.hpp +++ b/include/user_config.hpp @@ -20,6 +20,8 @@ class UserConfigWrapper : public ConfigBaseImpl, public Napi::ObjectWrap std::optional { + auto env = info.Env(); + auto pic = config.get_profile_pic(); + // if pic.key and url are set, return a combined string with both merged by a hash, and the + // key in hex + if (!pic.url.empty() && !pic.key.empty()) { + return std::string(pic.url + "#" + to_hex(pic.key)); + } + return std::nullopt; + }); +} + Napi::Value UserConfigWrapper::getEnableBlindedMsgRequest(const Napi::CallbackInfo& info) { return wrapResult(info, [&] { auto env = info.Env(); diff --git a/types/user/userconfig.d.ts b/types/user/userconfig.d.ts index 582e09f..dc75d01 100644 --- a/types/user/userconfig.d.ts +++ b/types/user/userconfig.d.ts @@ -45,6 +45,13 @@ declare module 'libsession_util_nodejs' { setReuploadProfilePic: (pic: ProfilePicture) => void; getProfileUpdatedSeconds: () => number; + /** + * Returns the profile picture url and key merged as a url fragment, or null if not set. + * So this could return something like + * `http://filev2.getsession.org/file/1234#ba7e23ef3d4dc54b706d79c149ec571a4d64043e13771236afc030f6c8118575` + * + */ + getProfilePicWithKeyHex: () => string | undefined; setEnableBlindedMsgRequest: (msgRequest: boolean) => void; getEnableBlindedMsgRequest: () => boolean | undefined; @@ -72,6 +79,7 @@ declare module 'libsession_util_nodejs' { public setReuploadProfilePic: UserConfigWrapper['setReuploadProfilePic']; public setUserConfig: UserConfigWrapper['setUserConfig']; public getProfileUpdatedSeconds: UserConfigWrapper['getProfileUpdatedSeconds']; + public getProfilePicWithKeyHex: UserConfigWrapper['getProfilePicWithKeyHex']; public getEnableBlindedMsgRequest: UserConfigWrapper['getEnableBlindedMsgRequest']; public setEnableBlindedMsgRequest: UserConfigWrapper['setEnableBlindedMsgRequest']; public getNoteToSelfExpiry: UserConfigWrapper['getNoteToSelfExpiry']; @@ -96,6 +104,7 @@ declare module 'libsession_util_nodejs' { | MakeActionCall | MakeActionCall | MakeActionCall + | MakeActionCall | MakeActionCall | MakeActionCall | MakeActionCall From a67725506f7cca041a9b2dda0e86a3efef648797 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 15 Sep 2025 10:43:48 +0200 Subject: [PATCH 4/5] feat: merge set profileUpdatedAt to the set actions --- include/contacts_config.hpp | 1 - include/groups/meta_group_wrapper.hpp | 4 +- include/utilities.hpp | 18 +++++---- libsession-util | 2 +- src/contacts_config.cpp | 50 ++++++++---------------- src/groups/meta_group_wrapper.cpp | 55 ++++++++------------------- src/utilities.cpp | 13 ++++++- types/groups/groupmembers.d.ts | 11 ++++-- types/groups/metagroup.d.ts | 8 +--- types/user/contacts.d.ts | 28 +++++++------- 10 files changed, 80 insertions(+), 110 deletions(-) diff --git a/include/contacts_config.hpp b/include/contacts_config.hpp index 9741e2b..7074da4 100644 --- a/include/contacts_config.hpp +++ b/include/contacts_config.hpp @@ -20,7 +20,6 @@ class ContactsConfigWrapper : public ConfigBaseImpl, Napi::Value get(const Napi::CallbackInfo& info); Napi::Value getAll(const Napi::CallbackInfo& info); void set(const Napi::CallbackInfo& info); - Napi::Value setProfileUpdatedSeconds(const Napi::CallbackInfo& info); Napi::Value erase(const Napi::CallbackInfo& info); }; diff --git a/include/groups/meta_group_wrapper.hpp b/include/groups/meta_group_wrapper.hpp index c194e8e..dead88e 100644 --- a/include/groups/meta_group_wrapper.hpp +++ b/include/groups/meta_group_wrapper.hpp @@ -62,7 +62,6 @@ class MetaGroupWrapper : public Napi::ObjectWrap { Napi::Value memberGetOrConstruct(const Napi::CallbackInfo& info); Napi::Value memberConstructAndSet(const Napi::CallbackInfo& info); - void memberSetNameTruncated(const Napi::CallbackInfo& info); void memberSetInviteFailed(const Napi::CallbackInfo& info); void memberSetInviteSent(const Napi::CallbackInfo& info); void memberSetInviteNotSent(const Napi::CallbackInfo& info); @@ -71,8 +70,7 @@ class MetaGroupWrapper : public Napi::ObjectWrap { void memberSetPromotionSent(const Napi::CallbackInfo& info); void memberSetPromotionFailed(const Napi::CallbackInfo& info); void memberSetPromotionAccepted(const Napi::CallbackInfo& info); - void memberSetProfilePicture(const Napi::CallbackInfo& info); - void memberSetProfileUpdatedSeconds(const Napi::CallbackInfo& info); + void memberSetProfileDetails(const Napi::CallbackInfo& info); Napi::Value memberResetAllSendingState(const Napi::CallbackInfo& info); void memberSetSupplement(const Napi::CallbackInfo& info); Napi::Value memberEraseAndRekey(const Napi::CallbackInfo& info); diff --git a/include/utilities.hpp b/include/utilities.hpp index 93aadc3..1a51e7e 100644 --- a/include/utilities.hpp +++ b/include/utilities.hpp @@ -63,10 +63,12 @@ std::optional maybeNonemptyBoolean(Napi::Value x, const std::string& ident std::optional maybeNonemptySysSeconds( Napi::Value x, const std::string& identifier); +std::chrono::sys_seconds toCppSysSeconds(Napi::Value x, const std::string& identifier); + bool toCppBoolean(Napi::Value x, const std::string& identifier); -// If the object is null/undef/empty returns nullopt, otherwise if a String returns a std::string of -// the value. Throws if something else. +// If the object is null/undef/empty returns nullopt, otherwise if a String returns a +// std::string of the value. Throws if something else. std::optional maybeNonemptyString(Napi::Value x, const std::string& identifier); // If the object is null/undef/empty returns nullopt, otherwise if a Uint8Array returns a @@ -74,8 +76,8 @@ std::optional maybeNonemptyString(Napi::Value x, const std::string& std::optional> maybeNonemptyBuffer( Napi::Value x, const std::string& identifier); -// Implementation struct of toJs(); we add specializations of this for any C++ types we want to be -// able to convert into JS types. +// Implementation struct of toJs(); we add specializations of this for any C++ types we want to +// be able to convert into JS types. template struct toJs_impl { // If this gets instantiated it means we're missing a specialization and so fail to compile: @@ -219,8 +221,8 @@ static Napi::Array get_all_impl(const Napi::CallbackInfo& info, size_t size, It }); } -// Wraps a string in an optional which will be nullopt if the input string is empty. -// This is particularly useful with `toJs` to convert empty strings into Null. +// Wraps a string in an optional which will be nullopt if the input string is +// empty. This is particularly useful with `toJs` to convert empty strings into Null. inline std::optional maybe_string(std::string_view val) { if (val.empty()) return std::nullopt; @@ -258,8 +260,8 @@ auto wrapResult(const Napi::Env& env, Call&& call) { } } -// Similar to wrapResult(), but a small shortcut to allow passing `info` instead of `info.Env()` as -// the first argument. +// Similar to wrapResult(), but a small shortcut to allow passing `info` instead of `info.Env()` +// as the first argument. template auto wrapResult(const Napi::CallbackInfo& info, Call&& call) { return wrapResult(info.Env(), std::forward(call)); diff --git a/libsession-util b/libsession-util index 700915c..5e3835b 160000 --- a/libsession-util +++ b/libsession-util @@ -1 +1 @@ -Subproject commit 700915ce0912c0c2a2c9c521b1c2f80a8c8a44e7 +Subproject commit 5e3835bcfa7202cf307bc299bb1e40e160e8d357 diff --git a/src/contacts_config.cpp b/src/contacts_config.cpp index 6d4a1df..6572d60 100644 --- a/src/contacts_config.cpp +++ b/src/contacts_config.cpp @@ -65,9 +65,6 @@ void ContactsConfigWrapper::Init(Napi::Env env, Napi::Object exports) { InstanceMethod("get", &ContactsConfigWrapper::get), InstanceMethod("getAll", &ContactsConfigWrapper::getAll), InstanceMethod("set", &ContactsConfigWrapper::set), - InstanceMethod( - "setProfileUpdatedSeconds", - &ContactsConfigWrapper::setProfileUpdatedSeconds), InstanceMethod("erase", &ContactsConfigWrapper::erase), }); @@ -127,8 +124,6 @@ void ContactsConfigWrapper::set(const Napi::CallbackInfo& info) { // created time) contact.created = unix_timestamp_now(); - if (auto name = maybeNonemptyString(obj.Get("name"), "contacts.set name")) - contact.set_name(std::move(*name)); if (auto nickname = maybeNonemptyString(obj.Get("nickname"), "contacts.set nickname")) contact.set_nickname(std::move(*nickname)); else @@ -144,40 +139,27 @@ void ContactsConfigWrapper::set(const Napi::CallbackInfo& info) { toCppString(obj.Get("expirationMode"), "contacts.set expirationMode")); contact.exp_timer = std::chrono::seconds{toCppInteger( obj.Get("expirationTimerSeconds"), "contacts.set expirationTimerSeconds")}; - if (auto pic = obj.Get("profilePicture"); !pic.IsUndefined()) - contact.profile_picture = profile_pic_from_object(pic); - else - contact.profile_picture.clear(); - // if no profile picture are given from the JS side, - // reset that user profile picture - config.set(contact); - }); -} + auto newProfileUpdateSeconds = toCppSysSeconds( + obj.Get("profileUpdatedSeconds"), "contacts.set, profileUpdatedSeconds"); -Napi::Value ContactsConfigWrapper::setProfileUpdatedSeconds(const Napi::CallbackInfo& info) { - return wrapResult(info, [&] { - assertInfoLength(info, 1); + // if the saved profile info is older than the new one, update it and the profile fields + // provided + if (contact.profile_updated < newProfileUpdateSeconds) { + contact.profile_updated = newProfileUpdateSeconds; - auto arg = info[0]; - assertIsObject(arg); - auto obj = arg.As(); + if (auto name = maybeNonemptyString(obj.Get("name"), "contacts.set name")) + contact.set_name(std::move(*name)); - if (obj.IsEmpty()) - throw std::invalid_argument("setProfileUpdatedSeconds received empty"); - - auto sessionID = toCppString(obj.Get("id"), "contacts.setProfileUpdatedSeconds, id"); - auto contact = config.get(sessionID); - - auto seconds = maybeNonemptySysSeconds( - obj.Get("profileUpdatedSeconds"), - "contacts.setProfileUpdatedSeconds, profileUpdatedSeconds"); - if (contact && seconds) { - config.set_profile_updated(sessionID, seconds.value()); - config.set(*contact); - return true; + // if no profile picture are given from the JS side, + // reset that user profile picture + if (auto pic = obj.Get("profilePicture"); !pic.IsUndefined()) + contact.profile_picture = profile_pic_from_object(pic); + else + contact.profile_picture.clear(); } - return false; + + config.set(contact); }); } diff --git a/src/groups/meta_group_wrapper.cpp b/src/groups/meta_group_wrapper.cpp index 078ab0b..cc5a392 100644 --- a/src/groups/meta_group_wrapper.cpp +++ b/src/groups/meta_group_wrapper.cpp @@ -111,8 +111,6 @@ void MetaGroupWrapper::Init(Napi::Env env, Napi::Object exports) { InstanceMethod( "membersMarkPendingRemoval", &MetaGroupWrapper::membersMarkPendingRemoval), - InstanceMethod( - "memberSetNameTruncated", &MetaGroupWrapper::memberSetNameTruncated), InstanceMethod("memberSetSupplement", &MetaGroupWrapper::memberSetSupplement), InstanceMethod("memberSetInviteSent", &MetaGroupWrapper::memberSetInviteSent), InstanceMethod( @@ -131,10 +129,7 @@ void MetaGroupWrapper::Init(Napi::Env env, Napi::Object exports) { "memberSetPromotionAccepted", &MetaGroupWrapper::memberSetPromotionAccepted), InstanceMethod( - "memberSetProfilePicture", &MetaGroupWrapper::memberSetProfilePicture), - InstanceMethod( - "memberSetProfileUpdatedSeconds", - &MetaGroupWrapper::memberSetProfileUpdatedSeconds), + "memberSetProfileDetails", &MetaGroupWrapper::memberSetProfileDetails), InstanceMethod( "memberResetAllSendingState", &MetaGroupWrapper::memberResetAllSendingState), @@ -523,21 +518,6 @@ Napi::Value MetaGroupWrapper::memberConstructAndSet(const Napi::CallbackInfo& in }); } -void MetaGroupWrapper::memberSetNameTruncated(const Napi::CallbackInfo& info) { - wrapExceptions(info, [&] { - assertIsString(info[0]); - assertIsString(info[1]); - - auto pubkeyHex = toCppString(info[0], "memberSetNameTruncated pubkeyHex"); - auto newName = toCppString(info[1], "memberSetNameTruncated newName"); - auto m = this->meta_group->members->get(pubkeyHex); - if (m) { - m->set_name(newName); - this->meta_group->members->set(*m); - } - }); -} - void MetaGroupWrapper::memberSetSupplement(const Napi::CallbackInfo& info) { wrapExceptions(info, [&] { assertIsString(info[0]); @@ -656,35 +636,30 @@ void MetaGroupWrapper::memberSetPromotionAccepted(const Napi::CallbackInfo& info }); } -void MetaGroupWrapper::memberSetProfilePicture(const Napi::CallbackInfo& info) { +void MetaGroupWrapper::memberSetProfileDetails(const Napi::CallbackInfo& info) { wrapExceptions(info, [&] { assertInfoLength(info, 2); assertIsString(info[0]); assertIsObject(info[1]); - auto pubkeyHex = toCppString(info[0], "memberSetProfilePicture"); - auto profilePicture = profile_pic_from_object(info[1]); + auto pubkeyHex = toCppString(info[0], "memberSetProfileDetails"); auto m = this->meta_group->members->get(pubkeyHex); - if (m) { - m->profile_picture = profilePicture; - this->meta_group->members->set(*m); - } - }); -} + auto argsAsObj = info[1].As(); + auto updatedAtSeconds = + toCppSysSeconds(argsAsObj.Get("profileUpdatedSeconds"), "memberSetProfileDetails"); -void MetaGroupWrapper::memberSetProfileUpdatedSeconds(const Napi::CallbackInfo& info) { - wrapExceptions(info, [&] { - assertInfoLength(info, 2); - assertIsString(info[0]); - assertIsObject(info[1]); + // if the profile details provided are more recent that the ones saved, update them + if (m && updatedAtSeconds > m->profile_updated) { + m->profile_updated = updatedAtSeconds; + + auto profilePicture = profile_pic_from_object(argsAsObj.Get("profilePicture")); + m->profile_picture = profilePicture; - auto pubkeyHex = toCppString(info[0], "memberSetProfiUpdatedSeconds"); - auto updatedAtSeconds = maybeNonemptySysSeconds(info[1], "memberSetProfiUpdatedSeconds"); + // this will truncate silently if the name is too long + auto newName = toCppString(argsAsObj.Get("name"), "memberSetProfileDetails newName"); + m->set_name_truncated(newName); - auto m = this->meta_group->members->get(pubkeyHex); - if (m) { - m->profile_updated = updatedAtSeconds.value(); this->meta_group->members->set(*m); } }); diff --git a/src/utilities.cpp b/src/utilities.cpp index 3a5df49..15bd2f3 100644 --- a/src/utilities.cpp +++ b/src/utilities.cpp @@ -149,7 +149,18 @@ std::optional maybeNonemptySysSeconds( return std::chrono::sys_seconds{std::chrono::seconds{num}}; } - throw std::invalid_argument{"maybeNonemptyTime with invalid type, called from " + identifier}; + throw std::invalid_argument{ + "maybeNonemptySysSeconds with invalid type, called from " + identifier}; +} + +std::chrono::sys_seconds toCppSysSeconds(Napi::Value x, const std::string& identifier) { + + if (x.IsNumber()) { + auto num = x.As().Int64Value(); + return std::chrono::sys_seconds{std::chrono::seconds{num}}; + } + + throw std::invalid_argument{"toCppSysSeconds with invalid type, called from " + identifier}; } std::optional maybeNonemptyProfilePic( diff --git a/types/groups/groupmembers.d.ts b/types/groups/groupmembers.d.ts index 4c7bbd0..6d65745 100644 --- a/types/groups/groupmembers.d.ts +++ b/types/groups/groupmembers.d.ts @@ -87,7 +87,6 @@ declare module 'libsession_util_nodejs' { memberGetAllPendingRemovals: () => Array; // setters - memberSetNameTruncated: (pubkeyHex: PubkeyType, newName: string) => void; /** * A member's invite state defaults to invite-not-sent. @@ -117,8 +116,14 @@ declare module 'libsession_util_nodejs' { /** Called when the member accepted the promotion */ memberSetPromotionAccepted: (pubkeyHex: PubkeyType) => void; - memberSetProfilePicture: (pubkeyHex: PubkeyType, profilePicture: ProfilePicture) => void; - memberSetProfileUpdatedSeconds: (pubkeyHex: PubkeyType, profileUpdatedSeconds: number) => void; + memberSetProfileDetails: ( + pubkeyHex: PubkeyType, + profileDetails: { + profileUpdatedSeconds: number; + name: string; + profilePicture: ProfilePicture; + } + ) => void; memberResetAllSendingState: () => boolean; memberSetSupplement: (pubkeyHex: PubkeyType) => void; membersMarkPendingRemoval: (members: Array, withMessages: boolean) => void; diff --git a/types/groups/metagroup.d.ts b/types/groups/metagroup.d.ts index 765c8e4..deed1bd 100644 --- a/types/groups/metagroup.d.ts +++ b/types/groups/metagroup.d.ts @@ -97,7 +97,6 @@ declare module 'libsession_util_nodejs' { public memberGetAll: MetaGroupWrapper['memberGetAll']; public memberGetAllPendingRemovals: MetaGroupWrapper['memberGetAllPendingRemovals']; public memberSetInviteAccepted: MetaGroupWrapper['memberSetInviteAccepted']; - public memberSetNameTruncated: MetaGroupWrapper['memberSetNameTruncated']; public memberSetPromoted: MetaGroupWrapper['memberSetPromoted']; public memberSetPromotionAccepted: MetaGroupWrapper['memberSetPromotionAccepted']; public memberSetPromotionSent: MetaGroupWrapper['memberSetPromotionSent']; @@ -107,8 +106,7 @@ declare module 'libsession_util_nodejs' { public memberSetInviteFailed: MetaGroupWrapper['memberSetInviteFailed']; public memberEraseAndRekey: MetaGroupWrapper['memberEraseAndRekey']; public membersMarkPendingRemoval: MetaGroupWrapper['membersMarkPendingRemoval']; - public memberSetProfilePicture: MetaGroupWrapper['memberSetProfilePicture']; - public memberSetProfileUpdatedSeconds: MetaGroupWrapper['memberSetProfileUpdatedSeconds']; + public memberSetProfileDetails: MetaGroupWrapper['memberSetProfileDetails']; public memberResetAllSendingState: MetaGroupWrapper['memberResetAllSendingState']; public memberSetSupplement: MetaGroupWrapper['memberSetSupplement']; @@ -149,7 +147,6 @@ declare module 'libsession_util_nodejs' { | MakeActionCall | MakeActionCall | MakeActionCall - | MakeActionCall | MakeActionCall | MakeActionCall | MakeActionCall @@ -159,8 +156,7 @@ declare module 'libsession_util_nodejs' { | MakeActionCall | MakeActionCall | MakeActionCall - | MakeActionCall - | MakeActionCall + | MakeActionCall | MakeActionCall | MakeActionCall diff --git a/types/user/contacts.d.ts b/types/user/contacts.d.ts index 9095d02..a2eee0f 100644 --- a/types/user/contacts.d.ts +++ b/types/user/contacts.d.ts @@ -10,10 +10,9 @@ declare module 'libsession_util_nodejs' { init: (secretKey: Uint8Array, dump: Uint8Array | null) => void; /** This function is used to free wrappers from memory only */ free: () => void; - get: (pubkeyHex: string) => ContactInfo | null; + get: (pubkeyHex: string) => ContactInfoGet | null; set: (contact: ContactInfoSet) => void; - setProfileUpdatedSeconds: (pubkeyHex: string, profileUpdatedSeconds: number) => void; - getAll: () => Array; + getAll: () => Array; erase: (pubkeyHex: string) => void; }; @@ -27,15 +26,25 @@ declare module 'libsession_util_nodejs' { type ContactInfoShared = WithPriority & { id: string; - name?: string; nickname?: string; - profilePicture?: ProfilePicture; /** * Can only be set the first time a contact is created, a new change won't override the value in the wrapper. */ createdAtSeconds: number; expirationMode?: DisappearingMessageConversationModeType; expirationTimerSeconds?: number; + /** + * A name & profile pic change won't be applied unless this value is more recent than the currently saved one. + */ + profileUpdatedSeconds: number; + /** + * see `profileUpdatedSeconds` for more info. + */ + name?: string; + /** + * see `profileUpdatedSeconds` for more info. + */ + profilePicture?: ProfilePicture; }; export type ContactInfoSet = ContactInfoShared & { @@ -44,22 +53,16 @@ declare module 'libsession_util_nodejs' { blocked?: boolean; }; - export type ContactInfo = ContactInfoShared & { + export type ContactInfoGet = ContactInfoShared & { approved: boolean; approvedMe: boolean; blocked: boolean; - /** - * This should be bumped whenever the contact profile is updated through - * `setProfileUpdatedSeconds`. - */ - profileUpdatedSeconds: number; }; export class ContactsConfigWrapperNode extends BaseConfigWrapperNode { constructor(secretKey: Uint8Array, dump: Uint8Array | null); public get: ContactsWrapper['get']; public set: ContactsWrapper['set']; - public setProfileUpdatedSeconds: ContactsWrapper['setProfileUpdatedSeconds']; public getAll: ContactsWrapper['getAll']; public erase: ContactsWrapper['erase']; } @@ -69,7 +72,6 @@ declare module 'libsession_util_nodejs' { | MakeActionCall | MakeActionCall | MakeActionCall - | MakeActionCall | MakeActionCall | MakeActionCall; } From 830d444a1d1ee560e89687712a34fc34a9cb4741 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 15 Sep 2025 10:51:54 +0200 Subject: [PATCH 5/5] chore: remove setUserConfig as unused --- include/user_config.hpp | 1 - src/user_config.cpp | 56 -------------------------------------- types/user/userconfig.d.ts | 21 +------------- 3 files changed, 1 insertion(+), 77 deletions(-) diff --git a/include/user_config.hpp b/include/user_config.hpp index e8aa921..d6099d1 100644 --- a/include/user_config.hpp +++ b/include/user_config.hpp @@ -26,7 +26,6 @@ class UserConfigWrapper : public ConfigBaseImpl, public Napi::ObjectWrap(); - if (obj.IsEmpty()) { - return; - } - // Handle priority field - if (auto priority = maybeNonemptyInt( - obj.Get("priority"), "UserConfigWrapper::setUserConfig - priority")) { - auto new_priority = toPriority(priority.value(), config.get_nts_priority()); - config.set_nts_priority(new_priority); - } - - // Handle nameTruncated field - if (auto new_name = maybeNonemptyString( - obj.Get("name"), "UserConfigWrapper::setUserConfig - name")) { - config.set_name_truncated(*new_name); // truncates silently if too long - } - - // Handle newProfilePic field - if (auto newProfilePic = maybeNonemptyProfilePic( - obj.Get("newProfilePic"), "UserConfigWrapper::setUserConfig - newProfilePic")) { - config.set_profile_pic(*newProfilePic); - } - - // Handle reuploadProfilePic field - if (auto reuploadProfilePic = maybeNonemptyProfilePic( - obj.Get("reuploadProfilePic"), - "UserConfigWrapper::setUserConfig - reuploadProfilePic")) { - config.set_reupload_profile_pic(*reuploadProfilePic); - } - - // Handle enableBlindedMsgRequest field - if (auto blindedMsgReqCpp = maybeNonemptyBoolean( - obj.Get("enableBlindedMsgRequest"), - "UserConfigWrapper::setUserConfig - enableBlindedMsgRequest")) { - config.set_blinded_msgreqs(*blindedMsgReqCpp); - } - - // Handle noteToSelfExpiry field - if (auto new_nts_expiry_seconds = maybeNonemptyInt( - obj.Get("noteToSelfExpirySeconds"), - "UserConfigWrapper::setUserConfig - noteToSelfExpiry")) { - auto expiry = obj.Get("noteToSelfExpiry"); - - config.set_nts_expiry(std::chrono::seconds{*new_nts_expiry_seconds}); - } - }); -} - void UserConfigWrapper::setPriority(const Napi::CallbackInfo& info) { return wrapExceptions(info, [&] { auto env = info.Env(); diff --git a/types/user/userconfig.d.ts b/types/user/userconfig.d.ts index dc75d01..9f6b6ea 100644 --- a/types/user/userconfig.d.ts +++ b/types/user/userconfig.d.ts @@ -17,24 +17,7 @@ declare module 'libsession_util_nodejs' { setPriority: (priority: number) => void; setName: (name: string) => void; setNameTruncated: (name: string) => void; - /** - * - * Batch set user config fields. - * Set a field to null to not change it - * for example, - * - set newProfilePic to null to not change the profile picture - * - set newProfilePic to url: null, key: null to erase it - * - * `name` set to null means to not change it, but `name` set to `''` means to erase it - */ - setUserConfig: (details: { - priority: number | null; - name: string | null; - newProfilePic: ProfilePicture | null; - reuploadProfilePic: ProfilePicture | null; - enableBlindedMsgRequest: boolean | null; - noteToSelfExpiry: number | null; - }) => void; + /** * Call this when uploading a new profile picture (i.e. not an auto reupload) */ @@ -77,7 +60,6 @@ declare module 'libsession_util_nodejs' { public setNameTruncated: UserConfigWrapper['setNameTruncated']; public setNewProfilePic: UserConfigWrapper['setNewProfilePic']; public setReuploadProfilePic: UserConfigWrapper['setReuploadProfilePic']; - public setUserConfig: UserConfigWrapper['setUserConfig']; public getProfileUpdatedSeconds: UserConfigWrapper['getProfileUpdatedSeconds']; public getProfilePicWithKeyHex: UserConfigWrapper['getProfilePicWithKeyHex']; public getEnableBlindedMsgRequest: UserConfigWrapper['getEnableBlindedMsgRequest']; @@ -102,7 +84,6 @@ declare module 'libsession_util_nodejs' { | MakeActionCall | MakeActionCall | MakeActionCall - | MakeActionCall | MakeActionCall | MakeActionCall | MakeActionCall