+3
package.json
+3
package.json
···
12
12
"@atcute/cbor": "^2.1.3",
13
13
"@atcute/client": "^2.0.8",
14
14
"@atcute/crypto": "^2.2.0",
15
+
"@atcute/did-plc": "^0.1.0",
16
+
"@atcute/identity": "^0.1.1",
17
+
"@atcute/identity-resolver": "^0.1.2",
15
18
"@atcute/multibase": "^1.1.2",
16
19
"@badrap/valita": "^0.4.3",
17
20
"@mary/array-fns": "npm:@jsr/mary__array-fns@^0.1.4",
+46
pnpm-lock.yaml
+46
pnpm-lock.yaml
···
23
23
'@atcute/crypto':
24
24
specifier: ^2.2.0
25
25
version: 2.2.0
26
+
'@atcute/did-plc':
27
+
specifier: ^0.1.0
28
+
version: 0.1.0
29
+
'@atcute/identity':
30
+
specifier: ^0.1.1
31
+
version: 0.1.1
32
+
'@atcute/identity-resolver':
33
+
specifier: ^0.1.2
34
+
version: 0.1.2(@atcute/identity@0.1.1)
26
35
'@atcute/multibase':
27
36
specifier: ^1.1.2
28
37
version: 1.1.2
···
115
124
'@atcute/crypto@2.2.0':
116
125
resolution: {integrity: sha512-Q/64Qn1AI8J0ZNy4hPDPpW/3poKf4OWRUxIYceCDI+btEOcIG5YMlhEQeZd6ojnI3oBMXy03sbOktekaBYRK9Q==}
117
126
127
+
'@atcute/did-plc@0.1.0':
128
+
resolution: {integrity: sha512-ORW9s9etge/icaJEcbAMiZEFWbydljqHngqU0fSR94KZiK2bXRZxMec7ktpO9oXLDKuSeFH9H23i1xlyqCwjjw==}
129
+
130
+
'@atcute/identity-resolver@0.1.2':
131
+
resolution: {integrity: sha512-fP2VbHD04kVcCdNi/Kszo6jFzqM7Pg3p33oGhfp2zVkwFKaVBlwCaFRWEga/Xvu/IDLwNdASGWnLqoA34SFeSg==}
132
+
peerDependencies:
133
+
'@atcute/identity': ^0.1.0
134
+
135
+
'@atcute/identity@0.1.1':
136
+
resolution: {integrity: sha512-TijKOgvvOfp/QoMAqaiKLn+FnQi5XrxsWLVcVnvr5JoKlgF2yppNvVo0y62XEXZbgDuEMSav1v1tEjC4Hn7MzQ==}
137
+
118
138
'@atcute/multibase@1.1.2':
119
139
resolution: {integrity: sha512-KFX+c7a/u2jSNcRw0rLaUHG/XEKf1A1c8XF5soHnsb1JMCShihf/anfZ1kJ4no/IlIp9HEHV3PQRQO2sWL6ASQ==}
120
140
121
141
'@atcute/uint8array@1.0.1':
122
142
resolution: {integrity: sha512-AAnlFKyfDRgb9GNZJbhQ6OuMhbmNPirQyapb8KnmcEhxQZ3+tt+4NcwqekEegY4MpNqSTYeeTdyxq0wGZv1JHg==}
143
+
144
+
'@atcute/util-fetch@1.0.0':
145
+
resolution: {integrity: sha512-Mjt5Bow3NApiEhgwuXWwvdZ4fBGgsgXju41MeJvAag1nhg2qObgg19FNccpC63QfXrKdXMGUOJOiZkxIw6wILA==}
123
146
124
147
'@atcute/varint@1.0.2':
125
148
resolution: {integrity: sha512-0O31hePzzr4O3NGWHUKKOyta6CGSL+AtN8iir8grGxu9jXyI7DBARlw6PbgKA6uTAvsXdpmRmF8MX+p0TsLnNg==}
···
1725
1748
'@atcute/uint8array': 1.0.1
1726
1749
'@noble/secp256k1': 2.2.3
1727
1750
1751
+
'@atcute/did-plc@0.1.0':
1752
+
dependencies:
1753
+
'@atcute/cbor': 2.1.3
1754
+
'@atcute/cid': 2.1.0
1755
+
'@atcute/crypto': 2.2.0
1756
+
'@atcute/multibase': 1.1.2
1757
+
'@atcute/uint8array': 1.0.1
1758
+
'@badrap/valita': 0.4.3
1759
+
1760
+
'@atcute/identity-resolver@0.1.2(@atcute/identity@0.1.1)':
1761
+
dependencies:
1762
+
'@atcute/identity': 0.1.1
1763
+
'@atcute/util-fetch': 1.0.0
1764
+
'@badrap/valita': 0.4.3
1765
+
1766
+
'@atcute/identity@0.1.1':
1767
+
dependencies:
1768
+
'@badrap/valita': 0.4.3
1769
+
1728
1770
'@atcute/multibase@1.1.2':
1729
1771
dependencies:
1730
1772
'@atcute/uint8array': 1.0.1
1731
1773
1732
1774
'@atcute/uint8array@1.0.1': {}
1775
+
1776
+
'@atcute/util-fetch@1.0.0':
1777
+
dependencies:
1778
+
'@badrap/valita': 0.4.3
1733
1779
1734
1780
'@atcute/varint@1.0.2': {}
1735
1781
+14
-47
src/api/queries/did-doc.ts
+14
-47
src/api/queries/did-doc.ts
···
1
-
import { At } from '@atcute/client/lexicons';
1
+
import type { AtprotoDid, DidDocument } from '@atcute/identity';
2
+
import {
3
+
CompositeDidDocumentResolver,
4
+
PlcDidDocumentResolver,
5
+
WebDidDocumentResolver,
6
+
} from '@atcute/identity-resolver';
2
7
3
-
import { didDocument, DidDocument } from '../types/did-doc';
4
-
import { DID_PLC_RE, DID_WEB_RE } from '../utils/strings';
8
+
const didDocumentResolver = new CompositeDidDocumentResolver({
9
+
methods: {
10
+
plc: new PlcDidDocumentResolver(),
11
+
web: new WebDidDocumentResolver(),
12
+
},
13
+
});
5
14
6
15
export const getDidDocument = async ({
7
16
did,
8
17
signal,
9
18
}: {
10
-
did: At.DID;
19
+
did: AtprotoDid;
11
20
signal?: AbortSignal;
12
21
}): Promise<DidDocument> => {
13
-
const colon_index = did.indexOf(':', 4);
14
-
15
-
const type = did.slice(4, colon_index);
16
-
const ident = did.slice(colon_index + 1);
17
-
18
-
let rawDoc: any;
19
-
20
-
if (type === 'plc') {
21
-
if (!DID_PLC_RE.test(did)) {
22
-
throw new Error(`invalid did:plc identifier`);
23
-
}
24
-
25
-
const origin = import.meta.env.VITE_PLC_DIRECTORY_URL;
26
-
const response = await fetch(`${origin}/${did}`, { signal });
27
-
28
-
if (response.status === 404) {
29
-
throw new Error(`did not found in directory`);
30
-
} else if (!response.ok) {
31
-
throw new Error(`directory is unreachable`);
32
-
}
33
-
34
-
const json = await response.json();
35
-
36
-
rawDoc = json;
37
-
} else if (type === 'web') {
38
-
if (!DID_WEB_RE.test(did)) {
39
-
throw new Error(`invalid did:web identifier`);
40
-
}
41
-
42
-
const response = await fetch(`https://${ident}/.well-known/did.json`, { signal });
43
-
44
-
if (!response.ok) {
45
-
throw new Error(`did document is unreachable`);
46
-
}
47
-
48
-
const json = await response.json();
49
-
50
-
rawDoc = json;
51
-
} else {
52
-
throw new Error(`unsupported did method`);
53
-
}
54
-
55
-
return didDocument.parse(rawDoc, { mode: 'passthrough' });
22
+
return didDocumentResolver.resolve(did, { signal });
56
23
};
+15
-19
src/api/queries/handle.ts
+15
-19
src/api/queries/handle.ts
···
1
-
import { simpleFetchHandler, XRPC } from '@atcute/client';
2
-
import { At } from '@atcute/client/lexicons';
1
+
import { type AtprotoDid, type Handle, isHandle } from '@atcute/identity';
2
+
import { XrpcHandleResolver } from '@atcute/identity-resolver';
3
3
4
-
import { appViewRpc } from '~/globals/rpc';
4
+
const handleResolver = new XrpcHandleResolver({
5
+
serviceUrl: import.meta.env.VITE_APPVIEW_URL,
6
+
});
5
7
6
8
export const resolveHandleViaAppView = async ({
7
9
handle,
8
10
signal,
9
11
}: {
10
-
handle: string;
12
+
handle: Handle;
11
13
signal?: AbortSignal;
12
-
}): Promise<At.DID> => {
13
-
const { data } = await appViewRpc.get('com.atproto.identity.resolveHandle', {
14
-
signal: signal,
15
-
params: { handle: handle },
16
-
});
14
+
}): Promise<AtprotoDid> => {
15
+
if (!isHandle(handle)) {
16
+
throw new Error(`invalid handle: ${handle}`);
17
+
}
17
18
18
-
return data.did;
19
+
return await handleResolver.resolve(handle, { signal });
19
20
};
20
21
21
22
export const resolveHandleViaPds = async ({
···
24
25
signal,
25
26
}: {
26
27
service: string;
27
-
handle: string;
28
+
handle: Handle;
28
29
signal?: AbortSignal;
29
-
}): Promise<At.DID> => {
30
-
const rpc = new XRPC({ handler: simpleFetchHandler({ service }) });
30
+
}): Promise<AtprotoDid> => {
31
+
const resolver = new XrpcHandleResolver({ serviceUrl: service });
31
32
32
-
const { data } = await rpc.get('com.atproto.identity.resolveHandle', {
33
-
signal,
34
-
params: { handle },
35
-
});
36
-
37
-
return data.did;
33
+
return await resolver.resolve(handle, { signal });
38
34
};
+4
-5
src/api/queries/plc.ts
+4
-5
src/api/queries/plc.ts
···
1
-
import { At } from '@atcute/client/lexicons';
1
+
import { defs } from '@atcute/did-plc';
2
+
import { Did } from '@atcute/identity';
2
3
3
-
import { plcLogEntries } from '../types/plc';
4
-
5
-
export const getPlcAuditLogs = async ({ did, signal }: { did: At.DID; signal?: AbortSignal }) => {
4
+
export const getPlcAuditLogs = async ({ did, signal }: { did: Did<'plc'>; signal?: AbortSignal }) => {
6
5
const origin = import.meta.env.VITE_PLC_DIRECTORY_URL;
7
6
const response = await fetch(`${origin}/${did}/log/audit`, { signal });
8
7
if (!response.ok) {
···
10
9
}
11
10
12
11
const json = await response.json();
13
-
return plcLogEntries.parse(json);
12
+
return defs.indexedEntryLog.parse(json);
14
13
};
-86
src/api/types/did-doc.ts
-86
src/api/types/did-doc.ts
···
1
-
import * as v from '@badrap/valita';
2
-
3
-
import { didString, serviceUrlString, urlString } from './strings';
4
-
5
-
const PUBLIC_KEY_MULTIBASE_RE = /^z[a-km-zA-HJ-NP-Z1-9]+$/;
6
-
7
-
const verificationMethod = v.object({
8
-
id: v.string(),
9
-
type: v.string(),
10
-
controller: didString,
11
-
publicKeyMultibase: v
12
-
.string()
13
-
.assert((input) => PUBLIC_KEY_MULTIBASE_RE.test(input), `must be a valid base58btc multibase key`),
14
-
});
15
-
16
-
const service = v
17
-
.object({
18
-
id: v.string(),
19
-
type: v.string(),
20
-
serviceEndpoint: v.union(urlString, v.record(urlString), v.array(urlString)),
21
-
})
22
-
.chain((input) => {
23
-
switch (input.type) {
24
-
case 'AtprotoPersonalDataServer':
25
-
case 'AtprotoLabeler':
26
-
case 'BskyFeedGenerator':
27
-
case 'BskyNotificationService': {
28
-
const result = serviceUrlString.try(input.serviceEndpoint);
29
-
if (!result.ok) {
30
-
return v.err({
31
-
message: `must be a valid atproto service url`,
32
-
path: ['serviceEndpoint'],
33
-
});
34
-
}
35
-
}
36
-
}
37
-
38
-
return v.ok(input);
39
-
});
40
-
41
-
export const didDocument = v.object({
42
-
'@context': v.array(urlString),
43
-
id: didString,
44
-
alsoKnownAs: v.array(urlString).optional(() => []),
45
-
verificationMethod: v.array(verificationMethod).optional(() => []),
46
-
service: v.array(service).chain((input) => {
47
-
for (let i = 0, len = input.length; i < len; i++) {
48
-
const service = input[i];
49
-
const id = service.id;
50
-
51
-
for (let j = 0; j < i; j++) {
52
-
if (input[j].id === id) {
53
-
return v.err({
54
-
message: `duplicate service id`,
55
-
path: [i, 'id'],
56
-
});
57
-
}
58
-
}
59
-
}
60
-
61
-
return v.ok(input);
62
-
}),
63
-
});
64
-
65
-
export type DidDocument = v.Infer<typeof didDocument>;
66
-
67
-
export const getPdsEndpoint = (doc: DidDocument): string | undefined => {
68
-
return getServiceEndpoint(doc, '#atproto_pds', 'AtprotoPersonalDataServer');
69
-
};
70
-
71
-
export const getServiceEndpoint = (
72
-
doc: DidDocument,
73
-
serviceId: string,
74
-
serviceType: string,
75
-
): string | undefined => {
76
-
const did = doc.id;
77
-
78
-
const didServiceId = did + serviceId;
79
-
const found = doc.service?.find((service) => service.id === serviceId || service.id === didServiceId);
80
-
81
-
if (!found || found.type !== serviceType || typeof found.serviceEndpoint !== 'string') {
82
-
return undefined;
83
-
}
84
-
85
-
return found.serviceEndpoint;
86
-
};
+26
-78
src/api/types/plc.ts
+26
-78
src/api/types/plc.ts
···
1
1
import * as v from '@badrap/valita';
2
2
3
-
import { didKeyString, didString, handleString, serviceUrlString, urlString } from './strings';
4
-
5
-
const legacyGenesisOp = v.object({
6
-
type: v.literal('create'),
7
-
signingKey: didKeyString,
8
-
recoveryKey: didKeyString,
9
-
handle: handleString,
10
-
service: serviceUrlString,
11
-
prev: v.null(),
12
-
sig: v.string(),
13
-
});
14
-
15
-
const tombstoneOp = v.object({
16
-
type: v.literal('plc_tombstone'),
17
-
prev: v.string(),
18
-
sig: v.string(),
19
-
});
20
-
21
-
const service = v.object({
22
-
type: v.string().assert((input) => input.length <= 256, `service type too long (max 256)`),
23
-
endpoint: urlString.assert((input) => input.length <= 512, `service endpoint too long (max 512)`),
24
-
});
25
-
export type Service = v.Infer<typeof service>;
26
-
27
-
const updateOp = v.object({
28
-
type: v.literal('plc_operation'),
29
-
prev: v.string().nullable(),
30
-
sig: v.string(),
31
-
rotationKeys: v.array(didKeyString).chain((input) => {
32
-
const len = input.length;
33
-
34
-
if (len === 0) {
35
-
return v.err({ message: `missing rotation keys` });
36
-
} else if (len > 10) {
37
-
return v.err({ message: `too many rotation keys (max 10)` });
38
-
}
39
-
40
-
for (let i = 0; i < len; i++) {
41
-
const key = input[i];
42
-
43
-
for (let j = 0; j < i; j++) {
44
-
if (input[j] === key) {
45
-
return v.err({
46
-
message: `duplicate rotation key`,
47
-
path: [i],
48
-
});
49
-
}
50
-
}
51
-
}
52
-
53
-
return v.ok(input);
54
-
}),
55
-
verificationMethods: v.record(didKeyString),
56
-
alsoKnownAs: v
57
-
.array(urlString.assert((input) => input.length <= 256, `alsoKnownAs entry too long (max 256)`))
58
-
.assert((input) => input.length <= 10, `too many alsoKnownAs entries (max 10)`),
59
-
services: v
60
-
.record(service)
61
-
.assert((input) => Object.keys(input).length <= 10, `too many service entries (max 10)`),
62
-
});
63
-
export type PlcUpdateOp = v.Infer<typeof updateOp>;
3
+
import { defs, UnsignedOperation } from '@atcute/did-plc';
64
4
65
-
const plcOperation = v.union(legacyGenesisOp, tombstoneOp, updateOp);
66
-
67
-
export const plcLogEntry = v.object({
68
-
did: didString,
69
-
cid: v.string(),
70
-
operation: plcOperation,
71
-
nullified: v.boolean(),
72
-
createdAt: v
73
-
.string()
74
-
.assert((input) => !Number.isNaN(new Date(input).getTime()), `must be a valid datetime string`),
75
-
});
76
-
export type PlcLogEntry = v.Infer<typeof plcLogEntry>;
5
+
import { ToValidator } from '../utils/valita';
6
+
import { serviceUrlString } from './strings';
77
7
78
-
export const plcLogEntries = v.array(plcLogEntry);
8
+
const _unsignedOperation = defs.unsignedOperation as ToValidator<UnsignedOperation>;
79
9
80
-
export const updatePayload = updateOp.omit('type', 'prev', 'sig').extend({
10
+
export const updatePayload = _unsignedOperation.omit('type', 'prev').extend({
81
11
services: v
82
12
.record(
83
-
service.chain((input) => {
13
+
defs.service.chain((input) => {
84
14
switch (input.type) {
85
15
case 'AtprotoPersonalDataServer':
86
16
case 'AtprotoLabeler':
···
107
37
return v.ok(input);
108
38
}),
109
39
)
110
-
.assert((input) => Object.keys(input).length <= 10, `too many service entries (max 10)`),
40
+
.chain((input) => {
41
+
const length = Object.keys(input).length;
42
+
43
+
if (length > 10) {
44
+
return v.err(`too many service entries (max 10)`);
45
+
}
46
+
47
+
for (const id in input) {
48
+
if (id.length > 32) {
49
+
return v.err({
50
+
message: `service id too long (max 32 characters)`,
51
+
path: [id],
52
+
});
53
+
}
54
+
}
55
+
56
+
return v.ok(input);
57
+
}),
111
58
});
112
-
export type PlcUpdatePayload = v.Infer<typeof updatePayload>;
59
+
60
+
export type UpdatePayload = v.Infer<typeof updatePayload>;
-14
src/api/types/strings.ts
-14
src/api/types/strings.ts
···
1
1
import * as v from '@badrap/valita';
2
2
3
-
import { DID_KEY_RE, DID_RE, HANDLE_RE } from '../utils/strings';
4
-
5
-
export const didString = v
6
-
.string()
7
-
.assert((input): input is `did:${string}:${string}` => DID_RE.test(input), `must be a valid did`);
8
-
9
-
export const didKeyString = v
10
-
.string()
11
-
.assert((input): input is `did:key:${string}` => DID_KEY_RE.test(input), `must be a valid did:key`);
12
-
13
-
export const handleString = v.string().assert((input) => HANDLE_RE.test(input), `must be a valid handle`);
14
-
15
-
export const urlString = v.string().assert((input) => URL.canParse(input), `must be a valid url`);
16
-
17
3
export const serviceUrlString = v.string().assert((input) => {
18
4
const url = URL.parse(input);
19
5
+53
src/api/utils/at-uri.ts
+53
src/api/utils/at-uri.ts
···
1
+
import { assert } from '~/lib/utils/invariant';
2
+
3
+
type Did<TMethod extends string = string> = `did:${TMethod}:${string}`;
4
+
5
+
type Nsid = `${string}.${string}.${string}`;
6
+
7
+
type RecordKey = string;
8
+
9
+
const DID_RE = /^did:([a-z]+):([a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-])$/;
10
+
11
+
const NSID_RE =
12
+
/^[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})?)$/;
13
+
14
+
const RECORD_KEY_RE = /^(?!\.{1,2}$)[a-zA-Z0-9_~.:-]{1,512}$/;
15
+
16
+
const ATURI_RE =
17
+
/^at:\/\/([a-zA-Z0-9._:%-]+)(?:\/([a-zA-Z0-9-.]+)(?:\/([a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(?:#(\/[a-zA-Z0-9._~:@!$&%')(*+,;=\-[\]/\\]*))?$/;
18
+
19
+
const isDid = (input: unknown): input is Did => {
20
+
return typeof input === 'string' && input.length >= 7 && input.length <= 2048 && DID_RE.test(input);
21
+
};
22
+
23
+
const isNsid = (input: unknown): input is Nsid => {
24
+
return typeof input === 'string' && input.length >= 5 && input.length <= 317 && NSID_RE.test(input);
25
+
};
26
+
27
+
const isRecordKey = (input: unknown): input is RecordKey => {
28
+
return typeof input === 'string' && input.length >= 1 && input.length <= 512 && RECORD_KEY_RE.test(input);
29
+
};
30
+
31
+
export interface AddressedAtUri {
32
+
repo: Did;
33
+
collection: Nsid;
34
+
rkey: string;
35
+
fragment: string | undefined;
36
+
}
37
+
38
+
export const parseAddressedAtUri = (str: string): AddressedAtUri => {
39
+
const match = ATURI_RE.exec(str);
40
+
assert(match !== null, `invalid addressed-at-uri: ${str}`);
41
+
42
+
const [, r, c, k, f] = match;
43
+
assert(isDid(r), `invalid repo in addressed-at-uri: ${r}`);
44
+
assert(isNsid(c), `invalid collection in addressed-at-uri: ${c}`);
45
+
assert(isRecordKey(k), `invalid rkey in addressed-at-uri: ${k}`);
46
+
47
+
return {
48
+
repo: r,
49
+
collection: c,
50
+
rkey: k,
51
+
fragment: f,
52
+
};
53
+
};
+1
-43
src/api/utils/strings.ts
+1
-43
src/api/utils/strings.ts
···
1
-
import type { At, Records } from '@atcute/client/lexicons';
2
-
3
-
import { assert } from '~/lib/utils/invariant';
4
-
5
-
export const ATURI_RE =
6
-
/^at:\/\/(did:[a-zA-Z0-9._:%\-]+|[a-zA-Z0-9-.]+)\/([a-zA-Z0-9-.]+)\/([a-zA-Z0-9._~:@!$&%')(*+,;=\-]+)(?:#(\/[a-zA-Z0-9._~:@!$&%')(*+,;=\-[\]/\\]*))?$/;
7
-
8
-
export const DID_RE = /^did:([a-z]+):([a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-])$/;
9
-
10
-
export const DID_WEB_RE = /^did:web:([a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*(?:\.[a-zA-Z]{2,}))$/;
11
-
12
-
export const DID_PLC_RE = /^did:plc:([a-z2-7]{24})$/;
13
-
14
-
export const DID_KEY_RE = /^did:key:z[a-km-zA-HJ-NP-Z1-9]+$/;
15
-
16
-
export const HANDLE_RE = /^[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(?:\.[a-zA-Z]{2,})$/;
1
+
import type { Records } from '@atcute/client/lexicons';
17
2
18
3
export const DID_OR_HANDLE_RE =
19
4
/^[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*(?:\.[a-zA-Z]{2,})$|^did:[a-z]+:[a-zA-Z0-9._:%\-]*[a-zA-Z0-9._\-]$/;
20
-
21
-
export interface AtUri {
22
-
repo: string;
23
-
collection: string;
24
-
rkey: string;
25
-
fragment: string | undefined;
26
-
}
27
-
28
-
export const isDid = (value: string): value is At.DID => {
29
-
return value.length >= 7 && DID_RE.test(value);
30
-
};
31
-
32
-
export const isHandle = (value: string): boolean => {
33
-
return value.length >= 4 && HANDLE_RE.test(value);
34
-
};
35
-
36
-
export const parseAtUri = (str: string): AtUri => {
37
-
const match = ATURI_RE.exec(str);
38
-
assert(match !== null, `Failed to parse AT URI for ${str}`);
39
-
40
-
return {
41
-
repo: match[1],
42
-
collection: match[2],
43
-
rkey: match[3],
44
-
fragment: match[4],
45
-
};
46
-
};
47
5
48
6
export const makeAtUri = (repo: string, collection: keyof Records | (string & {}), rkey: string) => {
49
7
return `at://${repo}/${collection}/${rkey}`;
+26
src/api/utils/valita.ts
+26
src/api/utils/valita.ts
···
1
+
import * as v from '@badrap/valita';
2
+
3
+
export type ToValidator<T> = T extends readonly [infer Head, ...infer Rest]
4
+
? Rest extends []
5
+
? v.TupleType<[ToValidator<Head>]>
6
+
: v.TupleType<[ToValidator<Head>, ...ToValidatorTuple<Rest>]>
7
+
: T extends ReadonlyArray<infer E>
8
+
? v.ArrayType<ToValidator<E>>
9
+
: T extends object
10
+
? ToObjectValidator<T>
11
+
: v.Type<T>;
12
+
13
+
// Helper type for converting tuple types
14
+
type ToValidatorTuple<T extends readonly unknown[]> = T extends readonly [infer Head, ...infer Rest]
15
+
? Rest extends []
16
+
? [ToValidator<Head>]
17
+
: [ToValidator<Head>, ...ToValidatorTuple<Rest>]
18
+
: [];
19
+
20
+
// Helper type for converting object types
21
+
type ToObjectValidator<T extends object> = v.ObjectType<
22
+
{
23
+
[K in keyof T]-?: undefined extends T[K] ? v.Optional<Exclude<T[K], undefined>> : ToValidator<T[K]>;
24
+
},
25
+
undefined
26
+
>;
+6
-6
src/components/wizards/bluesky-login-step.tsx
+6
-6
src/components/wizards/bluesky-login-step.tsx
···
1
1
import { batch, createSignal, Match, Show, Switch } from 'solid-js';
2
2
3
3
import { CredentialManager, XRPCError } from '@atcute/client';
4
-
import { At } from '@atcute/client/lexicons';
4
+
import { type AtprotoDid, type DidDocument, getPdsEndpoint, isAtprotoDid, isHandle } from '@atcute/identity';
5
5
6
6
import { getDidDocument } from '~/api/queries/did-doc';
7
7
import { resolveHandleViaAppView } from '~/api/queries/handle';
8
-
import { DidDocument, getPdsEndpoint } from '~/api/types/did-doc';
9
8
import { formatTotpCode, TOTP_RE } from '~/api/utils/auth';
10
-
import { isDid } from '~/api/utils/strings';
11
9
12
10
import { createMutation } from '~/lib/utils/mutation';
13
11
···
54
52
service = service?.trim() || undefined;
55
53
56
54
if (service === undefined) {
57
-
let did: At.DID;
58
-
if (!isDid(identifier)) {
55
+
let did: AtprotoDid;
56
+
if (isAtprotoDid(identifier)) {
57
+
did = identifier;
58
+
} else if (isHandle(identifier)) {
59
59
did = await resolveHandleViaAppView({ handle: identifier });
60
60
} else {
61
-
did = identifier;
61
+
throw new InsufficientLoginError(`Invalid identifier`);
62
62
}
63
63
64
64
const didDoc = await getDidDocument({ did });
+4
-4
src/lib/utils/search-params.ts
+4
-4
src/lib/utils/search-params.ts
···
1
1
import { batch, createSignal } from 'solid-js';
2
2
3
3
import { At } from '@atcute/client/lexicons';
4
+
import { isDid, isHandle } from '@atcute/identity';
4
5
5
-
import { DID_OR_HANDLE_RE, DID_RE, HANDLE_RE } from '~/api/utils/strings';
6
6
import { UnwrapArray } from '~/api/utils/types';
7
7
8
8
export interface ParamParser<T> {
···
223
223
224
224
export const asDID = createParser({
225
225
parse(value) {
226
-
if (typeof value === 'string' && DID_RE.test(value)) {
226
+
if (typeof value === 'string' && isDid(value)) {
227
227
return value as At.DID;
228
228
}
229
229
···
236
236
237
237
export const asHandle = createParser({
238
238
parse(value) {
239
-
if (typeof value === 'string' && HANDLE_RE.test(value)) {
239
+
if (typeof value === 'string' && isHandle(value)) {
240
240
return value;
241
241
}
242
242
···
249
249
250
250
export const asIdentifier = createParser({
251
251
parse(value) {
252
-
if (typeof value === 'string' && DID_OR_HANDLE_RE.test(value)) {
252
+
if (typeof value === 'string' && (isDid(value) || isHandle(value))) {
253
253
return value;
254
254
}
255
255
+14
-10
src/views/blob/blob-export.tsx
+14
-10
src/views/blob/blob-export.tsx
···
2
2
import { createSignal } from 'solid-js';
3
3
4
4
import { simpleFetchHandler, XRPC, XRPCError } from '@atcute/client';
5
-
import { At } from '@atcute/client/lexicons';
5
+
import { type AtprotoDid, getPdsEndpoint, isAtprotoDid, isHandle } from '@atcute/identity';
6
6
import { writeTarEntry } from '@mary/tar';
7
7
8
8
import { getDidDocument } from '~/api/queries/did-doc';
9
9
import { resolveHandleViaAppView, resolveHandleViaPds } from '~/api/queries/handle';
10
-
import { getPdsEndpoint } from '~/api/types/did-doc';
11
10
import { isServiceUrlString } from '~/api/types/strings';
12
-
import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings';
11
+
import { DID_OR_HANDLE_RE } from '~/api/utils/strings';
13
12
14
13
import { useTitle } from '~/lib/navigation/router';
15
14
import { makeAbortable } from '~/lib/utils/abortable';
···
36
35
}) => {
37
36
logger.info(`Starting export for ${identifier}`);
38
37
39
-
let did: At.DID;
40
-
if (isDid(identifier)) {
38
+
let did: AtprotoDid;
39
+
if (isAtprotoDid(identifier)) {
41
40
did = identifier;
42
-
} else if (service) {
43
-
did = await resolveHandleViaPds({ service, handle: identifier, signal });
44
-
logger.log(`Resolved handle to ${did}`);
41
+
} else if (isHandle(identifier)) {
42
+
if (service) {
43
+
did = await resolveHandleViaPds({ service, handle: identifier, signal });
44
+
logger.log(`Resolved handle to ${did}`);
45
+
} else {
46
+
did = await resolveHandleViaAppView({ handle: identifier, signal });
47
+
logger.log(`Resolved handle to ${did}`);
48
+
}
45
49
} else {
46
-
did = await resolveHandleViaAppView({ handle: identifier, signal });
47
-
logger.log(`Resolved handle to ${did}`);
50
+
logger.error(`Invalid identifier`);
51
+
return;
48
52
}
49
53
50
54
if (!service) {
+1
-1
src/views/bluesky/threadgate-applicator/page.tsx
+1
-1
src/views/bluesky/threadgate-applicator/page.tsx
···
2
2
3
3
import { CredentialManager } from '@atcute/client';
4
4
import { AppBskyFeedDefs, AppBskyFeedThreadgate } from '@atcute/client/lexicons';
5
+
import { DidDocument } from '@atcute/identity';
5
6
6
-
import { DidDocument } from '~/api/types/did-doc';
7
7
import { UnwrapArray } from '~/api/utils/types';
8
8
9
9
import { history } from '~/globals/navigation';
+8
-5
src/views/bluesky/threadgate-applicator/steps/step1_handle-input.tsx
+8
-5
src/views/bluesky/threadgate-applicator/steps/step1_handle-input.tsx
···
1
1
import { createSignal } from 'solid-js';
2
2
3
-
import type { AppBskyFeedThreadgate, At } from '@atcute/client/lexicons';
3
+
import type { AppBskyFeedThreadgate } from '@atcute/client/lexicons';
4
+
import { type AtprotoDid, isAtprotoDid, isHandle } from '@atcute/identity';
4
5
5
6
import { getDidDocument } from '~/api/queries/did-doc';
6
7
import { resolveHandleViaAppView } from '~/api/queries/handle';
7
-
import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings';
8
+
import { DID_OR_HANDLE_RE } from '~/api/utils/strings';
8
9
9
10
import { appViewRpc } from '~/globals/rpc';
10
11
···
32
33
async mutationFn({ identifier }: { identifier: string }, signal) {
33
34
setStatus(`Resolving identity`);
34
35
35
-
let did: At.DID;
36
-
if (isDid(identifier)) {
36
+
let did: AtprotoDid;
37
+
if (isAtprotoDid(identifier)) {
37
38
did = identifier;
38
-
} else {
39
+
} else if (isHandle(identifier)) {
39
40
did = await resolveHandleViaAppView({ handle: identifier, signal });
41
+
} else {
42
+
throw new Error(`Invalid identifier`);
40
43
}
41
44
42
45
const didDoc = await getDidDocument({ did, signal });
+7
-6
src/views/bluesky/threadgate-applicator/steps/step4_confirmation.tsx
+7
-6
src/views/bluesky/threadgate-applicator/steps/step4_confirmation.tsx
···
1
1
import { createSignal, Show } from 'solid-js';
2
2
3
3
import { XRPC, XRPCError } from '@atcute/client';
4
-
import { AppBskyFeedThreadgate, ComAtprotoRepoApplyWrites } from '@atcute/client/lexicons';
4
+
import type { AppBskyFeedThreadgate, ComAtprotoRepoApplyWrites } from '@atcute/client/lexicons';
5
5
import { chunked } from '@mary/array-fns';
6
+
7
+
import { parseAddressedAtUri } from '~/api/utils/at-uri';
6
8
7
9
import { dequal } from '~/lib/utils/dequal';
8
10
import { createMutation } from '~/lib/utils/mutation';
9
11
10
12
import Button from '~/components/inputs/button';
11
13
import ToggleInput from '~/components/inputs/toggle-input';
14
+
import Logger, { createLogger } from '~/components/logger';
12
15
import { Stage, StageActions, StageErrorView, WizardStepProps } from '~/components/wizard';
13
16
14
-
import { parseAtUri } from '~/api/utils/strings';
15
-
import Logger, { createLogger } from '~/components/logger';
16
17
import { ThreadgateApplicatorConstraints } from '../page';
17
18
18
19
const Step4_Confirmation = ({
···
39
40
for (const { post, threadgate } of data.threads) {
40
41
if (threadgate === null) {
41
42
if (rules !== undefined) {
42
-
const { rkey } = parseAtUri(post.uri);
43
+
const { rkey } = parseAddressedAtUri(post.uri);
43
44
44
45
const record: AppBskyFeedThreadgate.Record = {
45
46
$type: 'app.bsky.feed.threadgate',
···
58
59
}
59
60
} else {
60
61
if (rules === undefined && !threadgate.hiddenReplies?.length) {
61
-
const { rkey } = parseAtUri(threadgate.uri);
62
+
const { rkey } = parseAddressedAtUri(threadgate.uri);
62
63
63
64
writes.push({
64
65
$type: 'com.atproto.repo.applyWrites#delete',
···
66
67
rkey: rkey,
67
68
});
68
69
} else if (!dequal(threadgate.allow, rules)) {
69
-
const { rkey } = parseAtUri(threadgate.uri);
70
+
const { rkey } = parseAddressedAtUri(threadgate.uri);
70
71
71
72
const record: AppBskyFeedThreadgate.Record = {
72
73
$type: 'app.bsky.feed.threadgate',
+11
-13
src/views/identity/did-lookup.tsx
+11
-13
src/views/identity/did-lookup.tsx
···
1
1
import { Match, Switch } from 'solid-js';
2
2
3
-
import { At } from '@atcute/client/lexicons';
3
+
import { isAtprotoDid, isHandle, type AtprotoDid, type Did, type Handle } from '@atcute/identity';
4
4
5
5
import { getDidDocument } from '~/api/queries/did-doc';
6
6
import { resolveHandleViaAppView } from '~/api/queries/handle';
7
7
import { isServiceUrlString } from '~/api/types/strings';
8
-
import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/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';
···
24
24
const query = createQuery(
25
25
() => params.q,
26
26
async (identifier, signal) => {
27
-
let did: At.DID;
28
-
if (isDid(identifier)) {
27
+
let did: AtprotoDid;
28
+
if (isAtprotoDid(identifier)) {
29
29
did = identifier;
30
-
} else {
30
+
} else if (isHandle(identifier)) {
31
31
did = await resolveHandleViaAppView({ handle: identifier, signal });
32
+
} else {
33
+
throw new Error(`Invalid identifier`);
32
34
}
33
35
34
36
const doc = await getDidDocument({ did, signal });
···
55
57
const formData = new FormData(ev.currentTarget);
56
58
ev.preventDefault();
57
59
58
-
const ident = formData.get('ident') as string;
60
+
const ident = formData.get('ident') as Did | Handle;
59
61
setParams({ q: ident });
60
62
}}
61
63
class="m-4 flex flex-col gap-4"
···
99
101
100
102
<div>
101
103
<p class="font-semibold text-gray-600">Identifies as</p>
102
-
<ol class="list-disc pl-4">
103
-
{doc.alsoKnownAs.map((ident) => (
104
-
<li>{ident}</li>
105
-
))}
106
-
</ol>
104
+
<ol class="list-disc pl-4">{doc.alsoKnownAs?.map((ident) => <li>{ident}</li>)}</ol>
107
105
</div>
108
106
109
107
<div>
110
108
<p class="font-semibold text-gray-600">Services</p>
111
109
<ol class="list-disc pl-4">
112
-
{doc.service.map(({ id, type, serviceEndpoint }, idx) => {
110
+
{doc.service?.map(({ id, type, serviceEndpoint }, idx) => {
113
111
const isString = typeof serviceEndpoint === 'string';
114
112
const isURL = isString && URL.canParse('' + serviceEndpoint);
115
113
const isServiceUrl = isString && isServiceUrlString(serviceEndpoint);
···
167
165
<div>
168
166
<p class="font-semibold text-gray-600">Verification methods</p>
169
167
<ol class="list-disc pl-4">
170
-
{doc.verificationMethod.map(({ id, type, publicKeyMultibase }, idx) => {
168
+
{doc.verificationMethod?.map(({ id, type, publicKeyMultibase }, idx) => {
171
169
return (
172
170
<li class={idx !== 0 ? `mt-3` : ``}>
173
171
<p class="font-medium">{id.replace(doc.id, '')}</p>
+4
-4
src/views/identity/plc-applicator/page.tsx
+4
-4
src/views/identity/plc-applicator/page.tsx
···
3
3
import type { CredentialManager } from '@atcute/client';
4
4
import type { ComAtprotoIdentityGetRecommendedDidCredentials } from '@atcute/client/lexicons';
5
5
import type { P256PrivateKey, Secp256k1PrivateKey } from '@atcute/crypto';
6
+
import type { DidDocument } from '@atcute/identity';
6
7
7
-
import type { DidDocument } from '~/api/types/did-doc';
8
-
import type { PlcUpdatePayload } from '~/api/types/plc';
8
+
import { UpdatePayload } from '~/api/types/plc';
9
9
10
10
import { history } from '~/globals/navigation';
11
11
···
69
69
info: PlcInformation;
70
70
method: PdsSigningMethod;
71
71
base: DetailedPlcEntry;
72
-
payload: PlcUpdatePayload;
72
+
payload: UpdatePayload;
73
73
};
74
74
Step5_PrivateKeyConfirmation: {
75
75
info: PlcInformation;
76
76
method: PrivateKeySigningMethod;
77
77
base: DetailedPlcEntry;
78
-
payload: PlcUpdatePayload;
78
+
payload: UpdatePayload;
79
79
};
80
80
81
81
Step6_Finished: {};
+5
-4
src/views/identity/plc-applicator/plc-utils.ts
+5
-4
src/views/identity/plc-applicator/plc-utils.ts
···
1
1
import * as CBOR from '@atcute/cbor';
2
2
import { verifySigWithDidKey } from '@atcute/crypto';
3
+
import type { IndexedEntry } from '@atcute/did-plc';
3
4
import { fromBase64Url } from '@atcute/multibase';
4
5
5
-
import { PlcLogEntry, PlcUpdatePayload } from '~/api/types/plc';
6
+
import { UpdatePayload } from '~/api/types/plc';
6
7
import { UnwrapArray } from '~/api/utils/types';
7
8
8
9
import { assert } from '~/lib/utils/invariant';
9
10
10
-
export const getPlcPayload = (entry: PlcLogEntry): PlcUpdatePayload => {
11
+
export const getPlcPayload = (entry: IndexedEntry): UpdatePayload => {
11
12
const op = entry.operation;
12
13
assert(op.type === 'plc_operation' || op.type === 'create');
13
14
···
37
38
assert(false);
38
39
};
39
40
40
-
export const getPlcKeying = async (logs: PlcLogEntry[]) => {
41
+
export const getPlcKeying = async (logs: IndexedEntry[]) => {
41
42
logs = logs.filter((entry) => !entry.nullified);
42
43
43
44
const length = logs.length;
···
112
113
type DetailedEntries = Awaited<ReturnType<typeof getPlcKeying>>;
113
114
export type DetailedPlcEntry = UnwrapArray<DetailedEntries>;
114
115
115
-
export const getCurrentSignersFromEntry = (entry: PlcLogEntry): string[] => {
116
+
export const getCurrentSignersFromEntry = (entry: IndexedEntry): string[] => {
116
117
const operation = entry.operation;
117
118
118
119
/** keys that can sign the next operation */
+12
-9
src/views/identity/plc-applicator/steps/step1_handle-input.tsx
+12
-9
src/views/identity/plc-applicator/steps/step1_handle-input.tsx
···
1
1
import { createSignal } from 'solid-js';
2
2
3
3
import { XRPCError } from '@atcute/client';
4
-
import { At } from '@atcute/client/lexicons';
4
+
import { type Did, isHandle, isPlcDid } from '@atcute/identity';
5
5
6
6
import { getDidDocument } from '~/api/queries/did-doc';
7
7
import { resolveHandleViaAppView } from '~/api/queries/handle';
8
8
import { getPlcAuditLogs } from '~/api/queries/plc';
9
-
import { DID_OR_HANDLE_RE, DID_PLC_RE, isDid } from '~/api/utils/strings';
9
+
import { DID_OR_HANDLE_RE } from '~/api/utils/strings';
10
10
11
11
import { createMutation } from '~/lib/utils/mutation';
12
12
···
38
38
39
39
const mutation = createMutation({
40
40
async mutationFn({ identifier }: MutationVariables): Promise<PlcInformation> {
41
-
let did: At.DID;
42
-
if (isDid(identifier)) {
41
+
let did: Did<'plc'>;
42
+
if (isPlcDid(identifier)) {
43
43
did = identifier;
44
-
} else {
45
-
did = await resolveHandleViaAppView({ handle: identifier });
46
-
}
44
+
} else if (isHandle(identifier)) {
45
+
const resolved = await resolveHandleViaAppView({ handle: identifier });
46
+
if (!isPlcDid(resolved)) {
47
+
throw new DidIsNotPlcError(`${resolved} does not resolve to a did:plc`);
48
+
}
47
49
48
-
if (!DID_PLC_RE.test(did)) {
49
-
throw new DidIsNotPlcError(`"${did}" is not did:plc`);
50
+
did = resolved;
51
+
} else {
52
+
throw new DidIsNotPlcError(`${identifier} is not a valid did:plc or handle`);
50
53
}
51
54
52
55
const [didDoc, logs] = await Promise.all([getDidDocument({ did }), getPlcAuditLogs({ did })]);
+2
-2
src/views/identity/plc-applicator/steps/step2_pds-authentication.tsx
+2
-2
src/views/identity/plc-applicator/steps/step2_pds-authentication.tsx
···
2
2
3
3
import { AtpAccessJwt, CredentialManager, XRPC, XRPCError } from '@atcute/client';
4
4
import { decodeJwt } from '@atcute/client/utils/jwt';
5
+
import { getPdsEndpoint } from '@atcute/identity';
5
6
6
-
import { getPdsEndpoint } from '~/api/types/did-doc';
7
-
import { TOTP_RE, formatTotpCode } from '~/api/utils/auth';
7
+
import { formatTotpCode, TOTP_RE } from '~/api/utils/auth';
8
8
9
9
import { createMutation } from '~/lib/utils/mutation';
10
10
+4
-5
src/views/identity/plc-applicator/steps/step5_private-key-confirmation.tsx
+4
-5
src/views/identity/plc-applicator/steps/step5_private-key-confirmation.tsx
···
1
1
import { createSignal } from 'solid-js';
2
2
3
3
import * as CBOR from '@atcute/cbor';
4
+
import type { Operation, UnsignedOperation } from '@atcute/did-plc';
4
5
import { toBase64Url } from '@atcute/multibase';
5
-
6
-
import { PlcUpdateOp } from '~/api/types/plc';
7
6
8
7
import { generateConfirmationCode } from '~/lib/utils/confirmation-code';
9
8
import { createMutation } from '~/lib/utils/mutation';
···
30
29
const payload = data.payload;
31
30
const prev = data.base;
32
31
33
-
const operation: Omit<PlcUpdateOp, 'sig'> = {
32
+
const operation: UnsignedOperation = {
34
33
type: 'plc_operation',
35
34
prev: prev!.cid,
36
35
···
45
44
46
45
const signature = toBase64Url(sigBytes);
47
46
48
-
const signedOperation: PlcUpdateOp = {
47
+
const signedOperation: Operation = {
49
48
...operation,
50
49
sig: signature,
51
50
};
···
122
121
123
122
export default Step5_PrivateKeyConfirmation;
124
123
125
-
const pushPlcOperation = async (did: string, operation: PlcUpdateOp) => {
124
+
const pushPlcOperation = async (did: string, operation: Operation) => {
126
125
const origin = import.meta.env.VITE_PLC_DIRECTORY_URL;
127
126
const response = await fetch(`${origin}/${did}`, {
128
127
method: 'post',
+28
-25
src/views/identity/plc-oplogs.tsx
+28
-25
src/views/identity/plc-oplogs.tsx
···
1
1
import { createSignal, JSX, Match, onCleanup, Switch } from 'solid-js';
2
2
3
-
import { At } from '@atcute/client/lexicons';
3
+
import type { IndexedEntry, Service } from '@atcute/did-plc';
4
+
import { type Did, type Handle, isHandle, isPlcDid } from '@atcute/identity';
4
5
5
6
import { resolveHandleViaAppView } from '~/api/queries/handle';
6
-
import { PlcLogEntry, Service } from '~/api/types/plc';
7
-
import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings';
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';
···
29
29
const query = createQuery(
30
30
() => params.q,
31
31
async (identifier, signal) => {
32
-
let did: At.DID;
33
-
if (isDid(identifier)) {
32
+
let did: Did<'plc'>;
33
+
if (isPlcDid(identifier)) {
34
34
did = identifier;
35
-
} else {
36
-
did = await resolveHandleViaAppView({ handle: identifier, signal });
37
-
}
35
+
} else if (isHandle(identifier)) {
36
+
const resolved = await resolveHandleViaAppView({ handle: identifier, signal });
37
+
if (!isPlcDid(resolved)) {
38
+
throw new Error(`${identifier} is not a valid identifier`);
39
+
}
38
40
39
-
if (!did.startsWith('did:plc:')) {
40
-
throw new Error(`${did} is not plc`);
41
+
did = resolved;
42
+
} else {
43
+
throw new Error(`${identifier} is not a valid identifier`);
41
44
}
42
45
43
46
const logs = await getPlcAuditLogs({ did, signal });
···
63
66
const formData = new FormData(ev.currentTarget);
64
67
ev.preventDefault();
65
68
66
-
const ident = formData.get('ident') as string;
69
+
const ident = formData.get('ident') as Did | Handle;
67
70
setParams({ q: ident });
68
71
}}
69
72
class="m-4 flex flex-col gap-4"
···
372
375
type DiffEntry =
373
376
| {
374
377
type: 'identity_created';
375
-
orig: PlcLogEntry;
378
+
orig: IndexedEntry;
376
379
nullified: boolean;
377
380
at: string;
378
381
rotationKeys: string[];
···
382
385
}
383
386
| {
384
387
type: 'identity_tombstoned';
385
-
orig: PlcLogEntry;
388
+
orig: IndexedEntry;
386
389
nullified: boolean;
387
390
at: string;
388
391
}
389
392
| {
390
393
type: 'rotation_key_added';
391
-
orig: PlcLogEntry;
394
+
orig: IndexedEntry;
392
395
nullified: boolean;
393
396
at: string;
394
397
rotation_key: string;
395
398
}
396
399
| {
397
400
type: 'rotation_key_removed';
398
-
orig: PlcLogEntry;
401
+
orig: IndexedEntry;
399
402
nullified: boolean;
400
403
at: string;
401
404
rotation_key: string;
402
405
}
403
406
| {
404
407
type: 'verification_method_added';
405
-
orig: PlcLogEntry;
408
+
orig: IndexedEntry;
406
409
nullified: boolean;
407
410
at: string;
408
411
method_id: string;
···
410
413
}
411
414
| {
412
415
type: 'verification_method_removed';
413
-
orig: PlcLogEntry;
416
+
orig: IndexedEntry;
414
417
nullified: boolean;
415
418
at: string;
416
419
method_id: string;
···
418
421
}
419
422
| {
420
423
type: 'verification_method_changed';
421
-
orig: PlcLogEntry;
424
+
orig: IndexedEntry;
422
425
nullified: boolean;
423
426
at: string;
424
427
method_id: string;
···
427
430
}
428
431
| {
429
432
type: 'handle_added';
430
-
orig: PlcLogEntry;
433
+
orig: IndexedEntry;
431
434
nullified: boolean;
432
435
at: string;
433
436
handle: string;
434
437
}
435
438
| {
436
439
type: 'handle_removed';
437
-
orig: PlcLogEntry;
440
+
orig: IndexedEntry;
438
441
nullified: boolean;
439
442
at: string;
440
443
handle: string;
441
444
}
442
445
| {
443
446
type: 'handle_changed';
444
-
orig: PlcLogEntry;
447
+
orig: IndexedEntry;
445
448
nullified: boolean;
446
449
at: string;
447
450
prev_handle: string;
···
449
452
}
450
453
| {
451
454
type: 'service_added';
452
-
orig: PlcLogEntry;
455
+
orig: IndexedEntry;
453
456
nullified: boolean;
454
457
at: string;
455
458
service_id: string;
···
458
461
}
459
462
| {
460
463
type: 'service_removed';
461
-
orig: PlcLogEntry;
464
+
orig: IndexedEntry;
462
465
nullified: boolean;
463
466
at: string;
464
467
service_id: string;
···
467
470
}
468
471
| {
469
472
type: 'service_changed';
470
-
orig: PlcLogEntry;
473
+
orig: IndexedEntry;
471
474
nullified: boolean;
472
475
at: string;
473
476
service_id: string;
···
477
480
next_service_endpoint: string;
478
481
};
479
482
480
-
const createOperationHistory = (entries: PlcLogEntry[]): DiffEntry[] => {
483
+
const createOperationHistory = (entries: IndexedEntry[]): DiffEntry[] => {
481
484
const history: DiffEntry[] = [];
482
485
483
486
for (let idx = 0, len = entries.length; idx < len; idx++) {
+14
-10
src/views/repository/repo-export.tsx
+14
-10
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 { At } from '@atcute/client/lexicons';
4
+
import { type AtprotoDid, getPdsEndpoint, isAtprotoDid, isHandle } from '@atcute/identity';
5
5
6
6
import { getDidDocument } from '~/api/queries/did-doc';
7
7
import { resolveHandleViaAppView, resolveHandleViaPds } from '~/api/queries/handle';
8
-
import { getPdsEndpoint } from '~/api/types/did-doc';
9
8
import { isServiceUrlString } from '~/api/types/strings';
10
-
import { DID_OR_HANDLE_RE, isDid } from '~/api/utils/strings';
9
+
import { DID_OR_HANDLE_RE } from '~/api/utils/strings';
11
10
12
11
import { useTitle } from '~/lib/navigation/router';
13
12
import { makeAbortable } from '~/lib/utils/abortable';
···
34
33
}) => {
35
34
logger.info(`Starting export for ${identifier}`);
36
35
37
-
let did: At.DID;
38
-
if (isDid(identifier)) {
36
+
let did: AtprotoDid;
37
+
if (isAtprotoDid(identifier)) {
39
38
did = identifier;
40
-
} else if (service) {
41
-
did = await resolveHandleViaPds({ service, handle: identifier, signal });
42
-
logger.log(`Resolved handle to ${did}`);
39
+
} else if (isHandle(identifier)) {
40
+
if (service) {
41
+
did = await resolveHandleViaPds({ service, handle: identifier, signal });
42
+
logger.log(`Resolved handle to ${did}`);
43
+
} else {
44
+
did = await resolveHandleViaAppView({ handle: identifier, signal });
45
+
logger.log(`Resolved handle to ${did}`);
46
+
}
43
47
} else {
44
-
did = await resolveHandleViaAppView({ handle: identifier, signal });
45
-
logger.log(`Resolved handle to ${did}`);
48
+
logger.error(`Invalid identifier`);
49
+
return;
46
50
}
47
51
48
52
if (!service) {