···11-import type { AtprotoDid, DidDocument } from '@atcute/identity';
11+import type { DidDocument } from '@atcute/identity';
22import {
33 CompositeDidDocumentResolver,
44 PlcDidDocumentResolver,
55 WebDidDocumentResolver,
66} from '@atcute/identity-resolver';
77+import type { AtprotoDid } from '@atcute/lexicons/syntax';
7889const didDocumentResolver = new CompositeDidDocumentResolver({
910 methods: {
+1-1
src/api/queries/handle.ts
···11-import { type AtprotoDid, type Handle, isHandle } from '@atcute/identity';
21import { XrpcHandleResolver } from '@atcute/identity-resolver';
22+import { type AtprotoDid, type Handle, isHandle } from '@atcute/lexicons/syntax';
3344const handleResolver = new XrpcHandleResolver({
55 serviceUrl: import.meta.env.VITE_APPVIEW_URL,
+1-1
src/api/queries/plc.ts
···11import { defs } from '@atcute/did-plc';
22-import { Did } from '@atcute/identity';
22+import { Did } from '@atcute/lexicons/syntax';
3344export const getPlcAuditLogs = async ({ did, signal }: { did: Did<'plc'>; signal?: AbortSignal }) => {
55 const origin = import.meta.env.VITE_PLC_DIRECTORY_URL;
···11import { createSignal, Show } from 'solid-js';
2233-import { XRPC, XRPCError } from '@atcute/client';
44-import type { AppBskyFeedThreadgate, ComAtprotoRepoApplyWrites } from '@atcute/client/lexicons';
33+import type { ComAtprotoRepoApplyWrites } from '@atcute/atproto';
44+import type { AppBskyFeedThreadgate } from '@atcute/bluesky';
55+import { Client, ClientResponseError } from '@atcute/client';
66+import { InferXRPCBodyInput } from '@atcute/lexicons';
57import { chunked } from '@mary/array-fns';
66-77-import { parseAddressedAtUri } from '~/api/utils/at-uri';
88+import { parseCanonicalResourceUri } from '@atcute/lexicons';
89910import { dequal } from '~/lib/utils/dequal';
1011import { createMutation } from '~/lib/utils/mutation';
···3435 logger.log(`Preparing writes`);
35363637 const rules = data.rules;
3737- const writes: ComAtprotoRepoApplyWrites.Input['writes'] = [];
3838+ const writes: InferXRPCBodyInput<ComAtprotoRepoApplyWrites.mainSchema['input']>['writes'] = [];
38393940 const now = new Date().toISOString();
4041 for (const { post, threadgate } of data.threads) {
4142 if (threadgate === null) {
4243 if (rules !== undefined) {
4343- const { rkey } = parseAddressedAtUri(post.uri);
4444+ const postUri = parseCanonicalResourceUri(post.uri);
4545+ if (!postUri.ok) {
4646+ throw new Error(`failed to parse ${post.uri}`);
4747+ }
44484545- const record: AppBskyFeedThreadgate.Record = {
4949+ const record: AppBskyFeedThreadgate.Main = {
4650 $type: 'app.bsky.feed.threadgate',
4751 createdAt: now,
4852 post: post.uri,
···5357 writes.push({
5458 $type: 'com.atproto.repo.applyWrites#create',
5559 collection: 'app.bsky.feed.threadgate',
5656- rkey: rkey,
6060+ rkey: postUri.value.rkey,
5761 value: record,
5862 });
5963 }
6064 } else {
6165 if (rules === undefined && !threadgate.hiddenReplies?.length) {
6262- const { rkey } = parseAddressedAtUri(threadgate.uri);
6666+ const threadgateUri = parseCanonicalResourceUri(threadgate.uri);
6767+ if (!threadgateUri.ok) {
6868+ throw new Error(`failed to parse ${threadgate.uri}`);
6969+ }
63706471 writes.push({
6572 $type: 'com.atproto.repo.applyWrites#delete',
6673 collection: 'app.bsky.feed.threadgate',
6767- rkey: rkey,
7474+ rkey: threadgateUri.value.rkey,
6875 });
6976 } else if (!dequal(threadgate.allow, rules)) {
7070- const { rkey } = parseAddressedAtUri(threadgate.uri);
7777+ const threadgateUri = parseCanonicalResourceUri(threadgate.uri);
7878+ if (!threadgateUri.ok) {
7979+ throw new Error(`failed to parse ${threadgate.uri}`);
8080+ }
71817272- const record: AppBskyFeedThreadgate.Record = {
8282+ const record: AppBskyFeedThreadgate.Main = {
7383 $type: 'app.bsky.feed.threadgate',
7484 createdAt: threadgate.createdAt,
7585 post: post.uri,
···8090 writes.push({
8191 $type: 'com.atproto.repo.applyWrites#update',
8292 collection: 'app.bsky.feed.threadgate',
8383- rkey: rkey,
9393+ rkey: threadgateUri.value.rkey,
8494 value: record,
8595 });
8696 }
···90100 logger.log(`${writes.length} write operations to apply`);
9110192102 const did = data.profile.didDoc.id;
9393- const rpc = new XRPC({ handler: data.manager });
9494-9595- const RATELIMIT_POINT_LIMIT = 150 * 3;
103103+ const client = new Client({ handler: data.manager });
9610497105 {
98106 using progress = logger.progress(`Applying writes`);
99107100108 let written = 0;
101109 for (const chunk of chunked(writes, 200)) {
102102- try {
103103- const { headers } = await rpc.call('com.atproto.repo.applyWrites', {
104104- data: {
105105- repo: did,
106106- writes: chunk,
107107- },
108108- });
110110+ let attempts = 0;
109111110110- written += chunk.length;
111111- progress.update(`Applying writes (${written} applied)`);
112112+ while (true) {
113113+ if (attempts > 0) {
114114+ await sleep(2_000);
115115+ }
112116113113- if ('ratelimit-remaining' in headers) {
114114- const remaining = +headers['ratelimit-remaining'];
115115- const reset = +headers['ratelimit-reset'] * 1_000;
117117+ attempts++;
116118117117- if (remaining < RATELIMIT_POINT_LIMIT) {
118118- // add some delay to be sure
119119- const delta = reset - Date.now() + 5_000;
120120- using _progress = logger.progress(`Reached ratelimit, waiting ${delta}ms`);
119119+ try {
120120+ const response = await client.post('com.atproto.repo.applyWrites', {
121121+ input: {
122122+ repo: did,
123123+ writes: chunk,
124124+ },
125125+ });
121126122122- await new Promise((resolve) => setTimeout(resolve, delta));
127127+ if (response.ok) {
128128+ written += chunk.length;
129129+ progress.update(`Applying writes (${written} applied)`);
130130+ break;
123131 }
124124- }
125125- } catch (err) {
126126- if (!(err instanceof XRPCError) || err.kind !== 'RateLimitExceeded') {
127127- throw err;
128128- }
132132+133133+ if (response.status === 429) {
134134+ // not exposed by CORS, hoping that someday it will
135135+ const reset = response.headers.get('ratelimit-reset');
136136+137137+ using _progress = logger.progress(`Ratelimited, waiting`);
138138+139139+ if (reset !== null) {
140140+ const refreshAt = +reset * 1_000;
141141+ const delta = refreshAt - Date.now();
129142130130- const headers = err.headers;
131131- if ('ratelimit-remaining' in headers) {
132132- const remaining = +headers['ratelimit-remaining'];
133133- const reset = +headers['ratelimit-reset'] * 1_000;
143143+ await sleep(delta);
144144+ } else {
145145+ await sleep(10_000);
146146+ }
147147+ }
134148135135- if (remaining < RATELIMIT_POINT_LIMIT) {
136136- // add some delay to be sure
137137- const delta = reset - Date.now() + 5_000;
138138- using _progress = logger.progress(`Ratelimited, waiting ${delta}ms`);
149149+ if (attempts < 3) {
150150+ continue;
151151+ }
139152140140- await new Promise((resolve) => setTimeout(resolve, delta));
153153+ throw new ClientResponseError(response);
154154+ } catch (err) {
155155+ // Network errors, etc
156156+ if (attempts < 3) {
157157+ continue;
141158 }
142142- } else {
143143- using _progress = logger.progress(`Ratelimited, waiting`);
144159145145- await new Promise((resolve) => setTimeout(resolve, 60 * 1_000));
160160+ throw err;
146161 }
147162 }
148163 }
···202217};
203218204219export default Step4_Confirmation;
220220+221221+const sleep = (ms: number): Promise<void> => {
222222+ return new Promise((resolve) => setTimeout(resolve, ms));
223223+};
+2-3
src/views/identity/did-lookup.tsx
···11import { Match, Switch } from 'solid-js';
2233-import { isAtprotoDid, isHandle, type AtprotoDid, type Did, type Handle } from '@atcute/identity';
33+import { isAtprotoDid } from '@atcute/identity';
44+import { isHandle, type AtprotoDid, type Did, type Handle } from '@atcute/lexicons/syntax';
4556import { getDidDocument } from '~/api/queries/did-doc';
67import { resolveHandleViaAppView } from '~/api/queries/handle';
78import { isServiceUrlString } from '~/api/types/strings';
88-import { DID_OR_HANDLE_RE } from '~/api/utils/strings';
991010import { useTitle } from '~/lib/navigation/router';
1111import { createQuery } from '~/lib/utils/query';
···6767 type="text"
6868 name="ident"
6969 autocomplete="username"
7070- pattern={/* @once */ DID_OR_HANDLE_RE.source}
7170 placeholder="paul.bsky.social"
7271 autofocus
7372 />
+5-3
src/views/identity/plc-applicator/page.tsx
···11import { createEffect, createSignal, onCleanup } from 'solid-js';
2233+import type { ComAtprotoIdentityGetRecommendedDidCredentials } from '@atcute/atproto';
34import type { CredentialManager } from '@atcute/client';
44-import type { ComAtprotoIdentityGetRecommendedDidCredentials } from '@atcute/client/lexicons';
55import type { P256PrivateKey, Secp256k1PrivateKey } from '@atcute/crypto';
66import type { CompatibleOperation, IndexedEntry, IndexedEntryWithSigner } from '@atcute/did-plc';
77-import type { Did, DidDocument } from '@atcute/identity';
77+import type { DidDocument } from '@atcute/identity';
88+import { InferXRPCBodyInput } from '@atcute/lexicons';
99+import type { Did } from '@atcute/lexicons/syntax';
810911import { UpdatePayload } from '~/api/types/plc';
1012···3133export interface PdsSigningMethod {
3234 type: 'pds';
3335 manager: CredentialManager;
3434- recommendedDidDoc: ComAtprotoIdentityGetRecommendedDidCredentials.Output;
3636+ recommendedDidDoc: InferXRPCBodyInput<ComAtprotoIdentityGetRecommendedDidCredentials.mainSchema['output']>;
3537}
36383739export type Keypair = P256PrivateKey | Secp256k1PrivateKey;
···77 type IndexedEntryWithSigner,
88 normalizeOp,
99} from '@atcute/did-plc';
1010-import type { Did } from '@atcute/identity';
1010+import type { Did } from '@atcute/lexicons';
11111212import Button from '~/components/inputs/button';
1313+import RadioInput from '~/components/inputs/radio-input';
1314import SelectInput from '~/components/inputs/select-input';
1415import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard';
15161617import { PlcApplicatorConstraints } from '../page';
1717-import RadioInput from '~/components/inputs/radio-input';
18181919const Step3_OperationSelect = ({
2020 data,