Suite of AT Protocol TypeScript libraries built on web standards
21
fork

Configure Feed

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

at main 262 lines 6.2 kB view raw
1import { concat, equals } from "@atp/bytes"; 2import { 3 cidForCbor as cidForCborBytes, 4 cidForRawHash, 5 decode as decodeLexCbor, 6 encode as encodeLexCbor, 7 parseCidFromBytes as parseDaslCidFromBytes, 8 verifyCidForBytes as verifyLexCidForBytes, 9} from "@atp/lex/cbor"; 10import { 11 asCid, 12 type Cid, 13 createCid, 14 isPlainObject, 15 type LexMap, 16 type LexValue, 17 SHA2_256_MULTIHASH_CODE, 18 validateCidString, 19} from "@atp/lex/data"; 20import { 21 encodeLexBytes, 22 encodeLexLink, 23 parseLexBytes, 24 parseLexLink, 25} from "@atp/lex/json"; 26import { create as createDigest } from "multiformats/hashes/digest"; 27 28export type CborBlock<T = unknown> = { 29 cid: Cid; 30 bytes: Uint8Array; 31 value: T; 32}; 33 34export const cborEncode = <T = unknown>(data: T): Uint8Array => { 35 return encodeLexCbor(normalizeLexValue(data) as LexValue); 36}; 37 38export const cborDecode = <T = unknown>(bytes: Uint8Array): T => { 39 return decodeLexCbor(bytes) as T; 40}; 41 42export const dataToCborBlock = async <T = unknown>( 43 data: T, 44): Promise<CborBlock<T>> => { 45 const bytes = cborEncode(data); 46 const cid = await cidForCborBytes(bytes); 47 return { cid, bytes, value: data }; 48}; 49 50export const cidForCbor = async (data: unknown): Promise<Cid> => { 51 return await cidForCborBytes(cborEncode(data)); 52}; 53 54export const isValidCid = (cidStr: string): boolean => { 55 return validateCidString(cidStr); 56}; 57 58export const cborBytesToRecord = ( 59 bytes: Uint8Array, 60): Record<string, unknown> => { 61 const val = cborDecode(bytes); 62 if (!isPlainObject(val)) { 63 throw new Error(`Expected object, got: ${val}`); 64 } 65 return val as Record<string, unknown>; 66}; 67 68export const verifyCidForBytes = async ( 69 cid: Cid, 70 bytes: Uint8Array, 71): Promise<void> => { 72 await verifyLexCidForBytes(cid, bytes); 73}; 74 75export const sha256ToCid = (hash: Uint8Array, codec: number): Cid => { 76 const digest = createDigest(SHA2_256_MULTIHASH_CODE, hash); 77 return createCid(codec, digest); 78}; 79 80export const sha256RawToCid = (hash: Uint8Array): Cid => { 81 return cidForRawHash(hash); 82}; 83 84export const parseCidFromBytes = (cidBytes: Uint8Array): Cid => { 85 return parseDaslCidFromBytes(cidBytes); 86}; 87 88export class VerifyCidTransform 89 extends TransformStream<Uint8Array, Uint8Array> { 90 private chunks: Uint8Array[] = []; 91 92 constructor(public cid: Cid) { 93 super({ 94 transform: (chunk: Uint8Array, controller) => { 95 this.chunks.push(chunk); 96 controller.enqueue(chunk); 97 }, 98 flush: async (controller) => { 99 try { 100 const data = concat(this.chunks); 101 const hash = new Uint8Array( 102 await crypto.subtle.digest("SHA-256", new Uint8Array(data)), 103 ); 104 const actual = sha256RawToCid(hash); 105 if (!actual.equals(cid)) { 106 controller.error(new VerifyCidError(cid, actual)); 107 } 108 } catch (err) { 109 controller.error(asError(err)); 110 } 111 }, 112 }); 113 } 114} 115 116const asError = (err: unknown): Error => 117 err instanceof Error ? err : new Error("Unexpected error", { cause: err }); 118 119export class VerifyCidError extends Error { 120 constructor( 121 public expected: Cid, 122 public actual: Cid, 123 ) { 124 super("Bad cid check"); 125 } 126} 127 128export type JsonValue = 129 | boolean 130 | number 131 | string 132 | null 133 | undefined 134 | unknown 135 | Array<JsonValue> 136 | { [key: string]: JsonValue }; 137 138export type IpldValue = 139 | JsonValue 140 | Cid 141 | Uint8Array 142 | Array<IpldValue> 143 | { [key: string]: IpldValue }; 144 145export const jsonToIpld = (val: JsonValue): IpldValue => { 146 if (Array.isArray(val)) { 147 return val.map((item) => jsonToIpld(item)); 148 } 149 if (val && typeof val === "object") { 150 const obj = val as Record<string, unknown>; 151 const link = parseLexLink(obj); 152 if (link) { 153 return link; 154 } 155 156 const bytes = parseLexBytes(obj); 157 if (bytes) { 158 return bytes; 159 } 160 161 const toReturn: Record<string, IpldValue> = {}; 162 for (const [key, value] of Object.entries(obj)) { 163 toReturn[key] = jsonToIpld(value as JsonValue); 164 } 165 return toReturn; 166 } 167 return val; 168}; 169 170export const ipldToJson = (val: IpldValue): JsonValue => { 171 if (Array.isArray(val)) { 172 return val.map((item) => ipldToJson(item)); 173 } 174 if (val && typeof val === "object") { 175 if (val instanceof Uint8Array) { 176 return encodeLexBytes(val); 177 } 178 179 const cid = asCid(val); 180 if (cid) { 181 return encodeLexLink(cid); 182 } 183 184 const toReturn: Record<string, JsonValue> = {}; 185 for ( 186 const [key, value] of Object.entries(val as Record<string, IpldValue>) 187 ) { 188 toReturn[key] = ipldToJson(value); 189 } 190 return toReturn; 191 } 192 return val as JsonValue; 193}; 194 195export const ipldEquals = (a: IpldValue, b: IpldValue): boolean => { 196 if (Array.isArray(a) && Array.isArray(b)) { 197 if (a.length !== b.length) return false; 198 for (let i = 0; i < a.length; i++) { 199 if (!ipldEquals(a[i], b[i])) return false; 200 } 201 return true; 202 } 203 204 if (a && b && typeof a === "object" && typeof b === "object") { 205 if (a instanceof Uint8Array && b instanceof Uint8Array) { 206 return equals(a, b); 207 } 208 209 const cidA = asCid(a); 210 const cidB = asCid(b); 211 if (cidA && cidB) { 212 return cidA.equals(cidB); 213 } 214 215 const objA = a as Record<string, IpldValue>; 216 const objB = b as Record<string, IpldValue>; 217 if (Object.keys(objA).length !== Object.keys(objB).length) return false; 218 for (const key of Object.keys(objA)) { 219 if (!ipldEquals(objA[key], objB[key])) return false; 220 } 221 return true; 222 } 223 224 return a === b; 225}; 226 227function normalizeLexValue(input: unknown): LexValue { 228 if (input instanceof Uint8Array) { 229 return input; 230 } 231 232 if (ArrayBuffer.isView(input)) { 233 return new Uint8Array( 234 input.buffer, 235 input.byteOffset, 236 input.byteLength, 237 ); 238 } 239 240 if (input instanceof ArrayBuffer) { 241 return new Uint8Array(input); 242 } 243 244 if (Array.isArray(input)) { 245 return input.map((item) => normalizeLexValue(item)); 246 } 247 248 const cid = asCid(input); 249 if (cid) { 250 return cid; 251 } 252 253 if (isPlainObject(input)) { 254 const normalized: LexMap = {}; 255 for (const [key, value] of Object.entries(input)) { 256 normalized[key] = normalizeLexValue(value); 257 } 258 return normalized; 259 } 260 261 return input as LexValue; 262}