Encrypted, ephemeral, private memos on atproto

fix(consumer): return decrypted items

graham.systems ee9625c5 4da335fa

verified
Changed files
+43 -7
packages
consumer
+37 -6
packages/consumer/mod.ts
··· 1 1 import { produceRequirements } from "@cistern/shared"; 2 - import { generateKeys } from "@cistern/crypto"; 2 + import { decryptText, generateKeys } from "@cistern/crypto"; 3 3 import { generateRandomName } from "@puregarlic/randimal"; 4 4 import { is, parse, type RecordKey } from "@atcute/lexicons"; 5 5 import { JetstreamSubscription } from "@atcute/jetstream"; ··· 9 9 AppCisternLexiconItem, 10 10 type AppCisternLexiconPubkey, 11 11 } from "@cistern/lexicon"; 12 - import type { ConsumerOptions, ConsumerParams, LocalKeyPair } from "./types.ts"; 12 + import type { 13 + ConsumerOptions, 14 + ConsumerParams, 15 + DecryptedItem, 16 + LocalKeyPair, 17 + } from "./types.ts"; 13 18 14 19 import type {} from "@atcute/atproto"; 15 20 ··· 88 93 * Asynchronously iterate through items in the user's PDS 89 94 */ 90 95 async *listItems(): AsyncIterator< 91 - AppCisternLexiconItem.Main, 96 + DecryptedItem, 92 97 void, 93 98 undefined 94 99 > { 100 + if (!this.keypair) { 101 + throw new Error("no key pair set; generate a key before listing items"); 102 + } 103 + 95 104 let cursor: string | undefined; 96 105 97 106 while (true) { ··· 112 121 if (res.data.cursor) cursor = res.data.cursor; 113 122 114 123 for (const record of res.data.records) { 115 - yield parse(AppCisternLexiconItem.mainSchema, record.value); 124 + const item = parse(AppCisternLexiconItem.mainSchema, record.value); 125 + const decrypted = decryptText(this.keypair.privateKey, { 126 + nonce: item.nonce, 127 + cipherText: item.ciphertext, 128 + content: item.payload, 129 + hash: item.contentHash, 130 + length: item.contentLength, 131 + }); 132 + 133 + yield { 134 + tid: item.tid, 135 + text: decrypted, 136 + }; 116 137 } 117 138 118 139 if (!cursor) return; ··· 124 145 * @todo Allow specifying Jetstream endpoint 125 146 */ 126 147 async *subscribeToItems(): AsyncIterator< 127 - AppCisternLexiconItem.Main, 148 + DecryptedItem, 128 149 void, 129 150 "stop" | undefined 130 151 > { ··· 150 171 continue; 151 172 } 152 173 153 - if ((yield record) === "stop") return; 174 + const decrypted = decryptText(this.keypair.privateKey, { 175 + nonce: record.nonce, 176 + cipherText: record.ciphertext, 177 + content: record.payload, 178 + hash: record.contentHash, 179 + length: record.contentLength, 180 + }); 181 + 182 + const command = yield { tid: record.tid, text: decrypted }; 183 + 184 + if (command === "stop") return; 154 185 } 155 186 } 156 187 }
+6 -1
packages/consumer/types.ts
··· 1 1 import type { BaseClientOptions, ClientRequirements } from "@cistern/shared"; 2 - import type { ResourceUri } from "@atcute/lexicons"; 2 + import type { ResourceUri, Tid } from "@atcute/lexicons"; 3 3 4 4 export interface InputLocalKeyPair { 5 5 privateKey: string; ··· 16 16 } 17 17 18 18 export type ConsumerParams = ClientRequirements<ConsumerOptions>; 19 + 20 + export interface DecryptedItem { 21 + tid: Tid; 22 + text: string; 23 + }