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
75 changes: 75 additions & 0 deletions src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,5 +308,80 @@
},
"postInstall_text_wallet_connected_2": {
"message": "Access the extension from the browser toolbar."
},
"postInstallConsent_text_title": {
"message": "Welcome to the Web Monetization Extension"
},
"postInstallConsent_text_header1": {
"message": "We’re transparent about what data is shared and how it’s used."
},
"postInstallConsent_text_header2": {
"message": "By continuing, you consent to share the information below."
},
"postInstallConsent_text_dataShared_title": {
"message": "What’s shared"
},
"postInstallConsent_text_dataShared_yourWallet_title": {
"message": "With your wallet provider:"
},
"postInstallConsent_text_dataShared_yourWallet_keyName": {
"message": " If you choose automatic key addition, the extension shares:"
},
"postInstallConsent_text_dataShared_yourWallet_keyConsent": {
"message": "Note: You will always be asked for consent before any connection."
},
"postInstallConsent_text_dataShared_yourWallet_headers": {
"message": "Your IP address, language, and browser version while you use the extension."
},
"postInstallConsent_text_dataShared_websiteWallets_title": {
"message": "With the wallets used on websites you visit that use Web Monetization:"
},
"postInstallConsent_text_dataShared_websiteWallets_headers": {
"message": "Your IP address, language and browser version information."
},
"postInstallConsent_text_dataShared_websiteWallets_wa": {
"message": "Your wallet address."
},
"postInstallConsent_text_dataShared_headers": {
"message": "Browsers send certain HTTP headers by default each request that inform the servers about your language (`Accept-Language` header), browser version (`User-Agent` header) and more. Your IP address is also sent by default."
},
"postInstallConsent_text_dataNotShared_title": {
"message": "What’s not shared"
},
"postInstallConsent_text_dataNotShared_walletDetails": {
"message": "Your wallet address, balance, or currency, are never shared with websites you visit."
},
"postInstallConsent_text_dataNotShared_browsingHistory": {
"message": "Your browsing history. It stays private in your browser."
},
"postInstallConsent_text_permissions_title": {
"message": "Extension Permissions"
},
"postInstallConsent_text_permissions_text": {
"message": "The extension needs basic permissions to work."
},
"postInstallConsent_text_permissions_linkText": {
"message": "See details."
},
"postInstallConsent_state_consentProvided": {
"message": "You have provided your consent to the above. Access the extension from the browser toolbar."
},
"postInstallConsent_text_confirmation": {
"message": "I confirm that I understand and consent to this data usage."
},
"postInstallConsent_action_confirm": {
"message": "Confirm and continue"
},
"consentRequired_text_title": {
"message": "Consent Required: Data Collection & Transmission"
},
"consentRequired_text_subtitle": {
"message": "We want to be transparent about what data is shared and how it’s used."
},
"consentRequired_text_msg": {
"message": "To use the the Web Monetization Extension, we require your consent to our data collection & transmission policy."
},
"consentRequired_action_primary": {
"message": "View Policy"
}
}
44 changes: 43 additions & 1 deletion src/background/services/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
getWalletInformation,
isErrorWithKey,
moveToFront,
CURRENT_DATA_CONSENT_VERSION,
isConsentRequired,
} from '@/shared/helpers';
import { KeyAutoAddService } from '@/background/services/keyAutoAdd';
import { OpenPaymentsClientError } from '@interledger/open-payments/dist/client/error';
Expand Down Expand Up @@ -124,7 +126,12 @@ export class Background {
}
await this.storage.populate();
await this.checkPermissions();
const { consent } = await this.storage.get(['consent']);
if (isConsentRequired(consent)) {
await this.storage.setState({ consent_required: true });
}
await this.scheduleResetOutOfFundsState();
await this.updateVisualIndicatorsForCurrentTab().catch(() => {});
}

async scheduleResetOutOfFundsState() {
Expand All @@ -147,12 +154,14 @@ export class Background {
}

async getAppData(): Promise<AppStore> {
const { connected, publicKey } = await this.storage.get([
const { connected, publicKey, consent } = await this.storage.get([
'connected',
'publicKey',
'consent',
]);

return {
consent,
connected,
publicKey,
transientState: this.storage.getPopupTransientState(),
Expand Down Expand Up @@ -295,6 +304,10 @@ export class Background {
await this.monetizationService.pay(message.payload),
);

case 'OPEN_APP':
await this.openAppPage(message.payload.path);
return success(undefined);

// endregion

// region Content
Expand Down Expand Up @@ -337,6 +350,13 @@ export class Background {
// region App
case 'GET_DATA_APP':
return success(await this.getAppData());

case 'PROVIDE_CONSENT': {
await this.storage.set({ consent: CURRENT_DATA_CONSENT_VERSION });
await this.storage.setState({ consent_required: false });
return success(CURRENT_DATA_CONSENT_VERSION);
}

// endregion

default:
Expand Down Expand Up @@ -406,6 +426,9 @@ export class Background {
this.events.on('storage.state_update', async ({ state, prevState }) => {
this.sendToPopup.send('SET_STATE', { state, prevState });
await this.updateVisualIndicatorsForCurrentTab();
if (state.consent_required) {
await this.openAppPage('/post-install/consent');
}
});

this.events.on('monetization.state_update', async (tabId) => {
Expand Down Expand Up @@ -446,6 +469,9 @@ export class Background {
);
}
}
if (isConsentRequired(data.consent)) {
await this.storage.setState({ consent_required: true });
}
});
}

Expand All @@ -460,4 +486,20 @@ export class Background {
this.logger.error(error);
}
};

async openAppPage(path: string) {
const appUrl = this.browser.runtime.getURL(APP_URL);

const allTabs = await this.browser.tabs.query({});
const appTab = allTabs.find((t) => t.url?.startsWith(appUrl));

const url = `${appUrl}#${path}`;
if (appTab?.id) {
await this.browser.tabs.update(appTab.id, { url });
await this.sendToPopup.send('CLOSE_POPUP', undefined);
return appTab;
} else {
return await this.browser.tabs.create({ url });
}
}
}
1 change: 1 addition & 0 deletions src/background/services/monetization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ export class MonetizationService {

async getPopupData(tab: Pick<Tabs.Tab, 'id' | 'url'>): Promise<PopupStore> {
const storedData = await this.storage.get([
'consent',
'enabled',
'continuousPaymentsEnabled',
'connected',
Expand Down
19 changes: 14 additions & 5 deletions src/background/services/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import type {
StorageKey,
WalletAmount,
} from '@/shared/types';
import { objectEquals, ThrottleBatch } from '@/shared/helpers';
import { bigIntMax, computeBalance } from '../utils';
import type { Cradle } from '../container';
import {
isConsentRequired,
objectEquals,
ThrottleBatch,
} from '@/shared/helpers';
import { bigIntMax, computeBalance } from '@/background/utils';
import type { Cradle } from '@/background/container';

const defaultStorage = {
/**
Expand All @@ -20,6 +24,7 @@ const defaultStorage = {
* existing installations.
*/
version: 5,
consent: 0,
state: {},
connected: false,
enabled: true,
Expand Down Expand Up @@ -76,8 +81,12 @@ export class StorageService {
}

async clear(): Promise<void> {
await this.set(defaultStorage);
this.currentState = { ...defaultStorage.state };
const preservedValues = await this.get(['consent']);
await this.set({ ...defaultStorage, ...preservedValues });
this.currentState = {
...defaultStorage.state,
consent_required: isConsentRequired(preservedValues.consent),
};
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/pages/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import * as PAGES from './pages/index';

export const ROUTES = {
DEFAULT: '/',
CONSENT: '/consent',
} as const;

const P = ROUTES;
const C = PAGES;
const Routes = () => (
<Switch>
<Route path={P.DEFAULT} component={C.PostInstall} />
<Route path={P.CONSENT} component={C.Consent} />
</Switch>
);

Expand Down
6 changes: 5 additions & 1 deletion src/pages/app/lib/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ export const dispatch = async ({ type, data }: Actions) => {
case 'SET_TRANSIENT_STATE':
store.transientState = data;
break;
case 'SET_CONSENT':
store.consent = data;
break;
default:
throw new Error('Unknown action');
}
};

type Actions =
| { type: 'SET_TRANSIENT_STATE'; data: PopupTransientState }
| { type: 'SET_DATA_APP'; data: Pick<Storage, 'connected' | 'publicKey'> };
| { type: 'SET_CONSENT'; data: NonNullable<AppStore['consent']> }
| { type: 'SET_DATA_APP'; data: Pick<AppStore, 'connected' | 'publicKey'> };
14 changes: 13 additions & 1 deletion src/pages/app/pages/PostInstall.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import React from 'react';
import { Redirect } from 'wouter';
import {
ArrowBack,
CaretDownIcon,
ExternalIcon,
} from '@/pages/shared/components/Icons';
import { getBrowserName, type BrowserName } from '@/shared/helpers';
import {
getBrowserName,
isConsentRequired,
type BrowserName,
} from '@/shared/helpers';
import { getResponseOrThrow } from '@/shared/messages';
import { useBrowser, useTranslation } from '@/app/lib/context';
import { ConnectWalletForm } from '@/popup/components/ConnectWalletForm';
import { cn } from '@/pages/shared/lib/utils';
import { useMessage } from '@/app/lib/context';
import { useAppState } from '@/app/lib/store';
import { ROUTES } from '../App';

export default () => {
return (
Expand Down Expand Up @@ -52,6 +58,12 @@ const Header = () => {

const Main = () => {
const t = useTranslation();
const { consent } = useAppState();

if (isConsentRequired(consent)) {
return <Redirect to={ROUTES.CONSENT} />;
}

return (
<div className="mx-auto flex w-full max-w-2xl flex-col gap-6 rounded-lg border border-gray-200 bg-gray-50/75 p-3 shadow-md backdrop-blur-0 sm:p-8">
<h2 className="rounded-sm bg-gray-100 p-2 text-center text-base font-medium sm:rounded-2xl sm:p-4 sm:text-lg">
Expand Down
Loading