@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/dist/link-injections.js +2 -2
- package/dist/mount.js +9 -9
- package/package.json +1 -1
- package/src/lib/api/auth.ts +4 -2
- package/src/lib/injectionElements.ts +10 -0
- package/src/lib/url.ts +7 -0
- package/src/main.ts +6 -4
- package/src/routes/+page.svelte +9 -19
- package/src/tests/lib/afterArticle.test.js +2 -1
- package/src/tests/lib/api/auth.test.js +14 -10
- package/src/tests/lib/disclaimer.test.js +2 -1
- package/src/tests/lib/url.test.js +24 -2
- package/src/tests/routes/+page.test.js +9 -5
package/package.json
CHANGED
package/src/lib/api/auth.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
const pageText =
|
|
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
|
|
package/src/routes/+page.svelte
CHANGED
|
@@ -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 {
|
|
5
|
+
import { getPageText, getPageTextAndElements } from '$lib/injectionElements'
|
|
6
6
|
import { fireQueuedTrackingEvents, setTrackingSids, track } from '$lib/tracking'
|
|
7
|
-
import {
|
|
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
|
|
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 =
|
|
81
|
+
isUrlExcluded = isUrlExcludedViaConfig(config)
|
|
84
82
|
if (isUrlExcluded) return
|
|
85
83
|
|
|
86
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
104
|
+
window.PlayPiloLinkInjections = {}
|
|
101
105
|
})
|
|
102
106
|
|
|
103
107
|
it('Should call pollLinkInjections and fetchConfig on mount', async () => {
|