@atproto/dev-env 0.0.0-spaces-alpha-20260818163953

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/CHANGELOG.md +3424 -0
  2. package/LICENSE.txt +7 -0
  3. package/README.md +39 -0
  4. package/bin.js +7 -0
  5. package/dist/bin-multi-pds.d.ts +2 -0
  6. package/dist/bin-multi-pds.d.ts.map +1 -0
  7. package/dist/bin-multi-pds.js +130 -0
  8. package/dist/bin-multi-pds.js.map +1 -0
  9. package/dist/bin.d.ts +2 -0
  10. package/dist/bin.d.ts.map +1 -0
  11. package/dist/bin.js +56 -0
  12. package/dist/bin.js.map +1 -0
  13. package/dist/bsky.d.ts +27 -0
  14. package/dist/bsky.d.ts.map +1 -0
  15. package/dist/bsky.js +165 -0
  16. package/dist/bsky.js.map +1 -0
  17. package/dist/bsync.d.ts +22 -0
  18. package/dist/bsync.d.ts.map +1 -0
  19. package/dist/bsync.js +54 -0
  20. package/dist/bsync.js.map +1 -0
  21. package/dist/const.d.ts +4 -0
  22. package/dist/const.d.ts.map +1 -0
  23. package/dist/const.js +4 -0
  24. package/dist/const.js.map +1 -0
  25. package/dist/env.d.ts +2 -0
  26. package/dist/env.d.ts.map +1 -0
  27. package/dist/env.js +10 -0
  28. package/dist/env.js.map +1 -0
  29. package/dist/feed-gen.d.ts +15 -0
  30. package/dist/feed-gen.d.ts.map +1 -0
  31. package/dist/feed-gen.js +73 -0
  32. package/dist/feed-gen.js.map +1 -0
  33. package/dist/index.d.ts +17 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +17 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/introspect.d.ts +22 -0
  38. package/dist/introspect.d.ts.map +1 -0
  39. package/dist/introspect.js +65 -0
  40. package/dist/introspect.js.map +1 -0
  41. package/dist/mock/data.d.ts +3 -0
  42. package/dist/mock/data.d.ts.map +1 -0
  43. package/dist/mock/data.js +46 -0
  44. package/dist/mock/data.js.map +1 -0
  45. package/dist/mock/img/blur-hash-avatar-b64.d.ts +3 -0
  46. package/dist/mock/img/blur-hash-avatar-b64.d.ts.map +1 -0
  47. package/dist/mock/img/blur-hash-avatar-b64.js +2 -0
  48. package/dist/mock/img/blur-hash-avatar-b64.js.map +1 -0
  49. package/dist/mock/img/labeled-img-b64.d.ts +3 -0
  50. package/dist/mock/img/labeled-img-b64.d.ts.map +1 -0
  51. package/dist/mock/img/labeled-img-b64.js +2 -0
  52. package/dist/mock/img/labeled-img-b64.js.map +1 -0
  53. package/dist/mock/index.d.ts +3 -0
  54. package/dist/mock/index.d.ts.map +1 -0
  55. package/dist/mock/index.js +574 -0
  56. package/dist/mock/index.js.map +1 -0
  57. package/dist/moderator-client.d.ts +58 -0
  58. package/dist/moderator-client.d.ts.map +1 -0
  59. package/dist/moderator-client.js +110 -0
  60. package/dist/moderator-client.js.map +1 -0
  61. package/dist/network-no-appview.d.ts +21 -0
  62. package/dist/network-no-appview.d.ts.map +1 -0
  63. package/dist/network-no-appview.js +74 -0
  64. package/dist/network-no-appview.js.map +1 -0
  65. package/dist/network.d.ts +32 -0
  66. package/dist/network.d.ts.map +1 -0
  67. package/dist/network.js +190 -0
  68. package/dist/network.js.map +1 -0
  69. package/dist/ozone.d.ts +31 -0
  70. package/dist/ozone.d.ts.map +1 -0
  71. package/dist/ozone.js +254 -0
  72. package/dist/ozone.js.map +1 -0
  73. package/dist/pds.d.ts +23 -0
  74. package/dist/pds.d.ts.map +1 -0
  75. package/dist/pds.js +97 -0
  76. package/dist/pds.js.map +1 -0
  77. package/dist/plc.d.ts +14 -0
  78. package/dist/plc.d.ts.map +1 -0
  79. package/dist/plc.js +28 -0
  80. package/dist/plc.js.map +1 -0
  81. package/dist/seed/author-feed.d.ts +4 -0
  82. package/dist/seed/author-feed.d.ts.map +1 -0
  83. package/dist/seed/author-feed.js +50 -0
  84. package/dist/seed/author-feed.js.map +1 -0
  85. package/dist/seed/basic.d.ts +16 -0
  86. package/dist/seed/basic.d.ts.map +1 -0
  87. package/dist/seed/basic.js +113 -0
  88. package/dist/seed/basic.js.map +1 -0
  89. package/dist/seed/client.d.ts +151 -0
  90. package/dist/seed/client.d.ts.map +1 -0
  91. package/dist/seed/client.js +365 -0
  92. package/dist/seed/client.js.map +1 -0
  93. package/dist/seed/follows.d.ts +4 -0
  94. package/dist/seed/follows.d.ts.map +1 -0
  95. package/dist/seed/follows.js +55 -0
  96. package/dist/seed/follows.js.map +1 -0
  97. package/dist/seed/index.d.ts +11 -0
  98. package/dist/seed/index.d.ts.map +1 -0
  99. package/dist/seed/index.js +11 -0
  100. package/dist/seed/index.js.map +1 -0
  101. package/dist/seed/likes.d.ts +4 -0
  102. package/dist/seed/likes.d.ts.map +1 -0
  103. package/dist/seed/likes.js +37 -0
  104. package/dist/seed/likes.js.map +1 -0
  105. package/dist/seed/quotes.d.ts +4 -0
  106. package/dist/seed/quotes.d.ts.map +1 -0
  107. package/dist/seed/quotes.js +21 -0
  108. package/dist/seed/quotes.js.map +1 -0
  109. package/dist/seed/reposts.d.ts +4 -0
  110. package/dist/seed/reposts.d.ts.map +1 -0
  111. package/dist/seed/reposts.js +17 -0
  112. package/dist/seed/reposts.js.map +1 -0
  113. package/dist/seed/thread-v2.d.ts +227 -0
  114. package/dist/seed/thread-v2.d.ts.map +1 -0
  115. package/dist/seed/thread-v2.js +755 -0
  116. package/dist/seed/thread-v2.js.map +1 -0
  117. package/dist/seed/users-bulk.d.ts +3 -0
  118. package/dist/seed/users-bulk.d.ts.map +1 -0
  119. package/dist/seed/users-bulk.js +224 -0
  120. package/dist/seed/users-bulk.js.map +1 -0
  121. package/dist/seed/users.d.ts +3 -0
  122. package/dist/seed/users.d.ts.map +1 -0
  123. package/dist/seed/users.js +45 -0
  124. package/dist/seed/users.js.map +1 -0
  125. package/dist/seed/verifications.d.ts +5 -0
  126. package/dist/seed/verifications.d.ts.map +1 -0
  127. package/dist/seed/verifications.js +164 -0
  128. package/dist/seed/verifications.js.map +1 -0
  129. package/dist/service-profile-lexicon.d.ts +11 -0
  130. package/dist/service-profile-lexicon.d.ts.map +1 -0
  131. package/dist/service-profile-lexicon.js +115 -0
  132. package/dist/service-profile-lexicon.js.map +1 -0
  133. package/dist/service-profile-ozone.d.ts +18 -0
  134. package/dist/service-profile-ozone.d.ts.map +1 -0
  135. package/dist/service-profile-ozone.js +333 -0
  136. package/dist/service-profile-ozone.js.map +1 -0
  137. package/dist/service-profile.d.ts +24 -0
  138. package/dist/service-profile.d.ts.map +1 -0
  139. package/dist/service-profile.js +61 -0
  140. package/dist/service-profile.js.map +1 -0
  141. package/dist/types.d.ts +61 -0
  142. package/dist/types.d.ts.map +1 -0
  143. package/dist/types.js +2 -0
  144. package/dist/types.js.map +1 -0
  145. package/dist/util.d.ts +14 -0
  146. package/dist/util.d.ts.map +1 -0
  147. package/dist/util.js +86 -0
  148. package/dist/util.js.map +1 -0
  149. package/package.json +69 -0
  150. package/src/bin-multi-pds.ts +166 -0
  151. package/src/bin.ts +64 -0
  152. package/src/bsky.ts +198 -0
  153. package/src/bsync.ts +66 -0
  154. package/src/const.ts +3 -0
  155. package/src/env.ts +9 -0
  156. package/src/feed-gen.ts +96 -0
  157. package/src/index.ts +17 -0
  158. package/src/introspect.ts +91 -0
  159. package/src/mock/data.ts +46 -0
  160. package/src/mock/img/blur-hash-avatar-b64.ts +1 -0
  161. package/src/mock/img/labeled-img-b64.ts +1 -0
  162. package/src/mock/index.ts +702 -0
  163. package/src/moderator-client.ts +267 -0
  164. package/src/network-no-appview.ts +89 -0
  165. package/src/network.ts +243 -0
  166. package/src/ozone.ts +287 -0
  167. package/src/pds.ts +118 -0
  168. package/src/plc.ts +33 -0
  169. package/src/seed/author-feed.ts +98 -0
  170. package/src/seed/basic.ts +189 -0
  171. package/src/seed/client.ts +676 -0
  172. package/src/seed/follows.ts +57 -0
  173. package/src/seed/index.ts +11 -0
  174. package/src/seed/likes.ts +43 -0
  175. package/src/seed/quotes.ts +53 -0
  176. package/src/seed/reposts.ts +18 -0
  177. package/src/seed/thread-v2.ts +925 -0
  178. package/src/seed/users-bulk.ts +229 -0
  179. package/src/seed/users.ts +64 -0
  180. package/src/seed/verifications.ts +254 -0
  181. package/src/service-profile-lexicon.ts +127 -0
  182. package/src/service-profile-ozone.ts +375 -0
  183. package/src/service-profile.ts +95 -0
  184. package/src/types.ts +68 -0
  185. package/src/util.ts +118 -0
  186. package/tsconfig.build.json +25 -0
  187. package/tsconfig.json +4 -0
@@ -0,0 +1,375 @@
1
+ import type { AtpAgent } from '@atproto/api'
2
+ import { Secp256k1Keypair } from '@atproto/crypto'
3
+ import type { TestPds } from './pds.js'
4
+ import {
5
+ type ServiceMigrationOptions,
6
+ ServiceProfile,
7
+ type ServiceUserDetails,
8
+ } from './service-profile.js'
9
+
10
+ export class OzoneServiceProfile extends ServiceProfile {
11
+ static async create(
12
+ pds: TestPds,
13
+ ozoneUrl: string,
14
+ userDetails = {
15
+ email: 'mod-authority@test.com',
16
+ handle: 'mod-authority.test',
17
+ password: 'hunter2',
18
+ },
19
+ ) {
20
+ const agent = pds.getAgent()
21
+ await agent.createAccount(userDetails)
22
+
23
+ const key = await Secp256k1Keypair.create({ exportable: true })
24
+
25
+ return new OzoneServiceProfile(pds, agent, userDetails, ozoneUrl, key)
26
+ }
27
+
28
+ protected constructor(
29
+ pds: TestPds,
30
+ agent: AtpAgent,
31
+ userDetails: ServiceUserDetails,
32
+ readonly ozoneUrl: string,
33
+ readonly key: Secp256k1Keypair,
34
+ ) {
35
+ super(pds, agent, userDetails)
36
+ }
37
+
38
+ async createAppPasswordForVerification() {
39
+ const { data } = await this.agent.com.atproto.server.createAppPassword({
40
+ name: 'ozone-verifier',
41
+ })
42
+ return data.password
43
+ }
44
+
45
+ async migrateTo(pds: TestPds, options: ServiceMigrationOptions = {}) {
46
+ await super.migrateTo(pds, {
47
+ ...options,
48
+ services: {
49
+ ...options.services,
50
+ atproto_labeler: {
51
+ type: 'AtprotoLabeler',
52
+ endpoint: this.ozoneUrl,
53
+ },
54
+ },
55
+ verificationMethods: {
56
+ ...options.verificationMethods,
57
+ atproto_label: this.key.did(),
58
+ },
59
+ })
60
+ }
61
+
62
+ async createRecords() {
63
+ await this.agent.app.bsky.actor.profile.create(
64
+ { repo: this.did },
65
+ {
66
+ displayName: 'Dev-env Moderation',
67
+ description: `The pretend version of mod.bsky.app`,
68
+ },
69
+ )
70
+
71
+ await this.agent.app.bsky.labeler.service.create(
72
+ { repo: this.did, rkey: 'self' },
73
+ {
74
+ policies: {
75
+ labelValues: [
76
+ '!hide',
77
+ '!warn',
78
+ 'porn',
79
+ 'sexual',
80
+ 'nudity',
81
+ 'sexual-figurative',
82
+ 'graphic-media',
83
+ 'self-harm',
84
+ 'sensitive',
85
+ 'extremist',
86
+ 'intolerant',
87
+ 'threat',
88
+ 'rude',
89
+ 'illicit',
90
+ 'security',
91
+ 'unsafe-link',
92
+ 'impersonation',
93
+ 'misinformation',
94
+ 'scam',
95
+ 'engagement-farming',
96
+ 'spam',
97
+ 'rumor',
98
+ 'misleading',
99
+ 'inauthentic',
100
+ ],
101
+ labelValueDefinitions: [
102
+ {
103
+ identifier: 'spam',
104
+ blurs: 'content',
105
+ severity: 'inform',
106
+ defaultSetting: 'hide',
107
+ adultOnly: false,
108
+ locales: [
109
+ {
110
+ lang: 'en',
111
+ name: 'Spam',
112
+ description:
113
+ 'Unwanted, repeated, or unrelated actions that bother users.',
114
+ },
115
+ ],
116
+ },
117
+ {
118
+ identifier: 'impersonation',
119
+ blurs: 'none',
120
+ severity: 'inform',
121
+ defaultSetting: 'hide',
122
+ adultOnly: false,
123
+ locales: [
124
+ {
125
+ lang: 'en',
126
+ name: 'Impersonation',
127
+ description:
128
+ 'Pretending to be someone else without permission.',
129
+ },
130
+ ],
131
+ },
132
+ {
133
+ identifier: 'scam',
134
+ blurs: 'content',
135
+ severity: 'alert',
136
+ defaultSetting: 'hide',
137
+ adultOnly: false,
138
+ locales: [
139
+ {
140
+ lang: 'en',
141
+ name: 'Scam',
142
+ description: 'Scams, phishing & fraud.',
143
+ },
144
+ ],
145
+ },
146
+ {
147
+ identifier: 'intolerant',
148
+ blurs: 'content',
149
+ severity: 'alert',
150
+ defaultSetting: 'warn',
151
+ adultOnly: false,
152
+ locales: [
153
+ {
154
+ lang: 'en',
155
+ name: 'Intolerance',
156
+ description: 'Discrimination against protected groups.',
157
+ },
158
+ ],
159
+ },
160
+ {
161
+ identifier: 'self-harm',
162
+ blurs: 'content',
163
+ severity: 'alert',
164
+ defaultSetting: 'warn',
165
+ adultOnly: false,
166
+ locales: [
167
+ {
168
+ lang: 'en',
169
+ name: 'Self-Harm',
170
+ description:
171
+ 'Promotes self-harm, including graphic images, glorifying discussions, or triggering stories.',
172
+ },
173
+ ],
174
+ },
175
+ {
176
+ identifier: 'security',
177
+ blurs: 'content',
178
+ severity: 'alert',
179
+ defaultSetting: 'hide',
180
+ adultOnly: false,
181
+ locales: [
182
+ {
183
+ lang: 'en',
184
+ name: 'Security Concerns',
185
+ description:
186
+ 'May be unsafe and could harm your device, steal your info, or get your account hacked.',
187
+ },
188
+ ],
189
+ },
190
+ {
191
+ identifier: 'misleading',
192
+ blurs: 'content',
193
+ severity: 'alert',
194
+ defaultSetting: 'warn',
195
+ adultOnly: false,
196
+ locales: [
197
+ {
198
+ lang: 'en',
199
+ name: 'Misleading',
200
+ description:
201
+ 'Altered images/videos, deceptive links, or false statements.',
202
+ },
203
+ ],
204
+ },
205
+ {
206
+ identifier: 'threat',
207
+ blurs: 'content',
208
+ severity: 'inform',
209
+ defaultSetting: 'hide',
210
+ adultOnly: false,
211
+ locales: [
212
+ {
213
+ lang: 'en',
214
+ name: 'Threats',
215
+ description:
216
+ 'Promotes violence or harm towards others, including threats, incitement, or advocacy of harm.',
217
+ },
218
+ ],
219
+ },
220
+ {
221
+ identifier: 'unsafe-link',
222
+ blurs: 'content',
223
+ severity: 'alert',
224
+ defaultSetting: 'hide',
225
+ adultOnly: false,
226
+ locales: [
227
+ {
228
+ lang: 'en',
229
+ name: 'Unsafe link',
230
+ description:
231
+ 'Links to harmful sites with malware, phishing, or violating content that risk security and privacy.',
232
+ },
233
+ ],
234
+ },
235
+ {
236
+ identifier: 'illicit',
237
+ blurs: 'content',
238
+ severity: 'alert',
239
+ defaultSetting: 'hide',
240
+ adultOnly: false,
241
+ locales: [
242
+ {
243
+ lang: 'en',
244
+ name: 'Illicit',
245
+ description:
246
+ 'Promoting or selling potentially illicit goods, services, or activities.',
247
+ },
248
+ ],
249
+ },
250
+ {
251
+ identifier: 'misinformation',
252
+ blurs: 'content',
253
+ severity: 'inform',
254
+ defaultSetting: 'warn',
255
+ adultOnly: false,
256
+ locales: [
257
+ {
258
+ lang: 'en',
259
+ name: 'Misinformation',
260
+ description:
261
+ 'Spreading false or misleading info, including unverified claims and harmful conspiracy theories.',
262
+ },
263
+ ],
264
+ },
265
+ {
266
+ identifier: 'rumor',
267
+ blurs: 'content',
268
+ severity: 'inform',
269
+ defaultSetting: 'warn',
270
+ adultOnly: false,
271
+ locales: [
272
+ {
273
+ lang: 'en',
274
+ name: 'Rumor',
275
+ description:
276
+ 'Approach with caution, as these claims lack evidence from credible sources.',
277
+ },
278
+ ],
279
+ },
280
+ {
281
+ identifier: 'rude',
282
+ blurs: 'content',
283
+ severity: 'inform',
284
+ defaultSetting: 'hide',
285
+ adultOnly: false,
286
+ locales: [
287
+ {
288
+ lang: 'en',
289
+ name: 'Rude',
290
+ description:
291
+ 'Rude or impolite, including crude language and disrespectful comments, without constructive purpose.',
292
+ },
293
+ ],
294
+ },
295
+ {
296
+ identifier: 'extremist',
297
+ blurs: 'content',
298
+ severity: 'alert',
299
+ defaultSetting: 'hide',
300
+ adultOnly: false,
301
+ locales: [
302
+ {
303
+ lang: 'en',
304
+ name: 'Extremist',
305
+ description:
306
+ 'Radical views advocating violence, hate, or discrimination against individuals or groups.',
307
+ },
308
+ ],
309
+ },
310
+ {
311
+ identifier: 'sensitive',
312
+ blurs: 'content',
313
+ severity: 'alert',
314
+ defaultSetting: 'warn',
315
+ adultOnly: false,
316
+ locales: [
317
+ {
318
+ lang: 'en',
319
+ name: 'Sensitive',
320
+ description:
321
+ 'May be upsetting, covering topics like substance abuse or mental health issues, cautioning sensitive viewers.',
322
+ },
323
+ ],
324
+ },
325
+ {
326
+ identifier: 'engagement-farming',
327
+ blurs: 'content',
328
+ severity: 'alert',
329
+ defaultSetting: 'hide',
330
+ adultOnly: false,
331
+ locales: [
332
+ {
333
+ lang: 'en',
334
+ name: 'Engagement Farming',
335
+ description:
336
+ 'Insincere content or bulk actions aimed at gaining followers, including frequent follows, posts, and likes.',
337
+ },
338
+ ],
339
+ },
340
+ {
341
+ identifier: 'inauthentic',
342
+ blurs: 'content',
343
+ severity: 'alert',
344
+ defaultSetting: 'hide',
345
+ adultOnly: false,
346
+ locales: [
347
+ {
348
+ lang: 'en',
349
+ name: 'Inauthentic Account',
350
+ description: 'Bot or a person pretending to be someone else.',
351
+ },
352
+ ],
353
+ },
354
+ {
355
+ identifier: 'sexual-figurative',
356
+ blurs: 'media',
357
+ severity: 'none',
358
+ defaultSetting: 'show',
359
+ adultOnly: true,
360
+ locales: [
361
+ {
362
+ lang: 'en',
363
+ name: 'Sexually Suggestive (Cartoon)',
364
+ description:
365
+ 'Art with explicit or suggestive sexual themes, including provocative imagery or partial nudity.',
366
+ },
367
+ ],
368
+ },
369
+ ],
370
+ },
371
+ createdAt: new Date().toISOString(),
372
+ },
373
+ )
374
+ }
375
+ }
@@ -0,0 +1,95 @@
1
+ import type { AtpAgent } from '@atproto/api'
2
+ import type { DidString } from '@atproto/lex'
3
+ import type { TestPds } from './pds.js'
4
+
5
+ export type ServiceUserDetails = {
6
+ email: string
7
+ handle: string
8
+ password: string
9
+ }
10
+
11
+ export type ServiceMigrationOptions = {
12
+ services?: Record<string, unknown>
13
+ verificationMethods?: Record<string, unknown>
14
+ }
15
+
16
+ export class ServiceProfile {
17
+ protected constructor(
18
+ protected pds: TestPds,
19
+ /** @note assumes the session is already authenticated */
20
+ protected agent: AtpAgent,
21
+ protected userDetails: ServiceUserDetails,
22
+ ) {}
23
+
24
+ get did(): DidString {
25
+ return this.agent.assertDid
26
+ }
27
+
28
+ async migrateTo(newPds: TestPds, options: ServiceMigrationOptions = {}) {
29
+ const newAgent = newPds.getAgent()
30
+
31
+ const newPdsDesc = await newAgent.com.atproto.server.describeServer()
32
+ const serviceAuth = await this.agent.com.atproto.server.getServiceAuth({
33
+ aud: newPdsDesc.data.did,
34
+ lxm: 'com.atproto.server.createAccount',
35
+ })
36
+
37
+ const inviteCode = newPds.ctx.cfg.invites.required
38
+ ? await newAgent.com.atproto.server
39
+ .createInviteCode(
40
+ { useCount: 1 },
41
+ {
42
+ encoding: 'application/json',
43
+ headers: newPds.adminAuthHeaders(),
44
+ },
45
+ )
46
+ .then((res) => res.data.code)
47
+ : undefined
48
+
49
+ await newAgent.createAccount(
50
+ {
51
+ ...this.userDetails,
52
+ inviteCode,
53
+ did: this.did,
54
+ },
55
+ {
56
+ encoding: 'application/json',
57
+ headers: { authorization: `Bearer ${serviceAuth.data.token}` },
58
+ },
59
+ )
60
+
61
+ // The session manager will use the "didDoc" in the result of
62
+ // "createAccount" in order to setup the pdsUrl. However, since are in the
63
+ // process of migrating, that didDoc references the old PDS. In order to
64
+ // avoid calling the old PDS, let's clear the pdsUrl, which will result in
65
+ // the (new) serviceUrl being used.
66
+ newAgent.sessionManager.pdsUrl = undefined
67
+
68
+ const newDidCredentialsRes =
69
+ await newAgent.com.atproto.identity.getRecommendedDidCredentials()
70
+
71
+ await this.agent.com.atproto.identity.requestPlcOperationSignature()
72
+ const { token } = await this.pds.ctx.accountManager.db.db
73
+ .selectFrom('email_token')
74
+ .select('token')
75
+ .where('did', '=', this.did)
76
+ .where('purpose', '=', 'plc_operation')
77
+ .executeTakeFirstOrThrow()
78
+
79
+ const op = { ...newDidCredentialsRes.data, token }
80
+ Object.assign((op.services ??= {}), options.services)
81
+ Object.assign((op.verificationMethods ??= {}), options.verificationMethods)
82
+
83
+ const signedPlcOperation =
84
+ await this.agent.com.atproto.identity.signPlcOperation(op)
85
+
86
+ await newAgent.com.atproto.identity.submitPlcOperation({
87
+ operation: signedPlcOperation.data.operation,
88
+ })
89
+
90
+ await newAgent.com.atproto.server.activateAccount()
91
+
92
+ this.pds = newPds
93
+ this.agent = newAgent
94
+ }
95
+ }
package/src/types.ts ADDED
@@ -0,0 +1,68 @@
1
+ import type * as bsky from '@atproto/bsky'
2
+ import type * as bsync from '@atproto/bsync'
3
+ import type { ExportableKeypair, Keypair } from '@atproto/crypto'
4
+ import type * as ozone from '@atproto/ozone'
5
+ import type * as pds from '@atproto/pds'
6
+
7
+ export type IntrospectConfig = {
8
+ port?: number
9
+ }
10
+
11
+ export type PlcConfig = {
12
+ port?: number
13
+ version?: string
14
+ }
15
+
16
+ export type PdsConfig = Partial<pds.ServerEnvironment> & {
17
+ didPlcUrl: string
18
+ migration?: string
19
+ }
20
+
21
+ export type BskyConfig = Partial<bsky.ServerConfig> & {
22
+ bsyncUrl: string
23
+ plcUrl: string
24
+ repoProvider: string
25
+ dbPostgresUrl: string
26
+ dbPostgresSchema: string
27
+ redisHost: string
28
+ pdsPort: number
29
+ migration?: string
30
+ privateKey?: string
31
+ }
32
+
33
+ export type BsyncConfig = Partial<bsync.ServerEnvironment> & {
34
+ dbUrl: string
35
+ }
36
+
37
+ export type OzoneConfig = Partial<ozone.OzoneEnvironment> & {
38
+ plcUrl: string
39
+ appviewUrl: string
40
+ appviewDid: string
41
+ dbPostgresUrl: string
42
+ migration?: string
43
+ signingKey?: ExportableKeypair
44
+ imgInvalidator?: ozone.ImageInvalidator
45
+ }
46
+
47
+ export type TestServerParams = {
48
+ dbPostgresUrl: string
49
+ dbPostgresSchema: string
50
+ pds: Partial<PdsConfig>
51
+ plc: Partial<PlcConfig>
52
+ bsky: Partial<BskyConfig>
53
+ ozone: Partial<OzoneConfig>
54
+ introspect: Partial<IntrospectConfig>
55
+ extraPdses: number
56
+ /**
57
+ * Create a lexicon-authority account and point every PDS at it, so lexicon
58
+ * documents (permission sets, `space` declarations) resolve. Opt-in: it costs
59
+ * an account creation plus a record write per document, which most tests don't
60
+ * need. `TestNetwork` always does this.
61
+ */
62
+ lexiconAuthority: boolean
63
+ }
64
+
65
+ export type DidAndKey = {
66
+ did: string
67
+ key: Keypair
68
+ }
package/src/util.ts ADDED
@@ -0,0 +1,118 @@
1
+ import * as plc from '@did-plc/lib'
2
+ import { request } from 'undici'
3
+ import { Secp256k1Keypair } from '@atproto/crypto'
4
+ import type { IdResolver } from '@atproto/identity'
5
+ import type { TestBsky } from './bsky.js'
6
+ import type { TestPds } from './pds.js'
7
+ import type { DidAndKey } from './types.js'
8
+
9
+ export const mockNetworkUtilities = (
10
+ pds: TestPds | TestPds[],
11
+ bsky?: TestBsky,
12
+ ) => {
13
+ const pdses = Array.isArray(pds) ? pds : [pds]
14
+ for (const p of pdses) {
15
+ mockResolvers(p.ctx.idResolver, pdses)
16
+ }
17
+ if (bsky) {
18
+ mockResolvers(bsky.ctx.idResolver, pdses)
19
+ mockResolvers(bsky.dataplane.idResolver, pdses)
20
+ }
21
+ }
22
+
23
+ export const mockResolvers = (
24
+ idResolver: IdResolver,
25
+ pds: TestPds | TestPds[],
26
+ // handles resolve against the matching pds by default, but may point
27
+ // elsewhere e.g. an entryway
28
+ handleResolveUrl?: string,
29
+ ) => {
30
+ const pdses = Array.isArray(pds) ? pds : [pds]
31
+
32
+ // Map pds public url to its local url when resolving from plc
33
+ const origResolveDid = idResolver.did.resolveNoCache
34
+ idResolver.did.resolveNoCache = async (did: string) => {
35
+ const result = await (origResolveDid.call(
36
+ idResolver.did,
37
+ did,
38
+ ) as ReturnType<typeof origResolveDid>)
39
+ const service = result?.service?.find((svc) => svc.id === '#atproto_pds')
40
+ if (typeof service?.serviceEndpoint === 'string') {
41
+ for (const p of pdses) {
42
+ service.serviceEndpoint = service.serviceEndpoint.replace(
43
+ p.ctx.cfg.service.publicUrl,
44
+ `http://localhost:${p.port}`,
45
+ )
46
+ }
47
+ }
48
+ return result
49
+ }
50
+
51
+ const origResolveHandleDns = idResolver.handle.resolveDns
52
+ idResolver.handle.resolve = async (handle: string) => {
53
+ // Handle domains across PDSes are disjoint suffixes (.test, .test2, ...);
54
+ // first match wins.
55
+ const match = pdses.find((p) =>
56
+ p.ctx.cfg.identity.serviceHandleDomains.some((d) => handle.endsWith(d)),
57
+ )
58
+ if (!match) {
59
+ return origResolveHandleDns.call(idResolver.handle, handle)
60
+ }
61
+
62
+ const url = new URL(
63
+ `/.well-known/atproto-did`,
64
+ handleResolveUrl ?? match.url,
65
+ )
66
+ try {
67
+ const res = await request(url, { headers: { host: handle } })
68
+ if (res.statusCode !== 200) {
69
+ await res.body.dump()
70
+ return undefined
71
+ }
72
+
73
+ return res.body.text()
74
+ } catch (err) {
75
+ return undefined
76
+ }
77
+ }
78
+ }
79
+
80
+ export const mockMailer = (pds: TestPds) => {
81
+ const mailer = pds.ctx.mailer
82
+ const _origSendMail = mailer.transporter.sendMail
83
+ mailer.transporter.sendMail = async (opts) => {
84
+ const result = await _origSendMail.call(mailer.transporter, opts)
85
+ console.log(`✉️ Email: ${JSON.stringify(result, null, 2)}`)
86
+ return result
87
+ }
88
+ }
89
+
90
+ const usedLockIds = new Set()
91
+ export const uniqueLockId = () => {
92
+ let lockId: number
93
+ do {
94
+ lockId = 1000 + Math.ceil(1000 * Math.random())
95
+ } while (usedLockIds.has(lockId))
96
+ usedLockIds.add(lockId)
97
+ return lockId
98
+ }
99
+
100
+ export const createDidAndKey = async (opts: {
101
+ plcUrl: string
102
+ handle: string
103
+ pds: string
104
+ }): Promise<DidAndKey> => {
105
+ const { plcUrl, handle, pds } = opts
106
+ const key = await Secp256k1Keypair.create({ exportable: true })
107
+ const did = await new plc.Client(plcUrl).createDid({
108
+ signingKey: key.did(),
109
+ rotationKeys: [key.did()],
110
+ handle,
111
+ pds,
112
+ signer: key,
113
+ })
114
+ return {
115
+ key,
116
+ did,
117
+ }
118
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "extends": ["../../tsconfig/node.tsconfig.json"],
3
+ "include": ["./src"],
4
+ "references": [
5
+ { "path": "../api/tsconfig.build.json" },
6
+ { "path": "../bsky/tsconfig.build.json" },
7
+ { "path": "../bsync/tsconfig.build.json" },
8
+ { "path": "../common-web/tsconfig.build.json" },
9
+ { "path": "../crypto/tsconfig.build.json" },
10
+ { "path": "../identity/tsconfig.build.json" },
11
+ { "path": "../lex/lex/tsconfig.build.json" },
12
+ { "path": "../lexicon/tsconfig.build.json" },
13
+ { "path": "../ozone/tsconfig.build.json" },
14
+ { "path": "../pds/tsconfig.build.json" },
15
+ { "path": "../sync/tsconfig.build.json" },
16
+ { "path": "../syntax/tsconfig.build.json" },
17
+ { "path": "../xrpc-server/tsconfig.build.json" },
18
+ ],
19
+ "compilerOptions": {
20
+ "rootDir": "./src",
21
+ "outDir": "./dist",
22
+ // @TODO Change to true and fix implicit any errors
23
+ "noImplicitAny": false,
24
+ },
25
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "include": [],
3
+ "references": [{ "path": "./tsconfig.build.json" }],
4
+ }