diff --git a/web/icon/event-like.svg b/web/icon/event-like.svg index de99f58..414fbc2 100644 --- a/web/icon/event-like.svg +++ b/web/icon/event-like.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/web/icon/no-user.svg b/web/icon/no-user.svg new file mode 100644 index 0000000..e856dcf --- /dev/null +++ b/web/icon/no-user.svg @@ -0,0 +1,78 @@ + + + + + + Abstract user icon + + + + + + + + + + + + Abstract user icon + + + + diff --git a/web/js/core.js b/web/js/core.js index 3b28cf4..424ac5b 100644 --- a/web/js/core.js +++ b/web/js/core.js @@ -171,7 +171,7 @@ function model_get_reacts_to(model, pubkey, evid, emoji) { if (!r) return; for (const id of r.keys()) { - if (is_deleted(model, id)) + if (model_is_event_deleted(model, id)) continue; const reaction = model.all_events[id]; if (!reaction || reaction.pubkey != pubkey) @@ -189,7 +189,7 @@ function get_reactions(model, evid) { let reactions = [] for (const id of reactions_set.keys()) { - if (is_deleted(model, id)) + if (model_is_event_deleted(model, id)) continue const reaction = model.all_events[id] if (!reaction) diff --git a/web/js/damus.js b/web/js/damus.js index 2a767bf..efef17c 100644 --- a/web/js/damus.js +++ b/web/js/damus.js @@ -2,18 +2,20 @@ let DAMUS const BOOTSTRAP_RELAYS = [ "wss://relay.damus.io", - "wss://nostr-relay.wlvs.space", - "wss://nostr-pub.wellorder.net", + //"wss://nostr-relay.wlvs.space", + //"wss://nostr-pub.wellorder.net", ] // TODO autogenerate these constants with a bash script const IMG_EVENT_LIKED = "icon/event-liked.svg"; const IMG_EVENT_LIKE = "icon/event-like.svg"; +const IMG_NO_USER = "icon/no-user.svg"; -const SID_META = "meta"; -const SID_HISTORY = "history"; +const SID_META = "meta"; +const SID_HISTORY = "history"; const SID_NOTIFICATIONS = "notifications"; -const SID_EXPLORE = "explore"; +const SID_EXPLORE = "explore"; +const SID_PROFILES = "profiles"; async function damus_web_init() { init_message_textareas(); @@ -23,7 +25,7 @@ async function damus_web_init() { const max_wait = 500; const interval = 20; if (window.nostr || tries >= (max_wait/interval)) { - console.info("init after", tries); + log_info("init after", tries); damus_web_init_ready(); return; } @@ -62,7 +64,6 @@ async function damus_web_init_ready() { pool.on("event", on_pool_event); pool.on("notice", on_pool_notice); pool.on("eose", on_pool_eose); - pool.on("ok", on_pool_ok); // Load all events from storage and re-process them so that apply correct // effects. @@ -77,6 +78,7 @@ async function damus_web_init_ready() { on_timer_timestamps(); on_timer_invalidations(); on_timer_save(); + on_timer_tick(); return pool; } @@ -93,7 +95,7 @@ function on_timer_invalidations() { if (DAMUS.invalidated.length > 0) view_timeline_update(DAMUS); on_timer_invalidations(); - }, 1 * 1000); + }, 100); } function on_timer_save() { @@ -104,6 +106,15 @@ function on_timer_save() { }, 10 * 1000); } +function on_timer_tick() { + setTimeout(() => { + DAMUS.relay_que.forEach((que, relay) => { + model_fetch_next_profile(DAMUS, relay); + }); + on_timer_tick(); + }, 1 * 1000); +} + /* on_pool_open occurs when a relay is opened. It then subscribes for the * relative REQ as needed. */ @@ -122,10 +133,10 @@ function on_pool_open(relay) { limit: 5000, }]); - // Subscribe to the relay's world. We will also never close this. - relay.subscribe(SID_EXPLORE, [{ + // Get the latest history as a prefetch + relay.subscribe(SID_HISTORY, [{ kinds: STANDARD_KINDS, - limit: 10000, // TODO this is a lot to handle and we should deal with it another way + limit: 500, }]); } @@ -141,36 +152,42 @@ async function on_pool_eose(relay, sub_id) { const { pool } = model; const sid = sub_id.slice(0, sub_id.indexOf(":")); - switch (sub_id) { + switch (sid) { + case SID_PROFILES: case SID_META: - model_get_relay_que(model, relay).busy = false; - model_fetch_next_profile(model, relay); case SID_HISTORY: pool.unsubscribe(sub_id, relay); break; } } -function on_pool_ok(relay) { - log_info(`OK(${relay.url})`, arguments); -} - function on_pool_event(relay, sub_id, ev) { const model = DAMUS; // Simply ignore any events that happened in the future. if (new Date(ev.created_at * 1000) > new Date()) { + log_debug(`blocked event caust it was newer`, ev); return; } model_process_event(model, relay, ev); } +function fetch_profiles(pool, relay, pubkeys) { + log_debug(`(${relay.url}) fetching '${pubkeys.length} profiles'`); + pool.subscribe(SID_PROFILES, [{ + kinds: [KIND_METADATA], + authors: pubkeys, + }], relay); +} + function fetch_profile_info(pubkey, pool, relay) { - pool.subscribe(`${SID_META}:${pubkey}`, [{ + const sid = `${SID_META}:${pubkey}`; + pool.subscribe(sid, [{ kinds: [KIND_METADATA, KIND_CONTACT], authors: [pubkey], limit: 1, }], relay); + return sid; } function fetch_profile(pubkey, pool, relay) { @@ -182,51 +199,13 @@ function fetch_profile(pubkey, pool, relay) { }], relay); } -/* -function new_sub_id(prefix) { - return `${prefix}:${uuidv4()}`; +function subscribe_explore(limit) { + DAMUS.pool.subscribe(SID_EXPLORE, [{ + kinds: STANDARD_KINDS, + limit: limit, + }]); } -let request_profiles_timer; -function request_profiles() { - if (request_profiles_timer) - clearTimeout(request_profiles_timer); - request_profiles_timer = setTimeout(()=>{ - if (fetch_queued_profiles(DAMUS)) - request_profiles(); - }, 200); +function unsubscribe_explore() { + DAMUS.pool.unsubscribe(SID_EXPLORE); } - -function fetch_queued_profiles(model) { - const delayed = []; - const m = new Map(); - for(let i = 0; model.requested_profiles.length > 0 && i < 300; i++) { - let r = model.requested_profiles.pop(); - let s; - if (!m.has(r.relay)) { - s = new Set(); - m.set(r.relay, s); - } else { - s = m.get(r.relay); - } - if (s.size >= 50) { - delayed.push(r); - continue; - } - s.add(r.pubkey); - } - model.requested_profiles = model.requested_profiles.concat(delayed); - //log_debug(`m size ${m.size}`); - m.forEach((set, relay) => { - let filters = [{ - kinds: [KIND_METADATA, KIND_CONTACT], - authors: Array.from(set), - }]; - let sid = new_sub_id(SID_PROFILES); - model.pool.subscribe(sid, filters, relay); - log_debug(`(${relay.url}) fetching profiles ${sid} size(${set.size})`); - }) - return model.requested_profiles.length > 0; -}*/ - - diff --git a/web/js/model.js b/web/js/model.js index e27e2bc..82bb34c 100644 --- a/web/js/model.js +++ b/web/js/model.js @@ -41,24 +41,66 @@ function model_process_event(model, relay, ev) { // Request the profile if we have never seen it if (!model.profile_events[ev.pubkey]) - model_fetch_next_profile(model, relay, ev.pubkey); + model_que_profile(model, relay, ev.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:[]}); + return map_get(model.relay_que, relay, { + profiles: [], + timestamp: 0, + }); } -function model_fetch_next_profile(model, relay, pubkey) { +function model_que_profile(model, relay, pubkey) { const que = model_get_relay_que(model, relay); - if (pubkey) - que.profiles.push(pubkey); - if (que.busy || que.profiles.length == 0) + if (que.profiles.indexOf(pubkey) >= 0) return; - que.busy = true; - fetch_profile_info(que.profiles.shift(), model.pool, relay); + 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 @@ -72,6 +114,13 @@ function model_process_event_metadata(model, ev, update_view) { model.profiles[ev.pubkey] = safe_parse_json(ev.content, "profile contents") if (update_view) view_timeline_update_profiles(model, ev); + + // 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 (ev.pubkey == model.pubkey) { + redraw_my_pfp(model); + } } function model_process_event_following(model, ev, update_view) { @@ -238,9 +287,6 @@ async function model_save_events(model) { const db = ev.target.result; let tx = db.transaction("events", "readwrite"); let store = tx.objectStore("events"); - for (const evid in model.all_events) { - store.put(model.all_events[evid]); - } tx.oncomplete = (ev) => { db.close(); resolve(); @@ -251,6 +297,11 @@ async function model_save_events(model) { 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); } diff --git a/web/js/ui/render.js b/web/js/ui/render.js index a061814..5a179d2 100644 --- a/web/js/ui/render.js +++ b/web/js/ui/render.js @@ -288,7 +288,7 @@ function render_pfp(pk, profile, opts={}) { $${str} data-pubkey="${pk}" title="${name}" - onerror="this.onerror=null;this.src='${robohash(pk)}';" + onerror="this.onerror=null;this.src='./icon/no-user.svg';" src="${get_picture(pk, profile)}"/>` } diff --git a/web/js/ui/state.js b/web/js/ui/state.js index b76b679..a2af100 100644 --- a/web/js/ui/state.js +++ b/web/js/ui/state.js @@ -13,6 +13,12 @@ function view_timeline_apply_mode(model, mode, opts={}) { const { pubkey, thread_id } = opts; const el = view_get_timeline_el(); + if (mode == VM_EXPLORE) { + subscribe_explore(100); + } else { + unsubscribe_explore(); + } + el.dataset.mode = mode; switch(mode) { case VM_THREAD: @@ -129,10 +135,10 @@ function view_timeline_update(model) { if (count > 0) { // If we have things to show and we have initted and we don't have // anything update the current view - if (!latest_ev && model.inited) { + if (!latest_ev) { view_timeline_show_new(model); } - view_set_show_count(count, true, !model.inited); + view_set_show_count(count, true, false); } } @@ -198,12 +204,6 @@ function view_timeline_update_profiles(model, ev) { const el = view_get_timeline_el(); const pk = ev.pubkey; const p = model.profiles[pk]; - - // If it's my pubkey let's redraw my pfp that is not located in the view - if (pk == model.pubkey) { - redraw_my_pfp(model); - } - const name = fmt_profile_name(p, fmt_pubkey(pk)); const pic = get_picture(pk, p) for (const evid in model.elements) { @@ -268,7 +268,6 @@ function view_mode_contains_event(model, ev, mode, opts={}) { return ev.id == opts.thread_id || (ev.refs && ( ev.refs.root == opts.thread_id || ev.refs.reply == opts.thread_id)); - //event_refs_event(ev, opts.thread_id); case VM_NOTIFICATIONS: return event_refs_pubkey(ev, model.pubkey); } @@ -277,9 +276,6 @@ function view_mode_contains_event(model, ev, mode, opts={}) { function event_is_renderable(ev={}) { return ev.kind == KIND_NOTE || ev.kind == KIND_SHARE; - return ev.kind == KIND_NOTE || - ev.kind == KIND_REACTION || - ev.kind == KIND_DELETE; } function get_default_max_depth(damus, view) { diff --git a/web/js/util.js b/web/js/util.js index fb08bed..d294945 100644 --- a/web/js/util.js +++ b/web/js/util.js @@ -195,10 +195,6 @@ function fmt_since_str(current, previous) { } } -function robohash(str) { - return "https://robohash.org/" + str -} - function is_valid_reaction_content(content) { return content === "+" || content === "" || is_emoji(content) } @@ -233,10 +229,10 @@ function get_qs(loc=location.href) { function get_picture(pk, profile) { if (!profile) - return robohash(pk) + return IMG_NO_USER; if (profile.resolved_picture) return profile.resolved_picture - profile.resolved_picture = html`${profile.picture}` || robohash(pk) + profile.resolved_picture = html`${profile.picture}` || IMG_NO_USER; return profile.resolved_picture }