Skip to content

Commit

Permalink
Refactor the chat service
Browse files Browse the repository at this point in the history
  • Loading branch information
miladsoft committed Sep 24, 2024
1 parent 684c69d commit 29dbbc9
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 56 deletions.
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"localforage": "^1.10.0",
"lodash-es": "4.17.21",
"luxon": "3.5.0",
"moment": "^2.30.1",
"ng-apexcharts": "1.12.0",
"ngx-quill": "26.0.8",
"nostr-tools": "^2.7.2",
Expand Down
104 changes: 59 additions & 45 deletions src/app/components/chat/chat.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export class ChatService implements OnDestroy {
async getContact(pubkey: string): Promise<void> {
try {
if (!pubkey) {
console.error('Public key is undefined.');
return;
}

Expand Down Expand Up @@ -122,6 +121,7 @@ export class ChatService implements OnDestroy {
}



async getProfile(): Promise<void> {
try {
const publicKey = this._signerService.getPublicKey();
Expand All @@ -143,23 +143,60 @@ export class ChatService implements OnDestroy {
}
}

private updateContactInChats(pubKey: string, updatedMetadata: any): void {
const chatToUpdate = this.chatList.find(chat => chat.contact.pubKey === pubKey);

if (chatToUpdate) {
chatToUpdate.contact = {
...chatToUpdate.contact,
...updatedMetadata
};

this.chatList = this.chatList.map(chat => chat.contact.pubKey === pubKey ? chatToUpdate : chat);

this._chats.next(this.chatList);

this._indexedDBService.saveChat(chatToUpdate);
}
}


async getChats(): Promise<Observable<Chat[]>> {
const pubkey = this._signerService.getPublicKey();
const useExtension = await this._signerService.isUsingExtension();
const decryptedPrivateKey = await this._signerService.getSecretKey("123");
this.subscribeToChatList(pubkey, useExtension, decryptedPrivateKey);

const storedChats = await this._indexedDBService.getAllChats();
if (storedChats && storedChats.length > 0) {
this.chatList = storedChats;
this._chats.next(this.chatList);
}

this.chatList.forEach(chat => this.loadChatHistory(chat.id!));
setTimeout(async () => {
try {
if (storedChats && storedChats.length > 0) {
const pubkeys = storedChats.map(chat => chat.contact.pubKey);

const metadataList = await this._metadataService.fetchMetadataForMultipleKeys(pubkeys);
metadataList.forEach(metadata => {
this.updateContactInChats(metadata.pubkey, metadata.metadata);
});
}
} catch (error) {
console.error('Error updating chat contacts metadata:', error);
}
}, 0);
this.subscribeToChatList(pubkey, useExtension, decryptedPrivateKey);
return this.getChatListStream();
}

subscribeToChatList(pubkey: string, useExtension: boolean, decryptedSenderPrivateKey: string): Observable<Chat[]> {
this._relayService.ensureConnectedRelays().then(() => {
this._relayService.ensureConnectedRelays().then(async () => {
const lastSavedTimestamp = await this._indexedDBService.getLastSavedTimestamp();

const filters: Filter[] = [
{ kinds: [EncryptedDirectMessage], authors: [pubkey], limit: 25 },
{ kinds: [EncryptedDirectMessage], '#p': [pubkey], limit: 25 }
{ kinds: [EncryptedDirectMessage], authors: [pubkey], since: Math.floor(lastSavedTimestamp / 1000) },
{ kinds: [EncryptedDirectMessage], '#p': [pubkey], since: Math.floor(lastSavedTimestamp / 1000) }
];

this._relayService.getPool().subscribeMany(this._relayService.getConnectedRelays(), filters, {
Expand All @@ -174,7 +211,6 @@ export class ChatService implements OnDestroy {
if (event.created_at > lastTimestamp) {
this.messageQueue.push(event);


await this.processNextMessage(pubkey, useExtension, decryptedSenderPrivateKey);
}
},
Expand Down Expand Up @@ -213,14 +249,14 @@ export class ChatService implements OnDestroy {
);

if (decryptedMessage) {
const messageTimestamp = event.created_at * 1000;
const messageTimestamp = event.created_at;
this.addOrUpdateChatList(otherPartyPubKey, decryptedMessage, messageTimestamp, isSentByUser);


const chatToUpdate = this.chatList.find(chat => chat.id === otherPartyPubKey);
if (chatToUpdate) {
await this._indexedDBService.saveChat(chatToUpdate);
await this._indexedDBService.saveLastSavedTimestamp(messageTimestamp * 1000);
this._chat.next(chatToUpdate);

}
}
}
Expand All @@ -231,6 +267,7 @@ export class ChatService implements OnDestroy {
}
}


private addOrUpdateChatList(pubKey: string, message: string, createdAt: number, isMine: boolean): void {
const existingChat = this.chatList.find(chat => chat.contact?.pubKey === pubKey);

Expand All @@ -240,23 +277,21 @@ export class ChatService implements OnDestroy {
contactId: pubKey,
isMine,
value: message,
createdAt: new Date(createdAt).toISOString(),
createdAt: new Date(createdAt * 1000).toISOString(),
};


const currentChat = this._chat.value;

if (existingChat) {

const messageExists = existingChat.messages?.some(m => m.id === newMessage.id);

if (!messageExists) {
existingChat.messages = (existingChat.messages || []).concat(newMessage)
.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());

if (new Date(existingChat.lastMessageAt!).getTime() < createdAt) {
const lastMessageAtTimestamp = Number(existingChat.lastMessageAt) || 0;

if (lastMessageAtTimestamp < createdAt) {
existingChat.lastMessage = message;
existingChat.lastMessageAt = new Date(createdAt).toLocaleDateString() + ' ' + new Date(createdAt).toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit' });
existingChat.lastMessageAt = createdAt.toString();
}
}
} else {
Expand All @@ -272,28 +307,19 @@ export class ChatService implements OnDestroy {
displayName: contactInfo.displayName || contactInfo.name || "Unknown"
},
lastMessage: message,
lastMessageAt: new Date(createdAt).toLocaleDateString() + ' ' + new Date(createdAt).toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit' }),
lastMessageAt: createdAt.toString(),
messages: [newMessage]
};
this.chatList.push(newChat);
this.fetchMetadataForPubKey(pubKey);
}


this.chatList.sort((a, b) => new Date(b.lastMessageAt!).getTime() - new Date(a.lastMessageAt!).getTime());

this.chatList.sort((a, b) => Number(b.lastMessageAt!) - Number(a.lastMessageAt!));

this._chats.next(this.chatList);


if (currentChat) {
const restoredChat = this.chatList.find(chat => chat.id === currentChat.id);
if (restoredChat) {
this._chat.next(restoredChat);
}
}
}


private fetchMetadataForPubKey(pubKey: string): void {
this._metadataService.fetchMetadataWithCache(pubKey)
.then(metadata => {
Expand Down Expand Up @@ -329,16 +355,14 @@ export class ChatService implements OnDestroy {
const myPubKey = this._signerService.getPublicKey();

const historyFilter: Filter[] = [
{ kinds: [EncryptedDirectMessage], authors: [myPubKey], '#p': [pubKey] },
{ kinds: [EncryptedDirectMessage], authors: [pubKey], '#p': [myPubKey] }
{ kinds: [EncryptedDirectMessage], authors: [myPubKey], '#p': [pubKey], limit: 10 },
{ kinds: [EncryptedDirectMessage], authors: [pubKey], '#p': [myPubKey], limit: 10 }
];

console.log("Subscribing to history for chat with: ", pubKey);

this._relayService.getPool().subscribeMany(this._relayService.getConnectedRelays(), historyFilter, {
onevent: async (event: NostrEvent) => {
console.log("Received historical event: ", event);
const isSentByMe = event.pubkey === myPubKey;
const isSentByMe = event.pubkey === myPubKey;
const senderOrRecipientPubKey = isSentByMe ? pubKey : event.pubkey;
const decryptedMessage = await this.decryptReceivedMessage(
event,
Expand All @@ -348,7 +372,7 @@ export class ChatService implements OnDestroy {
);

if (decryptedMessage) {
const messageTimestamp = event.created_at * 1000;
const messageTimestamp = Math.floor(event.created_at / 1000);


this.addOrUpdateChatList(pubKey, decryptedMessage, messageTimestamp, isSentByMe);
Expand Down Expand Up @@ -418,8 +442,6 @@ export class ChatService implements OnDestroy {
const cachedChat = chats?.find(chat => chat.id === id);
if (cachedChat) {
this._chat.next(cachedChat);
console.log("Fetching chat history for: ", this.recipientPublicKey);

this.loadChatHistory(this.recipientPublicKey);
return of(cachedChat);
}
Expand All @@ -435,9 +457,6 @@ export class ChatService implements OnDestroy {
const updatedChats = chats ? [...chats, newChat] : [newChat];
this._chats.next(updatedChats);
this._chat.next(newChat);

console.log("Fetching chat history for: ", this.recipientPublicKey);

this.loadChatHistory(this.recipientPublicKey);
return of(newChat);
})
Expand Down Expand Up @@ -469,7 +488,6 @@ export class ChatService implements OnDestroy {
const cachedChat = chats?.find(chat => chat.id === contact.pubKey);
if (cachedChat) {
this._chat.next(cachedChat);
console.log("Fetching chat history for: ", contact.pubKey);
this.loadChatHistory(contact.pubKey);
return of(cachedChat);
}
Expand All @@ -491,8 +509,6 @@ export class ChatService implements OnDestroy {
const updatedChats = chats ? [...chats, newChat] : [newChat];
this._chats.next(updatedChats);
this._chat.next(newChat);

console.log("Fetching chat history for: ", contact.pubKey);
this.loadChatHistory(contact.pubKey);

return of(newChat);
Expand Down Expand Up @@ -539,7 +555,6 @@ export class ChatService implements OnDestroy {
const published = await this._relayService.publishEventToRelays(signedEvent);

if (published) {
console.log('Message sent successfully!');
this.message = '';
} else {
console.error('Failed to send the message.');
Expand Down Expand Up @@ -569,7 +584,6 @@ export class ChatService implements OnDestroy {
const published = await this._relayService.publishEventToRelays(signedEvent);

if (published) {
console.log('Message sent successfully with extension!');
this.message = '';
} else {
console.error('Failed to send the message with extension.');
Expand Down
11 changes: 5 additions & 6 deletions src/app/components/chat/chats/chats.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@
<div
class="flex h-full w-full items-center justify-center rounded-full bg-gray-200 text-lg uppercase text-gray-600 dark:bg-gray-700 dark:text-gray-200"
>
{{ chat?.contact?.name ? chat.contact.name.charAt(0) : '' }}
{{ chat?.contact?.name ? chat.contact.name.charAt(0) : ''}}

</div>
}
Expand All @@ -220,11 +220,10 @@
<div
class="ml-auto flex flex-col items-end self-start pl-2"
>
<div
class="text-secondary text-sm leading-5"
>
{{ chat.lastMessageAt }}
</div>
<div class="text-secondary text-sm leading-5 whitespace-nowrap overflow-hidden">
{{ chat.lastMessageAt | ago }}
</div>

@if (chat.muted) {
<mat-icon
class="text-hint icon-size-5"
Expand Down
2 changes: 2 additions & 0 deletions src/app/components/chat/chats/chats.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ChatService } from 'app/components/chat/chat.service';
import { Chat, Profile } from 'app/components/chat/chat.types';
import { NewChatComponent } from 'app/components/chat/new-chat/new-chat.component';
import { ProfileComponent } from 'app/components/chat/profile/profile.component';
import { AgoPipe } from 'app/shared/ago.pipe';
import { Subject, takeUntil } from 'rxjs';

@Component({
Expand All @@ -38,6 +39,7 @@ import { Subject, takeUntil } from 'rxjs';
NgClass,
RouterLink,
RouterOutlet,
AgoPipe
],
})
export class ChatsComponent implements OnInit, OnDestroy {
Expand Down
11 changes: 6 additions & 5 deletions src/app/services/indexed-db.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,23 +261,24 @@ export class IndexedDBService {
async getAllChats(): Promise<Chat[]> {
try {
const chats: Chat[] = [];

await this.chatStore.iterate<Chat, void>((value) => {
chats.push(value);
});

// مرتب‌سازی چت‌ها بر اساس زمان آخرین پیام به‌طوری که آخرین چت‌ها اول نمایش داده شوند
chats.sort((a, b) => {
const dateA = new Date(a.lastMessageAt!).getTime();
const dateB = new Date(b.lastMessageAt!).getTime();
return dateB - dateA; // چت‌هایی که تاریخ جدیدتری دارند در ابتدا قرار می‌گیرند
const dateA = Number(a.lastMessageAt);
const dateB = Number(b.lastMessageAt);
return dateB - dateA;
});

return chats;
} catch (error) {
console.error('Error getting chats from IndexedDB:', error);
return [];
}
}
}



async saveLastSavedTimestamp(timestamp: number): Promise<void> {
Expand Down
1 change: 1 addition & 0 deletions src/app/services/metadata.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export class MetadataService {
const metadata = JSON.parse(event.content);
await this.indexedDBService.saveUserMetadata(event.pubkey, metadata);
metadataList.push({ pubkey: event.pubkey, metadata });

} catch (error) {
console.error('Error parsing metadata:', error);
}
Expand Down
16 changes: 16 additions & 0 deletions src/app/shared/ago.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';

@Pipe({ name: 'ago', standalone: true })
export class AgoPipe implements PipeTransform {

transform(value: number): string {

if (value === 0) {
return '';
}

const date = moment.unix(value);
return date.fromNow();
}
}
Loading

0 comments on commit 29dbbc9

Please sign in to comment.