Migration for did:webs specifically

This commit is contained in:
lewis
2025-12-30 23:07:26 +02:00
parent 70fce2f1f9
commit 76506511a1
10 changed files with 269 additions and 11 deletions
@@ -22,6 +22,7 @@
let checkingHandle = $state(false)
const isResumedMigration = $derived(flow.state.progress.repoImported)
const isDidWeb = $derived(flow.state.sourceDid.startsWith("did:web:"))
$effect(() => {
if (flow.state.step === 'welcome' || flow.state.step === 'choose-handle') {
@@ -187,7 +188,20 @@
}
}
const steps = ['Login', 'Handle', 'Review', 'Transfer', 'Verify Email', 'Verify PLC', 'Complete']
async function completeDidWeb() {
loading = true
try {
await flow.completeDidWebMigration()
} catch (err) {
flow.setError((err as Error).message)
} finally {
loading = false
}
}
const steps = $derived(isDidWeb
? ['Login', 'Handle', 'Review', 'Transfer', 'Verify Email', 'Update DID', 'Complete']
: ['Login', 'Handle', 'Review', 'Transfer', 'Verify Email', 'Verify PLC', 'Complete'])
function getCurrentStepIndex(): number {
switch (flow.state.step) {
case 'welcome':
@@ -197,6 +211,7 @@
case 'migrating': return 3
case 'email-verify': return 4
case 'plc-token':
case 'did-web-update':
case 'finalizing': return 5
case 'success': return 6
default: return 0
@@ -589,6 +604,46 @@
</form>
</div>
{:else if flow.state.step === 'did-web-update'}
<div class="step-content">
<h2>{$_('migration.inbound.didWebUpdate.title')}</h2>
<p>{$_('migration.inbound.didWebUpdate.desc')}</p>
<div class="info-box">
<p>
{$_('migration.inbound.didWebUpdate.yourDid')} <code>{flow.state.sourceDid}</code>
</p>
<p style="margin-top: 12px;">
{$_('migration.inbound.didWebUpdate.updateInstructions')}
</p>
</div>
<div class="code-block">
<pre>{`{
"id": "${flow.state.sourceDid}",
"service": [
{
"id": "#atproto_pds",
"type": "AtprotoPersonalDataServer",
"serviceEndpoint": "${window.location.origin}"
}
]
}`}</pre>
</div>
<div class="warning-box">
<strong>{$_('migration.inbound.didWebUpdate.important')}</strong> {$_('migration.inbound.didWebUpdate.verifyFirst')}
{$_('migration.inbound.didWebUpdate.fileLocation')} <code>https://{flow.state.sourceDid.replace('did:web:', '')}/.well-known/did.json</code>
</div>
<div class="button-row">
<button class="ghost" onclick={() => flow.setStep('email-verify')} disabled={loading}>Back</button>
<button onclick={completeDidWeb} disabled={loading}>
{loading ? $_('migration.inbound.didWebUpdate.completing') : $_('migration.inbound.didWebUpdate.complete')}
</button>
</div>
</div>
{:else if flow.state.step === 'finalizing'}
<div class="step-content">
<h2>Finalizing Migration</h2>
@@ -1021,4 +1076,29 @@
border-radius: var(--radius-lg);
margin-bottom: var(--space-5);
}
.code-block {
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: var(--space-4);
margin-bottom: var(--space-5);
overflow-x: auto;
}
.code-block pre {
margin: 0;
font-family: var(--font-mono);
font-size: var(--text-sm);
white-space: pre-wrap;
word-break: break-all;
}
code {
font-family: var(--font-mono);
background: var(--bg-primary);
padding: 2px 6px;
border-radius: var(--radius-sm);
font-size: 0.9em;
}
</style>
+63 -5
View File
@@ -371,9 +371,13 @@ export function createInboundMigrationFlow() {
return;
}
setProgress({ currentOperation: "Requesting PLC operation token..." });
await sourceClient.requestPlcOperationSignature();
setStep("plc-token");
if (state.sourceDid.startsWith("did:web:")) {
setStep("did-web-update");
} else {
setProgress({ currentOperation: "Requesting PLC operation token..." });
await sourceClient.requestPlcOperationSignature();
setStep("plc-token");
}
} catch (e) {
const err = e as Error & { error?: string; status?: number };
const message = err.message || err.error ||
@@ -401,8 +405,12 @@ export function createInboundMigrationFlow() {
state.targetEmail,
state.targetPassword,
);
await sourceClient.requestPlcOperationSignature();
setStep("plc-token");
if (state.sourceDid.startsWith("did:web:")) {
setStep("did-web-update");
} else {
await sourceClient.requestPlcOperationSignature();
setStep("plc-token");
}
return true;
} catch (e) {
const err = e as Error & { error?: string };
@@ -543,6 +551,55 @@ export function createInboundMigrationFlow() {
await sourceClient.requestPlcOperationSignature();
}
async function completeDidWebMigration(): Promise<void> {
migrationLog("completeDidWebMigration START", {
sourceDid: state.sourceDid,
sourceHandle: state.sourceHandle,
targetHandle: state.targetHandle,
});
if (!sourceClient || !localClient) {
migrationLog("completeDidWebMigration ERROR: Not connected to PDSes");
throw new Error("Not connected to PDSes");
}
setStep("finalizing");
setProgress({ currentOperation: "Activating account..." });
try {
migrationLog("Activating account on NEW PDS");
const activateStart = Date.now();
await localClient.activateAccount();
migrationLog("Account activated", { durationMs: Date.now() - activateStart });
setProgress({ activated: true });
setProgress({ currentOperation: "Deactivating old account..." });
migrationLog("Deactivating account on OLD PDS");
const deactivateStart = Date.now();
try {
await sourceClient.deactivateAccount();
migrationLog("Account deactivated on OLD PDS", {
durationMs: Date.now() - deactivateStart,
});
setProgress({ deactivated: true });
} catch (deactivateErr) {
const err = deactivateErr as Error & { error?: string };
migrationLog("Could not deactivate on OLD PDS", { error: err.message });
}
migrationLog("completeDidWebMigration SUCCESS");
setStep("success");
clearMigrationState();
} catch (e) {
const err = e as Error & { error?: string; status?: number };
const message = err.message || err.error ||
`Unknown error (status ${err.status || "unknown"})`;
migrationLog("completeDidWebMigration FAILED", { error: message });
setError(message);
setStep("did-web-update");
}
}
function reset(): void {
state = {
direction: "inbound",
@@ -614,6 +671,7 @@ export function createInboundMigrationFlow() {
requestPlcToken,
submitPlcToken,
resendPlcToken,
completeDidWebMigration,
reset,
resumeFromState,
getLocalSession,
+1
View File
@@ -6,6 +6,7 @@ export type InboundStep =
| "migrating"
| "email-verify"
| "plc-token"
| "did-web-update"
| "finalizing"
| "success"
| "error";
+11
View File
@@ -1106,6 +1106,17 @@
"resend": "Resend Token",
"resending": "Resending..."
},
"didWebUpdate": {
"title": "Update Your DID Document",
"desc": "Since you're using a did:web identity, you need to update your DID document to point to this PDS.",
"yourDid": "Your DID is:",
"updateInstructions": "Update the did.json file at your domain to point the atproto_pds service endpoint to this PDS:",
"important": "Important:",
"verifyFirst": "Make sure your DID document is updated and publicly accessible before completing the migration.",
"fileLocation": "The file should be at:",
"complete": "Complete Migration",
"completing": "Completing..."
},
"finalizing": {
"title": "Finalizing Migration",
"desc": "Please wait while we complete the migration...",
+11
View File
@@ -1106,6 +1106,17 @@
"resend": "Lähetä uudelleen",
"resending": "Lähetetään..."
},
"didWebUpdate": {
"title": "Päivitä DID-dokumenttisi",
"desc": "Koska käytät did:web-identiteettiä, sinun täytyy päivittää DID-dokumenttisi osoittamaan tähän PDS:ään.",
"yourDid": "DID:si on:",
"updateInstructions": "Päivitä verkkotunnuksesi did.json-tiedosto niin, että atproto_pds-palvelun päätepiste osoittaa tähän PDS:ään:",
"important": "Tärkeää:",
"verifyFirst": "Varmista, että DID-dokumenttisi on päivitetty ja julkisesti saatavilla ennen siirron viimeistelyä.",
"fileLocation": "Tiedoston tulee sijaita:",
"complete": "Viimeistele siirto",
"completing": "Viimeistellään..."
},
"finalizing": {
"title": "Viimeistellään siirtoa",
"desc": "Odota, kun viimeistelemme siirtoa...",
+11
View File
@@ -1106,6 +1106,17 @@
"resend": "再送信",
"resending": "送信中..."
},
"didWebUpdate": {
"title": "DIDドキュメントを更新",
"desc": "did:webアイデンティティを使用しているため、DIDドキュメントを更新してこのPDSを指すようにする必要があります。",
"yourDid": "あなたのDID",
"updateInstructions": "ドメインのdid.jsonファイルを更新して、atproto_pdsサービスエンドポイントをこのPDSに向けてください:",
"important": "重要:",
"verifyFirst": "移行を完了する前に、DIDドキュメントが更新され、公開アクセス可能であることを確認してください。",
"fileLocation": "ファイルの場所:",
"complete": "移行を完了",
"completing": "完了中..."
},
"finalizing": {
"title": "移行を完了中",
"desc": "移行を完了しています...",
+11
View File
@@ -1106,6 +1106,17 @@
"resend": "재전송",
"resending": "전송 중..."
},
"didWebUpdate": {
"title": "DID 문서 업데이트",
"desc": "did:web 아이덴티티를 사용하고 있으므로 DID 문서를 이 PDS를 가리키도록 업데이트해야 합니다.",
"yourDid": "당신의 DID:",
"updateInstructions": "도메인의 did.json 파일을 업데이트하여 atproto_pds 서비스 엔드포인트가 이 PDS를 가리키도록 하세요:",
"important": "중요:",
"verifyFirst": "마이그레이션을 완료하기 전에 DID 문서가 업데이트되고 공개적으로 접근 가능한지 확인하세요.",
"fileLocation": "파일 위치:",
"complete": "마이그레이션 완료",
"completing": "완료 중..."
},
"finalizing": {
"title": "마이그레이션 완료 중",
"desc": "마이그레이션을 완료하는 중입니다...",
+11
View File
@@ -1106,6 +1106,17 @@
"resend": "Skicka igen",
"resending": "Skickar..."
},
"didWebUpdate": {
"title": "Uppdatera ditt DID-dokument",
"desc": "Eftersom du använder en did:web-identitet måste du uppdatera ditt DID-dokument för att peka på denna PDS.",
"yourDid": "Ditt DID är:",
"updateInstructions": "Uppdatera did.json-filen på din domän så att atproto_pds-tjänstens slutpunkt pekar på denna PDS:",
"important": "Viktigt:",
"verifyFirst": "Se till att ditt DID-dokument är uppdaterat och offentligt tillgängligt innan du slutför flytten.",
"fileLocation": "Filen ska finnas på:",
"complete": "Slutför flytt",
"completing": "Slutför..."
},
"finalizing": {
"title": "Slutför flytt",
"desc": "Vänta medan vi slutför flytten...",
+11
View File
@@ -1106,6 +1106,17 @@
"resend": "重新发送",
"resending": "发送中..."
},
"didWebUpdate": {
"title": "更新您的DID文档",
"desc": "由于您使用的是did:web身份,您需要更新DID文档以指向此PDS。",
"yourDid": "您的DID是:",
"updateInstructions": "更新您域名上的did.json文件,将atproto_pds服务端点指向此PDS",
"important": "重要提示:",
"verifyFirst": "在完成迁移之前,请确保您的DID文档已更新并可公开访问。",
"fileLocation": "文件应位于:",
"complete": "完成迁移",
"completing": "完成中..."
},
"finalizing": {
"title": "正在完成迁移",
"desc": "请稍候,正在完成迁移...",
+58 -5
View File
@@ -495,10 +495,20 @@
.back {
color: var(--text-secondary);
text-decoration: none;
padding: var(--space-1) var(--space-2);
margin: calc(-1 * var(--space-1)) calc(-1 * var(--space-2));
border-radius: var(--radius-sm);
transition: background var(--transition-fast), color var(--transition-fast);
}
.back:hover {
color: var(--accent);
background: var(--accent-muted);
}
.back:focus {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.sep {
@@ -508,16 +518,25 @@
.breadcrumb-link {
background: none;
border: none;
padding: 0;
padding: var(--space-1) var(--space-2);
margin: calc(-1 * var(--space-1)) calc(-1 * var(--space-2));
color: var(--accent);
cursor: pointer;
font-size: inherit;
border-radius: var(--radius-sm);
transition: background var(--transition-fast);
}
.breadcrumb-link:hover {
background: var(--accent-muted);
text-decoration: underline;
}
.breadcrumb-link:focus {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.current {
color: var(--text-secondary);
}
@@ -683,19 +702,29 @@
align-items: center;
width: 100%;
padding: var(--space-3);
background: var(--bg-card);
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
cursor: pointer;
text-align: left;
color: var(--text-primary);
transition: border-color var(--transition-fast);
transition: background var(--transition-fast), border-color var(--transition-fast);
}
.collection-link:hover {
background: var(--bg-secondary);
border-color: var(--accent);
}
.collection-link:focus {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.collection-link:active {
background: var(--bg-tertiary);
}
.nsid {
font-weight: var(--font-medium);
color: var(--accent);
@@ -705,6 +734,10 @@
color: var(--text-muted);
}
.collection-link:hover .arrow {
color: var(--accent);
}
.record-list {
list-style: none;
padding: 0;
@@ -718,19 +751,29 @@
display: block;
width: 100%;
padding: var(--space-4);
background: var(--bg-card);
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
cursor: pointer;
text-align: left;
color: var(--text-primary);
transition: border-color var(--transition-fast);
transition: background var(--transition-fast), border-color var(--transition-fast);
}
.record-item:hover {
background: var(--bg-secondary);
border-color: var(--accent);
}
.record-item:focus {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.record-item:active {
background: var(--bg-tertiary);
}
.record-info {
display: flex;
justify-content: space-between;
@@ -927,4 +970,14 @@
padding: var(--space-6);
border-radius: var(--radius-xl);
}
.page ::selection {
background: var(--accent);
color: var(--text-inverse);
}
.page ::-moz-selection {
background: var(--accent);
color: var(--text-inverse);
}
</style>