Skip to content

Commit

Permalink
fix: update popup tab data along with icon (#611)
Browse files Browse the repository at this point in the history
fix: update popup data along with icon

- refactor(PopupState): add all popup data related to tab under `tab`
   - `tabUrl` -> `tab.url`
   - `isSiteMonetized` -> `tab.status === 'monetized'`
   - `hasAllSessionsInvalid` -> `tab.status === 'all_sessions_invalid'`
- refactor(monetization): extract `getPopupTabData` to tabState service
- refactor(tabEvents): use `getPopupTabData` instead of multiple params

Paves way to show more useful messages with `tab.status`
  (e.g. new tab, internal extension pages, non-https pages)
  • Loading branch information
sidvishnoi authored Sep 25, 2024
1 parent 04945e9 commit 2325cb3
Show file tree
Hide file tree
Showing 11 changed files with 133 additions and 90 deletions.
15 changes: 15 additions & 0 deletions src/background/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// cSpell:ignore newtab, webui, startpage

export const INTERNAL_PAGE_URL_PROTOCOLS = new Set([
'chrome:',
'about:',
'edge:',
]);

export const NEW_TAB_PAGES = [
'about:blank',
'chrome://newtab',
'about:newtab',
'edge://newtab',
'chrome://vivaldi-webui/startpage',
];
4 changes: 2 additions & 2 deletions src/background/services/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ export class Background {
private async updateVisualIndicatorsForCurrentTab() {
const activeTab = await this.windowState.getCurrentTab();
if (activeTab?.id) {
void this.tabEvents.updateVisualIndicators(activeTab.id, activeTab.url);
void this.tabEvents.updateVisualIndicators(activeTab);
}
}

Expand All @@ -313,7 +313,7 @@ export class Background {

this.events.on('monetization.state_update', async (tabId) => {
const tab = await this.browser.tabs.get(tabId);
void this.tabEvents.updateVisualIndicators(tabId, tab?.url);
void this.tabEvents.updateVisualIndicators(tab);
});

this.events.on('storage.balance_update', (balance) =>
Expand Down
24 changes: 1 addition & 23 deletions src/background/services/monetization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { PaymentSession } from './paymentSession';
import { computeRate, getSender, getTabId } from '../utils';
import { isOutOfBalanceError } from './openPayments';
import { isOkState, removeQueryParams } from '@/shared/helpers';
import { ALLOWED_PROTOCOLS } from '@/shared/defines';
import type { AmountValue, PopupStore, Storage } from '@/shared/types';
import type { Cradle } from '../container';

Expand Down Expand Up @@ -386,35 +385,14 @@ export class MonetizationService {

const { oneTimeGrant, recurringGrant, ...dataFromStorage } = storedData;

const tabId = tab.id;
if (!tabId) {
throw new Error('Tab ID not found');
}
let url;
if (tab && tab.url) {
try {
const tabUrl = new URL(tab.url);
if (ALLOWED_PROTOCOLS.includes(tabUrl.protocol)) {
// Do not include search params
url = `${tabUrl.origin}${tabUrl.pathname}`;
}
} catch {
// noop
}
}
const isSiteMonetized = this.tabState.isTabMonetized(tabId);
const hasAllSessionsInvalid = this.tabState.tabHasAllSessionsInvalid(tabId);

return {
...dataFromStorage,
balance: balance.total.toString(),
url,
tab: this.tabState.getPopupTabData(tab),
grants: {
oneTime: oneTimeGrant?.amount,
recurring: recurringGrant?.amount,
},
isSiteMonetized,
hasAllSessionsInvalid,
};
}

Expand Down
64 changes: 23 additions & 41 deletions src/background/services/tabEvents.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { isOkState, removeQueryParams } from '@/shared/helpers';
import { ALLOWED_PROTOCOLS } from '@/shared/defines';
import type { Storage, TabId } from '@/shared/types';
import type { PopupTabInfo, Storage, TabId } from '@/shared/types';
import type { Browser, Tabs } from 'webextension-polyfill';
import type { Cradle } from '@/background/container';

Expand Down Expand Up @@ -93,7 +92,8 @@ export class TabEvents {
if (clearOverpaying) {
this.tabState.clearOverpayingByTabId(tabId);
}
void this.updateVisualIndicators(tabId, url);
if (!tab.id) return;
void this.updateVisualIndicators(tab);
}
};

Expand All @@ -108,54 +108,38 @@ export class TabEvents {
const updated = this.windowState.setCurrentTabId(info.windowId, info.tabId);
if (!updated) return;
const tab = await this.browser.tabs.get(info.tabId);
await this.updateVisualIndicators(info.tabId, tab?.url);
await this.updateVisualIndicators(tab);
};

onCreatedTab: CallbackTab<'onCreated'> = async (tab) => {
if (!tab.id) return;
this.windowState.addTab(tab.id, tab.windowId);
await this.updateVisualIndicators(tab.id, tab.url);
await this.updateVisualIndicators(tab);
};

onFocussedTab = async (tab: Tabs.Tab) => {
if (!tab.id) return;
this.windowState.addTab(tab.id, tab.windowId);
const updated = this.windowState.setCurrentTabId(tab.windowId!, tab.id);
if (!updated) return;
const tabUrl = tab.url ?? (await this.browser.tabs.get(tab.id)).url;
await this.updateVisualIndicators(tab.id, tabUrl);
await this.updateVisualIndicators(tab);
};

updateVisualIndicators = async (
tabId: TabId,
tabUrl?: string,
isTabMonetized: boolean = tabId
? this.tabState.isTabMonetized(tabId)
: false,
hasTabAllSessionsInvalid: boolean = tabId
? this.tabState.tabHasAllSessionsInvalid(tabId)
: false,
) => {
const canMonetizeTab = ALLOWED_PROTOCOLS.some((scheme) =>
tabUrl?.startsWith(scheme),
);
updateVisualIndicators = async (tab: Tabs.Tab) => {
const tabInfo = this.tabState.getPopupTabData(tab);
this.sendToPopup.send('SET_TAB_DATA', tabInfo);
const { enabled, connected, state } = await this.storage.get([
'enabled',
'connected',
'state',
]);
const { path, title, isMonetized } = this.getIconAndTooltip({
const { path, title } = this.getIconAndTooltip({
enabled,
connected,
state,
canMonetizeTab,
isTabMonetized,
hasTabAllSessionsInvalid,
tabInfo,
});

this.sendToPopup.send('SET_IS_MONETIZED', isMonetized);
this.sendToPopup.send('SET_ALL_SESSIONS_INVALID', hasTabAllSessionsInvalid);
await this.setIconAndTooltip(tabId, path, title);
await this.setIconAndTooltip(tabInfo.tabId, path, title);
};

private setIconAndTooltip = async (
Expand Down Expand Up @@ -187,26 +171,28 @@ export class TabEvents {
enabled,
connected,
state,
canMonetizeTab,
isTabMonetized,
hasTabAllSessionsInvalid,
tabInfo,
}: {
enabled: Storage['enabled'];
connected: Storage['connected'];
state: Storage['state'];
canMonetizeTab: boolean;
isTabMonetized: boolean;
hasTabAllSessionsInvalid: boolean;
tabInfo: PopupTabInfo;
}) {
let title = this.t('appName');
let iconData = ICONS.default;
if (!connected || !canMonetizeTab) {
if (!connected) {
// use defaults
} else if (!isOkState(state) || hasTabAllSessionsInvalid) {
} else if (!isOkState(state) || tabInfo.status === 'all_sessions_invalid') {
iconData = enabled ? ICONS.enabled_warn : ICONS.disabled_warn;
const tabStateText = this.t('icon_state_actionRequired');
title = `${title} - ${tabStateText}`;
} else if (
tabInfo.status !== 'monetized' &&
tabInfo.status !== 'no_monetization_links'
) {
// use defaults
} else {
const isTabMonetized = tabInfo.status === 'monetized';
if (enabled) {
iconData = isTabMonetized
? ICONS.enabled_hasLinks
Expand All @@ -222,10 +208,6 @@ export class TabEvents {
title = `${title} - ${tabStateText}`;
}

return {
path: iconData,
isMonetized: isTabMonetized,
title,
};
return { path: iconData, title };
}
}
46 changes: 45 additions & 1 deletion src/background/services/tabState.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { Tabs } from 'webextension-polyfill';
import type { MonetizationEventDetails } from '@/shared/messages';
import type { TabId } from '@/shared/types';
import type { PopupTabInfo, TabId } from '@/shared/types';
import type { PaymentSession } from './paymentSession';
import type { Cradle } from '@/background/container';
import { removeQueryParams } from '@/shared/helpers';
import { ALLOWED_PROTOCOLS } from '@/shared/defines';
import { isBrowserInternalPage, isBrowserNewTabPage } from '@/background/utils';

type State = {
monetizationEvent: MonetizationEventDetails;
Expand Down Expand Up @@ -119,6 +123,46 @@ export class TabState {
return [...this.sessions.values()].flatMap((s) => [...s.values()]);
}

getPopupTabData(tab: Pick<Tabs.Tab, 'id' | 'url'>): PopupTabInfo {
if (!tab.id) {
throw new Error('Tab does not have an ID');
}

let tabUrl: URL | null = null;
try {
tabUrl = new URL(tab.url ?? '');
} catch {
// noop
}

let url = '';
if (tabUrl && ALLOWED_PROTOCOLS.includes(tabUrl.protocol)) {
// Do not include search params
url = removeQueryParams(tabUrl.href);
}

let status: PopupTabInfo['status'] = 'no_monetization_links';
if (!tabUrl) {
status = 'unsupported_scheme';
} else if (!ALLOWED_PROTOCOLS.includes(tabUrl.protocol)) {
if (tabUrl && isBrowserInternalPage(tabUrl)) {
if (isBrowserNewTabPage(tabUrl)) {
status = 'new_tab';
} else {
status = 'internal_page';
}
} else {
status = 'unsupported_scheme';
}
} else if (this.tabHasAllSessionsInvalid(tab.id)) {
status = 'all_sessions_invalid';
} else if (this.isTabMonetized(tab.id)) {
status = 'monetized';
}

return { tabId: tab.id, url, status };
}

getIcon(tabId: TabId) {
return this.currentIcon.get(tabId);
}
Expand Down
9 changes: 9 additions & 0 deletions src/background/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
} from '@/shared/types';
import type { Browser, Runtime } from 'webextension-polyfill';
import { DEFAULT_SCALE, EXCHANGE_RATES_URL } from './config';
import { INTERNAL_PAGE_URL_PROTOCOLS, NEW_TAB_PAGES } from './constants';
import { notNullOrUndef } from '@/shared/helpers';

export const getCurrentActiveTab = async (browser: Browser) => {
Expand Down Expand Up @@ -97,6 +98,14 @@ export const getSender = (sender: Runtime.MessageSender) => {
return { tabId, frameId, url: sender.url };
};

export const isBrowserInternalPage = (url: URL) => {
return INTERNAL_PAGE_URL_PROTOCOLS.has(url.protocol);
};

export const isBrowserNewTabPage = (url: URL) => {
return NEW_TAB_PAGES.some((e) => url.href.startsWith(e));
};

export const computeRate = (rate: string, sessionsCount: number): AmountValue =>
(BigInt(rate) / BigInt(sessionsCount)).toString();

Expand Down
4 changes: 2 additions & 2 deletions src/popup/components/PayWebsiteForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const BUTTON_STATE = {
export const PayWebsiteForm = () => {
const message = useMessage();
const {
state: { walletAddress, url },
state: { walletAddress, tab },
} = usePopupState();
const [buttonState, setButtonState] =
React.useState<keyof typeof BUTTON_STATE>('idle');
Expand Down Expand Up @@ -84,7 +84,7 @@ export const PayWebsiteForm = () => {
addOn={getCurrencySymbol(walletAddress.assetCode)}
label={
<p className="overflow-hidden text-ellipsis whitespace-nowrap">
Pay <span className="text-ellipsis text-primary">{url}</span>
Pay <span className="text-ellipsis text-primary">{tab.url}</span>
</p>
}
placeholder="0.00"
Expand Down
9 changes: 3 additions & 6 deletions src/popup/lib/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,10 @@ const reducer = (state: PopupState, action: ReducerActions): PopupState => {
return { ...state, rateOfPay: action.data.rateOfPay };
case 'SET_STATE':
return { ...state, state: action.data.state };
case 'SET_IS_MONETIZED':
return { ...state, isSiteMonetized: action.data };
case 'SET_TAB_DATA':
return { ...state, tab: action.data };
case 'SET_BALANCE':
return { ...state, balance: action.data.total };
case 'SET_ALL_SESSIONS_INVALID':
return { ...state, hasAllSessionsInvalid: action.data };
default:
return state;
}
Expand Down Expand Up @@ -113,8 +111,7 @@ export function PopupContextProvider({ children }: PopupContextProviderProps) {
switch (message.type) {
case 'SET_BALANCE':
case 'SET_STATE':
case 'SET_IS_MONETIZED':
case 'SET_ALL_SESSIONS_INVALID':
case 'SET_TAB_DATA':
return dispatch(message);
}
});
Expand Down
18 changes: 8 additions & 10 deletions src/popup/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@ export const Component = () => {
const {
state: {
enabled,
isSiteMonetized,
rateOfPay,
minRateOfPay,
maxRateOfPay,
balance,
walletAddress,
url,
hasAllSessionsInvalid,
tab,
},
dispatch,
} = usePopupState();
Expand Down Expand Up @@ -65,12 +63,12 @@ export const Component = () => {
dispatch({ type: 'TOGGLE_WM', data: {} });
};

if (!isSiteMonetized) {
return <SiteNotMonetized />;
}

if (hasAllSessionsInvalid) {
return <AllSessionsInvalid />;
if (tab.status !== 'monetized') {
if (tab.status === 'all_sessions_invalid') {
return <AllSessionsInvalid />;
} else {
return <SiteNotMonetized />;
}
}

return (
Expand Down Expand Up @@ -113,7 +111,7 @@ export const Component = () => {

<hr />

{url ? <PayWebsiteForm /> : null}
{tab.url ? <PayWebsiteForm /> : null}
</div>
);
};
3 changes: 1 addition & 2 deletions src/shared/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,8 @@ export const BACKGROUND_TO_POPUP_CONNECTION_NAME = 'popup';
// These methods are fire-and-forget, nothing is returned.
export interface BackgroundToPopupMessagesMap {
SET_BALANCE: Record<'recurring' | 'oneTime' | 'total', AmountValue>;
SET_IS_MONETIZED: boolean;
SET_TAB_DATA: PopupState['tab'];
SET_STATE: { state: Storage['state']; prevState: Storage['state'] };
SET_ALL_SESSIONS_INVALID: boolean;
}

export type BackgroundToPopupMessage = {
Expand Down
Loading

0 comments on commit 2325cb3

Please sign in to comment.