@playpilot/tpi 8.40.0-beta.4 → 8.40.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "8.40.0-beta.4",
3
+ "version": "8.40.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -14,3 +14,7 @@ export async function fetchConfig(): Promise<ConfigResponse | null> {
14
14
  return null
15
15
  }
16
16
  }
17
+
18
+ export function getConfigValue<K extends keyof ConfigResponse>(key: K): ConfigResponse[K] | undefined {
19
+ return window.PlayPilotLinkInjections?.config?.[key]
20
+ }
@@ -2,6 +2,7 @@ import { SupportedRegion } from '$lib/enums/Region'
2
2
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
3
3
  import { track } from '$lib/tracking'
4
4
  import { RETARGETING_REGION_KEY } from '@playpilot/retargeting-tracking'
5
+ import { getConfigValue } from './config'
5
6
 
6
7
  export async function getRegionBasedOnIp(timeout = 5000): Promise<string> {
7
8
  const savedRegion = window.PlayPilotLinkInjections?.region
@@ -23,7 +24,7 @@ export async function getRegionBasedOnIp(timeout = 5000): Promise<string> {
23
24
  } catch (error: any) {
24
25
  track(TrackingEvent.RegionRequestFailed, null, { message: error.message })
25
26
 
26
- const fallbackRegion = window.PlayPilotLinkInjections?.config?.fallback_region
27
+ const fallbackRegion = getConfigValue('fallback_region')
27
28
  return fallbackRegion || ''
28
29
  }
29
30
  }
@@ -1,16 +1,17 @@
1
1
  import { mount, unmount } from 'svelte'
2
2
  import InTextDisclaimer from '../routes/components/InTextDisclaimer.svelte'
3
+ import { getConfigValue } from './api/config'
3
4
 
4
5
  export let inTextDisclaimerInsertComponent: object | null = null
5
6
 
6
7
  export function insertInTextDisclaimer(elements: HTMLElement[]): void {
7
- if (!window.PlayPilotLinkInjections?.config?.in_text_disclaimer_enabled) return
8
+ if (!getConfigValue('in_text_disclaimer_enabled')) return
8
9
  if (inTextDisclaimerInsertComponent) return
9
10
 
10
11
  const target = document.createElement('div')
11
- const selector = window.PlayPilotLinkInjections?.config?.in_text_disclaimer_selector
12
+ const selector = getConfigValue('in_text_disclaimer_selector')
12
13
  const insertElement = (selector ? document.querySelector(selector) : null) || elements[0]
13
- const insertPosition = window.PlayPilotLinkInjections?.config?.in_text_disclaimer_insert_position || 'beforebegin'
14
+ const insertPosition = getConfigValue('in_text_disclaimer_insert_position') || 'beforebegin'
14
15
 
15
16
  target.dataset.playpilotInTextDisclaimer = 'true'
16
17
  insertElement.insertAdjacentElement(insertPosition, target)
@@ -1,5 +1,6 @@
1
1
  import { mount, unmount } from 'svelte'
2
2
  import ExploreRouter from '../routes/components/Explore/ExploreRouter.svelte'
3
+ import { getConfigValue } from './api/config'
3
4
 
4
5
  export const exploreParentSelector = '[data-playpilot-explore]'
5
6
  export const explorePreConsentSelector = '[data-playpilot-pre-consent-explore]'
@@ -68,7 +69,7 @@ export function insertExploreIntoNavigation(): boolean {
68
69
  }
69
70
 
70
71
  function insertExploreCustomStyle(): void {
71
- const customStyleString = window.PlayPilotLinkInjections.config?.explore_custom_style
72
+ const customStyleString = getConfigValue('explore_custom_style')
72
73
 
73
74
  if (!customStyleString) return
74
75
 
@@ -81,5 +82,5 @@ function insertExploreCustomStyle(): void {
81
82
  }
82
83
 
83
84
  export function useExploreRouter(): boolean {
84
- return !!window.PlayPilotLinkInjections?.config?.explore_use_router
85
+ return !!getConfigValue('explore_use_router')
85
86
  }
@@ -9,6 +9,7 @@ import { exploreModalUrl, participantUrl, titleUrl } from './routes'
9
9
  import { clearInTextWidgets, insertInTextWidgets } from './inTextWidgets'
10
10
  import { encodeHtmlEntities } from './html'
11
11
  import type { ParticipantData } from './types/participant'
12
+ import { getConfigValue } from './api/config'
12
13
 
13
14
  export const keyDataAttribute = 'data-playpilot-injection-key'
14
15
  export const keySelector = `[${keyDataAttribute}]`
@@ -30,39 +31,15 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
30
31
 
31
32
  const validInjections = filterInvalidInTextInjections(mergedInjections)
32
33
  const foundInjections = validInjections.filter(i => fullText.includes(cleanPhrase(i.sentence)))
33
-
34
- const failedMessages: Record<string, string> = {}
35
- const aiInjectionSidsCount: Record<string, number> = {}
36
- let aiParticipantInjectionsCount = 0
34
+ const { injections: limitedInjections, failedMessages } = limitInjectionsByCount(foundInjections)
37
35
 
38
36
  let injectionIndex = -1 // This index is used in Option 3 below.
39
- for (const injection of sortInjections(foundInjections)) {
37
+ for (const injection of sortInjections(limitedInjections)) {
40
38
  injectionIndex++
41
39
 
42
- if (!injection.manual) {
43
- const previousCount = aiInjectionSidsCount[injection.sid] || 0
44
-
45
- const exceedsUniqueCount =
46
- window.PlayPilotLinkInjections?.config?.unique_injection_count_limit &&
47
- previousCount >= window.PlayPilotLinkInjections?.config?.unique_injection_count_limit
48
-
49
- const exceedsTotalParticipantsCount =
50
- injection.type === 'participant' &&
51
- window.PlayPilotLinkInjections?.config?.total_participant_injection_count_limit &&
52
- aiParticipantInjectionsCount >= window.PlayPilotLinkInjections?.config?.total_participant_injection_count_limit
53
-
54
- if (exceedsUniqueCount || exceedsTotalParticipantsCount) {
55
- failedMessages[injection.key] = 'Injection was withheld because it exceeded the maximum unique count.'
56
- continue
57
- } else {
58
- aiInjectionSidsCount[injection.sid] = previousCount + 1
59
- if (injection.type === 'participant') aiParticipantInjectionsCount++
60
- }
61
-
62
- if (injection.type === 'participant' && !doesParticipantMeetThreshold(mergedInjections, injection.participant_details!)) {
63
- failedMessages[injection.key] = 'Participant does not meet popularity threshold.'
64
- continue
65
- }
40
+ if (!injection.manual && injection.type === 'participant' && !doesParticipantMeetThreshold(mergedInjections, injection.participant_details!)) {
41
+ failedMessages[injection.key] = 'Participant does not meet popularity threshold.'
42
+ continue
66
43
  }
67
44
 
68
45
  const elementIndex = elements.findIndex(element => cleanPhrase(element.innerText).includes(cleanPhrase(injection.sentence)))
@@ -243,7 +220,7 @@ function createLinkInjectionElement(injection: LinkInjection): { injectionElemen
243
220
  injectionElement.dataset.playpilotInjectionKey = injection.key
244
221
  injectionElement.dataset.playpilotInjectionType = injection.type
245
222
 
246
- const openInExplore = !!window.PlayPilotLinkInjections?.config?.open_tpi_links_in_explore
223
+ const openInExplore = !!getConfigValue('open_tpi_links_in_explore')
247
224
 
248
225
  const sid = injection.title_details?.sid || injection.participant_details?.sid
249
226
  const href = openInExplore ? exploreModalUrl(sid!) : (injection.type === 'title' ? titleUrl(injection.title_details!) : participantUrl(injection.participant_details!))
@@ -360,7 +337,7 @@ function addLinkInjectionEventListeners(injections: LinkInjection[]): void {
360
337
  window.addEventListener('mousemove', destroyLinkPopoverOnMouseleave)
361
338
 
362
339
  window.addEventListener('click', (event) => {
363
- if (window.PlayPilotLinkInjections?.config?.open_tpi_links_in_explore) return
340
+ if (getConfigValue('open_tpi_links_in_explore')) return
364
341
 
365
342
  openModalForInjectedLink(event, injections)
366
343
  })
@@ -468,6 +445,38 @@ export function filterRemovedAndInactiveInjections(injections: LinkInjection[]):
468
445
  })
469
446
  }
470
447
 
448
+ export function limitInjectionsByCount(injections: LinkInjection[]): { injections: LinkInjection[], failedMessages: Record<string, string> } {
449
+ const limitedInjections: LinkInjection[] = []
450
+ const failedMessages: Record<string, string> = {}
451
+
452
+ const aiInjectionSidsCount: Record<string, number> = {}
453
+ let aiParticipantInjectionsCount = 0
454
+
455
+ for (const injection of injections) {
456
+ if (!injection.manual) {
457
+ const previousCount = aiInjectionSidsCount[injection.sid] || 0
458
+ const uniqueLimit = getConfigValue('unique_injection_count_limit')
459
+ const totalLimit = getConfigValue('total_participant_injection_count_limit')
460
+
461
+ const exceedsUniqueCount = uniqueLimit && previousCount >= uniqueLimit
462
+
463
+ const exceedsTotalParticipantsCount = injection.type === 'participant' && totalLimit && aiParticipantInjectionsCount >= totalLimit
464
+
465
+ if (exceedsUniqueCount || exceedsTotalParticipantsCount) {
466
+ failedMessages[injection.key] = `Injection was withheld because it exceeded the maximum ${exceedsUniqueCount ? 'unique' : 'total'} count.`
467
+ continue
468
+ } else {
469
+ aiInjectionSidsCount[injection.sid] = previousCount + 1
470
+ if (injection.type === 'participant') aiParticipantInjectionsCount++
471
+ }
472
+ }
473
+
474
+ limitedInjections.push(injection)
475
+ }
476
+
477
+ return { injections: limitedInjections, failedMessages }
478
+ }
479
+
471
480
  /**
472
481
  * Injections are sorted first by their sentence and second by their title length. The first sorting, sentence, is so that injections remain
473
482
  * roughly grouped. The second sorting is to prevent shorter titles from merging into longer ones. For instance;
@@ -510,9 +519,9 @@ export function getPlayPilotWrapperElement(): Element {
510
519
  export function doesParticipantMeetThreshold(injections: LinkInjection[], participant: ParticipantData): boolean {
511
520
  if (participant.popularity === null || participant.popularity === undefined) return true
512
521
 
513
- const useArticleAverage = window.PlayPilotLinkInjections?.config?.participant_injection_popularity_threshold_use_article_average ?? true
522
+ const useArticleAverage = getConfigValue('participant_injection_popularity_threshold_use_article_average') ?? true
514
523
  const defaultThreshold = 0
515
- const configThreshold = window.PlayPilotLinkInjections?.config?.participant_injection_popularity_threshold
524
+ const configThreshold = getConfigValue('participant_injection_popularity_threshold')
516
525
  const threshold = configThreshold ?? defaultThreshold
517
526
 
518
527
  if (threshold === 0) return true
@@ -1,9 +1,10 @@
1
1
  import { RETARGETING_USER_ID_KEY } from '@playpilot/retargeting-tracking'
2
2
  import { hasConsentedTo } from './consent'
3
3
  import { getUserId } from './user'
4
+ import { getConfigValue } from './api/config'
4
5
 
5
6
  export function isRetargetingAllowed(): boolean {
6
- if (!window.PlayPilotLinkInjections.config?.allow_retargeting_pixels) return false
7
+ if (!getConfigValue('allow_retargeting_pixels')) return false
7
8
 
8
9
  if (!hasConsentedTo('pixels')) return false
9
10
 
package/src/lib/routes.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { getConfigValue } from './api/config'
1
2
  import { playPilotBaseUrl } from './constants'
2
3
  import type { ParticipantData } from './types/participant'
3
4
  import type { TitleData } from './types/title'
@@ -11,5 +12,5 @@ export function participantUrl(participant: ParticipantData): string {
11
12
  }
12
13
 
13
14
  export function exploreModalUrl(sid: string): string {
14
- return window.PlayPilotLinkInjections?.config?.explore_navigation_path + `?route=modal&sid=${sid}`
15
+ return getConfigValue('explore_navigation_path') + `?route=modal&sid=${sid}`
15
16
  }
package/src/lib/user.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { getConfigValue } from './api/config'
1
2
  import { hasConsentedTo } from './consent'
2
3
  import { generateRandomHash } from './hash'
3
4
 
@@ -33,5 +34,5 @@ export function getSessionId(): string | null {
33
34
  }
34
35
 
35
36
  export function isUserTrackingAllowed(): boolean {
36
- return !!window.PlayPilotLinkInjections?.config?.allow_user_id_tracking && hasConsentedTo('tracking')
37
+ return !!getConfigValue('allow_user_id_tracking') && hasConsentedTo('tracking')
37
38
  }
@@ -7,7 +7,7 @@
7
7
  import { isCrawler } from '$lib/crawler'
8
8
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
9
9
  import { fetchAds } from '$lib/api/ads'
10
- import { fetchConfig } from '$lib/api/config'
10
+ import { fetchConfig, getConfigValue } from '$lib/api/config'
11
11
  import { insertExplore, insertExploreIntoNavigation } from '$lib/explore'
12
12
  import { authorize, getAuthToken, isEditorialModeEnabled, removeAuthCookie, setEditorialParamInUrl } from '$lib/api/auth'
13
13
  import { getPageTextAndElements } from '$lib/injectionElements'
@@ -55,7 +55,7 @@
55
55
  fireQueuedTrackingEvents()
56
56
  track(TrackingEvent.ArticlePageView)
57
57
 
58
- const explorePath = window.PlayPilotLinkInjections.config?.explore_navigation_path
58
+ const explorePath = getConfigValue('explore_navigation_path')
59
59
 
60
60
  if ((!aiInjections.length && !manualInjections.length) && !(explorePath && document.location.href.includes(explorePath))) return
61
61
 
@@ -102,7 +102,7 @@
102
102
  loading = false
103
103
 
104
104
  if (!response) return
105
- if (window.PlayPilotLinkInjections.config?.disable_public_injections && !(isEditorialMode && authorized)) return
105
+ if (getConfigValue('disable_public_injections') && !(isEditorialMode && authorized)) return
106
106
 
107
107
  setTrackingSids({ organizationSid: response.organization_sid, domainSid: response.domain_sid })
108
108
 
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { getConfigValue } from '$lib/api/config'
2
3
  import { fetchLinkInjections } from '$lib/api/externalPages'
3
4
  import { Language } from '$lib/enums/Language'
4
5
  import { SplitTest } from '$lib/enums/SplitTest'
@@ -131,7 +132,7 @@
131
132
 
132
133
  <hr />
133
134
 
134
- {#if !!(window.PlayPilotLinkInjections.config?.exclude_urls_pattern && getFullUrlPath().match(window.PlayPilotLinkInjections?.config?.exclude_urls_pattern))}
135
+ {#if !!(getConfigValue('exclude_urls_pattern') && getFullUrlPath().match(getConfigValue('exclude_urls_pattern')!))}
135
136
  <span class="error">The current URL is excluded via the config</span>
136
137
  <hr />
137
138
  {/if}
@@ -1,10 +1,11 @@
1
1
  <script lang="ts">
2
+ import { getConfigValue } from '$lib/api/config'
2
3
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
3
4
  import { track } from '$lib/tracking'
4
5
  import IconArrow from '../Icons/IconArrow.svelte'
5
6
 
6
7
  const src = 'https://cdn.jsdelivr.net/npm/@playpilot/tpi@latest/src/lib/images/titles-list.webp'
7
- const href = window.PlayPilotLinkInjections?.config?.explore_navigation_path
8
+ const href = getConfigValue('explore_navigation_path')
8
9
  </script>
9
10
 
10
11
  <a class="call-to-action" {href} onclick={() => track(TrackingEvent.CardToExploreCta)}>
@@ -6,6 +6,7 @@
6
6
  import { exploreParentSelector } from '$lib/explore'
7
7
  import { onMount, setContext } from 'svelte'
8
8
  import { isRetargetingAllowed } from '$lib/retargeting'
9
+ import { getConfigValue } from '$lib/api/config'
9
10
  import { trackViaPixel } from '@playpilot/retargeting-tracking'
10
11
  import Modal from './Modal.svelte'
11
12
  import Title from '../Title.svelte'
@@ -24,7 +25,7 @@
24
25
 
25
26
  const topScrollAd = getFirstAdOfType('top_scroll')
26
27
  const displayAd = getFirstAdOfType('card')
27
- const insertExploreCta = window.PlayPilotLinkInjections?.config?.explore_insert_cta_in_tpi
28
+ const insertExploreCta = getConfigValue('explore_insert_cta_in_tpi')
28
29
  const useBubble = !!topScrollAd || (insertExploreCta && !document.querySelector(exploreParentSelector))
29
30
 
30
31
  let hasTrackedScrolling = false
@@ -8,6 +8,7 @@
8
8
  import { trackClickViaPixel, trackViewViaPixel } from '@playpilot/retargeting-tracking'
9
9
  import { MetaEvent, MetaSource } from '$lib/enums/TrackingEvent'
10
10
  import { isRetargetingAllowed } from '$lib/retargeting'
11
+ import { getConfigValue } from '$lib/api/config'
11
12
 
12
13
  interface Props {
13
14
  playlink: PlaylinkData
@@ -36,7 +37,7 @@
36
37
  <!-- svelte-ignore a11y_no_static_element_interactions -->
37
38
  <svelte:element
38
39
  {onclick}
39
- this={!window.PlayPilotLinkInjections?.config?.require_affiliate_consent || hasConsentedTo('affiliate') ? 'a' : 'span'}
40
+ this={!getConfigValue('require_affiliate_consent') || hasConsentedTo('affiliate') ? 'a' : 'span'}
40
41
  {@attach usePixel ? trackViewViaPixel(MetaEvent.ProviderInterest, { provider: playlink.slug, source: MetaSource.Card }) : null}
41
42
  {@attach usePixel ? trackClickViaPixel(MetaEvent.ProviderClick, { provider: playlink.slug }) : null}
42
43
  href={url}
@@ -11,6 +11,7 @@
11
11
  import Playlink from './Playlink.svelte'
12
12
  import Button from '../Button.svelte'
13
13
  import PlaylinksDisclaimer from './PlaylinksDisclaimer.svelte'
14
+ import { getConfigValue } from '$lib/api/config'
14
15
 
15
16
  type Category = PlaylinkCategory | '' | 'Other'
16
17
  type CategorizedPlaylinks = Partial<Record<Category, PlaylinkData[]>>
@@ -27,7 +28,7 @@
27
28
  const isModal = getContext('scope') === 'modal'
28
29
  const type = getContext('type')
29
30
  const displayAd = getFirstAdOfType('card')
30
- const categorize = !!window.PlayPilotLinkInjections?.config?.categorize_playlinks
31
+ const categorize = !!getConfigValue('categorize_playlinks')
31
32
 
32
33
  let outerWidth = $state(0)
33
34
  let limited = $state(!categorize)
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { getConfigValue } from '$lib/api/config'
2
3
  import { mobileBreakpoint } from '$lib/constants'
3
4
  import { onDestroy } from 'svelte'
4
5
  import { SvelteSet } from 'svelte/reactivity'
@@ -17,7 +18,7 @@
17
18
  onDestroy(destroyAllPosters)
18
19
 
19
20
  function onscroll(): void {
20
- if (window.PlayPilotLinkInjections?.config?.disable_scroll_reveal_posters) return
21
+ if (getConfigValue('disable_scroll_reveal_posters')) return
21
22
  if (!isMobile) return
22
23
 
23
24
  setScrollTimeout()
@@ -11,6 +11,7 @@
11
11
  import ParticipantImage from '../Participants/ParticipantImage.svelte'
12
12
  import Rail from './Rail.svelte'
13
13
  import { exploreModalUrl } from '$lib/routes'
14
+ import { getConfigValue } from '$lib/api/config'
14
15
 
15
16
  interface Props {
16
17
  title: TitleData
@@ -45,7 +46,7 @@
45
46
  async function onclick(event: MouseEvent, participant: ParticipantData): Promise<void> {
46
47
  track(TrackingEvent.ParticipantClick, null, { title_source: title.original_title || title.title, participant: participant.name })
47
48
 
48
- if (!window.PlayPilotLinkInjections?.config?.open_tpi_links_in_explore || window.location.href.includes(window.PlayPilotLinkInjections.config.explore_navigation_path || '')) {
49
+ if (!getConfigValue('open_tpi_links_in_explore') || window.location.href.includes(getConfigValue('explore_navigation_path') || '')) {
49
50
  const fetchedParticipant = await fetchParticipantBySid(participant.sid)
50
51
 
51
52
  openModal({ event, type: 'participant', data: fetchedParticipant })
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { getConfigValue } from '$lib/api/config'
2
3
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
3
4
  import { t } from '$lib/localization'
4
5
  import { track } from '$lib/tracking'
@@ -38,7 +39,7 @@
38
39
  <TitlesRail
39
40
  {titles}
40
41
  {heading}
41
- navigateToExplore={!!window.PlayPilotLinkInjections?.config?.open_tpi_links_in_explore}
42
+ navigateToExplore={!!getConfigValue('open_tpi_links_in_explore')}
42
43
  onclick={(title) => track(TrackingEvent.WidgetArticleRailTitleClick, title)} />
43
44
  </div>
44
45
  {/if}
@@ -19,9 +19,14 @@ vi.mock('$lib/api/externalPages', () => ({
19
19
  pollLinkInjections: vi.fn(() => {}),
20
20
  }))
21
21
 
22
- vi.mock('$lib/api/config', () => ({
23
- fetchConfig: vi.fn(),
24
- }))
22
+ // @ts-ignore
23
+ vi.mock(import('$lib/api/config'), async (importOriginal) => {
24
+ const actual = await importOriginal()
25
+ return {
26
+ ...actual,
27
+ fetchConfig: vi.fn(() => {}),
28
+ }
29
+ })
25
30
 
26
31
  // @ts-ignore
27
32
  vi.mock(import('$lib/api/session'), async (importOriginal) => {