this repo has no description

Migration for did:webs specifically

lewis 799a5db9 12bb2a0a

Changed files
+269 -11
frontend
+81 -1
frontend/src/components/migration/InboundWizard.svelte
··· 22 let checkingHandle = $state(false) 23 24 const isResumedMigration = $derived(flow.state.progress.repoImported) 25 26 $effect(() => { 27 if (flow.state.step === 'welcome' || flow.state.step === 'choose-handle') { ··· 187 } 188 } 189 190 - const steps = ['Login', 'Handle', 'Review', 'Transfer', 'Verify Email', 'Verify PLC', 'Complete'] 191 function getCurrentStepIndex(): number { 192 switch (flow.state.step) { 193 case 'welcome': ··· 197 case 'migrating': return 3 198 case 'email-verify': return 4 199 case 'plc-token': 200 case 'finalizing': return 5 201 case 'success': return 6 202 default: return 0 ··· 589 </form> 590 </div> 591 592 {:else if flow.state.step === 'finalizing'} 593 <div class="step-content"> 594 <h2>Finalizing Migration</h2> ··· 1020 padding: var(--space-4); 1021 border-radius: var(--radius-lg); 1022 margin-bottom: var(--space-5); 1023 } 1024 </style>
··· 22 let checkingHandle = $state(false) 23 24 const isResumedMigration = $derived(flow.state.progress.repoImported) 25 + const isDidWeb = $derived(flow.state.sourceDid.startsWith("did:web:")) 26 27 $effect(() => { 28 if (flow.state.step === 'welcome' || flow.state.step === 'choose-handle') { ··· 188 } 189 } 190 191 + async function completeDidWeb() { 192 + loading = true 193 + try { 194 + await flow.completeDidWebMigration() 195 + } catch (err) { 196 + flow.setError((err as Error).message) 197 + } finally { 198 + loading = false 199 + } 200 + } 201 + 202 + const steps = $derived(isDidWeb 203 + ? ['Login', 'Handle', 'Review', 'Transfer', 'Verify Email', 'Update DID', 'Complete'] 204 + : ['Login', 'Handle', 'Review', 'Transfer', 'Verify Email', 'Verify PLC', 'Complete']) 205 function getCurrentStepIndex(): number { 206 switch (flow.state.step) { 207 case 'welcome': ··· 211 case 'migrating': return 3 212 case 'email-verify': return 4 213 case 'plc-token': 214 + case 'did-web-update': 215 case 'finalizing': return 5 216 case 'success': return 6 217 default: return 0 ··· 604 </form> 605 </div> 606 607 + {:else if flow.state.step === 'did-web-update'} 608 + <div class="step-content"> 609 + <h2>{$_('migration.inbound.didWebUpdate.title')}</h2> 610 + <p>{$_('migration.inbound.didWebUpdate.desc')}</p> 611 + 612 + <div class="info-box"> 613 + <p> 614 + {$_('migration.inbound.didWebUpdate.yourDid')} <code>{flow.state.sourceDid}</code> 615 + </p> 616 + <p style="margin-top: 12px;"> 617 + {$_('migration.inbound.didWebUpdate.updateInstructions')} 618 + </p> 619 + </div> 620 + 621 + <div class="code-block"> 622 + <pre>{`{ 623 + "id": "${flow.state.sourceDid}", 624 + "service": [ 625 + { 626 + "id": "#atproto_pds", 627 + "type": "AtprotoPersonalDataServer", 628 + "serviceEndpoint": "${window.location.origin}" 629 + } 630 + ] 631 + }`}</pre> 632 + </div> 633 + 634 + <div class="warning-box"> 635 + <strong>{$_('migration.inbound.didWebUpdate.important')}</strong> {$_('migration.inbound.didWebUpdate.verifyFirst')} 636 + {$_('migration.inbound.didWebUpdate.fileLocation')} <code>https://{flow.state.sourceDid.replace('did:web:', '')}/.well-known/did.json</code> 637 + </div> 638 + 639 + <div class="button-row"> 640 + <button class="ghost" onclick={() => flow.setStep('email-verify')} disabled={loading}>Back</button> 641 + <button onclick={completeDidWeb} disabled={loading}> 642 + {loading ? $_('migration.inbound.didWebUpdate.completing') : $_('migration.inbound.didWebUpdate.complete')} 643 + </button> 644 + </div> 645 + </div> 646 + 647 {:else if flow.state.step === 'finalizing'} 648 <div class="step-content"> 649 <h2>Finalizing Migration</h2> ··· 1075 padding: var(--space-4); 1076 border-radius: var(--radius-lg); 1077 margin-bottom: var(--space-5); 1078 + } 1079 + 1080 + .code-block { 1081 + background: var(--bg-primary); 1082 + border: 1px solid var(--border); 1083 + border-radius: var(--radius-lg); 1084 + padding: var(--space-4); 1085 + margin-bottom: var(--space-5); 1086 + overflow-x: auto; 1087 + } 1088 + 1089 + .code-block pre { 1090 + margin: 0; 1091 + font-family: var(--font-mono); 1092 + font-size: var(--text-sm); 1093 + white-space: pre-wrap; 1094 + word-break: break-all; 1095 + } 1096 + 1097 + code { 1098 + font-family: var(--font-mono); 1099 + background: var(--bg-primary); 1100 + padding: 2px 6px; 1101 + border-radius: var(--radius-sm); 1102 + font-size: 0.9em; 1103 } 1104 </style>
+63 -5
frontend/src/lib/migration/flow.svelte.ts
··· 371 return; 372 } 373 374 - setProgress({ currentOperation: "Requesting PLC operation token..." }); 375 - await sourceClient.requestPlcOperationSignature(); 376 - setStep("plc-token"); 377 } catch (e) { 378 const err = e as Error & { error?: string; status?: number }; 379 const message = err.message || err.error || ··· 401 state.targetEmail, 402 state.targetPassword, 403 ); 404 - await sourceClient.requestPlcOperationSignature(); 405 - setStep("plc-token"); 406 return true; 407 } catch (e) { 408 const err = e as Error & { error?: string }; ··· 543 await sourceClient.requestPlcOperationSignature(); 544 } 545 546 function reset(): void { 547 state = { 548 direction: "inbound", ··· 614 requestPlcToken, 615 submitPlcToken, 616 resendPlcToken, 617 reset, 618 resumeFromState, 619 getLocalSession,
··· 371 return; 372 } 373 374 + if (state.sourceDid.startsWith("did:web:")) { 375 + setStep("did-web-update"); 376 + } else { 377 + setProgress({ currentOperation: "Requesting PLC operation token..." }); 378 + await sourceClient.requestPlcOperationSignature(); 379 + setStep("plc-token"); 380 + } 381 } catch (e) { 382 const err = e as Error & { error?: string; status?: number }; 383 const message = err.message || err.error || ··· 405 state.targetEmail, 406 state.targetPassword, 407 ); 408 + if (state.sourceDid.startsWith("did:web:")) { 409 + setStep("did-web-update"); 410 + } else { 411 + await sourceClient.requestPlcOperationSignature(); 412 + setStep("plc-token"); 413 + } 414 return true; 415 } catch (e) { 416 const err = e as Error & { error?: string }; ··· 551 await sourceClient.requestPlcOperationSignature(); 552 } 553 554 + async function completeDidWebMigration(): Promise<void> { 555 + migrationLog("completeDidWebMigration START", { 556 + sourceDid: state.sourceDid, 557 + sourceHandle: state.sourceHandle, 558 + targetHandle: state.targetHandle, 559 + }); 560 + 561 + if (!sourceClient || !localClient) { 562 + migrationLog("completeDidWebMigration ERROR: Not connected to PDSes"); 563 + throw new Error("Not connected to PDSes"); 564 + } 565 + 566 + setStep("finalizing"); 567 + setProgress({ currentOperation: "Activating account..." }); 568 + 569 + try { 570 + migrationLog("Activating account on NEW PDS"); 571 + const activateStart = Date.now(); 572 + await localClient.activateAccount(); 573 + migrationLog("Account activated", { durationMs: Date.now() - activateStart }); 574 + setProgress({ activated: true }); 575 + 576 + setProgress({ currentOperation: "Deactivating old account..." }); 577 + migrationLog("Deactivating account on OLD PDS"); 578 + const deactivateStart = Date.now(); 579 + try { 580 + await sourceClient.deactivateAccount(); 581 + migrationLog("Account deactivated on OLD PDS", { 582 + durationMs: Date.now() - deactivateStart, 583 + }); 584 + setProgress({ deactivated: true }); 585 + } catch (deactivateErr) { 586 + const err = deactivateErr as Error & { error?: string }; 587 + migrationLog("Could not deactivate on OLD PDS", { error: err.message }); 588 + } 589 + 590 + migrationLog("completeDidWebMigration SUCCESS"); 591 + setStep("success"); 592 + clearMigrationState(); 593 + } catch (e) { 594 + const err = e as Error & { error?: string; status?: number }; 595 + const message = err.message || err.error || 596 + `Unknown error (status ${err.status || "unknown"})`; 597 + migrationLog("completeDidWebMigration FAILED", { error: message }); 598 + setError(message); 599 + setStep("did-web-update"); 600 + } 601 + } 602 + 603 function reset(): void { 604 state = { 605 direction: "inbound", ··· 671 requestPlcToken, 672 submitPlcToken, 673 resendPlcToken, 674 + completeDidWebMigration, 675 reset, 676 resumeFromState, 677 getLocalSession,
+1
frontend/src/lib/migration/types.ts
··· 6 | "migrating" 7 | "email-verify" 8 | "plc-token" 9 | "finalizing" 10 | "success" 11 | "error";
··· 6 | "migrating" 7 | "email-verify" 8 | "plc-token" 9 + | "did-web-update" 10 | "finalizing" 11 | "success" 12 | "error";
+11
frontend/src/locales/en.json
··· 1106 "resend": "Resend Token", 1107 "resending": "Resending..." 1108 }, 1109 "finalizing": { 1110 "title": "Finalizing Migration", 1111 "desc": "Please wait while we complete the migration...",
··· 1106 "resend": "Resend Token", 1107 "resending": "Resending..." 1108 }, 1109 + "didWebUpdate": { 1110 + "title": "Update Your DID Document", 1111 + "desc": "Since you're using a did:web identity, you need to update your DID document to point to this PDS.", 1112 + "yourDid": "Your DID is:", 1113 + "updateInstructions": "Update the did.json file at your domain to point the atproto_pds service endpoint to this PDS:", 1114 + "important": "Important:", 1115 + "verifyFirst": "Make sure your DID document is updated and publicly accessible before completing the migration.", 1116 + "fileLocation": "The file should be at:", 1117 + "complete": "Complete Migration", 1118 + "completing": "Completing..." 1119 + }, 1120 "finalizing": { 1121 "title": "Finalizing Migration", 1122 "desc": "Please wait while we complete the migration...",
+11
frontend/src/locales/fi.json
··· 1106 "resend": "Lähetä uudelleen", 1107 "resending": "Lähetetään..." 1108 }, 1109 "finalizing": { 1110 "title": "Viimeistellään siirtoa", 1111 "desc": "Odota, kun viimeistelemme siirtoa...",
··· 1106 "resend": "Lähetä uudelleen", 1107 "resending": "Lähetetään..." 1108 }, 1109 + "didWebUpdate": { 1110 + "title": "Päivitä DID-dokumenttisi", 1111 + "desc": "Koska käytät did:web-identiteettiä, sinun täytyy päivittää DID-dokumenttisi osoittamaan tähän PDS:ään.", 1112 + "yourDid": "DID:si on:", 1113 + "updateInstructions": "Päivitä verkkotunnuksesi did.json-tiedosto niin, että atproto_pds-palvelun päätepiste osoittaa tähän PDS:ään:", 1114 + "important": "Tärkeää:", 1115 + "verifyFirst": "Varmista, että DID-dokumenttisi on päivitetty ja julkisesti saatavilla ennen siirron viimeistelyä.", 1116 + "fileLocation": "Tiedoston tulee sijaita:", 1117 + "complete": "Viimeistele siirto", 1118 + "completing": "Viimeistellään..." 1119 + }, 1120 "finalizing": { 1121 "title": "Viimeistellään siirtoa", 1122 "desc": "Odota, kun viimeistelemme siirtoa...",
+11
frontend/src/locales/ja.json
··· 1106 "resend": "再送信", 1107 "resending": "送信中..." 1108 }, 1109 "finalizing": { 1110 "title": "移行を完了中", 1111 "desc": "移行を完了しています...",
··· 1106 "resend": "再送信", 1107 "resending": "送信中..." 1108 }, 1109 + "didWebUpdate": { 1110 + "title": "DIDドキュメントを更新", 1111 + "desc": "did:webアイデンティティを使用しているため、DIDドキュメントを更新してこのPDSを指すようにする必要があります。", 1112 + "yourDid": "あなたのDID:", 1113 + "updateInstructions": "ドメインのdid.jsonファイルを更新して、atproto_pdsサービスエンドポイントをこのPDSに向けてください:", 1114 + "important": "重要:", 1115 + "verifyFirst": "移行を完了する前に、DIDドキュメントが更新され、公開アクセス可能であることを確認してください。", 1116 + "fileLocation": "ファイルの場所:", 1117 + "complete": "移行を完了", 1118 + "completing": "完了中..." 1119 + }, 1120 "finalizing": { 1121 "title": "移行を完了中", 1122 "desc": "移行を完了しています...",
+11
frontend/src/locales/ko.json
··· 1106 "resend": "재전송", 1107 "resending": "전송 중..." 1108 }, 1109 "finalizing": { 1110 "title": "마이그레이션 완료 중", 1111 "desc": "마이그레이션을 완료하는 중입니다...",
··· 1106 "resend": "재전송", 1107 "resending": "전송 중..." 1108 }, 1109 + "didWebUpdate": { 1110 + "title": "DID 문서 업데이트", 1111 + "desc": "did:web 아이덴티티를 사용하고 있으므로 DID 문서를 이 PDS를 가리키도록 업데이트해야 합니다.", 1112 + "yourDid": "당신의 DID:", 1113 + "updateInstructions": "도메인의 did.json 파일을 업데이트하여 atproto_pds 서비스 엔드포인트가 이 PDS를 가리키도록 하세요:", 1114 + "important": "중요:", 1115 + "verifyFirst": "마이그레이션을 완료하기 전에 DID 문서가 업데이트되고 공개적으로 접근 가능한지 확인하세요.", 1116 + "fileLocation": "파일 위치:", 1117 + "complete": "마이그레이션 완료", 1118 + "completing": "완료 중..." 1119 + }, 1120 "finalizing": { 1121 "title": "마이그레이션 완료 중", 1122 "desc": "마이그레이션을 완료하는 중입니다...",
+11
frontend/src/locales/sv.json
··· 1106 "resend": "Skicka igen", 1107 "resending": "Skickar..." 1108 }, 1109 "finalizing": { 1110 "title": "Slutför flytt", 1111 "desc": "Vänta medan vi slutför flytten...",
··· 1106 "resend": "Skicka igen", 1107 "resending": "Skickar..." 1108 }, 1109 + "didWebUpdate": { 1110 + "title": "Uppdatera ditt DID-dokument", 1111 + "desc": "Eftersom du använder en did:web-identitet måste du uppdatera ditt DID-dokument för att peka på denna PDS.", 1112 + "yourDid": "Ditt DID är:", 1113 + "updateInstructions": "Uppdatera did.json-filen på din domän så att atproto_pds-tjänstens slutpunkt pekar på denna PDS:", 1114 + "important": "Viktigt:", 1115 + "verifyFirst": "Se till att ditt DID-dokument är uppdaterat och offentligt tillgängligt innan du slutför flytten.", 1116 + "fileLocation": "Filen ska finnas på:", 1117 + "complete": "Slutför flytt", 1118 + "completing": "Slutför..." 1119 + }, 1120 "finalizing": { 1121 "title": "Slutför flytt", 1122 "desc": "Vänta medan vi slutför flytten...",
+11
frontend/src/locales/zh.json
··· 1106 "resend": "重新发送", 1107 "resending": "发送中..." 1108 }, 1109 "finalizing": { 1110 "title": "正在完成迁移", 1111 "desc": "请稍候,正在完成迁移...",
··· 1106 "resend": "重新发送", 1107 "resending": "发送中..." 1108 }, 1109 + "didWebUpdate": { 1110 + "title": "更新您的DID文档", 1111 + "desc": "由于您使用的是did:web身份,您需要更新DID文档以指向此PDS。", 1112 + "yourDid": "您的DID是:", 1113 + "updateInstructions": "更新您域名上的did.json文件,将atproto_pds服务端点指向此PDS:", 1114 + "important": "重要提示:", 1115 + "verifyFirst": "在完成迁移之前,请确保您的DID文档已更新并可公开访问。", 1116 + "fileLocation": "文件应位于:", 1117 + "complete": "完成迁移", 1118 + "completing": "完成中..." 1119 + }, 1120 "finalizing": { 1121 "title": "正在完成迁移", 1122 "desc": "请稍候,正在完成迁移...",
+58 -5
frontend/src/routes/RepoExplorer.svelte
··· 495 .back { 496 color: var(--text-secondary); 497 text-decoration: none; 498 } 499 500 .back:hover { 501 color: var(--accent); 502 } 503 504 .sep { ··· 508 .breadcrumb-link { 509 background: none; 510 border: none; 511 - padding: 0; 512 color: var(--accent); 513 cursor: pointer; 514 font-size: inherit; 515 } 516 517 .breadcrumb-link:hover { 518 text-decoration: underline; 519 } 520 521 .current { ··· 683 align-items: center; 684 width: 100%; 685 padding: var(--space-3); 686 - background: var(--bg-card); 687 border: 1px solid var(--border-color); 688 border-radius: var(--radius-md); 689 cursor: pointer; 690 text-align: left; 691 color: var(--text-primary); 692 - transition: border-color var(--transition-fast); 693 } 694 695 .collection-link:hover { 696 border-color: var(--accent); 697 } 698 699 .nsid { 700 font-weight: var(--font-medium); 701 color: var(--accent); ··· 705 color: var(--text-muted); 706 } 707 708 .record-list { 709 list-style: none; 710 padding: 0; ··· 718 display: block; 719 width: 100%; 720 padding: var(--space-4); 721 - background: var(--bg-card); 722 border: 1px solid var(--border-color); 723 border-radius: var(--radius-md); 724 cursor: pointer; 725 text-align: left; 726 color: var(--text-primary); 727 - transition: border-color var(--transition-fast); 728 } 729 730 .record-item:hover { 731 border-color: var(--accent); 732 } 733 734 .record-info { ··· 926 background: var(--bg-secondary); 927 padding: var(--space-6); 928 border-radius: var(--radius-xl); 929 } 930 </style>
··· 495 .back { 496 color: var(--text-secondary); 497 text-decoration: none; 498 + padding: var(--space-1) var(--space-2); 499 + margin: calc(-1 * var(--space-1)) calc(-1 * var(--space-2)); 500 + border-radius: var(--radius-sm); 501 + transition: background var(--transition-fast), color var(--transition-fast); 502 } 503 504 .back:hover { 505 color: var(--accent); 506 + background: var(--accent-muted); 507 + } 508 + 509 + .back:focus { 510 + outline: 2px solid var(--accent); 511 + outline-offset: 2px; 512 } 513 514 .sep { ··· 518 .breadcrumb-link { 519 background: none; 520 border: none; 521 + padding: var(--space-1) var(--space-2); 522 + margin: calc(-1 * var(--space-1)) calc(-1 * var(--space-2)); 523 color: var(--accent); 524 cursor: pointer; 525 font-size: inherit; 526 + border-radius: var(--radius-sm); 527 + transition: background var(--transition-fast); 528 } 529 530 .breadcrumb-link:hover { 531 + background: var(--accent-muted); 532 text-decoration: underline; 533 + } 534 + 535 + .breadcrumb-link:focus { 536 + outline: 2px solid var(--accent); 537 + outline-offset: 2px; 538 } 539 540 .current { ··· 702 align-items: center; 703 width: 100%; 704 padding: var(--space-3); 705 + background: var(--bg-primary); 706 border: 1px solid var(--border-color); 707 border-radius: var(--radius-md); 708 cursor: pointer; 709 text-align: left; 710 color: var(--text-primary); 711 + transition: background var(--transition-fast), border-color var(--transition-fast); 712 } 713 714 .collection-link:hover { 715 + background: var(--bg-secondary); 716 border-color: var(--accent); 717 } 718 719 + .collection-link:focus { 720 + outline: 2px solid var(--accent); 721 + outline-offset: 2px; 722 + } 723 + 724 + .collection-link:active { 725 + background: var(--bg-tertiary); 726 + } 727 + 728 .nsid { 729 font-weight: var(--font-medium); 730 color: var(--accent); ··· 734 color: var(--text-muted); 735 } 736 737 + .collection-link:hover .arrow { 738 + color: var(--accent); 739 + } 740 + 741 .record-list { 742 list-style: none; 743 padding: 0; ··· 751 display: block; 752 width: 100%; 753 padding: var(--space-4); 754 + background: var(--bg-primary); 755 border: 1px solid var(--border-color); 756 border-radius: var(--radius-md); 757 cursor: pointer; 758 text-align: left; 759 color: var(--text-primary); 760 + transition: background var(--transition-fast), border-color var(--transition-fast); 761 } 762 763 .record-item:hover { 764 + background: var(--bg-secondary); 765 border-color: var(--accent); 766 + } 767 + 768 + .record-item:focus { 769 + outline: 2px solid var(--accent); 770 + outline-offset: 2px; 771 + } 772 + 773 + .record-item:active { 774 + background: var(--bg-tertiary); 775 } 776 777 .record-info { ··· 969 background: var(--bg-secondary); 970 padding: var(--space-6); 971 border-radius: var(--radius-xl); 972 + } 973 + 974 + .page ::selection { 975 + background: var(--accent); 976 + color: var(--text-inverse); 977 + } 978 + 979 + .page ::-moz-selection { 980 + background: var(--accent); 981 + color: var(--text-inverse); 982 } 983 </style>