@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.
- package/.eslintrc +49 -0
- package/.prettierignore +2 -0
- package/.prettierrc +7 -0
- package/.yarn/releases/yarn-4.1.1.cjs +893 -0
- package/.yarnrc.yml +3 -0
- package/README.md +1 -0
- package/dist/index.d.ts +196 -0
- package/dist/index.js +21 -0
- package/package.json +48 -0
- package/src/ads-actions/helpers.ts +100 -0
- package/src/ads-actions/index.ts +96 -0
- package/src/ads-actions/process-permanent-rule.ts +187 -0
- package/src/ads-actions/process-rule.ts +123 -0
- package/src/ads-configuration.ts +43 -0
- package/src/ads-meta.ts +108 -0
- package/src/constants.ts +2 -0
- package/src/execute-ads-actions/ads-views/index.ts +3 -0
- package/src/execute-ads-actions/ads-views/make-hypelab-ad.ts +77 -0
- package/src/execute-ads-actions/ads-views/make-persona-ad.ts +28 -0
- package/src/execute-ads-actions/ads-views/make-tkey-ad.ts +37 -0
- package/src/execute-ads-actions/index.ts +16 -0
- package/src/execute-ads-actions/observing.ts +115 -0
- package/src/execute-ads-actions/override-element-styles.ts +7 -0
- package/src/execute-ads-actions/process-insert-ad-action.ts +104 -0
- package/src/index.ts +19 -0
- package/src/temple-wallet-api.ts +80 -0
- package/src/transform-raw-rules.ts +139 -0
- package/src/types/ad-view.ts +4 -0
- package/src/types/ads-actions.ts +73 -0
- package/src/types/ads-meta.ts +51 -0
- package/src/types/ads-provider.ts +8 -0
- package/src/types/ads-rules.ts +17 -0
- package/src/types/temple-wallet-api.ts +56 -0
- package/src/utils.ts +10 -0
- package/tsconfig.json +25 -0
|
@@ -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,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,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,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>;
|