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

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.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
  }