Our Personal Data Server from scratch! tranquil.farm
pds rust database fun oauth atproto
238
fork

Configure Feed

Select the types of activity you want to include in your feed.

Migration for did:webs specifically

+269 -11
+81 -1
frontend/src/components/migration/InboundWizard.svelte
··· 22 22 let checkingHandle = $state(false) 23 23 24 24 const isResumedMigration = $derived(flow.state.progress.repoImported) 25 + const isDidWeb = $derived(flow.state.sourceDid.startsWith("did:web:")) 25 26 26 27 $effect(() => { 27 28 if (flow.state.step === 'welcome' || flow.state.step === 'choose-handle') { ··· 187 188 } 188 189 } 189 190 190 - const steps = ['Login', 'Handle', 'Review', 'Transfer', 'Verify Email', 'Verify PLC', 'Complete'] 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']) 191 205 function getCurrentStepIndex(): number { 192 206 switch (flow.state.step) { 193 207 case 'welcome': ··· 197 211 case 'migrating': return 3 198 212 case 'email-verify': return 4 199 213 case 'plc-token': 214 + case 'did-web-update': 200 215 case 'finalizing': return 5 201 216 case 'success': return 6 202 217 default: return 0 ··· 589 604 </form> 590 605 </div> 591 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 + 592 647 {:else if flow.state.step === 'finalizing'} 593 648 <div class="step-content"> 594 649 <h2>Finalizing Migration</h2> ··· 1020 1075 padding: var(--space-4); 1021 1076 border-radius: var(--radius-lg); 1022 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; 1023 1103 } 1024 1104 </style>
+63 -5
frontend/src/lib/migration/flow.svelte.ts
··· 371 371 return; 372 372 } 373 373 374 - setProgress({ currentOperation: "Requesting PLC operation token..." }); 375 - await sourceClient.requestPlcOperationSignature(); 376 - setStep("plc-token"); 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 + } 377 381 } catch (e) { 378 382 const err = e as Error & { error?: string; status?: number }; 379 383 const message = err.message || err.error || ··· 401 405 state.targetEmail, 402 406 state.targetPassword, 403 407 ); 404 - await sourceClient.requestPlcOperationSignature(); 405 - setStep("plc-token"); 408 + if (state.sourceDid.startsWith("did:web:")) { 409 + setStep("did-web-update"); 410 + } else { 411 + await sourceClient.requestPlcOperationSignature(); 412 + setStep("plc-token"); 413 + } 406 414 return true; 407 415 } catch (e) { 408 416 const err = e as Error & { error?: string }; ··· 543 551 await sourceClient.requestPlcOperationSignature(); 544 552 } 545 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 + 546 603 function reset(): void { 547 604 state = { 548 605 direction: "inbound", ··· 614 671 requestPlcToken, 615 672 submitPlcToken, 616 673 resendPlcToken, 674 + completeDidWebMigration, 617 675 reset, 618 676 resumeFromState, 619 677 getLocalSession,
+1
frontend/src/lib/migration/types.ts
··· 6 6 | "migrating" 7 7 | "email-verify" 8 8 | "plc-token" 9 + | "did-web-update" 9 10 | "finalizing" 10 11 | "success" 11 12 | "error";
+11
frontend/src/locales/en.json
··· 1106 1106 "resend": "Resend Token", 1107 1107 "resending": "Resending..." 1108 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 + }, 1109 1120 "finalizing": { 1110 1121 "title": "Finalizing Migration", 1111 1122 "desc": "Please wait while we complete the migration...",
+11
frontend/src/locales/fi.json
··· 1106 1106 "resend": "Lähetä uudelleen", 1107 1107 "resending": "Lähetetään..." 1108 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 + }, 1109 1120 "finalizing": { 1110 1121 "title": "Viimeistellään siirtoa", 1111 1122 "desc": "Odota, kun viimeistelemme siirtoa...",
+11
frontend/src/locales/ja.json
··· 1106 1106 "resend": "再送信", 1107 1107 "resending": "送信中..." 1108 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 + }, 1109 1120 "finalizing": { 1110 1121 "title": "移行を完了中", 1111 1122 "desc": "移行を完了しています...",
+11
frontend/src/locales/ko.json
··· 1106 1106 "resend": "재전송", 1107 1107 "resending": "전송 중..." 1108 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 + }, 1109 1120 "finalizing": { 1110 1121 "title": "마이그레이션 완료 중", 1111 1122 "desc": "마이그레이션을 완료하는 중입니다...",
+11
frontend/src/locales/sv.json
··· 1106 1106 "resend": "Skicka igen", 1107 1107 "resending": "Skickar..." 1108 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 + }, 1109 1120 "finalizing": { 1110 1121 "title": "Slutför flytt", 1111 1122 "desc": "Vänta medan vi slutför flytten...",
+11
frontend/src/locales/zh.json
··· 1106 1106 "resend": "重新发送", 1107 1107 "resending": "发送中..." 1108 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 + }, 1109 1120 "finalizing": { 1110 1121 "title": "正在完成迁移", 1111 1122 "desc": "请稍候,正在完成迁移...",
+58 -5
frontend/src/routes/RepoExplorer.svelte
··· 495 495 .back { 496 496 color: var(--text-secondary); 497 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); 498 502 } 499 503 500 504 .back:hover { 501 505 color: var(--accent); 506 + background: var(--accent-muted); 507 + } 508 + 509 + .back:focus { 510 + outline: 2px solid var(--accent); 511 + outline-offset: 2px; 502 512 } 503 513 504 514 .sep { ··· 508 518 .breadcrumb-link { 509 519 background: none; 510 520 border: none; 511 - padding: 0; 521 + padding: var(--space-1) var(--space-2); 522 + margin: calc(-1 * var(--space-1)) calc(-1 * var(--space-2)); 512 523 color: var(--accent); 513 524 cursor: pointer; 514 525 font-size: inherit; 526 + border-radius: var(--radius-sm); 527 + transition: background var(--transition-fast); 515 528 } 516 529 517 530 .breadcrumb-link:hover { 531 + background: var(--accent-muted); 518 532 text-decoration: underline; 533 + } 534 + 535 + .breadcrumb-link:focus { 536 + outline: 2px solid var(--accent); 537 + outline-offset: 2px; 519 538 } 520 539 521 540 .current { ··· 683 702 align-items: center; 684 703 width: 100%; 685 704 padding: var(--space-3); 686 - background: var(--bg-card); 705 + background: var(--bg-primary); 687 706 border: 1px solid var(--border-color); 688 707 border-radius: var(--radius-md); 689 708 cursor: pointer; 690 709 text-align: left; 691 710 color: var(--text-primary); 692 - transition: border-color var(--transition-fast); 711 + transition: background var(--transition-fast), border-color var(--transition-fast); 693 712 } 694 713 695 714 .collection-link:hover { 715 + background: var(--bg-secondary); 696 716 border-color: var(--accent); 697 717 } 698 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 + 699 728 .nsid { 700 729 font-weight: var(--font-medium); 701 730 color: var(--accent); ··· 705 734 color: var(--text-muted); 706 735 } 707 736 737 + .collection-link:hover .arrow { 738 + color: var(--accent); 739 + } 740 + 708 741 .record-list { 709 742 list-style: none; 710 743 padding: 0; ··· 718 751 display: block; 719 752 width: 100%; 720 753 padding: var(--space-4); 721 - background: var(--bg-card); 754 + background: var(--bg-primary); 722 755 border: 1px solid var(--border-color); 723 756 border-radius: var(--radius-md); 724 757 cursor: pointer; 725 758 text-align: left; 726 759 color: var(--text-primary); 727 - transition: border-color var(--transition-fast); 760 + transition: background var(--transition-fast), border-color var(--transition-fast); 728 761 } 729 762 730 763 .record-item:hover { 764 + background: var(--bg-secondary); 731 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); 732 775 } 733 776 734 777 .record-info { ··· 926 969 background: var(--bg-secondary); 927 970 padding: var(--space-6); 928 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); 929 982 } 930 983 </style>