Demo site for webassembly#376
Demo site for webassembly#376Sébastien Duquette (sduquette-devolutions) wants to merge 8 commits intomasterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a standalone Angular demo application under wrappers/wasm/devolutions-crypto-wasm-demo to showcase functionality from @devolutions/devolutions-crypto-web (WASM), with pages for encryption, secret sharing, password hashing, utilities, and asymmetric operations.
Changes:
- Introduces a new Angular app scaffold (config files, build/serve setup, Tailwind/PostCSS, global styles, assets).
- Adds an
EncryptionServicewrapper that lazy-loads/awaits the WASM module and exposes crypto helpers via an “inner” module. - Implements multiple standalone feature components + routing for the demo UI.
Reviewed changes
Copilot reviewed 29 out of 32 changed files in this pull request and generated 21 comments.
Show a summary per file
| File | Description |
|---|---|
| wrappers/wasm/devolutions-crypto-wasm-demo/tsconfig.json | Base TS/Angular compiler configuration for the demo app. |
| wrappers/wasm/devolutions-crypto-wasm-demo/tsconfig.app.json | App-specific TS configuration and entrypoint inclusion. |
| wrappers/wasm/devolutions-crypto-wasm-demo/angular.json | Angular CLI project configuration (build, serve, assets, styles). |
| wrappers/wasm/devolutions-crypto-wasm-demo/package.json | Demo app dependencies/scripts (Angular, Tailwind, crypto WASM lib). |
| wrappers/wasm/devolutions-crypto-wasm-demo/postcss.config.js | PostCSS config enabling Tailwind pipeline. |
| wrappers/wasm/devolutions-crypto-wasm-demo/README.md | Basic Angular CLI-generated README for the demo app. |
| wrappers/wasm/devolutions-crypto-wasm-demo/.gitignore | Ignores Angular/Node build outputs and tooling artifacts. |
| wrappers/wasm/devolutions-crypto-wasm-demo/.editorconfig | Formatting conventions for the demo app files. |
| wrappers/wasm/devolutions-crypto-wasm-demo/public/favicon.ico | Demo app favicon asset. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/index.html | Demo app HTML host document. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/main.ts | Bootstraps the standalone Angular application. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/styles.css | Global styling + Tailwind import and layout styles. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/app.config.ts | App-level providers (router, zone change detection). |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/app.routes.ts | Route definitions for the demo pages. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/app.component.ts | Root shell component for sidebar/navigation. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/app.component.html | Root layout with sidebar and overlay + router outlet. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/app.component.css | Root component stylesheet (currently empty). |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/shared/shared.component.ts | DOM helpers to open/close sidebar/overlay. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/service/encryption.service.ts | Lazy-load service to await WASM readiness. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/service/encryption.inner.service.ts | Thin wrapper around @devolutions/devolutions-crypto-web + WASM init. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/encryption/encryption.component.ts | Symmetric encrypt/decrypt demo logic. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/encryption/encryption.component.html | Symmetric encrypt/decrypt UI. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/secret-sharing/secret-sharing.component.ts | Shamir secret sharing demo logic (generate/join). |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/secret-sharing/secret-sharing.component.html | Shamir secret sharing UI. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/password/password.component.ts | Password hashing + verify demo logic. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/password/password.component.html | Password hashing + verify UI. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/utilities/utilities.component.ts | Base64 encode/decode + key generation + PBKDF2 derive demo logic. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/utilities/utilities.component.html | Utilities UI. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/asymmetric/asymmetric.component.ts | Argon2 params + keypair + key exchange + asymmetric encrypt/decrypt demo logic. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/asymmetric/asymmetric.component.html | Asymmetric crypto UI. |
| wrappers/wasm/devolutions-crypto-wasm-demo/src/app/model/encryption.ts | Adds an Encryption model class (currently unused). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
wrappers/wasm/devolutions-crypto-wasm-demo/src/app/secret-sharing/secret-sharing.component.html
Outdated
Show resolved
Hide resolved
wrappers/wasm/devolutions-crypto-wasm-demo/src/app/asymmetric/asymmetric.component.html
Outdated
Show resolved
Hide resolved
wrappers/wasm/devolutions-crypto-wasm-demo/src/app/utilities/utilities.component.html
Outdated
Show resolved
Hide resolved
| const nbShares: string = this.generateSharesForm.value.nbShares; | ||
| const threshold: string = this.generateSharesForm.value.threshold; | ||
| const secretLengthString: string = this.generateSharesForm.value.secretLength; | ||
| if (nbShares === null || nbShares === '' || threshold === null || threshold === '') { return; } | ||
|
|
||
| const secretLength: number | undefined = (secretLengthString === null || secretLengthString === '') ? undefined : Number(secretLengthString); | ||
| const generatedKeys: Uint8Array[] = service.generateSharedKey(Number(nbShares), Number(threshold), secretLength); | ||
|
|
There was a problem hiding this comment.
Number(...) is used to parse secretLength, nbShares, and threshold without checking for NaN or validating ranges. Non-numeric input (or invalid values like threshold > shares) will flow into generateSharedKey(...) and may throw or produce incorrect output. Add validation (e.g., parseInt + isNaN checks + range checks) before calling the WASM API.
| const nbShares: string = this.generateSharesForm.value.nbShares; | |
| const threshold: string = this.generateSharesForm.value.threshold; | |
| const secretLengthString: string = this.generateSharesForm.value.secretLength; | |
| if (nbShares === null || nbShares === '' || threshold === null || threshold === '') { return; } | |
| const secretLength: number | undefined = (secretLengthString === null || secretLengthString === '') ? undefined : Number(secretLengthString); | |
| const generatedKeys: Uint8Array[] = service.generateSharedKey(Number(nbShares), Number(threshold), secretLength); | |
| const nbSharesRaw: string = this.generateSharesForm.value.nbShares; | |
| const thresholdRaw: string = this.generateSharesForm.value.threshold; | |
| const secretLengthString: string = this.generateSharesForm.value.secretLength; | |
| if (nbSharesRaw === null || nbSharesRaw === '' || thresholdRaw === null || thresholdRaw === '') { return; } | |
| const nbSharesParsed: number = parseInt(nbSharesRaw, 10); | |
| const thresholdParsed: number = parseInt(thresholdRaw, 10); | |
| if (Number.isNaN(nbSharesParsed) || Number.isNaN(thresholdParsed)) { return; } | |
| if (nbSharesParsed <= 0 || thresholdParsed <= 0) { return; } | |
| if (thresholdParsed > nbSharesParsed) { return; } | |
| let secretLength: number | undefined; | |
| if (secretLengthString !== null && secretLengthString !== '') { | |
| const secretLengthParsed: number = parseInt(secretLengthString, 10); | |
| if (Number.isNaN(secretLengthParsed) || secretLengthParsed <= 0) { return; } | |
| secretLength = secretLengthParsed; | |
| } else { | |
| secretLength = undefined; | |
| } | |
| const generatedKeys: Uint8Array[] = service.generateSharedKey(nbSharesParsed, thresholdParsed, secretLength); |
wrappers/wasm/devolutions-crypto-wasm-demo/src/app/secret-sharing/secret-sharing.component.html
Outdated
Show resolved
Hide resolved
| generatedKeysBase64: string[] = []; | ||
| joinnedSharesToShow: string[] = []; | ||
| joinnedShares: Uint8Array[] = []; |
There was a problem hiding this comment.
joinnedSharesToShow / joinnedShares appear to be misspellings of “joined…”. Since these names are used in the component and template, correcting them now will improve readability and avoid spreading the typo.
| parameters.iterations = Number(argon2Iterations); | ||
| } | ||
| if (argon2Lanes) { | ||
| parameters.lanes = Number(argon2Lanes); | ||
| } | ||
| if (argon2Length) { | ||
| parameters.length = Number(argon2Length); | ||
| } | ||
| if (argon2Memory) { | ||
| parameters.memory = Number(argon2Memory); |
There was a problem hiding this comment.
Argon2 parameter fields are converted with Number(...) without validation. If the user enters non-numeric input, these properties become NaN, and encoding parameters.bytes may yield invalid parameters. Validate parsed values (integer/positive/range) before setting them.
| parameters.iterations = Number(argon2Iterations); | |
| } | |
| if (argon2Lanes) { | |
| parameters.lanes = Number(argon2Lanes); | |
| } | |
| if (argon2Length) { | |
| parameters.length = Number(argon2Length); | |
| } | |
| if (argon2Memory) { | |
| parameters.memory = Number(argon2Memory); | |
| const iterationsValue = Number(argon2Iterations); | |
| if (Number.isFinite(iterationsValue) && Number.isInteger(iterationsValue) && iterationsValue > 0) { | |
| parameters.iterations = iterationsValue; | |
| } | |
| } | |
| if (argon2Lanes) { | |
| const lanesValue = Number(argon2Lanes); | |
| if (Number.isFinite(lanesValue) && Number.isInteger(lanesValue) && lanesValue > 0) { | |
| parameters.lanes = lanesValue; | |
| } | |
| } | |
| if (argon2Length) { | |
| const lengthValue = Number(argon2Length); | |
| if (Number.isFinite(lengthValue) && Number.isInteger(lengthValue) && lengthValue > 0) { | |
| parameters.length = lengthValue; | |
| } | |
| } | |
| if (argon2Memory) { | |
| const memoryValue = Number(argon2Memory); | |
| if (Number.isFinite(memoryValue) && Number.isInteger(memoryValue) && memoryValue > 0) { | |
| parameters.memory = memoryValue; | |
| } |
| (document.getElementById('mySidebar') as HTMLElement).style.display = 'flex'; | ||
| (document.getElementById('myOverlay') as HTMLElement).style.display = 'block'; | ||
| } | ||
|
|
||
| export function w3_close() { | ||
| (document.getElementById('mySidebar') as HTMLElement).style.display = 'none'; | ||
| (document.getElementById('myOverlay') as HTMLElement).style.display = 'none'; |
There was a problem hiding this comment.
w3_close() has the same unchecked getElementById(...) as HTMLElement dereference as w3_open(), which can throw if the element isn’t present or IDs aren’t unique. Add a null check (or switch to Angular refs/Renderer2) before mutating .style.
| (document.getElementById('mySidebar') as HTMLElement).style.display = 'flex'; | |
| (document.getElementById('myOverlay') as HTMLElement).style.display = 'block'; | |
| } | |
| export function w3_close() { | |
| (document.getElementById('mySidebar') as HTMLElement).style.display = 'none'; | |
| (document.getElementById('myOverlay') as HTMLElement).style.display = 'none'; | |
| const sidebar = document.getElementById('mySidebar'); | |
| if (sidebar instanceof HTMLElement) { | |
| sidebar.style.display = 'flex'; | |
| } | |
| const overlay = document.getElementById('myOverlay'); | |
| if (overlay instanceof HTMLElement) { | |
| overlay.style.display = 'block'; | |
| } | |
| } | |
| export function w3_close() { | |
| const sidebar = document.getElementById('mySidebar'); | |
| if (sidebar instanceof HTMLElement) { | |
| sidebar.style.display = 'none'; | |
| } | |
| const overlay = document.getElementById('myOverlay'); | |
| if (overlay instanceof HTMLElement) { | |
| overlay.style.display = 'none'; | |
| } |
No description provided.