Graphical PDS migrator for AT Protocol

passport

Changed files
+103 -2
islands
routes
+73 -2
islands/MigrationSetup.tsx
··· 1 - import { useState } from "preact/hooks"; 1 + import { useState, useEffect } from "preact/hooks"; 2 + import { IS_BROWSER } from "fresh/runtime"; 2 3 3 4 interface MigrationSetupProps { 4 5 service?: string | null; ··· 10 11 interface ServerDescription { 11 12 inviteCodeRequired: boolean; 12 13 availableUserDomains: string[]; 14 + } 15 + 16 + interface UserPassport { 17 + did: string; 18 + handle: string; 19 + pds: string; 20 + createdAt?: string; 13 21 } 14 22 15 23 export default function MigrationSetup(props: MigrationSetupProps) { ··· 27 35 const [isLoading, setIsLoading] = useState(false); 28 36 const [showConfirmation, setShowConfirmation] = useState(false); 29 37 const [confirmationText, setConfirmationText] = useState(""); 38 + const [passport, setPassport] = useState<UserPassport | null>(null); 39 + 40 + useEffect(() => { 41 + if (!IS_BROWSER) return; 42 + 43 + const fetchPassport = async () => { 44 + try { 45 + const response = await fetch("/api/me", { 46 + credentials: "include", 47 + }); 48 + if (!response.ok) { 49 + throw new Error("Failed to fetch user profile"); 50 + } 51 + const userData = await response.json(); 52 + if (userData) { 53 + // Get PDS URL from the current service 54 + const pdsResponse = await fetch(`/api/resolve-pds?did=${userData.did}`); 55 + const pdsData = await pdsResponse.json(); 56 + 57 + setPassport({ 58 + did: userData.did, 59 + handle: userData.handle, 60 + pds: pdsData.pds || "Unknown", 61 + createdAt: new Date().toISOString() // TODO: Get actual creation date from API 62 + }); 63 + } 64 + } catch (error) { 65 + console.error("Failed to fetch passport:", error); 66 + } 67 + }; 68 + 69 + fetchPassport(); 70 + }, []); 30 71 31 72 const checkServerDescription = async (serviceUrl: string) => { 32 73 try { ··· 122 163 <div class="mt-2 text-sm text-gray-500 dark:text-gray-400 font-mono">FLIGHT: MIG-2024</div> 123 164 </div> 124 165 166 + {/* Passport Section */} 167 + {passport && ( 168 + <div class="mb-8 bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 border border-gray-200 dark:border-gray-700"> 169 + <div class="flex items-center justify-between mb-4"> 170 + <h3 class="text-lg font-semibold text-gray-900 dark:text-white">Current Passport</h3> 171 + <div class="text-xs text-gray-500 dark:text-gray-400 font-mono">ISSUED: {new Date().toLocaleDateString()}</div> 172 + </div> 173 + <div class="grid grid-cols-2 gap-4 text-sm"> 174 + <div> 175 + <div class="text-gray-500 dark:text-gray-400 mb-1">Handle</div> 176 + <div class="font-mono text-gray-900 dark:text-white">{passport.handle}</div> 177 + </div> 178 + <div> 179 + <div class="text-gray-500 dark:text-gray-400 mb-1">DID</div> 180 + <div class="font-mono text-gray-900 dark:text-white break-all">{passport.did}</div> 181 + </div> 182 + <div> 183 + <div class="text-gray-500 dark:text-gray-400 mb-1">Citizen of PDS</div> 184 + <div class="font-mono text-gray-900 dark:text-white break-all">{passport.pds}</div> 185 + </div> 186 + <div> 187 + <div class="text-gray-500 dark:text-gray-400 mb-1">Account Age</div> 188 + <div class="font-mono text-gray-900 dark:text-white"> 189 + {passport.createdAt ? new Date(passport.createdAt).toLocaleDateString() : "Unknown"} 190 + </div> 191 + </div> 192 + </div> 193 + </div> 194 + )} 195 + 125 196 <form onSubmit={handleSubmit} class="space-y-6"> 126 197 {error && ( 127 198 <div class="bg-red-50 dark:bg-red-900 rounded-lg"> ··· 221 292 222 293 <div> 223 294 <label class="block text-sm font-medium text-gray-700 dark:text-gray-300"> 224 - Contact Email 295 + Email 225 296 <span class="text-xs text-gray-500 ml-1">(Emergency Contact)</span> 226 297 </label> 227 298 <div class="relative">
+30
routes/api/resolve-pds.ts
··· 1 + import { resolver } from "../../lib/id-resolver.ts"; 2 + import { define } from "../../utils.ts"; 3 + 4 + export const handler = define.handlers({ 5 + async GET(ctx) { 6 + const url = new URL(ctx.req.url); 7 + const did = url.searchParams.get("did"); 8 + 9 + if (!did) { 10 + return new Response(JSON.stringify({ error: "DID parameter is required" }), { 11 + status: 400, 12 + headers: { "Content-Type": "application/json" } 13 + }); 14 + } 15 + 16 + try { 17 + const pds = await resolver.resolveDidToPdsUrl(did); 18 + return new Response(JSON.stringify({ pds }), { 19 + status: 200, 20 + headers: { "Content-Type": "application/json" } 21 + }); 22 + } catch (error) { 23 + console.error("Failed to resolve PDS:", error); 24 + return new Response(JSON.stringify({ error: "Failed to resolve PDS" }), { 25 + status: 500, 26 + headers: { "Content-Type": "application/json" } 27 + }); 28 + } 29 + } 30 + });