···11+import { configureOAuth } from "@atcute/oauth-browser-client";
22+33+import {
44+ CompositeDidDocumentResolver,
55+ LocalActorResolver,
66+ PlcDidDocumentResolver,
77+ WebDidDocumentResolver,
88+ XrpcHandleResolver,
99+} from "@atcute/identity-resolver";
1010+1111+import {
1212+ createAuthorizationUrl,
1313+ deleteStoredSession,
1414+ finalizeAuthorization,
1515+ getSession,
1616+ OAuthUserAgent,
1717+} from "@atcute/oauth-browser-client";
1818+1919+export { OAuthUserAgent };
2020+2121+/**
2222+ * @import {Session} from "@atcute/oauth-browser-client"
2323+ */
2424+2525+const STORAGE_KEY = "dor-atproto:did";
2626+2727+// CONFIGURE
2828+// =========
2929+3030+configureOAuth({
3131+ metadata: {
3232+ client_id: import.meta.env?.VITE_ATPROTO_CLIENT_ID ??
3333+ "https://elements.diffuse.sh/oauth-client-metadata.json",
3434+ redirect_uri:
3535+ window.location.origin.replace("://localhost", "://127.0.0.1") +
3636+ window.location.pathname,
3737+ },
3838+ identityResolver: new LocalActorResolver({
3939+ handleResolver: new XrpcHandleResolver({
4040+ serviceUrl: "https://public.api.bsky.app",
4141+ }),
4242+ didDocumentResolver: new CompositeDidDocumentResolver({
4343+ methods: {
4444+ plc: new PlcDidDocumentResolver(),
4545+ web: new WebDidDocumentResolver(),
4646+ },
4747+ }),
4848+ }),
4949+});
5050+5151+// LOGIN
5252+// =====
5353+5454+/**
5555+ * Initiate the OAuth authorization flow for a given handle.
5656+ * Navigates the browser away to the authorization server.
5757+ *
5858+ * @param {string} handle
5959+ */
6060+export async function login(handle) {
6161+ const authUrl = await createAuthorizationUrl({
6262+ target: { type: "account", identifier: /** @type {any} */ (handle) },
6363+ scope: "atproto transition:generic",
6464+ });
6565+6666+ window.location.assign(authUrl.toString());
6767+}
6868+6969+// SESSION RESTORE / CALLBACK
7070+// ==========================
7171+7272+/**
7373+ * Attempt to restore an existing session or finalize an OAuth callback.
7474+ * Returns the session if successful, or null if no session is available.
7575+ *
7676+ * @returns {Promise<Session | null>}
7777+ */
7878+export async function restoreOrFinalize() {
7979+ // Check for OAuth callback parameters (the library uses response_mode=fragment,
8080+ // so params arrive in the URL hash, not the query string)
8181+ const params = new URLSearchParams(window.location.hash.slice(1));
8282+8383+ if (params.has("code")) {
8484+ const result = await finalizeAuthorization(params);
8585+8686+ // Clean up URL (remove fragment containing OAuth params)
8787+ history.replaceState(
8888+ null,
8989+ "",
9090+ window.location.pathname + window.location.search,
9191+ );
9292+9393+ // Persist the DID for future session restoration
9494+ localStorage.setItem(STORAGE_KEY, result.session.info.sub);
9595+9696+ return result.session;
9797+ }
9898+9999+ // Try to restore a previously stored session
100100+ const did = localStorage.getItem(STORAGE_KEY);
101101+102102+ if (did) {
103103+ try {
104104+ return await getSession(
105105+ /** @type {`did:${string}:${string}`} */ (did),
106106+ { allowStale: true },
107107+ );
108108+ } catch {
109109+ localStorage.removeItem(STORAGE_KEY);
110110+ return null;
111111+ }
112112+ }
113113+114114+ return null;
115115+}
116116+117117+// LOGOUT
118118+// ======
119119+120120+/**
121121+ * Sign out and revoke the current session.
122122+ *
123123+ * @param {OAuthUserAgent} agent
124124+ */
125125+export async function logout(agent) {
126126+ const did = localStorage.getItem(STORAGE_KEY);
127127+128128+ try {
129129+ await agent.signOut();
130130+ } catch {
131131+ if (did) {
132132+ deleteStoredSession(
133133+ /** @type {`did:${string}:${string}`} */ (did),
134134+ );
135135+ }
136136+ }
137137+138138+ localStorage.removeItem(STORAGE_KEY);
139139+}
+10
src/components/output/raw/atproto/types.d.ts
···11+import type { Signal } from "@common/signal.d.ts";
22+import type { OutputElement } from "../../types.d.ts";
33+44+export type ATProtoOutputElement =
55+ & OutputElement
66+ & {
77+ $did: Signal<string | null>;
88+ login(handle: string): Promise<void>;
99+ logout(): Promise<void>;
1010+ };
+2-2
src/index.vto
···9696 - url: "components/output/polymorphic/indexed-db/element.js"
9797 title: "Polymorphic / IndexedDB"
9898 desc: "Stores output into the local indexedDB. Supports any type of data that indexedDB supports."
9999- - title: "Raw / AT Protocol"
9999+ - url: "components/output/raw/atproto/element.js"
100100+ title: "Raw / AT Protocol"
100101 desc: >
101102 Store your user data on the storage associated with your ATProtocol identity. Data is lexicon shaped by default so this element takes in that data directly without any transformations.
102102- todo: true
103103104104processors:
105105 - url: "components/processor/artwork/element.js"