atmosphere explorer

add handle resolution debug

handle.invalid 231df811 8c64c0c6

verified
+148 -10
+26
src/utils/api.ts
··· 215 215 ): Promise<LinksWithRecords> => 216 216 getConstellation("/links", target, collection, path, cursor, limit || 100); 217 217 218 + export interface HandleResolveResult { 219 + success: boolean; 220 + did?: string; 221 + error?: string; 222 + } 223 + 224 + export const resolveHandleDetailed = async (handle: Handle) => { 225 + const dnsResolver = new DohJsonHandleResolver({ dohUrl: "https://dns.google/resolve?" }); 226 + const httpResolver = new WellKnownHandleResolver(); 227 + 228 + const tryResolve = async ( 229 + resolver: DohJsonHandleResolver | WellKnownHandleResolver, 230 + ): Promise<HandleResolveResult> => { 231 + try { 232 + const did = await resolver.resolve(handle); 233 + return { success: true, did }; 234 + } catch (err: any) { 235 + return { success: false, error: err.message ?? String(err) }; 236 + } 237 + }; 238 + 239 + const [dns, http] = await Promise.all([tryResolve(dnsResolver), tryResolve(httpResolver)]); 240 + 241 + return { dns, http }; 242 + }; 243 + 218 244 export { 219 245 didDocCache, 220 246 getAllBacklinks,
+122 -10
src/views/repo.tsx
··· 23 23 NavMenu, 24 24 } from "../components/dropdown.jsx"; 25 25 import { Favicon } from "../components/favicon.jsx"; 26 + import { Modal } from "../components/modal.jsx"; 26 27 import { 27 28 addNotification, 28 29 removeNotification, 29 30 updateNotification, 30 31 } from "../components/notification.jsx"; 31 - import Tooltip from "../components/tooltip.jsx"; 32 32 import { isTouchDevice } from "../layout.jsx"; 33 33 import { 34 34 didDocCache, 35 + type HandleResolveResult, 35 36 labelerCache, 36 37 resolveHandle, 38 + resolveHandleDetailed, 37 39 resolveLexiconAuthority, 38 40 resolvePDS, 39 41 validateHandle, ··· 54 56 const [filter, setFilter] = createSignal<string>(); 55 57 const [validHandles, setValidHandles] = createStore<Record<string, boolean>>({}); 56 58 const [rotationKeys, setRotationKeys] = createSignal<Array<string>>([]); 59 + const [handleModalAlias, setHandleModalAlias] = createSignal<string | null>(null); 60 + const [handleDetailedResult, setHandleDetailedResult] = createSignal<{ 61 + dns: HandleResolveResult; 62 + http: HandleResolveResult; 63 + } | null>(null); 57 64 let rpc: Client; 58 65 let pds: string; 59 66 const did = params.repo!; ··· 502 509 <div class="flex items-center gap-1 text-sm text-neutral-700 dark:text-neutral-300"> 503 510 <span>{alias}</span> 504 511 <Show when={alias.startsWith("at://")}> 505 - <Tooltip 506 - text={ 507 - validHandles[alias] === true ? "Valid handle" 508 - : validHandles[alias] === undefined ? 509 - "Validating" 510 - : "Invalid handle" 511 - } 512 + <button 513 + class="flex items-center rounded p-0.5 hover:bg-neutral-200 dark:hover:bg-neutral-700" 514 + onClick={async () => { 515 + setHandleDetailedResult(null); 516 + setHandleModalAlias(alias); 517 + const handle = alias.replace("at://", "") as Handle; 518 + const result = await resolveHandleDetailed(handle); 519 + if (handleModalAlias() === alias) 520 + setHandleDetailedResult(result); 521 + }} 512 522 > 513 523 <span 514 524 classList={{ 515 - "iconify lucide--check text-green-600 dark:text-green-400": 525 + "iconify text-base lucide--check text-green-600 dark:text-green-400": 516 526 validHandles[alias] === true, 517 527 "iconify lucide--x text-red-500 dark:text-red-400": 518 528 validHandles[alias] === false, ··· 520 530 validHandles[alias] === undefined, 521 531 }} 522 532 ></span> 523 - </Tooltip> 533 + </button> 524 534 </Show> 525 535 </div> 526 536 )} 527 537 </For> 528 538 </div> 529 539 </Show> 540 + 541 + {/* Handle Verification Modal */} 542 + <Modal 543 + open={handleModalAlias() !== null} 544 + onClose={() => setHandleModalAlias(null)} 545 + contentClass="dark:bg-dark-300 dark:shadow-dark-700 pointer-events-auto w-max max-w-[90vw] rounded-lg border-[0.5px] border-neutral-300 bg-white p-4 shadow-md sm:max-w-xl dark:border-neutral-700" 546 + > 547 + <div class="mb-2 flex items-center justify-between gap-4"> 548 + <p class="truncate font-semibold"> 549 + {handleModalAlias()?.replace("at://", "")} 550 + </p> 551 + <button 552 + onclick={() => setHandleModalAlias(null)} 553 + class="flex shrink-0 items-center rounded-md p-1.5 text-neutral-500 hover:bg-neutral-100 hover:text-neutral-700 active:bg-neutral-200 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-200 dark:active:bg-neutral-600" 554 + > 555 + <span class="iconify lucide--x"></span> 556 + </button> 557 + </div> 558 + <div class="flex flex-col gap-2"> 559 + <Show 560 + when={handleDetailedResult()} 561 + fallback={ 562 + <div class="flex items-center gap-2 py-2"> 563 + <span class="iconify lucide--loader-circle animate-spin"></span> 564 + <span>Resolving handle...</span> 565 + </div> 566 + } 567 + > 568 + {(result) => { 569 + const expectedDid = didDocument().id; 570 + const dnsOk = () => 571 + result().dns.success && result().dns.did === expectedDid; 572 + const httpOk = () => 573 + result().http.success && result().http.did === expectedDid; 574 + const dnsMismatch = () => 575 + result().dns.success && result().dns.did !== expectedDid; 576 + const httpMismatch = () => 577 + result().http.success && result().http.did !== expectedDid; 578 + 579 + return ( 580 + <div class="grid grid-cols-[auto_1fr] items-center gap-x-1.5"> 581 + {/* DNS Result */} 582 + <span 583 + classList={{ 584 + "iconify lucide--check text-green-600 dark:text-green-400": 585 + dnsOk(), 586 + "iconify lucide--x text-red-500 dark:text-red-400": !dnsOk(), 587 + }} 588 + ></span> 589 + <span class="font-medium">DNS (TXT record)</span> 590 + <span></span> 591 + <div class="mb-2 text-sm wrap-anywhere text-neutral-500 dark:text-neutral-400"> 592 + <Show 593 + when={result().dns.success} 594 + fallback={ 595 + <div class="text-red-500 dark:text-red-400"> 596 + {result().dns.error} 597 + </div> 598 + } 599 + > 600 + <div>{result().dns.did}</div> 601 + <Show when={dnsMismatch()}> 602 + <div class="text-red-500 dark:text-red-400"> 603 + Expected: {expectedDid} 604 + </div> 605 + </Show> 606 + </Show> 607 + </div> 608 + 609 + {/* HTTP Result */} 610 + <span 611 + classList={{ 612 + "iconify lucide--check text-green-600 dark:text-green-400": 613 + httpOk(), 614 + "iconify lucide--x text-red-500 dark:text-red-400": !httpOk(), 615 + }} 616 + ></span> 617 + <span class="font-medium">HTTP (.well-known)</span> 618 + <span></span> 619 + <div class="text-sm wrap-anywhere text-neutral-500 dark:text-neutral-400"> 620 + <Show 621 + when={result().http.success} 622 + fallback={ 623 + <div class="text-red-500 dark:text-red-400"> 624 + {result().http.error} 625 + </div> 626 + } 627 + > 628 + <div>{result().http.did}</div> 629 + <Show when={httpMismatch()}> 630 + <div class="text-red-500 dark:text-red-400"> 631 + Expected: {expectedDid} 632 + </div> 633 + </Show> 634 + </Show> 635 + </div> 636 + </div> 637 + ); 638 + }} 639 + </Show> 640 + </div> 641 + </Modal> 530 642 531 643 {/* Services Section */} 532 644 <Show when={didDocument().service}>