Refactor @atproto/api to the AtpAgent interface ()

* Refactor @atproto/api to use the simplified AtpAgent API

* xrpc package: Export the defaultFetchHandler to reuse in api

* api package: Use the defaultFetchHandler defined in xrpc

* Update all usages of the api for the new AtpAgent

* Clear promise on thrown codepath

* Avoid updating the atpagent session until ready to return
This commit is contained in:
Paul Frazee 2023-02-07 15:30:29 -06:00 committed by GitHub
parent 0c58a937c9
commit 2242e8a313
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 1499 additions and 1114 deletions

@ -3,12 +3,50 @@
## Usage
```typescript
import API from '@atproto/api'
import AtpAgent from '@atproto/api'
const client = API.service('http://example.com')
const agent = new AtpAgent({service: 'https://example.com'})
// provide a custom fetch implementation (shouldnt be needed in node or the browser)
import {AtpAgentFetchHeaders, AtpAgentFetchHandlerResponse} from '@atproto/api'
AtpAgent.configure({
async fetch(
httpUri: string,
httpMethod: string,
httpHeaders: AtpAgentFetchHeaders,
httpReqBody: any,
): Promise<AtpAgentFetchHandlerResponse> {
// insert definition here...
return {status: 200, /*...*/}
}
})
```
### Session management
```typescript
import AtpAgent, {AtpSessionEvent, AtpSessionData} from '@atproto/api'
const agent = new AtpAgent({
service: 'https://example.com',
persistSession: (evt: AtpSessionEvent, sess?: AtpSessionData) {
// store the session-data for reuse
}
})
await agent.login({identifier: 'alice@mail.com', password: 'hunter2'})
await agent.resumeSession(savedSessionData)
await agent.createAccount({
email: 'alice@mail.com',
password: 'hunter2',
handle: 'alice.example.com'
})
```
### API calls
```typescript
// xrpc methods
const res1 = await client.com.atproto.repo.createRecord(
const res1 = await agent.api.com.atproto.repo.createRecord(
{
did: alice.did,
collection: 'app.bsky.feed.post',
@ -19,14 +57,14 @@ const res1 = await client.com.atproto.repo.createRecord(
}
}
)
const res2 = await client.com.atproto.repo.listRecords({did: alice.did, type: 'app.bsky.feed.post'})
const res2 = await agent.api.com.atproto.repo.listRecords({did: alice.did, type: 'app.bsky.feed.post'})
// repo record methods
const res3 = await client.app.bsky.feed.post.create({did: alice.did}, {
const res3 = await agent.api.app.bsky.feed.post.create({did: alice.did}, {
text: 'Hello, world!',
createdAt: (new Date()).toISOString()
})
const res4 = await client.app.bsky.feed.post.list({did: alice.did})
const res4 = await agent.api.app.bsky.feed.post.list({did: alice.did})
```
## License

305
packages/api/src/agent.ts Normal file

@ -0,0 +1,305 @@
import { ErrorResponseBody, errorResponseBody } from '@atproto/xrpc'
import { defaultFetchHandler } from '@atproto/xrpc'
import {
AtpBaseClient,
AtpServiceClient,
ComAtprotoAccountCreate,
ComAtprotoSessionCreate,
ComAtprotoSessionGet,
ComAtprotoSessionRefresh,
} from './client'
import {
AtpSessionData,
AtpAgentCreateAccountOpts,
AtpAgentLoginOpts,
AptAgentFetchHandler,
AtpAgentFetchHandlerResponse,
AtpAgentGlobalOpts,
AtpPersistSessionHandler,
AtpAgentOpts,
} from './types'
const REFRESH_SESSION = 'com.atproto.session.refresh'
/**
* An ATP "Agent"
* Manages session token lifecycles and provides convenience methods.
*/
export class AtpAgent {
service: URL
api: AtpServiceClient
session?: AtpSessionData
private _baseClient: AtpBaseClient
private _persistSession?: AtpPersistSessionHandler
private _refreshSessionPromise: Promise<void> | undefined
/**
* The `fetch` implementation; must be implemented for your platform.
*/
static fetch: AptAgentFetchHandler | undefined = defaultFetchHandler
/**
* Configures the API globally.
*/
static configure(opts: AtpAgentGlobalOpts) {
AtpAgent.fetch = opts.fetch
}
constructor(opts: AtpAgentOpts) {
this.service =
opts.service instanceof URL ? opts.service : new URL(opts.service)
this._persistSession = opts.persistSession
// create an ATP client instance for this agent
this._baseClient = new AtpBaseClient()
this._baseClient.xrpc.fetch = this._fetch.bind(this) // patch its fetch implementation
this.api = this._baseClient.service(opts.service)
}
/**
* Is there any active session?
*/
get hasSession() {
return !!this.session
}
/**
* Sets the "Persist Session" method which can be used to store access tokens
* as they change.
*/
setPersistSessionHandler(handler?: AtpPersistSessionHandler) {
this._persistSession = handler
}
/**
* Create a new account and hydrate its session in this agent.
*/
async createAccount(
opts: AtpAgentCreateAccountOpts,
): Promise<ComAtprotoAccountCreate.Response> {
try {
const res = await this.api.com.atproto.account.create({
handle: opts.handle,
password: opts.password,
email: opts.email,
inviteCode: opts.inviteCode,
})
this.session = {
accessJwt: res.data.accessJwt,
refreshJwt: res.data.refreshJwt,
handle: res.data.handle,
did: res.data.did,
}
return res
} catch (e) {
this.session = undefined
throw e
} finally {
if (this.session) {
this._persistSession?.('create', this.session)
} else {
this._persistSession?.('create-failed', undefined)
}
}
}
/**
* Start a new session with this agent.
*/
async login(
opts: AtpAgentLoginOpts,
): Promise<ComAtprotoSessionCreate.Response> {
try {
const res = await this.api.com.atproto.session.create({
identifier: opts.identifier,
password: opts.password,
})
this.session = {
accessJwt: res.data.accessJwt,
refreshJwt: res.data.refreshJwt,
handle: res.data.handle,
did: res.data.did,
}
return res
} catch (e) {
this.session = undefined
throw e
} finally {
if (this.session) {
this._persistSession?.('create', this.session)
} else {
this._persistSession?.('create-failed', undefined)
}
}
}
/**
* Resume a pre-existing session with this agent.
*/
async resumeSession(
session: AtpSessionData,
): Promise<ComAtprotoSessionGet.Response> {
try {
this.session = session
const res = await this.api.com.atproto.session.get()
if (!res.success || res.data.did !== this.session.did) {
throw new Error('Invalid session')
}
return res
} catch (e) {
this.session = undefined
throw e
} finally {
if (this.session) {
this._persistSession?.('create', this.session)
} else {
this._persistSession?.('create-failed', undefined)
}
}
}
/**
* Internal helper to add authorization headers to requests.
*/
private _addAuthHeader(reqHeaders: Record<string, string>) {
if (!reqHeaders.authorization && this.session?.accessJwt) {
return {
...reqHeaders,
authorization: `Bearer ${this.session.accessJwt}`,
}
}
return reqHeaders
}
/**
* Internal fetch handler which adds access-token management
*/
private async _fetch(
reqUri: string,
reqMethod: string,
reqHeaders: Record<string, string>,
reqBody: any,
): Promise<AtpAgentFetchHandlerResponse> {
if (!AtpAgent.fetch) {
throw new Error('AtpAgent fetch() method not configured')
}
// wait for any active session-refreshes to finish
await this._refreshSessionPromise
// send the request
let res = await AtpAgent.fetch(
reqUri,
reqMethod,
this._addAuthHeader(reqHeaders),
reqBody,
)
// handle session-refreshes as needed
if (isErrorResponse(res, ['ExpiredToken']) && this.session?.refreshJwt) {
// attempt refresh
await this._refreshSession()
// resend the request with the new access token
res = await AtpAgent.fetch(
reqUri,
reqMethod,
this._addAuthHeader(reqHeaders),
reqBody,
)
}
return res
}
/**
* Internal helper to refresh sessions
* - Wraps the actual implementation in a promise-guard to ensure only
* one refresh is attempted at a time.
*/
private async _refreshSession() {
if (this._refreshSessionPromise) {
return this._refreshSessionPromise
}
this._refreshSessionPromise = this._refreshSessionInner()
try {
await this._refreshSessionPromise
} finally {
this._refreshSessionPromise = undefined
}
}
/**
* Internal helper to refresh sessions (actual behavior)
*/
private async _refreshSessionInner() {
if (!AtpAgent.fetch) {
throw new Error('AtpAgent fetch() method not configured')
}
if (!this.session?.refreshJwt) {
return
}
// send the refresh request
const url = new URL(this.service.origin)
url.pathname = `/xrpc/${REFRESH_SESSION}`
const res = await AtpAgent.fetch(
url.toString(),
'POST',
{
authorization: `Bearer ${this.session.refreshJwt}`,
},
undefined,
)
if (isErrorResponse(res, ['ExpiredToken', 'InvalidToken'])) {
// failed due to a bad refresh token
this.session = undefined
this._persistSession?.('expired', undefined)
} else if (isNewSessionObject(this._baseClient, res.body)) {
// succeeded, update the session
this.session = {
accessJwt: res.body.accessJwt,
refreshJwt: res.body.refreshJwt,
handle: res.body.handle,
did: res.body.did,
}
this._persistSession?.('update', this.session)
}
// else: other failures should be ignored - the issue will
// propagate in the _fetch() handler's second attempt to run
// the request
}
}
function isErrorObject(v: unknown): v is ErrorResponseBody {
return errorResponseBody.safeParse(v).success
}
function isErrorResponse(
res: AtpAgentFetchHandlerResponse,
errorNames: string[],
): boolean {
if (res.status !== 400) {
return false
}
if (!isErrorObject(res.body)) {
return false
}
return (
typeof res.body.error === 'string' && errorNames.includes(res.body.error)
)
}
function isNewSessionObject(
client: AtpBaseClient,
v: unknown,
): v is ComAtprotoSessionRefresh.OutputSchema {
try {
client.xrpc.lex.assertValidXrpcOutput('com.atproto.session.refresh', v)
return true
} catch {
return false
}
}

@ -189,28 +189,25 @@ export const APP_BSKY_SYSTEM = {
ActorUser: 'app.bsky.system.actorUser',
}
export class Client {
export class AtpBaseClient {
xrpc: XrpcClient = new XrpcClient()
constructor() {
this.xrpc.addLexicons(schemas)
}
service(serviceUri: string | URL): ServiceClient {
return new ServiceClient(this, this.xrpc.service(serviceUri))
service(serviceUri: string | URL): AtpServiceClient {
return new AtpServiceClient(this, this.xrpc.service(serviceUri))
}
}
const defaultInst = new Client()
export default defaultInst
export class ServiceClient {
_baseClient: Client
export class AtpServiceClient {
_baseClient: AtpBaseClient
xrpc: XrpcServiceClient
com: ComNS
app: AppNS
constructor(baseClient: Client, xrpcService: XrpcServiceClient) {
constructor(baseClient: AtpBaseClient, xrpcService: XrpcServiceClient) {
this._baseClient = baseClient
this.xrpc = xrpcService
this.com = new ComNS(this)
@ -223,17 +220,17 @@ export class ServiceClient {
}
export class ComNS {
_service: ServiceClient
_service: AtpServiceClient
atproto: AtprotoNS
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
this.atproto = new AtprotoNS(service)
}
}
export class AtprotoNS {
_service: ServiceClient
_service: AtpServiceClient
account: AccountNS
admin: AdminNS
blob: BlobNS
@ -244,7 +241,7 @@ export class AtprotoNS {
session: SessionNS
sync: SyncNS
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
this.account = new AccountNS(service)
this.admin = new AdminNS(service)
@ -259,9 +256,9 @@ export class AtprotoNS {
}
export class AccountNS {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -344,9 +341,9 @@ export class AccountNS {
}
export class AdminNS {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -462,9 +459,9 @@ export class AdminNS {
}
export class BlobNS {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -481,9 +478,9 @@ export class BlobNS {
}
export class HandleNS {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -500,9 +497,9 @@ export class HandleNS {
}
export class RepoNS {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -585,9 +582,9 @@ export class RepoNS {
}
export class ReportNS {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -604,9 +601,9 @@ export class ReportNS {
}
export class ServerNS {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -623,9 +620,9 @@ export class ServerNS {
}
export class SessionNS {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -675,9 +672,9 @@ export class SessionNS {
}
export class SyncNS {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -738,17 +735,17 @@ export class SyncNS {
}
export class AppNS {
_service: ServiceClient
_service: AtpServiceClient
bsky: BskyNS
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
this.bsky = new BskyNS(service)
}
}
export class BskyNS {
_service: ServiceClient
_service: AtpServiceClient
actor: ActorNS
embed: EmbedNS
feed: FeedNS
@ -756,7 +753,7 @@ export class BskyNS {
notification: NotificationNS
system: SystemNS
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
this.actor = new ActorNS(service)
this.embed = new EmbedNS(service)
@ -768,10 +765,10 @@ export class BskyNS {
}
export class ActorNS {
_service: ServiceClient
_service: AtpServiceClient
profile: ProfileRecord
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
this.profile = new ProfileRecord(service)
}
@ -833,9 +830,9 @@ export class ActorNS {
}
export class ProfileRecord {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -894,20 +891,20 @@ export class ProfileRecord {
}
export class EmbedNS {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
}
export class FeedNS {
_service: ServiceClient
_service: AtpServiceClient
post: PostRecord
repost: RepostRecord
vote: VoteRecord
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
this.post = new PostRecord(service)
this.repost = new RepostRecord(service)
@ -982,9 +979,9 @@ export class FeedNS {
}
export class PostRecord {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -1043,9 +1040,9 @@ export class PostRecord {
}
export class RepostRecord {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -1104,9 +1101,9 @@ export class RepostRecord {
}
export class VoteRecord {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -1165,12 +1162,12 @@ export class VoteRecord {
}
export class GraphNS {
_service: ServiceClient
_service: AtpServiceClient
assertion: AssertionRecord
confirmation: ConfirmationRecord
follow: FollowRecord
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
this.assertion = new AssertionRecord(service)
this.confirmation = new ConfirmationRecord(service)
@ -1234,9 +1231,9 @@ export class GraphNS {
}
export class AssertionRecord {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -1299,9 +1296,9 @@ export class AssertionRecord {
}
export class ConfirmationRecord {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -1364,9 +1361,9 @@ export class ConfirmationRecord {
}
export class FollowRecord {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -1425,9 +1422,9 @@ export class FollowRecord {
}
export class NotificationNS {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}
@ -1466,19 +1463,19 @@ export class NotificationNS {
}
export class SystemNS {
_service: ServiceClient
_service: AtpServiceClient
declaration: DeclarationRecord
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
this.declaration = new DeclarationRecord(service)
}
}
export class DeclarationRecord {
_service: ServiceClient
_service: AtpServiceClient
constructor(service: ServiceClient) {
constructor(service: AtpServiceClient) {
this._service = service
}

@ -1,4 +1,4 @@
export * from './types'
export * from './client'
export { default } from './client'
export * from './session'
export { default as sessionClient } from './session'
export * from './agent'
export { AtpAgent as default } from './agent'

@ -1,194 +0,0 @@
import {
CallOptions,
Client as XrpcClient,
ServiceClient as XrpcServiceClient,
QueryParams,
ResponseType,
XRPCError,
XRPCResponse,
} from '@atproto/xrpc'
import EventEmitter from 'events'
import TypedEmitter from 'typed-emitter'
import { Client, ServiceClient } from './client'
import * as CreateSession from './client/types/com/atproto/session/create'
import * as RefreshSession from './client/types/com/atproto/session/refresh'
import * as CreateAccount from './client/types/com/atproto/session/create'
const CREATE_SESSION = 'com.atproto.session.create'
const REFRESH_SESSION = 'com.atproto.session.refresh'
const DELETE_SESSION = 'com.atproto.session.delete'
const CREATE_ACCOUNT = 'com.atproto.account.create'
export class SessionClient extends Client {
service(serviceUri: string | URL): SessionServiceClient {
const xrpcService = new SessionXrpcServiceClient(this.xrpc, serviceUri)
return new SessionServiceClient(this, xrpcService)
}
}
const defaultInst = new SessionClient()
export default defaultInst
export class SessionServiceClient extends ServiceClient {
xrpc: SessionXrpcServiceClient
sessionManager: SessionManager
constructor(baseClient: Client, xrpcService: SessionXrpcServiceClient) {
super(baseClient, xrpcService)
this.sessionManager = this.xrpc.sessionManager
}
}
export class SessionXrpcServiceClient extends XrpcServiceClient {
sessionManager = new SessionManager()
refreshing?: Promise<XRPCResponse>
constructor(baseClient: XrpcClient, serviceUri: string | URL) {
super(baseClient, serviceUri)
this.sessionManager.on('session', () => {
// Maintain access token headers when session changes
const accessHeaders = this.sessionManager.accessHeaders()
if (accessHeaders) {
this.setHeader('authorization', accessHeaders.authorization)
} else {
this.unsetHeader('authorization')
}
})
}
async call(
methodNsid: string,
params?: QueryParams,
data?: unknown,
opts?: CallOptions,
) {
const original = (overrideOpts?: CallOptions) =>
super.call(methodNsid, params, data, overrideOpts ?? opts)
// If someone is setting credentials manually, pass through as an escape hatch
if (opts?.headers?.authorization) {
return await original()
}
// Manage concurrent refreshes on session refresh
if (methodNsid === REFRESH_SESSION) {
return await this.refresh(opts)
}
// Complete any pending session refresh and then continue onto the original request with fresh credentials
await this.refreshing
// Setup session on session or account creation
if (methodNsid === CREATE_SESSION || methodNsid === CREATE_ACCOUNT) {
const result = await original()
const { accessJwt, refreshJwt } =
result.data as CreateSession.OutputSchema & CreateAccount.OutputSchema
this.sessionManager.set({ accessJwt, refreshJwt })
return result
}
// Clear session on session deletion
if (methodNsid === DELETE_SESSION) {
const result = await original({
...opts,
headers: {
...opts?.headers,
...this.sessionManager.refreshHeaders(),
},
})
this.sessionManager.unset()
return result
}
// For all other requests, if failed due to an expired token, refresh and retry with fresh credentials
try {
return await original()
} catch (err) {
if (
err instanceof XRPCError &&
err.status === ResponseType.InvalidRequest &&
err.error === 'ExpiredToken' &&
this.sessionManager.active()
) {
await this.refresh(opts)
return await original()
}
throw err
}
}
// Ensures a single refresh request at a time, deduping concurrent requests.
async refresh(opts?: CallOptions) {
this.refreshing ??= this._refresh(opts)
try {
return await this.refreshing
} finally {
this.refreshing = undefined
}
}
private async _refresh(opts?: CallOptions) {
try {
const result = await super.call(REFRESH_SESSION, undefined, undefined, {
...opts,
headers: {
...opts?.headers,
...this.sessionManager.refreshHeaders(),
},
})
const { accessJwt, refreshJwt } =
result.data as RefreshSession.OutputSchema
this.sessionManager.set({ accessJwt, refreshJwt })
return result
} catch (err) {
if (
err instanceof XRPCError &&
err.status === ResponseType.InvalidRequest &&
(err.error === 'ExpiredToken' || err.error === 'InvalidToken')
) {
this.sessionManager.unset()
}
throw err
}
}
}
export class SessionManager extends (EventEmitter as new () => TypedEmitter<SessionEvents>) {
session?: Session
get() {
return this.session
}
set(session: Session) {
this.session = session
this.emit('session', session)
}
unset() {
this.session = undefined
this.emit('session', undefined)
}
active() {
return !!this.session
}
accessHeaders() {
return (
this.session && {
authorization: `Bearer ${this.session.accessJwt}`,
}
)
}
refreshHeaders() {
return (
this.session && {
authorization: `Bearer ${this.session.refreshJwt}`,
}
)
}
}
export type Session = {
refreshJwt: string
accessJwt: string
}
type SessionEvents = {
session: (session?: Session) => void
}

71
packages/api/src/types.ts Normal file

@ -0,0 +1,71 @@
/**
* Used by the PersistSessionHandler to indicate what change occurred
*/
export type AtpSessionEvent = 'create' | 'create-failed' | 'update' | 'expired'
/**
* Used by AtpAgent to store active sessions
*/
export interface AtpSessionData {
refreshJwt: string
accessJwt: string
handle: string
did: string
}
/**
* Handler signature passed to AtpAgent to store session data
*/
export type AtpPersistSessionHandler = (
evt: AtpSessionEvent,
session: AtpSessionData | undefined,
) => void | Promise<void>
/**
* AtpAgent constructor() opts
*/
export interface AtpAgentOpts {
service: string | URL
persistSession?: AtpPersistSessionHandler
}
/**
* AtpAgent createAccount() opts
*/
export interface AtpAgentCreateAccountOpts {
email: string
password: string
handle: string
inviteCode?: string
}
/**
* AtpAgent login() opts
*/
export interface AtpAgentLoginOpts {
identifier: string
password: string
}
/**
* AtpAgent global fetch handler
*/
type AtpAgentFetchHeaders = Record<string, string>
export interface AtpAgentFetchHandlerResponse {
status: number
headers: Record<string, string>
body: any
}
export type AptAgentFetchHandler = (
httpUri: string,
httpMethod: string,
httpHeaders: AtpAgentFetchHeaders,
httpReqBody: any,
) => Promise<AtpAgentFetchHandlerResponse>
/**
* AtpAgent global config opts
*/
export interface AtpAgentGlobalOpts {
fetch: AptAgentFetchHandler
}

@ -0,0 +1,26 @@
import { AtpAgentFetchHandlerResponse } from '..'
export async function fetchHandler(
httpUri: string,
httpMethod: string,
httpHeaders: Record<string, string>,
httpReqBody: unknown,
): Promise<AtpAgentFetchHandlerResponse> {
// The duplex field is now required for streaming bodies, but not yet reflected
// anywhere in docs or types. See whatwg/fetch#1438, nodejs/node#46221.
const reqInit: RequestInit & { duplex: string } = {
method: httpMethod,
headers: httpHeaders,
body: httpReqBody
? new TextEncoder().encode(JSON.stringify(httpReqBody))
: undefined,
duplex: 'half',
}
const res = await fetch(httpUri, reqInit)
const resBody = await res.arrayBuffer()
return {
status: res.status,
headers: Object.fromEntries(res.headers.entries()),
body: resBody ? JSON.parse(new TextDecoder().decode(resBody)) : undefined,
}
}

@ -0,0 +1,391 @@
import { defaultFetchHandler } from '@atproto/xrpc'
import {
CloseFn,
runTestServer,
TestServerInfo,
} from '@atproto/pds/tests/_util'
import {
AtpAgent,
AtpAgentFetchHandlerResponse,
AtpSessionEvent,
AtpSessionData,
} from '..'
describe('agent', () => {
let server: TestServerInfo
let close: CloseFn
beforeAll(async () => {
server = await runTestServer({
dbPostgresSchema: 'session',
})
close = server.close
})
afterAll(async () => {
await close()
})
it('creates a new session on account creation.', async () => {
const events: string[] = []
const sessions: (AtpSessionData | undefined)[] = []
const persistSession = (evt: AtpSessionEvent, sess?: AtpSessionData) => {
events.push(evt)
sessions.push(sess)
}
const agent = new AtpAgent({ service: server.url, persistSession })
const res = await agent.createAccount({
handle: 'user1.test',
email: 'user1@test.com',
password: 'password',
})
expect(agent.hasSession).toEqual(true)
expect(agent.session?.accessJwt).toEqual(res.data.accessJwt)
expect(agent.session?.refreshJwt).toEqual(res.data.refreshJwt)
expect(agent.session?.handle).toEqual(res.data.handle)
expect(agent.session?.did).toEqual(res.data.did)
const { data: sessionInfo } = await agent.api.com.atproto.session.get({})
expect(sessionInfo).toEqual({
did: res.data.did,
handle: res.data.handle,
})
expect(events.length).toEqual(1)
expect(events[0]).toEqual('create')
expect(sessions.length).toEqual(1)
expect(sessions[0]?.accessJwt).toEqual(agent.session?.accessJwt)
})
it('creates a new session on login.', async () => {
const events: string[] = []
const sessions: (AtpSessionData | undefined)[] = []
const persistSession = (evt: AtpSessionEvent, sess?: AtpSessionData) => {
events.push(evt)
sessions.push(sess)
}
const agent1 = new AtpAgent({ service: server.url, persistSession })
await agent1.createAccount({
handle: 'user2.test',
email: 'user2@test.com',
password: 'password',
})
const agent2 = new AtpAgent({ service: server.url, persistSession })
const res1 = await agent2.login({
identifier: 'user2.test',
password: 'password',
})
expect(agent2.hasSession).toEqual(true)
expect(agent2.session?.accessJwt).toEqual(res1.data.accessJwt)
expect(agent2.session?.refreshJwt).toEqual(res1.data.refreshJwt)
expect(agent2.session?.handle).toEqual(res1.data.handle)
expect(agent2.session?.did).toEqual(res1.data.did)
const { data: sessionInfo } = await agent2.api.com.atproto.session.get({})
expect(sessionInfo).toEqual({
did: res1.data.did,
handle: res1.data.handle,
})
expect(events.length).toEqual(2)
expect(events[0]).toEqual('create')
expect(events[1]).toEqual('create')
expect(sessions.length).toEqual(2)
expect(sessions[0]?.accessJwt).toEqual(agent1.session?.accessJwt)
expect(sessions[1]?.accessJwt).toEqual(agent2.session?.accessJwt)
})
it('resumes an existing session.', async () => {
const events: string[] = []
const sessions: (AtpSessionData | undefined)[] = []
const persistSession = (evt: AtpSessionEvent, sess?: AtpSessionData) => {
events.push(evt)
sessions.push(sess)
}
const agent1 = new AtpAgent({ service: server.url, persistSession })
await agent1.createAccount({
handle: 'user3.test',
email: 'user3@test.com',
password: 'password',
})
if (!agent1.session) {
throw new Error('No session created')
}
const agent2 = new AtpAgent({ service: server.url, persistSession })
const res1 = await agent2.resumeSession(agent1.session)
expect(agent2.hasSession).toEqual(true)
expect(agent2.session?.handle).toEqual(res1.data.handle)
expect(agent2.session?.did).toEqual(res1.data.did)
const { data: sessionInfo } = await agent2.api.com.atproto.session.get({})
expect(sessionInfo).toEqual({
did: res1.data.did,
handle: res1.data.handle,
})
expect(events.length).toEqual(2)
expect(events[0]).toEqual('create')
expect(events[1]).toEqual('create')
expect(sessions.length).toEqual(2)
expect(sessions[0]?.accessJwt).toEqual(agent1.session?.accessJwt)
expect(sessions[1]?.accessJwt).toEqual(agent2.session?.accessJwt)
})
it('refreshes existing session.', async () => {
const events: string[] = []
const sessions: (AtpSessionData | undefined)[] = []
const persistSession = (evt: AtpSessionEvent, sess?: AtpSessionData) => {
events.push(evt)
sessions.push(sess)
}
const agent = new AtpAgent({ service: server.url, persistSession })
// create an account and a session with it
await agent.createAccount({
handle: 'user4.test',
email: 'user4@test.com',
password: 'password',
})
if (!agent.session) {
throw new Error('No session created')
}
const session1 = agent.session
const origAccessJwt = session1.accessJwt
// wait 1 second so that a token refresh will issue a new access token
// (if the timestamp, which has 1 second resolution, is the same -- then the access token won't change)
await new Promise((r) => setTimeout(r, 1000))
// patch the fetch handler to fake an expired token error on the next request
const tokenExpiredFetchHandler = async function (
httpUri: string,
httpMethod: string,
httpHeaders: Record<string, string>,
httpReqBody: unknown,
): Promise<AtpAgentFetchHandlerResponse> {
if (httpHeaders.authorization === `Bearer ${origAccessJwt}`) {
return {
status: 400,
headers: {},
body: { error: 'ExpiredToken' },
}
}
return defaultFetchHandler(httpUri, httpMethod, httpHeaders, httpReqBody)
}
// put the agent through the auth flow
AtpAgent.configure({ fetch: tokenExpiredFetchHandler })
const res1 = await agent.api.app.bsky.feed.getTimeline()
AtpAgent.configure({ fetch: defaultFetchHandler })
expect(res1.success).toEqual(true)
expect(agent.hasSession).toEqual(true)
expect(agent.session?.accessJwt).not.toEqual(session1.accessJwt)
expect(agent.session?.refreshJwt).not.toEqual(session1.refreshJwt)
expect(agent.session?.handle).toEqual(session1.handle)
expect(agent.session?.did).toEqual(session1.did)
expect(events.length).toEqual(2)
expect(events[0]).toEqual('create')
expect(events[1]).toEqual('update')
expect(sessions.length).toEqual(2)
expect(sessions[0]?.accessJwt).toEqual(origAccessJwt)
expect(sessions[1]?.accessJwt).toEqual(agent.session?.accessJwt)
})
it('dedupes session refreshes.', async () => {
const events: string[] = []
const sessions: (AtpSessionData | undefined)[] = []
const persistSession = (evt: AtpSessionEvent, sess?: AtpSessionData) => {
events.push(evt)
sessions.push(sess)
}
const agent = new AtpAgent({ service: server.url, persistSession })
// create an account and a session with it
await agent.createAccount({
handle: 'user5.test',
email: 'user5@test.com',
password: 'password',
})
if (!agent.session) {
throw new Error('No session created')
}
const session1 = agent.session
const origAccessJwt = session1.accessJwt
// wait 1 second so that a token refresh will issue a new access token
// (if the timestamp, which has 1 second resolution, is the same -- then the access token won't change)
await new Promise((r) => setTimeout(r, 1000))
// patch the fetch handler to fake an expired token error on the next request
let expiredCalls = 0
let refreshCalls = 0
const tokenExpiredFetchHandler = async function (
httpUri: string,
httpMethod: string,
httpHeaders: Record<string, string>,
httpReqBody: unknown,
): Promise<AtpAgentFetchHandlerResponse> {
if (httpHeaders.authorization === `Bearer ${origAccessJwt}`) {
expiredCalls++
return {
status: 400,
headers: {},
body: { error: 'ExpiredToken' },
}
}
if (httpUri.includes('com.atproto.session.refresh')) {
refreshCalls++
}
return defaultFetchHandler(httpUri, httpMethod, httpHeaders, httpReqBody)
}
// put the agent through the auth flow
AtpAgent.configure({ fetch: tokenExpiredFetchHandler })
const [res1, res2, res3] = await Promise.all([
agent.api.app.bsky.feed.getTimeline(),
agent.api.app.bsky.feed.getTimeline(),
agent.api.app.bsky.feed.getTimeline(),
])
AtpAgent.configure({ fetch: defaultFetchHandler })
expect(expiredCalls).toEqual(3)
expect(refreshCalls).toEqual(1)
expect(res1.success).toEqual(true)
expect(res2.success).toEqual(true)
expect(res3.success).toEqual(true)
expect(agent.hasSession).toEqual(true)
expect(agent.session?.accessJwt).not.toEqual(session1.accessJwt)
expect(agent.session?.refreshJwt).not.toEqual(session1.refreshJwt)
expect(agent.session?.handle).toEqual(session1.handle)
expect(agent.session?.did).toEqual(session1.did)
expect(events.length).toEqual(2)
expect(events[0]).toEqual('create')
expect(events[1]).toEqual('update')
expect(sessions.length).toEqual(2)
expect(sessions[0]?.accessJwt).toEqual(origAccessJwt)
expect(sessions[1]?.accessJwt).toEqual(agent.session?.accessJwt)
})
it('persists an empty session on login and resumeSession failures', async () => {
const events: string[] = []
const sessions: (AtpSessionData | undefined)[] = []
const persistSession = (evt: AtpSessionEvent, sess?: AtpSessionData) => {
events.push(evt)
sessions.push(sess)
}
const agent = new AtpAgent({ service: server.url, persistSession })
try {
await agent.login({
identifier: 'baduser.test',
password: 'password',
})
} catch (_e: any) {
// ignore
}
expect(agent.hasSession).toEqual(false)
try {
await agent.resumeSession({
accessJwt: 'bad',
refreshJwt: 'bad',
did: 'bad',
handle: 'bad',
})
} catch (_e: any) {
// ignore
}
expect(agent.hasSession).toEqual(false)
expect(events.length).toEqual(2)
expect(events[0]).toEqual('create-failed')
expect(events[1]).toEqual('create-failed')
expect(sessions.length).toEqual(2)
expect(typeof sessions[0]).toEqual('undefined')
expect(typeof sessions[1]).toEqual('undefined')
})
it('does not modify or persist the session on a failed refresh', async () => {
const events: string[] = []
const sessions: (AtpSessionData | undefined)[] = []
const persistSession = (evt: AtpSessionEvent, sess?: AtpSessionData) => {
events.push(evt)
sessions.push(sess)
}
const agent = new AtpAgent({ service: server.url, persistSession })
// create an account and a session with it
await agent.createAccount({
handle: 'user6.test',
email: 'user6@test.com',
password: 'password',
})
if (!agent.session) {
throw new Error('No session created')
}
const session1 = agent.session
const origAccessJwt = session1.accessJwt
// patch the fetch handler to fake an expired token error on the next request
const tokenExpiredFetchHandler = async function (
httpUri: string,
httpMethod: string,
httpHeaders: Record<string, string>,
httpReqBody: unknown,
): Promise<AtpAgentFetchHandlerResponse> {
if (httpHeaders.authorization === `Bearer ${origAccessJwt}`) {
return {
status: 400,
headers: {},
body: { error: 'ExpiredToken' },
}
}
if (httpUri.includes('com.atproto.session.refresh')) {
return {
status: 500,
headers: {},
body: undefined,
}
}
return defaultFetchHandler(httpUri, httpMethod, httpHeaders, httpReqBody)
}
// put the agent through the auth flow
AtpAgent.configure({ fetch: tokenExpiredFetchHandler })
try {
await agent.api.app.bsky.feed.getTimeline()
throw new Error('Should have failed')
} catch (e: any) {
// the original error passes through
expect(e.status).toEqual(400)
expect(e.error).toEqual('ExpiredToken')
}
AtpAgent.configure({ fetch: defaultFetchHandler })
// still has session because it wasn't invalidated
expect(agent.hasSession).toEqual(true)
expect(events.length).toEqual(1)
expect(events[0]).toEqual('create')
expect(sessions.length).toEqual(1)
expect(sessions[0]?.accessJwt).toEqual(origAccessJwt)
})
})

@ -3,22 +3,18 @@ import {
runTestServer,
TestServerInfo,
} from '@atproto/pds/tests/_util'
import {
sessionClient,
SessionServiceClient,
ComAtprotoAccountCreate,
} from '..'
import { AtpAgent, ComAtprotoAccountCreate } from '..'
describe('errors', () => {
let server: TestServerInfo
let client: SessionServiceClient
let client: AtpAgent
let close: CloseFn
beforeAll(async () => {
server = await runTestServer({
dbPostgresSchema: 'known_errors',
})
client = sessionClient.service(server.url)
client = new AtpAgent({ service: server.url })
close = server.close
})
@ -27,7 +23,7 @@ describe('errors', () => {
})
it('constructs the correct error instance', async () => {
const res = client.com.atproto.account.create({
const res = client.api.com.atproto.account.create({
handle: 'admin',
email: 'admin@test.com',
password: 'password',

@ -1,239 +0,0 @@
import {
CloseFn,
runTestServer,
TestServerInfo,
} from '@atproto/pds/tests/_util'
import { sessionClient, Session, SessionServiceClient } from '..'
describe('session', () => {
let server: TestServerInfo
let client: SessionServiceClient
let close: CloseFn
beforeAll(async () => {
server = await runTestServer({
dbPostgresSchema: 'session',
})
client = sessionClient.service(server.url)
close = server.close
})
afterAll(async () => {
await close()
})
it('manages a new session on account creation.', async () => {
const sessions: (Session | undefined)[] = []
client.sessionManager.on('session', (session) => sessions.push(session))
const { data: account } = await client.com.atproto.account.create({
handle: 'alice.test',
email: 'alice@test.com',
password: 'password',
})
expect(client.sessionManager.active()).toEqual(true)
expect(sessions).toEqual([
{ accessJwt: account.accessJwt, refreshJwt: account.refreshJwt },
])
const { data: sessionInfo } = await client.com.atproto.session.get({})
expect(sessionInfo).toEqual({
did: account.did,
handle: account.handle,
})
})
it('ends a new session on session deletion.', async () => {
const sessions: (Session | undefined)[] = []
client.sessionManager.on('session', (session) => sessions.push(session))
await client.com.atproto.session.delete()
expect(sessions).toEqual([undefined])
expect(client.sessionManager.active()).toEqual(false)
const getSessionAfterDeletion = client.com.atproto.session.get({})
await expect(getSessionAfterDeletion).rejects.toThrow(
'Authentication Required',
)
})
it('manages a new session on session creation.', async () => {
const sessions: (Session | undefined)[] = []
client.sessionManager.on('session', (session) => sessions.push(session))
const { data: session } = await client.com.atproto.session.create({
handle: 'alice.test',
password: 'password',
})
expect(sessions).toEqual([
{ accessJwt: session.accessJwt, refreshJwt: session.refreshJwt },
])
expect(client.sessionManager.active()).toEqual(true)
const { data: sessionInfo } = await client.com.atproto.session.get({})
expect(sessionInfo).toEqual({
did: session.did,
handle: session.handle,
})
})
it('refreshes existing session.', async () => {
const sessions: (Session | undefined)[] = []
client.sessionManager.on('session', (session) => sessions.push(session))
const { data: session } = await client.com.atproto.session.create({
handle: 'alice.test',
password: 'password',
})
const { data: sessionRefresh } = await client.com.atproto.session.refresh()
expect(sessions).toEqual([
{ accessJwt: session.accessJwt, refreshJwt: session.refreshJwt },
{
accessJwt: sessionRefresh.accessJwt,
refreshJwt: sessionRefresh.refreshJwt,
},
])
expect(client.sessionManager.active()).toEqual(true)
const { data: sessionInfo } = await client.com.atproto.session.get({})
expect(sessionInfo).toEqual({
did: sessionRefresh.did,
handle: sessionRefresh.handle,
})
// Uses escape hatch: authorization set, so sessions are not managed by this call
const refreshStaleSession = client.com.atproto.session.refresh(undefined, {
headers: { authorization: `Bearer ${session.refreshJwt}` },
})
await expect(refreshStaleSession).rejects.toThrow('Token has been revoked')
expect(sessions.length).toEqual(2)
expect(client.sessionManager.active()).toEqual(true)
})
it('dedupes concurrent refreshes.', async () => {
const sessions: (Session | undefined)[] = []
client.sessionManager.on('session', (session) => sessions.push(session))
const { data: session } = await client.com.atproto.session.create({
handle: 'alice.test',
password: 'password',
})
const [{ data: sessionRefresh }] = await Promise.all(
[...Array(10)].map(() => client.com.atproto.session.refresh()),
)
expect(sessions).toEqual([
{ accessJwt: session.accessJwt, refreshJwt: session.refreshJwt },
{
accessJwt: sessionRefresh.accessJwt,
refreshJwt: sessionRefresh.refreshJwt,
},
])
expect(client.sessionManager.active()).toEqual(true)
const { data: sessionInfo } = await client.com.atproto.session.get({})
expect(sessionInfo).toEqual({
did: sessionRefresh.did,
handle: sessionRefresh.handle,
})
})
it('manually sets and unsets existing session.', async () => {
const sessions: (Session | undefined)[] = []
client.sessionManager.on('session', (session) => sessions.push(session))
const { data: session } = await client.com.atproto.session.create({
handle: 'alice.test',
password: 'password',
})
const sessionCreds = {
accessJwt: session.accessJwt,
refreshJwt: session.refreshJwt,
}
expect(client.sessionManager.active()).toEqual(true)
client.sessionManager.unset()
expect(client.sessionManager.active()).toEqual(false)
const getSessionAfterUnset = client.com.atproto.session.get({})
await expect(getSessionAfterUnset).rejects.toThrow(
'Authentication Required',
)
client.sessionManager.set(sessionCreds)
expect(client.sessionManager.active()).toEqual(true)
const { data: sessionInfo } = await client.com.atproto.session.get({})
expect(sessionInfo).toEqual({
did: session.did,
handle: session.handle,
})
expect(sessions).toEqual([sessionCreds, undefined, sessionCreds])
expect(client.sessionManager.active()).toEqual(true)
})
it('refreshes and retries request when access token is expired.', async () => {
const sessions: (Session | undefined)[] = []
client.sessionManager.on('session', (session) => sessions.push(session))
const auth = server.ctx.auth
const { data: sessionInfo } = await client.com.atproto.session.get({})
const accessExpired = await auth.createAccessToken(sessionInfo.did, -1)
expect(sessions.length).toEqual(0)
expect(client.sessionManager.active()).toEqual(true)
client.sessionManager.set({
refreshJwt: 'not-used-since-session-is-active',
...client.sessionManager.get(),
accessJwt: accessExpired.jwt,
})
expect(sessions.length).toEqual(1)
expect(client.sessionManager.active()).toEqual(true)
const { data: updatedSessionInfo } = await client.com.atproto.session.get(
{},
)
expect(updatedSessionInfo).toEqual(sessionInfo)
expect(sessions.length).toEqual(2) // New session was created during session.get()
expect(client.sessionManager.active()).toEqual(true)
})
it('unsets session when refresh token becomes expired.', async () => {
const sessions: (Session | undefined)[] = []
client.sessionManager.on('session', (session) => sessions.push(session))
const auth = server.ctx.auth
const { data: sessionInfo } = await client.com.atproto.session.get({})
const accessExpired = await auth.createAccessToken(sessionInfo.did, -1)
const refreshExpired = await auth.createRefreshToken(sessionInfo.did, -1)
expect(sessions.length).toEqual(0)
expect(client.sessionManager.active()).toEqual(true)
client.sessionManager.set({
accessJwt: accessExpired.jwt,
refreshJwt: refreshExpired.jwt,
})
expect(sessions.length).toEqual(1)
expect(client.sessionManager.active()).toEqual(true)
const getSessionAfterExpired = client.com.atproto.session.get({})
await expect(getSessionAfterExpired).rejects.toThrow('Token has expired')
expect(sessions.length).toEqual(2)
expect(sessions[1]).toEqual(undefined)
expect(client.sessionManager.active()).toEqual(false)
})
})

@ -88,7 +88,7 @@ const envApi = {
// create the PDS account
const client = pds.getClient()
const pdsRes = await client.com.atproto.account.create({
const pdsRes = await client.api.com.atproto.account.create({
email: handleNoTld + '@test.com',
handle,
password: handleNoTld + '-pass',

@ -8,7 +8,7 @@ import PDSServer, {
} from '@atproto/pds'
import * as plc from '@atproto/plc'
import * as crypto from '@atproto/crypto'
import AtpApi, { ServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { ServerType, ServerConfig, StartParams } from './types.js'
interface Startable {
@ -128,8 +128,8 @@ export class DevEnvServer {
}
}
getClient(): ServiceClient {
return AtpApi.service(`http://localhost:${this.port}`)
getClient(): AtpAgent {
return new AtpAgent({ service: `http://localhost:${this.port}` })
}
}

@ -1,5 +1,5 @@
import { AtUri } from '@atproto/uri'
import { ServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import {
SPAM,
OTHER,
@ -54,7 +54,7 @@ export async function generateMockSetup(env: DevEnv) {
declarationCid: string
handle: string
password: string
api: ServiceClient
agent: AtpAgent
}
const users: User[] = [
{
@ -63,7 +63,7 @@ export async function generateMockSetup(env: DevEnv) {
declarationCid: '',
handle: `alice.test`,
password: 'hunter2',
api: clients.alice,
agent: clients.alice,
},
{
email: 'bob@test.com',
@ -71,7 +71,7 @@ export async function generateMockSetup(env: DevEnv) {
declarationCid: '',
handle: `bob.test`,
password: 'hunter2',
api: clients.bob,
agent: clients.bob,
},
{
email: 'carla@test.com',
@ -79,7 +79,7 @@ export async function generateMockSetup(env: DevEnv) {
declarationCid: '',
handle: `carla.test`,
password: 'hunter2',
api: clients.carla,
agent: clients.carla,
},
]
const alice = users[0]
@ -88,18 +88,18 @@ export async function generateMockSetup(env: DevEnv) {
let _i = 1
for (const user of users) {
const res = await clients.loggedout.com.atproto.account.create({
const res = await clients.loggedout.api.com.atproto.account.create({
email: user.email,
handle: user.handle,
password: user.password,
})
user.api.setHeader('Authorization', `Bearer ${res.data.accessJwt}`)
const { data: profile } = await user.api.app.bsky.actor.getProfile({
user.agent.api.setHeader('Authorization', `Bearer ${res.data.accessJwt}`)
const { data: profile } = await user.agent.api.app.bsky.actor.getProfile({
actor: user.handle,
})
user.did = res.data.did
user.declarationCid = profile.declaration.cid
await user.api.app.bsky.actor.profile.create(
await user.agent.api.app.bsky.actor.profile.create(
{ did: user.did },
{
displayName: ucfirst(user.handle).slice(0, -5),
@ -110,7 +110,7 @@ export async function generateMockSetup(env: DevEnv) {
// Report one user
const reporter = picka(users)
await reporter.api.com.atproto.report.create({
await reporter.agent.api.com.atproto.report.create({
reasonType: picka([SPAM, OTHER]),
reason: picka(["Didn't look right to me", undefined, undefined]),
subject: {
@ -121,7 +121,7 @@ export async function generateMockSetup(env: DevEnv) {
// everybody follows everybody
const follow = async (author: User, subject: User) => {
await author.api.app.bsky.graph.follow.create(
await author.agent.api.app.bsky.graph.follow.create(
{ did: author.did },
{
subject: {
@ -143,7 +143,7 @@ export async function generateMockSetup(env: DevEnv) {
const posts: { uri: string; cid: string }[] = []
for (let i = 0; i < postTexts.length; i++) {
const author = picka(users)
const post = await author.api.app.bsky.feed.post.create(
const post = await author.agent.api.app.bsky.feed.post.create(
{ did: author.did },
{
text: postTexts[i],
@ -153,7 +153,7 @@ export async function generateMockSetup(env: DevEnv) {
posts.push(post)
if (rand(10) === 0) {
const reposter = picka(users)
await reposter.api.app.bsky.feed.repost.create(
await reposter.agent.api.app.bsky.feed.repost.create(
{ did: reposter.did },
{
subject: picka(posts),
@ -163,7 +163,7 @@ export async function generateMockSetup(env: DevEnv) {
}
if (rand(6) === 0) {
const reporter = picka(users)
await reporter.api.com.atproto.report.create({
await reporter.agent.api.com.atproto.report.create({
reasonType: picka([SPAM, OTHER]),
reason: picka(["Didn't look right to me", undefined, undefined]),
subject: {
@ -178,13 +178,13 @@ export async function generateMockSetup(env: DevEnv) {
for (let i = 0; i < 100; i++) {
const targetUri = picka(posts).uri
const urip = new AtUri(targetUri)
const target = await alice.api.app.bsky.feed.post.get({
const target = await alice.agent.api.app.bsky.feed.post.get({
user: urip.host,
rkey: urip.rkey,
})
const author = picka(users)
posts.push(
await author.api.app.bsky.feed.post.create(
await author.agent.api.app.bsky.feed.post.create(
{ did: author.did },
{
text: picka(replyTexts),
@ -202,7 +202,7 @@ export async function generateMockSetup(env: DevEnv) {
for (const post of posts) {
for (const user of users) {
if (rand(3) === 0) {
await user.api.app.bsky.feed.vote.create(
await user.agent.api.app.bsky.feed.vote.create(
{ did: user.did },
{
direction: rand(3) !== 0 ? 'up' : 'down',

@ -68,7 +68,7 @@ const indexTs = (
nsidTokens: Record<string, string[]>,
) =>
gen(project, '/index.ts', async (file) => {
//= import {Client as XrpcClient, ServiceClient as XrpcServiceClient} from '@atproto/xrpc'
//= import {Client as XrpcClient, AtpServiceClient as XrpcServiceClient} from '@atproto/xrpc'
const xrpcImport = file.addImportDeclaration({
moduleSpecifier: '@atproto/xrpc',
})
@ -116,9 +116,9 @@ const indexTs = (
})
}
//= export class Client {...}
//= export class AtpBaseClient {...}
const clientCls = file.addClass({
name: 'Client',
name: 'AtpBaseClient',
isExported: true,
})
//= xrpc: XrpcClient = new XrpcClient()
@ -131,39 +131,26 @@ const indexTs = (
//= this.xrpc.addLexicons(schemas)
//= }
clientCls.addConstructor().setBodyText(`this.xrpc.addLexicons(schemas)`)
//= service(serviceUri: string | URL): ServiceClient {
//= return new ServiceClient(this, this.xrpc.service(serviceUri))
//= service(serviceUri: string | URL): AtpServiceClient {
//= return new AtpServiceClient(this, this.xrpc.service(serviceUri))
//= }
clientCls
.addMethod({
name: 'service',
parameters: [{ name: 'serviceUri', type: 'string | URL' }],
returnType: 'ServiceClient',
returnType: 'AtpServiceClient',
})
.setBodyText(
`return new ServiceClient(this, this.xrpc.service(serviceUri))`,
`return new AtpServiceClient(this, this.xrpc.service(serviceUri))`,
)
//= const defaultInst = new Client()
//= export default defaultInst
file.addVariableStatement({
declarationKind: VariableDeclarationKind.Const,
declarations: [
{
name: 'defaultInst',
initializer: 'new Client()',
},
],
})
file.insertText(file.getFullText().length, `export default defaultInst`)
//= export class ServiceClient {...}
//= export class AtpServiceClient {...}
const serviceClientCls = file.addClass({
name: 'ServiceClient',
name: 'AtpServiceClient',
isExported: true,
})
//= _baseClient: Client
serviceClientCls.addProperty({ name: '_baseClient', type: 'Client' })
//= _baseClient: AtpBaseClient
serviceClientCls.addProperty({ name: '_baseClient', type: 'AtpBaseClient' })
//= xrpc: XrpcServiceClient
serviceClientCls.addProperty({
name: 'xrpc',
@ -176,7 +163,7 @@ const indexTs = (
type: ns.className,
})
}
//= constructor (baseClient: Client, xrpcService: XrpcServiceClient) {
//= constructor (baseClient: AtpBaseClient, xrpcService: XrpcServiceClient) {
//= this.baseClient = baseClient
//= this.xrpcService = xrpcService
//= {namespace declarations}
@ -184,7 +171,7 @@ const indexTs = (
serviceClientCls
.addConstructor({
parameters: [
{ name: 'baseClient', type: 'Client' },
{ name: 'baseClient', type: 'AtpBaseClient' },
{ name: 'xrpcService', type: 'XrpcServiceClient' },
],
})
@ -227,10 +214,10 @@ function genNamespaceCls(file: SourceFile, ns: DefTreeNode) {
name: ns.className,
isExported: true,
})
//= _service: ServiceClient
//= _service: AtpServiceClient
cls.addProperty({
name: '_service',
type: 'ServiceClient',
type: 'AtpServiceClient',
})
for (const userType of ns.userTypes) {
@ -256,7 +243,7 @@ function genNamespaceCls(file: SourceFile, ns: DefTreeNode) {
genNamespaceCls(file, child)
}
//= constructor(service: ServiceClient) {
//= constructor(service: AtpServiceClient) {
//= this._service = service
//= {child namespace prop declarations}
//= {record prop declarations}
@ -264,7 +251,7 @@ function genNamespaceCls(file: SourceFile, ns: DefTreeNode) {
const cons = cls.addConstructor()
cons.addParameter({
name: 'service',
type: 'ServiceClient',
type: 'AtpServiceClient',
})
cons.setBodyText(
[
@ -338,19 +325,19 @@ function genRecordCls(file: SourceFile, userType: DefTreeNodeUserType) {
name: `${toTitleCase(name)}Record`,
isExported: true,
})
//= _service: ServiceClient
//= _service: AtpServiceClient
cls.addProperty({
name: '_service',
type: 'ServiceClient',
type: 'AtpServiceClient',
})
//= constructor(service: ServiceClient) {
//= constructor(service: AtpServiceClient) {
//= this._service = service
//= }
const cons = cls.addConstructor()
cons.addParameter({
name: 'service',
type: 'ServiceClient',
type: 'AtpServiceClient',
})
cons.setBodyText(`this._service = service`)

@ -1,6 +1,6 @@
import { once, EventEmitter } from 'events'
import Mail from 'nodemailer/lib/mailer'
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { SeedClient } from './seeds/client'
import basicSeed from './seeds/basic'
import { Database } from '../src'
@ -25,7 +25,7 @@ import { RepoCommitBlock } from '../src/db/tables/repo-commit-block'
import { Record } from '../src/db/tables/record'
describe('account deletion', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: util.CloseFn
let sc: SeedClient
@ -48,8 +48,8 @@ describe('account deletion', () => {
mailer = server.ctx.mailer
db = server.ctx.db
blobstore = server.ctx.blobstore
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc, server.ctx.messageQueue)
carol = sc.accounts[sc.dids.carol]
@ -83,7 +83,7 @@ describe('account deletion', () => {
it('requests account deletion', async () => {
const mail = await getMailFrom(
client.com.atproto.account.requestDelete(undefined, {
agent.api.com.atproto.account.requestDelete(undefined, {
headers: sc.getHeaders(carol.did),
}),
)
@ -98,7 +98,7 @@ describe('account deletion', () => {
})
it('fails account deletion with a bad token', async () => {
const attempt = client.com.atproto.account.delete({
const attempt = agent.api.com.atproto.account.delete({
token: '123456',
did: carol.did,
password: carol.password,
@ -107,7 +107,7 @@ describe('account deletion', () => {
})
it('fails account deletion with a bad password', async () => {
const attempt = client.com.atproto.account.delete({
const attempt = agent.api.com.atproto.account.delete({
token,
did: carol.did,
password: 'bad-pass',
@ -116,7 +116,7 @@ describe('account deletion', () => {
})
it('deletes account with a valid token & password', async () => {
await client.com.atproto.account.delete({
await agent.api.com.atproto.account.delete({
token,
did: carol.did,
password: carol.password,
@ -124,7 +124,7 @@ describe('account deletion', () => {
})
it('no longer lets the user log in', async () => {
const attempt = client.com.atproto.session.create({
const attempt = agent.api.com.atproto.session.create({
handle: carol.handle,
password: carol.password,
})
@ -205,7 +205,7 @@ describe('account deletion', () => {
})
it('no longer displays the users posts in feeds', async () => {
const feed = await client.app.bsky.feed.getTimeline(undefined, {
const feed = await agent.api.app.bsky.feed.getTimeline(undefined, {
headers: sc.getHeaders(sc.dids.alice),
})
const found = feed.data.feed.filter(
@ -215,7 +215,7 @@ describe('account deletion', () => {
})
it('removes notifications from the user', async () => {
const notifs = await client.app.bsky.notification.list(undefined, {
const notifs = await agent.api.app.bsky.notification.list(undefined, {
headers: sc.getHeaders(sc.dids.alice),
})
const found = notifs.data.notifications.filter(
@ -232,7 +232,7 @@ describe('account deletion', () => {
})
const mail = await getMailFrom(
client.com.atproto.account.requestDelete(undefined, {
agent.api.com.atproto.account.requestDelete(undefined, {
headers: sc.getHeaders(eve.did),
}),
)
@ -241,7 +241,7 @@ describe('account deletion', () => {
if (!token) {
return expect(token).toBeDefined()
}
await client.com.atproto.account.delete({
await agent.api.com.atproto.account.delete({
token,
did: eve.did,
password: eve.password,

@ -1,6 +1,5 @@
import { once, EventEmitter } from 'events'
import AtpApi, {
ServiceClient as AtpServiceClient,
import AtpAgent, {
ComAtprotoAccountCreate,
ComAtprotoAccountResetPassword as ResetAccountPassword,
} from '@atproto/api'
@ -18,10 +17,10 @@ const passwordAlt = 'test456'
const minsToMs = 60 * 1000
const createInviteCode = async (
client: AtpServiceClient,
agent: AtpAgent,
uses: number,
): Promise<string> => {
const res = await client.com.atproto.account.createInviteCode(
const res = await agent.api.com.atproto.account.createInviteCode(
{ useCount: uses },
{
headers: { authorization: util.adminAuth() },
@ -35,7 +34,7 @@ describe('account', () => {
let serverUrl: string
let cfg: ServerConfig
let serverKey: string
let client: AtpServiceClient
let agent: AtpAgent
let close: util.CloseFn
let mailer: ServerMailer
let db: Database
@ -55,7 +54,7 @@ describe('account', () => {
cfg = server.ctx.cfg
serverUrl = server.url
serverKey = server.ctx.keypair.did()
client = AtpApi.service(serverUrl)
agent = new AtpAgent({ service: serverUrl })
// Catch emails for use in tests
_origSendMail = mailer.transporter.sendMail
@ -76,14 +75,14 @@ describe('account', () => {
let inviteCode: string
it('creates an invite code', async () => {
inviteCode = await createInviteCode(client, 1)
inviteCode = await createInviteCode(agent, 1)
const [host, code] = inviteCode.split('-')
expect(host).toBe('pds.public.url') // Hostname of public url
expect(code.length).toBe(5)
})
it('serves the accounts system config', async () => {
const res = await client.com.atproto.server.getAccountsConfig({})
const res = await agent.api.com.atproto.server.getAccountsConfig({})
expect(res.data.inviteCodeRequired).toBe(true)
expect(res.data.availableUserDomains[0]).toBe('.test')
expect(typeof res.data.inviteCodeRequired).toBe('boolean')
@ -94,7 +93,7 @@ describe('account', () => {
})
it('fails on invalid handles', async () => {
const promise = client.com.atproto.account.create({
const promise = agent.api.com.atproto.account.create({
email: 'bad-handle@test.com',
handle: 'did:bad-handle.test',
password: 'asdf',
@ -106,7 +105,7 @@ describe('account', () => {
})
it('fails on bad invite code', async () => {
const promise = client.com.atproto.account.create({
const promise = agent.api.com.atproto.account.create({
email,
handle,
password,
@ -121,7 +120,7 @@ describe('account', () => {
let jwt: string
it('creates an account', async () => {
const res = await client.com.atproto.account.create({
const res = await agent.api.com.atproto.account.create({
email,
handle,
password,
@ -146,9 +145,9 @@ describe('account', () => {
})
it('allows a custom set recovery key', async () => {
const inviteCode = await createInviteCode(client, 1)
const inviteCode = await createInviteCode(agent, 1)
const recoveryKey = (await crypto.EcdsaKeypair.create()).did()
const res = await client.com.atproto.account.create({
const res = await agent.api.com.atproto.account.create({
email: 'custom-recovery@test.com',
handle: 'custom-recovery.test',
password: 'custom-recovery',
@ -163,11 +162,11 @@ describe('account', () => {
})
it('disallows duplicate email addresses and handles', async () => {
const inviteCode = await createInviteCode(client, 2)
const inviteCode = await createInviteCode(agent, 2)
const email = 'bob@test.com'
const handle = 'bob.test'
const password = 'test123'
await client.com.atproto.account.create({
await agent.api.com.atproto.account.create({
email,
handle,
password,
@ -175,7 +174,7 @@ describe('account', () => {
})
await expect(
client.com.atproto.account.create({
agent.api.com.atproto.account.create({
email: email.toUpperCase(),
handle: 'carol.test',
password,
@ -184,7 +183,7 @@ describe('account', () => {
).rejects.toThrow('Email already taken: BOB@TEST.COM')
await expect(
client.com.atproto.account.create({
agent.api.com.atproto.account.create({
email: 'carol@test.com',
handle: handle.toUpperCase(),
password,
@ -194,9 +193,9 @@ describe('account', () => {
})
it('disallows improperly formatted handles', async () => {
const inviteCode = await createInviteCode(client, 1)
const inviteCode = await createInviteCode(agent, 1)
const tryHandle = async (handle: string) => {
await client.com.atproto.account.create({
await agent.api.com.atproto.account.create({
email: 'john@test.com',
handle,
password: 'test123',
@ -242,7 +241,7 @@ describe('account', () => {
})
it('fails on used up invite code', async () => {
const promise = client.com.atproto.account.create({
const promise = agent.api.com.atproto.account.create({
email: 'bob@test.com',
handle: 'bob.test',
password: 'asdf',
@ -254,7 +253,7 @@ describe('account', () => {
})
it('handles racing invite code uses', async () => {
const inviteCode = await createInviteCode(client, 1)
const inviteCode = await createInviteCode(agent, 1)
const COUNT = 10
let successes = 0
@ -263,7 +262,7 @@ describe('account', () => {
for (let i = 0; i < COUNT; i++) {
const attempt = async () => {
try {
await client.com.atproto.account.create({
await agent.api.com.atproto.account.create({
email: `user${i}@test.com`,
handle: `user${i}.test`,
password: `password`,
@ -284,8 +283,8 @@ describe('account', () => {
it('handles racing signups for same handle', async () => {
const COUNT = 10
const invite1 = await createInviteCode(client, COUNT)
const invite2 = await createInviteCode(client, COUNT)
const invite1 = await createInviteCode(agent, COUNT)
const invite2 = await createInviteCode(agent, COUNT)
let successes = 0
let failures = 0
@ -296,7 +295,7 @@ describe('account', () => {
// Use two invites to ensure per-invite locking doesn't
// give the appearance of fixing a race for handle.
const invite = i % 2 ? invite1 : invite2
await client.com.atproto.account.create({
await agent.api.com.atproto.account.create({
email: `matching@test.com`,
handle: `matching.test`,
password: `password`,
@ -315,11 +314,11 @@ describe('account', () => {
})
it('fails on unauthenticated requests', async () => {
await expect(client.com.atproto.session.get({})).rejects.toThrow()
await expect(agent.api.com.atproto.session.get({})).rejects.toThrow()
})
it('logs in', async () => {
const res = await client.com.atproto.session.create({
const res = await agent.api.com.atproto.session.create({
identifier: handle,
password,
})
@ -330,8 +329,8 @@ describe('account', () => {
})
it('can perform authenticated requests', async () => {
client.setHeader('authorization', `Bearer ${jwt}`)
const res = await client.com.atproto.session.get({})
agent.api.setHeader('authorization', `Bearer ${jwt}`)
const res = await agent.api.com.atproto.session.get({})
expect(res.data.did).toBe(did)
expect(res.data.handle).toBe(handle)
})
@ -346,7 +345,7 @@ describe('account', () => {
it('can reset account password', async () => {
const mail = await getMailFrom(
client.com.atproto.account.requestPasswordReset({ email }),
agent.api.com.atproto.account.requestPasswordReset({ email }),
)
expect(mail.to).toEqual(email)
@ -359,18 +358,18 @@ describe('account', () => {
return expect(token).toBeDefined()
}
await client.com.atproto.account.resetPassword({
await agent.api.com.atproto.account.resetPassword({
token,
password: passwordAlt,
})
// Logs in with new password and not previous password
await expect(
client.com.atproto.session.create({ identifier: handle, password }),
agent.api.com.atproto.session.create({ identifier: handle, password }),
).rejects.toThrow('Invalid identifier or password')
await expect(
client.com.atproto.session.create({
agent.api.com.atproto.session.create({
identifier: handle,
password: passwordAlt,
}),
@ -379,7 +378,7 @@ describe('account', () => {
it('allows only single-use of password reset token', async () => {
const mail = await getMailFrom(
client.com.atproto.account.requestPasswordReset({ email }),
agent.api.com.atproto.account.requestPasswordReset({ email }),
)
const token = getTokenFromMail(mail)
@ -389,28 +388,28 @@ describe('account', () => {
}
// Reset back from passwordAlt to password
await client.com.atproto.account.resetPassword({ token, password })
await agent.api.com.atproto.account.resetPassword({ token, password })
// Reuse of token fails
await expect(
client.com.atproto.account.resetPassword({ token, password }),
agent.api.com.atproto.account.resetPassword({ token, password }),
).rejects.toThrow(ResetAccountPassword.InvalidTokenError)
// Logs in with new password and not previous password
await expect(
client.com.atproto.session.create({
agent.api.com.atproto.session.create({
identifier: handle,
password: passwordAlt,
}),
).rejects.toThrow('Invalid identifier or password')
await expect(
client.com.atproto.session.create({ identifier: handle, password }),
agent.api.com.atproto.session.create({ identifier: handle, password }),
).resolves.toBeDefined()
})
it('allows only unexpired password reset tokens', async () => {
await client.com.atproto.account.requestPasswordReset({ email })
await agent.api.com.atproto.account.requestPasswordReset({ email })
const user = await db.db
.updateTable('user')
@ -428,7 +427,7 @@ describe('account', () => {
// Use of expired token fails
await expect(
client.com.atproto.account.resetPassword({
agent.api.com.atproto.account.resetPassword({
token: user.passwordResetToken,
password: passwordAlt,
}),
@ -436,25 +435,25 @@ describe('account', () => {
// Still logs in with previous password
await expect(
client.com.atproto.session.create({
agent.api.com.atproto.session.create({
identifier: handle,
password: passwordAlt,
}),
).rejects.toThrow('Invalid identifier or password')
await expect(
client.com.atproto.session.create({ identifier: handle, password }),
agent.api.com.atproto.session.create({ identifier: handle, password }),
).resolves.toBeDefined()
})
it('resolves did for account', async () => {
const resolved = await client.com.atproto.handle.resolve({ handle })
const resolved = await agent.api.com.atproto.handle.resolve({ handle })
expect(resolved.data).toEqual({ did })
})
it('resolves did for server', async () => {
const resolvedImplicit = await client.com.atproto.handle.resolve({})
const resolvedExplicit = await client.com.atproto.handle.resolve({
const resolvedImplicit = await agent.api.com.atproto.handle.resolve({})
const resolvedExplicit = await agent.api.com.atproto.handle.resolve({
handle: 'pds.public.url',
})
expect(resolvedImplicit.data).toEqual({ did: cfg.serverDid })

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/moderationAction'
import * as CreateSession from '@atproto/api/src/client/types/com/atproto/session/create'
import * as RefreshSession from '@atproto/api/src/client/types/com/atproto/session/refresh'
@ -7,14 +7,14 @@ import { adminAuth, CloseFn, runTestServer, TestServerInfo } from './_util'
describe('auth', () => {
let server: TestServerInfo
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
beforeAll(async () => {
server = await runTestServer({
dbPostgresSchema: 'auth',
})
client = AtpApi.service(server.url)
agent = new AtpAgent({ service: server.url })
close = server.close
})
@ -23,11 +23,11 @@ describe('auth', () => {
})
const createAccount = async (info) => {
const { data } = await client.com.atproto.account.create(info)
const { data } = await agent.api.com.atproto.account.create(info)
return data
}
const getSession = async (jwt) => {
const { data } = await client.com.atproto.session.get(
const { data } = await agent.api.com.atproto.session.get(
{},
{
headers: SeedClient.getHeaders(jwt),
@ -36,16 +36,16 @@ describe('auth', () => {
return data
}
const createSession = async (info) => {
const { data } = await client.com.atproto.session.create(info)
const { data } = await agent.api.com.atproto.session.create(info)
return data
}
const deleteSession = async (jwt) => {
await client.com.atproto.session.delete(undefined, {
await agent.api.com.atproto.session.delete(undefined, {
headers: SeedClient.getHeaders(jwt),
})
}
const refreshSession = async (jwt) => {
const { data } = await client.com.atproto.session.refresh(undefined, {
const { data } = await agent.api.com.atproto.session.refresh(undefined, {
headers: SeedClient.getHeaders(jwt),
})
return data
@ -198,7 +198,7 @@ describe('auth', () => {
email: 'iris@test.com',
password: 'password',
})
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -224,7 +224,7 @@ describe('auth', () => {
email: 'jared@test.com',
password: 'password',
})
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {

@ -1,7 +1,7 @@
import fs from 'fs/promises'
import { CID } from 'multiformats/cid'
import { AtUri } from '@atproto/uri'
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import * as Post from '../src/lexicon/types/app/bsky/feed/post'
import { adminAuth, CloseFn, paginateAll, runTestServer } from './_util'
import { BlobNotFoundError } from '@atproto/repo'
@ -23,8 +23,8 @@ const bob = {
describe('crud operations', () => {
let ctx: AppContext
let client: AtpServiceClient
let aliceClient: AtpServiceClient
let agent: AtpAgent
let aliceAgent: AtpAgent
let close: CloseFn
beforeAll(async () => {
@ -33,8 +33,8 @@ describe('crud operations', () => {
})
ctx = server.ctx
close = server.close
client = AtpApi.service(server.url)
aliceClient = AtpApi.service(server.url)
agent = new AtpAgent({ service: server.url })
aliceAgent = new AtpAgent({ service: server.url })
})
afterAll(async () => {
@ -42,14 +42,14 @@ describe('crud operations', () => {
})
it('registers users', async () => {
const res = await client.com.atproto.account.create({
const res = await agent.api.com.atproto.account.create({
email: alice.email,
handle: alice.handle,
password: alice.password,
})
aliceClient.setHeader('authorization', `Bearer ${res.data.accessJwt}`)
aliceAgent.api.setHeader('authorization', `Bearer ${res.data.accessJwt}`)
alice.did = res.data.did
const res2 = await client.com.atproto.account.create({
const res2 = await agent.api.com.atproto.account.create({
email: bob.email,
handle: bob.handle,
password: bob.password,
@ -58,12 +58,12 @@ describe('crud operations', () => {
})
it('describes repo', async () => {
const description = await client.com.atproto.repo.describe({
const description = await agent.api.com.atproto.repo.describe({
user: alice.did,
})
expect(description.data.handle).toBe(alice.handle)
expect(description.data.did).toBe(alice.did)
const description2 = await client.com.atproto.repo.describe({
const description2 = await agent.api.com.atproto.repo.describe({
user: bob.did,
})
expect(description2.data.handle).toBe(bob.handle)
@ -72,7 +72,7 @@ describe('crud operations', () => {
let uri: AtUri
it('creates records', async () => {
const res = await aliceClient.com.atproto.repo.createRecord({
const res = await aliceAgent.api.com.atproto.repo.createRecord({
did: alice.did,
collection: 'app.bsky.feed.post',
record: {
@ -88,7 +88,7 @@ describe('crud operations', () => {
})
it('lists records', async () => {
const res1 = await client.com.atproto.repo.listRecords({
const res1 = await agent.api.com.atproto.repo.listRecords({
user: alice.did,
collection: 'app.bsky.feed.post',
})
@ -98,7 +98,7 @@ describe('crud operations', () => {
'Hello, world!',
)
const res2 = await client.app.bsky.feed.post.list({
const res2 = await agent.api.app.bsky.feed.post.list({
user: alice.did,
})
expect(res2.records.length).toBe(1)
@ -107,7 +107,7 @@ describe('crud operations', () => {
})
it('gets records', async () => {
const res1 = await client.com.atproto.repo.getRecord({
const res1 = await agent.api.com.atproto.repo.getRecord({
user: alice.did,
collection: 'app.bsky.feed.post',
rkey: uri.rkey,
@ -115,7 +115,7 @@ describe('crud operations', () => {
expect(res1.data.uri).toBe(uri.toString())
expect((res1.data.value as Post.Record).text).toBe('Hello, world!')
const res2 = await client.app.bsky.feed.post.get({
const res2 = await agent.api.app.bsky.feed.post.get({
user: alice.did,
rkey: uri.rkey,
})
@ -124,12 +124,12 @@ describe('crud operations', () => {
})
it('deletes records', async () => {
await aliceClient.com.atproto.repo.deleteRecord({
await aliceAgent.api.com.atproto.repo.deleteRecord({
did: alice.did,
collection: 'app.bsky.feed.post',
rkey: uri.rkey,
})
const res1 = await client.com.atproto.repo.listRecords({
const res1 = await agent.api.com.atproto.repo.listRecords({
user: alice.did,
collection: 'app.bsky.feed.post',
})
@ -137,7 +137,7 @@ describe('crud operations', () => {
})
it('CRUDs records with the semantic sugars', async () => {
const res1 = await aliceClient.app.bsky.feed.post.create(
const res1 = await aliceAgent.api.app.bsky.feed.post.create(
{ did: alice.did },
{
$type: 'app.bsky.feed.post',
@ -147,17 +147,17 @@ describe('crud operations', () => {
)
const uri = new AtUri(res1.uri)
const res2 = await client.app.bsky.feed.post.list({
const res2 = await agent.api.app.bsky.feed.post.list({
user: alice.did,
})
expect(res2.records.length).toBe(1)
await aliceClient.app.bsky.feed.post.delete({
await aliceAgent.api.app.bsky.feed.post.delete({
did: alice.did,
rkey: uri.rkey,
})
const res3 = await client.app.bsky.feed.post.list({
const res3 = await agent.api.app.bsky.feed.post.list({
user: alice.did,
})
expect(res3.records.length).toBe(0)
@ -167,7 +167,7 @@ describe('crud operations', () => {
const file = await fs.readFile(
'tests/image/fixtures/key-landscape-small.jpg',
)
const { data: image } = await aliceClient.com.atproto.blob.upload(file, {
const { data: image } = await aliceAgent.api.com.atproto.blob.upload(file, {
encoding: 'image/jpeg',
})
const imageCid = CID.parse(image.cid)
@ -176,7 +176,7 @@ describe('crud operations', () => {
BlobNotFoundError,
)
// Associate image with post, image should be placed in blobstore
const res = await aliceClient.app.bsky.feed.post.create(
const res = await aliceAgent.api.app.bsky.feed.post.create(
{ did: alice.did },
{
$type: 'app.bsky.feed.post',
@ -192,7 +192,7 @@ describe('crud operations', () => {
)
// Ensure image is on post record
const postUri = new AtUri(res.uri)
const post = await aliceClient.app.bsky.feed.post.get({
const post = await aliceAgent.api.app.bsky.feed.post.get({
rkey: postUri.rkey,
user: alice.did,
})
@ -202,14 +202,14 @@ describe('crud operations', () => {
// Ensure that the uploaded image is now in the blobstore, i.e. doesn't throw BlobNotFoundError
await ctx.blobstore.getBytes(imageCid)
// Cleanup
await aliceClient.app.bsky.feed.post.delete({
await aliceAgent.api.app.bsky.feed.post.delete({
rkey: postUri.rkey,
did: alice.did,
})
})
it('creates records with the correct key described by the schema', async () => {
const res1 = await aliceClient.app.bsky.actor.profile.create(
const res1 = await aliceAgent.api.app.bsky.actor.profile.create(
{ did: alice.did },
{
displayName: 'alice',
@ -230,7 +230,7 @@ describe('crud operations', () => {
}
const responses = await Promise.all(
postTexts.map((text) =>
aliceClient.app.bsky.feed.post.create(
aliceAgent.api.app.bsky.feed.post.create(
{ did: alice.did },
{
text,
@ -244,7 +244,7 @@ describe('crud operations', () => {
for (let i = 0; i < uris.length; i++) {
const uri = uris[i]
const got = await aliceClient.com.atproto.repo.getRecord({
const got = await aliceAgent.api.com.atproto.repo.getRecord({
user: alice.did,
collection: uri.collection,
rkey: uri.rkey,
@ -257,7 +257,7 @@ describe('crud operations', () => {
it('handles races on del', async () => {
await Promise.all(
uris.map((uri) =>
aliceClient.app.bsky.feed.post.delete({
aliceAgent.api.app.bsky.feed.post.delete({
did: alice.did,
rkey: uri.rkey,
}),
@ -265,7 +265,7 @@ describe('crud operations', () => {
)
for (const uri of uris) {
await expect(
aliceClient.com.atproto.repo.getRecord({
aliceAgent.api.com.atproto.repo.getRecord({
user: alice.did,
collection: uri.collection,
rkey: uri.rkey,
@ -284,7 +284,7 @@ describe('crud operations', () => {
beforeAll(async () => {
const createPost = async (text: string) => {
const res = await aliceClient.app.bsky.feed.post.create(
const res = await aliceAgent.api.app.bsky.feed.post.create(
{ did: alice.did },
{
$type: 'app.bsky.feed.post',
@ -304,7 +304,7 @@ describe('crud operations', () => {
afterAll(async () => {
await Promise.all(
[uri1, uri2, uri3, uri4, uri5].map((uri) =>
aliceClient.app.bsky.feed.post.delete({
aliceAgent.api.app.bsky.feed.post.delete({
did: alice.did,
rkey: uri.rkey,
}),
@ -315,7 +315,7 @@ describe('crud operations', () => {
it('in forwards order', async () => {
const results = (results) => results.flatMap((res) => res.records)
const paginator = async (cursor?: string) => {
const res = await client.app.bsky.feed.post.list({
const res = await agent.api.app.bsky.feed.post.list({
user: alice.did,
before: cursor,
limit: 2,
@ -328,7 +328,7 @@ describe('crud operations', () => {
expect(res.records.length).toBeLessThanOrEqual(2),
)
const full = await client.app.bsky.feed.post.list({
const full = await agent.api.app.bsky.feed.post.list({
user: alice.did,
})
@ -339,7 +339,7 @@ describe('crud operations', () => {
it('in reverse order', async () => {
const results = (results) => results.flatMap((res) => res.records)
const paginator = async (cursor?: string) => {
const res = await client.app.bsky.feed.post.list({
const res = await agent.api.app.bsky.feed.post.list({
user: alice.did,
reverse: true,
after: cursor,
@ -353,7 +353,7 @@ describe('crud operations', () => {
expect(res.records.length).toBeLessThanOrEqual(2),
)
const full = await client.app.bsky.feed.post.list({
const full = await agent.api.app.bsky.feed.post.list({
user: alice.did,
reverse: true,
})
@ -363,7 +363,7 @@ describe('crud operations', () => {
})
it('between two records', async () => {
const list = await client.app.bsky.feed.post.list({
const list = await agent.api.app.bsky.feed.post.list({
user: alice.did,
after: uri1.rkey,
before: uri5.rkey,
@ -375,10 +375,10 @@ describe('crud operations', () => {
})
it('reverses', async () => {
const forwards = await client.app.bsky.feed.post.list({
const forwards = await agent.api.app.bsky.feed.post.list({
user: alice.did,
})
const reverse = await client.app.bsky.feed.post.list({
const reverse = await agent.api.app.bsky.feed.post.list({
user: alice.did,
reverse: true,
})
@ -394,7 +394,7 @@ describe('crud operations', () => {
// --------------
it('defaults an undefined $type on records', async () => {
const res = await aliceClient.com.atproto.repo.createRecord({
const res = await aliceAgent.api.com.atproto.repo.createRecord({
did: alice.did,
collection: 'app.bsky.feed.post',
record: {
@ -403,7 +403,7 @@ describe('crud operations', () => {
},
})
const uri = new AtUri(res.data.uri)
const got = await client.com.atproto.repo.getRecord({
const got = await agent.api.com.atproto.repo.getRecord({
user: alice.did,
collection: uri.collection,
rkey: uri.rkey,
@ -412,7 +412,7 @@ describe('crud operations', () => {
})
it('requires the schema to be known if validating', async () => {
const prom = aliceClient.com.atproto.repo.createRecord({
const prom = aliceAgent.api.com.atproto.repo.createRecord({
did: alice.did,
collection: 'com.example.foobar',
record: { $type: 'com.example.foobar' },
@ -424,7 +424,7 @@ describe('crud operations', () => {
it('requires the $type to match the schema', async () => {
await expect(
aliceClient.com.atproto.repo.createRecord({
aliceAgent.api.com.atproto.repo.createRecord({
did: alice.did,
collection: 'app.bsky.feed.post',
record: { $type: 'app.bsky.feed.vote' },
@ -436,7 +436,7 @@ describe('crud operations', () => {
it('validates the record on write', async () => {
await expect(
aliceClient.com.atproto.repo.createRecord({
aliceAgent.api.com.atproto.repo.createRecord({
did: alice.did,
collection: 'app.bsky.feed.post',
record: { $type: 'app.bsky.feed.post' },
@ -450,7 +450,7 @@ describe('crud operations', () => {
// --------------
it("doesn't serve taken-down record", async () => {
const created = await aliceClient.app.bsky.feed.post.create(
const created = await aliceAgent.api.app.bsky.feed.post.create(
{ did: alice.did },
{
$type: 'app.bsky.feed.post',
@ -459,15 +459,15 @@ describe('crud operations', () => {
},
)
const postUri = new AtUri(created.uri)
const post = await client.app.bsky.feed.post.get({
const post = await agent.api.app.bsky.feed.post.get({
user: alice.did,
rkey: postUri.rkey,
})
const posts = await client.app.bsky.feed.post.list({ user: alice.did })
const posts = await agent.api.app.bsky.feed.post.list({ user: alice.did })
expect(posts.records.map((r) => r.uri)).toContain(post.uri)
const { data: action } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -483,18 +483,18 @@ describe('crud operations', () => {
},
)
const postTakedownPromise = client.app.bsky.feed.post.get({
const postTakedownPromise = agent.api.app.bsky.feed.post.get({
user: alice.did,
rkey: postUri.rkey,
})
await expect(postTakedownPromise).rejects.toThrow('Could not locate record')
const postsTakedown = await client.app.bsky.feed.post.list({
const postsTakedown = await agent.api.app.bsky.feed.post.list({
user: alice.did,
})
expect(postsTakedown.records.map((r) => r.uri)).not.toContain(post.uri)
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: action.id,
createdBy: 'X',

@ -1,5 +1,5 @@
import { sql } from 'kysely'
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { getWriteLog, RecordWriteOp, WriteOpAction } from '@atproto/repo'
import SqlRepoStorage from '../../src/sql-repo-storage'
import basicSeed from '../seeds/basic'
@ -16,7 +16,7 @@ import { AppContext } from '../../src'
describe('sync', () => {
let server: TestServerInfo
let ctx: AppContext
let client: AtpServiceClient
let agent: AtpAgent
let sc: SeedClient
beforeAll(async () => {
@ -24,8 +24,8 @@ describe('sync', () => {
dbPostgresSchema: 'event_stream_sync',
})
ctx = server.ctx
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc, ctx.messageQueue)
})
@ -41,7 +41,7 @@ describe('sync', () => {
indexedTables.map((t) => sql`delete from ${ref(t)}`.execute(db.db)),
)
// Confirm timeline empty
const emptiedTL = await client.app.bsky.feed.getTimeline(
const emptiedTL = await agent.api.app.bsky.feed.getTimeline(
{},
{ headers: sc.getHeaders(sc.dids.alice) },
)
@ -70,7 +70,7 @@ describe('sync', () => {
}
await messageQueue.processAll()
// Check indexed timeline
const aliceTL = await client.app.bsky.feed.getTimeline(
const aliceTL = await agent.api.app.bsky.feed.getTimeline(
{},
{ headers: sc.getHeaders(sc.dids.alice) },
)

@ -1,6 +1,6 @@
import fs from 'fs/promises'
import { gzipSync } from 'zlib'
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { CloseFn, runTestServer, TestServerInfo } from './_util'
import { CID } from 'multiformats/cid'
import { Database, ServerConfig } from '../src'
@ -25,9 +25,9 @@ const bob = {
describe('file uploads', () => {
let server: TestServerInfo
let client: AtpServiceClient
let aliceClient: AtpServiceClient
let bobClient: AtpServiceClient
let agent: AtpAgent
let aliceAgent: AtpAgent
let bobAgent: AtpAgent
let blobstore: DiskBlobStore
let db: Database
let cfg: ServerConfig
@ -41,9 +41,9 @@ describe('file uploads', () => {
blobstore = server.ctx.blobstore as DiskBlobStore
db = server.ctx.db
close = server.close
client = AtpApi.service(server.url)
aliceClient = AtpApi.service(server.url)
bobClient = AtpApi.service(server.url)
agent = new AtpAgent({ service: server.url })
aliceAgent = new AtpAgent({ service: server.url })
bobAgent = new AtpAgent({ service: server.url })
cfg = server.ctx.cfg
serverUrl = server.url
})
@ -53,19 +53,19 @@ describe('file uploads', () => {
})
it('registers users', async () => {
const res = await client.com.atproto.account.create({
const res = await agent.api.com.atproto.account.create({
email: alice.email,
handle: alice.handle,
password: alice.password,
})
aliceClient.setHeader('authorization', `Bearer ${res.data.accessJwt}`)
aliceAgent.api.setHeader('authorization', `Bearer ${res.data.accessJwt}`)
alice.did = res.data.did
const res2 = await client.com.atproto.account.create({
const res2 = await agent.api.com.atproto.account.create({
email: bob.email,
handle: bob.handle,
password: bob.password,
})
bobClient.setHeader('authorization', `Bearer ${res2.data.accessJwt}`)
bobAgent.api.setHeader('authorization', `Bearer ${res2.data.accessJwt}`)
bob.did = res2.data.did
})
@ -86,7 +86,7 @@ describe('file uploads', () => {
signal: abortController.signal,
headers: {
'content-type': 'image/jpeg',
authorization: aliceClient.xrpc.headers.authorization,
authorization: aliceAgent.api.xrpc.headers.authorization,
},
})
await expect(response).rejects.toThrow('operation was aborted')
@ -98,7 +98,7 @@ describe('file uploads', () => {
it('uploads files', async () => {
smallFile = await fs.readFile('tests/image/fixtures/key-portrait-small.jpg')
const res = await aliceClient.com.atproto.blob.upload(smallFile, {
const res = await aliceAgent.api.com.atproto.blob.upload(smallFile, {
encoding: 'image/jpeg',
} as any)
smallCid = CID.parse(res.data.cid)
@ -118,7 +118,7 @@ describe('file uploads', () => {
})
it('can reference the file', async () => {
await aliceClient.app.bsky.actor.updateProfile({
await aliceAgent.api.app.bsky.actor.updateProfile({
displayName: 'Alice',
avatar: { cid: smallCid.toString(), mimeType: 'image/jpeg' },
})
@ -138,7 +138,7 @@ describe('file uploads', () => {
})
it('serves the referenced blob', async () => {
const profile = await aliceClient.app.bsky.actor.getProfile({
const profile = await aliceAgent.api.app.bsky.actor.getProfile({
actor: 'alice.test',
})
const avatar = profile.data.avatar as string
@ -160,12 +160,12 @@ describe('file uploads', () => {
it('does not allow referencing a file that is outside blob constraints', async () => {
largeFile = await fs.readFile('tests/image/fixtures/hd-key.jpg')
const res = await aliceClient.com.atproto.blob.upload(largeFile, {
const res = await aliceAgent.api.com.atproto.blob.upload(largeFile, {
encoding: 'image/jpeg',
} as any)
largeCid = CID.parse(res.data.cid)
const profilePromise = aliceClient.app.bsky.actor.updateProfile({
const profilePromise = aliceAgent.api.app.bsky.actor.updateProfile({
avatar: { cid: largeCid.toString(), mimeType: 'image/jpeg' },
})
@ -188,25 +188,29 @@ describe('file uploads', () => {
const file = await fs.readFile(
'tests/image/fixtures/key-landscape-small.jpg',
)
const { data: uploadA } = await aliceClient.com.atproto.blob.upload(file, {
encoding: 'image/jpeg',
} as any)
const { data: uploadB } = await bobClient.com.atproto.blob.upload(file, {
const { data: uploadA } = await aliceAgent.api.com.atproto.blob.upload(
file,
{
encoding: 'image/jpeg',
} as any,
)
const { data: uploadB } = await bobAgent.api.com.atproto.blob.upload(file, {
encoding: 'image/jpeg',
} as any)
expect(uploadA).toEqual(uploadB)
const { data: profileA } = await aliceClient.app.bsky.actor.updateProfile({
displayName: 'Alice',
avatar: { cid: uploadA.cid, mimeType: 'image/jpeg' },
})
const { data: profileA } =
await aliceAgent.api.app.bsky.actor.updateProfile({
displayName: 'Alice',
avatar: { cid: uploadA.cid, mimeType: 'image/jpeg' },
})
expect((profileA.record as any).avatar.cid).toEqual(uploadA.cid)
const { data: profileB } = await bobClient.app.bsky.actor.updateProfile({
const { data: profileB } = await bobAgent.api.app.bsky.actor.updateProfile({
displayName: 'Bob',
avatar: { cid: uploadB.cid, mimeType: 'image/jpeg' },
})
expect((profileB.record as any).avatar.cid).toEqual(uploadA.cid)
const { data: uploadAfterPermanent } =
await aliceClient.com.atproto.blob.upload(file, {
await aliceAgent.api.com.atproto.blob.upload(file, {
encoding: 'image/jpeg',
} as any)
expect(uploadAfterPermanent).toEqual(uploadA)
@ -219,7 +223,7 @@ describe('file uploads', () => {
})
it('supports compression during upload', async () => {
const { data: uploaded } = await aliceClient.com.atproto.blob.upload(
const { data: uploaded } = await aliceAgent.api.com.atproto.blob.upload(
gzipSync(smallFile),
{
encoding: 'image/jpeg',
@ -235,7 +239,7 @@ describe('file uploads', () => {
const file = await fs.readFile(
'tests/image/fixtures/key-landscape-large.jpg',
)
const res = await aliceClient.com.atproto.blob.upload(file, {
const res = await aliceAgent.api.com.atproto.blob.upload(file, {
encoding: 'video/mp4',
} as any)
@ -252,7 +256,7 @@ describe('file uploads', () => {
it('handles unknown mimetypes', async () => {
const file = await randomBytes(20000)
const res = await aliceClient.com.atproto.blob.upload(file, {
const res = await aliceAgent.api.com.atproto.blob.upload(file, {
encoding: 'test/fake',
} as any)

@ -1,4 +1,4 @@
import AtpApi from '@atproto/api'
import AtpAgent from '@atproto/api'
import { Database } from '../../src'
import { Kysely } from 'kysely'
import { CloseFn, runTestServer } from '../_util'
@ -17,8 +17,8 @@ describe('repo sync data migration', () => {
db = server.ctx.db
rawDb = db.db
close = server.close
const client = AtpApi.service(server.url)
const sc = new SeedClient(client)
const agent = new AtpAgent({ service: server.url })
const sc = new SeedClient(agent)
await basicSeed(sc)
})

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { AtUri } from '@atproto/uri'
import {
adminAuth,
@ -21,7 +21,7 @@ import { BlobNotFoundError } from '@atproto/repo'
describe('moderation', () => {
let server: TestServerInfo
let close: CloseFn
let client: AtpServiceClient
let agent: AtpAgent
let sc: SeedClient
beforeAll(async () => {
@ -29,8 +29,8 @@ describe('moderation', () => {
dbPostgresSchema: 'moderation',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc)
})
@ -40,7 +40,7 @@ describe('moderation', () => {
describe('reporting', () => {
it('creates reports of a repo.', async () => {
const { data: reportA } = await client.com.atproto.report.create(
const { data: reportA } = await agent.api.com.atproto.report.create(
{
reasonType: SPAM,
subject: {
@ -50,7 +50,7 @@ describe('moderation', () => {
},
{ headers: sc.getHeaders(sc.dids.alice), encoding: 'application/json' },
)
const { data: reportB } = await client.com.atproto.report.create(
const { data: reportB } = await agent.api.com.atproto.report.create(
{
reasonType: OTHER,
reason: 'impersonation',
@ -65,7 +65,7 @@ describe('moderation', () => {
})
it("fails reporting a repo that doesn't exist.", async () => {
const promise = client.com.atproto.report.create(
const promise = agent.api.com.atproto.report.create(
{
reasonType: SPAM,
subject: {
@ -81,7 +81,7 @@ describe('moderation', () => {
it('creates reports of a record.', async () => {
const postA = sc.posts[sc.dids.bob][0].ref
const postB = sc.posts[sc.dids.bob][1].ref
const { data: reportA } = await client.com.atproto.report.create(
const { data: reportA } = await agent.api.com.atproto.report.create(
{
reasonType: SPAM,
subject: {
@ -91,7 +91,7 @@ describe('moderation', () => {
},
{ headers: sc.getHeaders(sc.dids.alice), encoding: 'application/json' },
)
const { data: reportB } = await client.com.atproto.report.create(
const { data: reportB } = await agent.api.com.atproto.report.create(
{
reasonType: OTHER,
reason: 'defamation',
@ -112,7 +112,7 @@ describe('moderation', () => {
const postUriBad = new AtUri(postA.uriStr)
postUriBad.rkey = 'badrkey'
const promiseA = client.com.atproto.report.create(
const promiseA = agent.api.com.atproto.report.create(
{
reasonType: SPAM,
subject: {
@ -124,7 +124,7 @@ describe('moderation', () => {
)
await expect(promiseA).rejects.toThrow('Record not found')
const promiseB = client.com.atproto.report.create(
const promiseB = agent.api.com.atproto.report.create(
{
reasonType: OTHER,
reason: 'defamation',
@ -142,7 +142,7 @@ describe('moderation', () => {
describe('actioning', () => {
it('resolves reports on repos and records.', async () => {
const { data: reportA } = await client.com.atproto.report.create(
const { data: reportA } = await agent.api.com.atproto.report.create(
{
reasonType: SPAM,
subject: {
@ -153,7 +153,7 @@ describe('moderation', () => {
{ headers: sc.getHeaders(sc.dids.alice), encoding: 'application/json' },
)
const post = sc.posts[sc.dids.bob][1].ref
const { data: reportB } = await client.com.atproto.report.create(
const { data: reportB } = await agent.api.com.atproto.report.create(
{
reasonType: OTHER,
reason: 'defamation',
@ -165,7 +165,7 @@ describe('moderation', () => {
{ headers: sc.getHeaders(sc.dids.carol), encoding: 'application/json' },
)
const { data: action } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -181,7 +181,7 @@ describe('moderation', () => {
},
)
const { data: actionResolvedReports } =
await client.com.atproto.admin.resolveModerationReports(
await agent.api.com.atproto.admin.resolveModerationReports(
{
actionId: action.id,
reportIds: [reportB.id, reportA.id],
@ -196,7 +196,7 @@ describe('moderation', () => {
expect(forSnapshot(actionResolvedReports)).toMatchSnapshot()
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: action.id,
createdBy: 'X',
@ -210,7 +210,7 @@ describe('moderation', () => {
})
it('does not resolve report for mismatching repo.', async () => {
const { data: report } = await client.com.atproto.report.create(
const { data: report } = await agent.api.com.atproto.report.create(
{
reasonType: SPAM,
subject: {
@ -221,7 +221,7 @@ describe('moderation', () => {
{ headers: sc.getHeaders(sc.dids.alice), encoding: 'application/json' },
)
const { data: action } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -237,7 +237,7 @@ describe('moderation', () => {
},
)
const promise = client.com.atproto.admin.resolveModerationReports(
const promise = agent.api.com.atproto.admin.resolveModerationReports(
{
actionId: action.id,
reportIds: [report.id],
@ -254,7 +254,7 @@ describe('moderation', () => {
)
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: action.id,
createdBy: 'X',
@ -270,7 +270,7 @@ describe('moderation', () => {
it('does not resolve report for mismatching record.', async () => {
const postUri1 = sc.posts[sc.dids.alice][0].ref.uri
const postUri2 = sc.posts[sc.dids.bob][0].ref.uri
const { data: report } = await client.com.atproto.report.create(
const { data: report } = await agent.api.com.atproto.report.create(
{
reasonType: SPAM,
subject: {
@ -281,7 +281,7 @@ describe('moderation', () => {
{ headers: sc.getHeaders(sc.dids.alice), encoding: 'application/json' },
)
const { data: action } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -297,7 +297,7 @@ describe('moderation', () => {
},
)
const promise = client.com.atproto.admin.resolveModerationReports(
const promise = agent.api.com.atproto.admin.resolveModerationReports(
{
actionId: action.id,
reportIds: [report.id],
@ -314,7 +314,7 @@ describe('moderation', () => {
)
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: action.id,
createdBy: 'X',
@ -331,7 +331,7 @@ describe('moderation', () => {
const postRef1 = sc.posts[sc.dids.alice][0].ref
const postRef2 = sc.posts[sc.dids.bob][0].ref
const { data: action1 } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: FLAG,
subject: {
@ -357,7 +357,7 @@ describe('moderation', () => {
}),
)
const { data: action2 } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: ACKNOWLEDGE,
subject: {
@ -383,7 +383,7 @@ describe('moderation', () => {
}),
)
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: action1.id,
createdBy: 'X',
@ -394,7 +394,7 @@ describe('moderation', () => {
headers: { authorization: adminAuth() },
},
)
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: action2.id,
createdBy: 'X',
@ -410,7 +410,7 @@ describe('moderation', () => {
it('only allows record to have one current action.', async () => {
const postUri = sc.posts[sc.dids.alice][0].ref.uri
const { data: acknowledge } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: ACKNOWLEDGE,
subject: {
@ -425,7 +425,7 @@ describe('moderation', () => {
headers: { authorization: adminAuth() },
},
)
const flagPromise = client.com.atproto.admin.takeModerationAction(
const flagPromise = agent.api.com.atproto.admin.takeModerationAction(
{
action: FLAG,
subject: {
@ -445,7 +445,7 @@ describe('moderation', () => {
)
// Reverse current then retry
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: acknowledge.id,
createdBy: 'X',
@ -457,7 +457,7 @@ describe('moderation', () => {
},
)
const { data: flag } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: FLAG,
subject: {
@ -474,7 +474,7 @@ describe('moderation', () => {
)
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: flag.id,
createdBy: 'X',
@ -489,7 +489,7 @@ describe('moderation', () => {
it('only allows repo to have one current action.', async () => {
const { data: acknowledge } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: ACKNOWLEDGE,
subject: {
@ -504,7 +504,7 @@ describe('moderation', () => {
headers: { authorization: adminAuth() },
},
)
const flagPromise = client.com.atproto.admin.takeModerationAction(
const flagPromise = agent.api.com.atproto.admin.takeModerationAction(
{
action: FLAG,
subject: {
@ -524,7 +524,7 @@ describe('moderation', () => {
)
// Reverse current then retry
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: acknowledge.id,
createdBy: 'X',
@ -536,7 +536,7 @@ describe('moderation', () => {
},
)
const { data: flag } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: FLAG,
subject: {
@ -553,7 +553,7 @@ describe('moderation', () => {
)
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: flag.id,
createdBy: 'X',
@ -571,7 +571,7 @@ describe('moderation', () => {
const postA = await sc.post(sc.dids.alice, 'image A', undefined, [img])
const postB = await sc.post(sc.dids.alice, 'image B', undefined, [img])
const { data: acknowledge } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: ACKNOWLEDGE,
subject: {
@ -587,7 +587,7 @@ describe('moderation', () => {
headers: { authorization: adminAuth() },
},
)
const flagPromise = client.com.atproto.admin.takeModerationAction(
const flagPromise = agent.api.com.atproto.admin.takeModerationAction(
{
action: FLAG,
subject: {
@ -607,7 +607,7 @@ describe('moderation', () => {
'Blob already has an active action:',
)
// Reverse current then retry
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: acknowledge.id,
createdBy: 'X',
@ -619,7 +619,7 @@ describe('moderation', () => {
},
)
const { data: flag } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: FLAG,
subject: {
@ -637,7 +637,7 @@ describe('moderation', () => {
)
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: flag.id,
createdBy: 'X',
@ -666,7 +666,7 @@ describe('moderation', () => {
await fetch(imageUri)
const cached = await fetch(imageUri)
expect(cached.headers.get('x-cache')).toEqual('hit')
const takeAction = await client.com.atproto.admin.takeModerationAction(
const takeAction = await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -710,7 +710,7 @@ describe('moderation', () => {
})
it('restores blob when action is reversed.', async () => {
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: actionId,
createdBy: 'X',

@ -1,5 +1,5 @@
import fs from 'fs/promises'
import { ServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { InputSchema as TakeActionInput } from '@atproto/api/src/client/types/com/atproto/admin/takeModerationAction'
import { InputSchema as CreateReportInput } from '@atproto/api/src/client/types/com/atproto/report/create'
import { AtUri } from '@atproto/uri'
@ -94,7 +94,7 @@ export class SeedClient {
reposts: Record<string, RecordRef[]>
dids: Record<string, string>
constructor(public client: ServiceClient) {
constructor(public agent: AtpAgent) {
this.accounts = {}
this.profiles = {}
this.follows = {}
@ -113,10 +113,10 @@ export class SeedClient {
password: string
},
) {
const { data: account } = await this.client.com.atproto.account.create(
const { data: account } = await this.agent.api.com.atproto.account.create(
params,
)
const { data: profile } = await this.client.app.bsky.actor.getProfile(
const { data: profile } = await this.agent.api.app.bsky.actor.getProfile(
{
actor: params.handle,
},
@ -144,7 +144,7 @@ export class SeedClient {
let avatarCid
{
const res = await this.client.com.atproto.blob.upload(AVATAR_IMG, {
const res = await this.agent.api.com.atproto.blob.upload(AVATAR_IMG, {
encoding: 'image/jpeg',
headers: this.getHeaders(fromUser || by),
} as any)
@ -152,7 +152,7 @@ export class SeedClient {
}
{
const res = await this.client.app.bsky.actor.profile.create(
const res = await this.agent.api.app.bsky.actor.profile.create(
{ did: by },
{
displayName,
@ -172,7 +172,7 @@ export class SeedClient {
}
async follow(from: string, to: ActorRef) {
const res = await this.client.app.bsky.graph.follow.create(
const res = await this.agent.api.app.bsky.graph.follow.create(
{ did: from },
{
subject: to.raw,
@ -192,7 +192,7 @@ export class SeedClient {
images,
}
: undefined
const res = await this.client.app.bsky.feed.post.create(
const res = await this.agent.api.app.bsky.feed.post.create(
{ did: by },
{
text: text,
@ -213,7 +213,7 @@ export class SeedClient {
}
async deletePost(by: string, uri: AtUri) {
await this.client.app.bsky.feed.post.delete(
await this.agent.api.app.bsky.feed.post.delete(
{
did: by,
rkey: uri.rkey,
@ -228,7 +228,7 @@ export class SeedClient {
encoding: string,
): Promise<ImageRef> {
const file = await fs.readFile(filePath)
const res = await this.client.com.atproto.blob.upload(file, {
const res = await this.agent.api.com.atproto.blob.upload(file, {
headers: this.getHeaders(by),
encoding,
} as any)
@ -236,7 +236,7 @@ export class SeedClient {
}
async vote(direction: 'up' | 'down', by: string, subject: RecordRef) {
const res = await this.client.app.bsky.feed.vote.create(
const res = await this.agent.api.app.bsky.feed.vote.create(
{ did: by },
{ direction, subject: subject.raw, createdAt: new Date().toISOString() },
this.getHeaders(by),
@ -260,7 +260,7 @@ export class SeedClient {
images,
}
: undefined
const res = await this.client.app.bsky.feed.post.create(
const res = await this.agent.api.app.bsky.feed.post.create(
{ did: by },
{
text: text,
@ -284,7 +284,7 @@ export class SeedClient {
}
async repost(by: string, subject: RecordRef) {
const res = await this.client.app.bsky.feed.repost.create(
const res = await this.agent.api.app.bsky.feed.repost.create(
{ did: by },
{ subject: subject.raw, createdAt: new Date().toISOString() },
this.getHeaders(by),
@ -306,7 +306,7 @@ export class SeedClient {
createdBy?: string
}) {
const { action, subject, reason = 'X', createdBy = 'Y' } = opts
const result = await this.client.com.atproto.admin.takeModerationAction(
const result = await this.agent.api.com.atproto.admin.takeModerationAction(
{ action, subject, createdBy, reason },
{
encoding: 'application/json',
@ -322,13 +322,14 @@ export class SeedClient {
createdBy?: string
}) {
const { id, reason = 'X', createdBy = 'Y' } = opts
const result = await this.client.com.atproto.admin.reverseModerationAction(
{ id, reason, createdBy },
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
},
)
const result =
await this.agent.api.com.atproto.admin.reverseModerationAction(
{ id, reason, createdBy },
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
},
)
return result.data
}
@ -338,13 +339,14 @@ export class SeedClient {
createdBy?: string
}) {
const { actionId, reportIds, createdBy = 'Y' } = opts
const result = await this.client.com.atproto.admin.resolveModerationReports(
{ actionId, createdBy, reportIds },
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
},
)
const result =
await this.agent.api.com.atproto.admin.resolveModerationReports(
{ actionId, createdBy, reportIds },
{
encoding: 'application/json',
headers: { authorization: adminAuth() },
},
)
return result.data
}
@ -355,7 +357,7 @@ export class SeedClient {
reportedByDid: string
}) {
const { reasonType, subject, reason, reportedByDid } = opts
const result = await this.client.com.atproto.report.create(
const result = await this.agent.api.com.atproto.report.create(
{ reasonType, subject, reason },
{
encoding: 'application/json',

@ -1,7 +1,7 @@
import { AddressInfo } from 'net'
import express from 'express'
import axios, { AxiosError } from 'axios'
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { CloseFn, runTestServer, TestServerInfo } from './_util'
import { handler as errorHandler } from '../src/error'
import { SeedClient } from './seeds/client'
@ -12,7 +12,7 @@ describe('server', () => {
let server: TestServerInfo
let close: CloseFn
let db: Database
let client: AtpServiceClient
let agent: AtpAgent
let sc: SeedClient
let alice: string
@ -22,8 +22,8 @@ describe('server', () => {
})
close = server.close
db = server.ctx.db
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await usersSeed(sc)
alice = sc.dids.alice
})

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { TID } from '@atproto/common'
import { randomStr } from '@atproto/crypto'
import { DidResolver } from '@atproto/did-resolver'
@ -9,7 +9,7 @@ import { CID } from 'multiformats/cid'
import { CloseFn, runTestServer } from './_util'
describe('repo sync', () => {
let client: AtpServiceClient
let agent: AtpAgent
let did: string
const repoData: repo.RepoContents = {}
@ -25,13 +25,13 @@ describe('repo sync', () => {
dbPostgresSchema: 'repo_sync',
})
close = server.close
client = AtpApi.service(server.url)
const res = await client.com.atproto.account.create({
agent = new AtpAgent({ service: server.url })
const res = await agent.api.com.atproto.account.create({
email: 'alice@test.com',
handle: 'alice.test',
password: 'alice-pass',
})
client.setHeader('authorization', `Bearer ${res.data.accessJwt}`)
agent.api.setHeader('authorization', `Bearer ${res.data.accessJwt}`)
did = res.data.did
didResolver = new DidResolver({ plcUrl: server.ctx.cfg.didPlcUrl })
repoData['app.bsky.system.declaration'] = {
@ -49,7 +49,7 @@ describe('repo sync', () => {
it('creates and syncs some records', async () => {
const ADD_COUNT = 10
for (let i = 0; i < ADD_COUNT; i++) {
const { obj, uri } = await makePost(client, did)
const { obj, uri } = await makePost(agent, did)
if (!repoData[uri.collection]) {
repoData[uri.collection] = {}
}
@ -57,7 +57,7 @@ describe('repo sync', () => {
uris.push(uri)
}
const car = await client.com.atproto.sync.getRepo({ did })
const car = await agent.api.com.atproto.sync.getRepo({ did })
const synced = await repo.loadFullRepo(
storage,
new Uint8Array(car.data),
@ -77,7 +77,7 @@ describe('repo sync', () => {
const ADD_COUNT = 10
const DEL_COUNT = 4
for (let i = 0; i < ADD_COUNT; i++) {
const { obj, uri } = await makePost(client, did)
const { obj, uri } = await makePost(agent, did)
if (!repoData[uri.collection]) {
repoData[uri.collection] = {}
}
@ -87,7 +87,7 @@ describe('repo sync', () => {
// delete two that are already sync & two that have not been
for (let i = 0; i < DEL_COUNT; i++) {
const uri = uris[i * 5]
await client.app.bsky.feed.post.delete({
await agent.api.app.bsky.feed.post.delete({
did,
colleciton: uri.collection,
rkey: uri.rkey,
@ -95,7 +95,7 @@ describe('repo sync', () => {
delete repoData[uri.collection][uri.rkey]
}
const car = await client.com.atproto.sync.getRepo({
const car = await agent.api.com.atproto.sync.getRepo({
did,
from: currRoot?.toString(),
})
@ -116,7 +116,7 @@ describe('repo sync', () => {
})
it('syncs current root', async () => {
const root = await client.com.atproto.sync.getHead({ did })
const root = await agent.api.com.atproto.sync.getHead({ did })
expect(root.data.root).toEqual(currRoot?.toString())
})
@ -126,10 +126,10 @@ describe('repo sync', () => {
throw new Error('Could not get local commit path')
}
const localStr = local.map((c) => c.toString())
const commitPath = await client.com.atproto.sync.getCommitPath({ did })
const commitPath = await agent.api.com.atproto.sync.getCommitPath({ did })
expect(commitPath.data.commits).toEqual(localStr)
const partialCommitPath = await client.com.atproto.sync.getCommitPath({
const partialCommitPath = await agent.api.com.atproto.sync.getCommitPath({
did,
earliest: localStr[2],
latest: localStr[15],
@ -138,7 +138,7 @@ describe('repo sync', () => {
})
it('sync a repo checkout', async () => {
const car = await client.com.atproto.sync.getCheckout({ did })
const car = await agent.api.com.atproto.sync.getCheckout({ did })
const checkoutStorage = new MemoryBlockstore()
const loaded = await repo.loadCheckout(
checkoutStorage,
@ -153,7 +153,7 @@ describe('repo sync', () => {
it('sync a record proof', async () => {
const collection = Object.keys(repoData)[0]
const rkey = Object.keys(repoData[collection])[0]
const car = await client.com.atproto.sync.getRecord({
const car = await agent.api.com.atproto.sync.getRecord({
did,
collection,
rkey,
@ -183,7 +183,7 @@ describe('repo sync', () => {
it('sync a proof of non-existence', async () => {
const collection = Object.keys(repoData)[0]
const rkey = TID.nextStr() // rkey that doesn't exist
const car = await client.com.atproto.sync.getRecord({
const car = await agent.api.com.atproto.sync.getRecord({
did,
collection,
rkey,
@ -204,13 +204,13 @@ describe('repo sync', () => {
})
})
const makePost = async (client: AtpServiceClient, did: string) => {
const makePost = async (agent: AtpAgent, did: string) => {
const obj = {
$type: 'app.bsky.feed.post',
text: randomStr(32, 'base32'),
createdAt: new Date().toISOString(),
}
const res = await client.app.bsky.feed.post.create({ did }, obj)
const res = await agent.api.app.bsky.feed.post.create({ did }, obj)
const uri = new AtUri(res.uri)
return { obj, uri }
}

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import {
FLAG,
TAKEDOWN,
@ -12,7 +12,7 @@ import { SeedClient } from '../../seeds/client'
import basicSeed from '../../seeds/basic'
describe('pds admin get moderation action view', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
@ -21,8 +21,8 @@ describe('pds admin get moderation action view', () => {
dbPostgresSchema: 'views_admin_get_moderation_action',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc)
})
@ -74,7 +74,7 @@ describe('pds admin get moderation action view', () => {
})
it('gets moderation action for a repo.', async () => {
const result = await client.com.atproto.admin.getModerationAction(
const result = await agent.api.com.atproto.admin.getModerationAction(
{ id: 1 },
{ headers: { authorization: adminAuth() } },
)
@ -82,7 +82,7 @@ describe('pds admin get moderation action view', () => {
})
it('gets moderation action for a record.', async () => {
const result = await client.com.atproto.admin.getModerationAction(
const result = await agent.api.com.atproto.admin.getModerationAction(
{ id: 2 },
{ headers: { authorization: adminAuth() } },
)
@ -90,7 +90,7 @@ describe('pds admin get moderation action view', () => {
})
it('fails when moderation action does not exist.', async () => {
const promise = client.com.atproto.admin.getModerationAction(
const promise = agent.api.com.atproto.admin.getModerationAction(
{ id: 100 },
{ headers: { authorization: adminAuth() } },
)

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import {
ACKNOWLEDGE,
FLAG,
@ -19,7 +19,7 @@ import { SeedClient } from '../../seeds/client'
import basicSeed from '../../seeds/basic'
describe('pds admin get moderation actions view', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
@ -28,8 +28,8 @@ describe('pds admin get moderation actions view', () => {
dbPostgresSchema: 'views_admin_get_moderation_actions',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc)
})
@ -120,7 +120,7 @@ describe('pds admin get moderation actions view', () => {
})
it('gets all moderation actions.', async () => {
const result = await client.com.atproto.admin.getModerationActions(
const result = await agent.api.com.atproto.admin.getModerationActions(
{},
{ headers: { authorization: adminAuth() } },
)
@ -128,7 +128,7 @@ describe('pds admin get moderation actions view', () => {
})
it('gets all moderation actions for a repo.', async () => {
const result = await client.com.atproto.admin.getModerationActions(
const result = await agent.api.com.atproto.admin.getModerationActions(
{ subject: Object.values(sc.dids)[0] },
{ headers: { authorization: adminAuth() } },
)
@ -136,7 +136,7 @@ describe('pds admin get moderation actions view', () => {
})
it('gets all moderation actions for a record.', async () => {
const result = await client.com.atproto.admin.getModerationActions(
const result = await agent.api.com.atproto.admin.getModerationActions(
{ subject: Object.values(sc.posts)[0][0].ref.uriStr },
{ headers: { authorization: adminAuth() } },
)
@ -146,7 +146,7 @@ describe('pds admin get moderation actions view', () => {
it('paginates.', async () => {
const results = (results) => results.flatMap((res) => res.actions)
const paginator = async (cursor?: string) => {
const res = await client.com.atproto.admin.getModerationActions(
const res = await agent.api.com.atproto.admin.getModerationActions(
{ before: cursor, limit: 3 },
{ headers: { authorization: adminAuth() } },
)
@ -158,7 +158,7 @@ describe('pds admin get moderation actions view', () => {
expect(res.actions.length).toBeLessThanOrEqual(3),
)
const full = await client.com.atproto.admin.getModerationActions(
const full = await agent.api.com.atproto.admin.getModerationActions(
{},
{ headers: { authorization: adminAuth() } },
)

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import {
FLAG,
TAKEDOWN,
@ -12,7 +12,7 @@ import { SeedClient } from '../../seeds/client'
import basicSeed from '../../seeds/basic'
describe('pds admin get moderation action view', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
@ -21,8 +21,8 @@ describe('pds admin get moderation action view', () => {
dbPostgresSchema: 'views_admin_get_moderation_report',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc)
})
@ -74,7 +74,7 @@ describe('pds admin get moderation action view', () => {
})
it('gets moderation report for a repo.', async () => {
const result = await client.com.atproto.admin.getModerationReport(
const result = await agent.api.com.atproto.admin.getModerationReport(
{ id: 1 },
{ headers: { authorization: adminAuth() } },
)
@ -82,7 +82,7 @@ describe('pds admin get moderation action view', () => {
})
it('gets moderation report for a record.', async () => {
const result = await client.com.atproto.admin.getModerationReport(
const result = await agent.api.com.atproto.admin.getModerationReport(
{ id: 2 },
{ headers: { authorization: adminAuth() } },
)
@ -90,7 +90,7 @@ describe('pds admin get moderation action view', () => {
})
it('fails when moderation report does not exist.', async () => {
const promise = client.com.atproto.admin.getModerationReport(
const promise = agent.api.com.atproto.admin.getModerationReport(
{ id: 100 },
{ headers: { authorization: adminAuth() } },
)

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import {
ACKNOWLEDGE,
FLAG,
@ -19,7 +19,7 @@ import { SeedClient } from '../../seeds/client'
import basicSeed from '../../seeds/basic'
describe('pds admin get moderation reports view', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
@ -28,8 +28,8 @@ describe('pds admin get moderation reports view', () => {
dbPostgresSchema: 'views_admin_get_moderation_reports',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc)
})
@ -119,7 +119,7 @@ describe('pds admin get moderation reports view', () => {
})
it('gets all moderation reports.', async () => {
const result = await client.com.atproto.admin.getModerationReports(
const result = await agent.api.com.atproto.admin.getModerationReports(
{},
{ headers: { authorization: adminAuth() } },
)
@ -127,7 +127,7 @@ describe('pds admin get moderation reports view', () => {
})
it('gets all moderation reports for a repo.', async () => {
const result = await client.com.atproto.admin.getModerationReports(
const result = await agent.api.com.atproto.admin.getModerationReports(
{ subject: Object.values(sc.dids)[0] },
{ headers: { authorization: adminAuth() } },
)
@ -135,7 +135,7 @@ describe('pds admin get moderation reports view', () => {
})
it('gets all moderation reports for a record.', async () => {
const result = await client.com.atproto.admin.getModerationReports(
const result = await agent.api.com.atproto.admin.getModerationReports(
{ subject: Object.values(sc.posts)[0][0].ref.uriStr },
{ headers: { authorization: adminAuth() } },
)
@ -143,12 +143,12 @@ describe('pds admin get moderation reports view', () => {
})
it('gets all resolved/unresolved moderation reports.', async () => {
const resolved = await client.com.atproto.admin.getModerationReports(
const resolved = await agent.api.com.atproto.admin.getModerationReports(
{ resolved: true },
{ headers: { authorization: adminAuth() } },
)
expect(forSnapshot(resolved.data.reports)).toMatchSnapshot()
const unresolved = await client.com.atproto.admin.getModerationReports(
const unresolved = await agent.api.com.atproto.admin.getModerationReports(
{ resolved: false },
{ headers: { authorization: adminAuth() } },
)
@ -158,7 +158,7 @@ describe('pds admin get moderation reports view', () => {
it('paginates.', async () => {
const results = (results) => results.flatMap((res) => res.reports)
const paginator = async (cursor?: string) => {
const res = await client.com.atproto.admin.getModerationReports(
const res = await agent.api.com.atproto.admin.getModerationReports(
{ before: cursor, limit: 3 },
{ headers: { authorization: adminAuth() } },
)
@ -170,7 +170,7 @@ describe('pds admin get moderation reports view', () => {
expect(res.reports.length).toBeLessThanOrEqual(3),
)
const full = await client.com.atproto.admin.getModerationReports(
const full = await agent.api.com.atproto.admin.getModerationReports(
{},
{ headers: { authorization: adminAuth() } },
)

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { AtUri } from '@atproto/uri'
import {
ACKNOWLEDGE,
@ -13,7 +13,7 @@ import { SeedClient } from '../../seeds/client'
import basicSeed from '../../seeds/basic'
describe('pds admin get record view', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
@ -22,8 +22,8 @@ describe('pds admin get record view', () => {
dbPostgresSchema: 'views_admin_get_record',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc)
})
@ -67,7 +67,7 @@ describe('pds admin get record view', () => {
})
it('gets a record by uri, even when taken down.', async () => {
const result = await client.com.atproto.admin.getRecord(
const result = await agent.api.com.atproto.admin.getRecord(
{ uri: sc.posts[sc.dids.alice][0].ref.uriStr },
{ headers: { authorization: adminAuth() } },
)
@ -75,7 +75,7 @@ describe('pds admin get record view', () => {
})
it('gets a record by uri and cid.', async () => {
const result = await client.com.atproto.admin.getRecord(
const result = await agent.api.com.atproto.admin.getRecord(
{
uri: sc.posts[sc.dids.alice][0].ref.uriStr,
cid: sc.posts[sc.dids.alice][0].ref.cidStr,
@ -86,7 +86,7 @@ describe('pds admin get record view', () => {
})
it('fails when record does not exist.', async () => {
const promise = client.com.atproto.admin.getRecord(
const promise = agent.api.com.atproto.admin.getRecord(
{
uri: AtUri.make(
sc.dids.alice,
@ -100,7 +100,7 @@ describe('pds admin get record view', () => {
})
it('fails when record cid does not exist.', async () => {
const promise = client.com.atproto.admin.getRecord(
const promise = agent.api.com.atproto.admin.getRecord(
{
uri: sc.posts[sc.dids.alice][0].ref.uriStr,
cid: sc.posts[sc.dids.alice][1].ref.cidStr, // Mismatching cid

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import {
ACKNOWLEDGE,
TAKEDOWN,
@ -12,7 +12,7 @@ import { SeedClient } from '../../seeds/client'
import basicSeed from '../../seeds/basic'
describe('pds admin get repo view', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
@ -21,8 +21,8 @@ describe('pds admin get repo view', () => {
dbPostgresSchema: 'views_admin_get_repo',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc)
})
@ -66,7 +66,7 @@ describe('pds admin get repo view', () => {
})
it('gets a repo by did, even when taken down.', async () => {
const result = await client.com.atproto.admin.getRepo(
const result = await agent.api.com.atproto.admin.getRepo(
{ did: sc.dids.alice },
{ headers: { authorization: adminAuth() } },
)
@ -74,7 +74,7 @@ describe('pds admin get repo view', () => {
})
it('fails when repo does not exist.', async () => {
const promise = client.com.atproto.admin.getRepo(
const promise = agent.api.com.atproto.admin.getRepo(
{ did: 'did:plc:doesnotexist' },
{ headers: { authorization: adminAuth() } },
)

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/moderationAction'
import {
runTestServer,
@ -12,7 +12,7 @@ import usersBulkSeed from '../../seeds/users-bulk'
import { Database } from '../../../src'
describe('pds admin repo search view', () => {
let client: AtpServiceClient
let agent: AtpAgent
let db: Database
let close: CloseFn
let sc: SeedClient
@ -24,8 +24,8 @@ describe('pds admin repo search view', () => {
})
close = server.close
db = server.ctx.db
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await usersBulkSeed(sc)
headers = { authorization: adminAuth() }
})
@ -45,7 +45,7 @@ describe('pds admin repo search view', () => {
})
it('gives relevant results', async () => {
const result = await client.com.atproto.admin.searchRepos(
const result = await agent.api.com.atproto.admin.searchRepos(
{ term: 'car' },
{ headers },
)
@ -91,7 +91,7 @@ describe('pds admin repo search view', () => {
it('paginates with term', async () => {
const results = (results) => results.flatMap((res) => res.users)
const paginator = async (cursor?: string) => {
const res = await client.com.atproto.admin.searchRepos(
const res = await agent.api.com.atproto.admin.searchRepos(
{ term: 'p', before: cursor, limit: 3 },
{ headers },
)
@ -103,7 +103,7 @@ describe('pds admin repo search view', () => {
expect(res.repos.length).toBeLessThanOrEqual(3),
)
const full = await client.com.atproto.admin.searchRepos(
const full = await agent.api.com.atproto.admin.searchRepos(
{ term: 'p' },
{ headers },
)
@ -115,7 +115,7 @@ describe('pds admin repo search view', () => {
it('paginates without term', async () => {
const results = (results) => results.flatMap((res) => res.repos)
const paginator = async (cursor?: string) => {
const res = await client.com.atproto.admin.searchRepos(
const res = await agent.api.com.atproto.admin.searchRepos(
{ before: cursor, limit: 3 },
{ headers },
)
@ -127,7 +127,7 @@ describe('pds admin repo search view', () => {
expect(res.repos.length).toBeLessThanOrEqual(3),
)
const full = await client.com.atproto.admin.searchRepos(
const full = await agent.api.com.atproto.admin.searchRepos(
{ limit: 15 },
{ headers },
)

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { AtUri } from '@atproto/uri'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/moderationAction'
import {
@ -12,7 +12,7 @@ import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
describe('pds author feed views', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
@ -27,8 +27,8 @@ describe('pds author feed views', () => {
dbPostgresSchema: 'views_author_feed',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc, server.ctx.messageQueue)
alice = sc.dids.alice
bob = sc.dids.bob
@ -41,7 +41,7 @@ describe('pds author feed views', () => {
})
it('fetches full author feeds for self (sorted, minimal myState).', async () => {
const aliceForAlice = await client.app.bsky.feed.getAuthorFeed(
const aliceForAlice = await agent.api.app.bsky.feed.getAuthorFeed(
{ author: sc.accounts[alice].handle },
{
headers: sc.getHeaders(alice),
@ -50,7 +50,7 @@ describe('pds author feed views', () => {
expect(forSnapshot(aliceForAlice.data.feed)).toMatchSnapshot()
const bobForBob = await client.app.bsky.feed.getAuthorFeed(
const bobForBob = await agent.api.app.bsky.feed.getAuthorFeed(
{ author: sc.accounts[bob].handle },
{
headers: sc.getHeaders(bob),
@ -59,7 +59,7 @@ describe('pds author feed views', () => {
expect(forSnapshot(bobForBob.data.feed)).toMatchSnapshot()
const carolForCarol = await client.app.bsky.feed.getAuthorFeed(
const carolForCarol = await agent.api.app.bsky.feed.getAuthorFeed(
{ author: sc.accounts[carol].handle },
{
headers: sc.getHeaders(carol),
@ -68,7 +68,7 @@ describe('pds author feed views', () => {
expect(forSnapshot(carolForCarol.data.feed)).toMatchSnapshot()
const danForDan = await client.app.bsky.feed.getAuthorFeed(
const danForDan = await agent.api.app.bsky.feed.getAuthorFeed(
{ author: sc.accounts[dan].handle },
{
headers: sc.getHeaders(dan),
@ -79,7 +79,7 @@ describe('pds author feed views', () => {
})
it("reflects fetching user's state in the feed.", async () => {
const aliceForCarol = await client.app.bsky.feed.getAuthorFeed(
const aliceForCarol = await agent.api.app.bsky.feed.getAuthorFeed(
{ author: sc.accounts[alice].handle },
{
headers: sc.getHeaders(carol),
@ -97,26 +97,26 @@ describe('pds author feed views', () => {
})
it('omits reposts from muted users.', async () => {
await client.app.bsky.graph.mute(
await agent.api.app.bsky.graph.mute(
{ user: alice }, // Has a repost by dan: will be omitted from dan's feed
{ headers: sc.getHeaders(bob), encoding: 'application/json' },
)
await client.app.bsky.graph.mute(
await agent.api.app.bsky.graph.mute(
{ user: dan }, // Feed author: their posts will still appear
{ headers: sc.getHeaders(bob), encoding: 'application/json' },
)
const bobForDan = await client.app.bsky.feed.getAuthorFeed(
const bobForDan = await agent.api.app.bsky.feed.getAuthorFeed(
{ author: sc.accounts[dan].handle },
{ headers: sc.getHeaders(bob) },
)
expect(forSnapshot(bobForDan.data.feed)).toMatchSnapshot()
await client.app.bsky.graph.unmute(
await agent.api.app.bsky.graph.unmute(
{ user: alice },
{ headers: sc.getHeaders(bob), encoding: 'application/json' },
)
await client.app.bsky.graph.unmute(
await agent.api.app.bsky.graph.unmute(
{ user: dan },
{ headers: sc.getHeaders(bob), encoding: 'application/json' },
)
@ -125,7 +125,7 @@ describe('pds author feed views', () => {
it('paginates', async () => {
const results = (results) => results.flatMap((res) => res.feed)
const paginator = async (cursor?: string) => {
const res = await client.app.bsky.feed.getAuthorFeed(
const res = await agent.api.app.bsky.feed.getAuthorFeed(
{
author: sc.accounts[alice].handle,
before: cursor,
@ -141,7 +141,7 @@ describe('pds author feed views', () => {
expect(res.feed.length).toBeLessThanOrEqual(2),
)
const full = await client.app.bsky.feed.getAuthorFeed(
const full = await agent.api.app.bsky.feed.getAuthorFeed(
{
author: sc.accounts[alice].handle,
},
@ -153,7 +153,7 @@ describe('pds author feed views', () => {
})
it('blocked by actor takedown.', async () => {
const { data: preBlock } = await client.app.bsky.feed.getAuthorFeed(
const { data: preBlock } = await agent.api.app.bsky.feed.getAuthorFeed(
{ author: alice },
{ headers: sc.getHeaders(carol) },
)
@ -161,7 +161,7 @@ describe('pds author feed views', () => {
expect(preBlock.feed.length).toBeGreaterThan(0)
const { data: action } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -177,7 +177,7 @@ describe('pds author feed views', () => {
},
)
const { data: postBlock } = await client.app.bsky.feed.getAuthorFeed(
const { data: postBlock } = await agent.api.app.bsky.feed.getAuthorFeed(
{ author: alice },
{ headers: sc.getHeaders(carol) },
)
@ -185,7 +185,7 @@ describe('pds author feed views', () => {
expect(postBlock.feed.length).toEqual(0)
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: action.id,
createdBy: 'X',
@ -199,7 +199,7 @@ describe('pds author feed views', () => {
})
it('blocked by record takedown.', async () => {
const { data: preBlock } = await client.app.bsky.feed.getAuthorFeed(
const { data: preBlock } = await agent.api.app.bsky.feed.getAuthorFeed(
{ author: alice },
{ headers: sc.getHeaders(carol) },
)
@ -209,7 +209,7 @@ describe('pds author feed views', () => {
const postUri = new AtUri(preBlock.feed[0].post.uri)
const { data: action } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -225,7 +225,7 @@ describe('pds author feed views', () => {
},
)
const { data: postBlock } = await client.app.bsky.feed.getAuthorFeed(
const { data: postBlock } = await agent.api.app.bsky.feed.getAuthorFeed(
{ author: alice },
{ headers: sc.getHeaders(carol) },
)
@ -236,7 +236,7 @@ describe('pds author feed views', () => {
)
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: action.id,
createdBy: 'X',

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/moderationAction'
import {
runTestServer,
@ -12,7 +12,7 @@ import { SeedClient } from '../seeds/client'
import followsSeed from '../seeds/follows'
describe('pds follow views', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
@ -24,8 +24,8 @@ describe('pds follow views', () => {
dbPostgresSchema: 'views_follows',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await followsSeed(sc)
alice = sc.dids.alice
})
@ -43,7 +43,7 @@ describe('pds follow views', () => {
const tstamp = (x: string) => new Date(x).getTime()
it('fetches followers', async () => {
const aliceFollowers = await client.app.bsky.graph.getFollowers(
const aliceFollowers = await agent.api.app.bsky.graph.getFollowers(
{ user: sc.dids.alice },
{ headers: sc.getHeaders(alice) },
)
@ -53,7 +53,7 @@ describe('pds follow views', () => {
getSortedCursors(aliceFollowers.data.followers),
)
const bobFollowers = await client.app.bsky.graph.getFollowers(
const bobFollowers = await agent.api.app.bsky.graph.getFollowers(
{ user: sc.dids.bob },
{ headers: sc.getHeaders(alice) },
)
@ -63,7 +63,7 @@ describe('pds follow views', () => {
getSortedCursors(bobFollowers.data.followers),
)
const carolFollowers = await client.app.bsky.graph.getFollowers(
const carolFollowers = await agent.api.app.bsky.graph.getFollowers(
{ user: sc.dids.carol },
{ headers: sc.getHeaders(alice) },
)
@ -73,7 +73,7 @@ describe('pds follow views', () => {
getSortedCursors(carolFollowers.data.followers),
)
const danFollowers = await client.app.bsky.graph.getFollowers(
const danFollowers = await agent.api.app.bsky.graph.getFollowers(
{ user: sc.dids.dan },
{ headers: sc.getHeaders(alice) },
)
@ -83,7 +83,7 @@ describe('pds follow views', () => {
getSortedCursors(danFollowers.data.followers),
)
const eveFollowers = await client.app.bsky.graph.getFollowers(
const eveFollowers = await agent.api.app.bsky.graph.getFollowers(
{ user: sc.dids.eve },
{ headers: sc.getHeaders(alice) },
)
@ -95,11 +95,11 @@ describe('pds follow views', () => {
})
it('fetches followers by handle', async () => {
const byDid = await client.app.bsky.graph.getFollowers(
const byDid = await agent.api.app.bsky.graph.getFollowers(
{ user: sc.dids.alice },
{ headers: sc.getHeaders(alice) },
)
const byHandle = await client.app.bsky.graph.getFollowers(
const byHandle = await agent.api.app.bsky.graph.getFollowers(
{ user: sc.accounts[alice].handle },
{ headers: sc.getHeaders(alice) },
)
@ -109,7 +109,7 @@ describe('pds follow views', () => {
it('paginates followers', async () => {
const results = (results) => results.flatMap((res) => res.followers)
const paginator = async (cursor?: string) => {
const res = await client.app.bsky.graph.getFollowers(
const res = await agent.api.app.bsky.graph.getFollowers(
{
user: sc.dids.alice,
before: cursor,
@ -125,7 +125,7 @@ describe('pds follow views', () => {
expect(res.followers.length).toBeLessThanOrEqual(2),
)
const full = await client.app.bsky.graph.getFollowers(
const full = await agent.api.app.bsky.graph.getFollowers(
{ user: sc.dids.alice },
{ headers: sc.getHeaders(alice) },
)
@ -136,7 +136,7 @@ describe('pds follow views', () => {
it('blocks followers by actor takedown', async () => {
const { data: modAction } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -152,14 +152,14 @@ describe('pds follow views', () => {
},
)
const aliceFollowers = await client.app.bsky.graph.getFollowers(
const aliceFollowers = await agent.api.app.bsky.graph.getFollowers(
{ user: sc.dids.alice },
{ headers: sc.getHeaders(alice) },
)
expect(forSnapshot(aliceFollowers.data)).toMatchSnapshot()
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: modAction.id,
createdBy: 'X',
@ -173,7 +173,7 @@ describe('pds follow views', () => {
})
it('fetches follows', async () => {
const aliceFollowers = await client.app.bsky.graph.getFollows(
const aliceFollowers = await agent.api.app.bsky.graph.getFollows(
{ user: sc.dids.alice },
{ headers: sc.getHeaders(alice) },
)
@ -183,7 +183,7 @@ describe('pds follow views', () => {
getSortedCursors(aliceFollowers.data.follows),
)
const bobFollowers = await client.app.bsky.graph.getFollows(
const bobFollowers = await agent.api.app.bsky.graph.getFollows(
{ user: sc.dids.bob },
{ headers: sc.getHeaders(alice) },
)
@ -193,7 +193,7 @@ describe('pds follow views', () => {
getSortedCursors(bobFollowers.data.follows),
)
const carolFollowers = await client.app.bsky.graph.getFollows(
const carolFollowers = await agent.api.app.bsky.graph.getFollows(
{ user: sc.dids.carol },
{ headers: sc.getHeaders(alice) },
)
@ -203,7 +203,7 @@ describe('pds follow views', () => {
getSortedCursors(carolFollowers.data.follows),
)
const danFollowers = await client.app.bsky.graph.getFollows(
const danFollowers = await agent.api.app.bsky.graph.getFollows(
{ user: sc.dids.dan },
{ headers: sc.getHeaders(alice) },
)
@ -213,7 +213,7 @@ describe('pds follow views', () => {
getSortedCursors(danFollowers.data.follows),
)
const eveFollowers = await client.app.bsky.graph.getFollows(
const eveFollowers = await agent.api.app.bsky.graph.getFollows(
{ user: sc.dids.eve },
{ headers: sc.getHeaders(alice) },
)
@ -225,11 +225,11 @@ describe('pds follow views', () => {
})
it('fetches follows by handle', async () => {
const byDid = await client.app.bsky.graph.getFollows(
const byDid = await agent.api.app.bsky.graph.getFollows(
{ user: sc.dids.alice },
{ headers: sc.getHeaders(alice) },
)
const byHandle = await client.app.bsky.graph.getFollows(
const byHandle = await agent.api.app.bsky.graph.getFollows(
{ user: sc.accounts[alice].handle },
{ headers: sc.getHeaders(alice) },
)
@ -239,7 +239,7 @@ describe('pds follow views', () => {
it('paginates follows', async () => {
const results = (results) => results.flatMap((res) => res.follows)
const paginator = async (cursor?: string) => {
const res = await client.app.bsky.graph.getFollows(
const res = await agent.api.app.bsky.graph.getFollows(
{
user: sc.dids.alice,
before: cursor,
@ -255,7 +255,7 @@ describe('pds follow views', () => {
expect(res.follows.length).toBeLessThanOrEqual(2),
)
const full = await client.app.bsky.graph.getFollows(
const full = await agent.api.app.bsky.graph.getFollows(
{ user: sc.dids.alice },
{ headers: sc.getHeaders(alice) },
)
@ -266,7 +266,7 @@ describe('pds follow views', () => {
it('blocks follows by actor takedown', async () => {
const { data: modAction } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -282,14 +282,14 @@ describe('pds follow views', () => {
},
)
const aliceFollows = await client.app.bsky.graph.getFollows(
const aliceFollows = await agent.api.app.bsky.graph.getFollows(
{ user: sc.dids.alice },
{ headers: sc.getHeaders(alice) },
)
expect(forSnapshot(aliceFollows.data)).toMatchSnapshot()
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: modAction.id,
createdBy: 'X',

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import {
runTestServer,
forSnapshot,
@ -10,7 +10,7 @@ import { SeedClient } from '../seeds/client'
import usersBulkSeed from '../seeds/users-bulk'
describe('mute views', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
let silas: string
@ -20,8 +20,8 @@ describe('mute views', () => {
dbPostgresSchema: 'views_mutes',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await usersBulkSeed(sc, 10)
silas = sc.dids['silas77.test']
const mutes = [
@ -33,7 +33,7 @@ describe('mute views', () => {
'elta48.test',
]
for (const did of mutes) {
await client.app.bsky.graph.mute(
await agent.api.app.bsky.graph.mute(
{ user: did },
{ headers: sc.getHeaders(silas), encoding: 'application/json' },
)
@ -53,7 +53,7 @@ describe('mute views', () => {
const tstamp = (x: string) => new Date(x).getTime()
it('fetches mutes for the logged-in user.', async () => {
const { data: view } = await client.app.bsky.graph.getMutes(
const { data: view } = await agent.api.app.bsky.graph.getMutes(
{},
{ headers: sc.getHeaders(silas) },
)
@ -64,7 +64,7 @@ describe('mute views', () => {
it('paginates.', async () => {
const results = (results) => results.flatMap((res) => res.mutes)
const paginator = async (cursor?: string) => {
const { data: view } = await client.app.bsky.graph.getMutes(
const { data: view } = await agent.api.app.bsky.graph.getMutes(
{ before: cursor, limit: 2 },
{ headers: sc.getHeaders(silas) },
)
@ -76,7 +76,7 @@ describe('mute views', () => {
expect(res.mutes.length).toBeLessThanOrEqual(2),
)
const full = await client.app.bsky.graph.getMutes(
const full = await agent.api.app.bsky.graph.getMutes(
{},
{ headers: sc.getHeaders(silas) },
)
@ -86,33 +86,33 @@ describe('mute views', () => {
})
it('removes mute.', async () => {
const { data: initial } = await client.app.bsky.graph.getMutes(
const { data: initial } = await agent.api.app.bsky.graph.getMutes(
{},
{ headers: sc.getHeaders(silas) },
)
expect(initial.mutes.length).toEqual(6)
expect(initial.mutes.map((m) => m.handle)).toContain('elta48.test')
await client.app.bsky.graph.unmute(
await agent.api.app.bsky.graph.unmute(
{ user: sc.dids['elta48.test'] },
{ headers: sc.getHeaders(silas), encoding: 'application/json' },
)
const { data: final } = await client.app.bsky.graph.getMutes(
const { data: final } = await agent.api.app.bsky.graph.getMutes(
{},
{ headers: sc.getHeaders(silas) },
)
expect(final.mutes.length).toEqual(5)
expect(final.mutes.map((m) => m.handle)).not.toContain('elta48.test')
await client.app.bsky.graph.mute(
await agent.api.app.bsky.graph.mute(
{ user: sc.dids['elta48.test'] },
{ headers: sc.getHeaders(silas), encoding: 'application/json' },
)
})
it('does not allow muting self.', async () => {
const promise = client.app.bsky.graph.mute(
const promise = agent.api.app.bsky.graph.mute(
{ user: silas },
{ headers: sc.getHeaders(silas), encoding: 'application/json' },
)

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/moderationAction'
import {
runTestServer,
@ -13,7 +13,7 @@ import { Database } from '../../src'
import { Notification } from '../../src/lexicon/types/app/bsky/notification/list'
describe('pds notification views', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let db: Database
let sc: SeedClient
@ -27,8 +27,8 @@ describe('pds notification views', () => {
})
close = server.close
db = server.ctx.db
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc, server.ctx.messageQueue)
alice = sc.dids.alice
})
@ -47,7 +47,7 @@ describe('pds notification views', () => {
}
it('fetches notification count without a last-seen', async () => {
const notifCount = await client.app.bsky.notification.getCount(
const notifCount = await agent.api.app.bsky.notification.getCount(
{},
{ headers: sc.getHeaders(alice) },
)
@ -56,7 +56,7 @@ describe('pds notification views', () => {
})
it('fetches notifications without a last-seen', async () => {
const notifRes = await client.app.bsky.notification.list(
const notifRes = await agent.api.app.bsky.notification.list(
{},
{ headers: sc.getHeaders(alice) },
)
@ -74,20 +74,20 @@ describe('pds notification views', () => {
})
it('fetches notifications omitting mentions and replies by a muted user', async () => {
await client.app.bsky.graph.mute(
await agent.api.app.bsky.graph.mute(
{ user: sc.dids.carol }, // Replier
{ headers: sc.getHeaders(alice), encoding: 'application/json' },
)
await client.app.bsky.graph.mute(
await agent.api.app.bsky.graph.mute(
{ user: sc.dids.dan }, // Mentioner
{ headers: sc.getHeaders(alice), encoding: 'application/json' },
)
const notifRes = await client.app.bsky.notification.list(
const notifRes = await agent.api.app.bsky.notification.list(
{},
{ headers: sc.getHeaders(alice) },
)
const notifCount = await client.app.bsky.notification.getCount(
const notifCount = await agent.api.app.bsky.notification.getCount(
{},
{ headers: sc.getHeaders(alice) },
)
@ -98,11 +98,11 @@ describe('pds notification views', () => {
expect(notifCount.data.count).toBe(7)
// Cleanup
await client.app.bsky.graph.unmute(
await agent.api.app.bsky.graph.unmute(
{ user: sc.dids.carol },
{ headers: sc.getHeaders(alice), encoding: 'application/json' },
)
await client.app.bsky.graph.unmute(
await agent.api.app.bsky.graph.unmute(
{ user: sc.dids.dan },
{ headers: sc.getHeaders(alice), encoding: 'application/json' },
)
@ -113,7 +113,7 @@ describe('pds notification views', () => {
const postUri2 = sc.posts[sc.dids.dan][1].ref.uri // Mention
const actionResults = await Promise.all(
[postUri1, postUri2].map((postUri) =>
client.com.atproto.admin.takeModerationAction(
agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -131,11 +131,11 @@ describe('pds notification views', () => {
),
)
const notifRes = await client.app.bsky.notification.list(
const notifRes = await agent.api.app.bsky.notification.list(
{},
{ headers: sc.getHeaders(alice) },
)
const notifCount = await client.app.bsky.notification.getCount(
const notifCount = await agent.api.app.bsky.notification.getCount(
{},
{ headers: sc.getHeaders(alice) },
)
@ -148,7 +148,7 @@ describe('pds notification views', () => {
// Cleanup
await Promise.all(
actionResults.map((result) =>
client.com.atproto.admin.reverseModerationAction(
agent.api.com.atproto.admin.reverseModerationAction(
{
id: result.data.id,
createdBy: 'X',
@ -167,7 +167,7 @@ describe('pds notification views', () => {
const results = (results) =>
sort(results.flatMap((res) => res.notifications))
const paginator = async (cursor?: string) => {
const res = await client.app.bsky.notification.list(
const res = await agent.api.app.bsky.notification.list(
{
before: cursor,
limit: 6,
@ -182,7 +182,7 @@ describe('pds notification views', () => {
expect(res.notifications.length).toBeLessThanOrEqual(6),
)
const full = await client.app.bsky.notification.list(
const full = await agent.api.app.bsky.notification.list(
{},
{
headers: sc.getHeaders(alice),
@ -194,7 +194,7 @@ describe('pds notification views', () => {
})
it('updates notifications last seen', async () => {
const full = await client.app.bsky.notification.list(
const full = await agent.api.app.bsky.notification.list(
{},
{
headers: sc.getHeaders(alice),
@ -208,14 +208,14 @@ describe('pds notification views', () => {
.where('recordUri', '=', full.data.notifications[3].uri)
.executeTakeFirstOrThrow()
await client.app.bsky.notification.updateSeen(
await agent.api.app.bsky.notification.updateSeen(
{ seenAt: beforeNotif.indexedAt },
{ encoding: 'application/json', headers: sc.getHeaders(alice) },
)
})
it('fetches notification count with a last-seen', async () => {
const notifCount = await client.app.bsky.notification.getCount(
const notifCount = await agent.api.app.bsky.notification.getCount(
{},
{ headers: sc.getHeaders(alice) },
)
@ -224,7 +224,7 @@ describe('pds notification views', () => {
})
it('fetches notifications with a last-seen', async () => {
const notifRes = await client.app.bsky.notification.list(
const notifRes = await agent.api.app.bsky.notification.list(
{},
{
headers: sc.getHeaders(alice),

@ -1,12 +1,12 @@
import fs from 'fs/promises'
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/moderationAction'
import { runTestServer, forSnapshot, CloseFn, adminAuth } from '../_util'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
describe('pds profile views', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
@ -20,8 +20,8 @@ describe('pds profile views', () => {
dbPostgresSchema: 'views_profile',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc)
alice = sc.dids.alice
bob = sc.dids.bob
@ -33,7 +33,7 @@ describe('pds profile views', () => {
})
it('fetches own profile', async () => {
const aliceForAlice = await client.app.bsky.actor.getProfile(
const aliceForAlice = await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: sc.getHeaders(alice) },
)
@ -42,7 +42,7 @@ describe('pds profile views', () => {
})
it("fetches other's profile, with a follow", async () => {
const aliceForBob = await client.app.bsky.actor.getProfile(
const aliceForBob = await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: sc.getHeaders(bob) },
)
@ -51,7 +51,7 @@ describe('pds profile views', () => {
})
it("fetches other's profile, without a follow", async () => {
const danForBob = await client.app.bsky.actor.getProfile(
const danForBob = await agent.api.app.bsky.actor.getProfile(
{ actor: dan },
{ headers: sc.getHeaders(bob) },
)
@ -60,12 +60,12 @@ describe('pds profile views', () => {
})
it('updates profile', async () => {
await client.app.bsky.actor.updateProfile(
await agent.api.app.bsky.actor.updateProfile(
{ displayName: 'ali', description: 'new descript' },
{ headers: sc.getHeaders(alice), encoding: 'application/json' },
)
const aliceForAlice = await client.app.bsky.actor.getProfile(
const aliceForAlice = await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: sc.getHeaders(alice) },
)
@ -74,12 +74,12 @@ describe('pds profile views', () => {
})
it('handles partial updates', async () => {
await client.app.bsky.actor.updateProfile(
await agent.api.app.bsky.actor.updateProfile(
{ description: 'blah blah' },
{ headers: sc.getHeaders(alice), encoding: 'application/json' },
)
const aliceForAlice = await client.app.bsky.actor.getProfile(
const aliceForAlice = await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: sc.getHeaders(alice) },
)
@ -94,16 +94,16 @@ describe('pds profile views', () => {
const bannerImg = await fs.readFile(
'tests/image/fixtures/key-landscape-small.jpg',
)
const avatarRes = await client.com.atproto.blob.upload(avatarImg, {
const avatarRes = await agent.api.com.atproto.blob.upload(avatarImg, {
headers: sc.getHeaders(alice),
encoding: 'image/jpeg',
})
const bannerRes = await client.com.atproto.blob.upload(bannerImg, {
const bannerRes = await agent.api.com.atproto.blob.upload(bannerImg, {
headers: sc.getHeaders(alice),
encoding: 'image/jpeg',
})
await client.app.bsky.actor.updateProfile(
await agent.api.app.bsky.actor.updateProfile(
{
avatar: { cid: avatarRes.data.cid, mimeType: 'image/jpeg' },
banner: { cid: bannerRes.data.cid, mimeType: 'image/jpeg' },
@ -111,7 +111,7 @@ describe('pds profile views', () => {
{ headers: sc.getHeaders(alice), encoding: 'application/json' },
)
const aliceForAlice = await client.app.bsky.actor.getProfile(
const aliceForAlice = await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: sc.getHeaders(alice) },
)
@ -120,12 +120,12 @@ describe('pds profile views', () => {
})
it('creates new profile', async () => {
await client.app.bsky.actor.updateProfile(
await agent.api.app.bsky.actor.updateProfile(
{ displayName: 'danny boy' },
{ headers: sc.getHeaders(dan), encoding: 'application/json' },
)
const danForDan = await client.app.bsky.actor.getProfile(
const danForDan = await agent.api.app.bsky.actor.getProfile(
{ actor: dan },
{ headers: sc.getHeaders(dan) },
)
@ -141,14 +141,14 @@ describe('pds profile views', () => {
}
await Promise.all(
descriptions.map(async (description) => {
await client.app.bsky.actor.updateProfile(
await agent.api.app.bsky.actor.updateProfile(
{ description },
{ headers: sc.getHeaders(alice), encoding: 'application/json' },
)
}),
)
const profile = await client.app.bsky.actor.getProfile(
const profile = await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: sc.getHeaders(alice) },
)
@ -161,14 +161,14 @@ describe('pds profile views', () => {
})
it('fetches profile by handle', async () => {
const byDid = await client.app.bsky.actor.getProfile(
const byDid = await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{
headers: sc.getHeaders(bob),
},
)
const byHandle = await client.app.bsky.actor.getProfile(
const byHandle = await agent.api.app.bsky.actor.getProfile(
{ actor: sc.accounts[alice].handle },
{ headers: sc.getHeaders(bob) },
)
@ -178,7 +178,7 @@ describe('pds profile views', () => {
it('blocked by actor takedown', async () => {
const { data: action } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -193,7 +193,7 @@ describe('pds profile views', () => {
headers: { authorization: adminAuth() },
},
)
const promise = client.app.bsky.actor.getProfile(
const promise = agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: sc.getHeaders(bob) },
)
@ -201,7 +201,7 @@ describe('pds profile views', () => {
await expect(promise).rejects.toThrow('Account has been taken down')
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: action.id,
createdBy: 'X',
@ -215,37 +215,39 @@ describe('pds profile views', () => {
})
it('includes muted status.', async () => {
const { data: initial } = await client.app.bsky.actor.getProfile(
const { data: initial } = await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: sc.getHeaders(bob) },
)
expect(initial.myState?.muted).toEqual(false)
await client.app.bsky.graph.mute(
await agent.api.app.bsky.graph.mute(
{ user: alice },
{ headers: sc.getHeaders(bob), encoding: 'application/json' },
)
const { data: muted } = await client.app.bsky.actor.getProfile(
const { data: muted } = await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: sc.getHeaders(bob) },
)
expect(muted.myState?.muted).toEqual(true)
const { data: fromBobUnrelated } = await client.app.bsky.actor.getProfile(
{ actor: dan },
{ headers: sc.getHeaders(bob) },
)
const { data: toAliceUnrelated } = await client.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: sc.getHeaders(dan) },
)
const { data: fromBobUnrelated } =
await agent.api.app.bsky.actor.getProfile(
{ actor: dan },
{ headers: sc.getHeaders(bob) },
)
const { data: toAliceUnrelated } =
await agent.api.app.bsky.actor.getProfile(
{ actor: alice },
{ headers: sc.getHeaders(dan) },
)
expect(fromBobUnrelated.myState?.muted).toEqual(false)
expect(toAliceUnrelated.myState?.muted).toEqual(false)
await client.app.bsky.graph.unmute(
await agent.api.app.bsky.graph.unmute(
{ user: alice },
{ headers: sc.getHeaders(bob), encoding: 'application/json' },
)

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import {
runTestServer,
forSnapshot,
@ -10,7 +10,7 @@ import { SeedClient } from '../seeds/client'
import repostsSeed from '../seeds/reposts'
describe('pds repost views', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
@ -23,8 +23,8 @@ describe('pds repost views', () => {
dbPostgresSchema: 'views_reposts',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await repostsSeed(sc)
alice = sc.dids.alice
bob = sc.dids.bob
@ -43,7 +43,7 @@ describe('pds repost views', () => {
const tstamp = (x: string) => new Date(x).getTime()
it('fetches reposted-by for a post', async () => {
const view = await client.app.bsky.feed.getRepostedBy(
const view = await agent.api.app.bsky.feed.getRepostedBy(
{ uri: sc.posts[alice][2].ref.uriStr },
{ headers: sc.getHeaders(alice) },
)
@ -55,7 +55,7 @@ describe('pds repost views', () => {
})
it('fetches reposted-by for a reply', async () => {
const view = await client.app.bsky.feed.getRepostedBy(
const view = await agent.api.app.bsky.feed.getRepostedBy(
{ uri: sc.replies[bob][0].ref.uriStr },
{ headers: sc.getHeaders(alice) },
)
@ -69,7 +69,7 @@ describe('pds repost views', () => {
it('paginates', async () => {
const results = (results) => results.flatMap((res) => res.repostedBy)
const paginator = async (cursor?: string) => {
const res = await client.app.bsky.feed.getRepostedBy(
const res = await agent.api.app.bsky.feed.getRepostedBy(
{
uri: sc.posts[alice][2].ref.uriStr,
before: cursor,
@ -85,7 +85,7 @@ describe('pds repost views', () => {
expect(res.repostedBy.length).toBeLessThanOrEqual(2),
)
const full = await client.app.bsky.feed.getRepostedBy(
const full = await agent.api.app.bsky.feed.getRepostedBy(
{ uri: sc.posts[alice][2].ref.uriStr },
{ headers: sc.getHeaders(alice) },
)

@ -1,10 +1,10 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { runTestServer, CloseFn } from '../_util'
import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
describe('pds user search views', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
@ -13,8 +13,8 @@ describe('pds user search views', () => {
dbPostgresSchema: 'views_suggestions',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc)
})
@ -23,7 +23,7 @@ describe('pds user search views', () => {
})
it('actor suggestion gives users', async () => {
const result = await client.app.bsky.actor.getSuggestions(
const result = await agent.api.app.bsky.actor.getSuggestions(
{ limit: 3 },
{ headers: sc.getHeaders(sc.dids.carol) },
)
@ -45,7 +45,7 @@ describe('pds user search views', () => {
})
it('does not suggest followed users', async () => {
const result = await client.app.bsky.actor.getSuggestions(
const result = await agent.api.app.bsky.actor.getSuggestions(
{ limit: 3 },
{ headers: sc.getHeaders(sc.dids.alice) },
)
@ -55,11 +55,11 @@ describe('pds user search views', () => {
})
it('paginates', async () => {
const result1 = await client.app.bsky.actor.getSuggestions(
const result1 = await agent.api.app.bsky.actor.getSuggestions(
{ limit: 1 },
{ headers: sc.getHeaders(sc.dids.carol) },
)
const result2 = await client.app.bsky.actor.getSuggestions(
const result2 = await agent.api.app.bsky.actor.getSuggestions(
{ limit: 1, cursor: result1.data.cursor },
{ headers: sc.getHeaders(sc.dids.carol) },
)

@ -1,7 +1,4 @@
import AtpApi, {
ServiceClient as AtpServiceClient,
AppBskyFeedGetPostThread,
} from '@atproto/api'
import AtpAgent, { AppBskyFeedGetPostThread } from '@atproto/api'
import { AtUri } from '@atproto/uri'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/moderationAction'
import { runTestServer, forSnapshot, CloseFn, adminAuth } from '../_util'
@ -9,7 +6,7 @@ import { SeedClient } from '../seeds/client'
import basicSeed from '../seeds/basic'
describe('pds thread views', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
@ -23,8 +20,8 @@ describe('pds thread views', () => {
dbPostgresSchema: 'views_thread',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc)
alice = sc.dids.alice
bob = sc.dids.bob
@ -41,7 +38,7 @@ describe('pds thread views', () => {
})
it('fetches deep post thread', async () => {
const thread = await client.app.bsky.feed.getPostThread(
const thread = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: sc.getHeaders(bob) },
)
@ -50,7 +47,7 @@ describe('pds thread views', () => {
})
it('fetches shallow post thread', async () => {
const thread = await client.app.bsky.feed.getPostThread(
const thread = await agent.api.app.bsky.feed.getPostThread(
{ depth: 1, uri: sc.posts[alice][1].ref.uriStr },
{ headers: sc.getHeaders(bob) },
)
@ -59,7 +56,7 @@ describe('pds thread views', () => {
})
it('fetches ancestors', async () => {
const thread = await client.app.bsky.feed.getPostThread(
const thread = await agent.api.app.bsky.feed.getPostThread(
{ depth: 1, uri: sc.replies[alice][0].ref.uriStr },
{ headers: sc.getHeaders(bob) },
)
@ -68,7 +65,7 @@ describe('pds thread views', () => {
})
it('fails for an unknown post', async () => {
const promise = client.app.bsky.feed.getPostThread(
const promise = agent.api.app.bsky.feed.getPostThread(
{ uri: 'does.not.exist' },
{ headers: sc.getHeaders(bob) },
)
@ -79,18 +76,18 @@ describe('pds thread views', () => {
})
it('includes the muted status of post authors.', async () => {
await client.app.bsky.graph.mute(
await agent.api.app.bsky.graph.mute(
{ user: alice },
{ headers: sc.getHeaders(bob), encoding: 'application/json' },
)
const thread = await client.app.bsky.feed.getPostThread(
const thread = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: sc.getHeaders(bob) },
)
expect(forSnapshot(thread.data.thread)).toMatchSnapshot()
await client.app.bsky.graph.unmute(
await agent.api.app.bsky.graph.unmute(
{ user: alice },
{ encoding: 'application/json', headers: sc.getHeaders(bob) },
)
@ -124,7 +121,7 @@ describe('pds thread views', () => {
)
indexes.aliceReplyReply = sc.replies[alice].length - 1
const thread1 = await client.app.bsky.feed.getPostThread(
const thread1 = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][indexes.aliceRoot].ref.uriStr },
{ headers: sc.getHeaders(bob) },
)
@ -132,13 +129,13 @@ describe('pds thread views', () => {
await sc.deletePost(bob, sc.replies[bob][indexes.bobReply].ref.uri)
const thread2 = await client.app.bsky.feed.getPostThread(
const thread2 = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][indexes.aliceRoot].ref.uriStr },
{ headers: sc.getHeaders(bob) },
)
expect(forSnapshot(thread2.data.thread)).toMatchSnapshot()
const thread3 = await client.app.bsky.feed.getPostThread(
const thread3 = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.replies[alice][indexes.aliceReplyReply].ref.uriStr },
{ headers: sc.getHeaders(bob) },
)
@ -147,7 +144,7 @@ describe('pds thread views', () => {
it('blocks post by actor takedown', async () => {
const { data: modAction } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -164,7 +161,7 @@ describe('pds thread views', () => {
)
// Same as shallow post thread test, minus alice
const promise = client.app.bsky.feed.getPostThread(
const promise = agent.api.app.bsky.feed.getPostThread(
{ depth: 1, uri: sc.posts[alice][1].ref.uriStr },
{ headers: sc.getHeaders(bob) },
)
@ -174,7 +171,7 @@ describe('pds thread views', () => {
)
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: modAction.id,
createdBy: 'X',
@ -189,7 +186,7 @@ describe('pds thread views', () => {
it('blocks replies by actor takedown', async () => {
const { data: modAction } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -206,7 +203,7 @@ describe('pds thread views', () => {
)
// Same as deep post thread test, minus carol
const thread = await client.app.bsky.feed.getPostThread(
const thread = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: sc.getHeaders(bob) },
)
@ -214,7 +211,7 @@ describe('pds thread views', () => {
expect(forSnapshot(thread.data.thread)).toMatchSnapshot()
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: modAction.id,
createdBy: 'X',
@ -229,7 +226,7 @@ describe('pds thread views', () => {
it('blocks ancestors by actor takedown', async () => {
const { data: modAction } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -246,7 +243,7 @@ describe('pds thread views', () => {
)
// Same as ancestor post thread test, minus bob
const thread = await client.app.bsky.feed.getPostThread(
const thread = await agent.api.app.bsky.feed.getPostThread(
{ depth: 1, uri: sc.replies[alice][0].ref.uriStr },
{ headers: sc.getHeaders(bob) },
)
@ -254,7 +251,7 @@ describe('pds thread views', () => {
expect(forSnapshot(thread.data.thread)).toMatchSnapshot()
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: modAction.id,
createdBy: 'X',
@ -270,7 +267,7 @@ describe('pds thread views', () => {
it('blocks post by record takedown', async () => {
const postUri = sc.posts[alice][1].ref.uri
const { data: modAction } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -286,7 +283,7 @@ describe('pds thread views', () => {
},
)
const promise = client.app.bsky.feed.getPostThread(
const promise = agent.api.app.bsky.feed.getPostThread(
{ depth: 1, uri: postUri.toString() },
{ headers: sc.getHeaders(bob) },
)
@ -296,7 +293,7 @@ describe('pds thread views', () => {
)
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: modAction.id,
createdBy: 'X',
@ -310,7 +307,7 @@ describe('pds thread views', () => {
})
it('blocks ancestors by record takedown', async () => {
const threadPreTakedown = await client.app.bsky.feed.getPostThread(
const threadPreTakedown = await agent.api.app.bsky.feed.getPostThread(
{ depth: 1, uri: sc.replies[alice][0].ref.uriStr },
{ headers: sc.getHeaders(bob) },
)
@ -319,7 +316,7 @@ describe('pds thread views', () => {
)
const { data: modAction } =
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -336,7 +333,7 @@ describe('pds thread views', () => {
)
// Same as ancestor post thread test, minus parent post
const thread = await client.app.bsky.feed.getPostThread(
const thread = await agent.api.app.bsky.feed.getPostThread(
{ depth: 1, uri: sc.replies[alice][0].ref.uriStr },
{ headers: sc.getHeaders(bob) },
)
@ -344,7 +341,7 @@ describe('pds thread views', () => {
expect(forSnapshot(thread.data.thread)).toMatchSnapshot()
// Cleanup
await client.com.atproto.admin.reverseModerationAction(
await agent.api.com.atproto.admin.reverseModerationAction(
{
id: modAction.id,
createdBy: 'X',
@ -358,7 +355,7 @@ describe('pds thread views', () => {
})
it('blocks replies by record takedown', async () => {
const threadPreTakedown = await client.app.bsky.feed.getPostThread(
const threadPreTakedown = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: sc.getHeaders(bob) },
)
@ -371,7 +368,7 @@ describe('pds thread views', () => {
const actionResults = await Promise.all(
[postUri1, postUri2].map((postUri) =>
client.com.atproto.admin.takeModerationAction(
agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -390,7 +387,7 @@ describe('pds thread views', () => {
)
// Same as deep post thread test, minus some replies
const thread = await client.app.bsky.feed.getPostThread(
const thread = await agent.api.app.bsky.feed.getPostThread(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: sc.getHeaders(bob) },
)
@ -400,7 +397,7 @@ describe('pds thread views', () => {
// Cleanup
await Promise.all(
actionResults.map((result) =>
client.com.atproto.admin.reverseModerationAction(
agent.api.com.atproto.admin.reverseModerationAction(
{
id: result.data.id,
createdBy: 'X',

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/moderationAction'
import { Main as FeedViewPost } from '../../src/lexicon/types/app/bsky/feed/feedViewPost'
import {
@ -14,7 +14,7 @@ import basicSeed from '../seeds/basic'
import { FeedAlgorithm } from '../../src/api/app/bsky/util/feed'
describe('timeline views', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
@ -29,8 +29,8 @@ describe('timeline views', () => {
dbPostgresSchema: 'views_home_feed',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await basicSeed(sc, server.ctx.messageQueue)
alice = sc.dids.alice
bob = sc.dids.bob
@ -51,7 +51,7 @@ describe('timeline views', () => {
}
}
const aliceTL = await client.app.bsky.feed.getTimeline(
const aliceTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{
headers: sc.getHeaders(alice),
@ -61,7 +61,7 @@ describe('timeline views', () => {
expect(forSnapshot(aliceTL.data.feed)).toMatchSnapshot()
aliceTL.data.feed.forEach(expectOriginatorFollowedBy(alice))
const bobTL = await client.app.bsky.feed.getTimeline(
const bobTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{
headers: sc.getHeaders(bob),
@ -71,7 +71,7 @@ describe('timeline views', () => {
expect(forSnapshot(bobTL.data.feed)).toMatchSnapshot()
bobTL.data.feed.forEach(expectOriginatorFollowedBy(bob))
const carolTL = await client.app.bsky.feed.getTimeline(
const carolTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{
headers: sc.getHeaders(carol),
@ -81,7 +81,7 @@ describe('timeline views', () => {
expect(forSnapshot(carolTL.data.feed)).toMatchSnapshot()
carolTL.data.feed.forEach(expectOriginatorFollowedBy(carol))
const danTL = await client.app.bsky.feed.getTimeline(
const danTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{
headers: sc.getHeaders(dan),
@ -93,13 +93,13 @@ describe('timeline views', () => {
})
it("fetches authenticated user's home feed w/ default algorithm", async () => {
const defaultTL = await client.app.bsky.feed.getTimeline(
const defaultTL = await agent.api.app.bsky.feed.getTimeline(
{},
{
headers: sc.getHeaders(alice),
},
)
const reverseChronologicalTL = await client.app.bsky.feed.getTimeline(
const reverseChronologicalTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{
headers: sc.getHeaders(alice),
@ -109,16 +109,16 @@ describe('timeline views', () => {
})
it('omits posts and reposts of muted authors.', async () => {
await client.app.bsky.graph.mute(
await agent.api.app.bsky.graph.mute(
{ user: bob },
{ headers: sc.getHeaders(alice), encoding: 'application/json' },
)
await client.app.bsky.graph.mute(
await agent.api.app.bsky.graph.mute(
{ user: carol },
{ headers: sc.getHeaders(alice), encoding: 'application/json' },
)
const aliceTL = await client.app.bsky.feed.getTimeline(
const aliceTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{ headers: sc.getHeaders(alice) },
)
@ -126,11 +126,11 @@ describe('timeline views', () => {
expect(forSnapshot(aliceTL.data.feed)).toMatchSnapshot()
// Cleanup
await client.app.bsky.graph.unmute(
await agent.api.app.bsky.graph.unmute(
{ user: bob },
{ encoding: 'application/json', headers: sc.getHeaders(alice) },
)
await client.app.bsky.graph.unmute(
await agent.api.app.bsky.graph.unmute(
{ user: carol },
{ encoding: 'application/json', headers: sc.getHeaders(alice) },
)
@ -139,7 +139,7 @@ describe('timeline views', () => {
it('paginates reverse-chronological feed', async () => {
const results = (results) => results.flatMap((res) => res.feed)
const paginator = async (cursor?: string) => {
const res = await client.app.bsky.feed.getTimeline(
const res = await agent.api.app.bsky.feed.getTimeline(
{
algorithm: FeedAlgorithm.ReverseChronological,
before: cursor,
@ -155,7 +155,7 @@ describe('timeline views', () => {
expect(res.feed.length).toBeLessThanOrEqual(4),
)
const full = await client.app.bsky.feed.getTimeline(
const full = await agent.api.app.bsky.feed.getTimeline(
{
algorithm: FeedAlgorithm.ReverseChronological,
},
@ -169,7 +169,7 @@ describe('timeline views', () => {
it('blocks posts, reposts, replies by actor takedown', async () => {
const actionResults = await Promise.all(
[bob, dan].map((did) =>
client.com.atproto.admin.takeModerationAction(
agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -187,7 +187,7 @@ describe('timeline views', () => {
),
)
const aliceTL = await client.app.bsky.feed.getTimeline(
const aliceTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{ headers: sc.getHeaders(alice) },
)
@ -197,7 +197,7 @@ describe('timeline views', () => {
// Cleanup
await Promise.all(
actionResults.map((result) =>
client.com.atproto.admin.reverseModerationAction(
agent.api.com.atproto.admin.reverseModerationAction(
{
id: result.data.id,
createdBy: 'X',
@ -217,7 +217,7 @@ describe('timeline views', () => {
const postUri2 = sc.replies[bob][0].ref.uri // Post and reply parent
const actionResults = await Promise.all(
[postUri1, postUri2].map((postUri) =>
client.com.atproto.admin.takeModerationAction(
agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -235,7 +235,7 @@ describe('timeline views', () => {
),
)
const aliceTL = await client.app.bsky.feed.getTimeline(
const aliceTL = await agent.api.app.bsky.feed.getTimeline(
{ algorithm: FeedAlgorithm.ReverseChronological },
{ headers: sc.getHeaders(alice) },
)
@ -245,7 +245,7 @@ describe('timeline views', () => {
// Cleanup
await Promise.all(
actionResults.map((result) =>
client.com.atproto.admin.reverseModerationAction(
agent.api.com.atproto.admin.reverseModerationAction(
{
id: result.data.id,
createdBy: 'X',

@ -1,4 +1,4 @@
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
import AtpAgent from '@atproto/api'
import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/moderationAction'
import {
runTestServer,
@ -12,7 +12,7 @@ import usersBulkSeed from '../seeds/users-bulk'
import { Database } from '../../src'
describe('pds user search views', () => {
let client: AtpServiceClient
let agent: AtpAgent
let db: Database
let close: CloseFn
let sc: SeedClient
@ -24,8 +24,8 @@ describe('pds user search views', () => {
})
close = server.close
db = server.ctx.db
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await usersBulkSeed(sc)
headers = sc.getHeaders(Object.values(sc.dids)[0])
})
@ -35,7 +35,7 @@ describe('pds user search views', () => {
})
it('typeahead gives relevant results', async () => {
const result = await client.app.bsky.actor.searchTypeahead(
const result = await agent.api.app.bsky.actor.searchTypeahead(
{ term: 'car' },
{ headers },
)
@ -79,7 +79,7 @@ describe('pds user search views', () => {
})
it('typeahead gives empty result set when provided empty term', async () => {
const result = await client.app.bsky.actor.searchTypeahead(
const result = await agent.api.app.bsky.actor.searchTypeahead(
{ term: '' },
{ headers },
)
@ -88,14 +88,14 @@ describe('pds user search views', () => {
})
it('typeahead applies limit', async () => {
const full = await client.app.bsky.actor.searchTypeahead(
const full = await agent.api.app.bsky.actor.searchTypeahead(
{ term: 'p' },
{ headers },
)
expect(full.data.users.length).toBeGreaterThan(5)
const limited = await client.app.bsky.actor.searchTypeahead(
const limited = await agent.api.app.bsky.actor.searchTypeahead(
{ term: 'p', limit: 5 },
{ headers },
)
@ -104,7 +104,7 @@ describe('pds user search views', () => {
})
it('search gives relevant results', async () => {
const result = await client.app.bsky.actor.search(
const result = await agent.api.app.bsky.actor.search(
{ term: 'car' },
{ headers },
)
@ -148,7 +148,10 @@ describe('pds user search views', () => {
})
it('search gives empty result set when provided empty term', async () => {
const result = await client.app.bsky.actor.search({ term: '' }, { headers })
const result = await agent.api.app.bsky.actor.search(
{ term: '' },
{ headers },
)
expect(result.data.users).toEqual([])
})
@ -156,7 +159,7 @@ describe('pds user search views', () => {
it('paginates', async () => {
const results = (results) => results.flatMap((res) => res.users)
const paginator = async (cursor?: string) => {
const res = await client.app.bsky.actor.search(
const res = await agent.api.app.bsky.actor.search(
{ term: 'p', before: cursor, limit: 3 },
{ headers },
)
@ -168,7 +171,10 @@ describe('pds user search views', () => {
expect(res.users.length).toBeLessThanOrEqual(3),
)
const full = await client.app.bsky.actor.search({ term: 'p' }, { headers })
const full = await agent.api.app.bsky.actor.search(
{ term: 'p' },
{ headers },
)
expect(full.data.users.length).toBeGreaterThan(5)
expect(results(paginatedAll)).toEqual(results([full.data]))
@ -177,7 +183,7 @@ describe('pds user search views', () => {
it('search handles bad input', async () => {
// Mostly for sqlite's benefit, since it uses LIKE and these are special characters that will
// get stripped. This input triggers a special case where there are no "safe" words for sqlite to search on.
const result = await client.app.bsky.actor.search(
const result = await agent.api.app.bsky.actor.search(
{ term: ' % _ ' },
{ headers },
)
@ -186,7 +192,7 @@ describe('pds user search views', () => {
})
it('search blocks by actor takedown', async () => {
await client.com.atproto.admin.takeModerationAction(
await agent.api.com.atproto.admin.takeModerationAction(
{
action: TAKEDOWN,
subject: {
@ -201,7 +207,7 @@ describe('pds user search views', () => {
headers: { authorization: adminAuth() },
},
)
const result = await client.app.bsky.actor.searchTypeahead(
const result = await agent.api.app.bsky.actor.searchTypeahead(
{ term: 'car' },
{ headers },
)

@ -1,7 +1,4 @@
import AtpApi, {
AppBskyFeedGetPostThread,
ServiceClient as AtpServiceClient,
} from '@atproto/api'
import AtpAgent, { AppBskyFeedGetPostThread } from '@atproto/api'
import { SeedClient } from '../seeds/client'
import votesSeed from '../seeds/votes'
import {
@ -13,7 +10,7 @@ import {
} from '../_util'
describe('pds vote views', () => {
let client: AtpServiceClient
let agent: AtpAgent
let close: CloseFn
let sc: SeedClient
@ -26,8 +23,8 @@ describe('pds vote views', () => {
dbPostgresSchema: 'views_votes',
})
close = server.close
client = AtpApi.service(server.url)
sc = new SeedClient(client)
agent = new AtpAgent({ service: server.url })
sc = new SeedClient(agent)
await votesSeed(sc)
alice = sc.dids.alice
bob = sc.dids.bob
@ -46,7 +43,7 @@ describe('pds vote views', () => {
const tstamp = (x: string) => new Date(x).getTime()
it('fetches post votes', async () => {
const alicePost = await client.app.bsky.feed.getVotes(
const alicePost = await agent.api.app.bsky.feed.getVotes(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: sc.getHeaders(alice) },
)
@ -58,7 +55,7 @@ describe('pds vote views', () => {
})
it('fetches reply votes', async () => {
const bobReply = await client.app.bsky.feed.getVotes(
const bobReply = await agent.api.app.bsky.feed.getVotes(
{ uri: sc.replies[bob][0].ref.uriStr },
{ headers: sc.getHeaders(alice) },
)
@ -72,7 +69,7 @@ describe('pds vote views', () => {
it('paginates', async () => {
const results = (results) => results.flatMap((res) => res.votes)
const paginator = async (cursor?: string) => {
const res = await client.app.bsky.feed.getVotes(
const res = await agent.api.app.bsky.feed.getVotes(
{
uri: sc.posts[alice][1].ref.uriStr,
before: cursor,
@ -88,7 +85,7 @@ describe('pds vote views', () => {
expect(res.votes.length).toBeLessThanOrEqual(2),
)
const full = await client.app.bsky.feed.getVotes(
const full = await agent.api.app.bsky.feed.getVotes(
{ uri: sc.posts[alice][1].ref.uriStr },
{ headers: sc.getHeaders(alice) },
)
@ -98,14 +95,14 @@ describe('pds vote views', () => {
})
it('filters by direction', async () => {
const full = await client.app.bsky.feed.getVotes(
const full = await agent.api.app.bsky.feed.getVotes(
{
uri: sc.posts[alice][1].ref.uriStr,
},
{ headers: sc.getHeaders(alice) },
)
const upvotes = await client.app.bsky.feed.getVotes(
const upvotes = await agent.api.app.bsky.feed.getVotes(
{
uri: sc.posts[alice][1].ref.uriStr,
direction: 'up',
@ -113,7 +110,7 @@ describe('pds vote views', () => {
{ headers: sc.getHeaders(alice) },
)
const downvotes = await client.app.bsky.feed.getVotes(
const downvotes = await agent.api.app.bsky.feed.getVotes(
{
uri: sc.posts[alice][1].ref.uriStr,
direction: 'down',
@ -150,7 +147,7 @@ describe('pds vote views', () => {
let post: AppBskyFeedGetPostThread.OutputSchema
const getPost = async () => {
const result = await client.app.bsky.feed.getPostThread(
const result = await agent.api.app.bsky.feed.getPostThread(
{
uri: sc.posts[bob][0].ref.uriStr,
depth: 0,
@ -170,7 +167,7 @@ describe('pds vote views', () => {
).toEqual({})
// Upvote
const { data: upvoted } = await client.app.bsky.feed.setVote(
const { data: upvoted } = await agent.api.app.bsky.feed.setVote(
{
direction: 'up',
subject: {
@ -196,7 +193,7 @@ describe('pds vote views', () => {
).toEqual(upvoted)
// Downvote
const { data: downvoted } = await client.app.bsky.feed.setVote(
const { data: downvoted } = await agent.api.app.bsky.feed.setVote(
{
direction: 'down',
subject: {
@ -222,7 +219,7 @@ describe('pds vote views', () => {
).toEqual(downvoted)
// No vote
const { data: novoted } = await client.app.bsky.feed.setVote(
const { data: novoted } = await agent.api.app.bsky.feed.setVote(
{
direction: 'none',
subject: {
@ -254,7 +251,7 @@ describe('pds vote views', () => {
headers: sc.getHeaders(alice),
} as const
const { data: upvotedA } = await client.app.bsky.feed.setVote(
const { data: upvotedA } = await agent.api.app.bsky.feed.setVote(
{
direction: 'up',
subject: {
@ -265,7 +262,7 @@ describe('pds vote views', () => {
asAlice,
)
const { data: upvotedB } = await client.app.bsky.feed.setVote(
const { data: upvotedB } = await agent.api.app.bsky.feed.setVote(
{
direction: 'up',
subject: {

@ -130,7 +130,7 @@ export class ServiceClient {
}
}
async function defaultFetchHandler(
export async function defaultFetchHandler(
httpUri: string,
httpMethod: string,
httpHeaders: Headers,