Skip to content
Open
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
4 changes: 4 additions & 0 deletions .github/workflows/nightly-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ jobs:
CHIMONEY_APP_WALLET_ADDRESS_URL: ${{ vars.E2E_CHIMONEY_APP_WALLET_ADDRESS_URL }}
CHIMONEY_APP_USERNAME: ${{ vars.E2E_CHIMONEY_APP_USERNAME }}
CHIMONEY_APP_PASSWORD: ${{ secrets.E2E_CHIMONEY_APP_PASSWORD }}
MMAON_WALLET_ORIGIN: ${{ vars.E2E_MMAON_WALLET_URL_ORIGIN }}
MMAON_WALLET_ADDRESS_URL: ${{ vars.E2E_MMAON_WALLET_ADDRESS_URL }}
MMAON_USERNAME: ${{ vars.E2E_MMAON_USERNAME }}
MMAON_PASSWORD: ${{ secrets.E2E_MMAON_PASSWORD }}
INTERLEDGER_CARDS_WALLET_ADDRESS_URL: ${{ vars.E2E_INTERLEDGER_CARDS_WALLET_ADDRESS_URL }}
INTERLEDGER_CARDS_ILP_DEV_WALLET_ADDRESS_URL: ${{ vars.E2E_INTERLEDGER_CARDS_ILP_DEV_WALLET_ADDRESS_URL }}
INTERLEDGER_CARDS_USERNAME: ${{ secrets.E2E_INTERLEDGER_CARDS_USERNAME }}
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/tests-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ jobs:
CHIMONEY_APP_WALLET_ADDRESS_URL: ${{ vars.E2E_CHIMONEY_APP_WALLET_ADDRESS_URL }}
CHIMONEY_APP_USERNAME: ${{ vars.E2E_CHIMONEY_APP_USERNAME }}
CHIMONEY_APP_PASSWORD: ${{ secrets.E2E_CHIMONEY_APP_PASSWORD }}
MMAON_WALLET_ORIGIN: ${{ vars.E2E_MMAON_WALLET_URL_ORIGIN }}
MMAON_WALLET_ADDRESS_URL: ${{ vars.E2E_MMAON_WALLET_ADDRESS_URL }}
MMAON_USERNAME: ${{ vars.E2E_MMAON_USERNAME }}
MMAON_PASSWORD: ${{ secrets.E2E_MMAON_PASSWORD }}
INTERLEDGER_CARDS_WALLET_ADDRESS_URL: ${{ vars.E2E_INTERLEDGER_CARDS_WALLET_ADDRESS_URL }}
INTERLEDGER_CARDS_ILP_DEV_WALLET_ADDRESS_URL: ${{ vars.E2E_INTERLEDGER_CARDS_ILP_DEV_WALLET_ADDRESS_URL }}
INTERLEDGER_CARDS_USERNAME: ${{ secrets.E2E_INTERLEDGER_CARDS_USERNAME }}
Expand Down
1 change: 1 addition & 0 deletions cspell-dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Fynbos
Rafiki
Chimoney
GateHub
MMAON

SPSP
webextension
Expand Down
38 changes: 31 additions & 7 deletions src/background/services/keyAutoAdd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import {
} from '@/shared/helpers';
import { createTab } from '@/background/utils';
import type { Browser, Runtime, Scripting } from 'webextension-polyfill';
import type { WalletAddress } from '@interledger/open-payments';
import type { TabId } from '@/shared/types';
import type { TabId, WalletInfo } from '@/shared/types';
import type { Cradle } from '@/background/container';
import type {
BeginPayload,
Expand Down Expand Up @@ -48,7 +47,7 @@ export class KeyAutoAddService {
}

async addPublicKeyToWallet(
walletAddress: WalletAddress,
walletAddress: WalletInfo,
onTabOpen: (tabId: TabId) => void,
) {
const keyAddUrl = walletAddressToProvider(walletAddress);
Expand Down Expand Up @@ -201,7 +200,7 @@ export class KeyAutoAddService {
}));
}

static supports(walletAddress: WalletAddress): boolean {
static supports(walletAddress: WalletInfo): boolean {
try {
walletAddressToProvider(walletAddress);
return true;
Expand Down Expand Up @@ -271,9 +270,34 @@ const CONTENT_SCRIPTS: Scripting.RegisteredContentScript[] = [
js: ['content/keyAutoAdd/gatehub.js'],
persistAcrossSessions: false,
},
{
id: 'keyAutoAdd/mmaon/sandbox',
matches: ['https://staging.mmaon.com/*'],
js: ['content/keyAutoAdd/mmaon.js'],
persistAcrossSessions: false,
},
{
id: 'keyAutoAdd/mmaon/prod',
matches: ['https://www.mmaon.com/*'],
js: ['content/keyAutoAdd/mmaon.js'],
persistAcrossSessions: false,
},
];

function walletAddressToProvider(walletAddress: WalletAddress): string {
function walletAddressToProvider(walletAddress: WalletInfo): string {
if (walletAddress.url) {
// Some wallet URLs have redirects to other "managing wallets" and need some
// special handling.
const { host } = new URL(walletAddress.url);
switch (host) {
case 'ilp-staging.mmaon.com':
return 'https://staging.mmaon.com/wallet/dashboard';
case 'ilp.mmaon.on':
return 'https://www.mmaon.com/wallet/dashboard';
// case 'ilp.dev' is handled normally as ilp.interledger.cards below
}
}

const { host } = new URL(walletAddress.id);
switch (host) {
case 'ilp.interledger-test.dev':
Expand All @@ -293,7 +317,7 @@ function walletAddressToProvider(walletAddress: WalletAddress): string {
return 'https://wallet.sandbox.gatehub.net/#/wallets/';
case 'ilp.gatehub.net':
return 'https://wallet.gatehub.net/#/wallets/';
default:
throw new ErrorWithKey('connectWalletKeyService_error_notImplemented');
}

throw new ErrorWithKey('connectWalletKeyService_error_notImplemented');
}
7 changes: 3 additions & 4 deletions src/background/services/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ import { isInvalidClientError } from '@/background/services/openPayments';
import { APP_URL } from '@/background/constants';
import { bytesToHex } from '@noble/hashes/utils';
import type { Cradle } from '@/background/container';
import type { TabId } from '@/shared/types';
import type { WalletAddress } from '@interledger/open-payments';
import type { TabId, WalletInfo } from '@/shared/types';
import type { Browser, Tabs } from 'webextension-polyfill';

export class WalletService {
Expand Down Expand Up @@ -409,7 +408,7 @@ export class WalletService {
* through the wallet's dashboard.
*/
private async addPublicKeyToWallet(
walletAddress: WalletAddress,
walletAddress: WalletInfo,
onTabOpen: (tabId: TabId) => void,
) {
const keyAutoAdd = new KeyAutoAddService({
Expand Down Expand Up @@ -448,7 +447,7 @@ export class WalletService {
}
}

private async retryAddPublicKeyToWallet(walletAddress: WalletAddress) {
private async retryAddPublicKeyToWallet(walletAddress: WalletInfo) {
let tabId: TabId | undefined;
try {
await this.addPublicKeyToWallet(walletAddress, (openedTabId) => {
Expand Down
13 changes: 13 additions & 0 deletions src/background/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ export const getExchangeRates = memoize(
throw new Error('Invalid rates format');
}

// MMAON rate is not listed at EXCHANGE_RATES_URL. Hardcode it here until
// it's either added to the list or we switch to the preferred solution:
// https://github.com/interledger/web-monetization-extension/issues/977
rates.rates.MMAON ??= 20; // 20 USD = 1 MMAON

return rates;
},
{ maxAge: 15 * 60 * 1000, mechanism: 'stale-while-revalidate' },
Expand Down Expand Up @@ -146,6 +151,14 @@ export const getBudgetRecommendationsData = memoize(
throw new Error('Failed to fetch budget recommendations data.');
}
const data: BudgetRecommendationsDataSchema = await response.json();

// MMAON rate is not listed at BUDGET_RECOMMENDATIONS_URL. Hardcode it here
// until it's added to the db.
data.MMAON = {
budget: { default: 60, max: 100 },
hourly: { default: 20, max: 30 },
};

return data;
},
{ maxAge: 30 * 60 * 1000, mechanism: 'stale-while-revalidate' },
Expand Down
90 changes: 90 additions & 0 deletions src/content/keyAutoAdd/mmaon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// cSpell:ignore nextjs
import { errorWithKey, ErrorWithKey, sleep } from '@/shared/helpers';
import {
KeyAutoAdd,
LOGIN_WAIT_TIMEOUT,
type StepRun as Run,
} from './lib/keyAutoAdd';
import { isTimedOut, waitForURL } from './lib/helpers';
import { walletAddressUrlToId } from './lib/helpers/gatehub';

// #region: Steps

const waitForLogin: Run<void> = async (
{ keyAddUrl },
{ skip, setNotificationSize },
) => {
await sleep(500);
let alreadyLoggedIn = window.location.href.startsWith(keyAddUrl);
if (!alreadyLoggedIn) setNotificationSize('notification');
try {
alreadyLoggedIn ||= await waitForURL(
(url) => (url.origin + url.pathname).startsWith(keyAddUrl),
{ timeout: LOGIN_WAIT_TIMEOUT },
);
setNotificationSize('fullscreen');
} catch (error) {
if (isTimedOut(error)) {
throw new ErrorWithKey('connectWalletKeyService_error_timeoutLogin');
}
throw new Error(error);
}

if (alreadyLoggedIn) {
skip(errorWithKey('connectWalletKeyService_error_skipAlreadyLoggedIn'));
}
};

const findWallet: Run<void> = async (
{ walletAddressUrl },
{ setNotificationSize },
) => {
setNotificationSize('fullscreen');
const accountAddress = walletAddressUrlToId(walletAddressUrl);

const res = await fetch('/api/gatehub/wallet', { credentials: 'include' });
if (!res.ok) {
throw new Error('Failed to get wallet details');
}
const accountInfo: { address: string } = await res.json();

if (accountInfo.address !== accountAddress) {
throw new ErrorWithKey('connectWalletKeyService_error_accountNotFound');
}
};

const addKey: Run<void> = async ({ publicKey, nickName }) => {
const res = await fetch('/api/open-payments/upload-keys', {
method: 'POST',
body: JSON.stringify({
base64Key: publicKey,
nickname: nickName,
}),
headers: { 'content-type': 'application/json' },
credentials: 'include',
}).catch((error) => {
return Response.json(null, { status: 599, statusText: error.message });
});

if (!res.ok) {
throw new Error(`Failed to upload public key (${res.statusText})`);
}
const data = await res.json().catch(() => null);
if (!data?.keyId) {
// Note: `keyId` is used for revoking keys; it's not same as JWK's `kid`
throw new Error(`Failed to upload public key (${await res.text()})`);
}
};
// #endregion

// #region: Main
new KeyAutoAdd([
{
name: 'Waiting for you to login',
run: waitForLogin,
maxDuration: LOGIN_WAIT_TIMEOUT,
},
{ name: 'Finding wallet', run: findWallet },
{ name: 'Adding key', run: addKey },
]).init();
// #endregion
6 changes: 6 additions & 0 deletions tests/e2e/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ CHIMONEY_WALLET_ADDRESS_URL=
CHIMONEY_USERNAME=
CHIMONEY_PASSWORD=

# MMAON specific tests, using MMAON sandbox
MMAON_WALLET_ORIGIN=
MMAON_WALLET_ADDRESS_URL=
MMAON_USERNAME=
MMAON_PASSWORD=

# Interledger.cards specific tests (optional)
INTERLEDGER_CARDS_USERNAME=
INTERLEDGER_CARDS_PASSWORD=
Expand Down
Loading
Loading