diff --git a/src/index.ts b/src/index.ts index 981cb02..8de3817 100644 --- a/src/index.ts +++ b/src/index.ts @@ -45,6 +45,10 @@ async function main() { }; }, }); + const renewClient = new Client({ + url: conf.mercuryGraphQL, + exchanges: [fetchExchange], + }); const mercurySession = { token: conf.mercuryKey, backend: conf.mercuryBackend, @@ -52,7 +56,13 @@ async function main() { password: conf.mercuryPassword, userId: conf.mercuryUserId, }; - const mercuryClient = new MercuryClient(mercurySession, client, logger); + const mercuryClient = new MercuryClient( + conf.mercuryGraphQL, + mercurySession, + client, + renewClient, + logger + ); const server = initApiServer(mercuryClient, conf); try { diff --git a/src/service/mercury/index.ts b/src/service/mercury/index.ts index a86d4a6..1bb76c2 100644 --- a/src/service/mercury/index.ts +++ b/src/service/mercury/index.ts @@ -1,9 +1,19 @@ -import { Client } from "@urql/core"; -import axios, { AxiosError } from "axios"; +import { Client, CombinedError, fetchExchange } from "@urql/core"; +import axios from "axios"; import { Logger } from "pino"; import { Address, nativeToScVal, xdr } from "soroban-client"; import { mutation, query } from "./queries"; +const ERROR_MESSAGES = { + JWT_EXPIRED: "jwt expired", +}; + +function getGraphQlError(error?: CombinedError) { + if (!error) return; + const [err] = error.graphQLErrors; + return err.message; +} + export interface NewEventSubscriptionPayload { contract_id?: string; max_single_size: number; @@ -25,21 +35,27 @@ interface MercurySession { } export class MercuryClient { + mercuryUrl: string; urqlClient: Client; + renewClient: Client; mercurySession: MercurySession; eventsURL: string; entryURL: string; logger: Logger; constructor( + mercuryUrl: string, mercurySession: MercurySession, urqlClient: Client, + renewClient: Client, logger: Logger ) { + this.mercuryUrl = mercuryUrl; this.mercurySession = mercurySession; this.eventsURL = `${mercurySession.backend}/event`; this.entryURL = `${mercurySession.backend}/entry`; this.urqlClient = urqlClient; + this.renewClient = renewClient; this.logger = logger; } @@ -53,10 +69,29 @@ export class MercuryClient { renewMercuryToken = async () => { try { - const { data } = await this.urqlClient.query(mutation.authenticate, { - email: this.mercurySession.email, - password: this.mercurySession.password, + const { data, error } = await this.renewClient.mutation( + mutation.authenticate, + { + email: this.mercurySession.email, + password: this.mercurySession.password, + } + ); + + if (error) { + throw new Error(getGraphQlError(error)); + } + + // rebuild client and hold onto new token for subscription rest calls + const client = new Client({ + url: this.mercuryUrl, + exchanges: [fetchExchange], + fetchOptions: () => { + return { + headers: { authorization: `Bearer ${data.authenticate.jwtToken}` }, + }; + }, }); + this.urqlClient = client; this.mercurySession.token = data.authenticate.jwtToken; return { @@ -65,7 +100,7 @@ export class MercuryClient { }; } catch (error) { const _error = JSON.stringify(error); - this.logger.error(_error); + this.logger.error(error); return { data: null, error: _error, @@ -77,16 +112,16 @@ export class MercuryClient { try { return await method(); } catch (error: unknown) { - let status = 400; - if (error instanceof AxiosError) { - status = error.response?.status || 400; + if (error instanceof Error) { + if (error.message === ERROR_MESSAGES.JWT_EXPIRED) { + await this.renewMercuryToken(); + this.logger.info("renewed expired jwt"); + return await method(); + } + this.logger.error(error.message); + throw new Error(error.message); } - if (status === 401) { - this.logger.debug("renewing jwt, and retrying"); - await this.renewMercuryToken(); - return await method(); - } const _error = JSON.stringify(error); this.logger.error(_error); throw new Error(_error); @@ -229,8 +264,13 @@ export class MercuryClient { try { const getData = async () => { const data = await this.urqlClient.query(query.getAccountHistory, { - publicKeyText: pubKey, + pubKey, }); + const errorMessage = getGraphQlError(data.error); + if (errorMessage) { + throw new Error(errorMessage); + } + return data; }; const data = await this.renewAndRetry(getData); @@ -241,7 +281,7 @@ export class MercuryClient { }; } catch (error) { const _error = JSON.stringify(error); - this.logger.error(_error); + this.logger.error(error); return { data: null, error: _error, diff --git a/src/service/mercury/queries.ts b/src/service/mercury/queries.ts index 2080fc3..eebbd51 100644 --- a/src/service/mercury/queries.ts +++ b/src/service/mercury/queries.ts @@ -1,15 +1,15 @@ export const mutation = { authenticate: ` - mutation Auth { + mutation Auth($email: String!, $password: String!) { authenticate(input: {email: $email, password: $password}) { jwtToken } } `, newAccountSubscription: ` - mutation NewAccountSubscription { + mutation NewAccountSubscription($pubKey: String!, $userId: String!) { createFullAccountSubscription( - input: {fullAccountSubscription: {$pubKey: PUBKEY!, $userId: USER_ID!}} + input: {fullAccountSubscription: pubKey: $pubKey, userId: $userId}} ) { fullAccountSubscription { publickey @@ -36,7 +36,7 @@ export const query = { ${contractIds.map( (id) => ` - entryUpdateByContractIdAndKey(ledgerKey: ${ledgerKey}, contract: ${id}) { + entryUpdateByContractIdAndKey(ledgerKey: $${ledgerKey}, contract: $${id}) { nodes { contractId keyXdr @@ -51,8 +51,8 @@ export const query = { } `, getAccountHistory: ` - query GetAccountHistory { - eventByTopic(t1: "AAAADgAAAARtaW50") { + query GetAccountHistory($pubKey: String!) { + mintEvent: eventByTopic(t1: "AAAADgAAAARtaW50") { edges { node { contractId @@ -66,7 +66,8 @@ export const query = { } } } - eventByTopic(t1: "AAAADgAAAAh0cmFuc2Zlcg==", $t2: PUBKEY!) { + + transferToEvent: eventByTopic(t1: "AAAADgAAAAh0cmFuc2Zlcg==", t2: $pubKey) { edges { node { contractId @@ -80,7 +81,7 @@ export const query = { } } } - eventByTopic(t1: "AAAADgAAAAh0cmFuc2Zlcg==", $t3: PUBKEY!) { + transferFromEvent: eventByTopic(t1: "AAAADgAAAAh0cmFuc2Zlcg==", t3: $pubKey) { edges { node { contractId @@ -94,17 +95,21 @@ export const query = { } } } - createAccountByPublicKey($publicKeyText: PUBKEY!) { + createAccountByPublicKey(publicKeyText: $pubKey) { edges { - node + node { + destination + } } } - createAccountToPublicKey($publicKeyText: PUBKEY!) { + createAccountToPublicKey(publicKeyText: $pubKey) { edges { - node + node { + destination + } } } - paymentsByPublicKey($publicKeyText: PUBKEY!) { + paymentsByPublicKey(publicKeyText: $pubKey) { edges { node { amount @@ -122,7 +127,7 @@ export const query = { } } } - paymentsToPublicKey($publicKeyText: PUBKEY!) { + paymentsToPublicKey(publicKeyText: $pubKey) { edges { node { amount @@ -140,6 +145,7 @@ export const query = { } } } + } `, };