Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/SmartTransactionsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2194,6 +2194,48 @@ describe('SmartTransactionsController', () => {
expect(apiCall.isDone()).toBe(true);
});
});

it('sends Authorization header when getBearerToken is provided', async () => {
const bearerToken = 'test-bearer-token-123';
await withController(
{
options: {
getBearerToken: async () => Promise.resolve(bearerToken),
},
},
async ({ controller }) => {
const apiCall = nock(API_BASE_URL)
.post(`/networks/${ethereumChainIdDec}/cancel`)
.matchHeader('Authorization', `Bearer ${bearerToken}`)
.reply(200, { message: 'successful' });

await controller.cancelSmartTransaction('uuid1');

expect(apiCall.isDone()).toBe(true);
},
);
});

it('sends Authorization header to Sentinel /network when getBearerToken is provided', async () => {
const bearerToken = 'test-bearer-token-456';
await withController(
{
options: {
getBearerToken: async () => Promise.resolve(bearerToken),
},
},
async ({ controller }) => {
const apiCall = nock(SENTINEL_API_BASE_URL_MAP[ethereumChainIdDec])
.get(`/network`)
.matchHeader('Authorization', `Bearer ${bearerToken}`)
.reply(200, createSuccessLivenessApiResponse());

await controller.fetchLiveness();

expect(apiCall.isDone()).toBe(true);
},
);
});
});

describe('getTransactions', () => {
Expand Down
39 changes: 37 additions & 2 deletions src/SmartTransactionsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@
import cloneDeep from 'lodash/cloneDeep';

import {
API_BASE_URL,
DEFAULT_DISABLED_SMART_TRANSACTIONS_FEATURE_FLAGS,
MetaMetricsEventCategory,
MetaMetricsEventName,
SENTINEL_API_BASE_URL_MAP,
SmartTransactionsTraceName,
} from './constants';
import {
Expand Down Expand Up @@ -230,6 +232,14 @@
* removed in a future version.
*/
getFeatureFlags?: () => FeatureFlags;
/**
* Optional callback to obtain a bearer token for authenticating requests to
* the Transaction API. When provided, the token is sent in the
* Authorization header for all Transaction API calls. Can be used with
* the authentication flow from @metamask/core-backend (e.g. from
* AuthenticationController.getBearerToken).
*/
getBearerToken?: () => Promise<string | undefined> | string | undefined;
trace?: TraceCallback;
};

Expand Down Expand Up @@ -258,6 +268,11 @@

readonly #getMetaMetricsProps: () => Promise<MetaMetricsProps>;

readonly #getBearerToken?: () =>
| Promise<string | undefined>
| string
| undefined;

#trace: TraceCallback;

/**
Expand Down Expand Up @@ -292,11 +307,28 @@

/* istanbul ignore next */
async #fetch(request: string, options?: RequestInit) {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(this.#clientId && { 'X-Client-Id': this.#clientId }),
};

const urlMatches =
request.startsWith(API_BASE_URL) ||
Object.values(SENTINEL_API_BASE_URL_MAP).some((baseUrl) =>
request.startsWith(baseUrl),
);
if (this.#getBearerToken && urlMatches) {
const token = await Promise.resolve(this.#getBearerToken());
if (token) {
headers.Authorization = `Bearer ${token}`;
}
}

const fetchOptions = {
...options,
headers: {
'Content-Type': 'application/json',
...(this.#clientId && { 'X-Client-Id': this.#clientId }),
...headers,
...options?.headers,
},
};

Expand All @@ -312,6 +344,7 @@
state = {},
messenger,
getMetaMetricsProps,
getBearerToken,
trace,
}: SmartTransactionsControllerOptions) {
super({
Expand All @@ -323,6 +356,7 @@
...state,
},
});

this.#interval = interval;
this.#clientId = clientId;
this.#chainId = InitialChainId;
Expand All @@ -331,6 +365,7 @@
this.#ethQuery = undefined;
this.#trackMetaMetricsEvent = trackMetaMetricsEvent;
this.#getMetaMetricsProps = getMetaMetricsProps;
this.#getBearerToken = getBearerToken;
this.#trace = trace ?? (((_request, fn) => fn?.()) as TraceCallback);

this.initializeSmartTransactionsForChainId();
Expand Down Expand Up @@ -389,9 +424,9 @@
isSmartTransactionPending,
);
if (!this.timeoutHandle && pendingTransactions?.length > 0) {
this.poll();

Check warning on line 427 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 427 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
} else if (this.timeoutHandle && pendingTransactions?.length === 0) {
this.stop();

Check warning on line 429 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 429 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
}
}

Expand All @@ -416,7 +451,7 @@
}

this.timeoutHandle = setInterval(() => {
safelyExecute(async () => this.updateSmartTransactions());

Check warning on line 454 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 454 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
}, this.#interval);
await safelyExecute(async () => this.updateSmartTransactions());
}
Expand Down Expand Up @@ -483,7 +518,7 @@
ethQuery = new EthQuery(provider);
}

this.#createOrUpdateSmartTransaction(smartTransaction, {

Check warning on line 521 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 521 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
chainId,
ethQuery,
});
Expand Down
Loading