@vouchfor/embeds 0.0.0-experiment.1689fef → 0.0.0-experiment.1795b49
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/es/browser-1dddccce.js +433 -0
- package/dist/es/browser-1dddccce.js.map +1 -0
- package/dist/es/embeds.js +7 -1229
- package/dist/es/embeds.js.map +1 -1
- package/dist/es/index-6e2fc940.js +9947 -0
- package/dist/es/index-6e2fc940.js.map +1 -0
- package/dist/es/src/components/Embed/controllers/fetcher.d.ts +14 -0
- package/dist/es/{components → src/components}/Embed/controllers/tracking.d.ts +7 -2
- package/dist/es/{components → src/components}/Embed/index.d.ts +12 -4
- package/dist/es/src/utils/env.d.ts +12 -0
- package/dist/iife/embeds.iife.js +375 -304
- package/dist/iife/embeds.iife.js.map +1 -1
- package/package.json +6 -6
- package/src/components/Embed/Embed.stories.ts +17 -4
- package/src/components/Embed/controllers/fetcher.ts +85 -48
- package/src/components/Embed/controllers/tracking.ts +98 -65
- package/src/components/Embed/index.ts +26 -11
- package/src/utils/env.ts +18 -32
- package/dist/es/components/Embed/controllers/event-forwarder.d.ts +0 -14
- package/dist/es/components/Embed/controllers/fetcher.d.ts +0 -20
- package/dist/es/utils/env.d.ts +0 -18
- /package/dist/es/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/es/{utils → src/utils}/events.d.ts +0 -0
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@vouchfor/embeds",
|
3
|
-
"version": "0.0.0-experiment.
|
3
|
+
"version": "0.0.0-experiment.1795b49",
|
4
4
|
"license": "MIT",
|
5
5
|
"author": "Aaron Williams",
|
6
6
|
"main": "dist/es/embeds.js",
|
@@ -26,7 +26,7 @@
|
|
26
26
|
"lint:staged": "lint-staged",
|
27
27
|
"prepublishOnly": "yarn build",
|
28
28
|
"size": "size-limit",
|
29
|
-
"storybook": "yarn prebuild && storybook dev -p
|
29
|
+
"storybook": "yarn prebuild && storybook dev -p 6007",
|
30
30
|
"prebuild": "yarn build:deps && yarn generate:manifest",
|
31
31
|
"test": "true"
|
32
32
|
},
|
@@ -35,12 +35,12 @@
|
|
35
35
|
"**/*.{md,json,yml}": "prettier --write"
|
36
36
|
},
|
37
37
|
"dependencies": {
|
38
|
-
"@lit/task": "1.0.0",
|
39
|
-
"@vouchfor/media-player": "0.0.0-experiment.
|
38
|
+
"@lit/task": "^1.0.0",
|
39
|
+
"@vouchfor/media-player": "0.0.0-experiment.1795b49",
|
40
40
|
"uuid": "^9.0.1"
|
41
41
|
},
|
42
42
|
"peerDependencies": {
|
43
|
-
"lit": "^3.0
|
43
|
+
"lit": "^3.1.0"
|
44
44
|
},
|
45
45
|
"devDependencies": {
|
46
46
|
"@esm-bundle/chai": "^4.3.4-fix.0",
|
@@ -62,7 +62,7 @@
|
|
62
62
|
"eslint": "^8.50.0",
|
63
63
|
"eslint-plugin-import": "^2.28.1",
|
64
64
|
"lint-staged": "^14.0.1",
|
65
|
-
"lit": "^
|
65
|
+
"lit": "^3.1.0",
|
66
66
|
"prettier": "^3.0.3",
|
67
67
|
"react": "^18.2.0",
|
68
68
|
"react-dom": "^18.2.0",
|
@@ -10,7 +10,17 @@ type EmbedArgs = EmbedProps & {
|
|
10
10
|
showVouch?: boolean;
|
11
11
|
};
|
12
12
|
|
13
|
-
const _Embed = ({
|
13
|
+
const _Embed = ({
|
14
|
+
vouchId,
|
15
|
+
templateId,
|
16
|
+
questions,
|
17
|
+
preload,
|
18
|
+
autoplay,
|
19
|
+
env,
|
20
|
+
apiKey,
|
21
|
+
controls,
|
22
|
+
aspectRatio
|
23
|
+
}: EmbedArgs) => {
|
14
24
|
return html`
|
15
25
|
<div style="height: 100vh">
|
16
26
|
<vouch-embed
|
@@ -18,10 +28,12 @@ const _Embed = ({ vouchId, templateId, preload, autoplay, env, apiKey, controls,
|
|
18
28
|
apiKey=${ifDefined(apiKey)}
|
19
29
|
vouchId=${ifDefined(vouchId)}
|
20
30
|
templateId=${ifDefined(templateId)}
|
31
|
+
.questions=${questions}
|
21
32
|
.controls=${controls}
|
22
33
|
?autoplay=${autoplay}
|
23
34
|
preload=${ifDefined(preload)}
|
24
35
|
aspectRatio=${ifDefined(aspectRatio)}
|
36
|
+
@error=${console.log}
|
25
37
|
></vouch-embed>
|
26
38
|
</div>
|
27
39
|
`;
|
@@ -39,10 +51,11 @@ type Story = StoryObj<EmbedArgs>;
|
|
39
51
|
|
40
52
|
const Embed: Story = {
|
41
53
|
args: {
|
42
|
-
env: '
|
54
|
+
env: 'local',
|
43
55
|
apiKey: 'TVik9uTMgE-PD25UTHIS6gyl0hMBWC7AT4dkpdlLBT4VIfDWZJrQiCk6Ak7m1',
|
44
56
|
vouchId: '6JQEIPeStt',
|
45
|
-
templateId: '
|
57
|
+
templateId: '357fc118-e179-4171-9446-ff2b8e9d1b29',
|
58
|
+
questions: [],
|
46
59
|
aspectRatio: 0,
|
47
60
|
preload: 'none',
|
48
61
|
autoplay: false
|
@@ -50,7 +63,7 @@ const Embed: Story = {
|
|
50
63
|
argTypes: {
|
51
64
|
env: {
|
52
65
|
control: 'radio',
|
53
|
-
options: ['
|
66
|
+
options: ['local', 'dev', 'staging', 'prod']
|
54
67
|
},
|
55
68
|
preload: {
|
56
69
|
control: 'radio',
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { Task } from '@lit/task';
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
2
3
|
|
3
4
|
import type { Embed, EmbedProps } from '..';
|
4
|
-
import type { TemplateInstance } from '@vouchfor/canvas-video';
|
5
5
|
import type { ReactiveControllerHost } from 'lit';
|
6
6
|
import type { Environment } from '~/utils/env';
|
7
7
|
|
@@ -9,7 +9,7 @@ import { getEnvUrls } from '~/utils/env';
|
|
9
9
|
|
10
10
|
type EmbedHost = ReactiveControllerHost & Embed;
|
11
11
|
|
12
|
-
type
|
12
|
+
type FetchTaskDeps = [
|
13
13
|
EmbedProps['env'],
|
14
14
|
EmbedProps['apiKey'],
|
15
15
|
EmbedProps['data'],
|
@@ -17,12 +17,13 @@ type TaskDeps = [
|
|
17
17
|
EmbedProps['templateId']
|
18
18
|
];
|
19
19
|
|
20
|
+
type FilterTaskDeps = [EmbedProps['data'], EmbedProps['questions']];
|
21
|
+
|
20
22
|
class FetcherController {
|
21
23
|
host: EmbedHost;
|
22
24
|
|
23
25
|
private _fetching = false;
|
24
|
-
private _vouch
|
25
|
-
private _template?: TemplateInstance;
|
26
|
+
private _vouch: EmbedProps['data'];
|
26
27
|
|
27
28
|
set fetching(value) {
|
28
29
|
if (this._fetching !== value) {
|
@@ -34,74 +35,94 @@ class FetcherController {
|
|
34
35
|
return this._fetching;
|
35
36
|
}
|
36
37
|
|
37
|
-
|
38
|
-
if (this._vouch !== value) {
|
39
|
-
this._vouch = value;
|
40
|
-
this.host.requestUpdate();
|
41
|
-
}
|
42
|
-
}
|
43
|
-
get vouch() {
|
44
|
-
return this._vouch;
|
45
|
-
}
|
46
|
-
|
47
|
-
set template(value) {
|
48
|
-
if (this._template !== value) {
|
49
|
-
this._template = value;
|
50
|
-
this.host.requestUpdate();
|
51
|
-
}
|
52
|
-
}
|
53
|
-
get template() {
|
54
|
-
return this._template;
|
55
|
-
}
|
56
|
-
|
57
|
-
private async getVouch(env: Environment, apiKey: string, vouchId?: string) {
|
38
|
+
private getVouch = async (env: Environment, apiKey: string, vouchId: string) => {
|
58
39
|
const { embedApiUrl } = getEnvUrls(env);
|
59
40
|
|
60
|
-
|
61
|
-
|
41
|
+
const cacheCheck = uuidv4();
|
42
|
+
const res = await fetch(`${embedApiUrl}/vouches/${vouchId}`, {
|
43
|
+
method: 'GET',
|
44
|
+
headers: [
|
45
|
+
['X-Api-Key', apiKey],
|
46
|
+
['X-Cache-Check', cacheCheck]
|
47
|
+
]
|
48
|
+
});
|
49
|
+
|
50
|
+
const vouch = await res.json();
|
51
|
+
this.host.dispatchEvent(new CustomEvent('vouch:loaded', { detail: vouch?.id }));
|
52
|
+
|
53
|
+
// HACK: we're currently using API Gateway caching on the embed API without any invalidation logic,
|
54
|
+
// so to ensure that the cache stays up to date, whenever we detect a cache hit we trigger another
|
55
|
+
// API call with the `Cache-Control` header which will re-fill the cache
|
56
|
+
const resCacheCheck = res?.headers?.get('X-Cache-Check');
|
57
|
+
if (resCacheCheck !== cacheCheck) {
|
58
|
+
fetch(`${embedApiUrl}/vouches/${vouchId}`, {
|
62
59
|
method: 'GET',
|
63
|
-
headers: [
|
64
|
-
|
60
|
+
headers: [
|
61
|
+
['X-Api-Key', apiKey],
|
62
|
+
['Cache-Control', 'max-age=0']
|
63
|
+
]
|
64
|
+
});
|
65
65
|
}
|
66
66
|
|
67
|
-
return
|
68
|
-
}
|
67
|
+
return vouch;
|
68
|
+
};
|
69
69
|
|
70
|
-
private async
|
70
|
+
private getTemplate = async (env: Environment, apiKey: string, templateId: string) => {
|
71
71
|
const { embedApiUrl } = getEnvUrls(env);
|
72
72
|
|
73
|
-
|
74
|
-
|
73
|
+
const cacheCheck = uuidv4();
|
74
|
+
const res = await fetch(`${embedApiUrl}/templates/${templateId}`, {
|
75
|
+
method: 'GET',
|
76
|
+
headers: [
|
77
|
+
['X-Api-Key', apiKey],
|
78
|
+
['X-Cache-Check', cacheCheck]
|
79
|
+
]
|
80
|
+
});
|
81
|
+
const template = await res.json();
|
82
|
+
|
83
|
+
// HACK: we're currently using API Gateway caching on the embed API without any invalidation logic,
|
84
|
+
// so to ensure that the cache stays up to date, whenever we detect a cache hit we trigger another
|
85
|
+
// API call with the `Cache-Control` header which will re-fill the cache
|
86
|
+
const resCacheCheck = res?.headers?.get('X-Cache-Check');
|
87
|
+
if (resCacheCheck !== cacheCheck) {
|
88
|
+
fetch(`${embedApiUrl}/templates/${templateId}`, {
|
75
89
|
method: 'GET',
|
76
|
-
headers: [
|
77
|
-
|
90
|
+
headers: [
|
91
|
+
['X-Api-Key', apiKey],
|
92
|
+
['Cache-Control', 'max-age=0']
|
93
|
+
]
|
94
|
+
});
|
78
95
|
}
|
79
96
|
|
80
|
-
return
|
81
|
-
}
|
97
|
+
return template;
|
98
|
+
};
|
82
99
|
|
83
100
|
constructor(host: EmbedHost) {
|
84
101
|
this.host = host;
|
85
|
-
new Task<
|
102
|
+
new Task<FetchTaskDeps, void>(
|
86
103
|
this.host,
|
87
|
-
async ([env, apiKey, data, vouchId, templateId]:
|
104
|
+
async ([env, apiKey, data, vouchId, templateId]: FetchTaskDeps) => {
|
88
105
|
try {
|
89
|
-
|
90
|
-
|
106
|
+
host.vouch = undefined;
|
107
|
+
host.template = undefined;
|
91
108
|
|
92
109
|
if (data) {
|
93
|
-
|
94
|
-
|
95
|
-
|
110
|
+
let template;
|
111
|
+
if (templateId) {
|
112
|
+
this.fetching = true;
|
113
|
+
template = await this.getTemplate(env, apiKey, templateId);
|
114
|
+
}
|
115
|
+
this._vouch = data;
|
116
|
+
host.template = template ?? data?.settings?.template?.instance;
|
96
117
|
} else if (vouchId) {
|
97
118
|
this.fetching = true;
|
98
119
|
|
99
120
|
const [vouch, template] = await Promise.all([
|
100
121
|
this.getVouch(env, apiKey, vouchId),
|
101
|
-
this.getTemplate(env, apiKey, templateId)
|
122
|
+
templateId ? this.getTemplate(env, apiKey, templateId) : null
|
102
123
|
]);
|
103
|
-
this.
|
104
|
-
|
124
|
+
this._vouch = vouch;
|
125
|
+
host.template = template ?? vouch?.settings?.template?.instance;
|
105
126
|
}
|
106
127
|
} finally {
|
107
128
|
this.fetching = false;
|
@@ -109,6 +130,22 @@ class FetcherController {
|
|
109
130
|
},
|
110
131
|
() => [host.env, host.apiKey, host.data, host.vouchId, host.templateId]
|
111
132
|
);
|
133
|
+
|
134
|
+
// This second task is to be able to filter the vouch without fetching it again if only the questions changed
|
135
|
+
new Task<FilterTaskDeps, void>(
|
136
|
+
this.host,
|
137
|
+
([vouch, questions]: FilterTaskDeps) => {
|
138
|
+
host.vouch = vouch
|
139
|
+
? {
|
140
|
+
...vouch,
|
141
|
+
questions: {
|
142
|
+
items: vouch?.questions.items.filter((_, index) => !questions?.length || questions?.includes(index + 1))
|
143
|
+
}
|
144
|
+
}
|
145
|
+
: undefined;
|
146
|
+
},
|
147
|
+
() => [this._vouch, host.questions]
|
148
|
+
);
|
112
149
|
}
|
113
150
|
}
|
114
151
|
|
@@ -1,18 +1,20 @@
|
|
1
|
+
import { TEMPLATE_VERSION } from '@vouchfor/canvas-video';
|
1
2
|
import { v4 as uuidv4 } from 'uuid';
|
2
3
|
|
3
4
|
import type { Embed } from '..';
|
4
5
|
import type { VideoEventDetail } from '@vouchfor/media-player';
|
5
6
|
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
6
7
|
|
8
|
+
import packageJson from '../../../../package.json';
|
7
9
|
import { getEnvUrls } from '~/utils/env';
|
8
10
|
|
9
|
-
const
|
11
|
+
const MINIMUM_SEND_THRESHOLD = 1;
|
10
12
|
|
11
13
|
type EmbedHost = ReactiveControllerHost & Embed;
|
12
14
|
|
13
15
|
type TrackingEvent = 'VOUCH_LOADED' | 'VOUCH_RESPONSE_VIEWED' | 'VIDEO_PLAYED' | 'VIDEO_STREAMED';
|
14
16
|
type TrackingPayload = {
|
15
|
-
vouchId
|
17
|
+
vouchId?: string;
|
16
18
|
answerId?: string;
|
17
19
|
streamStart?: number;
|
18
20
|
streamEnd?: number;
|
@@ -36,21 +38,23 @@ class TrackingController implements ReactiveController {
|
|
36
38
|
private _hasPlayed = false;
|
37
39
|
private _hasLoaded: BooleanMap = {};
|
38
40
|
private _answersViewed: BooleanMap = {};
|
39
|
-
private
|
40
|
-
private
|
41
|
+
private _streamStartTime: TimeMap = {};
|
42
|
+
private _streamLatestTime: TimeMap = {};
|
43
|
+
private _currentlyPlayingVideo: VideoEventDetail | null = null;
|
41
44
|
|
42
45
|
constructor(host: EmbedHost) {
|
43
46
|
this.host = host;
|
44
47
|
host.addController(this);
|
45
48
|
}
|
46
49
|
|
47
|
-
private _findVouchId() {
|
48
|
-
if (
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
return this.host.
|
50
|
+
private _findVouchId(payload?: TrackingPayload) {
|
51
|
+
if (payload && 'vouchId' in payload) {
|
52
|
+
return payload.vouchId;
|
53
|
+
}
|
54
|
+
if (this.host.vouch) {
|
55
|
+
return this.host.vouch.id;
|
53
56
|
}
|
57
|
+
return null;
|
54
58
|
}
|
55
59
|
|
56
60
|
private _createVisitor = (visitorId: string) => {
|
@@ -116,7 +120,7 @@ class TrackingController implements ReactiveController {
|
|
116
120
|
});
|
117
121
|
|
118
122
|
return {
|
119
|
-
source:
|
123
|
+
source: this.host.trackingSource,
|
120
124
|
time: new Date(),
|
121
125
|
region,
|
122
126
|
country,
|
@@ -124,20 +128,30 @@ class TrackingController implements ReactiveController {
|
|
124
128
|
screenWidth: window.screen.width,
|
125
129
|
referrer: document.referrer,
|
126
130
|
currentUrl: location.href,
|
131
|
+
embedVersion: packageJson.version,
|
132
|
+
templateVersion: TEMPLATE_VERSION,
|
127
133
|
...utmParams
|
128
134
|
};
|
129
135
|
};
|
130
136
|
|
131
|
-
private _sendTrackingEvent = (event: TrackingEvent, payload
|
137
|
+
private _sendTrackingEvent = (event: TrackingEvent, payload?: TrackingPayload) => {
|
138
|
+
const vouchId = this._findVouchId(payload);
|
139
|
+
|
140
|
+
if (!vouchId || this.host.disableTracking) {
|
141
|
+
return;
|
142
|
+
}
|
143
|
+
|
132
144
|
const { publicApiUrl } = getEnvUrls(this.host.env);
|
133
145
|
const { client, tab, request, visitor } = this._getUids();
|
134
146
|
|
135
|
-
// Don't send tracking if we don't have a source
|
136
147
|
navigator.sendBeacon(
|
137
|
-
`${publicApiUrl}/api/events`,
|
148
|
+
`${publicApiUrl}/api/v2/events`,
|
138
149
|
JSON.stringify({
|
139
150
|
event,
|
140
|
-
payload
|
151
|
+
payload: {
|
152
|
+
...payload,
|
153
|
+
vouchId
|
154
|
+
},
|
141
155
|
context: {
|
142
156
|
'x-uid-client': client,
|
143
157
|
'x-uid-tab': tab,
|
@@ -149,6 +163,26 @@ class TrackingController implements ReactiveController {
|
|
149
163
|
);
|
150
164
|
};
|
151
165
|
|
166
|
+
private _streamEnded = () => {
|
167
|
+
if (this._currentlyPlayingVideo) {
|
168
|
+
const { id, key } = this._currentlyPlayingVideo;
|
169
|
+
// Don't send a tracking event when seeking backwards
|
170
|
+
if (this._streamLatestTime[key] > this._streamStartTime[key] + MINIMUM_SEND_THRESHOLD) {
|
171
|
+
// Send a video streamed event any time the stream ends to capture the time between starting
|
172
|
+
// the video and the video stopping for any reason (pausing, deleting the embed node or closing the browser)
|
173
|
+
this._sendTrackingEvent('VIDEO_STREAMED', {
|
174
|
+
answerId: id,
|
175
|
+
streamStart: this._streamStartTime[key],
|
176
|
+
streamEnd: this._streamLatestTime[key]
|
177
|
+
});
|
178
|
+
}
|
179
|
+
|
180
|
+
// Make sure these events are only sent once by deleting the start and latest times
|
181
|
+
delete this._streamStartTime[key];
|
182
|
+
delete this._streamLatestTime[key];
|
183
|
+
}
|
184
|
+
};
|
185
|
+
|
152
186
|
private _handleVouchLoaded = ({ detail: vouchId }: CustomEvent<string>) => {
|
153
187
|
if (!vouchId) {
|
154
188
|
return;
|
@@ -156,91 +190,84 @@ class TrackingController implements ReactiveController {
|
|
156
190
|
|
157
191
|
// Only send loaded event once per session
|
158
192
|
if (!this._hasLoaded[vouchId]) {
|
159
|
-
this._sendTrackingEvent('VOUCH_LOADED', {
|
160
|
-
vouchId
|
161
|
-
});
|
193
|
+
this._sendTrackingEvent('VOUCH_LOADED', { vouchId });
|
162
194
|
this._hasLoaded[vouchId] = true;
|
163
195
|
}
|
164
196
|
};
|
165
197
|
|
166
198
|
private _handlePlay = () => {
|
167
|
-
const vouchId = this._findVouchId();
|
168
|
-
|
169
|
-
if (!vouchId) {
|
170
|
-
return;
|
171
|
-
}
|
172
|
-
|
173
199
|
// Only send the video played event once per session
|
174
200
|
if (!this._hasPlayed) {
|
175
201
|
this._sendTrackingEvent('VIDEO_PLAYED', {
|
176
|
-
vouchId,
|
177
202
|
streamStart: this.host.currentTime
|
178
203
|
});
|
179
204
|
this._hasPlayed = true;
|
180
205
|
}
|
181
206
|
};
|
182
207
|
|
183
|
-
private _handleVideoPlay = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
|
184
|
-
const vouchId = this._findVouchId();
|
185
|
-
if (!vouchId) {
|
186
|
-
return;
|
187
|
-
}
|
208
|
+
private _handleVideoPlay = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
|
188
209
|
// Only increment play count once per session
|
189
|
-
if (!this._answersViewed[
|
210
|
+
if (!this._answersViewed[key]) {
|
190
211
|
this._sendTrackingEvent('VOUCH_RESPONSE_VIEWED', {
|
191
|
-
vouchId,
|
192
212
|
answerId: id
|
193
213
|
});
|
194
|
-
this._answersViewed[
|
214
|
+
this._answersViewed[key] = true;
|
195
215
|
}
|
196
|
-
this._streamedTime[id] = node.currentTime;
|
197
|
-
this._streamedPrevTimestamp[id] = Date.now();
|
198
|
-
};
|
199
216
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
return;
|
217
|
+
if (!this._streamStartTime[key]) {
|
218
|
+
this._streamStartTime[key] = node.currentTime;
|
219
|
+
this._streamLatestTime[key] = node.currentTime;
|
204
220
|
}
|
205
|
-
|
221
|
+
};
|
222
|
+
|
223
|
+
private _handleVideoTimeUpdate = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
|
206
224
|
if (
|
225
|
+
// We only want to count any time that the video is actually playing
|
207
226
|
!this.host.paused &&
|
208
|
-
// Only
|
209
|
-
id === this.host.scene?.video?.id
|
210
|
-
// Throttle the frequency that we send streamed events while playing
|
211
|
-
currentTimestamp - this._streamedPrevTimestamp[id] > STREAMED_THROTTLE
|
227
|
+
// Only update the latest time if this event fires for the currently active video
|
228
|
+
id === this.host.scene?.video?.id
|
212
229
|
) {
|
230
|
+
this._currentlyPlayingVideo = { id, key, node };
|
231
|
+
this._streamLatestTime[key] = node.currentTime;
|
232
|
+
}
|
233
|
+
};
|
234
|
+
|
235
|
+
private _handleVideoPause = ({ detail: { id, key } }: CustomEvent<VideoEventDetail>) => {
|
236
|
+
if (this._streamLatestTime[key] > this._streamStartTime[key] + MINIMUM_SEND_THRESHOLD) {
|
213
237
|
this._sendTrackingEvent('VIDEO_STREAMED', {
|
214
|
-
vouchId,
|
215
238
|
answerId: id,
|
216
|
-
streamStart: this.
|
217
|
-
streamEnd:
|
239
|
+
streamStart: this._streamStartTime[key],
|
240
|
+
streamEnd: this._streamLatestTime[key]
|
218
241
|
});
|
219
|
-
this._streamedTime[id] = node.currentTime;
|
220
|
-
this._streamedPrevTimestamp[id] = currentTimestamp;
|
221
242
|
}
|
243
|
+
delete this._streamStartTime[key];
|
244
|
+
delete this._streamLatestTime[key];
|
222
245
|
};
|
223
246
|
|
224
|
-
private
|
225
|
-
|
226
|
-
|
227
|
-
|
247
|
+
private _pageUnloading = () => {
|
248
|
+
this._streamEnded();
|
249
|
+
// This will try to send the same stream event again so we delete the start and latest
|
250
|
+
// time in stream ended so that there is no times to send and the pause event does nothing
|
251
|
+
this.host.pause();
|
252
|
+
};
|
253
|
+
|
254
|
+
private _handleVisibilityChange = () => {
|
255
|
+
if (document.visibilityState === 'hidden') {
|
256
|
+
this._pageUnloading();
|
228
257
|
}
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
this.
|
233
|
-
vouchId,
|
234
|
-
answerId: id,
|
235
|
-
streamStart: this._streamedTime[id],
|
236
|
-
streamEnd: node.currentTime
|
237
|
-
});
|
238
|
-
delete this._streamedTime[id];
|
239
|
-
delete this._streamedPrevTimestamp[id];
|
258
|
+
};
|
259
|
+
|
260
|
+
private _handlePageHide = () => {
|
261
|
+
this._pageUnloading();
|
240
262
|
};
|
241
263
|
|
242
264
|
hostConnected() {
|
243
265
|
requestAnimationFrame(() => {
|
266
|
+
if ('onvisibilitychange' in document) {
|
267
|
+
document.addEventListener('visibilitychange', this._handleVisibilityChange);
|
268
|
+
} else {
|
269
|
+
window.addEventListener('pagehide', this._handlePageHide);
|
270
|
+
}
|
244
271
|
this.host.addEventListener('vouch:loaded', this._handleVouchLoaded);
|
245
272
|
this.host.mediaPlayer?.addEventListener('play', this._handlePlay);
|
246
273
|
this.host.mediaPlayer?.addEventListener('video:play', this._handleVideoPlay);
|
@@ -250,6 +277,12 @@ class TrackingController implements ReactiveController {
|
|
250
277
|
}
|
251
278
|
|
252
279
|
hostDisconnected() {
|
280
|
+
this._streamEnded();
|
281
|
+
if ('onvisibilitychange' in document) {
|
282
|
+
document.removeEventListener('visibilitychange', this._handleVisibilityChange);
|
283
|
+
} else {
|
284
|
+
window.removeEventListener('pagehide', this._handlePageHide);
|
285
|
+
}
|
253
286
|
this.host.removeEventListener('vouch:loaded', this._handleVouchLoaded);
|
254
287
|
this.host.mediaPlayer?.removeEventListener('play', this._handlePlay);
|
255
288
|
this.host.mediaPlayer?.removeEventListener('video:play', this._handleVideoPlay);
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import { html, LitElement } from 'lit';
|
2
|
-
import { customElement, property } from 'lit/decorators.js';
|
1
|
+
import { css, html, LitElement } from 'lit';
|
2
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
3
3
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
4
4
|
import { createRef, ref } from 'lit/directives/ref.js';
|
5
5
|
|
6
|
-
import type { Scene, TemplateInstance } from '@vouchfor/canvas-video';
|
6
|
+
import type { Scene, Scenes, TemplateInstance } from '@vouchfor/canvas-video';
|
7
7
|
import type { MediaPlayer, MediaPlayerProps } from '@vouchfor/media-player';
|
8
8
|
import type { Ref } from 'lit/directives/ref.js';
|
9
9
|
import type { Environment } from '~/utils/env';
|
@@ -17,20 +17,35 @@ import '@vouchfor/media-player';
|
|
17
17
|
type EmbedProps = Pick<MediaPlayerProps, 'data' | 'aspectRatio' | 'preload' | 'autoplay' | 'controls'> & {
|
18
18
|
env: Environment;
|
19
19
|
apiKey: string;
|
20
|
+
disableTracking?: boolean;
|
21
|
+
trackingSource?: string;
|
20
22
|
vouchId?: string;
|
21
23
|
templateId?: string;
|
24
|
+
// Index of the questions to include starting from 1
|
25
|
+
questions?: number[];
|
22
26
|
};
|
23
27
|
|
24
28
|
@customElement('vouch-embed')
|
25
29
|
class Embed extends LitElement {
|
30
|
+
static styles = [
|
31
|
+
css`
|
32
|
+
:host {
|
33
|
+
display: flex;
|
34
|
+
}
|
35
|
+
`
|
36
|
+
];
|
37
|
+
|
26
38
|
private _mediaPlayerRef: Ref<MediaPlayer> = createRef();
|
27
39
|
|
28
|
-
@property({ type: Object
|
40
|
+
@property({ type: Object }) data: EmbedProps['data'];
|
29
41
|
@property({ type: String }) vouchId: EmbedProps['vouchId'];
|
30
42
|
@property({ type: String }) templateId: EmbedProps['templateId'];
|
43
|
+
@property({ type: Array }) questions: EmbedProps['questions'];
|
31
44
|
|
32
45
|
@property({ type: String }) env: EmbedProps['env'] = 'prod';
|
33
46
|
@property({ type: String }) apiKey: EmbedProps['apiKey'] = '';
|
47
|
+
@property({ type: Boolean }) disableTracking: EmbedProps['disableTracking'] = false;
|
48
|
+
@property({ type: String }) trackingSource: EmbedProps['trackingSource'] = 'embed';
|
34
49
|
|
35
50
|
@property({ type: Array }) controls: EmbedProps['controls'];
|
36
51
|
@property({ type: String }) preload: EmbedProps['preload'] = 'auto';
|
@@ -55,6 +70,7 @@ class Embed extends LitElement {
|
|
55
70
|
'waiting',
|
56
71
|
|
57
72
|
'video:loadeddata',
|
73
|
+
'video:seeking',
|
58
74
|
'video:seeked',
|
59
75
|
'video:play',
|
60
76
|
'video:playing',
|
@@ -69,13 +85,8 @@ class Embed extends LitElement {
|
|
69
85
|
// @ts-ignore
|
70
86
|
private _trackingController = new TrackingController(this);
|
71
87
|
|
72
|
-
|
73
|
-
|
74
|
-
}
|
75
|
-
|
76
|
-
get template(): TemplateInstance | undefined {
|
77
|
-
return this._fetcherController.template;
|
78
|
-
}
|
88
|
+
@state() vouch: EmbedProps['data'];
|
89
|
+
@state() template: TemplateInstance | undefined;
|
79
90
|
|
80
91
|
get fetching() {
|
81
92
|
return this._fetcherController.fetching;
|
@@ -149,6 +160,10 @@ class Embed extends LitElement {
|
|
149
160
|
return this._mediaPlayerRef.value?.scenes ?? [];
|
150
161
|
}
|
151
162
|
|
163
|
+
get sceneConfig(): Scenes | null {
|
164
|
+
return this._mediaPlayerRef.value?.sceneConfig ?? null;
|
165
|
+
}
|
166
|
+
|
152
167
|
get videoState() {
|
153
168
|
return this._mediaPlayerRef.value?.videoState;
|
154
169
|
}
|