New Feature: Direct Messages

This feature involved a lot of refactoring in order to get working
correctly. I wanted to continue using the timeline view for chats thus I
used alternative styling & structure for DM event kinds. This worked
create since the elements map does not care.

There is some queing that has to be done to decrypt message content thus
I allow viewing messages even if they haven't been decrypted yet. I
think this is good for transparency since you understand what is and is
not decrypted. I do think that the UX could improve, because even tho it
is fast, it's flashes on new messages.

I did not implement saving of latest messages. I will do this later, but
this feature is big enough to merge as is: an alpha state that works.

I further abstracted profile & name updating to work in a more global
manner. Additionally I rewrote code that had attribute scripts to use
addEventListener instead. This is needed to happen anyways for security
and made the codebase easier to manage.
This commit is contained in:
Thomas Mathews 2023-01-05 10:36:04 -08:00
parent 9badc35bf3
commit 077bf49fdb
17 changed files with 798 additions and 292 deletions

View file

@ -27,6 +27,9 @@ function model_process_event(model, relay, ev) {
case KIND_REACTION:
fn = model_process_event_reaction;
break;
case KIND_DM:
fn = model_process_event_dm;
break;
}
if (fn)
fn(model, ev, !!relay);
@ -45,9 +48,6 @@ function model_process_event(model, relay, ev) {
model_que_profile(model, relay, pubkey);
}
});
// TODO fetch unknown referenced events & pubkeys from this event
// TODO notify user of new events aimed at them!
}
function model_get_relay_que(model, relay) {
@ -123,9 +123,9 @@ function model_process_event_metadata(model, ev, update_view) {
// If it's my pubkey let's redraw my pfp that is not located in the view
// This has to happen regardless of update_view because of the it's not
// related to events
if (profile.pubkey == model.pubkey) {
/*if (profile.pubkey == model.pubkey) {
redraw_my_pfp(model);
}
}*/
}
function model_has_profile(model, pk) {
@ -153,6 +153,76 @@ function model_process_event_following(model, ev, update_view) {
// load_our_relays(model.pubkey, model.pool, ev)
}
function event_is_dm(ev, mykey) {
if (ev.kind != KIND_DM)
return false;
if (ev.pubkey != mykey && event_tags_pubkey(ev, mykey))
return true;
return ev.pubkey == mykey;
}
/* model_process_event_dm updates the internal dms hash map based on dms
* targeted at the user.
*/
function model_process_event_dm(model, ev, update_view) {
if (!event_is_dm(ev, model.pubkey))
return;
// We have to identify who the target DM is for since we are also in the
// chat. We simply use the first non-us key we find as the target. I am not
// sure that multi-sig chats are possible at this time in the spec. If no
// target, it's a bad DM.
let target;
const keys = event_get_pubkeys(ev);
for (let key of keys) {
target = key;
if (key == model.pubkey)
continue;
break;
}
if (!target)
return;
let dm = model_get_dm(model, target);
dm.needs_decryption = true;
dm.needs_redraw = true;
// It may be faster to not use binary search due to the newest always being
// at the front - but I could be totally wrong. Potentially it COULD be
// slower during history if history is imported ASCENDINGLY. But anything
// after this will always be faster and is insurance (race conditions).
let i = 0;
for (; i < dm.events.length; i++) {
const b = dm.events[i];
if (ev.created_at > b.created_at)
break;
}
dm.events.splice(i, 0, ev);
// Check if DM is new
const b = model.all_events[dm.last_seen];
if (!b || b.created_at < ev.created_at) {
// TODO update notification UI
dm.new_count++;
}
}
function model_get_dm(model, target) {
if (!model.dms.has(target)) {
// TODO think about using "pubkey:subject" so we have threads
model.dms.set(target, {
pubkey: target,
// events is an ordered list (new to old) of events referenced from
// all_events. It should not be a copy to reduce memory.
events: [],
// Last read event by the client/user
last_seen: "",
new_count: 0,
// Notifies the renderer that this dm is out of date
needs_redraw: false,
needs_decryption: false,
});
}
return model.dms.get(target);
}
/* model_process_event_reaction updates the reactions dictionary
*/
function model_process_event_reaction(model, ev, update_view) {
@ -388,6 +458,7 @@ function new_model() {
friends: new Set(),
friend_of_friends: new Set(),
},
dms: new Map(), // pubkey => event list
invalidated: [], // event ids which should be added/removed
elements: {}, // map of evid > rendered element
relay_que: new Map(),