Skip to content

Commit 0542219

Browse files
authored
Merge pull request #30 from session-foundation/feat/add-updated-profile-tracking
feat: add timestamp to track when a profile was updated
2 parents 90f64ef + 830d444 commit 0542219

15 files changed

+255
-79
lines changed

include/groups/meta_group_wrapper.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ class MetaGroupWrapper : public Napi::ObjectWrap<MetaGroupWrapper> {
6262
Napi::Value memberGetOrConstruct(const Napi::CallbackInfo& info);
6363
Napi::Value memberConstructAndSet(const Napi::CallbackInfo& info);
6464

65-
void memberSetNameTruncated(const Napi::CallbackInfo& info);
6665
void memberSetInviteFailed(const Napi::CallbackInfo& info);
6766
void memberSetInviteSent(const Napi::CallbackInfo& info);
6867
void memberSetInviteNotSent(const Napi::CallbackInfo& info);
@@ -71,7 +70,7 @@ class MetaGroupWrapper : public Napi::ObjectWrap<MetaGroupWrapper> {
7170
void memberSetPromotionSent(const Napi::CallbackInfo& info);
7271
void memberSetPromotionFailed(const Napi::CallbackInfo& info);
7372
void memberSetPromotionAccepted(const Napi::CallbackInfo& info);
74-
void memberSetProfilePicture(const Napi::CallbackInfo& info);
73+
void memberSetProfileDetails(const Napi::CallbackInfo& info);
7574
Napi::Value memberResetAllSendingState(const Napi::CallbackInfo& info);
7675
void memberSetSupplement(const Napi::CallbackInfo& info);
7776
Napi::Value memberEraseAndRekey(const Napi::CallbackInfo& info);

include/profile_pic.hpp

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,6 @@
55

66
namespace session::nodeapi {
77

8-
// Returns {"url": "...", "key": buffer} object; both values will be Null if the pic is not set.
9-
10-
template <>
11-
struct toJs_impl<config::profile_pic> {
12-
Napi::Object operator()(const Napi::Env& env, const config::profile_pic& pic) {
13-
auto obj = Napi::Object::New(env);
14-
if (pic) {
15-
obj["url"] = toJs(env, pic.url);
16-
obj["key"] = toJs(env, pic.key);
17-
} else {
18-
obj["url"] = env.Null();
19-
obj["key"] = env.Null();
20-
}
21-
return obj;
22-
}
23-
};
248

259
// Constructs a profile_pic from a Napi::Value which must be either Null or an Object; if an
2610
// Object then it *must* contain "url" (string or null) and "key" (uint8array of size 32 or

include/user_config.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@ class UserConfigWrapper : public ConfigBaseImpl, public Napi::ObjectWrap<UserCon
2020
Napi::Value getPriority(const Napi::CallbackInfo& info);
2121
Napi::Value getName(const Napi::CallbackInfo& info);
2222
Napi::Value getProfilePic(const Napi::CallbackInfo& info);
23+
Napi::Value getProfilePicWithKeyHex(const Napi::CallbackInfo& info);
24+
2325
void setPriority(const Napi::CallbackInfo& info);
2426
void setName(const Napi::CallbackInfo& info);
2527
void setNameTruncated(const Napi::CallbackInfo& info);
26-
void setProfilePic(const Napi::CallbackInfo& info);
28+
void setNewProfilePic(const Napi::CallbackInfo& info);
29+
30+
void setReuploadProfilePic(const Napi::CallbackInfo& info);
31+
Napi::Value getProfileUpdatedSeconds(const Napi::CallbackInfo& info);
2732

2833
Napi::Value getEnableBlindedMsgRequest(const Napi::CallbackInfo& info);
2934
void setEnableBlindedMsgRequest(const Napi::CallbackInfo& info);

include/utilities.hpp

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <vector>
1212

1313
#include "session/config/namespaces.hpp"
14+
#include "session/config/profile_pic.hpp"
1415
#include "session/types.hpp"
1516
#include "utilities.hpp"
1617

@@ -59,20 +60,24 @@ std::vector<unsigned char> toCppBuffer(Napi::Value x, const std::string& identif
5960
int64_t toCppInteger(Napi::Value x, const std::string& identifier, bool allowUndefined = false);
6061
std::optional<int64_t> maybeNonemptyInt(Napi::Value x, const std::string& identifier);
6162
std::optional<bool> maybeNonemptyBoolean(Napi::Value x, const std::string& identifier);
63+
std::optional<std::chrono::sys_seconds> maybeNonemptySysSeconds(
64+
Napi::Value x, const std::string& identifier);
65+
66+
std::chrono::sys_seconds toCppSysSeconds(Napi::Value x, const std::string& identifier);
6267

6368
bool toCppBoolean(Napi::Value x, const std::string& identifier);
6469

65-
// If the object is null/undef/empty returns nullopt, otherwise if a String returns a std::string of
66-
// the value. Throws if something else.
70+
// If the object is null/undef/empty returns nullopt, otherwise if a String returns a
71+
// std::string of the value. Throws if something else.
6772
std::optional<std::string> maybeNonemptyString(Napi::Value x, const std::string& identifier);
6873

6974
// If the object is null/undef/empty returns nullopt, otherwise if a Uint8Array returns a
7075
// std::vector<unsigned char> of the value. Throws if something else.
7176
std::optional<std::vector<unsigned char>> maybeNonemptyBuffer(
7277
Napi::Value x, const std::string& identifier);
7378

74-
// Implementation struct of toJs(); we add specializations of this for any C++ types we want to be
75-
// able to convert into JS types.
79+
// Implementation struct of toJs(); we add specializations of this for any C++ types we want to
80+
// be able to convert into JS types.
7681
template <typename T, typename SFINAE = void>
7782
struct toJs_impl {
7883
// If this gets instantiated it means we're missing a specialization and so fail to compile:
@@ -176,6 +181,30 @@ struct toJs_impl<std::optional<T>> {
176181
}
177182
};
178183

184+
template <>
185+
struct toJs_impl<std::chrono::sys_seconds> {
186+
auto operator()(const Napi::Env& env, std::chrono::sys_seconds t) const {
187+
return Napi::Number::New(env, t.time_since_epoch().count());
188+
}
189+
};
190+
191+
// Returns {"url": "...", "key": buffer} object; both values will be Null if the pic is not set.
192+
193+
template <>
194+
struct toJs_impl<config::profile_pic> {
195+
auto operator()(const Napi::Env& env, const config::profile_pic& pic) const {
196+
auto obj = Napi::Object::New(env);
197+
if (pic) {
198+
obj["url"] = toJs(env, pic.url);
199+
obj["key"] = toJs(env, pic.key);
200+
} else {
201+
obj["url"] = env.Null();
202+
obj["key"] = env.Null();
203+
}
204+
return obj;
205+
}
206+
};
207+
179208
// Helper for various "get_all" functions that copy [it...end) into a Napi::Array via toJs().
180209
// Throws a Napi::Error on any exception.
181210
template <typename It, typename EndIt>
@@ -192,8 +221,8 @@ static Napi::Array get_all_impl(const Napi::CallbackInfo& info, size_t size, It
192221
});
193222
}
194223

195-
// Wraps a string in an optional<string_view> which will be nullopt if the input string is empty.
196-
// This is particularly useful with `toJs` to convert empty strings into Null.
224+
// Wraps a string in an optional<string_view> which will be nullopt if the input string is
225+
// empty. This is particularly useful with `toJs` to convert empty strings into Null.
197226
inline std::optional<std::string_view> maybe_string(std::string_view val) {
198227
if (val.empty())
199228
return std::nullopt;
@@ -231,8 +260,8 @@ auto wrapResult(const Napi::Env& env, Call&& call) {
231260
}
232261
}
233262

234-
// Similar to wrapResult(), but a small shortcut to allow passing `info` instead of `info.Env()` as
235-
// the first argument.
263+
// Similar to wrapResult(), but a small shortcut to allow passing `info` instead of `info.Env()`
264+
// as the first argument.
236265
template <typename Call>
237266
auto wrapResult(const Napi::CallbackInfo& info, Call&& call) {
238267
return wrapResult(info.Env(), std::forward<Call>(call));
@@ -262,6 +291,10 @@ std::string printable(std::span<const unsigned char> x);
262291
* Keep the current priority if a wrapper
263292
*/
264293
int64_t toPriority(Napi::Value x, int64_t currentPriority);
294+
int64_t toPriority(int64_t newPriority, int64_t currentPriority);
295+
296+
std::optional<session::config::profile_pic> maybeNonemptyProfilePic(
297+
Napi::Value x, const std::string& identifier);
265298

266299
int64_t unix_timestamp_now();
267300

libsession-util

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"main": "index.js",
33
"name": "libsession_util_nodejs",
44
"description": "Wrappers for the Session Util Library",
5-
"version": "0.5.5",
5+
"version": "0.5.6",
66
"license": "GPL-3.0",
77
"author": {
88
"name": "Oxen Project",

src/contacts_config.cpp

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ struct toJs_impl<contact_info> {
4444
obj["nickname"] = toJs(env, maybe_string(contact.nickname));
4545
obj["approved"] = toJs(env, contact.approved);
4646
obj["approvedMe"] = toJs(env, contact.approved_me);
47+
obj["profileUpdatedSeconds"] = toJs(env, contact.profile_updated);
4748
obj["blocked"] = toJs(env, contact.blocked);
4849
obj["priority"] = toJs(env, contact.priority);
4950
obj["createdAtSeconds"] = toJs(env, contact.created);
@@ -65,6 +66,7 @@ void ContactsConfigWrapper::Init(Napi::Env env, Napi::Object exports) {
6566
InstanceMethod("getAll", &ContactsConfigWrapper::getAll),
6667
InstanceMethod("set", &ContactsConfigWrapper::set),
6768
InstanceMethod("erase", &ContactsConfigWrapper::erase),
69+
6870
});
6971
}
7072

@@ -122,8 +124,6 @@ void ContactsConfigWrapper::set(const Napi::CallbackInfo& info) {
122124
// created time)
123125
contact.created = unix_timestamp_now();
124126

125-
if (auto name = maybeNonemptyString(obj.Get("name"), "contacts.set name"))
126-
contact.set_name(std::move(*name));
127127
if (auto nickname = maybeNonemptyString(obj.Get("nickname"), "contacts.set nickname"))
128128
contact.set_nickname(std::move(*nickname));
129129
else
@@ -139,12 +139,25 @@ void ContactsConfigWrapper::set(const Napi::CallbackInfo& info) {
139139
toCppString(obj.Get("expirationMode"), "contacts.set expirationMode"));
140140
contact.exp_timer = std::chrono::seconds{toCppInteger(
141141
obj.Get("expirationTimerSeconds"), "contacts.set expirationTimerSeconds")};
142-
if (auto pic = obj.Get("profilePicture"); !pic.IsUndefined())
143-
contact.profile_picture = profile_pic_from_object(pic);
144-
else
145-
contact.profile_picture.clear();
146-
// if no profile picture are given from the JS side,
147-
// reset that user profile picture
142+
143+
auto newProfileUpdateSeconds = toCppSysSeconds(
144+
obj.Get("profileUpdatedSeconds"), "contacts.set, profileUpdatedSeconds");
145+
146+
// if the saved profile info is older than the new one, update it and the profile fields
147+
// provided
148+
if (contact.profile_updated < newProfileUpdateSeconds) {
149+
contact.profile_updated = newProfileUpdateSeconds;
150+
151+
if (auto name = maybeNonemptyString(obj.Get("name"), "contacts.set name"))
152+
contact.set_name(std::move(*name));
153+
154+
// if no profile picture are given from the JS side,
155+
// reset that user profile picture
156+
if (auto pic = obj.Get("profilePicture"); !pic.IsUndefined())
157+
contact.profile_picture = profile_pic_from_object(pic);
158+
else
159+
contact.profile_picture.clear();
160+
}
148161

149162
config.set(contact);
150163
});

src/groups/meta_group_wrapper.cpp

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Napi::Object member_to_js(const Napi::Env& env, const member& info, const member
1717
obj["pubkeyHex"] = toJs(env, info.session_id);
1818
obj["name"] = toJs(env, info.name);
1919
obj["profilePicture"] = toJs(env, info.profile_picture);
20+
obj["profileUpdatedSeconds"] = toJs(env, info.profile_updated);
2021
obj["supplement"] = toJs(env, info.supplement);
2122

2223
switch (status) {
@@ -110,8 +111,6 @@ void MetaGroupWrapper::Init(Napi::Env env, Napi::Object exports) {
110111
InstanceMethod(
111112
"membersMarkPendingRemoval",
112113
&MetaGroupWrapper::membersMarkPendingRemoval),
113-
InstanceMethod(
114-
"memberSetNameTruncated", &MetaGroupWrapper::memberSetNameTruncated),
115114
InstanceMethod("memberSetSupplement", &MetaGroupWrapper::memberSetSupplement),
116115
InstanceMethod("memberSetInviteSent", &MetaGroupWrapper::memberSetInviteSent),
117116
InstanceMethod(
@@ -130,7 +129,7 @@ void MetaGroupWrapper::Init(Napi::Env env, Napi::Object exports) {
130129
"memberSetPromotionAccepted",
131130
&MetaGroupWrapper::memberSetPromotionAccepted),
132131
InstanceMethod(
133-
"memberSetProfilePicture", &MetaGroupWrapper::memberSetProfilePicture),
132+
"memberSetProfileDetails", &MetaGroupWrapper::memberSetProfileDetails),
134133
InstanceMethod(
135134
"memberResetAllSendingState",
136135
&MetaGroupWrapper::memberResetAllSendingState),
@@ -519,21 +518,6 @@ Napi::Value MetaGroupWrapper::memberConstructAndSet(const Napi::CallbackInfo& in
519518
});
520519
}
521520

522-
void MetaGroupWrapper::memberSetNameTruncated(const Napi::CallbackInfo& info) {
523-
wrapExceptions(info, [&] {
524-
assertIsString(info[0]);
525-
assertIsString(info[1]);
526-
527-
auto pubkeyHex = toCppString(info[0], "memberSetNameTruncated pubkeyHex");
528-
auto newName = toCppString(info[1], "memberSetNameTruncated newName");
529-
auto m = this->meta_group->members->get(pubkeyHex);
530-
if (m) {
531-
m->set_name(newName);
532-
this->meta_group->members->set(*m);
533-
}
534-
});
535-
}
536-
537521
void MetaGroupWrapper::memberSetSupplement(const Napi::CallbackInfo& info) {
538522
wrapExceptions(info, [&] {
539523
assertIsString(info[0]);
@@ -652,18 +636,30 @@ void MetaGroupWrapper::memberSetPromotionAccepted(const Napi::CallbackInfo& info
652636
});
653637
}
654638

655-
void MetaGroupWrapper::memberSetProfilePicture(const Napi::CallbackInfo& info) {
639+
void MetaGroupWrapper::memberSetProfileDetails(const Napi::CallbackInfo& info) {
656640
wrapExceptions(info, [&] {
657641
assertInfoLength(info, 2);
658642
assertIsString(info[0]);
659643
assertIsObject(info[1]);
660644

661-
auto pubkeyHex = toCppString(info[0], "memberSetProfilePicture");
662-
auto profilePicture = profile_pic_from_object(info[1]);
645+
auto pubkeyHex = toCppString(info[0], "memberSetProfileDetails");
663646

664647
auto m = this->meta_group->members->get(pubkeyHex);
665-
if (m) {
648+
auto argsAsObj = info[1].As<Napi::Object>();
649+
auto updatedAtSeconds =
650+
toCppSysSeconds(argsAsObj.Get("profileUpdatedSeconds"), "memberSetProfileDetails");
651+
652+
// if the profile details provided are more recent that the ones saved, update them
653+
if (m && updatedAtSeconds > m->profile_updated) {
654+
m->profile_updated = updatedAtSeconds;
655+
656+
auto profilePicture = profile_pic_from_object(argsAsObj.Get("profilePicture"));
666657
m->profile_picture = profilePicture;
658+
659+
// this will truncate silently if the name is too long
660+
auto newName = toCppString(argsAsObj.Get("name"), "memberSetProfileDetails newName");
661+
m->set_name_truncated(newName);
662+
667663
this->meta_group->members->set(*m);
668664
}
669665
});

src/user_config.cpp

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,17 @@ void UserConfigWrapper::Init(Napi::Env env, Napi::Object exports) {
2020
InstanceMethod("getPriority", &UserConfigWrapper::getPriority),
2121
InstanceMethod("getName", &UserConfigWrapper::getName),
2222
InstanceMethod("getProfilePic", &UserConfigWrapper::getProfilePic),
23+
InstanceMethod(
24+
"getProfilePicWithKeyHex", &UserConfigWrapper::getProfilePicWithKeyHex),
25+
InstanceMethod(
26+
"getProfileUpdatedSeconds",
27+
&UserConfigWrapper::getProfileUpdatedSeconds),
28+
InstanceMethod(
29+
"setReuploadProfilePic", &UserConfigWrapper::setReuploadProfilePic),
2330
InstanceMethod("setPriority", &UserConfigWrapper::setPriority),
2431
InstanceMethod("setName", &UserConfigWrapper::setName),
2532
InstanceMethod("setNameTruncated", &UserConfigWrapper::setNameTruncated),
26-
InstanceMethod("setProfilePic", &UserConfigWrapper::setProfilePic),
33+
InstanceMethod("setNewProfilePic", &UserConfigWrapper::setNewProfilePic),
2734
InstanceMethod(
2835
"getEnableBlindedMsgRequest",
2936
&UserConfigWrapper::getEnableBlindedMsgRequest),
@@ -107,7 +114,7 @@ void UserConfigWrapper::setNameTruncated(const Napi::CallbackInfo& info) {
107114
});
108115
}
109116

110-
void UserConfigWrapper::setProfilePic(const Napi::CallbackInfo& info) {
117+
void UserConfigWrapper::setNewProfilePic(const Napi::CallbackInfo& info) {
111118
return wrapExceptions(info, [&] {
112119
assertInfoLength(info, 1);
113120
auto profile_pic_obj = info[0];
@@ -119,6 +126,36 @@ void UserConfigWrapper::setProfilePic(const Napi::CallbackInfo& info) {
119126
});
120127
}
121128

129+
void UserConfigWrapper::setReuploadProfilePic(const Napi::CallbackInfo& info) {
130+
assertInfoLength(info, 1);
131+
auto profile_pic_obj = info[0];
132+
133+
if (!profile_pic_obj.IsNull() && !profile_pic_obj.IsUndefined())
134+
assertIsObject(profile_pic_obj);
135+
136+
config.set_reupload_profile_pic(profile_pic_from_object(profile_pic_obj));
137+
}
138+
139+
Napi::Value UserConfigWrapper::getProfileUpdatedSeconds(const Napi::CallbackInfo& info) {
140+
return wrapResult(info, [&] {
141+
auto env = info.Env();
142+
return config.get_profile_updated();
143+
});
144+
}
145+
146+
Napi::Value UserConfigWrapper::getProfilePicWithKeyHex(const Napi::CallbackInfo& info) {
147+
return wrapResult(info, [&]() -> std::optional<std::string> {
148+
auto env = info.Env();
149+
auto pic = config.get_profile_pic();
150+
// if pic.key and url are set, return a combined string with both merged by a hash, and the
151+
// key in hex
152+
if (!pic.url.empty() && !pic.key.empty()) {
153+
return std::string(pic.url + "#" + to_hex(pic.key));
154+
}
155+
return std::nullopt;
156+
});
157+
}
158+
122159
Napi::Value UserConfigWrapper::getEnableBlindedMsgRequest(const Napi::CallbackInfo& info) {
123160
return wrapResult(info, [&] {
124161
auto env = info.Env();

0 commit comments

Comments
 (0)