+167
packages/producer/client.ts
+167
packages/producer/client.ts
···
1
+
import { produceRequirements } from "@cistern/shared";
2
+
import { encryptText } from "@cistern/crypto";
3
+
import type {
4
+
ProducerOptions,
5
+
ProducerParams,
6
+
PublicKeyOption,
7
+
} from "./types.ts";
8
+
import { type Did, parse, type ResourceUri } from "@atcute/lexicons";
9
+
import type { Client } from "@atcute/client";
10
+
import { now } from "@atcute/tid";
11
+
import { type AppCisternMemo, AppCisternPubkey } from "@cistern/lexicon";
12
+
13
+
/**
14
+
* Creates a `Producer` instance with all necessary requirements. This is the recommended way to construct a `Producer`.
15
+
*
16
+
* @description Resolves the user's DID using Slingshot, instantiates an `@atcute/client` instance, creates an initial session, and returns a new `Producer`. If a pubkey record key is provided, it will be resolved and set as the active key.
17
+
* @param {ProducerOptions} options - Information for constructing the underlying XRPC client
18
+
* @returns {Promise<Producer>} A Cistern producer client with an authorized session
19
+
*/
20
+
export async function createProducer(
21
+
{ publicKey: rkey, ...opts }: ProducerOptions,
22
+
): Promise<Producer> {
23
+
const reqs = await produceRequirements(opts);
24
+
25
+
let publicKey: PublicKeyOption | undefined;
26
+
if (rkey) {
27
+
const res = await reqs.rpc.get("com.atproto.repo.getRecord", {
28
+
params: {
29
+
repo: reqs.miniDoc.did,
30
+
rkey,
31
+
collection: "app.cistern.pubkey",
32
+
},
33
+
});
34
+
35
+
if (!res.ok) {
36
+
throw new Error(
37
+
`invalid public key record ID ${publicKey}, got: ${res.status} ${res.data.error}`,
38
+
);
39
+
}
40
+
41
+
const record = parse(AppCisternPubkey.mainSchema, res.data.value);
42
+
43
+
publicKey = {
44
+
uri: res.data.uri,
45
+
name: record.name,
46
+
content: record.content.$bytes,
47
+
};
48
+
}
49
+
50
+
return new Producer({
51
+
...reqs,
52
+
publicKey,
53
+
});
54
+
}
55
+
56
+
/**
57
+
* A client for encrypting and creating Cistern memos.
58
+
*/
59
+
export class Producer {
60
+
/** DID of the user this producer acts on behalf of */
61
+
did: Did;
62
+
63
+
/** `@atcute/client` instance with credential manager */
64
+
rpc: Client;
65
+
66
+
/** Partial public key record, used for encrypting items */
67
+
publicKey?: PublicKeyOption;
68
+
69
+
constructor(params: ProducerParams) {
70
+
this.did = params.miniDoc.did;
71
+
this.rpc = params.rpc;
72
+
this.publicKey = params.publicKey;
73
+
}
74
+
75
+
/**
76
+
* Creates a memo and saves it as a record in the user's PDS.
77
+
* @param {string} text - The contents of the memo you wish to create
78
+
*/
79
+
async createMemo(text: string): Promise<ResourceUri> {
80
+
if (!this.publicKey) {
81
+
throw new Error(
82
+
"no public key set; select a public key before creating a memo",
83
+
);
84
+
}
85
+
86
+
const payload = encryptText(
87
+
Uint8Array.fromBase64(this.publicKey.content),
88
+
text,
89
+
);
90
+
const record: AppCisternMemo.Main = {
91
+
$type: "app.cistern.memo",
92
+
tid: now(),
93
+
algorithm: "x_wing-xchacha20_poly1305-sha3_512",
94
+
ciphertext: { $bytes: payload.cipherText },
95
+
contentHash: { $bytes: payload.hash },
96
+
contentLength: payload.length,
97
+
nonce: { $bytes: payload.nonce },
98
+
payload: { $bytes: payload.content },
99
+
pubkey: this.publicKey.uri,
100
+
};
101
+
102
+
const res = await this.rpc.post("com.atproto.repo.createRecord", {
103
+
input: {
104
+
collection: "app.cistern.memo",
105
+
repo: this.did,
106
+
record,
107
+
},
108
+
});
109
+
110
+
if (!res.ok) {
111
+
throw new Error(
112
+
`failed to create new memo: ${res.status} ${res.data.error}`,
113
+
);
114
+
}
115
+
116
+
return res.data.uri;
117
+
}
118
+
119
+
/**
120
+
* Lists public keys registered in the user's PDS
121
+
*/
122
+
async *listPublicKeys(): AsyncGenerator<
123
+
PublicKeyOption,
124
+
void,
125
+
void
126
+
> {
127
+
let cursor: string | undefined;
128
+
129
+
while (true) {
130
+
const res = await this.rpc.get("com.atproto.repo.listRecords", {
131
+
params: {
132
+
collection: "app.cistern.pubkey",
133
+
repo: this.did,
134
+
cursor,
135
+
},
136
+
});
137
+
138
+
if (!res.ok) {
139
+
throw new Error(
140
+
`failed to list public keys: ${res.status} ${res.data.error}`,
141
+
);
142
+
}
143
+
144
+
cursor = res.data.cursor;
145
+
146
+
for (const record of res.data.records) {
147
+
const memo = parse(AppCisternPubkey.mainSchema, record.value);
148
+
149
+
yield {
150
+
uri: record.uri,
151
+
content: memo.content.$bytes,
152
+
name: memo.name,
153
+
};
154
+
}
155
+
156
+
if (!cursor) return;
157
+
}
158
+
}
159
+
160
+
/**
161
+
* Sets a public key as the main encryption key. This is not necessary to use if you instantiated the client with a public key.
162
+
* @param {PublicKeyOption} key - The key you want to use for encryption
163
+
*/
164
+
selectPublicKey(key: PublicKeyOption) {
165
+
this.publicKey = key;
166
+
}
167
+
}
+2
-167
packages/producer/mod.ts
+2
-167
packages/producer/mod.ts
···
1
-
import { produceRequirements } from "@cistern/shared";
2
-
import { encryptText } from "@cistern/crypto";
3
-
import type {
4
-
ProducerOptions,
5
-
ProducerParams,
6
-
PublicKeyOption,
7
-
} from "./types.ts";
8
-
import { type Did, parse, type ResourceUri } from "@atcute/lexicons";
9
-
import type { Client, CredentialManager } from "@atcute/client";
10
-
import { now } from "@atcute/tid";
11
-
import { type AppCisternMemo, AppCisternPubkey } from "@cistern/lexicon";
12
-
13
-
/**
14
-
* Creates a `Producer` instance with all necessary requirements. This is the recommended way to construct a `Producer`.
15
-
*
16
-
* @description Resolves the user's DID using Slingshot, instantiates an `@atcute/client` instance, creates an initial session, and returns a new `Producer`. If a pubkey record key is provided, it will be resolved and set as the active key.
17
-
* @param {ProducerOptions} options - Information for constructing the underlying XRPC client
18
-
* @returns {Promise<Producer>} A Cistern producer client with an authorized session
19
-
*/
20
-
export async function createProducer(
21
-
{ publicKey: rkey, ...opts }: ProducerOptions,
22
-
): Promise<Producer> {
23
-
const reqs = await produceRequirements(opts);
24
-
25
-
let publicKey: PublicKeyOption | undefined;
26
-
if (rkey) {
27
-
const res = await reqs.rpc.get("com.atproto.repo.getRecord", {
28
-
params: {
29
-
repo: reqs.miniDoc.did,
30
-
rkey,
31
-
collection: "app.cistern.pubkey",
32
-
},
33
-
});
34
-
35
-
if (!res.ok) {
36
-
throw new Error(
37
-
`invalid public key record ID ${publicKey}, got: ${res.status} ${res.data.error}`,
38
-
);
39
-
}
40
-
41
-
const record = parse(AppCisternPubkey.mainSchema, res.data.value);
42
-
43
-
publicKey = {
44
-
uri: res.data.uri,
45
-
name: record.name,
46
-
content: record.content.$bytes,
47
-
};
48
-
}
49
-
50
-
return new Producer({
51
-
...reqs,
52
-
publicKey,
53
-
});
54
-
}
55
-
56
-
/**
57
-
* A client for encrypting and creating Cistern memos.
58
-
*/
59
-
export class Producer {
60
-
/** DID of the user this producer acts on behalf of */
61
-
did: Did;
62
-
63
-
/** `@atcute/client` instance with credential manager */
64
-
rpc: Client;
65
-
66
-
/** Partial public key record, used for encrypting items */
67
-
publicKey?: PublicKeyOption;
68
-
69
-
constructor(params: ProducerParams) {
70
-
this.did = params.miniDoc.did;
71
-
this.rpc = params.rpc;
72
-
this.publicKey = params.publicKey;
73
-
}
74
-
75
-
/**
76
-
* Creates a memo and saves it as a record in the user's PDS.
77
-
* @param {string} text - The contents of the memo you wish to create
78
-
*/
79
-
async createMemo(text: string): Promise<ResourceUri> {
80
-
if (!this.publicKey) {
81
-
throw new Error(
82
-
"no public key set; select a public key before creating a memo",
83
-
);
84
-
}
85
-
86
-
const payload = encryptText(
87
-
Uint8Array.fromBase64(this.publicKey.content),
88
-
text,
89
-
);
90
-
const record: AppCisternMemo.Main = {
91
-
$type: "app.cistern.memo",
92
-
tid: now(),
93
-
algorithm: "x_wing-xchacha20_poly1305-sha3_512",
94
-
ciphertext: { $bytes: payload.cipherText },
95
-
contentHash: { $bytes: payload.hash },
96
-
contentLength: payload.length,
97
-
nonce: { $bytes: payload.nonce },
98
-
payload: { $bytes: payload.content },
99
-
pubkey: this.publicKey.uri,
100
-
};
101
-
102
-
const res = await this.rpc.post("com.atproto.repo.createRecord", {
103
-
input: {
104
-
collection: "app.cistern.memo",
105
-
repo: this.did,
106
-
record,
107
-
},
108
-
});
109
-
110
-
if (!res.ok) {
111
-
throw new Error(
112
-
`failed to create new memo: ${res.status} ${res.data.error}`,
113
-
);
114
-
}
115
-
116
-
return res.data.uri;
117
-
}
118
-
119
-
/**
120
-
* Lists public keys registered in the user's PDS
121
-
*/
122
-
async *listPublicKeys(): AsyncGenerator<
123
-
PublicKeyOption,
124
-
void,
125
-
void
126
-
> {
127
-
let cursor: string | undefined;
128
-
129
-
while (true) {
130
-
const res = await this.rpc.get("com.atproto.repo.listRecords", {
131
-
params: {
132
-
collection: "app.cistern.pubkey",
133
-
repo: this.did,
134
-
cursor,
135
-
},
136
-
});
137
-
138
-
if (!res.ok) {
139
-
throw new Error(
140
-
`failed to list public keys: ${res.status} ${res.data.error}`,
141
-
);
142
-
}
143
-
144
-
cursor = res.data.cursor;
145
-
146
-
for (const record of res.data.records) {
147
-
const memo = parse(AppCisternPubkey.mainSchema, record.value);
148
-
149
-
yield {
150
-
uri: record.uri,
151
-
content: memo.content.$bytes,
152
-
name: memo.name,
153
-
};
154
-
}
155
-
156
-
if (!cursor) return;
157
-
}
158
-
}
159
-
160
-
/**
161
-
* Sets a public key as the main encryption key. This is not necessary to use if you instantiated the client with a public key.
162
-
* @param {PublicKeyOption} key - The key you want to use for encryption
163
-
*/
164
-
selectPublicKey(key: PublicKeyOption) {
165
-
this.publicKey = key;
166
-
}
167
-
}
1
+
export * from "./client.ts";
2
+
export * from "./types.ts";