@atproto/pds 0.5.0 → 0.5.2

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 (279) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/account-manager/account-manager.d.ts +35 -4
  3. package/dist/account-manager/account-manager.d.ts.map +1 -1
  4. package/dist/account-manager/account-manager.js +67 -6
  5. package/dist/account-manager/account-manager.js.map +1 -1
  6. package/dist/account-manager/helpers/account.d.ts +1 -1
  7. package/dist/account-manager/helpers/account.d.ts.map +1 -1
  8. package/dist/account-manager/helpers/account.js +10 -4
  9. package/dist/account-manager/helpers/account.js.map +1 -1
  10. package/dist/account-manager/oauth-store.d.ts +2 -1
  11. package/dist/account-manager/oauth-store.d.ts.map +1 -1
  12. package/dist/account-manager/oauth-store.js +61 -12
  13. package/dist/account-manager/oauth-store.js.map +1 -1
  14. package/dist/actor-store/record/reader.d.ts +1 -1
  15. package/dist/actor-store/record/reader.d.ts.map +1 -1
  16. package/dist/actor-store/record/reader.js.map +1 -1
  17. package/dist/api/app/bsky/actor/getPreferences.d.ts.map +1 -1
  18. package/dist/api/app/bsky/actor/getPreferences.js +7 -2
  19. package/dist/api/app/bsky/actor/getPreferences.js.map +1 -1
  20. package/dist/api/app/bsky/actor/putPreferences.d.ts.map +1 -1
  21. package/dist/api/app/bsky/actor/putPreferences.js +7 -2
  22. package/dist/api/app/bsky/actor/putPreferences.js.map +1 -1
  23. package/dist/api/com/atproto/admin/updateAccountHandle.d.ts.map +1 -1
  24. package/dist/api/com/atproto/admin/updateAccountHandle.js +33 -43
  25. package/dist/api/com/atproto/admin/updateAccountHandle.js.map +1 -1
  26. package/dist/api/com/atproto/identity/updateHandle.d.ts.map +1 -1
  27. package/dist/api/com/atproto/identity/updateHandle.js +39 -61
  28. package/dist/api/com/atproto/identity/updateHandle.js.map +1 -1
  29. package/dist/api/com/atproto/repo/getRecord.js +3 -3
  30. package/dist/api/com/atproto/repo/getRecord.js.map +1 -1
  31. package/dist/api/com/atproto/repo/putRecord.js +2 -2
  32. package/dist/api/com/atproto/repo/putRecord.js.map +1 -1
  33. package/dist/api/com/atproto/server/getServiceAuth.d.ts.map +1 -1
  34. package/dist/api/com/atproto/server/getServiceAuth.js +4 -0
  35. package/dist/api/com/atproto/server/getServiceAuth.js.map +1 -1
  36. package/dist/config/config.d.ts +5 -2
  37. package/dist/config/config.d.ts.map +1 -1
  38. package/dist/config/config.js +50 -46
  39. package/dist/config/config.js.map +1 -1
  40. package/dist/config/env.d.ts +1 -0
  41. package/dist/config/env.d.ts.map +1 -1
  42. package/dist/config/env.js +1 -0
  43. package/dist/config/env.js.map +1 -1
  44. package/dist/context.d.ts.map +1 -1
  45. package/dist/context.js +3 -3
  46. package/dist/context.js.map +1 -1
  47. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts +8 -0
  48. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts.map +1 -1
  49. package/dist/lexicons/app/bsky/actor/defs.defs.js +3 -0
  50. package/dist/lexicons/app/bsky/actor/defs.defs.js.map +1 -1
  51. package/dist/lexicons/app/bsky/actor/profile.defs.d.ts.map +1 -1
  52. package/dist/lexicons/app/bsky/actor/status.defs.d.ts.map +1 -1
  53. package/dist/lexicons/app/bsky/draft/defs.defs.d.ts +22 -0
  54. package/dist/lexicons/app/bsky/draft/defs.defs.d.ts.map +1 -1
  55. package/dist/lexicons/app/bsky/draft/defs.defs.js +11 -0
  56. package/dist/lexicons/app/bsky/draft/defs.defs.js.map +1 -1
  57. package/dist/lexicons/app/bsky/embed/gallery.d.ts +3 -0
  58. package/dist/lexicons/app/bsky/embed/gallery.d.ts.map +1 -0
  59. package/dist/lexicons/app/bsky/embed/gallery.defs.d.ts +130 -0
  60. package/dist/lexicons/app/bsky/embed/gallery.defs.d.ts.map +1 -0
  61. package/dist/lexicons/app/bsky/embed/gallery.defs.js +47 -0
  62. package/dist/lexicons/app/bsky/embed/gallery.defs.js.map +1 -0
  63. package/dist/lexicons/app/bsky/embed/gallery.js +6 -0
  64. package/dist/lexicons/app/bsky/embed/gallery.js.map +1 -0
  65. package/dist/lexicons/app/bsky/embed/record.defs.d.ts +2 -1
  66. package/dist/lexicons/app/bsky/embed/record.defs.d.ts.map +1 -1
  67. package/dist/lexicons/app/bsky/embed/record.defs.js +2 -0
  68. package/dist/lexicons/app/bsky/embed/record.defs.js.map +1 -1
  69. package/dist/lexicons/app/bsky/embed/recordWithMedia.defs.d.ts +13 -12
  70. package/dist/lexicons/app/bsky/embed/recordWithMedia.defs.d.ts.map +1 -1
  71. package/dist/lexicons/app/bsky/embed/recordWithMedia.defs.js +3 -0
  72. package/dist/lexicons/app/bsky/embed/recordWithMedia.defs.js.map +1 -1
  73. package/dist/lexicons/app/bsky/embed.d.ts +1 -0
  74. package/dist/lexicons/app/bsky/embed.d.ts.map +1 -1
  75. package/dist/lexicons/app/bsky/embed.js +1 -0
  76. package/dist/lexicons/app/bsky/embed.js.map +1 -1
  77. package/dist/lexicons/app/bsky/feed/defs.defs.d.ts +2 -1
  78. package/dist/lexicons/app/bsky/feed/defs.defs.d.ts.map +1 -1
  79. package/dist/lexicons/app/bsky/feed/defs.defs.js +2 -0
  80. package/dist/lexicons/app/bsky/feed/defs.defs.js.map +1 -1
  81. package/dist/lexicons/app/bsky/feed/generator.defs.d.ts.map +1 -1
  82. package/dist/lexicons/app/bsky/feed/like.defs.d.ts.map +1 -1
  83. package/dist/lexicons/app/bsky/feed/post.defs.d.ts +12 -11
  84. package/dist/lexicons/app/bsky/feed/post.defs.d.ts.map +1 -1
  85. package/dist/lexicons/app/bsky/feed/post.defs.js +2 -0
  86. package/dist/lexicons/app/bsky/feed/post.defs.js.map +1 -1
  87. package/dist/lexicons/app/bsky/feed/postgate.defs.d.ts.map +1 -1
  88. package/dist/lexicons/app/bsky/feed/repost.defs.d.ts.map +1 -1
  89. package/dist/lexicons/app/bsky/feed/threadgate.defs.d.ts.map +1 -1
  90. package/dist/lexicons/app/bsky/graph/block.defs.d.ts.map +1 -1
  91. package/dist/lexicons/app/bsky/graph/follow.defs.d.ts.map +1 -1
  92. package/dist/lexicons/app/bsky/graph/list.defs.d.ts.map +1 -1
  93. package/dist/lexicons/app/bsky/graph/listblock.defs.d.ts.map +1 -1
  94. package/dist/lexicons/app/bsky/graph/listitem.defs.d.ts.map +1 -1
  95. package/dist/lexicons/app/bsky/graph/starterpack.defs.d.ts.map +1 -1
  96. package/dist/lexicons/app/bsky/graph/verification.defs.d.ts.map +1 -1
  97. package/dist/lexicons/app/bsky/labeler/service.defs.d.ts.map +1 -1
  98. package/dist/lexicons/app/bsky/notification/declaration.defs.d.ts.map +1 -1
  99. package/dist/lexicons/chat/bsky/actor/declaration.defs.d.ts.map +1 -1
  100. package/dist/lexicons/chat/bsky/actor/getStatus.defs.d.ts +2 -0
  101. package/dist/lexicons/chat/bsky/actor/getStatus.defs.d.ts.map +1 -1
  102. package/dist/lexicons/chat/bsky/actor/getStatus.defs.js +1 -0
  103. package/dist/lexicons/chat/bsky/actor/getStatus.defs.js.map +1 -1
  104. package/dist/lexicons/chat/bsky/authFullChatClient.defs.d.ts.map +1 -1
  105. package/dist/lexicons/chat/bsky/authFullChatClient.defs.js +1 -0
  106. package/dist/lexicons/chat/bsky/authFullChatClient.defs.js.map +1 -1
  107. package/dist/lexicons/chat/bsky/convo/defs.defs.d.ts +53 -14
  108. package/dist/lexicons/chat/bsky/convo/defs.defs.d.ts.map +1 -1
  109. package/dist/lexicons/chat/bsky/convo/defs.defs.js +33 -5
  110. package/dist/lexicons/chat/bsky/convo/defs.defs.js.map +1 -1
  111. package/dist/lexicons/chat/bsky/convo/getConvoForMembers.defs.d.ts +1 -1
  112. package/dist/lexicons/chat/bsky/convo/getConvoForMembers.defs.d.ts.map +1 -1
  113. package/dist/lexicons/chat/bsky/convo/getConvoForMembers.defs.js +1 -0
  114. package/dist/lexicons/chat/bsky/convo/getConvoForMembers.defs.js.map +1 -1
  115. package/dist/lexicons/chat/bsky/convo/getLog.defs.d.ts +2 -2
  116. package/dist/lexicons/chat/bsky/convo/getLog.defs.d.ts.map +1 -1
  117. package/dist/lexicons/chat/bsky/convo/getLog.defs.js +3 -0
  118. package/dist/lexicons/chat/bsky/convo/getLog.defs.js.map +1 -1
  119. package/dist/lexicons/chat/bsky/embed/joinLink.d.ts +3 -0
  120. package/dist/lexicons/chat/bsky/embed/joinLink.d.ts.map +1 -0
  121. package/dist/lexicons/chat/bsky/embed/joinLink.defs.d.ts +99 -0
  122. package/dist/lexicons/chat/bsky/embed/joinLink.defs.d.ts.map +1 -0
  123. package/dist/lexicons/chat/bsky/embed/joinLink.defs.js +28 -0
  124. package/dist/lexicons/chat/bsky/embed/joinLink.defs.js.map +1 -0
  125. package/dist/lexicons/chat/bsky/embed/joinLink.js +6 -0
  126. package/dist/lexicons/chat/bsky/embed/joinLink.js.map +1 -0
  127. package/dist/lexicons/chat/bsky/embed.d.ts +2 -0
  128. package/dist/lexicons/chat/bsky/embed.d.ts.map +1 -0
  129. package/dist/lexicons/chat/bsky/embed.js +5 -0
  130. package/dist/lexicons/chat/bsky/embed.js.map +1 -0
  131. package/dist/lexicons/chat/bsky/group/addMembers.defs.d.ts +1 -1
  132. package/dist/lexicons/chat/bsky/group/addMembers.defs.d.ts.map +1 -1
  133. package/dist/lexicons/chat/bsky/group/addMembers.defs.js +1 -0
  134. package/dist/lexicons/chat/bsky/group/addMembers.defs.js.map +1 -1
  135. package/dist/lexicons/chat/bsky/group/createGroup.defs.d.ts +1 -1
  136. package/dist/lexicons/chat/bsky/group/createGroup.defs.d.ts.map +1 -1
  137. package/dist/lexicons/chat/bsky/group/createGroup.defs.js +1 -0
  138. package/dist/lexicons/chat/bsky/group/createGroup.defs.js.map +1 -1
  139. package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.d.ts +1 -1
  140. package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.d.ts.map +1 -1
  141. package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.js +1 -1
  142. package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.js.map +1 -1
  143. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.d.ts +3 -0
  144. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.d.ts.map +1 -0
  145. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.defs.d.ts +20 -0
  146. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.defs.d.ts.map +1 -0
  147. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.defs.js +19 -0
  148. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.defs.js.map +1 -0
  149. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.js +6 -0
  150. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.js.map +1 -0
  151. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.d.ts +3 -0
  152. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.d.ts.map +1 -0
  153. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.defs.d.ts +20 -0
  154. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.defs.d.ts.map +1 -0
  155. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.defs.js +18 -0
  156. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.defs.js.map +1 -0
  157. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.js +6 -0
  158. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.js.map +1 -0
  159. package/dist/lexicons/chat/bsky/group.d.ts +2 -0
  160. package/dist/lexicons/chat/bsky/group.d.ts.map +1 -1
  161. package/dist/lexicons/chat/bsky/group.js +2 -0
  162. package/dist/lexicons/chat/bsky/group.js.map +1 -1
  163. package/dist/lexicons/chat/bsky/moderation/defs.d.ts +2 -0
  164. package/dist/lexicons/chat/bsky/moderation/defs.d.ts.map +1 -0
  165. package/dist/lexicons/chat/bsky/moderation/defs.defs.d.ts +58 -0
  166. package/dist/lexicons/chat/bsky/moderation/defs.defs.d.ts.map +1 -0
  167. package/dist/lexicons/chat/bsky/moderation/defs.defs.js +38 -0
  168. package/dist/lexicons/chat/bsky/moderation/defs.defs.js.map +1 -0
  169. package/dist/lexicons/chat/bsky/moderation/defs.js +5 -0
  170. package/dist/lexicons/chat/bsky/moderation/defs.js.map +1 -0
  171. package/dist/lexicons/chat/bsky/moderation/getConvo.d.ts +3 -0
  172. package/dist/lexicons/chat/bsky/moderation/getConvo.d.ts.map +1 -0
  173. package/dist/lexicons/chat/bsky/moderation/getConvo.defs.d.ts +22 -0
  174. package/dist/lexicons/chat/bsky/moderation/getConvo.defs.d.ts.map +1 -0
  175. package/dist/lexicons/chat/bsky/moderation/getConvo.defs.js +18 -0
  176. package/dist/lexicons/chat/bsky/moderation/getConvo.defs.js.map +1 -0
  177. package/dist/lexicons/chat/bsky/moderation/getConvo.js +6 -0
  178. package/dist/lexicons/chat/bsky/moderation/getConvo.js.map +1 -0
  179. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.d.ts +3 -0
  180. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.d.ts.map +1 -0
  181. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.defs.d.ts +28 -0
  182. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.defs.d.ts.map +1 -0
  183. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.defs.js +24 -0
  184. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.defs.js.map +1 -0
  185. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.js +6 -0
  186. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.js.map +1 -0
  187. package/dist/lexicons/chat/bsky/moderation/getConvos.d.ts +3 -0
  188. package/dist/lexicons/chat/bsky/moderation/getConvos.d.ts.map +1 -0
  189. package/dist/lexicons/chat/bsky/moderation/getConvos.defs.d.ts +22 -0
  190. package/dist/lexicons/chat/bsky/moderation/getConvos.defs.d.ts.map +1 -0
  191. package/dist/lexicons/chat/bsky/moderation/getConvos.defs.js +22 -0
  192. package/dist/lexicons/chat/bsky/moderation/getConvos.defs.js.map +1 -0
  193. package/dist/lexicons/chat/bsky/moderation/getConvos.js +6 -0
  194. package/dist/lexicons/chat/bsky/moderation/getConvos.js.map +1 -0
  195. package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.d.ts +20 -2
  196. package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.d.ts.map +1 -1
  197. package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.js +11 -0
  198. package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.js.map +1 -1
  199. package/dist/lexicons/chat/bsky/moderation.d.ts +4 -0
  200. package/dist/lexicons/chat/bsky/moderation.d.ts.map +1 -1
  201. package/dist/lexicons/chat/bsky/moderation.js +4 -0
  202. package/dist/lexicons/chat/bsky/moderation.js.map +1 -1
  203. package/dist/lexicons/chat/bsky.d.ts +1 -0
  204. package/dist/lexicons/chat/bsky.d.ts.map +1 -1
  205. package/dist/lexicons/chat/bsky.js +1 -0
  206. package/dist/lexicons/chat/bsky.js.map +1 -1
  207. package/dist/lexicons/com/atproto/lexicon/schema.defs.d.ts.map +1 -1
  208. package/dist/lexicons/com/atproto/server/getServiceAuth.defs.d.ts +2 -2
  209. package/dist/lexicons/com/atproto/server/getServiceAuth.defs.js +1 -1
  210. package/dist/lexicons/com/atproto/server/getServiceAuth.defs.js.map +1 -1
  211. package/dist/lexicons/com/germnetwork/declaration.defs.d.ts.map +1 -1
  212. package/dist/lexicons/site/standard/document.defs.d.ts.map +1 -1
  213. package/dist/lexicons/site/standard/graph/recommend.defs.d.ts.map +1 -1
  214. package/dist/lexicons/site/standard/graph/subscription.defs.d.ts.map +1 -1
  215. package/dist/lexicons/site/standard/publication.defs.d.ts.map +1 -1
  216. package/dist/lexicons/site/standard/theme/basic.defs.d.ts.map +1 -1
  217. package/dist/lexicons/tools/ozone/moderation/defs.defs.d.ts +11 -3
  218. package/dist/lexicons/tools/ozone/moderation/defs.defs.d.ts.map +1 -1
  219. package/dist/lexicons/tools/ozone/moderation/defs.defs.js +9 -0
  220. package/dist/lexicons/tools/ozone/moderation/defs.defs.js.map +1 -1
  221. package/dist/lexicons/tools/ozone/moderation/queryEvents.defs.d.ts +2 -2
  222. package/dist/lexicons/tools/ozone/moderation/queryEvents.defs.d.ts.map +1 -1
  223. package/dist/lexicons/tools/ozone/moderation/queryEvents.defs.js.map +1 -1
  224. package/dist/lexicons/tools/ozone/moderation/queryStatuses.defs.d.ts +2 -2
  225. package/dist/lexicons/tools/ozone/moderation/queryStatuses.defs.d.ts.map +1 -1
  226. package/dist/lexicons/tools/ozone/moderation/queryStatuses.defs.js.map +1 -1
  227. package/dist/mailer/index.d.ts +3 -3
  228. package/dist/mailer/index.d.ts.map +1 -1
  229. package/dist/mailer/index.js +18 -9
  230. package/dist/mailer/index.js.map +1 -1
  231. package/dist/mailer/templates/confirm-email.js +11 -3
  232. package/dist/mailer/templates/confirm-email.js.map +2 -2
  233. package/dist/mailer/templates/delete-account.js +2 -2
  234. package/dist/mailer/templates/delete-account.js.map +2 -2
  235. package/dist/mailer/templates/plc-operation.js +2 -2
  236. package/dist/mailer/templates/plc-operation.js.map +2 -2
  237. package/dist/mailer/templates/reset-password.js +2 -2
  238. package/dist/mailer/templates/reset-password.js.map +2 -2
  239. package/dist/mailer/templates/update-email.js +2 -2
  240. package/dist/mailer/templates/update-email.js.map +2 -2
  241. package/dist/mailer/templates.d.ts +11 -0
  242. package/dist/mailer/templates.d.ts.map +1 -1
  243. package/dist/mailer/templates.js.map +1 -1
  244. package/dist/pipethrough.d.ts +3 -0
  245. package/dist/pipethrough.d.ts.map +1 -1
  246. package/dist/pipethrough.js +25 -9
  247. package/dist/pipethrough.js.map +1 -1
  248. package/dist/read-after-write/viewer.d.ts +2 -2
  249. package/package.json +7 -6
  250. package/src/account-manager/account-manager.ts +105 -7
  251. package/src/account-manager/helpers/account.ts +15 -7
  252. package/src/account-manager/oauth-store.ts +76 -18
  253. package/src/actor-store/record/reader.ts +1 -1
  254. package/src/api/app/bsky/actor/getPreferences.ts +11 -2
  255. package/src/api/app/bsky/actor/putPreferences.ts +11 -2
  256. package/src/api/com/atproto/admin/updateAccountHandle.ts +37 -46
  257. package/src/api/com/atproto/identity/updateHandle.ts +45 -76
  258. package/src/api/com/atproto/repo/getRecord.ts +3 -3
  259. package/src/api/com/atproto/repo/putRecord.ts +2 -2
  260. package/src/api/com/atproto/server/getServiceAuth.ts +7 -0
  261. package/src/config/config.ts +69 -57
  262. package/src/config/env.ts +3 -0
  263. package/src/context.ts +13 -10
  264. package/src/mailer/index.ts +25 -9
  265. package/src/mailer/templates/confirm-email.hbs +18 -17
  266. package/src/mailer/templates/delete-account.hbs +6 -6
  267. package/src/mailer/templates/plc-operation.hbs +6 -6
  268. package/src/mailer/templates/reset-password.hbs +7 -7
  269. package/src/mailer/templates/update-email.hbs +6 -6
  270. package/src/mailer/templates.ts +12 -0
  271. package/src/pipethrough.ts +33 -12
  272. package/tests/_puppeteer.ts +8 -2
  273. package/tests/account-manager.test.ts +123 -50
  274. package/tests/app-passwords.test.ts +5 -5
  275. package/tests/get-service-auth.test.ts +81 -0
  276. package/tests/oauth.test.ts +5 -5
  277. package/tests/proxied/proxy-header.test.ts +1 -0
  278. package/tests/proxied/proxy-oauth-aud.test.ts +175 -0
  279. package/tsconfig.build.tsbuildinfo +1 -1
@@ -18,7 +18,7 @@ describe('account manager', () => {
18
18
  // For debugging:
19
19
  // headless: false,
20
20
  // devtools: true,
21
- // slowMo: 150,
21
+ // slowMo: 25,
22
22
  })
23
23
 
24
24
  network = await TestNetworkNoAppView.create({
@@ -34,6 +34,10 @@ describe('account manager', () => {
34
34
  })
35
35
  })
36
36
 
37
+ afterEach(async () => {
38
+ await network.processAll()
39
+ })
40
+
37
41
  afterAll(async () => {
38
42
  await network?.close()
39
43
  await browser?.close()
@@ -44,7 +48,7 @@ describe('account manager', () => {
44
48
 
45
49
  await page.goto(new URL('/account', network.pds.url))
46
50
 
47
- await page.assertTitle(`S'identifier`)
51
+ await page.assertTitle(`Se connecter`)
48
52
 
49
53
  await page.clickOnText('Créer un nouveau compte')
50
54
 
@@ -55,12 +59,14 @@ describe('account manager', () => {
55
59
  await page.typeInInput('email', 'bob@test.com')
56
60
  await page.typeInInput('password', 'bob-pass')
57
61
 
58
- await page.clickOnText("S'inscrire")
62
+ await page.clickOnText('Inscription')
59
63
 
60
- await page.assertTitle(`Compte utilisateur`)
64
+ await page.waitForNetworkIdle()
61
65
 
62
66
  await page.ensureTextVisibility('bob.test', 'span')
63
67
  await page.ensureTextVisibility('Votre compte Atmosphère est hébergé chez')
68
+
69
+ await page.assertTitle('Mon compte Atmosphère')
64
70
  })
65
71
 
66
72
  it('allows switching accounts', async () => {
@@ -68,7 +74,7 @@ describe('account manager', () => {
68
74
 
69
75
  await page.goto(new URL('/account', network.pds.url))
70
76
 
71
- await page.assertTitle(`Compte utilisateur`)
77
+ await page.assertTitle('Mon compte Atmosphère')
72
78
 
73
79
  await page.clickOnAriaLabel(`Sélecteur de compte`)
74
80
  await page.clickOnText('Sélectionner un autre compte')
@@ -93,18 +99,13 @@ describe('account manager', () => {
93
99
 
94
100
  await page.goto(new URL('/account', network.pds.url))
95
101
 
96
- await page.assertTitle(`Compte utilisateur`)
102
+ await page.assertTitle('Mon compte Atmosphère')
97
103
 
98
104
  await page.ensureTextVisibility('bob.test', 'span')
99
105
 
100
- await page.ensureTextVisibility('alice.test', 'span').then(
101
- () => {
102
- throw new Error('Should not be visible')
103
- },
104
- (err) => {
105
- expect(err).toBeInstanceOf(Error)
106
- },
107
- )
106
+ await expect(async () => {
107
+ await page.ensureTextVisibility('alice.test', 'span', 500)
108
+ }).rejects.toThrow('Waiting for selector')
108
109
  })
109
110
 
110
111
  it('allows changing the password', async () => {
@@ -112,9 +113,11 @@ describe('account manager', () => {
112
113
 
113
114
  await page.goto(new URL('/account', network.pds.url))
114
115
 
115
- await page.assertTitle(`Compte utilisateur`)
116
+ await page.assertTitle('Mon compte Atmosphère')
116
117
 
117
- await page.clickOnText('Mot de passe', 'a')
118
+ await page.clickOnText('Compte utilisateur', 'a')
119
+
120
+ await page.clickOnText('Mot de passe')
118
121
 
119
122
  using sendResetPasswordMock = jest
120
123
  .spyOn(network.pds.ctx.mailer, 'sendResetPassword')
@@ -122,7 +125,7 @@ describe('account manager', () => {
122
125
  // noop
123
126
  })
124
127
 
125
- await page.clickOnText('Envoyer le code')
128
+ await page.clickOnText('Envoyer le code de vérification')
126
129
 
127
130
  await page.waitForNetworkIdle()
128
131
 
@@ -137,20 +140,23 @@ describe('account manager', () => {
137
140
  await page.typeInInput('code', params.token)
138
141
  await page.typeInInput('password', 'bob-new-pass')
139
142
 
140
- await page.clickOnText('Soumettre')
143
+ await page.clickOnText('Valider')
141
144
 
142
- await page.ensureTextVisibility(
143
- 'Réinitialisation du mot de passe réussie',
144
- 'div',
145
- )
145
+ await page.ensureNotification('Réinitialisation du mot de passe réussie')
146
146
  })
147
147
 
148
- it('allows validating the email address', async () => {
148
+ it('allows verifying the email address', async () => {
149
149
  await using page = await PageHelper.from(browser, { languages })
150
150
 
151
151
  await page.goto(new URL('/account', network.pds.url))
152
152
 
153
- await page.clickOnText('Vérifier maintenant', 'button')
153
+ await page.assertTitle('Mon compte Atmosphère')
154
+
155
+ await page.clickOnText('Compte utilisateur', 'a')
156
+
157
+ await page.ensureTextVisibility('Votre adresse email doit être vérifiée.')
158
+
159
+ await page.clickOnText('Vérifier')
154
160
 
155
161
  using sendConfirmEmailMock = jest
156
162
  .spyOn(network.pds.ctx.mailer, 'sendConfirmEmail')
@@ -158,7 +164,7 @@ describe('account manager', () => {
158
164
  // noop
159
165
  })
160
166
 
161
- await page.clickOnText('Envoyer le code', 'button')
167
+ await page.clickOnText('Envoyer le code de vérification', 'button')
162
168
 
163
169
  await page.waitForNetworkIdle()
164
170
 
@@ -171,9 +177,31 @@ describe('account manager', () => {
171
177
  })
172
178
 
173
179
  await page.typeInInput('code', params.token)
174
- await page.clickOnText('Soumettre')
180
+ await page.clickOnText('Valider')
181
+
182
+ await page.ensureNotification('Adresse email vérifiée')
183
+ })
184
+
185
+ it('allows changing the username', async () => {
186
+ await using page = await PageHelper.from(browser, { languages })
187
+
188
+ await page.goto(new URL('/account', network.pds.url))
189
+
190
+ await page.assertTitle('Mon compte Atmosphère')
175
191
 
176
- await page.ensureTextVisibility('Adresse email vérifiée', 'div')
192
+ await page.clickOnText('Compte utilisateur', 'a')
193
+
194
+ await page.clickOnText("Nom d'utilisateur")
195
+
196
+ await page.clickOnText("Utiliser un nom d'utilisateur par défaut")
197
+
198
+ await page.typeInInput('handle', 'bob-renamed')
199
+
200
+ await page.clickOnText('Valider')
201
+
202
+ await page.waitForNetworkIdle()
203
+
204
+ await page.ensureTextVisibility('bob-renamed.test', 'span')
177
205
  })
178
206
 
179
207
  it('allows changing the email address', async () => {
@@ -181,7 +209,11 @@ describe('account manager', () => {
181
209
 
182
210
  await page.goto(new URL('/account', network.pds.url))
183
211
 
184
- await page.clickOnText('Email', 'a')
212
+ await page.assertTitle('Mon compte Atmosphère')
213
+
214
+ await page.clickOnText('Compte utilisateur', 'a')
215
+
216
+ await page.clickOnText('Adresse email')
185
217
 
186
218
  using sendUpdateEmailMock = jest
187
219
  .spyOn(network.pds.ctx.mailer, 'sendUpdateEmail')
@@ -189,13 +221,11 @@ describe('account manager', () => {
189
221
  // noop
190
222
  })
191
223
 
192
- using sendConfirmEmailMock = jest
193
- .spyOn(network.pds.ctx.mailer, 'sendConfirmEmail')
194
- .mockImplementation(async () => {
195
- // noop
196
- })
197
-
198
- await page.clickOnText('Envoyer le code', 'button')
224
+ const emailInput = await page.typeInInput(
225
+ 'email',
226
+ 'bob-new-email@example.com',
227
+ )
228
+ emailInput.press('Enter')
199
229
 
200
230
  await page.waitForNetworkIdle()
201
231
 
@@ -207,26 +237,69 @@ describe('account manager', () => {
207
237
  token: expect.any(String),
208
238
  })
209
239
 
210
- await page.typeInInput('code', updateParams.token)
211
- await page.typeInInput('email', 'bob-new-email@example.com')
212
- await page.clickOnText('Soumettre')
240
+ const codeInput = await page.typeInInput('code', updateParams.token)
241
+ codeInput.press('Enter')
242
+
243
+ await page.ensureNotification("Modification de l'adresse email réussie")
213
244
 
214
- await page.ensureTextVisibility(
215
- "Modification de l'adresse email réussie",
216
- 'div',
245
+ // The email needs to be verified again
246
+ await page.ensureTextVisibility('Votre adresse email doit être vérifiée.')
247
+ })
248
+
249
+ it('allows signing out & signing back in', async () => {
250
+ await using page = await PageHelper.from(browser, { languages })
251
+
252
+ await page.goto(new URL('/account', network.pds.url))
253
+
254
+ await page.assertTitle('Mon compte Atmosphère')
255
+
256
+ await page.clickOnAriaLabel(`Sélecteur de compte`)
257
+ await page.clickOnText('Se déconnecter')
258
+
259
+ await page.assertTitle(`Se connecter`)
260
+ await page.clickOnText('Se connecter')
261
+
262
+ await page.clickOnText('Se souvenir de ce compte sur cet appareil', 'label')
263
+ await page.typeInInput('username', 'bob-new-email@example.com')
264
+ const input = await page.typeInInput('password', 'bob-new-pass')
265
+
266
+ input.press('Enter')
267
+
268
+ await page.ensureTextVisibility('bob-renamed.test', 'span')
269
+ })
270
+
271
+ it('does not ask for a token when changing a non-verified email', async () => {
272
+ await using page = await PageHelper.from(browser, { languages })
273
+
274
+ await page.goto(new URL('/account', network.pds.url))
275
+
276
+ await page.assertTitle('Mon compte Atmosphère')
277
+
278
+ await page.clickOnText('Compte utilisateur', 'a')
279
+
280
+ await page.clickOnText('Adresse email')
281
+
282
+ using sendUpdateEmailMock = jest
283
+ .spyOn(network.pds.ctx.mailer, 'sendUpdateEmail')
284
+ .mockImplementation(async () => {
285
+ // noop
286
+ })
287
+
288
+ const emailInput = await page.typeInInput(
289
+ 'email',
290
+ 'bob-new-email@example.com',
217
291
  )
292
+ emailInput.press('Enter')
218
293
 
219
- expect(sendConfirmEmailMock).toHaveBeenCalledTimes(1)
294
+ await page.waitForNetworkIdle()
220
295
 
221
- const [confirmParams] = sendConfirmEmailMock.mock.lastCall!
222
- expect(confirmParams).toEqual({
223
- locale: 'fr',
224
- token: expect.any(String),
225
- })
296
+ expect(sendUpdateEmailMock).not.toHaveBeenCalled()
297
+
298
+ await page.clickOnText('Plus tard')
226
299
 
227
- await page.typeInInput('code', confirmParams.token)
228
- await page.clickOnText('Soumettre')
300
+ await page.ensureNotification("Modification de l'adresse email réussie")
229
301
 
230
- await page.ensureTextVisibility('Adresse email vérifiée', 'div')
302
+ // The email needs to be verified again
303
+ await page.ensureTextVisibility('Votre adresse email doit être vérifiée.')
231
304
  })
232
305
  })
@@ -115,7 +115,7 @@ describe('app_passwords', () => {
115
115
  it('restricts service auth token methods for non-privileged access tokens', async () => {
116
116
  const attemptCaseSensitive = appAgent.api.com.atproto.server.getServiceAuth(
117
117
  {
118
- aud: 'did:example:test',
118
+ aud: network.pds.ctx.cfg.service.did,
119
119
  lxm: 'com.atproto.server.createAccount',
120
120
  },
121
121
  )
@@ -124,7 +124,7 @@ describe('app_passwords', () => {
124
124
  )
125
125
  const attemptCaseInsensitive =
126
126
  appAgent.api.com.atproto.server.getServiceAuth({
127
- aud: 'did:example:test',
127
+ aud: network.pds.ctx.cfg.service.did,
128
128
  lxm: 'com.atproto.server.createaccount',
129
129
  })
130
130
  await expect(attemptCaseInsensitive).rejects.toThrow(
@@ -134,7 +134,7 @@ describe('app_passwords', () => {
134
134
 
135
135
  it('allows privileged service auth token scopes for privileged access tokens', async () => {
136
136
  await priviAgent.api.com.atproto.server.getServiceAuth({
137
- aud: 'did:example:test',
137
+ aud: network.pds.ctx.cfg.service.did,
138
138
  lxm: 'com.atproto.server.createAccount',
139
139
  })
140
140
  })
@@ -165,7 +165,7 @@ describe('app_passwords', () => {
165
165
 
166
166
  // allows privileged app passwords or higher
167
167
  const priviAttempt = appAgent.api.com.atproto.server.getServiceAuth({
168
- aud: 'did:example:test',
168
+ aud: network.pds.ctx.cfg.service.did,
169
169
  lxm: 'com.atproto.server.createAccount',
170
170
  })
171
171
  await expect(priviAttempt).rejects.toThrow(
@@ -211,7 +211,7 @@ describe('app_passwords', () => {
211
211
 
212
212
  // allows privileged app passwords or higher
213
213
  await priviAgent.api.com.atproto.server.getServiceAuth({
214
- aud: 'did:example:test',
214
+ aud: network.pds.ctx.cfg.service.did,
215
215
  })
216
216
 
217
217
  // allows only full access auth
@@ -0,0 +1,81 @@
1
+ import * as jose from 'jose'
2
+ import { AtpAgent } from '@atproto/api'
3
+ import { TestNetworkNoAppView } from '@atproto/dev-env'
4
+
5
+ describe('com.atproto.server.getServiceAuth', () => {
6
+ let network: TestNetworkNoAppView
7
+ let agent: AtpAgent
8
+ let aliceDid: string
9
+ let pdsDid: string
10
+
11
+ beforeAll(async () => {
12
+ network = await TestNetworkNoAppView.create({
13
+ dbPostgresSchema: 'get_service_auth',
14
+ })
15
+ pdsDid = network.pds.ctx.cfg.service.did
16
+ agent = network.pds.getAgent()
17
+ const session = await agent.createAccount({
18
+ handle: 'alice.test',
19
+ email: 'alice@test.com',
20
+ password: 'alice-pass',
21
+ })
22
+ aliceDid = session.data.did
23
+ })
24
+
25
+ afterAll(async () => {
26
+ await network.close()
27
+ })
28
+
29
+ it('issues a token whose aud matches a bare-DID input', async () => {
30
+ const res = await agent.api.com.atproto.server.getServiceAuth({
31
+ aud: pdsDid,
32
+ lxm: 'com.atproto.server.describeServer',
33
+ })
34
+ const decoded = jose.decodeJwt(res.data.token)
35
+ expect(decoded.aud).toBe(pdsDid)
36
+ expect(decoded.iss).toBe(aliceDid)
37
+ expect(decoded.lxm).toBe('com.atproto.server.describeServer')
38
+ })
39
+
40
+ it('issues a token whose aud matches a combined did#serviceId input', async () => {
41
+ const aud = `${pdsDid}#atproto_pds`
42
+ const res = await agent.api.com.atproto.server.getServiceAuth({
43
+ aud,
44
+ lxm: 'com.atproto.server.describeServer',
45
+ })
46
+ const decoded = jose.decodeJwt(res.data.token)
47
+ expect(decoded.aud).toBe(aud)
48
+ expect(decoded.iss).toBe(aliceDid)
49
+ expect(decoded.lxm).toBe('com.atproto.server.describeServer')
50
+ })
51
+
52
+ it('rejects malformed aud with InvalidRequest', async () => {
53
+ const attempt = agent.api.com.atproto.server.getServiceAuth({
54
+ aud: 'not-a-did',
55
+ lxm: 'com.atproto.server.describeServer',
56
+ })
57
+ await expect(attempt).rejects.toThrow(
58
+ /aud must be a valid atproto DID or did#serviceId reference/,
59
+ )
60
+ })
61
+
62
+ it('rejects an aud with a non-atproto DID method', async () => {
63
+ const attempt = agent.api.com.atproto.server.getServiceAuth({
64
+ aud: 'did:foo:bar',
65
+ lxm: 'com.atproto.server.describeServer',
66
+ })
67
+ await expect(attempt).rejects.toThrow(
68
+ /aud must be a valid atproto DID or did#serviceId reference/,
69
+ )
70
+ })
71
+
72
+ it('rejects an aud with empty fragment', async () => {
73
+ const attempt = agent.api.com.atproto.server.getServiceAuth({
74
+ aud: `${pdsDid}#`,
75
+ lxm: 'com.atproto.server.describeServer',
76
+ })
77
+ await expect(attempt).rejects.toThrow(
78
+ /aud must be a valid atproto DID or did#serviceId reference/,
79
+ )
80
+ })
81
+ })
@@ -25,7 +25,7 @@ describe('oauth', () => {
25
25
  // For debugging:
26
26
  // headless: false,
27
27
  // devtools: true,
28
- // slowMo: 250,
28
+ // slowMo: 25,
29
29
  })
30
30
 
31
31
  network = await TestNetworkNoAppView.create({
@@ -71,7 +71,7 @@ describe('oauth', () => {
71
71
 
72
72
  await page.navigationClick(`Sign up with ${new URL(network.pds.url).host}`)
73
73
 
74
- await page.assertTitle("S'inscrire")
74
+ await page.assertTitle('Inscription')
75
75
 
76
76
  await page.typeInInput('handle', 'bob')
77
77
 
@@ -80,10 +80,10 @@ describe('oauth', () => {
80
80
  await page.typeInInput('email', 'bob@test.com')
81
81
  await page.typeInInput('password', 'bob-pass')
82
82
 
83
- await page.clickOnText("S'inscrire")
83
+ await page.clickOnText('Inscription')
84
84
 
85
85
  await page.ensureTextVisibility(
86
- `L'application demande un contrôle total sur votre identité, ce qui signifie qu'elle pourrait casser de façon permanente, ou même usurper, votre compte. N'authorisez l'accès qu'aux applications auxquelles vous faites vraiment confiance.`,
86
+ `L'application demande un contrôle total sur votre identité, ce qui signifie qu'elle pourrait casser de façon permanente, ou même usurper, votre compte. N'autorisez l'accès qu'aux applications auxquelles vous faites vraiment confiance.`,
87
87
  )
88
88
 
89
89
  // Make sure the new account is propagated to the PLC directory, allowing
@@ -112,7 +112,7 @@ describe('oauth', () => {
112
112
 
113
113
  await page.navigationClick(`Login with ${new URL(network.pds.url).host}`)
114
114
 
115
- await page.assertTitle("S'identifier")
115
+ await page.assertTitle('Se connecter')
116
116
 
117
117
  // Cancel the OAuth flow:
118
118
  await page.navigationClick('Annuler')
@@ -77,6 +77,7 @@ describe('proxy header', () => {
77
77
  ).resolves.toEqual({
78
78
  did: proxyServer.did,
79
79
  url: proxyServer.url,
80
+ serviceId: 'atproto_test',
80
81
  })
81
82
  })
82
83
 
@@ -0,0 +1,175 @@
1
+ import { once } from 'node:events'
2
+ import http from 'node:http'
3
+ import { AddressInfo } from 'node:net'
4
+ import * as plc from '@did-plc/lib'
5
+ import express from 'express'
6
+ import { Keypair } from '@atproto/crypto'
7
+ import { SeedClient, TestNetworkNoAppView, usersSeed } from '@atproto/dev-env'
8
+ import { ScopePermissions } from '@atproto/oauth-scopes'
9
+ import { DidString } from '@atproto/syntax'
10
+ import { AppContext } from '../../src/context.js'
11
+ import { proxyHandler } from '../../src/pipethrough.js'
12
+
13
+ // Regression test for the OAuth service-proxying audience fix.
14
+ //
15
+ // Before the fix, proxyHandler passed a bare DID as `aud` to the rpc scope
16
+ // check. An OAuth caller granted `rpc:<lxm>?aud=<did>#<serviceId>` (the
17
+ // canonical scope shape used in atproto OAuth) had no way to match, so
18
+ // proxied calls failed at the scope check. The fix combines the proxied
19
+ // service id into the scope-check audience as `<did>#<serviceId>`.
20
+ //
21
+ // We use a real PDS AppContext (so every field is real-typed). Only the
22
+ // `authVerifier.authorization` method is replaced, with a thin stub that
23
+ // drives the OAuth path off an `x-test-scope` request header — letting us
24
+ // exercise the rpc scope check without minting a real OAuth token. A
25
+ // separate express app mounts a fresh proxyHandler against the real ctx
26
+ // and forwards to a real upstream ProxyServer.
27
+
28
+ describe('proxy oauth audience', () => {
29
+ let network: TestNetworkNoAppView
30
+ let sc: SeedClient
31
+ let alice: string
32
+ let upstream: ProxyServer
33
+ let server: http.Server
34
+ let serverUrl: string
35
+
36
+ beforeAll(async () => {
37
+ network = await TestNetworkNoAppView.create({
38
+ dbPostgresSchema: 'proxy_oauth_aud',
39
+ })
40
+ sc = network.getSeedClient()
41
+ await usersSeed(sc)
42
+ alice = sc.dids.alice
43
+ upstream = await ProxyServer.create(
44
+ network.pds.ctx.plcClient,
45
+ network.pds.ctx.plcRotationKey,
46
+ 'atproto_test',
47
+ )
48
+
49
+ // Replace only the `authorization` method on the real AuthVerifier so
50
+ // every other ctx field stays real and real-typed. The override's
51
+ // signature is constrained by AuthVerifier['authorization']: an OAuth
52
+ // shape change in the real verifier breaks the body of this stub at
53
+ // compile time.
54
+ const stubAuthorization: typeof network.pds.ctx.authVerifier.authorization =
55
+ ({ authorize }) => {
56
+ return async (ctx) => {
57
+ const scopeHeader = ctx.req.headers['x-test-scope'] as
58
+ | string
59
+ | undefined
60
+ const permissions = new ScopePermissions(
61
+ scopeHeader?.split(' ') ?? [],
62
+ )
63
+ await authorize(permissions, ctx)
64
+ return {
65
+ credentials: {
66
+ type: 'oauth',
67
+ did: alice as DidString,
68
+ permissions,
69
+ },
70
+ }
71
+ }
72
+ }
73
+ network.pds.ctx.authVerifier.authorization = stubAuthorization
74
+
75
+ const app = express()
76
+ // dev-env exports AppContext from its built dist/, while we import
77
+ // proxyHandler from src/. Cast at this boundary to bridge the two
78
+ // identical shapes; downstream behavior is real.
79
+ app.all('/xrpc/*', proxyHandler(network.pds.ctx as unknown as AppContext))
80
+ app.use(
81
+ (
82
+ err: Error & { status?: number; statusCode?: number },
83
+ _req: express.Request,
84
+ res: express.Response,
85
+ _next: express.NextFunction,
86
+ ) => {
87
+ if (res.headersSent) return
88
+ res.status(err.status ?? err.statusCode ?? 500).end()
89
+ },
90
+ )
91
+ server = app.listen(0)
92
+ await once(server, 'listening')
93
+ serverUrl = `http://localhost:${(server.address() as AddressInfo).port}`
94
+ })
95
+
96
+ afterAll(async () => {
97
+ await new Promise<void>((resolve) => server.close(() => resolve()))
98
+ await upstream.close()
99
+ await network.close()
100
+ })
101
+
102
+ it('matches an OAuth rpc scope granted with combined did#serviceId aud', async () => {
103
+ // Pre-fix this would have rejected because the scope check received
104
+ // bare-DID aud and never matched the combined-form scope. A 200 here
105
+ // implies the scope check ran with combined-form aud.
106
+ const res = await fetch(`${serverUrl}/xrpc/app.bsky.feed.getFeed`, {
107
+ headers: {
108
+ 'atproto-proxy': `${upstream.did}#atproto_test`,
109
+ 'x-test-scope': `rpc:app.bsky.feed.getFeed?aud=${encodeURIComponent(`${upstream.did}#atproto_test`)}`,
110
+ },
111
+ })
112
+ expect(res.status).toBe(200)
113
+ })
114
+
115
+ it('rejects an OAuth rpc scope granted for a different service id', async () => {
116
+ // Same DID, different service id — both forms parse as valid scope
117
+ // audiences, but the runtime aud (`upstream.did#atproto_test`) doesn't
118
+ // match the granted aud (`upstream.did#atproto_other`).
119
+ const res = await fetch(`${serverUrl}/xrpc/app.bsky.feed.getFeed`, {
120
+ headers: {
121
+ 'atproto-proxy': `${upstream.did}#atproto_test`,
122
+ 'x-test-scope': `rpc:app.bsky.feed.getFeed?aud=${encodeURIComponent(`${upstream.did}#atproto_other`)}`,
123
+ },
124
+ })
125
+ expect([401, 403]).toContain(res.status)
126
+ })
127
+ })
128
+
129
+ class ProxyServer {
130
+ constructor(
131
+ public server: http.Server,
132
+ public url: string,
133
+ public did: string,
134
+ ) {}
135
+
136
+ static async create(
137
+ plcClient: plc.Client,
138
+ keypair: Keypair,
139
+ serviceId: string,
140
+ ): Promise<ProxyServer> {
141
+ const app = express()
142
+ app.all('*', (_req, res) => res.sendStatus(200))
143
+
144
+ const server = app.listen(0)
145
+ await once(server, 'listening')
146
+
147
+ const { port } = server.address() as AddressInfo
148
+ const url = `http://localhost:${port}`
149
+ const plcOp = await plc.signOperation(
150
+ {
151
+ type: 'plc_operation',
152
+ rotationKeys: [keypair.did()],
153
+ alsoKnownAs: [],
154
+ verificationMethods: {},
155
+ services: {
156
+ [serviceId]: {
157
+ type: 'TestAtprotoService',
158
+ endpoint: url,
159
+ },
160
+ },
161
+ prev: null,
162
+ },
163
+ keypair,
164
+ )
165
+ const did = await plc.didForCreateOp(plcOp)
166
+ await plcClient.sendOperation(did, plcOp)
167
+ return new ProxyServer(server, url, did)
168
+ }
169
+
170
+ close(): Promise<void> {
171
+ return new Promise<void>((resolve) => {
172
+ this.server.close(() => resolve())
173
+ })
174
+ }
175
+ }