@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.
- package/README.md +34 -0
- package/babel.config.js +3 -0
- package/build.js +22 -0
- package/dist/index.js +8724 -0
- package/dist/index.js.map +7 -0
- package/dist/src/client/index.d.ts +462 -0
- package/dist/src/client/lexicons.d.ts +2910 -0
- package/dist/src/client/schemas.d.ts +17 -0
- package/dist/src/client/types/app/bsky/actor/createScene.d.ts +32 -0
- package/dist/src/client/types/app/bsky/actor/getProfile.d.ts +36 -0
- package/dist/src/client/types/app/bsky/actor/getSuggestions.d.ts +36 -0
- package/dist/src/client/types/app/bsky/actor/profile.d.ts +15 -0
- package/dist/src/client/types/app/bsky/actor/ref.d.ts +14 -0
- package/dist/src/client/types/app/bsky/actor/search.d.ts +32 -0
- package/dist/src/client/types/app/bsky/actor/searchTypeahead.d.ts +28 -0
- package/dist/src/client/types/app/bsky/actor/updateProfile.d.ts +48 -0
- package/dist/src/client/types/app/bsky/embed/external.d.ts +26 -0
- package/dist/src/client/types/app/bsky/embed/images.d.ts +23 -0
- package/dist/src/client/types/app/bsky/feed/debug.d.ts +57 -0
- package/dist/src/client/types/app/bsky/feed/embed.d.ts +36 -0
- package/dist/src/client/types/app/bsky/feed/feedViewPost.d.ts +26 -0
- package/dist/src/client/types/app/bsky/feed/getAuthorFeed.d.ts +22 -0
- package/dist/src/client/types/app/bsky/feed/getLikedBy.d.ts +29 -0
- package/dist/src/client/types/app/bsky/feed/getPostThread.d.ts +43 -0
- package/dist/src/client/types/app/bsky/feed/getRepostedBy.d.ts +35 -0
- package/dist/src/client/types/app/bsky/feed/getTimeline.d.ts +22 -0
- package/dist/src/client/types/app/bsky/feed/getVotes.d.ts +33 -0
- package/dist/src/client/types/app/bsky/feed/like.d.ts +10 -0
- package/dist/src/client/types/app/bsky/feed/mediaEmbed.d.ts +18 -0
- package/dist/src/client/types/app/bsky/feed/post.d.ts +54 -0
- package/dist/src/client/types/app/bsky/feed/repost.d.ts +6 -0
- package/dist/src/client/types/app/bsky/feed/setVote.d.ts +25 -0
- package/dist/src/client/types/app/bsky/feed/trend.d.ts +6 -0
- package/dist/src/client/types/app/bsky/feed/vote.d.ts +7 -0
- package/dist/src/client/types/app/bsky/graph/assertCreator.d.ts +1 -0
- package/dist/src/client/types/app/bsky/graph/assertMember.d.ts +1 -0
- package/dist/src/client/types/app/bsky/graph/assertion.d.ts +7 -0
- package/dist/src/client/types/app/bsky/graph/confirmation.d.ts +8 -0
- package/dist/src/client/types/app/bsky/graph/follow.d.ts +6 -0
- package/dist/src/client/types/app/bsky/graph/getAssertions.d.ts +43 -0
- package/dist/src/client/types/app/bsky/graph/getFollowers.d.ts +34 -0
- package/dist/src/client/types/app/bsky/graph/getFollows.d.ts +33 -0
- package/dist/src/client/types/app/bsky/graph/getMembers.d.ts +33 -0
- package/dist/src/client/types/app/bsky/graph/getMemberships.d.ts +33 -0
- package/dist/src/client/types/app/bsky/graph/invite.d.ts +10 -0
- package/dist/src/client/types/app/bsky/graph/inviteAccept.d.ts +14 -0
- package/dist/src/client/types/app/bsky/notification/getCount.d.ts +17 -0
- package/dist/src/client/types/app/bsky/notification/list.d.ts +32 -0
- package/dist/src/client/types/app/bsky/notification/updateSeen.d.ts +17 -0
- package/dist/src/client/types/app/bsky/system/actorScene.d.ts +1 -0
- package/dist/src/client/types/app/bsky/system/actorUser.d.ts +1 -0
- package/dist/src/client/types/app/bsky/system/declRef.d.ts +5 -0
- package/dist/src/client/types/app/bsky/system/declaration.d.ts +4 -0
- package/dist/src/client/types/com/atproto/account/create.d.ts +41 -0
- package/dist/src/client/types/com/atproto/account/createInviteCode.d.ts +22 -0
- package/dist/src/client/types/com/atproto/account/delete.d.ts +13 -0
- package/dist/src/client/types/com/atproto/account/get.d.ts +12 -0
- package/dist/src/client/types/com/atproto/account/requestPasswordReset.d.ts +17 -0
- package/dist/src/client/types/com/atproto/account/resetPassword.d.ts +24 -0
- package/dist/src/client/types/com/atproto/blob/upload.d.ts +19 -0
- package/dist/src/client/types/com/atproto/data/uploadFile.d.ts +22 -0
- package/dist/src/client/types/com/atproto/handle/resolve.d.ts +18 -0
- package/dist/src/client/types/com/atproto/repo/batchWrite.d.ts +39 -0
- package/dist/src/client/types/com/atproto/repo/createRecord.d.ts +26 -0
- package/dist/src/client/types/com/atproto/repo/deleteRecord.d.ts +19 -0
- package/dist/src/client/types/com/atproto/repo/describe.d.ts +22 -0
- package/dist/src/client/types/com/atproto/repo/getRecord.d.ts +23 -0
- package/dist/src/client/types/com/atproto/repo/listRecords.d.ts +30 -0
- package/dist/src/client/types/com/atproto/repo/putRecord.d.ts +27 -0
- package/dist/src/client/types/com/atproto/repo/strongRef.d.ts +5 -0
- package/dist/src/client/types/com/atproto/server/getAccountsConfig.d.ts +24 -0
- package/dist/src/client/types/com/atproto/session/create.d.ts +26 -0
- package/dist/src/client/types/com/atproto/session/delete.d.ts +13 -0
- package/dist/src/client/types/com/atproto/session/get.d.ts +18 -0
- package/dist/src/client/types/com/atproto/session/refresh.d.ts +21 -0
- package/dist/src/client/types/com/atproto/sync/getRepo.d.ts +15 -0
- package/dist/src/client/types/com/atproto/sync/getRoot.d.ts +18 -0
- package/dist/src/client/types/com/atproto/sync/updateRepo.d.ts +15 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/session.d.ts +42 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/jest.config.js +6 -0
- package/package.json +21 -0
- package/src/client/index.ts +1393 -0
- package/src/client/lexicons.ts +3041 -0
- package/src/client/types/app/bsky/actor/createScene.ts +52 -0
- package/src/client/types/app/bsky/actor/getProfile.ts +50 -0
- package/src/client/types/app/bsky/actor/getSuggestions.ts +51 -0
- package/src/client/types/app/bsky/actor/profile.ts +10 -0
- package/src/client/types/app/bsky/actor/ref.ts +19 -0
- package/src/client/types/app/bsky/actor/search.ts +46 -0
- package/src/client/types/app/bsky/actor/searchTypeahead.ts +42 -0
- package/src/client/types/app/bsky/actor/updateProfile.ts +69 -0
- package/src/client/types/app/bsky/embed/external.ts +28 -0
- package/src/client/types/app/bsky/embed/images.ts +25 -0
- package/src/client/types/app/bsky/feed/feedViewPost.ts +30 -0
- package/src/client/types/app/bsky/feed/getAuthorFeed.ts +35 -0
- package/src/client/types/app/bsky/feed/getPostThread.ts +63 -0
- package/src/client/types/app/bsky/feed/getRepostedBy.ts +49 -0
- package/src/client/types/app/bsky/feed/getTimeline.ts +35 -0
- package/src/client/types/app/bsky/feed/getVotes.ts +47 -0
- package/src/client/types/app/bsky/feed/post.ts +64 -0
- package/src/client/types/app/bsky/feed/repost.ts +10 -0
- package/src/client/types/app/bsky/feed/setVote.ts +37 -0
- package/src/client/types/app/bsky/feed/trend.ts +10 -0
- package/src/client/types/app/bsky/feed/vote.ts +11 -0
- package/src/client/types/app/bsky/graph/assertCreator.ts +5 -0
- package/src/client/types/app/bsky/graph/assertMember.ts +5 -0
- package/src/client/types/app/bsky/graph/assertion.ts +11 -0
- package/src/client/types/app/bsky/graph/confirmation.ts +12 -0
- package/src/client/types/app/bsky/graph/follow.ts +10 -0
- package/src/client/types/app/bsky/graph/getAssertions.ts +58 -0
- package/src/client/types/app/bsky/graph/getFollowers.ts +48 -0
- package/src/client/types/app/bsky/graph/getFollows.ts +47 -0
- package/src/client/types/app/bsky/graph/getMembers.ts +47 -0
- package/src/client/types/app/bsky/graph/getMemberships.ts +47 -0
- package/src/client/types/app/bsky/notification/getCount.ts +29 -0
- package/src/client/types/app/bsky/notification/list.ts +55 -0
- package/src/client/types/app/bsky/notification/updateSeen.ts +28 -0
- package/src/client/types/app/bsky/system/actorScene.ts +5 -0
- package/src/client/types/app/bsky/system/actorUser.ts +5 -0
- package/src/client/types/app/bsky/system/declRef.ts +12 -0
- package/src/client/types/app/bsky/system/declaration.ts +10 -0
- package/src/client/types/com/atproto/account/create.ts +69 -0
- package/src/client/types/com/atproto/account/createInviteCode.ts +34 -0
- package/src/client/types/com/atproto/account/delete.ts +24 -0
- package/src/client/types/com/atproto/account/get.ts +23 -0
- package/src/client/types/com/atproto/account/requestPasswordReset.ts +28 -0
- package/src/client/types/com/atproto/account/resetPassword.ts +43 -0
- package/src/client/types/com/atproto/blob/upload.ts +31 -0
- package/src/client/types/com/atproto/handle/resolve.ts +32 -0
- package/src/client/types/com/atproto/repo/batchWrite.ts +55 -0
- package/src/client/types/com/atproto/repo/createRecord.ts +42 -0
- package/src/client/types/com/atproto/repo/deleteRecord.ts +33 -0
- package/src/client/types/com/atproto/repo/describe.ts +36 -0
- package/src/client/types/com/atproto/repo/getRecord.ts +40 -0
- package/src/client/types/com/atproto/repo/listRecords.ts +50 -0
- package/src/client/types/com/atproto/repo/putRecord.ts +44 -0
- package/src/client/types/com/atproto/repo/strongRef.ts +8 -0
- package/src/client/types/com/atproto/server/getAccountsConfig.ts +37 -0
- package/src/client/types/com/atproto/session/create.ts +38 -0
- package/src/client/types/com/atproto/session/delete.ts +24 -0
- package/src/client/types/com/atproto/session/get.ts +30 -0
- package/src/client/types/com/atproto/session/refresh.ts +33 -0
- package/src/client/types/com/atproto/sync/getRepo.ts +29 -0
- package/src/client/types/com/atproto/sync/getRoot.ts +32 -0
- package/src/client/types/com/atproto/sync/updateRepo.ts +28 -0
- package/src/index.ts +4 -0
- package/src/session.ts +194 -0
- package/tests/errors.test.ts +39 -0
- package/tests/session.test.ts +239 -0
- package/tsconfig.build.json +4 -0
- 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
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
|
+
})
|
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
|
+
}
|