+15
-20
scripts/setup.js
+15
-20
scripts/setup.js
···
17
17
function parseArgs() {
18
18
const args = process.argv.slice(2)
19
19
const opts = {
20
-
handle: null,
21
20
pds: null,
22
21
plcUrl: 'https://plc.directory',
23
22
relayUrl: 'https://bsky.network'
24
23
}
25
24
26
25
for (let i = 0; i < args.length; i++) {
27
-
if (args[i] === '--handle' && args[i + 1]) {
28
-
opts.handle = args[++i]
29
-
} else if (args[i] === '--pds' && args[i + 1]) {
26
+
if (args[i] === '--pds' && args[i + 1]) {
30
27
opts.pds = args[++i]
31
28
} else if (args[i] === '--plc-url' && args[i + 1]) {
32
29
opts.plcUrl = args[++i]
···
35
32
}
36
33
}
37
34
38
-
if (!opts.handle || !opts.pds) {
39
-
console.error('Usage: node scripts/setup.js --handle <handle> --pds <pds-url>')
35
+
if (!opts.pds) {
36
+
console.error('Usage: node scripts/setup.js --pds <pds-url>')
40
37
console.error('')
41
38
console.error('Options:')
42
-
console.error(' --handle Handle name (e.g., "alice")')
43
39
console.error(' --pds PDS URL (e.g., "https://atproto-pds.chad-53c.workers.dev")')
44
40
console.error(' --plc-url PLC directory URL (default: https://plc.directory)')
45
41
console.error(' --relay-url Relay URL (default: https://bsky.network)')
46
42
process.exit(1)
47
43
}
44
+
45
+
// Handle is just the PDS hostname
46
+
opts.handle = new URL(opts.pds).host
48
47
49
48
return opts
50
49
}
···
289
288
async function createGenesisOperation(opts) {
290
289
const { didKey, handle, pdsUrl, cryptoKey } = opts
291
290
292
-
// Build the full handle
293
-
const pdsHost = new URL(pdsUrl).host
294
-
const fullHandle = `${handle}.${pdsHost}`
295
-
291
+
// Handle is already the full hostname
296
292
const operation = {
297
293
type: 'plc_operation',
298
294
rotationKeys: [didKey],
299
295
verificationMethods: {
300
296
atproto: didKey
301
297
},
302
-
alsoKnownAs: [`at://${fullHandle}`],
298
+
alsoKnownAs: [`at://${handle}`],
303
299
services: {
304
300
atproto_pds: {
305
301
type: 'AtprotoPersonalDataServer',
···
312
308
// Sign the operation
313
309
operation.sig = await signPlcOperation(operation, cryptoKey)
314
310
315
-
return { operation, fullHandle }
311
+
return { operation, handle }
316
312
}
317
313
318
314
async function deriveDidFromOperation(operation) {
···
429
425
430
426
console.log('PDS Federation Setup')
431
427
console.log('====================')
432
-
console.log(`Handle: ${opts.handle}`)
433
428
console.log(`PDS: ${opts.pds}`)
434
429
console.log('')
435
430
···
442
437
443
438
// Step 2: Create genesis operation
444
439
console.log('Creating PLC genesis operation...')
445
-
const { operation, fullHandle } = await createGenesisOperation({
440
+
const { operation, handle } = await createGenesisOperation({
446
441
didKey,
447
442
handle: opts.handle,
448
443
pdsUrl: opts.pds,
···
450
445
})
451
446
const did = await deriveDidFromOperation(operation)
452
447
console.log(` DID: ${did}`)
453
-
console.log(` Handle: ${fullHandle}`)
448
+
console.log(` Handle: ${handle}`)
454
449
console.log('')
455
450
456
451
// Step 3: Register with PLC directory
···
462
457
// Step 4: Initialize PDS
463
458
console.log(`Initializing PDS at ${opts.pds}...`)
464
459
const privateKeyHex = bytesToHex(keyPair.privateKey)
465
-
await initializePds(opts.pds, did, privateKeyHex, fullHandle)
460
+
await initializePds(opts.pds, did, privateKeyHex, handle)
466
461
console.log(' PDS initialized!')
467
462
console.log('')
468
463
···
477
472
478
473
// Step 6: Save credentials
479
474
const credentials = {
480
-
handle: fullHandle,
475
+
handle,
481
476
did,
482
477
privateKeyHex: bytesToHex(keyPair.privateKey),
483
478
didKey,
···
485
480
createdAt: new Date().toISOString()
486
481
}
487
482
488
-
const credentialsFile = `./credentials-${opts.handle}.json`
483
+
const credentialsFile = `./credentials.json`
489
484
saveCredentials(credentialsFile, credentials)
490
485
491
486
// Final output
492
487
console.log('Setup Complete!')
493
488
console.log('===============')
494
-
console.log(`Handle: ${fullHandle}`)
489
+
console.log(`Handle: ${handle}`)
495
490
console.log(`DID: ${did}`)
496
491
console.log(`PDS: ${opts.pds}`)
497
492
console.log('')
+62
-12
src/pds.js
+62
-12
src/pds.js
···
216
216
}
217
217
return obj
218
218
}
219
+
case 6: { // tag
220
+
// length is the tag number
221
+
const taggedValue = read()
222
+
if (length === CBOR_TAG_CID) {
223
+
// CID link: byte string with 0x00 multibase prefix, return raw CID bytes
224
+
return taggedValue.slice(1) // strip 0x00 prefix
225
+
}
226
+
return taggedValue
227
+
}
219
228
case 7: { // special
220
229
if (info === 20) return false
221
230
if (info === 21) return true
···
1014
1023
rkey = rkey || createTid()
1015
1024
const uri = `at://${did}/${collection}/${rkey}`
1016
1025
1017
-
// Encode and hash record
1018
-
const recordBytes = cborEncode(record)
1026
+
// Encode and hash record (must use DAG-CBOR for proper key ordering)
1027
+
const recordBytes = cborEncodeDagCbor(record)
1019
1028
const recordCid = await createCid(recordBytes)
1020
1029
const recordCidStr = cidToString(recordCid)
1021
1030
···
1404
1413
if (rows.length === 0) {
1405
1414
return Response.json({ error: 'RecordNotFound', message: 'record not found' }, { status: 404 })
1406
1415
}
1407
-
// Get the record block
1408
1416
const recordCid = rows[0].cid
1409
-
const blockRows = this.sql.exec(
1410
-
`SELECT cid, data FROM blocks WHERE cid = ?`, recordCid
1417
+
1418
+
// Get latest commit
1419
+
const commits = this.sql.exec(
1420
+
`SELECT cid FROM commits ORDER BY seq DESC LIMIT 1`
1421
+
).toArray()
1422
+
if (commits.length === 0) {
1423
+
return Response.json({ error: 'RepoNotFound', message: 'no commits' }, { status: 404 })
1424
+
}
1425
+
const commitCid = commits[0].cid
1426
+
1427
+
// Build proof chain: commit -> MST path -> record
1428
+
// Include commit block, all MST nodes on path to record, and record block
1429
+
const blocks = []
1430
+
const included = new Set()
1431
+
1432
+
const addBlock = (cidStr) => {
1433
+
if (included.has(cidStr)) return
1434
+
included.add(cidStr)
1435
+
const blockRows = this.sql.exec(
1436
+
`SELECT data FROM blocks WHERE cid = ?`, cidStr
1437
+
).toArray()
1438
+
if (blockRows.length > 0) {
1439
+
blocks.push({ cid: cidStr, data: new Uint8Array(blockRows[0].data) })
1440
+
}
1441
+
}
1442
+
1443
+
// Add commit block
1444
+
addBlock(commitCid)
1445
+
1446
+
// Get commit to find data root
1447
+
const commitRows = this.sql.exec(
1448
+
`SELECT data FROM blocks WHERE cid = ?`, commitCid
1411
1449
).toArray()
1412
-
if (blockRows.length === 0) {
1413
-
return Response.json({ error: 'RecordNotFound', message: 'block not found' }, { status: 404 })
1450
+
if (commitRows.length > 0) {
1451
+
const commit = cborDecode(new Uint8Array(commitRows[0].data))
1452
+
if (commit.data) {
1453
+
const dataRootCid = cidToString(commit.data)
1454
+
// Collect MST path blocks (this includes all MST nodes)
1455
+
const mstBlocks = this.collectMstBlocks(dataRootCid)
1456
+
for (const block of mstBlocks) {
1457
+
addBlock(block.cid)
1458
+
}
1459
+
}
1414
1460
}
1415
-
const blocks = blockRows.map(b => ({
1416
-
cid: b.cid,
1417
-
data: new Uint8Array(b.data)
1418
-
}))
1419
-
const car = buildCarFile(recordCid, blocks)
1461
+
1462
+
// Add record block
1463
+
addBlock(recordCid)
1464
+
1465
+
const car = buildCarFile(commitCid, blocks)
1420
1466
return new Response(car, {
1421
1467
headers: { 'content-type': 'application/vnd.ipld.car' }
1422
1468
})
···
1482
1528
}
1483
1529
1484
1530
const response = await handleRequest(request, env)
1531
+
// Don't wrap WebSocket upgrades - they need the webSocket property preserved
1532
+
if (response.status === 101) {
1533
+
return response
1534
+
}
1485
1535
return addCorsHeaders(response)
1486
1536
}
1487
1537
}