@temple-wallet/extension-ads 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,115 @@
1
+ import browser from 'webextension-polyfill';
2
+
3
+ import { AdsConfiguration } from 'src/ads-configuration';
4
+ import { AD_SEEN_THRESHOLD } from 'src/constants';
5
+ import { AdsProviderName, AdsProviderTitle } from 'src/types/ads-provider';
6
+
7
+ const loadingAdsIds = new Set();
8
+ const loadedAdsIds = new Set();
9
+ const alreadySentAnalyticsAdsIds = new Set();
10
+
11
+ const IFRAME_READY_TIMEOUT = 10_000;
12
+
13
+ export const subscribeToIframeLoadIfNecessary = (
14
+ adId: string,
15
+ providerName: AdsProviderName,
16
+ element: HTMLIFrameElement
17
+ ) => {
18
+ if (loadingAdsIds.has(adId)) {
19
+ return;
20
+ }
21
+
22
+ loadingAdsIds.add(adId);
23
+
24
+ return new Promise<void>((resolve, reject) => {
25
+ setTimeout(() => {
26
+ window.removeEventListener('message', messageListener);
27
+ reject(new Error(`Timeout exceeded for ${adId}`));
28
+ }, IFRAME_READY_TIMEOUT);
29
+
30
+ const messageListener = (event: MessageEvent<any>) => {
31
+ if (event.source !== element.contentWindow) return;
32
+
33
+ try {
34
+ const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
35
+
36
+ if (data.id !== adId) return;
37
+
38
+ if (data.type === 'ready') {
39
+ window.removeEventListener('message', messageListener);
40
+ resolve();
41
+ } else if (data.type === 'error') {
42
+ window.removeEventListener('message', messageListener);
43
+ reject(new Error(data.reason ?? 'Unknown error'));
44
+ }
45
+ } catch (error) {
46
+ console.error('Observing error:', error);
47
+ }
48
+ };
49
+
50
+ window.addEventListener('message', messageListener);
51
+ })
52
+ .then(() => {
53
+ if (loadedAdsIds.has(adId)) return;
54
+
55
+ loadedAdsIds.add(adId);
56
+ const adIsSeen = adRectIsSeen(element);
57
+
58
+ if (adIsSeen) {
59
+ sendExternalAdsActivity(adId, providerName);
60
+ } else {
61
+ observeIntersection(element, providerName);
62
+ }
63
+ })
64
+ .finally(() => void loadingAdsIds.delete(adId));
65
+ };
66
+
67
+ export const observeIntersection = (element: HTMLElement, providerName: AdsProviderName) => {
68
+ const observer = new IntersectionObserver(
69
+ entries => {
70
+ if (entries.some(entry => entry.isIntersecting)) {
71
+ sendExternalAdsActivity(element.id, providerName);
72
+ observer.disconnect();
73
+ }
74
+ },
75
+ { threshold: AD_SEEN_THRESHOLD }
76
+ );
77
+
78
+ observer.observe(element);
79
+ };
80
+
81
+ const sendExternalAdsActivity = (adId: string, providerName: AdsProviderName) => {
82
+ if (alreadySentAnalyticsAdsIds.has(adId)) {
83
+ return;
84
+ }
85
+
86
+ alreadySentAnalyticsAdsIds.add(adId);
87
+
88
+ const url = window.parent.location.href;
89
+
90
+ browser.runtime
91
+ .sendMessage({
92
+ type: AdsConfiguration.EXTERNAL_ADS_ACTIVITY_MESSAGE_TYPE,
93
+ url,
94
+ provider: AdsProviderTitle[providerName]
95
+ })
96
+ .catch(err => void console.error(err));
97
+ };
98
+
99
+ const adRectIsSeen = (element: Element) => {
100
+ const elementRect = element.getBoundingClientRect();
101
+ const viewport = window.visualViewport;
102
+
103
+ if (!viewport) {
104
+ return false;
105
+ }
106
+
107
+ const intersectionX0 = Math.min(Math.max(0, elementRect.x), viewport.width);
108
+ const intersectionX1 = Math.min(Math.max(0, elementRect.x + elementRect.width), viewport.width);
109
+ const intersectionY0 = Math.min(Math.max(0, elementRect.y), viewport.height);
110
+ const intersectionY1 = Math.min(Math.max(0, elementRect.y + elementRect.height), viewport.height);
111
+ const elementArea = elementRect.width * elementRect.height;
112
+ const intersectionArea = (intersectionX1 - intersectionX0) * (intersectionY1 - intersectionY0);
113
+
114
+ return intersectionArea / elementArea >= AD_SEEN_THRESHOLD;
115
+ };
@@ -0,0 +1,7 @@
1
+ import { StringRecord } from 'src/utils';
2
+
3
+ export const overrideElementStyles = (element: HTMLElement, overrides: StringRecord) => {
4
+ for (const stylePropName in overrides) {
5
+ element.style.setProperty(stylePropName, overrides[stylePropName]);
6
+ }
7
+ };
@@ -0,0 +1,104 @@
1
+ import { TEMPLE_WALLET_AD_ATTRIBUTE_NAME } from 'src/constants';
2
+ import { AdActionType, InsertAdAction, ReplaceElementWithAdAction } from 'src/types/ads-actions';
3
+ import { AdMetadata } from 'src/types/ads-meta';
4
+
5
+ import { makeHypelabAdView, makePersonaAdView, makeTKeyAdView } from './ads-views';
6
+ import { observeIntersection, subscribeToIframeLoadIfNecessary } from './observing';
7
+ import { overrideElementStyles } from './override-element-styles';
8
+
9
+ const processInsertAdActionOnce = async (action: InsertAdAction, ad: AdMetadata, wrapperElement: HTMLDivElement) => {
10
+ const { source, dimensions } = ad;
11
+
12
+ const { elementStyle = {}, stylesOverrides = [] } = action;
13
+
14
+ stylesOverrides.sort((a, b) => a.parentDepth - b.parentDepth);
15
+
16
+ let stylesOverridesCurrentElement: HTMLElement | null;
17
+
18
+ const { providerName } = source;
19
+
20
+ const { element: adElement, postAppend } =
21
+ providerName === 'Temple'
22
+ ? makeTKeyAdView(dimensions.width, dimensions.height, elementStyle)
23
+ : providerName === 'HypeLab'
24
+ ? makeHypelabAdView(source, dimensions, elementStyle)
25
+ : makePersonaAdView(source.shape, dimensions, elementStyle);
26
+
27
+ adElement.setAttribute(TEMPLE_WALLET_AD_ATTRIBUTE_NAME, 'true');
28
+ wrapperElement.appendChild(adElement);
29
+
30
+ switch (action.type) {
31
+ case AdActionType.ReplaceAllChildren:
32
+ stylesOverridesCurrentElement = action.parent;
33
+ action.parent.innerHTML = '';
34
+ action.parent.appendChild(wrapperElement);
35
+ break;
36
+ case AdActionType.ReplaceElement:
37
+ stylesOverridesCurrentElement = action.element.parentElement;
38
+ action.element.replaceWith(wrapperElement);
39
+ break;
40
+ default:
41
+ stylesOverridesCurrentElement = action.parent;
42
+ action.parent.insertBefore(wrapperElement, action.parent.children[action.insertionIndex]);
43
+ break;
44
+ }
45
+
46
+ if (postAppend) await postAppend();
47
+
48
+ if (adElement instanceof HTMLIFrameElement) {
49
+ await subscribeToIframeLoadIfNecessary(adElement.id, source.providerName, adElement);
50
+ } else {
51
+ observeIntersection(adElement, source.providerName);
52
+ }
53
+
54
+ let currentParentDepth = 0;
55
+ stylesOverrides.forEach(({ parentDepth, style }) => {
56
+ while (parentDepth > currentParentDepth && stylesOverridesCurrentElement) {
57
+ stylesOverridesCurrentElement = stylesOverridesCurrentElement.parentElement;
58
+ currentParentDepth++;
59
+ }
60
+ if (stylesOverridesCurrentElement) {
61
+ overrideElementStyles(stylesOverridesCurrentElement, style);
62
+ }
63
+ });
64
+ };
65
+
66
+ export const processInsertAdAction = async (action: InsertAdAction, ad: AdMetadata) => {
67
+ const { shouldUseDivWrapper, divWrapperStyle = {} } = action;
68
+
69
+ const wrapperElement = document.createElement('div');
70
+ wrapperElement.setAttribute(TEMPLE_WALLET_AD_ATTRIBUTE_NAME, 'true');
71
+
72
+ if (shouldUseDivWrapper) {
73
+ overrideElementStyles(wrapperElement, divWrapperStyle);
74
+ } else {
75
+ wrapperElement.style.display = 'contents';
76
+ }
77
+
78
+ await processInsertAdActionOnce(action, ad, wrapperElement).catch(error => {
79
+ console.error('Inserting an ad attempt error:', error);
80
+
81
+ const nextAd = action.fallbacks.shift();
82
+ if (nextAd) {
83
+ const { ad, fallbacks, divWrapperStyle, elementStyle, stylesOverrides } = action;
84
+ const newAction: ReplaceElementWithAdAction = {
85
+ type: AdActionType.ReplaceElement,
86
+ element: wrapperElement,
87
+ ad,
88
+ fallbacks,
89
+ divWrapperStyle,
90
+ elementStyle,
91
+ stylesOverrides
92
+ };
93
+
94
+ return processInsertAdAction(newAction, nextAd);
95
+ }
96
+
97
+ const emptyAdElement = document.createElement('div');
98
+ emptyAdElement.setAttribute(TEMPLE_WALLET_AD_ATTRIBUTE_NAME, 'true');
99
+ emptyAdElement.style.display = 'none';
100
+ wrapperElement.replaceWith(emptyAdElement);
101
+
102
+ throw error;
103
+ });
104
+ };
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ export type { AdAction, AdActionType } from './types/ads-actions';
2
+
3
+ export type { PersonaAdShape } from './types/ads-meta';
4
+
5
+ export type { AdsProviderName, AdsProviderTitle } from './types/ads-provider';
6
+
7
+ export type { AdsRules } from './types/ads-rules';
8
+
9
+ export type { RawAllAdsRules } from './types/temple-wallet-api';
10
+
11
+ export { getAdsActions } from './ads-actions';
12
+
13
+ export { configureAds } from './ads-configuration';
14
+
15
+ export { executeAdsActions } from './execute-ads-actions';
16
+
17
+ export { transformRawRules } from './transform-raw-rules';
18
+
19
+ export { TempleWalletApi } from './temple-wallet-api';
@@ -0,0 +1,80 @@
1
+ import axiosFetchAdapter from '@vespaiach/axios-fetch-adapter';
2
+ import axios, { AxiosInstance, AxiosResponse } from 'axios';
3
+
4
+ import {
5
+ RawAllAdsRules,
6
+ RawAdPlacesRule,
7
+ RawAdProvidersRule,
8
+ RawPermanentAdPlacesRule
9
+ } from 'src/types/temple-wallet-api';
10
+
11
+ const withFetchDataExtraction =
12
+ <A extends unknown[], T>(fetchFn: (...args: A) => Promise<AxiosResponse<T>>) =>
13
+ async (...args: A) => {
14
+ const { data } = await fetchFn(...args);
15
+
16
+ return data;
17
+ };
18
+
19
+ export class TempleWalletApi {
20
+ private api: AxiosInstance;
21
+
22
+ constructor(baseUrl: string) {
23
+ this.api = axios.create({
24
+ baseURL: new URL('/api', baseUrl).href,
25
+ adapter: axiosFetchAdapter
26
+ });
27
+ }
28
+
29
+ getAdPlacesRulesForAllDomains = withFetchDataExtraction(() =>
30
+ this.api.get<Record<string, RawAdPlacesRule[]>>('/slise-ad-rules/ad-places')
31
+ );
32
+
33
+ getProvidersToReplaceAtAllSites = withFetchDataExtraction(() =>
34
+ this.api.get<string[]>('/slise-ad-rules/providers/all-sites')
35
+ );
36
+
37
+ getProvidersRulesForAllDomains = withFetchDataExtraction(() =>
38
+ this.api.get<Record<string, RawAdProvidersRule[]>>(`/slise-ad-rules/providers/by-sites`)
39
+ );
40
+
41
+ getSelectorsForAllProviders = withFetchDataExtraction(() =>
42
+ this.api.get<Record<string, string[]>>('/slise-ad-rules/providers')
43
+ );
44
+
45
+ getPermanentAdPlacesRulesForAllDomains = withFetchDataExtraction(() =>
46
+ this.api.get<Record<string, RawPermanentAdPlacesRule[]>>('/slise-ad-rules/ad-places/permanent')
47
+ );
48
+
49
+ getPermanentNativeAdPlacesRulesForAllDomains = withFetchDataExtraction(() =>
50
+ this.api.get<Record<string, RawPermanentAdPlacesRule[]>>('/slise-ad-rules/ad-places/permanent-native')
51
+ );
52
+
53
+ getAllRules = async (): Promise<RawAllAdsRules> => {
54
+ const [
55
+ adPlacesRulesForAllDomains,
56
+ providersRulesForAllDomains,
57
+ providersSelectors,
58
+ providersToReplaceAtAllSites,
59
+ permanentAdPlacesRulesForAllDomains,
60
+ permanentNativeAdPlacesRulesForAllDomains
61
+ ] = await Promise.all([
62
+ this.getAdPlacesRulesForAllDomains(),
63
+ this.getProvidersRulesForAllDomains(),
64
+ this.getSelectorsForAllProviders(),
65
+ this.getProvidersToReplaceAtAllSites(),
66
+ this.getPermanentAdPlacesRulesForAllDomains(),
67
+ this.getPermanentNativeAdPlacesRulesForAllDomains()
68
+ ]);
69
+
70
+ return {
71
+ adPlacesRulesForAllDomains,
72
+ providersRulesForAllDomains,
73
+ providersSelectors,
74
+ providersToReplaceAtAllSites,
75
+ permanentAdPlacesRulesForAllDomains,
76
+ permanentNativeAdPlacesRulesForAllDomains,
77
+ timestamp: Date.now()
78
+ };
79
+ };
80
+ }
@@ -0,0 +1,139 @@
1
+ import { isEqual } from 'lodash';
2
+
3
+ import { AdPlacesRule, AdsRules } from 'src/types/ads-rules';
4
+ import { RawAllAdsRules } from 'src/types/temple-wallet-api';
5
+
6
+ export const transformRawRules = (location: Location, rules: RawAllAdsRules): AdsRules => {
7
+ const {
8
+ adPlacesRulesForAllDomains,
9
+ providersRulesForAllDomains,
10
+ providersSelectors,
11
+ providersToReplaceAtAllSites,
12
+ permanentAdPlacesRulesForAllDomains,
13
+ permanentNativeAdPlacesRulesForAllDomains = {},
14
+ timestamp
15
+ } = rules;
16
+ const { hostname, href } = location;
17
+ const hrefWithoutHash = href.replace(/#.*$/, '');
18
+
19
+ const hrefMatchPredicate = (regex: RegExp) => {
20
+ const hrefToTest = regex.source.includes('#') ? href : hrefWithoutHash;
21
+
22
+ return regex.test(hrefToTest);
23
+ };
24
+
25
+ return {
26
+ adPlacesRules: buildAdPlacesRules(hostname, hrefMatchPredicate, adPlacesRulesForAllDomains),
27
+ permanentAdPlacesRules: buildPermanentAdPlacesRules(
28
+ hostname,
29
+ hrefMatchPredicate,
30
+ permanentAdPlacesRulesForAllDomains,
31
+ permanentNativeAdPlacesRulesForAllDomains
32
+ ),
33
+ providersSelector: buildProvidersSelector(
34
+ hostname,
35
+ hrefMatchPredicate,
36
+ providersRulesForAllDomains,
37
+ providersSelectors,
38
+ providersToReplaceAtAllSites
39
+ ),
40
+ timestamp
41
+ };
42
+ };
43
+
44
+ const buildAdPlacesRules = (
45
+ hostname: string,
46
+ hrefMatchPredicate: (regex: RegExp) => boolean,
47
+ adPlacesRulesForAllDomains: RawAllAdsRules['adPlacesRulesForAllDomains']
48
+ ): AdsRules['adPlacesRules'] => {
49
+ const adPlacesRules = (adPlacesRulesForAllDomains[hostname] ?? []).map(({ urlRegexes, ...restRuleProps }) => ({
50
+ ...restRuleProps,
51
+ urlRegexes: urlRegexes.map(regex => new RegExp(regex))
52
+ }));
53
+
54
+ const aggregatedRelatedAdPlacesRules = adPlacesRules.reduce<Omit<AdPlacesRule, 'urlRegexes'>[]>(
55
+ (acc, { urlRegexes, selector, ...restProps }) => {
56
+ if (!urlRegexes.some(hrefMatchPredicate)) return acc;
57
+
58
+ const { cssString, ...restSelectorProps } = selector;
59
+ const ruleToComplementIndex = acc.findIndex(({ selector: candidateSelector, ...candidateRestProps }) => {
60
+ const { cssString: _candidateCssString, ...restCandidateSelectorProps } = candidateSelector;
61
+
62
+ return isEqual(restSelectorProps, restCandidateSelectorProps) && isEqual(restProps, candidateRestProps);
63
+ });
64
+ if (ruleToComplementIndex === -1) {
65
+ acc.push({ selector, ...restProps });
66
+ } else {
67
+ acc[ruleToComplementIndex].selector.cssString += ', '.concat(cssString);
68
+ }
69
+
70
+ return acc;
71
+ },
72
+ []
73
+ );
74
+
75
+ return aggregatedRelatedAdPlacesRules;
76
+ };
77
+
78
+ const buildPermanentAdPlacesRules = (
79
+ hostname: string,
80
+ hrefMatchPredicate: (regex: RegExp) => boolean,
81
+ permanentAdPlacesRulesForAllDomains: RawAllAdsRules['permanentAdPlacesRulesForAllDomains'],
82
+ permanentNativeAdPlacesRulesForAllDomains: RawAllAdsRules['permanentNativeAdPlacesRulesForAllDomains'] = {}
83
+ ): AdsRules['permanentAdPlacesRules'] => {
84
+ const rawPermanentAdPlacesRules = permanentAdPlacesRulesForAllDomains[hostname] ?? [];
85
+ const rawPermanentNativeAdPlacesRules = permanentNativeAdPlacesRulesForAllDomains[hostname] ?? [];
86
+
87
+ const permanentAdPlacesRules = rawPermanentAdPlacesRules
88
+ .map(({ urlRegexes, ...restRuleProps }) => ({
89
+ ...restRuleProps,
90
+ urlRegexes: urlRegexes.map(regex => new RegExp(regex)),
91
+ isNative: false
92
+ }))
93
+ .concat(
94
+ rawPermanentNativeAdPlacesRules.map(({ urlRegexes, ...restRuleProps }) => ({
95
+ ...restRuleProps,
96
+ urlRegexes: urlRegexes.map(regex => new RegExp(regex)),
97
+ isNative: true
98
+ }))
99
+ );
100
+
101
+ return permanentAdPlacesRules.filter(({ urlRegexes }) => urlRegexes.some(hrefMatchPredicate));
102
+ };
103
+
104
+ const buildProvidersSelector = (
105
+ hostname: string,
106
+ hrefMatchPredicate: (regex: RegExp) => boolean,
107
+ providersRulesForAllDomains: RawAllAdsRules['providersRulesForAllDomains'],
108
+ providersSelectors: RawAllAdsRules['providersSelectors'],
109
+ providersToReplaceAtAllSites: RawAllAdsRules['providersToReplaceAtAllSites']
110
+ ): string => {
111
+ const providersRules = (providersRulesForAllDomains[hostname] ?? []).map(({ urlRegexes, ...restRuleProps }) => ({
112
+ ...restRuleProps,
113
+ urlRegexes: urlRegexes.map(regex => new RegExp(regex))
114
+ }));
115
+
116
+ const relatedProvidersRules = providersRules.filter(({ urlRegexes }) => urlRegexes.some(hrefMatchPredicate));
117
+ const alreadyProcessedProviders = new Set<string>();
118
+ const selectorsForProvidersToReplace = new Set<string>();
119
+ const handleProvider = (provider: string) => {
120
+ if (alreadyProcessedProviders.has(provider)) return;
121
+
122
+ const newSelectors = providersSelectors[provider] ?? [];
123
+ newSelectors.forEach(selector => selectorsForProvidersToReplace.add(selector));
124
+ alreadyProcessedProviders.add(provider);
125
+ };
126
+
127
+ providersToReplaceAtAllSites.forEach(handleProvider);
128
+ relatedProvidersRules.forEach(({ providers }) => providers.forEach(handleProvider));
129
+
130
+ let providersSelector = '';
131
+ selectorsForProvidersToReplace.forEach(selector => {
132
+ providersSelector += selector + ', ';
133
+ });
134
+ if (providersSelector) {
135
+ providersSelector = providersSelector.slice(0, -2);
136
+ }
137
+
138
+ return providersSelector;
139
+ };
@@ -0,0 +1,4 @@
1
+ export interface AdView {
2
+ element: HTMLDivElement | HTMLIFrameElement;
3
+ postAppend?: () => void | Promise<unknown>;
4
+ }
@@ -0,0 +1,73 @@
1
+ import { StringRecord } from 'src/utils';
2
+
3
+ import type { AdMetadata } from './ads-meta';
4
+ import type { AdStylesOverrides } from './temple-wallet-api';
5
+
6
+ export enum AdActionType {
7
+ ReplaceAllChildren = 'replace-all-children',
8
+ ReplaceElement = 'replace-element',
9
+ SimpleInsertAd = 'simple-insert-ad',
10
+ RemoveElement = 'remove-element',
11
+ HideElement = 'hide-element'
12
+ }
13
+
14
+ interface AdActionBase {
15
+ type: AdActionType;
16
+ }
17
+
18
+ interface InsertAdActionProps {
19
+ ad: AdMetadata;
20
+ fallbacks: AdMetadata[];
21
+ /** @deprecated // Always wrapping now
22
+ * TODO: Clean-up usage
23
+ */
24
+ shouldUseDivWrapper?: boolean;
25
+ divWrapperStyle?: StringRecord<string>;
26
+ elementStyle?: StringRecord<string>;
27
+ stylesOverrides?: AdStylesOverrides[];
28
+ }
29
+
30
+ export interface ReplaceAllChildrenWithAdAction extends AdActionBase, InsertAdActionProps {
31
+ type: AdActionType.ReplaceAllChildren;
32
+ parent: HTMLElement;
33
+ }
34
+
35
+ export interface ReplaceElementWithAdAction extends AdActionBase, InsertAdActionProps {
36
+ type: AdActionType.ReplaceElement;
37
+ element: HTMLElement;
38
+ }
39
+
40
+ export interface SimpleInsertAdAction extends AdActionBase, InsertAdActionProps {
41
+ type: AdActionType.SimpleInsertAd;
42
+ parent: HTMLElement;
43
+ insertionIndex: number;
44
+ }
45
+
46
+ export interface RemoveElementAction extends AdActionBase {
47
+ type: AdActionType.RemoveElement;
48
+ element: HTMLElement;
49
+ }
50
+
51
+ export interface HideElementAction extends AdActionBase {
52
+ type: AdActionType.HideElement;
53
+ element: HTMLElement;
54
+ }
55
+
56
+ export type InsertAdAction = ReplaceAllChildrenWithAdAction | ReplaceElementWithAdAction | SimpleInsertAdAction;
57
+
58
+ export type OmitAdInAction<T extends InsertAdActionProps> = Omit<T, 'ad' | 'fallbacks'>;
59
+
60
+ export type InsertAdActionWithoutMeta =
61
+ | OmitAdInAction<ReplaceAllChildrenWithAdAction>
62
+ | OmitAdInAction<ReplaceElementWithAdAction>
63
+ | OmitAdInAction<SimpleInsertAdAction>;
64
+
65
+ export type AdAction = InsertAdAction | RemoveElementAction | HideElementAction;
66
+
67
+ export type AddActionsIfAdResolutionAvailable = (
68
+ elementToMeasure: Element,
69
+ shouldUseStrictContainerLimits: boolean,
70
+ minContainerWidthIsBannerWidth: boolean,
71
+ adIsNative: boolean,
72
+ ...actionsBases: (InsertAdActionWithoutMeta | HideElementAction | RemoveElementAction)[]
73
+ ) => boolean;
@@ -0,0 +1,51 @@
1
+ interface AdSourceBase {
2
+ shouldNotUseStrictContainerLimits?: boolean;
3
+ }
4
+
5
+ interface HypeLabBannerAdSource extends AdSourceBase {
6
+ providerName: 'HypeLab';
7
+ native: false;
8
+ size: 'small' | 'high' | 'wide';
9
+ }
10
+
11
+ interface HypeLabNativeAdSource extends AdSourceBase {
12
+ providerName: 'HypeLab';
13
+ native: true;
14
+ slug: string;
15
+ }
16
+
17
+ /** Only covers TKEY ads for now */
18
+ interface TempleAdSource extends AdSourceBase {
19
+ providerName: 'Temple';
20
+ }
21
+
22
+ /** See: https://pub.persona3.io/docs
23
+ * `regular` - 321x101
24
+ * `medium` - 600x160
25
+ * `wide` - 970x90
26
+ * `squarish` - 300x250
27
+ */
28
+ export type PersonaAdShape = 'regular' | 'medium' | 'wide' | 'squarish';
29
+
30
+ interface PersonaAdSource extends AdSourceBase {
31
+ providerName: 'Persona';
32
+ shape: PersonaAdShape;
33
+ }
34
+
35
+ export type HypeLabAdSources = HypeLabBannerAdSource | HypeLabNativeAdSource;
36
+
37
+ type AdSource = HypeLabAdSources | TempleAdSource | PersonaAdSource;
38
+
39
+ export interface AdDimensions {
40
+ width: number;
41
+ height: number;
42
+ minContainerWidth: number;
43
+ minContainerHeight: number;
44
+ maxContainerWidth: number;
45
+ maxContainerHeight: number;
46
+ }
47
+
48
+ export interface AdMetadata {
49
+ source: AdSource;
50
+ dimensions: AdDimensions;
51
+ }
@@ -0,0 +1,8 @@
1
+ export enum AdsProviderTitle {
2
+ Optimal = 'Optimal',
3
+ HypeLab = 'HypeLab',
4
+ Persona = 'Persona',
5
+ Temple = 'Temple Wallet'
6
+ }
7
+
8
+ export type AdsProviderName = keyof typeof AdsProviderTitle;
@@ -0,0 +1,17 @@
1
+ import { RawAdPlacesRule, RawPermanentAdPlacesRule } from './temple-wallet-api';
2
+
3
+ export interface AdPlacesRule extends Omit<RawAdPlacesRule, 'urlRegexes'> {
4
+ urlRegexes: RegExp[];
5
+ }
6
+
7
+ export interface PermanentAdPlacesRule extends Omit<RawPermanentAdPlacesRule, 'urlRegexes'> {
8
+ urlRegexes: RegExp[];
9
+ isNative: boolean;
10
+ }
11
+
12
+ export interface AdsRules {
13
+ adPlacesRules: Array<Omit<AdPlacesRule, 'urlRegexes'>>;
14
+ permanentAdPlacesRules: PermanentAdPlacesRule[];
15
+ providersSelector: string;
16
+ timestamp: number;
17
+ }
@@ -0,0 +1,56 @@
1
+ export interface AdStylesOverrides {
2
+ parentDepth: number;
3
+ style: Record<string, string>;
4
+ }
5
+
6
+ export interface RawAdPlacesRule {
7
+ urlRegexes: string[];
8
+ selector: {
9
+ isMultiple: boolean;
10
+ cssString: string;
11
+ parentDepth: number;
12
+ shouldUseDivWrapper: boolean;
13
+ divWrapperStyle?: Record<string, string>;
14
+ };
15
+ stylesOverrides?: AdStylesOverrides[];
16
+ shouldHideOriginal?: boolean;
17
+ }
18
+
19
+ export interface RawPermanentAdPlacesRule {
20
+ urlRegexes: string[];
21
+ adSelector: {
22
+ isMultiple: boolean;
23
+ cssString: string;
24
+ parentDepth: number;
25
+ };
26
+ parentSelector: {
27
+ isMultiple: boolean;
28
+ cssString: string;
29
+ parentDepth: number;
30
+ };
31
+ insertionIndex?: number;
32
+ insertBeforeSelector?: string;
33
+ insertAfterSelector?: string;
34
+ insertionsCount?: number;
35
+ shouldUseDivWrapper: boolean;
36
+ divWrapperStyle?: Record<string, string>;
37
+ elementStyle?: Record<string, string>;
38
+ elementToMeasureSelector?: string;
39
+ stylesOverrides?: AdStylesOverrides[];
40
+ shouldHideOriginal?: boolean;
41
+ }
42
+
43
+ export interface RawAdProvidersRule {
44
+ urlRegexes: string[];
45
+ providers: string[];
46
+ }
47
+
48
+ export interface RawAllAdsRules {
49
+ adPlacesRulesForAllDomains: Record<string, RawAdPlacesRule[]>;
50
+ providersRulesForAllDomains: Record<string, RawAdProvidersRule[]>;
51
+ providersSelectors: Record<string, string[]>;
52
+ providersToReplaceAtAllSites: string[];
53
+ permanentAdPlacesRulesForAllDomains: Record<string, RawPermanentAdPlacesRule[]>;
54
+ permanentNativeAdPlacesRulesForAllDomains: Record<string, RawPermanentAdPlacesRule[]>;
55
+ timestamp: number;
56
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,10 @@
1
+ /** From lodash */
2
+ type Truthy<T> = T extends null | undefined | void | false | '' | 0 | 0n ? never : T;
3
+
4
+ export const isTruthy = <T>(value: T): value is Truthy<T> => Boolean(value);
5
+
6
+ const DEFAULT_DELAY = 300;
7
+
8
+ export const delay = (ms = DEFAULT_DELAY) => new Promise(res => setTimeout(res, ms));
9
+
10
+ export type StringRecord<T = string> = Record<string, T>;