
Improved recognization of replying to thread and being able to open it. Rewrote profile storing on the model. Additionally fixed issues where the profile was not getting loaded for referenced pubkeys on an event.
429 lines
11 KiB
JavaScript
429 lines
11 KiB
JavaScript
/* model_process_event is the main point where events are post-processed from
|
|
* a relay. Additionally other side effects happen such as notification checks
|
|
* and fetching of unknown pubkey profiles.
|
|
*/
|
|
function model_process_event(model, relay, ev) {
|
|
if (model.all_events[ev.id]) {
|
|
return;
|
|
}
|
|
|
|
model.all_events[ev.id] = ev;
|
|
ev.refs = event_get_tag_refs(ev.tags);
|
|
ev.pow = event_calculate_pow(ev);
|
|
|
|
// Process specific event needs based on it's kind. Not using a map because
|
|
// integers can't be used.
|
|
let fn;
|
|
switch(ev.kind) {
|
|
case KIND_METADATA:
|
|
fn = model_process_event_metadata;
|
|
break;
|
|
case KIND_CONTACT:
|
|
fn = model_process_event_following;
|
|
break;
|
|
case KIND_DELETE:
|
|
fn = model_process_event_deletion;
|
|
break;
|
|
case KIND_REACTION:
|
|
fn = model_process_event_reaction;
|
|
break;
|
|
}
|
|
if (fn)
|
|
fn(model, ev, !!relay);
|
|
|
|
// Queue event for rendering
|
|
model.invalidated.push(ev.id);
|
|
|
|
// If the processing did not come from a relay, but instead storage then
|
|
// let us simply ignore fetching new things.
|
|
if (!relay)
|
|
return;
|
|
|
|
// Request new profiles for unseen pubkeys of the event
|
|
event_get_pubkeys(ev).forEach((pubkey) => {
|
|
if (!model_has_profile(model, pubkey)) {
|
|
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) {
|
|
return map_get(model.relay_que, relay, {
|
|
profiles: [],
|
|
timestamp: 0,
|
|
contacts_init: false,
|
|
});
|
|
}
|
|
|
|
function model_que_profile(model, relay, pubkey) {
|
|
const que = model_get_relay_que(model, relay);
|
|
if (que.profiles.indexOf(pubkey) >= 0)
|
|
return;
|
|
que.profiles.push(pubkey);
|
|
}
|
|
|
|
function model_clear_profile_que(model, relay, sid) {
|
|
const que = model_get_relay_que(model, relay);
|
|
//log_debug(`cmp '${que.sid}' vs '${sid}'`);
|
|
if (que.sid != sid)
|
|
return;
|
|
delete que.current;
|
|
delete que.sid;
|
|
que.timestamp = 0;
|
|
log_debug("cleared qued");
|
|
}
|
|
|
|
function model_fetch_next_profile(model, relay) {
|
|
const que = model_get_relay_que(model, relay);
|
|
|
|
// Give up on existing case and add it back to the que
|
|
if (que.current) {
|
|
if ((new Date().getTime() - que.timestamp) / 1000 > 30) {
|
|
que.profiles = que.profiles.concat(que.current);
|
|
model.pool.unsubscribe(SID_PROFILES, relay);
|
|
log_debug(`(${relay.url}) gave up on ${que.current.length} profiles`);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (que.profiles.length == 0) {
|
|
delete que.current;
|
|
return;
|
|
}
|
|
log_debug(`(${relay.url}) has '${que.profiles.length} left profiles to fetch'`);
|
|
|
|
const set = new Set();
|
|
let i = 0;
|
|
while (que.profiles.length > 0 && i < 100) {
|
|
set.add(que.profiles.shift());
|
|
i++;
|
|
}
|
|
que.timestamp = new Date().getTime();
|
|
que.current = Array.from(set);
|
|
fetch_profiles(model.pool, relay, que.current);
|
|
}
|
|
|
|
/* model_process_event_profile updates the matching profile with the contents found
|
|
* in the event.
|
|
*/
|
|
function model_process_event_metadata(model, ev, update_view) {
|
|
const profile = model_get_profile(model, ev.pubkey);
|
|
const evs = model.all_events;
|
|
if (profile.evid &&
|
|
evs[ev.id].created_at < evs[profile.evid].created_at)
|
|
return;
|
|
profile.evid = ev.id;
|
|
profile.data = safe_parse_json(ev.content, "profile contents");
|
|
if (update_view)
|
|
view_timeline_update_profiles(model, profile.pubkey);
|
|
// 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) {
|
|
redraw_my_pfp(model);
|
|
}
|
|
}
|
|
|
|
function model_has_profile(model, pk) {
|
|
return !!model_get_profile(model, pk).evid;
|
|
}
|
|
|
|
function model_get_profile(model, pubkey) {
|
|
if (model.profiles.has(pubkey)) {
|
|
return model.profiles.get(pubkey);
|
|
}
|
|
model.profiles.set(pubkey, {
|
|
pubkey: pubkey,
|
|
evid: "",
|
|
relays: [],
|
|
data: {},
|
|
});
|
|
return model.profiles.get(pubkey);
|
|
}
|
|
|
|
function model_process_event_following(model, ev, update_view) {
|
|
contacts_process_event(model.contacts, model.pubkey, ev)
|
|
// TODO support loading relays that are stored on the initial relay
|
|
// I find this wierd since I may never want to do that and only have that
|
|
// information provided by the client - to be better understood
|
|
// load_our_relays(model.pubkey, model.pool, ev)
|
|
}
|
|
|
|
/* model_process_event_reaction updates the reactions dictionary
|
|
*/
|
|
function model_process_event_reaction(model, ev, update_view) {
|
|
let reaction = event_parse_reaction(ev);
|
|
if (!reaction) {
|
|
return;
|
|
}
|
|
if (!model.reactions_to[reaction.e])
|
|
model.reactions_to[reaction.e] = new Set();
|
|
model.reactions_to[reaction.e].add(ev.id);
|
|
if (update_view)
|
|
view_timeline_update_reaction(model, ev);
|
|
}
|
|
|
|
/* event_process_deletion updates the list of deleted events. Additionally
|
|
* pushes event ids onto the invalidated stack for any found.
|
|
*/
|
|
function model_process_event_deletion(model, ev, update_view) {
|
|
for (const tag of ev.tags) {
|
|
if (tag.length >= 2 && tag[0] === "e" && tag[1]) {
|
|
let evid = tag[1];
|
|
model.invalidated.push(evid);
|
|
model_remove_reaction(model, evid, update_view);
|
|
if (model.deleted[evid])
|
|
continue;
|
|
let ds = model.deletions[evid] =
|
|
(model.deletions[evid] || new Set());
|
|
ds.add(ev.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
function model_remove_reaction(model, evid, update_view) {
|
|
// deleted_ev -> target_ev -> original_ev
|
|
// Here we want to find the original react event to and remove it from our
|
|
// reactions map, then we want to update the element on the page. If the
|
|
// server does not clean up events correctly the increment/decrement method
|
|
// should work fine in theory.
|
|
const target_ev = model.all_events[evid];
|
|
if (!target_ev)
|
|
return;
|
|
const reaction = event_parse_reaction(target_ev);
|
|
if (!reaction)
|
|
return;
|
|
if (model.reactions_to[reaction.e])
|
|
model.reactions_to[reaction.e].delete(target_ev.id);
|
|
if (update_view)
|
|
view_timeline_update_reaction(model, target_ev);
|
|
}
|
|
|
|
/* model_event_has_unknown_ids checks the event if there are any referenced keys with
|
|
* unknown user profiles in the provided scope.
|
|
*/
|
|
function model_event_has_unknown_ids(damus, ev) {
|
|
// make sure this event itself is removed from unknowns
|
|
if (ev.kind === 0)
|
|
delete damus.unknown_pks[ev.pubkey]
|
|
delete damus.unknown_ids[ev.id]
|
|
let got_some = false
|
|
for (const tag of ev.tags) {
|
|
if (tag.length >= 2) {
|
|
if (tag[0] === "p") {
|
|
const pk = tag[1]
|
|
if (!model_has_profile(damus, pk) && is_valid_id(pk)) {
|
|
got_some = true
|
|
damus.unknown_pks[pk] = make_unk(tag[2], ev)
|
|
}
|
|
} else if (tag[0] === "e") {
|
|
const evid = tag[1]
|
|
if (!model_has_event(damus, evid) && is_valid_id(evid)) {
|
|
got_some = true
|
|
damus.unknown_ids[evid] = make_unk(tag[2], ev)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return got_some
|
|
}
|
|
|
|
function model_is_event_deleted(model, evid) {
|
|
// we've already know it's deleted
|
|
if (model.deleted[evid])
|
|
return model.deleted[evid]
|
|
|
|
const ev = model.all_events[evid]
|
|
if (!ev)
|
|
return false
|
|
|
|
// all deletion events
|
|
const ds = model.deletions[ev.id]
|
|
if (!ds)
|
|
return false
|
|
|
|
// find valid deletion events
|
|
for (const id of ds.keys()) {
|
|
const d_ev = model.all_events[id]
|
|
if (!d_ev)
|
|
continue
|
|
|
|
// only allow deletes from the user who created it
|
|
if (d_ev.pubkey === ev.pubkey) {
|
|
model.deleted[ev.id] = d_ev
|
|
delete model.deletions[ev.id]
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
function model_has_event(model, evid) {
|
|
return evid in model.all_events
|
|
}
|
|
|
|
function model_events_arr(model) {
|
|
const events = model.all_events;
|
|
let arr = [];
|
|
for (const evid in events) {
|
|
const ev = events[evid];
|
|
const i = arr_bsearch_insert(arr, ev, event_cmp_created);
|
|
arr.splice(i, 0, ev);
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
function test_model_events_arr() {
|
|
const arr = model_events_arr({all_events: {
|
|
"c": {name: "c", created_at: 2},
|
|
"a": {name: "a", created_at: 0},
|
|
"b": {name: "b", created_at: 1},
|
|
"e": {name: "e", created_at: 4},
|
|
"d": {name: "d", created_at: 3},
|
|
}});
|
|
let last;
|
|
while(arr.length > 0) {
|
|
let ev = arr.pop();
|
|
log_debug("test:", ev.name, ev.created_at);
|
|
if (!last) {
|
|
last = ev;
|
|
continue;
|
|
}
|
|
if (ev.created_at > last.created_at) {
|
|
log_error(`ev ${ev.name} should be before ${last.name}`);
|
|
}
|
|
last = ev;
|
|
}
|
|
}
|
|
|
|
async function model_save_settings(model) {
|
|
function _settings_save(ev, resolve, reject) {
|
|
const db = ev.target.result;
|
|
const tx = db.transaction("settings", "readwrite");
|
|
const store = tx.objectStore("settings");
|
|
tx.oncomplete = (ev) => {
|
|
db.close();
|
|
resolve();
|
|
//log_debug("settings saved");
|
|
};
|
|
tx.onerror = (ev) => {
|
|
db.close();
|
|
log_error("failed to save events");
|
|
reject(ev);
|
|
};
|
|
store.clear().onsuccess = () => {
|
|
store.put({
|
|
pubkey: model.pubkey,
|
|
notifications_last_viewed: model.notifications.last_viewed,
|
|
});
|
|
};
|
|
}
|
|
return dbcall(_settings_save);
|
|
}
|
|
|
|
async function model_load_settings(model) {
|
|
function _settings_load(ev, resolve, reject) {
|
|
const db = ev.target.result;
|
|
const tx = db.transaction("settings", "readonly");
|
|
const store = tx.objectStore("settings");
|
|
const req = store.get(model.pubkey);
|
|
req.onsuccess = (ev) => {
|
|
const settings = ev.target.result;
|
|
if (settings) {
|
|
model.notifications.last_viewed = settings.notifications_last_viewed;
|
|
}
|
|
db.close();
|
|
resolve();
|
|
log_debug("Successfully loaded events");
|
|
}
|
|
req.onerror = (ev) => {
|
|
db.close();
|
|
reject(ev);
|
|
log_error("Could not load settings.");
|
|
};
|
|
}
|
|
return dbcall(_settings_load);
|
|
}
|
|
|
|
async function model_save_events(model) {
|
|
function _events_save(ev, resolve, reject) {
|
|
const db = ev.target.result;
|
|
let tx = db.transaction("events", "readwrite");
|
|
let store = tx.objectStore("events");
|
|
tx.oncomplete = (ev) => {
|
|
db.close();
|
|
resolve();
|
|
//log_debug("saved events!");
|
|
};
|
|
tx.onerror = (ev) => {
|
|
db.close();
|
|
log_error("failed to save events");
|
|
reject(ev);
|
|
};
|
|
store.clear().onsuccess = ()=> {
|
|
for (const evid in model.all_events) {
|
|
store.put(model.all_events[evid]);
|
|
}
|
|
}
|
|
}
|
|
return dbcall(_events_save);
|
|
}
|
|
|
|
async function model_load_events(model, fn) {
|
|
function _events_load(ev, resolve, reject) {
|
|
const db = ev.target.result;
|
|
const tx = db.transaction("events", "readonly");
|
|
const store = tx.objectStore("events");
|
|
const cursor = store.openCursor();
|
|
cursor.onsuccess = (ev) => {
|
|
var cursor = ev.target.result;
|
|
if (cursor) {
|
|
fn(cursor.value);
|
|
cursor.continue();
|
|
} else {
|
|
db.close();
|
|
resolve();
|
|
log_debug("Successfully loaded events");
|
|
}
|
|
}
|
|
cursor.onerror = (ev) => {
|
|
db.close();
|
|
reject(ev);
|
|
log_error("Could not load events.");
|
|
};
|
|
}
|
|
return dbcall(_events_load);
|
|
}
|
|
|
|
function new_model() {
|
|
return {
|
|
all_events: {}, // our master list of all events
|
|
notifications: {
|
|
last_viewed: 0, // time since last looking at notifications
|
|
count: 0, // the number not seen since last looking
|
|
},
|
|
max_depth: 2,
|
|
reactions_to: {},
|
|
|
|
unknown_ids: {},
|
|
unknown_pks: {},
|
|
but_wait_theres_more: 0,
|
|
|
|
deletions: {},
|
|
deleted: {},
|
|
pow: 0, // pow difficulty target
|
|
profiles: new Map(), // pubkey => profile data
|
|
contacts: {
|
|
event: null,
|
|
friends: new Set(),
|
|
friend_of_friends: new Set(),
|
|
},
|
|
invalidated: [], // event ids which should be added/removed
|
|
elements: {}, // map of evid > rendered element
|
|
relay_que: new Map(),
|
|
};
|
|
}
|