@vouchfor/embeds 0.0.0-experiment.2ab669a → 0.0.0-experiment.2d4da6c
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/browser-8b4ecf1d.js +433 -0
- package/dist/es/browser-8b4ecf1d.js.map +1 -0
- package/dist/es/embeds.js +7 -1217
- package/dist/es/embeds.js.map +1 -1
- package/dist/es/index-4c628d0f.js +9948 -0
- package/dist/es/index-4c628d0f.js.map +1 -0
- package/dist/es/{components → src/components}/Embed/controllers/fetcher.d.ts +1 -0
- package/dist/es/{components → src/components}/Embed/controllers/tracking.d.ts +7 -2
- package/dist/es/{components → src/components}/Embed/index.d.ts +10 -2
- package/dist/es/src/utils/env.d.ts +12 -0
- package/dist/iife/embeds.iife.js +433 -297
- package/dist/iife/embeds.iife.js.map +1 -1
- package/package.json +4 -4
- package/src/components/Embed/Embed.stories.ts +17 -4
- package/src/components/Embed/controllers/fetcher.ts +79 -15
- package/src/components/Embed/controllers/tracking.ts +99 -65
- package/src/components/Embed/index.ts +25 -5
- package/src/utils/env.ts +15 -29
- package/dist/es/components/Embed/controllers/event-forwarder.d.ts +0 -14
- package/dist/es/utils/env.d.ts +0 -18
- /package/dist/es/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/es/{utils → src/utils}/events.d.ts +0 -0
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.2d4da6c",
|
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.2d4da6c",
|
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",
|
@@ -10,7 +10,17 @@ type EmbedArgs = EmbedProps & {
|
|
10
10
|
showVouch?: boolean;
|
11
11
|
};
|
12
12
|
|
13
|
-
const _Embed = ({
|
13
|
+
const _Embed = ({
|
14
|
+
vouchId,
|
15
|
+
templateId,
|
16
|
+
questions,
|
17
|
+
preload,
|
18
|
+
autoplay,
|
19
|
+
env,
|
20
|
+
apiKey,
|
21
|
+
controls,
|
22
|
+
aspectRatio
|
23
|
+
}: EmbedArgs) => {
|
14
24
|
return html`
|
15
25
|
<div style="height: 100vh">
|
16
26
|
<vouch-embed
|
@@ -18,10 +28,12 @@ const _Embed = ({ vouchId, templateId, preload, autoplay, env, apiKey, controls,
|
|
18
28
|
apiKey=${ifDefined(apiKey)}
|
19
29
|
vouchId=${ifDefined(vouchId)}
|
20
30
|
templateId=${ifDefined(templateId)}
|
31
|
+
.questions=${questions}
|
21
32
|
.controls=${controls}
|
22
33
|
?autoplay=${autoplay}
|
23
34
|
preload=${ifDefined(preload)}
|
24
35
|
aspectRatio=${ifDefined(aspectRatio)}
|
36
|
+
@error=${console.log}
|
25
37
|
></vouch-embed>
|
26
38
|
</div>
|
27
39
|
`;
|
@@ -39,10 +51,11 @@ type Story = StoryObj<EmbedArgs>;
|
|
39
51
|
|
40
52
|
const Embed: Story = {
|
41
53
|
args: {
|
42
|
-
env: '
|
54
|
+
env: 'local',
|
43
55
|
apiKey: 'TVik9uTMgE-PD25UTHIS6gyl0hMBWC7AT4dkpdlLBT4VIfDWZJrQiCk6Ak7m1',
|
44
56
|
vouchId: '6JQEIPeStt',
|
45
|
-
templateId: '
|
57
|
+
templateId: '357fc118-e179-4171-9446-ff2b8e9d1b29',
|
58
|
+
questions: [],
|
46
59
|
aspectRatio: 0,
|
47
60
|
preload: 'none',
|
48
61
|
autoplay: false
|
@@ -50,7 +63,7 @@ const Embed: Story = {
|
|
50
63
|
argTypes: {
|
51
64
|
env: {
|
52
65
|
control: 'radio',
|
53
|
-
options: ['
|
66
|
+
options: ['local', 'dev', 'staging', 'prod']
|
54
67
|
},
|
55
68
|
preload: {
|
56
69
|
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';
|
@@ -8,7 +9,7 @@ import { getEnvUrls } from '~/utils/env';
|
|
8
9
|
|
9
10
|
type EmbedHost = ReactiveControllerHost & Embed;
|
10
11
|
|
11
|
-
type
|
12
|
+
type FetchTaskDeps = [
|
12
13
|
EmbedProps['env'],
|
13
14
|
EmbedProps['apiKey'],
|
14
15
|
EmbedProps['data'],
|
@@ -16,10 +17,13 @@ type TaskDeps = [
|
|
16
17
|
EmbedProps['templateId']
|
17
18
|
];
|
18
19
|
|
20
|
+
type FilterTaskDeps = [EmbedProps['data'], EmbedProps['questions']];
|
21
|
+
|
19
22
|
class FetcherController {
|
20
23
|
host: EmbedHost;
|
21
24
|
|
22
25
|
private _fetching = false;
|
26
|
+
private _vouch: EmbedProps['data'];
|
23
27
|
|
24
28
|
set fetching(value) {
|
25
29
|
if (this._fetching !== value) {
|
@@ -31,29 +35,73 @@ class FetcherController {
|
|
31
35
|
return this._fetching;
|
32
36
|
}
|
33
37
|
|
34
|
-
private async
|
38
|
+
private getVouch = async (env: Environment, apiKey: string, vouchId: string) => {
|
35
39
|
const { embedApiUrl } = getEnvUrls(env);
|
36
40
|
|
37
|
-
|
41
|
+
const cacheCheck = uuidv4();
|
42
|
+
const res = await fetch(`${embedApiUrl}/vouches/${vouchId}`, {
|
38
43
|
method: 'GET',
|
39
|
-
headers: [
|
40
|
-
|
41
|
-
|
44
|
+
headers: [
|
45
|
+
['X-Api-Key', apiKey],
|
46
|
+
['X-Cache-Check', cacheCheck]
|
47
|
+
]
|
48
|
+
});
|
49
|
+
|
50
|
+
const vouch = await res.json();
|
51
|
+
this.host.dispatchEvent(new CustomEvent('vouch:loaded', { detail: vouch?.id }));
|
52
|
+
|
53
|
+
// HACK: we're currently using API Gateway caching on the embed API without any invalidation logic,
|
54
|
+
// so to ensure that the cache stays up to date, whenever we detect a cache hit we trigger another
|
55
|
+
// API call with the `Cache-Control` header which will re-fill the cache
|
56
|
+
const resCacheCheck = res?.headers?.get('X-Cache-Check');
|
57
|
+
if (resCacheCheck !== cacheCheck) {
|
58
|
+
fetch(`${embedApiUrl}/vouches/${vouchId}`, {
|
59
|
+
method: 'GET',
|
60
|
+
headers: [
|
61
|
+
['X-Api-Key', apiKey],
|
62
|
+
['Cache-Control', 'max-age=0']
|
63
|
+
]
|
64
|
+
});
|
65
|
+
}
|
66
|
+
|
67
|
+
return vouch;
|
68
|
+
};
|
42
69
|
|
43
|
-
private async
|
70
|
+
private getTemplate = async (env: Environment, apiKey: string, templateId: string) => {
|
44
71
|
const { embedApiUrl } = getEnvUrls(env);
|
45
72
|
|
46
|
-
|
73
|
+
const cacheCheck = uuidv4();
|
74
|
+
const res = await fetch(`${embedApiUrl}/templates/${templateId}`, {
|
47
75
|
method: 'GET',
|
48
|
-
headers: [
|
49
|
-
|
50
|
-
|
76
|
+
headers: [
|
77
|
+
['X-Api-Key', apiKey],
|
78
|
+
['X-Cache-Check', cacheCheck]
|
79
|
+
]
|
80
|
+
});
|
81
|
+
const template = await res.json();
|
82
|
+
|
83
|
+
// HACK: we're currently using API Gateway caching on the embed API without any invalidation logic,
|
84
|
+
// so to ensure that the cache stays up to date, whenever we detect a cache hit we trigger another
|
85
|
+
// API call with the `Cache-Control` header which will re-fill the cache
|
86
|
+
const resCacheCheck = res?.headers?.get('X-Cache-Check');
|
87
|
+
if (resCacheCheck !== cacheCheck) {
|
88
|
+
fetch(`${embedApiUrl}/templates/${templateId}`, {
|
89
|
+
method: 'GET',
|
90
|
+
headers: [
|
91
|
+
['X-Api-Key', apiKey],
|
92
|
+
['Cache-Control', 'max-age=0']
|
93
|
+
]
|
94
|
+
});
|
95
|
+
}
|
96
|
+
|
97
|
+
return template;
|
98
|
+
};
|
51
99
|
|
52
100
|
constructor(host: EmbedHost) {
|
53
101
|
this.host = host;
|
54
|
-
new Task<
|
102
|
+
new Task<FetchTaskDeps, void>(
|
55
103
|
this.host,
|
56
|
-
async ([env, apiKey, data, vouchId, templateId]:
|
104
|
+
async ([env, apiKey, data, vouchId, templateId]: FetchTaskDeps) => {
|
57
105
|
try {
|
58
106
|
host.vouch = undefined;
|
59
107
|
host.template = undefined;
|
@@ -64,7 +112,7 @@ class FetcherController {
|
|
64
112
|
this.fetching = true;
|
65
113
|
template = await this.getTemplate(env, apiKey, templateId);
|
66
114
|
}
|
67
|
-
|
115
|
+
this._vouch = data;
|
68
116
|
host.template = template ?? data?.settings?.template?.instance;
|
69
117
|
} else if (vouchId) {
|
70
118
|
this.fetching = true;
|
@@ -73,7 +121,7 @@ class FetcherController {
|
|
73
121
|
this.getVouch(env, apiKey, vouchId),
|
74
122
|
templateId ? this.getTemplate(env, apiKey, templateId) : null
|
75
123
|
]);
|
76
|
-
|
124
|
+
this._vouch = vouch;
|
77
125
|
host.template = template ?? vouch?.settings?.template?.instance;
|
78
126
|
}
|
79
127
|
} finally {
|
@@ -82,6 +130,22 @@ class FetcherController {
|
|
82
130
|
},
|
83
131
|
() => [host.env, host.apiKey, host.data, host.vouchId, host.templateId]
|
84
132
|
);
|
133
|
+
|
134
|
+
// This second task is to be able to filter the vouch without fetching it again if only the questions changed
|
135
|
+
new Task<FilterTaskDeps, void>(
|
136
|
+
this.host,
|
137
|
+
([vouch, questions]: FilterTaskDeps) => {
|
138
|
+
host.vouch = vouch
|
139
|
+
? {
|
140
|
+
...vouch,
|
141
|
+
questions: {
|
142
|
+
items: vouch?.questions.items.filter((_, index) => !questions?.length || questions?.includes(index + 1))
|
143
|
+
}
|
144
|
+
}
|
145
|
+
: undefined;
|
146
|
+
},
|
147
|
+
() => [this._vouch, host.questions]
|
148
|
+
);
|
85
149
|
}
|
86
150
|
}
|
87
151
|
|
@@ -1,18 +1,20 @@
|
|
1
|
+
import { TEMPLATE_VERSION } from '@vouchfor/canvas-video';
|
1
2
|
import { v4 as uuidv4 } from 'uuid';
|
2
3
|
|
3
4
|
import type { Embed } from '..';
|
4
5
|
import type { VideoEventDetail } from '@vouchfor/media-player';
|
5
6
|
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
6
7
|
|
8
|
+
import packageJson from '../../../../package.json';
|
7
9
|
import { getEnvUrls } from '~/utils/env';
|
8
10
|
|
9
|
-
const
|
11
|
+
const MINIMUM_SEND_THRESHOLD = 1;
|
10
12
|
|
11
13
|
type EmbedHost = ReactiveControllerHost & Embed;
|
12
14
|
|
13
15
|
type TrackingEvent = 'VOUCH_LOADED' | 'VOUCH_RESPONSE_VIEWED' | 'VIDEO_PLAYED' | 'VIDEO_STREAMED';
|
14
16
|
type TrackingPayload = {
|
15
|
-
vouchId
|
17
|
+
vouchId?: string;
|
16
18
|
answerId?: string;
|
17
19
|
streamStart?: number;
|
18
20
|
streamEnd?: number;
|
@@ -36,21 +38,23 @@ class TrackingController implements ReactiveController {
|
|
36
38
|
private _hasPlayed = false;
|
37
39
|
private _hasLoaded: BooleanMap = {};
|
38
40
|
private _answersViewed: BooleanMap = {};
|
39
|
-
private
|
40
|
-
private
|
41
|
+
private _streamStartTime: TimeMap = {};
|
42
|
+
private _streamLatestTime: TimeMap = {};
|
43
|
+
private _currentlyPlayingVideo: VideoEventDetail | null = null;
|
41
44
|
|
42
45
|
constructor(host: EmbedHost) {
|
43
46
|
this.host = host;
|
44
47
|
host.addController(this);
|
45
48
|
}
|
46
49
|
|
47
|
-
private _findVouchId() {
|
48
|
-
if (
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
return this.host.
|
50
|
+
private _findVouchId(payload?: TrackingPayload) {
|
51
|
+
if (payload && 'vouchId' in payload) {
|
52
|
+
return payload.vouchId;
|
53
|
+
}
|
54
|
+
if (this.host.vouch) {
|
55
|
+
return this.host.vouch.id;
|
53
56
|
}
|
57
|
+
return null;
|
54
58
|
}
|
55
59
|
|
56
60
|
private _createVisitor = (visitorId: string) => {
|
@@ -116,7 +120,7 @@ class TrackingController implements ReactiveController {
|
|
116
120
|
});
|
117
121
|
|
118
122
|
return {
|
119
|
-
source:
|
123
|
+
source: this.host.trackingSource,
|
120
124
|
time: new Date(),
|
121
125
|
region,
|
122
126
|
country,
|
@@ -124,20 +128,31 @@ class TrackingController implements ReactiveController {
|
|
124
128
|
screenWidth: window.screen.width,
|
125
129
|
referrer: document.referrer,
|
126
130
|
currentUrl: location.href,
|
131
|
+
embedType: 'media-player-embed',
|
132
|
+
embedVersion: packageJson.version,
|
133
|
+
templateVersion: TEMPLATE_VERSION,
|
127
134
|
...utmParams
|
128
135
|
};
|
129
136
|
};
|
130
137
|
|
131
|
-
private _sendTrackingEvent = (event: TrackingEvent, payload
|
138
|
+
private _sendTrackingEvent = (event: TrackingEvent, payload?: TrackingPayload) => {
|
139
|
+
const vouchId = this._findVouchId(payload);
|
140
|
+
|
141
|
+
if (!vouchId || this.host.disableTracking) {
|
142
|
+
return;
|
143
|
+
}
|
144
|
+
|
132
145
|
const { publicApiUrl } = getEnvUrls(this.host.env);
|
133
146
|
const { client, tab, request, visitor } = this._getUids();
|
134
147
|
|
135
|
-
// Don't send tracking if we don't have a source
|
136
148
|
navigator.sendBeacon(
|
137
|
-
`${publicApiUrl}/api/events`,
|
149
|
+
`${publicApiUrl}/api/v2/events`,
|
138
150
|
JSON.stringify({
|
139
151
|
event,
|
140
|
-
payload
|
152
|
+
payload: {
|
153
|
+
...payload,
|
154
|
+
vouchId
|
155
|
+
},
|
141
156
|
context: {
|
142
157
|
'x-uid-client': client,
|
143
158
|
'x-uid-tab': tab,
|
@@ -149,6 +164,26 @@ class TrackingController implements ReactiveController {
|
|
149
164
|
);
|
150
165
|
};
|
151
166
|
|
167
|
+
private _streamEnded = () => {
|
168
|
+
if (this._currentlyPlayingVideo) {
|
169
|
+
const { id, key } = this._currentlyPlayingVideo;
|
170
|
+
// Don't send a tracking event when seeking backwards
|
171
|
+
if (this._streamLatestTime[key] > this._streamStartTime[key] + MINIMUM_SEND_THRESHOLD) {
|
172
|
+
// Send a video streamed event any time the stream ends to capture the time between starting
|
173
|
+
// the video and the video stopping for any reason (pausing, deleting the embed node or closing the browser)
|
174
|
+
this._sendTrackingEvent('VIDEO_STREAMED', {
|
175
|
+
answerId: id,
|
176
|
+
streamStart: this._streamStartTime[key],
|
177
|
+
streamEnd: this._streamLatestTime[key]
|
178
|
+
});
|
179
|
+
}
|
180
|
+
|
181
|
+
// Make sure these events are only sent once by deleting the start and latest times
|
182
|
+
delete this._streamStartTime[key];
|
183
|
+
delete this._streamLatestTime[key];
|
184
|
+
}
|
185
|
+
};
|
186
|
+
|
152
187
|
private _handleVouchLoaded = ({ detail: vouchId }: CustomEvent<string>) => {
|
153
188
|
if (!vouchId) {
|
154
189
|
return;
|
@@ -156,91 +191,84 @@ class TrackingController implements ReactiveController {
|
|
156
191
|
|
157
192
|
// Only send loaded event once per session
|
158
193
|
if (!this._hasLoaded[vouchId]) {
|
159
|
-
this._sendTrackingEvent('VOUCH_LOADED', {
|
160
|
-
vouchId
|
161
|
-
});
|
194
|
+
this._sendTrackingEvent('VOUCH_LOADED', { vouchId });
|
162
195
|
this._hasLoaded[vouchId] = true;
|
163
196
|
}
|
164
197
|
};
|
165
198
|
|
166
199
|
private _handlePlay = () => {
|
167
|
-
const vouchId = this._findVouchId();
|
168
|
-
|
169
|
-
if (!vouchId) {
|
170
|
-
return;
|
171
|
-
}
|
172
|
-
|
173
200
|
// Only send the video played event once per session
|
174
201
|
if (!this._hasPlayed) {
|
175
202
|
this._sendTrackingEvent('VIDEO_PLAYED', {
|
176
|
-
vouchId,
|
177
203
|
streamStart: this.host.currentTime
|
178
204
|
});
|
179
205
|
this._hasPlayed = true;
|
180
206
|
}
|
181
207
|
};
|
182
208
|
|
183
|
-
private _handleVideoPlay = ({ detail: { id, node } }: CustomEvent<VideoEventDetail>) => {
|
184
|
-
const vouchId = this._findVouchId();
|
185
|
-
if (!vouchId) {
|
186
|
-
return;
|
187
|
-
}
|
209
|
+
private _handleVideoPlay = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
|
188
210
|
// Only increment play count once per session
|
189
|
-
if (!this._answersViewed[
|
211
|
+
if (!this._answersViewed[key]) {
|
190
212
|
this._sendTrackingEvent('VOUCH_RESPONSE_VIEWED', {
|
191
|
-
vouchId,
|
192
213
|
answerId: id
|
193
214
|
});
|
194
|
-
this._answersViewed[
|
215
|
+
this._answersViewed[key] = true;
|
195
216
|
}
|
196
|
-
this._streamedTime[id] = node.currentTime;
|
197
|
-
this._streamedPrevTimestamp[id] = Date.now();
|
198
|
-
};
|
199
217
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
return;
|
218
|
+
if (!this._streamStartTime[key]) {
|
219
|
+
this._streamStartTime[key] = node.currentTime;
|
220
|
+
this._streamLatestTime[key] = node.currentTime;
|
204
221
|
}
|
205
|
-
|
222
|
+
};
|
223
|
+
|
224
|
+
private _handleVideoTimeUpdate = ({ detail: { id, key, node } }: CustomEvent<VideoEventDetail>) => {
|
206
225
|
if (
|
226
|
+
// We only want to count any time that the video is actually playing
|
207
227
|
!this.host.paused &&
|
208
|
-
// Only
|
209
|
-
id === this.host.scene?.video?.id
|
210
|
-
// Throttle the frequency that we send streamed events while playing
|
211
|
-
currentTimestamp - this._streamedPrevTimestamp[id] > STREAMED_THROTTLE
|
228
|
+
// Only update the latest time if this event fires for the currently active video
|
229
|
+
id === this.host.scene?.video?.id
|
212
230
|
) {
|
231
|
+
this._currentlyPlayingVideo = { id, key, node };
|
232
|
+
this._streamLatestTime[key] = node.currentTime;
|
233
|
+
}
|
234
|
+
};
|
235
|
+
|
236
|
+
private _handleVideoPause = ({ detail: { id, key } }: CustomEvent<VideoEventDetail>) => {
|
237
|
+
if (this._streamLatestTime[key] > this._streamStartTime[key] + MINIMUM_SEND_THRESHOLD) {
|
213
238
|
this._sendTrackingEvent('VIDEO_STREAMED', {
|
214
|
-
vouchId,
|
215
239
|
answerId: id,
|
216
|
-
streamStart: this.
|
217
|
-
streamEnd:
|
240
|
+
streamStart: this._streamStartTime[key],
|
241
|
+
streamEnd: this._streamLatestTime[key]
|
218
242
|
});
|
219
|
-
this._streamedTime[id] = node.currentTime;
|
220
|
-
this._streamedPrevTimestamp[id] = currentTimestamp;
|
221
243
|
}
|
244
|
+
delete this._streamStartTime[key];
|
245
|
+
delete this._streamLatestTime[key];
|
222
246
|
};
|
223
247
|
|
224
|
-
private
|
225
|
-
|
226
|
-
|
227
|
-
|
248
|
+
private _pageUnloading = () => {
|
249
|
+
this._streamEnded();
|
250
|
+
// This will try to send the same stream event again so we delete the start and latest
|
251
|
+
// time in stream ended so that there is no times to send and the pause event does nothing
|
252
|
+
this.host.pause();
|
253
|
+
};
|
254
|
+
|
255
|
+
private _handleVisibilityChange = () => {
|
256
|
+
if (document.visibilityState === 'hidden') {
|
257
|
+
this._pageUnloading();
|
228
258
|
}
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
this.
|
233
|
-
vouchId,
|
234
|
-
answerId: id,
|
235
|
-
streamStart: this._streamedTime[id],
|
236
|
-
streamEnd: node.currentTime
|
237
|
-
});
|
238
|
-
delete this._streamedTime[id];
|
239
|
-
delete this._streamedPrevTimestamp[id];
|
259
|
+
};
|
260
|
+
|
261
|
+
private _handlePageHide = () => {
|
262
|
+
this._pageUnloading();
|
240
263
|
};
|
241
264
|
|
242
265
|
hostConnected() {
|
243
266
|
requestAnimationFrame(() => {
|
267
|
+
if ('onvisibilitychange' in document) {
|
268
|
+
document.addEventListener('visibilitychange', this._handleVisibilityChange);
|
269
|
+
} else {
|
270
|
+
window.addEventListener('pagehide', this._handlePageHide);
|
271
|
+
}
|
244
272
|
this.host.addEventListener('vouch:loaded', this._handleVouchLoaded);
|
245
273
|
this.host.mediaPlayer?.addEventListener('play', this._handlePlay);
|
246
274
|
this.host.mediaPlayer?.addEventListener('video:play', this._handleVideoPlay);
|
@@ -250,6 +278,12 @@ class TrackingController implements ReactiveController {
|
|
250
278
|
}
|
251
279
|
|
252
280
|
hostDisconnected() {
|
281
|
+
this._streamEnded();
|
282
|
+
if ('onvisibilitychange' in document) {
|
283
|
+
document.removeEventListener('visibilitychange', this._handleVisibilityChange);
|
284
|
+
} else {
|
285
|
+
window.removeEventListener('pagehide', this._handlePageHide);
|
286
|
+
}
|
253
287
|
this.host.removeEventListener('vouch:loaded', this._handleVouchLoaded);
|
254
288
|
this.host.mediaPlayer?.removeEventListener('play', this._handlePlay);
|
255
289
|
this.host.mediaPlayer?.removeEventListener('video:play', this._handleVideoPlay);
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import { html, LitElement } from 'lit';
|
1
|
+
import { css, html, LitElement } from 'lit';
|
2
2
|
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';
|
@@ -17,20 +17,35 @@ 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;
|
24
|
+
// Index of the questions to include starting from 1
|
25
|
+
questions?: number[];
|
22
26
|
};
|
23
27
|
|
24
28
|
@customElement('vouch-embed')
|
25
29
|
class Embed extends LitElement {
|
30
|
+
static styles = [
|
31
|
+
css`
|
32
|
+
:host {
|
33
|
+
display: flex;
|
34
|
+
}
|
35
|
+
`
|
36
|
+
];
|
37
|
+
|
26
38
|
private _mediaPlayerRef: Ref<MediaPlayer> = createRef();
|
27
39
|
|
28
|
-
@property({ type: Object
|
40
|
+
@property({ type: Object }) data: EmbedProps['data'];
|
29
41
|
@property({ type: String }) vouchId: EmbedProps['vouchId'];
|
30
42
|
@property({ type: String }) templateId: EmbedProps['templateId'];
|
43
|
+
@property({ type: Array }) questions: EmbedProps['questions'];
|
31
44
|
|
32
45
|
@property({ type: String }) env: EmbedProps['env'] = 'prod';
|
33
46
|
@property({ type: String }) apiKey: EmbedProps['apiKey'] = '';
|
47
|
+
@property({ type: Boolean }) disableTracking: EmbedProps['disableTracking'] = false;
|
48
|
+
@property({ type: String }) trackingSource: EmbedProps['trackingSource'] = 'embedded_player';
|
34
49
|
|
35
50
|
@property({ type: Array }) controls: EmbedProps['controls'];
|
36
51
|
@property({ type: String }) preload: EmbedProps['preload'] = 'auto';
|
@@ -55,6 +70,7 @@ class Embed extends LitElement {
|
|
55
70
|
'waiting',
|
56
71
|
|
57
72
|
'video:loadeddata',
|
73
|
+
'video:seeking',
|
58
74
|
'video:seeked',
|
59
75
|
'video:play',
|
60
76
|
'video:playing',
|
@@ -144,6 +160,10 @@ class Embed extends LitElement {
|
|
144
160
|
return this._mediaPlayerRef.value?.scenes ?? [];
|
145
161
|
}
|
146
162
|
|
163
|
+
get sceneConfig(): Scenes | null {
|
164
|
+
return this._mediaPlayerRef.value?.sceneConfig ?? null;
|
165
|
+
}
|
166
|
+
|
147
167
|
get videoState() {
|
148
168
|
return this._mediaPlayerRef.value?.videoState;
|
149
169
|
}
|
@@ -166,7 +186,7 @@ class Embed extends LitElement {
|
|
166
186
|
|
167
187
|
render() {
|
168
188
|
return html`
|
169
|
-
<vmp-
|
189
|
+
<vmp-media-player
|
170
190
|
${ref(this._mediaPlayerRef)}
|
171
191
|
${this.eventController.register()}
|
172
192
|
?autoplay=${this.autoplay}
|
@@ -176,7 +196,7 @@ class Embed extends LitElement {
|
|
176
196
|
aspectRatio=${ifDefined(this.aspectRatio)}
|
177
197
|
preload=${ifDefined(this.preload)}
|
178
198
|
.controls=${this.controls}
|
179
|
-
></vmp-
|
199
|
+
></vmp-media-player>
|
180
200
|
`;
|
181
201
|
}
|
182
202
|
}
|
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 };
|