@atproto/api 0.13.2 → 0.13.4

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 (46) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/OAUTH.md +24 -13
  3. package/README.md +17 -7
  4. package/dist/agent.d.ts +17 -8
  5. package/dist/agent.d.ts.map +1 -1
  6. package/dist/agent.js +59 -6
  7. package/dist/agent.js.map +1 -1
  8. package/dist/atp-agent.d.ts +32 -18
  9. package/dist/atp-agent.d.ts.map +1 -1
  10. package/dist/atp-agent.js +117 -103
  11. package/dist/atp-agent.js.map +1 -1
  12. package/dist/client/index.d.ts.map +1 -1
  13. package/dist/client/index.js +1 -6
  14. package/dist/client/index.js.map +1 -1
  15. package/dist/client/lexicons.d.ts +7 -0
  16. package/dist/client/lexicons.d.ts.map +1 -1
  17. package/dist/client/lexicons.js +8 -1
  18. package/dist/client/lexicons.js.map +1 -1
  19. package/dist/client/types/app/bsky/embed/record.d.ts +1 -0
  20. package/dist/client/types/app/bsky/embed/record.d.ts.map +1 -1
  21. package/dist/client/types/app/bsky/embed/record.js.map +1 -1
  22. package/dist/client/types/app/bsky/feed/getPostThread.d.ts +1 -0
  23. package/dist/client/types/app/bsky/feed/getPostThread.d.ts.map +1 -1
  24. package/dist/client/types/app/bsky/feed/getPostThread.js.map +1 -1
  25. package/dist/client/types/app/bsky/feed/getQuotes.d.ts +4 -3
  26. package/dist/client/types/app/bsky/feed/getQuotes.d.ts.map +1 -1
  27. package/dist/client/types/app/bsky/feed/getQuotes.js +0 -6
  28. package/dist/client/types/app/bsky/feed/getQuotes.js.map +1 -1
  29. package/dist/index.d.ts +3 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +5 -3
  32. package/dist/index.js.map +1 -1
  33. package/dist/session-manager.d.ts +5 -0
  34. package/dist/session-manager.d.ts.map +1 -0
  35. package/dist/session-manager.js +3 -0
  36. package/dist/session-manager.js.map +1 -0
  37. package/package.json +2 -2
  38. package/src/agent.ts +35 -14
  39. package/src/atp-agent.ts +149 -128
  40. package/src/client/index.ts +1 -5
  41. package/src/client/lexicons.ts +8 -1
  42. package/src/client/types/app/bsky/embed/record.ts +1 -0
  43. package/src/client/types/app/bsky/feed/getPostThread.ts +1 -0
  44. package/src/client/types/app/bsky/feed/getQuotes.ts +4 -5
  45. package/src/index.ts +5 -2
  46. package/src/session-manager.ts +5 -0
package/src/atp-agent.ts CHANGED
@@ -4,16 +4,18 @@ import {
4
4
  Gettable,
5
5
  ResponseType,
6
6
  XRPCError,
7
- combineHeaders,
7
+ XrpcClient,
8
8
  errorResponseBody,
9
9
  } from '@atproto/xrpc'
10
10
  import { Agent } from './agent'
11
11
  import {
12
- AtpBaseClient,
13
12
  ComAtprotoServerCreateAccount,
14
13
  ComAtprotoServerCreateSession,
15
14
  ComAtprotoServerGetSession,
15
+ ComAtprotoServerNS,
16
16
  } from './client'
17
+ import { schemas } from './client/lexicons'
18
+ import { SessionManager } from './session-manager'
17
19
  import {
18
20
  AtpAgentLoginOpts,
19
21
  AtpPersistSessionHandler,
@@ -32,92 +34,44 @@ export type AtpAgentOptions = {
32
34
  }
33
35
 
34
36
  /**
35
- * An {@link AtpAgent} extends the {@link Agent} abstract class by
36
- * implementing password based session management.
37
+ * A wrapper around the {@link Agent} class that uses credential based session
38
+ * management. This class also exposes most of the session management methods
39
+ * directly.
40
+ *
41
+ * This class will be deprecated in the near future. Use {@link Agent} directly
42
+ * with a {@link CredentialSession} instead:
43
+ *
44
+ * ```ts
45
+ * const session = new CredentialSession({
46
+ * service: new URL('https://example.com'),
47
+ * })
48
+ *
49
+ * const agent = new Agent(session)
50
+ * ```
37
51
  */
38
52
  export class AtpAgent extends Agent {
39
- public readonly headers: Map<string, Gettable<null | string>>
40
- public readonly sessionManager: SessionManager
41
-
42
- constructor(options: AtpAgentOptions | SessionManager) {
43
- super(async (url: string, init?: RequestInit): Promise<Response> => {
44
- // wait for any active session-refreshes to finish
45
- await this.sessionManager.refreshSessionPromise
46
-
47
- const initialHeaders = combineHeaders(init?.headers, this.headers)
48
- const reqInit = { ...init, headers: initialHeaders }
49
-
50
- const initialUri = new URL(url, this.dispatchUrl)
51
- const initialReq = new Request(initialUri, reqInit)
52
-
53
- const initialToken = this.session?.accessJwt
54
- if (!initialToken || initialReq.headers.has('authorization')) {
55
- return (0, this.sessionManager.fetch)(initialReq)
56
- }
57
-
58
- initialReq.headers.set('authorization', `Bearer ${initialToken}`)
59
- const initialRes = await (0, this.sessionManager.fetch)(initialReq)
60
-
61
- if (!this.session?.refreshJwt) {
62
- return initialRes
63
- }
64
- const isExpiredToken = await isErrorResponse(
65
- initialRes,
66
- [400],
67
- ['ExpiredToken'],
68
- )
69
-
70
- if (!isExpiredToken) {
71
- return initialRes
53
+ readonly sessionManager: CredentialSession
54
+
55
+ constructor(options: AtpAgentOptions | CredentialSession) {
56
+ const sessionManager =
57
+ options instanceof CredentialSession
58
+ ? options
59
+ : new CredentialSession(
60
+ new URL(options.service),
61
+ options.fetch,
62
+ options.persistSession,
63
+ )
64
+
65
+ super(sessionManager)
66
+
67
+ // This assignment is already being done in the super constructor, but we
68
+ // need to do it here to make TypeScript happy.
69
+ this.sessionManager = sessionManager
70
+
71
+ if (!(options instanceof CredentialSession) && options.headers) {
72
+ for (const [key, value] of options.headers) {
73
+ this.setHeader(key, value)
72
74
  }
73
-
74
- try {
75
- await this.sessionManager.refreshSession()
76
- } catch {
77
- return initialRes
78
- }
79
-
80
- if (reqInit?.signal?.aborted) {
81
- return initialRes
82
- }
83
-
84
- // The stream was already consumed. We cannot retry the request. A solution
85
- // would be to tee() the input stream but that would bufferize the entire
86
- // stream in memory which can lead to memory starvation. Instead, we will
87
- // return the original response and let the calling code handle retries.
88
- if (ReadableStream && reqInit.body instanceof ReadableStream) {
89
- return initialRes
90
- }
91
-
92
- // Return initial "ExpiredToken" response if the session was not refreshed.
93
- const updatedToken = this.session?.accessJwt
94
- if (!updatedToken || updatedToken === initialToken) {
95
- return initialRes
96
- }
97
-
98
- // Make sure the initial request is cancelled to avoid leaking resources
99
- // (NodeJS 👀): https://undici.nodejs.org/#/?id=garbage-collection
100
- await initialRes.body?.cancel()
101
-
102
- // We need to re-compute the URI in case the PDS endpoint has changed
103
- const updatedUri = new URL(url, this.dispatchUrl)
104
- const updatedReq = new Request(updatedUri, reqInit)
105
-
106
- updatedReq.headers.set('authorization', `Bearer ${updatedToken}`)
107
-
108
- return await (0, this.sessionManager.fetch)(updatedReq)
109
- })
110
-
111
- if (options instanceof SessionManager) {
112
- this.headers = new Map()
113
- this.sessionManager = options
114
- } else {
115
- this.headers = new Map(options.headers)
116
- this.sessionManager = new SessionManager(
117
- new URL(options.service),
118
- options.fetch,
119
- options.persistSession,
120
- )
121
75
  }
122
76
  }
123
77
 
@@ -125,36 +79,16 @@ export class AtpAgent extends Agent {
125
79
  return this.copyInto(new AtpAgent(this.sessionManager))
126
80
  }
127
81
 
128
- copyInto<T extends Agent>(inst: T): T {
129
- if (inst instanceof AtpAgent) {
130
- for (const [key] of inst.headers) {
131
- inst.unsetHeader(key)
132
- }
133
- for (const [key, value] of this.headers) {
134
- inst.setHeader(key, value)
135
- }
136
- }
137
- return super.copyInto(inst)
138
- }
139
-
140
- setHeader(key: string, value: Gettable<null | string>): void {
141
- this.headers.set(key.toLowerCase(), value)
142
- }
143
-
144
- unsetHeader(key: string): void {
145
- this.headers.delete(key.toLowerCase())
146
- }
147
-
148
82
  get session() {
149
83
  return this.sessionManager.session
150
84
  }
151
85
 
152
86
  get hasSession() {
153
- return !!this.session
87
+ return this.sessionManager.hasSession
154
88
  }
155
89
 
156
90
  get did() {
157
- return this.session?.did
91
+ return this.sessionManager.did
158
92
  }
159
93
 
160
94
  get serviceUrl() {
@@ -166,7 +100,7 @@ export class AtpAgent extends Agent {
166
100
  }
167
101
 
168
102
  get dispatchUrl() {
169
- return this.pdsUrl || this.serviceUrl
103
+ return this.sessionManager.dispatchUrl
170
104
  }
171
105
 
172
106
  /** @deprecated use {@link serviceUrl} instead */
@@ -186,7 +120,7 @@ export class AtpAgent extends Agent {
186
120
  )
187
121
  }
188
122
 
189
- /** @deprecated This will be removed in OAuthAtpAgent */
123
+ /** @deprecated use {@link AtpAgent.serviceUrl} instead */
190
124
  getServiceUrl() {
191
125
  return this.serviceUrl
192
126
  }
@@ -216,23 +150,34 @@ export class AtpAgent extends Agent {
216
150
  }
217
151
 
218
152
  /**
219
- * Private class meant to be used by clones of {@link AtpAgent} so they can
220
- * share the same session across multiple instances (with different
221
- * proxying/labelers/headers options).
153
+ * Credentials (username / password) based session manager. Instances of this
154
+ * class will typically be used as the session manager for an {@link AtpAgent}.
155
+ * They can also be used with an {@link XrpcClient}, if you want to use you
156
+ * own Lexicons.
222
157
  */
223
- class SessionManager {
158
+ export class CredentialSession implements SessionManager {
224
159
  public pdsUrl?: URL // The PDS URL, driven by the did doc
225
160
  public session?: AtpSessionData
226
161
  public refreshSessionPromise: Promise<void> | undefined
227
162
 
228
163
  /**
229
- * Private {@link AtpBaseClient} used to perform session management API
164
+ * Private {@link ComAtprotoServerNS} used to perform session management API
230
165
  * calls on the service endpoint. Calls performed by this agent will not be
231
- * authenticated using the user's session.
166
+ * authenticated using the user's session to allow proper manual configuration
167
+ * of the headers when performing session management operations.
232
168
  */
233
- protected api = new AtpBaseClient((url, init) => {
234
- return (0, this.fetch)(new URL(url, this.serviceUrl), init)
235
- })
169
+ protected server = new ComAtprotoServerNS(
170
+ // Note that the use of the codegen "schemas" (to instantiate `this.api`),
171
+ // as well as the use of `ComAtprotoServerNS` will cause this class to
172
+ // reference (way) more code than it actually needs. It is not possible,
173
+ // with the current state of the codegen, to generate a client that only
174
+ // includes the methods that are actually used by this class. This is a
175
+ // known limitation that should be addressed in a future version of the
176
+ // codegen.
177
+ new XrpcClient((url, init) => {
178
+ return (0, this.fetch)(new URL(url, this.serviceUrl), init)
179
+ }, schemas),
180
+ )
236
181
 
237
182
  constructor(
238
183
  public readonly serviceUrl: URL,
@@ -240,6 +185,18 @@ class SessionManager {
240
185
  protected readonly persistSession?: AtpPersistSessionHandler,
241
186
  ) {}
242
187
 
188
+ get did() {
189
+ return this.session?.did
190
+ }
191
+
192
+ get dispatchUrl() {
193
+ return this.pdsUrl || this.serviceUrl
194
+ }
195
+
196
+ get hasSession() {
197
+ return !!this.session
198
+ }
199
+
243
200
  /**
244
201
  * Sets a WhatWG "fetch()" function to be used for making HTTP requests.
245
202
  */
@@ -247,6 +204,71 @@ class SessionManager {
247
204
  this.fetch = fetch
248
205
  }
249
206
 
207
+ async fetchHandler(url: string, init?: RequestInit): Promise<Response> {
208
+ // wait for any active session-refreshes to finish
209
+ await this.refreshSessionPromise
210
+
211
+ const initialUri = new URL(url, this.dispatchUrl)
212
+ const initialReq = new Request(initialUri, init)
213
+
214
+ const initialToken = this.session?.accessJwt
215
+ if (!initialToken || initialReq.headers.has('authorization')) {
216
+ return (0, this.fetch)(initialReq)
217
+ }
218
+
219
+ initialReq.headers.set('authorization', `Bearer ${initialToken}`)
220
+ const initialRes = await (0, this.fetch)(initialReq)
221
+
222
+ if (!this.session?.refreshJwt) {
223
+ return initialRes
224
+ }
225
+ const isExpiredToken = await isErrorResponse(
226
+ initialRes,
227
+ [400],
228
+ ['ExpiredToken'],
229
+ )
230
+
231
+ if (!isExpiredToken) {
232
+ return initialRes
233
+ }
234
+
235
+ try {
236
+ await this.refreshSession()
237
+ } catch {
238
+ return initialRes
239
+ }
240
+
241
+ if (init?.signal?.aborted) {
242
+ return initialRes
243
+ }
244
+
245
+ // The stream was already consumed. We cannot retry the request. A solution
246
+ // would be to tee() the input stream but that would bufferize the entire
247
+ // stream in memory which can lead to memory starvation. Instead, we will
248
+ // return the original response and let the calling code handle retries.
249
+ if (ReadableStream && init?.body instanceof ReadableStream) {
250
+ return initialRes
251
+ }
252
+
253
+ // Return initial "ExpiredToken" response if the session was not refreshed.
254
+ const updatedToken = this.session?.accessJwt
255
+ if (!updatedToken || updatedToken === initialToken) {
256
+ return initialRes
257
+ }
258
+
259
+ // Make sure the initial request is cancelled to avoid leaking resources
260
+ // (NodeJS 👀): https://undici.nodejs.org/#/?id=garbage-collection
261
+ await initialRes.body?.cancel()
262
+
263
+ // We need to re-compute the URI in case the PDS endpoint has changed
264
+ const updatedUri = new URL(url, this.dispatchUrl)
265
+ const updatedReq = new Request(updatedUri, init)
266
+
267
+ updatedReq.headers.set('authorization', `Bearer ${updatedToken}`)
268
+
269
+ return await (0, this.fetch)(updatedReq)
270
+ }
271
+
250
272
  /**
251
273
  * Create a new account and hydrate its session in this agent.
252
274
  */
@@ -255,7 +277,7 @@ class SessionManager {
255
277
  opts?: ComAtprotoServerCreateAccount.CallOptions,
256
278
  ): Promise<ComAtprotoServerCreateAccount.Response> {
257
279
  try {
258
- const res = await this.api.com.atproto.server.createAccount(data, opts)
280
+ const res = await this.server.createAccount(data, opts)
259
281
  this.session = {
260
282
  accessJwt: res.data.accessJwt,
261
283
  refreshJwt: res.data.refreshJwt,
@@ -283,7 +305,7 @@ class SessionManager {
283
305
  opts: AtpAgentLoginOpts,
284
306
  ): Promise<ComAtprotoServerCreateSession.Response> {
285
307
  try {
286
- const res = await this.api.com.atproto.server.createSession({
308
+ const res = await this.server.createSession({
287
309
  identifier: opts.identifier,
288
310
  password: opts.password,
289
311
  authFactorToken: opts.authFactorToken,
@@ -312,7 +334,7 @@ class SessionManager {
312
334
  async logout(): Promise<void> {
313
335
  if (this.session) {
314
336
  try {
315
- await this.api.com.atproto.server.deleteSession(undefined, {
337
+ await this.server.deleteSession(undefined, {
316
338
  headers: {
317
339
  authorization: `Bearer ${this.session.accessJwt}`,
318
340
  },
@@ -335,7 +357,7 @@ class SessionManager {
335
357
  this.session = session
336
358
 
337
359
  try {
338
- const res = await this.api.com.atproto.server
360
+ const res = await this.server
339
361
  .getSession(undefined, {
340
362
  headers: { authorization: `Bearer ${session.accessJwt}` },
341
363
  })
@@ -346,15 +368,14 @@ class SessionManager {
346
368
  session.refreshJwt
347
369
  ) {
348
370
  try {
349
- const res = await this.api.com.atproto.server.refreshSession(
350
- undefined,
351
- { headers: { authorization: `Bearer ${session.refreshJwt}` } },
352
- )
371
+ const res = await this.server.refreshSession(undefined, {
372
+ headers: { authorization: `Bearer ${session.refreshJwt}` },
373
+ })
353
374
 
354
375
  session.accessJwt = res.data.accessJwt
355
376
  session.refreshJwt = res.data.refreshJwt
356
377
 
357
- return this.api.com.atproto.server.getSession(undefined, {
378
+ return this.server.getSession(undefined, {
358
379
  headers: { authorization: `Bearer ${session.accessJwt}` },
359
380
  })
360
381
  } catch {
@@ -425,7 +446,7 @@ class SessionManager {
425
446
  }
426
447
 
427
448
  try {
428
- const res = await this.api.com.atproto.server.refreshSession(undefined, {
449
+ const res = await this.server.refreshSession(undefined, {
429
450
  headers: { authorization: `Bearer ${this.session.refreshJwt}` },
430
451
  })
431
452
  // succeeded, update the session
@@ -1749,11 +1749,7 @@ export class AppBskyFeedNS {
1749
1749
  params?: AppBskyFeedGetQuotes.QueryParams,
1750
1750
  opts?: AppBskyFeedGetQuotes.CallOptions,
1751
1751
  ): Promise<AppBskyFeedGetQuotes.Response> {
1752
- return this._client
1753
- .call('app.bsky.feed.getQuotes', params, undefined, opts)
1754
- .catch((e) => {
1755
- throw AppBskyFeedGetQuotes.toKnownErr(e)
1756
- })
1752
+ return this._client.call('app.bsky.feed.getQuotes', params, undefined, opts)
1757
1753
  }
1758
1754
 
1759
1755
  getRepostedBy(
@@ -5034,6 +5034,9 @@ export const schemaDict = {
5034
5034
  likeCount: {
5035
5035
  type: 'integer',
5036
5036
  },
5037
+ quoteCount: {
5038
+ type: 'integer',
5039
+ },
5037
5040
  embeds: {
5038
5041
  type: 'array',
5039
5042
  items: {
@@ -5749,7 +5752,7 @@ export const schemaDict = {
5749
5752
  main: {
5750
5753
  type: 'query',
5751
5754
  description:
5752
- 'Get a list of posts liked by an actor. Does not require auth.',
5755
+ 'Get a list of posts liked by an actor. Requires auth, actor must be the requesting account.',
5753
5756
  parameters: {
5754
5757
  type: 'params',
5755
5758
  required: ['actor'],
@@ -6248,6 +6251,10 @@ export const schemaDict = {
6248
6251
  'lex:app.bsky.feed.defs#blockedPost',
6249
6252
  ],
6250
6253
  },
6254
+ threadgate: {
6255
+ type: 'ref',
6256
+ ref: 'lex:app.bsky.feed.defs#threadgateView',
6257
+ },
6251
6258
  },
6252
6259
  },
6253
6260
  },
@@ -67,6 +67,7 @@ export interface ViewRecord {
67
67
  replyCount?: number
68
68
  repostCount?: number
69
69
  likeCount?: number
70
+ quoteCount?: number
70
71
  embeds?: (
71
72
  | AppBskyEmbedImages.View
72
73
  | AppBskyEmbedExternal.View
@@ -25,6 +25,7 @@ export interface OutputSchema {
25
25
  | AppBskyFeedDefs.NotFoundPost
26
26
  | AppBskyFeedDefs.BlockedPost
27
27
  | { $type: string; [k: string]: unknown }
28
+ threadgate?: AppBskyFeedDefs.ThreadgateView
28
29
  [k: string]: unknown
29
30
  }
30
31
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * GENERATED CODE - DO NOT MODIFY
3
3
  */
4
- import { Headers, XRPCError } from '@atproto/xrpc'
4
+ import { HeadersMap, XRPCError } from '@atproto/xrpc'
5
5
  import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
6
  import { isObj, hasProp } from '../../../../util'
7
7
  import { lexicons } from '../../../../lexicons'
@@ -28,17 +28,16 @@ export interface OutputSchema {
28
28
  }
29
29
 
30
30
  export interface CallOptions {
31
- headers?: Headers
31
+ signal?: AbortSignal
32
+ headers?: HeadersMap
32
33
  }
33
34
 
34
35
  export interface Response {
35
36
  success: boolean
36
- headers: Headers
37
+ headers: HeadersMap
37
38
  data: OutputSchema
38
39
  }
39
40
 
40
41
  export function toKnownErr(e: any) {
41
- if (e instanceof XRPCError) {
42
- }
43
42
  return e
44
43
  }
package/src/index.ts CHANGED
@@ -25,10 +25,13 @@ export { LABELS, DEFAULT_LABEL_SETTINGS } from './moderation/const/labels'
25
25
  export { Agent } from './agent'
26
26
 
27
27
  export { AtpAgent, type AtpAgentOptions } from './atp-agent'
28
+ export { CredentialSession } from './atp-agent'
28
29
  export { BskyAgent } from './bsky-agent'
29
30
 
30
- /** @deprecated */
31
- export { AtpAgent as default } from './atp-agent'
31
+ export {
32
+ /** @deprecated */
33
+ AtpAgent as default,
34
+ } from './atp-agent'
32
35
 
33
36
  // Expose a copy to prevent alteration of the internal Lexicon instance used by
34
37
  // the AtpBaseClient class.
@@ -0,0 +1,5 @@
1
+ import { FetchHandlerObject } from '@atproto/xrpc'
2
+
3
+ export interface SessionManager extends FetchHandlerObject {
4
+ readonly did?: string
5
+ }