Add some debug methods for pairing workflow
Some checks failed
Build and Push to Registry / build-and-push (push) Failing after 44s

This commit is contained in:
NicolasCantu 2025-10-31 14:10:26 +01:00
parent 79633ed923
commit 7ea4ef1920
3 changed files with 423 additions and 235 deletions

View File

@ -401,13 +401,13 @@ export async function registerAllListeners() {
// Retrieve the state for the process
const process = await services.getProcess(processId);
if (!process) {
throw new Error('Can\'t find process');
throw new Error("Can't find process");
}
const state = services.getStateFromId(process, stateId);
await services.checkConnections(process, stateId);
let res: Record<string, any> = {};
const res: Record<string, any> = {};
if (state) {
// Decrypt all the data we have the key for
for (const attribute of Object.keys(state.pcd_commitment)) {
@ -424,10 +424,10 @@ export async function registerAllListeners() {
}
window.parent.postMessage(
{
{
type: MessageType.DATA_RETRIEVED,
data: res,
messageId: event.data.messageId
messageId: event.data.messageId,
},
event.origin
);
@ -500,6 +500,8 @@ export async function registerAllListeners() {
if (!services.isPaired()) {
const errorMsg = 'Device not paired';
console.log('Device not paired - running diagnosis...');
await services.diagnosePairingState();
errorResponse(errorMsg, event.origin, event.data.messageId);
return;
}

View File

@ -123,6 +123,7 @@ export default class Services {
this.relayReadyResolver = null;
this.relayReadyPromise = null;
}
}
public async addWebsocketConnection(url: string): Promise<void> {
console.log('Opening new websocket connection');
@ -171,7 +172,21 @@ export default class Services {
public isPaired(): boolean {
try {
return this.sdkClient.is_paired();
const result = this.sdkClient.is_paired();
console.log('isPaired() called, result:', result);
// Additional debugging: check device state
try {
const device = this.dumpDeviceFromMemory();
console.log('Current device state:', {
pairing_process_commitment: device.pairing_process_commitment,
paired_member: device.paired_member
});
} catch (deviceError) {
console.error('Failed to dump device for debugging:', deviceError);
}
return result;
} catch (e) {
throw new Error(`isPaired ~ Error: ${e}`);
}
@ -230,7 +245,7 @@ export default class Services {
// We will take the roles from the last state, wheter it's commited or not
public async checkConnections(process: Process, stateId: string | null = null): Promise<void> {
if (process.states.length < 2) {
throw new Error('Process doesn\'t have any state yet');
throw new Error("Process doesn't have any state yet");
}
let roles: Record<string, RoleDefinition> | null = null;
if (!stateId) {
@ -251,12 +266,29 @@ export default class Services {
}
}
}
if (members.size === 0) {
// This must be a pairing process
// Check if we have a pairedAddresses in the public data
const publicData = process.states[0]?.public_data;
if (!publicData || !publicData['pairedAddresses']) {
let publicData: Record<string, any> | null = null;
if (!stateId) {
publicData = process.states[process.states.length - 2]?.public_data;
} else {
publicData = process.states.find(state => state.state_id === stateId)?.public_data || null;
}
// If pairedAddresses is not in the current state, look in previous states
if (!publicData?.['pairedAddresses']) {
// Look for pairedAddresses in previous states
for (let i = process.states.length - 1; i >= 0; i--) {
const state = process.states[i];
if (state.public_data && state.public_data['pairedAddresses']) {
publicData = state.public_data;
break;
}
}
}
if (!publicData?.['pairedAddresses']) {
throw new Error('Not a pairing process');
}
const decodedAddresses = this.decodeValue(publicData['pairedAddresses']);
@ -268,15 +300,19 @@ export default class Services {
// Ensure the amount is available before proceeding
await this.getTokensFromFaucet();
let unconnectedAddresses = new Set<string>();
const myAddress = this.getDeviceAddress();
const unconnectedAddresses = new Set<string>();
const myAddress = await this.getDeviceAddress();
for (const member of Array.from(members)) {
const sp_addresses = member.sp_addresses;
if (!sp_addresses || sp_addresses.length === 0) continue;
if (!sp_addresses || sp_addresses.length === 0) {
continue;
}
for (const address of 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;
if (await this.getSecretForAddress(address) === null) {
if (address === myAddress) {
continue;
}
if ((await this.getSecretForAddress(address)) === null) {
unconnectedAddresses.add(address);
}
}
@ -623,10 +659,17 @@ export default class Services {
try {
const parsedTx = this.sdkClient.parse_new_tx(newTxMsg, 0, membersList);
if (parsedTx) {
if (parsedTx && (parsedTx.partial_tx || parsedTx.new_tx_to_send || parsedTx.secrets || parsedTx.updated_process)) {
try {
await this.handleApiReturn(parsedTx);
const newDevice = this.dumpDeviceFromMemory();
// Preserve pairing_process_commitment from existing device
const existingDevice = await this.getDeviceFromDatabase();
if (existingDevice && existingDevice.pairing_process_commitment) {
newDevice.pairing_process_commitment = existingDevice.pairing_process_commitment;
}
await this.saveDeviceInDatabase(newDevice);
} catch (e) {
console.error('Failed to update device with new tx');
@ -639,6 +682,19 @@ export default class Services {
public async handleApiReturn(apiReturn: ApiReturn) {
console.log(apiReturn);
// Skip processing if apiReturn is empty or contains only null values
if (!apiReturn || Object.keys(apiReturn).length === 0) {
console.log('Skipping empty apiReturn');
return;
}
// Check if all values are null
const hasValidData = Object.values(apiReturn).some(value => value !== null && value !== undefined);
if (!hasValidData) {
console.log('Skipping apiReturn with only null values');
return;
}
if (apiReturn.partial_tx) {
try {
const res = this.sdkClient.sign_transaction(apiReturn.partial_tx);
@ -708,6 +764,21 @@ export default class Services {
console.error('Failed to save diffs to db:', e);
}
}
// Check if this is a pairing process that's ready for confirmation
const existingDevice = await this.getDeviceFromDatabase();
if (existingDevice && existingDevice.pairing_process_commitment === processId) {
// This is our pairing process, check if it has paired addresses
const lastState = updatedProcess.current_process.states[updatedProcess.current_process.states.length - 1];
if (lastState && lastState.public_data && lastState.public_data['pairedAddresses']) {
console.log('Pairing process updated with paired addresses, confirming pairing...');
try {
await this.confirmPairing();
} catch (e) {
console.error('Failed to auto-confirm pairing:', e);
}
}
}
}
if (apiReturn.push_to_storage && apiReturn.push_to_storage.length != 0) {
@ -756,13 +827,100 @@ export default class Services {
public async confirmPairing() {
try {
// Is the wasm paired?
const pairingId = this.getPairingProcessId();
// TODO confirm that the pairing process id is known, commited
// Get the pairing process ID from database
const existingDevice = await this.getDeviceFromDatabase();
if (!existingDevice || !existingDevice.pairing_process_commitment) {
console.error('No pairing process commitment found');
return;
}
const pairingProcessId = existingDevice.pairing_process_commitment;
// Get the pairing process to extract paired addresses
const myPairingProcess = await this.getProcess(pairingProcessId);
if (!myPairingProcess) {
console.error('Unknown pairing process');
return;
}
// Try to get committed state first, fallback to current state
let myPairingState = this.getLastCommitedState(myPairingProcess);
if (!myPairingState && myPairingProcess.states.length > 0) {
// If no committed state, use the current state
myPairingState = myPairingProcess.states[myPairingProcess.states.length - 1];
console.log('Using current state instead of committed state');
}
if (!myPairingState) {
console.error('No state found in pairing process');
return;
}
const encodedSpAddressList = myPairingState.public_data['pairedAddresses'];
if (!encodedSpAddressList) {
console.error('No paired addresses found in state');
return;
}
const spAddressList = this.decodeValue(encodedSpAddressList);
if (spAddressList.length === 0) {
console.error('Empty pairedAddresses');
return;
}
// Test parsing côté Rust
console.log('Checking if test_process_id_parsing is available:', typeof this.sdkClient.test_process_id_parsing);
try {
if (this.sdkClient.test_process_id_parsing) {
const rustParseResult = this.sdkClient.test_process_id_parsing(pairingProcessId);
console.log('Rust parsing test result:', rustParseResult);
} else {
console.error('test_process_id_parsing function not found in sdkClient');
console.log('Available functions:', Object.keys(this.sdkClient).filter(key => typeof this.sdkClient[key] === 'function'));
}
} catch (rustParseError) {
console.error('Rust parsing test failed:', rustParseError);
}
this.sdkClient.unpair_device(); // Clear any existing pairing
try {
this.sdkClient.pair_device(pairingProcessId, spAddressList);
console.log('pair_device() call succeeded');
} catch (pairError) {
console.error('pair_device() failed:', pairError);
throw pairError;
}
// Verify pairing was successful
const isPairedAfterPairing = this.sdkClient.is_paired();
console.log('Is paired after pair_device call:', isPairedAfterPairing);
// Save the updated device
const newDevice = this.dumpDeviceFromMemory();
console.log('Device from memory after pairing:', {
pairing_process_commitment: newDevice.pairing_process_commitment,
paired_member: newDevice.paired_member
});
// IMPORTANT: Only set pairing_process_commitment if WASM pairing succeeded
if (isPairedAfterPairing) {
console.log('WASM pairing succeeded, keeping WASM commitment');
// Don't override - use what WASM set
} else {
console.log('WASM pairing failed, manually setting commitment');
newDevice.pairing_process_commitment = pairingProcessId;
}
await this.saveDeviceInDatabase(newDevice);
// Final verification
const finalIsPaired = this.sdkClient.is_paired();
console.log('Final is_paired status:', finalIsPaired);
console.log('Device successfully paired with process:', pairingProcessId);
} catch (e) {
console.error('Failed to confirm pairing');
console.error('Failed to confirm pairing:', e);
return;
}
}
@ -852,17 +1010,34 @@ export default class Services {
const db = await Database.getInstance();
const walletStore = 'wallet';
try {
console.log('Saving device to database:', {
pairing_process_commitment: device.pairing_process_commitment,
paired_member: device.paired_member
});
const prevDevice = await this.getDeviceFromDatabase();
if (prevDevice) {
console.log('Previous device found, deleting...');
await db.deleteObject(walletStore, "1");
}
await db.addObject({
storeName: walletStore,
object: { pre_id: '1', device },
key: null,
});
console.log('Device saved successfully');
// Verify save
const savedDevice = await this.getDeviceFromDatabase();
console.log('Verification - saved device:', {
pairing_process_commitment: savedDevice?.pairing_process_commitment,
paired_member: savedDevice?.paired_member
});
} catch (e) {
console.error(e);
console.error('Error saving device to database:', e);
}
}

View File

@ -1,216 +1,227 @@
import Services from '../services/service';
import { getCorrectDOM } from './html.utils';
import { addSubscription } from './subscription.utils';
import QRCode from 'qrcode';
//Copy Address
export async function copyToClipboard(fullAddress: string) {
try {
await navigator.clipboard.writeText(fullAddress);
alert('Adresse copiée dans le presse-papiers !');
} catch (err) {
console.error('Failed to copy the address: ', err);
}
}
//Generate emojis list
export function generateEmojiList(): string[] {
const emojiRanges = [
[0x1f600, 0x1f64f],
[0x1f300, 0x1f5ff],
[0x1f680, 0x1f6ff],
[0x1f700, 0x1f77f],
];
const emojiList: string[] = [];
for (const range of emojiRanges) {
const [start, end] = range;
for (let i = start; i <= end && emojiList.length < 256; i++) {
emojiList.push(String.fromCodePoint(i));
}
if (emojiList.length >= 256) {
break;
}
}
return emojiList.slice(0, 256);
}
//Adress to emojis
export async function addressToEmoji(text: string): Promise<string> {
//Adress to Hash
const encoder = new TextEncoder();
const data = encoder.encode(text);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hash = new Uint8Array(hashBuffer);
const bytes = hash.slice(-4);
//Hash slice to emojis
const emojiList = generateEmojiList();
const emojis = Array.from(bytes)
.map((byte) => emojiList[byte])
.join('');
return emojis;
}
//Get emojis from other device
async function emojisPairingRequest() {
try {
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
const urlParams: URLSearchParams = new URLSearchParams(window.location.search);
const sp_adress: string | null = urlParams.get('sp_address');
if (!sp_adress) {
// console.error("No 'sp_adress' parameter found in the URL.");
return;
}
const emojis = await addressToEmoji(sp_adress);
const emojiDisplay = container?.querySelector('.pairing-request');
if (emojiDisplay) {
emojiDisplay.textContent = '(Request from: ' + emojis + ')';
}
} catch (err) {
console.error(err);
}
}
// Display address emojis and other device emojis
export async function displayEmojis(text: string) {
console.log('🚀 ~ Services ~ adressToEmoji');
try {
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
const emojis = await addressToEmoji(text);
const emojiDisplay = container?.querySelector('.emoji-display');
if (emojiDisplay) {
emojiDisplay.textContent = emojis;
}
emojisPairingRequest();
initAddressInput();
} catch (err) {
console.error(err);
}
}
// Verify Other address
export function initAddressInput() {
const container = getCorrectDOM('login-4nk-component') as HTMLElement
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;
const actionButton = container.querySelector('#actionButton') as HTMLButtonElement;
addSubscription(addressInput, 'input', async () => {
let address = addressInput.value;
// Vérifie si l'adresse est une URL
try {
const url = new URL(address);
// Si c'est une URL valide, extraire le paramètre sp_address
const urlParams = new URLSearchParams(url.search);
const extractedAddress = urlParams.get('sp_address') || ''; // Prend sp_address ou une chaîne vide
if (extractedAddress) {
address = extractedAddress;
addressInput.value = address; // Met à jour l'input pour afficher uniquement l'adresse extraite
}
} catch (e) {
// Si ce n'est pas une URL valide, on garde l'adresse originale
console.log("Ce n'est pas une URL valide, on garde l'adresse originale.");
}
if (address) {
const emojis = await addressToEmoji(address);
if (emojiDisplay) {
emojiDisplay.innerHTML = emojis;
}
if (okButton) {
okButton.style.display = 'inline-block';
}
} else {
if (emojiDisplay) {
emojiDisplay.innerHTML = '';
}
if (okButton) {
okButton.style.display = 'none';
}
}
});
if (createButton) {
addSubscription(createButton, 'click', () => {
onCreateButtonClick();
});
}
}
async function onCreateButtonClick() {
try {
await prepareAndSendPairingTx();
const service = await Services.getInstance();
await service.confirmPairing();
} catch (e) {
console.error(`onCreateButtonClick error: ${e}`);
}
}
export async function prepareAndSendPairingTx(): Promise<void> {
const service = await Services.getInstance();
try {
await service.checkConnections([]);
} catch (e) {
throw e;
}
try {
const relayAddress = service.getAllRelays();
const createPairingProcessReturn = await service.createPairingProcess(
"",
[],
);
if (!createPairingProcessReturn.updated_process) {
throw new Error('createPairingProcess returned an empty new process');
}
service.setProcessId(createPairingProcessReturn.updated_process.process_id);
service.setStateId(createPairingProcessReturn.updated_process.current_process.states[0].state_id);
await service.handleApiReturn(createPairingProcessReturn);
} catch (err) {
console.error(err);
}
}
export async function generateQRCode(spAddress: string) {
try {
const container = getCorrectDOM('login-4nk-component') as HTMLElement
const currentUrl = 'https://' + window.location.host;
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';
}
} catch (err) {
console.error(err);
}
import Services from '../services/service';
import { getCorrectDOM } from './html.utils';
import { addSubscription } from './subscription.utils';
import QRCode from 'qrcode';
//Copy Address
export async function copyToClipboard(fullAddress: string) {
try {
await navigator.clipboard.writeText(fullAddress);
alert('Adresse copiée dans le presse-papiers !');
} catch (err) {
console.error('Failed to copy the address: ', err);
}
}
//Generate emojis list
export function generateEmojiList(): string[] {
const emojiRanges = [
[0x1f600, 0x1f64f],
[0x1f300, 0x1f5ff],
[0x1f680, 0x1f6ff],
[0x1f700, 0x1f77f],
];
const emojiList: string[] = [];
for (const range of emojiRanges) {
const [start, end] = range;
for (let i = start; i <= end && emojiList.length < 256; i++) {
emojiList.push(String.fromCodePoint(i));
}
if (emojiList.length >= 256) {
break;
}
}
return emojiList.slice(0, 256);
}
//Adress to emojis
export async function addressToEmoji(text: string): Promise<string> {
//Adress to Hash
const encoder = new TextEncoder();
const data = encoder.encode(text);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hash = new Uint8Array(hashBuffer);
const bytes = hash.slice(-4);
//Hash slice to emojis
const emojiList = generateEmojiList();
const emojis = Array.from(bytes)
.map((byte) => emojiList[byte])
.join('');
return emojis;
}
//Get emojis from other device
async function emojisPairingRequest() {
try {
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
const urlParams: URLSearchParams = new URLSearchParams(window.location.search);
const sp_adress: string | null = urlParams.get('sp_address');
if (!sp_adress) {
// console.error("No 'sp_adress' parameter found in the URL.");
return;
}
const emojis = await addressToEmoji(sp_adress);
const emojiDisplay = container?.querySelector('.pairing-request');
if (emojiDisplay) {
emojiDisplay.textContent = '(Request from: ' + emojis + ')';
}
} catch (err) {
console.error(err);
}
}
// Display address emojis and other device emojis
export async function displayEmojis(text: string) {
console.log('🚀 ~ Services ~ adressToEmoji');
try {
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
const emojis = await addressToEmoji(text);
const emojiDisplay = container?.querySelector('.emoji-display');
if (emojiDisplay) {
emojiDisplay.textContent = emojis;
}
emojisPairingRequest();
initAddressInput();
} catch (err) {
console.error(err);
}
}
// Verify Other address
export function initAddressInput() {
const container = getCorrectDOM('login-4nk-component') as HTMLElement
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;
const actionButton = container.querySelector('#actionButton') as HTMLButtonElement;
addSubscription(addressInput, 'input', async () => {
let address = addressInput.value;
// Vérifie si l'adresse est une URL
try {
const url = new URL(address);
// Si c'est une URL valide, extraire le paramètre sp_address
const urlParams = new URLSearchParams(url.search);
const extractedAddress = urlParams.get('sp_address') || ''; // Prend sp_address ou une chaîne vide
if (extractedAddress) {
address = extractedAddress;
addressInput.value = address; // Met à jour l'input pour afficher uniquement l'adresse extraite
}
} catch (e) {
// Si ce n'est pas une URL valide, on garde l'adresse originale
console.log("Ce n'est pas une URL valide, on garde l'adresse originale.");
}
if (address) {
const emojis = await addressToEmoji(address);
if (emojiDisplay) {
emojiDisplay.innerHTML = emojis;
}
if (okButton) {
okButton.style.display = 'inline-block';
}
} else {
if (emojiDisplay) {
emojiDisplay.innerHTML = '';
}
if (okButton) {
okButton.style.display = 'none';
}
}
});
if (createButton) {
addSubscription(createButton, 'click', () => {
onCreateButtonClick();
});
}
}
async function onCreateButtonClick() {
try {
await prepareAndSendPairingTx();
// Don't call confirmPairing immediately - it will be called when the pairing process is complete
console.log('Pairing process initiated. Waiting for completion...');
} catch (e) {
console.error(`onCreateButtonClick error: ${e}`);
}
}
export async function prepareAndSendPairingTx(): Promise<void> {
const service = await Services.getInstance();
try {
const relayAddress = service.getAllRelays();
const createPairingProcessReturn = await service.createPairingProcess(
"",
[],
);
if (!createPairingProcessReturn.updated_process) {
throw new Error('createPairingProcess returned an empty new process');
}
try {
await service.checkConnections(createPairingProcessReturn.updated_process.current_process, createPairingProcessReturn.updated_process.current_process.states[0].state_id);
} catch (e) {
throw e;
}
service.setProcessId(createPairingProcessReturn.updated_process.process_id);
service.setStateId(createPairingProcessReturn.updated_process.current_process.states[0].state_id);
// Update device.pairing_process_commitment with the process_id
try {
const currentDevice = await service.getDeviceFromDatabase();
if (currentDevice) {
currentDevice.pairing_process_commitment = createPairingProcessReturn.updated_process.process_id;
await service.saveDeviceInDatabase(currentDevice);
}
} catch (err) {
console.error('Failed to update device pairing_process_commitment:', err);
}
await service.handleApiReturn(createPairingProcessReturn);
} catch (err) {
console.error(err);
}
}
export async function generateQRCode(spAddress: string) {
try {
const container = getCorrectDOM('login-4nk-component') as HTMLElement
const currentUrl = 'https://' + window.location.host;
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';
}
} catch (err) {
console.error(err);
}
}