diff --git a/.github/workflows/qrcode.yml b/.github/workflows/qrcode.yml
new file mode 100644
index 0000000000..22420b4c7e
--- /dev/null
+++ b/.github/workflows/qrcode.yml
@@ -0,0 +1,29 @@
+name: qrcode
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ paths:
+ - rust/qrcode/**
+ - .github/workflows/qrcode.yml
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ rust-qrcode:
+ runs-on: ubuntu-24.04
+ container: ghcr.io/dfinity/icp-dev-env-rust:1.0.0
+ env:
+ ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ steps:
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
+ - name: Deploy and test
+ working-directory: rust/qrcode
+ run: |
+ icp network start -d
+ icp deploy
+ make test
diff --git a/rust/qrcode/.devcontainer/devcontainer.json b/rust/qrcode/.devcontainer/devcontainer.json
deleted file mode 100644
index ebb0b8bcc6..0000000000
--- a/rust/qrcode/.devcontainer/devcontainer.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "name": "ICP Dev Environment",
- "image": "ghcr.io/dfinity/icp-dev-env-slim:22",
- "forwardPorts": [4943, 5173],
- "portsAttributes": {
- "4943": {
- "label": "dfx",
- "onAutoForward": "ignore"
- },
- "5173": {
- "label": "vite",
- "onAutoForward": "openBrowser"
- }
- },
- "customizations": {
- "vscode": {
- "extensions": ["dfinity-foundation.vscode-motoko"]
- }
- }
-}
diff --git a/rust/qrcode/.gitignore b/rust/qrcode/.gitignore
index 938bc33a56..bd54fc1381 100644
--- a/rust/qrcode/.gitignore
+++ b/rust/qrcode/.gitignore
@@ -7,11 +7,8 @@
.DS_Store
**/.DS_Store
-# dfx temporary files
-.dfx/
-
-# generated files
-src/declarations/
+# generated bindings
+**/src/bindings/
# rust
target/
@@ -19,6 +16,3 @@ target/
# frontend code
node_modules/
dist/
-
-# environment variables
-.env
diff --git a/rust/qrcode/BUILD.md b/rust/qrcode/BUILD.md
deleted file mode 100644
index 24cfcb7547..0000000000
--- a/rust/qrcode/BUILD.md
+++ /dev/null
@@ -1,113 +0,0 @@
-# Continue building locally
-
-Projects deployed through ICP Ninja are temporary; they will only be live for 20 minutes before they are removed. The command-line tool `dfx` can be used to continue building your ICP Ninja project locally and deploy it to the mainnet.
-
-To migrate your ICP Ninja project off of the web browser and develop it locally, follow these steps.
-
-### 1. Install developer tools.
-
-You can install the developer tools natively or use Dev Containers.
-
-#### Option 1: Natively install developer tools
-
-> Installing `dfx` natively is currently only supported on macOS and Linux systems. On Windows, it is recommended to use the Dev Containers option.
-
-1. Install `dfx` with the following command:
-
-```
-
-sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)"
-
-```
-
-> On Apple Silicon (e.g., Apple M1 chip), make sure you have Rosetta installed (`softwareupdate --install-rosetta`).
-
-2. [Install NodeJS](https://nodejs.org/en/download/package-manager).
-
-3. For Rust projects, you will also need to:
-
-- Install [Rust](https://doc.rust-lang.org/cargo/getting-started/installation.html#install-rust-and-cargo): `curl https://sh.rustup.rs -sSf | sh`
-
-- Install [candid-extractor](https://crates.io/crates/candid-extractor): `cargo install candid-extractor`
-
-4. For Motoko projects, you will also need to:
-
-- Install the Motoko package manager [Mops](https://docs.mops.one/quick-start#2-install-mops-cli): `npm i -g ic-mops`
-
-Lastly, navigate into your project's directory that you downloaded from ICP Ninja.
-
-#### Option 2: Dev Containers
-
-Continue building your projects locally by installing the [Dev Container extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) for VS Code and [Docker](https://docs.docker.com/engine/install/).
-
-Make sure Docker is running, then navigate into your project's directory that you downloaded from ICP Ninja and start the Dev Container by selecting `Dev-Containers: Reopen in Container` in VS Code's command palette (F1 or Ctrl/Cmd+Shift+P).
-
-> Note that local development ports (e.g. the ports used by `dfx` or `vite`) are forwarded from the Dev Container to your local machine. In the VS code terminal, use Ctrl/Cmd+Click on the displayed local URLs to open them in your browser. To view the current port mappings, click the "Ports" tab in the VS Code terminal window.
-
-### 2. Start the local development environment.
-
-```
-dfx start --background
-```
-
-### 3. Create a local developer identity.
-
-To manage your project's canisters, it is recommended that you create a local [developer identity](https://internetcomputer.org/docs/building-apps/getting-started/identities) rather than use the `dfx` default identity that is not stored securely.
-
-To create a new identity, run the commands:
-
-```
-
-dfx identity new IDENTITY_NAME
-
-dfx identity use IDENTITY_NAME
-
-```
-
-Replace `IDENTITY_NAME` with your preferred identity name. The first command `dfx start --background` starts the local `dfx` processes, then `dfx identity new` will create a new identity and return your identity's seed phase. Be sure to save this in a safe, secure location.
-
-The third command `dfx identity use` will tell `dfx` to use your new identity as the active identity. Any canister smart contracts created after running `dfx identity use` will be owned and controlled by the active identity.
-
-Your identity will have a principal ID associated with it. Principal IDs are used to identify different entities on ICP, such as users and canisters.
-
-[Learn more about ICP developer identities](https://internetcomputer.org/docs/building-apps/getting-started/identities).
-
-### 4. Deploy the project locally.
-
-Deploy your project to your local developer environment with:
-
-```
-npm install
-dfx deploy
-
-```
-
-Your project will be hosted on your local machine. The local canister URLs for your project will be shown in the terminal window as output of the `dfx deploy` command. You can open these URLs in your web browser to view the local instance of your project.
-
-### 5. Obtain cycles.
-
-To deploy your project to the mainnet for long-term public accessibility, first you will need [cycles](https://internetcomputer.org/docs/building-apps/getting-started/tokens-and-cycles). Cycles are used to pay for the resources your project uses on the mainnet, such as storage and compute.
-
-> This cost model is known as ICP's [reverse gas model](https://internetcomputer.org/docs/building-apps/essentials/gas-cost), where developers pay for their project's gas fees rather than users pay for their own gas fees. This model provides an enhanced end user experience since they do not need to hold tokens or sign transactions when using a dapp deployed on ICP.
-
-> Learn how much a project may cost by using the [pricing calculator](https://internetcomputer.org/docs/building-apps/essentials/cost-estimations-and-examples).
-
-Cycles can be obtained through [converting ICP tokens into cycles using `dfx`](https://internetcomputer.org/docs/building-apps/developer-tools/dfx/dfx-cycles#dfx-cycles-convert).
-
-### 6. Deploy to the mainnet.
-
-Once you have cycles, run the command:
-
-```
-
-dfx deploy --network ic
-
-```
-
-After your project has been deployed to the mainnet, it will continuously require cycles to pay for the resources it uses. You will need to [top up](https://internetcomputer.org/docs/building-apps/canister-management/topping-up) your project's canisters or set up automatic cycles management through a service such as [CycleOps](https://cycleops.dev/).
-
-> If your project's canisters run out of cycles, they will be removed from the network.
-
-## Additional examples
-
-Additional code examples and sample applications can be found in the [DFINITY examples repo](https://github.com/dfinity/examples).
diff --git a/rust/qrcode/Cargo.lock b/rust/qrcode/Cargo.lock
index 93571b47cf..3deccf31d8 100644
--- a/rust/qrcode/Cargo.lock
+++ b/rust/qrcode/Cargo.lock
@@ -570,8 +570,8 @@ dependencies = [
]
[[package]]
-name = "qrcode_backend"
-version = "1.1.0"
+name = "backend"
+version = "0.1.0"
dependencies = [
"anyhow",
"candid",
diff --git a/rust/qrcode/Cargo.toml b/rust/qrcode/Cargo.toml
index f3c6691db3..d1e49e317a 100644
--- a/rust/qrcode/Cargo.toml
+++ b/rust/qrcode/Cargo.toml
@@ -1,3 +1,3 @@
[workspace]
-members = ["src/qrcode_backend"]
+members = ["backend"]
resolver = "2"
diff --git a/rust/qrcode/Makefile b/rust/qrcode/Makefile
index 1eabc01a94..01f0e466e7 100644
--- a/rust/qrcode/Makefile
+++ b/rust/qrcode/Makefile
@@ -1,32 +1,44 @@
-.PHONY: all
-all: deploy
+.PHONY: test
-.PHONY: node_modules
-.SILENT: node_modules
-node_modules:
- npm install
+test:
+ @echo "=== Test 1: qrcode_query without logo or gradient ==="
+ @result=$$(icp canister call --query backend qrcode_query '("test", record {add_logo=false; add_gradient=false; add_transparency=null})') && \
+ echo "$$result" && \
+ echo "$$result" | grep -q 'Image = blob' && \
+ echo "PASS" || (echo "FAIL" && exit 1)
-.PHONY: deploy
-.SILENT: deploy
-deploy: node_modules
- dfx deploy
+ @echo "=== Test 2: qrcode_query with logo, no gradient ==="
+ @result=$$(icp canister call --query backend qrcode_query '("test", record {add_logo=true; add_gradient=false; add_transparency=null})') && \
+ echo "$$result" && \
+ echo "$$result" | grep -q 'Image = blob' && \
+ echo "PASS" || (echo "FAIL" && exit 1)
-.PHONY: test
-.SILENT: test
-test: deploy
- # Wait at least 2 seconds.
- sleep 2
- # Validate the image is generated as a query.
- dfx canister call qrcode_backend qrcode_query '("test", record {add_gradient=false; add_logo=false})' | fgrep -q 'Image = blob' && echo PASS
- dfx canister call qrcode_backend qrcode_query '("test", record {add_gradient=false; add_logo=true})' | fgrep -q 'Image = blob' && echo PASS
- dfx canister call qrcode_backend qrcode_query '("test", record {add_gradient=true; add_logo=false})' | fgrep -q 'Image = blob' && echo PASS
- dfx canister call qrcode_backend qrcode_query '("test", record {add_gradient=true; add_logo=true})' | fgrep -q 'Image = blob' && echo PASS
- dfx canister call qrcode_backend qrcode_query '("test", record {add_gradient=true; add_logo=true; add_trasparency=opt true})' | fgrep -q 'Image = blob' && echo PASS
- dfx canister call qrcode_backend qrcode_query '("test", record {add_gradient=true; add_logo=true; add_trasparency=opt false})' | fgrep -q 'Image = blob' && echo PASS
- # Validate the image is generated as an update call.
- dfx canister call qrcode_backend qrcode '("test", record {add_gradient=true; add_logo=true})' | fgrep -q 'Image = blob' && echo PASS
+ @echo "=== Test 3: qrcode_query with gradient, no logo ==="
+ @result=$$(icp canister call --query backend qrcode_query '("test", record {add_logo=false; add_gradient=true; add_transparency=null})') && \
+ echo "$$result" && \
+ echo "$$result" | grep -q 'Image = blob' && \
+ echo "PASS" || (echo "FAIL" && exit 1)
+
+ @echo "=== Test 4: qrcode_query with logo and gradient ==="
+ @result=$$(icp canister call --query backend qrcode_query '("test", record {add_logo=true; add_gradient=true; add_transparency=null})') && \
+ echo "$$result" && \
+ echo "$$result" | grep -q 'Image = blob' && \
+ echo "PASS" || (echo "FAIL" && exit 1)
+
+ @echo "=== Test 5: qrcode_query with transparency enabled ==="
+ @result=$$(icp canister call --query backend qrcode_query '("test", record {add_logo=true; add_gradient=true; add_transparency=opt true})') && \
+ echo "$$result" && \
+ echo "$$result" | grep -q 'Image = blob' && \
+ echo "PASS" || (echo "FAIL" && exit 1)
+
+ @echo "=== Test 6: qrcode_query with transparency disabled ==="
+ @result=$$(icp canister call --query backend qrcode_query '("test", record {add_logo=true; add_gradient=true; add_transparency=opt false})') && \
+ echo "$$result" && \
+ echo "$$result" | grep -q 'Image = blob' && \
+ echo "PASS" || (echo "FAIL" && exit 1)
-.PHONY: clean
-.SILENT: clean
-clean:
- rm -fr .dfx
+ @echo "=== Test 7: qrcode update call (waits for consensus) ==="
+ @result=$$(icp canister call backend qrcode '("test", record {add_logo=true; add_gradient=true; add_transparency=null})') && \
+ echo "$$result" && \
+ echo "$$result" | grep -q 'Image = blob' && \
+ echo "PASS" || (echo "FAIL" && exit 1)
diff --git a/rust/qrcode/README.md b/rust/qrcode/README.md
index d9981a1def..5167b7127e 100644
--- a/rust/qrcode/README.md
+++ b/rust/qrcode/README.md
@@ -4,74 +4,55 @@ This example shows that an Internet Computer dapp can perform a long-running com
This is possible due to a unique feature called Deterministic Time Slicing (DTS), which automatically divides long computations into smaller slices executed across multiple blocks.
Developers can write long-running code as usual and don't require anything special to take advantage of DTS, as demonstrated in this example.
-You try the live version of the dapp running on the mainnet here: [https://khpe2-4qaaa-aaaao-a2fnq-cai.icp0.io/](https://khpe2-4qaaa-aaaao-a2fnq-cai.icp0.io/).
+You can try the live version of the dapp running on the mainnet here: [https://khpe2-4qaaa-aaaao-a2fnq-cai.icp0.io/](https://khpe2-4qaaa-aaaao-a2fnq-cai.icp0.io/).
-## Deploying from ICP Ninja
+## How it works
+
+The frontend consists of an HTML page with a form where users can enter text for the QR code and choose various options.
+When the user clicks the "Generate!" button, a JavaScript handler initiates a call to the backend canister.
-When viewing this project in ICP Ninja, you can deploy it directly to the mainnet for free by clicking "Run" in the upper right corner. Open this project in ICP Ninja:
+The backend, written in Rust, uses the `qrcode-generator` and `image` crates to create a QR code from user text.
+It also performs some image processing to add the Internet Computer logo and a color gradient to the final result.
+Note that the amount of computational work may be significant for large images.
-[](https://icp.ninja/i?g=https://github.com/dfinity/examples/rust/qrcode)
+For educational purposes, the backend offers two public endpoints for QR code generation: one for updates and another for queries.
+Currently, DTS is supported for updates, but not for queries.
+As a result, the update endpoint has a larger instruction limit compared to the query endpoint and thus can handle larger images.
-## Build and deploy from the command-line
-This example requires an installation of:
+## Build and deploy from the command line
-- [x] Install the [IC SDK](https://internetcomputer.org/docs/current/developer-docs/getting-started/install).
-- [x] Make sure your Rust version is up-to-date (e.g., run `rustup update`).
-- [x] Add the `wasm32` target to your rust installation (by running `rustup target add wasm32-unknown-unknown`).
-- [x] Install `node.js` dependencies by running `npm install`.
-- [x] Clone the example dapp project: `git clone https://github.com/dfinity/examples`
+### Prerequisites
-## Running locally
+- Node.js
+- icp-cli: `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm`
+- Rust with the `wasm32-unknown-unknown` target: `rustup target add wasm32-unknown-unknown`
-Navigate into the folder containing the project's files and start a local instance of the replica with the command:
+### Install
-```sh
+```bash
+git clone https://github.com/dfinity/examples
cd examples/rust/qrcode
-dfx start --clean --background
```
-You can omit the `--background` argument if you want to see log messages of the dapp.
-
-Now you can build and deploy the dapp with a single command:
+### Deploy and test
```bash
-dfx deploy
+icp network start -d
+icp deploy
+make test
+icp network stop
```
-If you see any error, it might be worthwhile to consult the [developer forum](https://forum.dfinity.org/).
-In case of successful deployment, you will see an output with local URLs:
+If you want to interact with the frontend during development, run the Vite dev server for hot reload:
```bash
-Deployed canisters.
-URLs:
- Frontend canister via browser
- qrcode_frontend: ...
- Backend canister via Candid interface:
- qrcode_backend: ...
+npm run dev --prefix frontend
```
Navigate to the frontend URL in your browser and you'll be able to interact with the dapp.

-
-## How it works
-
-The initial code of the dapp was autogenerated by `dfx` using the standard frontend/backend template.
-
-The frontend consists of an HTML page with a form where users can enter text for the QR code and choose various options.
-When the user clicks the "Generate!" button, a JavaScript handler initiates a call to the backend canister.
-The heavy lifting of this call is managed by `candid`, `js-agent`, and `dfx`, which automatically generates a JavaScript object from the backend's Candid interface.
-That object contains `async` functions for each of the backend's endpoints, and the button handler uses them to make the calls.
-
-The backend, written in Rust, uses the `qrcode-generator` and `image` crates to create a QR code from user text.
-It also performs some image processing, to add the Internet Computer logo and a color gradient to the final result.
-Note the amount of computational work may be significant for large images.
-
-For educational purposes, the backend offers two public endpoints for QR code generation: one for updates and another for queries.
-Currently, DTS is supported for updates, but not for queries.
-As a result, the update endpoint has a larger instruction limit compared to the query endpoint and thus can handle larger images.
-
## Security considerations and best practices
-If you base your application on this example, it is recommended that you familiarize yourself with and adhere to the [security best practices](https://internetcomputer.org/docs/building-apps/security/overview) for developing on ICP. This example may not implement all the best practices.
+If you base your application on this example, it is recommended that you familiarize yourself with and adhere to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for developing on ICP. This example may not implement all the best practices.
diff --git a/rust/qrcode/src/qrcode_backend/Cargo.toml b/rust/qrcode/backend/Cargo.toml
similarity index 87%
rename from rust/qrcode/src/qrcode_backend/Cargo.toml
rename to rust/qrcode/backend/Cargo.toml
index 33bb517ab3..c50e24c943 100644
--- a/rust/qrcode/src/qrcode_backend/Cargo.toml
+++ b/rust/qrcode/backend/Cargo.toml
@@ -1,7 +1,7 @@
[package]
+name = "backend"
+version = "0.1.0"
edition = "2021"
-name = "qrcode_backend"
-version = "1.1.0"
[lib]
crate-type = ["cdylib"]
diff --git a/rust/qrcode/src/qrcode_backend/assets/logo_transparent.png b/rust/qrcode/backend/assets/logo_transparent.png
similarity index 100%
rename from rust/qrcode/src/qrcode_backend/assets/logo_transparent.png
rename to rust/qrcode/backend/assets/logo_transparent.png
diff --git a/rust/qrcode/src/qrcode_backend/assets/logo_white.png b/rust/qrcode/backend/assets/logo_white.png
similarity index 100%
rename from rust/qrcode/src/qrcode_backend/assets/logo_white.png
rename to rust/qrcode/backend/assets/logo_white.png
diff --git a/rust/qrcode/src/qrcode_backend/qrcode_backend.did b/rust/qrcode/backend/backend.did
similarity index 100%
rename from rust/qrcode/src/qrcode_backend/qrcode_backend.did
rename to rust/qrcode/backend/backend.did
diff --git a/rust/qrcode/src/qrcode_backend/src/core.rs b/rust/qrcode/backend/src/core.rs
similarity index 100%
rename from rust/qrcode/src/qrcode_backend/src/core.rs
rename to rust/qrcode/backend/src/core.rs
diff --git a/rust/qrcode/src/qrcode_backend/src/lib.rs b/rust/qrcode/backend/src/lib.rs
similarity index 100%
rename from rust/qrcode/src/qrcode_backend/src/lib.rs
rename to rust/qrcode/backend/src/lib.rs
diff --git a/rust/qrcode/dfx.json b/rust/qrcode/dfx.json
deleted file mode 100644
index 6c6a951aee..0000000000
--- a/rust/qrcode/dfx.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "canisters": {
- "qrcode_backend": {
- "candid": "src/qrcode_backend/qrcode_backend.did",
- "package": "qrcode_backend",
- "type": "rust",
- "metadata": [
- {
- "name": "candid:service"
- }
- ]
- },
- "qrcode_frontend": {
- "dependencies": ["qrcode_backend"],
- "frontend": {
- "entrypoint": "src/qrcode_frontend/src/index.html"
- },
- "source": ["dist/qrcode_frontend/"],
- "type": "assets"
- }
- },
- "output_env_file": ".env",
- "version": 1
-}
diff --git a/rust/qrcode/src/qrcode_frontend/assets/favicon.ico b/rust/qrcode/frontend/favicon.ico
similarity index 100%
rename from rust/qrcode/src/qrcode_frontend/assets/favicon.ico
rename to rust/qrcode/frontend/favicon.ico
diff --git a/rust/qrcode/frontend/index.html b/rust/qrcode/frontend/index.html
new file mode 100644
index 0000000000..495c4932a2
--- /dev/null
+++ b/rust/qrcode/frontend/index.html
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+ QR code generator
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rust/qrcode/frontend/package.json b/rust/qrcode/frontend/package.json
new file mode 100644
index 0000000000..38b23697d7
--- /dev/null
+++ b/rust/qrcode/frontend/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "frontend",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "prebuild": "npm i --include=dev",
+ "build": "vite build",
+ "dev": "vite"
+ },
+ "dependencies": {
+ "@icp-sdk/core": "~5.2.0"
+ },
+ "devDependencies": {
+ "@icp-sdk/bindgen": "~0.3.0",
+ "vite": "5.4.11"
+ }
+}
diff --git a/rust/qrcode/frontend/src/actor.js b/rust/qrcode/frontend/src/actor.js
new file mode 100644
index 0000000000..322a0601b4
--- /dev/null
+++ b/rust/qrcode/frontend/src/actor.js
@@ -0,0 +1,26 @@
+import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env";
+import { createActor } from "./bindings/backend";
+
+// The ic_env cookie is set by the asset canister (SDK >=0.30.2) on all HTML
+// responses. It contains the replica root key and any PUBLIC_* canister
+// environment variables. In dev mode the vite dev server sets the same cookie
+// via Set-Cookie header (see vite.config.js).
+const canisterEnv = safeGetCanisterEnv();
+
+// Resolve canister ID: cookie (icp-cli + dev server) → env var (dfx build-time)
+const canisterId =
+ canisterEnv?.["PUBLIC_CANISTER_ID:backend"] ??
+ process.env.CANISTER_ID_BACKEND;
+
+if (!canisterId) {
+ throw new Error(
+ "Canister ID for 'backend' not found. Run 'icp deploy' first."
+ );
+}
+
+export const backend = createActor(canisterId, {
+ agentOptions: {
+ host: window.location.origin,
+ rootKey: canisterEnv?.IC_ROOT_KEY,
+ },
+});
diff --git a/rust/qrcode/src/qrcode_frontend/src/index.js b/rust/qrcode/frontend/src/main.js
similarity index 81%
rename from rust/qrcode/src/qrcode_frontend/src/index.js
rename to rust/qrcode/frontend/src/main.js
index 1e600ca748..f6d38eb326 100644
--- a/rust/qrcode/src/qrcode_frontend/src/index.js
+++ b/rust/qrcode/frontend/src/main.js
@@ -1,8 +1,4 @@
-import { createActor, canisterId } from "../../declarations/qrcode_backend";
-
-const qrcode_backend = createActor(canisterId, {
- agentOptions: { host: window.location.origin },
-});
+import { backend } from "./actor.js";
document.getElementById("generate").onclick = onGenerateButtonClick;
@@ -22,27 +18,27 @@ async function onGenerateButtonClick(event) {
linkElement.href = "";
try {
- // Get the use input.
+ // Get the user input.
const text = document.getElementById("text").value.toString();
const options = {
add_logo: document.getElementById("logo").checked,
add_gradient: document.getElementById("gradient").checked,
add_transparency: [document.getElementById("transparent").checked],
- }
+ };
// Call the backend and wait for the result.
let result;
if (document.getElementById("consensus").checked) {
- result = await qrcode_backend.qrcode(text, options);
+ result = await backend.qrcode(text, options);
} else {
- result = await qrcode_backend.qrcode_query(text, options);
+ result = await backend.qrcode_query(text, options);
}
if ("Err" in result) {
throw result.Err;
}
- // Convert the image blob in to a data URL.
+ // Convert the image blob into a data URL.
const image = result.Image;
const blob = new Blob([image], { type: "image/png" });
const url = await convertToDataUrl(blob);
@@ -58,17 +54,16 @@ async function onGenerateButtonClick(event) {
buttonElement.disabled = false;
return false;
-
}
// Converts the given blob into a data url such that it can be assigned as a
-// target of a link of as an image source.
+// target of a link or as an image source.
function convertToDataUrl(blob) {
return new Promise((resolve, _) => {
const fileReader = new FileReader();
fileReader.readAsDataURL(blob);
fileReader.onloadend = function () {
resolve(fileReader.result);
- }
+ };
});
}
diff --git a/rust/qrcode/frontend/vite.config.js b/rust/qrcode/frontend/vite.config.js
new file mode 100644
index 0000000000..cfa898e9d0
--- /dev/null
+++ b/rust/qrcode/frontend/vite.config.js
@@ -0,0 +1,56 @@
+import { defineConfig, loadEnv } from "vite";
+import { execSync } from "child_process";
+import { icpBindgen } from "@icp-sdk/bindgen/plugins/vite";
+
+function getDevServerConfig() {
+ // Try icp-cli first
+ try {
+ const canisterId = execSync("icp canister status backend -e local -i", {
+ encoding: "utf-8",
+ stdio: "pipe",
+ }).trim();
+ const networkStatus = JSON.parse(
+ execSync("icp network status --json", {
+ encoding: "utf-8",
+ stdio: "pipe",
+ })
+ );
+ return {
+ headers: {
+ "Set-Cookie": `ic_env=${encodeURIComponent(
+ `ic_root_key=${networkStatus.root_key}&PUBLIC_CANISTER_ID:backend=${canisterId}`
+ )}; SameSite=Lax;`,
+ },
+ proxy: {
+ "/api": { target: "http://127.0.0.1:8000", changeOrigin: true },
+ },
+ };
+ } catch {}
+
+ throw new Error(
+ "No local network running. Start with:\n icp network start -d && icp deploy"
+ );
+}
+
+export default defineConfig(({ command, mode }) => {
+ const env = loadEnv(mode, "..", ["CANISTER_"]);
+
+ return {
+ base: "./",
+ plugins: [
+ icpBindgen({
+ didFile: "../backend/backend.did",
+ outDir: "./src/bindings",
+ }),
+ ],
+ define: {
+ "process.env.CANISTER_ID_BACKEND": JSON.stringify(
+ env.CANISTER_ID_BACKEND
+ ),
+ },
+ optimizeDeps: {
+ esbuildOptions: { define: { global: "globalThis" } },
+ },
+ server: command === "serve" ? getDevServerConfig() : undefined,
+ };
+});
diff --git a/rust/qrcode/icp.yaml b/rust/qrcode/icp.yaml
new file mode 100644
index 0000000000..c329eab309
--- /dev/null
+++ b/rust/qrcode/icp.yaml
@@ -0,0 +1,19 @@
+networks:
+ - name: local
+ mode: managed
+
+canisters:
+ - name: backend
+ recipe:
+ type: "@dfinity/rust@v3.3.0"
+ configuration:
+ candid: backend/backend.did
+
+ - name: frontend
+ recipe:
+ type: "@dfinity/asset-canister@v2.2.1"
+ configuration:
+ dir: frontend/dist
+ build:
+ - npm install --prefix frontend
+ - npm run build --prefix frontend
diff --git a/rust/qrcode/package.json b/rust/qrcode/package.json
index 76b038da0b..4b47459487 100644
--- a/rust/qrcode/package.json
+++ b/rust/qrcode/package.json
@@ -1,46 +1,12 @@
{
- "name": "qrcode_frontend",
- "version": "0.2.0",
- "description": "Internet Computer starter application",
- "keywords": [
- "Internet Computer",
- "Motoko",
- "JavaScript",
- "Canister"
- ],
+ "name": "qrcode",
"scripts": {
- "build": "webpack",
- "prebuild": "npm install --include=dev && dfx generate",
- "start": "webpack serve --mode development --env development",
- "deploy:local": "dfx deploy --network=local",
- "deploy:ic": "dfx deploy --network=ic",
- "generate": "dfx generate qrcode_backend"
+ "build": "npm run build --workspaces --if-present",
+ "prebuild": "npm run prebuild --workspaces --if-present",
+ "dev": "npm run dev --workspaces --if-present"
},
- "dependencies": {
- "@icp-sdk/core": "~5.2.0"
- },
- "devDependencies": {
- "assert": "2.0.0",
- "buffer": "6.0.3",
- "copy-webpack-plugin": "^11.0.0",
- "dotenv": "^16.0.3",
- "events": "3.3.0",
- "html-webpack-plugin": "5.5.0",
- "process": "0.11.10",
- "stream-browserify": "3.0.0",
- "terser-webpack-plugin": "^5.3.3",
- "util": "0.12.4",
- "webpack": "^5.73.0",
- "webpack-cli": "^4.10.0",
- "webpack-dev-server": "^4.8.1"
- },
- "engines": {
- "node": "^12 || ^14 || ^16 || ^18 || ^21"
- },
- "browserslist": [
- "last 2 chrome version",
- "last 2 firefox version",
- "last 2 safari version",
- "last 2 edge version"
+ "type": "module",
+ "workspaces": [
+ "frontend"
]
}
\ No newline at end of file
diff --git a/rust/qrcode/src/qrcode_frontend/assets/.ic-assets.json5 b/rust/qrcode/src/qrcode_frontend/assets/.ic-assets.json5
deleted file mode 100644
index 8dc76d1ee6..0000000000
--- a/rust/qrcode/src/qrcode_frontend/assets/.ic-assets.json5
+++ /dev/null
@@ -1,56 +0,0 @@
-[
- {
- "match": "**/*",
- "headers": {
- // Security: The Content Security Policy (CSP) given below aims at working with many apps rather than providing maximal security.
- // We recommend tightening the CSP for your specific application. Some recommendations are as follows:
- // - Use the CSP Evaluator (https://csp-evaluator.withgoogle.com/) to validate the CSP you define.
- // - Follow the “Strict CSP” recommendations (https://csp.withgoogle.com/docs/strict-csp.html). However, note that in the context of the IC,
- // nonces cannot be used because the response bodies must be static to work well with HTTP asset certification.
- // Thus, we recommend to include script hashes (in combination with strict-dynamic) in the CSP as described
- // in https://csp.withgoogle.com/docs/faq.html in section “What if my site is static and I can't add nonces to scripts?”.
- // See for example the II CSP (https://github.com/dfinity/internet-identity/blob/main/src/internet_identity/src/http.rs).
- // - It is recommended to tighten the connect-src directive. With the current CSP configuration the browser can
- // make requests to https://*.icp0.io, hence being able to call any canister via https://icp0.io/api/v2/canister/{canister-ID}.
- // This could potentially be used in combination with another vulnerability (e.g. XSS) to exfiltrate private data.
- // The developer can configure this policy to only allow requests to their specific canisters,
- // e.g: connect-src 'self' https://icp-api.io/api/v2/canister/{my-canister-ID}, where {my-canister-ID} has the following format: aaaaa-aaaaa-aaaaa-aaaaa-aaa
- // - It is recommended to configure style-src, style-src-elem and font-src directives with the resources your canister is going to use
- // instead of using the wild card (*) option. Normally this will include 'self' but also other third party styles or fonts resources (e.g: https://fonts.googleapis.com or other CDNs)
-
- // Notes about the CSP below:
- // - script-src 'unsafe-eval' is currently required because agent-js uses a WebAssembly module for the validation of bls signatures.
- // There is currently no other way to allow execution of WebAssembly modules with CSP.
- // See: https://github.com/WebAssembly/content-security-policy/blob/main/proposals/CSP.md.
- // - We added img-src data: because data: images are used often.
- // - frame-ancestors: none mitigates clickjacking attacks. See https://owasp.org/www-community/attacks/Clickjacking.
- "Content-Security-Policy": "default-src 'self';script-src 'self' 'unsafe-eval';connect-src 'self' https://icp0.io https://*.icp0.io;img-src 'self' data:;style-src * 'unsafe-inline';style-src-elem * 'unsafe-inline';font-src *;object-src 'none';base-uri 'self';frame-ancestors 'none';form-action 'self';upgrade-insecure-requests;",
-
- // Security: The permissions policy disables all features for security reasons. If your site needs such permissions, activate them.
- // To configure permissions go here https://www.permissionspolicy.com/
- "Permissions-Policy": "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=(), clipboard-read=(), clipboard-write=(), gamepad=(), speaker-selection=(), conversion-measurement=(), focus-without-user-activation=(), hid=(), idle-detection=(), interest-cohort=(), serial=(), sync-script=(), trust-token-redemption=(), window-placement=(), vertical-scroll=()",
-
- // Security: Mitigates clickjacking attacks.
- // See: https://owasp.org/www-community/attacks/Clickjacking.
- "X-Frame-Options": "DENY",
-
- // Security: Avoids forwarding referrer information to other origins.
- // See: https://owasp.org/www-project-secure-headers/#referrer-policy.
- "Referrer-Policy": "same-origin",
-
- // Security: Tells the user’s browser that it must always use HTTPS with your site.
- // See: https://owasp.org/www-project-secure-headers/#http-strict-transport-security
- "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
-
- // Security: Prevents the browser from interpreting files as a different MIME type to what is specified in the Content-Type header.
- // See: https://owasp.org/www-project-secure-headers/#x-content-type-options
- "X-Content-Type-Options": "nosniff",
-
- // Security: Enables browser features to mitigate some of the XSS attacks. Note that it has to be in mode=block.
- // See: https://owasp.org/www-community/attacks/xss/
- "X-XSS-Protection": "1; mode=block"
- },
- // redirect all requests from .raw.icp0.io to .icp0.io (this redirection is the default)
- "allow_raw_access": false
- },
-]
diff --git a/rust/qrcode/src/qrcode_frontend/assets/main.css b/rust/qrcode/src/qrcode_frontend/assets/main.css
deleted file mode 100644
index f1e2090dd2..0000000000
--- a/rust/qrcode/src/qrcode_frontend/assets/main.css
+++ /dev/null
@@ -1,106 +0,0 @@
-body {
- font-family: sans-serif;
-}
-
-form {
- display: flex;
- justify-content: center;
- max-width: 80vw;
- margin: auto;
-}
-
-div {
- display: flex;
- max-width: 80vw;
- flex-flow: column;
- align-items: baseline;
- row-gap: 20px;
-}
-
-img {
- max-width: 80vw;
- border: 1px solid #555;
- background-color: #ddd;
-}
-
-.qrcode {
- border: 1px solid #555;
-}
-
-h1 {
- font-size: 100%;
-}
-
-textarea {
- margin-top: -20px;
- max-width: 80vw;
-}
-
-.option {
- display: flex;
- flex-flow: row;
- justify-content: left;
- align-items: center;
-}
-
-.toggle-switch {
- position: relative;
- display: inline-block;
- width: 42px;
- height: 18px;
-}
-
-.toggle-switch input {
- opacity: 0;
- width: 0;
- height: 0;
-}
-
-.slider {
- position: absolute;
- cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: #ccc;
- border-radius: 18px;
- transition: 0.4s;
-}
-
-.slider:before {
- position: absolute;
- content: "";
- height: 16px;
- width: 16px;
- left: 1px;
- bottom: 1px;
- background-color: white;
- border-radius: 50%;
- transition: 0.4s;
-}
-
-input:checked+.slider {
- background-color: #b11dbb;
-}
-
-input:checked+.slider:before {
- transform: translateX(24px);
-}
-
-.clean-button {
- display: inline-block;
- padding: 5px 10px;
- border: 2px solid #a10dab;
- border-radius: 4px;
- background-color: #b11dbb;
- color: #fff;
- cursor: pointer;
- text-decoration: none;
- transition: background-color 0.2s, color 0.2s, border-color 0.2s;
-}
-
-.clean-button:disabled {
- background-color: #ccc;
- border-color: #ccc;
-}
\ No newline at end of file
diff --git a/rust/qrcode/src/qrcode_frontend/src/.ic-assets.json5 b/rust/qrcode/src/qrcode_frontend/src/.ic-assets.json5
deleted file mode 100644
index 8dc76d1ee6..0000000000
--- a/rust/qrcode/src/qrcode_frontend/src/.ic-assets.json5
+++ /dev/null
@@ -1,56 +0,0 @@
-[
- {
- "match": "**/*",
- "headers": {
- // Security: The Content Security Policy (CSP) given below aims at working with many apps rather than providing maximal security.
- // We recommend tightening the CSP for your specific application. Some recommendations are as follows:
- // - Use the CSP Evaluator (https://csp-evaluator.withgoogle.com/) to validate the CSP you define.
- // - Follow the “Strict CSP” recommendations (https://csp.withgoogle.com/docs/strict-csp.html). However, note that in the context of the IC,
- // nonces cannot be used because the response bodies must be static to work well with HTTP asset certification.
- // Thus, we recommend to include script hashes (in combination with strict-dynamic) in the CSP as described
- // in https://csp.withgoogle.com/docs/faq.html in section “What if my site is static and I can't add nonces to scripts?”.
- // See for example the II CSP (https://github.com/dfinity/internet-identity/blob/main/src/internet_identity/src/http.rs).
- // - It is recommended to tighten the connect-src directive. With the current CSP configuration the browser can
- // make requests to https://*.icp0.io, hence being able to call any canister via https://icp0.io/api/v2/canister/{canister-ID}.
- // This could potentially be used in combination with another vulnerability (e.g. XSS) to exfiltrate private data.
- // The developer can configure this policy to only allow requests to their specific canisters,
- // e.g: connect-src 'self' https://icp-api.io/api/v2/canister/{my-canister-ID}, where {my-canister-ID} has the following format: aaaaa-aaaaa-aaaaa-aaaaa-aaa
- // - It is recommended to configure style-src, style-src-elem and font-src directives with the resources your canister is going to use
- // instead of using the wild card (*) option. Normally this will include 'self' but also other third party styles or fonts resources (e.g: https://fonts.googleapis.com or other CDNs)
-
- // Notes about the CSP below:
- // - script-src 'unsafe-eval' is currently required because agent-js uses a WebAssembly module for the validation of bls signatures.
- // There is currently no other way to allow execution of WebAssembly modules with CSP.
- // See: https://github.com/WebAssembly/content-security-policy/blob/main/proposals/CSP.md.
- // - We added img-src data: because data: images are used often.
- // - frame-ancestors: none mitigates clickjacking attacks. See https://owasp.org/www-community/attacks/Clickjacking.
- "Content-Security-Policy": "default-src 'self';script-src 'self' 'unsafe-eval';connect-src 'self' https://icp0.io https://*.icp0.io;img-src 'self' data:;style-src * 'unsafe-inline';style-src-elem * 'unsafe-inline';font-src *;object-src 'none';base-uri 'self';frame-ancestors 'none';form-action 'self';upgrade-insecure-requests;",
-
- // Security: The permissions policy disables all features for security reasons. If your site needs such permissions, activate them.
- // To configure permissions go here https://www.permissionspolicy.com/
- "Permissions-Policy": "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=(), clipboard-read=(), clipboard-write=(), gamepad=(), speaker-selection=(), conversion-measurement=(), focus-without-user-activation=(), hid=(), idle-detection=(), interest-cohort=(), serial=(), sync-script=(), trust-token-redemption=(), window-placement=(), vertical-scroll=()",
-
- // Security: Mitigates clickjacking attacks.
- // See: https://owasp.org/www-community/attacks/Clickjacking.
- "X-Frame-Options": "DENY",
-
- // Security: Avoids forwarding referrer information to other origins.
- // See: https://owasp.org/www-project-secure-headers/#referrer-policy.
- "Referrer-Policy": "same-origin",
-
- // Security: Tells the user’s browser that it must always use HTTPS with your site.
- // See: https://owasp.org/www-project-secure-headers/#http-strict-transport-security
- "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
-
- // Security: Prevents the browser from interpreting files as a different MIME type to what is specified in the Content-Type header.
- // See: https://owasp.org/www-project-secure-headers/#x-content-type-options
- "X-Content-Type-Options": "nosniff",
-
- // Security: Enables browser features to mitigate some of the XSS attacks. Note that it has to be in mode=block.
- // See: https://owasp.org/www-community/attacks/xss/
- "X-XSS-Protection": "1; mode=block"
- },
- // redirect all requests from .raw.icp0.io to .icp0.io (this redirection is the default)
- "allow_raw_access": false
- },
-]
diff --git a/rust/qrcode/src/qrcode_frontend/src/index.html b/rust/qrcode/src/qrcode_frontend/src/index.html
deleted file mode 100644
index c05615fd0e..0000000000
--- a/rust/qrcode/src/qrcode_frontend/src/index.html
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
-
-
- QR code generator
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/rust/qrcode/webpack.config.js b/rust/qrcode/webpack.config.js
deleted file mode 100644
index 3f676645db..0000000000
--- a/rust/qrcode/webpack.config.js
+++ /dev/null
@@ -1,96 +0,0 @@
-require("dotenv").config();
-const path = require("path");
-const webpack = require("webpack");
-const HtmlWebpackPlugin = require("html-webpack-plugin");
-const TerserPlugin = require("terser-webpack-plugin");
-const CopyPlugin = require("copy-webpack-plugin");
-
-const isDevelopment = process.env.NODE_ENV !== "production";
-
-const frontendDirectory = "qrcode_frontend";
-
-const frontend_entry = path.join("src", frontendDirectory, "src", "index.html");
-
-module.exports = {
- target: "web",
- mode: isDevelopment ? "development" : "production",
- entry: {
- // The frontend.entrypoint points to the HTML file for this build, so we need
- // to replace the extension to `.js`.
- index: path.join(__dirname, frontend_entry).replace(/\.html$/, ".js"),
- },
- devtool: isDevelopment ? "source-map" : false,
- optimization: {
- minimize: !isDevelopment,
- minimizer: [new TerserPlugin()],
- },
- resolve: {
- extensions: [".js", ".ts", ".jsx", ".tsx"],
- fallback: {
- assert: require.resolve("assert/"),
- buffer: require.resolve("buffer/"),
- events: require.resolve("events/"),
- stream: require.resolve("stream-browserify/"),
- util: require.resolve("util/"),
- },
- },
- output: {
- filename: "index.js",
- path: path.join(__dirname, "dist", frontendDirectory),
- },
-
- // Depending in the language or framework you are using for
- // front-end development, add module loaders to the default
- // webpack configuration. For example, if you are using React
- // modules and CSS as described in the "Adding a stylesheet"
- // tutorial, uncomment the following lines:
- // module: {
- // rules: [
- // { test: /\.(ts|tsx|jsx)$/, loader: "ts-loader" },
- // { test: /\.css$/, use: ['style-loader','css-loader'] }
- // ]
- // },
- plugins: [
- new HtmlWebpackPlugin({
- template: path.join(__dirname, frontend_entry),
- cache: false,
- }),
- new webpack.EnvironmentPlugin([
- ...Object.keys(process.env).filter((key) => {
- if (key.includes("CANISTER")) return true;
- if (key.includes("DFX")) return true;
- return false;
- }),
- ]),
- new webpack.ProvidePlugin({
- Buffer: [require.resolve("buffer/"), "Buffer"],
- process: require.resolve("process/browser"),
- }),
- new CopyPlugin({
- patterns: [
- {
- from: `src/${frontendDirectory}/assets`,
- to: "./",
- noErrorOnMissing: true,
- },
- ],
- }),
- ],
- // proxy /api to port 4943 during development.
- // if you edit dfx.json to define a project-specific local network, change the port to match.
- devServer: {
- proxy: {
- "/api": {
- target: "http://127.0.0.1:4943",
- changeOrigin: true,
- pathRewrite: {
- "^/api": "/api",
- },
- },
- },
- static: path.resolve(__dirname, "src", frontendDirectory, "assets"),
- hot: true,
- watchFiles: [path.resolve(__dirname, "src", frontendDirectory)],
- liveReload: true,
- },
-};