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

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/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