mirror of
https://github.com/BlowaterNostr/blowater.git
synced 2024-10-18 15:43:20 +00:00
move DirectMessageGetter out from Database_Contextual_View (#229)
to DirectedMessageController
This commit is contained in:
parent
d6bbaed8e6
commit
f97a8c9dbf
48
UI/app.tsx
48
UI/app.tsx
@ -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,
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
159
database.ts
159
database.ts
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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();
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
Loading…
Reference in New Issue
Block a user