diff --git a/biome.json b/biome.json index abeea31b8..7e92b3b12 100644 --- a/biome.json +++ b/biome.json @@ -47,6 +47,16 @@ "enabled": false } }, + { + "include": ["./packages/**/*.test.ts"], + "linter": { + "rules": { + "style": { + "noNonNullAssertion": "off" + } + } + } + }, { "include": ["./packages/graphql/*.graphql"], "formatter": { diff --git a/packages/client/src/actions/authentication.ts b/packages/client/src/actions/authentication.ts index 8a6a372bf..f5095e951 100644 --- a/packages/client/src/actions/authentication.ts +++ b/packages/client/src/actions/authentication.ts @@ -127,7 +127,7 @@ export function legacyRolloverRefresh( * Switch to account managed. * * ```ts - * const result = await switchAccount(sessionClient{ + * const result = await switchAccount(sessionClient, { * account: evmAddress('0x90c8c68d0Abfb40D4fCD72316A65e42161520BC3'), * }); * ``` diff --git a/packages/client/src/actions/onboarding.test.ts b/packages/client/src/actions/onboarding.test.ts index 5a1df7da1..05ae4d815 100644 --- a/packages/client/src/actions/onboarding.test.ts +++ b/packages/client/src/actions/onboarding.test.ts @@ -1,11 +1,11 @@ import { account } from '@lens-protocol/metadata'; -import { assertOk } from '@lens-protocol/types'; -import { describe, it } from 'vitest'; +import { assertOk, never } from '@lens-protocol/types'; +import { describe, expect, it } from 'vitest'; -import { loginAsOnboardingUser, signerWallet } from '../../testing-utils'; +import { type Account, Role } from '@lens-protocol/graphql'; +import { loginAsOnboardingUser, signer, signerWallet } from '../../testing-utils'; import { handleWith } from '../viem'; import { createAccountWithUsername, fetchAccount } from './account'; -import { switchAccount } from './authentication'; const walletClient = signerWallet(); const metadata = account({ @@ -15,30 +15,50 @@ const metadata = account({ describe('Given an onboarding user', () => { describe('When switching to the newly created account', () => { - it.skip('Then it should be authenticated', async () => { + it('Then it should be authenticated', { timeout: 60000 }, async () => { + let newAccount: Account | null = null; + // Login as onboarding user - const result = await loginAsOnboardingUser().andThen((sessionClient) => - // Create an account with username - createAccountWithUsername(sessionClient, { - username: { localName: `testname${Date.now()}` }, - metadataUri: `data:application/json,${JSON.stringify(metadata)}`, - }) - // Sign if necessary - .andThen(handleWith(walletClient)) + const sessionClient = await loginAsOnboardingUser() + .andThen((sessionClient) => + // Create an account with username + createAccountWithUsername(sessionClient, { + username: { localName: `testname${Date.now()}` }, + metadataUri: `data:application/json,${JSON.stringify(metadata)}`, + }) + // Sign if necessary + // biome-ignore lint/suspicious/noExplicitAny: temporary + .andThen(handleWith(walletClient) as any) + + // Wait for the transaction to be mined + // biome-ignore lint/suspicious/noExplicitAny: temporary + .andThen(sessionClient.waitForTransaction as any) - // Wait for the transaction to be mined - .andThen(sessionClient.waitForTransaction) + // Fetch the account + .andThen((txHash) => fetchAccount(sessionClient, { txHash })) - // Fetch the account - .andThen((txHash) => fetchAccount(sessionClient, { txHash })) + .andTee((account) => { + newAccount = account ?? never('Account not found'); + }) - // Switch to the newly created account - .andThen((account) => switchAccount(sessionClient, { account: account?.address })), - ); + // Switch to the newly created account + .andThen((account) => sessionClient.switchAccount({ account: account?.address })), + ) + .match( + (value) => value, + (error) => { + throw error; + }, + ); - console.log(result); + const user = await sessionClient.getAuthenticatedUser(); + assertOk(user); - assertOk(result); + expect(user.value).toMatchObject({ + role: Role.AccountOwner, + account: newAccount!.address.toLowerCase(), + owner: signer.toLowerCase(), + }); }); }); }); diff --git a/packages/client/src/clients.test.ts b/packages/client/src/clients.test.ts index 2e55d5039..ac15012e3 100644 --- a/packages/client/src/clients.test.ts +++ b/packages/client/src/clients.test.ts @@ -92,6 +92,8 @@ describe(`Given an instance of the ${PublicClient.name}`, () => { environment: { backend: url('http://127.0.0.1'), name: 'broken', + indexingTimeout: 1000, + pollingInterval: 1000, }, origin: 'http://example.com', }); diff --git a/packages/client/src/clients.ts b/packages/client/src/clients.ts index b2ac14433..394949d40 100644 --- a/packages/client/src/clients.ts +++ b/packages/client/src/clients.ts @@ -29,8 +29,9 @@ import { } from '@urql/core'; import { type Logger, getLogger } from 'loglevel'; +import type { SwitchAccountRequest } from '@lens-protocol/graphql'; import { type AuthenticatedUser, authenticatedUser } from './AuthenticatedUser'; -import { transactionStatus } from './actions'; +import { switchAccount, transactionStatus } from './actions'; import type { ClientConfig } from './config'; import { type Context, configureContext } from './context'; import { @@ -345,6 +346,33 @@ class SessionClient extends AbstractClient< return true; } + /** + * Switch authenticated account to a new account. + * + * You MUST be authenticated as Onboarding User, Account Owner, or Account Manager to be able to switch. + * The signer associated with the current session MUST be the owner or a manager of the account. + * + * @returns The updated SessionClient if the switch was successful. + */ + switchAccount( + request: SwitchAccountRequest, + ): ResultAsync< + SessionClient, + AuthenticationError | UnauthenticatedError | UnexpectedError + > { + return switchAccount(this, request) + .andThen((result) => { + if (result.__typename === 'ForbiddenError') { + return AuthenticationError.from(result.reason).asResultAsync(); + } + return okAsync(result); + }) + .map(async (tokens) => { + await this.credentials.set(tokens); + return this; + }); + } + /** * Execute a GraphQL query operation. * diff --git a/packages/client/testing-utils.ts b/packages/client/testing-utils.ts index 74c1f95b3..c65d2901b 100644 --- a/packages/client/testing-utils.ts +++ b/packages/client/testing-utils.ts @@ -4,11 +4,12 @@ import { http, type Account, type Transport, type WalletClient, createWalletClie import { privateKeyToAccount } from 'viem/accounts'; import { PublicClient, testnet } from './src'; -const signer = privateKeyToAccount(import.meta.env.PRIVATE_KEY); -const owner = evmAddress(signer.address); +const pk = privateKeyToAccount(import.meta.env.PRIVATE_KEY); const account = evmAddress(import.meta.env.TEST_ACCOUNT); const app = evmAddress(import.meta.env.TEST_APP); +export const signer = evmAddress(pk.address); + export function loginAsAccountOwner() { const client = PublicClient.create({ environment: testnet, @@ -18,10 +19,10 @@ export function loginAsAccountOwner() { return client.login({ accountOwner: { account, - owner, + owner: signer, app, }, - signMessage: (message) => signer.signMessage({ message }), + signMessage: (message) => pk.signMessage({ message }), }); } @@ -33,10 +34,10 @@ export function loginAsOnboardingUser() { return client.login({ onboardingUser: { - wallet: owner, + wallet: signer, app, }, - signMessage: (message) => signer.signMessage({ message }), + signMessage: (message) => pk.signMessage({ message }), }); } diff --git a/packages/env/src/index.ts b/packages/env/src/index.ts index 42d322995..b3a0dbf09 100644 --- a/packages/env/src/index.ts +++ b/packages/env/src/index.ts @@ -57,6 +57,6 @@ export const staging: EnvironmentConfig = { export const local: EnvironmentConfig = { name: 'local', backend: url('http://localhost:3000/graphql'), - indexingTimeout: 5000, + indexingTimeout: 60000, pollingInterval: 500, };