+6
-6
src/api/mutations/post.ts
+6
-6
src/api/mutations/post.ts
···
15
15
shadow: () => PostShadowView,
16
16
) => {
17
17
const queryClient = useQueryClient();
18
-
const { rpc } = useAgent();
18
+
const { client } = useAgent();
19
19
const { currentAccount } = useSession();
20
20
21
21
const postUri = post().uri;
···
30
30
return prevLikeUri;
31
31
}
32
32
33
-
const result = await createRecord(rpc, {
33
+
const result = await createRecord(client, {
34
34
repo: currentAccount!.did,
35
35
collection: 'app.bsky.feed.like',
36
36
record: {
···
47
47
} else if (prevLikeUri) {
48
48
const uri = parseCanonicalResourceUri(prevLikeUri);
49
49
50
-
await deleteRecord(rpc, {
50
+
await deleteRecord(client, {
51
51
repo: currentAccount!.did,
52
52
collection: 'app.bsky.feed.like',
53
53
rkey: uri.rkey,
···
74
74
shadow: () => PostShadowView,
75
75
) => {
76
76
const queryClient = useQueryClient();
77
-
const { rpc } = useAgent();
77
+
const { client } = useAgent();
78
78
const { currentAccount } = useSession();
79
79
80
80
const postUri = post().uri;
···
89
89
return prevRepostUri;
90
90
}
91
91
92
-
const result = await createRecord(rpc, {
92
+
const result = await createRecord(client, {
93
93
repo: currentAccount!.did,
94
94
collection: 'app.bsky.feed.repost',
95
95
record: {
···
106
106
} else if (prevRepostUri) {
107
107
const uri = parseCanonicalResourceUri(prevRepostUri);
108
108
109
-
await deleteRecord(rpc, {
109
+
await deleteRecord(client, {
110
110
repo: currentAccount!.did,
111
111
collection: 'app.bsky.feed.repost',
112
112
rkey: uri.rkey,
+3
-3
src/api/mutations/profile.ts
+3
-3
src/api/mutations/profile.ts
···
15
15
shadow: () => ProfileShadowView,
16
16
) => {
17
17
const queryClient = useQueryClient();
18
-
const { rpc } = useAgent();
18
+
const { client } = useAgent();
19
19
const { currentAccount } = useSession();
20
20
21
21
const did = profile().did;
···
30
30
return prevFollowUri;
31
31
}
32
32
33
-
const result = await createRecord(rpc, {
33
+
const result = await createRecord(client, {
34
34
repo: currentAccount!.did,
35
35
collection: 'app.bsky.graph.follow',
36
36
record: {
···
44
44
} else if (prevFollowUri) {
45
45
const uri = parseCanonicalResourceUri(prevFollowUri);
46
46
47
-
await deleteRecord(rpc, {
47
+
await deleteRecord(client, {
48
48
repo: currentAccount!.did,
49
49
collection: 'app.bsky.graph.follow',
50
50
rkey: uri.rkey,
+8
-3
src/api/queries/blob.ts
+8
-3
src/api/queries/blob.ts
···
1
-
import type { XRPC } from '@atcute/client';
1
+
import { type Client, ok } from '@atcute/client';
2
2
import type { At } from '@atcute/client/lexicons';
3
3
4
-
export const uploadBlob = async (rpc: XRPC, blob: Blob): Promise<At.Blob<any>> => {
5
-
const { data } = await rpc.call('com.atproto.repo.uploadBlob', { data: blob });
4
+
export const uploadBlob = async (client: Client, blob: Blob): Promise<At.Blob<any>> => {
5
+
const data = await ok(
6
+
client.post('com.atproto.repo.uploadBlob', {
7
+
input: blob,
8
+
}),
9
+
);
10
+
6
11
return data.blob;
7
12
};
+10
-7
src/api/queries/bookmark-feed.ts
+10
-7
src/api/queries/bookmark-feed.ts
···
1
1
import { tokenize } from '@atcute/bluesky-search-parser';
2
+
import { ok } from '@atcute/client';
2
3
import type { AppBskyFeedDefs } from '@atcute/client/lexicons';
3
4
import { mapDefined } from '@mary/array-fns';
4
5
import { filter, map, take, toArray } from '@mary/async-iterator-fns';
···
41
42
42
43
export const createBookmarkFeedQuery = (tagId: () => string, search: () => string) => {
43
44
const bookmarks = inject(BookmarksService);
44
-
const { rpc } = useAgent();
45
+
const { client } = useAgent();
45
46
46
47
const listing = createInfiniteQuery(() => {
47
48
const $tagId = tagId();
···
95
96
let map: Map<string, AppBskyFeedDefs.PostView>;
96
97
97
98
try {
98
-
const { data } = await rpc.get('app.bsky.feed.getPosts', {
99
-
signal: ctx.signal,
100
-
params: {
101
-
uris: raws.map((item) => item.view.uri),
102
-
},
103
-
});
99
+
const data = await ok(
100
+
client.get('app.bsky.feed.getPosts', {
101
+
signal: ctx.signal,
102
+
params: {
103
+
uris: raws.map((item) => item.view.uri),
104
+
},
105
+
}),
106
+
);
104
107
105
108
map = new Map(data.posts.map((view) => [view.uri, view]));
106
109
} catch {}
+11
-8
src/api/queries/feed.ts
+11
-8
src/api/queries/feed.ts
···
1
1
import { modifyMutable, reconcile } from 'solid-js/store';
2
2
3
+
import { ok } from '@atcute/client';
3
4
import type { AppBskyFeedDefs, At } from '@atcute/client/lexicons';
4
5
import { createQuery } from '@mary/solid-query';
5
6
···
14
15
import { resolveHandle } from './handle';
15
16
16
17
export const createFeedMetaQuery = (feedUri: () => string) => {
17
-
const { rpc } = useAgent();
18
+
const { client } = useAgent();
18
19
const { currentAccount } = useSession();
19
20
20
21
return createQuery((queryClient) => {
···
29
30
if (isDid(uri.repo)) {
30
31
did = uri.repo;
31
32
} else {
32
-
did = await resolveHandle(rpc, uri.repo, ctx.signal);
33
+
did = await resolveHandle(client, uri.repo, ctx.signal);
33
34
}
34
35
35
-
const { data } = await rpc.get('app.bsky.feed.getFeedGenerator', {
36
-
signal: ctx.signal,
37
-
params: {
38
-
feed: makeAtUri(did, uri.collection, uri.rkey),
39
-
},
40
-
});
36
+
const data = await ok(
37
+
client.get('app.bsky.feed.getFeedGenerator', {
38
+
signal: ctx.signal,
39
+
params: {
40
+
feed: makeAtUri(did, uri.collection, uri.rkey),
41
+
},
42
+
}),
43
+
);
41
44
42
45
if (currentAccount) {
43
46
const found = currentAccount.preferences.feeds.find((item): item is SavedGeneratorFeed => {
+12
-10
src/api/queries/handle.ts
+12
-10
src/api/queries/handle.ts
···
1
-
import { XRPC } from '@atcute/client';
1
+
import { type Client, ok } from '@atcute/client';
2
2
import type { At } from '@atcute/client/lexicons';
3
3
import { createQuery } from '@mary/solid-query';
4
4
5
5
import { useAgent } from '~/lib/states/agent';
6
6
7
7
export const useResolveHandleQuery = (handle: () => At.Handle) => {
8
-
const { rpc } = useAgent();
8
+
const { client } = useAgent();
9
9
10
10
return createQuery(() => {
11
11
const $handle = handle();
···
13
13
return {
14
14
queryKey: ['resolve-handle', $handle],
15
15
async queryFn(ctx) {
16
-
return resolveHandle(rpc, $handle, ctx.signal);
16
+
return resolveHandle(client, $handle, ctx.signal);
17
17
},
18
18
};
19
19
});
20
20
};
21
21
22
-
export const resolveHandle = async (rpc: XRPC, handle: At.Handle, signal?: AbortSignal) => {
23
-
const { data } = await rpc.get('com.atproto.identity.resolveHandle', {
24
-
signal: signal,
25
-
params: {
26
-
handle: handle,
27
-
},
28
-
});
22
+
export const resolveHandle = async (client: Client, handle: At.Handle, signal?: AbortSignal) => {
23
+
const data = await ok(
24
+
client.get('com.atproto.identity.resolveHandle', {
25
+
signal: signal,
26
+
params: {
27
+
handle: handle,
28
+
},
29
+
}),
30
+
);
29
31
30
32
return data.did;
31
33
};
+14
-12
src/api/queries/labeler.ts
+14
-12
src/api/queries/labeler.ts
···
1
-
import { XRPCError } from '@atcute/client';
1
+
import { ClientResponseError, ok } from '@atcute/client';
2
2
import type { AppBskyLabelerDefs, At } from '@atcute/client/lexicons';
3
3
import { createQuery } from '@mary/solid-query';
4
4
···
7
7
import { interpretLabelerDefinition } from '../moderation/labeler';
8
8
9
9
export const createLabelerMetaQuery = (did: () => At.Did) => {
10
-
const { rpc } = useAgent();
10
+
const { client } = useAgent();
11
11
12
12
const query = createQuery(() => {
13
13
const $did = did();
···
15
15
return {
16
16
queryKey: ['labeler-definition', $did],
17
17
async queryFn(ctx) {
18
-
const { data } = await rpc.get('app.bsky.labeler.getServices', {
19
-
signal: ctx.signal,
20
-
params: {
21
-
dids: [$did],
22
-
detailed: true,
23
-
},
24
-
});
18
+
const data = await ok(
19
+
client.get('app.bsky.labeler.getServices', {
20
+
signal: ctx.signal,
21
+
params: {
22
+
dids: [$did],
23
+
detailed: true,
24
+
},
25
+
}),
26
+
);
25
27
26
28
const service = data.views[0] as AppBskyLabelerDefs.LabelerViewDetailed;
27
29
28
30
if (!service) {
29
-
throw new XRPCError(400, {
30
-
kind: 'NotFound',
31
-
description: `Labeler not found: ${$did}`,
31
+
throw new ClientResponseError({
32
+
status: 400,
33
+
data: { error: `NotFound`, message: `Labeler not found: ${$did}` },
32
34
});
33
35
}
34
36
+12
-9
src/api/queries/list-members.ts
+12
-9
src/api/queries/list-members.ts
···
1
+
import { ok } from '@atcute/client';
1
2
import type { At } from '@atcute/client/lexicons';
2
3
import { type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query';
3
4
4
5
import { useAgent } from '~/lib/states/agent';
5
6
6
7
export const createListMembersQuery = (listUri: () => At.ResourceUri) => {
7
-
const { rpc } = useAgent();
8
+
const { client } = useAgent();
8
9
9
10
return createInfiniteQuery(() => {
10
11
const $listUri = listUri();
···
12
13
return {
13
14
queryKey: ['list-members', $listUri],
14
15
async queryFn(ctx: QC<never, string | undefined>) {
15
-
const { data } = await rpc.get('app.bsky.graph.getList', {
16
-
signal: ctx.signal,
17
-
params: {
18
-
list: $listUri,
19
-
limit: 50,
20
-
cursor: ctx.pageParam,
21
-
},
22
-
});
16
+
const data = await ok(
17
+
client.get('app.bsky.graph.getList', {
18
+
signal: ctx.signal,
19
+
params: {
20
+
list: $listUri,
21
+
limit: 50,
22
+
cursor: ctx.pageParam,
23
+
},
24
+
}),
25
+
);
23
26
24
27
return {
25
28
cursor: data.cursor,
+12
-9
src/api/queries/list.ts
+12
-9
src/api/queries/list.ts
···
1
1
import { modifyMutable, reconcile } from 'solid-js/store';
2
2
3
+
import { ok } from '@atcute/client';
3
4
import type { AppBskyGraphDefs, At } from '@atcute/client/lexicons';
4
5
import { createQuery } from '@mary/solid-query';
5
6
···
14
15
import { resolveHandle } from './handle';
15
16
16
17
export const createListMetaQuery = (listUri: () => string) => {
17
-
const { rpc } = useAgent();
18
+
const { client } = useAgent();
18
19
const { currentAccount } = useSession();
19
20
20
21
return createQuery((queryClient) => {
···
29
30
if (isDid(uri.repo)) {
30
31
did = uri.repo;
31
32
} else {
32
-
did = await resolveHandle(rpc, uri.repo, ctx.signal);
33
+
did = await resolveHandle(client, uri.repo, ctx.signal);
33
34
}
34
35
35
-
const { data } = await rpc.get('app.bsky.graph.getList', {
36
-
signal: ctx.signal,
37
-
params: {
38
-
list: makeAtUri(did, uri.collection, uri.rkey),
39
-
limit: 1,
40
-
},
41
-
});
36
+
const data = await ok(
37
+
client.get('app.bsky.graph.getList', {
38
+
signal: ctx.signal,
39
+
params: {
40
+
list: makeAtUri(did, uri.collection, uri.rkey),
41
+
limit: 1,
42
+
},
43
+
}),
44
+
);
42
45
43
46
if (currentAccount) {
44
47
const found = currentAccount.preferences.feeds.find((item): item is SavedListFeed => {
+30
-23
src/api/queries/my-lists.ts
+30
-23
src/api/queries/my-lists.ts
···
1
+
import { ok } from '@atcute/client';
1
2
import type { AppBskyGraphDefs } from '@atcute/client/lexicons';
2
3
import { createQuery } from '@mary/solid-query';
3
4
···
7
8
export type MyListsFilter = 'all' | 'curation' | 'moderation' | 'all-including-subscribed';
8
9
9
10
export const createMyListsQuery = (filter: MyListsFilter) => {
10
-
const { rpc } = useAgent();
11
+
const { client } = useAgent();
11
12
const { currentAccount } = useSession();
12
13
13
14
return createQuery(() => ({
···
15
16
async queryFn({ signal }) {
16
17
const promises = [
17
18
accumulate(async (cursor) => {
18
-
const { data } = await rpc.get('app.bsky.graph.getLists', {
19
-
signal,
20
-
params: {
21
-
actor: currentAccount!.did,
22
-
cursor,
23
-
limit: 100,
24
-
},
25
-
});
19
+
const data = await ok(
20
+
client.get('app.bsky.graph.getLists', {
21
+
signal,
22
+
params: {
23
+
actor: currentAccount!.did,
24
+
cursor,
25
+
limit: 100,
26
+
},
27
+
}),
28
+
);
26
29
27
30
return {
28
31
cursor: data.cursor,
···
34
37
if (filter === 'all-including-subscribed' || filter === 'moderation') {
35
38
promises.push(
36
39
accumulate(async (cursor) => {
37
-
const { data } = await rpc.get('app.bsky.graph.getListMutes', {
38
-
signal,
39
-
params: {
40
-
cursor,
41
-
limit: 100,
42
-
},
43
-
});
40
+
const data = await ok(
41
+
client.get('app.bsky.graph.getListMutes', {
42
+
signal,
43
+
params: {
44
+
cursor,
45
+
limit: 100,
46
+
},
47
+
}),
48
+
);
44
49
45
50
return {
46
51
cursor: data.cursor,
···
51
56
52
57
promises.push(
53
58
accumulate(async (cursor) => {
54
-
const { data } = await rpc.get('app.bsky.graph.getListBlocks', {
55
-
signal,
56
-
params: {
57
-
cursor,
58
-
limit: 100,
59
-
},
60
-
});
59
+
const data = await ok(
60
+
client.get('app.bsky.graph.getListBlocks', {
61
+
signal,
62
+
params: {
63
+
cursor,
64
+
limit: 100,
65
+
},
66
+
}),
67
+
);
61
68
62
69
return {
63
70
cursor: data.cursor,
+7
-4
src/api/queries/notification-count.tsx
+7
-4
src/api/queries/notification-count.tsx
···
1
+
import { ok } from '@atcute/client';
1
2
import { createQuery } from '@mary/solid-query';
2
3
3
4
import { useAgent } from '~/lib/states/agent';
···
5
6
6
7
export const createNotificationCountQuery = (options?: { readonly disabled?: boolean }) => {
7
8
const { currentAccount } = useSession();
8
-
const { rpc } = useAgent();
9
+
const { client } = useAgent();
9
10
10
11
const query = createQuery(() => ({
11
12
queryKey: ['notification', 'count'],
12
13
enabled: currentAccount !== undefined && !options?.disabled,
13
14
async queryFn() {
14
-
const { data } = await rpc.get('app.bsky.notification.getUnreadCount', {
15
-
params: {},
16
-
});
15
+
const data = await ok(
16
+
client.get('app.bsky.notification.getUnreadCount', {
17
+
params: {},
18
+
}),
19
+
);
17
20
18
21
return data;
19
22
},
+31
-20
src/api/queries/notification-feed.tsx
+31
-20
src/api/queries/notification-feed.tsx
···
1
1
import { createSignal } from 'solid-js';
2
2
3
+
import { ok } from '@atcute/client';
3
4
import type { AppBskyFeedDefs, AppBskyNotificationListNotifications } from '@atcute/client/lexicons';
4
5
import { chunked, mapDefined } from '@mary/array-fns';
5
6
import { type QueryFunctionContext as QC, createInfiniteQuery, useQueryClient } from '@mary/solid-query';
···
84
85
export type NotificationsFilter = 'all' | 'mentions';
85
86
86
87
export const createNotificationFeedQuery = (filter: () => NotificationsFilter) => {
87
-
const { rpc } = useAgent();
88
+
const { client } = useAgent();
88
89
const queryClient = useQueryClient();
89
90
90
91
const [firstFetchedAt, setFirstFetchedAt] = createSignal(0);
···
104
105
reasons = ['mention', 'reply', 'quote'];
105
106
}
106
107
107
-
const { data } = await rpc.get('app.bsky.notification.listNotifications', {
108
-
signal: signal,
109
-
params: {
110
-
limit: 40,
111
-
reasons: reasons,
112
-
cursor: pageParam?.cursor,
113
-
},
114
-
});
108
+
const data = await ok(
109
+
client.get('app.bsky.notification.listNotifications', {
110
+
signal: signal,
111
+
params: {
112
+
limit: 40,
113
+
reasons: reasons,
114
+
cursor: pageParam?.cursor,
115
+
},
116
+
}),
117
+
);
115
118
116
119
const notifs = data.notifications;
117
120
const firstSeenAt = pageParam?.seenAt;
···
129
132
const subjectUri = item.reasonSubject;
130
133
131
134
// skip if they're not related to posts.
132
-
if (!subjectUri || parseCanonicalResourceUri(subjectUri).collection !== 'app.bsky.feed.post') {
135
+
if (
136
+
!subjectUri ||
137
+
parseCanonicalResourceUri(subjectUri).collection !== 'app.bsky.feed.post'
138
+
) {
133
139
return;
134
140
}
135
141
···
142
148
143
149
const chunkedPosts = await Promise.all(
144
150
chunked(Array.from(postUris), 25).map(async (uris) => {
145
-
const { data } = await rpc.get('app.bsky.feed.getPosts', {
146
-
params: {
147
-
uris: uris,
148
-
},
149
-
});
151
+
const data = await ok(
152
+
client.get('app.bsky.feed.getPosts', {
153
+
params: {
154
+
uris: uris,
155
+
},
156
+
}),
157
+
);
150
158
151
159
return data.posts;
152
160
}),
···
238
246
const indexedAt = new Date(notifs[0]?.indexedAt ?? 0).getTime();
239
247
const seenAt = Math.max(now, indexedAt);
240
248
241
-
const promise = rpc.call('app.bsky.notification.updateSeen', {
242
-
data: {
243
-
seenAt: new Date(seenAt).toISOString(),
244
-
},
245
-
});
249
+
const promise = ok(
250
+
client.post('app.bsky.notification.updateSeen', {
251
+
as: null,
252
+
input: {
253
+
seenAt: new Date(seenAt).toISOString(),
254
+
},
255
+
}),
256
+
);
246
257
247
258
queryClient.cancelQueries({
248
259
exact: true,
+12
-9
src/api/queries/post-quotes.ts
+12
-9
src/api/queries/post-quotes.ts
···
1
+
import { ok } from '@atcute/client';
1
2
import type { AppBskyFeedGetQuotes, At } from '@atcute/client/lexicons';
2
3
import { type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query';
3
4
4
5
import { useAgent } from '~/lib/states/agent';
5
6
6
7
export const createPostQuotesQuery = (uri: () => At.ResourceUri) => {
7
-
const { rpc } = useAgent();
8
+
const { client } = useAgent();
8
9
9
10
return createInfiniteQuery(() => {
10
11
const $uri = uri();
···
13
14
queryKey: ['post-quotes', $uri],
14
15
structuralSharing: false,
15
16
async queryFn(ctx: QC<never, string | undefined>): Promise<AppBskyFeedGetQuotes.Output> {
16
-
const { data } = await rpc.get('app.bsky.feed.getQuotes', {
17
-
signal: ctx.signal,
18
-
params: {
19
-
uri: $uri,
20
-
limit: 50,
21
-
cursor: ctx.pageParam,
22
-
},
23
-
});
17
+
const data = await ok(
18
+
client.get('app.bsky.feed.getQuotes', {
19
+
signal: ctx.signal,
20
+
params: {
21
+
uri: $uri,
22
+
limit: 50,
23
+
cursor: ctx.pageParam,
24
+
},
25
+
}),
26
+
);
24
27
25
28
return data;
26
29
},
+18
-13
src/api/queries/post-thread.ts
+18
-13
src/api/queries/post-thread.ts
···
1
-
import { XRPCError } from '@atcute/client';
1
+
import { ClientResponseError, ok } from '@atcute/client';
2
2
import type { AppBskyFeedDefs, At, Brand } from '@atcute/client/lexicons';
3
3
import { createQuery } from '@mary/solid-query';
4
4
···
12
12
type ThreadReturn = Brand.Union<AppBskyFeedDefs.ThreadViewPost | AppBskyFeedDefs.BlockedPost>;
13
13
14
14
export const usePostThreadQuery = (uri: () => At.ResourceUri) => {
15
-
const { rpc } = useAgent();
15
+
const { client } = useAgent();
16
16
17
17
return createQuery((queryClient) => {
18
18
const $uri = uri();
···
21
21
queryKey: ['post-thread', $uri],
22
22
structuralSharing: false,
23
23
async queryFn(ctx): Promise<ThreadReturn> {
24
-
const { data } = await rpc.get('app.bsky.feed.getPostThread', {
25
-
signal: ctx.signal,
26
-
params: {
27
-
uri: $uri,
28
-
depth: MAX_DEPTH,
29
-
parentHeight: MAX_HEIGHT,
30
-
},
31
-
});
24
+
const data = await ok(
25
+
client.get('app.bsky.feed.getPostThread', {
26
+
signal: ctx.signal,
27
+
params: {
28
+
uri: $uri,
29
+
depth: MAX_DEPTH,
30
+
parentHeight: MAX_HEIGHT,
31
+
},
32
+
}),
33
+
);
32
34
33
35
const thread = data.thread;
34
36
35
37
if (thread.$type === 'app.bsky.feed.defs#notFoundPost') {
36
-
throw new XRPCError(400, {
37
-
kind: 'NotFound',
38
-
description: `Post not found: ${$uri}`,
38
+
throw new ClientResponseError({
39
+
status: 400,
40
+
data: {
41
+
error: `NotFound`,
42
+
message: `Post not found: ${$uri}`,
43
+
},
39
44
});
40
45
}
41
46
+11
-8
src/api/queries/post.ts
+11
-8
src/api/queries/post.ts
···
1
+
import { ok } from '@atcute/client';
1
2
import type { At } from '@atcute/client/lexicons';
2
3
import { createQuery } from '@mary/solid-query';
3
4
···
10
11
import { resolveHandle } from './handle';
11
12
12
13
export const createPostQuery = (postUri: () => string) => {
13
-
const { rpc } = useAgent();
14
+
const { client } = useAgent();
14
15
15
16
return createQuery((queryClient) => {
16
17
const $postUri = postUri();
···
24
25
if (isDid(uri.repo)) {
25
26
did = uri.repo;
26
27
} else {
27
-
did = await resolveHandle(rpc, uri.repo, ctx.signal);
28
+
did = await resolveHandle(client, uri.repo, ctx.signal);
28
29
}
29
30
30
-
const { data } = await rpc.get('app.bsky.feed.getPosts', {
31
-
signal: ctx.signal,
32
-
params: {
33
-
uris: [makeAtUri(did, uri.collection, uri.rkey)],
34
-
},
35
-
});
31
+
const data = await ok(
32
+
client.get('app.bsky.feed.getPosts', {
33
+
signal: ctx.signal,
34
+
params: {
35
+
uris: [makeAtUri(did, uri.collection, uri.rkey)],
36
+
},
37
+
}),
38
+
);
36
39
37
40
const post = data.posts[0];
38
41
+11
-8
src/api/queries/profile-autocomplete.ts
+11
-8
src/api/queries/profile-autocomplete.ts
···
1
+
import { ok } from '@atcute/client';
1
2
import { createQuery, keepPreviousData } from '@mary/solid-query';
2
3
3
4
import { useAgent } from '~/lib/states/agent';
···
10
11
query: () => string,
11
12
opts?: ProfileAutocompleteQueryOptions,
12
13
) => {
13
-
const { rpc } = useAgent();
14
+
const { client } = useAgent();
14
15
15
16
return createQuery(() => {
16
17
const $query = query();
···
26
27
enabled: isEnabled,
27
28
placeholderData: isEnabled ? keepPreviousData : undefined,
28
29
async queryFn({ signal }) {
29
-
const { data } = await rpc.get('app.bsky.actor.searchActorsTypeahead', {
30
-
signal,
31
-
params: {
32
-
q: trimmed,
33
-
limit: 10,
34
-
},
35
-
});
30
+
const data = await ok(
31
+
client.get('app.bsky.actor.searchActorsTypeahead', {
32
+
signal,
33
+
params: {
34
+
q: trimmed,
35
+
limit: 10,
36
+
},
37
+
}),
38
+
);
36
39
37
40
return data;
38
41
},
+12
-9
src/api/queries/profile-feeds.ts
+12
-9
src/api/queries/profile-feeds.ts
···
1
+
import { ok } from '@atcute/client';
1
2
import type { At } from '@atcute/client/lexicons';
2
3
import { type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query';
3
4
4
5
import { useAgent } from '~/lib/states/agent';
5
6
6
7
export const createProfileFeedsQuery = (didOrHandle: () => At.Identifier) => {
7
-
const { rpc } = useAgent();
8
+
const { client } = useAgent();
8
9
9
10
return createInfiniteQuery(() => {
10
11
const $didOrHandle = didOrHandle();
···
12
13
return {
13
14
queryKey: ['profile-feeds', $didOrHandle],
14
15
async queryFn(ctx: QC<never, string | undefined>) {
15
-
const { data } = await rpc.get('app.bsky.feed.getActorFeeds', {
16
-
signal: ctx.signal,
17
-
params: {
18
-
actor: $didOrHandle,
19
-
limit: 100,
20
-
cursor: ctx.pageParam,
21
-
},
22
-
});
16
+
const data = await ok(
17
+
client.get('app.bsky.feed.getActorFeeds', {
18
+
signal: ctx.signal,
19
+
params: {
20
+
actor: $didOrHandle,
21
+
limit: 100,
22
+
cursor: ctx.pageParam,
23
+
},
24
+
}),
25
+
);
23
26
24
27
data.feeds.sort((a, b) => (b.likeCount ?? 0) - (a.likeCount ?? 0));
25
28
return data;
+12
-9
src/api/queries/profile-followers.ts
+12
-9
src/api/queries/profile-followers.ts
···
1
+
import { ok } from '@atcute/client';
1
2
import type { AppBskyActorDefs, At } from '@atcute/client/lexicons';
2
3
import { type InfiniteData, type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query';
3
4
···
6
7
import { type ProfilesListWithSubjectPage, toProfilesListWithSubjectPage } from '../types/profile-response';
7
8
8
9
export const createProfileFollowersQuery = (didOrHandle: () => At.Identifier) => {
9
-
const { rpc } = useAgent();
10
+
const { client } = useAgent();
10
11
11
12
return createInfiniteQuery((queryClient) => {
12
13
const $didOrHandle = didOrHandle();
···
14
15
return {
15
16
queryKey: ['profile-followers', $didOrHandle],
16
17
async queryFn(ctx: QC<never, string | undefined>): Promise<ProfilesListWithSubjectPage> {
17
-
const { data } = await rpc.get('app.bsky.graph.getFollowers', {
18
-
signal: ctx.signal,
19
-
params: {
20
-
actor: $didOrHandle,
21
-
limit: 50,
22
-
cursor: ctx.pageParam,
23
-
},
24
-
});
18
+
const data = await ok(
19
+
client.get('app.bsky.graph.getFollowers', {
20
+
signal: ctx.signal,
21
+
params: {
22
+
actor: $didOrHandle,
23
+
limit: 50,
24
+
cursor: ctx.pageParam,
25
+
},
26
+
}),
27
+
);
25
28
26
29
return toProfilesListWithSubjectPage(data, 'followers');
27
30
},
+12
-9
src/api/queries/profile-following.ts
+12
-9
src/api/queries/profile-following.ts
···
1
+
import { ok } from '@atcute/client';
1
2
import type { AppBskyActorDefs, At } from '@atcute/client/lexicons';
2
3
import { type InfiniteData, type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query';
3
4
···
6
7
import { type ProfilesListWithSubjectPage, toProfilesListWithSubjectPage } from '../types/profile-response';
7
8
8
9
export const createProfileFollowingQuery = (didOrHandle: () => At.Identifier) => {
9
-
const { rpc } = useAgent();
10
+
const { client } = useAgent();
10
11
11
12
return createInfiniteQuery((queryClient) => {
12
13
const $didOrHandle = didOrHandle();
···
14
15
return {
15
16
queryKey: ['profile-following', $didOrHandle],
16
17
async queryFn(ctx: QC<never, string | undefined>): Promise<ProfilesListWithSubjectPage> {
17
-
const { data } = await rpc.get('app.bsky.graph.getFollows', {
18
-
signal: ctx.signal,
19
-
params: {
20
-
actor: $didOrHandle,
21
-
limit: 50,
22
-
cursor: ctx.pageParam,
23
-
},
24
-
});
18
+
const data = await ok(
19
+
client.get('app.bsky.graph.getFollows', {
20
+
signal: ctx.signal,
21
+
params: {
22
+
actor: $didOrHandle,
23
+
limit: 50,
24
+
cursor: ctx.pageParam,
25
+
},
26
+
}),
27
+
);
25
28
26
29
return toProfilesListWithSubjectPage(data, 'follows');
27
30
},
+12
-9
src/api/queries/profile-known-followers.ts
+12
-9
src/api/queries/profile-known-followers.ts
···
1
+
import { ok } from '@atcute/client';
1
2
import type { AppBskyActorDefs, At } from '@atcute/client/lexicons';
2
3
import { type InfiniteData, type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query';
3
4
···
6
7
import { type ProfilesListWithSubjectPage, toProfilesListWithSubjectPage } from '../types/profile-response';
7
8
8
9
export const createProfileKnownFollowersQuery = (didOrHandle: () => At.Identifier) => {
9
-
const { rpc } = useAgent();
10
+
const { client } = useAgent();
10
11
11
12
return createInfiniteQuery((queryClient) => {
12
13
const $didOrHandle = didOrHandle();
···
14
15
return {
15
16
queryKey: ['profile-known-followers', $didOrHandle],
16
17
async queryFn(ctx: QC<never, string | undefined>): Promise<ProfilesListWithSubjectPage> {
17
-
const { data } = await rpc.get('app.bsky.graph.getKnownFollowers', {
18
-
signal: ctx.signal,
19
-
params: {
20
-
actor: $didOrHandle,
21
-
limit: 50,
22
-
cursor: ctx.pageParam,
23
-
},
24
-
});
18
+
const data = await ok(
19
+
client.get('app.bsky.graph.getKnownFollowers', {
20
+
signal: ctx.signal,
21
+
params: {
22
+
actor: $didOrHandle,
23
+
limit: 50,
24
+
cursor: ctx.pageParam,
25
+
},
26
+
}),
27
+
);
25
28
26
29
return toProfilesListWithSubjectPage(data, 'followers');
27
30
},
+12
-9
src/api/queries/profile-lists.ts
+12
-9
src/api/queries/profile-lists.ts
···
1
+
import { ok } from '@atcute/client';
1
2
import type { At } from '@atcute/client/lexicons';
2
3
import { type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query';
3
4
4
5
import { useAgent } from '~/lib/states/agent';
5
6
6
7
export const createProfileListsQuery = (didOrHandle: () => At.Identifier) => {
7
-
const { rpc } = useAgent();
8
+
const { client } = useAgent();
8
9
9
10
const collator = new Intl.Collator('en-US');
10
11
···
14
15
return {
15
16
queryKey: ['profile-lists', $didOrHandle],
16
17
async queryFn(ctx: QC<never, string | undefined>) {
17
-
const { data } = await rpc.get('app.bsky.graph.getLists', {
18
-
signal: ctx.signal,
19
-
params: {
20
-
actor: $didOrHandle,
21
-
limit: 100,
22
-
cursor: ctx.pageParam,
23
-
},
24
-
});
18
+
const data = await ok(
19
+
client.get('app.bsky.graph.getLists', {
20
+
signal: ctx.signal,
21
+
params: {
22
+
actor: $didOrHandle,
23
+
limit: 100,
24
+
cursor: ctx.pageParam,
25
+
},
26
+
}),
27
+
);
25
28
26
29
data.lists.sort((a, b) => collator.compare(a.name, b.name));
27
30
return data;
+10
-7
src/api/queries/profile.ts
+10
-7
src/api/queries/profile.ts
···
1
1
import { modifyMutable, reconcile } from 'solid-js/store';
2
2
3
+
import { ok } from '@atcute/client';
3
4
import type { AppBskyActorDefs, At } from '@atcute/client/lexicons';
4
5
import { createQuery } from '@mary/solid-query';
5
6
···
14
15
}
15
16
16
17
export const createProfileQuery = (didOrHandle: () => At.Identifier, opts: ProfileQueryOptions = {}) => {
17
-
const { rpc } = useAgent();
18
+
const { client } = useAgent();
18
19
const { currentAccount } = useSession();
19
20
20
21
return createQuery((queryClient) => {
···
25
26
staleTime: opts.staleTime,
26
27
gcTime: opts.gcTime,
27
28
async queryFn(ctx): Promise<AppBskyActorDefs.ProfileViewDetailed> {
28
-
const { data } = await rpc.get('app.bsky.actor.getProfile', {
29
-
signal: ctx.signal,
30
-
params: {
31
-
actor: $didOrHandle!,
32
-
},
33
-
});
29
+
const data = await ok(
30
+
client.get('app.bsky.actor.getProfile', {
31
+
signal: ctx.signal,
32
+
params: {
33
+
actor: $didOrHandle!,
34
+
},
35
+
}),
36
+
);
34
37
35
38
if (currentAccount !== undefined && currentAccount.did === data.did) {
36
39
// Unset `knownFollowers` as we don't need that on our own profile.
+12
-9
src/api/queries/search-feeds.ts
+12
-9
src/api/queries/search-feeds.ts
···
1
+
import { ok } from '@atcute/client';
1
2
import type { AppBskyUnspeccedGetPopularFeedGenerators } from '@atcute/client/lexicons';
2
3
import { type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query';
3
4
4
5
import { useAgent } from '~/lib/states/agent';
5
6
6
7
export const createSearchFeedsQuery = (query: () => string) => {
7
-
const { rpc } = useAgent();
8
+
const { client } = useAgent();
8
9
9
10
return createInfiniteQuery(() => {
10
11
const q = query();
···
14
15
async queryFn(
15
16
ctx: QC<never, string | undefined>,
16
17
): Promise<AppBskyUnspeccedGetPopularFeedGenerators.Output> {
17
-
const { data } = await rpc.get('app.bsky.unspecced.getPopularFeedGenerators', {
18
-
signal: ctx.signal,
19
-
params: {
20
-
query: q,
21
-
limit: 50,
22
-
cursor: ctx.pageParam,
23
-
},
24
-
});
18
+
const data = await ok(
19
+
client.get('app.bsky.unspecced.getPopularFeedGenerators', {
20
+
signal: ctx.signal,
21
+
params: {
22
+
query: q,
23
+
limit: 50,
24
+
cursor: ctx.pageParam,
25
+
},
26
+
}),
27
+
);
25
28
26
29
return data;
27
30
},
+12
-9
src/api/queries/search-profiles.ts
+12
-9
src/api/queries/search-profiles.ts
···
1
+
import { ok } from '@atcute/client';
1
2
import { type QueryFunctionContext as QC, createInfiniteQuery } from '@mary/solid-query';
2
3
3
4
import { useAgent } from '~/lib/states/agent';
···
5
6
import { type ProfilesListPage, toProfilesListPage } from '../types/profile-response';
6
7
7
8
export const createSearchProfilesQuery = (query: () => string) => {
8
-
const { rpc } = useAgent();
9
+
const { client } = useAgent();
9
10
10
11
return createInfiniteQuery(() => {
11
12
const q = query();
···
13
14
return {
14
15
queryKey: ['search-profiles', q],
15
16
async queryFn(ctx: QC<never, string | undefined>): Promise<ProfilesListPage> {
16
-
const { data } = await rpc.get('app.bsky.actor.searchActors', {
17
-
signal: ctx.signal,
18
-
params: {
19
-
q: q,
20
-
limit: 50,
21
-
cursor: ctx.pageParam,
22
-
},
23
-
});
17
+
const data = await ok(
18
+
client.get('app.bsky.actor.searchActors', {
19
+
signal: ctx.signal,
20
+
params: {
21
+
q: q,
22
+
limit: 50,
23
+
cursor: ctx.pageParam,
24
+
},
25
+
}),
26
+
);
24
27
25
28
return toProfilesListPage(data, 'actors');
26
29
},
+12
-9
src/api/queries/subject-likers.ts
+12
-9
src/api/queries/subject-likers.ts
···
1
+
import { ok } from '@atcute/client';
1
2
import type { At } from '@atcute/client/lexicons';
2
3
import type { QueryFunctionContext as QC } from '@mary/solid-query';
3
4
import { createInfiniteQuery } from '@mary/solid-query';
···
7
8
import type { ProfilesListPage } from '../types/profile-response';
8
9
9
10
export const createSubjectLikersQuery = (uri: () => At.ResourceUri) => {
10
-
const { rpc } = useAgent();
11
+
const { client } = useAgent();
11
12
12
13
return createInfiniteQuery(() => {
13
14
const $uri = uri();
···
16
17
queryKey: ['subject-likers', $uri],
17
18
structuralSharing: false,
18
19
async queryFn(ctx: QC<never, string | undefined>): Promise<ProfilesListPage> {
19
-
const { data } = await rpc.get('app.bsky.feed.getLikes', {
20
-
signal: ctx.signal,
21
-
params: {
22
-
uri: $uri,
23
-
limit: 50,
24
-
cursor: ctx.pageParam,
25
-
},
26
-
});
20
+
const data = await ok(
21
+
client.get('app.bsky.feed.getLikes', {
22
+
signal: ctx.signal,
23
+
params: {
24
+
uri: $uri,
25
+
limit: 50,
26
+
cursor: ctx.pageParam,
27
+
},
28
+
}),
29
+
);
27
30
28
31
return {
29
32
cursor: data.cursor,
+12
-9
src/api/queries/subject-reposters.ts
+12
-9
src/api/queries/subject-reposters.ts
···
1
+
import { ok } from '@atcute/client';
1
2
import type { At } from '@atcute/client/lexicons';
2
3
import type { QueryFunctionContext as QC } from '@mary/solid-query';
3
4
import { createInfiniteQuery } from '@mary/solid-query';
···
7
8
import { type ProfilesListPage, toProfilesListPage } from '../types/profile-response';
8
9
9
10
export const createSubjectRepostersQuery = (uri: () => At.ResourceUri) => {
10
-
const { rpc } = useAgent();
11
+
const { client } = useAgent();
11
12
12
13
return createInfiniteQuery(() => {
13
14
const $uri = uri();
···
16
17
queryKey: ['subject-reposters', $uri],
17
18
structuralSharing: false,
18
19
async queryFn(ctx: QC<never, string | undefined>): Promise<ProfilesListPage> {
19
-
const { data } = await rpc.get('app.bsky.feed.getRepostedBy', {
20
-
signal: ctx.signal,
21
-
params: {
22
-
uri: $uri,
23
-
limit: 50,
24
-
cursor: ctx.pageParam,
25
-
},
26
-
});
20
+
const data = await ok(
21
+
client.get('app.bsky.feed.getRepostedBy', {
22
+
signal: ctx.signal,
23
+
params: {
24
+
uri: $uri,
25
+
limit: 50,
26
+
cursor: ctx.pageParam,
27
+
},
28
+
}),
29
+
);
27
30
28
31
return toProfilesListPage(data, 'repostedBy');
29
32
},
+76
-68
src/api/queries/timeline.ts
+76
-68
src/api/queries/timeline.ts
···
1
1
import { createEffect, createMemo, createRenderEffect, onCleanup, untrack } from 'solid-js';
2
2
3
-
import type { XRPC } from '@atcute/client';
3
+
import { type Client, ok } from '@atcute/client';
4
4
import type { AppBskyFeedDefs, AppBskyFeedGetTimeline, AppBskyFeedPost, At } from '@atcute/client/lexicons';
5
5
import { type FalsyValue, definite } from '@mary/array-fns';
6
6
import { type InfiniteData, createInfiniteQuery, createQuery, useQueryClient } from '@mary/solid-query';
···
126
126
export const useTimelineQuery = (_params: () => TimelineParams) => {
127
127
const getParams = createMemo(() => _params(), EQUALS_DEQUAL);
128
128
129
-
const { rpc } = useAgent();
129
+
const { client } = useAgent();
130
130
const { currentAccount } = useSession();
131
131
const queryClient = useQueryClient();
132
132
···
195
195
postFilter = createLabelPostFilter(moderation);
196
196
}
197
197
198
-
const timeline = await fetchPage(rpc, params, limit, cursor, ctx.signal);
198
+
const timeline = await fetchPage(client, params, limit, cursor, ctx.signal);
199
199
200
200
const feed = timeline.feed;
201
201
const newCursor = timeline.cursor;
···
238
238
// const offset = params.type !== 'profile' ? timelineData!.pages[0].pinAmount : 0;
239
239
const offset = timelineData!.pages[0].pinAmount;
240
240
241
-
const timeline = await fetchPage(rpc, params, offset + 1, undefined, ctx.signal);
241
+
const timeline = await fetchPage(client, params, offset + 1, undefined, ctx.signal);
242
242
const feed = timeline.feed;
243
243
244
244
return { hash: getTimelineHash(feed) };
···
308
308
309
309
//// Raw fetch
310
310
const fetchPage = async (
311
-
rpc: XRPC,
311
+
client: Client,
312
312
params: TimelineParams,
313
313
limit: number,
314
314
cursor: string | undefined,
···
317
317
const type = params.type;
318
318
319
319
if (type === 'following') {
320
-
const response = await rpc.get('app.bsky.feed.getTimeline', {
321
-
signal: signal,
322
-
params: {
323
-
algorithm: 'reverse-chronological',
324
-
cursor: cursor,
325
-
limit: limit,
326
-
},
327
-
});
320
+
const data = await ok(
321
+
client.get('app.bsky.feed.getTimeline', {
322
+
signal: signal,
323
+
params: {
324
+
algorithm: 'reverse-chronological',
325
+
cursor: cursor,
326
+
limit: limit,
327
+
},
328
+
}),
329
+
);
328
330
329
-
return response.data;
331
+
return data;
330
332
} else if (type === 'feed') {
331
-
const response = await rpc.get('app.bsky.feed.getFeed', {
332
-
signal: signal,
333
-
headers: {
334
-
'accent-language': navigator.languages.join(','),
335
-
},
336
-
params: {
337
-
feed: params.uri,
338
-
cursor: cursor,
339
-
limit: limit,
340
-
},
341
-
});
342
-
343
-
const data = response.data;
333
+
const data = await ok(
334
+
client.get('app.bsky.feed.getFeed', {
335
+
signal: signal,
336
+
headers: {
337
+
'accent-language': navigator.languages.join(','),
338
+
},
339
+
params: {
340
+
feed: params.uri,
341
+
cursor: cursor,
342
+
limit: limit,
343
+
},
344
+
}),
345
+
);
344
346
345
347
return {
346
348
// Discover feed, wooo.
···
348
350
feed: data.feed,
349
351
};
350
352
} else if (type === 'list') {
351
-
const response = await rpc.get('app.bsky.feed.getListFeed', {
352
-
signal: signal,
353
-
params: {
354
-
list: params.uri,
355
-
cursor: cursor,
356
-
limit: limit,
357
-
},
358
-
});
359
-
360
-
return response.data;
361
-
} else if (type === 'profile') {
362
-
if (params.tab === 'likes') {
363
-
const response = await rpc.get('app.bsky.feed.getActorLikes', {
353
+
const data = await ok(
354
+
client.get('app.bsky.feed.getListFeed', {
364
355
signal: signal,
365
356
params: {
366
-
actor: params.actor,
357
+
list: params.uri,
367
358
cursor: cursor,
368
359
limit: limit,
369
360
},
370
-
});
361
+
}),
362
+
);
371
363
372
-
return response.data;
364
+
return data;
365
+
} else if (type === 'profile') {
366
+
if (params.tab === 'likes') {
367
+
const data = await ok(
368
+
client.get('app.bsky.feed.getActorLikes', {
369
+
signal: signal,
370
+
params: {
371
+
actor: params.actor,
372
+
cursor: cursor,
373
+
limit: limit,
374
+
},
375
+
}),
376
+
);
377
+
378
+
return data;
373
379
} else {
374
-
const response = await rpc.get('app.bsky.feed.getAuthorFeed', {
380
+
const data = await ok(
381
+
client.get('app.bsky.feed.getAuthorFeed', {
382
+
signal: signal,
383
+
params: {
384
+
actor: params.actor,
385
+
cursor: cursor,
386
+
limit: limit,
387
+
includePins: params.tab !== 'media',
388
+
filter:
389
+
params.tab === 'media'
390
+
? 'posts_with_media'
391
+
: params.tab === 'replies'
392
+
? 'posts_with_replies'
393
+
: 'posts_and_author_threads',
394
+
},
395
+
}),
396
+
);
397
+
398
+
return data;
399
+
}
400
+
} else if (type === 'search') {
401
+
const data = await ok(
402
+
client.get('app.bsky.feed.searchPosts', {
375
403
signal: signal,
376
404
params: {
377
-
actor: params.actor,
405
+
sort: params.sort,
406
+
q: params.query,
378
407
cursor: cursor,
379
408
limit: limit,
380
-
includePins: params.tab !== 'media',
381
-
filter:
382
-
params.tab === 'media'
383
-
? 'posts_with_media'
384
-
: params.tab === 'replies'
385
-
? 'posts_with_replies'
386
-
: 'posts_and_author_threads',
387
409
},
388
-
});
389
-
390
-
return response.data;
391
-
}
392
-
} else if (type === 'search') {
393
-
const response = await rpc.get('app.bsky.feed.searchPosts', {
394
-
signal: signal,
395
-
params: {
396
-
sort: params.sort,
397
-
q: params.query,
398
-
cursor: cursor,
399
-
limit: limit,
400
-
},
401
-
});
402
-
403
-
const data = response.data;
410
+
}),
411
+
);
404
412
405
413
return { cursor: data.cursor, feed: data.posts.map((view) => ({ post: view })) };
406
414
} else {
+9
-7
src/api/utils/did.ts
+9
-7
src/api/utils/did.ts
···
1
-
import type { XRPC } from '@atcute/client';
1
+
import { type Client, ok } from '@atcute/client';
2
2
import type { At } from '@atcute/client/lexicons';
3
3
4
4
import { isDid } from '../types/identity';
5
5
6
-
const getDid = async (rpc: XRPC, actor: At.Handle, signal?: AbortSignal) => {
6
+
const getDid = async (client: Client, actor: At.Handle, signal?: AbortSignal) => {
7
7
let did: At.Did;
8
8
if (isDid(actor)) {
9
9
did = actor;
10
10
} else {
11
-
const response = await rpc.get('com.atproto.identity.resolveHandle', {
12
-
signal: signal,
13
-
params: { handle: actor },
14
-
});
11
+
const data = await ok(
12
+
client.get('com.atproto.identity.resolveHandle', {
13
+
signal: signal,
14
+
params: { handle: actor },
15
+
}),
16
+
);
15
17
16
-
did = response.data.did;
18
+
did = data.did;
17
19
}
18
20
19
21
return did;
+5
-5
src/api/utils/error.ts
+5
-5
src/api/utils/error.ts
···
1
-
import { XRPCError } from '@atcute/client';
1
+
import { ClientResponseError } from '@atcute/client';
2
2
import { TokenRefreshError } from '@atcute/oauth-browser-client';
3
3
4
-
export const formatXRPCError = (err: XRPCError): string => {
5
-
const name = err.kind;
4
+
export const formatXRPCError = (err: ClientResponseError): string => {
5
+
const name = err.error;
6
6
return (name ? name + ': ' : '') + err.description;
7
7
};
8
8
···
11
11
return `Account session is no longer valid`;
12
12
}
13
13
14
-
if (err instanceof XRPCError) {
15
-
const kind = err.kind;
14
+
if (err instanceof ClientResponseError) {
15
+
const kind = err.error;
16
16
17
17
if (kind === 'invalid_token') {
18
18
return `Account session is no longer valid`;
+44
-23
src/api/utils/records.ts
+44
-23
src/api/utils/records.ts
···
1
-
import type { XRPC } from '@atcute/client';
1
+
import { type Client, ok } from '@atcute/client';
2
2
import type {
3
3
At,
4
4
ComAtprotoRepoGetRecord,
···
17
17
validate?: boolean;
18
18
}
19
19
20
-
export const createRecord = async <K extends RecordType>(rpc: XRPC, options: CreateRecordOptions<K>) => {
21
-
const { data } = await rpc.call('com.atproto.repo.createRecord', { data: options });
20
+
export const createRecord = async <K extends RecordType>(client: Client, options: CreateRecordOptions<K>) => {
21
+
const data = await ok(
22
+
client.post('com.atproto.repo.createRecord', {
23
+
input: options,
24
+
}),
25
+
);
22
26
23
27
return data;
24
28
};
···
33
37
validate?: boolean;
34
38
}
35
39
36
-
export const putRecord = async <K extends RecordType>(rpc: XRPC, options: PutRecordOptions<K>) => {
37
-
const { data } = await rpc.call('com.atproto.repo.putRecord', { data: options });
40
+
export const putRecord = async <K extends RecordType>(client: Client, options: PutRecordOptions<K>) => {
41
+
const data = await ok(
42
+
client.post('com.atproto.repo.putRecord', {
43
+
input: options,
44
+
}),
45
+
);
38
46
39
47
return data;
40
48
};
···
47
55
swapRecord?: string;
48
56
}
49
57
50
-
export const deleteRecord = async <K extends RecordType>(rpc: XRPC, options: DeleteRecordOptions<K>) => {
51
-
await rpc.call('com.atproto.repo.deleteRecord', {
52
-
data: options,
53
-
});
58
+
export const deleteRecord = async <K extends RecordType>(client: Client, options: DeleteRecordOptions<K>) => {
59
+
await ok(
60
+
client.post('com.atproto.repo.deleteRecord', {
61
+
input: options,
62
+
}),
63
+
);
54
64
};
55
65
56
66
export interface GetRecordOptions<K extends RecordType> {
67
+
signal?: AbortSignal;
57
68
repo: At.Did;
58
69
collection: K;
59
70
rkey: string;
···
65
76
}
66
77
67
78
export const getRecord = async <K extends RecordType>(
68
-
rpc: XRPC,
79
+
client: Client,
69
80
options: GetRecordOptions<K>,
70
81
): Promise<GetRecordOutput<Records[K]>> => {
71
-
const { data } = await rpc.get('com.atproto.repo.getRecord', {
72
-
params: options,
73
-
});
82
+
const data = await ok(
83
+
client.get('com.atproto.repo.getRecord', {
84
+
signal: options.signal,
85
+
params: {
86
+
repo: options.repo,
87
+
collection: options.collection,
88
+
rkey: options.rkey,
89
+
cid: options.cid,
90
+
},
91
+
}),
92
+
);
74
93
75
94
return data as any;
76
95
};
···
89
108
}
90
109
91
110
export const listRecords = async <K extends RecordType>(
92
-
rpc: XRPC,
111
+
client: Client,
93
112
options: ListRecordsOptions<K>,
94
113
): Promise<ListRecordsOutput<Records[K]>> => {
95
-
const { data } = await rpc.get('com.atproto.repo.listRecords', {
96
-
signal: options.signal,
97
-
params: {
98
-
repo: options.repo,
99
-
collection: options.collection,
100
-
limit: options.limit,
101
-
cursor: options.cursor,
102
-
},
103
-
});
114
+
const data = await ok(
115
+
client.get('com.atproto.repo.listRecords', {
116
+
signal: options.signal,
117
+
params: {
118
+
repo: options.repo,
119
+
collection: options.collection,
120
+
limit: options.limit,
121
+
cursor: options.cursor,
122
+
},
123
+
}),
124
+
);
104
125
105
126
return data as any;
106
127
};
+4
src/basa-env.d.ts
+4
src/basa-env.d.ts
···
57
57
58
58
interface Queries {
59
59
'x.basa.describeServer': {
60
+
response: { json: XBasaDescribeServer.Output };
61
+
/** @deprecated */
60
62
output: XBasaDescribeServer.Output;
61
63
};
62
64
'x.basa.translate': {
63
65
params: XBasaTranslate.Params;
66
+
response: { json: XBasaTranslate.Output };
67
+
/** @deprecated */
64
68
output: XBasaTranslate.Output;
65
69
};
66
70
}
+11
-8
src/components/composer/composer-input.tsx
+11
-8
src/components/composer/composer-input.tsx
···
12
12
createSignal,
13
13
} from 'solid-js';
14
14
15
+
import { ok } from '@atcute/client';
15
16
import type { AppBskyActorDefs } from '@atcute/client/lexicons';
16
17
17
18
import { safeUrlParse } from '~/api/utils/strings';
···
62
63
const onChange = props.onChange;
63
64
const onSubmit = props.onSubmit;
64
65
65
-
const { rpc } = useAgent();
66
+
const { client } = useAgent();
66
67
67
68
const [inputCursor, setInputCursor] = createSignal<number>();
68
69
const [menuSelection, setMenuSelection] = createSignal<number>();
···
135
136
const MATCH_LIMIT = 5;
136
137
137
138
if (type === Suggestion.MENTION) {
138
-
const response = await rpc.get('app.bsky.actor.searchActorsTypeahead', {
139
-
params: {
140
-
q: match.query,
141
-
limit: MATCH_LIMIT,
142
-
},
143
-
});
139
+
const data = await ok(
140
+
client.get('app.bsky.actor.searchActorsTypeahead', {
141
+
params: {
142
+
q: match.query,
143
+
limit: MATCH_LIMIT,
144
+
},
145
+
}),
146
+
);
144
147
145
-
return response.data.actors.map((item) => ({ type: Suggestion.MENTION, data: item }));
148
+
return data.actors.map((item) => ({ type: Suggestion.MENTION, data: item }));
146
149
}
147
150
148
151
assert(false, `expected match`);
+67
-51
src/components/composer/lib/api.ts
+67
-51
src/components/composer/lib/api.ts
···
1
1
import { nanoid } from 'nanoid/non-secure';
2
2
3
-
import { XRPC, XRPCError, simpleFetchHandler } from '@atcute/client';
3
+
import { Client, ClientResponseError, ok, simpleFetchHandler } from '@atcute/client';
4
4
import type {
5
5
AppBskyEmbedImages,
6
6
AppBskyEmbedRecord,
···
57
57
let cidPromise: Promise<typeof import('./cid')>;
58
58
59
59
export const publish = async ({ agent, queryClient, state, onLog: log }: PublishOptions) => {
60
-
const rpc = agent.rpc;
60
+
const client = agent.client;
61
61
const did = agent.did!;
62
62
63
63
const now = new Date();
···
80
80
if (isDid(uri.repo)) {
81
81
did = uri.repo;
82
82
} else {
83
-
did = await resolveHandle(rpc, uri.repo, ctx.signal);
83
+
did = await resolveHandle(client, uri.repo, ctx.signal);
84
84
}
85
85
86
-
const { data } = await rpc.get('app.bsky.feed.getPosts', {
87
-
signal: ctx.signal,
88
-
params: {
89
-
uris: [makeAtUri(did, uri.collection, uri.rkey)],
90
-
},
91
-
});
86
+
const data = await ok(
87
+
client.get('app.bsky.feed.getPosts', {
88
+
signal: ctx.signal,
89
+
params: {
90
+
uris: [makeAtUri(did, uri.collection, uri.rkey)],
91
+
},
92
+
}),
93
+
);
92
94
93
95
const post = data.posts[0];
94
96
···
226
228
227
229
log?.(`Posting`);
228
230
229
-
await rpc.call('com.atproto.repo.applyWrites', {
230
-
data: {
231
-
repo: did,
232
-
writes: writes,
233
-
},
234
-
});
231
+
await ok(
232
+
client.post('com.atproto.repo.applyWrites', {
233
+
input: {
234
+
repo: did,
235
+
writes: writes,
236
+
},
237
+
}),
238
+
);
235
239
236
240
if (state.redraftUri) {
237
241
updatePostShadow(queryClient, state.redraftUri, { deleted: true });
···
298
302
299
303
switch (source.type) {
300
304
case 'local': {
301
-
const uploaded = await uploadBlob(rpc, source.blob);
305
+
const uploaded = await uploadBlob(client, source.blob);
302
306
303
307
return {
304
308
image: uploaded,
···
339
343
340
344
const blob = source.blob;
341
345
342
-
const videoRpc = new XRPC({ handler: simpleFetchHandler({ service: 'https://video.bsky.app' }) });
346
+
const videoClient = new Client({
347
+
handler: simpleFetchHandler({ service: 'https://video.bsky.app' }),
348
+
});
343
349
344
350
// Get upload limit status
345
351
{
···
360
366
// GET https://porcini.us-east.host.bsky.network/xrpc/chat.bsky.convo.getLog
361
367
// atproto-proxy: did:web:api.bsky.chat#bsky_chat
362
368
//
363
-
const { data: tokenData } = await rpc.get('com.atproto.server.getServiceAuth', {
364
-
params: {
365
-
aud: 'did:web:video.bsky.app',
366
-
lxm: 'app.bsky.video.getUploadLimits',
367
-
},
368
-
});
369
+
const tokenData = await ok(
370
+
client.get('com.atproto.server.getServiceAuth', {
371
+
params: {
372
+
aud: 'did:web:video.bsky.app',
373
+
lxm: 'app.bsky.video.getUploadLimits',
374
+
},
375
+
}),
376
+
);
369
377
370
-
const { data } = await videoRpc.get('app.bsky.video.getUploadLimits', {
371
-
headers: {
372
-
authorization: `Bearer ${tokenData.token}`,
373
-
},
374
-
});
378
+
const data = await ok(
379
+
videoClient.get('app.bsky.video.getUploadLimits', {
380
+
headers: {
381
+
authorization: `Bearer ${tokenData.token}`,
382
+
},
383
+
}),
384
+
);
375
385
376
386
if (!data.canUpload) {
377
387
let message = data.message || `You've reached the limit on video uploads`;
···
408
418
409
419
// Create an access token *to the PDS*, allowing the video service to
410
420
// upload the final blobs to our repository on our behalf.
411
-
const { data: tokenData } = await rpc.get('com.atproto.server.getServiceAuth', {
412
-
params: {
413
-
// `did:web:porcini.us-east.host.bsky.network`
414
-
aud: `did:web:${new URL(session.info.aud).host}`,
415
-
lxm: 'com.atproto.repo.uploadBlob',
416
-
exp: Date.now() / 1000 + 60 * 30, // 30 minutes
417
-
},
418
-
});
421
+
const tokenData = await ok(
422
+
client.get('com.atproto.server.getServiceAuth', {
423
+
params: {
424
+
// `did:web:porcini.us-east.host.bsky.network`
425
+
aud: `did:web:${new URL(session.info.aud).host}`,
426
+
lxm: 'com.atproto.repo.uploadBlob',
427
+
exp: Date.now() / 1000 + 60 * 30, // 30 minutes
428
+
},
429
+
}),
430
+
);
419
431
420
432
jobId = await new Promise((resolve, reject) => {
421
433
const xhr = new XMLHttpRequest();
···
475
487
let status: AppBskyVideoDefs.JobStatus;
476
488
477
489
try {
478
-
const { data } = await videoRpc.get('app.bsky.video.getJobStatus', {
479
-
params: {
480
-
jobId: jobId,
481
-
},
482
-
});
490
+
const data = await ok(
491
+
videoClient.get('app.bsky.video.getJobStatus', {
492
+
params: {
493
+
jobId: jobId,
494
+
},
495
+
}),
496
+
);
483
497
484
498
status = data.jobStatus;
485
499
pollFailures = 0;
···
546
560
547
561
log?.(`Uploading GIF thumbnail`);
548
562
const compressed = await compressPostImage(gifBlob);
549
-
const blob = await uploadBlob(rpc, compressed.blob);
563
+
const blob = await uploadBlob(client, compressed.blob);
550
564
551
565
thumbBlob = blob;
552
566
}
···
584
598
log?.(`Uploading link thumbnail`);
585
599
586
600
const compressed = await compressPostImage(thumb);
587
-
const blob = await uploadBlob(rpc, compressed.blob);
601
+
const blob = await uploadBlob(client, compressed.blob);
588
602
589
603
thumbBlob = blob;
590
604
}
···
679
693
}
680
694
681
695
try {
682
-
const response = await rpc.get('com.atproto.identity.resolveHandle', {
683
-
params: {
684
-
handle: handle,
685
-
},
686
-
});
696
+
const data = await ok(
697
+
client.get('com.atproto.identity.resolveHandle', {
698
+
params: {
699
+
handle: handle,
700
+
},
701
+
}),
702
+
);
687
703
688
-
const did = response.data.did;
704
+
const did = data.did;
689
705
690
706
if (!hasSilent) {
691
707
facets.push({
···
699
715
});
700
716
}
701
717
} catch (err) {
702
-
if (err instanceof XRPCError && err.kind === 'InvalidRequest') {
718
+
if (err instanceof ClientResponseError && err.error === 'InvalidRequest') {
703
719
throw new InvalidHandleError(handle);
704
720
}
705
721
···
711
727
features: [{ $type: 'app.bsky.richtext.facet#tag', tag: token.name }],
712
728
});
713
729
} else if (type === 'emote') {
714
-
const { value } = await getRecord(rpc, {
730
+
const { value } = await getRecord(client, {
715
731
repo: did,
716
732
collection: 'blue.moji.collection.item',
717
733
rkey: token.name,
+4
-2
src/components/error-view.tsx
+4
-2
src/components/error-view.tsx
···
1
1
import { Match, Switch } from 'solid-js';
2
2
3
-
import { XRPCError } from '@atcute/client';
3
+
import { ClientResponseError } from '@atcute/client';
4
4
import type { AppBskyActorDefs } from '@atcute/client/lexicons';
5
5
import { TokenRefreshError } from '@atcute/oauth-browser-client';
6
6
import { useQueryClient } from '@mary/solid-query';
···
65
65
export default ErrorView;
66
66
67
67
const isInvalidTokenError = (err: unknown): boolean => {
68
-
return err instanceof TokenRefreshError || (err instanceof XRPCError && err.kind === 'invalid_token');
68
+
return (
69
+
err instanceof TokenRefreshError || (err instanceof ClientResponseError && err.error === 'invalid_token')
70
+
);
69
71
};
+4
-4
src/components/moderation/block-account-prompt.tsx
+4
-4
src/components/moderation/block-account-prompt.tsx
···
56
56
const { close } = useModalContext();
57
57
58
58
const { currentAccount } = useSession();
59
-
const { rpc } = useAgent();
59
+
const { client } = useAgent();
60
60
61
61
const mutation = createMutation((queryClient) => ({
62
62
async mutationFn() {
63
-
return await createRecord(rpc, {
63
+
return await createRecord(client, {
64
64
repo: currentAccount!.did,
65
65
collection: 'app.bsky.graph.block',
66
66
record: {
···
128
128
const UnblockPrompt = (props: BlockAccountPromptInnerProps) => {
129
129
const { close } = useModalContext();
130
130
131
-
const { rpc } = useAgent();
131
+
const { client } = useAgent();
132
132
133
133
const mutation = createMutation((queryClient) => ({
134
134
async mutationFn() {
135
135
const { repo, rkey } = parseCanonicalResourceUri(props.shadow.blockUri!);
136
136
137
-
return await deleteRecord(rpc, {
137
+
return await deleteRecord(client, {
138
138
repo: repo as At.Did,
139
139
collection: 'app.bsky.graph.block',
140
140
rkey: rkey,
+19
-12
src/components/moderation/mute-account-prompt.tsx
+19
-12
src/components/moderation/mute-account-prompt.tsx
···
1
1
import { Match, Switch, onMount } from 'solid-js';
2
2
3
+
import { ok } from '@atcute/client';
3
4
import type { AppBskyActorDefs } from '@atcute/client/lexicons';
4
5
import { createMutation } from '@mary/solid-query';
5
6
···
49
50
const MutePrompt = ({ profile }: MuteAccountPromptProps) => {
50
51
const { close } = useModalContext();
51
52
52
-
const { rpc } = useAgent();
53
+
const { client } = useAgent();
53
54
54
55
const mutation = createMutation((queryClient) => ({
55
56
async mutationFn() {
56
-
await rpc.call('app.bsky.graph.muteActor', {
57
-
data: {
58
-
actor: profile.did,
59
-
},
60
-
});
57
+
await ok(
58
+
client.post('app.bsky.graph.muteActor', {
59
+
as: null,
60
+
input: {
61
+
actor: profile.did,
62
+
},
63
+
}),
64
+
);
61
65
},
62
66
onSuccess() {
63
67
close();
···
115
119
const UnmutePrompt = ({ profile }: MuteAccountPromptProps) => {
116
120
const { close } = useModalContext();
117
121
118
-
const { rpc } = useAgent();
122
+
const { client } = useAgent();
119
123
120
124
const mutation = createMutation((queryClient) => ({
121
125
async mutationFn() {
122
-
await rpc.call('app.bsky.graph.unmuteActor', {
123
-
data: {
124
-
actor: profile.did,
125
-
},
126
-
});
126
+
await ok(
127
+
client.post('app.bsky.graph.unmuteActor', {
128
+
as: null,
129
+
input: {
130
+
actor: profile.did,
131
+
},
132
+
}),
133
+
);
127
134
},
128
135
onSuccess() {
129
136
close();
+7
-7
src/components/profiles/edit-profile-dialog.tsx
+7
-7
src/components/profiles/edit-profile-dialog.tsx
···
1
1
import { Show, createMemo, createSignal } from 'solid-js';
2
2
3
-
import { XRPCError } from '@atcute/client';
3
+
import { ClientResponseError } from '@atcute/client';
4
4
import type { AppBskyActorDefs, At } from '@atcute/client/lexicons';
5
5
import { createMutation } from '@mary/solid-query';
6
6
···
34
34
const EditProfileDialog = ({ profile }: EditProfileDialogProps) => {
35
35
const { close } = useModalContext();
36
36
37
-
const { rpc } = useAgent();
37
+
const { client } = useAgent();
38
38
const { currentAccount } = useSession();
39
39
40
40
const snapshot = {
···
78
78
let bannerPromise: Promise<At.Blob<any>> | undefined;
79
79
80
80
if ($avatar instanceof Blob) {
81
-
avatarPromise = compressProfileImage($avatar, 1000, 1000).then((res) => uploadBlob(rpc, res.blob));
81
+
avatarPromise = compressProfileImage($avatar, 1000, 1000).then((res) => uploadBlob(client, res.blob));
82
82
}
83
83
if ($banner instanceof Blob) {
84
-
bannerPromise = compressProfileImage($banner, 3000, 1000).then((res) => uploadBlob(rpc, res.blob));
84
+
bannerPromise = compressProfileImage($banner, 3000, 1000).then((res) => uploadBlob(client, res.blob));
85
85
}
86
86
87
87
let retriesRemaining = 3;
88
88
while (true) {
89
-
const existing = await getRecord(rpc, {
89
+
const existing = await getRecord(client, {
90
90
repo,
91
91
collection: 'app.bsky.actor.profile',
92
92
rkey: 'self',
···
114
114
}
115
115
116
116
try {
117
-
await putRecord(rpc, {
117
+
await putRecord(client, {
118
118
repo,
119
119
collection: 'app.bsky.actor.profile',
120
120
rkey: 'self',
···
122
122
swapRecord: existing?.cid ?? null,
123
123
});
124
124
} catch (err) {
125
-
if (err instanceof XRPCError && err.kind === 'InvalidSwapError') {
125
+
if (err instanceof ClientResponseError && err.error === 'InvalidSwapError') {
126
126
if (retriesRemaining--) {
127
127
continue;
128
128
}
+11
-8
src/components/search/suggestions/from-actor-autocompletion-view.tsx
+11
-8
src/components/search/suggestions/from-actor-autocompletion-view.tsx
···
1
1
import { For, Show, createMemo } from 'solid-js';
2
2
3
+
import { ok } from '@atcute/client';
3
4
import { createQuery, keepPreviousData } from '@mary/solid-query';
4
5
5
6
import { createProfileQuery } from '~/api/queries/profile';
···
17
18
}) => {
18
19
const { currentAccount } = useSession();
19
20
20
-
const { rpc } = useAgent();
21
+
const { client } = useAgent();
21
22
const isFocused = useIsFocused();
22
23
23
24
const match = createMemo(() => {
···
32
33
enabled: $match !== '' && isFocused(),
33
34
placeholderData: keepPreviousData,
34
35
async queryFn({ signal }) {
35
-
const { data } = await rpc.get('app.bsky.actor.searchActorsTypeahead', {
36
-
signal,
37
-
params: {
38
-
q: $match,
39
-
limit: 10,
40
-
},
41
-
});
36
+
const data = await ok(
37
+
client.get('app.bsky.actor.searchActorsTypeahead', {
38
+
signal,
39
+
params: {
40
+
q: $match,
41
+
limit: 10,
42
+
},
43
+
}),
44
+
);
42
45
43
46
return data;
44
47
},
+10
-7
src/components/settings/app-passwords/add-app-password-prompt.tsx
+10
-7
src/components/settings/app-passwords/add-app-password-prompt.tsx
···
1
1
import { Match, Switch, createSignal } from 'solid-js';
2
2
3
+
import { ok } from '@atcute/client';
3
4
import { createMutation } from '@mary/solid-query';
4
5
5
6
import { autofocusNode, modelChecked, modelText } from '~/lib/input-refs';
···
13
14
export interface AddAppPasswordPromptProps {}
14
15
15
16
const AddAppPasswordPrompt = ({}: AddAppPasswordPromptProps) => {
16
-
const { rpc } = useAgent();
17
+
const { client } = useAgent();
17
18
18
19
const [name, setName] = createSignal('');
19
20
const [privileged, setPrivileged] = createSignal(false);
···
22
23
const mutation = createMutation((queryClient) => {
23
24
return {
24
25
async mutationFn() {
25
-
const { data } = await rpc.call('com.atproto.server.createAppPassword', {
26
-
data: {
27
-
name: name().replace(/^\s+|\s+$|(?<=\s)\s+/g, ''),
28
-
privileged: privileged(),
29
-
},
30
-
});
26
+
const data = await ok(
27
+
client.post('com.atproto.server.createAppPassword', {
28
+
input: {
29
+
name: name().replace(/^\s+|\s+$|(?<=\s)\s+/g, ''),
30
+
privileged: privileged(),
31
+
},
32
+
}),
33
+
);
31
34
32
35
return data;
33
36
},
+5
-5
src/components/settings/bluemoji/add-emote-prompt.tsx
+5
-5
src/components/settings/bluemoji/add-emote-prompt.tsx
···
42
42
const AddEmotePrompt = ({ blob, onAdd }: AddEmotePromptProps) => {
43
43
const { close } = useModalContext();
44
44
45
-
const { rpc } = useAgent();
45
+
const { client } = useAgent();
46
46
const { currentAccount } = useSession();
47
47
48
48
const blobUrl = URL.createObjectURL(blob);
···
71
71
72
72
const { png_128, webp_128 } = await getCompressedEmotes(blob, cover() ? 'cover' : 'contain');
73
73
74
-
const orig_prom = uploadBlob(rpc, blob);
75
-
const png_prom = png_128 !== blob ? uploadBlob(rpc, png_128) : orig_prom;
76
-
const webp_prom = webp_128 !== blob ? uploadBlob(rpc, webp_128) : orig_prom;
74
+
const orig_prom = uploadBlob(client, blob);
75
+
const png_prom = png_128 !== blob ? uploadBlob(client, png_128) : orig_prom;
76
+
const webp_prom = webp_128 !== blob ? uploadBlob(client, webp_128) : orig_prom;
77
77
78
78
const [orig_blob, png_blob, webp_blob] = await Promise.all([orig_prom, png_prom, webp_prom]);
79
79
80
-
await createRecord(rpc, {
80
+
await createRecord(client, {
81
81
repo: currentAccount!.did,
82
82
collection: 'blue.moji.collection.item',
83
83
rkey: $name,
+3
-3
src/components/settings/content-translation/add-basa-instance-prompt.tsx
+3
-3
src/components/settings/content-translation/add-basa-instance-prompt.tsx
···
1
1
import { createMemo, createSignal } from 'solid-js';
2
2
3
-
import { XRPC, simpleFetchHandler } from '@atcute/client';
3
+
import { Client, ok, simpleFetchHandler } from '@atcute/client';
4
4
import { createMutation } from '@mary/solid-query';
5
5
6
6
import { formatQueryError } from '~/api/utils/error';
···
47
47
48
48
const mutation = createMutation(() => ({
49
49
async mutationFn({ url }: { url: URL }) {
50
-
const rpc = new XRPC({ handler: simpleFetchHandler({ service: url }) });
51
-
await rpc.get('x.basa.describeServer', {});
50
+
const client = new Client({ handler: simpleFetchHandler({ service: url }) });
51
+
await ok(client.get('x.basa.describeServer'));
52
52
},
53
53
onSuccess(_data, { url }) {
54
54
const href = url.toString();
+12
-12
src/components/threads/post-translation.tsx
+12
-12
src/components/threads/post-translation.tsx
···
1
1
import { Match, Switch, createMemo, createSignal } from 'solid-js';
2
2
3
-
import { XRPC, simpleFetchHandler } from '@atcute/client';
3
+
import { Client, ok, simpleFetchHandler } from '@atcute/client';
4
4
import { sampleOne } from '@mary/array-fns';
5
5
import { createQuery } from '@mary/solid-query';
6
6
···
66
66
targetLang = found;
67
67
}
68
68
69
-
console.log(targetLang);
70
-
71
-
const rpc = new XRPC({ handler: simpleFetchHandler({ service: $instanceUrl! }) });
72
-
const { data } = await rpc.get('x.basa.translate', {
73
-
params: {
74
-
engine: 'google',
75
-
text: $text,
76
-
from: $source,
77
-
to: targetLang,
78
-
},
79
-
});
69
+
const client = new Client({ handler: simpleFetchHandler({ service: $instanceUrl! }) });
70
+
const data = await ok(
71
+
client.get('x.basa.translate', {
72
+
params: {
73
+
engine: 'google',
74
+
text: $text,
75
+
from: $source,
76
+
to: targetLang,
77
+
},
78
+
}),
79
+
);
80
80
81
81
return data;
82
82
},
+39
-38
src/components/timeline/delete-post-prompt.tsx
+39
-38
src/components/timeline/delete-post-prompt.tsx
···
1
-
import { XRPCError } from '@atcute/client';
1
+
import { ClientResponseError, ok } from '@atcute/client';
2
2
import type { AppBskyFeedDefs, AppBskyFeedPost } from '@atcute/client/lexicons';
3
3
import { useQueryClient } from '@mary/solid-query';
4
4
···
19
19
20
20
const DeletePostPrompt = ({ post, onPostDelete }: DeletePostPromptProps) => {
21
21
const { currentAccount } = useSession();
22
-
const { rpc } = useAgent();
22
+
const { client } = useAgent();
23
23
24
24
const queryClient = useQueryClient();
25
25
26
-
const onDelete = () => {
26
+
const onDelete = async () => {
27
27
const uri = parseCanonicalResourceUri(post.uri);
28
28
29
-
const promise = rpc.call('com.atproto.repo.applyWrites', {
30
-
data: {
29
+
const write = await client.post('com.atproto.repo.applyWrites', {
30
+
input: {
31
31
repo: currentAccount!.did,
32
32
writes: [
33
33
{
···
39
39
},
40
40
});
41
41
42
-
promise.then(
43
-
() => {
44
-
updatePostShadow(queryClient, post.uri, { deleted: true });
45
-
onPostDelete?.();
46
-
},
47
-
async (err) => {
48
-
if (err instanceof XRPCError && err.kind === 'InternalServerError') {
49
-
await rpc.call('com.atproto.repo.putRecord', {
50
-
data: {
51
-
repo: currentAccount!.did,
52
-
collection: 'app.bsky.feed.post',
53
-
rkey: uri.rkey,
54
-
validate: false,
55
-
record: {
56
-
$type: 'app.bsky.feed.post',
57
-
text: '',
58
-
createdAt: '1970-01-01T00:00:00.000Z',
59
-
} satisfies AppBskyFeedPost.Record,
60
-
},
61
-
});
42
+
if (write.ok) {
43
+
updatePostShadow(queryClient, post.uri, { deleted: true });
44
+
onPostDelete?.();
45
+
return;
46
+
}
62
47
63
-
await rpc.call('com.atproto.repo.deleteRecord', {
64
-
data: {
65
-
repo: currentAccount!.did,
66
-
collection: 'app.bsky.feed.post',
67
-
rkey: uri.rkey,
68
-
},
69
-
});
48
+
if (write.data.error !== 'InternalServerError') {
49
+
throw new ClientResponseError(write);
50
+
}
70
51
71
-
updatePostShadow(queryClient, post.uri, { deleted: true });
72
-
onPostDelete?.();
73
-
return;
74
-
}
52
+
await ok(
53
+
client.post('com.atproto.repo.putRecord', {
54
+
input: {
55
+
repo: currentAccount!.did,
56
+
collection: 'app.bsky.feed.post',
57
+
rkey: uri.rkey,
58
+
validate: false,
59
+
record: {
60
+
$type: 'app.bsky.feed.post',
61
+
text: '',
62
+
createdAt: '1970-01-01T00:00:00.000Z',
63
+
} satisfies AppBskyFeedPost.Record,
64
+
},
65
+
}),
66
+
);
75
67
76
-
throw err;
77
-
},
68
+
await ok(
69
+
client.post('com.atproto.repo.deleteRecord', {
70
+
input: {
71
+
repo: currentAccount!.did,
72
+
collection: 'app.bsky.feed.post',
73
+
rkey: uri.rkey,
74
+
},
75
+
}),
78
76
);
77
+
78
+
updatePostShadow(queryClient, post.uri, { deleted: true });
79
+
onPostDelete?.();
79
80
};
80
81
81
82
return (
+5
-5
src/components/timeline/pin-post-prompt.tsx
+5
-5
src/components/timeline/pin-post-prompt.tsx
···
1
1
import { Match, Show, Switch, batch } from 'solid-js';
2
2
3
-
import { XRPCError } from '@atcute/client';
3
+
import { ClientResponseError } from '@atcute/client';
4
4
import type { AppBskyFeedDefs } from '@atcute/client/lexicons';
5
5
import { createMutation } from '@mary/solid-query';
6
6
···
23
23
24
24
const PinPostPrompt = ({ post }: PinPostPromptProps) => {
25
25
const { currentAccount } = useSession();
26
-
const { rpc } = useAgent();
26
+
const { client } = useAgent();
27
27
28
28
const { close } = useModalContext();
29
29
···
40
40
updatePostShadow(queryClient, post.uri, { pinned: next });
41
41
42
42
while (true) {
43
-
const existing = await getRecord(rpc, {
43
+
const existing = await getRecord(client, {
44
44
repo,
45
45
collection: 'app.bsky.actor.profile',
46
46
rkey: 'self',
···
63
63
record.pinnedPost = next ? { uri: post.uri, cid: post.cid } : undefined;
64
64
65
65
try {
66
-
await putRecord(rpc, {
66
+
await putRecord(client, {
67
67
repo,
68
68
collection: 'app.bsky.actor.profile',
69
69
rkey: 'self',
···
71
71
swapRecord: existing?.cid ?? null,
72
72
});
73
73
} catch (err) {
74
-
if (err instanceof XRPCError && err.kind === 'InvalidSwapError') {
74
+
if (err instanceof ClientResponseError && err.error === 'InvalidSwapError') {
75
75
if (retriesRemaining--) {
76
76
continue;
77
77
}
+4
-4
src/lib/states/agent.tsx
+4
-4
src/lib/states/agent.tsx
···
1
1
import { type JSX, type ParentProps, createContext, createMemo, useContext } from 'solid-js';
2
2
3
-
import { XRPC, simpleFetchHandler } from '@atcute/client';
3
+
import { Client, simpleFetchHandler } from '@atcute/client';
4
4
import type { At } from '@atcute/client/lexicons';
5
5
import type { OAuthUserAgent } from '@atcute/oauth-browser-client';
6
6
import { QueryClient, QueryClientProvider } from '@mary/solid-query';
···
15
15
16
16
export interface AgentContext {
17
17
did: At.Did | null;
18
-
rpc: XRPC;
18
+
client: Client;
19
19
handler: OAuthUserAgent | null;
20
20
persister: ReturnType<typeof createQueryPersister>;
21
21
}
···
32
32
return {
33
33
did: currentAccount.did,
34
34
handler: currentAccount.agent ?? null,
35
-
rpc: currentAccount.rpc,
35
+
client: currentAccount.client,
36
36
persister: createQueryPersister({ name: `queryCache-${currentAccount.did}` }),
37
37
};
38
38
}
···
40
40
return {
41
41
did: null,
42
42
handler: null,
43
-
rpc: new XRPC({ handler: simpleFetchHandler({ service: DEFAULT_APPVIEW_URL }) }),
43
+
client: new Client({ handler: simpleFetchHandler({ service: DEFAULT_APPVIEW_URL }) }),
44
44
persister: createQueryPersister({ name: `queryCache-public` }),
45
45
};
46
46
});
+7
-7
src/lib/states/session.tsx
+7
-7
src/lib/states/session.tsx
···
10
10
useContext,
11
11
} from 'solid-js';
12
12
13
-
import { type FetchHandler, type FetchHandlerObject, XRPC, XRPCError } from '@atcute/client';
13
+
import { Client, ClientResponseError, type FetchHandler, type FetchHandlerObject } from '@atcute/client';
14
14
import type { At } from '@atcute/client/lexicons';
15
15
import { OAuthUserAgent, deleteStoredSession, getSession } from '@atcute/oauth-browser-client';
16
16
import { mapDefined } from '@mary/array-fns';
···
31
31
readonly data: AccountData;
32
32
readonly preferences: PerAccountPreferenceSchema;
33
33
34
-
readonly rpc: XRPC;
34
+
readonly client: Client;
35
35
readonly agent: OAuthUserAgent | undefined;
36
36
readonly _cleanup: () => void;
37
37
}
···
62
62
const createAccountState = (
63
63
did: At.Did,
64
64
session: OAuthUserAgent | undefined,
65
-
rpc: XRPC,
65
+
client: Client,
66
66
): CurrentAccountState => {
67
67
return createRoot((cleanup): CurrentAccountState => {
68
68
const preferences = createAccountPreferences(did);
···
80
80
});
81
81
82
82
// A bit of a hack, but works right now.
83
-
rpc.handle = attachLabelerHeaders(rpc.handle, labelers);
83
+
client.handler = attachLabelerHeaders(client.handler, labelers);
84
84
85
85
createEffect(() => {
86
86
const signal = abortable();
···
129
129
return $data;
130
130
},
131
131
132
-
rpc: rpc,
132
+
client: client,
133
133
agent: session,
134
134
_cleanup: cleanup,
135
135
};
···
181
181
// Dirty hack to account for the fact that we aren't dumping the user
182
182
// directly to the login modal.
183
183
handler = () => {
184
-
throw new XRPCError(400, { kind: 'invalid_token' });
184
+
throw new ClientResponseError({ status: 400, data: { error: 'invalid_token' } });
185
185
};
186
186
}
187
187
188
-
const rpc = new XRPC({ handler });
188
+
const rpc = new Client({ handler });
189
189
190
190
signal.throwIfAborted();
191
191
+13
-13
src/lib/states/singletons/moderation.ts
+13
-13
src/lib/states/singletons/moderation.ts
···
1
1
import { createMemo } from 'solid-js';
2
2
import { unwrap } from 'solid-js/store';
3
3
4
-
import type { AppBskyLabelerDefs, At } from '@atcute/client/lexicons';
4
+
import { ok } from '@atcute/client';
5
+
import type { At } from '@atcute/client/lexicons';
5
6
import { mapDefined } from '@mary/array-fns';
6
7
import { createBatchedFetch } from '@mary/batch-fetch';
7
8
import { type QueryFunctionContext as QC, createQueries } from '@mary/solid-query';
···
14
15
import { useSession } from '../session';
15
16
import { define } from '../singleton';
16
17
17
-
type Labeler = AppBskyLabelerDefs.LabelerViewDetailed;
18
-
19
18
const ModerationService = define('moderation', () => {
20
-
const { rpc, persister } = useAgent();
19
+
const { client, persister } = useAgent();
21
20
const { currentAccount } = useSession();
22
21
23
22
const modPreferences = createMemo((): ModerationPreferences => {
···
44
43
timeout: 1,
45
44
idFromResource: (labeler) => labeler.did,
46
45
async fetch(dids, signal) {
47
-
const { data } = await rpc.get('app.bsky.labeler.getServices', {
48
-
signal,
49
-
params: {
50
-
dids: dids,
51
-
detailed: true,
52
-
},
53
-
});
54
-
55
-
const views = data.views as Labeler[];
46
+
const data = await ok(
47
+
client.get('app.bsky.labeler.getServices', {
48
+
signal,
49
+
params: {
50
+
dids: dids,
51
+
detailed: true,
52
+
},
53
+
}),
54
+
);
56
55
56
+
const views = data.views.filter((view) => view.$type === 'app.bsky.labeler.defs#labelerViewDetailed');
57
57
return views.map((view) => interpretLabelerDefinition(view));
58
58
},
59
59
});
+2
-2
src/views/bluemoji-emotes.tsx
+2
-2
src/views/bluemoji-emotes.tsx
···
45
45
openModal(() => <AddEmotePrompt blob={blob} onAdd={() => {}} />);
46
46
};
47
47
48
-
const { rpc } = useAgent();
48
+
const { client } = useAgent();
49
49
const { currentAccount } = useSession();
50
50
51
51
const query = createInfiniteQuery(() => ({
52
52
queryKey: ['bluemoji', 'emotes'],
53
53
async queryFn(ctx) {
54
-
return listRecords(rpc, {
54
+
return listRecords(client, {
55
55
repo: currentAccount!.did,
56
56
collection: 'blue.moji.collection.item',
57
57
limit: 100,
+9
-7
src/views/oauth-callback.tsx
+9
-7
src/views/oauth-callback.tsx
···
1
1
import { Match, Switch, createResource } from 'solid-js';
2
2
3
-
import { XRPC } from '@atcute/client';
3
+
import { Client, ok } from '@atcute/client';
4
4
import {
5
5
AuthorizationError,
6
6
OAuthResponseError,
···
27
27
const did = session.info.sub;
28
28
29
29
const agent = new OAuthUserAgent(session);
30
-
const rpc = new XRPC({ handler: agent });
30
+
const client = new Client({ handler: agent });
31
31
32
-
const { data: profile } = await rpc.get('app.bsky.actor.getProfile', {
33
-
params: {
34
-
actor: did,
35
-
},
36
-
});
32
+
const profile = await ok(
33
+
client.get('app.bsky.actor.getProfile', {
34
+
params: {
35
+
actor: did,
36
+
},
37
+
}),
38
+
);
37
39
38
40
{
39
41
// Update UI preferences
+5
-5
src/views/post-thread.tsx
+5
-5
src/views/post-thread.tsx
···
1
1
import { For, Match, Switch, createEffect, createMemo, createSignal } from 'solid-js';
2
2
3
-
import { XRPCError } from '@atcute/client';
3
+
import { ClientResponseError } from '@atcute/client';
4
4
import type { AppBskyFeedDefs, AppBskyFeedPost, At, Brand } from '@atcute/client/lexicons';
5
5
import { useQueryClient } from '@mary/solid-query';
6
6
···
78
78
79
79
<Switch>
80
80
<Match when={query.error} keyed>
81
-
{(error) => {
82
-
if (error instanceof XRPCError) {
83
-
if (error.kind === 'NotFound') {
81
+
{(err) => {
82
+
if (err instanceof ClientResponseError) {
83
+
if (err.error === 'NotFound') {
84
84
return (
85
85
<div class="px-4 py-3">
86
86
<div class="rounded-md border border-outline p-3">
···
91
91
}
92
92
}
93
93
94
-
return <ErrorView error={error} onRetry={() => query.refetch()} />;
94
+
return <ErrorView error={err} onRetry={() => query.refetch()} />;
95
95
}}
96
96
</Match>
97
97
+7
-7
src/views/profile.tsx
+7
-7
src/views/profile.tsx
···
1
1
import { Match, Show, Switch, createMemo } from 'solid-js';
2
2
3
-
import { XRPCError } from '@atcute/client';
3
+
import { ClientResponseError } from '@atcute/client';
4
4
import type { AppBskyActorDefs, At } from '@atcute/client/lexicons';
5
5
import { useQueryClient } from '@mary/solid-query';
6
6
···
117
117
<Match when={profile.error} keyed>
118
118
{(err) => {
119
119
if (
120
-
err instanceof XRPCError &&
121
-
(err.kind === 'InvalidRequest' ||
122
-
err.kind === 'AccountTakedown' ||
123
-
err.kind === 'AccountDeactivated')
120
+
err instanceof ClientResponseError &&
121
+
(err.error === 'InvalidRequest' ||
122
+
err.error === 'AccountTakedown' ||
123
+
err.error === 'AccountDeactivated')
124
124
) {
125
125
const text =
126
-
err.kind === 'AccountTakedown'
126
+
err.error === 'AccountTakedown'
127
127
? `This account is taken down`
128
-
: err.kind === 'AccountDeactivated'
128
+
: err.error === 'AccountDeactivated'
129
129
? `This account has deactivated`
130
130
: `This account doesn't exist`;
131
131
+7
-6
src/views/settings-account.tsx
+7
-6
src/views/settings-account.tsx
···
1
+
import { ok } from '@atcute/client';
1
2
import { createQuery } from '@mary/solid-query';
2
3
3
4
import { useTitle } from '~/lib/navigation/router';
···
7
8
import * as Page from '~/components/page';
8
9
9
10
const AccountSettingsPage = () => {
10
-
const { did, rpc, persister } = useAgent();
11
+
const { did, client, persister } = useAgent();
11
12
12
13
const repo = createQuery(() => ({
13
14
queryKey: ['describe-repo'],
14
15
persister: persister as any,
15
16
async queryFn() {
16
-
const [repoResponse, serverResponse] = await Promise.all([
17
-
rpc.get('com.atproto.repo.describeRepo', { params: { repo: did! } }),
18
-
rpc.handle('/xrpc/com.atproto.server.describeServer', {}),
17
+
const [repo, server] = await Promise.all([
18
+
ok(client.get('com.atproto.repo.describeRepo', { params: { repo: did! } })),
19
+
ok(client.get('com.atproto.server.describeServer')),
19
20
]);
20
21
21
22
return {
22
-
handle: repoResponse.data.handle,
23
-
pds: new URL(serverResponse.url).host,
23
+
handle: repo.handle,
24
+
pds: server.did.replace(/^did:web:/, ''),
24
25
};
25
26
},
26
27
}));
+10
-6
src/views/settings-app-passwords.tsx
+10
-6
src/views/settings-app-passwords.tsx
···
1
1
import { For, Match, Show, Switch } from 'solid-js';
2
2
3
+
import { ok } from '@atcute/client';
3
4
import type { ComAtprotoServerListAppPasswords } from '@atcute/client/lexicons';
4
5
import { createMutation, createQuery } from '@mary/solid-query';
5
6
···
22
23
import AddAppPasswordPrompt from '~/components/settings/app-passwords/add-app-password-prompt';
23
24
24
25
const AppPasswordsSettingsPage = () => {
25
-
const { rpc } = useAgent();
26
+
const { client } = useAgent();
26
27
27
28
const passwords = createQuery(() => {
28
29
return {
29
30
queryKey: ['app-passwords'],
30
31
async queryFn() {
31
-
const { data } = await rpc.get('com.atproto.server.listAppPasswords', {});
32
+
const data = await ok(client.get('com.atproto.server.listAppPasswords'));
32
33
33
34
return data.passwords;
34
35
},
···
105
106
}
106
107
107
108
const PasswordEntry = ({ item }: PasswordEntryProps) => {
108
-
const { rpc } = useAgent();
109
+
const { client } = useAgent();
109
110
110
111
const isPrivileged = item.privileged;
111
112
112
113
const mutation = createMutation((queryClient) => {
113
114
return {
114
115
async mutationFn() {
115
-
await rpc.call('com.atproto.server.revokeAppPassword', {
116
-
data: { name: item.name },
117
-
});
116
+
await ok(
117
+
client.post('com.atproto.server.revokeAppPassword', {
118
+
as: null,
119
+
input: { name: item.name },
120
+
}),
121
+
);
118
122
},
119
123
async onSuccess() {
120
124
await queryClient.invalidateQueries({ queryKey: ['app-passwords'] });