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
9 changes: 9 additions & 0 deletions src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,15 @@
"connectWallet_error_timeout": {
"message": "Connecting to wallet took too long. Please try again."
},
"connectWallet_error_hashFailed": {
"message": "Connection failed. Please try again."
},
"connectWallet_error_continuationFailed": {
"message": "Connection failed. Please try again."
},
"connectWallet_error_grantInvalid": {
"message": "Connection failed. Please try again."
},
"connectWalletKeyService_text_consentP1": {
"message": "We will automatically connect with your wallet provider."
},
Expand Down
76 changes: 18 additions & 58 deletions src/background/services/outgoingPaymentGrant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,7 @@ import {
isInvalidContinuationError,
isNotFoundError,
} from '@/background/services/openPayments';
import {
createTabIfNotExists,
ErrorCode,
GrantResult,
InteractionIntent,
redirectToWelcomeScreen,
} from '@/background/utils';
import { createTabIfNotExists, InteractionIntent } from '@/background/utils';

interface InteractionParams {
interactRef: string;
Expand Down Expand Up @@ -132,14 +126,13 @@ export class OutgoingPaymentGrantService {
walletAmount: WalletAmount,
walletAddress: WalletAddress,
{ grant, nonce }: Awaited<ReturnType<this['createOutgoingPaymentGrant']>>,
intent: InteractionIntent,
onTabOpen: (tabId: TabId) => void,
existingTabId?: TabId,
timeout = ACCEPT_GRANT_TIMEOUT,
): Promise<GrantDetails> {
const signal = AbortSignal.timeout(timeout);

const { interactRef, hash, tabId } = await this.getInteractionInfo(
const { interactRef, hash } = await this.getInteractionInfo(
grant.interact.redirect,
onTabOpen,
signal,
Expand All @@ -152,17 +145,10 @@ export class OutgoingPaymentGrantService {
interactRef,
hash,
walletAddress.authServer,
intent,
tabId,
);
signal.throwIfAborted();

const continuation = await this.continueGrant(
grant,
interactRef,
intent,
tabId,
);
const continuation = await this.continueGrant(grant, interactRef);
if (!isFinalizedGrantWithAccessToken(continuation)) {
throw new Error(
'Expected finalized grant. Received non-finalized grant.',
Expand All @@ -173,13 +159,6 @@ export class OutgoingPaymentGrantService {
this.grant = this.buildGrantDetails(continuation, walletAmount);
await this.persistGrantDetails(this.grant);

await redirectToWelcomeScreen(
this.browser,
tabId,
GrantResult.GRANT_SUCCESS,
intent,
);

return this.grant;
}

Expand Down Expand Up @@ -336,6 +315,8 @@ export class OutgoingPaymentGrantService {
resolve({ interactRef, hash, tabId });
} else if (result === 'grant_rejected') {
reject(new ErrorWithKey('connectWallet_error_grantRejected'));
} else if (result === 'grant_invalid') {
reject(new ErrorWithKey('connectWallet_error_grantInvalid'));
}
} catch {
/* do nothing */
Expand All @@ -354,29 +335,17 @@ export class OutgoingPaymentGrantService {
interactRef: string,
hash: string,
authServer: string,
intent: InteractionIntent,
tabId: TabId,
) {
const computeHash = (authServer: string) =>
this.computeHash(clientNonce, interactionNonce, interactRef, authServer);
try {
if (hash === (await computeHash(authServer))) return;
// See https://github.com/interledger/web-monetization-extension/pull/1230
this.logger.warn(
'verifyInteractionHash failed with authServer without trailing slash',
);
if (hash === (await computeHash(ensureEnd(authServer, '/')))) return;
throw new Error('Invalid interaction hash');
} catch (error) {
await redirectToWelcomeScreen(
this.browser,
tabId,
GrantResult.GRANT_ERROR,
intent,
ErrorCode.HASH_FAILED,
);
throw error;
}

if (hash === (await computeHash(authServer))) return;
// See https://github.com/interledger/web-monetization-extension/pull/1230
this.logger.warn(
'verifyInteractionHash failed with authServer without trailing slash',
);
if (hash === (await computeHash(ensureEnd(authServer, '/')))) return;
throw new ErrorWithKey('connectWallet_error_hashFailed');
}

private computeHash = async (
Expand All @@ -392,12 +361,7 @@ export class OutgoingPaymentGrantService {
return btoa(String.fromCharCode.apply(null, new Uint8Array(digest)));
};

private async continueGrant(
grant: PendingGrant,
interactRef: string,
intent: InteractionIntent,
tabId: TabId,
) {
private async continueGrant(grant: PendingGrant, interactRef: string) {
try {
const continuation = await this.openPaymentsService.client.grant.continue(
{
Expand All @@ -409,14 +373,10 @@ export class OutgoingPaymentGrantService {

return continuation;
} catch (error) {
await redirectToWelcomeScreen(
this.browser,
tabId,
GrantResult.GRANT_ERROR,
intent,
ErrorCode.CONTINUATION_FAILED,
);
throw error;
this.logger.error('connectWallet_error_continuationFailed', {
cause: error,
});
throw new ErrorWithKey('connectWallet_error_continuationFailed');
}
}

Expand Down
55 changes: 51 additions & 4 deletions src/background/services/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ export class WalletService {
walletAmount,
walletAddress,
grant,
intent,
(openedTabId) => {
tabId = openedTabId;
cleanupListeners = highlightTabOnPopupOpen(browser, tabId);
Expand All @@ -195,11 +194,19 @@ export class WalletService {
cleanupListeners();
if (isAbortSignalTimeout(error)) {
onTimeoutAbort();
} else if (isErrorWithKey(error)) {
await this.handleGrantCompletionError(error, intent, tabId!);
}
this.setConnectStateError(error);
throw error;
}

await redirectToWelcomeScreen(
this.browser,
tabId,
GrantResult.GRANT_SUCCESS,
intent,
);
await this.storage.set({
walletAddress,
rateOfPay,
Expand Down Expand Up @@ -317,7 +324,6 @@ export class WalletService {
walletAmount,
walletAddress,
grant,
intent,
(openedTabId) => {
tabId = openedTabId;
},
Expand All @@ -326,12 +332,21 @@ export class WalletService {
if (isAbortSignalTimeout(error)) {
await this.redirectOnTimeout(intent, tabId);
throw new ErrorWithKey('connectWallet_error_timeout');
} else if (isErrorWithKey(error)) {
await this.handleGrantCompletionError(error, intent, tabId!);
}
throw error;
}

await this.storage.setState({ out_of_funds: false });

await redirectToWelcomeScreen(
this.browser,
tabId,
GrantResult.GRANT_SUCCESS,
intent,
);

// cancel existing grants of same type, if any
if (grants.oneTimeGrant && !recurring) {
await this.outgoingPaymentGrantService.cancelGrant(
Expand Down Expand Up @@ -372,7 +387,6 @@ export class WalletService {
walletAmount,
walletAddress,
grant,
intent,
(openedTabId) => {
tabId = openedTabId;
},
Expand All @@ -381,10 +395,19 @@ export class WalletService {
if (isAbortSignalTimeout(error)) {
await this.redirectOnTimeout(intent, tabId);
throw new ErrorWithKey('connectWallet_error_timeout');
} else if (isErrorWithKey(error)) {
await this.handleGrantCompletionError(error, intent, tabId!);
}
throw error;
}

await redirectToWelcomeScreen(
this.browser,
tabId,
GrantResult.GRANT_SUCCESS,
intent,
);

// Revoke all existing grants.
// Note: Clear storage only if new grant type is not same as previous grant
// type (as completeGrant already sets new grant state)
Expand Down Expand Up @@ -461,7 +484,7 @@ export class WalletService {
: ErrorCode.KEY_ADD_FAILED,
);
}
if (error instanceof ErrorWithKey) {
if (isErrorWithKey(error)) {
throw error;
} else {
// TODO: check if need to handle errors here
Expand Down Expand Up @@ -528,6 +551,30 @@ export class WalletService {
);
}

private async handleGrantCompletionError(
error: ErrorWithKeyLike,
intent: InteractionIntent,
tabId: TabId,
) {
if (error.key === 'connectWallet_error_tabClosed') {
return;
}

let code: ErrorCode | undefined;
if (error.key === 'connectWallet_error_hashFailed') {
code = ErrorCode.HASH_FAILED;
} else if (error.key === 'connectWallet_error_continuationFailed') {
code = ErrorCode.CONTINUATION_FAILED;
}
await redirectToWelcomeScreen(
this.browser,
tabId,
GrantResult.GRANT_ERROR,
intent,
code,
);
}

public resetConnectState() {
this.storage.setTransientState('connect', () => null);
}
Expand Down
Loading