@playpilot/tpi 7.0.0-beta.4 → 7.0.0-beta.6

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": "7.0.0-beta.4",
3
+ "version": "7.0.0-beta.6",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -5,7 +5,7 @@ const cookieName = 'EncryptedToken'
5
5
  const urlParam = 'articleReplacementEditToken'
6
6
  const editorialParam = 'playpilot-editorial-mode'
7
7
 
8
- export async function authorize(href: string = window.location.href): Promise<boolean> {
8
+ export async function authorize({ href, throwError }: { href?: string, throwError?: boolean } = { href: window.location.href, throwError: false }): Promise<boolean> {
9
9
  try {
10
10
  const apiToken = getApiToken()
11
11
  if (!apiToken) throw new Error('No token was provided')
@@ -26,10 +26,12 @@ export async function authorize(href: string = window.location.href): Promise<bo
26
26
  return true
27
27
  } catch(error: any) {
28
28
  console.error(error)
29
- throw error
29
+ if (throwError) throw error
30
30
  } finally {
31
31
  removeAuthParamFromUrl()
32
32
  }
33
+
34
+ return false
33
35
  }
34
36
 
35
37
  export function getAuthToken(href: string = ''): string {
@@ -1,3 +1,5 @@
1
+ import type { ConfigResponse } from './types/config'
2
+
1
3
  /**
2
4
  * Return a list of all valid text containing elements that may get injected into.
3
5
  * This excludes duplicates, empty elements, links, buttons, and header tags.
@@ -88,3 +90,11 @@ export function getLinkInjectionsParentElement(): HTMLElement {
88
90
  export function getPageText(elements: HTMLElement[]): string {
89
91
  return elements.map(element => element.innerText).join('\n\n')
90
92
  }
93
+
94
+ export function getPageTextAndElements(config: ConfigResponse | null): { parentElement: HTMLElement, elements: HTMLElement[], pageText: string } {
95
+ const parentElement = getLinkInjectionsParentElement()
96
+ const elements = getLinkInjectionElements(parentElement, config?.exclude_elements_selector || '')
97
+ const pageText = getPageText(elements)
98
+
99
+ return { parentElement, elements, pageText }
100
+ }
package/src/lib/url.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { ConfigResponse } from './types/config'
2
+
1
3
  /**
2
4
  * Get the full path, including host and protocol, but excluding url params.
3
5
  * Removes any extra slashes that might be present. //path -> /path. This is because some sites accept both,
@@ -12,3 +14,8 @@ export function paramsToString(params: Record<string, any>): string {
12
14
  const filteredParams = Object.entries(params).filter(([_, value]) => value !== undefined && value !== null && value !== '')
13
15
  return filteredParams.map(([key, value]) => `${key}=${value}`).join('&')
14
16
  }
17
+
18
+ export function isUrlExcludedViaConfig(config: ConfigResponse): boolean {
19
+ console.log(getFullUrlPath())
20
+ return !!(config.exclude_urls_pattern && getFullUrlPath().match(new RegExp(config.exclude_urls_pattern)))
21
+ }
package/src/main.ts CHANGED
@@ -2,7 +2,8 @@ import { fetchConfig } from '$lib/api/config'
2
2
  import { pollLinkInjections } from '$lib/api/externalPages'
3
3
  import { setConsent } from '$lib/consent'
4
4
  import { isCrawler } from '$lib/crawler'
5
- import { getLinkInjectionElements, getLinkInjectionsParentElement, getPageText } from '$lib/injectionElements'
5
+ import { getPageTextAndElements } from '$lib/injectionElements'
6
+ import { isUrlExcludedViaConfig } from '$lib/url'
6
7
 
7
8
  window.PlayPilotLinkInjections = {
8
9
  token: '',
@@ -62,11 +63,12 @@ window.PlayPilotLinkInjections = {
62
63
 
63
64
  this.config = await fetchConfig() || {}
64
65
 
65
- const parentElement = getLinkInjectionsParentElement()
66
- const elements = getLinkInjectionElements(parentElement, this.config.exclude_elements_selector || '')
67
- const pageText = getPageText(elements)
66
+ if (isUrlExcludedViaConfig(this.config)) return
67
+
68
+ const { pageText } = getPageTextAndElements(this.config)
68
69
 
69
70
  this.initial_link_injections_promise = pollLinkInjections(pageText, { maxTries: 1, runAiWhenRelevant: !isCrawler() })
71
+
70
72
  this.mount()
71
73
  },
72
74
 
@@ -2,9 +2,9 @@
2
2
  import { onDestroy } from 'svelte'
3
3
  import { pollLinkInjections } from '$lib/api/externalPages'
4
4
  import { clearLinkInjections, injectLinksInDocument, separateLinkInjectionTypes } from '$lib/injection'
5
- import { getLinkInjectionElements, getLinkInjectionsParentElement, getPageText } from '$lib/injectionElements'
5
+ import { getPageText, getPageTextAndElements } from '$lib/injectionElements'
6
6
  import { fireQueuedTrackingEvents, setTrackingSids, track } from '$lib/tracking'
7
- import { getFullUrlPath } from '$lib/url'
7
+ import { isUrlExcludedViaConfig } from '$lib/url'
8
8
  import { isCrawler } from '$lib/crawler'
9
9
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
10
10
  import { fetchAds } from '$lib/api/ads'
@@ -21,7 +21,6 @@
21
21
  import UserJourney from './components/UserJourney.svelte'
22
22
  import PostersScrollReveal from './components/PostersScrollReveal.svelte'
23
23
 
24
- let parentElement: HTMLElement | null = $state(null)
25
24
  let elements: HTMLElement[] = $state([])
26
25
 
27
26
  let response: LinkInjectionResponse | null = $state(null)
@@ -67,23 +66,24 @@
67
66
  loading = true
68
67
 
69
68
  try {
70
- if (isEditorialMode) authorized = await authorize()
69
+ if (isEditorialMode) authorized = await authorize({ throwError: true })
71
70
  } catch {
72
71
  track(TrackingEvent.AuthFailed)
73
72
  }
74
73
 
75
74
  try {
76
- const url = getFullUrlPath()
77
-
78
- const config = window.PlayPilotLinkInjections.config || await fetchConfig()
75
+ const config = window.PlayPilotLinkInjections.config || await fetchConfig() || {}
76
+ window.PlayPilotLinkInjections.config = config
79
77
 
80
78
  if (config?.custom_style) insertCustomStyle(config.custom_style || '')
81
79
  if (config?.explore_navigation_selector) insertExploreIntoNavigation()
82
80
 
83
- isUrlExcluded = !!(config?.exclude_urls_pattern && url.match(new RegExp(config.exclude_urls_pattern)))
81
+ isUrlExcluded = isUrlExcludedViaConfig(config)
84
82
  if (isUrlExcluded) return
85
83
 
86
- setElements(config?.html_selector || '', config?.exclude_elements_selector || '')
84
+ if (config?.html_selector) window.PlayPilotLinkInjections.selector = config?.html_selector
85
+
86
+ elements = getPageTextAndElements(window.PlayPilotLinkInjections.config).elements
87
87
  } catch(error) {
88
88
  // We return if the config did not get fetched properly, as we can't determine what should and should
89
89
  // get injected without it.
@@ -146,16 +146,6 @@
146
146
  if (!isEditorialMode) trackInjections(filteredInjections)
147
147
  }
148
148
 
149
- // Set elements to be used by script, if a selector is passed from the config request we update
150
- // the selector on the window object.
151
- // Additionally, a selector can be passed to exclude certain elements.
152
- function setElements(configSelector: string, configExcludeElementsSelector: string): void {
153
- if (configSelector) window.PlayPilotLinkInjections.selector = configSelector
154
-
155
- parentElement = getLinkInjectionsParentElement()
156
- elements = getLinkInjectionElements(parentElement, configExcludeElementsSelector)
157
- }
158
-
159
149
  function openEditorialMode(): void {
160
150
  isEditorialMode = true
161
151
  setEditorialParamInUrl()
@@ -1,7 +1,8 @@
1
1
  import { beforeEach, describe, expect, it, vi } from 'vitest'
2
2
 
3
3
  import { generateInjection } from '../helpers'
4
- import { clearLinkInjections, getLinkInjectionElements } from '$lib/injection'
4
+ import { clearLinkInjections } from '$lib/injection'
5
+ import { getLinkInjectionElements } from '$lib/injectionElements'
5
6
  import { insertAfterArticlePlaylinks } from '$lib/afterArticle'
6
7
  import { mount } from 'svelte'
7
8
 
@@ -1,7 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest'
2
2
 
3
3
  import { authorize, getAuthToken, isEditorialModeEnabled, removeAuthCookie, removeAuthParamFromUrl, setEditorialParamInUrl } from '$lib/api/auth'
4
- import { track } from '$lib/tracking'
5
4
  import { api } from '$lib/api/api'
6
5
 
7
6
  vi.mock('$lib/tracking', () => ({
@@ -19,7 +18,7 @@ describe('$lib/api/auth', () => {
19
18
 
20
19
  describe('authorize', () => {
21
20
  it('Should authorize correctly with url token', async () => {
22
- const authorized = await authorize('https://example.com/some-path?articleReplacementEditToken=some-token')
21
+ const authorized = await authorize({ href: 'https://example.com/some-path?articleReplacementEditToken=some-token' })
23
22
 
24
23
  expect(authorized).toBeTruthy()
25
24
  })
@@ -27,40 +26,45 @@ describe('$lib/api/auth', () => {
27
26
  it('Should authorize correctly with editorial token', async () => {
28
27
  // @ts-ignore
29
28
  window.PlayPilotLinkInjections = { token: 'some-api-token', editorial_token: 'some-token' }
30
- const authorized = await authorize('https://example.com/some-path')
29
+ const authorized = await authorize({ href: 'https://example.com/some-path' })
31
30
 
32
31
  expect(authorized).toBeTruthy()
33
32
  })
34
33
 
35
34
  it('Should not authorize if required url param is not present', async () => {
36
- const authorized = await authorize('https://example.com/some-path')
35
+ const authorized = await authorize({ href: 'https://example.com/some-path' })
37
36
 
38
37
  expect(api).not.toHaveBeenCalled()
39
38
  expect(authorized).not.toBeTruthy()
40
39
  })
41
40
 
42
41
  it('Should authorize with cookie if previously authorized with url param', async () => {
43
- let authorized = await authorize('https://example.com/some-path?articleReplacementEditToken=some-token')
42
+ let authorized = await authorize({ href: 'https://example.com/some-path?articleReplacementEditToken=some-token' })
44
43
  expect(authorized).toBeTruthy()
45
44
 
46
- authorized = await authorize('https://example.com/some-path')
45
+ authorized = await authorize({ href: 'https://example.com/some-path' })
47
46
  expect(authorized).toBeTruthy()
48
47
  })
49
48
 
50
49
  it('Should authorize with cookie if previously authorized with url param and no url is given', async () => {
51
- let authorized = await authorize('https://example.com/some-path?articleReplacementEditToken=some-token')
50
+ let authorized = await authorize({ href: 'https://example.com/some-path?articleReplacementEditToken=some-token' })
52
51
  expect(authorized).toBeTruthy()
53
52
 
54
53
  authorized = await authorize()
55
54
  expect(authorized).toBeTruthy()
56
55
  })
57
56
 
58
- it('Should not authorize is api response was negative', async () => {
57
+ it('Should not authorize if api response was negative', async () => {
59
58
  vi.mocked(api).mockRejectedValueOnce({ response: '', ok: false, status: 403 })
60
- const authorized = await authorize('https://example.com/some-path?articleReplacementEditToken=some-token')
59
+ const authorized = await authorize({ href: 'https://example.com/some-path?articleReplacementEditToken=some-token' })
61
60
 
62
61
  expect(authorized).not.toBeTruthy()
63
- expect(track).toHaveBeenCalled()
62
+ })
63
+
64
+ it('Should throw error if api response was negative and throwError is true', async () => {
65
+ vi.mocked(api).mockRejectedValueOnce({ response: '', ok: false, status: 403 })
66
+
67
+ expect(async () => await authorize({ href: 'https://example.com/some-path?articleReplacementEditToken=some-token', throwError: true })).rejects.toThrowError()
64
68
  })
65
69
  })
66
70
 
@@ -1,6 +1,7 @@
1
1
  import { beforeEach, describe, expect, it, vi } from 'vitest'
2
2
 
3
- import { clearLinkInjections, getLinkInjectionElements } from '$lib/injection'
3
+ import { clearLinkInjections } from '$lib/injection'
4
+ import { getLinkInjectionElements } from '$lib/injectionElements'
4
5
  import { mount } from 'svelte'
5
6
  import { insertInTextDisclaimer } from '$lib/disclaimer'
6
7
 
@@ -1,6 +1,5 @@
1
1
  import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
- import { getFullUrlPath, paramsToString } from '$lib/url'
3
-
2
+ import { getFullUrlPath, isUrlExcludedViaConfig, paramsToString } from '$lib/url'
4
3
 
5
4
  describe('url.ts', () => {
6
5
  describe('getFullUrlPath', () => {
@@ -75,4 +74,27 @@ describe('url.ts', () => {
75
74
  expect(paramsToString({ some: 'param', no: '' })).toBe('some=param')
76
75
  })
77
76
  })
77
+
78
+ describe('isUrlExcludedViaConfig', () => {
79
+ it('Should return false if no key is given in config object', () => {
80
+ expect(isUrlExcludedViaConfig({})).toBe(false)
81
+ })
82
+
83
+ it('Should return false if no key is given but is empty', () => {
84
+ expect(isUrlExcludedViaConfig({ exclude_urls_pattern: '' })).toBe(false)
85
+ })
86
+
87
+ it('Should return false if pattern is given but does not match url', () => {
88
+ window.location.pathname = '/path'
89
+ expect(isUrlExcludedViaConfig({ exclude_urls_pattern: '/not-path' })).toBe(false)
90
+ })
91
+
92
+ it('Should return true if pattern is given and does matches url', () => {
93
+ window.location.pathname = '/path'
94
+ expect(isUrlExcludedViaConfig({ exclude_urls_pattern: '/path' })).toBe(true)
95
+
96
+ window.location.pathname = '/some-page/subpath'
97
+ expect(isUrlExcludedViaConfig({ exclude_urls_pattern: '/some-page(/.*)?$' })).toBe(true)
98
+ })
99
+ })
78
100
  })
@@ -60,9 +60,13 @@ vi.mock('$lib/crawler', () => ({
60
60
  isCrawler: vi.fn(() => false),
61
61
  }))
62
62
 
63
- vi.mock('$lib/url', () => ({
64
- getFullUrlPath: vi.fn(),
65
- }))
63
+ vi.mock(import('$lib/url'), async (importOriginal) => {
64
+ const actual = await importOriginal()
65
+ return {
66
+ ...actual,
67
+ getFullUrlPath: vi.fn(),
68
+ }
69
+ })
66
70
 
67
71
  vi.mock('$lib/splitTest', () => ({
68
72
  trackSplitTestView: vi.fn(),
@@ -93,11 +97,11 @@ describe('$routes/+page.svelte', () => {
93
97
  afterEach(() => {
94
98
  Object.defineProperty(window, 'location', {
95
99
  writable: true,
96
- value: { search: '', href: 'http://localhost:3000/' },
100
+ value: { search: '', href: 'http://localhost:3000/', pathname: '/test' },
97
101
  })
98
102
 
99
103
  // @ts-ignore
100
- window.PlayPiloLinkInjections = null
104
+ window.PlayPiloLinkInjections = {}
101
105
  })
102
106
 
103
107
  it('Should call pollLinkInjections and fetchConfig on mount', async () => {