Handling PDS, darkmode and pretending interactions
My second #notai generated post is about another day of developing the kiesel app. I figured that atproto is an awesome source of subscribable data, thus it was supported from the beginning. Only using app passwords in first place, since I didn't know the app name, yet. This day went on PDS resolution, dark mode and making likes feel fast.
Handle/PDS Resolution
Since I am hosting my account on eurosky.social, it is not bsky.social as storage. And my initial approach did not work. The resolution needs 3 steps: resolveHandle -> did -> PDS service endpoint. The handle (in my case dracoblue.de) is just the alias. The identifier can be a did:web which stores a .well-known did.json - otherwise, it can talk to the plc.directory to fetch details. The fallback is to resolve the whole handle via bsky.social.
My resolver logic looks like this now:
export async function resolveHandleToPds(identifier: string): Promise<string> {
const DEFAULT_PDS = "https://bsky.social";
if (identifier.startsWith("did:")) {
try {
const res = await fetch(
identifier.startsWith("did:web:")
? `https://${identifier.slice(8)}/.well-known/did.json`
: `https://plc.directory/${identifier}`
);
const doc = await res.json();
const pdsService = doc.service?.find(
(s: { id: string; serviceEndpoint: string }) =>
s.id === "#atproto_pds"
);
if (pdsService?.serviceEndpoint) return pdsService.serviceEndpoint;
} catch {
// fall through to default
}
return DEFAULT_PDS;
}
// It's a handle – resolve to DID first, then find PDS
try {
const resolveAgent = createAgent(DEFAULT_PDS);
const resolved = await resolveAgent.resolveHandle({ handle: identifier });
return resolveHandleToPds(resolved.data.did);
} catch {
return DEFAULT_PDS;
}
}Maybe too much recursion? But it works decent.
Pretending Likes
It's quite common, to show like counts next to posts. One challenge is to show instant feedback to the user when she presses the button - do you wait for the backend to answer?
My implementation remembers the like count on the object (and marks it as liked) and then does a simple:
setLiked(true);
setLikeCount((c) => c + 1);
try {
const result = await adapter.like(uri, cid);
setLikeUri(result);
} catch {
setLiked(false);
setLikeCount((c) => c - 1);
}to pretend that it was liked before actually writing the value to the adapter. Makes the app feel snappy and might fail: but at least feels fast. The rollback will most probably never fire: but if it does, it works, too.
Darkmode
The initial version did not support darkmode - but you don't want to add it too late. Changing 10 components at the beginning is much easier than 100 at the end.
So I defined a set of lightColors and darkColors and made a useColors() which can be used anywhere to figure what colors we have. It also handles the case when we don't know if the user activated light or dark mode. The result is something like this:
{
background: "#ffffff",
surface: "#ffffff",
text: "#000000",
textSecondary: "#333333",
// … 13 additional ones
}that's fun. I am thinking about adding the possibility to choose your own color scheme in the future. With this it will be easy.
See you in the next dev day post!