@vouchfor/embeds 0.0.0-experiment.b7c103f → 0.0.0-experiment.bb5f326
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 +384 -309
- package/dist/es/embeds.js.map +1 -1
- package/dist/es/{components → src/components}/Embed/controllers/tracking.d.ts +6 -3
- package/dist/es/{components → src/components}/Embed/index.d.ts +2 -1
- package/dist/iife/embeds.iife.js +128 -125
- 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 +84 -98
- package/src/components/Embed/index.ts +5 -1
- /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.bb5f326",
|
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.bb5f326",
|
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: [
|
@@ -1,19 +1,19 @@
|
|
1
|
-
/* eslint-disable max-lines */
|
2
1
|
import { v4 as uuidv4 } from 'uuid';
|
3
2
|
|
4
3
|
import type { Embed } from '..';
|
5
4
|
import type { VideoEventDetail } from '@vouchfor/media-player';
|
6
5
|
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
7
6
|
|
7
|
+
import packageJson from '../../../../package.json';
|
8
8
|
import { getEnvUrls } from '~/utils/env';
|
9
9
|
|
10
|
-
const
|
10
|
+
const MINIMUM_SEND_THRESHOLD = 1;
|
11
11
|
|
12
12
|
type EmbedHost = ReactiveControllerHost & Embed;
|
13
13
|
|
14
14
|
type TrackingEvent = 'VOUCH_LOADED' | 'VOUCH_RESPONSE_VIEWED' | 'VIDEO_PLAYED' | 'VIDEO_STREAMED';
|
15
15
|
type TrackingPayload = {
|
16
|
-
vouchId
|
16
|
+
vouchId?: string;
|
17
17
|
answerId?: string;
|
18
18
|
streamStart?: number;
|
19
19
|
streamEnd?: number;
|
@@ -37,22 +37,23 @@ class TrackingController implements ReactiveController {
|
|
37
37
|
private _hasPlayed = false;
|
38
38
|
private _hasLoaded: BooleanMap = {};
|
39
39
|
private _answersViewed: BooleanMap = {};
|
40
|
-
private
|
40
|
+
private _streamStartTime: TimeMap = {};
|
41
41
|
private _streamLatestTime: TimeMap = {};
|
42
|
-
private
|
42
|
+
private _currentlyPlayingVideo: VideoEventDetail | null = null;
|
43
43
|
|
44
44
|
constructor(host: EmbedHost) {
|
45
45
|
this.host = host;
|
46
46
|
host.addController(this);
|
47
47
|
}
|
48
48
|
|
49
|
-
private _findVouchId() {
|
49
|
+
private _findVouchId(payload?: TrackingPayload) {
|
50
|
+
if (payload && 'vouchId' in payload) {
|
51
|
+
return payload.vouchId;
|
52
|
+
}
|
50
53
|
if (this.host.vouch) {
|
51
|
-
if ('uuid' in this.host.vouch) {
|
52
|
-
return this.host.vouch.uuid;
|
53
|
-
}
|
54
54
|
return this.host.vouch.id;
|
55
55
|
}
|
56
|
+
return null;
|
56
57
|
}
|
57
58
|
|
58
59
|
private _createVisitor = (visitorId: string) => {
|
@@ -130,30 +131,56 @@ class TrackingController implements ReactiveController {
|
|
130
131
|
};
|
131
132
|
};
|
132
133
|
|
133
|
-
private _sendTrackingEvent = (event: TrackingEvent, payload
|
134
|
-
const
|
135
|
-
const { client, tab, request, visitor } = this._getUids();
|
134
|
+
private _sendTrackingEvent = (event: TrackingEvent, payload?: TrackingPayload) => {
|
135
|
+
const vouchId = this._findVouchId(payload);
|
136
136
|
|
137
|
-
if (this.host.disableTracking) {
|
137
|
+
if (!vouchId || this.host.disableTracking) {
|
138
138
|
return;
|
139
139
|
}
|
140
140
|
|
141
|
+
const { publicApiUrl } = getEnvUrls(this.host.env);
|
142
|
+
const { client, tab, request, visitor } = this._getUids();
|
143
|
+
|
141
144
|
navigator.sendBeacon(
|
142
|
-
`${publicApiUrl}/api/events`,
|
145
|
+
`${publicApiUrl}/api/v2/events`,
|
143
146
|
JSON.stringify({
|
144
147
|
event,
|
145
|
-
payload
|
148
|
+
payload: {
|
149
|
+
...payload,
|
150
|
+
vouchId
|
151
|
+
},
|
146
152
|
context: {
|
147
153
|
'x-uid-client': client,
|
148
154
|
'x-uid-tab': tab,
|
149
155
|
'x-uid-request': request,
|
150
156
|
'x-uid-visitor': visitor,
|
151
|
-
'x-reporting-metadata': this._getReportingMetadata()
|
157
|
+
'x-reporting-metadata': this._getReportingMetadata(),
|
158
|
+
'x-embeds-version': packageJson.version
|
152
159
|
}
|
153
160
|
})
|
154
161
|
);
|
155
162
|
};
|
156
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];
|
181
|
+
}
|
182
|
+
};
|
183
|
+
|
157
184
|
private _handleVouchLoaded = ({ detail: vouchId }: CustomEvent<string>) => {
|
158
185
|
if (!vouchId) {
|
159
186
|
return;
|
@@ -161,24 +188,15 @@ class TrackingController implements ReactiveController {
|
|
161
188
|
|
162
189
|
// Only send loaded event once per session
|
163
190
|
if (!this._hasLoaded[vouchId]) {
|
164
|
-
this._sendTrackingEvent('VOUCH_LOADED', {
|
165
|
-
vouchId
|
166
|
-
});
|
191
|
+
this._sendTrackingEvent('VOUCH_LOADED', { vouchId });
|
167
192
|
this._hasLoaded[vouchId] = true;
|
168
193
|
}
|
169
194
|
};
|
170
195
|
|
171
196
|
private _handlePlay = () => {
|
172
|
-
const vouchId = this._findVouchId();
|
173
|
-
|
174
|
-
if (!vouchId) {
|
175
|
-
return;
|
176
|
-
}
|
177
|
-
|
178
197
|
// Only send the video played event once per session
|
179
198
|
if (!this._hasPlayed) {
|
180
199
|
this._sendTrackingEvent('VIDEO_PLAYED', {
|
181
|
-
vouchId,
|
182
200
|
streamStart: this.host.currentTime
|
183
201
|
});
|
184
202
|
this._hasPlayed = true;
|
@@ -186,118 +204,86 @@ class TrackingController implements ReactiveController {
|
|
186
204
|
};
|
187
205
|
|
188
206
|
private _handleVideoPlay = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
|
189
|
-
const vouchId = this._findVouchId();
|
190
|
-
|
191
|
-
if (!vouchId) {
|
192
|
-
return;
|
193
|
-
}
|
194
|
-
|
195
207
|
// Only increment play count once per session
|
196
208
|
if (!this._answersViewed[key]) {
|
197
209
|
this._sendTrackingEvent('VOUCH_RESPONSE_VIEWED', {
|
198
|
-
vouchId,
|
199
210
|
answerId: id
|
200
211
|
});
|
201
212
|
this._answersViewed[key] = true;
|
202
213
|
}
|
203
214
|
|
204
|
-
this.
|
205
|
-
|
206
|
-
|
207
|
-
};
|
208
|
-
|
209
|
-
private _handleVideoSeeking = ({ detail: { id, key } }: CustomEvent<VideoEventDetail>) => {
|
210
|
-
const vouchId = this._findVouchId();
|
211
|
-
|
212
|
-
if (!vouchId) {
|
213
|
-
return;
|
215
|
+
if (!this._streamStartTime[key]) {
|
216
|
+
this._streamStartTime[key] = node.currentTime;
|
217
|
+
this._streamLatestTime[key] = node.currentTime;
|
214
218
|
}
|
215
|
-
|
216
|
-
if (this._streamLatestTime[key]) {
|
217
|
-
this._sendTrackingEvent('VIDEO_STREAMED', {
|
218
|
-
vouchId,
|
219
|
-
answerId: id,
|
220
|
-
streamStart: this._streamedTime[key],
|
221
|
-
streamEnd: this._streamLatestTime[key]
|
222
|
-
});
|
223
|
-
}
|
224
|
-
|
225
|
-
delete this._streamedTime[key];
|
226
|
-
delete this._streamLatestTime[key];
|
227
|
-
delete this._streamedPrevTimestamp[key];
|
228
219
|
};
|
229
220
|
|
230
221
|
private _handleVideoTimeUpdate = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
|
231
|
-
const vouchId = this._findVouchId();
|
232
|
-
|
233
|
-
if (!vouchId) {
|
234
|
-
return;
|
235
|
-
}
|
236
|
-
|
237
|
-
const currentTimestamp = Date.now();
|
238
222
|
if (
|
239
|
-
|
240
|
-
!node.paused &&
|
223
|
+
// We only want to count any time that the video is actually playing
|
241
224
|
!this.host.paused &&
|
242
|
-
// Only
|
243
|
-
id === this.host.scene?.video?.id
|
244
|
-
// Throttle the frequency that we send streamed events while playing
|
245
|
-
currentTimestamp - this._streamedPrevTimestamp[key] > STREAMED_THROTTLE
|
225
|
+
// Only update the latest time if this event fires for the currently active video
|
226
|
+
id === this.host.scene?.video?.id
|
246
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) {
|
247
235
|
this._sendTrackingEvent('VIDEO_STREAMED', {
|
248
|
-
vouchId,
|
249
236
|
answerId: id,
|
250
|
-
streamStart: this.
|
251
|
-
streamEnd:
|
237
|
+
streamStart: this._streamStartTime[key],
|
238
|
+
streamEnd: this._streamLatestTime[key]
|
252
239
|
});
|
253
|
-
this._streamedTime[key] = node.currentTime;
|
254
|
-
this._streamedPrevTimestamp[key] = currentTimestamp;
|
255
240
|
}
|
256
|
-
|
257
|
-
this._streamLatestTime[key]
|
241
|
+
delete this._streamStartTime[key];
|
242
|
+
delete this._streamLatestTime[key];
|
258
243
|
};
|
259
244
|
|
260
|
-
private
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
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
|
+
};
|
266
251
|
|
267
|
-
|
268
|
-
if (
|
269
|
-
|
270
|
-
// We do this to capture the last bit of time that the video was played between the previous
|
271
|
-
// stream event and the video being paused manually or stopping because it ended
|
272
|
-
this._sendTrackingEvent('VIDEO_STREAMED', {
|
273
|
-
vouchId,
|
274
|
-
answerId: id,
|
275
|
-
streamStart: this._streamedTime[key],
|
276
|
-
streamEnd: node.currentTime
|
277
|
-
});
|
252
|
+
private _handleVisibilityChange = () => {
|
253
|
+
if (document.visibilityState === 'hidden') {
|
254
|
+
this._pageUnloading();
|
278
255
|
}
|
256
|
+
};
|
279
257
|
|
280
|
-
|
281
|
-
|
282
|
-
delete this._streamedPrevTimestamp[key];
|
258
|
+
private _handlePageHide = () => {
|
259
|
+
this._pageUnloading();
|
283
260
|
};
|
284
261
|
|
285
262
|
hostConnected() {
|
286
263
|
requestAnimationFrame(() => {
|
264
|
+
if ('onvisibilitychange' in document) {
|
265
|
+
document.addEventListener('visibilitychange', this._handleVisibilityChange);
|
266
|
+
} else {
|
267
|
+
window.addEventListener('pagehide', this._handlePageHide);
|
268
|
+
}
|
287
269
|
this.host.addEventListener('vouch:loaded', this._handleVouchLoaded);
|
288
270
|
this.host.mediaPlayer?.addEventListener('play', this._handlePlay);
|
289
271
|
this.host.mediaPlayer?.addEventListener('video:play', this._handleVideoPlay);
|
290
|
-
this.host.mediaPlayer?.addEventListener('video:seeking', this._handleVideoSeeking);
|
291
272
|
this.host.mediaPlayer?.addEventListener('video:pause', this._handleVideoPause);
|
292
273
|
this.host.mediaPlayer?.addEventListener('video:timeupdate', this._handleVideoTimeUpdate);
|
293
274
|
});
|
294
275
|
}
|
295
276
|
|
296
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
|
+
}
|
297
284
|
this.host.removeEventListener('vouch:loaded', this._handleVouchLoaded);
|
298
285
|
this.host.mediaPlayer?.removeEventListener('play', this._handlePlay);
|
299
286
|
this.host.mediaPlayer?.removeEventListener('video:play', this._handleVideoPlay);
|
300
|
-
this.host.mediaPlayer?.removeEventListener('video:seeking', this._handleVideoSeeking);
|
301
287
|
this.host.mediaPlayer?.removeEventListener('video:pause', this._handleVideoPause);
|
302
288
|
this.host.mediaPlayer?.removeEventListener('video:timeupdate', this._handleVideoTimeUpdate);
|
303
289
|
}
|
@@ -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';
|
@@ -149,6 +149,10 @@ class Embed extends LitElement {
|
|
149
149
|
return this._mediaPlayerRef.value?.scenes ?? [];
|
150
150
|
}
|
151
151
|
|
152
|
+
get sceneConfig(): Scenes | null {
|
153
|
+
return this._mediaPlayerRef.value?.sceneConfig ?? null;
|
154
|
+
}
|
155
|
+
|
152
156
|
get videoState() {
|
153
157
|
return this._mediaPlayerRef.value?.videoState;
|
154
158
|
}
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|