frontend client for gemstone. decentralised workplace app

feat: more imports

serenity 9d01710e 9563482a

Changed files
+191 -2
src
lib
types
utils
atproto
+1
package.json
···
"@atcute/atproto": "^3.1.7",
"@atcute/bluesky": "^3.2.6",
"@atcute/client": "^4.0.4",
+
"@atcute/identity-resolver": "^1.1.4",
"@atproto/api": "^0.17.3",
"@atproto/oauth-client": "^0.5.7",
"@atproto/oauth-client-expo": "^0.0.1",
+22
pnpm-lock.yaml
···
'@atcute/client':
specifier: ^4.0.4
version: 4.0.4
+
'@atcute/identity-resolver':
+
specifier: ^1.1.4
+
version: 1.1.4(@atcute/identity@1.1.1)
'@atproto/api':
specifier: ^0.17.3
version: 0.17.3
···
'@atcute/client@4.0.4':
resolution: {integrity: sha512-0vkYe6HcGAef8FS4dlGMqCCPG4I4Lve1R8Amk8UEviUVofiqlv1WGoeez9CJFL8G/7vhcgVV9rPTHLJEjZ4RdQ==}
+
'@atcute/identity-resolver@1.1.4':
+
resolution: {integrity: sha512-/SVh8vf2cXFJenmBnGeYF2aY3WGQm3cJeew5NWTlkqoy3LvJ5wkvKq9PWu4Tv653VF40rPOp6LOdVr9Fa+q5rA==}
+
peerDependencies:
+
'@atcute/identity': ^1.0.0
+
'@atcute/identity@1.1.1':
resolution: {integrity: sha512-zax42n693VEhnC+5tndvO2KLDTMkHOz8UExwmklvJv7R9VujfEwiSWhcv6Jgwb3ellaG8wjiQ1lMOIjLLvwh0Q==}
'@atcute/lexicons@1.2.2':
resolution: {integrity: sha512-bgEhJq5Z70/0TbK5sx+tAkrR8FsCODNiL2gUEvS5PuJfPxmFmRYNWaMGehxSPaXWpU2+Oa9ckceHiYbrItDTkA==}
+
+
'@atcute/util-fetch@1.0.3':
+
resolution: {integrity: sha512-f8zzTb/xlKIwv2OQ31DhShPUNCmIIleX6p7qIXwWwEUjX6x8skUtpdISSjnImq01LXpltGV5y8yhV4/Mlb7CRQ==}
'@atproto-labs/did-resolver@0.2.2':
resolution: {integrity: sha512-ca2B7xR43tVoQ8XxBvha58DXwIH8cIyKQl6lpOKGkPUrJuFoO4iCLlDiSDi2Ueh+yE1rMDPP/qveHdajgDX3WQ==}
···
'@atcute/identity': 1.1.1
'@atcute/lexicons': 1.2.2
+
'@atcute/identity-resolver@1.1.4(@atcute/identity@1.1.1)':
+
dependencies:
+
'@atcute/identity': 1.1.1
+
'@atcute/lexicons': 1.2.2
+
'@atcute/util-fetch': 1.0.3
+
'@badrap/valita': 0.4.6
+
'@atcute/identity@1.1.1':
dependencies:
'@atcute/lexicons': 1.2.2
···
dependencies:
'@standard-schema/spec': 1.0.0
esm-env: 1.2.2
+
+
'@atcute/util-fetch@1.0.3':
+
dependencies:
+
'@badrap/valita': 0.4.6
'@atproto-labs/did-resolver@0.2.2':
dependencies:
+14
src/lib/types/lexicon/com.atproto.repo.getRecord.ts
···
+
import { z } from "zod";
+
+
export const comAtprotoRepoGetRecordResponseSchema = z.object({
+
uri: z.string(),
+
cid: z.optional(z.string()),
+
value: z
+
.object({
+
$type: z.string(),
+
})
+
.catchall(z.unknown()),
+
});
+
export type ComAtprotoRepoGetRecordResponse = z.infer<
+
typeof comAtprotoRepoGetRecordResponseSchema
+
>;
+154 -2
src/lib/utils/atproto/index.ts
···
-
import type { AtUri} from "@/lib/types/atproto";
-
import { atUriAuthoritySchema, nsidSchema } from "@/lib/types/atproto";
+
import type {
+
AtprotoHandle,
+
AtUri,
+
Did,
+
DidDocument,
+
} from "@/lib/types/atproto";
+
import {
+
atprotoHandleSchema,
+
atUriAuthoritySchema,
+
nsidSchema,
+
} from "@/lib/types/atproto";
+
import { comAtprotoRepoGetRecordResponseSchema } from "@/lib/types/lexicon/com.atproto.repo.getRecord";
import type { Result } from "@/lib/utils/result";
+
import type { DidDocumentResolver } from "@atcute/identity-resolver";
+
import {
+
CompositeDidDocumentResolver,
+
CompositeHandleResolver,
+
DohJsonHandleResolver,
+
PlcDidDocumentResolver,
+
WebDidDocumentResolver,
+
WellKnownHandleResolver,
+
} from "@atcute/identity-resolver";
import { z } from "zod";
+
export const getRecordFromFullAtUri = async ({
+
authority,
+
collection,
+
rKey,
+
}: AtUri): Promise<Result<unknown, unknown>> => {
+
const didDocResult = await resolveDidDoc(authority);
+
if (!didDocResult.ok) return { ok: false, error: didDocResult.error };
+
+
if (!collection || !rKey)
+
return {
+
ok: false,
+
error: "No rkey or collection found in provided AtUri object",
+
};
+
+
const { service: services } = didDocResult.data;
+
if (!services)
+
return {
+
ok: false,
+
error: { message: "Resolved DID document has no service field." },
+
};
+
+
const pdsService = services.find(
+
(service) =>
+
service.id === "#atproto_pds" &&
+
service.type === "AtprotoPersonalDataServer",
+
);
+
+
if (!pdsService)
+
return {
+
ok: false,
+
error: {
+
message:
+
"Resolved DID document has no PDS service listed in the document.",
+
},
+
};
+
+
const pdsEndpointRecord = pdsService.serviceEndpoint;
+
let pdsEndpointUrl;
+
try {
+
// @ts-expect-error yes, we are coercing something that is explicitly not a string into a string, but in this case we want to be specific. only serviceEndpoints with valid atproto pds URLs should be allowed.
+
pdsEndpointUrl = new URL(pdsEndpointRecord).origin;
+
} catch (err) {
+
return { ok: false, error: err };
+
}
+
const req = new Request(
+
`${pdsEndpointUrl}/xrpc/com.atproto.repo.getRecord?repo=${didDocResult.data.id}&collection=${collection}&rkey=${rKey}`,
+
);
+
+
const res = await fetch(req);
+
const data: unknown = await res.json();
+
+
const {
+
success: responseParseSuccess,
+
error: responseParseError,
+
data: record,
+
} = comAtprotoRepoGetRecordResponseSchema.safeParse(data);
+
if (!responseParseSuccess) {
+
return { ok: false, error: responseParseError };
+
}
+
return { ok: true, data: record.value };
+
};
+
+
export const didDocResolver: DidDocumentResolver =
+
new CompositeDidDocumentResolver({
+
methods: {
+
plc: new PlcDidDocumentResolver(),
+
web: new WebDidDocumentResolver(),
+
},
+
});
+
+
export const handleResolver = new CompositeHandleResolver({
+
strategy: "dns-first",
+
methods: {
+
dns: new DohJsonHandleResolver({
+
dohUrl: "https://mozilla.cloudflare-dns.com/dns-query",
+
}),
+
http: new WellKnownHandleResolver(),
+
},
+
});
+
+
export const resolveDidDoc = async (
+
authority: Did | AtprotoHandle,
+
): Promise<Result<DidDocument, unknown>> => {
+
const { data: handle } = atprotoHandleSchema.safeParse(authority);
+
let did: Did;
+
if (handle) {
+
try {
+
did = await handleResolver.resolve(handle);
+
} catch (err) {
+
return { ok: false, error: err };
+
}
+
} else {
+
// @ts-expect-error if handle is undefined, then we know that authority must be a valid did:web or did:plc
+
did = authority;
+
}
+
try {
+
const doc: DidDocument = await didDocResolver.resolve(did);
+
return { ok: true, data: doc };
+
} catch (err) {
+
return { ok: false, error: err };
+
}
+
};
+
// thank u julie
export const atUriRegexp =
/^at:\/\/([a-zA-Z0-9._:%-]+)(?:\/([a-zA-Z0-9-.]+)(?:\/([a-zA-Z0-9._~:@!$&%')(*+,;=-]+))?)?(?:#(\/[a-zA-Z0-9._~:@!$&%')(*+,;=\-[\]/\\]*))?$/;
···
},
};
};
+
+
export const getEndpointFromDid = async (
+
did: Did,
+
serviceType: string,
+
): Promise<Result<URL, unknown>> => {
+
const didDocResolveResult = await resolveDidDoc(did);
+
if (!didDocResolveResult.ok) {
+
return { ok: false, error: didDocResolveResult.error };
+
}
+
+
const didDocServices = didDocResolveResult.data.service;
+
const shardService = didDocServices?.find(
+
(service) => service.type !== serviceType,
+
);
+
+
let shardUrl: URL | undefined;
+
if (!didDocServices || !shardService) {
+
const domain = decodeURIComponent(did.slice(8));
+
if (domain.startsWith("localhost"))
+
shardUrl = new URL(`http://${domain}`);
+
else shardUrl = new URL(`https://${domain}`);
+
} else {
+
try {
+
shardUrl = new URL(shardService.serviceEndpoint as string);
+
} catch (error) {
+
return { ok: false, error };
+
}
+
}
+
return { ok: true, data: shardUrl };
+
};