···11+MIT License
22+33+Copyright (c) 2024 Lexicon Community
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+46
packages/lex-agent/README.md
···11+# @happyview/lex-agent
22+33+Adapter that creates an [`@atproto/lex`](https://www.npmjs.com/package/@atproto/lex) `Agent` from a `HappyViewSession`. This lets you use `@atproto/lex`'s type-safe `Client` and `xrpc()` calls with HappyView's DPoP authentication.
44+55+All XRPC requests made through the agent are routed to your HappyView instance. HappyView handles requests for its own lexicons locally and proxies standard AT Protocol methods (e.g., `com.atproto.repo.createRecord`) to the user's PDS.
66+77+## Installation
88+99+```bash
1010+npm install @happyview/lex-agent @atproto/lex
1111+```
1212+1313+`@atproto/lex` is a peer dependency (`>=0.0.20`).
1414+1515+## Usage
1616+1717+```typescript
1818+import { Client } from "@atproto/lex";
1919+import { HappyViewBrowserClient } from "@happyview/oauth-client-browser";
2020+import { createAgent } from "@happyview/lex-agent";
2121+2222+// Authenticate with HappyView
2323+const client = new HappyViewBrowserClient({
2424+ instanceUrl: "https://happyview.example.com",
2525+ clientKey: "hvc_your_client_key",
2626+});
2727+const session = await client.restore();
2828+2929+// Create a Lex agent from the session
3030+const agent = createAgent(session);
3131+const lex = new Client(agent);
3232+3333+// Make type-safe XRPC calls
3434+const game = await lex.xrpc(myLexicons.com.example.getGame, {
3535+ params: { slug: "celeste" },
3636+});
3737+```
3838+3939+## API
4040+4141+### `createAgent(session: HappyViewSession): Agent`
4242+4343+Creates an `@atproto/lex` `Agent` from a `HappyViewSession`. The returned agent:
4444+4545+- Exposes the session's DID via `agent.did`
4646+- Delegates all fetch requests to `session.fetchHandler`, which attaches DPoP authentication headers and prepends the HappyView instance URL
+21
packages/oauth-client-browser/LICENSE.md
···11+MIT License
22+33+Copyright (c) 2024 Lexicon Community
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+89
packages/oauth-client-browser/README.md
···11+# @happyview/oauth-client-browser
22+33+Browser OAuth client for authenticating with a [HappyView](https://github.com/gamesgamesgamesgamesgames/happyview) instance using AT Protocol.
44+55+Built on top of [`@happyview/oauth-client`](https://www.npmjs.com/package/@happyview/oauth-client) with Web Crypto and localStorage adapters included.
66+77+## Installation
88+99+```bash
1010+npm install @happyview/oauth-client-browser
1111+```
1212+1313+## Usage
1414+1515+### Setup
1616+1717+```typescript
1818+import { HappyViewBrowserClient } from "@happyview/oauth-client-browser";
1919+2020+const client = new HappyViewBrowserClient({
2121+ instanceUrl: "https://happyview.example.com",
2222+ clientKey: "hvc_your_client_key",
2323+});
2424+```
2525+2626+### Login
2727+2828+Redirects the user to their PDS authorization server:
2929+3030+```typescript
3131+await client.login("alice.bsky.social");
3232+// User is redirected to their PDS for authorization
3333+```
3434+3535+If you need the authorization URL without an immediate redirect (e.g., to open in a popup), use `prepareLogin`:
3636+3737+```typescript
3838+const { authorizationUrl, did, state } =
3939+ await client.prepareLogin("alice.bsky.social");
4040+```
4141+4242+### OAuth Callback
4343+4444+On the `/oauth/callback` route, call `callback()` to complete the token exchange:
4545+4646+```typescript
4747+const session = await client.callback();
4848+// Session is now stored in localStorage
4949+```
5050+5151+### Restore Session
5252+5353+On subsequent page loads, restore the session from localStorage:
5454+5555+```typescript
5656+const session = await client.restore();
5757+if (session) {
5858+ // User is still logged in
5959+}
6060+```
6161+6262+### Authenticated Requests
6363+6464+The session's `fetchHandler` attaches DPoP proof headers automatically. Pass it a path (relative to the HappyView instance) or a full URL:
6565+6666+```typescript
6767+const response = await session.fetchHandler(
6868+ "/xrpc/com.example.getStuff?limit=10",
6969+ { method: "GET" },
7070+);
7171+```
7272+7373+### Logout
7474+7575+```typescript
7676+await client.logout("did:plc:abc123");
7777+```
7878+7979+## Exports
8080+8181+This package re-exports everything from `@happyview/oauth-client`, plus:
8282+8383+- `HappyViewBrowserClient` -- the main browser client
8484+- `LocalStorageAdapter` -- `StorageAdapter` backed by `window.localStorage`
8585+- `WebCryptoAdapter` -- `CryptoAdapter` backed by the Web Crypto API
8686+- `resolveHandleToDid` -- resolve an AT Protocol handle to a DID
8787+- `resolveDidDocument` -- fetch a DID document
8888+- `resolvePdsUrl` -- extract the PDS URL from a DID document
8989+- `resolveAuthServerMetadata` -- fetch OAuth authorization server metadata from a PDS
+21
packages/oauth-client/LICENSE.md
···11+MIT License
22+33+Copyright (c) 2024 Lexicon Community
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+111
packages/oauth-client/README.md
···11+# @happyview/oauth-client
22+33+Core OAuth client for authenticating with a [HappyView](https://github.com/gamesgamesgamesgamesgames/happyview) instance.
44+55+This is a platform-agnostic package. If you're building a browser app, use [`@happyview/oauth-client-browser`](https://www.npmjs.com/package/@happyview/oauth-client-browser) instead. It wraps this package with Web Crypto, localStorage, and a complete OAuth redirect flow.
66+77+## Installation
88+99+```bash
1010+npm install @happyview/oauth-client
1111+```
1212+1313+## Usage
1414+1515+`HappyViewOAuthClient` manages DPoP key provisioning, session registration, and session restoration lifecycle. You provide a `CryptoAdapter` and optional `StorageAdapter` for your platform.
1616+1717+```typescript
1818+import { HappyViewOAuthClient } from "@happyview/oauth-client";
1919+2020+const client = new HappyViewOAuthClient({
2121+ instanceUrl: "https://happyview.example.com",
2222+ clientKey: "hvc_your_client_key",
2323+ clientSecret: "hvs_your_secret", // optional, for confidential clients (server-to-server)
2424+ crypto: myCryptoAdapter,
2525+ storage: myStorageAdapter, // optional, defaults to in-memory
2626+});
2727+```
2828+2929+### DPoP Key Provisioning
3030+3131+Request a DPoP keypair from the HappyView instance:
3232+3333+```typescript
3434+const { provisionId, dpopKey, pkceVerifier } = await client.provisionDpopKey();
3535+```
3636+3737+### Session Registration
3838+3939+After completing OAuth authorization with the user's PDS, register the session with HappyView:
4040+4141+```typescript
4242+const session = await client.registerSession({
4343+ provisionId,
4444+ pkceVerifier,
4545+ did: "did:plc:abc123",
4646+ accessToken: tokens.access_token,
4747+ refreshToken: tokens.refresh_token,
4848+ scopes: "atproto",
4949+ pdsUrl: "https://pds.example.com",
5050+ issuer: tokens.iss,
5151+ dpopKey,
5252+});
5353+```
5454+5555+### Making Authenticated Requests
5656+5757+The returned `HappyViewSession` provides a `fetchHandler` that automatically attaches DPoP proof headers:
5858+5959+```typescript
6060+const response = await session.fetchHandler("/xrpc/com.example.getStuff", {
6161+ method: "GET",
6262+});
6363+```
6464+6565+### Session Restoration
6666+6767+Restore a previously stored session:
6868+6969+```typescript
7070+// Restore the last active session
7171+const session = await client.restore();
7272+7373+// Or restore a specific user's session
7474+const session = await client.restoreSession("did:plc:abc123");
7575+```
7676+7777+### Logout
7878+7979+```typescript
8080+await client.deleteSession("did:plc:abc123");
8181+```
8282+8383+## Adapters
8484+8585+### CryptoAdapter
8686+8787+Implement this interface for your platform's cryptographic primitives:
8888+8989+```typescript
9090+interface CryptoAdapter {
9191+ generatePkceVerifier(): Promise<string>;
9292+ computePkceChallenge(verifier: string): Promise<string>;
9393+ signEs256(privateKey: JsonWebKey, payload: Uint8Array): Promise<Uint8Array>;
9494+ sha256(data: Uint8Array): Promise<Uint8Array>;
9595+ getRandomValues(length: number): Uint8Array;
9696+}
9797+```
9898+9999+### StorageAdapter
100100+101101+Implement this interface to persist sessions across restarts:
102102+103103+```typescript
104104+interface StorageAdapter {
105105+ get(key: string): Promise<string | null>;
106106+ set(key: string, value: string): Promise<void>;
107107+ delete(key: string): Promise<void>;
108108+}
109109+```
110110+111111+If no `StorageAdapter` is provided, sessions are stored in memory and will not survive page reloads or process restarts.