Fork of github.com/did-method-plc/did-method-plc
1import { check, cidForCbor } from '@atproto/common' 2import { Keypair } from '@atproto/crypto' 3import axios, { AxiosError } from 'axios' 4import { 5 atprotoOp, 6 createUpdateOp, 7 didForCreateOp, 8 tombstoneOp, 9 updateAtprotoKeyOp, 10 updateHandleOp, 11 updatePdsOp, 12 updateRotationKeysOp, 13} from './operations' 14import * as t from './types' 15 16export class Client { 17 constructor(public url: string) {} 18 19 private async makeGetReq(url: string) { 20 try { 21 const res = await axios.get(url) 22 return res.data 23 } catch (err) { 24 if (!axios.isAxiosError(err)) { 25 throw err 26 } 27 throw PlcClientError.fromAxiosError(err) 28 } 29 } 30 31 async getDocument(did: string): Promise<t.DidDocument> { 32 return await this.makeGetReq(`${this.url}/${encodeURIComponent(did)}`) 33 } 34 35 async getDocumentData(did: string): Promise<t.DocumentData> { 36 return await this.makeGetReq(`${this.url}/${encodeURIComponent(did)}/data`) 37 } 38 39 async getOperationLog(did: string): Promise<t.CompatibleOpOrTombstone[]> { 40 return await this.makeGetReq(`${this.url}/${encodeURIComponent(did)}/log`) 41 } 42 43 async getAuditableLog(did: string): Promise<t.ExportedOp[]> { 44 return await this.makeGetReq( 45 `${this.url}/${encodeURIComponent(did)}/log/audit`, 46 ) 47 } 48 49 postOpUrl(did: string): string { 50 return `${this.url}/${encodeURIComponent(did)}` 51 } 52 53 async getLastOp(did: string): Promise<t.CompatibleOpOrTombstone> { 54 return await this.makeGetReq( 55 `${this.url}/${encodeURIComponent(did)}/log/last`, 56 ) 57 } 58 59 async sendOperation(did: string, op: t.OpOrTombstone) { 60 try { 61 await axios.post(this.postOpUrl(did), op) 62 } catch (err) { 63 if (!axios.isAxiosError(err)) { 64 throw err 65 } 66 throw PlcClientError.fromAxiosError(err) 67 } 68 } 69 70 async export(after?: string, count?: number): Promise<t.ExportedOp[]> { 71 const url = new URL(`${this.url}/export`) 72 if (after) { 73 url.searchParams.append('after', after) 74 } 75 if (count !== undefined) { 76 url.searchParams.append('count', count.toString(10)) 77 } 78 const res = await axios.get(url.toString()) 79 const lines = res.data.split('\n') 80 return lines.map((l) => JSON.parse(l)) 81 } 82 83 async createDid(opts: { 84 signingKey: string 85 handle: string 86 pds: string 87 rotationKeys: string[] 88 signer: Keypair 89 }): Promise<string> { 90 const op = await atprotoOp({ ...opts, prev: null }) 91 const did = await didForCreateOp(op) 92 await this.sendOperation(did, op) 93 return did 94 } 95 96 async ensureLastOp(did) { 97 const lastOp = await this.getLastOp(did) 98 if (check.is(lastOp, t.def.tombstone)) { 99 throw new Error('Cannot apply op to tombstone') 100 } 101 return lastOp 102 } 103 104 async updateData( 105 did: string, 106 signer: Keypair, 107 fn: (lastOp: t.UnsignedOperation) => Omit<t.UnsignedOperation, 'prev'>, 108 ) { 109 const lastOp = await this.ensureLastOp(did) 110 const op = await createUpdateOp(lastOp, signer, fn) 111 await this.sendOperation(did, op) 112 } 113 114 async updateAtprotoKey(did: string, signer: Keypair, atprotoKey: string) { 115 const lastOp = await this.ensureLastOp(did) 116 const op = await updateAtprotoKeyOp(lastOp, signer, atprotoKey) 117 await this.sendOperation(did, op) 118 } 119 120 async updateHandle(did: string, signer: Keypair, handle: string) { 121 const lastOp = await this.ensureLastOp(did) 122 const op = await updateHandleOp(lastOp, signer, handle) 123 await this.sendOperation(did, op) 124 } 125 126 async updatePds(did: string, signer: Keypair, endpoint: string) { 127 const lastOp = await this.ensureLastOp(did) 128 const op = await updatePdsOp(lastOp, signer, endpoint) 129 await this.sendOperation(did, op) 130 } 131 132 async updateRotationKeys(did: string, signer: Keypair, keys: string[]) { 133 const lastOp = await this.ensureLastOp(did) 134 const op = await updateRotationKeysOp(lastOp, signer, keys) 135 await this.sendOperation(did, op) 136 } 137 138 async tombstone(did: string, signer: Keypair) { 139 const lastOp = await this.ensureLastOp(did) 140 const prev = await cidForCbor(lastOp) 141 const op = await tombstoneOp(prev, signer) 142 await this.sendOperation(did, op) 143 } 144 145 async health() { 146 return await this.makeGetReq(`${this.url}/_health`) 147 } 148} 149 150export class PlcClientError extends Error { 151 constructor( 152 public status: number, 153 public data: unknown, 154 public message: string, 155 ) { 156 super(message) 157 } 158 159 static fromAxiosError(err: AxiosError) { 160 return new PlcClientError( 161 err.response?.status || 500, 162 err.response?.data, 163 err.message, 164 ) 165 } 166} 167 168export default Client