@sprig-technologies/sprig-browser 2.14.0
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/LICENSE.md +176 -0
- package/README.md +23 -0
- package/controller/controller.js +1038 -0
- package/controller/encodedViewJs.js +1 -0
- package/controller/iframe.js +184 -0
- package/controller/index.js +3 -0
- package/controller/queue.js +96 -0
- package/controller/recordingAPI/index.js +166 -0
- package/controller/recordingAPI/permissionStubs.json +65 -0
- package/index.js +207 -0
- package/package.json +35 -0
- package/shared/conflicting_widgets/index.js +13 -0
- package/shared/conflicting_widgets/intercom.js +28 -0
- package/shared/constants.js +59 -0
- package/shared/deferred.js +15 -0
- package/shared/eventEmitter.js +52 -0
- package/shared/network.js +130 -0
- package/shared/networkHelper.js +9 -0
- package/shared/shouldDirectEmbed.js +8 -0
- package/shared/tool.js +19 -0
- package/shared/ulEvents.js +45 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/* globals window, UserLeap, document */
|
|
2
|
+
|
|
3
|
+
import eventEmitter from '../shared/eventEmitter';
|
|
4
|
+
import { ulEvents } from '../shared/ulEvents';
|
|
5
|
+
|
|
6
|
+
if (window.UserLeap && window.Sprig) {
|
|
7
|
+
if (window.Sprig._gtm) {
|
|
8
|
+
// if sdk installation snippet references UserLeap, but Google Tag Manager (GTM) references Sprig,
|
|
9
|
+
// overwrite Sprig by assigning it the value of UserLeap to make sure Sprig receives the full functionality without losing any configuration
|
|
10
|
+
window.Sprig = window.UserLeap;
|
|
11
|
+
} else {
|
|
12
|
+
// if sdk installation snippet references Sprig, but Google Tag Manager (GTM) references UserLeap,
|
|
13
|
+
// overwrite UserLeap by assigning it the value of Sprig to make sure Sprig receives the full functionality without losing any configuration
|
|
14
|
+
window.UserLeap = window.Sprig;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// for an all-new GTM or snippet-based installation referencing Sprig,
|
|
18
|
+
// assign Sprig to UserLeap so that when functionality is defined on UserLeap, it's accessible on Sprig.
|
|
19
|
+
if (!window.UserLeap) window.UserLeap = window.Sprig;
|
|
20
|
+
// for a legacy GTM or snippet-based installation referencing UserLeap,
|
|
21
|
+
// assign UserLeap to Sprig so that the functionality is accessible on Sprig, too.
|
|
22
|
+
if (!window.Sprig) window.Sprig = window.UserLeap;
|
|
23
|
+
|
|
24
|
+
const LIGHT_OVERLAY_BACKGROUND_COLOR = 'rgba(255,255,255, 0.95)';
|
|
25
|
+
const DARK_OVERLAY_BACKGROUND_COLOR = 'rgba(0,0,0,0.9)';
|
|
26
|
+
|
|
27
|
+
export function createContainer() {
|
|
28
|
+
// Create home container
|
|
29
|
+
UserLeap.container = document.createElement('div');
|
|
30
|
+
UserLeap.container.className = 'ul-container';
|
|
31
|
+
document.body.appendChild(UserLeap.container);
|
|
32
|
+
}
|
|
33
|
+
export function removeContainer(initiator) {
|
|
34
|
+
const container = UserLeap.container;
|
|
35
|
+
if (!container) return;
|
|
36
|
+
try {
|
|
37
|
+
document.body.removeChild(container);
|
|
38
|
+
UserLeap.container = null;
|
|
39
|
+
eventEmitter.emit(ulEvents.SURVEY_LIFE_CYCLE, { state: 'dismissed' });
|
|
40
|
+
eventEmitter.emit(ulEvents.SURVEY_CLOSED, { name: ulEvents.SURVEY_CLOSED, initiator });
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.warn(`[UserLeap] (ERR-412) Error removing UserLeap container by ${initiator} ` + container);
|
|
43
|
+
UserLeap.reportError('dismissActiveSurvey', err);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function removeContainerOnClose() {
|
|
48
|
+
eventEmitter.subscribe(ulEvents.SURVEY_WILL_CLOSE, ({ initiator }) => {
|
|
49
|
+
removeContainer(initiator);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Create iframe
|
|
55
|
+
* @return {Object}
|
|
56
|
+
*/
|
|
57
|
+
export function createFrame(productConfig, useMobileStyling) {
|
|
58
|
+
const WIDTH = '360px';
|
|
59
|
+
const MARGIN = '0px';
|
|
60
|
+
const frameId = 'ul-frame';
|
|
61
|
+
|
|
62
|
+
createContainer();
|
|
63
|
+
// Create iframe for application
|
|
64
|
+
const iframe = document.createElement('iframe');
|
|
65
|
+
iframe.id = frameId;
|
|
66
|
+
iframe.setAttribute('title', 'Sprig User Feedback Dialog');
|
|
67
|
+
const hasOverlay = configureFrame(iframe, productConfig, useMobileStyling);
|
|
68
|
+
eventEmitter.subscribe(ulEvents.SURVEY_FADING_OUT, () => {
|
|
69
|
+
Object.assign(UserLeap.container.style, { 'background-color': 'rgba(0,0,0,0)' });
|
|
70
|
+
});
|
|
71
|
+
removeContainerOnClose();
|
|
72
|
+
|
|
73
|
+
function configureOverlayContainer(styleConfig, useMobileStyling) {
|
|
74
|
+
const defaultContainerStyle = {
|
|
75
|
+
position: 'fixed',
|
|
76
|
+
overflow: 'auto',
|
|
77
|
+
top: '0px',
|
|
78
|
+
left: '0px',
|
|
79
|
+
display: 'none', // overwritten after view is loaded, to prevent blocking website until we can display the survey.
|
|
80
|
+
height: '100%',
|
|
81
|
+
width: '100%',
|
|
82
|
+
transition: 'background-color 0.3s ease-out',
|
|
83
|
+
zIndex: 2147483646,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const containerStyle = { ...defaultContainerStyle };
|
|
87
|
+
const overlayStyleConfig = useMobileStyling ? styleConfig.overlayStyleMobile : styleConfig.overlayStyle;
|
|
88
|
+
containerStyle['background-color'] =
|
|
89
|
+
overlayStyleConfig === 'light' ? LIGHT_OVERLAY_BACKGROUND_COLOR : DARK_OVERLAY_BACKGROUND_COLOR;
|
|
90
|
+
|
|
91
|
+
if (!useMobileStyling) containerStyle.margin = 'auto';
|
|
92
|
+
Object.assign(UserLeap.container.style, containerStyle);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function configureFrame(iframe, productConfig, useMobileStyling) {
|
|
96
|
+
const defaultFrameStyle = {
|
|
97
|
+
position: 'fixed',
|
|
98
|
+
bottom: '0px',
|
|
99
|
+
right: MARGIN,
|
|
100
|
+
border: 0,
|
|
101
|
+
backgroundColor: 'rgba(0,0,0,0)',
|
|
102
|
+
zIndex: 2147483646,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const styleConfig = Object.assign({}, productConfig, UserLeap);
|
|
106
|
+
let framePositionStyle;
|
|
107
|
+
let hasOverlay = false;
|
|
108
|
+
|
|
109
|
+
if (useMobileStyling) {
|
|
110
|
+
if (UserLeap.windowDimensions && UserLeap.windowDimensions.width) {
|
|
111
|
+
defaultFrameStyle.width = `${UserLeap.windowDimensions.width}px`;
|
|
112
|
+
} else {
|
|
113
|
+
defaultFrameStyle.width = '100%';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (UserLeap.windowDimensions && UserLeap.windowDimensions.height) {
|
|
117
|
+
defaultFrameStyle.maxHeight = `${UserLeap.windowDimensions.height - 20}px`;
|
|
118
|
+
} else if (UserLeap.maxHeight) {
|
|
119
|
+
defaultFrameStyle.maxHeight = UserLeap.maxHeight;
|
|
120
|
+
} else {
|
|
121
|
+
defaultFrameStyle.maxHeight = `${document.body.clientHeight - 20}px`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (['light', 'dark'].includes(styleConfig.overlayStyleMobile)) {
|
|
125
|
+
hasOverlay = true;
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
defaultFrameStyle.width = WIDTH;
|
|
129
|
+
defaultFrameStyle.maxHeight = UserLeap.maxHeight || '66vh';
|
|
130
|
+
if (styleConfig.framePosition === 'bottomLeft') {
|
|
131
|
+
framePositionStyle = { left: MARGIN };
|
|
132
|
+
} else if (styleConfig.framePosition === 'topLeft') {
|
|
133
|
+
framePositionStyle = { left: MARGIN, top: '0px' };
|
|
134
|
+
} else if (styleConfig.framePosition === 'topRight') {
|
|
135
|
+
framePositionStyle = { top: '0px' };
|
|
136
|
+
} else if (styleConfig.framePosition === 'center') {
|
|
137
|
+
hasOverlay = true;
|
|
138
|
+
framePositionStyle = { margin: 'auto', position: 'static' };
|
|
139
|
+
defaultFrameStyle.maxHeight = null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (hasOverlay) configureOverlayContainer(styleConfig, useMobileStyling);
|
|
144
|
+
Object.assign(iframe.style, defaultFrameStyle, framePositionStyle);
|
|
145
|
+
return hasOverlay;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Set iframe height
|
|
149
|
+
* @param {Number} height
|
|
150
|
+
*/
|
|
151
|
+
iframe.setHeight = (height) => {
|
|
152
|
+
if (parseInt(iframe.style.height) != height) {
|
|
153
|
+
iframe.style.height = `${height}px`;
|
|
154
|
+
eventEmitter.emit(ulEvents.SURVEY_HEIGHT, {
|
|
155
|
+
name: ulEvents.SURVEY_HEIGHT,
|
|
156
|
+
contentFrameHeight: iframe.clientHeight,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
UserLeap.container.appendChild(iframe);
|
|
162
|
+
if (productConfig && (useMobileStyling ? productConfig.exitOnOverlayClickMobile : productConfig.exitOnOverlayClick)) {
|
|
163
|
+
UserLeap.container.onclick = () => {
|
|
164
|
+
eventEmitter.emit(ulEvents.CLOSE_SURVEY_ON_OVERLAY_CLICK);
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
eventEmitter.emit(ulEvents.SURVEY_LIFE_CYCLE, { state: 'presented' });
|
|
168
|
+
eventEmitter.emit(ulEvents.SURVEY_PRESENTED, { name: ulEvents.SURVEY_PRESENTED });
|
|
169
|
+
|
|
170
|
+
// Inject the application frame
|
|
171
|
+
const contentWinDoc = iframe.contentWindow.document;
|
|
172
|
+
contentWinDoc.open('text/html', 'replace'),
|
|
173
|
+
contentWinDoc.write('<!doctype html><head></head><body style="background-color: rgba(0,0,0,0)"></body></html>'),
|
|
174
|
+
contentWinDoc.close();
|
|
175
|
+
|
|
176
|
+
const contentWinDocHead = contentWinDoc.head;
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
frameId,
|
|
180
|
+
contentWinDocHead,
|
|
181
|
+
contentWindow: iframe.contentWindow,
|
|
182
|
+
hasOverlay,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/* globals UserLeap */
|
|
2
|
+
import 'core-js';
|
|
3
|
+
|
|
4
|
+
// Queue is an intermediatator for calls from developers
|
|
5
|
+
// integrating the userleap. Before the library loads,
|
|
6
|
+
// the snippet stores calls in a simple array.
|
|
7
|
+
// Once the library is initialized, the simple array
|
|
8
|
+
// is replaced by a Queue. All stored actions are processed,
|
|
9
|
+
// and subsequent actions are processed immediately.
|
|
10
|
+
//
|
|
11
|
+
// Pause functionality is used internally to enable UI actions
|
|
12
|
+
// to be delayed while Userleap is 'muted'
|
|
13
|
+
UserLeap.Queue = class {
|
|
14
|
+
constructor(ul, queue) {
|
|
15
|
+
this.ul = ul;
|
|
16
|
+
this.paused = false;
|
|
17
|
+
this.queue = [];
|
|
18
|
+
this.flush(queue);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
flush(queue) {
|
|
22
|
+
const length = queue.length;
|
|
23
|
+
if (length) {
|
|
24
|
+
for (let i = 0; i < length; i++) {
|
|
25
|
+
this.push(queue[i]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
isPaused() {
|
|
31
|
+
return this.paused;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pause() {
|
|
35
|
+
this.paused = true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
unpause() {
|
|
39
|
+
this.paused = false;
|
|
40
|
+
const queue = this.queue.slice(); //copies
|
|
41
|
+
this.empty();
|
|
42
|
+
this.flush(queue);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// push methods meant to make queue a drop-in
|
|
46
|
+
// replacement for the Array that's used pre-init.
|
|
47
|
+
push(action) {
|
|
48
|
+
if (!this.paused) {
|
|
49
|
+
// Queue is not paused. Perform the specified action
|
|
50
|
+
if (action instanceof Function) {
|
|
51
|
+
// Action is a closure
|
|
52
|
+
action();
|
|
53
|
+
} else {
|
|
54
|
+
// Action is an array
|
|
55
|
+
const args = Array.prototype.slice.call(action, 1);
|
|
56
|
+
const actionName = action[0];
|
|
57
|
+
const actionFunc = this.ul[actionName];
|
|
58
|
+
if (actionFunc && actionFunc instanceof Function) {
|
|
59
|
+
this.ul[actionName].apply(this.ul, args);
|
|
60
|
+
} else {
|
|
61
|
+
if (actionName) {
|
|
62
|
+
console.warn('[UserLeap] (ERR-100) No valid UserLeap action called', actionName);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
// Queue is paused. Store the action in the queue
|
|
68
|
+
this.queue.push(action);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Enqueue a closure to be performed by the queue
|
|
73
|
+
// This is primarily designed to be used to enable
|
|
74
|
+
// internal behaviors to be paused when the library
|
|
75
|
+
// is muted, and resume correctly when the mute is over
|
|
76
|
+
perform(func) {
|
|
77
|
+
if (!this.paused) {
|
|
78
|
+
return func();
|
|
79
|
+
} else {
|
|
80
|
+
let a;
|
|
81
|
+
const p = new Promise(function(resolve) {
|
|
82
|
+
a = function() {
|
|
83
|
+
resolve(func());
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
this.queue.push(a);
|
|
87
|
+
return p;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Removes all queued items
|
|
92
|
+
*/
|
|
93
|
+
empty() {
|
|
94
|
+
this.queue.length = 0;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/* global navigator, MediaRecorder, UpChunk, File, Blob*/
|
|
2
|
+
/*
|
|
3
|
+
recording api requirements
|
|
4
|
+
- check for and request for permissions
|
|
5
|
+
- handle recording exceptions (premature closing)
|
|
6
|
+
- track start end times
|
|
7
|
+
- show splash for permissions
|
|
8
|
+
*/
|
|
9
|
+
export const PERMISSION = {
|
|
10
|
+
CAMERA: 'camera',
|
|
11
|
+
MICROPHONE: 'microphone',
|
|
12
|
+
SCREEN: 'screen',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const STREAM_TYPE = {
|
|
16
|
+
AV: 'av',
|
|
17
|
+
SCREEN_CAPTURE: 'screen_capture',
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Gets the local audio stream of the current caller
|
|
21
|
+
* @param callbacks - an object to set the success/error behavior
|
|
22
|
+
* @returns {void}
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
async function getAVStream(permissionDescriptor) {
|
|
26
|
+
try {
|
|
27
|
+
return navigator.mediaDevices.getUserMedia(permissionDescriptor);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.log('oops got an error:' + err);
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const createUpload = async (body) => {
|
|
35
|
+
const response = await fetch(`https://api-staging.sprig.com/2/environments/integrations/upload`, {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
cache: 'no-cache',
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
body: JSON.stringify(body),
|
|
40
|
+
});
|
|
41
|
+
if (response.ok) {
|
|
42
|
+
const uploadResponse = await response.json();
|
|
43
|
+
console.log(`create upload response ${response}`);
|
|
44
|
+
return uploadResponse.upload.url;
|
|
45
|
+
} else {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const recorder = {
|
|
51
|
+
grantedPermissions: [],
|
|
52
|
+
streams: [],
|
|
53
|
+
recorders: [],
|
|
54
|
+
startingTimeOnTask: undefined,
|
|
55
|
+
configure(eventEmitter) {
|
|
56
|
+
eventEmitter.subscribe('permission', async (payload) => {
|
|
57
|
+
const stream = await this.requestPermissions(payload.permissionDescriptors);
|
|
58
|
+
payload.callback(stream);
|
|
59
|
+
});
|
|
60
|
+
eventEmitter.subscribe('taskComplete', (payload) => {
|
|
61
|
+
console.log('congrats you have ' + payload.completed ? 'completed' : 'given up' + ' the task');
|
|
62
|
+
//doesn't necessarily stop when the next question also requires media
|
|
63
|
+
const body = {
|
|
64
|
+
surveyId: 5105,
|
|
65
|
+
updatedAt: new Date().toISOString(),
|
|
66
|
+
mediaType: 'video',
|
|
67
|
+
};
|
|
68
|
+
this.recorders.map((recorder) => {
|
|
69
|
+
recorder.stop();
|
|
70
|
+
});
|
|
71
|
+
this.recorders.map(async (recorder) => {
|
|
72
|
+
recorder.stream.getTracks().map((track) => {
|
|
73
|
+
track.stop();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const url = await createUpload(body);
|
|
77
|
+
if (!url) return;
|
|
78
|
+
const blob = new Blob(recorder.chunks, { type: 'video/webm' });
|
|
79
|
+
const upload = UpChunk.createUpload({
|
|
80
|
+
// endpoint computed from backend separately and hacked together
|
|
81
|
+
endpoint: url,
|
|
82
|
+
file: new File([blob], `recording video ${Date.now()}`),
|
|
83
|
+
chunkSize: 5120, // Uploads the file in ~5mb chunks
|
|
84
|
+
});
|
|
85
|
+
upload.startTime = Date.now();
|
|
86
|
+
|
|
87
|
+
// subscribe to events
|
|
88
|
+
upload.on('error', (err) => {
|
|
89
|
+
console.log(`upload error ${err}`);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
upload.on('progress', (progress) => {
|
|
93
|
+
console.log(progress);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
upload.on('success', () => {
|
|
97
|
+
console.log('uploaded!');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
eventEmitter.subscribe('trackTime', (payload) => {
|
|
102
|
+
if (payload.timetracker === 'start') {
|
|
103
|
+
this.startingTimeOnTask = new Date();
|
|
104
|
+
} else {
|
|
105
|
+
const currentTime = new Date();
|
|
106
|
+
console.log(`task duration ${currentTime.getTime() - this.startingTimeOnTask.getTime()} ms`);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
// TODO clean up this logic and combine the permission request and exception handling
|
|
112
|
+
async requestPermissions(permissions) {
|
|
113
|
+
// unsupported permissions
|
|
114
|
+
if (!permissions.every((val) => Object.values(PERMISSION).includes(val))) return false;
|
|
115
|
+
const streamResults = {};
|
|
116
|
+
const recordingOptions = {
|
|
117
|
+
audioBitsPerSecond: 128000,
|
|
118
|
+
videoBitsPerSecond: 2500000,
|
|
119
|
+
// mimeType: 'video/webm',
|
|
120
|
+
};
|
|
121
|
+
if (permissions.includes(PERMISSION.CAMERA) || permissions.includes(PERMISSION.MICROPHONE)) {
|
|
122
|
+
const stream = await getAVStream({
|
|
123
|
+
video: permissions.includes(PERMISSION.CAMERA),
|
|
124
|
+
audio: permissions.includes(PERMISSION.MICROPHONE),
|
|
125
|
+
});
|
|
126
|
+
if (stream && this.grantedPermissions.length === 0) {
|
|
127
|
+
[PERMISSION.CAMERA, PERMISSION.MICROPHONE].every(
|
|
128
|
+
(val) => permissions.includes(val) && this.grantedPermissions.push(val)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
console.log('avstream successful');
|
|
132
|
+
console.log(stream);
|
|
133
|
+
streamResults[STREAM_TYPE.AV] = stream;
|
|
134
|
+
const mediaRecorder = new MediaRecorder(stream, recordingOptions);
|
|
135
|
+
mediaRecorder.chunks = [];
|
|
136
|
+
mediaRecorder.ondataavailable = function(e) {
|
|
137
|
+
mediaRecorder.chunks.push(e.data);
|
|
138
|
+
};
|
|
139
|
+
mediaRecorder.start();
|
|
140
|
+
this.recorders.push(mediaRecorder);
|
|
141
|
+
this.streams.push(stream);
|
|
142
|
+
}
|
|
143
|
+
if (permissions.includes(PERMISSION.SCREEN)) {
|
|
144
|
+
const captureStream = await navigator.mediaDevices.getDisplayMedia({
|
|
145
|
+
video: {
|
|
146
|
+
cursor: 'always',
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
console.log(captureStream);
|
|
150
|
+
const audioTrack = this.streams[0].getAudioTracks()[0];
|
|
151
|
+
captureStream.addTrack(audioTrack);
|
|
152
|
+
streamResults[STREAM_TYPE.SCREEN_CAPTURE] = captureStream;
|
|
153
|
+
const mediaRecorder = new MediaRecorder(captureStream, recordingOptions);
|
|
154
|
+
mediaRecorder.chunks = [];
|
|
155
|
+
mediaRecorder.ondataavailable = function(e) {
|
|
156
|
+
mediaRecorder.chunks.push(e.data);
|
|
157
|
+
};
|
|
158
|
+
mediaRecorder.start();
|
|
159
|
+
this.recorders.push(mediaRecorder);
|
|
160
|
+
this.streams.push(captureStream);
|
|
161
|
+
}
|
|
162
|
+
return streamResults;
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export default recorder;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"questions": [
|
|
3
|
+
{
|
|
4
|
+
"name": 103850,
|
|
5
|
+
"props": {
|
|
6
|
+
"message": "get your video/audio permissions",
|
|
7
|
+
"options": [],
|
|
8
|
+
"routingOptions": [],
|
|
9
|
+
"properties": {
|
|
10
|
+
"captionText": "click this button",
|
|
11
|
+
"buttonText": "get permission",
|
|
12
|
+
"permissions": ["microphone", "camera"],
|
|
13
|
+
"showVideoPlayer": true
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"type": "av_permission",
|
|
17
|
+
"surveyId": 1234
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": 103851,
|
|
21
|
+
"props": {
|
|
22
|
+
"message": "get your screen permissions",
|
|
23
|
+
"options": [],
|
|
24
|
+
"routingOptions": [],
|
|
25
|
+
"properties": {
|
|
26
|
+
"captionText": "click this button",
|
|
27
|
+
"buttonText": "get permission",
|
|
28
|
+
"permissions": ["screen"]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"type": "screen_permission",
|
|
32
|
+
"surveyId": 1234
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": 103852,
|
|
36
|
+
"props": {
|
|
37
|
+
"message": "record task",
|
|
38
|
+
"options": [],
|
|
39
|
+
"routingOptions": [],
|
|
40
|
+
"properties": {
|
|
41
|
+
"captionText": "get started",
|
|
42
|
+
"buttonText": "get started",
|
|
43
|
+
"timetracker": "start"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"type": "record_task_start",
|
|
47
|
+
"surveyId": 1234
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": 103853,
|
|
51
|
+
"props": {
|
|
52
|
+
"message": "complete task",
|
|
53
|
+
"options": [],
|
|
54
|
+
"routingOptions": [],
|
|
55
|
+
"properties": {
|
|
56
|
+
"captionText": "get started",
|
|
57
|
+
"completeText": "complete task",
|
|
58
|
+
"buttonText": "I give up",
|
|
59
|
+
"timetracker": "end"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"type": "record_task_finish",
|
|
63
|
+
"surveyId": 1234
|
|
64
|
+
}
|
|
65
|
+
]}
|