@temple-wallet/extension-ads 8.1.0 → 9.0.0-dev.1

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.
Files changed (102) hide show
  1. package/dist/index.d.ts +9 -3
  2. package/dist/index.js +12 -14
  3. package/dist/referrals/index.d.ts +33 -0
  4. package/dist/referrals/index.js +18 -0
  5. package/package.json +7 -5
  6. package/src/ads-actions/helpers.ts +36 -2
  7. package/src/ads-actions/process-permanent-rule.ts +8 -3
  8. package/src/ads-actions/process-providers-ads.ts +29 -2
  9. package/src/ads-actions/process-rule.ts +4 -0
  10. package/src/ads-configuration.ts +17 -2
  11. package/src/execute-ads-actions/observing.ts +3 -1
  12. package/src/execute-ads-actions/process-insert-ad-action.ts +26 -5
  13. package/src/referrals/index.ts +5 -0
  14. package/src/referrals/replace.ts +129 -0
  15. package/src/referrals/takeads.ts +44 -0
  16. package/src/referrals/utils.ts +14 -0
  17. package/src/render-ads-stack.ts +218 -102
  18. package/src/temple-wallet-api.ts +10 -4
  19. package/src/transform-raw-rules.ts +6 -2
  20. package/src/types/ads-actions.ts +2 -0
  21. package/src/types/ads-rules.ts +1 -0
  22. package/src/types/temple-wallet-api.ts +1 -0
  23. package/src/utils.ts +4 -0
  24. package/dist/ads-actions/helpers.d.ts +0 -17
  25. package/dist/ads-actions/helpers.js +0 -96
  26. package/dist/ads-actions/helpers.js.map +0 -1
  27. package/dist/ads-actions/index.d.ts +0 -3
  28. package/dist/ads-actions/index.js +0 -63
  29. package/dist/ads-actions/index.js.map +0 -1
  30. package/dist/ads-actions/process-elements-to-hide-or-remove-rule.d.ts +0 -3
  31. package/dist/ads-actions/process-elements-to-hide-or-remove-rule.js +0 -19
  32. package/dist/ads-actions/process-elements-to-hide-or-remove-rule.js.map +0 -1
  33. package/dist/ads-actions/process-permanent-rule.d.ts +0 -3
  34. package/dist/ads-actions/process-permanent-rule.js +0 -143
  35. package/dist/ads-actions/process-permanent-rule.js.map +0 -1
  36. package/dist/ads-actions/process-providers-ads.d.ts +0 -3
  37. package/dist/ads-actions/process-providers-ads.js +0 -137
  38. package/dist/ads-actions/process-providers-ads.js.map +0 -1
  39. package/dist/ads-actions/process-rule.d.ts +0 -3
  40. package/dist/ads-actions/process-rule.js +0 -98
  41. package/dist/ads-actions/process-rule.js.map +0 -1
  42. package/dist/ads-configuration.d.ts +0 -31
  43. package/dist/ads-configuration.js +0 -30
  44. package/dist/ads-configuration.js.map +0 -1
  45. package/dist/constants.d.ts +0 -11
  46. package/dist/constants.js +0 -12
  47. package/dist/constants.js.map +0 -1
  48. package/dist/execute-ads-actions/ads-views/index.d.ts +0 -3
  49. package/dist/execute-ads-actions/ads-views/index.js +0 -4
  50. package/dist/execute-ads-actions/ads-views/index.js.map +0 -1
  51. package/dist/execute-ads-actions/ads-views/make-ads-tw-view.d.ts +0 -3
  52. package/dist/execute-ads-actions/ads-views/make-ads-tw-view.js +0 -40
  53. package/dist/execute-ads-actions/ads-views/make-ads-tw-view.js.map +0 -1
  54. package/dist/execute-ads-actions/ads-views/make-extension-iframe-view.d.ts +0 -3
  55. package/dist/execute-ads-actions/ads-views/make-extension-iframe-view.js +0 -22
  56. package/dist/execute-ads-actions/ads-views/make-extension-iframe-view.js.map +0 -1
  57. package/dist/execute-ads-actions/ads-views/make-tkey-ad.d.ts +0 -3
  58. package/dist/execute-ads-actions/ads-views/make-tkey-ad.js +0 -38
  59. package/dist/execute-ads-actions/ads-views/make-tkey-ad.js.map +0 -1
  60. package/dist/execute-ads-actions/index.d.ts +0 -2
  61. package/dist/execute-ads-actions/index.js +0 -23
  62. package/dist/execute-ads-actions/index.js.map +0 -1
  63. package/dist/execute-ads-actions/observing.d.ts +0 -1
  64. package/dist/execute-ads-actions/observing.js +0 -129
  65. package/dist/execute-ads-actions/observing.js.map +0 -1
  66. package/dist/execute-ads-actions/override-element-styles.d.ts +0 -2
  67. package/dist/execute-ads-actions/override-element-styles.js +0 -6
  68. package/dist/execute-ads-actions/override-element-styles.js.map +0 -1
  69. package/dist/execute-ads-actions/process-insert-ad-action.d.ts +0 -2
  70. package/dist/execute-ads-actions/process-insert-ad-action.js +0 -153
  71. package/dist/execute-ads-actions/process-insert-ad-action.js.map +0 -1
  72. package/dist/index.js.map +0 -1
  73. package/dist/render-ads-stack.d.ts +0 -7
  74. package/dist/render-ads-stack.js +0 -156
  75. package/dist/render-ads-stack.js.map +0 -1
  76. package/dist/temple-wallet-api.d.ts +0 -23
  77. package/dist/temple-wallet-api.js +0 -67
  78. package/dist/temple-wallet-api.js.map +0 -1
  79. package/dist/transform-raw-rules.d.ts +0 -3
  80. package/dist/transform-raw-rules.js +0 -137
  81. package/dist/transform-raw-rules.js.map +0 -1
  82. package/dist/types/ad-view.d.ts +0 -7
  83. package/dist/types/ad-view.js +0 -2
  84. package/dist/types/ad-view.js.map +0 -1
  85. package/dist/types/ads-actions.d.ts +0 -56
  86. package/dist/types/ads-actions.js +0 -15
  87. package/dist/types/ads-actions.js.map +0 -1
  88. package/dist/types/ads-meta.d.ts +0 -19
  89. package/dist/types/ads-meta.js +0 -2
  90. package/dist/types/ads-meta.js.map +0 -1
  91. package/dist/types/ads-provider.d.ts +0 -8
  92. package/dist/types/ads-provider.js +0 -9
  93. package/dist/types/ads-provider.js.map +0 -1
  94. package/dist/types/ads-rules.d.ts +0 -26
  95. package/dist/types/ads-rules.js +0 -2
  96. package/dist/types/ads-rules.js.map +0 -1
  97. package/dist/types/temple-wallet-api.d.ts +0 -78
  98. package/dist/types/temple-wallet-api.js +0 -2
  99. package/dist/types/temple-wallet-api.js.map +0 -1
  100. package/dist/utils.d.ts +0 -6
  101. package/dist/utils.js +0 -4
  102. package/dist/utils.js.map +0 -1
@@ -33,7 +33,16 @@ const processInsertAdActionOnce = async (
33
33
  insertionPoint: HTMLDivElement | HTMLTableCellElement,
34
34
  onAdsStackError: (error: Error) => void
35
35
  ) => {
36
- const { elementStyle = {}, stylesOverrides = [], adsMetadata, wrapperType, originalHeight, originalWidth } = action;
36
+ const {
37
+ elementStyle = {},
38
+ stylesOverrides = [],
39
+ adsMetadata,
40
+ wrapperType,
41
+ originalHeight,
42
+ originalWidth,
43
+ shouldUseBlurredBackground,
44
+ adCategories
45
+ } = action;
37
46
 
38
47
  stylesOverrides.sort((a, b) => a.parentDepth - b.parentDepth);
39
48
 
@@ -42,15 +51,15 @@ const processInsertAdActionOnce = async (
42
51
  const adId = nanoid();
43
52
  const adElement = document.createElement('iframe');
44
53
  adElement.loading = 'lazy';
45
- adElement.src = AdsConfiguration.getAdsStackIframeURL(adId, adsMetadata, window.location.href);
54
+ adElement.src = AdsConfiguration.getAdsStackIframeURL(adId, adsMetadata, window.location.href, adCategories);
46
55
  adElement.id = adId;
47
56
  adElement.allow = IFRAME_ALLOWANCE;
48
57
  const firstAdMetadataOrId = adsMetadata[0];
49
58
  const { dimensions: minDimensions } =
50
59
  typeof firstAdMetadataOrId === 'number' ? AdsConfiguration.bannerAdsMeta[firstAdMetadataOrId] : firstAdMetadataOrId;
51
60
  const dimensions = {
52
- width: Math.max(originalWidth, minDimensions.width),
53
- height: Math.max(originalHeight, minDimensions.height)
61
+ width: shouldUseBlurredBackground ? Math.max(originalWidth, minDimensions.width) : minDimensions.width,
62
+ height: shouldUseBlurredBackground ? Math.max(originalHeight, minDimensions.height) : minDimensions.height
54
63
  };
55
64
  adElement.style.width = wrapperType === 'tbody' ? '100%' : `${dimensions.width}px`;
56
65
  adElement.style.height = `${dimensions.height}px`;
@@ -74,11 +83,23 @@ const processInsertAdActionOnce = async (
74
83
  action.element.replaceWith(wrapperElement);
75
84
  break;
76
85
  default:
86
+ const nextSibling = action.parent.children[action.insertionIndex];
77
87
  if (action.isSiblingReplacement) {
78
88
  wrapperElement.setAttribute(SIBLING_REPLACEMENT_ATTRIBUTE_NAME, 'true');
89
+ const observer = new MutationObserver(mutations => {
90
+ const hasRemoveSiblingMutation = mutations.some(mutation =>
91
+ Array.from(mutation.removedNodes).includes(nextSibling)
92
+ );
93
+
94
+ if (hasRemoveSiblingMutation) {
95
+ wrapperElement.remove();
96
+ observer.disconnect();
97
+ }
98
+ });
99
+ observer.observe(action.parent, { childList: true });
79
100
  }
80
101
  stylesOverridesCurrentElement = action.parent;
81
- action.parent.insertBefore(wrapperElement, action.parent.children[action.insertionIndex]);
102
+ action.parent.insertBefore(wrapperElement, nextSibling);
82
103
  break;
83
104
  }
84
105
 
@@ -0,0 +1,5 @@
1
+ export { processAnchors } from './replace';
2
+
3
+ export { buildTakeadsClient } from './takeads';
4
+
5
+ export { getCurrentPageDomain } from './utils';
@@ -0,0 +1,129 @@
1
+ import browser from 'webextension-polyfill';
2
+
3
+ import { IS_DEV, IS_MAC_OS, isTruthy } from 'src/utils';
4
+
5
+ import type { TekeadsAffiliateResponse } from './takeads';
6
+ import { getCurrentPageDomain, stripSubdomain } from './utils';
7
+
8
+ const TEMPLE_WALLET_ANCHOR_ATTRIBUTE = 'data-tw-referral';
9
+
10
+ export async function processAnchors(
11
+ supportedDomains: Set<string>,
12
+ AdsBrowserExtensionMessageType: AdsBrowserExtensionMessageTypeI
13
+ ) {
14
+ const anchors = Array.from(document.querySelectorAll('a'));
15
+ if (!anchors.length) throw new Error('No anchors found');
16
+
17
+ const items = anchors
18
+ .map(aElem => {
19
+ if (aElem.hasAttribute(TEMPLE_WALLET_ANCHOR_ATTRIBUTE)) return null;
20
+
21
+ const urlDomain = getDomain(aElem.href);
22
+ if (!urlDomain || !supportedDomains.has(urlDomain)) return null;
23
+
24
+ const iri = cleanLink(aElem.href);
25
+ if (!iri) return null;
26
+
27
+ return { iri, aElem, urlDomain };
28
+ })
29
+ .filter(isTruthy);
30
+
31
+ if (!items.length) return void (IS_DEV && console.info('Nothing to replace'));
32
+
33
+ const links = items.map(l => l.iri);
34
+
35
+ // Not requesting directly in content script because of CORS.
36
+ const takeadsItems: TekeadsAffiliateResponse = await browser.runtime.sendMessage({
37
+ type: AdsBrowserExtensionMessageType.FetchReferrals,
38
+ links
39
+ });
40
+
41
+ if (!takeadsItems.data.length) return void (IS_DEV && console.info('No referrals received'));
42
+
43
+ IS_DEV && console.info('Replacing', takeadsItems.data.length, 'referralas');
44
+
45
+ for (const { iri, aElem, urlDomain } of items) {
46
+ const referralUrl = takeadsItems.data.find(item => item.iri === iri)?.trackingLink;
47
+
48
+ if (!referralUrl) {
49
+ IS_DEV && console.warn('No affiliate link for', aElem);
50
+ continue;
51
+ }
52
+
53
+ processAnchorElement({ iri, aElem, urlDomain, referralUrl }, AdsBrowserExtensionMessageType);
54
+ }
55
+
56
+ return;
57
+ }
58
+
59
+ interface AdsBrowserExtensionMessageTypeI {
60
+ FetchReferrals: string;
61
+ ReferralClick: string;
62
+ }
63
+
64
+ interface PreppedItem {
65
+ iri: string;
66
+ aElem: HTMLAnchorElement;
67
+ urlDomain: string;
68
+ referralUrl: string;
69
+ }
70
+
71
+ function processAnchorElement(item: PreppedItem, AdsBrowserExtensionMessageType: AdsBrowserExtensionMessageTypeI) {
72
+ const { aElem, urlDomain, referralUrl } = item;
73
+
74
+ const showHref = aElem.href;
75
+
76
+ IS_DEV && console.info('Replacing referral:', showHref, 'to', referralUrl, 'for anchor:', aElem);
77
+
78
+ aElem.setAttribute(TEMPLE_WALLET_ANCHOR_ATTRIBUTE, 'set');
79
+
80
+ aElem.onclick = event => {
81
+ event.preventDefault();
82
+
83
+ IS_DEV && console.log('Referral clicked:', showHref, '->', referralUrl);
84
+
85
+ browser.runtime.sendMessage({
86
+ type: AdsBrowserExtensionMessageType.ReferralClick,
87
+ urlDomain,
88
+ pageDomain: getCurrentPageDomain()
89
+ });
90
+
91
+ const newTab = event.button === 1 || (IS_MAC_OS ? event.metaKey : event.ctrlKey);
92
+
93
+ window.open(referralUrl, newTab ? '_blank' : '_self');
94
+ };
95
+
96
+ aElem.oncontextmenu = () => {
97
+ /*
98
+ Making sure, user opens referral URL via Context Menu's 'Open in new tab' button.
99
+ Note: 'Copy to clipboard' button will also provide referral URL this way.
100
+ */
101
+
102
+ aElem.href = referralUrl;
103
+
104
+ const revertHref = () => {
105
+ aElem.href = showHref;
106
+ window.removeEventListener('focus', revertHref);
107
+ };
108
+
109
+ window.addEventListener('focus', revertHref);
110
+ };
111
+ }
112
+
113
+ function getDomain(href: string) {
114
+ try {
115
+ return stripSubdomain(new URL(href).hostname, 'www');
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
120
+
121
+ function cleanLink(href: string) {
122
+ try {
123
+ const dirtyLink = new URL(href);
124
+
125
+ return dirtyLink.origin + dirtyLink.pathname;
126
+ } catch {
127
+ return null;
128
+ }
129
+ }
@@ -0,0 +1,44 @@
1
+ import axios, { AxiosInstance } from 'axios';
2
+ import memoizee from 'memoizee';
3
+
4
+ /**
5
+ * API docs: https://docs.takeads.com
6
+ */
7
+ export class TakeAdsClient {
8
+ private axios: AxiosInstance;
9
+
10
+ constructor(
11
+ private publicKey: string,
12
+ readonly apiUrl: string = 'https://api.takeads.com'
13
+ ) {
14
+ this.axios = axios.create({
15
+ baseURL: apiUrl,
16
+ headers: {
17
+ Authorization: `Bearer ${this.publicKey}`
18
+ },
19
+ adapter: 'fetch'
20
+ });
21
+ }
22
+
23
+ async affiliateLinks(websiteUrls: string[], subId?: string) {
24
+ const response = await this.axios.put<TekeadsAffiliateResponse>('/v1/product/monetize-api/v2/resolve', {
25
+ iris: websiteUrls,
26
+ subId,
27
+ withImages: false
28
+ });
29
+
30
+ return response.data;
31
+ }
32
+ }
33
+
34
+ export const buildTakeadsClient = memoizee((publicKey: string) => new TakeAdsClient(publicKey), { max: 2 });
35
+
36
+ export interface TekeadsAffiliateResponse {
37
+ data: AffiliateLink[];
38
+ }
39
+
40
+ interface AffiliateLink {
41
+ iri: string;
42
+ trackingLink: string;
43
+ imageUrl: string | null;
44
+ }
@@ -0,0 +1,14 @@
1
+ import { memoize } from 'lodash';
2
+
3
+ /** Current page's domain with 'www' stripped off */
4
+ export const getCurrentPageDomain = memoize(() => {
5
+ return stripSubdomain(window.location.hostname, 'www');
6
+ });
7
+
8
+ export function stripSubdomain(hostname: string, subdomain: string) {
9
+ if (hostname.includes(`${subdomain}.`)) {
10
+ return hostname.slice(subdomain.length + 1);
11
+ }
12
+
13
+ return hostname;
14
+ }
@@ -10,57 +10,178 @@ import {
10
10
  import { makeAdsTwView, makeExtensionIframeView, makeTKeyAdView } from './execute-ads-actions/ads-views';
11
11
  import { overrideElementStyles } from './execute-ads-actions/override-element-styles';
12
12
  import { AdView, CreativeSet } from './types/ad-view';
13
- import { AdMetadata } from './types/ads-meta';
13
+ import { AdMetadata, AdSource } from './types/ads-meta';
14
14
 
15
- const broadcastMessage = (content: unknown) => window.parent.postMessage(JSON.stringify(content), '*');
15
+ class AdsStack {
16
+ private wasReady = false;
17
+ private messageListener: ((event: MessageEvent<any>) => void) | undefined;
18
+ private responseTimeout: NodeJS.Timeout | undefined;
19
+ private rootElement: HTMLElement = document.getElementById('root')!;
20
+ private validAdsCounter = 0;
21
+ private readonly adsMetadata: AdMetadata[];
22
+ private currentAdMetadata: AdMetadata | undefined;
16
23
 
17
- /**
18
- * Tries to render specified ads one-by-one until one of them is loaded or loading of all of them failed. Use it
19
- * in a dedicated iframe. Sends messages to the parent window on an attempt to render an ad, on successful rendering,
20
- * on ad resize, and on error.
21
- */
22
- export const renderAdsStack = async (
23
- adId: string,
24
- adsMetadata: (number | AdMetadata)[],
25
- origin: string
26
- ): Promise<void> => {
27
- if (adsMetadata.length === 0) {
28
- broadcastMessage({ id: adId, type: AD_ERROR_MESSAGE_TYPE, reason: 'No more ads to render' });
24
+ constructor(
25
+ private readonly id: string,
26
+ initialAdsMetadata: (number | AdMetadata)[],
27
+ private readonly origin: string,
28
+ private readonly pageHasPlacesRules: boolean,
29
+ private readonly adCategories: string[],
30
+ private readonly hypelabBlacklistedCampaignsSlugs: string[]
31
+ ) {
32
+ this.adsMetadata = initialAdsMetadata.map(adMetadataOrId =>
33
+ typeof adMetadataOrId === 'number' ? AdsConfiguration.bannerAdsMeta[adMetadataOrId] : adMetadataOrId
34
+ );
35
+ }
36
+
37
+ startRendering() {
38
+ if (this.adsMetadata.length === 0) {
39
+ this.broadcastNoMoreAds();
40
+
41
+ return;
42
+ }
43
+
44
+ this.currentAdMetadata = AdsConfiguration.pickNextAdMetadata(
45
+ this.adsMetadata,
46
+ this.currentAdMetadata,
47
+ this.validAdsCounter,
48
+ this.pageHasPlacesRules,
49
+ this.adCategories
50
+ );
51
+ const adMetadata = this.currentAdMetadata;
52
+
53
+ if (!adMetadata) {
54
+ this.broadcastNoMoreAds();
55
+
56
+ return;
57
+ }
58
+
59
+ this.broadcastMessage({ id: this.id, type: AD_RENDER_START_MESSAGE_TYPE, adMetadata: this.currentAdMetadata });
60
+
61
+ const { element, creativeSet: initialCreativeSet } = this.placeNewAd(adMetadata);
29
62
 
30
- return;
63
+ if (element instanceof HTMLIFrameElement) {
64
+ this.listenToIframeMessages(element, adMetadata);
65
+ } else {
66
+ this.handleValidAd({ ad: { creative_set: initialCreativeSet } });
67
+ }
31
68
  }
32
69
 
33
- const [adMetadataOrId, ...rest] = adsMetadata;
34
- const adMetadata =
35
- typeof adMetadataOrId === 'number' ? AdsConfiguration.bannerAdsMeta[adMetadataOrId] : adMetadataOrId;
36
- broadcastMessage({ id: adId, type: AD_RENDER_START_MESSAGE_TYPE, adMetadata });
37
-
38
- const { source, dimensions } = adMetadata;
39
- let adView: AdView;
40
- switch (source.providerName) {
41
- case 'Temple':
42
- adView = makeTKeyAdView(dimensions.width, dimensions.height, source.native);
43
- break;
44
- case 'HypeLab':
45
- case 'SmartyAds':
46
- adView = makeAdsTwView(adId, source, dimensions, origin);
47
- break;
48
- default:
49
- adView = makeExtensionIframeView(adId, source, dimensions);
70
+ private listenToIframeMessages(element: HTMLIFrameElement, adMetadata: AdMetadata) {
71
+ const nextAdMetadata = AdsConfiguration.pickNextAdMetadata(
72
+ this.adsMetadata,
73
+ adMetadata,
74
+ this.validAdsCounter,
75
+ this.pageHasPlacesRules,
76
+ this.adCategories
77
+ );
78
+ this.responseTimeout = setTimeout(
79
+ () => {
80
+ console.error(`Timeout exceeded for ${this.id}, ad source: ${JSON.stringify(adMetadata.source)}`);
81
+ this.handleInvalidAd();
82
+ },
83
+ (nextAdMetadata ? 10_000 : 20_000) * (AdsConfiguration.IS_MISES_BROWSER ? 2 : 1)
84
+ );
85
+
86
+ this.messageListener = (event: MessageEvent<any>) => {
87
+ if (event.source !== element.contentWindow) {
88
+ return;
89
+ }
90
+
91
+ try {
92
+ const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
93
+
94
+ switch (data.type) {
95
+ case AD_READY_MESSAGE_TYPE:
96
+ if (
97
+ adMetadata.source.providerName === 'HypeLab' &&
98
+ this.hypelabBlacklistedCampaignsSlugs.includes(data.ad?.campaign_slug) &&
99
+ nextAdMetadata
100
+ ) {
101
+ this.handleInvalidAd();
102
+ break;
103
+ }
104
+
105
+ this.handleValidAd(data);
106
+ break;
107
+ case AD_ERROR_MESSAGE_TYPE:
108
+ if (!this.wasReady) {
109
+ this.handleInvalidAd();
110
+ }
111
+ break;
112
+ case AD_RESIZE_MESSAGE_TYPE:
113
+ const { width, height } = data;
114
+ if (width > 0 && height > 0) {
115
+ if (adMetadata.source.native) {
116
+ element.style.height = `${height}px`;
117
+ } else {
118
+ extendOrKeepDimension(element, 'width', width);
119
+ extendOrKeepDimension(element, 'height', height);
120
+ }
121
+ this.broadcastMessage({ ...data, id: this.id });
122
+ }
123
+ }
124
+ } catch (error) {
125
+ console.error('Message handling error:', error);
126
+ }
127
+ };
128
+
129
+ window.addEventListener('message', this.messageListener);
130
+ }
131
+
132
+ private handleInvalidAd() {
133
+ this.removeMessageListener();
134
+ this.clearResponseTimeout();
135
+ this.startRendering();
136
+ this.validAdsCounter = 0;
50
137
  }
51
138
 
52
- const { element } = adView;
53
- const rootElement = document.getElementById('root')!;
54
- const templeLabelElement = document.getElementById('temple-label')!;
55
- rootElement.replaceChildren(element);
139
+ private removeMessageListener() {
140
+ if (!this.messageListener) return;
56
141
 
57
- if (!source.native) templeLabelElement.setAttribute('active', '');
58
- else templeLabelElement.removeAttribute('active');
142
+ window.removeEventListener('message', this.messageListener);
143
+ this.messageListener = undefined;
144
+ }
145
+
146
+ private placeNewAd(adMetadata: AdMetadata) {
147
+ document.body.style.backgroundColor = adMetadata.source.native ? 'transparent' : '#F2F2F2';
148
+
149
+ const { source, dimensions } = adMetadata;
150
+ let adView: AdView;
151
+ switch (source.providerName) {
152
+ case 'Temple':
153
+ adView = makeTKeyAdView(dimensions.width, dimensions.height, source.native);
154
+ break;
155
+ case 'HypeLab':
156
+ case 'SmartyAds':
157
+ adView = makeAdsTwView(this.id, source, dimensions, this.origin);
158
+ break;
159
+ default:
160
+ adView = makeExtensionIframeView(this.id, source, dimensions);
161
+ }
162
+
163
+ const { element } = adView;
164
+ const templeLabelElement = document.getElementById('temple-label')!;
165
+ this.rootElement.replaceChildren(element);
166
+
167
+ if (!source.native) templeLabelElement.setAttribute('active', '');
168
+ else templeLabelElement.removeAttribute('active');
169
+
170
+ return adView;
171
+ }
172
+
173
+ private updateBackground(source: AdSource, creativeSet: CreativeSet = {}) {
174
+ const prevBackgroundElement = document.getElementById(BACKGROUND_ELEMENT_ID);
175
+ if (source.native && prevBackgroundElement) {
176
+ this.rootElement.removeChild(prevBackgroundElement);
177
+ }
178
+
179
+ if (source.native) {
180
+ return;
181
+ }
59
182
 
60
- const updateBackground = (creativeSet: CreativeSet = {}) => {
61
183
  try {
62
184
  const { image, video } = creativeSet;
63
- const prevBackgroundElement = document.getElementById(BACKGROUND_ELEMENT_ID);
64
185
  let backgroundElement: HTMLImageElement | HTMLVideoElement | undefined;
65
186
  if (image) {
66
187
  backgroundElement = document.createElement('img');
@@ -77,7 +198,7 @@ export const renderAdsStack = async (
77
198
  }
78
199
 
79
200
  if (!backgroundElement && prevBackgroundElement) {
80
- rootElement.removeChild(prevBackgroundElement);
201
+ this.rootElement.removeChild(prevBackgroundElement);
81
202
  }
82
203
 
83
204
  if (!backgroundElement) return;
@@ -96,76 +217,71 @@ export const renderAdsStack = async (
96
217
  filter: 'blur(12px)'
97
218
  });
98
219
  if (prevBackgroundElement) {
99
- rootElement.replaceChild(backgroundElement, prevBackgroundElement);
220
+ this.rootElement.replaceChild(backgroundElement, prevBackgroundElement);
100
221
  } else {
101
- rootElement.appendChild(backgroundElement);
222
+ this.rootElement.prepend(backgroundElement);
102
223
  }
103
224
  } catch (e) {
104
225
  console.error(e);
105
226
  }
106
- };
107
-
108
- if (element instanceof HTMLIFrameElement) {
109
- return new Promise<void>((resolve, reject) => {
110
- const responseTimeout = setTimeout(
111
- () => {
112
- window.removeEventListener('message', messageListener);
113
- reject(new Error(`Timeout exceeded for ${adId}, ad source: ${JSON.stringify(source)}`));
114
- },
115
- (rest.length === 0 ? 20_000 : 10_000) * (AdsConfiguration.IS_MISES_BROWSER ? 2 : 1)
116
- );
117
-
118
- let wasReady = false;
119
- const messageListener = (event: MessageEvent<any>) => {
120
- if (event.source !== element.contentWindow) {
121
- return;
122
- }
227
+ }
123
228
 
124
- try {
125
- const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
229
+ private broadcastMessage(content: unknown) {
230
+ window.parent.postMessage(JSON.stringify(content), '*');
231
+ }
126
232
 
127
- switch (data.type) {
128
- case AD_READY_MESSAGE_TYPE:
129
- if (!wasReady) {
130
- wasReady = true;
131
- broadcastMessage({ id: adId, type: AD_READY_MESSAGE_TYPE, adMetadata });
132
- clearTimeout(responseTimeout);
133
- resolve();
134
- }
135
- updateBackground(data.ad?.creative_set);
136
- break;
137
- case AD_ERROR_MESSAGE_TYPE:
138
- if (!wasReady) {
139
- window.removeEventListener('message', messageListener);
140
- clearTimeout(responseTimeout);
141
- reject(new Error(data.reason ?? 'Unknown error'));
142
- }
143
- break;
144
- case AD_RESIZE_MESSAGE_TYPE:
145
- const { width, height } = data;
146
- if (width > 0 && height > 0) {
147
- if (adMetadata.source.native) {
148
- element.style.height = `${height}px`;
149
- } else {
150
- extendOrKeepDimension(element, 'width', width);
151
- extendOrKeepDimension(element, 'height', height);
152
- }
153
- broadcastMessage({ ...data, id: adId });
154
- }
155
- }
156
- } catch (error) {
157
- console.error('Observing error:', error);
158
- }
159
- };
233
+ private broadcastNoMoreAds() {
234
+ this.broadcastMessage({ id: this.id, type: AD_ERROR_MESSAGE_TYPE, reason: 'No more ads to render' });
235
+ }
160
236
 
161
- window.addEventListener('message', messageListener);
162
- }).catch(e => {
163
- console.error(e);
237
+ private clearResponseTimeout() {
238
+ if (this.responseTimeout !== undefined) {
239
+ clearTimeout(this.responseTimeout);
240
+ this.responseTimeout = undefined;
241
+ }
242
+ }
164
243
 
165
- return renderAdsStack(adId, rest, origin);
166
- });
244
+ private handleValidAd(data: any) {
245
+ const adMetadata = this.currentAdMetadata;
246
+
247
+ if (!adMetadata) {
248
+ return;
249
+ }
250
+
251
+ if (!this.wasReady) {
252
+ this.wasReady = true;
253
+ this.broadcastMessage({ id: this.id, type: AD_READY_MESSAGE_TYPE, adMetadata });
254
+ }
255
+ this.validAdsCounter++;
256
+ this.clearResponseTimeout();
257
+ this.updateBackground(adMetadata.source, data.ad?.creative_set);
167
258
  }
259
+ }
168
260
 
169
- updateBackground(adView.creativeSet);
170
- broadcastMessage({ id: adId, type: AD_READY_MESSAGE_TYPE, adMetadata });
261
+ /**
262
+ * Tries to render specified ads one-by-one until one of them is loaded or loading of all of them failed. Use it
263
+ * in a dedicated iframe. Sends messages to the parent window on an attempt to render an ad, on successful rendering,
264
+ * on ad resize, and on error.
265
+ */
266
+ export const renderAdsStack = (
267
+ adId: string,
268
+ adsMetadata: (number | AdMetadata)[],
269
+ origin: string,
270
+ pageHasPlacesRules = false,
271
+ adCategories: string[],
272
+ hypelabBlacklistedCampaignsSlugs: string[]
273
+ ) => {
274
+ try {
275
+ const adsStack = new AdsStack(
276
+ adId,
277
+ adsMetadata,
278
+ origin,
279
+ pageHasPlacesRules,
280
+ adCategories,
281
+ hypelabBlacklistedCampaignsSlugs
282
+ );
283
+ adsStack.startRendering();
284
+ } catch (error) {
285
+ console.error('Error while rendering ads stack:', error);
286
+ }
171
287
  };
@@ -1,4 +1,3 @@
1
- import axiosFetchAdapter from '@vespaiach/axios-fetch-adapter';
2
1
  import axios, { AxiosInstance, AxiosResponse } from 'axios';
3
2
 
4
3
  import {
@@ -25,7 +24,7 @@ export class TempleWalletApi {
25
24
  constructor(baseUrl: string) {
26
25
  this.api = axios.create({
27
26
  baseURL: new URL('/api', baseUrl).href,
28
- adapter: axiosFetchAdapter
27
+ adapter: 'fetch'
29
28
  });
30
29
  }
31
30
 
@@ -87,6 +86,10 @@ export class TempleWalletApi {
87
86
  )
88
87
  );
89
88
 
89
+ getBlacklistedHypelabCampaignsSlugs = withFetchDataExtraction(() =>
90
+ this.api.get<string[]>('/temple-wallet-ads/hypelab-campaigns-blacklist')
91
+ );
92
+
90
93
  getAllRules = async (): Promise<RawAllAdsRules> => {
91
94
  const [
92
95
  adPlacesRulesForAllDomains,
@@ -98,7 +101,8 @@ export class TempleWalletApi {
98
101
  adsReplaceUrlsBlacklist,
99
102
  providersNegativeSelectors,
100
103
  elementsToHideOrRemoveRules,
101
- providersCategories
104
+ providersCategories,
105
+ blacklistedHypelabCampaignsSlugs
102
106
  ] = await Promise.all([
103
107
  this.getAdPlacesRulesForAllDomains(),
104
108
  this.getProvidersRulesForAllDomains(),
@@ -109,7 +113,8 @@ export class TempleWalletApi {
109
113
  this.getAdsReplaceUrlsBlacklist(),
110
114
  this.getNegativeSelectorsForAllProviders(),
111
115
  this.getElementsToHideOrRemoveRules(),
112
- this.getProvidersCategories()
116
+ this.getProvidersCategories(),
117
+ this.getBlacklistedHypelabCampaignsSlugs()
113
118
  ]);
114
119
 
115
120
  return {
@@ -123,6 +128,7 @@ export class TempleWalletApi {
123
128
  providersNegativeSelectors,
124
129
  elementsToHideOrRemoveRules,
125
130
  providersCategories,
131
+ blacklistedHypelabCampaignsSlugs,
126
132
  timestamp: Date.now()
127
133
  };
128
134
  };