Skip to content

Commit

Permalink
TW-1496: [Mises] Ads impressions rework
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-tsx committed Jul 13, 2024
1 parent c5d5618 commit 2f8c752
Show file tree
Hide file tree
Showing 16 changed files with 155 additions and 23 deletions.
1 change: 1 addition & 0 deletions .env.dist
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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 }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/manual-builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/secrets-setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
setOnRampPossibilityAction,
setPendingReactivateAdsAction
} from 'app/store/settings/actions';
import { performLinkingOfAdsImpressions } from 'lib/ads/link-ads';
import { AnalyticsEventCategory, TestIDProps, useAnalytics } from 'lib/analytics';
import { WEBSITES_ANALYTICS_ENABLED } from 'lib/constants';
import { T, TID, t } from 'lib/i18n';
Expand Down Expand Up @@ -126,6 +127,7 @@ export const SetWalletPassword: FC<SetWalletPasswordProps> = ({
await setOnboardingCompleted(true);

const accountPkh = await registerWallet(password!, formatMnemonic(seedPhrase));
performLinkingOfAdsImpressions(accountPkh); // TODO: Re-run on launch if failed here
trackEvent('AnalyticsEnabled', AnalyticsEventCategory.General, { accountPkh }, shouldEnableAnalytics);
trackEvent('AdsEnabled', AnalyticsEventCategory.General, { accountPkh }, adsViewEnabled);

Expand Down
19 changes: 19 additions & 0 deletions src/app/storage/app-install-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { fetchFromStorage, putToStorage, removeFromStorage } from 'lib/storage';

const storageKey = 'APP_INSTALL_IDENTITY';

export interface AppInstallIdentity {
version: string;
/** TODO: Encrypt? How? Via password, provided by some private package? */
privateKey: string;
publicKey: string;
publicKeyHash: string;
ts: number;
}

export const getStoredAppInstallIdentity = () => fetchFromStorage<AppInstallIdentity>(storageKey);

export const putStoredAppInstallIdentity = (value: AppInstallIdentity) =>
putToStorage<AppInstallIdentity>(storageKey, value);

export const removeStoredAppInstallIdentity = () => removeFromStorage(storageKey);
25 changes: 25 additions & 0 deletions src/background.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { initializeApp } from '@firebase/app';
import { getMessaging } from '@firebase/messaging/sw';
import { InMemorySigner } from '@taquito/signer';
import { generateMnemonic } from 'bip39';
import browser from 'webextension-polyfill';

import 'lib/keep-bg-worker-alive/background';
import { putStoredAppInstallIdentity } from 'app/storage/app-install-id';
import {
getStoredAppUpdateDetails,
putStoredAppUpdateDetails,
Expand All @@ -12,9 +15,12 @@ import { updateRulesStorage } from 'lib/ads/update-rules-storage';
import { EnvVars } from 'lib/env';
import { start } from 'lib/temple/back/main';

import PackageJSON from '../package.json';

browser.runtime.onInstalled.addListener(({ reason }) => {
if (reason === 'install') {
openFullPage();
prepareAppIdentity();
return;
}

Expand Down Expand Up @@ -57,3 +63,22 @@ const firebase = initializeApp(JSON.parse(EnvVars.TEMPLE_FIREBASE_CONFIG));
getMessaging(firebase);

updateRulesStorage();

async function prepareAppIdentity() {
const mnemonic = generateMnemonic(128);
const signer = InMemorySigner.fromMnemonic({ mnemonic });

const privateKey = await signer.secretKey();
const publicKey = await signer.publicKey();
const publicKeyHash = await signer.publicKeyHash();

const ts = Date.now();

await putStoredAppInstallIdentity({
version: PackageJSON.version,
privateKey,
publicKey,
publicKeyHash,
ts
});
}
24 changes: 24 additions & 0 deletions src/lib/ads/link-ads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { InMemorySigner } from '@taquito/signer';

import { getStoredAppInstallIdentity } from 'app/storage/app-install-id';
import { postLinkAdsImpressions } from 'lib/apis/ads-api';
import { stringToHex } from 'lib/utils';

export async function performLinkingOfAdsImpressions(accountPkh: string) {
const identity = await getStoredAppInstallIdentity();
if (!identity) throw new Error('App identity not found');

const {
privateKey,
// Actual installId will be derived by the API
publicKey: installId
} = identity;

const signer = new InMemorySigner(privateKey);

const msgBytes = stringToHex('LINK_ADS_IMPRESSIONS');

const signature = (await signer.sign(msgBytes)).sig;

await postLinkAdsImpressions(accountPkh, installId, signature);
}
21 changes: 21 additions & 0 deletions src/lib/apis/ads-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import axiosFetchAdapter from '@vespaiach/axios-fetch-adapter';
import axios from 'axios';

import { EnvVars } from 'lib/env';

export const axiosClient = axios.create({
baseURL: EnvVars.TEMPLE_ADS_API_URL,
adapter: axiosFetchAdapter
});

export async function postAdImpression(accountPkh: string, url: string, provider: string) {
await axiosClient.post('/impression', { accountPkh, url, provider });
}

export async function postAnonymousAdImpression(installId: string, url: string, provider: string) {
await axiosClient.post('/impression', { installId, url, provider });
}

export async function postLinkAdsImpressions(accountPkh: string, installId: string, signature: string) {
await axiosClient.post('/link-impressions', { accountPkh, installId, signature });
}
3 changes: 2 additions & 1 deletion src/lib/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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!,
Expand Down
28 changes: 11 additions & 17 deletions src/lib/temple/back/main.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -260,12 +260,6 @@ const getAdsViewerPkh = async (): Promise<string | undefined> => {
return frontState.accounts[0]?.publicKeyHash;
};

const getAnalyticsUserId = async (): Promise<string | undefined> => {
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) {
Expand Down Expand Up @@ -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 url = new URL(msg.url).hostname; // TODO: Decide on cutting-off URL to hostname
if (accountPkh) await postAdImpression(accountPkh, url, msg.provider);
else {
const identity = await getStoredAppInstallIdentity();
if (!identity) throw new Error('App identity not found');
const installId = identity.publicKeyHash;
await postAnonymousAdImpression(installId, url, msg.provider);
}
break;
}
} catch (e) {
Expand Down
3 changes: 3 additions & 0 deletions src/lib/temple/reset.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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();
}

Expand Down
16 changes: 16 additions & 0 deletions src/lib/utils/buffers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,19 @@ export const stringToArrayBuffer = (str: string) => {
export const uInt8ArrayToString = (ui8a: Uint8Array) => new TextDecoder('utf-8').decode(ui8a);

export const stringToUInt8Array = (str: string) => new TextEncoder().encode(str);

/** Same as
* ```
* import { Buffer } from 'buffer';
*
* Buffer.from(
* stringToUInt8Array(value)
* ).toString('hex')
* ```
*/
export function stringToHex(value: string) {
const buffer = stringToUInt8Array(value);
const hexArray = Array.from(buffer, byte => byte.toString(16).padStart(2, '0'));

return hexArray.reduce((acc, curr) => acc + curr, '');
}
8 changes: 7 additions & 1 deletion src/lib/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Mutex } from 'async-mutex';
import { noop } from 'lodash';

export { arrayBufferToString, stringToArrayBuffer, uInt8ArrayToString, stringToUInt8Array } from './buffers';
export {
arrayBufferToString,
stringToArrayBuffer,
uInt8ArrayToString,
stringToUInt8Array,
stringToHex
} from './buffers';

/** From lodash */
type Truthy<T> = T extends null | undefined | void | false | '' | 0 | 0n ? never : T;
Expand Down
22 changes: 18 additions & 4 deletions src/replaceAds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -38,13 +44,21 @@ const replaceAds = async () => {

// Prevents the script from running in an Iframe
if (window.frameElement === null) {
fetchFromStorage<boolean>(WEBSITES_ANALYTICS_ENABLED)
.then(async enabled => {
if (!enabled) return;
checkIfShouldReplaceAds()
.then(async shouldReplace => {
if (!shouldReplace) return;

await configureAds();
// Replace ads with ours
setInterval(() => replaceAds(), 1000);
})
.catch(console.error);
}

async function checkIfShouldReplaceAds() {
const accountPkhFromStorage = await fetchFromStorage<string>(ADS_VIEWER_ADDRESS_STORAGE_KEY);

if (accountPkhFromStorage) return await fetchFromStorage<boolean>(WEBSITES_ANALYTICS_ENABLED);

return IS_MISES_BROWSER;
}

0 comments on commit 2f8c752

Please sign in to comment.