handy online tools for AT Protocol boat.kelinci.net
atproto bluesky atcute typescript solidjs
20
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: move to new atcute

Mary b04b9700 89c167d7

+333 -274
+6 -4
package.json
··· 7 7 "fmt": "prettier --cache --write ." 8 8 }, 9 9 "dependencies": { 10 - "@atcute/bluesky": "^2.1.1", 10 + "@atcute/atproto": "^3.0.2", 11 + "@atcute/bluesky": "^3.0.2", 11 12 "@atcute/car": "^3.1.1", 12 13 "@atcute/cbor": "^2.2.4", 13 14 "@atcute/cid": "^2.2.3", 14 - "@atcute/client": "^3.1.0", 15 + "@atcute/client": "^4.0.3", 15 16 "@atcute/crypto": "^2.2.2", 16 17 "@atcute/did-plc": "^0.1.5", 17 - "@atcute/identity": "^0.1.3", 18 - "@atcute/identity-resolver": "^0.1.2", 18 + "@atcute/identity": "^1.0.2", 19 + "@atcute/identity-resolver": "^1.1.0", 20 + "@atcute/lexicons": "^1.0.3", 19 21 "@atcute/multibase": "^1.1.4", 20 22 "@atcute/tid": "^1.0.2", 21 23 "@badrap/valita": "^0.4.5",
+54 -25
pnpm-lock.yaml
··· 8 8 9 9 .: 10 10 dependencies: 11 + '@atcute/atproto': 12 + specifier: ^3.0.2 13 + version: 3.0.2 11 14 '@atcute/bluesky': 12 - specifier: ^2.1.1 13 - version: 2.1.1(@atcute/client@3.1.0) 15 + specifier: ^3.0.2 16 + version: 3.0.2 14 17 '@atcute/car': 15 18 specifier: ^3.1.1 16 19 version: 3.1.1 ··· 21 24 specifier: ^2.2.3 22 25 version: 2.2.3 23 26 '@atcute/client': 24 - specifier: ^3.1.0 25 - version: 3.1.0 27 + specifier: ^4.0.3 28 + version: 4.0.3 26 29 '@atcute/crypto': 27 30 specifier: ^2.2.2 28 31 version: 2.2.2 ··· 30 33 specifier: ^0.1.5 31 34 version: 0.1.5 32 35 '@atcute/identity': 33 - specifier: ^0.1.3 34 - version: 0.1.3 36 + specifier: ^1.0.2 37 + version: 1.0.2 35 38 '@atcute/identity-resolver': 36 - specifier: ^0.1.2 37 - version: 0.1.2(@atcute/identity@0.1.3) 39 + specifier: ^1.1.0 40 + version: 1.1.0(@atcute/identity@1.0.2) 41 + '@atcute/lexicons': 42 + specifier: ^1.0.3 43 + version: 1.0.3 38 44 '@atcute/multibase': 39 45 specifier: ^1.1.4 40 46 version: 1.1.4 ··· 113 119 resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 114 120 engines: {node: '>=6.0.0'} 115 121 116 - '@atcute/bluesky@2.1.1': 117 - resolution: {integrity: sha512-wEZfFW58J6yC1SqHcVJOn4qbHENTTzjeCEWthRT5HvKovADLqk54HSMSAuXDMBUbintSTBr0khQNZQ3ZdgzDdQ==} 118 - peerDependencies: 119 - '@atcute/client': ^3.0.0 122 + '@atcute/atproto@3.0.2': 123 + resolution: {integrity: sha512-p37GqTmrxc1XaxtX8JsePEuomL+PtDeGdy0lcBm+HisD03ZZTia7MouxUpnYezX0l926fFaDc9tllIBtX9iSsQ==} 124 + 125 + '@atcute/bluesky@3.0.2': 126 + resolution: {integrity: sha512-xDRu/8Rlu3uTG/Mf625vUvKiFvy3hdCE371pXSJpHofivNZxi+MburdmYgOsBWZstNMo4vTBUviWaLJpL23rFg==} 120 127 121 128 '@atcute/car@3.1.1': 122 129 resolution: {integrity: sha512-yhez/LqIl0zHubG6z/G/gqWYHmg7wJ5L4jNkbXj5FvZ4eOvmzsw8+ojbdq6wfMU4p5NhP0pUJNLkTZHbYSPmLg==} ··· 127 134 '@atcute/cid@2.2.3': 128 135 resolution: {integrity: sha512-WEzNSL1EuCVtCQDFYEBIm4dEP6PcMEwi8IYUVIWvT77eO5EjY58F63z5T4qMABxSBM0+L4kqMxypdL1Fzf6LZw==} 129 136 130 - '@atcute/client@3.1.0': 131 - resolution: {integrity: sha512-+rQPsHXSf0DUm8XoHoaH7Y2E8tIpbsW84djyPj7dqAyrFIjvGuJ1X1DvMufwbTIcmLerdy+dzl34iZcz/h3Vhg==} 137 + '@atcute/client@4.0.3': 138 + resolution: {integrity: sha512-RIOZWFVLca/HiPAAUDqQPOdOreCxTbL5cb+WUf5yqQOKIu5yEAP3eksinmlLmgIrlr5qVOE7brazUUzaskFCfw==} 132 139 133 140 '@atcute/crypto@2.2.2': 134 141 resolution: {integrity: sha512-zpJjB6m0+Wy4YrgBJTSps4wbi3Xv1z/yZO/m/3sUYl1bIqUdHhH3vmDJtiXkRONgKNhiaQdukyt0omd9n4vDUA==} ··· 136 143 '@atcute/did-plc@0.1.5': 137 144 resolution: {integrity: sha512-f0UTge7i1MgHNk73SKN2FE/nt/FzRyEypIbc85hL32sig0kCs87yZ2W+1okBkaBoQ6Fis2ILwwAA5VAUJE3Yng==} 138 145 139 - '@atcute/identity-resolver@0.1.2': 140 - resolution: {integrity: sha512-fP2VbHD04kVcCdNi/Kszo6jFzqM7Pg3p33oGhfp2zVkwFKaVBlwCaFRWEga/Xvu/IDLwNdASGWnLqoA34SFeSg==} 146 + '@atcute/identity-resolver@1.1.0': 147 + resolution: {integrity: sha512-Ak41aYsQwW1xPan7BXM6TfQ18AkQg8RVH2s7Ppcg3b7YJUo8v24KJXaYoha3t+Tcr0T1xx56j/vZPIfwUG+b4g==} 141 148 peerDependencies: 142 - '@atcute/identity': ^0.1.0 149 + '@atcute/identity': ^1.0.0 143 150 144 - '@atcute/identity@0.1.3': 145 - resolution: {integrity: sha512-ndlD8nypHt8G00wixbozKdSNL0O8HTzBjFGEXeAcBUCXSZPBjRWbqtgyJxhgUWnr7swgxgw1mSbZwRB5b7xCiQ==} 151 + '@atcute/identity@1.0.2': 152 + resolution: {integrity: sha512-SrDPHuEarEHj9bx7NfYn7DYG6kIgJIMRU581iOCIaVaiZ1WhE9D8QxTxeYG/rbGNSa85E891ECp1sQcKiBN0kg==} 153 + 154 + '@atcute/lexicons@1.0.3': 155 + resolution: {integrity: sha512-R4xa3AMD+uMNn67/Nly0ohieT+vuN2qeV8Oq/mkpb0O3pFTuG7IkhXEGIXVnFY6I/NEQGhWB1FjHYpgRyL35Pw==} 146 156 147 157 '@atcute/multibase@1.1.4': 148 158 resolution: {integrity: sha512-NUf5AeeSOmuZHGU+4GAaMtISJoG+ZHtW/vUVA4lK/YDt/7LODAW0Fd0NNIIUPVUoW0xJS6zSEIWvwLLuxmEHhA==} ··· 1077 1087 resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 1078 1088 engines: {node: '>=6'} 1079 1089 1090 + esm-env@1.2.2: 1091 + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} 1092 + 1080 1093 exit-hook@2.2.1: 1081 1094 resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} 1082 1095 engines: {node: '>=6'} ··· 1759 1772 '@jridgewell/gen-mapping': 0.3.8 1760 1773 '@jridgewell/trace-mapping': 0.3.25 1761 1774 1762 - '@atcute/bluesky@2.1.1(@atcute/client@3.1.0)': 1775 + '@atcute/atproto@3.0.2': 1776 + dependencies: 1777 + '@atcute/lexicons': 1.0.3 1778 + 1779 + '@atcute/bluesky@3.0.2': 1763 1780 dependencies: 1764 - '@atcute/client': 3.1.0 1781 + '@atcute/atproto': 3.0.2 1782 + '@atcute/lexicons': 1.0.3 1765 1783 1766 1784 '@atcute/car@3.1.1': 1767 1785 dependencies: ··· 1782 1800 '@atcute/multibase': 1.1.4 1783 1801 '@atcute/uint8array': 1.0.2 1784 1802 1785 - '@atcute/client@3.1.0': {} 1803 + '@atcute/client@4.0.3': 1804 + dependencies: 1805 + '@atcute/identity': 1.0.2 1806 + '@atcute/lexicons': 1.0.3 1786 1807 1787 1808 '@atcute/crypto@2.2.2': 1788 1809 dependencies: ··· 1799 1820 '@atcute/uint8array': 1.0.2 1800 1821 '@badrap/valita': 0.4.5 1801 1822 1802 - '@atcute/identity-resolver@0.1.2(@atcute/identity@0.1.3)': 1823 + '@atcute/identity-resolver@1.1.0(@atcute/identity@1.0.2)': 1803 1824 dependencies: 1804 - '@atcute/identity': 0.1.3 1825 + '@atcute/identity': 1.0.2 1826 + '@atcute/lexicons': 1.0.3 1805 1827 '@atcute/util-fetch': 1.0.1 1806 1828 '@badrap/valita': 0.4.5 1807 1829 1808 - '@atcute/identity@0.1.3': 1830 + '@atcute/identity@1.0.2': 1809 1831 dependencies: 1832 + '@atcute/lexicons': 1.0.3 1810 1833 '@badrap/valita': 0.4.5 1834 + 1835 + '@atcute/lexicons@1.0.3': 1836 + dependencies: 1837 + esm-env: 1.2.2 1811 1838 1812 1839 '@atcute/multibase@1.1.4': 1813 1840 dependencies: ··· 2560 2587 '@esbuild/win32-x64': 0.25.5 2561 2588 2562 2589 escalade@3.2.0: {} 2590 + 2591 + esm-env@1.2.2: {} 2563 2592 2564 2593 exit-hook@2.2.1: {} 2565 2594
+2 -1
src/api/queries/did-doc.ts
··· 1 - import type { AtprotoDid, DidDocument } from '@atcute/identity'; 1 + import type { DidDocument } from '@atcute/identity'; 2 2 import { 3 3 CompositeDidDocumentResolver, 4 4 PlcDidDocumentResolver, 5 5 WebDidDocumentResolver, 6 6 } from '@atcute/identity-resolver'; 7 + import type { AtprotoDid } from '@atcute/lexicons/syntax'; 7 8 8 9 const didDocumentResolver = new CompositeDidDocumentResolver({ 9 10 methods: {
+1 -1
src/api/queries/handle.ts
··· 1 - import { type AtprotoDid, type Handle, isHandle } from '@atcute/identity'; 2 1 import { XrpcHandleResolver } from '@atcute/identity-resolver'; 2 + import { type AtprotoDid, type Handle, isHandle } from '@atcute/lexicons/syntax'; 3 3 4 4 const handleResolver = new XrpcHandleResolver({ 5 5 serviceUrl: import.meta.env.VITE_APPVIEW_URL,
+1 -1
src/api/queries/plc.ts
··· 1 1 import { defs } from '@atcute/did-plc'; 2 - import { Did } from '@atcute/identity'; 2 + import { Did } from '@atcute/lexicons/syntax'; 3 3 4 4 export const getPlcAuditLogs = async ({ did, signal }: { did: Did<'plc'>; signal?: AbortSignal }) => { 5 5 const origin = import.meta.env.VITE_PLC_DIRECTORY_URL;
-49
src/api/utils/at-uri.ts
··· 1 - import type { At } from '@atcute/client/lexicons'; 2 - 3 - import { assert } from '~/lib/utils/invariant'; 4 - 5 - const DID_RE = /^did:([a-z]+):([a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-])$/; 6 - 7 - const NSID_RE = 8 - /^[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?:\.[a-zA-Z](?:[a-zA-Z0-9]{0,62})?)$/; 9 - 10 - const RECORD_KEY_RE = /^(?!\.{1,2}$)[a-zA-Z0-9_~.:-]{1,512}$/; 11 - 12 - const ATURI_RE = 13 - /^at:\/\/([a-zA-Z0-9._:%-]+)(?:\/([a-zA-Z0-9-.]+)(?:\/([a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(?:#(\/[a-zA-Z0-9._~:@!$&%')(*+,;=\-[\]/\\]*))?$/; 14 - 15 - const isDid = (input: unknown): input is At.Did => { 16 - return typeof input === 'string' && input.length >= 7 && input.length <= 2048 && DID_RE.test(input); 17 - }; 18 - 19 - const isNsid = (input: unknown): input is At.Nsid => { 20 - return typeof input === 'string' && input.length >= 5 && input.length <= 317 && NSID_RE.test(input); 21 - }; 22 - 23 - const isRecordKey = (input: unknown): input is At.RecordKey => { 24 - return typeof input === 'string' && input.length >= 1 && input.length <= 512 && RECORD_KEY_RE.test(input); 25 - }; 26 - 27 - export interface AddressedAtUri { 28 - repo: At.Did; 29 - collection: At.Nsid; 30 - rkey: At.RecordKey; 31 - fragment: string | undefined; 32 - } 33 - 34 - export const parseAddressedAtUri = (str: string): AddressedAtUri => { 35 - const match = ATURI_RE.exec(str); 36 - assert(match !== null, `invalid addressed-at-uri: ${str}`); 37 - 38 - const [, r, c, k, f] = match; 39 - assert(isDid(r), `invalid repo in addressed-at-uri: ${r}`); 40 - assert(isNsid(c), `invalid collection in addressed-at-uri: ${c}`); 41 - assert(isRecordKey(k), `invalid rkey in addressed-at-uri: ${k}`); 42 - 43 - return { 44 - repo: r, 45 - collection: c, 46 - rkey: k, 47 - fragment: f, 48 - }; 49 - };
+7 -12
src/api/utils/error.ts
··· 1 - import { XRPCError } from '@atcute/client'; 2 - 3 - export const formatXRPCError = (err: XRPCError): string => { 4 - const name = err.kind; 5 - return (name ? name + ': ' : '') + err.message; 6 - }; 1 + import { ClientResponseError } from '@atcute/client'; 7 2 8 3 export const formatQueryError = (err: unknown) => { 9 - if (err instanceof XRPCError) { 10 - const kind = err.kind; 4 + if (err instanceof ClientResponseError) { 5 + const error = err.error; 11 6 12 - if (kind === 'InvalidToken' || kind === 'ExpiredToken') { 7 + if (error === 'InvalidToken' || error === 'ExpiredToken') { 13 8 return `Account session is no longer valid`; 14 9 } 15 10 16 - if (kind === 'UpstreamFailure') { 11 + if (error === 'UpstreamFailure') { 17 12 return `Server appears to be experiencing issues, try again later`; 18 13 } 19 14 20 - if (kind === 'InternalServerError') { 15 + if (error === 'InternalServerError') { 21 16 return `Server is having issues processing this request, try again later`; 22 17 } 23 18 24 - return formatXRPCError(err); 19 + return err.message; 25 20 } 26 21 27 22 if (err instanceof Error) {
+69
src/api/utils/jwt.ts
··· 1 + /** 2 + * Decodes a JWT token 3 + * @param token The token string 4 + * @returns JSON object from the token 5 + */ 6 + export const decodeJwt = (token: string): unknown => { 7 + const pos = 1; 8 + const part = token.split('.')[1]; 9 + 10 + let decoded: string; 11 + 12 + if (typeof part !== 'string') { 13 + throw new Error('invalid token: missing part ' + (pos + 1)); 14 + } 15 + 16 + try { 17 + decoded = base64UrlDecode(part); 18 + } catch (e) { 19 + throw new Error('invalid token: invalid b64 for part ' + (pos + 1) + ' (' + (e as Error).message + ')'); 20 + } 21 + 22 + try { 23 + return JSON.parse(decoded); 24 + } catch (e) { 25 + throw new Error('invalid token: invalid json for part ' + (pos + 1) + ' (' + (e as Error).message + ')'); 26 + } 27 + }; 28 + 29 + /** 30 + * Decodes a URL-safe Base64 string 31 + * @param str URL-safe Base64 that needed to be decoded 32 + * @returns The actual string 33 + */ 34 + const base64UrlDecode = (str: string): string => { 35 + let output = str.replace(/-/g, '+').replace(/_/g, '/'); 36 + 37 + switch (output.length % 4) { 38 + case 0: 39 + break; 40 + case 2: 41 + output += '=='; 42 + break; 43 + case 3: 44 + output += '='; 45 + break; 46 + default: 47 + throw new Error('base64 string is not of the correct length'); 48 + } 49 + 50 + try { 51 + return b64DecodeUnicode(output); 52 + } catch { 53 + return atob(output); 54 + } 55 + }; 56 + 57 + const b64DecodeUnicode = (str: string): string => { 58 + return decodeURIComponent( 59 + atob(str).replace(/(.)/g, (_m, p) => { 60 + let code = p.charCodeAt(0).toString(16).toUpperCase(); 61 + 62 + if (code.length < 2) { 63 + code = '0' + code; 64 + } 65 + 66 + return '%' + code; 67 + }), 68 + ); 69 + };
-8
src/api/utils/strings.ts
··· 1 - import type { Records } from '@atcute/client/lexicons'; 2 - 3 - export const DID_OR_HANDLE_RE = 4 - /^[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(?:\.[a-zA-Z]{2,})$|^did:[a-z]+:[a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-]$/; 5 - 6 - export const makeAtUri = (repo: string, collection: keyof Records | (string & {}), rkey: string) => { 7 - return `at://${repo}/${collection}/${rkey}`; 8 - };
+13 -12
src/components/wizards/bluesky-login-step.tsx
··· 1 1 import { batch, createSignal, Match, Show, Switch } from 'solid-js'; 2 2 3 - import { CredentialManager, XRPCError } from '@atcute/client'; 4 - import { type AtprotoDid, type DidDocument, getPdsEndpoint, isAtprotoDid, isHandle } from '@atcute/identity'; 3 + import { ClientResponseError, CredentialManager } from '@atcute/client'; 4 + import { getPdsEndpoint, isAtprotoDid, type DidDocument } from '@atcute/identity'; 5 + import { isHandle, type AtprotoDid } from '@atcute/lexicons/syntax'; 5 6 6 7 import { getDidDocument } from '~/api/queries/did-doc'; 7 8 import { resolveHandleViaAppView } from '~/api/queries/handle'; ··· 88 89 setIsTotpRequired(false); 89 90 }); 90 91 }, 91 - onError(error) { 92 + onError(err) { 92 93 let message: string | undefined; 93 94 94 - if (error instanceof XRPCError) { 95 - if (error.kind === 'AuthFactorTokenRequired') { 95 + if (err instanceof ClientResponseError) { 96 + if (err.error === 'AuthFactorTokenRequired') { 96 97 setOtp(''); 97 98 setIsTotpRequired(true); 98 99 return; 99 100 } 100 101 101 - if (error.kind === 'AuthenticationRequired') { 102 + if (err.error === 'AuthenticationRequired') { 102 103 message = `Invalid identifier or password`; 103 - } else if (error.kind === 'AccountTakedown') { 104 + } else if (err.error === 'AccountTakedown') { 104 105 message = `Account has been taken down`; 105 - } else if (error.message.includes('Token is invalid')) { 106 + } else if (err.message.includes('Token is invalid')) { 106 107 message = `Invalid one-time confirmation code`; 107 108 setIsTotpRequired(true); 108 109 } 109 - } else if (error instanceof InsufficientLoginError) { 110 - message = error.message; 110 + } else if (err instanceof InsufficientLoginError) { 111 + message = err.message; 111 112 } 112 113 113 114 if (message !== undefined) { 114 115 setError(message); 115 116 } else { 116 - console.error(error); 117 - setError(`Something went wrong: ${error}`); 117 + console.error(err); 118 + setError(`Something went wrong: ${err}`); 118 119 } 119 120 }, 120 121 });
+2 -2
src/globals/rpc.ts
··· 1 - import { simpleFetchHandler, XRPC } from '@atcute/client'; 1 + import { Client, simpleFetchHandler } from '@atcute/client'; 2 2 3 3 const APPVIEW_URL = import.meta.env.VITE_APPVIEW_URL; 4 4 5 - export const appViewRpc = new XRPC({ handler: simpleFetchHandler({ service: APPVIEW_URL }) }); 5 + export const appViewRpc = new Client({ handler: simpleFetchHandler({ service: APPVIEW_URL }) });
+4 -5
src/lib/utils/search-params.ts
··· 1 1 import { batch, createSignal } from 'solid-js'; 2 2 3 - import { At } from '@atcute/client/lexicons'; 4 - import { isDid, isHandle } from '@atcute/identity'; 3 + import { isDid, isHandle } from '@atcute/lexicons/syntax'; 5 4 6 5 import { UnwrapArray } from '~/api/utils/types'; 7 6 ··· 223 222 224 223 export const asDID = createParser({ 225 224 parse(value) { 226 - if (typeof value === 'string' && isDid(value)) { 227 - return value as At.Did; 225 + if (isDid(value)) { 226 + return value; 228 227 } 229 228 230 229 return null; ··· 236 235 237 236 export const asHandle = createParser({ 238 237 parse(value) { 239 - if (typeof value === 'string' && isHandle(value)) { 238 + if (isHandle(value)) { 240 239 return value; 241 240 } 242 241
+2 -3
src/views/blob/blob-export.tsx
··· 2 2 import { createSignal } from 'solid-js'; 3 3 4 4 import { Client, ClientResponseError, ok, simpleFetchHandler } from '@atcute/client'; 5 - import { type AtprotoDid, getPdsEndpoint, isAtprotoDid, isHandle } from '@atcute/identity'; 5 + import { getPdsEndpoint, isAtprotoDid } from '@atcute/identity'; 6 + import { isHandle, type AtprotoDid } from '@atcute/lexicons/syntax'; 6 7 import { writeTarEntry } from '@mary/tar'; 7 8 8 9 import { getDidDocument } from '~/api/queries/did-doc'; 9 10 import { resolveHandleViaAppView, resolveHandleViaPds } from '~/api/queries/handle'; 10 11 import { isServiceUrlString } from '~/api/types/strings'; 11 - import { DID_OR_HANDLE_RE } from '~/api/utils/strings'; 12 12 13 13 import { useTitle } from '~/lib/navigation/router'; 14 14 import { makeAbortable } from '~/lib/utils/abortable'; ··· 284 284 type="text" 285 285 name="ident" 286 286 autocomplete="username" 287 - pattern={/* @once */ DID_OR_HANDLE_RE.source} 288 287 placeholder="paul.bsky.social" 289 288 autofocus 290 289 />
+3 -3
src/views/bluesky/threadgate-applicator/page.tsx
··· 1 1 import { createEffect, createSignal, onCleanup } from 'solid-js'; 2 2 3 + import { AppBskyFeedDefs, AppBskyFeedThreadgate } from '@atcute/bluesky'; 3 4 import { CredentialManager } from '@atcute/client'; 4 - import { AppBskyFeedDefs, AppBskyFeedThreadgate } from '@atcute/client/lexicons'; 5 5 import { DidDocument } from '@atcute/identity'; 6 6 7 7 import { UnwrapArray } from '~/api/utils/types'; ··· 19 19 import Step5_Finished from './steps/step5_finished'; 20 20 21 21 export interface ThreadgateState 22 - extends Pick<AppBskyFeedThreadgate.Record, 'allow' | 'hiddenReplies' | 'createdAt'> { 22 + extends Pick<AppBskyFeedThreadgate.Main, 'allow' | 'hiddenReplies' | 'createdAt'> { 23 23 uri: string; 24 24 } 25 25 26 - export type ThreadgateRule = UnwrapArray<AppBskyFeedThreadgate.Record['allow']>; 26 + export type ThreadgateRule = UnwrapArray<AppBskyFeedThreadgate.Main['allow']>; 27 27 28 28 export interface ThreadItem { 29 29 post: AppBskyFeedDefs.PostView;
+16 -14
src/views/bluesky/threadgate-applicator/steps/step1_handle-input.tsx
··· 1 1 import { createSignal } from 'solid-js'; 2 2 3 - import type { AppBskyFeedThreadgate } from '@atcute/client/lexicons'; 4 - import { type AtprotoDid, isAtprotoDid, isHandle } from '@atcute/identity'; 3 + import type { AppBskyFeedThreadgate } from '@atcute/bluesky'; 4 + import { ok } from '@atcute/client'; 5 + import { isAtprotoDid } from '@atcute/identity'; 6 + import { isHandle, type AtprotoDid } from '@atcute/lexicons/syntax'; 5 7 6 8 import { getDidDocument } from '~/api/queries/did-doc'; 7 9 import { resolveHandleViaAppView } from '~/api/queries/handle'; 8 - import { DID_OR_HANDLE_RE } from '~/api/utils/strings'; 9 10 10 11 import { appViewRpc } from '~/globals/rpc'; 11 12 ··· 50 51 51 52 let cursor: string | undefined; 52 53 do { 53 - const { data } = await appViewRpc.get('app.bsky.feed.getAuthorFeed', { 54 - signal, 55 - params: { 56 - actor: did, 57 - filter: 'posts_no_replies', 58 - limit: 100, 59 - cursor, 60 - }, 61 - }); 54 + const data = await ok( 55 + appViewRpc.get('app.bsky.feed.getAuthorFeed', { 56 + signal, 57 + params: { 58 + actor: did, 59 + filter: 'posts_no_replies', 60 + limit: 100, 61 + cursor, 62 + }, 63 + }), 64 + ); 62 65 63 66 cursor = data.cursor; 64 67 ··· 83 86 let threadgate: ThreadgateState | null = null; 84 87 85 88 if (tg?.record) { 86 - const record = tg.record as AppBskyFeedThreadgate.Record; 89 + const record = tg.record as AppBskyFeedThreadgate.Main; 87 90 88 91 const allow = record?.allow; 89 92 const hiddenReplies = record?.hiddenReplies; ··· 153 156 placeholder="paul.bsky.social" 154 157 value={identifier()} 155 158 required 156 - pattern={/* @once */ DID_OR_HANDLE_RE.source} 157 159 autofocus={isActive()} 158 160 onChange={setIdentifier} 159 161 />
+18 -18
src/views/bluesky/threadgate-applicator/steps/step2_rules-input.tsx
··· 1 1 import { batch, createMemo, createSignal, For, Show } from 'solid-js'; 2 2 3 - import { AppBskyFeedThreadgate, Brand } from '@atcute/client/lexicons'; 4 - 5 - import { UnwrapArray } from '~/api/utils/types'; 3 + import { AppBskyFeedThreadgate } from '@atcute/bluesky'; 4 + import { ok } from '@atcute/client'; 5 + import { $type } from '@atcute/lexicons'; 6 6 7 7 import { appViewRpc } from '~/globals/rpc'; 8 8 ··· 17 17 import Button from '~/components/inputs/button'; 18 18 import ToggleInput from '~/components/inputs/toggle-input'; 19 19 20 - import { ThreadgateApplicatorConstraints } from '../page'; 20 + import { ThreadgateApplicatorConstraints, ThreadgateRule } from '../page'; 21 21 import { sortThreadgateAllow } from '../utils'; 22 22 23 23 const enum FilterType { ··· 31 31 CUSTOM = 'custom', 32 32 } 33 33 34 - type ThreadRule = UnwrapArray<AppBskyFeedThreadgate.Record['allow']>; 35 - 36 34 const Step2_RulesInput = ({ 37 35 data, 38 36 isActive, ··· 41 39 }: WizardStepProps<ThreadgateApplicatorConstraints, 'Step2_RulesInput'>) => { 42 40 const [filter, setFilter] = createSignal(FilterType.MISSING_ONLY); 43 41 44 - const [threadRules, _setThreadRules] = createSignal<ThreadRule[] | undefined>([ 42 + const [threadRules, _setThreadRules] = createSignal<ThreadgateRule[] | undefined>([ 45 43 { $type: 'app.bsky.feed.threadgate#followingRule' }, 46 44 { $type: 'app.bsky.feed.threadgate#mentionRule' }, 47 45 ]); ··· 64 62 () => data.profile.didDoc.id, 65 63 async (did, signal) => { 66 64 const lists = await accumulate(async (cursor) => { 67 - const { data } = await appViewRpc.get('app.bsky.graph.getLists', { 68 - signal, 69 - params: { 70 - actor: did, 71 - cursor, 72 - limit: 100, 73 - }, 74 - }); 65 + const data = await ok( 66 + appViewRpc.get('app.bsky.graph.getLists', { 67 + signal, 68 + params: { 69 + actor: did, 70 + cursor, 71 + limit: 100, 72 + }, 73 + }), 74 + ); 75 75 76 76 return { 77 77 cursor: data.cursor, ··· 121 121 ); 122 122 }); 123 123 124 - const hasThreadRule = (predicate: ThreadRule): boolean => { 124 + const hasThreadRule = (predicate: ThreadgateRule): boolean => { 125 125 return !!threadRules()?.find((rule) => dequal(rule, predicate)); 126 126 }; 127 127 128 - const setCustomThreadRules = (next: ThreadRule[] | undefined) => { 128 + const setCustomThreadRules = (next: ThreadgateRule[] | undefined) => { 129 129 batch(() => { 130 130 _setThreadRules(next); 131 131 setThreadRulesPreset(ThreadRulePreset.CUSTOM); ··· 226 226 } 227 227 > 228 228 {(list) => { 229 - const rule: Brand.Union<AppBskyFeedThreadgate.ListRule> = { 229 + const rule: $type.enforce<AppBskyFeedThreadgate.ListRule> = { 230 230 $type: 'app.bsky.feed.threadgate#listRule', 231 231 list: list.uri, 232 232 };
+69 -50
src/views/bluesky/threadgate-applicator/steps/step4_confirmation.tsx
··· 1 1 import { createSignal, Show } from 'solid-js'; 2 2 3 - import { XRPC, XRPCError } from '@atcute/client'; 4 - import type { AppBskyFeedThreadgate, ComAtprotoRepoApplyWrites } from '@atcute/client/lexicons'; 3 + import type { ComAtprotoRepoApplyWrites } from '@atcute/atproto'; 4 + import type { AppBskyFeedThreadgate } from '@atcute/bluesky'; 5 + import { Client, ClientResponseError } from '@atcute/client'; 6 + import { InferXRPCBodyInput } from '@atcute/lexicons'; 5 7 import { chunked } from '@mary/array-fns'; 6 - 7 - import { parseAddressedAtUri } from '~/api/utils/at-uri'; 8 + import { parseCanonicalResourceUri } from '@atcute/lexicons'; 8 9 9 10 import { dequal } from '~/lib/utils/dequal'; 10 11 import { createMutation } from '~/lib/utils/mutation'; ··· 34 35 logger.log(`Preparing writes`); 35 36 36 37 const rules = data.rules; 37 - const writes: ComAtprotoRepoApplyWrites.Input['writes'] = []; 38 + const writes: InferXRPCBodyInput<ComAtprotoRepoApplyWrites.mainSchema['input']>['writes'] = []; 38 39 39 40 const now = new Date().toISOString(); 40 41 for (const { post, threadgate } of data.threads) { 41 42 if (threadgate === null) { 42 43 if (rules !== undefined) { 43 - const { rkey } = parseAddressedAtUri(post.uri); 44 + const postUri = parseCanonicalResourceUri(post.uri); 45 + if (!postUri.ok) { 46 + throw new Error(`failed to parse ${post.uri}`); 47 + } 44 48 45 - const record: AppBskyFeedThreadgate.Record = { 49 + const record: AppBskyFeedThreadgate.Main = { 46 50 $type: 'app.bsky.feed.threadgate', 47 51 createdAt: now, 48 52 post: post.uri, ··· 53 57 writes.push({ 54 58 $type: 'com.atproto.repo.applyWrites#create', 55 59 collection: 'app.bsky.feed.threadgate', 56 - rkey: rkey, 60 + rkey: postUri.value.rkey, 57 61 value: record, 58 62 }); 59 63 } 60 64 } else { 61 65 if (rules === undefined && !threadgate.hiddenReplies?.length) { 62 - const { rkey } = parseAddressedAtUri(threadgate.uri); 66 + const threadgateUri = parseCanonicalResourceUri(threadgate.uri); 67 + if (!threadgateUri.ok) { 68 + throw new Error(`failed to parse ${threadgate.uri}`); 69 + } 63 70 64 71 writes.push({ 65 72 $type: 'com.atproto.repo.applyWrites#delete', 66 73 collection: 'app.bsky.feed.threadgate', 67 - rkey: rkey, 74 + rkey: threadgateUri.value.rkey, 68 75 }); 69 76 } else if (!dequal(threadgate.allow, rules)) { 70 - const { rkey } = parseAddressedAtUri(threadgate.uri); 77 + const threadgateUri = parseCanonicalResourceUri(threadgate.uri); 78 + if (!threadgateUri.ok) { 79 + throw new Error(`failed to parse ${threadgate.uri}`); 80 + } 71 81 72 - const record: AppBskyFeedThreadgate.Record = { 82 + const record: AppBskyFeedThreadgate.Main = { 73 83 $type: 'app.bsky.feed.threadgate', 74 84 createdAt: threadgate.createdAt, 75 85 post: post.uri, ··· 80 90 writes.push({ 81 91 $type: 'com.atproto.repo.applyWrites#update', 82 92 collection: 'app.bsky.feed.threadgate', 83 - rkey: rkey, 93 + rkey: threadgateUri.value.rkey, 84 94 value: record, 85 95 }); 86 96 } ··· 90 100 logger.log(`${writes.length} write operations to apply`); 91 101 92 102 const did = data.profile.didDoc.id; 93 - const rpc = new XRPC({ handler: data.manager }); 94 - 95 - const RATELIMIT_POINT_LIMIT = 150 * 3; 103 + const client = new Client({ handler: data.manager }); 96 104 97 105 { 98 106 using progress = logger.progress(`Applying writes`); 99 107 100 108 let written = 0; 101 109 for (const chunk of chunked(writes, 200)) { 102 - try { 103 - const { headers } = await rpc.call('com.atproto.repo.applyWrites', { 104 - data: { 105 - repo: did, 106 - writes: chunk, 107 - }, 108 - }); 110 + let attempts = 0; 109 111 110 - written += chunk.length; 111 - progress.update(`Applying writes (${written} applied)`); 112 + while (true) { 113 + if (attempts > 0) { 114 + await sleep(2_000); 115 + } 112 116 113 - if ('ratelimit-remaining' in headers) { 114 - const remaining = +headers['ratelimit-remaining']; 115 - const reset = +headers['ratelimit-reset'] * 1_000; 117 + attempts++; 116 118 117 - if (remaining < RATELIMIT_POINT_LIMIT) { 118 - // add some delay to be sure 119 - const delta = reset - Date.now() + 5_000; 120 - using _progress = logger.progress(`Reached ratelimit, waiting ${delta}ms`); 119 + try { 120 + const response = await client.post('com.atproto.repo.applyWrites', { 121 + input: { 122 + repo: did, 123 + writes: chunk, 124 + }, 125 + }); 121 126 122 - await new Promise((resolve) => setTimeout(resolve, delta)); 127 + if (response.ok) { 128 + written += chunk.length; 129 + progress.update(`Applying writes (${written} applied)`); 130 + break; 123 131 } 124 - } 125 - } catch (err) { 126 - if (!(err instanceof XRPCError) || err.kind !== 'RateLimitExceeded') { 127 - throw err; 128 - } 132 + 133 + if (response.status === 429) { 134 + // not exposed by CORS, hoping that someday it will 135 + const reset = response.headers.get('ratelimit-reset'); 136 + 137 + using _progress = logger.progress(`Ratelimited, waiting`); 138 + 139 + if (reset !== null) { 140 + const refreshAt = +reset * 1_000; 141 + const delta = refreshAt - Date.now(); 129 142 130 - const headers = err.headers; 131 - if ('ratelimit-remaining' in headers) { 132 - const remaining = +headers['ratelimit-remaining']; 133 - const reset = +headers['ratelimit-reset'] * 1_000; 143 + await sleep(delta); 144 + } else { 145 + await sleep(10_000); 146 + } 147 + } 134 148 135 - if (remaining < RATELIMIT_POINT_LIMIT) { 136 - // add some delay to be sure 137 - const delta = reset - Date.now() + 5_000; 138 - using _progress = logger.progress(`Ratelimited, waiting ${delta}ms`); 149 + if (attempts < 3) { 150 + continue; 151 + } 139 152 140 - await new Promise((resolve) => setTimeout(resolve, delta)); 153 + throw new ClientResponseError(response); 154 + } catch (err) { 155 + // Network errors, etc 156 + if (attempts < 3) { 157 + continue; 141 158 } 142 - } else { 143 - using _progress = logger.progress(`Ratelimited, waiting`); 144 159 145 - await new Promise((resolve) => setTimeout(resolve, 60 * 1_000)); 160 + throw err; 146 161 } 147 162 } 148 163 } ··· 202 217 }; 203 218 204 219 export default Step4_Confirmation; 220 + 221 + const sleep = (ms: number): Promise<void> => { 222 + return new Promise((resolve) => setTimeout(resolve, ms)); 223 + };
+2 -3
src/views/identity/did-lookup.tsx
··· 1 1 import { Match, Switch } from 'solid-js'; 2 2 3 - import { isAtprotoDid, isHandle, type AtprotoDid, type Did, type Handle } from '@atcute/identity'; 3 + import { isAtprotoDid } from '@atcute/identity'; 4 + import { isHandle, type AtprotoDid, type Did, type Handle } from '@atcute/lexicons/syntax'; 4 5 5 6 import { getDidDocument } from '~/api/queries/did-doc'; 6 7 import { resolveHandleViaAppView } from '~/api/queries/handle'; 7 8 import { isServiceUrlString } from '~/api/types/strings'; 8 - import { DID_OR_HANDLE_RE } from '~/api/utils/strings'; 9 9 10 10 import { useTitle } from '~/lib/navigation/router'; 11 11 import { createQuery } from '~/lib/utils/query'; ··· 67 67 type="text" 68 68 name="ident" 69 69 autocomplete="username" 70 - pattern={/* @once */ DID_OR_HANDLE_RE.source} 71 70 placeholder="paul.bsky.social" 72 71 autofocus 73 72 />
+5 -3
src/views/identity/plc-applicator/page.tsx
··· 1 1 import { createEffect, createSignal, onCleanup } from 'solid-js'; 2 2 3 + import type { ComAtprotoIdentityGetRecommendedDidCredentials } from '@atcute/atproto'; 3 4 import type { CredentialManager } from '@atcute/client'; 4 - import type { ComAtprotoIdentityGetRecommendedDidCredentials } from '@atcute/client/lexicons'; 5 5 import type { P256PrivateKey, Secp256k1PrivateKey } from '@atcute/crypto'; 6 6 import type { CompatibleOperation, IndexedEntry, IndexedEntryWithSigner } from '@atcute/did-plc'; 7 - import type { Did, DidDocument } from '@atcute/identity'; 7 + import type { DidDocument } from '@atcute/identity'; 8 + import { InferXRPCBodyInput } from '@atcute/lexicons'; 9 + import type { Did } from '@atcute/lexicons/syntax'; 8 10 9 11 import { UpdatePayload } from '~/api/types/plc'; 10 12 ··· 31 33 export interface PdsSigningMethod { 32 34 type: 'pds'; 33 35 manager: CredentialManager; 34 - recommendedDidDoc: ComAtprotoIdentityGetRecommendedDidCredentials.Output; 36 + recommendedDidDoc: InferXRPCBodyInput<ComAtprotoIdentityGetRecommendedDidCredentials.mainSchema['output']>; 35 37 } 36 38 37 39 export type Keypair = P256PrivateKey | Secp256k1PrivateKey;
+10 -11
src/views/identity/plc-applicator/steps/step1_handle-input.tsx
··· 1 1 import { createSignal } from 'solid-js'; 2 2 3 - import { XRPCError } from '@atcute/client'; 3 + import { ClientResponseError } from '@atcute/client'; 4 4 import { processIndexedEntryLog } from '@atcute/did-plc'; 5 - import { type Did, isHandle, isPlcDid } from '@atcute/identity'; 5 + import { isPlcDid } from '@atcute/identity'; 6 + import { isHandle, type Did } from '@atcute/lexicons/syntax'; 6 7 7 8 import { getDidDocument } from '~/api/queries/did-doc'; 8 9 import { resolveHandleViaAppView } from '~/api/queries/handle'; 9 10 import { getPlcAuditLogs } from '~/api/queries/plc'; 10 - import { DID_OR_HANDLE_RE } from '~/api/utils/strings'; 11 11 12 12 import { createMutation } from '~/lib/utils/mutation'; 13 13 ··· 16 16 import TextInput from '~/components/inputs/text-input'; 17 17 import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard'; 18 18 19 - import { type PlcInformation, PlcApplicatorConstraints } from '../page'; 19 + import { PlcApplicatorConstraints, type PlcInformation } from '../page'; 20 20 21 21 type Method = 'pds' | 'key'; 22 22 ··· 70 70 onNext('Step2_PrivateKeyInput', { info }); 71 71 } 72 72 }, 73 - onError(error) { 73 + onError(err) { 74 74 let message: string | undefined; 75 75 76 - if (error instanceof XRPCError) { 77 - if (error.kind === 'InvalidRequest' && error.message.includes('resolve handle')) { 76 + if (err instanceof ClientResponseError) { 77 + if (err.error === 'InvalidRequest' && err.description?.includes('resolve handle')) { 78 78 message = `Can't seem to resolve handle, is it typed correctly?`; 79 79 } 80 - } else if (error instanceof DidIsNotPlcError) { 81 - message = error.message; 80 + } else if (err instanceof DidIsNotPlcError) { 81 + message = err.message; 82 82 } 83 83 84 84 if (message !== undefined) { 85 85 setError(message); 86 86 } else { 87 - setError(`Something went wrong: ${error}`); 87 + setError(`Something went wrong: ${err}`); 88 88 } 89 89 }, 90 90 }); ··· 105 105 placeholder="paul.bsky.social" 106 106 value={identifier()} 107 107 required 108 - pattern={/* @once */ DID_OR_HANDLE_RE.source} 109 108 autofocus={isActive()} 110 109 onChange={setIdentifier} 111 110 />
+14 -17
src/views/identity/plc-applicator/steps/step2_pds-authentication.tsx
··· 1 1 import { createSignal, Match, Show, Switch } from 'solid-js'; 2 2 3 - import { AtpAccessJwt, CredentialManager, XRPC, XRPCError } from '@atcute/client'; 4 - import { decodeJwt } from '@atcute/client/utils/jwt'; 3 + import { AtpAccessJwt, Client, ClientResponseError, CredentialManager, ok } from '@atcute/client'; 5 4 import { getPdsEndpoint } from '@atcute/identity'; 6 5 7 6 import { formatTotpCode, TOTP_RE } from '~/api/utils/auth'; 7 + import { decodeJwt } from '~/api/utils/jwt'; 8 8 9 9 import { createMutation } from '~/lib/utils/mutation'; 10 10 ··· 59 59 setPassword(''); 60 60 setIsTotpRequired(false); 61 61 }, 62 - onError(error) { 62 + onError(err) { 63 63 let message: string | undefined; 64 64 65 - if (error instanceof XRPCError) { 66 - if (error.kind === 'AuthFactorTokenRequired') { 65 + if (err instanceof ClientResponseError) { 66 + if (err.error === 'AuthFactorTokenRequired') { 67 67 setOtp(''); 68 68 setIsTotpRequired(true); 69 69 return; 70 70 } 71 71 72 - if (error.kind === 'AuthenticationRequired') { 72 + if (err.error === 'AuthenticationRequired') { 73 73 message = `Invalid identifier or password`; 74 - } else if (error.kind === 'AccountTakedown') { 74 + } else if (err.error === 'AccountTakedown') { 75 75 message = `Account has been taken down`; 76 - } else if (error.message.includes('Token is invalid')) { 76 + } else if (err.description?.includes('Token is invalid')) { 77 77 message = `Invalid one-time confirmation code`; 78 78 setIsTotpRequired(true); 79 79 } 80 - } else if (error instanceof InsufficientLoginError) { 81 - message = error.message; 80 + } else if (err instanceof InsufficientLoginError) { 81 + message = err.message; 82 82 } 83 83 84 84 if (message !== undefined) { 85 85 setError(message); 86 86 } else { 87 - console.error(error); 88 - setError(`Something went wrong: ${error}`); 87 + console.error(err); 88 + setError(`Something went wrong: ${err}`); 89 89 } 90 90 }, 91 91 }); 92 92 93 93 const dispatchMutation = createMutation({ 94 94 async mutationFn({ manager }: { manager: CredentialManager }) { 95 - const rpc = new XRPC({ handler: manager }); 96 - const { data: recommendedDidDoc } = await rpc.get( 97 - 'com.atproto.identity.getRecommendedDidCredentials', 98 - {}, 99 - ); 95 + const rpc = new Client({ handler: manager }); 96 + const recommendedDidDoc = await ok(rpc.get('com.atproto.identity.getRecommendedDidCredentials')); 100 97 101 98 return { recommendedDidDoc }; 102 99 },
+2 -2
src/views/identity/plc-applicator/steps/step3_operation-select.tsx
··· 7 7 type IndexedEntryWithSigner, 8 8 normalizeOp, 9 9 } from '@atcute/did-plc'; 10 - import type { Did } from '@atcute/identity'; 10 + import type { Did } from '@atcute/lexicons'; 11 11 12 12 import Button from '~/components/inputs/button'; 13 + import RadioInput from '~/components/inputs/radio-input'; 13 14 import SelectInput from '~/components/inputs/select-input'; 14 15 import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard'; 15 16 16 17 import { PlcApplicatorConstraints } from '../page'; 17 - import RadioInput from '~/components/inputs/radio-input'; 18 18 19 19 const Step3_OperationSelect = ({ 20 20 data,
+28 -23
src/views/identity/plc-applicator/steps/step5_pds-confirmation.tsx
··· 1 1 import { createSignal } from 'solid-js'; 2 2 3 - import { XRPC, XRPCError } from '@atcute/client'; 3 + import { Client, ClientResponseError, ok } from '@atcute/client'; 4 4 5 5 import { formatTotpCode, TOTP_RE } from '~/api/utils/auth'; 6 6 ··· 27 27 const requestMutation = createMutation({ 28 28 async mutationFn() { 29 29 const manager = data.method.manager; 30 - const rpc = new XRPC({ handler: manager }); 30 + const rpc = new Client({ handler: manager }); 31 31 32 - await rpc.call('com.atproto.identity.requestPlcOperationSignature', {}); 32 + await ok(rpc.post('com.atproto.identity.requestPlcOperationSignature', { as: null })); 33 33 }, 34 34 onMutate() { 35 35 setRequestError(); ··· 49 49 const applyMutation = createMutation({ 50 50 async mutationFn({ code }: { code: string }) { 51 51 const manager = data.method.manager; 52 - const rpc = new XRPC({ handler: manager }); 52 + const client = new Client({ handler: manager }); 53 53 54 54 const payload = data.payload; 55 55 56 - const { data: signage } = await rpc.call('com.atproto.identity.signPlcOperation', { 57 - data: { 58 - token: formatTotpCode(code), 59 - alsoKnownAs: payload.alsoKnownAs, 60 - rotationKeys: payload.rotationKeys, 61 - services: payload.services, 62 - verificationMethods: payload.verificationMethods, 63 - }, 64 - }); 56 + const signage = await ok( 57 + client.post('com.atproto.identity.signPlcOperation', { 58 + input: { 59 + token: formatTotpCode(code), 60 + alsoKnownAs: payload.alsoKnownAs, 61 + rotationKeys: payload.rotationKeys, 62 + services: payload.services, 63 + verificationMethods: payload.verificationMethods, 64 + }, 65 + }), 66 + ); 65 67 66 - await rpc.call('com.atproto.identity.submitPlcOperation', { 67 - data: { 68 - operation: signage.operation, 69 - }, 70 - }); 68 + await ok( 69 + client.post('com.atproto.identity.submitPlcOperation', { 70 + as: null, 71 + input: { 72 + operation: signage.operation, 73 + }, 74 + }), 75 + ); 71 76 }, 72 77 onMutate() { 73 78 setApplyError(); ··· 75 80 onSuccess() { 76 81 onNext('Step6_Finished', {}); 77 82 }, 78 - onError(error) { 83 + onError(err) { 79 84 let message: string | undefined; 80 85 81 - if (error instanceof XRPCError) { 82 - if (error.kind === 'InvalidToken' || error.kind === 'ExpiredToken') { 86 + if (err instanceof ClientResponseError) { 87 + if (err.error === 'InvalidToken' || err.error === 'ExpiredToken') { 83 88 message = `Confirmation code has expired`; 84 89 } 85 90 } ··· 87 92 if (message !== undefined) { 88 93 setApplyError(message); 89 94 } else { 90 - console.error(error); 91 - setApplyError(`Something went wrong: ${error}`); 95 + console.error(err); 96 + setApplyError(`Something went wrong: ${err}`); 92 97 } 93 98 }, 94 99 });
+2 -3
src/views/identity/plc-oplogs.tsx
··· 1 1 import { createSignal, JSX, Match, onCleanup, Switch } from 'solid-js'; 2 2 3 3 import type { IndexedEntry, Service } from '@atcute/did-plc'; 4 - import { type Did, type Handle, isHandle, isPlcDid } from '@atcute/identity'; 4 + import { isPlcDid } from '@atcute/identity'; 5 + import { isHandle, type Did, type Handle } from '@atcute/lexicons/syntax'; 5 6 6 7 import { resolveHandleViaAppView } from '~/api/queries/handle'; 7 - import { DID_OR_HANDLE_RE } from '~/api/utils/strings'; 8 8 9 9 import { getPlcAuditLogs } from '~/api/queries/plc'; 10 10 import { useTitle } from '~/lib/navigation/router'; ··· 76 76 type="text" 77 77 name="ident" 78 78 autocomplete="username" 79 - pattern={/* @once */ DID_OR_HANDLE_RE.source} 80 79 placeholder="paul.bsky.social" 81 80 autofocus 82 81 />
+2 -3
src/views/repository/repo-export.tsx
··· 1 1 import { type FileSystemFileHandle, showSaveFilePicker } from 'native-file-system-adapter'; 2 2 import { createSignal } from 'solid-js'; 3 3 4 - import { type AtprotoDid, getPdsEndpoint, isAtprotoDid, isHandle } from '@atcute/identity'; 4 + import { getPdsEndpoint, isAtprotoDid } from '@atcute/identity'; 5 + import { type AtprotoDid, isHandle } from '@atcute/lexicons/syntax'; 5 6 6 7 import { getDidDocument } from '~/api/queries/did-doc'; 7 8 import { resolveHandleViaAppView, resolveHandleViaPds } from '~/api/queries/handle'; 8 9 import { isServiceUrlString } from '~/api/types/strings'; 9 - import { DID_OR_HANDLE_RE } from '~/api/utils/strings'; 10 10 11 11 import { useTitle } from '~/lib/navigation/router'; 12 12 import { makeAbortable } from '~/lib/utils/abortable'; ··· 190 190 type="text" 191 191 name="ident" 192 192 autocomplete="username" 193 - pattern={/* @once */ DID_OR_HANDLE_RE.source} 194 193 placeholder="paul.bsky.social" 195 194 autofocus 196 195 />
+1 -1
tsconfig.app.json
··· 3 3 "target": "ESNext", 4 4 "module": "ESNext", 5 5 "lib": ["ESNext", "DOM", "DOM.Iterable"], 6 - "types": [], 6 + "types": ["@atcute/atproto", "@atcute/bluesky"], 7 7 "skipLibCheck": true, 8 8 9 9 "moduleResolution": "Bundler",