diff --git a/hosting/oisy-signer-demo/frontend/package.json b/hosting/oisy-signer-demo/frontend/package.json index b1edad45c5..2592cf49f3 100644 --- a/hosting/oisy-signer-demo/frontend/package.json +++ b/hosting/oisy-signer-demo/frontend/package.json @@ -13,9 +13,9 @@ "format:check": "prettier --check ." }, "dependencies": { - "@icp-sdk/canisters": "~3.5.0", - "@icp-sdk/core": "~5.0.0", - "@icp-sdk/signer": "5.3.0", + "@icp-sdk/canisters": "~4.0.0", + "@icp-sdk/core": "~6.1.0", + "@icp-sdk/signer": "~6.0.0", "class-variance-authority": "~0.7.1", "lucide-react": "~0.575.0", "react": "~19.2.4", diff --git a/motoko/backend_only/icp.yaml b/motoko/backend_only/icp.yaml index fb741fadec..92c2fa343e 100644 --- a/motoko/backend_only/icp.yaml +++ b/motoko/backend_only/icp.yaml @@ -1,4 +1,4 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" diff --git a/motoko/basic_bitcoin/icp.yaml b/motoko/basic_bitcoin/icp.yaml index ddb94934dd..1a11eef52e 100644 --- a/motoko/basic_bitcoin/icp.yaml +++ b/motoko/basic_bitcoin/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" networks: - name: local diff --git a/motoko/canister_factory/icp.yaml b/motoko/canister_factory/icp.yaml index fb741fadec..92c2fa343e 100644 --- a/motoko/canister_factory/icp.yaml +++ b/motoko/canister_factory/icp.yaml @@ -1,4 +1,4 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" diff --git a/motoko/canister_logs/icp.yaml b/motoko/canister_logs/icp.yaml index fb741fadec..92c2fa343e 100644 --- a/motoko/canister_logs/icp.yaml +++ b/motoko/canister_logs/icp.yaml @@ -1,4 +1,4 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" diff --git a/motoko/cert-var/frontend/package.json b/motoko/cert-var/frontend/package.json index d0cc35cdb0..c5f9d65ed9 100644 --- a/motoko/cert-var/frontend/package.json +++ b/motoko/cert-var/frontend/package.json @@ -8,10 +8,10 @@ "dev": "vite" }, "dependencies": { - "@icp-sdk/core": "~5.0.0" + "@icp-sdk/core": "~6.1.0" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.2.2", + "@icp-sdk/bindgen": "~0.4.1", "vite": "5.4.11" } } diff --git a/motoko/cert-var/icp.yaml b/motoko/cert-var/icp.yaml index 491bcb0316..f4a871c2ed 100644 --- a/motoko/cert-var/icp.yaml +++ b/motoko/cert-var/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" - name: frontend recipe: diff --git a/motoko/composite_query/icp.yaml b/motoko/composite_query/icp.yaml index fb741fadec..92c2fa343e 100644 --- a/motoko/composite_query/icp.yaml +++ b/motoko/composite_query/icp.yaml @@ -1,4 +1,4 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" diff --git a/motoko/daily_planner/frontend/package.json b/motoko/daily_planner/frontend/package.json index 859836e9b9..47cd6254aa 100644 --- a/motoko/daily_planner/frontend/package.json +++ b/motoko/daily_planner/frontend/package.json @@ -8,12 +8,12 @@ "dev": "vite" }, "dependencies": { - "@icp-sdk/core": "~5.2.0", + "@icp-sdk/core": "~6.1.0", "react": "18.3.1", "react-dom": "18.3.1" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.2.2", + "@icp-sdk/bindgen": "~0.4.1", "@types/react": "18.3.12", "@types/react-dom": "18.3.1", "@vitejs/plugin-react": "4.3.3", diff --git a/motoko/daily_planner/icp.yaml b/motoko/daily_planner/icp.yaml index 491bcb0316..f4a871c2ed 100644 --- a/motoko/daily_planner/icp.yaml +++ b/motoko/daily_planner/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" - name: frontend recipe: diff --git a/motoko/evm_block_explorer/frontend/package.json b/motoko/evm_block_explorer/frontend/package.json index b3bbed2ca0..61c36672ee 100644 --- a/motoko/evm_block_explorer/frontend/package.json +++ b/motoko/evm_block_explorer/frontend/package.json @@ -8,13 +8,13 @@ "dev": "vite" }, "dependencies": { - "@icp-sdk/core": "~6.0.0", + "@icp-sdk/core": "~6.1.0", "react": "18.3.1", "react-dom": "18.3.1", "react-json-view-lite": "2.3.0" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.4.0", + "@icp-sdk/bindgen": "~0.4.1", "@types/react": "18.3.12", "@types/react-dom": "18.3.1", "@vitejs/plugin-react": "4.3.3", diff --git a/motoko/evm_block_explorer/icp.yaml b/motoko/evm_block_explorer/icp.yaml index 15c407ff07..f7041537e0 100644 --- a/motoko/evm_block_explorer/icp.yaml +++ b/motoko/evm_block_explorer/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" - name: frontend recipe: diff --git a/motoko/filevault/README.md b/motoko/filevault/README.md index b3c0ba4952..ad564444ef 100644 --- a/motoko/filevault/README.md +++ b/motoko/filevault/README.md @@ -34,6 +34,8 @@ bash test.sh icp network stop ``` +> Internet Identity sign-in runs against the Internet Identity canister deployed on the local network (`ii: true` in `icp.yaml`). If signing in fails, update the local network launcher with `icp network update` and restart the network. + The frontend is served by the asset canister. To run the Vite dev server with hot reload during frontend development: ```bash diff --git a/motoko/filevault/frontend/package.json b/motoko/filevault/frontend/package.json index accf38438e..ea0451838f 100644 --- a/motoko/filevault/frontend/package.json +++ b/motoko/filevault/frontend/package.json @@ -8,13 +8,13 @@ "dev": "vite" }, "dependencies": { - "@icp-sdk/auth": "~5.0.0", - "@icp-sdk/core": "~5.2.0", + "@icp-sdk/auth": "~10.0.0", + "@icp-sdk/core": "~6.1.0", "react": "18.3.1", "react-dom": "18.3.1" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.3.0", + "@icp-sdk/bindgen": "~0.4.1", "@types/react": "18.3.12", "@types/react-dom": "18.3.1", "@vitejs/plugin-react": "4.3.3", diff --git a/motoko/filevault/frontend/src/App.jsx b/motoko/filevault/frontend/src/App.jsx index cfa1d585d2..c3320c6575 100644 --- a/motoko/filevault/frontend/src/App.jsx +++ b/motoko/filevault/frontend/src/App.jsx @@ -1,20 +1,21 @@ import React, { useState, useEffect } from 'react'; -import { AuthClient } from '@icp-sdk/auth/client'; -import { createBackendActor, identityProviderUrl } from './actor'; +import { authClient, getBackendActor } from './actor'; import '../index.css'; function App() { - const [isAuthenticated, setIsAuthenticated] = useState(false); - const [authClient, setAuthClient] = useState(); - const [actor, setActor] = useState(); + const [isAuthenticated, setIsAuthenticated] = useState(authClient.isAuthenticated()); const [files, setFiles] = useState([]); const [errorMessage, setErrorMessage] = useState(); const [fileTransferProgress, setFileTransferProgress] = useState(); - useEffect(() => { - updateActor(); - setErrorMessage(); - }, []); + // Sign-out or session expiry, including from another tab. + useEffect( + () => + authClient.subscribe(() => { + if (!authClient.isAuthenticated()) setIsAuthenticated(false); + }), + [] + ); useEffect(() => { if (isAuthenticated) { @@ -22,31 +23,22 @@ function App() { } }, [isAuthenticated]); - async function updateActor() { - const authClient = await AuthClient.create(); - const identity = authClient.getIdentity(); - const actor = createBackendActor(identity); - const isAuthenticated = await authClient.isAuthenticated(); - - setActor(actor); - setAuthClient(authClient); - setIsAuthenticated(isAuthenticated); - } - async function login() { - await authClient.login({ - identityProvider: identityProviderUrl, - onSuccess: updateActor - }); + try { + await authClient.signIn(); + setIsAuthenticated(true); + } catch (error) { + console.error('Sign-in failed:', error); + } } async function logout() { - await authClient.logout(); - updateActor(); + await authClient.signOut(); } async function loadFiles() { try { + const actor = await getBackendActor(); const fileList = await actor.getFiles(); setFiles(fileList); } catch (error) { @@ -64,6 +56,7 @@ function App() { return; } + const actor = await getBackendActor(); if (await actor.checkFileExists(file.name)) { setErrorMessage(`File "${file.name}" already exists. Please choose a different file name.`); return; @@ -111,6 +104,7 @@ function App() { progress: 0 }); try { + const actor = await getBackendActor(); const totalChunks = Number(await actor.getTotalChunks(name)); const fileType = await actor.getFileType(name) ?? ''; let chunks = []; @@ -147,6 +141,7 @@ function App() { async function handleFileDelete(name) { if (window.confirm(`Are you sure you want to delete "${name}"?`)) { try { + const actor = await getBackendActor(); const success = await actor.deleteFile(name); if (success) { await loadFiles(); diff --git a/motoko/filevault/frontend/src/actor.js b/motoko/filevault/frontend/src/actor.js index e35f888b2d..f625439699 100644 --- a/motoko/filevault/frontend/src/actor.js +++ b/motoko/filevault/frontend/src/actor.js @@ -1,3 +1,4 @@ +import { AuthClient } from "@icp-sdk/auth/client"; import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"; import { createActor } from "./bindings/backend"; @@ -16,18 +17,38 @@ const agentOptions = { rootKey: canisterEnv?.IC_ROOT_KEY, }; +// Internet Identity is deployed on the local network (`ii: true` in icp.yaml). const isLocal = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.hostname.endsWith(".localhost"); -const II_CANISTER_ID = "uqzsh-gqaaa-aaaaq-qaada-cai"; const networkPort = process.env.REPLICA_PORT || window.location.port || "8000"; -export const identityProviderUrl = isLocal - ? `http://${II_CANISTER_ID}.localhost:${networkPort}` - : "https://id.ai"; + +// The client mints its delegations by calling the II canister, so its agent +// needs the network's root key to verify the responses. +export const authClient = new AuthClient({ + identityProvider: { + authorizeUrl: isLocal + ? `http://id.ai.localhost:${networkPort}/authorize` + : "https://id.ai/authorize", + canisterId: "rdmx6-jaaaa-aaaaa-aaadq-cai", + }, + agentOptions: { rootKey: canisterEnv?.IC_ROOT_KEY }, +}); export function createBackendActor(identity) { return createActor(canisterId, { agentOptions: { ...agentOptions, identity }, }); } + +let cached; + +// Resolves the identity at call time and reuses the actor while it is unchanged. +export async function getBackendActor() { + const identity = authClient.isAuthenticated() ? await authClient.getIdentity() : undefined; + if (!cached || cached.identity !== identity) { + cached = { identity, actor: createBackendActor(identity) }; + } + return cached.actor; +} diff --git a/motoko/filevault/icp.yaml b/motoko/filevault/icp.yaml index 3c04c7fd0a..a9678a69fd 100644 --- a/motoko/filevault/icp.yaml +++ b/motoko/filevault/icp.yaml @@ -6,7 +6,7 @@ networks: canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" - name: frontend recipe: diff --git a/motoko/flying_ninja/frontend/package.json b/motoko/flying_ninja/frontend/package.json index c98614e9c6..47cd6254aa 100644 --- a/motoko/flying_ninja/frontend/package.json +++ b/motoko/flying_ninja/frontend/package.json @@ -8,12 +8,12 @@ "dev": "vite" }, "dependencies": { - "@icp-sdk/core": "~5.0.0", + "@icp-sdk/core": "~6.1.0", "react": "18.3.1", "react-dom": "18.3.1" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.2.2", + "@icp-sdk/bindgen": "~0.4.1", "@types/react": "18.3.12", "@types/react-dom": "18.3.1", "@vitejs/plugin-react": "4.3.3", diff --git a/motoko/flying_ninja/icp.yaml b/motoko/flying_ninja/icp.yaml index 491bcb0316..f4a871c2ed 100644 --- a/motoko/flying_ninja/icp.yaml +++ b/motoko/flying_ninja/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" - name: frontend recipe: diff --git a/motoko/hello_cycles/icp.yaml b/motoko/hello_cycles/icp.yaml index fb741fadec..92c2fa343e 100644 --- a/motoko/hello_cycles/icp.yaml +++ b/motoko/hello_cycles/icp.yaml @@ -1,4 +1,4 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" diff --git a/motoko/hello_world/frontend/package.json b/motoko/hello_world/frontend/package.json index d0cc35cdb0..c5f9d65ed9 100644 --- a/motoko/hello_world/frontend/package.json +++ b/motoko/hello_world/frontend/package.json @@ -8,10 +8,10 @@ "dev": "vite" }, "dependencies": { - "@icp-sdk/core": "~5.0.0" + "@icp-sdk/core": "~6.1.0" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.2.2", + "@icp-sdk/bindgen": "~0.4.1", "vite": "5.4.11" } } diff --git a/motoko/hello_world/icp.yaml b/motoko/hello_world/icp.yaml index 491bcb0316..f4a871c2ed 100644 --- a/motoko/hello_world/icp.yaml +++ b/motoko/hello_world/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" - name: frontend recipe: diff --git a/motoko/ic-pos/README.md b/motoko/ic-pos/README.md index 079de02ed7..efdce674b7 100644 --- a/motoko/ic-pos/README.md +++ b/motoko/ic-pos/README.md @@ -46,6 +46,8 @@ icp network start -d bash deploy.sh ``` +> Internet Identity sign-in runs against the Internet Identity canister deployed on the local network (`ii: true` in `icp.yaml`). If signing in fails, update the local network launcher with `icp network update` and restart the network. + > **Use `bash deploy.sh`, not `icp deploy`, locally.** The ICRC-1 ledger and index require init args (minting account, initial balances, and the ledger's canister ID) that are only known after identities and canisters are created, so `deploy.sh` installs them with the right arguments. A plain `icp deploy` traps because those canisters receive no init args. `deploy.sh` installs a local ICRC-1 ledger + index (a throwaway token named **LICRC1**, distinct from the mainnet TICRC1), the `backend`, and the `frontend`. Internet Identity is provided by the local network (`ii: true` in `icp.yaml`) at `http://id.ai.localhost:8000` — no separate deployment. Open the frontend URL printed by the script. diff --git a/motoko/ic-pos/frontend/package.json b/motoko/ic-pos/frontend/package.json index 5b5ec7fa57..dfbb8f3658 100644 --- a/motoko/ic-pos/frontend/package.json +++ b/motoko/ic-pos/frontend/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@hookform/resolvers": "^3.1.1", - "@icp-sdk/auth": "~7.1.0", - "@icp-sdk/canisters": "~3.6.0", - "@icp-sdk/core": "~5.4.0", + "@icp-sdk/auth": "~10.0.0", + "@icp-sdk/canisters": "~4.0.0", + "@icp-sdk/core": "~6.1.0", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-switch": "^1.0.3", @@ -32,7 +32,7 @@ "zod": "^3.22.3" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.4.0", + "@icp-sdk/bindgen": "~0.4.1", "@tailwindcss/forms": "^0.5.10", "@tailwindcss/typography": "^0.5.19", "@tailwindcss/vite": "~4", diff --git a/motoko/ic-pos/frontend/src/lib/auth.tsx b/motoko/ic-pos/frontend/src/lib/auth.tsx index 6b6b5599ed..75db25e8fd 100644 --- a/motoko/ic-pos/frontend/src/lib/auth.tsx +++ b/motoko/ic-pos/frontend/src/lib/auth.tsx @@ -9,7 +9,7 @@ import { } from "react"; import { AuthClient } from "@icp-sdk/auth/client"; import type { Identity } from "@icp-sdk/core/agent"; -import { iiUrl } from "./env"; +import { identityProvider, rootKey } from "./env"; interface AuthContextValue { /** The authenticated identity, or `undefined` when logged out. */ @@ -30,30 +30,35 @@ export function AuthProvider({ children }: { children: ReactNode }) { const [isInitializing, setIsInitializing] = useState(true); useEffect(() => { + // The client mints its delegations by calling the II canister, so its + // agent needs the network's root key to verify the responses. const client = new AuthClient({ - identityProvider: iiUrl, - // Keep the session alive; expiry is handled explicitly via - // useHandleAgentError when the delegation is rejected. - idleOptions: { disableIdle: true }, + identityProvider, + agentOptions: { rootKey }, }); setAuthClient(client); + // Leave the signed-in views when the session ends, including from another tab. + const unsubscribe = client.subscribe(() => { + if (!client.isAuthenticated()) setIdentity(undefined); + }); + void (async () => { - // getIdentity() restores a previous session from storage if present. - await client.getIdentity(); if (client.isAuthenticated()) { setIdentity(await client.getIdentity()); } setIsInitializing(false); })(); + + return () => { + unsubscribe(); + client.dispose(); + }; }, []); const login = useCallback(async () => { if (!authClient) return; - await authClient.signIn(); - if (authClient.isAuthenticated()) { - setIdentity(await authClient.getIdentity()); - } + setIdentity(await authClient.signIn()); }, [authClient]); const clear = useCallback(async () => { diff --git a/motoko/ic-pos/frontend/src/lib/env.ts b/motoko/ic-pos/frontend/src/lib/env.ts index a87c9c92f0..f7668fbbd5 100644 --- a/motoko/ic-pos/frontend/src/lib/env.ts +++ b/motoko/ic-pos/frontend/src/lib/env.ts @@ -39,8 +39,11 @@ export const host = window.location.origin; export const isLocal = /localhost|127\.0\.0\.1/.test(window.location.hostname); // Internet Identity provider. Locally the network serves II at -// id.ai.localhost (icp.yaml `ii: true`); on mainnet it's id.ai. The -// `/authorize` path is required by @icp-sdk/auth. -export const iiUrl = isLocal - ? "http://id.ai.localhost:8000/authorize" - : "https://id.ai/authorize"; +// id.ai.localhost (icp.yaml `ii: true`); on mainnet it's id.ai. The canister +// that mints delegations has the same ID on both. +export const identityProvider = { + authorizeUrl: isLocal + ? "http://id.ai.localhost:8000/authorize" + : "https://id.ai/authorize", + canisterId: "rdmx6-jaaaa-aaaaa-aaadq-cai", +}; diff --git a/motoko/ic-pos/icp.yaml b/motoko/ic-pos/icp.yaml index c704f3d567..c22ce1317d 100644 --- a/motoko/ic-pos/icp.yaml +++ b/motoko/ic-pos/icp.yaml @@ -23,7 +23,7 @@ canisters: - name: backend init_args: "(0 : nat)" recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" - name: frontend recipe: diff --git a/motoko/icp_transfer/icp.yaml b/motoko/icp_transfer/icp.yaml index fb741fadec..92c2fa343e 100644 --- a/motoko/icp_transfer/icp.yaml +++ b/motoko/icp_transfer/icp.yaml @@ -1,4 +1,4 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" diff --git a/motoko/icrc2-swap/icp.yaml b/motoko/icrc2-swap/icp.yaml index a295ab4da4..ce09bff934 100644 --- a/motoko/icrc2-swap/icp.yaml +++ b/motoko/icrc2-swap/icp.yaml @@ -13,4 +13,4 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" diff --git a/motoko/llm_chatbot/frontend/package.json b/motoko/llm_chatbot/frontend/package.json index 8f65ae308c..6fe18a395b 100644 --- a/motoko/llm_chatbot/frontend/package.json +++ b/motoko/llm_chatbot/frontend/package.json @@ -8,14 +8,14 @@ "dev": "vite" }, "dependencies": { - "@icp-sdk/core": "~6.0.0", + "@icp-sdk/core": "~6.1.0", "react": "18.3.1", "react-dom": "18.3.1", "react-markdown": "~10.1.0", "remark-gfm": "~4.0.1" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.4.0", + "@icp-sdk/bindgen": "~0.4.1", "@tailwindcss/typography": "~0.5.20", "@vitejs/plugin-react": "4.3.3", "autoprefixer": "^10.4.20", diff --git a/motoko/llm_chatbot/icp.yaml b/motoko/llm_chatbot/icp.yaml index e3fe8e543b..9919047e00 100644 --- a/motoko/llm_chatbot/icp.yaml +++ b/motoko/llm_chatbot/icp.yaml @@ -11,7 +11,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" - name: frontend recipe: diff --git a/motoko/low_wasm_memory/icp.yaml b/motoko/low_wasm_memory/icp.yaml index 34d1ae7e65..a408bf0f96 100644 --- a/motoko/low_wasm_memory/icp.yaml +++ b/motoko/low_wasm_memory/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" settings: wasm_memory_limit: 8mib wasm_memory_threshold: 2mib diff --git a/motoko/parallel_calls/icp.yaml b/motoko/parallel_calls/icp.yaml index 13aed299f3..643dd04a6f 100644 --- a/motoko/parallel_calls/icp.yaml +++ b/motoko/parallel_calls/icp.yaml @@ -1,8 +1,8 @@ canisters: - name: caller recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" - name: callee recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" diff --git a/motoko/pub-sub/icp.yaml b/motoko/pub-sub/icp.yaml index f109b28d2c..d66db129b7 100644 --- a/motoko/pub-sub/icp.yaml +++ b/motoko/pub-sub/icp.yaml @@ -1,8 +1,8 @@ canisters: - name: publisher recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" - name: subscriber recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" diff --git a/motoko/query_stats/icp.yaml b/motoko/query_stats/icp.yaml index fb741fadec..92c2fa343e 100644 --- a/motoko/query_stats/icp.yaml +++ b/motoko/query_stats/icp.yaml @@ -1,4 +1,4 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" diff --git a/motoko/random_maze/frontend/package.json b/motoko/random_maze/frontend/package.json index c98614e9c6..47cd6254aa 100644 --- a/motoko/random_maze/frontend/package.json +++ b/motoko/random_maze/frontend/package.json @@ -8,12 +8,12 @@ "dev": "vite" }, "dependencies": { - "@icp-sdk/core": "~5.0.0", + "@icp-sdk/core": "~6.1.0", "react": "18.3.1", "react-dom": "18.3.1" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.2.2", + "@icp-sdk/bindgen": "~0.4.1", "@types/react": "18.3.12", "@types/react-dom": "18.3.1", "@vitejs/plugin-react": "4.3.3", diff --git a/motoko/random_maze/icp.yaml b/motoko/random_maze/icp.yaml index 491bcb0316..f4a871c2ed 100644 --- a/motoko/random_maze/icp.yaml +++ b/motoko/random_maze/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" - name: frontend recipe: diff --git a/motoko/send_http_get/icp.yaml b/motoko/send_http_get/icp.yaml index fb741fadec..92c2fa343e 100644 --- a/motoko/send_http_get/icp.yaml +++ b/motoko/send_http_get/icp.yaml @@ -1,4 +1,4 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" diff --git a/motoko/send_http_post/icp.yaml b/motoko/send_http_post/icp.yaml index fb741fadec..92c2fa343e 100644 --- a/motoko/send_http_post/icp.yaml +++ b/motoko/send_http_post/icp.yaml @@ -1,4 +1,4 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" diff --git a/motoko/superheroes/frontend/package.json b/motoko/superheroes/frontend/package.json index 7bfe8e876e..7100a5e818 100644 --- a/motoko/superheroes/frontend/package.json +++ b/motoko/superheroes/frontend/package.json @@ -8,12 +8,12 @@ "dev": "vite" }, "dependencies": { - "@icp-sdk/core": "~5.2.0", + "@icp-sdk/core": "~6.1.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.2.2", + "@icp-sdk/bindgen": "~0.4.1", "@vitejs/plugin-react": "^4.3.4", "vite": "5.4.11" } diff --git a/motoko/superheroes/icp.yaml b/motoko/superheroes/icp.yaml index 491bcb0316..f4a871c2ed 100644 --- a/motoko/superheroes/icp.yaml +++ b/motoko/superheroes/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" - name: frontend recipe: diff --git a/motoko/threshold-ecdsa/icp.yaml b/motoko/threshold-ecdsa/icp.yaml index fb741fadec..92c2fa343e 100644 --- a/motoko/threshold-ecdsa/icp.yaml +++ b/motoko/threshold-ecdsa/icp.yaml @@ -1,4 +1,4 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" diff --git a/motoko/threshold-schnorr/icp.yaml b/motoko/threshold-schnorr/icp.yaml index fb741fadec..92c2fa343e 100644 --- a/motoko/threshold-schnorr/icp.yaml +++ b/motoko/threshold-schnorr/icp.yaml @@ -1,4 +1,4 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" diff --git a/motoko/vetkeys/basic_bls_signing/README.md b/motoko/vetkeys/basic_bls_signing/README.md index c3f1d762f3..e8f8cdadcd 100644 --- a/motoko/vetkeys/basic_bls_signing/README.md +++ b/motoko/vetkeys/basic_bls_signing/README.md @@ -39,6 +39,8 @@ icp network start -d icp deploy ``` +> Internet Identity sign-in runs against the Internet Identity canister deployed on the local network (`ii: true` in `icp.yaml`). If signing in fails, update the local network launcher with `icp network update` and restart the network. + Open the frontend URL printed by `icp deploy`. To run the frontend in development mode with hot reloading (after `icp deploy`): diff --git a/motoko/vetkeys/basic_bls_signing/frontend/package.json b/motoko/vetkeys/basic_bls_signing/frontend/package.json index 0a68d864e4..04a4ff4f0a 100644 --- a/motoko/vetkeys/basic_bls_signing/frontend/package.json +++ b/motoko/vetkeys/basic_bls_signing/frontend/package.json @@ -8,12 +8,12 @@ "dev": "vite" }, "dependencies": { - "@icp-sdk/auth": "^7.1.0", - "@icp-sdk/core": "^5.4.0", - "@icp-sdk/vetkeys": "^0.5.0-beta.0" + "@icp-sdk/auth": "^10.0.0", + "@icp-sdk/core": "^6.1.0", + "@icp-sdk/vetkeys": "^0.7.0" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.2.2", + "@icp-sdk/bindgen": "~0.4.1", "typescript": "~5.7.2", "vite": "^6.4.1" } diff --git a/motoko/vetkeys/basic_bls_signing/frontend/src/main.ts b/motoko/vetkeys/basic_bls_signing/frontend/src/main.ts index 18c7c4de4d..a2c4a5510e 100644 --- a/motoko/vetkeys/basic_bls_signing/frontend/src/main.ts +++ b/motoko/vetkeys/basic_bls_signing/frontend/src/main.ts @@ -1,6 +1,6 @@ import "./style.css"; import { Principal } from "@icp-sdk/core/principal"; -import { AuthClient, LocalStorage } from "@icp-sdk/auth/client"; +import { AuthClient } from "@icp-sdk/auth/client"; import { HttpAgent } from "@icp-sdk/core/agent"; import { createActor, type Backend, type Signature } from "./bindings/backend"; import { DerivedPublicKey, verifyBlsSignature } from "@icp-sdk/vetkeys"; @@ -59,15 +59,16 @@ async function initAuth() { const isLocalEnv = window.location.hostname === "localhost" || window.location.hostname.endsWith(".localhost"); - // Workaround for https://github.com/dfinity/icp-js-auth/issues/120 - // IdbStorage has a race condition on localhost dev servers. LocalStorage - // avoids IDB on local but uses plain string storage (less secure), so - // production deployments keep the default secure IdbStorage + ECDSA key. + // The client mints its delegations by calling the II canister, so its agent + // needs the network's root key to verify the responses. authClient = new AuthClient({ - identityProvider: isLocalEnv - ? "http://id.ai.localhost:8000/authorize" - : "https://id.ai/authorize", - ...(isLocalEnv ? { storage: new LocalStorage(), keyType: "Ed25519" as const } : {}), + identityProvider: { + authorizeUrl: isLocalEnv + ? "http://id.ai.localhost:8000/authorize" + : "https://id.ai/authorize", + canisterId: "rdmx6-jaaaa-aaaaa-aaadq-cai", + }, + agentOptions: { rootKey: canisterEnv?.IC_ROOT_KEY }, }); const isAuthenticated = authClient.isAuthenticated(); diff --git a/motoko/vetkeys/basic_bls_signing/icp.yaml b/motoko/vetkeys/basic_bls_signing/icp.yaml index fd456ac78b..11439e3add 100644 --- a/motoko/vetkeys/basic_bls_signing/icp.yaml +++ b/motoko/vetkeys/basic_bls_signing/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" settings: # The vetKD master key this canister derives from, read by the canister via # `Runtime.envVar("VETKD_KEY_NAME")` and captured in stable state at the first diff --git a/motoko/vetkeys/basic_ibe/README.md b/motoko/vetkeys/basic_ibe/README.md index 11377cd68c..9062f04f39 100644 --- a/motoko/vetkeys/basic_ibe/README.md +++ b/motoko/vetkeys/basic_ibe/README.md @@ -40,6 +40,8 @@ icp network start -d icp deploy ``` +> Internet Identity sign-in runs against the Internet Identity canister deployed on the local network (`ii: true` in `icp.yaml`). If signing in fails, update the local network launcher with `icp network update` and restart the network. + Open the frontend URL printed by `icp deploy`. To run the frontend in development mode with hot reloading (after `icp deploy`): diff --git a/motoko/vetkeys/basic_ibe/frontend/package.json b/motoko/vetkeys/basic_ibe/frontend/package.json index 0a68d864e4..04a4ff4f0a 100644 --- a/motoko/vetkeys/basic_ibe/frontend/package.json +++ b/motoko/vetkeys/basic_ibe/frontend/package.json @@ -8,12 +8,12 @@ "dev": "vite" }, "dependencies": { - "@icp-sdk/auth": "^7.1.0", - "@icp-sdk/core": "^5.4.0", - "@icp-sdk/vetkeys": "^0.5.0-beta.0" + "@icp-sdk/auth": "^10.0.0", + "@icp-sdk/core": "^6.1.0", + "@icp-sdk/vetkeys": "^0.7.0" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.2.2", + "@icp-sdk/bindgen": "~0.4.1", "typescript": "~5.7.2", "vite": "^6.4.1" } diff --git a/motoko/vetkeys/basic_ibe/frontend/src/main.ts b/motoko/vetkeys/basic_ibe/frontend/src/main.ts index 0396ebcb82..d37760c1bd 100644 --- a/motoko/vetkeys/basic_ibe/frontend/src/main.ts +++ b/motoko/vetkeys/basic_ibe/frontend/src/main.ts @@ -10,7 +10,7 @@ import { IbeSeed, } from "@icp-sdk/vetkeys"; import { createActor, type Backend, type Inbox } from "./bindings/backend"; -import { AuthClient, LocalStorage } from "@icp-sdk/auth/client"; +import { AuthClient } from "@icp-sdk/auth/client"; import { HttpAgent } from "@icp-sdk/core/agent"; import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"; @@ -275,15 +275,16 @@ async function initAuth() { const isLocal = window.location.hostname === "localhost" || window.location.hostname.endsWith(".localhost"); - // Workaround for https://github.com/dfinity/icp-js-auth/issues/120 - // IdbStorage has a race condition on localhost dev servers. LocalStorage - // avoids IDB on local but uses plain string storage (less secure), so - // production deployments keep the default secure IdbStorage + ECDSA key. + // The client mints its delegations by calling the II canister, so its agent + // needs the network's root key to verify the responses. authClient = new AuthClient({ - identityProvider: isLocal - ? "http://id.ai.localhost:8000/authorize" - : "https://id.ai/authorize", - ...(isLocal ? { storage: new LocalStorage(), keyType: "Ed25519" as const } : {}), + identityProvider: { + authorizeUrl: isLocal + ? "http://id.ai.localhost:8000/authorize" + : "https://id.ai/authorize", + canisterId: "rdmx6-jaaaa-aaaaa-aaadq-cai", + }, + agentOptions: { rootKey: canisterEnv?.IC_ROOT_KEY }, }); const isAuthenticated = authClient.isAuthenticated(); diff --git a/motoko/vetkeys/basic_ibe/icp.yaml b/motoko/vetkeys/basic_ibe/icp.yaml index fd456ac78b..11439e3add 100644 --- a/motoko/vetkeys/basic_ibe/icp.yaml +++ b/motoko/vetkeys/basic_ibe/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" settings: # The vetKD master key this canister derives from, read by the canister via # `Runtime.envVar("VETKD_KEY_NAME")` and captured in stable state at the first diff --git a/motoko/vetkeys/basic_vetkd/README.md b/motoko/vetkeys/basic_vetkd/README.md index d22e21c1ea..b0d61f2214 100644 --- a/motoko/vetkeys/basic_vetkd/README.md +++ b/motoko/vetkeys/basic_vetkd/README.md @@ -34,6 +34,8 @@ icp network start -d icp deploy ``` +> Internet Identity sign-in runs against the Internet Identity canister deployed on the local network (`ii: true` in `icp.yaml`). If signing in fails, update the local network launcher with `icp network update` and restart the network. + Open the frontend URL printed by `icp deploy`. When done: `icp network stop` ## Updating the Candid interface diff --git a/motoko/vetkeys/basic_vetkd/frontend/package.json b/motoko/vetkeys/basic_vetkd/frontend/package.json index 258c8684c1..8f41708613 100644 --- a/motoko/vetkeys/basic_vetkd/frontend/package.json +++ b/motoko/vetkeys/basic_vetkd/frontend/package.json @@ -8,12 +8,12 @@ "dev": "vite" }, "dependencies": { - "@icp-sdk/auth": "^7.1.0", - "@icp-sdk/core": "^5.4.0", - "@icp-sdk/vetkeys": "^0.5.0-beta.0" + "@icp-sdk/auth": "^10.0.0", + "@icp-sdk/core": "^6.1.0", + "@icp-sdk/vetkeys": "^0.7.0" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.2.2", + "@icp-sdk/bindgen": "~0.4.1", "vite": "^6.4.1" } } diff --git a/motoko/vetkeys/basic_vetkd/frontend/src/main.js b/motoko/vetkeys/basic_vetkd/frontend/src/main.js index 1de276229b..beeb81f019 100644 --- a/motoko/vetkeys/basic_vetkd/frontend/src/main.js +++ b/motoko/vetkeys/basic_vetkd/frontend/src/main.js @@ -1,7 +1,7 @@ import "./style.css"; import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"; import { createActor } from "./bindings/backend"; -import { AuthClient, LocalStorage } from "@icp-sdk/auth/client"; +import { AuthClient } from "@icp-sdk/auth/client"; import { HttpAgent } from "@icp-sdk/core/agent"; import { Principal } from "@icp-sdk/core/principal"; import { @@ -207,11 +207,16 @@ async function initAuth() { const isLocal = window.location.hostname === "localhost" || window.location.hostname.endsWith(".localhost"); + // The client mints its delegations by calling the II canister, so its agent + // needs the network's root key to verify the responses. authClient = new AuthClient({ - identityProvider: isLocal - ? "http://id.ai.localhost:8000/authorize" - : "https://id.ai/authorize", - ...(isLocal ? { storage: new LocalStorage(), keyType: "Ed25519" } : {}), + identityProvider: { + authorizeUrl: isLocal + ? "http://id.ai.localhost:8000/authorize" + : "https://id.ai/authorize", + canisterId: "rdmx6-jaaaa-aaaaa-aaadq-cai", + }, + agentOptions: { rootKey: canisterEnv?.IC_ROOT_KEY }, }); if (authClient.isAuthenticated()) { myPrincipal = (await authClient.getIdentity()).getPrincipal(); diff --git a/motoko/vetkeys/basic_vetkd/icp.yaml b/motoko/vetkeys/basic_vetkd/icp.yaml index fd456ac78b..11439e3add 100644 --- a/motoko/vetkeys/basic_vetkd/icp.yaml +++ b/motoko/vetkeys/basic_vetkd/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" settings: # The vetKD master key this canister derives from, read by the canister via # `Runtime.envVar("VETKD_KEY_NAME")` and captured in stable state at the first diff --git a/motoko/vetkeys/encrypted_notes_app_vetkd/README.md b/motoko/vetkeys/encrypted_notes_app_vetkd/README.md index 10f202b9dd..909904f59e 100644 --- a/motoko/vetkeys/encrypted_notes_app_vetkd/README.md +++ b/motoko/vetkeys/encrypted_notes_app_vetkd/README.md @@ -34,6 +34,8 @@ icp network start -d icp deploy ``` +> Internet Identity sign-in runs against the Internet Identity canister deployed on the local network (`ii: true` in `icp.yaml`). If signing in fails, update the local network launcher with `icp network update` and restart the network. + Open the frontend URL printed by `icp deploy`. To run the frontend in development mode with hot reloading (after `icp deploy`): diff --git a/motoko/vetkeys/encrypted_notes_app_vetkd/frontend/package.json b/motoko/vetkeys/encrypted_notes_app_vetkd/frontend/package.json index a47e8cdc67..beeac766b4 100644 --- a/motoko/vetkeys/encrypted_notes_app_vetkd/frontend/package.json +++ b/motoko/vetkeys/encrypted_notes_app_vetkd/frontend/package.json @@ -10,7 +10,7 @@ "check": "svelte-check --tsconfig ./tsconfig.json" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.2.2", + "@icp-sdk/bindgen": "~0.4.1", "@sveltejs/vite-plugin-svelte": "^3.0.2", "@tsconfig/svelte": "^5.0.4", "@types/node": "^24.0.10", @@ -24,9 +24,9 @@ "vite": "^5.4.21" }, "dependencies": { - "@icp-sdk/auth": "^7.1.0", - "@icp-sdk/core": "^5.4.0", - "@icp-sdk/vetkeys": "^0.5.0-beta.0", + "@icp-sdk/auth": "^10.0.0", + "@icp-sdk/core": "^6.1.0", + "@icp-sdk/vetkeys": "^0.7.0", "daisyui": "^1.25.4", "idb-keyval": "6.2.1", "isomorphic-dompurify": "^2.25.0", diff --git a/motoko/vetkeys/encrypted_notes_app_vetkd/frontend/src/store/auth.ts b/motoko/vetkeys/encrypted_notes_app_vetkd/frontend/src/store/auth.ts index 88f138897d..4618ed5b2c 100644 --- a/motoko/vetkeys/encrypted_notes_app_vetkd/frontend/src/store/auth.ts +++ b/motoko/vetkeys/encrypted_notes_app_vetkd/frontend/src/store/auth.ts @@ -1,11 +1,14 @@ import { get, writable } from "svelte/store"; import { type BackendActor, createActor } from "../lib/actor"; -import { AuthClient, LocalStorage } from "@icp-sdk/auth/client"; +import { AuthClient } from "@icp-sdk/auth/client"; +import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"; import type { Principal } from "@icp-sdk/core/principal"; import { CryptoService } from "../lib/crypto"; import { showError } from "./notifications"; import { push } from "svelte-spa-router"; +const rootKey = safeGetCanisterEnv()?.IC_ROOT_KEY; + export type AuthState = | { state: "initializing-auth" } | { state: "anonymous"; actor: BackendActor; client: AuthClient } @@ -26,17 +29,20 @@ async function initAuth() { const isLocal = window.location.hostname === "localhost" || window.location.hostname.endsWith(".localhost"); - // Workaround for https://github.com/dfinity/icp-js-auth/issues/120 - // IdbStorage has a race condition on localhost dev servers. LocalStorage - // avoids IDB on local but uses plain string storage (less secure), so - // production deployments keep the default secure IdbStorage + ECDSA key. + // The client mints its delegations by calling the II canister, so its agent + // needs the network's root key to verify the responses. const client = new AuthClient({ - identityProvider: isLocal - ? "http://id.ai.localhost:8000/authorize" - : "https://id.ai/authorize", - ...(isLocal - ? { storage: new LocalStorage(), keyType: "Ed25519" as const } - : {}), + identityProvider: { + authorizeUrl: isLocal + ? "http://id.ai.localhost:8000/authorize" + : "https://id.ai/authorize", + canisterId: "rdmx6-jaaaa-aaaaa-aaadq-cai", + }, + agentOptions: { rootKey }, + }); + // Leave the signed-in views when the session ends, including from another tab. + client.subscribe(() => { + if (!client.isAuthenticated()) void logout(); }); if (client.isAuthenticated()) { authenticate(client); @@ -65,6 +71,8 @@ export async function logout() { const currentAuth = get(auth); if (currentAuth.state === "initialized") { + // Switch first so the session subscription does not sign out twice. + auth.set({ state: "initializing-auth" }); await currentAuth.client.signOut(); const actor = await createActor(); auth.update(() => ({ @@ -77,8 +85,6 @@ export async function logout() { } export async function authenticate(client: AuthClient) { - handleSessionTimeout(); - try { const identity = await client.getIdentity(); const principal = identity.getPrincipal(); @@ -107,26 +113,3 @@ export async function authenticate(client: AuthClient) { })); } } - -function handleSessionTimeout() { - setTimeout(() => { - try { - const delegation = JSON.parse( - window.localStorage.getItem("ic-delegation") ?? "null", - ) as { - delegations: Array<{ delegation: { expiration: string } }>; - } | null; - if (!delegation) return; - - const expirationTimeMs = - Number.parseInt(delegation.delegations[0].delegation.expiration, 16) / - 1000000; - - setTimeout(() => { - logout(); - }, expirationTimeMs - Date.now()); - } catch { - console.error("Could not handle delegation expiry."); - } - }); -} diff --git a/motoko/vetkeys/encrypted_notes_app_vetkd/icp.yaml b/motoko/vetkeys/encrypted_notes_app_vetkd/icp.yaml index fd456ac78b..11439e3add 100644 --- a/motoko/vetkeys/encrypted_notes_app_vetkd/icp.yaml +++ b/motoko/vetkeys/encrypted_notes_app_vetkd/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" settings: # The vetKD master key this canister derives from, read by the canister via # `Runtime.envVar("VETKD_KEY_NAME")` and captured in stable state at the first diff --git a/motoko/vetkeys/password_manager/README.md b/motoko/vetkeys/password_manager/README.md index ee92c2c046..f0209820a0 100644 --- a/motoko/vetkeys/password_manager/README.md +++ b/motoko/vetkeys/password_manager/README.md @@ -36,6 +36,8 @@ icp network start -d icp deploy ``` +> Internet Identity sign-in runs against the Internet Identity canister deployed on the local network (`ii: true` in `icp.yaml`). If signing in fails, update the local network launcher with `icp network update` and restart the network. + Open the frontend URL printed by `icp deploy`. To run the frontend in development mode with hot reloading (after `icp deploy`): diff --git a/motoko/vetkeys/password_manager/frontend/package.json b/motoko/vetkeys/password_manager/frontend/package.json index ba0e1f6b23..46c7e9e9e7 100644 --- a/motoko/vetkeys/password_manager/frontend/package.json +++ b/motoko/vetkeys/password_manager/frontend/package.json @@ -18,9 +18,9 @@ "vite": "^5.4.21" }, "dependencies": { - "@icp-sdk/auth": "^7.1.0", - "@icp-sdk/core": "^5.4.0", - "@icp-sdk/vetkeys": "^0.5.0-beta.0", + "@icp-sdk/auth": "^10.0.0", + "@icp-sdk/core": "^6.1.0", + "@icp-sdk/vetkeys": "^0.7.0", "@popperjs/core": "^2.11.8", "@sveltejs/vite-plugin-svelte": "^3.0.2", "@tailwindcss/postcss": "^4.0.6", diff --git a/motoko/vetkeys/password_manager/frontend/src/store/auth.ts b/motoko/vetkeys/password_manager/frontend/src/store/auth.ts index 54a75e50ae..ebc284a7f0 100644 --- a/motoko/vetkeys/password_manager/frontend/src/store/auth.ts +++ b/motoko/vetkeys/password_manager/frontend/src/store/auth.ts @@ -1,12 +1,14 @@ import "../lib/init.ts"; import { get, writable } from "svelte/store"; -import { AuthClient, LocalStorage } from "@icp-sdk/auth/client"; -import { DelegationIdentity } from "@icp-sdk/core/identity"; +import { AuthClient } from "@icp-sdk/auth/client"; +import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"; import type { Principal } from "@icp-sdk/core/principal"; import { replace } from "svelte-spa-router"; import { createEncryptedMaps } from "../lib/encrypted_maps.js"; import { EncryptedMaps } from "@icp-sdk/vetkeys/encrypted_maps"; +const rootKey = safeGetCanisterEnv()?.IC_ROOT_KEY; + export type AuthState = | { state: "initializing-auth"; @@ -34,17 +36,20 @@ async function initAuth() { const isLocalEnv = window.location.hostname === "localhost" || window.location.hostname.endsWith(".localhost"); - // Workaround for https://github.com/dfinity/icp-js-auth/issues/120 - // IdbStorage has a race condition on localhost dev servers. LocalStorage - // avoids IDB on local but uses plain string storage (less secure), so - // production deployments keep the default secure IdbStorage + ECDSA key. + // The client mints its delegations by calling the II canister, so its agent + // needs the network's root key to verify the responses. const client = new AuthClient({ - identityProvider: isLocalEnv - ? "http://id.ai.localhost:8000/authorize" - : "https://id.ai/authorize", - ...(isLocalEnv - ? { storage: new LocalStorage(), keyType: "Ed25519" as const } - : {}), + identityProvider: { + authorizeUrl: isLocalEnv + ? "http://id.ai.localhost:8000/authorize" + : "https://id.ai/authorize", + canisterId: "rdmx6-jaaaa-aaaaa-aaadq-cai", + }, + agentOptions: { rootKey }, + }); + // Leave the signed-in views when the session ends, including from another tab. + client.subscribe(() => { + if (!client.isAuthenticated()) void logout(); }); if (client.isAuthenticated()) { void authenticate(client); @@ -79,18 +84,17 @@ export async function logout() { const currentAuth = get(auth); if (currentAuth.state === "initialized") { - await currentAuth.client.signOut(); + // Switch first so the session subscription does not sign out twice. auth.update(() => ({ state: "anonymous", client: currentAuth.client, })); + await currentAuth.client.signOut(); void replace("/"); } } export async function authenticate(client: AuthClient) { - void handleSessionTimeout(client); - try { const identity = await client.getIdentity(); const encryptedMaps = await createEncryptedMaps({ identity }); @@ -108,22 +112,3 @@ export async function authenticate(client: AuthClient) { })); } } - -// set a timer when the II session will expire and log the user out -async function handleSessionTimeout(client: AuthClient) { - try { - const identity = await client.getIdentity(); - if (!(identity instanceof DelegationIdentity)) return; - - const chain = identity.getDelegation(); - // expiration is a BigInt of nanoseconds since epoch - const expirationMs = - Number(chain.delegations[0].delegation.expiration) / 1_000_000; - - setTimeout(() => { - void logout(); - }, expirationMs - Date.now()); - } catch { - console.error("Could not handle delegation expiry."); - } -} diff --git a/motoko/vetkeys/password_manager/icp.yaml b/motoko/vetkeys/password_manager/icp.yaml index fd456ac78b..11439e3add 100644 --- a/motoko/vetkeys/password_manager/icp.yaml +++ b/motoko/vetkeys/password_manager/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" settings: # The vetKD master key this canister derives from, read by the canister via # `Runtime.envVar("VETKD_KEY_NAME")` and captured in stable state at the first diff --git a/motoko/vetkeys/password_manager_with_metadata/README.md b/motoko/vetkeys/password_manager_with_metadata/README.md index fe0f4cad91..70f5a3a4b5 100644 --- a/motoko/vetkeys/password_manager_with_metadata/README.md +++ b/motoko/vetkeys/password_manager_with_metadata/README.md @@ -39,6 +39,8 @@ icp network start -d icp deploy ``` +> Internet Identity sign-in runs against the Internet Identity canister deployed on the local network (`ii: true` in `icp.yaml`). If signing in fails, update the local network launcher with `icp network update` and restart the network. + Open the frontend URL printed by `icp deploy`. To run the frontend in development mode with hot reloading (after `icp deploy`): diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/package.json b/motoko/vetkeys/password_manager_with_metadata/frontend/package.json index adaf711ac4..d1d2d6a3f2 100644 --- a/motoko/vetkeys/password_manager_with_metadata/frontend/package.json +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/package.json @@ -9,7 +9,7 @@ "preview": "vite preview" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.2.2", + "@icp-sdk/bindgen": "~0.4.1", "@rollup/plugin-typescript": "^12.1.2", "@tailwindcss/postcss": "^4.0.6", "@tailwindcss/vite": "^4.0.0", @@ -21,9 +21,9 @@ "vite": "^5.4.21" }, "dependencies": { - "@icp-sdk/auth": "^7.1.0", - "@icp-sdk/core": "^5.4.0", - "@icp-sdk/vetkeys": "^0.5.0-beta.0", + "@icp-sdk/auth": "^10.0.0", + "@icp-sdk/core": "^6.1.0", + "@icp-sdk/vetkeys": "^0.7.0", "@sveltejs/vite-plugin-svelte": "^3.0.2", "daisyui": "^4.12.23", "svelte": "^4.2.19", diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts b/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts index 1f54fb574e..f6ab41b664 100644 --- a/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts @@ -1,6 +1,6 @@ import { get, writable } from "svelte/store"; -import { AuthClient, LocalStorage } from "@icp-sdk/auth/client"; -import { DelegationIdentity } from "@icp-sdk/core/identity"; +import { AuthClient } from "@icp-sdk/auth/client"; +import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"; import type { Principal } from "@icp-sdk/core/principal"; import { replace } from "svelte-spa-router"; import { @@ -8,6 +8,8 @@ import { createPasswordManager, } from "../lib/password_manager.js"; +const rootKey = safeGetCanisterEnv()?.IC_ROOT_KEY; + export type AuthState = | { state: "initializing-auth"; @@ -35,17 +37,20 @@ async function initAuth() { const isLocalEnv = window.location.hostname === "localhost" || window.location.hostname.endsWith(".localhost"); - // Workaround for https://github.com/dfinity/icp-js-auth/issues/120 - // IdbStorage has a race condition on localhost dev servers. LocalStorage - // avoids IDB on local but uses plain string storage (less secure), so - // production deployments keep the default secure IdbStorage + ECDSA key. + // The client mints its delegations by calling the II canister, so its agent + // needs the network's root key to verify the responses. const client = new AuthClient({ - identityProvider: isLocalEnv - ? "http://id.ai.localhost:8000/authorize" - : "https://id.ai/authorize", - ...(isLocalEnv - ? { storage: new LocalStorage(), keyType: "Ed25519" as const } - : {}), + identityProvider: { + authorizeUrl: isLocalEnv + ? "http://id.ai.localhost:8000/authorize" + : "https://id.ai/authorize", + canisterId: "rdmx6-jaaaa-aaaaa-aaadq-cai", + }, + agentOptions: { rootKey }, + }); + // Leave the signed-in views when the session ends, including from another tab. + client.subscribe(() => { + if (!client.isAuthenticated()) void logout(); }); if (client.isAuthenticated()) { await authenticate(client); @@ -80,18 +85,17 @@ export async function logout() { const currentAuth = get(auth); if (currentAuth.state === "initialized") { - await currentAuth.client.signOut(); + // Switch first so the session subscription does not sign out twice. auth.update(() => ({ state: "anonymous", client: currentAuth.client, })); + await currentAuth.client.signOut(); await replace("/"); } } export async function authenticate(client: AuthClient) { - void handleSessionTimeout(client); - try { const identity = await client.getIdentity(); const passwordManager = await createPasswordManager({ identity }); @@ -109,22 +113,3 @@ export async function authenticate(client: AuthClient) { })); } } - -// set a timer when the II session will expire and log the user out -async function handleSessionTimeout(client: AuthClient) { - try { - const identity = await client.getIdentity(); - if (!(identity instanceof DelegationIdentity)) return; - - const chain = identity.getDelegation(); - // expiration is a BigInt of nanoseconds since epoch - const expirationMs = - Number(chain.delegations[0].delegation.expiration) / 1_000_000; - - setTimeout(() => { - void logout(); - }, expirationMs - Date.now()); - } catch { - console.error("Could not handle delegation expiry."); - } -} diff --git a/motoko/vetkeys/password_manager_with_metadata/icp.yaml b/motoko/vetkeys/password_manager_with_metadata/icp.yaml index fd456ac78b..11439e3add 100644 --- a/motoko/vetkeys/password_manager_with_metadata/icp.yaml +++ b/motoko/vetkeys/password_manager_with_metadata/icp.yaml @@ -1,7 +1,7 @@ canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" settings: # The vetKD master key this canister derives from, read by the canister via # `Runtime.envVar("VETKD_KEY_NAME")` and captured in stable state at the first diff --git a/motoko/who_am_i/README.md b/motoko/who_am_i/README.md index f036142bf6..570db6d718 100644 --- a/motoko/who_am_i/README.md +++ b/motoko/who_am_i/README.md @@ -33,6 +33,8 @@ icp network start -d icp deploy ``` +> Internet Identity sign-in runs against the Internet Identity canister deployed on the local network (`ii: true` in `icp.yaml`). If signing in fails, update the local network launcher with `icp network update` and restart the network. + The frontend is served by the asset canister. To run the Vite dev server with hot reload during frontend development: ```bash diff --git a/motoko/who_am_i/icp.yaml b/motoko/who_am_i/icp.yaml index 0987be668c..2302e4c7c0 100644 --- a/motoko/who_am_i/icp.yaml +++ b/motoko/who_am_i/icp.yaml @@ -6,7 +6,7 @@ networks: canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@v5.1.0" - name: frontend recipe: diff --git a/motoko/who_am_i/src/frontend/package.json b/motoko/who_am_i/src/frontend/package.json index 412b87e6d0..d4df8d9d58 100644 --- a/motoko/who_am_i/src/frontend/package.json +++ b/motoko/who_am_i/src/frontend/package.json @@ -8,13 +8,13 @@ "dev": "vite" }, "dependencies": { - "@icp-sdk/auth": "~5.0.0", - "@icp-sdk/core": "~5.2.0", + "@icp-sdk/auth": "~10.0.0", + "@icp-sdk/core": "~6.1.0", "react": "18.3.1", "react-dom": "18.3.1" }, "devDependencies": { - "@icp-sdk/bindgen": "~0.3.0", + "@icp-sdk/bindgen": "~0.4.1", "@types/react": "18.3.12", "@types/react-dom": "18.3.1", "@vitejs/plugin-react": "4.3.3", diff --git a/motoko/who_am_i/src/frontend/src/App.jsx b/motoko/who_am_i/src/frontend/src/App.jsx index 08af5ce2e0..7564430b8b 100644 --- a/motoko/who_am_i/src/frontend/src/App.jsx +++ b/motoko/who_am_i/src/frontend/src/App.jsx @@ -1,61 +1,35 @@ import React, { useState, useEffect } from 'react'; -import { AuthClient } from '@icp-sdk/auth/client'; -import { createBackendActor, identityProviderUrl } from './actor'; +import { authClient, createBackendActor } from './actor'; // Reusable button component const Button = ({ onClick, children }) => ; const App = () => { - const [state, setState] = useState({ - actor: undefined, - authClient: undefined, - isAuthenticated: false, - principal: 'Click "Whoami" to see your principal ID' - }); + const [isAuthenticated, setIsAuthenticated] = useState(authClient.isAuthenticated()); + const [principal, setPrincipal] = useState('Click "Whoami" to see your principal ID'); - // Initialize auth client - useEffect(() => { - updateActor(); - }, []); - - const updateActor = async () => { - const authClient = await AuthClient.create(); - const identity = authClient.getIdentity(); - const actor = createBackendActor(identity); - const isAuthenticated = await authClient.isAuthenticated(); - - setState((prev) => ({ - ...prev, - actor, - authClient, - isAuthenticated - })); - }; + // Track the sign-in status, including changes made in another tab. + useEffect(() => authClient.subscribe(() => setIsAuthenticated(authClient.isAuthenticated())), []); const login = async () => { - await state.authClient.login({ - identityProvider: identityProviderUrl, - onSuccess: updateActor - }); + try { + await authClient.signIn(); + } catch (error) { + console.error('Sign-in failed:', error); + } }; const logout = async () => { - await state.authClient.logout(); - updateActor(); + await authClient.signOut(); }; const whoami = async () => { - setState((prev) => ({ - ...prev, - principal: 'Loading...' - })); + setPrincipal('Loading...'); - const result = await state.actor.whoami(); - const principal = result.toString(); - setState((prev) => ({ - ...prev, - principal - })); + // Read the identity at call time: it is installed only once signIn() completes. + const identity = authClient.isAuthenticated() ? await authClient.getIdentity() : undefined; + const result = await createBackendActor(identity).whoami(); + setPrincipal(result.toString()); }; return ( @@ -82,7 +56,7 @@ const App = () => { - {!state.isAuthenticated ? ( + {!isAuthenticated ? ( ) : ( @@ -90,10 +64,10 @@ const App = () => { - {state.principal && ( + {principal && (