1130 lines
43 KiB
TypeScript
Executable File

declare global {
interface Window {
toggleUserList: () => void;
switchUser: (userId: string | number) => void;
loadMemberChat: (memberId: string | number) => void;
}
}
import { groupsMock } from '../../mocks/mock-signature/groupsMock';
import { messagesMock as initialMessagesMock, messagesMock } from '../../mocks/mock-signature/messagesMock';
import { membersMock } from '../../mocks/mock-signature/membersMocks';
import { ApiReturn, Device, Member } from '../../../pkg/sdk_client';
import {
DocumentSignature,
} from '../../models/signature.models';
import { messageStore } from '../../utils/messageMock';
import { Group } from '../../interface/groupInterface';
import { getCorrectDOM } from '../../utils/document.utils';
import chatStyle from '../../../public/style/chat.css?inline';
import { addressToEmoji } from '../../utils/sp-address.utils';
import Database from '../../services/database.service';
const storageUrl = `/storage`;
interface LocalNotification {
memberId: string;
text: string;
time: string;
}
interface Message {
content: string;
content_type: "text" | "file";
created_at: number;
sender: string [];
recipient: string;
}
export function initChat() {
const chatElement = document.createElement('chat-element');
const container = document.querySelector('.container');
if (container) {
container.appendChild(chatElement);
}
}
class ChatElement extends HTMLElement {
static get observedAttributes() {
return ['process-id'];
}
private sdkClient: any;
private processId: string | null = null;
private selectedMemberId: string | null = null;
private messagesMock: any[] = [];
private dom: Node;
private notifications: LocalNotification[] = [];
private notificationBadge = document.querySelector('.notification-badge');
private notificationBoard = document.getElementById('notification-board');
private notificationBell = document.getElementById('notification-bell');
private selectedSignatories: DocumentSignature[] = [];
private allMembers = membersMock.map(member => ({
id: member.id,
name: member.name,
roleName: 'Default Role'
}));
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.messagesMock = messageStore.getMessages();
this.dom = getCorrectDOM('signature-element');
this.processId = this.getAttribute('process-id');
// Initialiser sdkClient
this.initSDKClient();
// Récupérer le processId depuis l'attribut du composant
console.log('🔍 Constructor - Process ID from element:', this.processId);
this.shadowRoot!.innerHTML = `
<style>
${chatStyle}
</style>
<div class="container">
<!-- List of groups -->
<div class="group-list">
<ul id="group-list">
</ul>
</div>
<!-- Chat area -->
<div class="chat-area">
<div class="chat-header" id="chat-header">
<!-- Chat title -->
</div>
<div class="messages" id="messages">
<!-- Messages -->
</div>
<!-- Input area -->
<div class="input-area">
<label for="file-input" class="attachment-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M13.514 2.444l-10.815 10.785c-.449.449-.678 1.074-.625 1.707l.393 4.696c.041.479.422.86.9.9l4.697.394c.633.053 1.258-.177 1.707-.626l11.875-11.844c.196-.196.195-.512 0-.707l-3.536-3.536c-.195-.195-.511-.196-.707 0l-8.878 8.848c-.162.162-.253.382-.253.611v.725c0 .184.148.332.332.332h.725c.229 0 .448-.092.61-.254l7.11-7.08 1.415 1.415-7.386 7.354c-.375.375-.885.586-1.414.586h-2.414c-.555 0-1-.448-1-1v-2.414c0-.53.211-1.039.586-1.414l9.506-9.477c.781-.781 2.049-.781 2.829-.001l4.243 4.243c.391.391.586.902.586 1.414 0 .512-.196 1.025-.587 1.416l-12.35 12.319c-.748.747-1.76 1.164-2.81 1.164-.257 0-6.243-.467-6.499-.487-.664-.052-1.212-.574-1.268-1.267-.019-.242-.486-6.246-.486-6.499 0-1.05.416-2.062 1.164-2.811l10.936-10.936 1.414 1.444z"/>
</svg>
</label>
<input type="file" id="file-input" style="display: none;" />
<textarea id="message-input" rows="3" placeholder="Type your message..."></textarea>
<button id="send-button">Send</button>
</div>
</div>
</div>
`;
window.toggleUserList = this.toggleUserList.bind(this);
window.loadMemberChat = this.loadMemberChat.bind(this);
this.notificationBadge = document.querySelector('.notification-badge');
this.notificationBoard = document.getElementById('notification-board');
this.notificationBell = document.getElementById('notification-bell');
if (!this.notificationBadge || !this.notificationBoard || !this.notificationBell) {
console.error('Notification elements not found');
}
// Initialiser les événements de notification
document.addEventListener('click', (event: Event): void => {
if (this.notificationBoard && this.notificationBoard.style.display === 'block' &&
!this.notificationBoard.contains(event.target as Node) &&
this.notificationBell && !this.notificationBell.contains(event.target as Node)) {
this.notificationBoard.style.display = 'none';
}
});
this.initMessageEvents();
document.addEventListener('newMessagingProcess', ((event: CustomEvent) => {
console.log('🎯 Received newMessagingProcess event:', event.detail);
this.addNewMessagingProcess(event.detail.processId, event.detail.processName);
}) as EventListener);
}
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
console.log(`🔄 Attribute ${name} changed from ${oldValue} to ${newValue}`);
if (name === 'process-id' && newValue) {
console.log('🔍 Loading chat with new process ID:', newValue);
this.loadGroupList(newValue);
}
}
private initMessageEvents() {
const sendButton = this.shadowRoot?.querySelector('#send-button');
if (sendButton) {
sendButton.addEventListener('click', () => this.sendMessage());
}
const messageInput = this.shadowRoot?.querySelector('#message-input');
if (messageInput) {
messageInput.addEventListener('keypress', (event: Event) => {
const keyEvent = event as KeyboardEvent;
if (keyEvent.key === 'Enter' && !keyEvent.shiftKey) {
event.preventDefault();
this.sendMessage();
}
});
}
const fileInput = this.shadowRoot?.querySelector('#file-input') as HTMLInputElement;
if (fileInput) {
fileInput.addEventListener('change', (event: Event) => {
const target = event.target as HTMLInputElement;
if (target.files && target.files.length > 0) {
this.sendFile(target.files[0]);
}
});
}
}
///////////////////// Notification module /////////////////////
// Delete a notification
private removeNotification(index: number) {
this.notifications?.splice(index, 1); // Ajout de ?.
this.renderNotifications();
this.updateNotificationBadge();
}
// Show notifications
private renderNotifications() {
if (!this.notificationBoard) return;
// Reset the interface
this.notificationBoard.innerHTML = '';
// Displays "No notifications available" if there are no notifications
if (this.notifications.length === 0) {
this.notificationBoard.innerHTML = '<div class="no-notification">No notifications available</div>';
return;
}
// Add each notification to the list
this.notifications.forEach((notif, index) => {
const notifElement = document.createElement('div');
notifElement.className = 'notification-item';
notifElement.textContent = `${notif.text} at ${notif.time}`;
notifElement.onclick = () => {
this.loadMemberChat(notif.memberId);
this.removeNotification(index);
};
this.notificationBoard?.appendChild(notifElement);
});
}
private updateNotificationBadge() {
if (!this.notificationBadge) return;
const count = this.notifications.length;
this.notificationBadge.textContent = count > 99 ? '+99' : count.toString();
(this.notificationBadge as HTMLElement).style.display = count > 0 ? 'block' : 'none';
}
// Add notification
private async addNotification(memberId: string, message: any) {
try {
// Obtenir l'emoji de l'adresse
const memberEmoji = await addressToEmoji(memberId);
// Obtenir le processus et le rôle
const groupItem = this.shadowRoot?.querySelector('[data-process-id]');
const processId = groupItem?.getAttribute('data-process-id');
const processEmoji = processId ? await addressToEmoji(processId) : '📝';
// Trouver le rôle du membre
const member = this.allMembers.find(m => String(m.id) === memberId);
const role = member?.roleName || 'Member';
// Déterminer le texte de la notification
let notificationText = '';
if (message.type === 'file') {
notificationText = `${memberEmoji} (${role}) in ${processEmoji}: New file - ${message.fileName}`;
} else {
notificationText = `${memberEmoji} (${role}) in ${processEmoji}: ${message.text}`;
}
// Créer la notification
const notification = {
memberId,
text: notificationText,
time: message.time
};
// Ajouter la notification et mettre à jour l'interface
this.notifications.push(notification);
this.renderNotifications();
this.updateNotificationBadge();
} catch (error) {
console.error('Error creating notification:', error);
}
}
public isPaired(): boolean {
try {
return this.sdkClient.is_paired();
} catch (e) {
throw new Error(`isPaired ~ Error: ${e}`);
}
}
public async createMessagingProcess(otherMembers: Member[], relayAddress: string, feeRate: number): Promise<ApiReturn> {
if (!this.isPaired()) {
throw new Error('Device not paired');
}
const me = await this.getMemberFromDevice();
console.log('My SP addresses:', me);
if (!me) {
throw new Error('No paired member in device');
}
const allMembers: Member[] = otherMembers;
allMembers.push({ sp_addresses: me });
const meAndOne = [{ sp_addresses: me }, otherMembers.pop()!];
const everyOneElse = otherMembers;
const messagingTemplate = {
process_id: crypto.randomUUID(),
parent_id: null,
description: 'messaging',
messages: {
state: 'initial',
object: {
type: 'message_list',
content: [],
content_type: {
allowed: ['text', 'file'],
default: 'text'
},
metadata: {
created_at: Date.now(),
last_updated: Date.now()
}
}
},
roles: {
public: {
members: allMembers,
validation_rules: [
{
quorum: 0.0,
fields: ['description', 'roles', 'messages'],
min_sig_member: 0.0,
},
],
storages: [storageUrl]
},
owner: {
members: meAndOne,
validation_rules: [
{
quorum: 1.0,
fields: ['description', 'roles', 'messages'],
min_sig_member: 1.0,
},
],
storages: [storageUrl]
},
users: {
members: everyOneElse,
validation_rules: [
{
quorum: 0.0,
fields: ['description', 'roles', 'messages'],
min_sig_member: 0.0,
},
],
storages: [storageUrl]
},
},
};
try {
return this.sdkClient.create_new_process(JSON.stringify(messagingTemplate), null, relayAddress, feeRate);
} catch (e) {
throw new Error(`Creating process failed: ${e}`);
}
}
async getMemberFromDevice(): Promise<string[] | null> {
try {
const device = await this.getDeviceFromDatabase();
if (device) {
const parsed: Device = JSON.parse(device);
const pairedMember = parsed['paired_member'];
return pairedMember.sp_addresses;
} else {
return null;
}
} catch (e) {
throw new Error(`Failed to retrieve paired_member from device: ${e}`);
}
}
async getDeviceFromDatabase(): Promise<string | null> {
const db = await Database.getInstance();
const walletStore = 'wallet';
try {
const dbRes = await db.getObject(walletStore, '1');
if (dbRes) {
const wallet = dbRes['device'];
return wallet;
} else {
return null;
}
} catch (e) {
throw new Error(`Failed to retrieve device from db: ${e}`);
}
}
// Send a messsage
private async sendMessage() {
const messageInput = this.shadowRoot?.querySelector('#message-input') as HTMLInputElement;
if (!messageInput || !this.selectedMemberId) return;
const messageText = messageInput.value.trim();
if (messageText === '') return;
try {
const myAddresses = await this.getMemberFromDevice();
if (!myAddresses) {
throw new Error('No paired member found');
}
const now = new Date();
const formattedTime = now.toLocaleString('fr-FR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
const newMessage = {
id: Date.now(),
sender: myAddresses[0],
text: messageText,
time: formattedTime,
type: 'text',
class: 'message user'
};
if (this.selectedMemberId) {
messageStore.addMessage(this.selectedMemberId!, newMessage);
this.messagesMock = messageStore.getMessages();
}
// Récupérer le process_id du parent (conversation)
const groupItem = this.shadowRoot?.querySelector('[data-process-id]');
const parentProcessId = groupItem?.getAttribute('data-process-id');
if (!parentProcessId) {
throw new Error('Parent process ID not found');
}
const messageTemplate = {
process_id: parentProcessId,
description: 'message',
message:{
content: "Hello",
content_type: 'file',
created_at: Date.now(),
sender: myAddresses,
recipient: this.selectedMemberId
} as Message,
roles: {
public: {
members: [
{ sp_addresses: myAddresses },
{ sp_addresses: [this.selectedMemberId] }
],
validation_rules: [
{
quorum: 0.0,
fields: ['description', 'messages'],
min_sig_member: 0.0,
},
],
storages: [storageUrl]
},
owner: {
members: [
{ sp_addresses: myAddresses },
{ sp_addresses: [this.selectedMemberId] }
],
validation_rules: [
{
quorum: 1.0,
fields: ['description', 'messages'],
min_sig_member: 1.0,
},
],
storages: [storageUrl]
}
}
};
console.log('Message template:', {
timestamp: formattedTime,
template: messageTemplate
});
const result = await this.createMessagingProcess(
[{ sp_addresses: [this.selectedMemberId] }],
'relay_address',
1
);
console.log('Final message process:', {
template: messageTemplate,
result: result,
timestamp: new Date().toISOString()
});
messageInput.value = '';
this.loadMemberChat(this.selectedMemberId);
setTimeout(() => {
const autoReply = this.generateAutoReply(this.selectedMemberId!);
messageStore.addMessage(this.selectedMemberId!, autoReply);
this.messagesMock = messageStore.getMessages();
this.loadMemberChat(this.selectedMemberId!);
this.addNotification(this.selectedMemberId!, autoReply);
}, 1000);
} catch (error) {
console.error('Error sending message:', error);
}
}
private scrollToBottom(container: Element) {
(container as HTMLElement).scrollTop = (container as HTMLElement).scrollHeight;
}
// Load the list of members
private async loadMemberChat(memberId: string | number) {
const myAddresses = await this.getMemberFromDevice();
if (!myAddresses) {
console.error('No paired member found');
return;
}
this.selectedMemberId = String(memberId);
const memberMessages = this.messagesMock.find(m => String(m.memberId) === String(memberId));
const chatHeader = this.shadowRoot?.querySelector('#chat-header');
const messagesContainer = this.shadowRoot?.querySelector('#messages');
if (!chatHeader || !messagesContainer) return;
const memberAddress = String(memberId);
const emojis = await addressToEmoji(memberAddress);
chatHeader.textContent = `Chat with ${emojis}`;
messagesContainer.innerHTML = '';
if (memberMessages) {
for (const message of memberMessages.messages) {
const messageElement = document.createElement('div');
messageElement.className = 'message-container';
// Ajouter le style pour aligner les messages
if (message.sender === myAddresses[0]) {
messageElement.style.justifyContent = 'flex-end';
} else {
messageElement.style.justifyContent = 'flex-start';
}
const messageContent = document.createElement('div');
messageContent.className = message.class || 'message';
if (message.type === 'file') {
messageContent.innerHTML = `
<div class="message-content">
<strong>${await addressToEmoji(message.sender)}</strong>:
<span class="file-message" style="cursor: pointer; color: #0066cc; text-decoration: underline;">
📎 ${message.fileName}
</span>
</div>
<div class="message-time">${message.time}</div>
`;
// Ajouter le gestionnaire de clic pour le téléchargement
const fileSpan = messageContent.querySelector('.file-message');
fileSpan?.addEventListener('click', () => {
const fileKey = `file_${message.id}`;
const fileData = localStorage.getItem(fileKey);
if (fileData) {
// Créer un lien de téléchargement
const link = document.createElement('a');
link.href = fileData;
link.download = message.fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
});
} else {
messageContent.innerHTML = `
<div class="message-content">
<strong>${await addressToEmoji(message.sender)}</strong>: ${message.text}
</div>
<div class="message-time">${message.time}</div>
`;
}
messageElement.appendChild(messageContent);
messagesContainer.appendChild(messageElement);
}
}
this.scrollToBottom(messagesContainer);
}
private async toggleMembers(roleData: any, roleElement: HTMLElement) {
let memberList = roleElement.querySelector('.member-list');
if (memberList) {
(memberList as HTMLElement).style.display =
(memberList as HTMLElement).style.display === 'none' ? 'block' : 'none';
return;
}
memberList = document.createElement('ul');
memberList.className = 'member-list';
if (roleData.members) {
for (const member of roleData.members) {
const memberItem = document.createElement('li');
memberItem.className = 'member-item';
const memberContainer = document.createElement('div');
memberContainer.className = 'member-container';
const emojiSpan = document.createElement('span');
emojiSpan.className = 'member-emoji';
if (member.sp_addresses?.[0]) {
const emojis = await addressToEmoji(member.sp_addresses[0]);
emojiSpan.textContent = emojis;
}
memberContainer.appendChild(emojiSpan);
memberItem.appendChild(memberContainer);
memberItem.onclick = async (event) => {
event.stopPropagation();
try {
// S'assurer que le SDK est initialisé
if (!this.sdkClient) {
await this.initSDKClient();
}
const groupItem = roleElement.closest('[data-process-id]');
const processId = groupItem?.getAttribute('data-process-id');
if (!processId) {
throw new Error('Process ID not found');
}
console.log('Creating messaging process with:', {
processId,
member,
sdkClientInitialized: !!this.sdkClient
});
const result = await this.createMessagingProcess(
[member],
'relay_address',
1
);
console.log('Messaging process created:', {
processId,
template: result,
member: member
});
this.loadMemberChat(member.sp_addresses[0]);
} catch (error) {
console.error('Error creating messaging process:', error);
}
};
memberList.appendChild(memberItem);
}
}
roleElement.appendChild(memberList);
}
// Toggle the list of Roles
private toggleRoles(group: Group, groupElement: HTMLElement) {
console.log('=== toggleRoles START ===');
console.log('Group:', group.name);
console.log('Group roles:', group.roles); // Afficher tous les rôles disponibles
let roleList = groupElement.querySelector('.role-list');
console.log('Existing roleList:', roleList);
if (roleList) {
const roleItems = roleList.querySelectorAll('.role-item');
roleItems.forEach(roleItem => {
console.log('Processing roleItem:', roleItem.innerHTML); // Voir le contenu HTML complet
let container = roleItem.querySelector('.role-item-container');
if (!container) {
container = document.createElement('div');
container.className = 'role-item-container';
// Créer un span pour le nom du rôle
const nameSpan = document.createElement('span');
nameSpan.className = 'role-name';
nameSpan.textContent = roleItem.textContent?.trim() || '';
container.appendChild(nameSpan);
roleItem.textContent = '';
roleItem.appendChild(container);
}
// Récupérer le nom du rôle
const roleName = roleItem.textContent?.trim();
console.log('Role name from textContent:', roleName);
// Alternative pour obtenir le nom du rôle
const roleNameAlt = container.querySelector('.role-name')?.textContent;
console.log('Role name from span:', roleNameAlt);
if (!container.querySelector('.folder-icon')) {
const folderButton = document.createElement('span');
folderButton.className = 'folder-icon';
folderButton.addEventListener('click', (event) => {
event.stopPropagation();
console.log('Clicked role name:', roleName);
console.log('Available roles:', group.roles.map(r => r.name));
const role = group.roles.find(r => r.name === roleName);
if (role) {
console.log('Found role:', role);
} else {
console.error('Role not found. Name:', roleName);
console.error('Available roles:', group.roles);
}
});
container.appendChild(folderButton);
}
});
(roleList as HTMLElement).style.display =
(roleList as HTMLElement).style.display === 'none' ? 'block' : 'none';
}
}
private async loadGroupList(processId: string): Promise<void> {
console.log('🔍 Loading group list with processId:', processId);
const groupList = this.shadowRoot?.querySelector('#group-list');
if (!groupList) return;
groupList.innerHTML = '';
const dbRequest = window.indexedDB.open('4nk');
const db = await new Promise<IDBDatabase>((resolve, reject) => {
dbRequest.onsuccess = () => resolve(dbRequest.result);
dbRequest.onerror = () => reject(dbRequest.error);
});
const transaction = db.transaction(['processes'], 'readonly');
const processStore = transaction.objectStore('processes');
const processRequest = processStore.get(processId);
const process = await new Promise<any>((resolve, reject) => {
processRequest.onsuccess = () => {
console.log('🔍 Process found:', processRequest.result);
resolve(processRequest.result);
};
processRequest.onerror = () => reject(processRequest.error);
});
if (!process?.states?.[0]?.encrypted_pcd?.roles) {
console.error('❌ Process structure invalid:', process);
return;
}
const roles = process.states[0].encrypted_pcd.roles;
console.log('🔑 Roles found:', roles);
const li = document.createElement('li');
li.className = 'group-list-item';
li.setAttribute('data-process-id', processId);
const container = document.createElement('div');
container.className = 'group-item-container';
const nameSpan = document.createElement('span');
nameSpan.textContent = `Process : `;
nameSpan.className = 'process-name';
container.appendChild(nameSpan);
await addressToEmoji(processId).then(emojis => {
const emojiSpan = document.createElement('span');
emojiSpan.className = 'process-emoji';
emojiSpan.textContent = emojis;
container.appendChild(emojiSpan);
});
li.appendChild(container);
const roleList = document.createElement('ul');
roleList.className = 'role-list';
// Traiter chaque rôle
Object.entries(roles).forEach(([roleName, roleData]: [string, any]) => {
const roleItem = document.createElement('li');
roleItem.className = 'role-item';
const roleContainer = document.createElement('div');
roleContainer.className = 'role-item-container';
const roleNameSpan = document.createElement('span');
roleNameSpan.className = 'role-name';
roleNameSpan.textContent = roleName;
// Filtrer les membres dupliqués ici, avant de les passer à toggleMembers
const uniqueMembers = new Map<string, any>();
roleData.members?.forEach((member: any) => {
const spAddress = member.sp_addresses?.[0];
if (spAddress && !uniqueMembers.has(spAddress)) {
uniqueMembers.set(spAddress, member);
}
});
// Créer un nouveau roleData avec les membres uniques
const filteredRoleData = {
...roleData,
members: Array.from(uniqueMembers.values())
};
roleContainer.addEventListener('click', (event) => {
event.stopPropagation();
this.toggleMembers(filteredRoleData, roleItem);
});
roleContainer.appendChild(roleNameSpan);
roleItem.appendChild(roleContainer);
roleList.appendChild(roleItem);
});
li.appendChild(roleList);
groupList.appendChild(li);
}
// Function to manage the list of users
private toggleUserList() {
const userList = getCorrectDOM('userList');
if (!userList) return;
if (!(userList as HTMLElement).classList.contains('show')) {
(userList as HTMLElement).innerHTML = membersMock.map(member => `
<div class="user-list-item" onclick="switchUser('${member.id}')">
<span class="user-avatar">${member.avatar}</span>
<div>
<span class="user-name">${member.name}</span>
<span class="user-email">${member.email}</span>
</div>
</div>
`).join('');
}
(userList as HTMLElement).classList.toggle('show');
}
// Generate an automatic response
private generateAutoReply(senderName: string): Message {
const now = new Date();
const formattedTime = now.toLocaleString('fr-FR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
return {
id: Date.now(),
sender: senderName,
text: "OK...",
time: formattedTime,
type: 'text' as const
};
}
// Send a file
private async sendFile(file: File) {
const MAX_FILE_SIZE = 1 * 1024 * 1024;
if (file.size > MAX_FILE_SIZE) {
alert('Le fichier est trop volumineux. Taille maximum : 1MB');
return;
}
try {
const myAddresses = await this.getMemberFromDevice();
if (!myAddresses) {
throw new Error('No paired member found');
}
let fileData: string;
if (file.type.startsWith('image/')) {
fileData = await this.compressImage(file);
} else {
fileData = await this.readFileAsBase64(file);
}
const newMessage = {
id: Date.now(),
sender: myAddresses[0],
text: `Fichier envoyé: ${file.name}`,
fileName: file.name,
time: new Date().toLocaleString('fr-FR'),
type: 'file',
class: 'message user'
};
try {
const fileKey = `file_${newMessage.id}`;
localStorage.setItem(fileKey, fileData);
} catch (storageError) {
console.error('Erreur de stockage du fichier:', storageError);
alert('Erreur lors du stockage du fichier. Essayez avec un fichier plus petit.');
return;
}
if (this.selectedMemberId) {
messageStore.addMessage(this.selectedMemberId, newMessage);
this.messagesMock = messageStore.getMessages();
}
const groupItem = this.shadowRoot?.querySelector('[data-process-id]');
const parentProcessId = groupItem?.getAttribute('data-process-id');
if (!parentProcessId) {
throw new Error('Parent process ID not found');
}
const messageTemplate = {
parent_id: parentProcessId,
description: 'file_message',
message:{
content: fileData,
content_type: 'file',
created_at: Date.now(),
sender: myAddresses,
recipient: this.selectedMemberId
} as Message,
roles: {
dm: {
members: [
{ sp_addresses: myAddresses },
{ sp_addresses: [this.selectedMemberId] }
],
validation_rules: [
{
quorum: 0.01,
fields: ['message'],
min_sig_member: 0.01,
},
],
storages: [storageUrl]
}
}
};
const result = await this.createMessagingProcess(
[{ sp_addresses: [this.selectedMemberId!] }],
'relay_address',
1
);
console.log('Final file message process:', {
template: messageTemplate,
result: result,
timestamp: new Date().toISOString()
});
const fileInput = this.shadowRoot?.querySelector('#file-input') as HTMLInputElement;
if (fileInput) fileInput.value = '';
this.loadMemberChat(this.selectedMemberId!);
setTimeout(() => {
const autoReply = this.generateAutoReply(this.selectedMemberId!);
messageStore.addMessage(this.selectedMemberId!, autoReply);
this.messagesMock = messageStore.getMessages();
this.loadMemberChat(this.selectedMemberId!);
this.addNotification(this.selectedMemberId!, autoReply);
}, 1000);
} catch (error) {
console.error('Error sending file:', error);
}
}
private async readFileAsBase64(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
private async compressImage(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const img = new Image();
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
img.onload = () => {
// Calculer les nouvelles dimensions
let width = img.width;
let height = img.height;
const MAX_WIDTH = 800;
const MAX_HEIGHT = 600;
if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
canvas.width = width;
canvas.height = height;
ctx?.drawImage(img, 0, 0, width, height);
// Compression avec qualité réduite
resolve(canvas.toDataURL('image/jpeg', 0.7));
};
img.onerror = reject;
img.src = URL.createObjectURL(file);
});
}
connectedCallback() {
if (this.processId) {
console.log('🔍 Loading chat with process ID:', this.processId);
this.loadGroupList(this.processId);
} else {
console.error('❌ No process ID found in element attributes');
}
// Si un membre est sélectionné par défaut, charger ses messages
if (this.selectedMemberId) {
this.loadMemberChat(this.selectedMemberId);
}
}
private addNewMessagingProcess(processId: string, processName: string) {
console.log('🎯 Adding new messaging process:', { processId, processName });
const groupList = this.shadowRoot?.querySelector('#group-list');
if (!groupList) {
console.error('Group list not found in shadow DOM');
return;
}
// Vérifier si le processus existe déjà
const existingProcess = groupList.querySelector(`[data-process-id="${processId}"]`);
if (existingProcess) {
console.log('Process already exists:', processId);
return;
}
// Créer le nouveau groupe
const li = document.createElement('li');
li.className = 'group-list-item';
li.setAttribute('data-process-id', processId);
// Créer le conteneur flex
const container = document.createElement('div');
container.className = 'group-item-container';
// Créer un span pour le nom du processus
const nameSpan = document.createElement('span');
nameSpan.textContent = processName;
nameSpan.className = 'process-name';
// Créer un span pour les emojis
const emojiSpan = document.createElement('span');
emojiSpan.className = 'process-emoji';
// Ajouter les emojis de l'adresse
addressToEmoji(processId).then(emojis => {
emojiSpan.textContent = emojis;
});
container.appendChild(nameSpan);
container.appendChild(emojiSpan);
li.appendChild(container);
// Créer la liste des rôles
const roleList = document.createElement('ul');
roleList.className = 'role-list';
roleList.style.display = 'none';
// Ajouter un rôle par défaut pour le messaging
const roleItem = document.createElement('li');
roleItem.className = 'role-item';
roleItem.textContent = 'Messaging';
roleList.appendChild(roleItem);
li.appendChild(roleList);
groupList.appendChild(li);
console.log('🎯 New messaging process added successfully');
}
private async initSDKClient() {
try {
// Récupérer l'instance du SDK depuis window ou l'initialiser
this.sdkClient = (window as any).sdk || await this.createSDKClient();
if (!this.sdkClient) {
throw new Error('Failed to initialize SDK client');
}
} catch (error) {
console.error('Error initializing SDK client:', error);
}
}
private async createSDKClient() {
// Implémentez ici la logique de création du SDK client
// Ceci est un exemple, ajustez selon votre implémentation réelle
return new Promise((resolve) => {
// Logique d'initialisation du SDK
resolve({
is_paired: () => true, // Valeur par défaut pour le test
create_new_process: async (template: string, parentId: string | null, relayAddress: string, feeRate: number) => {
// Implémentation de create_new_process
return { success: true };
}
});
});
}
}
customElements.define('chat-element', ChatElement);
export { ChatElement };