lens-convex-auth 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,7 @@ creation, and **Convex** auth, wired to one session.
7
7
  - An account dialog that lists the wallet's Lens accounts, or creates one (username, name, bio, picture)
8
8
  - Works on mobile wallets: challenges are fetched ahead of time, so the tap that signs can open the wallet app
9
9
  - Lens tokens are kept fresh in the background and shared by the Lens SDK, Convex, and your API routes
10
+ - Switching accounts and logging out revoke the Lens session, so tokens copied from the browser stop working
10
11
  - Convex functions know which Lens account is calling (`requireLensAccount(ctx)`)
11
12
 
12
13
  ```
@@ -161,7 +162,8 @@ export function Navbar() {
161
162
  ```
162
163
 
163
164
  Connecting a wallet opens the account dialog, which stays open until the user signs in to an account
164
- (or disconnects). The button then becomes `Logout`.
165
+ (or disconnects). The button then becomes `Logout`. Switching to another account from the dialog
166
+ revokes the previous account's session (see [Security](#security)).
165
167
 
166
168
  In Convex functions:
167
169
 
@@ -228,6 +230,26 @@ export const ensureCurrentUser = mutation({
228
230
  })
229
231
  ```
230
232
 
233
+ ### Delete your app's data when the account changes
234
+
235
+ If your app keeps anything in the browser for the signed-in account (drafts, caches, keys in IndexedDB),
236
+ delete it in `onAccountChange`. It runs when the signed-in account changes, however that happens:
237
+ signing in, switching accounts, logging out, or the session ending (it expired, or another tab logged
238
+ out). It doesn't run when a stored session is restored on load.
239
+
240
+ ```tsx
241
+ <LensAuthProvider
242
+ auth={auth}
243
+ convex={convex}
244
+ onAccountChange={async (previous, next) => {
245
+ if (previous) await deleteLocalData(previous.address)
246
+ }}
247
+ >
248
+ ```
249
+
250
+ It runs after the change, so it can't call Convex as `previous`. To do something as the account before it
251
+ logs out, do it before calling `signOut()`.
252
+
231
253
  ### Protect your own API routes
232
254
 
233
255
  On the client, `auth.fetch` sends the Lens ID token and retries once with a refreshed token on 401:
@@ -261,7 +283,33 @@ if (session) await fetchMutation(api.users.ensureCurrentUser, {}, { token: await
261
283
 
262
284
  ### Your own dialog placement or UI
263
285
 
264
- Pass `accountDialog={false}` and render `<AccountDialog />` where you want it. `CreateAccountForm` is
286
+ Pass `accountDialog={false}` and render `<AccountDialog />` where you want it.
287
+
288
+ To match your app's own dialogs, render `<AccountPicker />` (the account list, or the form to create
289
+ one) inside them instead. `useAccountDialog()` has the dialog's state and its title:
290
+
291
+ ```tsx
292
+ import { AccountPicker, useAccountDialog, useLensAuth } from "lens-convex-auth/react"
293
+
294
+ function MyAccountDialog() {
295
+ const { signOut } = useLensAuth()
296
+ const { open, required, setOpen, title, description } = useAccountDialog()
297
+ return (
298
+ <MyDialog open={open} onOpenChange={setOpen} dismissible={!required} title={title} description={description}>
299
+ <AccountPicker
300
+ actions={
301
+ <MyButton onClick={signOut}>{required ? "Disconnect Wallet" : "Log Out"}</MyButton>
302
+ }
303
+ />
304
+ </MyDialog>
305
+ )
306
+ }
307
+ ```
308
+
309
+ `required` is true while a wallet is connected without a Lens account signed in: the dialog shouldn't
310
+ close then (`setOpen(false)` is ignored), so give it a way out, like the button above. Buttons passed
311
+ as `actions` go in the picker's button group, under "Create a new account" or the create form's
312
+ buttons. `AccountPicker` has no horizontal padding, so give it your dialog's. `CreateAccountForm` is
265
313
  exported too, and the lower-level building blocks (`auth.requestChallenge`, `auth.authenticate`,
266
314
  `createLensAccount`, `uploadAccountMetadata`, …) are in the main entry.
267
315
 
@@ -284,7 +332,13 @@ Omit the `convex` prop. Everything else works the same.
284
332
  | `usernameNamespace` | global Lens namespace | Namespace (0x…) new accounts get their username in. The account list shows usernames from it first, then the global one, then any other |
285
333
 
286
334
  Returns `auth` with `thirdwebClient`, `chain`, `lensClient`, `tokens`, `getSession()`,
287
- `resumeSession()`, `requestChallenge()`, `authenticate()`, `logout()`, `fetch()` and `fetchConvexToken()`.
335
+ `resumeSession()`, `requestChallenge()`, `authenticate()`, `startSession()`, `logout()`, `fetch()` and
336
+ `fetchConvexToken()`.
337
+
338
+ - `authenticate(challenge, signature)` and `startSession(credentials)` make a session the app's, and revoke
339
+ the session they replace. `startSession` resolves `false` when that revocation failed.
340
+ - `logout()` clears the session from the browser, then revokes it. Resolves `{ revoked }`, false when the
341
+ Lens API couldn't be reached or refused.
288
342
 
289
343
  ### `<LensAuthProvider>` (`lens-convex-auth/react`)
290
344
 
@@ -293,6 +347,7 @@ Returns `auth` with `thirdwebClient`, `chain`, `lensClient`, `tokens`, `getSessi
293
347
  | `auth` | From `createLensAuth` |
294
348
  | `convex` | Your `ConvexReactClient` (optional) |
295
349
  | `onSignIn(account)` | Called after a sign-in from the dialog, once Convex accepts it |
350
+ | `onAccountChange(previous, next)` | Called when the signed-in account changes: sign-in, switch, log out, or the session ending. See the recipe above |
296
351
  | `accountDialog` | Render the account dialog (default `true`) |
297
352
  | `wallets` | Wallets to reconnect after a reload while a Lens session is stored (default: thirdweb's). Pass the same list as `SignInButton`'s `connectButtonProps.wallets` if you customize it |
298
353
 
@@ -300,6 +355,9 @@ Returns `auth` with `thirdwebClient`, `chain`, `lensClient`, `tokens`, `getSessi
300
355
 
301
356
  `{ status, isLoading, isSignedIn, wallet, account, signOut, openAccountDialog, auth }`
302
357
 
358
+ `signOut()` disconnects the wallet and logs out, revoking the session. It resolves `{ revoked }`, false
359
+ when revoking failed (the session is cleared from the browser either way), so you can tell the user.
360
+
303
361
  `status` is `"loading"` (first render and SSR), `"signed-out"`, `"choosing-account"` (wallet connected,
304
362
  no Lens account yet) or `"signed-in"`. `account` is `{ address, username }`.
305
363
 
@@ -337,6 +395,29 @@ Options: `lensAppAddress` (required), `environment`, and for Convex tokens `priv
337
395
 
338
396
  - `npx lens-convex-auth keys [--kid <id>]`: generates a key pair for Convex tokens
339
397
 
398
+ ## Security
399
+
400
+ What the browser holds while someone is signed in, and what ends it:
401
+
402
+ | Stored | Where | What it allows | Ended by |
403
+ | --- | --- | --- | --- |
404
+ | Lens access, ID and refresh tokens | `localStorage` (`lens_access_token`, `lens_id_token`, `lens_refresh_token`) | Acting as the Lens account through the Lens API, and getting Convex tokens from your token route | Switching accounts or logging out: the session is revoked on the Lens API |
405
+ | Selected account | `localStorage` (`lens_account`) | Nothing: its address and username | Logging out |
406
+ | Convex token | Memory | Calling Convex as the account | Expires with the Lens ID token |
407
+ | thirdweb wallet session | thirdweb's own storage | Email, social and passkey wallets: signing as the wallet, which owns its Lens accounts. Extension and mobile wallets keep their keys to themselves | Logging out disconnects the wallet. thirdweb clears its session from the browser; it doesn't revoke it on its servers |
408
+
409
+ Revoking stops the refresh token right away. An access or ID token already issued stays valid until it
410
+ expires, 10 minutes at most, because your server and Convex verify tokens by signature without asking
411
+ Lens. A copied session is cut off within those 10 minutes.
412
+
413
+ `localStorage` is readable by any script running on your pages, so a cross-site scripting bug, or a
414
+ script you load from a compromised source, can copy these tokens. Revocation limits how long a copy
415
+ works; it doesn't prevent the copy. Keep scripts off your pages with a Content Security Policy, and load
416
+ third-party scripts only from sources you trust.
417
+
418
+ If revoking fails (offline, or the Lens API is down), the session is still cleared from the browser,
419
+ `signOut()` resolves `{ revoked: false }`, and the refresh token works until Lens expires it.
420
+
340
421
  ## Troubleshooting
341
422
 
342
423
  **Turbopack fails to resolve `@x402/*` packages.** Recent `@coinbase/cdp-sdk` versions (a
@@ -37,7 +37,10 @@ declare class TokenService {
37
37
  getFreshIdToken(forceRefresh?: boolean): Promise<string | null>;
38
38
  isAuthenticated(): Promise<boolean>;
39
39
  refreshTokens(): Promise<TokenData | null>;
40
+ revokeSession(tokens: LensCredentials): Promise<boolean>;
40
41
  private performRefresh;
42
+ private requestRefresh;
43
+ private graphql;
41
44
  }
42
45
 
43
46
  type LensAuthOptions = {
@@ -100,7 +103,10 @@ declare function createLensAuth(options: LensAuthOptions): {
100
103
  }) => Promise<LensChallenge>;
101
104
  signChallenge: typeof signLensChallenge;
102
105
  authenticate: (challenge: LensChallenge, signature: string) => Promise<SessionClient>;
103
- logout: () => Promise<void>;
106
+ startSession: (credentials: LensCredentials) => Promise<boolean>;
107
+ logout: () => Promise<{
108
+ revoked: boolean;
109
+ }>;
104
110
  fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
105
111
  fetchConvexToken: (forceRefresh?: boolean) => Promise<string | null>;
106
112
  };
@@ -118,6 +118,11 @@ var REFRESH_MUTATION = `
118
118
  }
119
119
  }
120
120
  `;
121
+ var REVOKE_MUTATION = `
122
+ mutation RevokeAuthentication($request: RevokeAuthenticationRequest!) {
123
+ revokeAuthentication(request: $request)
124
+ }
125
+ `;
121
126
  function notifyTokensChanged() {
122
127
  try {
123
128
  window.dispatchEvent(new Event(TOKENS_CHANGED_EVENT));
@@ -202,6 +207,27 @@ var TokenService = class {
202
207
  });
203
208
  return this.refreshPromise;
204
209
  }
210
+ // Ends a session on the Lens API, so its tokens stop working even if they were copied out of this
211
+ // browser. Takes the session's tokens rather than the stored ones, because by the time a session is
212
+ // revoked the storage already holds the next one (or nothing). Its refresh token stops working right
213
+ // away; an access or ID token already issued stays valid until it expires (10 minutes at most).
214
+ // Resolves false when the API couldn't be reached or refused.
215
+ async revokeSession(tokens) {
216
+ const authenticationId = decodeJwtPayload(tokens.idToken)?.sid;
217
+ if (typeof authenticationId !== "string") return false;
218
+ try {
219
+ let accessToken = tokens.accessToken;
220
+ if ((jwtExpiresAt(accessToken) ?? 0) <= Date.now() + REFRESH_BUFFER_MS) {
221
+ const refreshed = await this.requestRefresh(tokens.refreshToken);
222
+ if (!refreshed) return true;
223
+ accessToken = refreshed.accessToken;
224
+ }
225
+ const data = await this.graphql(REVOKE_MUTATION, { request: { authenticationId } }, accessToken);
226
+ return !data.errors;
227
+ } catch {
228
+ return false;
229
+ }
230
+ }
205
231
  async performRefresh() {
206
232
  const refreshToken = this.getStoredTokens()?.refreshToken;
207
233
  if (!refreshToken) {
@@ -209,27 +235,31 @@ var TokenService = class {
209
235
  return null;
210
236
  }
211
237
  try {
212
- const response = await fetch(this.options.graphqlUrl, {
213
- method: "POST",
214
- headers: { "Content-Type": "application/json" },
215
- body: JSON.stringify({ query: REFRESH_MUTATION, variables: { request: { refreshToken } } })
216
- });
217
- const data = await response.json();
218
- const result = data?.data?.refresh;
219
- if (data.errors || !result || result.reason) throw new Error(result?.reason ?? "Refresh failed");
220
- const tokens = {
221
- accessToken: result.accessToken,
222
- idToken: result.idToken,
223
- refreshToken: result.refreshToken,
224
- expiresAt: Date.now() + LENS_TOKEN_LIFETIME_MS
225
- };
238
+ const refreshed = await this.requestRefresh(refreshToken);
239
+ if (this.getStoredTokens()?.refreshToken !== refreshToken) return this.getStoredTokens();
240
+ if (!refreshed) throw new Error("Refresh refused");
241
+ const tokens = { ...refreshed, expiresAt: Date.now() + LENS_TOKEN_LIFETIME_MS };
226
242
  this.storeTokens(tokens);
227
243
  return tokens;
228
244
  } catch {
229
- this.clearTokens();
245
+ if (this.getStoredTokens()?.refreshToken === refreshToken) this.clearTokens();
230
246
  return null;
231
247
  }
232
248
  }
249
+ // New tokens for a session, from its refresh token. Doesn't store them. Null when the API refuses (the
250
+ // session has ended); throws when it can't be reached.
251
+ async requestRefresh(refreshToken) {
252
+ const data = await this.graphql(REFRESH_MUTATION, { request: { refreshToken } });
253
+ const result = data?.data?.refresh;
254
+ if (data?.errors || !result?.accessToken || !result.idToken || !result.refreshToken) return null;
255
+ return { accessToken: result.accessToken, idToken: result.idToken, refreshToken: result.refreshToken };
256
+ }
257
+ async graphql(query, variables, accessToken) {
258
+ const headers = { "Content-Type": "application/json" };
259
+ if (accessToken) headers.Authorization = `Bearer ${accessToken}`;
260
+ const response = await fetch(this.options.graphqlUrl, { method: "POST", headers, body: JSON.stringify({ query, variables }) });
261
+ return response.json();
262
+ }
233
263
  };
234
264
 
235
265
  // src/core/wallet.ts
@@ -319,6 +349,7 @@ function createLensAuth(options) {
319
349
  environment: lensEnvironment,
320
350
  storage: typeof window !== "undefined" ? new LensTokenStorage(tokens, `${storagePrefix}custom_`) : void 0
321
351
  });
352
+ const sessionIdOf = (idToken) => decodeJwtPayload(idToken)?.sid;
322
353
  function getSession() {
323
354
  const idToken = tokens.getStoredTokens()?.idToken;
324
355
  return idToken ? lensSessionFromIdToken(idToken) : null;
@@ -339,15 +370,30 @@ function createLensAuth(options) {
339
370
  return challenge.value;
340
371
  }
341
372
  async function authenticate(challenge, signature) {
373
+ const previous = tokens.getStoredTokens();
342
374
  const authenticated = await lensClient.authenticate({ id: challenge.id, signature: signatureFrom(signature) });
343
375
  if (authenticated.isErr()) throw authenticated.error;
344
- tokens.storeCredentials(sessionCredentials(authenticated.value));
376
+ const credentials = sessionCredentials(authenticated.value);
377
+ tokens.storeCredentials(credentials);
378
+ await revokeReplacedSession(previous, credentials);
345
379
  return authenticated.value;
346
380
  }
381
+ async function startSession(credentials) {
382
+ const previous = tokens.getStoredTokens();
383
+ tokens.storeCredentials(credentials);
384
+ return revokeReplacedSession(previous, credentials);
385
+ }
386
+ async function revokeReplacedSession(previous, next) {
387
+ if (!previous || sessionIdOf(previous.idToken) === sessionIdOf(next.idToken)) return true;
388
+ const revoked = await tokens.revokeSession(previous);
389
+ if (!revoked) console.warn("lens-convex-auth: couldn't revoke the previous Lens session");
390
+ return revoked;
391
+ }
347
392
  async function logout() {
393
+ const previous = tokens.getStoredTokens();
348
394
  tokens.clearTokens();
349
- const session = lensClient.currentSession;
350
- if (session && session.isSessionClient()) await session.logout();
395
+ lensClient.currentSession = lensClient;
396
+ return { revoked: previous ? await tokens.revokeSession(previous) : true };
351
397
  }
352
398
  async function authFetch(input, init = {}) {
353
399
  const send = async (forceRefresh) => {
@@ -387,6 +433,7 @@ function createLensAuth(options) {
387
433
  requestChallenge,
388
434
  signChallenge: signLensChallenge,
389
435
  authenticate,
436
+ startSession,
390
437
  logout,
391
438
  fetch: authFetch,
392
439
  fetchConvexToken
@@ -517,7 +564,7 @@ async function fetchAccountUsername(auth, accountAddress) {
517
564
  async function signInToCreatedAccount(auth, session, accountAddress) {
518
565
  const switched = await session.switchAccount({ account: evmAddress2(accountAddress) });
519
566
  if (switched.isErr()) throw switched.error;
520
- auth.tokens.storeCredentials(sessionCredentials(switched.value));
567
+ await auth.startSession(sessionCredentials(switched.value));
521
568
  const resumed = await auth.lensClient.resumeSession();
522
569
  if (resumed.isErr()) throw resumed.error;
523
570
  }
@@ -550,4 +597,4 @@ export {
550
597
  fetchAccountUsername,
551
598
  signInToCreatedAccount
552
599
  };
553
- //# sourceMappingURL=chunk-PJC2S3IU.js.map
600
+ //# sourceMappingURL=chunk-OLO3H3YN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/constants.ts","../src/core/jwt.ts","../src/core/storage.ts","../src/core/tokens.ts","../src/core/wallet.ts","../src/core/auth.ts","../src/core/onboarding.ts"],"sourcesContent":["// Dependency-free values shared by every entry point, including the server and Convex ones.\n\nexport type LensEnvironmentName = \"mainnet\" | \"testnet\"\n\nconst LENS_API_ORIGINS: Record<LensEnvironmentName, string> = {\n mainnet: \"https://api.lens.xyz\",\n testnet: \"https://api.testnet.lens.xyz\",\n}\n\n// Lens signs its tokens as the API origin, and publishes its keys under it.\nexport function lensApiOrigin(environment: LensEnvironmentName = \"mainnet\"): string {\n return LENS_API_ORIGINS[environment]\n}\n\nexport const LENS_CHAIN_IDS: Record<LensEnvironmentName, number> = {\n mainnet: 232,\n testnet: 37111,\n}\n\n// Lens access and ID tokens are valid for 10 minutes.\nexport const LENS_TOKEN_LIFETIME_MS = 10 * 60 * 1000\n\nexport const ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/\n\n// The token the server issues for Convex (server/, convex/, cli/). Both sides must agree on these.\nexport const DEFAULT_CONVEX_TOKEN_ISSUER = \"https://lens-convex-auth.local\"\nexport const DEFAULT_CONVEX_TOKEN_AUDIENCE = \"convex\"\nexport const DEFAULT_CONVEX_TOKEN_KEY_ID = \"convex-auth-1\"\nexport const DEFAULT_CONVEX_TOKEN_ENDPOINT = \"/api/convex-token\"\n","// Claims of a JWT, without verifying it (the server verifies). Null if it isn't a JWT.\nexport function decodeJwtPayload(token: string): Record<string, unknown> | null {\n try {\n const payload = token.split(\".\")[1]\n if (!payload) return null\n const base64 = payload.replace(/-/g, \"+\").replace(/_/g, \"/\")\n const binary = atob(base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), \"=\"))\n const json = new TextDecoder().decode(Uint8Array.from(binary, (c) => c.charCodeAt(0)))\n const claims = JSON.parse(json)\n return claims && typeof claims === \"object\" ? claims : null\n } catch {\n return null\n }\n}\n\n// When a JWT expires (ms since epoch), from its `exp` claim\nexport function jwtExpiresAt(token: string): number | null {\n const exp = Number(decodeJwtPayload(token)?.exp)\n return Number.isFinite(exp) && exp > 0 ? exp * 1000 : null\n}\n\n// The Lens account a Lens ID token acts for (`act.sub`), lowercased, and the session it belongs to.\n// Null for tokens without an account (onboarding users) or that aren't JWTs.\nexport function lensSessionFromIdToken(idToken: string): { account: string; sessionId: string } | null {\n const claims = decodeJwtPayload(idToken)\n const act = claims?.act as { sub?: unknown } | null | undefined\n if (typeof act?.sub !== \"string\") return null\n return { account: act.sub.toLowerCase(), sessionId: String(claims?.sid ?? \"\") }\n}\n","import type { IStorageProvider } from \"@lens-protocol/client\"\nimport type { TokenService } from \"./tokens\"\n\n// Storage for the Lens SDK that keeps its credentials in the TokenService, so the SDK and the app\n// share one copy of the session. Everything else the SDK stores goes to localStorage under a prefix.\nexport class LensTokenStorage implements IStorageProvider {\n constructor(private tokens: TokenService, private prefix: string) {}\n\n private isCredentials(key: string) {\n return key.includes(\"credentials\") || key.includes(\"session\")\n }\n\n // The SDK only accepts a stored credentials item whose metadata is exactly\n // { version, createdAt, updatedAt }, so keep the metadata it wrote and hand it back unchanged.\n private metadataKey(key: string) {\n return `${this.prefix}${key}:metadata`\n }\n\n private credentialsMetadata(key: string) {\n const stored = localStorage.getItem(this.metadataKey(key))\n if (stored) return JSON.parse(stored)\n // Tokens stored before the metadata was kept: 3 is the SDK's current credentials schema version.\n const now = Date.now()\n return { version: 3, createdAt: now, updatedAt: now }\n }\n\n getItem(key: string): string | null {\n if (typeof window === \"undefined\") return null\n try {\n if (!this.isCredentials(key)) return localStorage.getItem(this.prefix + key)\n const tokens = this.tokens.getStoredTokens()\n if (!tokens) return null\n return JSON.stringify({\n data: { accessToken: tokens.accessToken, idToken: tokens.idToken, refreshToken: tokens.refreshToken },\n metadata: this.credentialsMetadata(key),\n })\n } catch {\n return null\n }\n }\n\n setItem(key: string, value: string): void {\n if (typeof window === \"undefined\") return\n try {\n if (!this.isCredentials(key)) return localStorage.setItem(this.prefix + key, value)\n const parsed = JSON.parse(value)\n const data = typeof parsed.data === \"string\" ? JSON.parse(parsed.data) : parsed.data\n if (data?.accessToken && data?.idToken && data?.refreshToken) {\n this.tokens.storeCredentials(data)\n localStorage.setItem(this.metadataKey(key), JSON.stringify(parsed.metadata))\n }\n } catch {}\n }\n\n removeItem(key: string): void {\n if (typeof window === \"undefined\") return\n try {\n if (!this.isCredentials(key)) return localStorage.removeItem(this.prefix + key)\n this.tokens.clearTokens()\n localStorage.removeItem(this.metadataKey(key))\n } catch {}\n }\n}\n","import { LENS_TOKEN_LIFETIME_MS } from \"./constants\"\nimport { decodeJwtPayload, jwtExpiresAt } from \"./jwt\"\n\nexport interface TokenData {\n accessToken: string\n idToken: string\n refreshToken: string\n expiresAt: number // when the access token expires (ms since epoch)\n}\n\nexport type LensCredentials = Pick<TokenData, \"accessToken\" | \"idToken\" | \"refreshToken\">\n\n// Dispatched on window whenever tokens are stored or cleared in this tab\nexport const TOKENS_CHANGED_EVENT = \"lens-convex-auth:tokens-changed\"\n\n// Refresh a minute before expiry for smoother UX\nconst REFRESH_BUFFER_MS = 60 * 1000\n\nconst REFRESH_MUTATION = `\n mutation Refresh($request: RefreshRequest!) {\n refresh(request: $request) {\n ... on AuthenticationTokens {\n accessToken\n idToken\n refreshToken\n }\n ... on ForbiddenError {\n reason\n }\n }\n }\n`\n\nconst REVOKE_MUTATION = `\n mutation RevokeAuthentication($request: RevokeAuthenticationRequest!) {\n revokeAuthentication(request: $request)\n }\n`\n\nfunction notifyTokensChanged() {\n try {\n window.dispatchEvent(new Event(TOKENS_CHANGED_EVENT))\n } catch {}\n}\n\n// Keeps the Lens session's tokens in localStorage, the single place every part of the app (the Lens\n// SDK, Convex, API calls) reads them from, and refreshes them.\nexport class TokenService {\n readonly storageKeys: { accessToken: string; idToken: string; refreshToken: string; expiresAt: string }\n private refreshPromise: Promise<TokenData | null> | null = null\n\n constructor(private options: { storagePrefix: string; graphqlUrl: string }) {\n const p = options.storagePrefix\n this.storageKeys = {\n accessToken: `${p}access_token`,\n idToken: `${p}id_token`,\n refreshToken: `${p}refresh_token`,\n expiresAt: `${p}token_expires_at`,\n }\n }\n\n storeTokens(tokens: TokenData): void {\n if (typeof window === \"undefined\") return\n try {\n localStorage.setItem(this.storageKeys.accessToken, tokens.accessToken)\n localStorage.setItem(this.storageKeys.idToken, tokens.idToken)\n localStorage.setItem(this.storageKeys.refreshToken, tokens.refreshToken)\n localStorage.setItem(this.storageKeys.expiresAt, tokens.expiresAt.toString())\n } catch {}\n notifyTokensChanged()\n }\n\n storeCredentials(credentials: LensCredentials): void {\n this.storeTokens({ ...credentials, expiresAt: Date.now() + LENS_TOKEN_LIFETIME_MS })\n }\n\n getStoredTokens(): TokenData | null {\n if (typeof window === \"undefined\") return null\n try {\n const accessToken = localStorage.getItem(this.storageKeys.accessToken)\n const idToken = localStorage.getItem(this.storageKeys.idToken)\n const refreshToken = localStorage.getItem(this.storageKeys.refreshToken)\n const storedExpiresAt = localStorage.getItem(this.storageKeys.expiresAt)\n if (!accessToken || !idToken || !refreshToken) return null\n // The access token's own expiry. The stored value is only an estimate (store time + 10 min),\n // which marks tokens read back from an old session as fresh long after they expired.\n const expiresAt = jwtExpiresAt(accessToken) ?? (storedExpiresAt ? parseInt(storedExpiresAt, 10) : 0)\n return { accessToken, idToken, refreshToken, expiresAt }\n } catch {\n return null\n }\n }\n\n clearTokens(): void {\n if (typeof window === \"undefined\") return\n try {\n for (const key of Object.values(this.storageKeys)) localStorage.removeItem(key)\n } catch {}\n notifyTokensChanged()\n }\n\n // Current access token, refreshed first if it's about to expire\n async getValidAccessToken(): Promise<string | null> {\n const tokens = this.getStoredTokens()\n if (!tokens) return null\n if (tokens.expiresAt > Date.now() + REFRESH_BUFFER_MS) return tokens.accessToken\n return (await this.refreshTokens())?.accessToken ?? null\n }\n\n // ID token for authenticating to your backend (Convex, API routes), refreshed when it's about to\n // expire (by the token's own `exp` claim). `forceRefresh` is for when the backend rejected it.\n async getFreshIdToken(forceRefresh = false): Promise<string | null> {\n const tokens = this.getStoredTokens()\n if (!tokens) return null\n const expiresAt = jwtExpiresAt(tokens.idToken) ?? 0\n const fresh = expiresAt > Date.now() + REFRESH_BUFFER_MS\n // A forced refresh right after another tab or the background refresher got new tokens reuses them\n const justIssued = expiresAt > Date.now() + 2 * REFRESH_BUFFER_MS\n if (fresh && (!forceRefresh || justIssued)) return tokens.idToken\n return (await this.refreshTokens())?.idToken ?? null\n }\n\n async isAuthenticated(): Promise<boolean> {\n return !!(await this.getValidAccessToken())\n }\n\n // Concurrent calls share one request. Clears the tokens when the session can't be refreshed.\n refreshTokens(): Promise<TokenData | null> {\n this.refreshPromise ??= this.performRefresh().finally(() => {\n this.refreshPromise = null\n })\n return this.refreshPromise\n }\n\n // Ends a session on the Lens API, so its tokens stop working even if they were copied out of this\n // browser. Takes the session's tokens rather than the stored ones, because by the time a session is\n // revoked the storage already holds the next one (or nothing). Its refresh token stops working right\n // away; an access or ID token already issued stays valid until it expires (10 minutes at most).\n // Resolves false when the API couldn't be reached or refused.\n async revokeSession(tokens: LensCredentials): Promise<boolean> {\n const authenticationId = decodeJwtPayload(tokens.idToken)?.sid\n if (typeof authenticationId !== \"string\") return false\n try {\n // Revoking needs a valid access token for the session itself\n let accessToken = tokens.accessToken\n if ((jwtExpiresAt(accessToken) ?? 0) <= Date.now() + REFRESH_BUFFER_MS) {\n const refreshed = await this.requestRefresh(tokens.refreshToken)\n // A session that can't be refreshed has already ended (expired, or revoked elsewhere)\n if (!refreshed) return true\n accessToken = refreshed.accessToken\n }\n const data = await this.graphql(REVOKE_MUTATION, { request: { authenticationId } }, accessToken)\n return !data.errors\n } catch {\n return false\n }\n }\n\n private async performRefresh(): Promise<TokenData | null> {\n const refreshToken = this.getStoredTokens()?.refreshToken\n if (!refreshToken) {\n this.clearTokens()\n return null\n }\n try {\n // Resuming the Lens session only reads these same tokens back, so ask the API for new ones.\n // (Each Lens SDK session keeps its own copy of the tokens and refreshes that copy itself.)\n const refreshed = await this.requestRefresh(refreshToken)\n // Signed out, or signed in again, while the request was in flight: those tokens are for a session\n // that's no longer this browser's\n if (this.getStoredTokens()?.refreshToken !== refreshToken) return this.getStoredTokens()\n if (!refreshed) throw new Error(\"Refresh refused\")\n const tokens: TokenData = { ...refreshed, expiresAt: Date.now() + LENS_TOKEN_LIFETIME_MS }\n this.storeTokens(tokens)\n return tokens\n } catch {\n if (this.getStoredTokens()?.refreshToken === refreshToken) this.clearTokens()\n return null\n }\n }\n\n // New tokens for a session, from its refresh token. Doesn't store them. Null when the API refuses (the\n // session has ended); throws when it can't be reached.\n private async requestRefresh(refreshToken: string): Promise<LensCredentials | null> {\n const data = await this.graphql(REFRESH_MUTATION, { request: { refreshToken } })\n const result = data?.data?.refresh\n if (data?.errors || !result?.accessToken || !result.idToken || !result.refreshToken) return null\n return { accessToken: result.accessToken, idToken: result.idToken, refreshToken: result.refreshToken }\n }\n\n private async graphql(query: string, variables: Record<string, unknown>, accessToken?: string) {\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" }\n if (accessToken) headers.Authorization = `Bearer ${accessToken}`\n const response = await fetch(this.options.graphqlUrl, { method: \"POST\", headers, body: JSON.stringify({ query, variables }) })\n return response.json()\n }\n}\n","import { createWalletClient, custom, defineChain as defineViemChain, getAddress, hexToBigInt, hexToNumber, type Account as ViemAccount, type Chain as ViemChain, type Hex, type Transport, type WalletClient } from \"viem\"\nimport { lens as viemLens, lensTestnet as viemLensTestnet } from \"viem/chains\"\nimport { chainConfig } from \"viem/zksync\"\nimport { defineChain, type Chain } from \"thirdweb/chains\"\nimport { EIP1193, type Wallet } from \"thirdweb/wallets\"\nimport type { ThirdwebClient } from \"thirdweb\"\nimport { LENS_CHAIN_IDS, type LensEnvironmentName } from \"./constants\"\n\n// Lens Chain for thirdweb. Without an RPC URL thirdweb uses its own RPC for the chain.\nexport function lensThirdwebChain(environment: LensEnvironmentName, rpcUrl?: string): Chain {\n const testnet = environment === \"testnet\"\n return defineChain({\n id: LENS_CHAIN_IDS[environment],\n name: testnet ? \"Lens Chain Testnet\" : \"Lens Chain\",\n nativeCurrency: testnet\n ? { name: \"GRASS\", symbol: \"GRASS\", decimals: 18 }\n : { name: \"GHO\", symbol: \"GHO\", decimals: 18 },\n ...(rpcUrl ? { rpc: rpcUrl } : {}),\n ...(testnet ? { testnet: true as const } : {}),\n })\n}\n\n// Lens Chain with the zkSync config (same as `chains.mainnet` from @lens-chain/sdk/viem), so viem can\n// sign the sponsored EIP-712 transactions the Lens API returns. Typed as a plain `Chain` because\n// `handleOperationWith` takes a plain viem `WalletClient`.\nexport function lensViemChain(environment: LensEnvironmentName): ViemChain {\n return defineViemChain({ ...(environment === \"testnet\" ? viemLensTestnet : viemLens), ...chainConfig })\n}\n\n// A viem wallet client for the connected thirdweb wallet, as `handleOperationWith` expects:\n// hoisted account and a Lens chain. Works for every thirdweb wallet, not only injected ones.\nexport function createLensWalletClient(opts: {\n wallet: Wallet\n client: ThirdwebClient\n chain: Chain\n viemChain: ViemChain\n}): WalletClient<Transport, ViemChain, ViemAccount> {\n const account = opts.wallet.getAccount()\n if (!account) throw new Error(\"Connect your wallet\")\n const provider = EIP1193.toProvider({ wallet: opts.wallet, chain: opts.chain, client: opts.client })\n return createWalletClient({\n account: getAddress(account.address),\n chain: opts.viemChain,\n transport: custom({\n request: ({ method, params }) => {\n if (method === \"eth_sendTransaction\") {\n // viem sends hex-encoded fields (e.g. `type: \"0x2\"`) that thirdweb's local signers reject.\n // thirdweb re-estimates gas and fees for Lens Chain anyway, so pass only what it needs.\n const [tx] = params as [{ to: Hex; data?: Hex; value?: Hex; nonce?: Hex }]\n return provider.request({\n method,\n params: [{\n to: tx.to,\n data: tx.data,\n value: tx.value ? hexToBigInt(tx.value) : undefined,\n nonce: tx.nonce ? hexToNumber(tx.nonce) : undefined,\n }],\n })\n }\n return provider.request({ method, params })\n },\n }),\n })\n}\n","import { PublicClient, evmAddress, mainnet, signatureFrom, testnet, type SessionClient, type UUID } from \"@lens-protocol/client\"\nimport { createThirdwebClient, type ThirdwebClient } from \"thirdweb\"\nimport type { Chain } from \"thirdweb/chains\"\nimport type { Chain as ViemChain } from \"viem\"\nimport { ADDRESS_RE, DEFAULT_CONVEX_TOKEN_ENDPOINT, lensApiOrigin, type LensEnvironmentName } from \"./constants\"\nimport { decodeJwtPayload, lensSessionFromIdToken } from \"./jwt\"\nimport { LensTokenStorage } from \"./storage\"\nimport { TokenService, type LensCredentials } from \"./tokens\"\nimport { lensThirdwebChain, lensViemChain } from \"./wallet\"\n\nexport type LensAuthOptions = {\n /** thirdweb client ID (https://thirdweb.com/dashboard) */\n thirdwebClientId: string\n /** Your Lens app address (https://developer.lens.xyz/apps). Tokens are issued for this app. */\n lensAppAddress: string\n /** Defaults to \"mainnet\" */\n environment?: LensEnvironmentName\n /** Lens Chain RPC for thirdweb. Defaults to thirdweb's RPC. */\n lensChainRpcUrl?: string\n /** Prefix of the localStorage keys the session is stored under. Defaults to \"lens_\". */\n storagePrefix?: string\n /** Route that exchanges a Lens ID token for a Convex token. Defaults to \"/api/convex-token\". */\n convexTokenEndpoint?: string\n /**\n * Username namespace (0x…) new accounts get their username in, and whose usernames are shown first.\n * Defaults to the global Lens namespace.\n */\n usernameNamespace?: string\n}\n\nexport type LensLoginRole = \"ACCOUNT_OWNER\" | \"ACCOUNT_MANAGER\"\n\nexport type LensChallenge = { id: UUID; text: string }\n\nexport type MessageSigner = {\n signMessage: (args: { message: string }) => Promise<string>\n}\n\n// The signed-in Lens account (lowercased address) and its session, read from the stored ID token\nexport type LensSessionInfo = { account: string; sessionId: string }\n\nexport type LensAuth = ReturnType<typeof createLensAuth>\n\n// Challenges are fetched ahead of time so a tap can sign right away: how often they're replaced, and how\n// old one can be to still be used.\nexport const CHALLENGE_REFRESH_MS = 2 * 60 * 1000\nexport const CHALLENGE_MAX_AGE_MS = 4 * 60 * 1000\n\n// On phones and tablets thirdweb opens the wallet app with a deep link to sign (same check as its\n// internal isMobile), and that deep link only works right after a tap.\nexport function opensWalletWithDeepLink() {\n if (typeof navigator === \"undefined\") return false\n const ua = navigator.userAgent\n return /iPhone|iPad|iPod|Android/i.test(ua) || (/Macintosh/.test(ua) && navigator.maxTouchPoints > 1)\n}\n\n// Asks the wallet to sign synchronously (no awaits before it), so call it directly from a click handler.\nexport function signLensChallenge(challenge: LensChallenge, signer: MessageSigner): Promise<string> {\n return signer.signMessage({ message: challenge.text })\n}\n\n// A Lens SDK session's tokens\nexport function sessionCredentials(session: SessionClient): LensCredentials {\n const credentials = session.getCredentials()\n if (credentials.isErr()) throw credentials.error\n if (!credentials.value) throw new Error(\"The Lens session has no credentials\")\n return credentials.value\n}\n\n// Create once per app (at module level) and pass to <LensAuthProvider>.\nexport function createLensAuth(options: LensAuthOptions) {\n if (!options.thirdwebClientId) throw new Error(\"lens-convex-auth: thirdwebClientId is required\")\n if (!ADDRESS_RE.test(options.lensAppAddress ?? \"\")) {\n throw new Error(\"lens-convex-auth: lensAppAddress must be your Lens app address (0x…)\")\n }\n if (options.usernameNamespace !== undefined && !ADDRESS_RE.test(options.usernameNamespace)) {\n throw new Error(\"lens-convex-auth: usernameNamespace must be a Lens namespace address (0x…)\")\n }\n\n const environment: LensEnvironmentName = options.environment ?? \"mainnet\"\n const storagePrefix = options.storagePrefix ?? \"lens_\"\n const appAddress = options.lensAppAddress\n const convexTokenEndpoint = options.convexTokenEndpoint ?? DEFAULT_CONVEX_TOKEN_ENDPOINT\n const usernameNamespace = options.usernameNamespace\n\n const tokens = new TokenService({ storagePrefix, graphqlUrl: `${lensApiOrigin(environment)}/graphql` })\n const thirdwebClient: ThirdwebClient = createThirdwebClient({ clientId: options.thirdwebClientId })\n const chain: Chain = lensThirdwebChain(environment, options.lensChainRpcUrl)\n const viemChain: ViemChain = lensViemChain(environment)\n const lensEnvironment = environment === \"testnet\" ? testnet : mainnet\n const lensClient = PublicClient.create({\n environment: lensEnvironment,\n storage: typeof window !== \"undefined\" ? new LensTokenStorage(tokens, `${storagePrefix}custom_`) : undefined,\n })\n\n const sessionIdOf = (idToken: string) => decodeJwtPayload(idToken)?.sid\n\n // The signed-in account, or null when signed out\n function getSession(): LensSessionInfo | null {\n const idToken = tokens.getStoredTokens()?.idToken\n return idToken ? lensSessionFromIdToken(idToken) : null\n }\n\n // True when there's a valid session, refreshing its tokens first if needed (a session that can't be\n // refreshed is cleared). Also resumes it in the Lens client, so Lens SDK calls and hooks act as the\n // signed-in account.\n async function resumeSession(): Promise<boolean> {\n if (!(await tokens.isAuthenticated())) return false\n if (!lensClient.currentSession?.isSessionClient()) await lensClient.resumeSession()\n return true\n }\n\n // Logging in is split in two (challenge, then sign + authenticate) instead of using `lensClient.login`,\n // so the signature can be requested straight from a tap. On mobile, WalletConnect opens the wallet app\n // with a deep link when asked to sign, and iOS only allows that right after a user gesture: after the\n // challenge round trip the link is blocked and the wallet never shows the request.\n async function requestChallenge(opts: { role: LensLoginRole; account: string; signer: string }): Promise<LensChallenge> {\n const app = evmAddress(appAddress)\n const account = evmAddress(opts.account)\n const signer = evmAddress(opts.signer)\n const challenge = await lensClient.challenge(\n opts.role === \"ACCOUNT_MANAGER\"\n ? { accountManager: { account, app, manager: signer } }\n : { accountOwner: { account, app, owner: signer } },\n )\n if (challenge.isErr()) throw challenge.error\n return challenge.value\n }\n\n async function authenticate(challenge: LensChallenge, signature: string): Promise<SessionClient> {\n // Read first: the Lens client stores the new session's tokens itself, through LensTokenStorage\n const previous = tokens.getStoredTokens()\n const authenticated = await lensClient.authenticate({ id: challenge.id, signature: signatureFrom(signature) })\n if (authenticated.isErr()) throw authenticated.error\n const credentials = sessionCredentials(authenticated.value)\n tokens.storeCredentials(credentials)\n await revokeReplacedSession(previous, credentials)\n return authenticated.value\n }\n\n // Makes these the app's session, like a sign-in (used for accounts created on another Lens client).\n async function startSession(credentials: LensCredentials): Promise<boolean> {\n const previous = tokens.getStoredTokens()\n tokens.storeCredentials(credentials)\n return revokeReplacedSession(previous, credentials)\n }\n\n // Switching accounts revokes the session switched away from, so its tokens stop working even if they\n // were copied out of this browser. The new session is stored first: it doesn't wait on the revocation,\n // and a failed one doesn't undo it. False when the previous session couldn't be revoked.\n async function revokeReplacedSession(previous: LensCredentials | null, next: LensCredentials): Promise<boolean> {\n if (!previous || sessionIdOf(previous.idToken) === sessionIdOf(next.idToken)) return true\n const revoked = await tokens.revokeSession(previous)\n if (!revoked) console.warn(\"lens-convex-auth: couldn't revoke the previous Lens session\")\n return revoked\n }\n\n // Signs out: clears the session from this browser, then revokes it on the Lens API, so its tokens\n // stop working even if they were copied. Resolves `revoked: false` when there was a session and the\n // API couldn't be reached or refused; it's cleared from this browser either way.\n async function logout(): Promise<{ revoked: boolean }> {\n const previous = tokens.getStoredTokens()\n tokens.clearTokens()\n // Lens SDK calls and hooks act as the public client again. (The SDK's own logout() would revoke with\n // the session's in-memory copy of the tokens, which the background refresh may have replaced.)\n lensClient.currentSession = lensClient\n return { revoked: previous ? await tokens.revokeSession(previous) : true }\n }\n\n // fetch() with the Lens ID token as `Authorization: Bearer`, retried once with a refreshed token on 401.\n // Verify it on the server with `getLensSession` from \"lens-convex-auth/server\".\n async function authFetch(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {\n const send = async (forceRefresh: boolean) => {\n const idToken = await tokens.getFreshIdToken(forceRefresh)\n const headers = new Headers(init.headers)\n if (idToken) headers.set(\"Authorization\", `Bearer ${idToken}`)\n return fetch(input, { ...init, headers })\n }\n const response = await send(false)\n return response.status === 401 && getSession() ? send(true) : response\n }\n\n // Convex can't verify Lens ID tokens itself (they have no `kid` header), so the server verifies the\n // Lens ID token and issues a Convex token for the same account (see \"lens-convex-auth/server\").\n async function fetchConvexToken(forceRefresh = false): Promise<string | null> {\n const idToken = await tokens.getFreshIdToken(forceRefresh)\n if (!idToken) return null\n try {\n const res = await fetch(convexTokenEndpoint, { method: \"POST\", headers: { Authorization: `Bearer ${idToken}` } })\n if (!res.ok) return null\n const { token } = await res.json()\n return typeof token === \"string\" ? token : null\n } catch {\n return null\n }\n }\n\n return {\n environment,\n appAddress,\n usernameNamespace,\n storagePrefix,\n thirdwebClient,\n chain,\n viemChain,\n lensClient,\n lensEnvironment,\n tokens,\n getSession,\n resumeSession,\n requestChallenge,\n signChallenge: signLensChallenge,\n authenticate,\n startSession,\n logout,\n fetch: authFetch,\n fetchConvexToken,\n }\n}\n","import { PageSize, PublicClient, evmAddress, signatureFrom, uri, type Account, type Cursor, type SessionClient } from \"@lens-protocol/client\"\nimport { canCreateUsername, createAccountWithUsername, fetchAccount, fetchUsername, fetchUsernames } from \"@lens-protocol/client/actions\"\nimport { handleOperationWith } from \"@lens-protocol/client/viem\"\nimport { account as accountMetadata } from \"@lens-protocol/metadata\"\nimport { StorageClient, immutable } from \"@lens-chain/storage-client\"\nimport { getTransactionReceipt } from \"viem/actions\"\nimport type { Wallet } from \"thirdweb/wallets\"\nimport type { LensAuth, LensChallenge } from \"./auth\"\nimport { sessionCredentials } from \"./auth\"\nimport { LENS_CHAIN_IDS } from \"./constants\"\nimport { createLensWalletClient } from \"./wallet\"\n\n// An error whose message can be shown to the user as is.\nexport class OnboardingError extends Error {\n name = \"OnboardingError\"\n}\n\n// Creating an account takes a session as an onboarding user (a wallet signed in without a Lens Account).\n// It runs on its own client, whose storage is in memory, so that session never reaches the app's token\n// storage: until the account exists and is signed in to, the app must not treat the wallet as signed in.\nexport function createOnboardingClient(auth: LensAuth) {\n return PublicClient.create({ environment: auth.lensEnvironment })\n}\n\nexport async function requestOnboardingChallenge(auth: LensAuth, client: PublicClient, walletAddress: string): Promise<LensChallenge> {\n const challenge = await client.challenge({\n onboardingUser: { app: evmAddress(auth.appAddress), wallet: evmAddress(walletAddress) },\n })\n if (challenge.isErr()) throw challenge.error\n return challenge.value\n}\n\nexport async function authenticateOnboarding(client: PublicClient, challenge: LensChallenge, signature: string): Promise<SessionClient> {\n const authenticated = await client.authenticate({ id: challenge.id, signature: signatureFrom(signature) })\n if (authenticated.isErr()) throw authenticated.error\n return authenticated.value\n}\n\n// The global Lens namespace takes lowercase letters, numbers, \"-\" and \"_\", and can't start with \"-\" or \"_\".\n// Its other rules (length, reserved names, price) are checked by the Lens API in checkUsername.\nconst USERNAME_PATTERN = /^[a-z0-9][a-z0-9_-]*$/\n\nexport function usernameFormatError(localName: string): string | null {\n if (!localName) return \"Choose a username\"\n if (/^[-_]/.test(localName)) return \"Start with a letter or a number\"\n if (!USERNAME_PATTERN.test(localName)) return \"Use only lowercase letters, numbers, - and _\"\n return null\n}\n\n// The username to create, in the app's namespace when it has one (else the global Lens namespace)\nfunction usernameInput(auth: LensAuth, localName: string) {\n return auth.usernameNamespace ? { localName, namespace: evmAddress(auth.usernameNamespace) } : { localName }\n}\n\n// Needs no session, so it can run while the user types.\nexport async function isUsernameTaken(auth: LensAuth, localName: string): Promise<boolean> {\n const result = await fetchUsername(auth.lensClient, { username: usernameInput(auth, localName) })\n if (result.isErr()) throw result.error\n return result.value !== null\n}\n\ntype NamespaceValidationFailed = {\n reason: string\n unsatisfiedRules: { required: { message: string }[] } | null\n}\n\nfunction validationFailedMessage(failed: NamespaceValidationFailed) {\n return failed.unsatisfiedRules?.required[0]?.message || failed.reason\n}\n\n// Returns why the username can't be created, or null if it can.\nexport async function checkUsername(auth: LensAuth, session: SessionClient, localName: string): Promise<string | null> {\n const result = await canCreateUsername(session, usernameInput(auth, localName))\n if (result.isErr()) throw result.error\n switch (result.value.__typename) {\n case \"NamespaceOperationValidationPassed\":\n return null\n case \"UsernameTaken\":\n return \"This username is taken\"\n case \"NamespaceOperationValidationFailed\":\n return validationFailedMessage(result.value)\n case \"NamespaceOperationValidationUnknown\":\n return \"This username can’t be created here. Try another one.\"\n }\n}\n\nlet storageClient: StorageClient | null = null\n\nexport type AccountProfile = {\n name?: string\n bio?: string\n picture?: File | null\n}\n\n// Uploads the account metadata (and its picture) to Grove, and returns the metadata URI.\nexport async function uploadAccountMetadata(auth: LensAuth, profile: AccountProfile): Promise<string> {\n storageClient ??= StorageClient.create()\n const acl = immutable(LENS_CHAIN_IDS[auth.environment])\n const picture = profile.picture ? (await storageClient.uploadFile(profile.picture, { acl })).uri : undefined\n const metadata = accountMetadata({\n name: profile.name || undefined,\n bio: profile.bio || undefined,\n picture,\n })\n const { uri: metadataUri } = await storageClient.uploadAsJson(metadata, { acl })\n return metadataUri\n}\n\n// How long to wait for a new account to be listed once its transaction is indexed\nconst ACCOUNT_POLL_ATTEMPTS = 15\nconst ACCOUNT_POLL_INTERVAL_MS = 2000\n\n// Creates an account owned by the onboarding user's wallet. Most apps sponsor it; otherwise the wallet\n// is asked to send the transaction.\nexport async function createLensAccount(\n auth: LensAuth,\n session: SessionClient,\n wallet: Wallet,\n request: { localName: string; metadataUri: string },\n): Promise<Account> {\n const created = await createAccountWithUsername(session, {\n username: usernameInput(auth, request.localName),\n metadataUri: uri(request.metadataUri),\n })\n if (created.isErr()) throw created.error\n const result = created.value\n switch (result.__typename) {\n case \"UsernameTaken\":\n throw new OnboardingError(\"This username is taken\")\n case \"NamespaceOperationValidationFailed\":\n throw new OnboardingError(validationFailedMessage(result))\n case \"TransactionWillFail\":\n throw new OnboardingError(result.reason)\n }\n\n const walletClient = createLensWalletClient({ wallet, client: auth.thirdwebClient, chain: auth.chain, viemChain: auth.viemChain })\n const sent = await handleOperationWith(walletClient)(result)\n if (sent.isErr()) throw sent.error\n const hash = sent.value\n\n const indexed = await session.waitForTransaction(hash)\n if (indexed.isErr()) {\n // The Lens API stops waiting for indexing after 10s on mainnet, so the receipt decides whether\n // the transaction failed. If it can't be read either, the account lookup below still settles it.\n const receipt = await getTransactionReceipt(walletClient, { hash }).catch(() => null)\n if (receipt?.status === \"reverted\") throw indexed.error\n }\n\n for (let attempt = 0; attempt < ACCOUNT_POLL_ATTEMPTS; attempt++) {\n const fetched = await fetchAccount(session, { txHash: hash })\n if (fetched.isOk() && fetched.value) return fetched.value\n await new Promise((resolve) => setTimeout(resolve, ACCOUNT_POLL_INTERVAL_MS))\n }\n throw new OnboardingError(\"Your account was created but isn’t ready yet. Sign in to it again in a minute.\")\n}\n\n// An account's username in the namespace it prefers: the app's namespace, then the global Lens one, then\n// any other. So accounts from apps with their own namespace can sign in too. Null if it has none.\nexport async function fetchAccountUsername(auth: LensAuth, accountAddress: string): Promise<string | null> {\n const usernames: { localName: string; namespace: string; value: string }[] = []\n let cursor: Cursor | null = null\n do {\n const result = await fetchUsernames(auth.lensClient, {\n filter: { linkedTo: evmAddress(accountAddress) },\n pageSize: PageSize.Fifty,\n cursor,\n })\n if (result.isErr()) throw result.error\n usernames.push(...result.value.items)\n cursor = (result.value.pageInfo.next as Cursor | null) ?? null\n } while (cursor)\n const preferred = auth.usernameNamespace?.toLowerCase()\n const rank = (username: { namespace: string; value: string }) =>\n username.namespace.toLowerCase() === preferred ? 0 : username.value.startsWith(\"lens/\") ? 1 : 2\n const [best] = usernames.sort((a, b) => rank(a) - rank(b))\n return best?.localName ?? null\n}\n\n// Signs the app in to the new account without another signature: the onboarding session switches to it,\n// and its tokens are handed to the app's Lens client, like a sign-in from the account list (which revokes\n// the session it replaces, when creating an account while signed in to another).\nexport async function signInToCreatedAccount(auth: LensAuth, session: SessionClient, accountAddress: string): Promise<void> {\n const switched = await session.switchAccount({ account: evmAddress(accountAddress) })\n if (switched.isErr()) throw switched.error\n await auth.startSession(sessionCredentials(switched.value))\n const resumed = await auth.lensClient.resumeSession()\n if (resumed.isErr()) throw resumed.error\n}\n"],"mappings":"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","names":["testnet","PublicClient","evmAddress","signatureFrom","PublicClient","evmAddress","signatureFrom"]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { L as LensChallenge, a as LensAuth, T as TokenService, b as LensEnvironmentName } from './auth-vTn7nb6F.js';
2
- export { C as CHALLENGE_MAX_AGE_MS, c as CHALLENGE_REFRESH_MS, d as LENS_CHAIN_IDS, e as LensAuthOptions, f as LensCredentials, g as LensLoginRole, h as LensSessionInfo, M as MessageSigner, i as TOKENS_CHANGED_EVENT, j as TokenData, k as createLensAuth, l as lensApiOrigin, o as opensWalletWithDeepLink, s as signLensChallenge } from './auth-vTn7nb6F.js';
1
+ import { L as LensChallenge, a as LensAuth, T as TokenService, b as LensEnvironmentName } from './auth-D0mva6fi.js';
2
+ export { C as CHALLENGE_MAX_AGE_MS, c as CHALLENGE_REFRESH_MS, d as LENS_CHAIN_IDS, e as LensAuthOptions, f as LensCredentials, g as LensLoginRole, h as LensSessionInfo, M as MessageSigner, i as TOKENS_CHANGED_EVENT, j as TokenData, k as createLensAuth, l as lensApiOrigin, o as opensWalletWithDeepLink, s as signLensChallenge } from './auth-D0mva6fi.js';
3
3
  import * as _lens_protocol_client from '@lens-protocol/client';
4
4
  import { PublicClient, SessionClient, Account, IStorageProvider } from '@lens-protocol/client';
5
5
  import { Wallet } from 'thirdweb/wallets';
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  signLensChallenge,
27
27
  uploadAccountMetadata,
28
28
  usernameFormatError
29
- } from "./chunk-PJC2S3IU.js";
29
+ } from "./chunk-OLO3H3YN.js";
30
30
  export {
31
31
  CHALLENGE_MAX_AGE_MS,
32
32
  CHALLENGE_REFRESH_MS,
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { Account, Wallet } from 'thirdweb/wallets';
3
3
  import { ConvexReactClient } from 'convex/react';
4
- import { a as LensAuth } from '../auth-vTn7nb6F.js';
4
+ import { a as LensAuth } from '../auth-D0mva6fi.js';
5
5
  import { ConnectButtonProps } from 'thirdweb/react';
6
6
  import '@lens-protocol/client';
7
7
  import 'thirdweb/chains';
@@ -32,12 +32,30 @@ type LensAuthContextValue = {
32
32
  wallet: Account | undefined;
33
33
  /** The signed-in Lens account */
34
34
  account: SignedInAccount | null;
35
- /** Ends the Lens session and disconnects the wallet */
36
- signOut: () => Promise<void>;
35
+ /**
36
+ * Ends the Lens session and disconnects the wallet. The session is revoked on the Lens API, so its tokens
37
+ * stop working even if they were copied; `revoked` is false when that failed (the session is cleared
38
+ * from this browser either way).
39
+ */
40
+ signOut: () => Promise<{
41
+ revoked: boolean;
42
+ }>;
37
43
  /** Opens the account dialog, e.g. to switch to another account */
38
44
  openAccountDialog: () => void;
39
45
  };
46
+ type AccountDialogHeader = {
47
+ /** "Select Account", or "Create Account" while the form to create one is shown */
48
+ title: string;
49
+ description: string;
50
+ };
51
+ type AccountDialogControls = AccountDialogHeader & {
52
+ open: boolean;
53
+ /** True while a wallet is connected without a Lens session: the dialog can't be dismissed */
54
+ required: boolean;
55
+ setOpen: (open: boolean) => void;
56
+ };
40
57
  declare function useLensAuth(): LensAuthContextValue;
58
+ declare function useAccountDialog(): AccountDialogControls;
41
59
  type LensAuthProviderProps = {
42
60
  /** From createLensAuth(), created once at module level */
43
61
  auth: LensAuth;
@@ -47,7 +65,13 @@ type LensAuthProviderProps = {
47
65
  * Called after the user signs in from the account dialog (picking an account or creating one), once
48
66
  * Convex accepts the session. A good place to create the user's row: `convex.mutation(api.users.ensure)`
49
67
  */
50
- onSignIn?: (account: SignedInAccount) => void | Promise<void>;
68
+ onSignIn?: (account: SignedInAccount) => unknown;
69
+ /**
70
+ * Called when the signed-in Lens account changes, however it happens: signing in, switching accounts,
71
+ * logging out, or the session ending (it expired, or another tab logged out). Not called when a stored
72
+ * session is restored on load. A good place to delete data your app keeps in the browser for `previous`.
73
+ */
74
+ onAccountChange?: (previous: SignedInAccount | null, next: SignedInAccount | null) => unknown;
51
75
  /** Render the account dialog. Set to false to render <AccountDialog /> yourself. Defaults to true. */
52
76
  accountDialog?: boolean;
53
77
  /**
@@ -57,7 +81,7 @@ type LensAuthProviderProps = {
57
81
  wallets?: Wallet[];
58
82
  children: React.ReactNode;
59
83
  };
60
- declare function LensAuthProvider({ auth, convex, onSignIn, accountDialog, wallets, children }: LensAuthProviderProps): React.JSX.Element;
84
+ declare function LensAuthProvider({ auth, convex, onSignIn, onAccountChange, accountDialog, wallets, children }: LensAuthProviderProps): React.JSX.Element;
61
85
 
62
86
  type SignInButtonProps = {
63
87
  /** Defaults to "Sign in" */
@@ -74,15 +98,20 @@ type SignInButtonProps = {
74
98
  declare function SignInButton({ label, signOutLabel, theme, className, connectButtonProps }: SignInButtonProps): React.JSX.Element;
75
99
 
76
100
  declare function AccountDialog(): React.JSX.Element;
101
+ declare function AccountPicker({ actions }: {
102
+ actions?: React.ReactNode;
103
+ }): React.JSX.Element | null;
77
104
 
78
105
  type CreatedAccount = {
79
106
  address: string;
80
107
  localName: string;
81
108
  };
82
- declare function CreateAccountForm({ ownerAddress, onCreated, onBack }: {
109
+ declare function CreateAccountForm({ ownerAddress, onCreated, onBack, actions }: {
83
110
  ownerAddress: string;
84
111
  onCreated: (account: CreatedAccount) => Promise<void>;
85
112
  onBack?: () => void;
113
+ /** More buttons, shown under the form's own */
114
+ actions?: React.ReactNode;
86
115
  }): React.JSX.Element;
87
116
 
88
- export { AccountDialog, CreateAccountForm, type CreatedAccount, type LensAuthContextValue, LensAuthProvider, type LensAuthProviderProps, type LensAuthStatus, SignInButton, type SignInButtonProps, type SignedInAccount, useLensAuth };
117
+ export { AccountDialog, type AccountDialogControls, type AccountDialogHeader, AccountPicker, CreateAccountForm, type CreatedAccount, type LensAuthContextValue, LensAuthProvider, type LensAuthProviderProps, type LensAuthStatus, SignInButton, type SignInButtonProps, type SignedInAccount, useAccountDialog, useLensAuth };