@atproto/api 0.0.1

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 (153) hide show
  1. package/README.md +34 -0
  2. package/babel.config.js +3 -0
  3. package/build.js +22 -0
  4. package/dist/index.js +8724 -0
  5. package/dist/index.js.map +7 -0
  6. package/dist/src/client/index.d.ts +462 -0
  7. package/dist/src/client/lexicons.d.ts +2910 -0
  8. package/dist/src/client/schemas.d.ts +17 -0
  9. package/dist/src/client/types/app/bsky/actor/createScene.d.ts +32 -0
  10. package/dist/src/client/types/app/bsky/actor/getProfile.d.ts +36 -0
  11. package/dist/src/client/types/app/bsky/actor/getSuggestions.d.ts +36 -0
  12. package/dist/src/client/types/app/bsky/actor/profile.d.ts +15 -0
  13. package/dist/src/client/types/app/bsky/actor/ref.d.ts +14 -0
  14. package/dist/src/client/types/app/bsky/actor/search.d.ts +32 -0
  15. package/dist/src/client/types/app/bsky/actor/searchTypeahead.d.ts +28 -0
  16. package/dist/src/client/types/app/bsky/actor/updateProfile.d.ts +48 -0
  17. package/dist/src/client/types/app/bsky/embed/external.d.ts +26 -0
  18. package/dist/src/client/types/app/bsky/embed/images.d.ts +23 -0
  19. package/dist/src/client/types/app/bsky/feed/debug.d.ts +57 -0
  20. package/dist/src/client/types/app/bsky/feed/embed.d.ts +36 -0
  21. package/dist/src/client/types/app/bsky/feed/feedViewPost.d.ts +26 -0
  22. package/dist/src/client/types/app/bsky/feed/getAuthorFeed.d.ts +22 -0
  23. package/dist/src/client/types/app/bsky/feed/getLikedBy.d.ts +29 -0
  24. package/dist/src/client/types/app/bsky/feed/getPostThread.d.ts +43 -0
  25. package/dist/src/client/types/app/bsky/feed/getRepostedBy.d.ts +35 -0
  26. package/dist/src/client/types/app/bsky/feed/getTimeline.d.ts +22 -0
  27. package/dist/src/client/types/app/bsky/feed/getVotes.d.ts +33 -0
  28. package/dist/src/client/types/app/bsky/feed/like.d.ts +10 -0
  29. package/dist/src/client/types/app/bsky/feed/mediaEmbed.d.ts +18 -0
  30. package/dist/src/client/types/app/bsky/feed/post.d.ts +54 -0
  31. package/dist/src/client/types/app/bsky/feed/repost.d.ts +6 -0
  32. package/dist/src/client/types/app/bsky/feed/setVote.d.ts +25 -0
  33. package/dist/src/client/types/app/bsky/feed/trend.d.ts +6 -0
  34. package/dist/src/client/types/app/bsky/feed/vote.d.ts +7 -0
  35. package/dist/src/client/types/app/bsky/graph/assertCreator.d.ts +1 -0
  36. package/dist/src/client/types/app/bsky/graph/assertMember.d.ts +1 -0
  37. package/dist/src/client/types/app/bsky/graph/assertion.d.ts +7 -0
  38. package/dist/src/client/types/app/bsky/graph/confirmation.d.ts +8 -0
  39. package/dist/src/client/types/app/bsky/graph/follow.d.ts +6 -0
  40. package/dist/src/client/types/app/bsky/graph/getAssertions.d.ts +43 -0
  41. package/dist/src/client/types/app/bsky/graph/getFollowers.d.ts +34 -0
  42. package/dist/src/client/types/app/bsky/graph/getFollows.d.ts +33 -0
  43. package/dist/src/client/types/app/bsky/graph/getMembers.d.ts +33 -0
  44. package/dist/src/client/types/app/bsky/graph/getMemberships.d.ts +33 -0
  45. package/dist/src/client/types/app/bsky/graph/invite.d.ts +10 -0
  46. package/dist/src/client/types/app/bsky/graph/inviteAccept.d.ts +14 -0
  47. package/dist/src/client/types/app/bsky/notification/getCount.d.ts +17 -0
  48. package/dist/src/client/types/app/bsky/notification/list.d.ts +32 -0
  49. package/dist/src/client/types/app/bsky/notification/updateSeen.d.ts +17 -0
  50. package/dist/src/client/types/app/bsky/system/actorScene.d.ts +1 -0
  51. package/dist/src/client/types/app/bsky/system/actorUser.d.ts +1 -0
  52. package/dist/src/client/types/app/bsky/system/declRef.d.ts +5 -0
  53. package/dist/src/client/types/app/bsky/system/declaration.d.ts +4 -0
  54. package/dist/src/client/types/com/atproto/account/create.d.ts +41 -0
  55. package/dist/src/client/types/com/atproto/account/createInviteCode.d.ts +22 -0
  56. package/dist/src/client/types/com/atproto/account/delete.d.ts +13 -0
  57. package/dist/src/client/types/com/atproto/account/get.d.ts +12 -0
  58. package/dist/src/client/types/com/atproto/account/requestPasswordReset.d.ts +17 -0
  59. package/dist/src/client/types/com/atproto/account/resetPassword.d.ts +24 -0
  60. package/dist/src/client/types/com/atproto/blob/upload.d.ts +19 -0
  61. package/dist/src/client/types/com/atproto/data/uploadFile.d.ts +22 -0
  62. package/dist/src/client/types/com/atproto/handle/resolve.d.ts +18 -0
  63. package/dist/src/client/types/com/atproto/repo/batchWrite.d.ts +39 -0
  64. package/dist/src/client/types/com/atproto/repo/createRecord.d.ts +26 -0
  65. package/dist/src/client/types/com/atproto/repo/deleteRecord.d.ts +19 -0
  66. package/dist/src/client/types/com/atproto/repo/describe.d.ts +22 -0
  67. package/dist/src/client/types/com/atproto/repo/getRecord.d.ts +23 -0
  68. package/dist/src/client/types/com/atproto/repo/listRecords.d.ts +30 -0
  69. package/dist/src/client/types/com/atproto/repo/putRecord.d.ts +27 -0
  70. package/dist/src/client/types/com/atproto/repo/strongRef.d.ts +5 -0
  71. package/dist/src/client/types/com/atproto/server/getAccountsConfig.d.ts +24 -0
  72. package/dist/src/client/types/com/atproto/session/create.d.ts +26 -0
  73. package/dist/src/client/types/com/atproto/session/delete.d.ts +13 -0
  74. package/dist/src/client/types/com/atproto/session/get.d.ts +18 -0
  75. package/dist/src/client/types/com/atproto/session/refresh.d.ts +21 -0
  76. package/dist/src/client/types/com/atproto/sync/getRepo.d.ts +15 -0
  77. package/dist/src/client/types/com/atproto/sync/getRoot.d.ts +18 -0
  78. package/dist/src/client/types/com/atproto/sync/updateRepo.d.ts +15 -0
  79. package/dist/src/index.d.ts +4 -0
  80. package/dist/src/session.d.ts +42 -0
  81. package/dist/tsconfig.build.tsbuildinfo +1 -0
  82. package/jest.config.js +6 -0
  83. package/package.json +21 -0
  84. package/src/client/index.ts +1393 -0
  85. package/src/client/lexicons.ts +3041 -0
  86. package/src/client/types/app/bsky/actor/createScene.ts +52 -0
  87. package/src/client/types/app/bsky/actor/getProfile.ts +50 -0
  88. package/src/client/types/app/bsky/actor/getSuggestions.ts +51 -0
  89. package/src/client/types/app/bsky/actor/profile.ts +10 -0
  90. package/src/client/types/app/bsky/actor/ref.ts +19 -0
  91. package/src/client/types/app/bsky/actor/search.ts +46 -0
  92. package/src/client/types/app/bsky/actor/searchTypeahead.ts +42 -0
  93. package/src/client/types/app/bsky/actor/updateProfile.ts +69 -0
  94. package/src/client/types/app/bsky/embed/external.ts +28 -0
  95. package/src/client/types/app/bsky/embed/images.ts +25 -0
  96. package/src/client/types/app/bsky/feed/feedViewPost.ts +30 -0
  97. package/src/client/types/app/bsky/feed/getAuthorFeed.ts +35 -0
  98. package/src/client/types/app/bsky/feed/getPostThread.ts +63 -0
  99. package/src/client/types/app/bsky/feed/getRepostedBy.ts +49 -0
  100. package/src/client/types/app/bsky/feed/getTimeline.ts +35 -0
  101. package/src/client/types/app/bsky/feed/getVotes.ts +47 -0
  102. package/src/client/types/app/bsky/feed/post.ts +64 -0
  103. package/src/client/types/app/bsky/feed/repost.ts +10 -0
  104. package/src/client/types/app/bsky/feed/setVote.ts +37 -0
  105. package/src/client/types/app/bsky/feed/trend.ts +10 -0
  106. package/src/client/types/app/bsky/feed/vote.ts +11 -0
  107. package/src/client/types/app/bsky/graph/assertCreator.ts +5 -0
  108. package/src/client/types/app/bsky/graph/assertMember.ts +5 -0
  109. package/src/client/types/app/bsky/graph/assertion.ts +11 -0
  110. package/src/client/types/app/bsky/graph/confirmation.ts +12 -0
  111. package/src/client/types/app/bsky/graph/follow.ts +10 -0
  112. package/src/client/types/app/bsky/graph/getAssertions.ts +58 -0
  113. package/src/client/types/app/bsky/graph/getFollowers.ts +48 -0
  114. package/src/client/types/app/bsky/graph/getFollows.ts +47 -0
  115. package/src/client/types/app/bsky/graph/getMembers.ts +47 -0
  116. package/src/client/types/app/bsky/graph/getMemberships.ts +47 -0
  117. package/src/client/types/app/bsky/notification/getCount.ts +29 -0
  118. package/src/client/types/app/bsky/notification/list.ts +55 -0
  119. package/src/client/types/app/bsky/notification/updateSeen.ts +28 -0
  120. package/src/client/types/app/bsky/system/actorScene.ts +5 -0
  121. package/src/client/types/app/bsky/system/actorUser.ts +5 -0
  122. package/src/client/types/app/bsky/system/declRef.ts +12 -0
  123. package/src/client/types/app/bsky/system/declaration.ts +10 -0
  124. package/src/client/types/com/atproto/account/create.ts +69 -0
  125. package/src/client/types/com/atproto/account/createInviteCode.ts +34 -0
  126. package/src/client/types/com/atproto/account/delete.ts +24 -0
  127. package/src/client/types/com/atproto/account/get.ts +23 -0
  128. package/src/client/types/com/atproto/account/requestPasswordReset.ts +28 -0
  129. package/src/client/types/com/atproto/account/resetPassword.ts +43 -0
  130. package/src/client/types/com/atproto/blob/upload.ts +31 -0
  131. package/src/client/types/com/atproto/handle/resolve.ts +32 -0
  132. package/src/client/types/com/atproto/repo/batchWrite.ts +55 -0
  133. package/src/client/types/com/atproto/repo/createRecord.ts +42 -0
  134. package/src/client/types/com/atproto/repo/deleteRecord.ts +33 -0
  135. package/src/client/types/com/atproto/repo/describe.ts +36 -0
  136. package/src/client/types/com/atproto/repo/getRecord.ts +40 -0
  137. package/src/client/types/com/atproto/repo/listRecords.ts +50 -0
  138. package/src/client/types/com/atproto/repo/putRecord.ts +44 -0
  139. package/src/client/types/com/atproto/repo/strongRef.ts +8 -0
  140. package/src/client/types/com/atproto/server/getAccountsConfig.ts +37 -0
  141. package/src/client/types/com/atproto/session/create.ts +38 -0
  142. package/src/client/types/com/atproto/session/delete.ts +24 -0
  143. package/src/client/types/com/atproto/session/get.ts +30 -0
  144. package/src/client/types/com/atproto/session/refresh.ts +33 -0
  145. package/src/client/types/com/atproto/sync/getRepo.ts +29 -0
  146. package/src/client/types/com/atproto/sync/getRoot.ts +32 -0
  147. package/src/client/types/com/atproto/sync/updateRepo.ts +28 -0
  148. package/src/index.ts +4 -0
  149. package/src/session.ts +194 -0
  150. package/tests/errors.test.ts +39 -0
  151. package/tests/session.test.ts +239 -0
  152. package/tsconfig.build.json +4 -0
  153. package/tsconfig.json +12 -0
@@ -0,0 +1,32 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import { Headers, XRPCError } from '@atproto/xrpc'
5
+
6
+ export interface QueryParams {
7
+ /** The DID of the repo. */
8
+ did: string
9
+ }
10
+
11
+ export type InputSchema = undefined
12
+
13
+ export interface OutputSchema {
14
+ root: string
15
+ [k: string]: unknown
16
+ }
17
+
18
+ export interface CallOptions {
19
+ headers?: Headers
20
+ }
21
+
22
+ export interface Response {
23
+ success: boolean
24
+ headers: Headers
25
+ data: OutputSchema
26
+ }
27
+
28
+ export function toKnownErr(e: any) {
29
+ if (e instanceof XRPCError) {
30
+ }
31
+ return e
32
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import { Headers, XRPCError } from '@atproto/xrpc'
5
+
6
+ export interface QueryParams {
7
+ /** The DID of the repo. */
8
+ did: string
9
+ }
10
+
11
+ export type InputSchema = string | Uint8Array
12
+
13
+ export interface CallOptions {
14
+ headers?: Headers
15
+ qp?: QueryParams
16
+ encoding: 'application/cbor'
17
+ }
18
+
19
+ export interface Response {
20
+ success: boolean
21
+ headers: Headers
22
+ }
23
+
24
+ export function toKnownErr(e: any) {
25
+ if (e instanceof XRPCError) {
26
+ }
27
+ return e
28
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './client'
2
+ export { default } from './client'
3
+ export * from './session'
4
+ export { default as sessionClient } from './session'
package/src/session.ts ADDED
@@ -0,0 +1,194 @@
1
+ import {
2
+ CallOptions,
3
+ Client as XrpcClient,
4
+ ServiceClient as XrpcServiceClient,
5
+ QueryParams,
6
+ ResponseType,
7
+ XRPCError,
8
+ XRPCResponse,
9
+ } from '@atproto/xrpc'
10
+ import EventEmitter from 'events'
11
+ import TypedEmitter from 'typed-emitter'
12
+ import { Client, ServiceClient } from './client'
13
+ import * as CreateSession from './client/types/com/atproto/session/create'
14
+ import * as RefreshSession from './client/types/com/atproto/session/refresh'
15
+ import * as CreateAccount from './client/types/com/atproto/session/create'
16
+
17
+ const CREATE_SESSION = 'com.atproto.session.create'
18
+ const REFRESH_SESSION = 'com.atproto.session.refresh'
19
+ const DELETE_SESSION = 'com.atproto.session.delete'
20
+ const CREATE_ACCOUNT = 'com.atproto.account.create'
21
+
22
+ export class SessionClient extends Client {
23
+ service(serviceUri: string | URL): SessionServiceClient {
24
+ const xrpcService = new SessionXrpcServiceClient(this.xrpc, serviceUri)
25
+ return new SessionServiceClient(this, xrpcService)
26
+ }
27
+ }
28
+
29
+ const defaultInst = new SessionClient()
30
+ export default defaultInst
31
+
32
+ export class SessionServiceClient extends ServiceClient {
33
+ xrpc: SessionXrpcServiceClient
34
+ sessionManager: SessionManager
35
+ constructor(baseClient: Client, xrpcService: SessionXrpcServiceClient) {
36
+ super(baseClient, xrpcService)
37
+ this.sessionManager = this.xrpc.sessionManager
38
+ }
39
+ }
40
+
41
+ export class SessionXrpcServiceClient extends XrpcServiceClient {
42
+ sessionManager = new SessionManager()
43
+ refreshing?: Promise<XRPCResponse>
44
+
45
+ constructor(baseClient: XrpcClient, serviceUri: string | URL) {
46
+ super(baseClient, serviceUri)
47
+ this.sessionManager.on('session', () => {
48
+ // Maintain access token headers when session changes
49
+ const accessHeaders = this.sessionManager.accessHeaders()
50
+ if (accessHeaders) {
51
+ this.setHeader('authorization', accessHeaders.authorization)
52
+ } else {
53
+ this.unsetHeader('authorization')
54
+ }
55
+ })
56
+ }
57
+
58
+ async call(
59
+ methodNsid: string,
60
+ params?: QueryParams,
61
+ data?: unknown,
62
+ opts?: CallOptions,
63
+ ) {
64
+ const original = (overrideOpts?: CallOptions) =>
65
+ super.call(methodNsid, params, data, overrideOpts ?? opts)
66
+
67
+ // If someone is setting credentials manually, pass through as an escape hatch
68
+ if (opts?.headers?.authorization) {
69
+ return await original()
70
+ }
71
+
72
+ // Manage concurrent refreshes on session refresh
73
+ if (methodNsid === REFRESH_SESSION) {
74
+ return await this.refresh(opts)
75
+ }
76
+
77
+ // Complete any pending session refresh and then continue onto the original request with fresh credentials
78
+ await this.refreshing
79
+
80
+ // Setup session on session or account creation
81
+ if (methodNsid === CREATE_SESSION || methodNsid === CREATE_ACCOUNT) {
82
+ const result = await original()
83
+ const { accessJwt, refreshJwt } =
84
+ result.data as CreateSession.OutputSchema & CreateAccount.OutputSchema
85
+ this.sessionManager.set({ accessJwt, refreshJwt })
86
+ return result
87
+ }
88
+
89
+ // Clear session on session deletion
90
+ if (methodNsid === DELETE_SESSION) {
91
+ const result = await original({
92
+ ...opts,
93
+ headers: {
94
+ ...opts?.headers,
95
+ ...this.sessionManager.refreshHeaders(),
96
+ },
97
+ })
98
+ this.sessionManager.unset()
99
+ return result
100
+ }
101
+
102
+ // For all other requests, if failed due to an expired token, refresh and retry with fresh credentials
103
+ try {
104
+ return await original()
105
+ } catch (err) {
106
+ if (
107
+ err instanceof XRPCError &&
108
+ err.status === ResponseType.InvalidRequest &&
109
+ err.error === 'ExpiredToken' &&
110
+ this.sessionManager.active()
111
+ ) {
112
+ await this.refresh(opts)
113
+ return await original()
114
+ }
115
+ throw err
116
+ }
117
+ }
118
+
119
+ // Ensures a single refresh request at a time, deduping concurrent requests.
120
+ async refresh(opts?: CallOptions) {
121
+ this.refreshing ??= this._refresh(opts)
122
+ try {
123
+ return await this.refreshing
124
+ } finally {
125
+ this.refreshing = undefined
126
+ }
127
+ }
128
+
129
+ private async _refresh(opts?: CallOptions) {
130
+ try {
131
+ const result = await super.call(REFRESH_SESSION, undefined, undefined, {
132
+ ...opts,
133
+ headers: {
134
+ ...opts?.headers,
135
+ ...this.sessionManager.refreshHeaders(),
136
+ },
137
+ })
138
+ const { accessJwt, refreshJwt } =
139
+ result.data as RefreshSession.OutputSchema
140
+ this.sessionManager.set({ accessJwt, refreshJwt })
141
+ return result
142
+ } catch (err) {
143
+ if (
144
+ err instanceof XRPCError &&
145
+ err.status === ResponseType.InvalidRequest &&
146
+ (err.error === 'ExpiredToken' || err.error === 'InvalidToken')
147
+ ) {
148
+ this.sessionManager.unset()
149
+ }
150
+ throw err
151
+ }
152
+ }
153
+ }
154
+
155
+ export class SessionManager extends (EventEmitter as new () => TypedEmitter<SessionEvents>) {
156
+ session?: Session
157
+ get() {
158
+ return this.session
159
+ }
160
+ set(session: Session) {
161
+ this.session = session
162
+ this.emit('session', session)
163
+ }
164
+ unset() {
165
+ this.session = undefined
166
+ this.emit('session', undefined)
167
+ }
168
+ active() {
169
+ return !!this.session
170
+ }
171
+ accessHeaders() {
172
+ return (
173
+ this.session && {
174
+ authorization: `Bearer ${this.session.accessJwt}`,
175
+ }
176
+ )
177
+ }
178
+ refreshHeaders() {
179
+ return (
180
+ this.session && {
181
+ authorization: `Bearer ${this.session.refreshJwt}`,
182
+ }
183
+ )
184
+ }
185
+ }
186
+
187
+ export type Session = {
188
+ refreshJwt: string
189
+ accessJwt: string
190
+ }
191
+
192
+ type SessionEvents = {
193
+ session: (session?: Session) => void
194
+ }
@@ -0,0 +1,39 @@
1
+ import {
2
+ CloseFn,
3
+ runTestServer,
4
+ TestServerInfo,
5
+ } from '@atproto/pds/tests/_util'
6
+ import {
7
+ sessionClient,
8
+ SessionServiceClient,
9
+ ComAtprotoAccountCreate,
10
+ } from '..'
11
+
12
+ describe('errors', () => {
13
+ let server: TestServerInfo
14
+ let client: SessionServiceClient
15
+ let close: CloseFn
16
+
17
+ beforeAll(async () => {
18
+ server = await runTestServer({
19
+ dbPostgresSchema: 'known_errors',
20
+ })
21
+ client = sessionClient.service(server.url)
22
+ close = server.close
23
+ })
24
+
25
+ afterAll(async () => {
26
+ await close()
27
+ })
28
+
29
+ it('constructs the correct error instance', async () => {
30
+ const res = client.com.atproto.account.create({
31
+ handle: 'admin',
32
+ email: 'admin@test.com',
33
+ password: 'password',
34
+ })
35
+ await expect(res).rejects.toThrow(
36
+ ComAtprotoAccountCreate.InvalidHandleError,
37
+ )
38
+ })
39
+ })
@@ -0,0 +1,239 @@
1
+ import {
2
+ CloseFn,
3
+ runTestServer,
4
+ TestServerInfo,
5
+ } from '@atproto/pds/tests/_util'
6
+ import { sessionClient, Session, SessionServiceClient } from '..'
7
+
8
+ describe('session', () => {
9
+ let server: TestServerInfo
10
+ let client: SessionServiceClient
11
+ let close: CloseFn
12
+
13
+ beforeAll(async () => {
14
+ server = await runTestServer({
15
+ dbPostgresSchema: 'session',
16
+ })
17
+ client = sessionClient.service(server.url)
18
+ close = server.close
19
+ })
20
+
21
+ afterAll(async () => {
22
+ await close()
23
+ })
24
+
25
+ it('manages a new session on account creation.', async () => {
26
+ const sessions: (Session | undefined)[] = []
27
+ client.sessionManager.on('session', (session) => sessions.push(session))
28
+
29
+ const { data: account } = await client.com.atproto.account.create({
30
+ handle: 'alice.test',
31
+ email: 'alice@test.com',
32
+ password: 'password',
33
+ })
34
+
35
+ expect(client.sessionManager.active()).toEqual(true)
36
+ expect(sessions).toEqual([
37
+ { accessJwt: account.accessJwt, refreshJwt: account.refreshJwt },
38
+ ])
39
+
40
+ const { data: sessionInfo } = await client.com.atproto.session.get({})
41
+ expect(sessionInfo).toEqual({
42
+ did: account.did,
43
+ handle: account.handle,
44
+ })
45
+ })
46
+
47
+ it('ends a new session on session deletion.', async () => {
48
+ const sessions: (Session | undefined)[] = []
49
+ client.sessionManager.on('session', (session) => sessions.push(session))
50
+
51
+ await client.com.atproto.session.delete()
52
+
53
+ expect(sessions).toEqual([undefined])
54
+ expect(client.sessionManager.active()).toEqual(false)
55
+
56
+ const getSessionAfterDeletion = client.com.atproto.session.get({})
57
+ await expect(getSessionAfterDeletion).rejects.toThrow(
58
+ 'Authentication Required',
59
+ )
60
+ })
61
+
62
+ it('manages a new session on session creation.', async () => {
63
+ const sessions: (Session | undefined)[] = []
64
+ client.sessionManager.on('session', (session) => sessions.push(session))
65
+
66
+ const { data: session } = await client.com.atproto.session.create({
67
+ handle: 'alice.test',
68
+ password: 'password',
69
+ })
70
+
71
+ expect(sessions).toEqual([
72
+ { accessJwt: session.accessJwt, refreshJwt: session.refreshJwt },
73
+ ])
74
+ expect(client.sessionManager.active()).toEqual(true)
75
+
76
+ const { data: sessionInfo } = await client.com.atproto.session.get({})
77
+ expect(sessionInfo).toEqual({
78
+ did: session.did,
79
+ handle: session.handle,
80
+ })
81
+ })
82
+
83
+ it('refreshes existing session.', async () => {
84
+ const sessions: (Session | undefined)[] = []
85
+ client.sessionManager.on('session', (session) => sessions.push(session))
86
+
87
+ const { data: session } = await client.com.atproto.session.create({
88
+ handle: 'alice.test',
89
+ password: 'password',
90
+ })
91
+
92
+ const { data: sessionRefresh } = await client.com.atproto.session.refresh()
93
+
94
+ expect(sessions).toEqual([
95
+ { accessJwt: session.accessJwt, refreshJwt: session.refreshJwt },
96
+ {
97
+ accessJwt: sessionRefresh.accessJwt,
98
+ refreshJwt: sessionRefresh.refreshJwt,
99
+ },
100
+ ])
101
+ expect(client.sessionManager.active()).toEqual(true)
102
+
103
+ const { data: sessionInfo } = await client.com.atproto.session.get({})
104
+ expect(sessionInfo).toEqual({
105
+ did: sessionRefresh.did,
106
+ handle: sessionRefresh.handle,
107
+ })
108
+
109
+ // Uses escape hatch: authorization set, so sessions are not managed by this call
110
+ const refreshStaleSession = client.com.atproto.session.refresh(undefined, {
111
+ headers: { authorization: `Bearer ${session.refreshJwt}` },
112
+ })
113
+ await expect(refreshStaleSession).rejects.toThrow('Token has been revoked')
114
+
115
+ expect(sessions.length).toEqual(2)
116
+ expect(client.sessionManager.active()).toEqual(true)
117
+ })
118
+
119
+ it('dedupes concurrent refreshes.', async () => {
120
+ const sessions: (Session | undefined)[] = []
121
+ client.sessionManager.on('session', (session) => sessions.push(session))
122
+
123
+ const { data: session } = await client.com.atproto.session.create({
124
+ handle: 'alice.test',
125
+ password: 'password',
126
+ })
127
+
128
+ const [{ data: sessionRefresh }] = await Promise.all(
129
+ [...Array(10)].map(() => client.com.atproto.session.refresh()),
130
+ )
131
+
132
+ expect(sessions).toEqual([
133
+ { accessJwt: session.accessJwt, refreshJwt: session.refreshJwt },
134
+ {
135
+ accessJwt: sessionRefresh.accessJwt,
136
+ refreshJwt: sessionRefresh.refreshJwt,
137
+ },
138
+ ])
139
+ expect(client.sessionManager.active()).toEqual(true)
140
+
141
+ const { data: sessionInfo } = await client.com.atproto.session.get({})
142
+ expect(sessionInfo).toEqual({
143
+ did: sessionRefresh.did,
144
+ handle: sessionRefresh.handle,
145
+ })
146
+ })
147
+
148
+ it('manually sets and unsets existing session.', async () => {
149
+ const sessions: (Session | undefined)[] = []
150
+ client.sessionManager.on('session', (session) => sessions.push(session))
151
+
152
+ const { data: session } = await client.com.atproto.session.create({
153
+ handle: 'alice.test',
154
+ password: 'password',
155
+ })
156
+ const sessionCreds = {
157
+ accessJwt: session.accessJwt,
158
+ refreshJwt: session.refreshJwt,
159
+ }
160
+ expect(client.sessionManager.active()).toEqual(true)
161
+
162
+ client.sessionManager.unset()
163
+ expect(client.sessionManager.active()).toEqual(false)
164
+
165
+ const getSessionAfterUnset = client.com.atproto.session.get({})
166
+ await expect(getSessionAfterUnset).rejects.toThrow(
167
+ 'Authentication Required',
168
+ )
169
+
170
+ client.sessionManager.set(sessionCreds)
171
+ expect(client.sessionManager.active()).toEqual(true)
172
+
173
+ const { data: sessionInfo } = await client.com.atproto.session.get({})
174
+ expect(sessionInfo).toEqual({
175
+ did: session.did,
176
+ handle: session.handle,
177
+ })
178
+
179
+ expect(sessions).toEqual([sessionCreds, undefined, sessionCreds])
180
+ expect(client.sessionManager.active()).toEqual(true)
181
+ })
182
+
183
+ it('refreshes and retries request when access token is expired.', async () => {
184
+ const sessions: (Session | undefined)[] = []
185
+ client.sessionManager.on('session', (session) => sessions.push(session))
186
+ const auth = server.ctx.auth
187
+
188
+ const { data: sessionInfo } = await client.com.atproto.session.get({})
189
+ const accessExpired = await auth.createAccessToken(sessionInfo.did, -1)
190
+
191
+ expect(sessions.length).toEqual(0)
192
+ expect(client.sessionManager.active()).toEqual(true)
193
+
194
+ client.sessionManager.set({
195
+ refreshJwt: 'not-used-since-session-is-active',
196
+ ...client.sessionManager.get(),
197
+ accessJwt: accessExpired.jwt,
198
+ })
199
+
200
+ expect(sessions.length).toEqual(1)
201
+ expect(client.sessionManager.active()).toEqual(true)
202
+
203
+ const { data: updatedSessionInfo } = await client.com.atproto.session.get(
204
+ {},
205
+ )
206
+ expect(updatedSessionInfo).toEqual(sessionInfo)
207
+
208
+ expect(sessions.length).toEqual(2) // New session was created during session.get()
209
+ expect(client.sessionManager.active()).toEqual(true)
210
+ })
211
+
212
+ it('unsets session when refresh token becomes expired.', async () => {
213
+ const sessions: (Session | undefined)[] = []
214
+ client.sessionManager.on('session', (session) => sessions.push(session))
215
+ const auth = server.ctx.auth
216
+
217
+ const { data: sessionInfo } = await client.com.atproto.session.get({})
218
+ const accessExpired = await auth.createAccessToken(sessionInfo.did, -1)
219
+ const refreshExpired = await auth.createRefreshToken(sessionInfo.did, -1)
220
+
221
+ expect(sessions.length).toEqual(0)
222
+ expect(client.sessionManager.active()).toEqual(true)
223
+
224
+ client.sessionManager.set({
225
+ accessJwt: accessExpired.jwt,
226
+ refreshJwt: refreshExpired.jwt,
227
+ })
228
+
229
+ expect(sessions.length).toEqual(1)
230
+ expect(client.sessionManager.active()).toEqual(true)
231
+
232
+ const getSessionAfterExpired = client.com.atproto.session.get({})
233
+ await expect(getSessionAfterExpired).rejects.toThrow('Token has expired')
234
+
235
+ expect(sessions.length).toEqual(2)
236
+ expect(sessions[1]).toEqual(undefined)
237
+ expect(client.sessionManager.active()).toEqual(false)
238
+ })
239
+ })
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["**/*.spec.ts", "**/*.test.ts"]
4
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist", // Your outDir,
5
+ "emitDeclarationOnly": true
6
+ },
7
+ "include": ["./src"],
8
+ "references": [
9
+ { "path": "../xrpc/tsconfig.build.json" },
10
+ { "path": "../lex-cli/tsconfig.build.json" }
11
+ ]
12
+ }