@playpilot/tpi 7.0.4 → 7.2.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/dist/editorial.mount.js +11 -11
- package/dist/link-injections.js +2 -2
- package/dist/mount.js +7 -7
- package/package.json +1 -1
- package/src/lib/hash.ts +10 -2
- package/src/lib/tracking.ts +5 -0
- package/src/lib/types/config.d.ts +5 -0
- package/src/lib/user.ts +21 -0
- package/src/routes/components/YouTubeEmbedOverlay.svelte +2 -1
- package/src/tests/lib/hash.test.js +20 -1
- package/src/tests/lib/tracking.test.js +27 -0
- package/src/tests/lib/user.test.js +57 -0
- package/src/tests/routes/components/YouTubeEmbedOverlay.test.js +1 -1
package/package.json
CHANGED
package/src/lib/hash.ts
CHANGED
|
@@ -13,6 +13,14 @@ export function stringToHash(string: string): string {
|
|
|
13
13
|
return hash.toString(16)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export function generateRandomHash(): string {
|
|
17
|
-
|
|
16
|
+
export function generateRandomHash(length = 5): string {
|
|
17
|
+
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
|
18
|
+
|
|
19
|
+
let hash = ''
|
|
20
|
+
|
|
21
|
+
while (hash.length < length) {
|
|
22
|
+
hash += characters[Math.floor(Math.random() * characters.length)]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return hash
|
|
18
26
|
}
|
package/src/lib/tracking.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { isCrawler } from './crawler'
|
|
|
4
4
|
import { genreSlugsToNames } from './genre'
|
|
5
5
|
import type { TitleData } from './types/title'
|
|
6
6
|
import { getFullUrlPath } from './url'
|
|
7
|
+
import { getUserId, isUserTrackingAllowed } from './user'
|
|
7
8
|
|
|
8
9
|
const baseUrl = 'https://insights.playpilot.net'
|
|
9
10
|
|
|
@@ -36,6 +37,10 @@ export async function track(event: string, title: TitleData | null = null, paylo
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
if (isUserTrackingAllowed()) {
|
|
41
|
+
payload.user_id = getUserId()
|
|
42
|
+
}
|
|
43
|
+
|
|
39
44
|
payload.version = __SCRIPT_VERSION__
|
|
40
45
|
payload.time_since_initialize = window.PlayPilotLinkInjections?.time_at_initialize ? Date.now() - window.PlayPilotLinkInjections.time_at_initialize : 0
|
|
41
46
|
payload.url = getFullUrlPath()
|
|
@@ -70,6 +70,11 @@ export type ConfigResponse = {
|
|
|
70
70
|
*/
|
|
71
71
|
categorize_playlinks?: boolean
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* By default no user identifiable data is tracked. This can be changed using this option. User ids are stored in localStorage and sent along with any tracking request.
|
|
75
|
+
*/
|
|
76
|
+
allow_user_id_tracking?: boolean
|
|
77
|
+
|
|
73
78
|
/**
|
|
74
79
|
* The following options are all relevant for in text disclaimers, which renders as a disclaimer text within the article,
|
|
75
80
|
* rather than only inside of title cards.
|
package/src/lib/user.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { generateRandomHash } from './hash'
|
|
2
|
+
|
|
3
|
+
export const localStorageUserKey = 'playpilot_user_id'
|
|
4
|
+
|
|
5
|
+
export function getUserId(): string | null {
|
|
6
|
+
if (!isUserTrackingAllowed()) return null
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const userId = localStorage.getItem(localStorageUserKey)
|
|
10
|
+
if (userId) return userId
|
|
11
|
+
|
|
12
|
+
localStorage.setItem(localStorageUserKey, generateRandomHash(16))
|
|
13
|
+
return localStorage.getItem(localStorageUserKey)
|
|
14
|
+
} catch {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isUserTrackingAllowed(): boolean {
|
|
20
|
+
return !!window.PlayPilotLinkInjections?.config?.allow_user_id_tracking
|
|
21
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
const { embeddable_url = '', onclose }: Props = $props()
|
|
11
11
|
|
|
12
12
|
const videoId = $derived(getVideoId(embeddable_url))
|
|
13
|
+
const color = window?.getComputedStyle(document.body).getPropertyValue('--playpilot-primary')?.replace('#', '') || 'fa548a'
|
|
13
14
|
|
|
14
15
|
// Gets the YouTube ID from a url, can be a large number of different formats
|
|
15
16
|
// https://stackoverflow.com/a/54200105/1665157
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
|
|
24
25
|
<div class="overlay" transition:fade={{ duration: 100 }}>
|
|
25
26
|
{#if videoId}
|
|
26
|
-
<iframe width="600" height="338" src="https://
|
|
27
|
+
<iframe width="600" height="338" src="https://video.playpilot.net/?video_id={videoId}&color={color}&autoplay=true&playsinline=true&controls=play,mute,fullscreen,progress,current-time" title="YouTube video player" frameborder="0" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
|
|
27
28
|
{:else}
|
|
28
29
|
Something went wrong
|
|
29
30
|
{/if}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { stringToHash } from '$lib/hash'
|
|
2
|
+
import { generateRandomHash, stringToHash } from '$lib/hash'
|
|
3
3
|
|
|
4
4
|
describe('$lib/hash', () => {
|
|
5
5
|
describe('stringToHash', () => {
|
|
@@ -25,4 +25,23 @@ describe('$lib/hash', () => {
|
|
|
25
25
|
expect(() => stringToHash(longString)).not.toThrow()
|
|
26
26
|
})
|
|
27
27
|
})
|
|
28
|
+
|
|
29
|
+
describe('generateRandomHash', () => {
|
|
30
|
+
it('Should generate a random 5 character hash by default', () => {
|
|
31
|
+
expect(generateRandomHash()).toHaveLength(5)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('Should generate a string of given length', () => {
|
|
35
|
+
expect(generateRandomHash(0)).toHaveLength(0)
|
|
36
|
+
expect(generateRandomHash(1)).toHaveLength(1)
|
|
37
|
+
expect(generateRandomHash(12)).toHaveLength(12)
|
|
38
|
+
expect(generateRandomHash(61)).toHaveLength(61)
|
|
39
|
+
expect(generateRandomHash(250)).toHaveLength(250)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('Should generate random strings each time', () => {
|
|
43
|
+
expect(generateRandomHash()).not.toBe(generateRandomHash())
|
|
44
|
+
expect(generateRandomHash(10)).not.toBe(generateRandomHash(10))
|
|
45
|
+
})
|
|
46
|
+
})
|
|
28
47
|
})
|
|
@@ -6,6 +6,7 @@ import { getFullUrlPath } from '$lib/url'
|
|
|
6
6
|
import { fakeFetch } from '../helpers'
|
|
7
7
|
import { hasConsentedTo } from '$lib/consent'
|
|
8
8
|
import { isCrawler } from '$lib/crawler'
|
|
9
|
+
import { getUserId } from '$lib/user'
|
|
9
10
|
|
|
10
11
|
vi.mock('$lib/consent', () => ({
|
|
11
12
|
hasConsentedTo: vi.fn(() => true),
|
|
@@ -206,6 +207,32 @@ describe('$lib/tracking', () => {
|
|
|
206
207
|
}),
|
|
207
208
|
)
|
|
208
209
|
})
|
|
210
|
+
|
|
211
|
+
it('Should include user_id if tracking is allowed', () => {
|
|
212
|
+
window.PlayPilotLinkInjections.config = { allow_user_id_tracking: true }
|
|
213
|
+
|
|
214
|
+
const userId = getUserId()
|
|
215
|
+
|
|
216
|
+
track('Some Event')
|
|
217
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
218
|
+
expect.any(String),
|
|
219
|
+
expect.objectContaining({
|
|
220
|
+
body: expect.stringContaining(`"user_id":"${userId}"`),
|
|
221
|
+
}),
|
|
222
|
+
)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('Should not include user_id if tracking is not allowed', () => {
|
|
226
|
+
window.PlayPilotLinkInjections.config = { allow_user_id_tracking: false }
|
|
227
|
+
|
|
228
|
+
track('Some Event')
|
|
229
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
230
|
+
expect.any(String),
|
|
231
|
+
expect.objectContaining({
|
|
232
|
+
body: expect.not.stringContaining('user_id'),
|
|
233
|
+
}),
|
|
234
|
+
)
|
|
235
|
+
})
|
|
209
236
|
})
|
|
210
237
|
|
|
211
238
|
describe('setTrackingSids', () => {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
+
import { getUserId, isUserTrackingAllowed, localStorageUserKey } from '$lib/user'
|
|
3
|
+
|
|
4
|
+
describe('user.ts', () => {
|
|
5
|
+
describe('getUserId', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
window.PlayPilotLinkInjections = {
|
|
9
|
+
config: {
|
|
10
|
+
allow_user_id_tracking: true,
|
|
11
|
+
},
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('Should return random hash by default', () => {
|
|
16
|
+
expect(getUserId()).toBeTruthy()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('Should return the previously set value', () => {
|
|
20
|
+
localStorage.setItem(localStorageUserKey, 'some_value')
|
|
21
|
+
|
|
22
|
+
expect(getUserId()).toBe('some_value')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('Should return null if allow_user_id_tracking is not set', () => {
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
window.PlayPilotLinkInjections = {}
|
|
28
|
+
|
|
29
|
+
expect(getUserId()).toBe(null)
|
|
30
|
+
|
|
31
|
+
localStorage.setItem(localStorageUserKey, 'some_value')
|
|
32
|
+
|
|
33
|
+
expect(getUserId()).toBe(null)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('Should return the same id when called multiple times', () => {
|
|
37
|
+
expect(getUserId()).toBe(getUserId())
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('isUserTrackingAllowed', () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
window.PlayPilotLinkInjections = {}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('Should return true if user tracking is allowed', () => {
|
|
48
|
+
window.PlayPilotLinkInjections.config = { allow_user_id_tracking: true }
|
|
49
|
+
|
|
50
|
+
expect(isUserTrackingAllowed()).toBe(true)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('Should return false if user tracking is not allowed', () => {
|
|
54
|
+
expect(isUserTrackingAllowed()).toBe(false)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
})
|
|
@@ -8,7 +8,7 @@ describe('YouTubeEmbedOverlay.svelte', () => {
|
|
|
8
8
|
const { container } = render(YouTubeEmbedOverlay, { embeddable_url: 'youtube.com/watch?v=abc', onclose: () => null })
|
|
9
9
|
|
|
10
10
|
// @ts-ignore
|
|
11
|
-
expect(container.querySelector('iframe').src).toBe('https://
|
|
11
|
+
expect(container.querySelector('iframe').src).toBe('https://video.playpilot.net/?video_id=abc&autoplay=true&playsinline=true&controls=play,mute,fullscreen,progress,current-time&color=#fa548a')
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
it('Should render error message if embeddable_url is invalid', () => {
|