From 01b1b50d6bf91e05cd31dbdb68725874deb02533 Mon Sep 17 00:00:00 2001 From: 4NK CI Bot Date: Thu, 18 Sep 2025 15:41:05 +0000 Subject: [PATCH] ci: docker_tag=ext - Fix WASM compilation for web target - Recompile sdk_client WASM with --target web for ES modules - Update Dockerfile to build WASM in web target instead of bundler - Modify index.html to initialize WASM before router - Update service.ts and router.ts to use ES module imports - Fix 'module is not defined' error in browser --- Dockerfile | 35 ++++++++++++++++++++++- index.html | 18 ++++++++++-- src/router.ts | 2 +- src/services/service.ts | 63 +++++++++++++++++++++-------------------- 4 files changed, 83 insertions(+), 35 deletions(-) diff --git a/Dockerfile b/Dockerfile index e5f70e5..bbdc010 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,46 @@ # syntax=docker/dockerfile:1.4 + +# Stage 1: Build WASM for web target +FROM rust:1.82-alpine AS wasm-builder +WORKDIR /build + +# Install dependencies for WASM compilation +RUN apk update && apk add --no-cache git openssh-client curl nodejs npm build-base pkgconfig clang llvm musl-dev + +# Install wasm-bindgen-cli +RUN cargo install wasm-bindgen-cli --version 0.2.103 --locked && rustup target add wasm32-unknown-unknown + +# Setup SSH for git clone +RUN mkdir -p /root/.ssh && ssh-keyscan git.4nkweb.com >> /root/.ssh/known_hosts + +# Copy project files +COPY . ihm_client/ + +# Clone and build sdk_client for web target +RUN --mount=type=ssh git clone -b dev ssh://git@git.4nkweb.com/4nk/sdk_client.git +WORKDIR /build/sdk_client + +# Build WASM for web target (ES modules) +RUN cargo build --target wasm32-unknown-unknown --profile dev && \ + wasm-bindgen target/wasm32-unknown-unknown/debug/sdk_client.wasm \ + --out-dir /build/ihm_client/pkg \ + --typescript \ + --target web \ + --debug + +# Stage 2: Final application FROM node:20-alpine WORKDIR /app # Installation des dépendances nécessaires RUN apk update && apk add --no-cache git nginx -# Copie du projet (incluant pkg pré-compilé) +# Copy project files COPY . . +# Copy the web-compiled WASM package +COPY --from=wasm-builder /build/ihm_client/pkg ./pkg + # Installation des dépendances Node.js RUN npm install diff --git a/index.html b/index.html index f7f60fb..5cc1b9c 100755 --- a/index.html +++ b/index.html @@ -17,9 +17,23 @@ diff --git a/src/router.ts b/src/router.ts index 38dec1e..ec38abb 100755 --- a/src/router.ts +++ b/src/router.ts @@ -10,7 +10,7 @@ import { prepareAndSendPairingTx } from './utils/sp-address.utils'; import ModalService from './services/modal.service'; import { MessageType } from './models/process.model'; import { splitPrivateData, isValid32ByteHex } from './utils/service.utils'; -import { MerkleProofResult } from 'pkg/sdk_client'; +import { MerkleProofResult } from 'pkg/sdk_client.js'; const routes: { [key: string]: string } = { home: '/src/pages/home/home.html', diff --git a/src/services/service.ts b/src/services/service.ts index 5e67fb9..4e931a7 100755 --- a/src/services/service.ts +++ b/src/services/service.ts @@ -1,7 +1,7 @@ import { INotification } from '~/models/notification.model'; import { IProcess } from '~/models/process.model'; import { initWebsocket, sendMessage } from '../websockets'; -import { ApiReturn, Device, HandshakeMessage, Member, MerkleProofResult, NewTxMessage, OutPointProcessMap, Process, ProcessState, RoleDefinition, SecretsStore, UserDiff } from '../../pkg/sdk_client'; +import { ApiReturn, Device, HandshakeMessage, Member, MerkleProofResult, NewTxMessage, OutPointProcessMap, Process, ProcessState, RoleDefinition, SecretsStore, UserDiff } from '../../pkg/sdk_client.js'; import ModalService from './modal.service'; import Database from './database.service'; import { navigate } from '../router'; @@ -58,7 +58,8 @@ export default class Services { public async init(): Promise { this.notifications = this.getNotifications(); - this.sdkClient = await import('../../pkg/sdk_client'); + // SDK is now imported as ES module at the top of the file + this.sdkClient = await import('../../pkg/sdk_client.js'); this.sdkClient.setup(); for (const wsurl of Object.values(BOOTSTRAPURL)) { this.updateRelay(wsurl, ''); @@ -87,7 +88,7 @@ export default class Services { */ public async connectAllRelays(): Promise { const connectedUrls: string[] = []; - + // Connect to all relays for (const wsurl of Object.keys(this.relayAddresses)) { try { @@ -99,7 +100,7 @@ export default class Services { console.error(`Failed to connect to ${wsurl}:`, error); } } - + // Wait for at least one handshake message if we have connections if (connectedUrls.length > 0) { await this.waitForHandshakeMessage(); @@ -381,18 +382,18 @@ export default class Services { roles: Record, ): Promise { let relayAddress = this.getAllRelays()[0]?.spAddress; - + if (!relayAddress || relayAddress === '') { console.log('No relay address found, connecting to relays...'); await this.connectAllRelays(); - + // After connectAllRelays completes, relay addresses should be updated relayAddress = this.getAllRelays()[0]?.spAddress; if (!relayAddress || relayAddress === '') { throw new Error('No relay address available after connecting to relays'); } } - + const feeRate = 1; // We can't encode files as the rest because Uint8Array is not valid json @@ -400,12 +401,12 @@ export default class Services { // TODO encoding of relatively large binaries (=> 1M) is a bit long now and blocking const privateSplitData = this.splitData(privateData); const publicSplitData = this.splitData(publicData); - const encodedPrivateData = { - ...this.sdkClient.encode_json(privateSplitData.jsonCompatibleData), + const encodedPrivateData = { + ...this.sdkClient.encode_json(privateSplitData.jsonCompatibleData), ...this.sdkClient.encode_binary(privateSplitData.binaryData) }; - const encodedPublicData = { - ...this.sdkClient.encode_json(publicSplitData.jsonCompatibleData), + const encodedPublicData = { + ...this.sdkClient.encode_json(publicSplitData.jsonCompatibleData), ...this.sdkClient.encode_binary(publicSplitData.binaryData) }; @@ -413,10 +414,10 @@ export default class Services { console.log('encoded data:', encodedPublicData); const result = this.sdkClient.create_new_process ( - encodedPrivateData, + encodedPrivateData, roles, encodedPublicData, - relayAddress, + relayAddress, feeRate, this.getAllMembers() ); @@ -440,12 +441,12 @@ export default class Services { } const privateSplitData = this.splitData(privateData); const publicSplitData = this.splitData(publicData); - const encodedPrivateData = { - ...this.sdkClient.encode_json(privateSplitData.jsonCompatibleData), + const encodedPrivateData = { + ...this.sdkClient.encode_json(privateSplitData.jsonCompatibleData), ...this.sdkClient.encode_binary(privateSplitData.binaryData) }; - const encodedPublicData = { - ...this.sdkClient.encode_json(publicSplitData.jsonCompatibleData), + const encodedPublicData = { + ...this.sdkClient.encode_json(publicSplitData.jsonCompatibleData), ...this.sdkClient.encode_binary(publicSplitData.binaryData) }; try { @@ -569,7 +570,7 @@ export default class Services { } async parseNewTx(newTxMsg: string) { - const parsedMsg: NewTxMessage = JSON.parse(newTxMsg); + const parsedMsg: NewTxMessage = JSON.parse(newTxMsg); if (parsedMsg.error !== null) { console.error('Received error in new tx message:', parsedMsg.error); return; @@ -864,7 +865,7 @@ export default class Services { try { const device = await this.getDeviceFromDatabase(); if (device) { - const pairedMember = device['paired_member']; + const pairedMember = device['paired_member']; return pairedMember.sp_addresses; } else { return null; @@ -1051,7 +1052,7 @@ export default class Services { } catch (e) { console.error(`Failed to save data to db: ${e}`); } - } + } public async getBlobFromDb(hash: string): Promise { const db = await Database.getInstance(); @@ -1178,7 +1179,7 @@ export default class Services { }); } - // Now we can transfer them to memory + // Now we can transfer them to memory await this.restoreSecretsFromDB(); } @@ -1219,7 +1220,7 @@ export default class Services { if (!key) { const roles = state.roles; let hasAccess = false; - // If we're not supposed to have access to this attribute, ignore + // If we're not supposed to have access to this attribute, ignore for (const role of Object.values(roles)) { for (const rule of Object.values(role.validation_rules)) { if (rule.fields.includes(attribute)) { @@ -1241,7 +1242,7 @@ export default class Services { 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 @@ -1274,7 +1275,7 @@ export default class Services { } } } - + return null; } @@ -1316,7 +1317,7 @@ export default class Services { await this.resetDevice(); await this.saveDeviceInDatabase(device); - + this.restoreDevice(device); // TODO restore secrets and processes from file @@ -1495,7 +1496,7 @@ export default class Services { private async waitForHandshakeMessage(timeoutMs: number = 10000): Promise { const startTime = Date.now(); const pollInterval = 100; // Check every 100ms - + return new Promise((resolve, reject) => { const checkForHandshake = () => { // Check if we have any members or any relays (indicating handshake was received) @@ -1504,17 +1505,17 @@ export default class Services { resolve(); return; } - + // Check timeout if (Date.now() - startTime >= timeoutMs) { reject(new Error(`No handshake message received after ${timeoutMs}ms timeout`)); return; } - + // Continue polling setTimeout(checkForHandshake, pollInterval); }; - + checkForHandshake(); }); } @@ -1565,7 +1566,7 @@ export default class Services { this.sendCommitMessage(JSON.stringify(content)); }, 1000) } - + public getRoles(process: Process): Record | null { const lastCommitedState = this.getLastCommitedState(process); if (lastCommitedState && lastCommitedState.roles && Object.keys(lastCommitedState.roles).length != 0) { @@ -1745,7 +1746,7 @@ export default class Services { return process.states[index + 1]; } - return null; + return null; } public isPairingProcess(roles: Record): boolean {