@vouchfor/embeds 0.0.0-experiment.de7febc → 0.0.0-experiment.e14607a
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/embeds.js +585 -501
- package/dist/es/embeds.js.map +1 -1
- package/dist/es/{components → src/components}/Embed/controllers/tracking.d.ts +7 -2
- package/dist/es/{components → src/components}/Embed/index.d.ts +4 -3
- package/dist/iife/embeds.iife.js +215 -146
- package/dist/iife/embeds.iife.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Embed/controllers/fetcher.ts +3 -3
- package/src/components/Embed/controllers/tracking.ts +102 -81
- package/src/components/Embed/index.ts +8 -3
- /package/dist/es/{components → src/components}/Embed/controllers/fetcher.d.ts +0 -0
- /package/dist/es/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/es/{utils → src/utils}/env.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.e14607a",
|
4
4
|
"license": "MIT",
|
5
5
|
"author": "Aaron Williams",
|
6
6
|
"main": "dist/es/embeds.js",
|
@@ -36,7 +36,7 @@
|
|
36
36
|
},
|
37
37
|
"dependencies": {
|
38
38
|
"@lit/task": "^1.0.0",
|
39
|
-
"@vouchfor/media-player": "0.0.0-experiment.
|
39
|
+
"@vouchfor/media-player": "0.0.0-experiment.e14607a",
|
40
40
|
"uuid": "^9.0.1"
|
41
41
|
},
|
42
42
|
"peerDependencies": {
|
@@ -45,13 +45,13 @@ class FetcherController {
|
|
45
45
|
});
|
46
46
|
|
47
47
|
const vouch = await res.json();
|
48
|
-
this.host.dispatchEvent(new CustomEvent('vouch:loaded', { detail:
|
48
|
+
this.host.dispatchEvent(new CustomEvent('vouch:loaded', { detail: vouch?.id }));
|
49
49
|
|
50
50
|
// HACK: we're currently using API Gateway caching on the embed API without any invalidation logic,
|
51
51
|
// so to ensure that the cache stays up to date, whenever we detect a cache hit we trigger another
|
52
52
|
// API call with the `Cache-Control` header which will re-fill the cache
|
53
53
|
const resCacheCheck = res?.headers?.get('X-Cache-Check');
|
54
|
-
if (resCacheCheck
|
54
|
+
if (resCacheCheck !== cacheCheck) {
|
55
55
|
fetch(`${embedApiUrl}/vouches/${vouchId}`, {
|
56
56
|
method: 'GET',
|
57
57
|
headers: [
|
@@ -81,7 +81,7 @@ class FetcherController {
|
|
81
81
|
// so to ensure that the cache stays up to date, whenever we detect a cache hit we trigger another
|
82
82
|
// API call with the `Cache-Control` header which will re-fill the cache
|
83
83
|
const resCacheCheck = res?.headers?.get('X-Cache-Check');
|
84
|
-
if (resCacheCheck
|
84
|
+
if (resCacheCheck !== cacheCheck) {
|
85
85
|
fetch(`${embedApiUrl}/templates/${templateId}`, {
|
86
86
|
method: 'GET',
|
87
87
|
headers: [
|
@@ -4,15 +4,16 @@ import type { Embed } from '..';
|
|
4
4
|
import type { VideoEventDetail } from '@vouchfor/media-player';
|
5
5
|
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
6
6
|
|
7
|
+
import packageJson from '../../../../package.json';
|
7
8
|
import { getEnvUrls } from '~/utils/env';
|
8
9
|
|
9
|
-
const
|
10
|
+
const MINIMUM_SEND_THRESHOLD = 1;
|
10
11
|
|
11
12
|
type EmbedHost = ReactiveControllerHost & Embed;
|
12
13
|
|
13
14
|
type TrackingEvent = 'VOUCH_LOADED' | 'VOUCH_RESPONSE_VIEWED' | 'VIDEO_PLAYED' | 'VIDEO_STREAMED';
|
14
15
|
type TrackingPayload = {
|
15
|
-
vouchId
|
16
|
+
vouchId?: string;
|
16
17
|
answerId?: string;
|
17
18
|
streamStart?: number;
|
18
19
|
streamEnd?: number;
|
@@ -36,21 +37,23 @@ class TrackingController implements ReactiveController {
|
|
36
37
|
private _hasPlayed = false;
|
37
38
|
private _hasLoaded: BooleanMap = {};
|
38
39
|
private _answersViewed: BooleanMap = {};
|
39
|
-
private
|
40
|
-
private
|
40
|
+
private _streamStartTime: TimeMap = {};
|
41
|
+
private _streamLatestTime: TimeMap = {};
|
42
|
+
private _currentlyPlayingVideo: VideoEventDetail | null = null;
|
41
43
|
|
42
44
|
constructor(host: EmbedHost) {
|
43
45
|
this.host = host;
|
44
46
|
host.addController(this);
|
45
47
|
}
|
46
48
|
|
47
|
-
private _findVouchId() {
|
49
|
+
private _findVouchId(payload?: TrackingPayload) {
|
50
|
+
if (payload && 'vouchId' in payload) {
|
51
|
+
return payload.vouchId;
|
52
|
+
}
|
48
53
|
if (this.host.vouch) {
|
49
|
-
if ('uuid' in this.host.vouch) {
|
50
|
-
return this.host.vouch.uuid;
|
51
|
-
}
|
52
54
|
return this.host.vouch.id;
|
53
55
|
}
|
56
|
+
return null;
|
54
57
|
}
|
55
58
|
|
56
59
|
private _createVisitor = (visitorId: string) => {
|
@@ -128,25 +131,53 @@ class TrackingController implements ReactiveController {
|
|
128
131
|
};
|
129
132
|
};
|
130
133
|
|
131
|
-
private _sendTrackingEvent = (event: TrackingEvent, payload
|
134
|
+
private _sendTrackingEvent = (event: TrackingEvent, payload?: TrackingPayload) => {
|
135
|
+
const vouchId = this._findVouchId(payload);
|
136
|
+
|
137
|
+
if (!vouchId || this.host.disableTracking) {
|
138
|
+
return;
|
139
|
+
}
|
140
|
+
|
132
141
|
const { publicApiUrl } = getEnvUrls(this.host.env);
|
133
142
|
const { client, tab, request, visitor } = this._getUids();
|
134
143
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
payload,
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
144
|
+
navigator.sendBeacon(
|
145
|
+
`${publicApiUrl}/api/v2/events`,
|
146
|
+
JSON.stringify({
|
147
|
+
event,
|
148
|
+
payload: {
|
149
|
+
...payload,
|
150
|
+
vouchId
|
151
|
+
},
|
152
|
+
context: {
|
153
|
+
'x-uid-client': client,
|
154
|
+
'x-uid-tab': tab,
|
155
|
+
'x-uid-request': request,
|
156
|
+
'x-uid-visitor': visitor,
|
157
|
+
'x-reporting-metadata': this._getReportingMetadata(),
|
158
|
+
'x-embeds-version': packageJson.version
|
159
|
+
}
|
160
|
+
})
|
161
|
+
);
|
162
|
+
};
|
163
|
+
|
164
|
+
private _streamEnded = () => {
|
165
|
+
if (this._currentlyPlayingVideo) {
|
166
|
+
const { id, key } = this._currentlyPlayingVideo;
|
167
|
+
// Don't send a tracking event when seeking backwards
|
168
|
+
if (this._streamLatestTime[key] > this._streamStartTime[key] + MINIMUM_SEND_THRESHOLD) {
|
169
|
+
// Send a video streamed event any time the stream ends to capture the time between starting
|
170
|
+
// the video and the video stopping for any reason (pausing, deleting the embed node or closing the browser)
|
171
|
+
this._sendTrackingEvent('VIDEO_STREAMED', {
|
172
|
+
answerId: id,
|
173
|
+
streamStart: this._streamStartTime[key],
|
174
|
+
streamEnd: this._streamLatestTime[key]
|
175
|
+
});
|
176
|
+
}
|
177
|
+
|
178
|
+
// Make sure these events are only sent once by deleting the start and latest times
|
179
|
+
delete this._streamStartTime[key];
|
180
|
+
delete this._streamLatestTime[key];
|
150
181
|
}
|
151
182
|
};
|
152
183
|
|
@@ -157,100 +188,84 @@ class TrackingController implements ReactiveController {
|
|
157
188
|
|
158
189
|
// Only send loaded event once per session
|
159
190
|
if (!this._hasLoaded[vouchId]) {
|
160
|
-
this._sendTrackingEvent('VOUCH_LOADED', {
|
161
|
-
vouchId
|
162
|
-
});
|
191
|
+
this._sendTrackingEvent('VOUCH_LOADED', { vouchId });
|
163
192
|
this._hasLoaded[vouchId] = true;
|
164
193
|
}
|
165
194
|
};
|
166
195
|
|
167
196
|
private _handlePlay = () => {
|
168
|
-
const vouchId = this._findVouchId();
|
169
|
-
|
170
|
-
if (!vouchId) {
|
171
|
-
return;
|
172
|
-
}
|
173
|
-
|
174
197
|
// Only send the video played event once per session
|
175
198
|
if (!this._hasPlayed) {
|
176
199
|
this._sendTrackingEvent('VIDEO_PLAYED', {
|
177
|
-
vouchId,
|
178
200
|
streamStart: this.host.currentTime
|
179
201
|
});
|
180
202
|
this._hasPlayed = true;
|
181
203
|
}
|
182
204
|
};
|
183
205
|
|
184
|
-
private _handleVideoPlay = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
|
185
|
-
const vouchId = this._findVouchId();
|
186
|
-
|
187
|
-
if (!vouchId) {
|
188
|
-
return;
|
189
|
-
}
|
206
|
+
private _handleVideoPlay = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
|
190
207
|
// Only increment play count once per session
|
191
|
-
if (!this._answersViewed[
|
208
|
+
if (!this._answersViewed[key]) {
|
192
209
|
this._sendTrackingEvent('VOUCH_RESPONSE_VIEWED', {
|
193
|
-
vouchId,
|
194
210
|
answerId: id
|
195
211
|
});
|
196
|
-
this._answersViewed[
|
212
|
+
this._answersViewed[key] = true;
|
197
213
|
}
|
198
|
-
this._streamedTime[id] = node.currentTime;
|
199
|
-
this._streamedPrevTimestamp[id] = Date.now();
|
200
|
-
};
|
201
|
-
|
202
|
-
private _handleVideoTimeUpdate = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
|
203
|
-
const vouchId = this._findVouchId();
|
204
214
|
|
205
|
-
if (!
|
206
|
-
|
215
|
+
if (!this._streamStartTime[key]) {
|
216
|
+
this._streamStartTime[key] = node.currentTime;
|
217
|
+
this._streamLatestTime[key] = node.currentTime;
|
207
218
|
}
|
208
|
-
|
219
|
+
};
|
220
|
+
|
221
|
+
private _handleVideoTimeUpdate = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
|
209
222
|
if (
|
210
|
-
|
211
|
-
!node.paused &&
|
223
|
+
// We only want to count any time that the video is actually playing
|
212
224
|
!this.host.paused &&
|
213
|
-
// Only
|
214
|
-
id === this.host.scene?.video?.id
|
215
|
-
// Throttle the frequency that we send streamed events while playing
|
216
|
-
currentTimestamp - this._streamedPrevTimestamp[id] > STREAMED_THROTTLE
|
225
|
+
// Only update the latest time if this event fires for the currently active video
|
226
|
+
id === this.host.scene?.video?.id
|
217
227
|
) {
|
228
|
+
this._currentlyPlayingVideo = { id, key, node };
|
229
|
+
this._streamLatestTime[key] = node.currentTime;
|
230
|
+
}
|
231
|
+
};
|
232
|
+
|
233
|
+
private _handleVideoPause = ({ detail: { id, key } }: CustomEvent<VideoEventDetail>) => {
|
234
|
+
if (this._streamLatestTime[key] > this._streamStartTime[key] + MINIMUM_SEND_THRESHOLD) {
|
218
235
|
this._sendTrackingEvent('VIDEO_STREAMED', {
|
219
|
-
vouchId,
|
220
236
|
answerId: id,
|
221
|
-
streamStart: this.
|
222
|
-
streamEnd:
|
237
|
+
streamStart: this._streamStartTime[key],
|
238
|
+
streamEnd: this._streamLatestTime[key]
|
223
239
|
});
|
224
|
-
this._streamedTime[id] = node.currentTime;
|
225
|
-
this._streamedPrevTimestamp[id] = currentTimestamp;
|
226
240
|
}
|
241
|
+
delete this._streamStartTime[key];
|
242
|
+
delete this._streamLatestTime[key];
|
227
243
|
};
|
228
244
|
|
229
|
-
private
|
230
|
-
|
245
|
+
private _pageUnloading = () => {
|
246
|
+
this._streamEnded();
|
247
|
+
// This will try to send the same stream event again so we delete the start and latest
|
248
|
+
// time in stream ended so that there is no times to send and the pause event does nothing
|
249
|
+
this.host.pause();
|
250
|
+
};
|
231
251
|
|
232
|
-
|
233
|
-
|
252
|
+
private _handleVisibilityChange = () => {
|
253
|
+
if (document.visibilityState === 'hidden') {
|
254
|
+
this._pageUnloading();
|
234
255
|
}
|
256
|
+
};
|
235
257
|
|
236
|
-
|
237
|
-
|
238
|
-
// Send a video streamed event any time the video pauses then reset the streamed state
|
239
|
-
// We do this to capture the last bit of time that the video was played between the previous
|
240
|
-
// stream event and the video being paused manually or stopping because it ended
|
241
|
-
this._sendTrackingEvent('VIDEO_STREAMED', {
|
242
|
-
vouchId,
|
243
|
-
answerId: id,
|
244
|
-
streamStart: this._streamedTime[id],
|
245
|
-
streamEnd: node.currentTime
|
246
|
-
});
|
247
|
-
}
|
248
|
-
delete this._streamedTime[id];
|
249
|
-
delete this._streamedPrevTimestamp[id];
|
258
|
+
private _handlePageHide = () => {
|
259
|
+
this._pageUnloading();
|
250
260
|
};
|
251
261
|
|
252
262
|
hostConnected() {
|
253
263
|
requestAnimationFrame(() => {
|
264
|
+
if ('onvisibilitychange' in document) {
|
265
|
+
document.addEventListener('visibilitychange', this._handleVisibilityChange);
|
266
|
+
} else {
|
267
|
+
window.addEventListener('pagehide', this._handlePageHide);
|
268
|
+
}
|
254
269
|
this.host.addEventListener('vouch:loaded', this._handleVouchLoaded);
|
255
270
|
this.host.mediaPlayer?.addEventListener('play', this._handlePlay);
|
256
271
|
this.host.mediaPlayer?.addEventListener('video:play', this._handleVideoPlay);
|
@@ -260,6 +275,12 @@ class TrackingController implements ReactiveController {
|
|
260
275
|
}
|
261
276
|
|
262
277
|
hostDisconnected() {
|
278
|
+
this._streamEnded();
|
279
|
+
if ('onvisibilitychange' in document) {
|
280
|
+
document.removeEventListener('visibilitychange', this._handleVisibilityChange);
|
281
|
+
} else {
|
282
|
+
window.removeEventListener('pagehide', this._handlePageHide);
|
283
|
+
}
|
263
284
|
this.host.removeEventListener('vouch:loaded', this._handleVouchLoaded);
|
264
285
|
this.host.mediaPlayer?.removeEventListener('play', this._handlePlay);
|
265
286
|
this.host.mediaPlayer?.removeEventListener('video:play', this._handleVideoPlay);
|
@@ -3,7 +3,7 @@ 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,7 +17,7 @@ import '@vouchfor/media-player';
|
|
17
17
|
type EmbedProps = Pick<MediaPlayerProps, 'data' | 'aspectRatio' | 'preload' | 'autoplay' | 'controls'> & {
|
18
18
|
env: Environment;
|
19
19
|
apiKey: string;
|
20
|
-
|
20
|
+
disableTracking?: boolean;
|
21
21
|
trackingSource?: string;
|
22
22
|
vouchId?: string;
|
23
23
|
templateId?: string;
|
@@ -33,7 +33,7 @@ class Embed extends LitElement {
|
|
33
33
|
|
34
34
|
@property({ type: String }) env: EmbedProps['env'] = 'prod';
|
35
35
|
@property({ type: String }) apiKey: EmbedProps['apiKey'] = '';
|
36
|
-
@property({ type: Boolean })
|
36
|
+
@property({ type: Boolean }) disableTracking: EmbedProps['disableTracking'] = false;
|
37
37
|
@property({ type: String }) trackingSource: EmbedProps['trackingSource'] = 'embed';
|
38
38
|
|
39
39
|
@property({ type: Array }) controls: EmbedProps['controls'];
|
@@ -59,6 +59,7 @@ class Embed extends LitElement {
|
|
59
59
|
'waiting',
|
60
60
|
|
61
61
|
'video:loadeddata',
|
62
|
+
'video:seeking',
|
62
63
|
'video:seeked',
|
63
64
|
'video:play',
|
64
65
|
'video:playing',
|
@@ -148,6 +149,10 @@ class Embed extends LitElement {
|
|
148
149
|
return this._mediaPlayerRef.value?.scenes ?? [];
|
149
150
|
}
|
150
151
|
|
152
|
+
get sceneConfig(): Scenes | null {
|
153
|
+
return this._mediaPlayerRef.value?.sceneConfig ?? null;
|
154
|
+
}
|
155
|
+
|
151
156
|
get videoState() {
|
152
157
|
return this._mediaPlayerRef.value?.videoState;
|
153
158
|
}
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|