An in browser local PDS localpds.at
20

Configure Feed

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

handle more methods in pod-relay

authored by

Niall Bunting and committed by
Niall Bunting
(May 31, 2026, 2:24 AM +0100) bd9fe54d d9ac39ed

+75 -12
-1
background.js
··· 492 492 await fulfill(source, requestId, 200, { 493 493 did: userDid, 494 494 active: true, 495 - status: "active", 496 495 rev: repo.rev || "", 497 496 }); 498 497 return;
+75 -11
pod-relay.js
··· 1 + import { generateTid } from './atproto-repo.js'; 2 + 1 3 const SIGNAL_URL = 'wss://ds-pod.com/signal'; 2 4 3 5 export class PodRelay { ··· 78 80 const repo = await this.getRepo(this.did, this.privateKey); 79 81 const { contentType, bytes } = await this._buildResponse(repo, method, params); 80 82 81 - // Send a JSON header frame first so the server knows content-type and length, 82 - // then send the raw bytes as a binary WebSocket frame. 83 - // Avoids base64 bloat and btoa() string-building bugs on large CAR files. 84 83 this._send({ type: 'response-header', requestId, contentType, length: bytes.length }); 85 84 if (this._ws?.readyState === WebSocket.OPEN) { 86 85 this._ws.send(bytes.buffer instanceof ArrayBuffer ? bytes : bytes.buffer); ··· 89 88 } 90 89 91 90 async _buildResponse(repo, method, params) { 91 + const json = (obj) => ({ 92 + contentType: 'application/json', 93 + bytes: new TextEncoder().encode(JSON.stringify(obj)), 94 + }); 95 + const car = async () => ({ 96 + contentType: 'application/vnd.ipld.car', 97 + bytes: await repo.exportCAR(), 98 + }); 99 + const notFound = (msg) => json({ error: 'RecordNotFound', message: msg }); 100 + 92 101 switch (method) { 102 + 103 + // ---------------------------------------------------------------- 104 + // Sync 105 + // ---------------------------------------------------------------- 106 + 93 107 case 'com.atproto.sync.getRepo': 94 - return { contentType: 'application/vnd.ipld.car', bytes: await repo.exportCAR() }; 108 + case 'com.atproto.sync.getBlocks': 109 + case 'com.atproto.sync.getRecord': 110 + return car(); 111 + 95 112 case 'com.atproto.sync.getLatestCommit': 96 113 case 'com.atproto.sync.getHead': 97 - return { contentType: 'application/json', 98 - bytes: new TextEncoder().encode(JSON.stringify({ cid: repo.headCid, rev: repo.rev })) }; 114 + return json({ cid: repo.headCid || '', rev: repo.rev || generateTid() }); 115 + 116 + case 'com.atproto.sync.getRepoStatus': 117 + return json({ did: repo.did, active: true, rev: repo.rev || '' }); 118 + 119 + case 'com.atproto.sync.listRepos': 120 + return json({ repos: [{ did: repo.did, head: repo.headCid || '', rev: repo.rev || '', active: true }] }); 121 + 122 + case 'com.atproto.sync.getBlob': 123 + return json({ error: 'BlobNotFound', message: 'Blob storage not implemented' }); 124 + 99 125 case 'com.atproto.sync.listBlobs': { 100 126 const cids = Array.from(repo.records.values()).map(e => e.cidStr); 101 - return { contentType: 'application/json', 102 - bytes: new TextEncoder().encode(JSON.stringify({ cids })) }; 127 + return json({ cids }); 103 128 } 129 + 130 + case 'com.atproto.sync.requestCrawl': 131 + return json({}); 132 + 133 + // ---------------------------------------------------------------- 134 + // Repo 135 + // ---------------------------------------------------------------- 136 + 137 + case 'com.atproto.repo.describeRepo': { 138 + const collections = [...new Set( 139 + Array.from(repo.records.keys()).map(k => k.split('/')[0]) 140 + )]; 141 + return json({ 142 + handle: repo.did.split(':')[2] || repo.did, 143 + did: repo.did, 144 + collections, 145 + handleIsCorrect: true, 146 + }); 147 + } 148 + 149 + case 'com.atproto.repo.getRecord': { 150 + const { collection, rkey } = params; 151 + const entry = repo.getRecord(collection, rkey); 152 + return entry ? json(entry) : notFound(`${collection}/${rkey} not found`); 153 + } 154 + 155 + case 'com.atproto.repo.listRecords': { 156 + const { collection } = params; 157 + const limit = parseInt(params.limit || '50'); 158 + const records = repo.listRecords(collection).slice(0, limit); 159 + return json({ records }); 160 + } 161 + 162 + case 'com.atproto.repo.listMissingBlobs': 163 + return json({ blobs: [] }); 164 + 165 + // ---------------------------------------------------------------- 166 + // Default 167 + // ---------------------------------------------------------------- 168 + 104 169 default: 105 - return { contentType: 'application/json', 106 - bytes: new TextEncoder().encode(JSON.stringify({ error: 'MethodNotFound', message: method })) }; 170 + return json({ error: 'MethodNotFound', message: method }); 107 171 } 108 172 } 109 - } 173 + }