From b9b8dca2bf3963385bed11d3180501a0bd4fb7ac Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 22 Jul 2024 14:11:07 +0300 Subject: [PATCH 1/5] TW-1474: Support Mises browser (#1167) * TW-1474: Support Mises browser. + IS_MISES_BROWSER + Minor UI fixes * TW-1474: Support Mises browser. Fix Accounts dropdown * TW-1474: Support Mises browser. -- Unnecessary scroll in overlays --- src/app/atoms/AccountTypeBadge.tsx | 2 +- src/app/layouts/PageLayout.tsx | 9 +- src/app/layouts/PageLayout/Header.tsx | 7 +- .../Header/AccountDropdown/AccountItem.tsx | 4 +- .../Header/AccountDropdown/index.tsx | 10 +- .../NewsletterOverlay/NewsletterOverlay.tsx | 114 ++++++++---------- .../OnRampOverlay/OnRampOverlay.tsx | 20 ++- src/app/pages/Welcome/Welcome.tsx | 53 +++----- src/lib/env.ts | 4 + 9 files changed, 97 insertions(+), 126 deletions(-) diff --git a/src/app/atoms/AccountTypeBadge.tsx b/src/app/atoms/AccountTypeBadge.tsx index a8e9f8d00..94268bc69 100644 --- a/src/app/atoms/AccountTypeBadge.tsx +++ b/src/app/atoms/AccountTypeBadge.tsx @@ -16,7 +16,7 @@ const AccountTypeBadge = memo(({ account, darkTheme = fal return title ? ( = ({ children, contentContainerStyle, ...t return ( <> - + { + /* + Mises browser has an issue with 's height - not reaching 100% no matter what CSS, + unless it is expanded by content. We at least won't color it to not highlight that. + */ + !IS_MISES_BROWSER && + }
diff --git a/src/app/layouts/PageLayout/Header.tsx b/src/app/layouts/PageLayout/Header.tsx index b4d2c3ec7..7cc34fbfd 100644 --- a/src/app/layouts/PageLayout/Header.tsx +++ b/src/app/layouts/PageLayout/Header.tsx @@ -57,12 +57,7 @@ const Control: FC = () => {
- } - > + }> {({ ref, opened, toggleOpened }) => ( + + + Newsletter + +
+

{t('subscribeToNewsletter')}

+ + {t('keepLatestNews')} + +
+ + {!isValid &&
{errors.email?.message}
}
+ +
- - - + + + ); -}; +}); diff --git a/src/app/layouts/PageLayout/OnRampOverlay/OnRampOverlay.tsx b/src/app/layouts/PageLayout/OnRampOverlay/OnRampOverlay.tsx index ffe76ae81..cf4dfdca7 100644 --- a/src/app/layouts/PageLayout/OnRampOverlay/OnRampOverlay.tsx +++ b/src/app/layouts/PageLayout/OnRampOverlay/OnRampOverlay.tsx @@ -1,4 +1,4 @@ -import React, { FC, useMemo } from 'react'; +import React, { memo } from 'react'; import classNames from 'clsx'; import { useDispatch } from 'react-redux'; @@ -24,32 +24,26 @@ import { OnRampOverlaySelectors } from './OnRampOverlay.selectors'; import { OnRampSmileButton } from './OnRampSmileButton/OnRampSmileButton'; import { getWertLink } from './utils/getWertLink.util'; -export const OnRampOverlay: FC = () => { +export const OnRampOverlay = memo(() => { const dispatch = useDispatch(); const { publicKeyHash } = useAccount(); const { popup } = useAppEnv(); const isOnRampPossibility = useOnRampPossibilitySelector(); const { onboardingCompleted } = useOnboardingProgress(); - const popupClassName = useMemo( - () => (popup ? 'inset-0 p-4' : 'top-1/2 left-1/2 transform -translate-y-1/2 -translate-x-1/2'), - [popup] - ); const close = () => void dispatch(setOnRampPossibilityAction(false)); if (!isOnRampPossibility || !onboardingCompleted) return null; return ( - <> -
+
{

- +
); -}; +}); diff --git a/src/app/pages/Welcome/Welcome.tsx b/src/app/pages/Welcome/Welcome.tsx index a5ab5b96c..9688b7af6 100644 --- a/src/app/pages/Welcome/Welcome.tsx +++ b/src/app/pages/Welcome/Welcome.tsx @@ -51,21 +51,18 @@ const Welcome: FC = () => { return (
-
+
-
- -
+ -
+
{SIGNS.map(({ key, linkTo, filled, Icon, titleI18nKey, descriptionI18nKey, testID }) => (
{ )} testID={testID} > -
+
-
- -
+ - - {message =>

{message}

} -
+

+ +

-
- - {message => ( -

- {message} -

- )} -
-
+

+ +

diff --git a/src/lib/env.ts b/src/lib/env.ts index a64e630e4..26f9da18d 100644 --- a/src/lib/env.ts +++ b/src/lib/env.ts @@ -2,6 +2,10 @@ import PackageJSON from '../../package.json'; export const APP_VERSION = PackageJSON.version; +/** Only Mises browser among supported vendors counts as a mobile platform */ +// @ts-expect-error +export const IS_MISES_BROWSER = navigator.userAgentData?.mobile ?? false; + export const IS_DEV_ENV = process.env.NODE_ENV === 'development'; const IS_DEV_GITHUB_ACTION_RUN_ENV = process.env.GITHUB_ACTION_RUN_ENV === 'development'; From a4cfad4a7a740b4db1522c8c46fe31d814a1bf96 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 26 Jul 2024 14:50:33 +0300 Subject: [PATCH 2/5] TW-1496: [Mises] Ads impressions rework (#1169) * TW-1474: Support Mises browser. + IS_MISES_BROWSER + Minor UI fixes * TW-1474: Support Mises browser. Fix Accounts dropdown * TW-1474: Support Mises browser. -- Unnecessary scroll in overlays * TW-1496: [Mises] Ads impressions rework * TW-1496: [Mises] Ads impressions rework. Enable pipeline * TW-1496: [Mises] Ads impressions rework. Tuned logic to requirements * TW-1496: [Mises] Ads impressions rework. + useAdsImpressionsLinking() * TW-1496: [Mises] Ads impressions rework. + ECDSA * TW-1496: [Mises] Ads impressions rework. ECDSA. jwk -> pkcs8 + spki --- .env.dist | 1 + .github/workflows/code-quality.yml | 2 + .github/workflows/manual-builds.yml | 1 + .github/workflows/release.yml | 1 + .github/workflows/secrets-setup/action.yml | 3 + src/app/WithDataLoading.tsx | 2 + src/app/hooks/use-ads-impressions-linking.ts | 18 ++++++ src/app/storage/app-install-id.ts | 16 +++++ src/app/store/settings/actions.ts | 2 + src/app/store/settings/reducers.ts | 5 ++ src/app/store/settings/selectors.ts | 2 + src/app/store/settings/state.mock.ts | 3 +- src/app/store/settings/state.ts | 4 +- .../templates/partners-promotion/index.tsx | 18 ++---- src/background.ts | 20 ++++++- src/lib/ads/link-ads-impressions.ts | 21 +++++++ src/lib/apis/ads-api.ts | 38 ++++++++++++ src/lib/env.ts | 3 +- src/lib/temple/back/main.ts | 28 ++++----- src/lib/temple/reset.ts | 3 + src/lib/utils/ecdsa.ts | 58 +++++++++++++++++++ src/replaceAds.ts | 22 +++++-- webpack.config.ts | 5 ++ 23 files changed, 237 insertions(+), 39 deletions(-) create mode 100644 src/app/hooks/use-ads-impressions-linking.ts create mode 100644 src/app/storage/app-install-id.ts create mode 100644 src/lib/ads/link-ads-impressions.ts create mode 100644 src/lib/apis/ads-api.ts create mode 100644 src/lib/utils/ecdsa.ts diff --git a/.env.dist b/.env.dist index 971e8b82d..3282744d5 100644 --- a/.env.dist +++ b/.env.dist @@ -8,6 +8,7 @@ TEMPLE_WALLET_UTORG_SID= TEMPLE_WALLET_API_URL=https://temple-api-mainnet.prod.templewallet.com TEMPLE_WALLET_DEXES_API_URL=wss://dexes-api-mainnet.prod.templewallet.com +TEMPLE_ADS_API_URL= TEMPLE_WALLET_ROUTE3_AUTH_TOKEN= TEMPLE_WALLET_MOONPAY_API_KEY= diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 36f954419..57db80eb3 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -5,6 +5,7 @@ on: branches: - master - development + - 'TW-[0-9]+-epic-**' jobs: pull-request-check: @@ -31,6 +32,7 @@ jobs: TEMPLE_WALLET_UTORG_SID: ${{ secrets.TEMPLE_WALLET_UTORG_SID }} TEMPLE_WALLET_API_URL: ${{ vars.TEMPLE_WALLET_API_URL }} TEMPLE_WALLET_DEXES_API_URL: ${{ vars.TEMPLE_WALLET_DEXES_API_URL }} + TEMPLE_ADS_API_URL: ${{ vars.TEMPLE_ADS_API_URL }} TEMPLE_WALLET_ROUTE3_AUTH_TOKEN: ${{ vars.TEMPLE_WALLET_ROUTE3_AUTH_TOKEN }} TEMPLE_WALLET_MOONPAY_API_KEY: ${{ secrets.TEMPLE_WALLET_MOONPAY_API_KEY }} TEMPLE_FIREBASE_CONFIG: ${{ secrets.TEMPLE_FIREBASE_CONFIG }} diff --git a/.github/workflows/manual-builds.yml b/.github/workflows/manual-builds.yml index 065bcc843..1ceacdf5e 100644 --- a/.github/workflows/manual-builds.yml +++ b/.github/workflows/manual-builds.yml @@ -50,6 +50,7 @@ jobs: TEMPLE_WALLET_UTORG_SID: ${{ secrets.TEMPLE_WALLET_UTORG_SID }} TEMPLE_WALLET_API_URL: ${{ vars.TEMPLE_WALLET_API_URL }} TEMPLE_WALLET_DEXES_API_URL: ${{ vars.TEMPLE_WALLET_DEXES_API_URL }} + TEMPLE_ADS_API_URL: ${{ vars.TEMPLE_ADS_API_URL }} TEMPLE_WALLET_ROUTE3_AUTH_TOKEN: ${{ vars.TEMPLE_WALLET_ROUTE3_AUTH_TOKEN }} TEMPLE_WALLET_MOONPAY_API_KEY: ${{ secrets.TEMPLE_WALLET_MOONPAY_API_KEY }} TEMPLE_FIREBASE_CONFIG: ${{ secrets.TEMPLE_FIREBASE_CONFIG }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ffb3dd89..ff4dbd685 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,6 +32,7 @@ jobs: TEMPLE_WALLET_UTORG_SID: ${{ secrets.TEMPLE_WALLET_UTORG_SID }} TEMPLE_WALLET_API_URL: ${{ vars.TEMPLE_WALLET_API_URL }} TEMPLE_WALLET_DEXES_API_URL: ${{ vars.TEMPLE_WALLET_DEXES_API_URL }} + TEMPLE_ADS_API_URL: ${{ vars.TEMPLE_ADS_API_URL }} TEMPLE_WALLET_ROUTE3_AUTH_TOKEN: ${{ vars.TEMPLE_WALLET_ROUTE3_AUTH_TOKEN }} TEMPLE_WALLET_MOONPAY_API_KEY: ${{ secrets.TEMPLE_WALLET_MOONPAY_API_KEY }} TEMPLE_FIREBASE_CONFIG: ${{ secrets.TEMPLE_FIREBASE_CONFIG }} diff --git a/.github/workflows/secrets-setup/action.yml b/.github/workflows/secrets-setup/action.yml index a28a36032..fd13c31de 100644 --- a/.github/workflows/secrets-setup/action.yml +++ b/.github/workflows/secrets-setup/action.yml @@ -23,6 +23,8 @@ inputs: required: true TEMPLE_WALLET_DEXES_API_URL: required: true + TEMPLE_ADS_API_URL: + required: true TEMPLE_FIREBASE_CONFIG: required: true TEMPLE_FIREBASE_MESSAGING_VAPID_KEY: @@ -123,6 +125,7 @@ runs: TEMPLE_WALLET_API_URL=${{ inputs.TEMPLE_WALLET_API_URL }} TEMPLE_WALLET_DEXES_API_URL=${{ inputs.TEMPLE_WALLET_DEXES_API_URL }} + TEMPLE_ADS_API_URL=${{ inputs.TEMPLE_ADS_API_URL }} TEMPLE_WALLET_ROUTE3_AUTH_TOKEN=${{ inputs.TEMPLE_WALLET_ROUTE3_AUTH_TOKEN }} TEMPLE_WALLET_MOONPAY_API_KEY=${{ inputs.TEMPLE_WALLET_MOONPAY_API_KEY }} TEMPLE_FIREBASE_CONFIG=${{ inputs.TEMPLE_FIREBASE_CONFIG }} diff --git a/src/app/WithDataLoading.tsx b/src/app/WithDataLoading.tsx index e6e874fd0..1a80b8e4c 100644 --- a/src/app/WithDataLoading.tsx +++ b/src/app/WithDataLoading.tsx @@ -4,6 +4,7 @@ import { dispatch } from 'app/store'; import { loadTokensScamlistActions } from 'app/store/assets/actions'; import { loadSwapDexesAction, loadSwapTokensAction } from 'app/store/swap/actions'; +import { useAdsImpressionsLinking } from './hooks/use-ads-impressions-linking'; import { useAdvertisingLoading } from './hooks/use-advertising.hook'; import { useAssetsLoading } from './hooks/use-assets-loading'; import { useAssetsMigrations } from './hooks/use-assets-migrations'; @@ -42,6 +43,7 @@ export const WithDataLoading: FC = ({ children }) => { useStorageAnalytics(); useConversionTracking(); useUserIdAccountPkhSync(); + useAdsImpressionsLinking(); return <>{children}; }; diff --git a/src/app/hooks/use-ads-impressions-linking.ts b/src/app/hooks/use-ads-impressions-linking.ts new file mode 100644 index 000000000..ff5185e1d --- /dev/null +++ b/src/app/hooks/use-ads-impressions-linking.ts @@ -0,0 +1,18 @@ +import { dispatch } from 'app/store'; +import { setAdsImpressionsLinkedAction } from 'app/store/settings/actions'; +import { useIsAdsImpressionsLinkedSelector } from 'app/store/settings/selectors'; +import { performLinkingOfAdsImpressions } from 'lib/ads/link-ads-impressions'; +import { useDidMount } from 'lib/ui/hooks'; + +import { useAdsViewerPkh } from './use-ads-viewer-pkh'; + +export const useAdsImpressionsLinking = () => { + const linked = useIsAdsImpressionsLinkedSelector(); + const accountPkh = useAdsViewerPkh(); + + useDidMount(() => { + if (linked) return; + + performLinkingOfAdsImpressions(accountPkh).then(() => void dispatch(setAdsImpressionsLinkedAction())); + }); +}; diff --git a/src/app/storage/app-install-id.ts b/src/app/storage/app-install-id.ts new file mode 100644 index 000000000..e12971571 --- /dev/null +++ b/src/app/storage/app-install-id.ts @@ -0,0 +1,16 @@ +import { fetchFromStorage, putToStorage } from 'lib/storage'; + +const storageKey = 'APP_INSTALL_IDENTITY'; + +interface AppInstallIdentity { + version: string; + privateKey: string; + publicKey: string; + publicKeyHash: string; + ts: string; +} + +export const getStoredAppInstallIdentity = () => fetchFromStorage(storageKey); + +export const putStoredAppInstallIdentity = (value: AppInstallIdentity) => + putToStorage(storageKey, value); diff --git a/src/app/store/settings/actions.ts b/src/app/store/settings/actions.ts index 900b396d7..db37d3acd 100644 --- a/src/app/store/settings/actions.ts +++ b/src/app/store/settings/actions.ts @@ -11,3 +11,5 @@ export const setOnRampPossibilityAction = createAction('settings/SET_ON export const setConversionTrackedAction = createAction('settings/SET_CONVERSION_TRACKED'); export const setPendingReactivateAdsAction = createAction('settings/SET_PENDING_REACTIVATE_ADS'); + +export const setAdsImpressionsLinkedAction = createAction('settings/SET_ADS_IMPRESSIONS_LINKED'); diff --git a/src/app/store/settings/reducers.ts b/src/app/store/settings/reducers.ts index 7e3077c5a..cba64aacd 100644 --- a/src/app/store/settings/reducers.ts +++ b/src/app/store/settings/reducers.ts @@ -1,6 +1,7 @@ import { createReducer } from '@reduxjs/toolkit'; import { + setAdsImpressionsLinkedAction, setConversionTrackedAction, setIsAnalyticsEnabledAction, setOnRampPossibilityAction, @@ -29,4 +30,8 @@ export const settingsReducer = createReducer(settingsInitialState builder.addCase(setPendingReactivateAdsAction, (state, { payload }) => { state.pendingReactivateAds = payload; }); + + builder.addCase(setAdsImpressionsLinkedAction, state => { + state.adsImpressionsLinked = true; + }); }); diff --git a/src/app/store/settings/selectors.ts b/src/app/store/settings/selectors.ts index 625f42e33..edc68e591 100644 --- a/src/app/store/settings/selectors.ts +++ b/src/app/store/settings/selectors.ts @@ -11,3 +11,5 @@ export const useOnRampPossibilitySelector = () => useSelector(({ settings }) => export const useIsConversionTrackedSelector = () => useSelector(({ settings }) => settings.isConversionTracked); export const useIsPendingReactivateAdsSelector = () => useSelector(({ settings }) => settings.pendingReactivateAds); + +export const useIsAdsImpressionsLinkedSelector = () => useSelector(({ settings }) => settings.adsImpressionsLinked); diff --git a/src/app/store/settings/state.mock.ts b/src/app/store/settings/state.mock.ts index 68d6a1e84..531d5f857 100644 --- a/src/app/store/settings/state.mock.ts +++ b/src/app/store/settings/state.mock.ts @@ -6,5 +6,6 @@ export const mockSettingsState: SettingsState = { balanceMode: BalanceMode.Fiat, isOnRampPossibility: false, isConversionTracked: false, - pendingReactivateAds: false + pendingReactivateAds: false, + adsImpressionsLinked: false }; diff --git a/src/app/store/settings/state.ts b/src/app/store/settings/state.ts index 87f6a1552..4e7075a65 100644 --- a/src/app/store/settings/state.ts +++ b/src/app/store/settings/state.ts @@ -12,6 +12,7 @@ export interface SettingsState { isOnRampPossibility: boolean; isConversionTracked: boolean; pendingReactivateAds: boolean; + adsImpressionsLinked: boolean; } export const settingsInitialState: SettingsState = { @@ -20,5 +21,6 @@ export const settingsInitialState: SettingsState = { balanceMode: BalanceMode.Fiat, isOnRampPossibility: false, isConversionTracked: false, - pendingReactivateAds: false + pendingReactivateAds: false, + adsImpressionsLinked: false }; diff --git a/src/app/templates/partners-promotion/index.tsx b/src/app/templates/partners-promotion/index.tsx index 67d72a7bb..81fa8c4fa 100644 --- a/src/app/templates/partners-promotion/index.tsx +++ b/src/app/templates/partners-promotion/index.tsx @@ -12,7 +12,7 @@ import { usePromotionHidingTimestampSelector } from 'app/store/partners-promotion/selectors'; import { AdsProviderName, AdsProviderTitle } from 'lib/ads'; -import { AnalyticsEventCategory, useAnalytics } from 'lib/analytics'; +import { postAdImpression } from 'lib/apis/ads-api'; import { AD_HIDING_TIMEOUT } from 'lib/constants'; import { HypelabPromotion } from './components/hypelab-promotion'; @@ -40,7 +40,6 @@ const shouldBeHiddenTemporarily = (hiddenAt: number) => { export const PartnersPromotion = memo(({ variant, id, pageName, withPersonaProvider }) => { const isImageAd = variant === PartnersPromotionVariant.Image; const adsViewerAddress = useAdsViewerPkh(); - const { trackEvent } = useAnalytics(); const { popup } = useAppEnv(); const dispatch = useDispatch(); const hiddenAt = usePromotionHidingTimestampSelector(id); @@ -72,19 +71,10 @@ export const PartnersPromotion = memo(({ variant, id, pa const handleAdRectSeen = useCallback(() => { if (isAnalyticsSentRef.current) return; - trackEvent( - 'Internal Ads Activity', - AnalyticsEventCategory.General, - { - variant: providerName === 'Persona' ? PartnersPromotionVariant.Image : variant, - page: pageName, - provider: AdsProviderTitle[providerName], - accountPkh: adsViewerAddress - }, - true - ); + postAdImpression(adsViewerAddress, AdsProviderTitle[providerName], { pageName }); + isAnalyticsSentRef.current = true; - }, [providerName, pageName, adsViewerAddress, variant, trackEvent]); + }, [providerName, pageName, adsViewerAddress]); const handleClosePartnersPromoClick = useCallback>( e => { diff --git a/src/background.ts b/src/background.ts index 2e8339f56..106492270 100644 --- a/src/background.ts +++ b/src/background.ts @@ -3,6 +3,7 @@ import { getMessaging } from '@firebase/messaging/sw'; import browser from 'webextension-polyfill'; import 'lib/keep-bg-worker-alive/background'; +import { putStoredAppInstallIdentity } from 'app/storage/app-install-id'; import { getStoredAppUpdateDetails, putStoredAppUpdateDetails, @@ -11,10 +12,13 @@ import { import { updateRulesStorage } from 'lib/ads/update-rules-storage'; import { EnvVars } from 'lib/env'; import { start } from 'lib/temple/back/main'; +import { generateKeyPair } from 'lib/utils/ecdsa'; + +import PackageJSON from '../package.json'; browser.runtime.onInstalled.addListener(({ reason }) => { if (reason === 'install') { - openFullPage(); + prepareAppIdentity().finally(openFullPage); return; } @@ -57,3 +61,17 @@ const firebase = initializeApp(JSON.parse(EnvVars.TEMPLE_FIREBASE_CONFIG)); getMessaging(firebase); updateRulesStorage(); + +async function prepareAppIdentity() { + const { privateKey, publicKey, publicKeyHash } = await generateKeyPair(); + + const ts = new Date().toISOString(); + + await putStoredAppInstallIdentity({ + version: PackageJSON.version, + privateKey, + publicKey, + publicKeyHash: publicKeyHash.slice(0, 32), + ts + }); +} diff --git a/src/lib/ads/link-ads-impressions.ts b/src/lib/ads/link-ads-impressions.ts new file mode 100644 index 000000000..10dada05b --- /dev/null +++ b/src/lib/ads/link-ads-impressions.ts @@ -0,0 +1,21 @@ +import { getStoredAppInstallIdentity } from 'app/storage/app-install-id'; +import { postLinkAdsImpressions } from 'lib/apis/ads-api'; +import { signData } from 'lib/utils/ecdsa'; + +export async function performLinkingOfAdsImpressions(accountPkh: string) { + const identity = await getStoredAppInstallIdentity(); + if (!identity) { + console.warn('App identity not found'); + return; + } + + const { + privateKey, + // Actual installId will be derived by the API + publicKey: installId + } = identity; + + const signature = await signData(privateKey, 'LINK_ADS_IMPRESSIONS'); + + await postLinkAdsImpressions(accountPkh, installId, signature); +} diff --git a/src/lib/apis/ads-api.ts b/src/lib/apis/ads-api.ts new file mode 100644 index 000000000..603a69d13 --- /dev/null +++ b/src/lib/apis/ads-api.ts @@ -0,0 +1,38 @@ +import axiosFetchAdapter from '@vespaiach/axios-fetch-adapter'; +import axios from 'axios'; + +import { APP_VERSION, EnvVars } from 'lib/env'; + +const axiosClient = axios.create({ + baseURL: EnvVars.TEMPLE_ADS_API_URL, + adapter: axiosFetchAdapter +}); + +interface ImpressionDetails { + /** For external */ + urlDomain?: string; + /** For internal */ + pageName?: string; +} + +export async function postAdImpression( + accountPkh: string, + provider: string, + { urlDomain, pageName }: ImpressionDetails +) { + await axiosClient.post('/impression', { + accountPkh, + urlDomain, + pageName, + provider, + appVersion: APP_VERSION + }); +} + +export async function postAnonymousAdImpression(installId: string, urlDomain: string, provider: string) { + await axiosClient.post('/impression', { installId, urlDomain, provider, appVersion: APP_VERSION }); +} + +export async function postLinkAdsImpressions(accountPkh: string, installId: string, signature: string) { + await axiosClient.post('/link-impressions', { accountPkh, installId, signature, appVersion: APP_VERSION }); +} diff --git a/src/lib/env.ts b/src/lib/env.ts index 26f9da18d..f99efb679 100644 --- a/src/lib/env.ts +++ b/src/lib/env.ts @@ -4,7 +4,7 @@ export const APP_VERSION = PackageJSON.version; /** Only Mises browser among supported vendors counts as a mobile platform */ // @ts-expect-error -export const IS_MISES_BROWSER = navigator.userAgentData?.mobile ?? false; +export const IS_MISES_BROWSER = Boolean(navigator.userAgentData?.mobile); export const IS_DEV_ENV = process.env.NODE_ENV === 'development'; @@ -17,6 +17,7 @@ export const BACKGROUND_IS_WORKER = process.env.BACKGROUND_IS_WORKER === 'true'; export const EnvVars = { TEMPLE_WALLET_API_URL: process.env.TEMPLE_WALLET_API_URL!, TEMPLE_WALLET_DEXES_API_URL: process.env.TEMPLE_WALLET_DEXES_API_URL!, + TEMPLE_ADS_API_URL: process.env.TEMPLE_ADS_API_URL!, TEMPLE_WALLET_JITSU_TRACKING_HOST: process.env.TEMPLE_WALLET_JITSU_TRACKING_HOST!, TEMPLE_WALLET_JITSU_WRITE_KEY: process.env.TEMPLE_WALLET_JITSU_WRITE_KEY!, TEMPLE_WALLET_EXOLIX_API_KEY: process.env.TEMPLE_WALLET_EXOLIX_API_KEY!, diff --git a/src/lib/temple/back/main.ts b/src/lib/temple/back/main.ts index cd28aace5..cbf73e318 100644 --- a/src/lib/temple/back/main.ts +++ b/src/lib/temple/back/main.ts @@ -1,7 +1,9 @@ import browser, { Runtime } from 'webextension-polyfill'; +import { getStoredAppInstallIdentity } from 'app/storage/app-install-id'; import { updateRulesStorage } from 'lib/ads/update-rules-storage'; -import { ADS_VIEWER_ADDRESS_STORAGE_KEY, ANALYTICS_USER_ID_STORAGE_KEY, ContentScriptType } from 'lib/constants'; +import { postAdImpression, postAnonymousAdImpression } from 'lib/apis/ads-api'; +import { ADS_VIEWER_ADDRESS_STORAGE_KEY, ContentScriptType } from 'lib/constants'; import { E2eMessageType } from 'lib/e2e/types'; import { BACKGROUND_IS_WORKER } from 'lib/env'; import { fetchFromStorage } from 'lib/storage'; @@ -10,8 +12,6 @@ import { clearAsyncStorages } from 'lib/temple/reset'; import { TempleMessageType, TempleRequest, TempleResponse } from 'lib/temple/types'; import { getTrackedCashbackServiceDomain, getTrackedUrl } from 'lib/utils/url-track/url-track.utils'; -import { AnalyticsEventCategory } from '../analytics-types'; - import * as Actions from './actions'; import * as Analytics from './analytics'; import { intercom } from './defaults'; @@ -260,12 +260,6 @@ const getAdsViewerPkh = async (): Promise => { return frontState.accounts[0]?.publicKeyHash; }; -const getAnalyticsUserId = async (): Promise => { - const { [ANALYTICS_USER_ID_STORAGE_KEY]: userId } = await browser.storage.local.get(ANALYTICS_USER_ID_STORAGE_KEY); - - return userId; -}; - browser.runtime.onMessage.addListener(async msg => { try { switch (msg?.type) { @@ -294,14 +288,14 @@ browser.runtime.onMessage.addListener(async msg => { break; case ContentScriptType.ExternalAdsActivity: - const userId = await getAnalyticsUserId(); - await Analytics.trackEvent({ - category: AnalyticsEventCategory.General, - userId: userId ?? '', - event: 'External Ads Activity', - properties: { domain: new URL(msg.url).hostname, accountPkh, provider: msg.provider }, - rpc: undefined - }); + const urlDomain = new URL(msg.url).hostname; + if (accountPkh) await postAdImpression(accountPkh, msg.provider, { urlDomain }); + else { + const identity = await getStoredAppInstallIdentity(); + if (!identity) throw new Error('App identity not found'); + const installId = identity.publicKeyHash; + await postAnonymousAdImpression(installId, urlDomain, msg.provider); + } break; } } catch (e) { diff --git a/src/lib/temple/reset.ts b/src/lib/temple/reset.ts index f966b0dc2..5f167ef98 100644 --- a/src/lib/temple/reset.ts +++ b/src/lib/temple/reset.ts @@ -1,3 +1,4 @@ +import { getStoredAppInstallIdentity, putStoredAppInstallIdentity } from 'app/storage/app-install-id'; import { browser } from 'lib/browser'; import * as Repo from 'lib/temple/repo'; @@ -9,7 +10,9 @@ export async function clearAllStorages() { export async function clearAsyncStorages() { await Repo.db.delete(); await Repo.db.open(); + const appIdentity = await getStoredAppInstallIdentity(); await browser.storage.local.clear(); + if (appIdentity) putStoredAppInstallIdentity(appIdentity); await browser.storage.session?.clear(); } diff --git a/src/lib/utils/ecdsa.ts b/src/lib/utils/ecdsa.ts new file mode 100644 index 000000000..849d0480d --- /dev/null +++ b/src/lib/utils/ecdsa.ts @@ -0,0 +1,58 @@ +import { stringToUInt8Array } from './buffers'; + +export async function generateKeyPair() { + const keyPair = await crypto.subtle.generateKey( + { + name: 'ECDSA', + namedCurve: 'P-256' + }, + true, + ['sign', 'verify'] + ); + + const publicKeyBuffer = await crypto.subtle.exportKey('spki', keyPair.publicKey!); + const privateKeyBuffer = await crypto.subtle.exportKey('pkcs8', keyPair.privateKey!); + + const publicKey = Buffer.from(publicKeyBuffer).toString('hex'); + const privateKey = Buffer.from(privateKeyBuffer).toString('hex'); + const publicKeyHash = await hashPublicKey(publicKey); + + return { publicKey, privateKey, publicKeyHash }; +} + +function importKey(keyHex: string, isPrivate: boolean) { + return crypto.subtle.importKey( + isPrivate ? 'pkcs8' : 'spki', + Buffer.from(keyHex, 'hex'), + { + name: 'ECDSA', + namedCurve: 'P-256' + }, + true, + isPrivate ? ['sign'] : ['verify'] + ); +} + +export async function signData(privateKeyHex: string, message: string) { + const privateKey = await importKey(privateKeyHex, true); + const encoder = new TextEncoder(); + const messageBytes = encoder.encode(message); + + const signature = await crypto.subtle.sign( + { + name: 'ECDSA', + hash: { name: 'SHA-256' } + }, + privateKey, + messageBytes + ); + + return Buffer.from(signature).toString('base64'); +} + +async function hashPublicKey(publicKey: string) { + const publicKeyBytes = stringToUInt8Array(publicKey); + const hashBuffer = await crypto.subtle.digest('SHA-256', publicKeyBytes); + + return Buffer.from(hashBuffer).toString('hex'); +} diff --git a/src/replaceAds.ts b/src/replaceAds.ts index 180e6a24f..920909416 100644 --- a/src/replaceAds.ts +++ b/src/replaceAds.ts @@ -2,7 +2,13 @@ import browser from 'webextension-polyfill'; import { configureAds } from 'lib/ads/configure-ads'; import { importExtensionAdsModule } from 'lib/ads/import-extension-ads-module'; -import { ContentScriptType, ADS_RULES_UPDATE_INTERVAL, WEBSITES_ANALYTICS_ENABLED } from 'lib/constants'; +import { + ContentScriptType, + ADS_RULES_UPDATE_INTERVAL, + WEBSITES_ANALYTICS_ENABLED, + ADS_VIEWER_ADDRESS_STORAGE_KEY +} from 'lib/constants'; +import { IS_MISES_BROWSER } from 'lib/env'; import { fetchFromStorage } from 'lib/storage'; import { getRulesFromContentScript, clearRulesCache } from './content-scripts/replace-ads'; @@ -38,9 +44,9 @@ const replaceAds = async () => { // Prevents the script from running in an Iframe if (window.frameElement === null) { - fetchFromStorage(WEBSITES_ANALYTICS_ENABLED) - .then(async enabled => { - if (!enabled) return; + checkIfShouldReplaceAds() + .then(async shouldReplace => { + if (!shouldReplace) return; await configureAds(); // Replace ads with ours @@ -48,3 +54,11 @@ if (window.frameElement === null) { }) .catch(console.error); } + +async function checkIfShouldReplaceAds() { + const accountPkhFromStorage = await fetchFromStorage(ADS_VIEWER_ADDRESS_STORAGE_KEY); + + if (accountPkhFromStorage) return await fetchFromStorage(WEBSITES_ANALYTICS_ENABLED); + + return IS_MISES_BROWSER; +} diff --git a/webpack.config.ts b/webpack.config.ts index 148010efa..4c882b25a 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -11,6 +11,7 @@ import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import * as Path from 'path'; +import WebPack from 'webpack'; import ExtensionReloaderMV3BadlyTyped, { ExtensionReloader as ExtensionReloaderMV3Type } from 'webpack-ext-reloader-mv3'; @@ -234,6 +235,10 @@ const backgroundConfig = (() => { color: '#ed8936' }), + new WebPack.NormalModuleReplacementPlugin(/^react$/, () => { + throw new Error('React is not allowed in BG script'); + }), + new CleanWebpackPlugin({ cleanOnceBeforeBuildPatterns: ['background/**'], cleanStaleWebpackAssets: false, From 249afe014a9347c69a63ba51e6877426466e8aad Mon Sep 17 00:00:00 2001 From: Inokentii Mazhara Date: Mon, 29 Jul 2024 13:43:03 +0300 Subject: [PATCH 3/5] TW-1492: Adapt ads to Mises extension (#1170) * TW-1492 Adapt ads to Mises extension * TW-1492 Update @temple-wallet/extension-ads * TW-1492 Revert project version change * TW-1492 Additional fixtures * TW-1492 Refactoring according to comments --- .env.dist | 8 + .github/workflows/code-quality.yml | 8 + .github/workflows/manual-builds.yml | 8 + .github/workflows/release.yml | 8 + .github/workflows/secrets-setup/action.yml | 24 +++ package.json | 2 +- .../misc/ad-banners/small-tkey-inpage-ad.png | Bin 0 -> 42220 bytes src/content-scripts/replace-ads/ads-rules.ts | 2 + .../replace-ads/persona-ad.iframe.ts | 36 +--- src/lib/ads/configure-ads.ts | 197 ++++++++++++++++-- src/lib/env.ts | 12 +- src/lib/utils/index.ts | 4 +- yarn.lock | 14 +- 13 files changed, 263 insertions(+), 60 deletions(-) create mode 100644 public/misc/ad-banners/small-tkey-inpage-ad.png diff --git a/.env.dist b/.env.dist index 3282744d5..fa271bcef 100644 --- a/.env.dist +++ b/.env.dist @@ -17,17 +17,25 @@ TEMPLE_FIREBASE_CONFIG= TEMPLE_FIREBASE_MESSAGING_VAPID_KEY= HYPELAB_API_URL= +HYPELAB_MISES_SMALL_PLACEMENT_SLUG= HYPELAB_SMALL_PLACEMENT_SLUG= +HYPELAB_MISES_HIGH_PLACEMENT_SLUG= HYPELAB_HIGH_PLACEMENT_SLUG= +HYPELAB_MISES_WIDE_PLACEMENT_SLUG= HYPELAB_WIDE_PLACEMENT_SLUG= +HYPELAB_MISES_NATIVE_PLACEMENT_SLUG= HYPELAB_NATIVE_PLACEMENT_SLUG= HYPELAB_PROPERTY_SLUG= HYPELAB_ADS_WINDOW_URL= PERSONA_ADS_API_KEY= +PERSONA_ADS_MISES_BANNER_UNIT_ID= PERSONA_ADS_BANNER_UNIT_ID= +PERSONA_ADS_MISES_WIDE_BANNER_UNIT_ID= PERSONA_ADS_WIDE_BANNER_UNIT_ID= +PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID= PERSONA_ADS_MEDIUM_BANNER_UNIT_ID= +PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID= PERSONA_ADS_SQUARISH_BANNER_UNIT_ID= TEMPLE_ADS_ORIGIN_PASSPHRASE= diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 57db80eb3..010c88762 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -38,16 +38,24 @@ jobs: TEMPLE_FIREBASE_CONFIG: ${{ secrets.TEMPLE_FIREBASE_CONFIG }} TEMPLE_FIREBASE_MESSAGING_VAPID_KEY: ${{ secrets.TEMPLE_FIREBASE_MESSAGING_VAPID_KEY }} HYPELAB_API_URL: ${{ vars.HYPELAB_API_URL }} + HYPELAB_MISES_SMALL_PLACEMENT_SLUG: ${{ vars.HYPELAB_MISES_SMALL_PLACEMENT_SLUG }} HYPELAB_SMALL_PLACEMENT_SLUG: ${{ vars.HYPELAB_SMALL_PLACEMENT_SLUG }} + HYPELAB_MISES_HIGH_PLACEMENT_SLUG: ${{ vars.HYPELAB_MISES_HIGH_PLACEMENT_SLUG }} HYPELAB_HIGH_PLACEMENT_SLUG: ${{ vars.HYPELAB_HIGH_PLACEMENT_SLUG }} + HYPELAB_MISES_WIDE_PLACEMENT_SLUG: ${{ vars.HYPELAB_MISES_WIDE_PLACEMENT_SLUG }} HYPELAB_WIDE_PLACEMENT_SLUG: ${{ vars.HYPELAB_WIDE_PLACEMENT_SLUG }} + HYPELAB_MISES_NATIVE_PLACEMENT_SLUG: ${{ vars.HYPELAB_MISES_NATIVE_PLACEMENT_SLUG }} HYPELAB_NATIVE_PLACEMENT_SLUG: ${{ vars.HYPELAB_NATIVE_PLACEMENT_SLUG }} HYPELAB_PROPERTY_SLUG: ${{ vars.HYPELAB_PROPERTY_SLUG }} HYPELAB_ADS_WINDOW_URL: ${{ vars.HYPELAB_ADS_WINDOW_URL }} PERSONA_ADS_API_KEY: ${{ vars.PERSONA_ADS_API_KEY }} + PERSONA_ADS_MISES_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MISES_BANNER_UNIT_ID }} PERSONA_ADS_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_BANNER_UNIT_ID }} + PERSONA_ADS_MISES_WIDE_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MISES_WIDE_BANNER_UNIT_ID }} PERSONA_ADS_WIDE_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_WIDE_BANNER_UNIT_ID }} + PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID }} PERSONA_ADS_MEDIUM_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MEDIUM_BANNER_UNIT_ID }} + PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID }} PERSONA_ADS_SQUARISH_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_SQUARISH_BANNER_UNIT_ID }} TEMPLE_ADS_ORIGIN_PASSPHRASE: ${{ vars.TEMPLE_ADS_ORIGIN_PASSPHRASE }} CONVERSION_VERIFICATION_URL: ${{ vars.CONVERSION_VERIFICATION_URL }} diff --git a/.github/workflows/manual-builds.yml b/.github/workflows/manual-builds.yml index 1ceacdf5e..668531f12 100644 --- a/.github/workflows/manual-builds.yml +++ b/.github/workflows/manual-builds.yml @@ -56,16 +56,24 @@ jobs: TEMPLE_FIREBASE_CONFIG: ${{ secrets.TEMPLE_FIREBASE_CONFIG }} TEMPLE_FIREBASE_MESSAGING_VAPID_KEY: ${{ secrets.TEMPLE_FIREBASE_MESSAGING_VAPID_KEY }} HYPELAB_API_URL: ${{ vars.HYPELAB_API_URL }} + HYPELAB_MISES_SMALL_PLACEMENT_SLUG: ${{ vars.HYPELAB_MISES_SMALL_PLACEMENT_SLUG }} HYPELAB_SMALL_PLACEMENT_SLUG: ${{ vars.HYPELAB_SMALL_PLACEMENT_SLUG }} + HYPELAB_MISES_HIGH_PLACEMENT_SLUG: ${{ vars.HYPELAB_MISES_HIGH_PLACEMENT_SLUG }} HYPELAB_HIGH_PLACEMENT_SLUG: ${{ vars.HYPELAB_HIGH_PLACEMENT_SLUG }} + HYPELAB_MISES_WIDE_PLACEMENT_SLUG: ${{ vars.HYPELAB_MISES_WIDE_PLACEMENT_SLUG }} HYPELAB_WIDE_PLACEMENT_SLUG: ${{ vars.HYPELAB_WIDE_PLACEMENT_SLUG }} + HYPELAB_MISES_NATIVE_PLACEMENT_SLUG: ${{ vars.HYPELAB_MISES_NATIVE_PLACEMENT_SLUG }} HYPELAB_NATIVE_PLACEMENT_SLUG: ${{ vars.HYPELAB_NATIVE_PLACEMENT_SLUG }} HYPELAB_PROPERTY_SLUG: ${{ vars.HYPELAB_PROPERTY_SLUG }} HYPELAB_ADS_WINDOW_URL: ${{ vars.HYPELAB_ADS_WINDOW_URL }} PERSONA_ADS_API_KEY: ${{ vars.PERSONA_ADS_API_KEY }} + PERSONA_ADS_MISES_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MISES_BANNER_UNIT_ID }} PERSONA_ADS_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_BANNER_UNIT_ID }} + PERSONA_ADS_MISES_WIDE_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MISES_WIDE_BANNER_UNIT_ID }} PERSONA_ADS_WIDE_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_WIDE_BANNER_UNIT_ID }} + PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID }} PERSONA_ADS_MEDIUM_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MEDIUM_BANNER_UNIT_ID }} + PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID }} PERSONA_ADS_SQUARISH_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_SQUARISH_BANNER_UNIT_ID }} TEMPLE_ADS_ORIGIN_PASSPHRASE: ${{ vars.TEMPLE_ADS_ORIGIN_PASSPHRASE }} CONVERSION_VERIFICATION_URL: ${{ vars.CONVERSION_VERIFICATION_URL }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff4dbd685..f65de5794 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,16 +38,24 @@ jobs: TEMPLE_FIREBASE_CONFIG: ${{ secrets.TEMPLE_FIREBASE_CONFIG }} TEMPLE_FIREBASE_MESSAGING_VAPID_KEY: ${{ secrets.TEMPLE_FIREBASE_MESSAGING_VAPID_KEY }} HYPELAB_API_URL: ${{ vars.HYPELAB_API_URL }} + HYPELAB_MISES_SMALL_PLACEMENT_SLUG: ${{ vars.HYPELAB_MISES_SMALL_PLACEMENT_SLUG }} HYPELAB_SMALL_PLACEMENT_SLUG: ${{ vars.HYPELAB_SMALL_PLACEMENT_SLUG }} + HYPELAB_MISES_HIGH_PLACEMENT_SLUG: ${{ vars.HYPELAB_MISES_HIGH_PLACEMENT_SLUG }} HYPELAB_HIGH_PLACEMENT_SLUG: ${{ vars.HYPELAB_HIGH_PLACEMENT_SLUG }} + HYPELAB_MISES_WIDE_PLACEMENT_SLUG: ${{ vars.HYPELAB_MISES_WIDE_PLACEMENT_SLUG }} HYPELAB_WIDE_PLACEMENT_SLUG: ${{ vars.HYPELAB_WIDE_PLACEMENT_SLUG }} + HYPELAB_MISES_NATIVE_PLACEMENT_SLUG: ${{ vars.HYPELAB_MISES_NATIVE_PLACEMENT_SLUG }} HYPELAB_NATIVE_PLACEMENT_SLUG: ${{ vars.HYPELAB_NATIVE_PLACEMENT_SLUG }} HYPELAB_PROPERTY_SLUG: ${{ vars.HYPELAB_PROPERTY_SLUG }} HYPELAB_ADS_WINDOW_URL: ${{ vars.HYPELAB_ADS_WINDOW_URL }} PERSONA_ADS_API_KEY: ${{ vars.PERSONA_ADS_API_KEY }} + PERSONA_ADS_MISES_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MISES_BANNER_UNIT_ID }} PERSONA_ADS_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_BANNER_UNIT_ID }} + PERSONA_ADS_MISES_WIDE_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MISES_WIDE_BANNER_UNIT_ID }} PERSONA_ADS_WIDE_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_WIDE_BANNER_UNIT_ID }} + PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID }} PERSONA_ADS_MEDIUM_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MEDIUM_BANNER_UNIT_ID }} + PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID }} PERSONA_ADS_SQUARISH_BANNER_UNIT_ID: ${{ vars.PERSONA_ADS_SQUARISH_BANNER_UNIT_ID }} TEMPLE_ADS_ORIGIN_PASSPHRASE: ${{ vars.TEMPLE_ADS_ORIGIN_PASSPHRASE }} CONVERSION_VERIFICATION_URL: ${{ vars.CONVERSION_VERIFICATION_URL }} diff --git a/.github/workflows/secrets-setup/action.yml b/.github/workflows/secrets-setup/action.yml index fd13c31de..4c861f776 100644 --- a/.github/workflows/secrets-setup/action.yml +++ b/.github/workflows/secrets-setup/action.yml @@ -35,12 +35,20 @@ inputs: required: true HYPELAB_API_URL: required: true + HYPELAB_MISES_SMALL_PLACEMENT_SLUG: + required: true HYPELAB_SMALL_PLACEMENT_SLUG: required: true + HYPELAB_MISES_HIGH_PLACEMENT_SLUG: + required: true HYPELAB_HIGH_PLACEMENT_SLUG: required: true + HYPELAB_MISES_WIDE_PLACEMENT_SLUG: + required: true HYPELAB_WIDE_PLACEMENT_SLUG: required: true + HYPELAB_MISES_NATIVE_PLACEMENT_SLUG: + required: true HYPELAB_NATIVE_PLACEMENT_SLUG: required: true HYPELAB_PROPERTY_SLUG: @@ -49,12 +57,20 @@ inputs: required: true PERSONA_ADS_API_KEY: required: true + PERSONA_ADS_MISES_BANNER_UNIT_ID: + required: true PERSONA_ADS_BANNER_UNIT_ID: required: true + PERSONA_ADS_MISES_WIDE_BANNER_UNIT_ID: + required: true PERSONA_ADS_WIDE_BANNER_UNIT_ID: required: true + PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID: + required: true PERSONA_ADS_MEDIUM_BANNER_UNIT_ID: required: true + PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID: + required: true PERSONA_ADS_SQUARISH_BANNER_UNIT_ID: required: true CONVERSION_VERIFICATION_URL: @@ -132,17 +148,25 @@ runs: TEMPLE_FIREBASE_MESSAGING_VAPID_KEY=${{ inputs.TEMPLE_FIREBASE_MESSAGING_VAPID_KEY }} HYPELAB_API_URL=${{ inputs.HYPELAB_API_URL }} + HYPELAB_MISES_SMALL_PLACEMENT_SLUG=${{ inputs.HYPELAB_MISES_SMALL_PLACEMENT_SLUG }} HYPELAB_SMALL_PLACEMENT_SLUG=${{ inputs.HYPELAB_SMALL_PLACEMENT_SLUG }} + HYPELAB_MISES_HIGH_PLACEMENT_SLUG=${{ inputs.HYPELAB_MISES_HIGH_PLACEMENT_SLUG }} HYPELAB_HIGH_PLACEMENT_SLUG=${{ inputs.HYPELAB_HIGH_PLACEMENT_SLUG }} + HYPELAB_MISES_WIDE_PLACEMENT_SLUG=${{ inputs.HYPELAB_MISES_WIDE_PLACEMENT_SLUG }} HYPELAB_WIDE_PLACEMENT_SLUG=${{ inputs.HYPELAB_WIDE_PLACEMENT_SLUG }} + HYPELAB_MISES_NATIVE_PLACEMENT_SLUG=${{ inputs.HYPELAB_MISES_NATIVE_PLACEMENT_SLUG }} HYPELAB_NATIVE_PLACEMENT_SLUG=${{ inputs.HYPELAB_NATIVE_PLACEMENT_SLUG }} HYPELAB_PROPERTY_SLUG=${{ inputs.HYPELAB_PROPERTY_SLUG }} HYPELAB_ADS_WINDOW_URL=${{ inputs.HYPELAB_ADS_WINDOW_URL }} PERSONA_ADS_API_KEY=${{ inputs.PERSONA_ADS_API_KEY }} + PERSONA_ADS_MISES_BANNER_UNIT_ID=${{ inputs.PERSONA_ADS_MISES_BANNER_UNIT_ID }} PERSONA_ADS_BANNER_UNIT_ID=${{ inputs.PERSONA_ADS_BANNER_UNIT_ID }} + PERSONA_ADS_MISES_WIDE_BANNER_UNIT_ID=${{ inputs.PERSONA_ADS_MISES_WIDE_BANNER_UNIT_ID }} PERSONA_ADS_WIDE_BANNER_UNIT_ID=${{ inputs.PERSONA_ADS_WIDE_BANNER_UNIT_ID }} + PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID=${{ inputs.PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID }} PERSONA_ADS_MEDIUM_BANNER_UNIT_ID=${{ inputs.PERSONA_ADS_MEDIUM_BANNER_UNIT_ID }} + PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID=${{ inputs.PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID }} PERSONA_ADS_SQUARISH_BANNER_UNIT_ID=${{ inputs.PERSONA_ADS_SQUARISH_BANNER_UNIT_ID }} TEMPLE_ADS_ORIGIN_PASSPHRASE=${{ inputs.TEMPLE_ADS_ORIGIN_PASSPHRASE }} CONVERSION_VERIFICATION_URL=${{ inputs.CONVERSION_VERIFICATION_URL }} diff --git a/package.json b/package.json index 057bdd36c..302145b59 100644 --- a/package.json +++ b/package.json @@ -232,6 +232,6 @@ "follow-redirects": "^1.15.4" }, "optionalDependencies": { - "@temple-wallet/extension-ads": "^6.3.2" + "@temple-wallet/extension-ads": "^7.1.0" } } diff --git a/public/misc/ad-banners/small-tkey-inpage-ad.png b/public/misc/ad-banners/small-tkey-inpage-ad.png new file mode 100644 index 0000000000000000000000000000000000000000..6e4a197f84a776f9cc2b0d0fa053e8d2fc6fe614 GIT binary patch literal 42220 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 .then(accountPkhFromStorage => getPersonaAdClient(accountPkhFromStorage)) - .then(({ client, environment }) => { - const adUnitId = getUnitId(shape as PersonaAdShape, environment === 'staging'); - + .then(({ client }) => { return client.showBannerAd( // @ts-expect-error // for missung `adConfig` prop - { adUnitId, containerId: CONTAINER_ID }, + { adUnitId: slug, containerId: CONTAINER_ID }, errorMsg => { throw new Error(String(errorMsg)); } @@ -37,28 +32,3 @@ const postMessage = (message: object) => JSON.stringify({ ...message, id }), '*' // This is required ); - -const getUnitId = (shape: PersonaAdShape, isStaging: boolean) => { - if (isStaging) - switch (shape) { - case 'wide': - return '3a094192-4c7b-4761-a50c-bd9b6a67e987'; - case 'medium': - return 'cf20c750-2fe4-4761-861f-b73b2247fd4d'; - case 'squarish': - return 'bf498e26-eb16-4e35-8954-e65690f28819'; - default: - return PERSONA_STAGING_ADS_BANNER_UNIT_ID; - } - - switch (shape) { - case 'wide': - return EnvVars.PERSONA_ADS_WIDE_BANNER_UNIT_ID; - case 'medium': - return EnvVars.PERSONA_ADS_MEDIUM_BANNER_UNIT_ID; - case 'squarish': - return EnvVars.PERSONA_ADS_SQUARISH_BANNER_UNIT_ID; - default: - return EnvVars.PERSONA_ADS_BANNER_UNIT_ID; - } -}; diff --git a/src/lib/ads/configure-ads.ts b/src/lib/ads/configure-ads.ts index a306fa6d3..daab63e63 100644 --- a/src/lib/ads/configure-ads.ts +++ b/src/lib/ads/configure-ads.ts @@ -2,10 +2,12 @@ import browser from 'webextension-polyfill'; import { buildSwapPageUrlQuery } from 'app/pages/Swap/utils/build-url-query'; import { ADS_META_SEARCH_PARAM_NAME, ContentScriptType, ORIGIN_SEARCH_PARAM_NAME } from 'lib/constants'; -import { APP_VERSION, EnvVars } from 'lib/env'; +import { APP_VERSION, EnvVars, IS_MISES_BROWSER } from 'lib/env'; +import { isTruthy } from 'lib/utils'; import { importExtensionAdsModule } from './import-extension-ads-module'; +const smallTkeyInpageAdUrl = browser.runtime.getURL(`/misc/ad-banners/small-tkey-inpage-ad.png`); const tkeyInpageAdUrl = browser.runtime.getURL(`/misc/ad-banners/tkey-inpage-ad.png`); const swapTkeyUrl = `${browser.runtime.getURL('fullpage.html')}#/swap?${buildSwapPageUrlQuery( @@ -14,33 +16,188 @@ const swapTkeyUrl = `${browser.runtime.getURL('fullpage.html')}#/swap?${buildSwa true )}`; +const getAdsStackIframeURL = (id: string, adsMetadataIds: any[], origin: string) => { + const url = new URL(browser.runtime.getURL('iframes/ads-stack.html')); + url.searchParams.set('id', id); + adsMetadataIds.forEach(adMetadataId => + url.searchParams.append(ADS_META_SEARCH_PARAM_NAME, JSON.stringify(adMetadataId)) + ); + url.searchParams.set(ORIGIN_SEARCH_PARAM_NAME, origin); + + return url.toString(); +}; + +const buildNativeAdsMeta = (containerWidth: number, containerHeight: number) => + [ + { + source: { + providerName: 'HypeLab' as const, + native: true as const, + slug: IS_MISES_BROWSER ? EnvVars.HYPELAB_MISES_NATIVE_PLACEMENT_SLUG : EnvVars.HYPELAB_NATIVE_PLACEMENT_SLUG + }, + dimensions: { + width: Math.max(160, containerWidth), + height: Math.max(16, containerHeight), + minContainerWidth: 2, + minContainerHeight: 2, + maxContainerWidth: Infinity, + maxContainerHeight: Infinity + } + }, + EnvVars.USE_ADS_STUBS && { + source: { + providerName: 'Temple' as const, + native: true as const + }, + dimensions: { + width: Math.max(160, containerWidth), + height: Math.max(16, containerHeight), + minContainerWidth: 2, + minContainerHeight: 2, + maxContainerWidth: Infinity, + maxContainerHeight: Infinity + } + } + ].filter(isTruthy); + +const bannerAdsMeta = [ + { + source: { + providerName: 'HypeLab' as const, + native: false, + slug: IS_MISES_BROWSER ? EnvVars.HYPELAB_MISES_WIDE_PLACEMENT_SLUG : EnvVars.HYPELAB_WIDE_PLACEMENT_SLUG + }, + dimensions: { + width: 728, + height: 90, + minContainerWidth: 728, + minContainerHeight: 90, + maxContainerWidth: Infinity, + maxContainerHeight: 300 + } + }, + { + source: { + providerName: 'Temple' as const + }, + dimensions: { + width: 728, + height: 90, + minContainerWidth: 728, + minContainerHeight: 90, + maxContainerWidth: Infinity, + maxContainerHeight: 300 + } + }, + { + source: { + providerName: 'Persona' as const, + slug: IS_MISES_BROWSER + ? EnvVars.PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID + : EnvVars.PERSONA_ADS_MEDIUM_BANNER_UNIT_ID + }, + dimensions: { + width: 600, + height: 160, + minContainerWidth: 600, + minContainerHeight: 160, + maxContainerWidth: 800, + maxContainerHeight: 300 + } + }, + { + source: { + providerName: 'HypeLab' as const, + native: false, + slug: IS_MISES_BROWSER ? EnvVars.HYPELAB_MISES_HIGH_PLACEMENT_SLUG : EnvVars.HYPELAB_HIGH_PLACEMENT_SLUG + }, + dimensions: { + width: 300, + height: 250, + minContainerWidth: 300, + minContainerHeight: 250, + maxContainerWidth: 700, + maxContainerHeight: Infinity + } + }, + { + source: { + providerName: 'Persona' as const, + slug: IS_MISES_BROWSER + ? EnvVars.PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID + : EnvVars.PERSONA_ADS_SQUARISH_BANNER_UNIT_ID + }, + dimensions: { + width: 300, + height: 250, + minContainerWidth: 300, + minContainerHeight: 250, + maxContainerWidth: 700, + maxContainerHeight: Infinity + } + }, + { + source: { + providerName: 'HypeLab' as const, + native: false, + slug: IS_MISES_BROWSER ? EnvVars.HYPELAB_MISES_SMALL_PLACEMENT_SLUG : EnvVars.HYPELAB_SMALL_PLACEMENT_SLUG, + shouldNotUseStrictContainerLimits: true + }, + dimensions: { + width: 320, + height: 50, + minContainerWidth: 320, + minContainerHeight: 50, + maxContainerWidth: 420, + maxContainerHeight: 130 + } + }, + { + source: { + providerName: 'Persona' as const, + slug: IS_MISES_BROWSER ? EnvVars.PERSONA_ADS_MISES_BANNER_UNIT_ID : EnvVars.PERSONA_ADS_BANNER_UNIT_ID + }, + dimensions: { + width: 321, + height: 101, + minContainerWidth: 321, + minContainerHeight: 101, + maxContainerWidth: 420, + maxContainerHeight: 130 + } + }, + EnvVars.USE_ADS_STUBS && { + source: { + providerName: 'Temple' as const, + shouldNotUseStrictContainerLimits: true + }, + dimensions: { + width: 320, + height: 50, + minContainerWidth: 320, + minContainerHeight: 50, + maxContainerWidth: 420, + maxContainerHeight: 130 + } + } +].filter(isTruthy); + export const configureAds = async () => { const { configureAds: originalConfigureAds } = await importExtensionAdsModule(); originalConfigureAds({ hypelabAdsWindowUrl: EnvVars.HYPELAB_ADS_WINDOW_URL, - hypelab: { - regular: EnvVars.HYPELAB_HIGH_PLACEMENT_SLUG, - native: EnvVars.HYPELAB_NATIVE_PLACEMENT_SLUG, - small: EnvVars.HYPELAB_SMALL_PLACEMENT_SLUG, - wide: EnvVars.HYPELAB_WIDE_PLACEMENT_SLUG - }, swapTkeyUrl, tkeyInpageAdUrl, + smallTkeyInpageAdUrl, externalAdsActivityMessageType: ContentScriptType.ExternalAdsActivity, // Types are added to prevent TS errors for the core build - getPersonaIframeURL: (id: string, shape: string) => - browser.runtime.getURL(`iframes/persona-ad.html?id=${id}&shape=${shape}`), - getAdsStackIframeURL: (id: string, adsMetadataIds: any[], origin: string) => { - const url = new URL(browser.runtime.getURL('iframes/ads-stack.html')); - url.searchParams.set('id', id); - adsMetadataIds.forEach(adMetadataId => - url.searchParams.append(ADS_META_SEARCH_PARAM_NAME, JSON.stringify(adMetadataId)) - ); - url.searchParams.set(ORIGIN_SEARCH_PARAM_NAME, origin); - - return url.toString(); - }, + getPersonaIframeURL: (id: string, slug: string) => + browser.runtime.getURL(`iframes/persona-ad.html?id=${id}&slug=${slug}`), + getAdsStackIframeURL, + buildNativeAdsMeta, + bannerAdsMeta, extVersion: APP_VERSION, - templePassphrase: EnvVars.TEMPLE_ADS_ORIGIN_PASSPHRASE + templePassphrase: EnvVars.TEMPLE_ADS_ORIGIN_PASSPHRASE, + isMisesBrowser: IS_MISES_BROWSER }); }; diff --git a/src/lib/env.ts b/src/lib/env.ts index f99efb679..991abe4a7 100644 --- a/src/lib/env.ts +++ b/src/lib/env.ts @@ -30,17 +30,27 @@ export const EnvVars = { TEMPLE_FIREBASE_MESSAGING_VAPID_KEY: process.env.TEMPLE_FIREBASE_MESSAGING_VAPID_KEY!, TEMPLE_WALLET_DEVELOPMENT_BRANCH_NAME: process.env.TEMPLE_WALLET_DEVELOPMENT_BRANCH_NAME!, HYPELAB_API_URL: process.env.HYPELAB_API_URL!, + HYPELAB_MISES_SMALL_PLACEMENT_SLUG: process.env.HYPELAB_MISES_SMALL_PLACEMENT_SLUG!, HYPELAB_SMALL_PLACEMENT_SLUG: process.env.HYPELAB_SMALL_PLACEMENT_SLUG!, + HYPELAB_MISES_HIGH_PLACEMENT_SLUG: process.env.HYPELAB_MISES_HIGH_PLACEMENT_SLUG!, HYPELAB_HIGH_PLACEMENT_SLUG: process.env.HYPELAB_HIGH_PLACEMENT_SLUG!, + HYPELAB_MISES_WIDE_PLACEMENT_SLUG: process.env.HYPELAB_MISES_WIDE_PLACEMENT_SLUG!, HYPELAB_WIDE_PLACEMENT_SLUG: process.env.HYPELAB_WIDE_PLACEMENT_SLUG!, + HYPELAB_MISES_NATIVE_PLACEMENT_SLUG: process.env.HYPELAB_MISES_NATIVE_PLACEMENT_SLUG!, HYPELAB_NATIVE_PLACEMENT_SLUG: process.env.HYPELAB_NATIVE_PLACEMENT_SLUG!, HYPELAB_PROPERTY_SLUG: process.env.HYPELAB_PROPERTY_SLUG!, HYPELAB_ADS_WINDOW_URL: process.env.HYPELAB_ADS_WINDOW_URL!, PERSONA_ADS_API_KEY: process.env.PERSONA_ADS_API_KEY!, + PERSONA_ADS_MISES_BANNER_UNIT_ID: process.env.PERSONA_ADS_MISES_BANNER_UNIT_ID!, PERSONA_ADS_BANNER_UNIT_ID: process.env.PERSONA_ADS_BANNER_UNIT_ID!, + PERSONA_ADS_MISES_WIDE_BANNER_UNIT_ID: process.env.PERSONA_ADS_MISES_WIDE_BANNER_UNIT_ID!, PERSONA_ADS_WIDE_BANNER_UNIT_ID: process.env.PERSONA_ADS_WIDE_BANNER_UNIT_ID!, + PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID: process.env.PERSONA_ADS_MISES_MEDIUM_BANNER_UNIT_ID!, PERSONA_ADS_MEDIUM_BANNER_UNIT_ID: process.env.PERSONA_ADS_MEDIUM_BANNER_UNIT_ID!, + PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID: process.env.PERSONA_ADS_MISES_SQUARISH_BANNER_UNIT_ID!, PERSONA_ADS_SQUARISH_BANNER_UNIT_ID: process.env.PERSONA_ADS_SQUARISH_BANNER_UNIT_ID!, TEMPLE_ADS_ORIGIN_PASSPHRASE: process.env.TEMPLE_ADS_ORIGIN_PASSPHRASE!, - CONVERSION_VERIFICATION_URL: process.env.CONVERSION_VERIFICATION_URL! + CONVERSION_VERIFICATION_URL: process.env.CONVERSION_VERIFICATION_URL!, + /** Whether ads stubs should be added if loading failed. Set it to `true` only for testing */ + USE_ADS_STUBS: process.env.USE_ADS_STUBS === 'true' } as const; diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 5d72db06d..64d27f395 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -1,11 +1,13 @@ import { Mutex } from 'async-mutex'; -import { noop } from 'lodash'; export { arrayBufferToString, stringToArrayBuffer, uInt8ArrayToString, stringToUInt8Array } from './buffers'; /** From lodash */ type Truthy = T extends null | undefined | void | false | '' | 0 | 0n ? never : T; +/** From lodash */ +function noop() {} + export const isTruthy = (value: T): value is Truthy => Boolean(value); /** With strict equality check (i.e. `===`) */ diff --git a/yarn.lock b/yarn.lock index df5637c11..ddf27e2c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3617,16 +3617,17 @@ dependencies: nanoid "^3.1.25" -"@temple-wallet/extension-ads@^6.3.2": - version "6.3.2" - resolved "https://registry.yarnpkg.com/@temple-wallet/extension-ads/-/extension-ads-6.3.2.tgz#ed404ff82dbeb3f15e93572d2d8275312e9157d8" - integrity sha512-45Lflim8Mwgy0EYNcW5KkmBw+odReQS5EJQFtSqoH4kbs2ZcLn9AMSBy/mPEAktRmBfKZ7y+8hC8lN6tLDS0/A== +"@temple-wallet/extension-ads@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@temple-wallet/extension-ads/-/extension-ads-7.1.0.tgz#b72bc7bb51192e84ca7614a08fc0f85a3415a8e5" + integrity sha512-/cQ4KP8Xze1rtHVDu1ydvlZBxwc2OkNs5NDsBIukUXBP86yJ7skl6dcYQEyGF1fgzt1HOuYjHwuM0+i55W821Q== dependencies: "@vespaiach/axios-fetch-adapter" "^0.3.1" axios "^1.6.7" crypto-js "^4.2.0" lodash "^4.17.21" nanoid "^5.0.6" + semver "^7.6.2" webextension-polyfill "^0.10.0" "@temple-wallet/jest-webextension-mock@^4.1.0": @@ -12167,6 +12168,11 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^7.6.2: + version "7.6.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" From 42d1c94bcc4fed64ad8cdd7ce96cd2e1c6f1e300 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 29 Jul 2024 13:43:42 +0300 Subject: [PATCH 4/5] TW-1504: [Mises] Temple ads native prompt (#1174) * TW-1504: [Mises] Temple ads native prompt * TW-1504: [Mises] Temple ads native prompt. Refactor * TW-1504: [Mises] Temple ads native prompt. + === true check --- src/app/storage/app-install-id.ts | 6 +++--- src/app/storage/mises-browser.ts | 6 ++++++ src/lib/env.ts | 6 +++++- src/lib/temple/reset.ts | 10 +++++++--- src/replaceAds.ts | 4 ++-- 5 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 src/app/storage/mises-browser.ts diff --git a/src/app/storage/app-install-id.ts b/src/app/storage/app-install-id.ts index e12971571..60e6936bd 100644 --- a/src/app/storage/app-install-id.ts +++ b/src/app/storage/app-install-id.ts @@ -1,6 +1,6 @@ import { fetchFromStorage, putToStorage } from 'lib/storage'; -const storageKey = 'APP_INSTALL_IDENTITY'; +export const APP_INSTALL_IDENTITY_STORAGE_KEY = 'APP_INSTALL_IDENTITY'; interface AppInstallIdentity { version: string; @@ -10,7 +10,7 @@ interface AppInstallIdentity { ts: string; } -export const getStoredAppInstallIdentity = () => fetchFromStorage(storageKey); +export const getStoredAppInstallIdentity = () => fetchFromStorage(APP_INSTALL_IDENTITY_STORAGE_KEY); export const putStoredAppInstallIdentity = (value: AppInstallIdentity) => - putToStorage(storageKey, value); + putToStorage(APP_INSTALL_IDENTITY_STORAGE_KEY, value); diff --git a/src/app/storage/mises-browser.ts b/src/app/storage/mises-browser.ts new file mode 100644 index 000000000..02d1c76b1 --- /dev/null +++ b/src/app/storage/mises-browser.ts @@ -0,0 +1,6 @@ +import { fetchFromStorage } from 'lib/storage'; + +export const MISES_INSTALL_ENABLED_ADS_STORAGE_KEY = 'MISES_ACCEPT_TOS'; + +export const getMisesInstallEnabledAds = () => + fetchFromStorage<'true'>(MISES_INSTALL_ENABLED_ADS_STORAGE_KEY).then(r => r === 'true' || r === true); diff --git a/src/lib/env.ts b/src/lib/env.ts index 991abe4a7..b6313ce38 100644 --- a/src/lib/env.ts +++ b/src/lib/env.ts @@ -2,7 +2,11 @@ import PackageJSON from '../../package.json'; export const APP_VERSION = PackageJSON.version; -/** Only Mises browser among supported vendors counts as a mobile platform */ +/** + * Only Mises browser among supported vendors counts as a mobile platform + * + * `navigator.userAgentData.brands.find(b => b.brand === 'Mises')` will be available in future versions. + */ // @ts-expect-error export const IS_MISES_BROWSER = Boolean(navigator.userAgentData?.mobile); diff --git a/src/lib/temple/reset.ts b/src/lib/temple/reset.ts index 5f167ef98..2372ae22a 100644 --- a/src/lib/temple/reset.ts +++ b/src/lib/temple/reset.ts @@ -1,4 +1,5 @@ -import { getStoredAppInstallIdentity, putStoredAppInstallIdentity } from 'app/storage/app-install-id'; +import { APP_INSTALL_IDENTITY_STORAGE_KEY } from 'app/storage/app-install-id'; +import { MISES_INSTALL_ENABLED_ADS_STORAGE_KEY } from 'app/storage/mises-browser'; import { browser } from 'lib/browser'; import * as Repo from 'lib/temple/repo'; @@ -10,9 +11,12 @@ export async function clearAllStorages() { export async function clearAsyncStorages() { await Repo.db.delete(); await Repo.db.open(); - const appIdentity = await getStoredAppInstallIdentity(); + const keptRecord = await browser.storage.local.get([ + APP_INSTALL_IDENTITY_STORAGE_KEY, + MISES_INSTALL_ENABLED_ADS_STORAGE_KEY + ]); await browser.storage.local.clear(); - if (appIdentity) putStoredAppInstallIdentity(appIdentity); + await browser.storage.local.set(keptRecord); await browser.storage.session?.clear(); } diff --git a/src/replaceAds.ts b/src/replaceAds.ts index 920909416..ce7ecbc7b 100644 --- a/src/replaceAds.ts +++ b/src/replaceAds.ts @@ -1,5 +1,6 @@ import browser from 'webextension-polyfill'; +import { getMisesInstallEnabledAds } from 'app/storage/mises-browser'; import { configureAds } from 'lib/ads/configure-ads'; import { importExtensionAdsModule } from 'lib/ads/import-extension-ads-module'; import { @@ -8,7 +9,6 @@ import { WEBSITES_ANALYTICS_ENABLED, ADS_VIEWER_ADDRESS_STORAGE_KEY } from 'lib/constants'; -import { IS_MISES_BROWSER } from 'lib/env'; import { fetchFromStorage } from 'lib/storage'; import { getRulesFromContentScript, clearRulesCache } from './content-scripts/replace-ads'; @@ -60,5 +60,5 @@ async function checkIfShouldReplaceAds() { if (accountPkhFromStorage) return await fetchFromStorage(WEBSITES_ANALYTICS_ENABLED); - return IS_MISES_BROWSER; + return await getMisesInstallEnabledAds(); } From 6913593c878822f18fe871c09f1b8e51abed949c Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 29 Jul 2024 14:14:38 +0300 Subject: [PATCH 5/5] TW-1491: [epic][Mises] Bump version to 1.23.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 302145b59..20baf2584 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "temple-wallet", - "version": "1.23.1", + "version": "1.23.2", "private": true, "scripts": { "start-run": "cross-env TS_NODE_PROJECT=\"webpack/tsconfig.json\" webpack --watch --stats errors-warnings",