ci: docker_tag=ext - Fix WASM compilation for web target
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 3m44s
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 3m44s
- 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
This commit is contained in:
parent
5da295be1a
commit
01b1b50d6b
35
Dockerfile
35
Dockerfile
@ -1,13 +1,46 @@
|
|||||||
# syntax=docker/dockerfile:1.4
|
# 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
|
FROM node:20-alpine
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Installation des dépendances nécessaires
|
# Installation des dépendances nécessaires
|
||||||
RUN apk update && apk add --no-cache git nginx
|
RUN apk update && apk add --no-cache git nginx
|
||||||
|
|
||||||
# Copie du projet (incluant pkg pré-compilé)
|
# Copy project files
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Copy the web-compiled WASM package
|
||||||
|
COPY --from=wasm-builder /build/ihm_client/pkg ./pkg
|
||||||
|
|
||||||
# Installation des dépendances Node.js
|
# Installation des dépendances Node.js
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
|
18
index.html
18
index.html
@ -17,9 +17,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- <script type="module" src="/src/index.ts"></script> -->
|
<!-- <script type="module" src="/src/index.ts"></script> -->
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { init } from '/src/router.ts';
|
// Initialize WASM first
|
||||||
|
import init, * as sdk from './pkg/sdk_client.js';
|
||||||
|
|
||||||
|
// Initialize the application
|
||||||
|
import { init as initRouter } from '/src/router.ts';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await init();
|
try {
|
||||||
|
// Initialize WASM
|
||||||
|
await init();
|
||||||
|
console.log('WASM initialized successfully');
|
||||||
|
|
||||||
|
// Initialize the router
|
||||||
|
await initRouter();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize application:', error);
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -10,7 +10,7 @@ import { prepareAndSendPairingTx } from './utils/sp-address.utils';
|
|||||||
import ModalService from './services/modal.service';
|
import ModalService from './services/modal.service';
|
||||||
import { MessageType } from './models/process.model';
|
import { MessageType } from './models/process.model';
|
||||||
import { splitPrivateData, isValid32ByteHex } from './utils/service.utils';
|
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 } = {
|
const routes: { [key: string]: string } = {
|
||||||
home: '/src/pages/home/home.html',
|
home: '/src/pages/home/home.html',
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { INotification } from '~/models/notification.model';
|
import { INotification } from '~/models/notification.model';
|
||||||
import { IProcess } from '~/models/process.model';
|
import { IProcess } from '~/models/process.model';
|
||||||
import { initWebsocket, sendMessage } from '../websockets';
|
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 ModalService from './modal.service';
|
||||||
import Database from './database.service';
|
import Database from './database.service';
|
||||||
import { navigate } from '../router';
|
import { navigate } from '../router';
|
||||||
@ -58,7 +58,8 @@ export default class Services {
|
|||||||
|
|
||||||
public async init(): Promise<void> {
|
public async init(): Promise<void> {
|
||||||
this.notifications = this.getNotifications();
|
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();
|
this.sdkClient.setup();
|
||||||
for (const wsurl of Object.values(BOOTSTRAPURL)) {
|
for (const wsurl of Object.values(BOOTSTRAPURL)) {
|
||||||
this.updateRelay(wsurl, '');
|
this.updateRelay(wsurl, '');
|
||||||
@ -87,7 +88,7 @@ export default class Services {
|
|||||||
*/
|
*/
|
||||||
public async connectAllRelays(): Promise<void> {
|
public async connectAllRelays(): Promise<void> {
|
||||||
const connectedUrls: string[] = [];
|
const connectedUrls: string[] = [];
|
||||||
|
|
||||||
// Connect to all relays
|
// Connect to all relays
|
||||||
for (const wsurl of Object.keys(this.relayAddresses)) {
|
for (const wsurl of Object.keys(this.relayAddresses)) {
|
||||||
try {
|
try {
|
||||||
@ -99,7 +100,7 @@ export default class Services {
|
|||||||
console.error(`Failed to connect to ${wsurl}:`, error);
|
console.error(`Failed to connect to ${wsurl}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for at least one handshake message if we have connections
|
// Wait for at least one handshake message if we have connections
|
||||||
if (connectedUrls.length > 0) {
|
if (connectedUrls.length > 0) {
|
||||||
await this.waitForHandshakeMessage();
|
await this.waitForHandshakeMessage();
|
||||||
@ -381,18 +382,18 @@ export default class Services {
|
|||||||
roles: Record<string, RoleDefinition>,
|
roles: Record<string, RoleDefinition>,
|
||||||
): Promise<ApiReturn> {
|
): Promise<ApiReturn> {
|
||||||
let relayAddress = this.getAllRelays()[0]?.spAddress;
|
let relayAddress = this.getAllRelays()[0]?.spAddress;
|
||||||
|
|
||||||
if (!relayAddress || relayAddress === '') {
|
if (!relayAddress || relayAddress === '') {
|
||||||
console.log('No relay address found, connecting to relays...');
|
console.log('No relay address found, connecting to relays...');
|
||||||
await this.connectAllRelays();
|
await this.connectAllRelays();
|
||||||
|
|
||||||
// After connectAllRelays completes, relay addresses should be updated
|
// After connectAllRelays completes, relay addresses should be updated
|
||||||
relayAddress = this.getAllRelays()[0]?.spAddress;
|
relayAddress = this.getAllRelays()[0]?.spAddress;
|
||||||
if (!relayAddress || relayAddress === '') {
|
if (!relayAddress || relayAddress === '') {
|
||||||
throw new Error('No relay address available after connecting to relays');
|
throw new Error('No relay address available after connecting to relays');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const feeRate = 1;
|
const feeRate = 1;
|
||||||
|
|
||||||
// We can't encode files as the rest because Uint8Array is not valid json
|
// 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
|
// TODO encoding of relatively large binaries (=> 1M) is a bit long now and blocking
|
||||||
const privateSplitData = this.splitData(privateData);
|
const privateSplitData = this.splitData(privateData);
|
||||||
const publicSplitData = this.splitData(publicData);
|
const publicSplitData = this.splitData(publicData);
|
||||||
const encodedPrivateData = {
|
const encodedPrivateData = {
|
||||||
...this.sdkClient.encode_json(privateSplitData.jsonCompatibleData),
|
...this.sdkClient.encode_json(privateSplitData.jsonCompatibleData),
|
||||||
...this.sdkClient.encode_binary(privateSplitData.binaryData)
|
...this.sdkClient.encode_binary(privateSplitData.binaryData)
|
||||||
};
|
};
|
||||||
const encodedPublicData = {
|
const encodedPublicData = {
|
||||||
...this.sdkClient.encode_json(publicSplitData.jsonCompatibleData),
|
...this.sdkClient.encode_json(publicSplitData.jsonCompatibleData),
|
||||||
...this.sdkClient.encode_binary(publicSplitData.binaryData)
|
...this.sdkClient.encode_binary(publicSplitData.binaryData)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -413,10 +414,10 @@ export default class Services {
|
|||||||
console.log('encoded data:', encodedPublicData);
|
console.log('encoded data:', encodedPublicData);
|
||||||
|
|
||||||
const result = this.sdkClient.create_new_process (
|
const result = this.sdkClient.create_new_process (
|
||||||
encodedPrivateData,
|
encodedPrivateData,
|
||||||
roles,
|
roles,
|
||||||
encodedPublicData,
|
encodedPublicData,
|
||||||
relayAddress,
|
relayAddress,
|
||||||
feeRate,
|
feeRate,
|
||||||
this.getAllMembers()
|
this.getAllMembers()
|
||||||
);
|
);
|
||||||
@ -440,12 +441,12 @@ export default class Services {
|
|||||||
}
|
}
|
||||||
const privateSplitData = this.splitData(privateData);
|
const privateSplitData = this.splitData(privateData);
|
||||||
const publicSplitData = this.splitData(publicData);
|
const publicSplitData = this.splitData(publicData);
|
||||||
const encodedPrivateData = {
|
const encodedPrivateData = {
|
||||||
...this.sdkClient.encode_json(privateSplitData.jsonCompatibleData),
|
...this.sdkClient.encode_json(privateSplitData.jsonCompatibleData),
|
||||||
...this.sdkClient.encode_binary(privateSplitData.binaryData)
|
...this.sdkClient.encode_binary(privateSplitData.binaryData)
|
||||||
};
|
};
|
||||||
const encodedPublicData = {
|
const encodedPublicData = {
|
||||||
...this.sdkClient.encode_json(publicSplitData.jsonCompatibleData),
|
...this.sdkClient.encode_json(publicSplitData.jsonCompatibleData),
|
||||||
...this.sdkClient.encode_binary(publicSplitData.binaryData)
|
...this.sdkClient.encode_binary(publicSplitData.binaryData)
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
@ -569,7 +570,7 @@ export default class Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async parseNewTx(newTxMsg: string) {
|
async parseNewTx(newTxMsg: string) {
|
||||||
const parsedMsg: NewTxMessage = JSON.parse(newTxMsg);
|
const parsedMsg: NewTxMessage = JSON.parse(newTxMsg);
|
||||||
if (parsedMsg.error !== null) {
|
if (parsedMsg.error !== null) {
|
||||||
console.error('Received error in new tx message:', parsedMsg.error);
|
console.error('Received error in new tx message:', parsedMsg.error);
|
||||||
return;
|
return;
|
||||||
@ -864,7 +865,7 @@ export default class Services {
|
|||||||
try {
|
try {
|
||||||
const device = await this.getDeviceFromDatabase();
|
const device = await this.getDeviceFromDatabase();
|
||||||
if (device) {
|
if (device) {
|
||||||
const pairedMember = device['paired_member'];
|
const pairedMember = device['paired_member'];
|
||||||
return pairedMember.sp_addresses;
|
return pairedMember.sp_addresses;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
@ -1051,7 +1052,7 @@ export default class Services {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Failed to save data to db: ${e}`);
|
console.error(`Failed to save data to db: ${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getBlobFromDb(hash: string): Promise<Blob | null> {
|
public async getBlobFromDb(hash: string): Promise<Blob | null> {
|
||||||
const db = await Database.getInstance();
|
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();
|
await this.restoreSecretsFromDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1219,7 +1220,7 @@ export default class Services {
|
|||||||
if (!key) {
|
if (!key) {
|
||||||
const roles = state.roles;
|
const roles = state.roles;
|
||||||
let hasAccess = false;
|
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 role of Object.values(roles)) {
|
||||||
for (const rule of Object.values(role.validation_rules)) {
|
for (const rule of Object.values(role.validation_rules)) {
|
||||||
if (rule.fields.includes(attribute)) {
|
if (rule.fields.includes(attribute)) {
|
||||||
@ -1241,7 +1242,7 @@ export default class Services {
|
|||||||
const maxRetries = 5;
|
const maxRetries = 5;
|
||||||
const retryDelay = 500; // delay in milliseconds
|
const retryDelay = 500; // delay in milliseconds
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
|
|
||||||
while ((!hash || !key) && retries < maxRetries) {
|
while ((!hash || !key) && retries < maxRetries) {
|
||||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||||
// Re-read hash and key after waiting
|
// Re-read hash and key after waiting
|
||||||
@ -1274,7 +1275,7 @@ export default class Services {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1316,7 +1317,7 @@ export default class Services {
|
|||||||
await this.resetDevice();
|
await this.resetDevice();
|
||||||
|
|
||||||
await this.saveDeviceInDatabase(device);
|
await this.saveDeviceInDatabase(device);
|
||||||
|
|
||||||
this.restoreDevice(device);
|
this.restoreDevice(device);
|
||||||
|
|
||||||
// TODO restore secrets and processes from file
|
// TODO restore secrets and processes from file
|
||||||
@ -1495,7 +1496,7 @@ export default class Services {
|
|||||||
private async waitForHandshakeMessage(timeoutMs: number = 10000): Promise<void> {
|
private async waitForHandshakeMessage(timeoutMs: number = 10000): Promise<void> {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const pollInterval = 100; // Check every 100ms
|
const pollInterval = 100; // Check every 100ms
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const checkForHandshake = () => {
|
const checkForHandshake = () => {
|
||||||
// Check if we have any members or any relays (indicating handshake was received)
|
// Check if we have any members or any relays (indicating handshake was received)
|
||||||
@ -1504,17 +1505,17 @@ export default class Services {
|
|||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check timeout
|
// Check timeout
|
||||||
if (Date.now() - startTime >= timeoutMs) {
|
if (Date.now() - startTime >= timeoutMs) {
|
||||||
reject(new Error(`No handshake message received after ${timeoutMs}ms timeout`));
|
reject(new Error(`No handshake message received after ${timeoutMs}ms timeout`));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Continue polling
|
// Continue polling
|
||||||
setTimeout(checkForHandshake, pollInterval);
|
setTimeout(checkForHandshake, pollInterval);
|
||||||
};
|
};
|
||||||
|
|
||||||
checkForHandshake();
|
checkForHandshake();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1565,7 +1566,7 @@ export default class Services {
|
|||||||
this.sendCommitMessage(JSON.stringify(content));
|
this.sendCommitMessage(JSON.stringify(content));
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRoles(process: Process): Record<string, RoleDefinition> | null {
|
public getRoles(process: Process): Record<string, RoleDefinition> | null {
|
||||||
const lastCommitedState = this.getLastCommitedState(process);
|
const lastCommitedState = this.getLastCommitedState(process);
|
||||||
if (lastCommitedState && lastCommitedState.roles && Object.keys(lastCommitedState.roles).length != 0) {
|
if (lastCommitedState && lastCommitedState.roles && Object.keys(lastCommitedState.roles).length != 0) {
|
||||||
@ -1745,7 +1746,7 @@ export default class Services {
|
|||||||
return process.states[index + 1];
|
return process.states[index + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isPairingProcess(roles: Record<string, RoleDefinition>): boolean {
|
public isPairingProcess(roles: Record<string, RoleDefinition>): boolean {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user