Skip to content

Commit

Permalink
client: add frames module
Browse files Browse the repository at this point in the history
  • Loading branch information
krzysu committed Mar 21, 2024
1 parent d094fd4 commit f6d4364
Show file tree
Hide file tree
Showing 20 changed files with 1,092 additions and 18 deletions.
26 changes: 26 additions & 0 deletions examples/node/scripts/frames/createFrameTypedData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { FramesEip721TypedDataSpec, LensClient, development } from '@lens-protocol/client';

async function main() {
const client = new LensClient({
environment: development,
});

const deadline = new Date();
deadline.setMinutes(deadline.getMinutes() + 30);

const result = await client.frames.createFrameTypedData({
actionResponse: '0x0000000000000000000000000000000000000000',
buttonIndex: 2,
deadline: deadline.getTime(),
inputText: 'Hello, World!',
profileId: '0x01',
pubId: '0x01-0x01',
specVersion: FramesEip721TypedDataSpec.OnePointOnePointOne,
state: '{"counter":1,"idempotency_key":"431b8b38-eb4d-455b"}',
url: 'https://mylensframe.xyz',
});

console.log(`Result: `, result);
}

main();
31 changes: 31 additions & 0 deletions examples/node/scripts/frames/signFrameAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { FramesEip721TypedDataSpec } from '@lens-protocol/client';

import { getAuthenticatedClient } from '../shared/getAuthenticatedClient';
import { setupWallet } from '../shared/setupWallet';

async function main() {
const wallet = setupWallet();
const client = await getAuthenticatedClient(wallet, '0x58');

const result = await client.frames.signFrameAction({
actionResponse: '0x0000000000000000000000000000000000000000',
buttonIndex: 2,
inputText: 'Hello, World!',
profileId: '0x01',
pubId: '0x01-0x01',
specVersion: FramesEip721TypedDataSpec.OnePointOnePointOne,
state: '{"counter":1,"idempotency_key":"431b8b38-eb4d-455b"}',
url: 'https://mylensframe.xyz',
});

if (result.isFailure()) {
console.error(result.error); // CredentialsExpiredError or NotAuthenticatedError
process.exit(1);
}

const data = result.value;

console.log(`Result: `, data);
}

main();
44 changes: 44 additions & 0 deletions examples/node/scripts/frames/verifyFrameSignature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { FramesEip721TypedDataSpec } from '@lens-protocol/client';

import { getAuthenticatedClient } from '../shared/getAuthenticatedClient';
import { setupWallet } from '../shared/setupWallet';

async function main() {
const wallet = setupWallet();
const client = await getAuthenticatedClient(wallet, '0x58');
const idTokenResult = await client.authentication.getIdentityToken();

const idToken = idTokenResult.unwrap();

// get signature
const result = await client.frames.signFrameAction({
actionResponse: '0x0000000000000000000000000000000000000000',
buttonIndex: 2,
inputText: 'Hello, World!',
profileId: '0x01',
pubId: '0x01-0x01',
specVersion: FramesEip721TypedDataSpec.OnePointOnePointOne,
state: '{"counter":1,"idempotency_key":"431b8b38-eb4d-455b"}',
url: 'https://mylensframe.xyz',
});

if (result.isFailure()) {
console.error(result.error); // CredentialsExpiredError or NotAuthenticatedError
process.exit(1);
}

const data = result.value;

console.log(`SignFrameAction result: `, data);

// verify
const verifyResult = await client.frames.verifyFrameSignature({
identityToken: idToken,
signature: data.signature,
signedTypedData: data.signedTypedData,
});

console.log(`Is signature valid? `, verifyResult);
}

main();
8 changes: 8 additions & 0 deletions packages/client/src/LensClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { QueryParams } from './queryParams';
import {
Explore,
Feed,
Frames,
Handle,
Invites,
Modules,
Expand Down Expand Up @@ -108,6 +109,13 @@ export class LensClient {
return new Feed(this.context, this._authentication);
}

/**
* The Frames module
*/
get frames(): Frames {
return new Frames(this.context, this._authentication);
}

/**
* The Handle module
*/
Expand Down
28 changes: 27 additions & 1 deletion packages/client/src/authentication/Authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class Authentication implements IAuthentication {
}

async authenticateWith({ refreshToken }: { refreshToken: string }): Promise<void> {
const credentials = new Credentials(undefined, refreshToken);
const credentials = new Credentials(undefined, undefined, refreshToken);
await this.credentials.set(credentials);
}

Expand All @@ -59,6 +59,7 @@ export class Authentication implements IAuthentication {
});
}

// TODO: add identity token
async verify(accessToken: string): Promise<boolean> {
return this.api.verify(accessToken);
}
Expand Down Expand Up @@ -109,6 +110,31 @@ export class Authentication implements IAuthentication {
return failure(new CredentialsExpiredError());
}

async getIdentityToken(): PromiseResult<string, CredentialsExpiredError | NotAuthenticatedError> {
const credentials = await this.credentials.get();

if (!credentials) {
return failure(new NotAuthenticatedError());
}

if (!credentials.shouldRefresh() && credentials.identityToken) {
return success(credentials.identityToken);
}

if (credentials.canRefresh()) {
const newCredentials = await this.api.refresh(credentials.refreshToken);
await this.credentials.set(newCredentials);

if (!newCredentials.identityToken) {
return failure(new CredentialsExpiredError());
}

return success(newCredentials.identityToken);
}

return failure(new CredentialsExpiredError());
}

async getProfileId(): Promise<string | null> {
const result = await this.getCredentials();

Expand Down
7 changes: 7 additions & 0 deletions packages/client/src/authentication/IAuthentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ export interface IAuthentication {
*/
getAccessToken(): PromiseResult<string, CredentialsExpiredError | NotAuthenticatedError>;

/**
* Get the identity token. If it expired, try to refresh it.
*
* @returns A Result with the identity token or possible error scenarios
*/
getIdentityToken(): PromiseResult<string, CredentialsExpiredError | NotAuthenticatedError>;

/**
* Get the authenticated profile id.
*
Expand Down
16 changes: 8 additions & 8 deletions packages/client/src/authentication/adapters/AuthenticationApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,19 @@ export class AuthenticationApi {

async authenticate(request: SignedAuthChallenge): Promise<Credentials> {
const result = await this.sdk.AuthAuthenticate({ request });
const { accessToken, refreshToken } = result.data.result;
const { accessToken, identityToken, refreshToken } = result.data.result;

const credentials = new Credentials(accessToken, refreshToken);
const credentials = new Credentials(accessToken, identityToken, refreshToken);
credentials.checkClock();

return credentials;
}

async refresh(refreshToken: string): Promise<Credentials> {
const result = await this.sdk.AuthRefresh({ request: { refreshToken } });
const { accessToken: newAccessToken, refreshToken: newRefreshToken } = result.data.result;
async refresh(currentRefreshToken: string): Promise<Credentials> {
const result = await this.sdk.AuthRefresh({ request: { refreshToken: currentRefreshToken } });
const { accessToken, identityToken, refreshToken } = result.data.result;

const credentials = new Credentials(newAccessToken, newRefreshToken);
const credentials = new Credentials(accessToken, identityToken, refreshToken);
credentials.checkClock();

return credentials;
Expand All @@ -64,9 +64,9 @@ export class AuthenticationApi {
headers?: Record<string, string>,
): Promise<Credentials> {
const result = await this.sdk.WalletAuthenticationToProfileAuthentication({ request }, headers);
const { accessToken, refreshToken } = result.data.result;
const { accessToken, identityToken, refreshToken } = result.data.result;

const credentials = new Credentials(accessToken, refreshToken);
const credentials = new Credentials(accessToken, identityToken, refreshToken);

return credentials;
}
Expand Down
14 changes: 12 additions & 2 deletions packages/client/src/authentication/adapters/Credentials.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const profileSession = {
refreshToken:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjB4NTYiLCJldm1BZGRyZXNzIjoiMHgzZkM0N2NkRGNGZDU5ZGNlMjA2OTRiNTc1QUZjMUQ5NDE4Njc3NWIwIiwicm9sZSI6InByb2ZpbGVfcmVmcmVzaCIsImF1dGhvcml6YXRpb25JZCI6IjA0NWM2N2I2LWIzNTEtNDVjOS1hNWE1LWM0YWQ5ODg5ZDYyYyIsImlhdCI6MTcwMTM1MzA5NiwiZXhwIjoxNzAxOTU3ODk2fQ.i2kzT4I6VBTuZvjly0TEdGN_YsuBaTDopMQU4_398kA',
refreshTokenExp: 1701957896000,
identityToken: '',
};

const walletSession = {
Expand All @@ -18,14 +19,23 @@ const walletSession = {
refreshToken:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjB4YTU2NTNlODhEOWMzNTIzODdkZURkQzc5YmNmOTlmMGFkYTYyZTljNiIsInJvbGUiOiJ3YWxsZXRfcmVmcmVzaCIsImF1dGhvcml6YXRpb25JZCI6IjMxMWVkNzNkLTZjYjMtNDRmZi05NzdmLTJjMmM1MjI4YWJiNCIsImlhdCI6MTcwMTM1NTA3NywiZXhwIjoxNzAxOTU5ODc3fQ.WTUpWsH-Fvv8U4WIwL_Sk6cpHvRSGY_vdBsy1IQrCmM',
refreshTokenExp: 1701959877000,
identityToken: '',
};

const buildProfileCredentials = () => {
return new Credentials(profileSession.accessToken, profileSession.refreshToken);
return new Credentials(
profileSession.accessToken,
profileSession.identityToken,
profileSession.refreshToken,
);
};

const buildWalletCredentials = () => {
return new Credentials(walletSession.accessToken, walletSession.refreshToken);
return new Credentials(
walletSession.accessToken,
walletSession.identityToken,
walletSession.refreshToken,
);
};

describe(`Given the ${Credentials.name} class`, () => {
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/authentication/adapters/Credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function isProfileJwtContent(decodedJwt: DecodedJwt): decodedJwt is ProfileJwtPa
export class Credentials {
constructor(
readonly accessToken: string | undefined,
readonly identityToken: string | undefined,
readonly refreshToken: string,
) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CredentialsStorage } from './CredentialsStorage';

const accessToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjB4YjE5QzI4OTBjZjk0N0FEM2YwYjdkN0U1QTlmZkJjZTM2ZDNmOWJkMiIsInJvbGUiOiJub3JtYWwiLCJpYXQiOjE2Mzc3NTQ2ODEsImV4cCI6MTYzNzc1NDc0MX0.Be1eGBvVuFL4fj4pHHqc0yWDledsgS2GP3Jgonmy-xw';
const identityToken = '';
const refreshToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjB4YjE5QzI4OTBjZjk0N0FEM2YwYjdkN0U1QTlmZkJjZTM2ZDNmOWJkMiIsInJvbGUiOiJyZWZyZXNoIiwiaWF0IjoxNjM3NzU0NjgxLCJleHAiOjE2Mzc3NTQ5ODF9.3SqgsVMyqFPBcem2W9Iog91SWC8cIAFixXBkDue73Rc';

Expand All @@ -13,7 +14,7 @@ describe(`Given the ${CredentialsStorage.name} class`, () => {
it(`should make the credentials available with ${CredentialsStorage.prototype.get.name} method`, async () => {
const storage = new CredentialsStorage(new InMemoryStorageProvider(), 'namespace');

const creds = new Credentials(accessToken, refreshToken);
const creds = new Credentials(accessToken, identityToken, refreshToken);
await storage.set(creds);

const storedCreds = await storage.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ import { Credentials } from './Credentials';
export class CredentialsStorage implements IStorage<Credentials> {
private refreshTokenStorage: IStorage<PersistedCredentials>;
private accessToken: string | undefined;
private identityToken: string | undefined;

constructor(storageProvider: IStorageProvider, namespace: string) {
const authStorageSchema = new CredentialsStorageSchema(`lens.${namespace}.credentials`);
this.refreshTokenStorage = Storage.createForSchema(authStorageSchema, storageProvider);
}

async set({ accessToken, refreshToken }: Credentials): Promise<void> {
async set({ accessToken, identityToken, refreshToken }: Credentials): Promise<void> {
this.accessToken = accessToken;
this.identityToken = identityToken;

await this.refreshTokenStorage.set({ refreshToken });
}
Expand All @@ -38,12 +40,14 @@ export class CredentialsStorage implements IStorage<Credentials> {
}

const accessToken = this.accessToken;
const identityToken = this.identityToken;

return new Credentials(accessToken, refreshToken);
return new Credentials(accessToken, identityToken, refreshToken);
}

async reset(): Promise<void> {
this.accessToken = undefined;
this.identityToken = undefined;
await this.refreshTokenStorage.reset();
}

Expand Down
13 changes: 10 additions & 3 deletions packages/client/src/authentication/graphql/auth.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,24 @@ export type AuthAuthenticateMutationVariables = Types.Exact<{
request: Types.SignedAuthChallenge;
}>;

export type AuthAuthenticateMutation = { result: { accessToken: string; refreshToken: string } };
export type AuthAuthenticateMutation = {
result: { accessToken: string; refreshToken: string; identityToken: string };
};

export type AuthRefreshMutationVariables = Types.Exact<{
request: Types.RefreshRequest;
}>;

export type AuthRefreshMutation = { result: { accessToken: string; refreshToken: string } };
export type AuthRefreshMutation = {
result: { accessToken: string; refreshToken: string; identityToken: string };
};

export type WalletAuthenticationToProfileAuthenticationMutationVariables = Types.Exact<{
request: Types.WalletAuthenticationToProfileAuthenticationRequest;
}>;

export type WalletAuthenticationToProfileAuthenticationMutation = {
result: { accessToken: string; refreshToken: string };
result: { accessToken: string; refreshToken: string; identityToken: string };
};

export type RevokeAuthenticationMutationVariables = Types.Exact<{
Expand Down Expand Up @@ -388,6 +392,7 @@ export const AuthAuthenticateDocument = {
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'accessToken' } },
{ kind: 'Field', name: { kind: 'Name', value: 'refreshToken' } },
{ kind: 'Field', name: { kind: 'Name', value: 'identityToken' } },
],
},
},
Expand Down Expand Up @@ -432,6 +437,7 @@ export const AuthRefreshDocument = {
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'accessToken' } },
{ kind: 'Field', name: { kind: 'Name', value: 'refreshToken' } },
{ kind: 'Field', name: { kind: 'Name', value: 'identityToken' } },
],
},
},
Expand Down Expand Up @@ -479,6 +485,7 @@ export const WalletAuthenticationToProfileAuthenticationDocument = {
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'accessToken' } },
{ kind: 'Field', name: { kind: 'Name', value: 'refreshToken' } },
{ kind: 'Field', name: { kind: 'Name', value: 'identityToken' } },
],
},
},
Expand Down
3 changes: 3 additions & 0 deletions packages/client/src/authentication/graphql/auth.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@ mutation AuthAuthenticate($request: SignedAuthChallenge!) {
result: authenticate(request: $request) {
accessToken
refreshToken
identityToken
}
}

mutation AuthRefresh($request: RefreshRequest!) {
result: refresh(request: $request) {
accessToken
refreshToken
identityToken
}
}

Expand All @@ -63,6 +65,7 @@ mutation WalletAuthenticationToProfileAuthentication(
result: walletAuthenticationToProfileAuthentication(request: $request) {
accessToken
refreshToken
identityToken
}
}

Expand Down
Loading

0 comments on commit f6d4364

Please sign in to comment.