@vouchfor/embeds 0.0.0-experiment.853d689 → 0.0.0-experiment.896f0e4

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.853d689",
3
+ "version": "0.0.0-experiment.896f0e4",
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.853d689",
39
+ "@vouchfor/media-player": "0.0.0-experiment.896f0e4",
40
40
  "uuid": "^9.0.1"
41
41
  },
42
42
  "peerDependencies": {
@@ -51,7 +51,7 @@ class FetcherController {
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: [
@@ -1,4 +1,3 @@
1
- /* eslint-disable max-lines */
2
1
  import { v4 as uuidv4 } from 'uuid';
3
2
 
4
3
  import type { Embed } from '..';
@@ -7,13 +6,13 @@ import type { ReactiveController, ReactiveControllerHost } from 'lit';
7
6
 
8
7
  import { getEnvUrls } from '~/utils/env';
9
8
 
10
- const STREAMED_THROTTLE = 10000;
9
+ // In seconds due to checking against node.currentTime
10
+ const STREAMED_THROTTLE = 10;
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;
17
16
  answerId?: string;
18
17
  streamStart?: number;
19
18
  streamEnd?: number;
@@ -37,9 +36,9 @@ class TrackingController implements ReactiveController {
37
36
  private _hasPlayed = false;
38
37
  private _hasLoaded: BooleanMap = {};
39
38
  private _answersViewed: BooleanMap = {};
40
- private _streamedTime: TimeMap = {};
39
+ private _streamStartTime: TimeMap = {};
41
40
  private _streamLatestTime: TimeMap = {};
42
- private _streamedPrevTimestamp: TimeMap = {};
41
+ private _currentlyPlayingVideo: VideoEventDetail | null = null;
43
42
 
44
43
  constructor(host: EmbedHost) {
45
44
  this.host = host;
@@ -48,11 +47,9 @@ class TrackingController implements ReactiveController {
48
47
 
49
48
  private _findVouchId() {
50
49
  if (this.host.vouch) {
51
- if ('uuid' in this.host.vouch) {
52
- return this.host.vouch.uuid;
53
- }
54
50
  return this.host.vouch.id;
55
51
  }
52
+ return null;
56
53
  }
57
54
 
58
55
  private _createVisitor = (visitorId: string) => {
@@ -130,19 +127,24 @@ class TrackingController implements ReactiveController {
130
127
  };
131
128
  };
132
129
 
133
- private _sendTrackingEvent = (event: TrackingEvent, payload: TrackingPayload) => {
134
- const { publicApiUrl } = getEnvUrls(this.host.env);
135
- const { client, tab, request, visitor } = this._getUids();
130
+ private _sendTrackingEvent = (event: TrackingEvent, payload?: TrackingPayload) => {
131
+ const vouchId = this._findVouchId();
136
132
 
137
- if (this.host.disableTracking) {
133
+ if (!vouchId || this.host.disableTracking) {
138
134
  return;
139
135
  }
140
136
 
137
+ const { publicApiUrl } = getEnvUrls(this.host.env);
138
+ const { client, tab, request, visitor } = this._getUids();
139
+
141
140
  navigator.sendBeacon(
142
141
  `${publicApiUrl}/api/events`,
143
142
  JSON.stringify({
144
143
  event,
145
- payload,
144
+ payload: {
145
+ vouchId,
146
+ ...payload
147
+ },
146
148
  context: {
147
149
  'x-uid-client': client,
148
150
  'x-uid-tab': tab,
@@ -161,125 +163,74 @@ class TrackingController implements ReactiveController {
161
163
 
162
164
  // Only send loaded event once per session
163
165
  if (!this._hasLoaded[vouchId]) {
164
- this._sendTrackingEvent('VOUCH_LOADED', {
165
- vouchId
166
- });
166
+ this._sendTrackingEvent('VOUCH_LOADED');
167
167
  this._hasLoaded[vouchId] = true;
168
168
  }
169
169
  };
170
170
 
171
171
  private _handlePlay = () => {
172
- const vouchId = this._findVouchId();
173
-
174
- if (!vouchId) {
175
- return;
176
- }
177
-
178
172
  // Only send the video played event once per session
179
173
  if (!this._hasPlayed) {
180
174
  this._sendTrackingEvent('VIDEO_PLAYED', {
181
- vouchId,
182
175
  streamStart: this.host.currentTime
183
176
  });
184
177
  this._hasPlayed = true;
185
178
  }
186
179
  };
187
180
 
188
- private _handleVideoPlay = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
189
- const vouchId = this._findVouchId();
190
-
191
- if (!vouchId) {
192
- return;
193
- }
194
-
181
+ private _handleVideoPlay = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
195
182
  // Only increment play count once per session
196
- if (!this._answersViewed[id]) {
183
+ if (!this._answersViewed[key]) {
197
184
  this._sendTrackingEvent('VOUCH_RESPONSE_VIEWED', {
198
- vouchId,
199
185
  answerId: id
200
186
  });
201
- this._answersViewed[id] = true;
187
+ this._answersViewed[key] = true;
202
188
  }
203
189
 
204
- this._streamedTime[id] = node.currentTime;
205
- this._streamLatestTime[id] = node.currentTime;
206
- this._streamedPrevTimestamp[id] = Date.now();
190
+ this._streamStartTime[key] = node.currentTime;
191
+ this._streamLatestTime[key] = node.currentTime;
207
192
  };
208
193
 
209
- private _handleVideoSeeking = ({ detail: { id } }: CustomEvent<VideoEventDetail>) => {
210
- const vouchId = this._findVouchId();
211
-
212
- if (!vouchId) {
213
- return;
194
+ private _handleVideoTimeUpdate = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
195
+ // We only want to count any time that the video is actually playing
196
+ if (!this.host.paused) {
197
+ this._currentlyPlayingVideo = { id, key, node };
198
+ this._streamLatestTime[key] = node.currentTime;
214
199
  }
215
200
 
216
- if (this._streamLatestTime[id]) {
217
- this._sendTrackingEvent('VIDEO_STREAMED', {
218
- vouchId,
219
- answerId: id,
220
- streamStart: this._streamedTime[id],
221
- streamEnd: this._streamLatestTime[id]
222
- });
223
- }
224
-
225
- delete this._streamedTime[id];
226
- delete this._streamLatestTime[id];
227
- delete this._streamedPrevTimestamp[id];
228
- };
229
-
230
- private _handleVideoTimeUpdate = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
231
- const vouchId = this._findVouchId();
232
-
233
- if (!vouchId) {
234
- return;
235
- }
236
-
237
- const currentTimestamp = Date.now();
238
201
  if (
239
- node.currentTime &&
240
202
  !node.paused &&
241
203
  !this.host.paused &&
242
204
  // Only fire the video seeked event when this video is the active one
243
205
  id === this.host.scene?.video?.id &&
244
206
  // Throttle the frequency that we send streamed events while playing
245
- currentTimestamp - this._streamedPrevTimestamp[id] > STREAMED_THROTTLE
207
+ this._streamLatestTime[key] - this._streamStartTime[key] > STREAMED_THROTTLE
246
208
  ) {
247
209
  this._sendTrackingEvent('VIDEO_STREAMED', {
248
- vouchId,
249
210
  answerId: id,
250
- streamStart: this._streamedTime[id],
251
- streamEnd: node.currentTime
211
+ streamStart: this._streamStartTime[key],
212
+ streamEnd: this._streamLatestTime[key]
252
213
  });
253
- this._streamedTime[id] = node.currentTime;
254
- this._streamedPrevTimestamp[id] = currentTimestamp;
255
- }
256
-
257
- this._streamLatestTime[id] = node.currentTime;
258
- };
259
214
 
260
- private _handleVideoPause = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
261
- const vouchId = this._findVouchId();
262
-
263
- if (!vouchId) {
264
- return;
215
+ this._streamStartTime[key] = node.currentTime;
265
216
  }
217
+ };
266
218
 
267
- // Don't send a tracking event if the video pauses when seeking backwards
268
- if (node.currentTime > this._streamedTime[id]) {
219
+ private _handleVideoPause = ({ detail: { id, key } }: CustomEvent<VideoEventDetail>) => {
220
+ // Don't send a tracking event when seeking backwards
221
+ if (this._streamLatestTime[key] > this._streamStartTime[key]) {
269
222
  // Send a video streamed event any time the video pauses then reset the streamed state
270
223
  // We do this to capture the last bit of time that the video was played between the previous
271
224
  // stream event and the video being paused manually or stopping because it ended
272
225
  this._sendTrackingEvent('VIDEO_STREAMED', {
273
- vouchId,
274
226
  answerId: id,
275
- streamStart: this._streamedTime[id],
276
- streamEnd: node.currentTime
227
+ streamStart: this._streamStartTime[key],
228
+ streamEnd: this._streamLatestTime[key]
277
229
  });
278
230
  }
279
-
280
- delete this._streamedTime[id];
281
- delete this._streamLatestTime[id];
282
- delete this._streamedPrevTimestamp[id];
231
+ this._currentlyPlayingVideo = null;
232
+ delete this._streamStartTime[key];
233
+ delete this._streamLatestTime[key];
283
234
  };
284
235
 
285
236
  hostConnected() {
@@ -287,17 +238,29 @@ class TrackingController implements ReactiveController {
287
238
  this.host.addEventListener('vouch:loaded', this._handleVouchLoaded);
288
239
  this.host.mediaPlayer?.addEventListener('play', this._handlePlay);
289
240
  this.host.mediaPlayer?.addEventListener('video:play', this._handleVideoPlay);
290
- this.host.mediaPlayer?.addEventListener('video:seeking', this._handleVideoSeeking);
291
241
  this.host.mediaPlayer?.addEventListener('video:pause', this._handleVideoPause);
292
242
  this.host.mediaPlayer?.addEventListener('video:timeupdate', this._handleVideoTimeUpdate);
293
243
  });
294
244
  }
295
245
 
296
246
  hostDisconnected() {
247
+ if (this._currentlyPlayingVideo) {
248
+ const { id, key } = this._currentlyPlayingVideo;
249
+ if (this._streamLatestTime[key] > this._streamStartTime[key]) {
250
+ // Send a video streamed event any time the video pauses then reset the streamed state
251
+ // We do this to capture the last bit of time that the video was played between the previous
252
+ // stream event and the video being paused manually or stopping because it ended
253
+ this._sendTrackingEvent('VIDEO_STREAMED', {
254
+ answerId: id,
255
+ streamStart: this._streamStartTime[key],
256
+ streamEnd: this._streamLatestTime[key]
257
+ });
258
+ }
259
+ }
260
+
297
261
  this.host.removeEventListener('vouch:loaded', this._handleVouchLoaded);
298
262
  this.host.mediaPlayer?.removeEventListener('play', this._handlePlay);
299
263
  this.host.mediaPlayer?.removeEventListener('video:play', this._handleVideoPlay);
300
- this.host.mediaPlayer?.removeEventListener('video:seeking', this._handleVideoSeeking);
301
264
  this.host.mediaPlayer?.removeEventListener('video:pause', this._handleVideoPause);
302
265
  this.host.mediaPlayer?.removeEventListener('video:timeupdate', this._handleVideoTimeUpdate);
303
266
  }