@vouchfor/embeds 0.0.0-experiment.279acaf → 0.0.0-experiment.2a3ebd2

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.279acaf",
3
+ "version": "0.0.0-experiment.2a3ebd2",
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.279acaf",
39
+ "@vouchfor/media-player": "0.0.0-experiment.2a3ebd2",
40
40
  "uuid": "^9.0.1"
41
41
  },
42
42
  "peerDependencies": {
@@ -45,7 +45,7 @@ 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
@@ -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;
@@ -140,21 +142,45 @@ class TrackingController implements ReactiveController {
140
142
  const { client, tab, request, visitor } = this._getUids();
141
143
 
142
144
  navigator.sendBeacon(
143
- `${publicApiUrl}/api/events`,
145
+ `${publicApiUrl}/api/v2/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
+
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
+
158
184
  private _handleVouchLoaded = ({ detail: vouchId }: CustomEvent<string>) => {
159
185
  if (!vouchId) {
160
186
  return;
@@ -162,7 +188,7 @@ class TrackingController implements ReactiveController {
162
188
 
163
189
  // Only send loaded event once per session
164
190
  if (!this._hasLoaded[vouchId]) {
165
- this._sendTrackingEvent('VOUCH_LOADED');
191
+ this._sendTrackingEvent('VOUCH_LOADED', { vouchId });
166
192
  this._hasLoaded[vouchId] = true;
167
193
  }
168
194
  };
@@ -186,54 +212,60 @@ class TrackingController implements ReactiveController {
186
212
  this._answersViewed[key] = true;
187
213
  }
188
214
 
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 };
215
+ if (!this._streamStartTime[key]) {
216
+ this._streamStartTime[key] = node.currentTime;
197
217
  this._streamLatestTime[key] = node.currentTime;
198
218
  }
219
+ };
199
220
 
221
+ private _handleVideoTimeUpdate = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
200
222
  if (
201
- !node.paused &&
223
+ // We only want to count any time that the video is actually playing
202
224
  !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
225
+ // Only update the latest time if this event fires for the currently active video
226
+ id === this.host.scene?.video?.id
207
227
  ) {
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;
228
+ this._currentlyPlayingVideo = { id, key, node };
229
+ this._streamLatestTime[key] = node.currentTime;
215
230
  }
216
231
  };
217
232
 
218
233
  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
234
+ if (this._streamLatestTime[key] > this._streamStartTime[key] + MINIMUM_SEND_THRESHOLD) {
224
235
  this._sendTrackingEvent('VIDEO_STREAMED', {
225
236
  answerId: id,
226
237
  streamStart: this._streamStartTime[key],
227
238
  streamEnd: this._streamLatestTime[key]
228
239
  });
229
240
  }
230
- this._currentlyPlayingVideo = null;
231
241
  delete this._streamStartTime[key];
232
242
  delete this._streamLatestTime[key];
233
243
  };
234
244
 
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
+ };
251
+
252
+ private _handleVisibilityChange = () => {
253
+ if (document.visibilityState === 'hidden') {
254
+ this._pageUnloading();
255
+ }
256
+ };
257
+
258
+ private _handlePageHide = () => {
259
+ this._pageUnloading();
260
+ };
261
+
235
262
  hostConnected() {
236
263
  requestAnimationFrame(() => {
264
+ if ('onvisibilitychange' in document) {
265
+ document.addEventListener('visibilitychange', this._handleVisibilityChange);
266
+ } else {
267
+ window.addEventListener('pagehide', this._handlePageHide);
268
+ }
237
269
  this.host.addEventListener('vouch:loaded', this._handleVouchLoaded);
238
270
  this.host.mediaPlayer?.addEventListener('play', this._handlePlay);
239
271
  this.host.mediaPlayer?.addEventListener('video:play', this._handleVideoPlay);
@@ -243,20 +275,12 @@ class TrackingController implements ReactiveController {
243
275
  }
244
276
 
245
277
  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
- }
278
+ this._streamEnded();
279
+ if ('onvisibilitychange' in document) {
280
+ document.removeEventListener('visibilitychange', this._handleVisibilityChange);
281
+ } else {
282
+ window.removeEventListener('pagehide', this._handlePageHide);
258
283
  }
259
-
260
284
  this.host.removeEventListener('vouch:loaded', this._handleVouchLoaded);
261
285
  this.host.mediaPlayer?.removeEventListener('play', this._handlePlay);
262
286
  this.host.mediaPlayer?.removeEventListener('video:play', this._handleVideoPlay);
File without changes
File without changes
File without changes