maintain all user information in a stateful class

This commit is contained in:
BlowaterNostr 2023-07-15 16:40:21 +00:00
parent 13eba08177
commit 934818e7a9
5 changed files with 68 additions and 54 deletions

View File

@ -14,10 +14,9 @@ import { MessagePanel } from "./message-panel.tsx";
import { Setting } from "./setting.tsx"; import { Setting } from "./setting.tsx";
import { Database_Contextual_View } from "../database.ts"; import { Database_Contextual_View } from "../database.ts";
import { getAllUsersInformation, ProfilesSyncer, UserInfo } from "./contact-list.ts"; import { AllUsersInformation, ProfilesSyncer, UserInfo } from "./contact-list.ts";
import { new_DM_EditorModel } from "./editor.tsx"; import { new_DM_EditorModel } from "./editor.tsx";
import { Channel } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
import { initialModel, Model } from "./app_model.ts"; import { initialModel, Model } from "./app_model.ts";
import { import {
AppEventBus, AppEventBus,
@ -156,6 +155,7 @@ async function initProfileSyncer(
export class App { export class App {
profileSyncer!: ProfilesSyncer; profileSyncer!: ProfilesSyncer;
eventSyncer: EventSyncer; eventSyncer: EventSyncer;
public readonly allUsersInfo: AllUsersInformation;
constructor( constructor(
public readonly database: Database_Contextual_View, public readonly database: Database_Contextual_View,
@ -166,10 +166,11 @@ export class App {
public readonly relayPool: ConnectionPool, public readonly relayPool: ConnectionPool,
) { ) {
this.eventSyncer = new EventSyncer(this.relayPool, this.database); this.eventSyncer = new EventSyncer(this.relayPool, this.database);
this.allUsersInfo = new AllUsersInformation(myAccountContext);
} }
initApp = async (accountContext: NostrAccountContext) => { initApp = async (accountContext: NostrAccountContext) => {
const events = this.database.filterEvents((e) => e.kind == NostrKind.TEXT_NOTE); // const events = this.database.filterEvents((e) => e.kind == NostrKind.TEXT_NOTE);
console.log("App.initApp"); console.log("App.initApp");
const profilesSyncer = await initProfileSyncer(this.relayPool, accountContext, this.database); const profilesSyncer = await initProfileSyncer(this.relayPool, accountContext, this.database);
@ -179,15 +180,16 @@ export class App {
this.profileSyncer = profilesSyncer; this.profileSyncer = profilesSyncer;
this.model.allUsersInfo = getAllUsersInformation(this.database, this.myAccountContext); this.allUsersInfo.addEvents(this.database.events);
console.log("App allUsersInfo"); console.log("App allUsersInfo");
this.model.social.threads = getSocialPosts(this.database, this.model.allUsersInfo); this.model.social.threads = getSocialPosts(this.database, this.allUsersInfo.userInfos);
/* my profile */ /* my profile */
this.model.myProfile = this.model.allUsersInfo.get(accountContext.publicKey.hex)?.profile?.profile; this.model.myProfile = this.allUsersInfo.userInfos.get(accountContext.publicKey.hex)?.profile
?.profile;
/* contacts */ /* contacts */
for (const contact of this.model.allUsersInfo.values()) { for (const contact of this.allUsersInfo.userInfos.values()) {
const editor = this.model.editors.get(contact.pubkey.hex); const editor = this.model.editors.get(contact.pubkey.hex);
if (editor == null) { if (editor == null) {
const pubkey = PublicKey.FromHex(contact.pubkey.hex); const pubkey = PublicKey.FromHex(contact.pubkey.hex);
@ -206,7 +208,7 @@ export class App {
} }
await profilesSyncer.add( await profilesSyncer.add(
...Array.from(this.model.allUsersInfo.keys()), ...Array.from(this.allUsersInfo.userInfos.keys()),
); );
console.log("user set", profilesSyncer.userSet); console.log("user set", profilesSyncer.userSet);
@ -221,6 +223,7 @@ export class App {
this.profileSyncer, this.profileSyncer,
this.lamport, this.lamport,
this.eventBus, this.eventBus,
this.allUsersInfo,
) )
) { ) {
render(<AppComponent model={this.model} eventBus={this.eventBus} />, document.body); render(<AppComponent model={this.model} eventBus={this.eventBus} />, document.body);
@ -261,7 +264,11 @@ export function AppComponent(props: {
if (model.navigationModel.activeNav == "Social") { if (model.navigationModel.activeNav == "Social") {
let focusedContentGetter = () => { let focusedContentGetter = () => {
// console.log("AppComponent:getFocusedContent before", Date.now() - t); // console.log("AppComponent:getFocusedContent before", Date.now() - t);
let _ = getFocusedContent(model.social.focusedContent, model.allUsersInfo, model.social.threads); let _ = getFocusedContent(
model.social.focusedContent,
app.allUsersInfo.userInfos,
model.social.threads,
);
// console.log("AppComponent:getFocusedContent", Date.now() - t); // console.log("AppComponent:getFocusedContent", Date.now() - t);
if (_?.type === "MessageThread") { if (_?.type === "MessageThread") {
let editor = model.social.replyEditors.get(_.data.root.event.id); let editor = model.social.replyEditors.get(_.data.root.event.id);
@ -354,7 +361,7 @@ export function AppComponent(props: {
myAccountContext: myAccountCtx, myAccountContext: myAccountCtx,
db: app.database, db: app.database,
pool: app.relayPool, pool: app.relayPool,
allUserInfo: model.allUsersInfo, allUserInfo: app.allUsersInfo.userInfos,
profilesSyncer: app.profileSyncer, profilesSyncer: app.profileSyncer,
eventSyncer: app.eventSyncer, eventSyncer: app.eventSyncer,
})} })}

View File

@ -24,7 +24,7 @@ export type Model = {
key: string; key: string;
value: string; value: string;
}; };
allUsersInfo: Map<string, UserInfo>; // allUsersInfo: Map<string, UserInfo>;
// social // social
social: { social: {
@ -57,7 +57,7 @@ export function initialModel(): Model {
hasNewMessages: new Set(), hasNewMessages: new Set(),
currentSelectedContact: undefined, currentSelectedContact: undefined,
}, },
allUsersInfo: new Map(), // allUsersInfo: new Map(),
editors: editors, editors: editors,
newProfileField: { newProfileField: {
key: "", key: "",

View File

@ -1,7 +1,7 @@
import { getProfileEvent, getProfilesByName, saveProfile } from "../features/profile.ts"; import { getProfileEvent, getProfilesByName, saveProfile } from "../features/profile.ts";
import { App } from "./app.tsx"; import { App } from "./app.tsx";
import { getAllUsersInformation, getGroupOf, ProfilesSyncer, UserInfo } from "./contact-list.ts"; import { AllUsersInformation, getGroupOf, ProfilesSyncer, UserInfo } from "./contact-list.ts";
import * as csp from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts"; import * as csp from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
import { Database_Contextual_View } from "../database.ts"; import { Database_Contextual_View } from "../database.ts";
@ -27,7 +27,15 @@ import {
} from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/nostr.ts"; } from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/nostr.ts";
import { ConnectionPool } from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/relay.ts"; import { ConnectionPool } from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/relay.ts";
import { SignInEvent, signInWithExtension, signInWithPrivateKey } from "./signIn.tsx"; import { SignInEvent, signInWithExtension, signInWithPrivateKey } from "./signIn.tsx";
import { computeThreads, getTags, PinContact, UnpinContact } from "../nostr.ts"; import {
computeThreads,
Decrypted_Nostr_Event,
getTags,
PinContact,
PlainText_Nostr_Event,
Profile_Nostr_Event,
UnpinContact,
} from "../nostr.ts";
import { MessageThread } from "./dm.tsx"; import { MessageThread } from "./dm.tsx";
import { DexieDatabase } from "./dexie-db.ts"; import { DexieDatabase } from "./dexie-db.ts";
import { getSocialPosts } from "../features/social.ts"; import { getSocialPosts } from "../features/social.ts";
@ -192,7 +200,7 @@ export async function* UI_Interaction_Update(
}; };
const group = getGroupOf( const group = getGroupOf(
event.pubkey, event.pubkey,
model.allUsersInfo, model.app.allUsersInfo.userInfos,
); );
model.dm.selectedContactGroup = group; model.dm.selectedContactGroup = group;
updateConversation(model.app.model, event.pubkey); updateConversation(model.app.model, event.pubkey);
@ -440,12 +448,13 @@ export async function* Database_Update(
profileSyncer: ProfilesSyncer, profileSyncer: ProfilesSyncer,
lamport: LamportTime, lamport: LamportTime,
eventEmitter: EventEmitter<SelectProfile>, eventEmitter: EventEmitter<SelectProfile>,
allUserInfo: AllUsersInformation,
) { ) {
const changes = database.onChange((_) => true); const changes = database.onChange((_) => true);
while (true) { while (true) {
await csp.sleep(333); await csp.sleep(333);
await changes.ready(); await changes.ready();
const changes_events: NostrEvent[] = []; const changes_events: (PlainText_Nostr_Event | Decrypted_Nostr_Event | Profile_Nostr_Event)[] = [];
while (true) { while (true) {
if (!changes.isReadyToPop()) { if (!changes.isReadyToPop()) {
break; break;
@ -460,7 +469,7 @@ export async function* Database_Update(
let hasKind_1 = false; let hasKind_1 = false;
for (let e of changes_events) { for (let e of changes_events) {
model.allUsersInfo = getAllUsersInformation(database, ctx); allUserInfo.addEvents([e]);
const t = getTags(e).lamport_timestamp; const t = getTags(e).lamport_timestamp;
if (t) { if (t) {
lamport.set(t); lamport.set(t);
@ -470,7 +479,7 @@ export async function* Database_Update(
await profileSyncer.add(key.hex); await profileSyncer.add(key.hex);
} }
if (e.kind == NostrKind.META_DATA || e.kind == NostrKind.DIRECT_MESSAGE) { if (e.kind == NostrKind.META_DATA || e.kind == NostrKind.DIRECT_MESSAGE) {
for (const contact of model.allUsersInfo.values()) { for (const contact of allUserInfo.userInfos.values()) {
const editor = model.editors.get(contact.pubkey.hex); const editor = model.editors.get(contact.pubkey.hex);
if (editor == null) { // a stranger sends a message if (editor == null) { // a stranger sends a message
const pubkey = PublicKey.FromHex(contact.pubkey.hex); const pubkey = PublicKey.FromHex(contact.pubkey.hex);
@ -547,7 +556,7 @@ export async function* Database_Update(
} }
} }
if (hasKind_1) { if (hasKind_1) {
model.social.threads = getSocialPosts(database, model.allUsersInfo); model.social.threads = getSocialPosts(database, allUserInfo.userInfos);
} }
yield model; yield model;
} }

View File

@ -1,5 +1,5 @@
import { Database_Contextual_View } from "../database.ts"; import { Database_Contextual_View } from "../database.ts";
import { ProfileFromNostrEvent, profilesStream } from "../features/profile.ts"; import { profilesStream } from "../features/profile.ts";
import { Channel } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts"; import { Channel } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
import { ContactGroup } from "./contact-list.tsx"; import { ContactGroup } from "./contact-list.tsx";
@ -13,8 +13,13 @@ import {
ConnectionPool, ConnectionPool,
newSubID, newSubID,
} from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/relay.ts"; } from "https://raw.githubusercontent.com/BlowaterNostr/nostr.ts/main/relay.ts";
import { CustomAppData, getTags, Profile_Nostr_Event } from "../nostr.ts"; import {
import { assertEquals } from "https://deno.land/std@0.176.0/testing/asserts.ts"; CustomAppData,
Decrypted_Nostr_Event,
getTags,
PlainText_Nostr_Event,
Profile_Nostr_Event,
} from "../nostr.ts";
export interface UserInfo { export interface UserInfo {
pubkey: PublicKey; pubkey: PublicKey;
@ -97,18 +102,18 @@ function socialPostsStream(pubkeys: Iterable<string>, pool: ConnectionPool) {
return chan; return chan;
} }
export function getAllUsersInformation( export class AllUsersInformation {
database: Database_Contextual_View, readonly userInfos = new Map<string, UserInfo>();
ctx: NostrAccountContext,
): Map<string, UserInfo> { constructor(public readonly ctx: NostrAccountContext) {}
const t = Date.now();
const res = new Map<string, UserInfo>(); addEvents(events: (Profile_Nostr_Event | PlainText_Nostr_Event | Decrypted_Nostr_Event)[]) {
{ // const t = Date.now();
for (const event of database.filterEvents((_) => true)) { for (const event of events) {
switch (event.kind) { switch (event.kind) {
case NostrKind.META_DATA: case NostrKind.META_DATA:
{ {
const userInfo = res.get(event.pubkey); const userInfo = this.userInfos.get(event.pubkey);
const profileEvent = event; const profileEvent = event;
if (userInfo) { if (userInfo) {
if (userInfo.profile) { if (userInfo.profile) {
@ -126,7 +131,7 @@ export function getAllUsersInformation(
newestEventSendByMe: undefined, newestEventSendByMe: undefined,
profile: profileEvent, profile: profileEvent,
}; };
res.set(event.pubkey, newUserInfo); this.userInfos.set(event.pubkey, newUserInfo);
} }
} }
break; break;
@ -139,19 +144,19 @@ export function getAllUsersInformation(
case NostrKind.DIRECT_MESSAGE: case NostrKind.DIRECT_MESSAGE:
{ {
let whoAm_I_TalkingTo = ""; let whoAm_I_TalkingTo = "";
if (event.pubkey == ctx.publicKey.hex) { if (event.pubkey == this.ctx.publicKey.hex) {
// I am the sender // I am the sender
whoAm_I_TalkingTo = getTags(event).p[0]; whoAm_I_TalkingTo = getTags(event).p[0];
} else if (getTags(event).p[0] == ctx.publicKey.hex) { } else if (getTags(event).p[0] == this.ctx.publicKey.hex) {
// I am the receiver // I am the receiver
whoAm_I_TalkingTo = event.pubkey; whoAm_I_TalkingTo = event.pubkey;
} else { } else {
// I am neither. Possible because other user has used this device before // I am neither. Possible because other user has used this device before
break; break;
} }
const userInfo = res.get(whoAm_I_TalkingTo); const userInfo = this.userInfos.get(whoAm_I_TalkingTo);
if (userInfo) { if (userInfo) {
if (whoAm_I_TalkingTo == ctx.publicKey.hex) { if (whoAm_I_TalkingTo == this.ctx.publicKey.hex) {
// talking to myself // talking to myself
if (userInfo.newestEventSendByMe) { if (userInfo.newestEventSendByMe) {
if (event.created_at > userInfo.newestEventSendByMe?.created_at) { if (event.created_at > userInfo.newestEventSendByMe?.created_at) {
@ -163,7 +168,7 @@ export function getAllUsersInformation(
userInfo.newestEventReceivedByMe = event; userInfo.newestEventReceivedByMe = event;
} }
} else { } else {
if (ctx.publicKey.hex == event.pubkey) { if (this.ctx.publicKey.hex == event.pubkey) {
// I am the sender // I am the sender
if (userInfo.newestEventSendByMe) { if (userInfo.newestEventSendByMe) {
if (event.created_at > userInfo.newestEventSendByMe.created_at) { if (event.created_at > userInfo.newestEventSendByMe.created_at) {
@ -191,12 +196,12 @@ export function getAllUsersInformation(
newestEventSendByMe: undefined, newestEventSendByMe: undefined,
profile: undefined, profile: undefined,
}; };
if (whoAm_I_TalkingTo == ctx.publicKey.hex) { if (whoAm_I_TalkingTo == this.ctx.publicKey.hex) {
// talking to myself // talking to myself
newUserInfo.newestEventSendByMe = event; newUserInfo.newestEventSendByMe = event;
newUserInfo.newestEventReceivedByMe = event; newUserInfo.newestEventReceivedByMe = event;
} else { } else {
if (ctx.publicKey.hex == event.pubkey) { if (this.ctx.publicKey.hex == event.pubkey) {
// I am the sender // I am the sender
newUserInfo.newestEventSendByMe = event; newUserInfo.newestEventSendByMe = event;
} else { } else {
@ -204,7 +209,7 @@ export function getAllUsersInformation(
newUserInfo.newestEventReceivedByMe = event; newUserInfo.newestEventReceivedByMe = event;
} }
} }
res.set(whoAm_I_TalkingTo, newUserInfo); this.userInfos.set(whoAm_I_TalkingTo, newUserInfo);
} }
} }
break; break;
@ -216,7 +221,7 @@ export function getAllUsersInformation(
} }
const obj: CustomAppData = JSON.parse(event.decryptedContent); const obj: CustomAppData = JSON.parse(event.decryptedContent);
if (obj.type == "PinContact" || obj.type == "UnpinContact") { if (obj.type == "PinContact" || obj.type == "UnpinContact") {
const userInfo = res.get(obj.pubkey); const userInfo = this.userInfos.get(obj.pubkey);
if (userInfo) { if (userInfo) {
if (userInfo.pinEvent) { if (userInfo.pinEvent) {
if (event.created_at > userInfo.pinEvent.created_at) { if (event.created_at > userInfo.pinEvent.created_at) {
@ -232,7 +237,7 @@ export function getAllUsersInformation(
}; };
} }
} else { } else {
res.set(obj.pubkey, { this.userInfos.set(obj.pubkey, {
pubkey: PublicKey.FromHex(obj.pubkey) as PublicKey, // todo: could throw pubkey: PublicKey.FromHex(obj.pubkey) as PublicKey, // todo: could throw
pinEvent: { pinEvent: {
content: obj, content: obj,
@ -247,17 +252,8 @@ export function getAllUsersInformation(
} }
} }
} }
// console.log("AllUsersInformation:addEvents", Date.now() - t);
} }
// todo: should write a unit test for it instead of runtime assertion
for (const [pubkey, userInfo] of res) {
assertEquals(pubkey, userInfo.pubkey.hex);
if (userInfo.profile) {
assertEquals(pubkey, userInfo.profile.pubkey);
}
}
console.log("getAllUsersInformation", Date.now() - t);
return res;
} }
export const sortUserInfo = (a: UserInfo, b: UserInfo) => { export const sortUserInfo = (a: UserInfo, b: UserInfo) => {

View File

@ -332,9 +332,11 @@ export class Database_Contextual_View {
////////////////// //////////////////
// On DB Change // // On DB Change //
////////////////// //////////////////
onChange(filter: (e: NostrEvent) => boolean) { onChange(filter: (e: PlainText_Nostr_Event | Decrypted_Nostr_Event | Profile_Nostr_Event) => boolean) {
const c = this.caster.copy(); const c = this.caster.copy();
const res = csp.chan<NostrEvent>(buffer_size); const res = csp.chan<PlainText_Nostr_Event | Decrypted_Nostr_Event | Profile_Nostr_Event>(
buffer_size,
);
(async () => { (async () => {
for await (const newE of c) { for await (const newE of c) {
if (filter(newE)) { if (filter(newE)) {