@vouchfor/embeds 0.0.0-experiment.88ebbc0 → 0.0.0-experiment.8a05fac
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/dist/es/components/Embed/controllers/tracking.d.ts +1 -1
- package/dist/es/components/Embed/index.d.ts +5 -1
- package/dist/es/embeds.js +880 -587
- package/dist/es/embeds.js.map +1 -1
- package/dist/es/utils/env.d.ts +2 -8
- package/dist/iife/embeds.iife.js +225 -213
- package/dist/iife/embeds.iife.js.map +1 -1
- package/package.json +4 -4
- package/src/components/Embed/Embed.stories.ts +3 -3
- package/src/components/Embed/controllers/fetcher.ts +55 -10
- package/src/components/Embed/controllers/tracking.ts +51 -31
- package/src/components/Embed/index.ts +5 -0
- package/src/utils/env.ts +15 -29
- package/dist/es/components/Embed/controllers/event-forwarder.d.ts +0 -14
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@vouchfor/embeds",
|
3
|
-
"version": "0.0.0-experiment.
|
3
|
+
"version": "0.0.0-experiment.8a05fac",
|
4
4
|
"license": "MIT",
|
5
5
|
"author": "Aaron Williams",
|
6
6
|
"main": "dist/es/embeds.js",
|
@@ -36,11 +36,11 @@
|
|
36
36
|
},
|
37
37
|
"dependencies": {
|
38
38
|
"@lit/task": "^1.0.0",
|
39
|
-
"@vouchfor/media-player": "0.0.0-experiment.
|
39
|
+
"@vouchfor/media-player": "0.0.0-experiment.8a05fac",
|
40
40
|
"uuid": "^9.0.1"
|
41
41
|
},
|
42
42
|
"peerDependencies": {
|
43
|
-
"lit": "^3.0
|
43
|
+
"lit": "^3.1.0"
|
44
44
|
},
|
45
45
|
"devDependencies": {
|
46
46
|
"@esm-bundle/chai": "^4.3.4-fix.0",
|
@@ -62,7 +62,7 @@
|
|
62
62
|
"eslint": "^8.50.0",
|
63
63
|
"eslint-plugin-import": "^2.28.1",
|
64
64
|
"lint-staged": "^14.0.1",
|
65
|
-
"lit": "^
|
65
|
+
"lit": "^3.1.0",
|
66
66
|
"prettier": "^3.0.3",
|
67
67
|
"react": "^18.2.0",
|
68
68
|
"react-dom": "^18.2.0",
|
@@ -39,10 +39,10 @@ type Story = StoryObj<EmbedArgs>;
|
|
39
39
|
|
40
40
|
const Embed: Story = {
|
41
41
|
args: {
|
42
|
-
env: '
|
42
|
+
env: 'local',
|
43
43
|
apiKey: 'TVik9uTMgE-PD25UTHIS6gyl0hMBWC7AT4dkpdlLBT4VIfDWZJrQiCk6Ak7m1',
|
44
44
|
vouchId: '6JQEIPeStt',
|
45
|
-
templateId: '
|
45
|
+
templateId: '357fc118-e179-4171-9446-ff2b8e9d1b29',
|
46
46
|
aspectRatio: 0,
|
47
47
|
preload: 'none',
|
48
48
|
autoplay: false
|
@@ -50,7 +50,7 @@ const Embed: Story = {
|
|
50
50
|
argTypes: {
|
51
51
|
env: {
|
52
52
|
control: 'radio',
|
53
|
-
options: ['
|
53
|
+
options: ['local', 'dev', 'staging', 'prod']
|
54
54
|
},
|
55
55
|
preload: {
|
56
56
|
control: 'radio',
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { Task } from '@lit/task';
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
2
3
|
|
3
4
|
import type { Embed, EmbedProps } from '..';
|
4
5
|
import type { ReactiveControllerHost } from 'lit';
|
@@ -31,23 +32,67 @@ class FetcherController {
|
|
31
32
|
return this._fetching;
|
32
33
|
}
|
33
34
|
|
34
|
-
private async
|
35
|
+
private getVouch = async (env: Environment, apiKey: string, vouchId: string) => {
|
35
36
|
const { embedApiUrl } = getEnvUrls(env);
|
36
37
|
|
37
|
-
|
38
|
+
const cacheCheck = uuidv4();
|
39
|
+
const res = await fetch(`${embedApiUrl}/vouches/${vouchId}`, {
|
38
40
|
method: 'GET',
|
39
|
-
headers: [
|
40
|
-
|
41
|
-
|
41
|
+
headers: [
|
42
|
+
['X-Api-Key', apiKey],
|
43
|
+
['X-Cache-Check', cacheCheck]
|
44
|
+
]
|
45
|
+
});
|
46
|
+
|
47
|
+
const vouch = await res.json();
|
48
|
+
this.host.dispatchEvent(new CustomEvent('vouch:loaded', { detail: vouchId }));
|
49
|
+
|
50
|
+
// HACK: we're currently using API Gateway caching on the embed API without any invalidation logic,
|
51
|
+
// so to ensure that the cache stays up to date, whenever we detect a cache hit we trigger another
|
52
|
+
// API call with the `Cache-Control` header which will re-fill the cache
|
53
|
+
const resCacheCheck = res?.headers?.get('X-Cache-Check');
|
54
|
+
if (resCacheCheck && resCacheCheck !== cacheCheck) {
|
55
|
+
fetch(`${embedApiUrl}/vouches/${vouchId}`, {
|
56
|
+
method: 'GET',
|
57
|
+
headers: [
|
58
|
+
['X-Api-Key', apiKey],
|
59
|
+
['Cache-Control', 'max-age=0']
|
60
|
+
]
|
61
|
+
});
|
62
|
+
}
|
63
|
+
|
64
|
+
return vouch;
|
65
|
+
};
|
42
66
|
|
43
|
-
private async
|
67
|
+
private getTemplate = async (env: Environment, apiKey: string, templateId: string) => {
|
44
68
|
const { embedApiUrl } = getEnvUrls(env);
|
45
69
|
|
46
|
-
|
70
|
+
const cacheCheck = uuidv4();
|
71
|
+
const res = await fetch(`${embedApiUrl}/templates/${templateId}`, {
|
47
72
|
method: 'GET',
|
48
|
-
headers: [
|
49
|
-
|
50
|
-
|
73
|
+
headers: [
|
74
|
+
['X-Api-Key', apiKey],
|
75
|
+
['X-Cache-Check', cacheCheck]
|
76
|
+
]
|
77
|
+
});
|
78
|
+
const template = await res.json();
|
79
|
+
|
80
|
+
// HACK: we're currently using API Gateway caching on the embed API without any invalidation logic,
|
81
|
+
// so to ensure that the cache stays up to date, whenever we detect a cache hit we trigger another
|
82
|
+
// API call with the `Cache-Control` header which will re-fill the cache
|
83
|
+
const resCacheCheck = res?.headers?.get('X-Cache-Check');
|
84
|
+
if (resCacheCheck && resCacheCheck !== cacheCheck) {
|
85
|
+
fetch(`${embedApiUrl}/templates/${templateId}`, {
|
86
|
+
method: 'GET',
|
87
|
+
headers: [
|
88
|
+
['X-Api-Key', apiKey],
|
89
|
+
['Cache-Control', 'max-age=0']
|
90
|
+
]
|
91
|
+
});
|
92
|
+
}
|
93
|
+
|
94
|
+
return template;
|
95
|
+
};
|
51
96
|
|
52
97
|
constructor(host: EmbedHost) {
|
53
98
|
this.host = host;
|
@@ -1,3 +1,4 @@
|
|
1
|
+
/* eslint-disable max-lines */
|
1
2
|
import { v4 as uuidv4 } from 'uuid';
|
2
3
|
|
3
4
|
import type { Embed } from '..';
|
@@ -6,7 +7,8 @@ import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
6
7
|
|
7
8
|
import { getEnvUrls } from '~/utils/env';
|
8
9
|
|
9
|
-
|
10
|
+
// In seconds due to checking against node.currentTime
|
11
|
+
const STREAMED_THROTTLE = 10;
|
10
12
|
|
11
13
|
type EmbedHost = ReactiveControllerHost & Embed;
|
12
14
|
|
@@ -37,7 +39,7 @@ class TrackingController implements ReactiveController {
|
|
37
39
|
private _hasLoaded: BooleanMap = {};
|
38
40
|
private _answersViewed: BooleanMap = {};
|
39
41
|
private _streamedTime: TimeMap = {};
|
40
|
-
private
|
42
|
+
private _streamLatestTime: TimeMap = {};
|
41
43
|
|
42
44
|
constructor(host: EmbedHost) {
|
43
45
|
this.host = host;
|
@@ -45,11 +47,11 @@ class TrackingController implements ReactiveController {
|
|
45
47
|
}
|
46
48
|
|
47
49
|
private _findVouchId() {
|
48
|
-
if (this.host.
|
49
|
-
if ('uuid' in this.host.
|
50
|
-
return this.host.
|
50
|
+
if (this.host.vouch) {
|
51
|
+
if ('uuid' in this.host.vouch) {
|
52
|
+
return this.host.vouch.uuid;
|
51
53
|
}
|
52
|
-
return this.host.
|
54
|
+
return this.host.vouch.id;
|
53
55
|
}
|
54
56
|
}
|
55
57
|
|
@@ -116,7 +118,7 @@ class TrackingController implements ReactiveController {
|
|
116
118
|
});
|
117
119
|
|
118
120
|
return {
|
119
|
-
source:
|
121
|
+
source: this.host.trackingSource,
|
120
122
|
time: new Date(),
|
121
123
|
region,
|
122
124
|
country,
|
@@ -132,7 +134,10 @@ class TrackingController implements ReactiveController {
|
|
132
134
|
const { publicApiUrl } = getEnvUrls(this.host.env);
|
133
135
|
const { client, tab, request, visitor } = this._getUids();
|
134
136
|
|
135
|
-
|
137
|
+
if (this.host.disableTracking) {
|
138
|
+
return;
|
139
|
+
}
|
140
|
+
|
136
141
|
navigator.sendBeacon(
|
137
142
|
`${publicApiUrl}/api/events`,
|
138
143
|
JSON.stringify({
|
@@ -180,63 +185,78 @@ class TrackingController implements ReactiveController {
|
|
180
185
|
}
|
181
186
|
};
|
182
187
|
|
183
|
-
private _handleVideoPlay = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
|
188
|
+
private _handleVideoPlay = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
|
184
189
|
const vouchId = this._findVouchId();
|
190
|
+
|
185
191
|
if (!vouchId) {
|
186
192
|
return;
|
187
193
|
}
|
194
|
+
|
188
195
|
// Only increment play count once per session
|
189
|
-
if (!this._answersViewed[
|
196
|
+
if (!this._answersViewed[key]) {
|
190
197
|
this._sendTrackingEvent('VOUCH_RESPONSE_VIEWED', {
|
191
198
|
vouchId,
|
192
199
|
answerId: id
|
193
200
|
});
|
194
|
-
this._answersViewed[
|
201
|
+
this._answersViewed[key] = true;
|
195
202
|
}
|
196
|
-
|
197
|
-
this.
|
203
|
+
|
204
|
+
this._streamedTime[key] = node.currentTime;
|
205
|
+
this._streamLatestTime[key] = node.currentTime;
|
198
206
|
};
|
199
207
|
|
200
|
-
private _handleVideoTimeUpdate = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
|
208
|
+
private _handleVideoTimeUpdate = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
|
201
209
|
const vouchId = this._findVouchId();
|
210
|
+
|
202
211
|
if (!vouchId) {
|
203
212
|
return;
|
204
213
|
}
|
205
|
-
|
214
|
+
|
206
215
|
if (
|
216
|
+
node.currentTime &&
|
217
|
+
!node.paused &&
|
207
218
|
!this.host.paused &&
|
208
219
|
// Only fire the video seeked event when this video is the active one
|
209
220
|
id === this.host.scene?.video?.id &&
|
210
221
|
// Throttle the frequency that we send streamed events while playing
|
211
|
-
|
222
|
+
node.currentTime - this._streamedTime[key] > STREAMED_THROTTLE
|
212
223
|
) {
|
213
224
|
this._sendTrackingEvent('VIDEO_STREAMED', {
|
214
225
|
vouchId,
|
215
226
|
answerId: id,
|
216
|
-
streamStart: this._streamedTime[
|
227
|
+
streamStart: this._streamedTime[key],
|
217
228
|
streamEnd: node.currentTime
|
218
229
|
});
|
219
|
-
this._streamedTime[
|
220
|
-
|
230
|
+
this._streamedTime[key] = node.currentTime;
|
231
|
+
}
|
232
|
+
|
233
|
+
if (!this.host.paused) {
|
234
|
+
this._streamLatestTime[key] = node.currentTime;
|
221
235
|
}
|
222
236
|
};
|
223
237
|
|
224
|
-
private _handleVideoPause = ({ detail: { id,
|
238
|
+
private _handleVideoPause = ({ detail: { id, key } }: CustomEvent<VideoEventDetail>) => {
|
225
239
|
const vouchId = this._findVouchId();
|
240
|
+
|
226
241
|
if (!vouchId) {
|
227
242
|
return;
|
228
243
|
}
|
229
|
-
|
230
|
-
//
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
244
|
+
|
245
|
+
// Don't send a tracking event if the video pauses when seeking backwards
|
246
|
+
if (this._streamLatestTime[key] > this._streamedTime[key] + 0.5) {
|
247
|
+
// Send a video streamed event any time the video pauses then reset the streamed state
|
248
|
+
// We do this to capture the last bit of time that the video was played between the previous
|
249
|
+
// stream event and the video being paused manually or stopping because it ended
|
250
|
+
this._sendTrackingEvent('VIDEO_STREAMED', {
|
251
|
+
vouchId,
|
252
|
+
answerId: id,
|
253
|
+
streamStart: this._streamedTime[key],
|
254
|
+
streamEnd: this._streamLatestTime[key]
|
255
|
+
});
|
256
|
+
}
|
257
|
+
|
258
|
+
delete this._streamedTime[key];
|
259
|
+
delete this._streamLatestTime[key];
|
240
260
|
};
|
241
261
|
|
242
262
|
hostConnected() {
|
@@ -17,6 +17,8 @@ import '@vouchfor/media-player';
|
|
17
17
|
type EmbedProps = Pick<MediaPlayerProps, 'data' | 'aspectRatio' | 'preload' | 'autoplay' | 'controls'> & {
|
18
18
|
env: Environment;
|
19
19
|
apiKey: string;
|
20
|
+
disableTracking?: boolean;
|
21
|
+
trackingSource?: string;
|
20
22
|
vouchId?: string;
|
21
23
|
templateId?: string;
|
22
24
|
};
|
@@ -31,6 +33,8 @@ class Embed extends LitElement {
|
|
31
33
|
|
32
34
|
@property({ type: String }) env: EmbedProps['env'] = 'prod';
|
33
35
|
@property({ type: String }) apiKey: EmbedProps['apiKey'] = '';
|
36
|
+
@property({ type: Boolean }) disableTracking: EmbedProps['disableTracking'] = false;
|
37
|
+
@property({ type: String }) trackingSource: EmbedProps['trackingSource'] = 'embed';
|
34
38
|
|
35
39
|
@property({ type: Array }) controls: EmbedProps['controls'];
|
36
40
|
@property({ type: String }) preload: EmbedProps['preload'] = 'auto';
|
@@ -55,6 +59,7 @@ class Embed extends LitElement {
|
|
55
59
|
'waiting',
|
56
60
|
|
57
61
|
'video:loadeddata',
|
62
|
+
'video:seeking',
|
58
63
|
'video:seeked',
|
59
64
|
'video:play',
|
60
65
|
'video:playing',
|
package/src/utils/env.ts
CHANGED
@@ -1,15 +1,11 @@
|
|
1
|
-
type Environment = 'dev' | 'staging' | 'prod';
|
1
|
+
type Environment = 'local' | 'dev' | 'staging' | 'prod';
|
2
2
|
|
3
3
|
type GetEnvUrlsReturn = {
|
4
|
-
marketingUrl: string;
|
5
4
|
videoUrl: string;
|
6
5
|
publicApiUrl: string;
|
7
6
|
embedApiUrl: string;
|
8
|
-
publicRecorderUrl: string;
|
9
7
|
};
|
10
8
|
|
11
|
-
const marketingUrl = 'https://vouchfor.com';
|
12
|
-
|
13
9
|
const devVideoUrl = 'https://d2rxhdlm2q91uk.cloudfront.net';
|
14
10
|
const stagingVideoUrl = 'https://d1ix11aj5kfygl.cloudfront.net';
|
15
11
|
const prodVideoUrl = 'https://d157jlwnudd93d.cloudfront.net';
|
@@ -18,61 +14,51 @@ const devPublicApiUrl = 'https://bshyfw4h5a.execute-api.ap-southeast-2.amazonaws
|
|
18
14
|
const stagingPublicApiUrl = 'https://gyzw7rpbq3.execute-api.ap-southeast-2.amazonaws.com/staging';
|
19
15
|
const prodPublicApiUrl = 'https://vfcjuim1l3.execute-api.ap-southeast-2.amazonaws.com/prod';
|
20
16
|
|
17
|
+
const localEmbedApiUrl = 'http://localhost:6060/v2';
|
21
18
|
const devEmbedApiUrl = 'https://embed-dev.vouchfor.com/v2';
|
22
19
|
const stagingEmbedApiUrl = 'https://embed-staging.vouchfor.com/v2';
|
23
20
|
const prodEmbedApiUrl = 'https://embed.vouchfor.com/v2';
|
24
21
|
|
25
|
-
const devPublicRecorderUrl = 'https://dev.vouchfor.com';
|
26
|
-
const stagingPublicRecorderUrl = 'https://staging.vouchfor.com';
|
27
|
-
const prodPublicRecorderUrl = 'https://app.vouchfor.com';
|
28
|
-
|
29
22
|
// We are handling the case where env is an unknown string so the ts error is a lie
|
30
23
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
31
24
|
// @ts-ignore
|
32
25
|
function getEnvUrls(env: Environment): GetEnvUrlsReturn {
|
33
|
-
if (!['dev', 'staging', 'prod'].includes(env)) {
|
26
|
+
if (!['local', 'dev', 'staging', 'prod'].includes(env)) {
|
34
27
|
throw new Error(`Unknown environment: ${env}`);
|
35
28
|
}
|
36
29
|
|
30
|
+
if (env === 'local') {
|
31
|
+
return {
|
32
|
+
videoUrl: devVideoUrl,
|
33
|
+
publicApiUrl: devPublicApiUrl,
|
34
|
+
embedApiUrl: localEmbedApiUrl
|
35
|
+
};
|
36
|
+
}
|
37
|
+
|
37
38
|
if (env === 'dev') {
|
38
39
|
return {
|
39
|
-
marketingUrl,
|
40
40
|
videoUrl: devVideoUrl,
|
41
41
|
publicApiUrl: devPublicApiUrl,
|
42
|
-
embedApiUrl: devEmbedApiUrl
|
43
|
-
publicRecorderUrl: devPublicRecorderUrl
|
42
|
+
embedApiUrl: devEmbedApiUrl
|
44
43
|
};
|
45
44
|
}
|
46
45
|
|
47
46
|
if (env === 'staging') {
|
48
47
|
return {
|
49
|
-
marketingUrl,
|
50
48
|
videoUrl: stagingVideoUrl,
|
51
49
|
publicApiUrl: stagingPublicApiUrl,
|
52
|
-
embedApiUrl: stagingEmbedApiUrl
|
53
|
-
publicRecorderUrl: stagingPublicRecorderUrl
|
50
|
+
embedApiUrl: stagingEmbedApiUrl
|
54
51
|
};
|
55
52
|
}
|
56
53
|
|
57
54
|
if (env === 'prod') {
|
58
55
|
return {
|
59
|
-
marketingUrl,
|
60
56
|
videoUrl: prodVideoUrl,
|
61
57
|
publicApiUrl: prodPublicApiUrl,
|
62
|
-
embedApiUrl: prodEmbedApiUrl
|
63
|
-
publicRecorderUrl: prodPublicRecorderUrl
|
58
|
+
embedApiUrl: prodEmbedApiUrl
|
64
59
|
};
|
65
60
|
}
|
66
61
|
}
|
67
62
|
|
68
|
-
export {
|
69
|
-
marketingUrl,
|
70
|
-
devEmbedApiUrl,
|
71
|
-
stagingEmbedApiUrl,
|
72
|
-
prodEmbedApiUrl,
|
73
|
-
devPublicRecorderUrl,
|
74
|
-
stagingPublicRecorderUrl,
|
75
|
-
prodPublicRecorderUrl,
|
76
|
-
getEnvUrls
|
77
|
-
};
|
63
|
+
export { devEmbedApiUrl, stagingEmbedApiUrl, prodEmbedApiUrl, getEnvUrls };
|
78
64
|
export type { Environment };
|
@@ -1,14 +0,0 @@
|
|
1
|
-
import type { Embed } from '..';
|
2
|
-
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
3
|
-
type EmbedHost = ReactiveControllerHost & Embed;
|
4
|
-
declare class EventForwardController implements ReactiveController {
|
5
|
-
host: EmbedHost;
|
6
|
-
private _events;
|
7
|
-
private _cleanup;
|
8
|
-
private _forwardElementRef;
|
9
|
-
constructor(host: EmbedHost, events: string[]);
|
10
|
-
register(): import("lit-html/directive.js").DirectiveResult<typeof import("lit-html/directives/ref.js").RefDirective>;
|
11
|
-
hostConnected(): void;
|
12
|
-
hostDisconnected(): void;
|
13
|
-
}
|
14
|
-
export { EventForwardController };
|