diff --git a/infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/baselineProfiles/0/app-arm64-release.dm b/infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/baselineProfiles/0/app-arm64-release.dm new file mode 100644 index 00000000..31d9873c Binary files /dev/null and b/infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/baselineProfiles/0/app-arm64-release.dm differ diff --git a/infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/baselineProfiles/1/app-arm64-release.dm b/infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/baselineProfiles/1/app-arm64-release.dm new file mode 100644 index 00000000..a437c2ee Binary files /dev/null and b/infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/baselineProfiles/1/app-arm64-release.dm differ diff --git a/infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/eid-w3ds-v0.2.1.0.apk b/infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/eid-w3ds-v0.2.1.0.apk new file mode 100644 index 00000000..2e334d32 Binary files /dev/null and b/infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/eid-w3ds-v0.2.1.0.apk differ diff --git a/infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/output-metadata.json b/infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/output-metadata.json new file mode 100644 index 00000000..625869fa --- /dev/null +++ b/infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/output-metadata.json @@ -0,0 +1,37 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "foundation.metastate.eid_wallet", + "variantName": "arm64Release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 2, + "versionName": "0.1.8", + "outputFile": "app-arm64-release.apk" + } + ], + "elementType": "File", + "baselineProfiles": [ + { + "minApi": 28, + "maxApi": 30, + "baselineProfiles": [ + "baselineProfiles/1/app-arm64-release.dm" + ] + }, + { + "minApi": 31, + "maxApi": 2147483647, + "baselineProfiles": [ + "baselineProfiles/0/app-arm64-release.dm" + ] + } + ], + "minSdkVersionForDexing": 24 +} \ No newline at end of file diff --git a/infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte b/infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte index a7e18281..e4f24ddc 100644 --- a/infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte @@ -374,13 +374,14 @@ "🔍 DEBUG: Is poll request?", !!(signingData?.pollId && signingData?.voteData), ); + + // Only set signing modal for regular signing requests (not blind voting) + isSigningRequest = true; + signingDrawerOpen = true; } catch (error) { console.error("Error decoding signing data:", error); return; } - - isSigningRequest = true; - signingDrawerOpen = true; } catch (error) { console.error("Error parsing signing request:", error); } @@ -597,6 +598,15 @@ blindVoteError = null; // Clear any previous errors console.log("✅ Blind voting request set up successfully"); + console.log("🔍 DEBUG: State after setup:"); + console.log(" - isBlindVotingRequest:", isBlindVotingRequest); + console.log(" - signingDrawerOpen:", signingDrawerOpen); + console.log(" - signingData:", signingData); + console.log(" - signingData.pollId:", signingData?.pollId); + console.log( + " - signingData.pollDetails:", + !!signingData?.pollDetails, + ); } catch (error) { console.error("❌ Error handling blind voting request:", error); } @@ -953,7 +963,7 @@ console.log("Scan QR page mounted, checking for deep link data..."); // Function to handle deep link data - function handleDeepLinkData(data: any) { + async function handleDeepLinkData(data: any) { console.log("Handling deep link data:", data); console.log("Data type:", data.type); console.log("Platform:", data.platform); @@ -999,24 +1009,91 @@ const decodedString = atob(base64Data); signingData = JSON.parse(decodedString); console.log("Decoded signing data:", signingData); + + // Check if this is actually a blind voting request + if (signingData.type === "blind-vote") { + console.log( + "🔍 Blind voting request detected in sign deep link", + ); + + // Set up blind voting state + isBlindVotingRequest = true; + selectedBlindVoteOption = null; + signingDrawerOpen = true; + blindVoteError = null; + + // Extract platform URL from the data + const platformUrl = + signingData.platformUrl || + "http://192.168.0.225:7777"; + + // Set up signingData for blind voting UI + signingData = { + pollId: signingData.pollId, + sessionId: signingData.sessionId, + platform_url: platformUrl, + redirect: redirectUri, // Add the redirect URI from the deep link + // We'll need to fetch poll details, but for now set a placeholder + pollDetails: { + title: "Loading poll details...", + creatorName: "Loading...", + options: ["Loading..."], + }, + }; + + // Fetch poll details in the background + try { + const pollResponse = await fetch( + `${platformUrl}/api/polls/${signingData.pollId}`, + ); + if (pollResponse.ok) { + const pollDetails = + await pollResponse.json(); + signingData.pollDetails = pollDetails; + console.log( + "✅ Poll details fetched:", + pollDetails, + ); + } + } catch (error) { + console.error( + "Failed to fetch poll details:", + error, + ); + blindVoteError = "Failed to load poll details"; + } + + return; + } + + // Regular signing request + isSigningRequest = true; + signingDrawerOpen = true; + console.log("Signing modal should now be open"); } catch (error) { console.error("Error decoding signing data:", error); return; } - isSigningRequest = true; - signingDrawerOpen = true; - console.log("Signing modal should now be open"); } - } - // Handle blind voting requests - if (data.type === "blind-vote") { - console.log("🔍 Blind voting request detected"); - isBlindVotingRequest = true; - selectedBlindVoteOption = null; - signingData = data; - signingDrawerOpen = true; - blindVoteError = null; // Clear any previous errors - return; + } else if (data.type === "reveal") { + console.log("Handling reveal deep link"); + // Handle reveal deep link + const pollId = data.pollId; + + if (pollId) { + console.log("🔍 Reveal request for poll:", pollId); + + // Set up reveal state + revealPollId = pollId; + isRevealRequest = true; + + console.log("✅ Reveal request set up successfully"); + console.log("🔍 DEBUG: State after setup:"); + console.log(" - revealPollId:", revealPollId); + console.log(" - isRevealRequest:", isRevealRequest); + } else { + console.error("Missing pollId in reveal request"); + } } } @@ -1032,7 +1109,7 @@ try { const data = JSON.parse(deepLinkData); console.log("Parsed deep link data:", data); - handleDeepLinkData(data); + await handleDeepLinkData(data); // Clear both storage keys to be safe sessionStorage.removeItem("deepLinkData"); sessionStorage.removeItem("pendingDeepLink"); @@ -1048,40 +1125,36 @@ } // Listen for deep link events when already on the page - const handleAuthEvent = (event: CustomEvent) => { + const handleAuthEvent = async (event: CustomEvent) => { console.log("Received deepLinkAuth event:", event.detail); - handleDeepLinkData({ + await handleDeepLinkData({ type: "auth", ...event.detail, }); }; - const handleSignEvent = (event: CustomEvent) => { + const handleSignEvent = async (event: CustomEvent) => { console.log("Received deepLinkSign event:", event.detail); - handleDeepLinkData({ + await handleDeepLinkData({ type: "sign", ...event.detail, }); }; - window.addEventListener( - "deepLinkAuth", - handleAuthEvent as EventListener, + window.addEventListener("deepLinkAuth", (event) => + handleAuthEvent(event as CustomEvent), ); - window.addEventListener( - "deepLinkSign", - handleSignEvent as EventListener, + window.addEventListener("deepLinkSign", (event) => + handleSignEvent(event as CustomEvent), ); // Cleanup event listeners onDestroy(() => { - window.removeEventListener( - "deepLinkAuth", - handleAuthEvent as EventListener, + window.removeEventListener("deepLinkAuth", (event) => + handleAuthEvent(event as CustomEvent), ); - window.removeEventListener( - "deepLinkSign", - handleSignEvent as EventListener, + window.removeEventListener("deepLinkSign", (event) => + handleSignEvent(event as CustomEvent), ); }); }); diff --git a/infrastructure/eid-wallet/src/routes/+layout.svelte b/infrastructure/eid-wallet/src/routes/+layout.svelte index 80fdfc5b..f60c1526 100644 --- a/infrastructure/eid-wallet/src/routes/+layout.svelte +++ b/infrastructure/eid-wallet/src/routes/+layout.svelte @@ -1,298 +1,360 @@ {#if showSplashScreen} diff --git a/platforms/eVoting/src/app/(app)/layout.tsx b/platforms/eVoting/src/app/(app)/layout.tsx index 2b0c898f..eaaf9d04 100644 --- a/platforms/eVoting/src/app/(app)/layout.tsx +++ b/platforms/eVoting/src/app/(app)/layout.tsx @@ -14,6 +14,13 @@ import { } from "@/components/ui/dialog"; import { ProtectedRoute } from "@/components/auth/protected-route"; +// Deeplink handling for reveal functionality +declare global { + interface WindowEventMap { + deepLinkReveal: CustomEvent<{ pollId: string }>; + } +} + export default function AppLayout({ children, }: Readonly<{ @@ -21,6 +28,29 @@ export default function AppLayout({ }>) { const { logout } = useAuth(); const [disclaimerAccepted, setDisclaimerAccepted] = useState(false); + const router = useRouter(); + + // Handle deeplink reveal requests + useEffect(() => { + const handleDeepLinkReveal = (event: CustomEvent<{ pollId: string }>) => { + console.log("🔍 Deep link reveal request received:", event.detail); + const { pollId } = event.detail; + + // Navigate to the poll page to show reveal interface + router.push(`/${pollId}`); + + // Store the reveal request in sessionStorage for the poll page to pick up + sessionStorage.setItem("revealRequest", JSON.stringify({ pollId, timestamp: Date.now() })); + }; + + // Listen for deeplink reveal events + window.addEventListener("deepLinkReveal", handleDeepLinkReveal as EventListener); + + // Cleanup + return () => { + window.removeEventListener("deepLinkReveal", handleDeepLinkReveal as EventListener); + }; + }, [router]); return ( diff --git a/platforms/eVoting/src/components/blind-voting-interface.tsx b/platforms/eVoting/src/components/blind-voting-interface.tsx index 0f3e1814..075cecef 100644 --- a/platforms/eVoting/src/components/blind-voting-interface.tsx +++ b/platforms/eVoting/src/components/blind-voting-interface.tsx @@ -4,6 +4,7 @@ import { Vote, UserX, Shield, CheckCircle } from 'lucide-react'; import { Button } from "@/components/ui/button"; import { useToast } from "@/hooks/use-toast"; import type { Poll } from "@/lib/pollApi"; +import { createRevealDeepLink } from "@/lib/utils/mobile-detection"; interface BlindVotingInterfaceProps { poll: Poll; @@ -19,8 +20,29 @@ export default function BlindVotingInterface({ poll, userId, hasVoted, onVoteSub const [error, setError] = useState(''); const [voteStatus, setVoteStatus] = useState<{ hasVoted: boolean; vote: any } | null>(null); const [isConnected, setIsConnected] = useState(false); + const [showRevealInterface, setShowRevealInterface] = useState(false); + const [revealRequest, setRevealRequest] = useState(null); const { toast } = useToast(); + // Check for reveal requests from deeplinks + useEffect(() => { + const storedRevealRequest = sessionStorage.getItem("revealRequest"); + if (storedRevealRequest) { + try { + const request = JSON.parse(storedRevealRequest); + if (request.pollId === poll.id) { + console.log("🔍 Reveal request detected for this poll:", request); + setRevealRequest(request); + setShowRevealInterface(true); + // Clear the stored request + sessionStorage.removeItem("revealRequest"); + } + } catch (error) { + console.error("Error parsing reveal request:", error); + } + } + }, [poll.id]); + // SSE connection for real-time vote status updates useEffect(() => { if (!poll.id || !userId) return; @@ -135,6 +157,47 @@ export default function BlindVotingInterface({ poll, userId, hasVoted, onVoteSub pollId: poll.id }); + // Show reveal interface if requested via deeplink + if (showRevealInterface) { + return ( +
+
+ +

Reveal Your Vote

+

Use your eID wallet to reveal your vote choice

+
+ +
+
+

Reveal Request

+

+ A reveal request was detected for this poll. Please use your eID wallet to reveal your vote. +

+
+ +
+

+ Poll ID: {poll.id} +

+

+ Reveal request received at: {revealRequest?.timestamp ? new Date(revealRequest.timestamp).toLocaleString() : 'Unknown'} +

+
+ +
+
Next Steps:
+
    +
  1. Open your eID wallet
  2. +
  3. Navigate to the reveal section
  4. +
  5. Select this poll to reveal your vote
  6. +
  7. Your vote choice will be displayed locally
  8. +
+
+
+
+ ); + } + if (currentHasVoted) { return (
@@ -151,41 +214,60 @@ export default function BlindVotingInterface({ poll, userId, hasVoted, onVoteSub

Reveal Your Vote

- Use this QR code to reveal your vote choice when you're ready + Use this to reveal your vote choice when you're ready

- {/* Reveal QR Code */} + {/* Responsive Reveal Section */}
-
- + +
+ + {/* Desktop: Show QR code */} +
+
+ -
- -
-

- Scan this QR code with your eID wallet to reveal your vote -

-

- Poll ID: {poll.id} -

+
+ +
+

+ Scan this QR code with your eID wallet to reveal your vote +

+

+ Poll ID: {poll.id} +

+
- {/* Instructions */} -
-
How to Reveal:
-
    -
  1. Scan the reveal QR code with your eID wallet
  2. -
  3. Confirm your identity in the wallet
  4. -
  5. Your vote choice will be revealed locally
  6. -
  7. Your vote remains completely private and anonymous
  8. -
-
+ {/* Instructions */} +
+
How to Reveal:
+
    +
  1. Use the button/QR code to open your eID wallet
  2. +
  3. Confirm your identity in the wallet
  4. +
  5. Your vote choice will be revealed locally
  6. +
  7. Your vote remains completely private and anonymous
  8. +
+
); diff --git a/platforms/eVoting/src/lib/utils/mobile-detection.ts b/platforms/eVoting/src/lib/utils/mobile-detection.ts index d381195b..d3d0ed0e 100644 --- a/platforms/eVoting/src/lib/utils/mobile-detection.ts +++ b/platforms/eVoting/src/lib/utils/mobile-detection.ts @@ -7,4 +7,8 @@ export function isMobileDevice(): boolean { export function getDeepLinkUrl(qrData: string): string { return qrData; +} + +export function createRevealDeepLink(pollId: string): string { + return `w3ds://reveal?pollId=${pollId}`; } \ No newline at end of file