@vouchfor/embeds 0.0.0-experiment.cbb21a3 → 0.0.0-experiment.d892f46

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vouchfor/embeds",
3
- "version": "0.0.0-experiment.cbb21a3",
3
+ "version": "0.0.0-experiment.d892f46",
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.cbb21a3",
39
+ "@vouchfor/media-player": "0.0.0-experiment.d892f46",
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: vouchId }));
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 && resCacheCheck !== cacheCheck) {
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 && resCacheCheck !== cacheCheck) {
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
- // In seconds due to checking against node.currentTime
10
- const STREAMED_THROTTLE = 10;
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?: string;
16
17
  answerId?: string;
17
18
  streamStart?: number;
18
19
  streamEnd?: number;
@@ -45,13 +46,14 @@ class TrackingController implements ReactiveController {
45
46
  host.addController(this);
46
47
  }
47
48
 
48
- private _findVouchId() {
49
+ private _findVouchId(payload?: TrackingPayload) {
50
+ if (payload && 'vouchId' in payload) {
51
+ return payload.vouchId;
52
+ }
49
53
  if (this.host.vouch) {
50
- if ('uuid' in this.host.vouch) {
51
- return this.host.vouch.uuid;
52
- }
53
54
  return this.host.vouch.id;
54
55
  }
56
+ return null;
55
57
  }
56
58
 
57
59
  private _createVisitor = (visitorId: string) => {
@@ -130,7 +132,7 @@ class TrackingController implements ReactiveController {
130
132
  };
131
133
 
132
134
  private _sendTrackingEvent = (event: TrackingEvent, payload?: TrackingPayload) => {
133
- const vouchId = this._findVouchId();
135
+ const vouchId = this._findVouchId(payload);
134
136
 
135
137
  if (!vouchId || this.host.disableTracking) {
136
138
  return;
@@ -143,18 +145,40 @@ class TrackingController implements ReactiveController {
143
145
  `${publicApiUrl}/api/events`,
144
146
  JSON.stringify({
145
147
  event,
146
- payload,
148
+ payload: {
149
+ ...payload,
150
+ vouchId
151
+ },
147
152
  context: {
148
153
  'x-uid-client': client,
149
154
  'x-uid-tab': tab,
150
155
  'x-uid-request': request,
151
156
  'x-uid-visitor': visitor,
152
- 'x-reporting-metadata': this._getReportingMetadata()
157
+ 'x-reporting-metadata': this._getReportingMetadata(),
158
+ 'x-embeds-version': packageJson.version
153
159
  }
154
160
  })
155
161
  );
156
162
  };
157
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
+ delete this._streamStartTime[key];
178
+ delete this._streamLatestTime[key];
179
+ }
180
+ };
181
+
158
182
  private _handleVouchLoaded = ({ detail: vouchId }: CustomEvent<string>) => {
159
183
  if (!vouchId) {
160
184
  return;
@@ -162,7 +186,7 @@ class TrackingController implements ReactiveController {
162
186
 
163
187
  // Only send loaded event once per session
164
188
  if (!this._hasLoaded[vouchId]) {
165
- this._sendTrackingEvent('VOUCH_LOADED');
189
+ this._sendTrackingEvent('VOUCH_LOADED', { vouchId });
166
190
  this._hasLoaded[vouchId] = true;
167
191
  }
168
192
  };
@@ -186,54 +210,46 @@ class TrackingController implements ReactiveController {
186
210
  this._answersViewed[key] = true;
187
211
  }
188
212
 
189
- this._streamStartTime[key] = node.currentTime;
190
- this._streamLatestTime[key] = node.currentTime;
191
- };
192
-
193
- private _handleVideoTimeUpdate = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
194
- // We only want to count any time that the video is actually playing
195
- if (!this.host.paused) {
196
- this._currentlyPlayingVideo = { id, key, node };
213
+ if (!this._streamStartTime[key]) {
214
+ this._streamStartTime[key] = node.currentTime;
197
215
  this._streamLatestTime[key] = node.currentTime;
198
216
  }
217
+ };
199
218
 
219
+ private _handleVideoTimeUpdate = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
200
220
  if (
201
- !node.paused &&
221
+ // We only want to count any time that the video is actually playing
202
222
  !this.host.paused &&
203
- // Only fire the video seeked event when this video is the active one
204
- id === this.host.scene?.video?.id &&
205
- // Throttle the frequency that we send streamed events while playing
206
- this._streamLatestTime[key] - this._streamStartTime[key] > STREAMED_THROTTLE
223
+ // Only update the latest time if this event fires for the currently active video
224
+ id === this.host.scene?.video?.id
207
225
  ) {
208
- this._sendTrackingEvent('VIDEO_STREAMED', {
209
- answerId: id,
210
- streamStart: this._streamStartTime[key],
211
- streamEnd: this._streamLatestTime[key]
212
- });
213
-
214
- this._streamStartTime[key] = node.currentTime;
226
+ this._currentlyPlayingVideo = { id, key, node };
227
+ this._streamLatestTime[key] = node.currentTime;
215
228
  }
216
229
  };
217
230
 
218
231
  private _handleVideoPause = ({ detail: { id, key } }: CustomEvent<VideoEventDetail>) => {
219
- // Don't send a tracking event when seeking backwards
220
- if (this._streamLatestTime[key] > this._streamStartTime[key]) {
221
- // Send a video streamed event any time the video pauses then reset the streamed state
222
- // We do this to capture the last bit of time that the video was played between the previous
223
- // stream event and the video being paused manually or stopping because it ended
232
+ if (this._streamLatestTime[key] > this._streamStartTime[key] + MINIMUM_SEND_THRESHOLD) {
224
233
  this._sendTrackingEvent('VIDEO_STREAMED', {
225
234
  answerId: id,
226
235
  streamStart: this._streamStartTime[key],
227
236
  streamEnd: this._streamLatestTime[key]
228
237
  });
229
238
  }
230
- this._currentlyPlayingVideo = null;
231
239
  delete this._streamStartTime[key];
232
240
  delete this._streamLatestTime[key];
233
241
  };
234
242
 
243
+ private _handleVisibilityChange = () => {
244
+ if (document.visibilityState === 'hidden') {
245
+ this.host.pause();
246
+ this._streamEnded();
247
+ }
248
+ };
249
+
235
250
  hostConnected() {
236
251
  requestAnimationFrame(() => {
252
+ document.addEventListener('visibilitychange', this._handleVisibilityChange);
237
253
  this.host.addEventListener('vouch:loaded', this._handleVouchLoaded);
238
254
  this.host.mediaPlayer?.addEventListener('play', this._handlePlay);
239
255
  this.host.mediaPlayer?.addEventListener('video:play', this._handleVideoPlay);
@@ -243,20 +259,8 @@ class TrackingController implements ReactiveController {
243
259
  }
244
260
 
245
261
  hostDisconnected() {
246
- if (this._currentlyPlayingVideo) {
247
- const { id, key } = this._currentlyPlayingVideo;
248
- if (this._streamLatestTime[key] > this._streamStartTime[key]) {
249
- // Send a video streamed event any time the video pauses then reset the streamed state
250
- // We do this to capture the last bit of time that the video was played between the previous
251
- // stream event and the video being paused manually or stopping because it ended
252
- this._sendTrackingEvent('VIDEO_STREAMED', {
253
- answerId: id,
254
- streamStart: this._streamStartTime[key],
255
- streamEnd: this._streamLatestTime[key]
256
- });
257
- }
258
- }
259
-
262
+ this._streamEnded();
263
+ document.removeEventListener('visibilitychange', this._handleVisibilityChange);
260
264
  this.host.removeEventListener('vouch:loaded', this._handleVouchLoaded);
261
265
  this.host.mediaPlayer?.removeEventListener('play', this._handlePlay);
262
266
  this.host.mediaPlayer?.removeEventListener('video:play', this._handleVideoPlay);
File without changes
File without changes
File without changes