@vouchfor/embeds 0.0.0-experiment.607fdcd → 0.0.0-experiment.6242786

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.607fdcd",
3
+ "version": "0.0.0-experiment.6242786",
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.607fdcd",
39
+ "@vouchfor/media-player": "0.0.0-experiment.6242786",
40
40
  "uuid": "^9.0.1"
41
41
  },
42
42
  "peerDependencies": {
@@ -22,6 +22,7 @@ const _Embed = ({ vouchId, templateId, preload, autoplay, env, apiKey, controls,
22
22
  ?autoplay=${autoplay}
23
23
  preload=${ifDefined(preload)}
24
24
  aspectRatio=${ifDefined(aspectRatio)}
25
+ @error=${console.log}
25
26
  ></vouch-embed>
26
27
  </div>
27
28
  `;
@@ -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,7 +46,10 @@ 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
54
  return this.host.vouch.id;
51
55
  }
@@ -128,7 +132,7 @@ class TrackingController implements ReactiveController {
128
132
  };
129
133
 
130
134
  private _sendTrackingEvent = (event: TrackingEvent, payload?: TrackingPayload) => {
131
- const vouchId = this._findVouchId();
135
+ const vouchId = this._findVouchId(payload);
132
136
 
133
137
  if (!vouchId || this.host.disableTracking) {
134
138
  return;
@@ -138,24 +142,45 @@ class TrackingController implements ReactiveController {
138
142
  const { client, tab, request, visitor } = this._getUids();
139
143
 
140
144
  navigator.sendBeacon(
141
- `${publicApiUrl}/api/events`,
145
+ `${publicApiUrl}/api/v2/events`,
142
146
  JSON.stringify({
143
147
  event,
144
148
  payload: {
145
- vouchId,
146
- ...payload
149
+ ...payload,
150
+ vouchId
147
151
  },
148
152
  context: {
149
153
  'x-uid-client': client,
150
154
  'x-uid-tab': tab,
151
155
  'x-uid-request': request,
152
156
  'x-uid-visitor': visitor,
153
- 'x-reporting-metadata': this._getReportingMetadata()
157
+ 'x-reporting-metadata': this._getReportingMetadata(),
158
+ 'x-embeds-version': packageJson.version
154
159
  }
155
160
  })
156
161
  );
157
162
  };
158
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
+
159
184
  private _handleVouchLoaded = ({ detail: vouchId }: CustomEvent<string>) => {
160
185
  if (!vouchId) {
161
186
  return;
@@ -163,7 +188,7 @@ class TrackingController implements ReactiveController {
163
188
 
164
189
  // Only send loaded event once per session
165
190
  if (!this._hasLoaded[vouchId]) {
166
- this._sendTrackingEvent('VOUCH_LOADED');
191
+ this._sendTrackingEvent('VOUCH_LOADED', { vouchId });
167
192
  this._hasLoaded[vouchId] = true;
168
193
  }
169
194
  };
@@ -187,54 +212,60 @@ class TrackingController implements ReactiveController {
187
212
  this._answersViewed[key] = true;
188
213
  }
189
214
 
190
- this._streamStartTime[key] = node.currentTime;
191
- this._streamLatestTime[key] = node.currentTime;
192
- };
193
-
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 };
215
+ if (!this._streamStartTime[key]) {
216
+ this._streamStartTime[key] = node.currentTime;
198
217
  this._streamLatestTime[key] = node.currentTime;
199
218
  }
219
+ };
200
220
 
221
+ private _handleVideoTimeUpdate = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
201
222
  if (
202
- !node.paused &&
223
+ // We only want to count any time that the video is actually playing
203
224
  !this.host.paused &&
204
- // Only fire the video seeked event when this video is the active one
205
- id === this.host.scene?.video?.id &&
206
- // Throttle the frequency that we send streamed events while playing
207
- 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
208
227
  ) {
209
- this._sendTrackingEvent('VIDEO_STREAMED', {
210
- answerId: id,
211
- streamStart: this._streamStartTime[key],
212
- streamEnd: this._streamLatestTime[key]
213
- });
214
-
215
- this._streamStartTime[key] = node.currentTime;
228
+ this._currentlyPlayingVideo = { id, key, node };
229
+ this._streamLatestTime[key] = node.currentTime;
216
230
  }
217
231
  };
218
232
 
219
233
  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]) {
222
- // Send a video streamed event any time the video pauses then reset the streamed state
223
- // We do this to capture the last bit of time that the video was played between the previous
224
- // stream event and the video being paused manually or stopping because it ended
234
+ if (this._streamLatestTime[key] > this._streamStartTime[key] + MINIMUM_SEND_THRESHOLD) {
225
235
  this._sendTrackingEvent('VIDEO_STREAMED', {
226
236
  answerId: id,
227
237
  streamStart: this._streamStartTime[key],
228
238
  streamEnd: this._streamLatestTime[key]
229
239
  });
230
240
  }
231
- this._currentlyPlayingVideo = null;
232
241
  delete this._streamStartTime[key];
233
242
  delete this._streamLatestTime[key];
234
243
  };
235
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
+
236
262
  hostConnected() {
237
263
  requestAnimationFrame(() => {
264
+ if ('onvisibilitychange' in document) {
265
+ document.addEventListener('visibilitychange', this._handleVisibilityChange);
266
+ } else {
267
+ window.addEventListener('pagehide', this._handlePageHide);
268
+ }
238
269
  this.host.addEventListener('vouch:loaded', this._handleVouchLoaded);
239
270
  this.host.mediaPlayer?.addEventListener('play', this._handlePlay);
240
271
  this.host.mediaPlayer?.addEventListener('video:play', this._handleVideoPlay);
@@ -244,20 +275,12 @@ class TrackingController implements ReactiveController {
244
275
  }
245
276
 
246
277
  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
- }
278
+ this._streamEnded();
279
+ if ('onvisibilitychange' in document) {
280
+ document.removeEventListener('visibilitychange', this._handleVisibilityChange);
281
+ } else {
282
+ window.removeEventListener('pagehide', this._handlePageHide);
259
283
  }
260
-
261
284
  this.host.removeEventListener('vouch:loaded', this._handleVouchLoaded);
262
285
  this.host.mediaPlayer?.removeEventListener('play', this._handlePlay);
263
286
  this.host.mediaPlayer?.removeEventListener('video:play', this._handleVideoPlay);
@@ -3,7 +3,7 @@ import { customElement, property, state } from 'lit/decorators.js';
3
3
  import { ifDefined } from 'lit/directives/if-defined.js';
4
4
  import { createRef, ref } from 'lit/directives/ref.js';
5
5
 
6
- import type { Scene, TemplateInstance } from '@vouchfor/canvas-video';
6
+ import type { Scene, Scenes, TemplateInstance } from '@vouchfor/canvas-video';
7
7
  import type { MediaPlayer, MediaPlayerProps } from '@vouchfor/media-player';
8
8
  import type { Ref } from 'lit/directives/ref.js';
9
9
  import type { Environment } from '~/utils/env';
@@ -149,6 +149,10 @@ class Embed extends LitElement {
149
149
  return this._mediaPlayerRef.value?.scenes ?? [];
150
150
  }
151
151
 
152
+ get sceneConfig(): Scenes | null {
153
+ return this._mediaPlayerRef.value?.sceneConfig ?? null;
154
+ }
155
+
152
156
  get videoState() {
153
157
  return this._mediaPlayerRef.value?.videoState;
154
158
  }
File without changes
File without changes
File without changes