move DirectMessageGetter out from Database_Contextual_View (#229)

to DirectedMessageController
This commit is contained in:
BlowaterNostr 2023-10-09 18:39:52 +00:00 committed by GitHub
parent d6bbaed8e6
commit f97a8c9dbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 179 additions and 213 deletions

View File

@ -26,10 +26,11 @@ import { About } from "./about.tsx";
import { ProfileSyncer } from "../features/profile.ts"; import { ProfileSyncer } from "../features/profile.ts";
import { Popover, PopOverInputChannel } from "./components/popover.tsx"; import { Popover, PopOverInputChannel } from "./components/popover.tsx";
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 { GroupChatController, GroupChatSyncer } from "../group-chat.ts"; import { GroupChatSyncer, GroupMessageController } from "../features/gm.ts";
import { OtherConfig } from "./config-other.ts"; import { OtherConfig } from "./config-other.ts";
import { ProfileGetter } from "./search.tsx"; import { ProfileGetter } from "./search.tsx";
import { fromEvents } from "../time.ts"; import { fromEvents } from "../time.ts";
import { DirectedMessageController } from "../features/dm.ts";
export async function Start(database: DexieDatabase) { export async function Start(database: DexieDatabase) {
console.log("Start the application"); console.log("Start the application");
@ -110,8 +111,9 @@ export class App {
public readonly eventSyncer: EventSyncer, public readonly eventSyncer: EventSyncer,
public readonly conversationLists: ConversationLists, public readonly conversationLists: ConversationLists,
public readonly relayConfig: RelayConfig, public readonly relayConfig: RelayConfig,
public readonly groupChatController: GroupChatController, public readonly groupChatController: GroupMessageController,
public readonly lamport: time.LamportTime, public readonly lamport: time.LamportTime,
public readonly dmController: DirectedMessageController,
) {} ) {}
static async Start(args: { static async Start(args: {
@ -137,24 +139,42 @@ export class App {
const conversationLists = new ConversationLists(args.ctx, profileSyncer); const conversationLists = new ConversationLists(args.ctx, profileSyncer);
conversationLists.addEvents(args.database.events); conversationLists.addEvents(args.database.events);
const dmControl = new DirectedMessageController(args.ctx);
const groupSyncer = new GroupChatSyncer(args.database, args.pool); const groupSyncer = new GroupChatSyncer(args.database, args.pool);
const groupChatController = new GroupChatController( const groupChatController = new GroupMessageController(
args.ctx, args.ctx,
conversationLists, conversationLists,
groupSyncer, groupSyncer,
profileSyncer, profileSyncer,
); );
for (const e of args.database.events) {
if (e.kind == NostrKind.Group_Message) { (async () => {
const err = await groupChatController.addEvent({ for (const e of args.database.events) {
...e, if (e.kind == NostrKind.Group_Message) {
kind: e.kind, const err = await groupChatController.addEvent({
}); ...e,
if (err instanceof Error) { kind: e.kind,
console.error(err.message); });
if (err instanceof Error) {
console.error(err.message);
}
} else if (e.kind == NostrKind.DIRECT_MESSAGE) {
const error = await dmControl.addEvent({
...e,
kind: e.kind,
});
if (error instanceof Error) {
console.error(error.message); // should delete the event
}
} else {
continue;
} }
// notify update loop to render
// todo: directly call render instead of go through database update loop
args.database.sourceOfChange.put(null);
} }
} })();
return new App( return new App(
args.database, args.database,
@ -170,6 +190,7 @@ export class App {
relayConfig, relayConfig,
groupChatController, groupChatController,
lamport, lamport,
dmControl,
); );
} }
@ -313,6 +334,7 @@ export class App {
this.lamport, this.lamport,
this.conversationLists, this.conversationLists,
this.groupChatController, this.groupChatController,
this.dmController,
) )
) { ) {
const t = Date.now(); const t = Date.now();
@ -403,7 +425,7 @@ export function AppComponent(props: {
rightPanelModel: model.rightPanelModel, rightPanelModel: model.rightPanelModel,
bus: app.eventBus, bus: app.eventBus,
ctx: myAccountCtx, ctx: myAccountCtx,
dmGetter: app.database, dmGetter: app.dmController,
profileGetter: app.database, profileGetter: app.database,
pool: props.pool, pool: props.pool,
conversationLists: app.conversationLists, conversationLists: app.conversationLists,

View File

@ -9,7 +9,7 @@ import * as csp from "https://raw.githubusercontent.com/BlowaterNostr/csp/master
import { Database_Contextual_View } from "../database.ts"; import { Database_Contextual_View } from "../database.ts";
import { convertEventsToChatMessages } from "./dm.ts"; import { convertEventsToChatMessages } from "./dm.ts";
import { sendDMandImages } from "../features/dm.ts"; import { DirectedMessageController, sendDMandImages } from "../features/dm.ts";
import { notify } from "./notification.ts"; import { notify } from "./notification.ts";
import { EventBus } from "../event-bus.ts"; import { EventBus } from "../event-bus.ts";
import { ContactUpdate } from "./conversation-list.tsx"; import { ContactUpdate } from "./conversation-list.tsx";
@ -25,6 +25,7 @@ import {
DirectedMessage_Event, DirectedMessage_Event,
Encrypted_Event, Encrypted_Event,
getTags, getTags,
Parsed_Event,
PinConversation, PinConversation,
Profile_Nostr_Event, Profile_Nostr_Event,
UnpinConversation, UnpinConversation,
@ -43,7 +44,7 @@ import { NostrAccountContext, NostrEvent, NostrKind } from "../lib/nostr-ts/nost
import { ConnectionPool } from "../lib/nostr-ts/relay.ts"; import { ConnectionPool } from "../lib/nostr-ts/relay.ts";
import { OtherConfig } from "./config-other.ts"; import { OtherConfig } from "./config-other.ts";
import { EditGroup, EditGroupChatProfile, StartEditGroupChatProfile } from "./edit-group.tsx"; import { EditGroup, EditGroupChatProfile, StartEditGroupChatProfile } from "./edit-group.tsx";
import { GroupChatController } from "../group-chat.ts"; import { GroupMessageController } from "../features/gm.ts";
import { ChatMessage } from "./message.ts"; import { ChatMessage } from "./message.ts";
export type UI_Interaction_Event = export type UI_Interaction_Event =
@ -481,14 +482,6 @@ export function getConversationMessages(args: {
} }
const messages = convertEventsToChatMessages(events); const messages = convertEventsToChatMessages(events);
if (messages.length > 0) {
messages.sort((m1, m2) => {
if (m1.lamport && m2.lamport && m1.lamport != m2.lamport) {
return m1.lamport - m2.lamport;
}
return m1.created_at.getTime() - m2.created_at.getTime();
});
}
return messages; return messages;
} }
@ -521,14 +514,15 @@ export async function* Database_Update(
profileSyncer: ProfileSyncer, profileSyncer: ProfileSyncer,
lamport: LamportTime, lamport: LamportTime,
convoLists: ConversationLists, convoLists: ConversationLists,
groupController: GroupChatController, groupController: GroupMessageController,
dmController: DirectedMessageController,
) { ) {
const changes = database.subscribe(); const changes = database.subscribe();
while (true) { while (true) {
await csp.sleep(333); await csp.sleep(333);
await changes.ready(); await changes.ready();
const t = Date.now(); const t = Date.now();
const changes_events: (Encrypted_Event | Profile_Nostr_Event | NostrEvent)[] = []; const changes_events: (Encrypted_Event | Profile_Nostr_Event | Parsed_Event)[] = [];
while (true) { while (true) {
if (!changes.isReadyToPop()) { if (!changes.isReadyToPop()) {
break; break;
@ -587,10 +581,12 @@ export async function* Database_Update(
model.myProfile = newProfile.profile; model.myProfile = newProfile.profile;
} }
} else if (e.kind == NostrKind.DIRECT_MESSAGE) { } else if (e.kind == NostrKind.DIRECT_MESSAGE) {
const pubkey = PublicKey.FromHex(e.pubkey); const err = await dmController.addEvent({
if (pubkey instanceof Error) { ...e,
console.error(pubkey); kind: e.kind,
continue; });
if (err instanceof Error) {
console.error(err);
} }
} }
} else if (e.kind == NostrKind.Group_Message) { } else if (e.kind == NostrKind.Group_Message) {
@ -628,10 +624,6 @@ export async function* Database_Update(
// } // }
// } // }
} }
// if (hasKind_1) {
// console.log("Database_Update: getSocialPosts");
// model.social.threads = getSocialPosts(database, convoLists.convoSummaries);
// }
yield model; yield model;
} }
} }
@ -656,7 +648,7 @@ export async function handle_SendMessage(
dmEditors: Map<string, EditorModel>, dmEditors: Map<string, EditorModel>,
gmEditors: Map<string, EditorModel>, gmEditors: Map<string, EditorModel>,
db: Database_Contextual_View, db: Database_Contextual_View,
groupControl: GroupChatController, groupControl: GroupMessageController,
) { ) {
if (event.isGroupChat) { if (event.isGroupChat) {
const groupCtx = groupControl.getGroupChatCtx(event.pubkey); const groupCtx = groupControl.getGroupChatCtx(event.pubkey);

View File

@ -1,9 +1,9 @@
import { ConversationListRetriever, NewMessageChecker } from "./conversation-list.tsx"; import { ConversationListRetriever, NewMessageChecker } from "./conversation-list.tsx";
import { PublicKey } from "../lib/nostr-ts/key.ts"; import { PublicKey } from "../lib/nostr-ts/key.ts";
import { NostrAccountContext, NostrEvent, NostrKind } from "../lib/nostr-ts/nostr.ts"; import { NostrAccountContext, NostrEvent, NostrKind } from "../lib/nostr-ts/nostr.ts";
import { getTags, Parsed_Event, Profile_Nostr_Event } from "../nostr.ts"; import { getTags, Parsed_Event } from "../nostr.ts";
import { ProfileSyncer } from "../features/profile.ts"; import { ProfileSyncer } from "../features/profile.ts";
import { GroupChatCreation } from "../group-chat.ts"; import { GroupChatCreation } from "../features/gm.ts";
export interface ConversationSummary { export interface ConversationSummary {
pubkey: PublicKey; pubkey: PublicKey;

View File

@ -27,8 +27,8 @@ export interface ConversationListRetriever {
getConversationType(pubkey: PublicKey, isGourpChat: boolean): "Contacts" | "Strangers" | "Group"; getConversationType(pubkey: PublicKey, isGourpChat: boolean): "Contacts" | "Strangers" | "Group";
} }
export interface GroupChatListGetter { export interface GroupMessageListGetter {
getGroupChat: () => Iterable<ConversationSummary>; getConversationList: () => Iterable<ConversationSummary>;
} }
export type ConversationType = "Contacts" | "Strangers" | "Group"; export type ConversationType = "Contacts" | "Strangers" | "Group";
@ -47,7 +47,7 @@ type Props = {
emit: emitFunc<ContactUpdate | SearchUpdate>; emit: emitFunc<ContactUpdate | SearchUpdate>;
eventBus: EventSubscriber<UI_Interaction_Event>; eventBus: EventSubscriber<UI_Interaction_Event>;
convoListRetriever: ConversationListRetriever; convoListRetriever: ConversationListRetriever;
groupChatListGetter: GroupChatListGetter; groupChatListGetter: GroupMessageListGetter;
hasNewMessages: NewMessageChecker; hasNewMessages: NewMessageChecker;
pinListGetter: PinListGetter; pinListGetter: PinListGetter;
profileGetter: ProfileGetter; profileGetter: ProfileGetter;
@ -86,7 +86,7 @@ export class ConversationList extends Component<Props, State> {
let listToRender: ConversationSummary[]; let listToRender: ConversationSummary[];
const contacts = Array.from(props.convoListRetriever.getContacts()); const contacts = Array.from(props.convoListRetriever.getContacts());
const strangers = Array.from(props.convoListRetriever.getStrangers()); const strangers = Array.from(props.convoListRetriever.getStrangers());
const groups = Array.from(props.groupChatListGetter.getGroupChat()); const groups = Array.from(props.groupChatListGetter.getConversationList());
switch (this.state.selectedContactGroup) { switch (this.state.selectedContactGroup) {
case "Contacts": case "Contacts":
listToRender = contacts; listToRender = contacts;

View File

@ -21,7 +21,7 @@ import { EventSyncer } from "./event_syncer.ts";
import { ButtonGroup } from "./components/button-group.tsx"; import { ButtonGroup } from "./components/button-group.tsx";
import { PrimaryTextColor } from "./style/colors.ts"; import { PrimaryTextColor } from "./style/colors.ts";
import { SettingIcon } from "./icons2/setting-icon.tsx"; import { SettingIcon } from "./icons2/setting-icon.tsx";
import { GroupChatController } from "../group-chat.ts"; import { GroupMessageController } from "../features/gm.ts";
import { ProfileGetter } from "./search.tsx"; import { ProfileGetter } from "./search.tsx";
import { InviteIcon } from "./icons2/invite-icon.tsx"; import { InviteIcon } from "./icons2/invite-icon.tsx";
import { PublicKey } from "../lib/nostr-ts/key.ts"; import { PublicKey } from "../lib/nostr-ts/key.ts";
@ -33,7 +33,7 @@ type DirectMessageContainerProps = {
bus: EventBus<UI_Interaction_Event>; bus: EventBus<UI_Interaction_Event>;
profilesSyncer: ProfileSyncer; profilesSyncer: ProfileSyncer;
eventSyncer: EventSyncer; eventSyncer: EventSyncer;
groupChatController: GroupChatController; groupChatController: GroupMessageController;
// getters // getters
profileGetter: ProfileGetter; profileGetter: ProfileGetter;
dmGetter: DirectMessageGetter; dmGetter: DirectMessageGetter;

View File

@ -1,11 +1,4 @@
import { import { Encrypted_Event, getTags, Parsed_Event, Profile_Nostr_Event } from "./nostr.ts";
compare,
DirectedMessage_Event,
Encrypted_Event,
getTags,
Parsed_Event,
Profile_Nostr_Event,
} from "./nostr.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 { parseJSON, ProfileData } from "./features/profile.ts"; import { parseJSON, ProfileData } from "./features/profile.ts";
import { parseContent } from "./UI/message.ts"; import { parseContent } from "./UI/message.ts";
@ -20,7 +13,6 @@ import {
} from "./lib/nostr-ts/nostr.ts"; } from "./lib/nostr-ts/nostr.ts";
import { PublicKey } from "./lib/nostr-ts/key.ts"; import { PublicKey } from "./lib/nostr-ts/key.ts";
import { NoteID } from "./lib/nostr-ts/nip19.ts"; import { NoteID } from "./lib/nostr-ts/nip19.ts";
import { DirectMessageGetter } from "./UI/app_update.tsx";
import { ProfileController } from "./UI/search.tsx"; import { ProfileController } from "./UI/search.tsx";
const buffer_size = 2000; const buffer_size = 2000;
@ -50,11 +42,9 @@ export interface EventPutter {
export type EventsAdapter = EventsFilter & EventRemover & EventGetter & EventPutter; export type EventsAdapter = EventsFilter & EventRemover & EventGetter & EventPutter;
type Accepted_Event = Encrypted_Event | Profile_Nostr_Event | NostrEvent; export class Database_Contextual_View implements ProfileController, EventGetter {
export class Database_Contextual_View implements DirectMessageGetter, ProfileController, EventGetter { public readonly sourceOfChange = csp.chan<Parsed_Event | null>(buffer_size);
private readonly sourceOfChange = csp.chan<Accepted_Event | null>(buffer_size); private readonly caster = csp.multi<Parsed_Event | null>(this.sourceOfChange);
private readonly caster = csp.multi<Accepted_Event | null>(this.sourceOfChange);
public readonly directed_messages = new Map<string, DirectedMessage_Event>();
public readonly profiles = new Map<string, Profile_Nostr_Event>(); public readonly profiles = new Map<string, Profile_Nostr_Event>();
private constructor( private constructor(
@ -63,6 +53,50 @@ export class Database_Contextual_View implements DirectMessageGetter, ProfileCon
private readonly ctx: NostrAccountContext, private readonly ctx: NostrAccountContext,
) {} ) {}
static async New(eventsAdapter: EventsAdapter, ctx: NostrAccountContext) {
const t = Date.now();
const allEvents = await eventsAdapter.filter();
console.log("Database_Contextual_View:onload", Date.now() - t, allEvents.length);
const initialEvents = [];
for (const e of allEvents) {
const pubkey = PublicKey.FromHex(e.pubkey);
if (pubkey instanceof Error) {
console.error("impossible state");
await eventsAdapter.remove(e.id);
continue;
}
const p: Parsed_Event = {
...e,
parsedTags: getTags(e),
publicKey: pubkey,
};
initialEvents.push(p);
}
console.log("Database_Contextual_View:parsed", Date.now() - t);
// Construct the View
const db = new Database_Contextual_View(
eventsAdapter,
initialEvents,
ctx,
);
console.log("Database_Contextual_View:New time spent", Date.now() - t);
for (const e of db.events) {
if (e.kind == NostrKind.META_DATA) {
// @ts-ignore
const pEvent = parseProfileEvent(e);
if (pEvent instanceof Error) {
return pEvent;
}
db.setProfile(pEvent);
}
}
return db;
}
get(keys: Indices): Parsed_Event | undefined { get(keys: Indices): Parsed_Event | undefined {
for (const e of this.events) { for (const e of this.events) {
if (e.id == keys.id) { if (e.id == keys.id) {
@ -118,86 +152,10 @@ export class Database_Contextual_View implements DirectMessageGetter, ProfileCon
} }
} }
static async New(eventsAdapter: EventsAdapter, ctx: NostrAccountContext) {
const t = Date.now();
const allEvents = await eventsAdapter.filter();
console.log("Database_Contextual_View:onload", Date.now() - t, allEvents.length);
const initialEvents = [];
for (const e of allEvents) {
const pubkey = PublicKey.FromHex(e.pubkey);
if (pubkey instanceof Error) {
console.error("impossible state");
await eventsAdapter.remove(e.id);
continue;
}
const p: Parsed_Event = {
...e,
parsedTags: getTags(e),
publicKey: pubkey,
};
initialEvents.push(p);
}
console.log("Database_Contextual_View:parsed", Date.now() - t);
// Construct the View
const db = new Database_Contextual_View(
eventsAdapter,
initialEvents,
ctx,
);
console.log("Database_Contextual_View:New time spent", Date.now() - t);
for (const e of db.events) {
if (e.kind == NostrKind.META_DATA) {
// @ts-ignore
const pEvent = parseProfileEvent(e);
if (pEvent instanceof Error) {
return pEvent;
}
db.setProfile(pEvent);
}
}
// load decrypted DMs
(async () => {
for (const event of allEvents) {
if (event.kind != NostrKind.DIRECT_MESSAGE) {
continue;
}
const dmEvent = await parseDM(
// @ts-ignore
event,
ctx,
getTags(event),
PublicKey.FromHex(event.pubkey),
);
if (dmEvent instanceof Error) {
await eventsAdapter.remove(event.id);
continue;
}
db.directed_messages.set(event.id, dmEvent);
db.sourceOfChange.put(null); // to render once
}
})();
return db;
}
public readonly filterEvents = (filter: (e: NostrEvent) => boolean) => { public readonly filterEvents = (filter: (e: NostrEvent) => boolean) => {
return this.events.filter(filter); return this.events.filter(filter);
}; };
// get the direct messages between me and this pubkey
public getDirectMessages(pubkey: string) {
const events = [];
for (const event of this.directed_messages.values()) {
if (is_DM_between(event, this.ctx.publicKey.hex, pubkey)) {
events.push(event);
}
}
return events.sort(compare);
}
async addEvent(event: NostrEvent) { async addEvent(event: NostrEvent) {
// check if the event exists // check if the event exists
const storedEvent = await this.eventsAdapter.get({ id: event.id }); const storedEvent = await this.eventsAdapter.get({ id: event.id });
@ -227,14 +185,7 @@ export class Database_Contextual_View implements DirectMessageGetter, ProfileCon
this.events.push(parsedEvent); this.events.push(parsedEvent);
if (parsedEvent.kind == NostrKind.DIRECT_MESSAGE) { if (parsedEvent.kind == NostrKind.META_DATA) {
// @ts-ignore
const dmEvent = await parseDM(parsedEvent, this.ctx, parsedEvent.parsedTags, parsedEvent.pubkey);
if (dmEvent instanceof Error) {
return dmEvent;
}
this.directed_messages.set(parsedEvent.id, dmEvent);
} else if (parsedEvent.kind == NostrKind.META_DATA) {
// @ts-ignore // @ts-ignore
const pEvent = parseProfileEvent(parsedEvent); const pEvent = parseProfileEvent(parsedEvent);
if (pEvent instanceof Error) { if (pEvent instanceof Error) {
@ -253,7 +204,7 @@ export class Database_Contextual_View implements DirectMessageGetter, ProfileCon
////////////////// //////////////////
subscribe() { subscribe() {
const c = this.caster.copy(); const c = this.caster.copy();
const res = csp.chan<Accepted_Event | null>(buffer_size); const res = csp.chan<Parsed_Event | null>(buffer_size);
(async () => { (async () => {
for await (const newE of c) { for await (const newE of c) {
const err = await res.put(newE); const err = await res.put(newE);
@ -356,13 +307,3 @@ export async function parseDM(
parsedContentItems: Array.from(parseContent(decrypted)), parsedContentItems: Array.from(parseContent(decrypted)),
}; };
} }
function is_DM_between(event: NostrEvent, myPubkey: string, theirPubKey: string) {
if (event.pubkey == myPubkey) {
return getTags(event).p[0] == theirPubKey;
} else if (event.pubkey == theirPubKey) {
return getTags(event).p[0] == myPubkey;
} else {
return false;
}
}

View File

@ -1,9 +1,18 @@
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 { NostrAccountContext, NostrEvent, NostrKind } from "../lib/nostr-ts/nostr.ts"; import { NostrAccountContext, NostrEvent, NostrKind } from "../lib/nostr-ts/nostr.ts";
import { ConnectionPool } from "../lib/nostr-ts/relay.ts"; import { ConnectionPool } from "../lib/nostr-ts/relay.ts";
import { prepareNostrImageEvent, Tag } from "../nostr.ts"; import {
compare,
DirectedMessage_Event,
getTags,
Parsed_Event,
prepareNostrImageEvent,
Tag,
} from "../nostr.ts";
import { PublicKey } from "../lib/nostr-ts/key.ts"; import { PublicKey } from "../lib/nostr-ts/key.ts";
import { prepareEncryptedNostrEvent, prepareNormalNostrEvent } from "../lib/nostr-ts/event.ts"; import { prepareEncryptedNostrEvent } from "../lib/nostr-ts/event.ts";
import { DirectMessageGetter } from "../UI/app_update.tsx";
import { parseDM } from "../database.ts";
export async function sendDMandImages(args: { export async function sendDMandImages(args: {
sender: NostrAccountContext; sender: NostrAccountContext;
@ -63,26 +72,6 @@ export async function sendDMandImages(args: {
return eventsToSend; return eventsToSend;
} }
export async function sendSocialPost(args: {
sender: NostrAccountContext;
message: string;
lamport_timestamp: number;
pool: ConnectionPool;
tags: Tag[];
}) {
const { sender, message, lamport_timestamp, pool, tags } = args;
console.log("sendSocialPost", message);
const event = await prepareNormalNostrEvent(sender, NostrKind.TEXT_NOTE, [
["lamport", String(lamport_timestamp)],
...tags,
], message);
const err = await pool.sendEvent(event);
if (err instanceof Error) {
return err;
}
return event;
}
export function getAllEncryptedMessagesOf( export function getAllEncryptedMessagesOf(
publicKey: PublicKey, publicKey: PublicKey,
relay: ConnectionPool, relay: ConnectionPool,
@ -164,3 +153,45 @@ function merge<T>(...iters: AsyncIterable<T>[]) {
} }
return merged; return merged;
} }
export class DirectedMessageController implements DirectMessageGetter {
constructor(
public readonly ctx: NostrAccountContext,
) {}
public readonly directed_messages = new Map<string, DirectedMessage_Event>();
// get the direct messages between me and this pubkey
public getDirectMessages(pubkey: string) {
const events = [];
for (const event of this.directed_messages.values()) {
if (is_DM_between(event, this.ctx.publicKey.hex, pubkey)) {
events.push(event);
}
}
return events.sort(compare);
}
async addEvent(event: Parsed_Event<NostrKind.DIRECT_MESSAGE>) {
const dmEvent = await parseDM(
event,
this.ctx,
event.parsedTags,
event.publicKey,
);
if (dmEvent instanceof Error) {
return dmEvent;
}
this.directed_messages.set(event.id, dmEvent);
}
}
function is_DM_between(event: NostrEvent, myPubkey: string, theirPubKey: string) {
if (event.pubkey == myPubkey) {
return getTags(event).p[0] == theirPubKey;
} else if (event.pubkey == theirPubKey) {
return getTags(event).p[0] == myPubkey;
} else {
return false;
}
}

View File

@ -1,11 +1,11 @@
import { assertEquals, fail } from "https://deno.land/std@0.176.0/testing/asserts.ts"; import { assertEquals, fail } from "https://deno.land/std@0.176.0/testing/asserts.ts";
import { prepareEncryptedNostrEvent } from "./lib/nostr-ts/event.ts";
import { PrivateKey } from "./lib/nostr-ts/key.ts";
import { InMemoryAccountContext, RelayResponse_REQ_Message } from "./lib/nostr-ts/nostr.ts";
import { Channel, closed } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts"; import { Channel, closed } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
import { ConnectionPool } from "./lib/nostr-ts/relay.ts"; import { prepareEncryptedNostrEvent } from "../lib/nostr-ts/event.ts";
import { relays } from "./lib/nostr-ts/relay-list.test.ts"; import { PrivateKey } from "../lib/nostr-ts/key.ts";
import { parseJSON } from "./features/profile.ts"; import { InMemoryAccountContext, RelayResponse_REQ_Message } from "../lib/nostr-ts/nostr.ts";
import { relays } from "../lib/nostr-ts/relay-list.test.ts";
import { ConnectionPool } from "../lib/nostr-ts/relay.ts";
import { parseJSON } from "./profile.ts";
Deno.test("group chat", async () => { Deno.test("group chat", async () => {
const pool = new ConnectionPool(); const pool = new ConnectionPool();

View File

@ -1,16 +1,16 @@
import { z } from "https://esm.sh/zod@3.22.4"; import { z } from "https://esm.sh/zod@3.22.4";
import { ConversationLists, ConversationSummary } from "./UI/conversation-list.ts";
import { parseJSON, ProfileSyncer } from "./features/profile.ts";
import { prepareEncryptedNostrEvent } from "./lib/nostr-ts/event.ts";
import { PrivateKey, PublicKey } from "./lib/nostr-ts/key.ts";
import { InMemoryAccountContext, NostrAccountContext, NostrEvent, NostrKind } from "./lib/nostr-ts/nostr.ts";
import { GroupMessageGetter } from "./UI/app_update.tsx";
import { getTags } from "./nostr.ts";
import { ChatMessage } from "./UI/message.ts";
import { GroupChatListGetter } from "./UI/conversation-list.tsx";
import { Channel, semaphore } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts"; import { Channel, semaphore } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
import { ConnectionPool } from "./lib/nostr-ts/relay.ts"; import { GroupMessageGetter } from "../UI/app_update.tsx";
import { Database_Contextual_View } from "./database.ts"; import { ConversationLists, ConversationSummary } from "../UI/conversation-list.ts";
import { GroupMessageListGetter } from "../UI/conversation-list.tsx";
import { ChatMessage } from "../UI/message.ts";
import { Database_Contextual_View } from "../database.ts";
import { prepareEncryptedNostrEvent } from "../lib/nostr-ts/event.ts";
import { PrivateKey, PublicKey } from "../lib/nostr-ts/key.ts";
import { InMemoryAccountContext, NostrAccountContext, NostrEvent, NostrKind } from "../lib/nostr-ts/nostr.ts";
import { ConnectionPool } from "../lib/nostr-ts/relay.ts";
import { getTags } from "../nostr.ts";
import { parseJSON, ProfileSyncer } from "./profile.ts";
export type GM_Types = "gm_creation" | "gm_message" | "gm_invitation"; export type GM_Types = "gm_creation" | "gm_message" | "gm_invitation";
@ -28,7 +28,7 @@ export type GroupChatInvitation = {
groupAddr: PublicKey; groupAddr: PublicKey;
}; };
export class GroupChatController implements GroupMessageGetter, GroupChatListGetter { export class GroupMessageController implements GroupMessageGetter, GroupMessageListGetter {
created_groups = new Map<string, GroupChatCreation>(); created_groups = new Map<string, GroupChatCreation>();
invitations = new Map<string, GroupChatInvitation>(); invitations = new Map<string, GroupChatInvitation>();
messages = new Map<string, ChatMessage[]>(); messages = new Map<string, ChatMessage[]>();
@ -41,7 +41,7 @@ export class GroupChatController implements GroupMessageGetter, GroupChatListGet
private readonly profileSyncer: ProfileSyncer, private readonly profileSyncer: ProfileSyncer,
) {} ) {}
getGroupChat() { getConversationList() {
const conversations: ConversationSummary[] = []; const conversations: ConversationSummary[] = [];
for (const v of this.created_groups.values()) { for (const v of this.created_groups.values()) {
conversations.push({ conversations.push({
@ -103,7 +103,7 @@ export class GroupChatController implements GroupMessageGetter, GroupChatListGet
} else if (type == "gm_invitation") { // I received } else if (type == "gm_invitation") { // I received
return await this.handleInvitation(event); return await this.handleInvitation(event);
} else { } else {
console.log(GroupChatController.name, "ignore", event, "type", type); console.log(GroupMessageController.name, "ignore", event, "type", type);
} }
} }

View File

@ -1,20 +0,0 @@
/*
Nostr support public group chat in https://github.com/nostr-protocol/nips/blob/master/28.md
It's globally public therefore no encryption
This module experiments with an encrypted private group chat
Basic design:
A, B, C are 3 users who want to chat together.
A sends a DM to [B, C] with a special marker in the content to signal the client
that this is a group chat.
content: {
message: string
,
group_chat_id: string
}
*/