ihm_client/src/services/service.ts

2025 lines
61 KiB
TypeScript
Executable File

// import { WebSocketClient } from '../websockets';
import { INotification } from '~/models/notification.model';
import { IProcess } from '~/models/process.model';
// import Database from './database';
import { initWebsocket, sendMessage } from '../websockets';
import { ApiReturn, Device, HandshakeMessage, Member, OutPointProcessMap, Process, ProcessState, RoleDefinition, SecretsStore, UserDiff } from '../../pkg/sdk_client';
import ModalService from './modal.service';
import Database from './database.service';
import { navigate } from '../router';
import { storeData, retrieveData, testData } from './storage.service';
import { BackUp } from '~/models/backup.model';
export const U32_MAX = 4294967295;
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 processId: string | null = null;
private stateId: string | null = null;
private sdkClient: any;
private myProcesses: Set<string> = new Set();
private notifications: any[] | null = null;
private subscriptions: { element: Element; event: string; eventHandler: string }[] = [];
private database: any;
private routingInstance!: ModalService;
private relayAddresses: { [wsurl: string]: string } = {};
private membersList: Record<string, Member> = {};
// Private constructor to prevent direct instantiation from outside
private constructor() {}
// Method to access the singleton instance of Services
public static async getInstance(): Promise<Services> {
if (Services.instance) {
return Services.instance;
}
if (!Services.initializing) {
Services.initializing = (async () => {
const instance = new Services();
await instance.init();
instance.routingInstance = await ModalService.getInstance();
return instance;
})();
}
console.log('initializing services');
Services.instance = await Services.initializing;
Services.initializing = null; // Reset for potential future use
return Services.instance;
}
public async init(): Promise<void> {
this.notifications = this.getNotifications();
this.sdkClient = await import('../../pkg/sdk_client');
this.sdkClient.setup();
for (const wsurl of Object.values(BOOTSTRAPURL)) {
this.updateRelay(wsurl, '');
}
await this.connectAllRelays();
}
public setProcessId(processId: string | null) {
this.processId = processId;
}
public setStateId(stateId: string | null) {
this.stateId = stateId;
}
public getProcessId(): string | null {
return this.processId;
}
public getStateId(): string | null {
return this.stateId;
}
/**
* Calls `this.addWebsocketConnection` for each `wsurl` in relayAddresses.
*/
public async connectAllRelays(): Promise<void> {
for (const wsurl of Object.keys(this.relayAddresses)) {
try {
console.log(`Connecting to: ${wsurl}`);
await this.addWebsocketConnection(wsurl);
console.log(`Successfully connected to: ${wsurl}`);
} catch (error) {
console.error(`Failed to connect to ${wsurl}:`, error);
}
}
}
public async addWebsocketConnection(url: string): Promise<void> {
console.log('Opening new websocket connection');
await initWebsocket(url);
}
/**
* Add or update a key/value pair in relayAddresses.
* @param wsurl - The WebSocket URL (key).
* @param spAddress - The SP Address (value).
*/
public updateRelay(wsurl: string, spAddress: string): void {
this.relayAddresses[wsurl] = spAddress;
console.log(`Updated: ${wsurl} -> ${spAddress}`);
}
/**
* Retrieve the spAddress for a given wsurl.
* @param wsurl - The WebSocket URL to look up.
* @returns The SP Address if found, or undefined if not.
*/
public getSpAddress(wsurl: string): string | undefined {
return this.relayAddresses[wsurl];
}
/**
* Get all key/value pairs from relayAddresses.
* @returns An array of objects containing wsurl and spAddress.
*/
public getAllRelays(): { wsurl: string; spAddress: string }[] {
return Object.entries(this.relayAddresses).map(([wsurl, spAddress]) => ({
wsurl,
spAddress,
}));
}
/**
* Print all key/value pairs for debugging.
*/
public printAllRelays(): void {
console.log("Current relay addresses:");
for (const [wsurl, spAddress] of Object.entries(this.relayAddresses)) {
console.log(`${wsurl} -> ${spAddress}`);
}
}
public isPaired(): boolean {
try {
return this.sdkClient.is_paired();
} catch (e) {
throw new Error(`isPaired ~ Error: ${e}`);
}
}
public async unpairDevice(): Promise<void> {
try {
this.sdkClient.unpair_device();
const newDevice = this.dumpDeviceFromMemory();
await this.saveDeviceInDatabase(newDevice);
} catch (e) {
throw new Error(`Failed to unpair device: ${e}`);
}
}
public async getSecretForAddress(address: string): Promise<string | null> {
const db = await Database.getInstance();
return await db.getObject('shared_secrets', address);
}
public async getAllSecrets(): Promise<SecretsStore> {
const db = await Database.getInstance();
const sharedSecrets = await db.dumpStore('shared_secrets');
const unconfirmedSecrets = await db.dumpStore('unconfirmed_secrets'); // keys are numeric values
const secretsStore = {
shared_secrets: sharedSecrets,
unconfirmed_secrets: Object.values(unconfirmedSecrets),
};
return secretsStore;
}
public async getAllDiffs(): Promise<Record<string, UserDiff>> {
const db = await Database.getInstance();
return await db.dumpStore('diffs');
}
public async getDiffByValue(value: string): Promise<UserDiff | null> {
const db = await Database.getInstance();
const store = 'diffs';
const res = await db.getObject(store, value);
return res;
}
private async getTokensFromFaucet(): Promise<void> {
try {
await this.ensureSufficientAmount();
} catch (e) {
console.error('Failed to get tokens from relay, check connection');
return;
}
}
public async checkConnections(members: Member[]): Promise<void> {
// Ensure the amount is available before proceeding
await this.getTokensFromFaucet();
let unconnectedAddresses = [];
const myAddress = await this.getDeviceAddress();
for (const member of members) {
for (const address of member.sp_addresses) {
// For now, we ignore our own device address, although there might be use cases for having a secret with ourselves
if (address == myAddress) continue;
const sharedSecret = await this.getSecretForAddress(address);
if (!sharedSecret) {
unconnectedAddresses.push(address);
}
}
}
if (unconnectedAddresses && unconnectedAddresses.length != 0) {
const apiResult = await this.connectAddresses(unconnectedAddresses);
await this.handleApiReturn(apiResult);
}
}
public async connectAddresses(addresses: string[]): Promise<ApiReturn> {
if (addresses.length === 0) {
throw new Error('Trying to connect to empty addresses list');
}
try {
return this.sdkClient.create_transaction(addresses, 1);
} catch (e) {
console.error('Failed to connect member:', e);
throw e;
}
}
private async ensureSufficientAmount(): Promise<void> {
const availableAmt = this.getAmount();
const target: BigInt = DEFAULTAMOUNT * BigInt(10);
if (availableAmt < target) {
const faucetMsg = this.createFaucetMessage();
this.sendFaucetMessage(faucetMsg);
await this.waitForAmount(target);
}
}
private async waitForAmount(target: BigInt): Promise<BigInt> {
let attempts = 3;
while (attempts > 0) {
const amount = this.getAmount();
if (amount >= target) {
return amount;
}
attempts--;
if (attempts > 0) {
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for 1 second
}
}
throw new Error('Amount is still 0 after 3 attempts');
}
public async createPairingProcess(userName: string, pairWith: string[], relayAddress: string, feeRate: number): Promise<ApiReturn> {
if (this.sdkClient.is_paired()) {
throw new Error('Device already paired');
}
const myAddress: string = this.sdkClient.get_address();
pairWith.push(myAddress);
const roles: Record<string, RoleDefinition> = {
pairing: {
members: [],
validation_rules: [
{
quorum: 1.0,
fields: ['description', 'counter', 'roles', 'memberPublicName', 'pairedAddresses'],
min_sig_member: 1.0,
},
],
storages: [STORAGEURL]
},
};
const pairingTemplate = {
description: 'pairing',
counter: 0,
};
const publicData = {
memberPublicName: userName,
pairedAddresses: pairWith,
};
try {
return this.sdkClient.create_new_process(
pairingTemplate,
roles,
publicData,
relayAddress,
feeRate,
this.getAllMembers()
);
} catch (e) {
throw new Error(`Creating process failed:, ${e}`);
}
}
public async createProcess(
privateData: Record<string, any>,
publicData: Record<string, any>,
roles: Record<string, RoleDefinition>,
): Promise<ApiReturn> {
const relayAddress = this.getAllRelays()[0]['spAddress'];
const feeRate = 1;
let members: Set<Member> = new Set();
for (const role of Object.values(roles!)) {
for (const member of role.members) {
// Check if we know the member that matches this id
const memberAddresses = this.getAddressesForMemberId(member);
if (memberAddresses && memberAddresses.length != 0) {
members.add({ sp_addresses: memberAddresses });
}
}
}
await this.checkConnections([...members]);
const result = this.sdkClient.create_new_process (
privateData,
roles,
publicData,
relayAddress,
feeRate,
this.getAllMembers()
);
return(result);
}
public async createDmProcess(
otherMember: string[],
): Promise<ApiReturn> {
if (otherMember.length === 0) {
throw new Error('Can\'t open dm with empty user');
}
try {
console.log('🚀 Début createDmProcess');
console.log('👥 Other Member:', otherMember);
if (!this.isPaired()) {
throw new Error('Device not paired');
}
const myAddresses = await this.getMemberFromDevice();
console.log('🔑 Mes adresses:', myAddresses);
if (!myAddresses) {
throw new Error('No paired member found');
}
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 },
{ sp_addresses: otherMember }
],
validation_rules: [
{
quorum: 0.01,
fields: ['message', 'description'],
min_sig_member: 0.01,
},
],
storages: [STORAGEURL]
}
}
const dmTemplate = {
description: 'dm',
message: '',
};
console.log('📋 Template final:', JSON.stringify(dmTemplate, null, 2));
const relayAddress = this.getAllRelays()[0]['spAddress'];
const feeRate = 1;
const publicData = {};
await this.checkConnections ([{ sp_addresses: otherMember }]);
const result = this.sdkClient.create_new_process (
dmTemplate,
roles,
publicData,
relayAddress,
feeRate
);
return result;
} catch (e) {
console.error('❌ Erreur:', e);
throw e;
}
}
public async createNotaryProcess(notaryTokens: string[] | null): Promise<ApiReturn> {
const notaryProcess = await this.lookForNotaryProcess();
if (notaryProcess) {
console.log('NOTARY PROCESS:', notaryProcess);
throw new Error('There is already a notary process');
}
const myProcessId: string = this.getPairingProcessId();
const roles: Record<string, RoleDefinition> = {
notary: {
members: [myProcessId],
validation_rules: [
{
quorum: 0,
fields: ['roles', 'idNotTokens'],
min_sig_member: 0,
},
],
storages: [STORAGEURL]
},
};
const pairingTemplate = {
description: 'notary',
};
const publicData = {
idNotTokens: notaryTokens,
}
const relayAddress = this.getAllRelays()[0]['spAddress'];
const feeRate = 1;
await this.getTokensFromFaucet();
try {
return this.sdkClient.create_new_process(
pairingTemplate,
roles,
publicData,
relayAddress,
feeRate,
this.getAllMembers()
);
} catch (e) {
throw new Error(`Creating process failed:, ${e}`);
}
}
public async createProfileProcess(userData: any): Promise<ApiReturn> {
const myProcessId: string = this.getPairingProcessId();
if (!myProcessId) {
throw new Error('Missing pairing id');
}
const validator = userData['validator'];
delete userData.validator; // We don't want that in the final pcd
const userDataKeys = Object.keys(userData);
const roles: Record<string, RoleDefinition> = {
demiurge: {
members: [myProcessId],
validation_rules: [],
storages: [STORAGEURL]
},
owner: {
members: [myProcessId],
validation_rules: [
{
quorum: 0.01,
fields: [
'description',
'roles',
...userDataKeys
],
min_sig_member: 0.01,
},
],
storages: [STORAGEURL]
},
idCertificator: {
members: [validator],
validation_rules: [
{
quorum: 1.0,
fields: ['identityCertified'],
min_sig_member: 1.0
},
{
quorum: 0.0,
fields: [
'description',
...userDataKeys
],
min_sig_member: 0.0
}
],
storages: [STORAGEURL]
},
blm: {
members: [],
validation_rules: [
{
quorum: 0.0,
fields: [
'description',
'name',
'lastName',
],
min_sig_member: 0.0
}
],
storages: [STORAGEURL]
},
};
const profileTemplate = {
description: 'profile',
...userData,
};
const publicData = {
identityCertified: false,
};
// Add name and lastName if profile_idn (notary profile)
// if (userDataKeys.includes('profile_idn')) {
// publicData.identityCertified = true;
// publicData.name = userData.name;
// publicData.lastName = userData.lastName;
// } else {
// publicData.identityCertified = false;
// }
const relayAddress = this.getAllRelays()[0]['spAddress'];
const feeRate = 1;
await this.getTokensFromFaucet();
try {
return this.sdkClient.create_new_process(
profileTemplate,
roles,
publicData,
relayAddress,
feeRate,
this.getAllMembers()
);
} catch (e) {
throw new Error(`Creating process failed:, ${e}`);
}
}
// create a process for folder
public async createFolderProcess(folderData: any): Promise<ApiReturn> {
const myProcessId: string = this.getPairingProcessId();
if (!myProcessId) {
throw new Error('Missing pairing id');
}
const folderDataKeys = Object.keys(folderData);
const roles: Record<string, RoleDefinition> = {
owner: {
members: [myProcessId],
validation_rules: [
{
quorum: 0.01,
fields: [
'description',
'roles',
...folderDataKeys
],
min_sig_member: 0.01,
},
],
storages: [STORAGEURL]
},
stakeholder: {
members: folderData.stakeholders,
validation_rules: [
{
quorum: 0.01,
fields: ['documents', 'notes'],
min_sig_member: 0.01,
},
],
storages: [STORAGEURL]
},
customer: {
members: [],
validation_rules: [
{
quorum: 0.0,
fields: ['documents', 'notes'],
min_sig_member: 0.0,
},
],
storages: [STORAGEURL]
}
};
const folderTemplate = {
description: 'folder',
...folderData,
};
console.log('🚀 ~ Services ~ createFolderProcess ~ folderTemplate:', folderTemplate);
const publicData = {
};
const relayAddress = this.getAllRelays()[0]['spAddress'];
const feeRate = 1;
await this.getTokensFromFaucet();
try {
return this.sdkClient.create_new_process(
folderTemplate,
roles,
publicData,
relayAddress,
feeRate,
this.getAllMembers()
);
} catch (e) {
throw new Error(`Creating folder process failed: ${e}`);
}
}
public MOCK_NOTARY = {
at_hash: "DQLI1_Wg0853tf0qf8BYxghzIXaMBaQu4UWz07iG7o",
sub: "IDN26889949I",
profile_id: "IDN26889949I_IDN009850",
arm: "1",
iss: "https://connexion.idnot.fr/idPOAuth2/idnot_idp_v1",
given_name: "Marie",
aud: "BB715912AFEEC6D1",
nbf: "1669713096",
auth_time: "1669713195",
entity_id: "IDN009850",
name: "DUPONT",
exp: "1669720396",
iat: "1669713196",
email: "marie.dupont@notaires.fr"
};
// This look for the process that holds all the notaries
// private async lookForNotaryProcess(): Promise<string | null> {
// const processes = await this.getProcesses();
// for (const processId of Object.keys(processes)) {
// try {
// const process = await this.getProcess(processId);
// const roles = this.getRoles(process);
// if (!roles) {
// console.error('Failed to getRoles');
// return null;
// }
// const roleKeys = Object.keys(roles);
// if (roleKeys.includes("notary")) {
// let publicData;
// const lastCommitedState = this.getLastCommitedState(process);
// // If we don't have a commited state, the notary process might be in initialization phase
// if (lastCommitedState) {
// publicData = lastCommitedState.public_data;
// } else {
// console.log(`Fallback to first, uncommited state`);
// publicData = process.states[0].public_data;
// }
// const publicDataKeys = Object.keys(publicData);
// if (publicDataKeys.includes("idNotTokens")){
// return processId;
// } else {
// continue;
// }
// } else {
// continue;
// }
// } catch (e) {
// console.error(e);
// }
// }
// return null;
// }
// private async checkIfNotary(): Promise<string | null> {
// const processes = await this.getMyProcesses();
// if (!processes) {
// return null;
// }
// for (const processId of processes) {
// try {
// const process = await this.getProcess(processId);
// const lastCommitedState = this.getLastCommitedState(process);
// if (lastCommitedState) {
// const fields = lastCommitedState.pcd_commitment;
// const fieldsKeys = Object.keys(fields);
// const publicData = lastCommitedState.public_data;
// if (fieldsKeys.includes("idNot") || publicData.identityCertified) {
// console.log(`Found idNot in profile ${processId}, member is notary`);
// return processId;
// } else {
// continue;
// }
// } else {
// continue;
// }
// } catch (e) {
// console.error(e);
// }
// }
// return null;
// }
public async updateProcess(process: Process, privateData: Record<string, any>, publicData: Record<string, 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);
} 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));
}
let members: Set<Member> = new Set();
for (const role of Object.values(roles!)) {
for (const member of role.members) {
members.add(member)
}
}
await this.checkConnections([...members]);
const membersList = this.getAllMembers();
try {
console.log(process);
return this.sdkClient.update_process(process, privateData, roles, publicData, membersList);
} catch (e) {
throw new Error(`Failed to update process: ${e}`);
}
}
public async createPrdUpdate(processId: string, stateId: string): Promise<ApiReturn> {
const process = await this.getProcess(processId);
if (!process) {
throw new Error('Unknown process');
}
try {
return this.sdkClient.create_update_message(process, stateId, this.getAllMembers());
} catch (e) {
throw new Error(`Failed to create prd update: ${e}`);
}
}
public async createPrdResponse(processId: string, stateId: string): Promise<ApiReturn> {
const process = await this.getProcess(processId);
if (!process) {
throw new Error('Unknown process');
}
try {
return this.sdkClient.create_response_prd(process, stateId, this.getAllMembers());
} catch (e) {
throw new Error(`Failed to create response prd: ${e}`);
}
}
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');
}
try {
return this.sdkClient.validate_state(process, stateId, this.getAllMembers());
} 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');
}
try {
return this.sdkClient.refuse_state(process, stateId);
} catch (e) {
throw new Error(`Failed to create prd response: ${e}`);
}
}
async resetDevice() {
this.sdkClient.reset_device();
// Clear all stores
const db = await Database.getInstance();
await db.clearStore('wallet');
await db.clearStore('shared_secrets');
await db.clearStore('unconfirmed_secrets');
await db.clearStore('processes');
await db.clearStore('diffs');
}
async sendNewTxMessage(message: string) {
sendMessage('NewTx', message);
}
async sendCommitMessage(message: string) {
sendMessage('Commit', message);
}
async sendCipherMessages(ciphers: string[]) {
for (let i = 0; i < ciphers.length; i++) {
const cipher = ciphers[i];
sendMessage('Cipher', cipher);
}
}
sendFaucetMessage(message: string): void {
sendMessage('Faucet', message);
}
async parseCipher(message: string) {
const membersList = this.getAllMembers();
try {
// console.log('parsing new cipher');
const apiReturn = this.sdkClient.parse_cipher(message, membersList);
console.log('🚀 ~ Services ~ parseCipher ~ apiReturn:', apiReturn);
await this.handleApiReturn(apiReturn);
// Device 1 wait Device 2
const waitingModal = document.getElementById('waiting-modal');
if (waitingModal) {
this.device2Ready = true;
}
} catch (e) {
console.error(`Parsed cipher with error: ${e}`);
}
// await this.saveCipherTxToDb(parsedTx)
}
async parseNewTx(tx: string) {
const membersList = this.getAllMembers();
try {
const parsedTx = this.sdkClient.parse_new_tx(tx, 0, membersList);
if (parsedTx) {
try {
await this.handleApiReturn(parsedTx);
const newDevice = this.dumpDeviceFromMemory();
await this.saveDeviceInDatabase(newDevice);
} catch (e) {
console.error('Failed to update device with new tx');
}
}
} catch (e) {
console.trace(e);
}
}
public async handleApiReturn(apiReturn: ApiReturn) {
console.log(apiReturn);
if (apiReturn.partial_tx) {
try {
const res = this.sdkClient.sign_transaction(apiReturn.partial_tx);
console.log('Adding tx to new_tx_to_send');
apiReturn.new_tx_to_send = res.new_tx_to_send;
} catch (e) {
console.error('Failed to sign transaction:', e);
}
}
if (apiReturn.new_tx_to_send && apiReturn.new_tx_to_send.transaction.length != 0) {
await this.sendNewTxMessage(JSON.stringify(apiReturn.new_tx_to_send));
await new Promise(r => setTimeout(r, 500));
}
if (apiReturn.secrets) {
const unconfirmedSecrets = apiReturn.secrets.unconfirmed_secrets;
const confirmedSecrets = apiReturn.secrets.shared_secrets;
const db = await Database.getInstance();
for (const secret of unconfirmedSecrets) {
await db.addObject({
storeName: 'unconfirmed_secrets',
object: secret,
key: null,
});
}
const entries = Object.entries(confirmedSecrets).map(([key, value]) => ({ key, value }));
for (const entry of entries) {
try {
await db.addObject({
storeName: 'shared_secrets',
object: entry.value,
key: entry.key,
});
} catch (e) {
throw e;
}
// We don't want to throw an error, it could simply be that we registered directly the shared secret
// this.removeUnconfirmedSecret(entry.value);
}
}
if (apiReturn.updated_process) {
const updatedProcess = apiReturn.updated_process;
const processId: string = updatedProcess.process_id;
if (updatedProcess.encrypted_data && Object.keys(updatedProcess.encrypted_data).length != 0) {
for (const [hash, cipher] of Object.entries(updatedProcess.encrypted_data)) {
// console.log(hash);
// console.log(cipher);
const blob = this.hexToBlob(cipher);
try {
await this.saveBlobToDb(hash, blob);
} catch (e) {
console.error(e);
}
}
}
// Save process to db
await this.saveProcessToDb(processId, updatedProcess.current_process);
if (updatedProcess.diffs && updatedProcess.diffs.length != 0) {
try {
await this.saveDiffsToDb(updatedProcess.diffs);
} catch (e) {
console.error('Failed to save diffs to db:', e);
}
}
}
if (apiReturn.push_to_storage && apiReturn.push_to_storage.length != 0) {
for (const hash of apiReturn.push_to_storage) {
const blob = await this.getBlobFromDb(hash);
if (blob) {
await this.saveDataToStorage(hash, blob, null);
} else {
console.error('Failed to get data from db');
}
}
}
if (apiReturn.commit_to_send) {
const commit = apiReturn.commit_to_send;
await this.sendCommitMessage(JSON.stringify(commit));
}
if (apiReturn.ciphers_to_send && apiReturn.ciphers_to_send.length != 0) {
await this.sendCipherMessages(apiReturn.ciphers_to_send);
}
}
public async openPairingConfirmationModal(processId: string) {
const process = await this.getProcess(processId);
if (!process) {
console.error('Failed to find pairing process');
return;
}
const firstState = process.states[0];
const roles = firstState.roles;
const stateId = firstState.state_id;
try {
await this.routingInstance.openPairingConfirmationModal(roles, processId, stateId);
} catch (e) {
console.error(e);
}
}
public async confirmPairing() {
if (!this.processId || !this.stateId) {
console.error('Missing process and/or state ID');
return;
}
let createPrdUpdateReturn;
try {
createPrdUpdateReturn = await this.createPrdUpdate(this.processId, this.stateId);
} catch (e) {
throw new Error(`createPrdUpdate failed: ${e}`);
}
await this.handleApiReturn(createPrdUpdateReturn);
let approveChangeReturn;
try {
approveChangeReturn = await this.approveChange(this.processId, this.stateId);
} catch (e) {
throw new Error(`approveChange failed: ${e}`);
}
await this.handleApiReturn(approveChangeReturn);
await this.pairDevice();
this.processId = null;
this.stateId = null;
const newDevice = this.dumpDeviceFromMemory();
console.log(newDevice);
await this.saveDeviceInDatabase(newDevice);
await navigate('account');
}
public async pairDevice() {
if (!this.processId) {
console.error('No processId set');
return;
}
const process = await this.getProcess(this.processId);
if (!process) {
console.error('Unknown process');
return;
}
const spAddressList = process.states[0].public_data['pairedAddresses'];
console.log(spAddressList);
try {
this.sdkClient.pair_device(this.processId, spAddressList);
} catch (e) {
throw new Error(`Failed to pair device: ${e}`);
}
}
public getAmount(): BigInt {
const amount = this.sdkClient.get_available_amount();
return amount;
}
async getDeviceAddress() {
return await this.sdkClient.get_address();
}
public dumpDeviceFromMemory(): string {
try {
return this.sdkClient.dump_device();
} catch (e) {
throw new Error(`Failed to dump device: ${e}`);
}
}
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();
} catch (e) {
throw new Error(`Failed to get pairing process: ${e}`);
}
}
async saveDeviceInDatabase(device: any): Promise<void> {
const db = await Database.getInstance();
const walletStore = 'wallet';
try {
const prevDevice = await this.getDeviceFromDatabase();
if (prevDevice) {
await db.deleteObject(walletStore, "1");
}
await db.addObject({
storeName: walletStore,
object: { pre_id: '1', device },
key: null,
});
} catch (e) {
console.error(e);
}
}
async getDeviceFromDatabase(): Promise<string | null> {
const db = await Database.getInstance();
const walletStore = 'wallet';
try {
const dbRes = await db.getObject(walletStore, '1');
if (dbRes) {
const wallet = dbRes['device'];
return wallet;
} else {
return null;
}
} catch (e) {
throw new Error(`Failed to retrieve device from db: ${e}`);
}
}
async getMemberFromDevice(): Promise<string[] | null> {
try {
const device = await this.getDeviceFromDatabase();
if (device) {
const parsed: Device = JSON.parse(device);
const pairedMember = parsed['paired_member'];
return pairedMember.sp_addresses;
} else {
return null;
}
} catch (e) {
throw new Error(`Failed to retrieve paired_member from device: ${e}`);
}
}
isChildRole(parent: any, child: any): boolean {
try {
this.sdkClient.is_child_role(JSON.stringify(parent), JSON.stringify(child));
} catch (e) {
console.error(e);
return false;
}
return true;
}
rolesContainsUs(roles: Record<string, RoleDefinition>): boolean {
let us;
try {
us = this.sdkClient.get_member();
} catch (e) {
throw e;
}
return this.rolesContainsMember(roles, us.sp_addresses);
}
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 }
// Get the addresses for the member
const otherMemberAddresses: string[] | null = this.getAddressesForMemberId(otherMember);
if (!otherMemberAddresses) {
// console.error('Failed to get addresses for member', otherMember);
continue;
}
res = this.compareMembers(member, otherMemberAddresses);
}
}
return res;
}
async dumpWallet() {
const wallet = await this.sdkClient.dump_wallet();
console.log('🚀 ~ Services ~ dumpWallet ~ wallet:', wallet);
return wallet;
}
public createFaucetMessage() {
const message = this.sdkClient.create_faucet_msg();
console.log('🚀 ~ Services ~ createFaucetMessage ~ message:', message);
return message;
}
async createNewDevice() {
let spAddress = '';
try {
spAddress = await this.sdkClient.create_new_device(0, 'signet');
const device = this.dumpDeviceFromMemory();
console.log('🚀 ~ Services ~ device:', device);
await this.saveDeviceInDatabase(device);
} catch (e) {
console.error('Services ~ Error:', e);
}
return spAddress;
}
restoreDevice(device: string) {
try {
this.sdkClient.restore_device(device);
const spAddress = this.sdkClient.get_address();
} catch (e) {
console.error(e);
}
}
private async removeProcess(processId: string): Promise<void> {
const db = await Database.getInstance();
const storeName = 'processes';
try {
await db.deleteObject(storeName, processId);
} catch (e) {
console.error(e);
}
}
public async saveProcessToDb(processId: string, process: Process) {
const db = await Database.getInstance();
try {
await db.addObject({
storeName: 'processes',
object: process,
key: processId,
});
} catch (e) {
console.error(`Failed to save process ${processId}: ${e}`);
}
}
public async saveBlobToDb(hash: string, data: Blob) {
const db = await Database.getInstance();
try {
await db.addObject({
storeName: 'data',
object: data,
key: hash,
});
} catch (e) {
console.error(`Failed to save data to db: ${e}`);
}
}
public async getBlobFromDb(hash: string): Promise<Blob | null> {
const db = await Database.getInstance();
try {
return await db.getObject('data', hash);
} catch (e) {
return null;
}
}
public async saveDataToStorage(hash: string, data: Blob, ttl: number | null) {
const storages = [STORAGEURL];
try {
await storeData(storages, hash, data, ttl);
} catch (e) {
console.error(`Failed to store data with hash ${hash}: ${e}`);
}
}
public async fetchValueFromStorage(hash: string): Promise<any | null> {
const storages = [STORAGEURL];
return await retrieveData(storages, hash);
}
public async testDataInStorage(hash: string): Promise<Record<string, boolean | null> | null> {
const storages = [STORAGEURL];
return await testData(storages, hash);
}
public async saveDiffsToDb(diffs: UserDiff[]) {
const db = await Database.getInstance();
try {
for (const diff of diffs) {
await db.addObject({
storeName: 'diffs',
object: diff,
key: null,
});
}
} catch (e) {
throw new Error(`Failed to save process: ${e}`);
}
}
public async getProcess(commitedIn: string): Promise<Process> {
const db = await Database.getInstance();
return await db.getObject('processes', commitedIn);
}
public async getProcesses(): Promise<Record<string, Process>> {
const db = await Database.getInstance();
const processes: Record<string, Process> = await db.dumpStore('processes');
return processes;
}
// TODO rewrite that it's a mess and we don't use it now
// public async getChildrenOfProcess(processId: string): Promise<string[]> {
// const processes = await this.getProcesses();
// const res = [];
// for (const [hash, process] of Object.entries(processes)) {
// const firstState = process.states[0];
// const pcdCommitment = firstState['pcd_commitment'];
// try {
// const parentIdHash = pcdCommitment['parent_id'];
// const diff = await this.getDiffByValue(parentIdHash);
// if (diff && diff['new_value'] === processId) {
// res.push(JSON.stringify(process));
// }
// } catch (e) {
// continue;
// }
// }
// return res;
// }
public async restoreProcessesFromBackUp(processes: Record<string, Process>) {
const db = await Database.getInstance();
for (const [commitedIn, process] of Object.entries(processes)) {
await db.addObject({ storeName: 'processes', object: process, key: commitedIn});
}
await this.restoreProcessesFromDB();
}
// Restore process in wasm with persistent storage
public async restoreProcessesFromDB() {
const db = await Database.getInstance();
try {
const processes: Record<string, Process> = await db.dumpStore('processes');
if (processes && Object.keys(processes).length != 0) {
console.log(`Restoring ${Object.keys(processes).length} processes`);
this.sdkClient.set_process_cache(JSON.stringify(processes));
} else {
console.log('No processes to restore!');
}
} catch (e) {
throw e;
}
}
public async clearSecretsFromDB() {
const db = await Database.getInstance();
try {
await db.clearStore('shared_secrets');
await db.clearStore('unconfirmed_secrets');
} catch (e) {
console.error(e);
}
}
public async restoreSecretsFromBackUp(secretsStore: SecretsStore) {
const db = await Database.getInstance();
for (const secret of secretsStore.unconfirmed_secrets) {
await db.addObject({
storeName: 'unconfirmed_secrets',
object: secret,
key: null,
});
}
const entries = Object.entries(secretsStore.shared_secrets).map(([key, value]) => ({ key, value }));
for (const entry of entries) {
await db.addObject({
storeName: 'shared_secrets',
object: entry.value,
key: entry.key,
});
}
// Now we can transfer them to memory
await this.restoreSecretsFromDB();
}
public async restoreSecretsFromDB() {
const db = await Database.getInstance();
try {
const sharedSecrets: Record<string, string> = await db.dumpStore('shared_secrets');
const unconfirmedSecrets = await db.dumpStore('unconfirmed_secrets');
const secretsStore = {
shared_secrets: sharedSecrets,
unconfirmed_secrets: Object.values(unconfirmedSecrets),
};
this.sdkClient.set_shared_secrets(JSON.stringify(secretsStore));
} catch (e) {
throw e;
}
}
async decryptAttribute(processId: string, state: ProcessState, attribute: string): Promise<string | null> {
let hash = state.pcd_commitment[attribute];
if (!hash) {
// attribute doesn't exist
return null;
}
let key = state.keys[attribute];
// If key is missing, request an update and then retry
if (!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++;
}
}
if (hash && key) {
const blob = await this.getBlobFromDb(hash);
if (blob) {
// Decrypt the data
const buf = await blob.arrayBuffer();
const cipher = new Uint8Array(buf);
const keyBlob = this.hexToBlob(key);
const keyBuf = await keyBlob.arrayBuffer();
const clear = this.sdkClient.decrypt_data(new Uint8Array(keyBuf), cipher);
if (clear) {
// Parse the stringified JSON
return JSON.parse(clear);
}
}
}
return null;
}
getNotifications(): any[] | null {
// return [
// {
// id: 1,
// title: 'Notif 1',
// description: 'A normal notification',
// sendToNotificationPage: false,
// path: '/notif1',
// },
// {
// id: 2,
// title: 'Notif 2',
// description: 'A normal notification',
// sendToNotificationPage: false,
// path: '/notif2',
// },
// {
// id: 3,
// title: 'Notif 3',
// description: 'A normal notification',
// sendToNotificationPage: false,
// path: '/notif3',
// },
// ];
return this.notifications;
}
setNotifications(notifications: any[]) {
this.notifications = notifications;
}
async importJSON(backup: BackUp): Promise<void> {
const device = JSON.stringify(backup.device);
// Reset current device
await this.resetDevice();
await this.saveDeviceInDatabase(device);
this.restoreDevice(device);
// TODO restore secrets and processes from file
const secretsStore = backup.secrets;
await this.restoreSecretsFromBackUp(secretsStore);
const processes = backup.processes;
await this.restoreProcessesFromBackUp(processes);
}
public async createBackUp(): Promise<BackUp | null> {
// Get the device from indexedDB
const deviceStr = await this.getDeviceFromDatabase();
if (!deviceStr) {
console.error('No device loaded');
return null;
}
const device: Device = JSON.parse(deviceStr);
// Get the processes
const processes = await this.getProcesses();
// Get the shared secrets
const secrets = await this.getAllSecrets();
// Create a backup object
const backUp = {
device: device,
secrets: secrets,
processes: processes,
};
return backUp;
}
// Device 1 wait Device 2
public device1: boolean = false;
public device2Ready: boolean = false;
public resetState() {
this.device1 = false;
this.device2Ready = false;
}
// Handle the handshake message
public async handleHandshakeMsg(url: string, parsedMsg: any) {
try {
const handshakeMsg: HandshakeMessage = JSON.parse(parsedMsg);
this.updateRelay(url, handshakeMsg.sp_address);
const members = handshakeMsg.peers_list;
if (this.membersList && Object.keys(this.membersList).length === 0) {
// We start from an empty list, just copy it over
this.membersList = handshakeMsg.peers_list;
} else {
// We are incrementing our list
for (const [processId, member] of Object.entries(handshakeMsg.peers_list)) {
this.membersList[processId] = member as Member;
}
}
setTimeout(async () => {
const newProcesses: OutPointProcessMap = handshakeMsg.processes_list;
if (newProcesses && Object.keys(newProcesses).length !== 0) {
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)) {
if (this.rolesContainsUs(state.roles)) {
new_states.push(state.state_id);
roles.push(state.roles);
}
}
}
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
// 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
// relay should always have more info than us, but we never know
// For now let's keep it simple and let the worker do the job
} else {
// We add it to db
console.log(`Saving ${processId} to db`);
await this.saveProcessToDb(processId, process as Process);
}
}
}
}, 500)
} catch (e) {
console.error('Failed to parse init message:', e);
}
}
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
* @returns Un tableau contenant tous les membres
*/
public getAllMembersSorted(): 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 {
try {
return this.membersList[memberId].sp_addresses;
} catch (e) {
return null;
}
}
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));
}, 1000)
}
public getRoles(process: Process): Record<string, RoleDefinition> | null {
const lastCommitedState = this.getLastCommitedState(process);
if (lastCommitedState && lastCommitedState.roles && Object.keys(lastCommitedState.roles).length != 0) {
return lastCommitedState!.roles;
} else if (process.states.length === 2) {
const firstState = process.states[0];
if (firstState && firstState.roles && Object.keys(firstState.roles).length != 0) {
return firstState!.roles;
}
}
return null;
}
public getPublicData(process: Process): Record<string, any> | null {
const lastCommitedState = this.getLastCommitedState(process);
if (lastCommitedState && lastCommitedState.public_data && Object.keys(lastCommitedState.public_data).length != 0) {
return lastCommitedState!.public_data;
} else if (process.states.length === 2) {
const firstState = process.states[0];
if (firstState && firstState.public_data && Object.keys(firstState.public_data).length != 0) {
return firstState!.public_data;
}
}
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[] | null> {
try {
const processes = await this.getProcesses();
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)) {
continue;
}
try {
const roles = this.getRoles(process);
if (roles && this.rolesContainsUs(roles)) {
this.myProcesses.add(processId);
}
} catch (e) {
console.error(e);
}
}
return Array.from(this.myProcesses);
} catch (e) {
console.error("Failed to get processes:", e);
return null;
}
}
public async requestDataFromPeers(processId: string, stateIds: string[], roles: Record<string, RoleDefinition>[]) {
console.log('Requesting data from peers');
console.log(roles);
const membersList = this.getAllMembers();
try {
const res = this.sdkClient.request_data(processId, stateIds, roles, membersList);
await this.handleApiReturn(res);
} catch (e) {
console.error(e);
}
}
public hexToBlob(hexString: string): Blob {
if (hexString.length % 2 !== 0) {
throw new Error("Invalid hex string: length must be even");
}
const uint8Array = new Uint8Array(hexString.length / 2);
for (let i = 0; i < hexString.length; i += 2) {
uint8Array[i / 2] = parseInt(hexString.substr(i, 2), 16);
}
return new Blob([uint8Array], { type: "application/octet-stream" });
}
public async blobToHex(blob: Blob): Promise<string> {
const buffer = await blob.arrayBuffer();
const bytes = new Uint8Array(buffer);
return Array.from(bytes)
.map(byte => byte.toString(16).padStart(2, '0'))
.join('');
}
public getLastCommitedState(process: Process): ProcessState | null {
if (process.states.length === 0) return null;
const processTip = process.states[process.states.length - 1].commited_in;
const lastCommitedState = process.states.findLast(state => state.commited_in !== processTip);
if (lastCommitedState) {
return lastCommitedState;
} else {
return null;
}
}
public getStateFromId(process: Process, stateId: string): ProcessState | null {
if (process.states.length === 0) return null;
const state = process.states.find(state => state.commited_in === stateId);
if (state) {
return state;
} else {
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;
}
}
public async updateMemberPublicName(process: Process, newName: string): Promise<ApiReturn> {
const publicData = {
'memberPublicName': newName
};
return await this.updateProcess(process, {}, publicData, null);
}
public async createAndSendNotaryTx(): Promise<void> {
try {
await this.checkConnections([]);
} catch (e) {
throw e;
}
try {
const createNotaryProcessReturn = await this.createNotaryProcess(
[],
);
if (!createNotaryProcessReturn.updated_process) {
throw new Error('createNotaryProcessReturn returned an empty new process');
}
await this.handleApiReturn(createNotaryProcessReturn);
this.setProcessId(createNotaryProcessReturn.updated_process.process_id);
console.log('PROCESS NOTARY:', createNotaryProcessReturn.updated_process.process_id);
this.setStateId(createNotaryProcessReturn.updated_process.current_process.states[0].state_id);
} catch (e) {
console.error(e);
}
try {
const createPrdUpdateReturn = await this.createPrdUpdate(this.processId!, this.stateId!);
await this.handleApiReturn(createPrdUpdateReturn);
} catch (e) {
throw new Error(`createPrdUpdate failed: ${e}`);
}
try {
const approveChangeReturn = await this.approveChange(this.processId!, this.stateId!);
await this.handleApiReturn(approveChangeReturn);
} catch (e) {
throw new Error(`approveChange failed: ${e}`);
}
this.processId = null;
this.stateId = null;
}
public async createAndSendProfileTx(userData: any): Promise<void> {
try {
const createProfileProcessReturn = await this.createProfileProcess(
userData,
);
await this.handleApiReturn(createProfileProcessReturn);
this.setProcessId(createProfileProcessReturn.updated_process!.process_id);
this.setStateId(createProfileProcessReturn.updated_process!.current_process.states[0].state_id);
} catch (e) {
throw new Error(`creatProfileProcess failed: ${e}`);
}
try {
const createPrdUpdateReturn = await this.createPrdUpdate(this.processId!, this.stateId!);
await this.handleApiReturn(createPrdUpdateReturn);
} catch (e) {
throw new Error(`createPrdUpdate failed: ${e}`);
}
try {
const approveChangeReturn = await this.approveChange(this.processId!, this.stateId!);
await this.handleApiReturn(approveChangeReturn);
this.processId = null;
this.stateId = null;
} catch (e) {
throw new Error(`approveChange failed: ${e}`);
}
}
public async createAndSendFolderTx(folderData: any): Promise<void> {
try {
await this.checkConnections([]);
} catch (e) {
throw e;
}
try {
console.log("folderData", folderData);
const createFolderProcessReturn = await this.createFolderProcess(
folderData,
);
await this.handleApiReturn(createFolderProcessReturn);
this.setProcessId(createFolderProcessReturn.updated_process!.process_id);
this.setStateId(createFolderProcessReturn.updated_process!.current_process.states[0].state_id);
} catch (e) {
throw new Error(`createFolderProcess failed: ${e}`);
}
try {
const createPrdUpdateReturn = await this.createPrdUpdate(this.processId!, this.stateId!);
await this.handleApiReturn(createPrdUpdateReturn);
} catch (e) {
throw new Error(`createPrdUpdate failed: ${e}`);
}
try {
const approveChangeReturn = await this.approveChange(this.processId!, this.stateId!);
await this.handleApiReturn(approveChangeReturn);
this.processId = null;
this.stateId = null;
} catch (e) {
throw new Error(`approveChange failed: ${e}`);
}
}
// public async getProfilesAttributes(): Promise<Record<string, Record<string, any>>> {
// const processes = await this.getProcesses();
// const profilesAttributes: Record<string, Record<string, any>> = {};
// for (const processId of Object.keys(processes)) {
// try {
// const process = await this.getProcess(processId);
// let lastCommitedState = this.getLastCommitedState(process);
// // If no committed state, use the first state
// if (!lastCommitedState) {
// lastCommitedState = process.states[0];
// }
// // Check if it's a profile process
// const description = await this.decryptAttribute(processId, lastCommitedState, 'description');
// if (!description || description !== "profile") {
// continue;
// }
// // Get all attributes for this profile
// if (lastCommitedState.pcd_commitment) {
// const attributesKeys = Object.keys(lastCommitedState.pcd_commitment);
// const decryptedAttributes: Record<string, any> = {};
// // Decrypt each attribute
// for (const key of attributesKeys) {
// try {
// const attribute = await this.decryptAttribute(processId, lastCommitedState, key);
// if (attribute !== null) {
// decryptedAttributes[key] = attribute;
// }
// } catch (e) {
// console.error(`Failed to decrypt attribute ${key}:`, e);
// }
// }
// // Add public data (like certification status)
// const publicData = this.getPublicData(process);
// if (publicData) {
// for (const [key, value] of Object.entries(publicData)) {
// decryptedAttributes[key] = value;
// }
// }
// // Only add profiles that have attributes
// if (Object.keys(decryptedAttributes).length > 0) {
// profilesAttributes[processId] = decryptedAttributes;
// }
// }
// } catch (e) {
// console.error(`Error processing process ${processId}:`, e);
// }
// }
// return profilesAttributes;
// }
// public async getNotaryAttributes(): Promise<Record<string, Record<string, any>>> {
// const processes = await this.getProcesses();
// const notaryAttributes: Record<string, Record<string, any>> = {};
// for (const [processId, process] of Object.entries(processes)) {
// try {
// const publicData = this.getPublicData(process);
// if (!publicData || !publicData.name || !publicData.lastName) {
// continue;
// }
// const attributes = {
// name: publicData.name,
// lastName: publicData.lastName,
// };
// notaryAttributes[processId] = attributes;
// } catch (e) {
// console.error(`Error processing process ${processId}:`, e);
// }
// }
// return notaryAttributes;
// }
// public async addDevice(processId: string) {
// const process = await this.getProcess(processId);
// const publicData = this.getPublicData(process);
// if (!publicData) {
// console.error('Invalid process: Failed to get public data');
// return;
// }
// const pairedAddresses = publicData.pairedAddresses;
// if (!pairedAddresses) {
// console.error('Not a pairing process, no pairedAddresses');
// return;
// }
// const roles = this.getRoles(process);
// if (!roles) {
// console.error('Invalid process: Failed to get roles');
// return;
// }
// if (!roles.pairing) {
// console.error('Not a pairing process, no pairing role');
// return;
// }
// const deviceAddress = await this.getDeviceAddress();
// this.device1 = true;
// publicData.pairedAddresses.push(deviceAddress);
// let newState = {
// pairedAddresses: publicData.pairedAddresses,
// }
// try {
// console.log("process", process);
// console.log("newState", newState);
// console.log("roles", roles);
// apiReturn = await this.updateProcess(process, newState, publicData, roles);
// } catch (e) {
// throw new Error(e);
// }
// try {
// const updatedProcess = apiReturn.updated_process.current_process;
// const newStateId = updatedProcess.states[0].state_id;
// await this.handleApiReturn(apiReturn);
// const modalService = await ModalService.getInstance();
// await modalService.openPairingConfirmationModal(roles, processId, newStateId);
// await modalService.confirmAddingDevice();
// } catch (e) {
// console.error('Failed to add device:', e);
// }
// }
}