Compare commits
2 Commits
create-acc
...
service-wo
Author | SHA1 | Date | |
---|---|---|---|
c0f8cc537c | |||
60b49a65e5 |
@ -5,7 +5,7 @@
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build_wasm": "wasm-pack build --out-dir ../ihm_client_dev1/pkg ../sdk_client --target bundler --dev",
|
||||
"build_wasm": "wasm-pack build --out-dir ../ihm_client/pkg ../sdk_client --target bundler --dev",
|
||||
"start": "vite --host 0.0.0.0",
|
||||
"build": "tsc && vite build",
|
||||
"deploy": "sudo cp -r dist/* /var/www/html/",
|
||||
|
@ -599,7 +599,7 @@ body {
|
||||
margin-top: 9vh;
|
||||
margin-left: -1%;
|
||||
text-align: left;
|
||||
width: 100vw;
|
||||
width: 209vh;
|
||||
}
|
||||
|
||||
/* Liste des information sur l'account */
|
||||
@ -1364,62 +1364,3 @@ body {
|
||||
border-radius: 50%;
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
/* ---------------------Style pour le QR code--------------------- */
|
||||
|
||||
.qr-code {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.qr-code:hover {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
.qr-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.qr-modal-content {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.close-qr-modal {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.close-qr-modal:hover {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.qr-code-large {
|
||||
max-width: 300px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.qr-address {
|
||||
margin-top: 10px;
|
||||
word-break: break-all;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
40
src/4nk.css
40
src/4nk.css
@ -316,7 +316,7 @@ h1 {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.create-btn {
|
||||
.sp-address-btn {
|
||||
margin-bottom: 2em;
|
||||
cursor: pointer;
|
||||
background-color: #d0d0d7;
|
||||
@ -778,41 +778,3 @@ select[data-multi-select-plugin] {
|
||||
.process-card-action {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/**************************************** Select Member Home Page ******************************************************/
|
||||
.custom-select {
|
||||
width: 100%;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
direction: ltr;
|
||||
background-color: white;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.custom-select option {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.custom-select option:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.custom-select::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.custom-select::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
.custom-select::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-select::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<div id="creation-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-title">Login</div>
|
||||
<div class="message">
|
||||
Do you want to create a 4NK member?<br />
|
||||
Attempting to create a member with address <br />
|
||||
<strong>{{device1}}</strong> <br />
|
||||
</div>
|
||||
<div class="confirmation-box">
|
||||
<a class="btn confirmation-btn" onclick="confirm()">Confirm</a>
|
||||
<a class="btn refusal-btn" onclick="closeConfirmationModal()">Refuse</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -2,7 +2,7 @@ declare global {
|
||||
interface Window {
|
||||
initAccount: () => void;
|
||||
showContractPopup: (contractId: string) => void;
|
||||
showPairing: () => Promise<void>;
|
||||
showPairing: () => void;
|
||||
showWallet: () => void;
|
||||
showData: () => void;
|
||||
addWalletRow: () => void;
|
||||
@ -36,7 +36,6 @@ declare global {
|
||||
generateRecoveryWords: () => string[];
|
||||
exportUserData: () => void;
|
||||
updateActionButtons: () => void;
|
||||
showQRCodeModal: (address: string) => void;
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,7 +45,6 @@ import { Row, WalletRow, DataRow, Notification, Contract, NotificationMessage }
|
||||
import { addressToEmoji } from '../../utils/sp-address.utils';
|
||||
import { getCorrectDOM } from '../../utils/document.utils';
|
||||
import accountStyle from '../../../public/style/account.css?inline';
|
||||
import Services from '../../services/service';
|
||||
|
||||
let isAddingRow = false;
|
||||
let currentRow: HTMLTableRowElement | null = null;
|
||||
@ -199,7 +197,6 @@ class AccountElement extends HTMLElement {
|
||||
window.updateActionButtons = () => this.updateActionButtons();
|
||||
window.openAvatarPopup = () => this.openAvatarPopup();
|
||||
window.closeAvatarPopup = () => this.closeAvatarPopup();
|
||||
window.showQRCodeModal = (address: string) => this.showQRCodeModal(address);
|
||||
|
||||
if (!localStorage.getItem('rows')) {
|
||||
localStorage.setItem('rows', JSON.stringify(defaultRows));
|
||||
@ -555,13 +552,6 @@ private updateTableContent(rows: Row[]): void {
|
||||
<td>${row.column1}</td>
|
||||
<td class="device-name" onclick="window.editDeviceName(this)">${row.column2}</td>
|
||||
<td>${row.column3}</td>
|
||||
<td>
|
||||
<img src="https://api.qrserver.com/v1/create-qr-code/?size=50x50&data=${encodeURIComponent(row.column1)}"
|
||||
alt="QR Code"
|
||||
title="${row.column1}"
|
||||
class="qr-code"
|
||||
onclick="window.showQRCodeModal('${encodeURIComponent(row.column1)}')">
|
||||
</td>
|
||||
<td>
|
||||
<button class="delete-button" onclick="window.deleteRow(this)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16" fill="red">
|
||||
@ -637,26 +627,29 @@ private deleteRow(button: HTMLButtonElement): void {
|
||||
const table = row.closest('tbody');
|
||||
if (!table) return;
|
||||
|
||||
// Vérifier le nombre de lignes restantes
|
||||
const remainingRows = table.getElementsByTagName('tr').length;
|
||||
if (remainingRows <= 2) {
|
||||
this.showAlert('You must keep at least 2 devices paired');
|
||||
return;
|
||||
}
|
||||
|
||||
const index = Array.from(table.children).indexOf(row);
|
||||
row.style.transition = 'opacity 0.3s, transform 0.3s';
|
||||
// Animation de suppression
|
||||
row.style.transition = 'opacity 0.3s';
|
||||
row.style.opacity = '0';
|
||||
row.style.transform = 'translateX(-100%)';
|
||||
|
||||
setTimeout(() => {
|
||||
// Obtenir l'index avant la suppression
|
||||
const index = Array.from(table.children).indexOf(row);
|
||||
|
||||
// Supprimer la ligne du DOM
|
||||
row.remove();
|
||||
|
||||
// Mettre à jour le localStorage
|
||||
const storageKey = STORAGE_KEYS[currentMode];
|
||||
const rows = JSON.parse(localStorage.getItem(storageKey) || '[]');
|
||||
if (index > -1) {
|
||||
rows.splice(index, 1);
|
||||
localStorage.setItem(storageKey, JSON.stringify(rows));
|
||||
}
|
||||
rows.splice(index, 1);
|
||||
localStorage.setItem(storageKey, JSON.stringify(rows));
|
||||
}, 300);
|
||||
}
|
||||
|
||||
@ -894,6 +887,9 @@ private showContractPopup(contractId: string) {
|
||||
});
|
||||
}
|
||||
|
||||
// Ajouter à l'objet window
|
||||
|
||||
|
||||
// Fonction utilitaire pour cacher tous les contenus
|
||||
private hideAllContent(): void {
|
||||
const contents = ['pairing-content', 'wallet-content', 'process-content', 'data-content'];
|
||||
@ -906,26 +902,29 @@ private hideAllContent(): void {
|
||||
}
|
||||
|
||||
// Fonctions d'affichage des sections
|
||||
private async showPairing(): Promise<void> {
|
||||
const service = await Services.getInstance();
|
||||
const spAddress = await service.getDeviceAddress();
|
||||
|
||||
private showPairing(): void {
|
||||
|
||||
isAddingRow = false;
|
||||
currentRow = null;
|
||||
|
||||
currentMode = 'pairing';
|
||||
|
||||
// Cacher tous les contenus
|
||||
this.hideAllContent();
|
||||
|
||||
// Mettre à jour le titre
|
||||
const headerElement = this.shadowRoot?.getElementById('parameter-header');
|
||||
if (headerElement) {
|
||||
headerElement.textContent = 'Pairing';
|
||||
}
|
||||
|
||||
|
||||
// Afficher le contenu de pairing
|
||||
const pairingContent = this.shadowRoot?.getElementById('pairing-content');
|
||||
|
||||
if (pairingContent) {
|
||||
pairingContent.style.display = 'block';
|
||||
pairingContent.innerHTML = `
|
||||
<div class="parameter-header" id="parameter-header">Pairing</div>
|
||||
<div class="parameter-header" id="parameter-header">Pairing</div>
|
||||
<div class="table-container">
|
||||
<table class="parameter-table" id="pairing-table">
|
||||
<thead>
|
||||
@ -933,8 +932,7 @@ private async showPairing(): Promise<void> {
|
||||
<th>SP Address</th>
|
||||
<th>Device Name</th>
|
||||
<th>SP Emojis</th>
|
||||
<th>QR Code</th>
|
||||
<th>Actions</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
@ -944,47 +942,9 @@ private async showPairing(): Promise<void> {
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.pairing) || '[]');
|
||||
|
||||
const deviceExists = rows.some((row: Row) => row.column1 === spAddress);
|
||||
|
||||
if (!deviceExists && spAddress) {
|
||||
const emojis = await addressToEmoji(spAddress);
|
||||
|
||||
try {
|
||||
// Déboguer le processus de pairing
|
||||
const pairingProcessId = await service.getPairingProcessId();
|
||||
console.log('Pairing Process ID:', pairingProcessId);
|
||||
|
||||
const pairingProcess = await service.getProcess(pairingProcessId);
|
||||
console.log('Pairing Process:', pairingProcess);
|
||||
|
||||
const userName = pairingProcess?.states?.[0]?.metadata?.userName
|
||||
|| pairingProcess?.states?.[0]?.metadata?.name
|
||||
|| localStorage.getItem('userName')
|
||||
|
||||
console.log('Username found:', userName);
|
||||
|
||||
const newRow = {
|
||||
column1: spAddress,
|
||||
column2: userName,
|
||||
column3: emojis
|
||||
};
|
||||
rows = [newRow, ...rows];
|
||||
localStorage.setItem(STORAGE_KEYS.pairing, JSON.stringify(rows));
|
||||
} catch (error) {
|
||||
console.error('Error getting pairing process:', error);
|
||||
const newRow = {
|
||||
column1: spAddress,
|
||||
column2: 'This Device',
|
||||
column3: emojis
|
||||
};
|
||||
rows = [newRow, ...rows];
|
||||
localStorage.setItem(STORAGE_KEYS.pairing, JSON.stringify(rows));
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour le contenu du tableau
|
||||
const rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.pairing) || '[]');
|
||||
this.updateTableContent(rows);
|
||||
}
|
||||
}
|
||||
@ -1459,28 +1419,6 @@ private initializeEventListeners() {
|
||||
avatarInput.addEventListener('change', this.handleAvatarUpload.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
private showQRCodeModal(address: string): void {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'qr-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="qr-modal-content">
|
||||
<span class="close-qr-modal">×</span>
|
||||
<img src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${address}"
|
||||
alt="QR Code Large"
|
||||
class="qr-code-large">
|
||||
<div class="qr-address">${decodeURIComponent(address)}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.shadowRoot?.appendChild(modal);
|
||||
|
||||
const closeBtn = modal.querySelector('.close-qr-modal');
|
||||
closeBtn?.addEventListener('click', () => modal.remove());
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) modal.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('account-element', AccountElement);
|
||||
|
@ -13,7 +13,6 @@ import Database from '../../services/database.service';
|
||||
import Services from '../../services/service';
|
||||
|
||||
const storageUrl = `/storage`;
|
||||
const defaultProcessName = 'Unnamed Process';
|
||||
|
||||
interface LocalNotification {
|
||||
memberId: string;
|
||||
@ -35,7 +34,7 @@ class ChatElement extends HTMLElement {
|
||||
return ['process-id'];
|
||||
}
|
||||
|
||||
private selectedChatProcessId: string | null = null;
|
||||
private processId: string | null = null;
|
||||
private processRoles: any | null = null;
|
||||
private selectedMember: string | null = null;
|
||||
private notifications: LocalNotification[] = [];
|
||||
@ -49,10 +48,9 @@ class ChatElement extends HTMLElement {
|
||||
}));
|
||||
private messageState: number = 0;
|
||||
private selectedRole: string | null = null;
|
||||
private userProcessSet: Set<string> = new Set();
|
||||
private dmMembersSet: Set<string> = new Set();
|
||||
private addressMap: Record<string, string> = {};
|
||||
private isLoading = false;
|
||||
|
||||
private addressMap: Record<string, string> = {};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -123,23 +121,6 @@ class ChatElement extends HTMLElement {
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('newDataReceived', async (event: CustomEvent) => {
|
||||
const { detail } = event;
|
||||
console.log('New data event received:', JSON.stringify(detail));
|
||||
|
||||
if (detail.processId && detail.processId === this.selectedChatProcessId) {
|
||||
console.log('Detected update to chat');
|
||||
if (this.selectedMember) {
|
||||
await this.loadMemberChat(this.selectedMember);
|
||||
} else {
|
||||
console.error('No selected member?');
|
||||
}
|
||||
} else {
|
||||
console.log('Received an update for another process');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
this.notificationBadge = document.querySelector('.notification-badge');
|
||||
this.notificationBoard = document.getElementById('notification-board');
|
||||
@ -150,6 +131,7 @@ class ChatElement extends HTMLElement {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Initialiser les événements de notification
|
||||
document.addEventListener('click', (event: Event): void => {
|
||||
if (this.notificationBoard && this.notificationBoard.style.display === 'block' &&
|
||||
@ -162,11 +144,19 @@ class ChatElement extends HTMLElement {
|
||||
|
||||
}
|
||||
|
||||
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.loadGroupListFromAProcess(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private initMessageEvents() {
|
||||
const sendButton = this.shadowRoot?.querySelector('#send-button');
|
||||
if (sendButton) {
|
||||
sendButton.addEventListener('click', async () => {
|
||||
await this.sendMessage();
|
||||
sendButton.addEventListener('click', () => {
|
||||
this.sendMessage();
|
||||
setTimeout(async () => await this.reloadMemberChat(this.selectedMember), 600);
|
||||
messageInput.value = '';
|
||||
});
|
||||
@ -174,11 +164,11 @@ class ChatElement extends HTMLElement {
|
||||
|
||||
const messageInput = this.shadowRoot?.querySelector('#message-input');
|
||||
if (messageInput) {
|
||||
messageInput.addEventListener('keypress', async (event: Event) => {
|
||||
messageInput.addEventListener('keypress', (event: Event) => {
|
||||
const keyEvent = event as KeyboardEvent;
|
||||
if (keyEvent.key === 'Enter' && !keyEvent.shiftKey) {
|
||||
event.preventDefault();
|
||||
await this.sendMessage();
|
||||
this.sendMessage();
|
||||
setTimeout(async () => await this.reloadMemberChat(this.selectedMember), 600);
|
||||
messageInput.value = '';
|
||||
}
|
||||
@ -283,7 +273,7 @@ class ChatElement extends HTMLElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.selectedChatProcessId) {
|
||||
if (!this.processId) {
|
||||
console.error('no process id set');
|
||||
return;
|
||||
}
|
||||
@ -316,8 +306,8 @@ class ChatElement extends HTMLElement {
|
||||
}
|
||||
};
|
||||
|
||||
console.log("----this.selectedChatProcessId",this.selectedChatProcessId );
|
||||
const process = await service.getProcess(this.selectedChatProcessId);
|
||||
console.log("----this.processId",this.processId );
|
||||
const process = await service.getProcess(this.processId);
|
||||
|
||||
if (!process) {
|
||||
console.error('Failed to retrieve process from DB');
|
||||
@ -345,26 +335,14 @@ class ChatElement extends HTMLElement {
|
||||
console.log(`newStateId: ${newStateId}`);
|
||||
await service.handleApiReturn(apiReturn);
|
||||
|
||||
const createPrdReturn = service.createPrdUpdate(this.selectedChatProcessId, newStateId);
|
||||
const createPrdReturn = service.createPrdUpdate(this.processId, newStateId);
|
||||
await service.handleApiReturn(createPrdReturn);
|
||||
|
||||
// Now we validate the new state
|
||||
const approveChangeReturn = await service.approveChange(this.selectedChatProcessId, newStateId);
|
||||
const approveChangeReturn = service.approveChange(this.processId, newStateId);
|
||||
await service.handleApiReturn(approveChangeReturn);
|
||||
|
||||
await this.lookForMyDms();
|
||||
|
||||
const groupList = this.shadowRoot?.querySelector('#group-list');
|
||||
const tabs = this.shadowRoot?.querySelectorAll('.tab');
|
||||
const memberList = groupList?.querySelector('.member-list');
|
||||
|
||||
if (memberList) {
|
||||
memberList.innerHTML = '';
|
||||
await this.loadAllMembers();
|
||||
if (tabs) {
|
||||
await this.switchTab('members', tabs);
|
||||
}
|
||||
}
|
||||
await this.loadMemberChat(this.selectedMember);
|
||||
} catch (error) {
|
||||
console.error('❌ Error in sendMessage:', error);
|
||||
}
|
||||
@ -388,16 +366,16 @@ class ChatElement extends HTMLElement {
|
||||
private async lookForChildren(): Promise<string | null> {
|
||||
// Filter processes for the children of current process
|
||||
const service = await Services.getInstance();
|
||||
if (!this.selectedChatProcessId) {
|
||||
if (!this.processId) {
|
||||
console.error('No process id');
|
||||
return null;
|
||||
}
|
||||
const children: string[] = await service.getChildrenOfProcess(this.selectedChatProcessId);
|
||||
const children: string[] = await service.getChildrenOfProcess(this.processId);
|
||||
|
||||
const processRoles = this.processRoles;
|
||||
const selectedMember = this.selectedMember;
|
||||
for (const child of children) {
|
||||
const roles = service.getRoles(JSON.parse(child));
|
||||
const roles = await service.getRoles(JSON.parse(child));
|
||||
// Check that we and the other members are in the role
|
||||
if (!service.isChildRole(processRoles, roles)) {
|
||||
console.error('Child process roles are not a subset of parent')
|
||||
@ -423,123 +401,106 @@ class ChatElement extends HTMLElement {
|
||||
|
||||
const service = await Services.getInstance();
|
||||
const members = await service.getAllMembers();
|
||||
const processes = await service.getProcesses();
|
||||
const database = await Database.getInstance();
|
||||
const db = database.db;
|
||||
|
||||
const memberList = document.createElement('ul');
|
||||
memberList.className = 'member-list active';
|
||||
|
||||
// Partition members into prioritized and remaining arrays.
|
||||
const prioritizedMembers: [string, Member][] = [];
|
||||
const remainingMembers: [string, Member][] = [];
|
||||
const prioritizedMembers: [string, any][] = [];
|
||||
const remainingMembers: [string, any][] = [];
|
||||
|
||||
for (const [processId, member] of Object.entries(members)) {
|
||||
if (this.dmMembersSet.has(processId)) {
|
||||
prioritizedMembers.push([processId, member]);
|
||||
prioritizedMembers.push([processId, member]);
|
||||
} else {
|
||||
remainingMembers.push([processId, member]);
|
||||
remainingMembers.push([processId, member]);
|
||||
}
|
||||
}
|
||||
|
||||
const sortedMembers = prioritizedMembers.concat(remainingMembers);
|
||||
|
||||
// Process each member.
|
||||
for (const [processId, member] of sortedMembers) {
|
||||
for (const [processId, member] of Object.entries(members)) {
|
||||
const memberItem = document.createElement('li');
|
||||
memberItem.className = 'member-item';
|
||||
|
||||
// Apply special styling if the member is prioritized.
|
||||
if (this.dmMembersSet.has(processId)) {
|
||||
memberItem.style.cssText = `
|
||||
background-color: var(--accent-color);
|
||||
transition: background-color 0.3s ease;
|
||||
cursor: pointer;
|
||||
`;
|
||||
memberItem.addEventListener('mouseover', () => {
|
||||
memberItem.style.backgroundColor = 'var(--accent-color-hover)';
|
||||
});
|
||||
memberItem.addEventListener('mouseout', () => {
|
||||
memberItem.style.backgroundColor = 'var(--accent-color)';
|
||||
});
|
||||
memberItem.style.cssText = `
|
||||
background-color: var(--accent-color);
|
||||
transition: background-color 0.3s ease;
|
||||
cursor: pointer;
|
||||
`;
|
||||
memberItem.onmouseover = () => {
|
||||
memberItem.style.backgroundColor = 'var(--accent-color-hover)';
|
||||
};
|
||||
memberItem.onmouseout = () => {
|
||||
memberItem.style.backgroundColor = 'var(--accent-color)';
|
||||
};
|
||||
}
|
||||
|
||||
// Create a container for the member content.
|
||||
|
||||
const memberContainer = document.createElement('div');
|
||||
memberContainer.className = 'member-container';
|
||||
|
||||
// Create the emoji span and load its label.
|
||||
const emojiSpan = document.createElement('span');
|
||||
emojiSpan.className = 'member-emoji';
|
||||
|
||||
const emojis = await addressToEmoji(processId);
|
||||
emojiSpan.dataset.emojis = emojis;
|
||||
|
||||
// Get the member name, if any, and add it to the display
|
||||
const process = processes[processId];
|
||||
let memberPublicName;
|
||||
if (process) {
|
||||
const publicMemberData = service.getPublicData(process);
|
||||
if (publicMemberData) {
|
||||
const extractedName = publicMemberData['memberPublicName'];
|
||||
if (extractedName !== undefined && extractedName !== null) {
|
||||
memberPublicName = extractedName;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!memberPublicName) {
|
||||
memberPublicName = 'Unnamed Member';
|
||||
}
|
||||
|
||||
emojiSpan.textContent = `${memberPublicName} (${emojis})`
|
||||
|
||||
const transaction = db.transaction("labels", "readonly");
|
||||
const store = transaction.objectStore("labels");
|
||||
const request = store.get(emojis);
|
||||
|
||||
request.onsuccess = () => {
|
||||
const label = request.result;
|
||||
emojiSpan.textContent = label ? `${label.label} (${emojis})` : `Member (${emojis})`;
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
emojiSpan.textContent = `Member (${emojis})`;
|
||||
};
|
||||
|
||||
memberContainer.appendChild(emojiSpan);
|
||||
memberItem.appendChild(memberContainer);
|
||||
|
||||
// Add click handler to load member chat.
|
||||
memberItem.addEventListener('click', async () => {
|
||||
await this.loadMemberChat(processId);
|
||||
await this.loadMemberChat(processId);
|
||||
});
|
||||
|
||||
// Create and configure the edit label button.
|
||||
|
||||
const editLabelButton = document.createElement('button');
|
||||
editLabelButton.className = 'edit-label-button';
|
||||
editLabelButton.textContent = "✏️";
|
||||
|
||||
editLabelButton.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
editLabelButton.addEventListener("dblclick", async (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
|
||||
const newLabel = prompt("Set a new name for the member:");
|
||||
if (!newLabel) return;
|
||||
|
||||
const db = await Database.getInstance();
|
||||
this.updateLabelForEmoji(emojis, newLabel, db, emojiSpan, processId);
|
||||
|
||||
const editTransaction = db.transaction("labels", "readwrite");
|
||||
const editStore = editTransaction.objectStore("labels");
|
||||
|
||||
const labelObject = { emoji: emojis, label: newLabel };
|
||||
const putRequest = editStore.put(labelObject);
|
||||
|
||||
putRequest.onsuccess = () => {
|
||||
emojiSpan.textContent = `${newLabel} : ${emojis}`;
|
||||
this.reloadMemberChat(processId);
|
||||
};
|
||||
});
|
||||
memberContainer.appendChild(editLabelButton);
|
||||
|
||||
|
||||
memberList.appendChild(memberItem);
|
||||
memberContainer.appendChild(editLabelButton);
|
||||
}
|
||||
|
||||
groupList.appendChild(memberList);
|
||||
}
|
||||
|
||||
// Helper function to update a label in IndexedDB.
|
||||
private updateLabelForEmoji(
|
||||
emojis: string,
|
||||
newLabel: string,
|
||||
db: IDBDatabase,
|
||||
emojiSpan: HTMLElement,
|
||||
processId: string
|
||||
) {
|
||||
const transaction = db.transaction("labels", "readwrite");
|
||||
const store = transaction.objectStore("labels");
|
||||
const labelObject = { emoji: emojis, label: newLabel };
|
||||
const request = store.put(labelObject);
|
||||
|
||||
request.onsuccess = () => {
|
||||
emojiSpan.textContent = `${newLabel} : ${emojis}`;
|
||||
this.reloadMemberChat(processId);
|
||||
};
|
||||
}
|
||||
|
||||
private async lookForDmProcess(): Promise<string | null> {
|
||||
const service = await Services.getInstance();
|
||||
const processes = await service.getMyProcesses();
|
||||
@ -552,12 +513,12 @@ class ChatElement extends HTMLElement {
|
||||
const process = await service.getProcess(processId);
|
||||
console.log(process);
|
||||
const state = process.states[0]; // We assume that description never change and that we are part of the process from the beginning
|
||||
const description = await service.decryptAttribute(processId, state, 'description');
|
||||
const description = await service.decryptAttribute(state, 'description');
|
||||
console.log(description);
|
||||
if (!description || description !== "dm") {
|
||||
continue;
|
||||
}
|
||||
const roles = service.getRoles(process);
|
||||
const roles = await service.getRoles(process);
|
||||
if (!service.rolesContainsMember(roles, recipientAddresses)) {
|
||||
console.error('Member is not part of the process');
|
||||
continue;
|
||||
@ -583,38 +544,40 @@ class ChatElement extends HTMLElement {
|
||||
for (const processId of processes) {
|
||||
const process = await service.getProcess(processId);
|
||||
const state = process.states[0];
|
||||
const description = await service.decryptAttribute(processId, state, 'description');
|
||||
const description = await service.decryptAttribute(state, 'description');
|
||||
if (!description || description !== "dm") {
|
||||
continue;
|
||||
}
|
||||
const roles = service.getRoles(process);
|
||||
const roles = await service.getRoles(process);
|
||||
if (!service.rolesContainsMember(roles, myAddresses)) {
|
||||
continue;
|
||||
}
|
||||
const members = roles.dm.members;
|
||||
for (const member of members) {;
|
||||
if (!service.compareMembers(member.sp_addresses, myAddresses)) {
|
||||
for (const [id, mem] of Object.entries(allMembers)) {
|
||||
if (service.compareMembers(mem.sp_addresses, member.sp_addresses)) {
|
||||
this.dmMembersSet.add(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (JSON.stringify(member.sp_addresses) !== JSON.stringify(myAddresses)) {
|
||||
this.dmMembersSet.add(member.sp_addresses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updatedDmMembersSet = new Set<string>();
|
||||
for (const dmMember of this.dmMembersSet) {
|
||||
for (const [processId, member] of Object.entries(allMembers)) {
|
||||
if (JSON.stringify(member.sp_addresses) === JSON.stringify(dmMember)) {
|
||||
updatedDmMembersSet.add(processId);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.dmMembersSet = updatedDmMembersSet;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
console.log("dmMembersSet:", this.dmMembersSet);
|
||||
console.log("SET DE MEMBRES AVEC QUI JE DM:", this.dmMembersSet);
|
||||
return null;
|
||||
}
|
||||
|
||||
private async loadMemberChat(pairingProcess: string) {
|
||||
if (this.isLoading) {
|
||||
console.log('Already loading messages, skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.isLoading = true;
|
||||
const service = await Services.getInstance();
|
||||
const myAddresses = await service.getMemberFromDevice();
|
||||
const database = await Database.getInstance();
|
||||
@ -633,8 +596,6 @@ class ChatElement extends HTMLElement {
|
||||
const messagesContainer = this.shadowRoot?.querySelector('#messages');
|
||||
|
||||
if (!chatHeader || !messagesContainer) return;
|
||||
|
||||
messagesContainer.innerHTML = '';
|
||||
|
||||
const emojis = await addressToEmoji(pairingProcess);
|
||||
|
||||
@ -651,6 +612,8 @@ class ChatElement extends HTMLElement {
|
||||
chatHeader.textContent = `Chat with member (${emojis})`;
|
||||
};
|
||||
|
||||
messagesContainer.innerHTML = '';
|
||||
|
||||
let dmProcessId = await this.lookForDmProcess();
|
||||
|
||||
if (dmProcessId === null) {
|
||||
@ -669,10 +632,12 @@ class ChatElement extends HTMLElement {
|
||||
setTimeout(async () => {
|
||||
// Now create a first commitment
|
||||
console.log('Created a dm process', processId);
|
||||
this.selectedChatProcessId = processId;
|
||||
this.processId = processId;
|
||||
const createPrdReturn = await service.createPrdUpdate(processId, stateId);
|
||||
console.log(createPrdReturn);
|
||||
await service.handleApiReturn(createPrdReturn);
|
||||
const approveChangeReturn = await service.approveChange(processId, stateId);
|
||||
const approveChangeReturn = service.approveChange(processId, stateId);
|
||||
console.log(approveChangeReturn);
|
||||
await service.handleApiReturn(approveChangeReturn);
|
||||
}, 500);
|
||||
} catch (e) {
|
||||
@ -686,7 +651,7 @@ class ChatElement extends HTMLElement {
|
||||
}
|
||||
} else {
|
||||
console.log('Found DM process', dmProcessId);
|
||||
this.selectedChatProcessId = dmProcessId;
|
||||
this.processId = dmProcessId;
|
||||
}
|
||||
|
||||
/* TODO
|
||||
@ -704,25 +669,25 @@ class ChatElement extends HTMLElement {
|
||||
// Récupérer les messages depuis les états du processus
|
||||
const allMessages: any[] = [];
|
||||
|
||||
const dmProcess = await service.getProcess(this.selectedChatProcessId);
|
||||
const dmProcess = await service.getProcess(dmProcessId);
|
||||
|
||||
console.log(dmProcess);
|
||||
|
||||
if (dmProcess?.states) {
|
||||
for (const state of dmProcess.states) {
|
||||
if (state.state_id === '') { continue; }
|
||||
const message = await service.decryptAttribute(this.selectedChatProcessId, state, 'message');
|
||||
const pcd_commitment = state.pcd_commitment;
|
||||
const message = await service.decryptAttribute(state, 'message');
|
||||
if (message === "" || message === undefined || message === null) {
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
console.log('message', message);
|
||||
allMessages.push(message);
|
||||
}
|
||||
}
|
||||
console.log('message', message);
|
||||
allMessages.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
allMessages.sort((a, b) => a.metadata.createdAt - b.metadata.createdAt);
|
||||
if (allMessages.length > 0) {
|
||||
console.log('Messages found:', allMessages);
|
||||
allMessages.sort((a, b) => a.metadata.createdAt - b.metadata.createdAt);
|
||||
for (const message of allMessages) {
|
||||
const messageElement = document.createElement('div');
|
||||
messageElement.className = 'message-container';
|
||||
@ -790,8 +755,6 @@ class ChatElement extends HTMLElement {
|
||||
this.scrollToBottom(messagesContainer);
|
||||
} catch (error) {
|
||||
console.error('❌ Error in loadMemberChat:', error);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -805,8 +768,6 @@ class ChatElement extends HTMLElement {
|
||||
const messagesContainer = this.shadowRoot?.querySelector('#messages');
|
||||
|
||||
if (!chatHeader || !messagesContainer) return;
|
||||
|
||||
messagesContainer.innerHTML = '';
|
||||
|
||||
const emojis = await addressToEmoji(pairingProcess);
|
||||
|
||||
@ -816,17 +777,16 @@ class ChatElement extends HTMLElement {
|
||||
|
||||
request.onsuccess = () => {
|
||||
const label = request.result;
|
||||
if (this.selectedMember === pairingProcess) {
|
||||
chatHeader.textContent = label ? `Chat with ${label.label} (${emojis})` : `Chat with member (${emojis})`;
|
||||
}
|
||||
|
||||
chatHeader.textContent = label ? `Chat with ${label.label} (${emojis})` : `Chat with member (${emojis})`;
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
chatHeader.textContent = `Chat with member (${emojis})`;
|
||||
};
|
||||
|
||||
let dmProcessId = await this.selectedChatProcessId;
|
||||
messagesContainer.innerHTML = '';
|
||||
|
||||
let dmProcessId = await this.processId;
|
||||
|
||||
// Récupérer les messages depuis les états du processus
|
||||
const allMessages: any[] = [];
|
||||
@ -837,8 +797,8 @@ class ChatElement extends HTMLElement {
|
||||
|
||||
if (dmProcess?.states) {
|
||||
for (const state of dmProcess.states) {
|
||||
if (!state.state_id) { continue; }
|
||||
const message = await service.decryptAttribute(dmProcessId, state, 'message');
|
||||
const pcd_commitment = state.pcd_commitment;
|
||||
const message = await service.decryptAttribute(state, 'message');
|
||||
if (message === "" || message === undefined || message === null) {
|
||||
continue;
|
||||
}
|
||||
@ -1047,8 +1007,6 @@ class ChatElement extends HTMLElement {
|
||||
|
||||
|
||||
private async switchTab(tabType: string, tabs: NodeListOf<Element>) {
|
||||
const service = await Services.getInstance();
|
||||
|
||||
// Mettre à jour les classes des onglets
|
||||
tabs.forEach(tab => {
|
||||
tab.classList.toggle('active', tab.getAttribute('data-tab') === tabType);
|
||||
@ -1068,7 +1026,7 @@ class ChatElement extends HTMLElement {
|
||||
// Charger le contenu approprié
|
||||
switch (tabType) {
|
||||
case 'processes':
|
||||
const processSet = await service.getMyProcesses();
|
||||
const processSet = await this.getProcessesWhereTheCurrentMemberIs();
|
||||
await this.loadAllProcesses(processSet);
|
||||
break;
|
||||
case 'members':
|
||||
@ -1081,14 +1039,13 @@ class ChatElement extends HTMLElement {
|
||||
}
|
||||
|
||||
//load all processes from the service
|
||||
private async loadAllProcesses() {
|
||||
private async loadAllProcesses(processSet: Set<string>) {
|
||||
console.log('🎯 Loading all processes');
|
||||
this.closeSignature();
|
||||
|
||||
const service = await Services.getInstance();
|
||||
const allProcesses: Record<string, Process> = await service.getProcesses();
|
||||
const myProcesses: string[] = await service.getMyProcesses();
|
||||
const allProcesses = await this.getProcesses();
|
||||
|
||||
|
||||
// Afficher les processus dans le container #group-list
|
||||
const groupList = this.shadowRoot?.querySelector('#group-list');
|
||||
if (!groupList) {
|
||||
console.warn('⚠️ Group list element not found');
|
||||
@ -1117,102 +1074,94 @@ class ChatElement extends HTMLElement {
|
||||
});
|
||||
|
||||
//trier les processus : ceux de l'utilisateur en premier
|
||||
const sortedEntries = Object.entries(allProcesses).sort(
|
||||
([keyA], [keyB]) => {
|
||||
const inSetA = myProcesses.includes(keyA);
|
||||
const inSetB = myProcesses.includes(keyB);
|
||||
return inSetB ? 1 : inSetA ? -1 : 0;
|
||||
}
|
||||
);
|
||||
allProcesses.sort((a, b) => {
|
||||
const aInSet = this.userProcessSet.has(a.value.states[0].commited_in);
|
||||
const bInSet = this.userProcessSet.has(b.value.states[0].commited_in);
|
||||
return bInSet ? 1 : aInSet ? -1 : 0;
|
||||
});
|
||||
|
||||
for (const [processId, process] of sortedEntries) {
|
||||
// Create and configure the main list item.
|
||||
for (const process of allProcesses) {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'group-list-item';
|
||||
li.setAttribute('data-process-id', processId);
|
||||
|
||||
// Retrieve roles for the current process.
|
||||
const roles = service.getRoles(process);
|
||||
if (!roles) {
|
||||
console.error('Failed to get roles for process:', process);
|
||||
const oneProcess = process.value.states[0].commited_in;
|
||||
let roles;
|
||||
try {
|
||||
//roles = await service.getRoles(process);
|
||||
if (!roles) {
|
||||
roles = await process.value.states[0]?.roles;
|
||||
}
|
||||
} catch (e) {
|
||||
// console.error('Failed to get roles for process:', process);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If process is a pairing process, we don't want it in the list
|
||||
if (service.isPairingProcess(roles)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const publicData = service.getPublicData(process);
|
||||
const processName = publicData['processName'];
|
||||
const emoji = await addressToEmoji(processId);
|
||||
|
||||
let displayName;
|
||||
if (processName) {
|
||||
displayName = `${processName} (${emoji})`;
|
||||
} else {
|
||||
displayName = `${defaultProcessName} (${emoji})`;
|
||||
}
|
||||
|
||||
// If the process is part of myProcesses, apply special styling.
|
||||
if (myProcesses && myProcesses.includes(processId)) {
|
||||
// Si le processus est dans notre Set, ajouter la classe my-process
|
||||
if (this.userProcessSet && this.userProcessSet.has(oneProcess)) {
|
||||
li.style.cssText = `
|
||||
background-color: var(--accent-color);
|
||||
transition: background-color 0.3s ease;
|
||||
cursor: pointer;
|
||||
background-color: var(--accent-color);
|
||||
transition: background-color 0.3s ease;
|
||||
cursor: pointer;
|
||||
`;
|
||||
li.addEventListener('mouseover', () => {
|
||||
li.style.backgroundColor = 'var(--accent-color-hover)';
|
||||
});
|
||||
li.addEventListener('mouseout', () => {
|
||||
li.style.backgroundColor = 'var(--accent-color)';
|
||||
});
|
||||
console.log("✅ Processus trouvé dans le set:", processId);
|
||||
li.onmouseover = () => {
|
||||
li.style.backgroundColor = 'var(--accent-color-hover)';
|
||||
};
|
||||
li.onmouseout = () => {
|
||||
li.style.backgroundColor = 'var(--accent-color)';
|
||||
};
|
||||
console.log("✅ Processus trouvé dans le set:", oneProcess);
|
||||
}
|
||||
|
||||
// Attach a click handler for the process.
|
||||
li.addEventListener('click', async (event) => {
|
||||
li.setAttribute('data-process-id', oneProcess);
|
||||
//----MANAGE THE CLICK ON PROCESS ----
|
||||
li.onclick = async (event) => {
|
||||
event.stopPropagation();
|
||||
console.log("CLICKED ON PROCESS:", processId);
|
||||
|
||||
// Update the signature header with the corresponding emoji.
|
||||
console.log("CLICKED ON PROCESS:", oneProcess);
|
||||
//viser le h1 de signature-header
|
||||
const signatureHeader = this.shadowRoot?.querySelector('.signature-header h1');
|
||||
if (signatureHeader) {
|
||||
if (processName) {
|
||||
signatureHeader.textContent = `Signature of ${displayName}`;
|
||||
} else {
|
||||
signatureHeader.textContent = `Signature of ${displayName}`;
|
||||
}
|
||||
const emoji = await addressToEmoji(oneProcess);
|
||||
signatureHeader.textContent = `Signature of ${emoji}`;
|
||||
}
|
||||
|
||||
this.openSignature();
|
||||
|
||||
//afficher les roles dans chaque processus
|
||||
console.log('🎯 Roles de signature:', roles);
|
||||
await this.loadAllRolesAndMembersInSignature(roles);
|
||||
await this.newRequest(processId);
|
||||
});
|
||||
//----MANAGE THE CLICK ON NEW REQUEST ----
|
||||
await this.newRequest(oneProcess);
|
||||
};
|
||||
groupList.appendChild(li);
|
||||
|
||||
// Create the container for the process name and emoji.
|
||||
const container = document.createElement('div');
|
||||
container.className = 'group-item-container';
|
||||
container.className = 'group-item-container';
|
||||
|
||||
// Create and set the process name element.
|
||||
const nameSpan = document.createElement('span');
|
||||
nameSpan.textContent = `Process : `;
|
||||
nameSpan.className = 'process-name';
|
||||
nameSpan.textContent = displayName;
|
||||
|
||||
container.appendChild(nameSpan);
|
||||
|
||||
addressToEmoji(oneProcess).then(emojis => {
|
||||
const emojiSpan = document.createElement('span');
|
||||
emojiSpan.className = 'process-emoji';
|
||||
emojiSpan.textContent = emojis;
|
||||
container.appendChild(emojiSpan);
|
||||
});
|
||||
|
||||
li.appendChild(container);
|
||||
|
||||
// Create a hidden list for roles.
|
||||
// afficher les roles dans chaque processus
|
||||
|
||||
//console.log('🎯 Roles:', roles);
|
||||
const roleList = document.createElement('ul');
|
||||
roleList.className = 'role-list';
|
||||
roleList.style.display = 'none';
|
||||
(roleList as HTMLElement).style.display = 'none';
|
||||
|
||||
// Process each role and create role items.
|
||||
Object.entries(roles).forEach(([roleName, roleData]) => {
|
||||
// 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';
|
||||
|
||||
@ -1220,46 +1169,41 @@ class ChatElement extends HTMLElement {
|
||||
roleNameSpan.className = 'role-name';
|
||||
roleNameSpan.textContent = roleName;
|
||||
|
||||
// Filter duplicate members by using the first sp_address as a key.
|
||||
const uniqueMembers = new Map();
|
||||
roleData.members?.forEach(member => {
|
||||
const spAddress = member.sp_addresses?.[0];
|
||||
if (spAddress && !uniqueMembers.has(spAddress)) {
|
||||
uniqueMembers.set(spAddress, member);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
|
||||
// Create a new roleData object with unique members.
|
||||
|
||||
// Créer un nouveau roleData avec les membres uniques
|
||||
const filteredRoleData = {
|
||||
...roleData,
|
||||
members: Array.from(uniqueMembers.values()),
|
||||
members: Array.from(uniqueMembers.values())
|
||||
};
|
||||
|
||||
// Attach a click handler for the role.
|
||||
roleContainer.addEventListener('click', async (event) => {
|
||||
event.stopPropagation();
|
||||
console.log("CLICKED ON ROLE:", roleName);
|
||||
await this.toggleMembers(filteredRoleData, roleItem);
|
||||
console.log("CLICKED ON ROLE:", roleName);
|
||||
event.stopPropagation();
|
||||
await this.toggleMembers(filteredRoleData, roleItem);
|
||||
});
|
||||
|
||||
|
||||
roleContainer.appendChild(roleNameSpan);
|
||||
roleItem.appendChild(roleContainer);
|
||||
roleList.appendChild(roleItem);
|
||||
});
|
||||
|
||||
li.appendChild(roleList);
|
||||
groupList.appendChild(li);
|
||||
|
||||
// Toggle role list display when the container is clicked.
|
||||
container.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
container.classList.toggle('expanded');
|
||||
roleList.style.display = container.classList.contains('expanded') ? 'block' : 'none';
|
||||
});
|
||||
|
||||
// Append the completed process list item once.
|
||||
groupList.appendChild(li);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private async newRequest(processId: string) {
|
||||
@ -1403,7 +1347,7 @@ class ChatElement extends HTMLElement {
|
||||
console.log("Process récupéré:", process);
|
||||
|
||||
// Récupérer les rôles directement depuis le dernier état
|
||||
const roles = service.getRoles(process);
|
||||
const roles = await service.getRoles(process);
|
||||
console.log("Roles trouvés:", roles);
|
||||
|
||||
if (!roles) return [];
|
||||
@ -1482,6 +1426,7 @@ class ChatElement extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
//Load tous les processus où le sp_adress est impliqué et renvoie un tableau d'adresses de processus
|
||||
private async getMyProcessId() {
|
||||
const service = await Services.getInstance();
|
||||
return service.getPairingProcessId();
|
||||
@ -1540,6 +1485,126 @@ class ChatElement extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Load the group list from all processes
|
||||
public async loadAllGroupListFromMyProcess(): Promise<void> {
|
||||
console.log('🎯 Loading all group list');
|
||||
const groupList = this.shadowRoot?.querySelector('#group-list');
|
||||
if (!groupList) {
|
||||
console.error('❌ Group list element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const processes = await this.getProcesses();
|
||||
|
||||
if (!processes || Object.keys(processes).length === 0) {
|
||||
console.log('⚠️ No processes found');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const {key} of processes) {
|
||||
const processName = await key;
|
||||
console.log("Je suis l'id process de la boucle :" ,processName);
|
||||
this.loadGroupListFromAProcess(processName);
|
||||
}
|
||||
}
|
||||
|
||||
// Load the group list from a process
|
||||
private async loadGroupListFromAProcess(processId: string): Promise<void> {
|
||||
console.log('Loading group list with processId:', processId);
|
||||
const groupList = this.shadowRoot?.querySelector('#group-list');
|
||||
if (!groupList) return;
|
||||
|
||||
groupList.innerHTML = '';
|
||||
|
||||
this.processId = processId;
|
||||
const service = await Services.getInstance();
|
||||
const process = await service.getProcess(this.processId);
|
||||
|
||||
const roles = await service.getRoles(process);
|
||||
if (roles === null) {
|
||||
console.error('no roles in process');
|
||||
return;
|
||||
}
|
||||
this.processRoles = roles;
|
||||
console.log('🔑 Roles found:', this.processRoles);
|
||||
|
||||
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';
|
||||
(roleList as HTMLElement).style.display = 'none';
|
||||
|
||||
// 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', async (event) => {
|
||||
console.log("CLICKED ON ROLE:", roleName);
|
||||
event.stopPropagation();
|
||||
await this.toggleMembers(filteredRoleData, roleItem);
|
||||
});
|
||||
|
||||
roleContainer.appendChild(roleNameSpan);
|
||||
roleItem.appendChild(roleContainer);
|
||||
roleList.appendChild(roleItem);
|
||||
});
|
||||
|
||||
li.appendChild(roleList);
|
||||
groupList.appendChild(li);
|
||||
|
||||
container.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
container.classList.toggle('expanded');
|
||||
roleList.style.display = container.classList.contains('expanded') ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// Send a file
|
||||
private async sendFile(file: File) {
|
||||
const MAX_FILE_SIZE = 1 * 1024 * 1024;
|
||||
@ -1717,28 +1782,30 @@ class ChatElement extends HTMLElement {
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
const service = await Services.getInstance();
|
||||
this.processId = this.getAttribute('process-id');
|
||||
|
||||
const loadPage = async () => {
|
||||
if (this.processId) {
|
||||
console.log("🔍 Chargement du chat avec processID");
|
||||
await this.loadGroupListFromAProcess(this.processId);
|
||||
} else {
|
||||
console.log("🔍 Chargement des processus par défaut");
|
||||
await this.loadAllProcesses();
|
||||
|
||||
if (this.selectedMember) {
|
||||
console.log('🔍 Loading chat for selected member:', this.selectedMember);
|
||||
await this.loadMemberChat(this.selectedMember);
|
||||
} else {
|
||||
console.warn('⚠️ No member selected yet. Waiting for selection...');
|
||||
}
|
||||
const processSet = await this.getProcessesWhereTheCurrentMemberIs();
|
||||
await this.loadAllProcesses(processSet);
|
||||
}
|
||||
|
||||
if (this.selectedMember && this.selectedMember.length > 0) {
|
||||
console.log('🔍 Loading chat for selected member:', this.selectedMember);
|
||||
await this.loadMemberChat(this.selectedMember);
|
||||
} else {
|
||||
console.warn('⚠️ No member selected yet. Waiting for selection...');
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timeout;
|
||||
window.addEventListener('process-updated', async (e: CustomEvent) => {
|
||||
const processId = e.detail.processId;
|
||||
console.log('Notified of an update for process', processId);
|
||||
await loadPage();
|
||||
if (processId === this.processId) {
|
||||
setTimeout(async () => await this.reloadMemberChat(this.selectedMember), 3000);
|
||||
}
|
||||
});
|
||||
|
||||
await loadPage();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,23 +4,24 @@
|
||||
|
||||
<div class="tab-container">
|
||||
<div class="tabs">
|
||||
<div class="tab active" data-tab="tab1">Create an account</div>
|
||||
<div class="tab" data-tab="tab2">Add a device for an existing memeber</div>
|
||||
<div class="tab active" data-tab="tab1">Scan QR Code</div>
|
||||
<div class="tab" data-tab="tab2">Scan other device</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-container">
|
||||
<div id="tab1" class="card tab-content active">
|
||||
<div class="card-description">Create an account :</div>
|
||||
<div class="card-description">Scan with your other device :</div>
|
||||
<div class="pairing-request"></div>
|
||||
<!-- <div class="card-image qr-code">
|
||||
<div class="card-image qr-code">
|
||||
<img src="assets/qr_code.png" alt="QR Code" width="150" height="150" />
|
||||
</div> -->
|
||||
<button id="createButton" class="create-btn"></button>
|
||||
</div>
|
||||
<button id="copyBtn" class="sp-address-btn"></button>
|
||||
<div class="card-image emoji-display" id="emoji-display"></div>
|
||||
</div>
|
||||
<div class="separator"></div>
|
||||
<div id="tab2" class="card tab-content">
|
||||
<div class="card-description">Add a device for an existing member :</div>
|
||||
<div class="card-description">Scan your other device :</div>
|
||||
<div class="card-image camera-card">
|
||||
<img id="scanner" src="assets/camera.jpg" alt="QR Code" width="150" height="150" />
|
||||
<button id="scan-btn" onclick="scanDevice()">Scan</button>
|
||||
@ -30,13 +31,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<p>Or</p>
|
||||
<!-- <input type="text" id="addressInput" placeholder="Paste address" />
|
||||
<div id="emoji-display-2"></div> -->
|
||||
<div class="card-description">Chose a member :</div>
|
||||
<select name="memberSelect" id="memberSelect" size="5" class="custom-select">
|
||||
<!-- Options -->
|
||||
</select>
|
||||
|
||||
<input type="text" id="addressInput" placeholder="Paste address" />
|
||||
<div id="emoji-display-2"></div>
|
||||
<button id="okButton" style="display: none">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Routing from '../../services/modal.service';
|
||||
import Services from '../../services/service';
|
||||
import { addSubscription } from '../../utils/subscription.utils';
|
||||
import { displayEmojis, generateQRCode, generateCreateBtn, addressToEmoji } from '../../utils/sp-address.utils';
|
||||
import { displayEmojis, generateQRCode } from '../../utils/sp-address.utils';
|
||||
import { getCorrectDOM } from '../../utils/html.utils';
|
||||
import QrScannerComponent from '../../components/qrcode-scanner/qrcode-scanner-component';
|
||||
export { QrScannerComponent };
|
||||
@ -20,12 +20,8 @@ export async function initHomePage(): Promise<void> {
|
||||
|
||||
const service = await Services.getInstance();
|
||||
const spAddress = await service.getDeviceAddress();
|
||||
// generateQRCode(spAddress);
|
||||
generateCreateBtn ();
|
||||
generateQRCode(spAddress);
|
||||
displayEmojis(spAddress);
|
||||
|
||||
// Add this line to populate the select when the page loads
|
||||
await populateMemberSelect();
|
||||
}
|
||||
|
||||
//// Modal
|
||||
@ -49,46 +45,4 @@ function scanDevice() {
|
||||
if (reader) reader.innerHTML = '<qr-scanner></qr-scanner>';
|
||||
}
|
||||
|
||||
async function populateMemberSelect() {
|
||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
||||
const memberSelect = container.querySelector('#memberSelect') as HTMLSelectElement;
|
||||
|
||||
if (!memberSelect) {
|
||||
console.error('Could not find memberSelect element');
|
||||
return;
|
||||
}
|
||||
|
||||
const service = await Services.getInstance();
|
||||
const members = service.getAllMembersSorted();
|
||||
|
||||
for (const [processId, member] of Object.entries(members)) {
|
||||
const process = await service.getProcess(processId);
|
||||
let memberPublicName;
|
||||
|
||||
if (process) {
|
||||
const publicMemberData = service.getPublicData(process);
|
||||
if (publicMemberData) {
|
||||
const extractedName = publicMemberData['memberPublicName'];
|
||||
if (extractedName !== undefined && extractedName !== null) {
|
||||
memberPublicName = extractedName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!memberPublicName) {
|
||||
memberPublicName = 'Unnamed Member';
|
||||
}
|
||||
|
||||
// Récupérer les emojis pour ce processId
|
||||
const emojis = await addressToEmoji(processId);
|
||||
|
||||
const option = document.createElement('option');
|
||||
option.value = processId;
|
||||
option.textContent = `${memberPublicName} (${emojis})`;
|
||||
memberSelect.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).populateMemberSelect = populateMemberSelect;
|
||||
|
||||
(window as any).scanDevice = scanDevice;
|
||||
|
@ -164,9 +164,10 @@ async function populateAutocompleteList(select: HTMLSelectElement, query: string
|
||||
let options_to_show = [];
|
||||
|
||||
const service = await Services.getInstance();
|
||||
const mineArray: string[] = await service.getMyProcesses();
|
||||
const allProcesses = await service.getProcesses();
|
||||
const allArray: string[] = Object.keys(allProcesses).filter(x => !mineArray.includes(x));
|
||||
const myProcesses = await service.getMyProcesses();
|
||||
const allProcesses = new Set(Object.keys(await service.getProcesses()));
|
||||
const mineArray = Array.from(myProcesses);
|
||||
const allArray = Array.from(allProcesses.difference(myProcesses));
|
||||
|
||||
const wrapper = select.parentNode;
|
||||
const input_search = wrapper?.querySelector('.search-container');
|
||||
@ -190,7 +191,7 @@ async function populateAutocompleteList(select: HTMLSelectElement, query: string
|
||||
mineArray.forEach(processId => addProcessToList(processId, true));
|
||||
allArray.forEach(processId => addProcessToList(processId, false));
|
||||
|
||||
if (mineArray.length === 0 && allArray.length === 0) {
|
||||
if (myProcesses.size === 0 && allProcesses.size === 0) {
|
||||
const li = document.createElement('li');
|
||||
li.classList.add('not-cursor');
|
||||
li.innerText = 'No options found';
|
||||
@ -479,7 +480,7 @@ async function createMessagingProcess(): Promise<void> {
|
||||
await service.handleApiReturn(createProcessReturn);
|
||||
const createPrdReturn = await service.createPrdUpdate(processId, stateId);
|
||||
await service.handleApiReturn(createPrdReturn);
|
||||
const approveChangeReturn = await service.approveChange(processId, stateId);
|
||||
const approveChangeReturn = service.approveChange(processId, stateId);
|
||||
await service.handleApiReturn(approveChangeReturn);
|
||||
}, 500)
|
||||
}
|
||||
|
@ -146,9 +146,14 @@ export async function init(): Promise<void> {
|
||||
}
|
||||
await services.restoreProcessesFromDB();
|
||||
await services.restoreSecretsFromDB();
|
||||
setTimeout(async () => {
|
||||
const myProcesses = await services.getMyProcesses();
|
||||
const db = await Database.getInstance();
|
||||
await db.updateMyProcesses(myProcesses);
|
||||
}, 200);
|
||||
|
||||
if (services.isPaired()) {
|
||||
await navigate('chat');
|
||||
await navigate('process');
|
||||
} else {
|
||||
const queryString = window.location.search;
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
|
@ -1,8 +0,0 @@
|
||||
const addResourcesToCache = async (resources) => {
|
||||
const cache = await caches.open('v1');
|
||||
await cache.addAll(resources);
|
||||
};
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(addResourcesToCache(['/', '/index.html', '/style.css', '/app.js', '/image-list.js', '/star-wars-logo.jpg', '/gallery/bountyHunters.jpg', '/gallery/myLittleVader.jpg', '/gallery/snowTroopers.jpg']));
|
||||
});
|
22
src/service-workers/cache.worker.ts
Normal file
22
src/service-workers/cache.worker.ts
Normal file
@ -0,0 +1,22 @@
|
||||
declare var self: ServiceWorkerGlobalScope;
|
||||
|
||||
const addResourcesToCache = async (resources: string[]): Promise<void> => {
|
||||
const cache = await caches.open('v1');
|
||||
await cache.addAll(resources);
|
||||
};
|
||||
|
||||
self.addEventListener('install', (event: ExtendableEvent) => {
|
||||
event.waitUntil(
|
||||
addResourcesToCache([
|
||||
'/',
|
||||
'/index.html',
|
||||
'/style.css',
|
||||
'/app.js',
|
||||
'/image-list.js',
|
||||
'/star-wars-logo.jpg',
|
||||
'/gallery/bountyHunters.jpg',
|
||||
'/gallery/myLittleVader.jpg',
|
||||
'/gallery/snowTroopers.jpg'
|
||||
])
|
||||
);
|
||||
});
|
@ -1,266 +0,0 @@
|
||||
const EMPTY32BYTES = String('').padStart(64, '0');
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(self.skipWaiting()); // Activate worker immediately
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(self.clients.claim()); // Become available to all pages
|
||||
});
|
||||
|
||||
// Event listener for messages from clients
|
||||
self.addEventListener('message', async (event) => {
|
||||
const data = event.data;
|
||||
console.log(data);
|
||||
|
||||
if (data.type === 'SCAN') {
|
||||
try {
|
||||
const myProcessesId = data.payload;
|
||||
if (myProcessesId && myProcessesId.length != 0) {
|
||||
const toDownload = await scanMissingData(myProcessesId);
|
||||
if (toDownload.length != 0) {
|
||||
console.log('Sending TO_DOWNLOAD message');
|
||||
event.source.postMessage({ type: 'TO_DOWNLOAD', data: toDownload});
|
||||
}
|
||||
} else {
|
||||
event.source.postMessage({ status: 'error', message: 'Empty lists' });
|
||||
}
|
||||
} catch (error) {
|
||||
event.source.postMessage({ status: 'error', message: error.message });
|
||||
}
|
||||
} else if (data.type === 'ADD_OBJECT') {
|
||||
try {
|
||||
const { storeName, object, key } = data.payload;
|
||||
const db = await openDatabase();
|
||||
const tx = db.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
|
||||
if (key) {
|
||||
await store.put(object, key);
|
||||
} else {
|
||||
await store.put(object);
|
||||
}
|
||||
|
||||
event.ports[0].postMessage({ status: 'success', message: '' });
|
||||
} catch (error) {
|
||||
event.ports[0].postMessage({ status: 'error', message: error.message });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function scanMissingData(processesToScan) {
|
||||
console.log('Scanning for missing data...');
|
||||
const myProcesses = await getProcesses(processesToScan);
|
||||
|
||||
let toDownload = new Set();
|
||||
// Iterate on each process
|
||||
if (myProcesses && myProcesses.length != 0) {
|
||||
for (const process of myProcesses) {
|
||||
// Iterate on states
|
||||
const firstState = process.states[0];
|
||||
const processId = firstState.commited_in;
|
||||
for (const state of process.states) {
|
||||
if (state.state_id === EMPTY32BYTES) continue;
|
||||
// iterate on pcd_commitment
|
||||
for (const [field, hash] of Object.entries(state.pcd_commitment)) {
|
||||
// Skip public fields
|
||||
if (state.public_data[field] !== undefined || field === 'roles') continue;
|
||||
// Check if we have the data in db
|
||||
const existingData = await getBlob(hash);
|
||||
if (!existingData) {
|
||||
toDownload.add(hash);
|
||||
// We also add an entry in diff, in case it doesn't already exist
|
||||
await addDiff(processId, state.state_id, hash, state.roles, field);
|
||||
} else {
|
||||
// We remove it if we have it in the set
|
||||
if (toDownload.delete(hash)) {
|
||||
console.log(`Removing ${hash} from the set`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(toDownload);
|
||||
return Array.from(toDownload);
|
||||
}
|
||||
|
||||
async function openDatabase() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open('4nk', 1);
|
||||
request.onerror = (event) => {
|
||||
reject(request.error);
|
||||
};
|
||||
request.onsuccess = (event) => {
|
||||
resolve(request.result);
|
||||
};
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = event.target.result;
|
||||
if (!db.objectStoreNames.contains('wallet')) {
|
||||
db.createObjectStore('wallet', { keyPath: 'pre_id' });
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get all processes because it is asynchronous
|
||||
async function getAllProcesses() {
|
||||
const db = await openDatabase();
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!db) {
|
||||
reject(new Error('Database is not available'));
|
||||
return;
|
||||
}
|
||||
const tx = db.transaction('processes', 'readonly');
|
||||
const store = tx.objectStore('processes');
|
||||
const request = store.getAll();
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result);
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
reject(request.error);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
async function getProcesses(processIds) {
|
||||
if (!processIds || processIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const db = await openDatabase();
|
||||
if (!db) {
|
||||
throw new Error('Database is not available');
|
||||
}
|
||||
|
||||
const tx = db.transaction('processes', 'readonly');
|
||||
const store = tx.objectStore('processes');
|
||||
|
||||
const requests = Array.from(processIds).map((processId) => {
|
||||
return new Promise((resolve) => {
|
||||
const request = store.get(processId);
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => {
|
||||
console.error(`Error fetching process ${processId}:`, request.error);
|
||||
resolve(undefined);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const results = await Promise.all(requests);
|
||||
return results.filter(result => result !== undefined);
|
||||
}
|
||||
|
||||
async function getAllDiffsNeedValidation() {
|
||||
const db = await openDatabase();
|
||||
|
||||
const allProcesses = await getAllProcesses();
|
||||
const tx = db.transaction('diffs', 'readonly');
|
||||
const store = tx.objectStore('diffs');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = store.getAll();
|
||||
request.onsuccess = (event) => {
|
||||
const allItems = event.target.result;
|
||||
const itemsWithFlag = allItems.filter((item) => item.need_validation);
|
||||
|
||||
const processMap = {};
|
||||
|
||||
for (const diff of itemsWithFlag) {
|
||||
const currentProcess = allProcesses.find((item) => {
|
||||
return item.states.some((state) => state.merkle_root === diff.new_state_merkle_root);
|
||||
});
|
||||
|
||||
if (currentProcess) {
|
||||
const processKey = currentProcess.merkle_root;
|
||||
|
||||
if (!processMap[processKey]) {
|
||||
processMap[processKey] = {
|
||||
process: currentProcess.states,
|
||||
processId: currentProcess.key,
|
||||
diffs: [],
|
||||
};
|
||||
}
|
||||
processMap[processKey].diffs.push(diff);
|
||||
}
|
||||
}
|
||||
|
||||
const results = Object.values(processMap).map((entry) => {
|
||||
const diffs = []
|
||||
for(const state of entry.process) {
|
||||
const filteredDiff = entry.diffs.filter(diff => diff.new_state_merkle_root === state.merkle_root);
|
||||
if(filteredDiff && filteredDiff.length) {
|
||||
diffs.push(filteredDiff)
|
||||
}
|
||||
}
|
||||
return {
|
||||
process: entry.process,
|
||||
processId: entry.processId,
|
||||
diffs: diffs,
|
||||
};
|
||||
});
|
||||
|
||||
resolve(results);
|
||||
};
|
||||
|
||||
request.onerror = (event) => {
|
||||
reject(event.target.error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function getBlob(hash) {
|
||||
const db = await openDatabase();
|
||||
const storeName = 'data';
|
||||
const tx = db.transaction(storeName, 'readonly');
|
||||
const store = tx.objectStore(storeName);
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
const getRequest = store.get(hash);
|
||||
getRequest.onsuccess = () => resolve(getRequest.result);
|
||||
getRequest.onerror = () => reject(getRequest.error);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
async function addDiff(processId, stateId, hash, roles, field) {
|
||||
const db = await openDatabase();
|
||||
const storeName = 'diffs';
|
||||
const tx = db.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
|
||||
// Check if the diff already exists
|
||||
const existingDiff = await new Promise((resolve, reject) => {
|
||||
const getRequest = store.get(hash);
|
||||
getRequest.onsuccess = () => resolve(getRequest.result);
|
||||
getRequest.onerror = () => reject(getRequest.error);
|
||||
});
|
||||
|
||||
if (!existingDiff) {
|
||||
const newDiff = {
|
||||
process_id: processId,
|
||||
state_id: stateId,
|
||||
value_commitment: hash,
|
||||
roles: roles,
|
||||
field: field,
|
||||
description: null,
|
||||
previous_value: null,
|
||||
new_value: null,
|
||||
notify_user: false,
|
||||
need_validation: false,
|
||||
validation_status: 'None'
|
||||
};
|
||||
|
||||
const insertResult = await new Promise((resolve, reject) => {
|
||||
const putRequest = store.put(newDiff);
|
||||
putRequest.onsuccess = () => resolve(putRequest.result);
|
||||
putRequest.onerror = () => reject(putRequest.error);
|
||||
});
|
||||
|
||||
return insertResult;
|
||||
}
|
||||
|
||||
return existingDiff;
|
||||
}
|
331
src/service-workers/database.worker.ts
Executable file
331
src/service-workers/database.worker.ts
Executable file
@ -0,0 +1,331 @@
|
||||
declare var self: ServiceWorkerGlobalScope;
|
||||
|
||||
// Interfaces
|
||||
interface Process {
|
||||
states: State[];
|
||||
merkle_root: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
state_id: string;
|
||||
commited_in: string;
|
||||
merkle_root: string;
|
||||
pcd_commitment?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface Diff {
|
||||
process_id: string;
|
||||
state_id: string;
|
||||
value_commitment: string;
|
||||
field: string;
|
||||
description: string | null;
|
||||
previous_value: any;
|
||||
new_value: any;
|
||||
notify_user: boolean;
|
||||
need_validation: boolean;
|
||||
validation_status: string;
|
||||
}
|
||||
|
||||
interface ProcessWithDiffs {
|
||||
process: State[];
|
||||
processId: string;
|
||||
diffs: Diff[][];
|
||||
}
|
||||
|
||||
interface MessagePayload {
|
||||
myProcessesId?: string[];
|
||||
storeName?: string;
|
||||
object?: any;
|
||||
key?: IDBValidKey;
|
||||
}
|
||||
|
||||
interface MessageEvent {
|
||||
type: string;
|
||||
payload?: MessagePayload;
|
||||
}
|
||||
|
||||
let processesToScan: Set<string> = new Set();
|
||||
let toDownload: Set<string> = new Set();
|
||||
|
||||
self.addEventListener('install', (event: ExtendableEvent) => {
|
||||
event.waitUntil(self.skipWaiting());
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event: ExtendableEvent) => {
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
self.addEventListener('message', async (event: ExtendableMessageEvent) => {
|
||||
const data = event.data as MessageEvent;
|
||||
|
||||
if (data.type === 'START') {
|
||||
processesToScan.clear();
|
||||
const fetchNotifications = async (): Promise<void> => {
|
||||
const itemsWithFlag = await getAllDiffsNeedValidation();
|
||||
|
||||
itemsWithFlag?.forEach((item) => {
|
||||
console.log(item);
|
||||
});
|
||||
|
||||
event.ports[0].postMessage({
|
||||
type: 'NOTIFICATIONS',
|
||||
data: itemsWithFlag,
|
||||
});
|
||||
};
|
||||
|
||||
const scanMissingData = async (): Promise<void> => {
|
||||
console.log('Scanning for missing data...');
|
||||
const myProcesses = await getProcesses(processesToScan);
|
||||
|
||||
if (myProcesses && myProcesses.length !== 0) {
|
||||
for (const process of myProcesses) {
|
||||
const firstState = process.states[0];
|
||||
const processId = firstState.commited_in;
|
||||
for (const state of process.states) {
|
||||
if (!state.pcd_commitment) continue;
|
||||
for (const [field, hash] of Object.entries(state.pcd_commitment)) {
|
||||
const existingData = await getBlob(hash);
|
||||
if (!existingData) {
|
||||
toDownload.add(hash);
|
||||
await addDiff(processId, state.state_id, hash, field);
|
||||
} else {
|
||||
if (toDownload.delete(hash)) {
|
||||
console.log(`Removing ${hash} from the set`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toDownload.size !== 0) {
|
||||
event.ports[0].postMessage({
|
||||
type: 'TO_DOWNLOAD',
|
||||
data: Array.from(toDownload),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
fetchNotifications();
|
||||
setInterval(fetchNotifications, 2 * 60 * 1000);
|
||||
scanMissingData();
|
||||
setInterval(scanMissingData, 10 * 1000);
|
||||
}
|
||||
|
||||
if (data.type === 'UPDATE_PROCESSES') {
|
||||
try {
|
||||
const { myProcessesId } = data.payload || {};
|
||||
console.log(myProcessesId);
|
||||
if (myProcessesId && myProcessesId.length !== 0) {
|
||||
for (const processId of myProcessesId) {
|
||||
processesToScan.add(processId);
|
||||
}
|
||||
console.log(processesToScan);
|
||||
} else {
|
||||
event.ports[0].postMessage({ status: 'error', message: 'Empty lists' });
|
||||
}
|
||||
} catch (error) {
|
||||
event.ports[0].postMessage({ status: 'error', message: (error as Error).message });
|
||||
}
|
||||
}
|
||||
|
||||
if (data.type === 'ADD_OBJECT') {
|
||||
try {
|
||||
const { storeName, object, key } = data.payload || {};
|
||||
if (!storeName) throw new Error('Store name is required');
|
||||
|
||||
const db = await openDatabase();
|
||||
const tx = db.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
|
||||
if (key) {
|
||||
await store.put(object, key);
|
||||
} else {
|
||||
await store.put(object);
|
||||
}
|
||||
|
||||
event.ports[0].postMessage({ status: 'success', message: '' });
|
||||
} catch (error) {
|
||||
event.ports[0].postMessage({ status: 'error', message: (error as Error).message });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function openDatabase(): Promise<IDBDatabase> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open('4nk', 1);
|
||||
|
||||
request.onerror = () => {
|
||||
reject(request.error);
|
||||
};
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result);
|
||||
};
|
||||
|
||||
request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result;
|
||||
if (!db.objectStoreNames.contains('wallet')) {
|
||||
db.createObjectStore('wallet', { keyPath: 'pre_id' });
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function getAllProcesses(): Promise<Process[]> {
|
||||
const db = await openDatabase();
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!db) {
|
||||
reject(new Error('Database is not available'));
|
||||
return;
|
||||
}
|
||||
const tx = db.transaction('processes', 'readonly');
|
||||
const store = tx.objectStore('processes');
|
||||
const request = store.getAll();
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result);
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
reject(request.error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function getProcesses(processIds: Set<string>): Promise<Process[]> {
|
||||
if (!processIds || processIds.size === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const db = await openDatabase();
|
||||
if (!db) {
|
||||
throw new Error('Database is not available');
|
||||
}
|
||||
|
||||
const tx = db.transaction('processes', 'readonly');
|
||||
const store = tx.objectStore('processes');
|
||||
|
||||
const requests = Array.from(processIds).map((processId) => {
|
||||
return new Promise<Process | undefined>((resolve) => {
|
||||
const request = store.get(processId);
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => {
|
||||
console.error(`Error fetching process ${processId}:`, request.error);
|
||||
resolve(undefined);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const results = await Promise.all(requests);
|
||||
return results.filter((result): result is Process => result !== undefined);
|
||||
}
|
||||
|
||||
async function getAllDiffsNeedValidation(): Promise<ProcessWithDiffs[]> {
|
||||
const db = await openDatabase();
|
||||
const allProcesses = await getAllProcesses();
|
||||
const tx = db.transaction('diffs', 'readonly');
|
||||
const store = tx.objectStore('diffs');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = store.getAll();
|
||||
request.onsuccess = (event: Event) => {
|
||||
const allItems = (event.target as IDBRequest).result as Diff[];
|
||||
const itemsWithFlag = allItems.filter((item) => item.need_validation);
|
||||
|
||||
const processMap: { [key: string]: { process: State[]; processId: string; diffs: Diff[]; } } = {};
|
||||
|
||||
for (const diff of itemsWithFlag) {
|
||||
const currentProcess = allProcesses.find((item) => {
|
||||
return item.states.some((state) => state.merkle_root === diff.new_state_merkle_root);
|
||||
});
|
||||
|
||||
if (currentProcess) {
|
||||
const processKey = currentProcess.merkle_root;
|
||||
|
||||
if (!processMap[processKey]) {
|
||||
processMap[processKey] = {
|
||||
process: currentProcess.states,
|
||||
processId: currentProcess.key,
|
||||
diffs: [],
|
||||
};
|
||||
}
|
||||
processMap[processKey].diffs.push(diff);
|
||||
}
|
||||
}
|
||||
|
||||
const results = Object.values(processMap).map((entry) => {
|
||||
const diffs: Diff[][] = [];
|
||||
for(const state of entry.process) {
|
||||
const filteredDiff = entry.diffs.filter(diff => diff.new_state_merkle_root === state.merkle_root);
|
||||
if(filteredDiff && filteredDiff.length) {
|
||||
diffs.push(filteredDiff);
|
||||
}
|
||||
}
|
||||
return {
|
||||
process: entry.process,
|
||||
processId: entry.processId,
|
||||
diffs: diffs,
|
||||
};
|
||||
});
|
||||
|
||||
resolve(results);
|
||||
};
|
||||
|
||||
request.onerror = (event: Event) => {
|
||||
reject((event.target as IDBRequest).error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function getBlob(hash: string): Promise<any> {
|
||||
const db = await openDatabase();
|
||||
const storeName = 'data';
|
||||
const tx = db.transaction(storeName, 'readonly');
|
||||
const store = tx.objectStore(storeName);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const getRequest = store.get(hash);
|
||||
getRequest.onsuccess = () => resolve(getRequest.result);
|
||||
getRequest.onerror = () => reject(getRequest.error);
|
||||
});
|
||||
}
|
||||
|
||||
async function addDiff(processId: string, stateId: string, hash: string, field: string): Promise<IDBValidKey> {
|
||||
const db = await openDatabase();
|
||||
const storeName = 'diffs';
|
||||
const tx = db.transaction(storeName, 'readwrite');
|
||||
const store = tx.objectStore(storeName);
|
||||
|
||||
const existingDiff = await new Promise<Diff | undefined>((resolve, reject) => {
|
||||
const getRequest = store.get(hash);
|
||||
getRequest.onsuccess = () => resolve(getRequest.result);
|
||||
getRequest.onerror = () => reject(getRequest.error);
|
||||
});
|
||||
|
||||
if (!existingDiff) {
|
||||
const newDiff: Diff = {
|
||||
process_id: processId,
|
||||
state_id: stateId,
|
||||
value_commitment: hash,
|
||||
field: field,
|
||||
description: null,
|
||||
previous_value: null,
|
||||
new_value: null,
|
||||
notify_user: false,
|
||||
need_validation: false,
|
||||
validation_status: 'None'
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const putRequest = store.put(newDiff);
|
||||
putRequest.onsuccess = () => resolve(putRequest.result);
|
||||
putRequest.onerror = () => reject(putRequest.error);
|
||||
});
|
||||
}
|
||||
|
||||
return existingDiff;
|
||||
}
|
@ -8,7 +8,6 @@ export class Database {
|
||||
private serviceWorkerRegistration: ServiceWorkerRegistration | null = null;
|
||||
private messageChannel: MessageChannel | null = null;
|
||||
private messageChannelForGet: MessageChannel | null = null;
|
||||
private serviceWorkerCheckIntervalId: number | null = null;
|
||||
private storeDefinitions = {
|
||||
AnkLabels: {
|
||||
name: 'labels',
|
||||
@ -82,10 +81,12 @@ export class Database {
|
||||
});
|
||||
};
|
||||
|
||||
request.onsuccess = async () => {
|
||||
this.db = request.result;
|
||||
await this.initServiceWorker();
|
||||
resolve();
|
||||
request.onsuccess = () => {
|
||||
setTimeout(() => {
|
||||
this.db = request.result;
|
||||
this.initServiceWorker();
|
||||
resolve();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
@ -114,45 +115,43 @@ export class Database {
|
||||
if (!('serviceWorker' in navigator)) return; // Ensure service workers are supported
|
||||
|
||||
try {
|
||||
// Get existing service worker registrations
|
||||
const registrations = await navigator.serviceWorker.getRegistrations();
|
||||
if (registrations.length === 0) {
|
||||
// No existing workers: register a new one.
|
||||
this.serviceWorkerRegistration = await navigator.serviceWorker.register('/src/service-workers/database.worker.js', { type: 'module' });
|
||||
console.log('Service Worker registered with scope:', this.serviceWorkerRegistration.scope);
|
||||
} else if (registrations.length === 1) {
|
||||
// One existing worker: update it (restart it) without unregistering.
|
||||
this.serviceWorkerRegistration = registrations[0];
|
||||
await this.serviceWorkerRegistration.update();
|
||||
console.log('Service Worker updated');
|
||||
} else {
|
||||
// More than one existing worker: unregister them all and register a new one.
|
||||
console.log('Multiple Service Worker(s) detected. Unregistering all...');
|
||||
await Promise.all(registrations.map(reg => reg.unregister()));
|
||||
console.log('All previous Service Workers unregistered.');
|
||||
this.serviceWorkerRegistration = await navigator.serviceWorker.register('/src/service-workers/database.worker.js', { type: 'module' });
|
||||
console.log('Service Worker registered with scope:', this.serviceWorkerRegistration.scope);
|
||||
}
|
||||
|
||||
await this.checkForUpdates();
|
||||
|
||||
// Set up a global message listener for responses from the service worker.
|
||||
navigator.serviceWorker.addEventListener('message', async (event) => {
|
||||
console.log('Received message from service worker:', event.data);
|
||||
await this.handleServiceWorkerMessage(event.data);
|
||||
});
|
||||
|
||||
// Set up a periodic check to ensure the service worker is active and to send a SYNC message.
|
||||
this.serviceWorkerCheckIntervalId = window.setInterval(async () => {
|
||||
const activeWorker = this.serviceWorkerRegistration.active || (await this.waitForServiceWorkerActivation(this.serviceWorkerRegistration));
|
||||
const service = await Services.getInstance();
|
||||
const payload = await service.getMyProcesses();
|
||||
if (payload.length != 0) {
|
||||
activeWorker?.postMessage({ type: 'SCAN', payload });
|
||||
// Get existing service worker registrations
|
||||
const registrations = await navigator.serviceWorker.getRegistrations();
|
||||
if (registrations.length === 0) {
|
||||
// No existing workers: register a new one.
|
||||
this.serviceWorkerRegistration = await navigator.serviceWorker.register('/src/service-workers/database.worker.ts', { type: 'module' });
|
||||
console.log('Service Worker registered with scope:', registration.scope);
|
||||
} else if (registrations.length === 1) {
|
||||
// One existing worker: update it (restart it) without unregistering.
|
||||
this.serviceWorkerRegistration = registrations[0];
|
||||
await this.serviceWorkerRegistration.update();
|
||||
console.log('Service worker updated');
|
||||
} else {
|
||||
// More than one existing worker: unregister them all and register a new one.
|
||||
console.log('Multiple Service Worker(s) detected. Unregistering all...');
|
||||
await Promise.all(registrations.map(reg => reg.unregister()));
|
||||
console.log('All previous Service Workers unregistered.');
|
||||
this.serviceWorkerRegistration = await navigator.serviceWorker.register('/src/service-workers/database.worker.ts', { type: 'module' });
|
||||
console.log('Service Worker registered with scope:', registration.scope);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
await this.checkForUpdates();
|
||||
|
||||
// Set up the message channels
|
||||
this.messageChannel = new MessageChannel();
|
||||
this.messageChannelForGet = new MessageChannel();
|
||||
this.messageChannel.port1.onmessage = this.handleAddObjectResponse;
|
||||
this.messageChannelForGet.port1.onmessage = this.handleGetObjectResponse;
|
||||
|
||||
// Ensure the new service worker is activated before sending messages
|
||||
const activeWorker = this.serviceWorkerRegistration.active || (await this.waitForServiceWorkerActivation(this.serviceWorkerRegistration));
|
||||
|
||||
activeWorker?.postMessage(
|
||||
{ type: 'START' },
|
||||
[this.messageChannel.port2],
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Service Worker registration failed:', error);
|
||||
console.error('Service Worker registration failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,58 +188,6 @@ export class Database {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleServiceWorkerMessage(message: any) {
|
||||
switch (message.type) {
|
||||
case 'TO_DOWNLOAD':
|
||||
await this.handleDownloadList(message.data);
|
||||
break;
|
||||
default:
|
||||
console.warn('Unknown message type received from service worker:', message);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleDownloadList(downloadList: string[]): void {
|
||||
// Download the missing data
|
||||
let requestedStateId = [];
|
||||
const service = await Services.getInstance();
|
||||
for (const hash of downloadList) {
|
||||
const diff = await service.getDiffByValue(hash);
|
||||
if (!diff) {
|
||||
// This should never happen
|
||||
console.warn(`Missing a diff for hash ${hash}`);
|
||||
continue;
|
||||
}
|
||||
const processId = diff.process_id;
|
||||
const stateId = diff.state_id;
|
||||
const roles = diff.roles;
|
||||
try {
|
||||
const valueBytes = await service.fetchValueFromStorage(hash);
|
||||
if (valueBytes) {
|
||||
// Save data to db
|
||||
const blob = new Blob([valueBytes], {type: "application/octet-stream"});
|
||||
await service.saveBlobToDb(hash, blob);
|
||||
document.dispatchEvent(new CustomEvent('newDataReceived', {
|
||||
detail: {
|
||||
processId,
|
||||
stateId,
|
||||
hash,
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
// We first request the data from managers
|
||||
console.log('Request data from managers of the process');
|
||||
// get the diff from db
|
||||
if (!requestedStateId.includes(stateId)) {
|
||||
await service.requestDataFromPeers(processId, [stateId], [roles]);
|
||||
requestedStateId.push(stateId);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleAddObjectResponse = async (event: MessageEvent) => {
|
||||
const data = event.data;
|
||||
console.log('Received response from service worker (ADD_OBJECT):', data);
|
||||
@ -248,7 +195,6 @@ export class Database {
|
||||
if (data.type === 'NOTIFICATIONS') {
|
||||
service.setNotifications(data.data);
|
||||
} else if (data.type === 'TO_DOWNLOAD') {
|
||||
console.log(`Received missing data ${data}`);
|
||||
// Download the missing data
|
||||
let requestedStateId = [];
|
||||
for (const hash of data.data) {
|
||||
@ -265,9 +211,8 @@ export class Database {
|
||||
const diff = await service.getDiffByValue(hash);
|
||||
const processId = diff.process_id;
|
||||
const stateId = diff.state_id;
|
||||
const roles = diff.roles;
|
||||
if (!requestedStateId.includes(stateId)) {
|
||||
await service.requestDataFromPeers(processId, [stateId], [roles]);
|
||||
await service.requestDataFromPeers(processId, stateId);
|
||||
requestedStateId.push(stateId);
|
||||
}
|
||||
}
|
||||
@ -275,6 +220,8 @@ export class Database {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
// try to update list of my processes
|
||||
await service.getMyProcesses();
|
||||
}
|
||||
};
|
||||
|
||||
@ -320,6 +267,49 @@ export class Database {
|
||||
});
|
||||
}
|
||||
|
||||
public updateMyProcesses(myProcessesId: string[]): Promise<void> {
|
||||
if (myProcessesId.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!this.serviceWorkerRegistration) {
|
||||
// console.warn('Service worker registration is not ready. Waiting...');
|
||||
this.serviceWorkerRegistration = await navigator.serviceWorker.ready;
|
||||
}
|
||||
|
||||
const activeWorker = await this.waitForServiceWorkerActivation(this.serviceWorkerRegistration);
|
||||
// Create a message channel for communication
|
||||
const messageChannel = new MessageChannel();
|
||||
|
||||
// Handle the response from the service worker
|
||||
messageChannel.port1.onmessage = (event) => {
|
||||
if (event.data.status === 'success') {
|
||||
resolve();
|
||||
} else {
|
||||
const error = event.data.message;
|
||||
reject(new Error(error || 'Unknown error occurred while scanning our processes'));
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const payload = { myProcessesId };
|
||||
console.log('Sending UPDATE_PROCESSES msg with payload', payload);
|
||||
activeWorker?.postMessage(
|
||||
{
|
||||
type: 'UPDATE_PROCESSES',
|
||||
|
||||
payload,
|
||||
},
|
||||
[messageChannel.port2],
|
||||
|
||||
);
|
||||
} catch (error) {
|
||||
reject(new Error(`Failed to send message to service worker: ${error}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async getObject(storeName: string, key: string): Promise<any | null> {
|
||||
const db = await this.getDb();
|
||||
const tx = db.transaction(storeName, 'readonly');
|
||||
|
@ -14,7 +14,6 @@ export default class ModalService {
|
||||
private processId: string | null = null;
|
||||
private constructor() {}
|
||||
private paired_addresses: string[] = [];
|
||||
private modal: HTMLElement | null = null;
|
||||
|
||||
// Method to access the singleton instance of Services
|
||||
public static async getInstance(): Promise<ModalService> {
|
||||
@ -55,21 +54,6 @@ export default class ModalService {
|
||||
}
|
||||
}
|
||||
|
||||
async injectCreationModal(members: any[]) {
|
||||
const container = document.querySelector('#containerId');
|
||||
if (container) {
|
||||
let html = await fetch('/src/components/modal/creation-modal.html').then((res) => res.text());
|
||||
html = html.replace('{{device1}}', await addressToEmoji(members[0]['sp_addresses'][0]));
|
||||
container.innerHTML += html;
|
||||
|
||||
// Dynamically load the header JS
|
||||
const script = document.createElement('script');
|
||||
script.src = '/src/components/modal/confirmation-modal.ts';
|
||||
script.type = 'module';
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
}
|
||||
|
||||
// Device 1 wait Device 2
|
||||
async injectWaitingModal() {
|
||||
const container = document.querySelector('#containerId');
|
||||
@ -122,37 +106,32 @@ export default class ModalService {
|
||||
throw new Error('Must have exactly 1 member');
|
||||
}
|
||||
|
||||
console.log("MEMBERS:", members);
|
||||
// We take all the addresses except our own
|
||||
const service = await Services.getInstance();
|
||||
const localAddress = await service.getDeviceAddress();
|
||||
for (const member of members) {
|
||||
if (member.sp_addresses) {
|
||||
for (const address of member.sp_addresses) {
|
||||
if (address !== localAddress) {
|
||||
this.paired_addresses.push(address);
|
||||
}
|
||||
for (const address of member['sp_addresses']) {
|
||||
if (address !== localAddress) {
|
||||
this.paired_addresses.push(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.processId = processId;
|
||||
this.stateId = stateId;
|
||||
await this.injectModal(members);
|
||||
const modal = document.getElementById('modal');
|
||||
if (modal) modal.style.display = 'flex';
|
||||
// const newScript = document.createElement('script');
|
||||
// newScript.setAttribute('type', 'module');
|
||||
// newScript.textContent = confirmationModalScript;
|
||||
// document.head.appendChild(newScript).parentNode?.removeChild(newScript);
|
||||
|
||||
if (members[0].sp_addresses.length === 1) {
|
||||
await this.injectCreationModal(members);
|
||||
this.modal = document.getElementById('creation-modal');
|
||||
console.log("LENGTH:", members[0].sp_addresses.length);
|
||||
} else {
|
||||
await this.injectModal(members);
|
||||
this.modal = document.getElementById('modal');
|
||||
console.log("LENGTH:", members[0].sp_addresses.length);
|
||||
}
|
||||
|
||||
if (this.modal) this.modal.style.display = 'flex';
|
||||
// Add correct text
|
||||
|
||||
// Close modal when clicking outside of it
|
||||
window.onclick = (event) => {
|
||||
if (event.target === this.modal) {
|
||||
const modal = document.getElementById('modal');
|
||||
if (event.target === modal) {
|
||||
this.closeConfirmationModal();
|
||||
}
|
||||
};
|
||||
@ -161,12 +140,14 @@ export default class ModalService {
|
||||
console.log('=============> Confirm Login');
|
||||
}
|
||||
async closeLoginModal() {
|
||||
if (this.modal) this.modal.style.display = 'none';
|
||||
const modal = document.getElementById('login-modal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
|
||||
async confirmPairing() {
|
||||
const service = await Services.getInstance();
|
||||
if (this.modal) this.modal.style.display = 'none';
|
||||
const modal = document.getElementById('modal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
|
||||
if (service.device1) {
|
||||
console.log("Device 1 detected");
|
||||
@ -185,7 +166,7 @@ export default class ModalService {
|
||||
|
||||
// We send confirmation that we validate the change
|
||||
try {
|
||||
const approveChangeReturn = await service.approveChange(this.processId!, this.stateId!);
|
||||
const approveChangeReturn = service.approveChange(this.processId!, this.stateId!);
|
||||
await service.handleApiReturn(approveChangeReturn);
|
||||
|
||||
await this.injectWaitingModal();
|
||||
@ -205,7 +186,7 @@ export default class ModalService {
|
||||
const newDevice = service.dumpDeviceFromMemory();
|
||||
console.log(newDevice);
|
||||
await service.saveDeviceInDatabase(newDevice);
|
||||
navigate('chat');
|
||||
navigate('process');
|
||||
service.resetState();
|
||||
|
||||
} catch (e) {
|
||||
@ -234,7 +215,7 @@ export default class ModalService {
|
||||
|
||||
// We send confirmation that we validate the change
|
||||
try {
|
||||
const approveChangeReturn = await service.approveChange(this.processId!, this.stateId!);
|
||||
const approveChangeReturn = service.approveChange(this.processId!, this.stateId!);
|
||||
await service.handleApiReturn(approveChangeReturn);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
@ -248,13 +229,14 @@ export default class ModalService {
|
||||
const newDevice = service.dumpDeviceFromMemory();
|
||||
console.log(newDevice);
|
||||
await service.saveDeviceInDatabase(newDevice);
|
||||
navigate('chat');
|
||||
navigate('process');
|
||||
}
|
||||
}
|
||||
|
||||
async closeConfirmationModal() {
|
||||
const service = await Services.getInstance();
|
||||
await service.unpairDevice();
|
||||
if (this.modal) this.modal.style.display = 'none';
|
||||
const modal = document.getElementById('modal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
@ -15,14 +15,17 @@ const BASEURL = `https://demo.4nkweb.com`;
|
||||
const BOOTSTRAPURL = [`${BASEURL}/ws/`];
|
||||
const STORAGEURL = `${BASEURL}/storage`
|
||||
const DEFAULTAMOUNT = 1000n;
|
||||
const EMPTY32BYTES = String('').padStart(64, '0');
|
||||
|
||||
export default class Services {
|
||||
private static initializing: Promise<Services> | null = null;
|
||||
private static instance: Services;
|
||||
private currentProcess: string | null = null;
|
||||
private pendingUpdates: any | null = null;
|
||||
private currentUpdateMerkleRoot: string | null = null;
|
||||
private localAddress: string | null = null;
|
||||
private pairedAddresses: string[] = [];
|
||||
private sdkClient: any;
|
||||
// private processes: IProcess[] | null = null;
|
||||
private myProcesses: Set = new Set();
|
||||
private notifications: any[] | null = null;
|
||||
private subscriptions: { element: Element; event: string; eventHandler: string }[] = [];
|
||||
@ -238,7 +241,64 @@ export default class Services {
|
||||
throw new Error('Amount is still 0 after 3 attempts');
|
||||
}
|
||||
|
||||
public async createPairingProcess(userName: string, pairWith: string[], relayAddress: string, feeRate: number): Promise<ApiReturn> {
|
||||
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();
|
||||
if (!me) {
|
||||
throw new Error('No paired member in device');
|
||||
}
|
||||
const allMembers: Member[] = otherMembers;
|
||||
const meAndOne = [{ sp_addresses: me }, otherMembers.pop()!];
|
||||
allMembers.push({ sp_addresses: me });
|
||||
const everyOneElse = otherMembers;
|
||||
const messagingTemplate = {
|
||||
description: 'messaging',
|
||||
roles: {
|
||||
public: {
|
||||
members: allMembers,
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.0,
|
||||
fields: ['description', 'roles'],
|
||||
min_sig_member: 0.0,
|
||||
},
|
||||
],
|
||||
storages: [STORAGEURL]
|
||||
},
|
||||
owner: {
|
||||
members: meAndOne,
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 1.0,
|
||||
fields: ['description', 'roles'],
|
||||
min_sig_member: 1.0,
|
||||
},
|
||||
],
|
||||
storages: [STORAGEURL]
|
||||
},
|
||||
users: {
|
||||
members: everyOneElse,
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.0,
|
||||
fields: ['description', 'roles'],
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async createPairingProcess(pairWith: string[], relayAddress: string, feeRate: number): Promise<ApiReturn> {
|
||||
if (this.sdkClient.is_paired()) {
|
||||
throw new Error('Device already paired');
|
||||
}
|
||||
@ -250,7 +310,7 @@ export default class Services {
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 1.0,
|
||||
fields: ['description', 'counter', 'roles', 'memberPublicName'],
|
||||
fields: ['description', 'counter'],
|
||||
min_sig_member: 1.0,
|
||||
},
|
||||
],
|
||||
@ -261,14 +321,11 @@ export default class Services {
|
||||
description: 'pairing',
|
||||
counter: 0,
|
||||
};
|
||||
const publicData = {
|
||||
memberPublicName: userName
|
||||
}
|
||||
try {
|
||||
return this.sdkClient.create_new_process(
|
||||
pairingTemplate,
|
||||
roles,
|
||||
publicData,
|
||||
JSON.stringify(pairingTemplate),
|
||||
JSON.stringify(roles),
|
||||
null,
|
||||
relayAddress,
|
||||
feeRate
|
||||
);
|
||||
@ -299,19 +356,6 @@ export default class Services {
|
||||
}
|
||||
|
||||
const roles = {
|
||||
demiurge: {
|
||||
members: [
|
||||
{ sp_addresses: myAddresses },
|
||||
],
|
||||
validation_rules: [
|
||||
{
|
||||
quorum: 0.01,
|
||||
fields: ['message', 'description', 'roles'],
|
||||
min_sig_member: 0.01,
|
||||
},
|
||||
],
|
||||
storages: [STORAGEURL]
|
||||
}
|
||||
dm: {
|
||||
members: [
|
||||
{ sp_addresses: myAddresses },
|
||||
@ -337,14 +381,14 @@ export default class Services {
|
||||
|
||||
const relayAddress = this.getAllRelays()[0]['spAddress'];
|
||||
const feeRate = 1;
|
||||
const publicData = {};
|
||||
const initState = JSON.stringify(dmTemplate);
|
||||
|
||||
await this.checkConnections ([{ sp_addresses: otherMember }]);
|
||||
|
||||
const result = this.sdkClient.create_new_process (
|
||||
dmTemplate,
|
||||
roles,
|
||||
publicData,
|
||||
initState,
|
||||
JSON.stringify(roles),
|
||||
null,
|
||||
relayAddress,
|
||||
feeRate
|
||||
);
|
||||
@ -360,7 +404,7 @@ export default class Services {
|
||||
public async updateProcess(process: Process, new_state: any, roles: Record<string, RoleDefinition> | null): Promise<ApiReturn> {
|
||||
// If roles is null, we just take the last commited state roles
|
||||
if (!roles) {
|
||||
roles = this.getRoles(process);
|
||||
roles = await this.getRoles(process);
|
||||
} else {
|
||||
// We should check that we have the right to change the roles here, or maybe it's better leave it to the wasm
|
||||
console.log('Provided new roles:', JSON.stringify(roles));
|
||||
@ -376,7 +420,7 @@ export default class Services {
|
||||
await this.checkConnections([...members]);
|
||||
try {
|
||||
console.log(process);
|
||||
return this.sdkClient.update_process(process, new_state, roles, {});
|
||||
return this.sdkClient.update_process(process, new_state, roles);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to update process: ${e}`);
|
||||
}
|
||||
@ -403,25 +447,21 @@ export default class Services {
|
||||
}
|
||||
}
|
||||
|
||||
public async approveChange(processId: string, stateId: string): Promise<ApiReturn> {
|
||||
const process = await this.getProcess(processId);
|
||||
if (!process) {
|
||||
throw new Error('Failed to get process from db');
|
||||
}
|
||||
public approveChange(processId: string, stateId: string): ApiReturn {
|
||||
try {
|
||||
return this.sdkClient.validate_state(process, stateId);
|
||||
return this.sdkClient.validate_state(processId, stateId);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to create prd response: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async rejectChange(processId: string, stateId: string): Promise<ApiReturn> {
|
||||
const process = await this.getProcess(processId);
|
||||
if (!process) {
|
||||
throw new Error('Failed to get process from db');
|
||||
public rejectChange(): ApiReturn {
|
||||
if (!this.currentProcess || !this.currentUpdateMerkleRoot) {
|
||||
throw new Error('No current process and/or current update defined');
|
||||
}
|
||||
|
||||
try {
|
||||
return this.sdkClient.refuse_state(process, stateId);
|
||||
return this.sdkClient.refuse_state(this.currentProcess, this.currentUpdateMerkleRoot);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to create prd response: ${e}`);
|
||||
}
|
||||
@ -494,6 +534,16 @@ export default class Services {
|
||||
}
|
||||
}
|
||||
|
||||
public async updateProcessesWorker() {
|
||||
try {
|
||||
const myProcesses = await this.getMyProcesses();
|
||||
const db = await Database.getInstance();
|
||||
await db.updateMyProcesses(myProcesses);
|
||||
} catch (e) {
|
||||
console.error('Failed to update processes worker:', e);
|
||||
}
|
||||
}
|
||||
|
||||
public async handleApiReturn(apiReturn: ApiReturn) {
|
||||
console.log(apiReturn);
|
||||
if (apiReturn.new_tx_to_send && apiReturn.new_tx_to_send.transaction.length != 0) {
|
||||
@ -551,6 +601,14 @@ export default class Services {
|
||||
// Save process to db
|
||||
await this.saveProcessToDb(processId, updatedProcess.current_process);
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await this.updateProcessesWorker();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}, 0)
|
||||
|
||||
const isPaired = this.isPaired();
|
||||
|
||||
if (updatedProcess.diffs && updatedProcess.diffs.length != 0) {
|
||||
@ -623,15 +681,6 @@ export default class Services {
|
||||
}
|
||||
}
|
||||
|
||||
public dumpNeuteredDevice(): Device | null {
|
||||
try {
|
||||
return this.sdkClient.dump_neutered_device();
|
||||
} catch (e) {
|
||||
console.error(`Failed to dump device: ${e}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public getPairingProcessId(): string {
|
||||
try {
|
||||
return this.sdkClient.get_pairing_process_id();
|
||||
@ -700,27 +749,24 @@ export default class Services {
|
||||
return true;
|
||||
}
|
||||
|
||||
rolesContainsUs(roles: Record<string, RoleDefinition>): boolean {
|
||||
let us;
|
||||
rolesContainsUs(roles: any): boolean {
|
||||
try {
|
||||
us = this.sdkClient.get_member();
|
||||
this.sdkClient.roles_contains_us(JSON.stringify(roles));
|
||||
} catch (e) {
|
||||
throw e;
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.rolesContainsMember(roles, us.sp_addresses);
|
||||
return true;
|
||||
}
|
||||
|
||||
rolesContainsMember(roles: Record<string, RoleDefinition>, member: string[]): boolean {
|
||||
let res = false;
|
||||
for (const [roleName, roleDef] of Object.entries(roles)) {
|
||||
for (const otherMember of roleDef.members) {
|
||||
if (res) { return true }
|
||||
res = this.compareMembers(member, otherMember.sp_addresses);
|
||||
}
|
||||
rolesContainsMember(roles: any, member: string[]): boolean {
|
||||
try {
|
||||
this.sdkClient.roles_contains_member(JSON.stringify(roles), member);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return res;
|
||||
return true;
|
||||
}
|
||||
|
||||
async dumpWallet() {
|
||||
@ -947,27 +993,21 @@ export default class Services {
|
||||
}
|
||||
}
|
||||
|
||||
async decryptAttribute(processId: string, state: ProcessState, attribute: string): Promise<string | null> {
|
||||
let hash = state.pcd_commitment[attribute];
|
||||
let key = state.keys[attribute];
|
||||
|
||||
// If hash or key is missing, request an update and then retry
|
||||
if (!hash || !key) {
|
||||
await this.requestDataFromPeers(processId, [state.state_id], [state.roles]);
|
||||
|
||||
const maxRetries = 5;
|
||||
const retryDelay = 500; // delay in milliseconds
|
||||
let retries = 0;
|
||||
|
||||
while ((!hash || !key) && retries < maxRetries) {
|
||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||
// Re-read hash and key after waiting
|
||||
hash = state.pcd_commitment[attribute];
|
||||
key = state.keys[attribute];
|
||||
retries++;
|
||||
}
|
||||
async decryptAttribute(state: ProcessState, attribute: string): Promise<string | null> {
|
||||
let hash;
|
||||
let key;
|
||||
try {
|
||||
hash = state.pcd_commitment[attribute];
|
||||
} catch (e) {
|
||||
console.error(`Failed to find hash for attribute ${attribute}`);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
key = state.keys[attribute];
|
||||
} catch (e) {
|
||||
console.error(`Failed to find key for attribute ${attribute}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hash && key) {
|
||||
const blob = await this.getBlobFromDb(hash);
|
||||
if (blob) {
|
||||
@ -979,12 +1019,12 @@ export default class Services {
|
||||
|
||||
const clear = this.sdkClient.decrypt_data(new Uint8Array(keyBuf), cipher);
|
||||
if (clear) {
|
||||
// Parse the stringified JSON
|
||||
// This is stringified json, we parse it back
|
||||
return JSON.parse(clear);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1095,23 +1135,12 @@ export default class Services {
|
||||
for (const [processId, process] of Object.entries(newProcesses)) {
|
||||
const existing = await this.getProcess(processId);
|
||||
if (existing) {
|
||||
// Look for state id we don't know yet
|
||||
let new_states = [];
|
||||
let roles = [];
|
||||
for (const state of process.states) {
|
||||
if (!state.state_id || state.state_id === EMPTY32BYTES) { continue; }
|
||||
if (!this.lookForStateId(existing, state.state_id)) {
|
||||
new_states.push(state.state_id);
|
||||
roles.push(state.roles);
|
||||
}
|
||||
}
|
||||
console.log(`${processId} already in db`);
|
||||
|
||||
if (new_states.length != 0) {
|
||||
// We request the new states
|
||||
await this.requestDataFromPeers(processId, new_states, roles);
|
||||
}
|
||||
|
||||
// Otherwise we're probably just in the initial loading at page initialization
|
||||
const event = new CustomEvent('process-updated', {
|
||||
detail: { processId }
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
|
||||
// We may learn an update for this process
|
||||
// TODO maybe actually check if what the relay is sending us contains more information than what we have
|
||||
@ -1123,6 +1152,7 @@ export default class Services {
|
||||
await this.saveProcessToDb(processId, process as Process);
|
||||
}
|
||||
}
|
||||
await this.updateProcessesWorker();
|
||||
}
|
||||
}, 500)
|
||||
} catch (e) {
|
||||
@ -1130,50 +1160,23 @@ export default class Services {
|
||||
}
|
||||
}
|
||||
|
||||
private lookForStateId(process: Process, stateId: string): boolean {
|
||||
for (const state of process.states) {
|
||||
if (state.state_id === stateId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la liste de tous les membres ordonnés par leur process id
|
||||
* Retourne la liste de tous les membres
|
||||
* @returns Un tableau contenant tous les membres
|
||||
*/
|
||||
public getAllMembersSorted(): Record<string, Member> {
|
||||
public getAllMembers(): Record<string, Member> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(this.membersList).sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
|
||||
);
|
||||
}
|
||||
|
||||
public getAllMembers(): Record<string, Member> {
|
||||
return this.membersList;
|
||||
}
|
||||
|
||||
public getAddressesForMemberId(memberId: string): string[] | null {
|
||||
return this.membersList[memberId];
|
||||
}
|
||||
|
||||
public compareMembers(memberA: string[], memberB: string[]): boolean {
|
||||
if (!memberA || !memberB) { return false }
|
||||
if (memberA.length !== memberB.length) { return false }
|
||||
|
||||
const res = memberA.every(item => memberB.includes(item)) && memberB.every(item => memberA.includes(item));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public async handleCommitError(response: string) {
|
||||
const content = JSON.parse(response);
|
||||
const error = content.error;
|
||||
const errorMsg = error['GenericError'];
|
||||
if (errorMsg === 'State is identical to the previous state') {
|
||||
return;
|
||||
} else if (errorMsg === 'Not enough valid proofs') { return; }
|
||||
// Wait and retry
|
||||
setTimeout(async () => {
|
||||
await this.sendCommitMessage(JSON.stringify(content));
|
||||
@ -1181,7 +1184,7 @@ export default class Services {
|
||||
}
|
||||
|
||||
|
||||
public getRoles(process: Process): Record<string, RoleDefinition> | null {
|
||||
public async getRoles(process: Process): Promise<record<string, RoleDefinition> | null> {
|
||||
const lastCommitedState = this.getLastCommitedState(process);
|
||||
if (lastCommitedState && lastCommitedState.roles && Object.keys(lastCommitedState.roles).length != 0) {
|
||||
return lastCommitedState!.roles;
|
||||
@ -1190,56 +1193,37 @@ export default class Services {
|
||||
}
|
||||
}
|
||||
|
||||
public getPublicData(process: Process): Record<string, any> | null {
|
||||
const lastCommitedState = this.getLastCommitedState(process);
|
||||
if (lastCommitedState && lastCommitedState.public_data) {
|
||||
return lastCommitedState.public_data;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public getProcessName(process: Process): string | null {
|
||||
const lastCommitedState = this.getLastCommitedState(process);
|
||||
if (lastCommitedState && lastCommitedState.public_data) {
|
||||
const processName = lastCommitedState!.public_data['processName'];
|
||||
if (processName) { return processName }
|
||||
else { return null }
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async getMyProcesses(): Promise<string[]> {
|
||||
async getMyProcesses(): Promise<Set<string>> {
|
||||
try {
|
||||
const processes = await this.getProcesses();
|
||||
const userProcessSet = new Set<string>();
|
||||
|
||||
for (const [processId, process] of Object.entries(processes)) {
|
||||
// We use myProcesses attribute to not reevaluate all processes everytime
|
||||
if (this.myProcesses && this.myProcesses.has(processId)) {
|
||||
userProcessSet.add(processId);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const roles = this.getRoles(process);
|
||||
const roles = await this.getRoles(process);
|
||||
|
||||
if (roles && this.rolesContainsUs(roles)) {
|
||||
if (this.rolesContainsUs(roles)) {
|
||||
this.myProcesses.add(processId);
|
||||
userProcessSet.add(processId);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
return Array.from(this.myProcesses);
|
||||
return userProcessSet;
|
||||
} catch (e) {
|
||||
console.error("Failed to get processes:", e);
|
||||
}
|
||||
}
|
||||
|
||||
public async requestDataFromPeers(processId: string, stateIds: string[], roles: Record<string, RoleDefinition>[]) {
|
||||
console.log('Requesting data from peers');
|
||||
console.log(roles);
|
||||
public async requestDataFromPeers(processId: string, stateId: string) {
|
||||
try {
|
||||
const res = this.sdkClient.request_data(processId, stateIds, roles);
|
||||
const res = this.sdkClient.request_data(processId, [stateId]);
|
||||
await this.handleApiReturn(res);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@ -1277,15 +1261,4 @@ export default class Services {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public isPairingProcess(roles: Record<string, RoleDefinition>): boolean {
|
||||
if (Object.keys(roles).length != 1) { return false }
|
||||
const pairingRole = roles['pairing'];
|
||||
if (pairingRole) {
|
||||
// For now that's enough, we should probably test more things
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +104,6 @@ export function initAddressInput() {
|
||||
const addressInput = container.querySelector('#addressInput') as HTMLInputElement;
|
||||
const emojiDisplay = container.querySelector('#emoji-display-2');
|
||||
const okButton = container.querySelector('#okButton') as HTMLButtonElement;
|
||||
const createButton = container.querySelector('#createButton') as HTMLButtonElement;
|
||||
addSubscription(addressInput, 'input', async () => {
|
||||
let address = addressInput.value;
|
||||
|
||||
@ -146,12 +145,6 @@ export function initAddressInput() {
|
||||
onOkButtonClick();
|
||||
});
|
||||
}
|
||||
|
||||
if (createButton) {
|
||||
addSubscription(createButton, 'click', () => {
|
||||
onCreateButtonClick();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function onOkButtonClick() {
|
||||
@ -165,46 +158,21 @@ async function onOkButtonClick() {
|
||||
}
|
||||
}
|
||||
|
||||
async function onCreateButtonClick() {
|
||||
try {
|
||||
await prepareAndSendPairingTx();
|
||||
} catch (e) {
|
||||
console.error(`onCreateButtonClick error: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function prepareAndSendPairingTx(promptName: boolean = false) {
|
||||
export async function prepareAndSendPairingTx(secondDeviceAddress: string) {
|
||||
const service = await Services.getInstance();
|
||||
|
||||
// Device 1 wait Device 2
|
||||
// service.device1 = true;
|
||||
service.device1 = true;
|
||||
|
||||
try {
|
||||
await service.checkConnections([]);
|
||||
await service.checkConnections([{ sp_addresses: [secondDeviceAddress] }]);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Prompt the user for a username.
|
||||
let userName;
|
||||
if (promptName) {
|
||||
userName = prompt("Please enter your user name:");
|
||||
} else {
|
||||
userName = "";
|
||||
}
|
||||
|
||||
// Create the process after a delay.
|
||||
// Create the process
|
||||
setTimeout(async () => {
|
||||
const relayAddress = service.getAllRelays();
|
||||
|
||||
// Pass the userName as an additional parameter.
|
||||
const createPairingProcessReturn = await service.createPairingProcess(
|
||||
userName,
|
||||
[],
|
||||
relayAddress[0].spAddress,
|
||||
1,
|
||||
userName
|
||||
);
|
||||
const relayAddress = service.getAllRelays();
|
||||
const createPairingProcessReturn = await service.createPairingProcess([secondDeviceAddress], relayAddress[0].spAddress, 1);
|
||||
|
||||
if (!createPairingProcessReturn.updated_process) {
|
||||
throw new Error('createPairingProcess returned an empty new process'); // This should never happen
|
||||
@ -221,21 +189,17 @@ export async function generateQRCode(spAddress: string) {
|
||||
const url = await QRCode.toDataURL(currentUrl + '?sp_address=' + spAddress);
|
||||
const qrCode = container?.querySelector('.qr-code img');
|
||||
qrCode?.setAttribute('src', url);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateCreateBtn() {
|
||||
try{
|
||||
//Generate CreateBtn
|
||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement
|
||||
const createBtn = container?.querySelector('.create-btn');
|
||||
if (createBtn) {
|
||||
createBtn.textContent = 'CREATE';
|
||||
//Generate Address CopyBtn
|
||||
const address = container?.querySelector('.sp-address-btn');
|
||||
if (address) {
|
||||
address.textContent = 'Copy address';
|
||||
}
|
||||
const copyBtn = container.querySelector('#copyBtn');
|
||||
if (copyBtn) {
|
||||
addSubscription(copyBtn, 'click', () => copyToClipboard(currentUrl + '?sp_address=' + spAddress));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user