@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
package/index.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/* global window */
|
|
2
|
+
|
|
3
|
+
import { INSTALLATION_METHOD } from './shared/constants';
|
|
4
|
+
class SprigAPI {
|
|
5
|
+
/**
|
|
6
|
+
* Triggers displaying specified survey. Does submit answers!
|
|
7
|
+
* @param {Number} surveyId
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
displaySurvey(surveyId) {
|
|
11
|
+
window.Sprig('displaySurvey', surveyId);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Triggers displaying specified survey template. Does not submit answers
|
|
16
|
+
* @param {String} surveyTemplateId
|
|
17
|
+
*/
|
|
18
|
+
previewSurvey(surveyTemplateId) {
|
|
19
|
+
window.Sprig('previewSurvey', surveyTemplateId);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Triggers displaying specified survey. Does not submit answers
|
|
24
|
+
* @param {Number} surveyId
|
|
25
|
+
*/
|
|
26
|
+
reviewSurvey(surveyId) {
|
|
27
|
+
window.Sprig('reviewSurvey', surveyId);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* pauses api interactions
|
|
32
|
+
*/
|
|
33
|
+
mute() {
|
|
34
|
+
window.Sprig('mute');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* restart api interactions
|
|
39
|
+
*/
|
|
40
|
+
unmute() {
|
|
41
|
+
window.Sprig('unmute');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Manually dismiss an opened survey
|
|
46
|
+
*/
|
|
47
|
+
dismissActiveSurvey() {
|
|
48
|
+
window.Sprig('dismissActiveSurvey');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Set an arbitrary attribute on the visitor
|
|
53
|
+
* @param {String} attribute
|
|
54
|
+
* @param {String} value
|
|
55
|
+
*/
|
|
56
|
+
setAttribute(attribute, value) {
|
|
57
|
+
window.Sprig('setAttribute', attribute, value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Set attributes on visitor
|
|
62
|
+
* @param {Object} attributes
|
|
63
|
+
*/
|
|
64
|
+
setAttributes(attributes) {
|
|
65
|
+
window.Sprig('setAttributes', attributes);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Set identifiers and attributes on visitor
|
|
70
|
+
* @param {Object} payload
|
|
71
|
+
* @param {Object} payload.attributes visitor attributes to set
|
|
72
|
+
* @param {String} payload.userId userId (optional)
|
|
73
|
+
* @param {String} payload.anonymousId anonymousId (optional)
|
|
74
|
+
*/
|
|
75
|
+
identifyAndSetAttributes(payload) {
|
|
76
|
+
window.Sprig('identifyAndSetAttributes', payload);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Remove attributes on visitor
|
|
81
|
+
* @param {Object} attributes
|
|
82
|
+
*/
|
|
83
|
+
removeAttributes(attributes) {
|
|
84
|
+
window.Sprig('removeAttributes', attributes);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Add a listener for an event defined in ulEvents
|
|
89
|
+
* @param {String} event
|
|
90
|
+
* @param {Function} listener
|
|
91
|
+
*/
|
|
92
|
+
addListener(event, listener) {
|
|
93
|
+
window.Sprig('addListener', event, listener);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Remove a listener for an event defined in ulEvents
|
|
98
|
+
* @param {String} event
|
|
99
|
+
* @param {Function} listener
|
|
100
|
+
*/
|
|
101
|
+
removeListener(event, listener) {
|
|
102
|
+
window.Sprig('removeListener', event, listener);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Remove all listeners set on Sprig
|
|
107
|
+
*/
|
|
108
|
+
removeAllListeners() {
|
|
109
|
+
window.Sprig('removeAllListeners');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Attach an email address to visitor
|
|
114
|
+
*/
|
|
115
|
+
setEmail(email) {
|
|
116
|
+
window.Sprig('setAttribute', '!email', email);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Attach a user id to the visitor
|
|
121
|
+
*/
|
|
122
|
+
setUserId(userId) {
|
|
123
|
+
window.Sprig('setUserId', userId);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Set a partner anonymous id for future requests.
|
|
128
|
+
*/
|
|
129
|
+
setPartnerAnonymousId(partnerAnonymousId) {
|
|
130
|
+
window.Sprig('setPartnerAnonymousId', partnerAnonymousId);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* track an event to show survey if eligible
|
|
135
|
+
* @param {String} eventName
|
|
136
|
+
* @param {Object} metadata
|
|
137
|
+
* @returns
|
|
138
|
+
*/
|
|
139
|
+
track(eventName, metadata = {}) {
|
|
140
|
+
window.Sprig('track', eventName, metadata);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* optionally set userId and/or anonymousId, track an event to show survey if eligible
|
|
145
|
+
* @param {Object} payload
|
|
146
|
+
* @param {String} payload.eventName name of event to track
|
|
147
|
+
* @param {Object} payload.metadata event metadata (optional)
|
|
148
|
+
* @param {String} payload.userId userId (optional)
|
|
149
|
+
* @param {String} payload.anonymousId anonymousId (optional)
|
|
150
|
+
* @returns
|
|
151
|
+
*/
|
|
152
|
+
identifyAndTrack(payload) {
|
|
153
|
+
window.Sprig('identifyAndTrack', payload);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param {String} styleString css string representing the customized styles
|
|
158
|
+
*/
|
|
159
|
+
applyStyles(styleString) {
|
|
160
|
+
window.Sprig('applyStyles', styleString);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
set viewport dimensions, in int pixels. necessary if Sprig is installed in an iframe/component defaulting to 0 width and height.
|
|
165
|
+
* @param {Number} width
|
|
166
|
+
* @param {Number} height
|
|
167
|
+
*/
|
|
168
|
+
setWindowDimensions(width, height) {
|
|
169
|
+
window.Sprig('setWindowDimensions', width, height);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* clears Sprig from window
|
|
174
|
+
*/
|
|
175
|
+
teardown() {
|
|
176
|
+
window.Sprig('teardown');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export default {
|
|
181
|
+
/**
|
|
182
|
+
* Sets up the sprig api and load the sprig sdk on document load
|
|
183
|
+
* @param {Object} config
|
|
184
|
+
* @returns {SprigAPI} an instance of the sprig api
|
|
185
|
+
*/
|
|
186
|
+
configure: (config) => {
|
|
187
|
+
if (!config.envId && !config.environmentId) {
|
|
188
|
+
throw new Error('Initialization Error: Sprig configure requires an environmentId');
|
|
189
|
+
}
|
|
190
|
+
if (!config.envId) config.envId = config.environmentId; // backwards compatible setting for environment id
|
|
191
|
+
config.installationMethod = INSTALLATION_METHOD.NPM;
|
|
192
|
+
if (window.Sprig) return window.Sprig;
|
|
193
|
+
window.Sprig = function() {
|
|
194
|
+
window.Sprig._queue.push(arguments);
|
|
195
|
+
};
|
|
196
|
+
Object.getOwnPropertyNames(SprigAPI.prototype).map((apiMethodName) => {
|
|
197
|
+
if (apiMethodName !== 'constructor') window.Sprig[apiMethodName] = SprigAPI.prototype[apiMethodName];
|
|
198
|
+
});
|
|
199
|
+
const S = window.Sprig;
|
|
200
|
+
S.appId = config.envId;
|
|
201
|
+
S._queue = [];
|
|
202
|
+
window.UserLeap = S;
|
|
203
|
+
const sprigInitializer = require('./controller/controller').default;
|
|
204
|
+
sprigInitializer(config);
|
|
205
|
+
return window.Sprig;
|
|
206
|
+
},
|
|
207
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sprig-technologies/sprig-browser",
|
|
3
|
+
"version": "2.14.0",
|
|
4
|
+
"description": "npm package for the sprig web sdk",
|
|
5
|
+
"browser": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "test"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/UserLeap/userleap-web-sdk.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"product",
|
|
15
|
+
"user-research"
|
|
16
|
+
],
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/UserLeap/userleap-web-sdk/issues"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://docs.sprig.com/docs/web-javascript",
|
|
21
|
+
"license": "See LICENSE file",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"autosize": "4.0.2",
|
|
24
|
+
"color": "3.1.2",
|
|
25
|
+
"core-js": "3.5.0",
|
|
26
|
+
"whatwg-fetch": "3.0.0",
|
|
27
|
+
"uuid": "8.3.2"
|
|
28
|
+
},
|
|
29
|
+
"browserslist": [
|
|
30
|
+
">0.2%",
|
|
31
|
+
"not dead",
|
|
32
|
+
"not ie <= 11",
|
|
33
|
+
"not op_mini all"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* global window, document */
|
|
2
|
+
const getIntercom = () => {
|
|
3
|
+
try {
|
|
4
|
+
return window.parent.Intercom;
|
|
5
|
+
} catch (err) {}
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const enable = () => {
|
|
9
|
+
const Intercom = getIntercom();
|
|
10
|
+
if (!Intercom) return;
|
|
11
|
+
|
|
12
|
+
if (Intercom.ul_wasVisible) {
|
|
13
|
+
Intercom('update', { hide_default_launcher: false });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
delete Intercom.ul_wasVisible;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const disable = () => {
|
|
20
|
+
const Intercom = getIntercom();
|
|
21
|
+
if (!Intercom) return;
|
|
22
|
+
|
|
23
|
+
Intercom.ul_wasVisible = !!document.querySelector('iframe.intercom-launcher-frame');
|
|
24
|
+
|
|
25
|
+
if (Intercom.ul_wasVisible) {
|
|
26
|
+
Intercom('update', { hide_default_launcher: true });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Various modes that the app can be in. For now test is the
|
|
3
|
+
* only one that matters
|
|
4
|
+
* @type {Object}
|
|
5
|
+
*/
|
|
6
|
+
export const APP_MODES = {
|
|
7
|
+
test: 'test',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const PAGE_URL_EVENT_NAME = 'pageUrl';
|
|
11
|
+
|
|
12
|
+
export const HEADERS = {
|
|
13
|
+
ENVIRONMENT_ID: 'x-ul-environment-id',
|
|
14
|
+
PARTNER_ANONYMOUS_ID: 'x-ul-anonymous-id',
|
|
15
|
+
USER_ID: 'x-ul-user-id',
|
|
16
|
+
VISITOR_ID: 'x-ul-visitor-id',
|
|
17
|
+
INSTALLATION_METHOD: 'x-ul-installation-method',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const INSTALLATION_METHOD = {
|
|
21
|
+
NPM: 'web-npm',
|
|
22
|
+
GTM: 'web-gtm',
|
|
23
|
+
SEGMENT: 'web-segment',
|
|
24
|
+
SNIPPET: 'web-snippet',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const CSS_CONSTANTS = {
|
|
28
|
+
CUSTOM_STYLE_TAG_ID: 'ul-custom-style',
|
|
29
|
+
CARD_CONTAINER_CLASS: 'ul-card__container',
|
|
30
|
+
VIDEO_CARD_CLASS: 'ul-card--video',
|
|
31
|
+
CLOSE_CONTAINER_CLASS: 'close-container',
|
|
32
|
+
CLOSE_BUTTON_CLASS: 'close-btn',
|
|
33
|
+
LIKERT_NUMBER_CLASS: 'likert-number',
|
|
34
|
+
NPS_NUMBER_CLASS: 'nps-number',
|
|
35
|
+
CHOICE_CLASS: 'choice',
|
|
36
|
+
CHOICE_LABEL_CLASS: 'select-label',
|
|
37
|
+
CHOICE_CHECKBOX_CLASS: 'select-checkbox',
|
|
38
|
+
CHOICE_RADIO_CLASS: 'select-radio',
|
|
39
|
+
CHOICE_GROUP_CLASS: 'ul-card__choices',
|
|
40
|
+
OPEN_TEXT_INPUT: 'ul-card-text__input',
|
|
41
|
+
DESKTOP_SUFFIX: '--desktop',
|
|
42
|
+
MOBILE_SUFFIX: '--mobile',
|
|
43
|
+
QUESTION_HEADER_CLASS: 'ul-question',
|
|
44
|
+
CAPTION_CLASS: 'ul-caption',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const EXPERIMENT_FLAGS = {
|
|
48
|
+
SURVEY_MOBILE_STYLING: 'survey-mobile-styling',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const MOBILE_MAX_WIDTH = 500;
|
|
52
|
+
|
|
53
|
+
export const getClasses = (baseClass, useMobileStyling) => {
|
|
54
|
+
const suffix = useMobileStyling ? CSS_CONSTANTS.MOBILE_SUFFIX : CSS_CONSTANTS.DESKTOP_SUFFIX;
|
|
55
|
+
|
|
56
|
+
return [baseClass + suffix, baseClass];
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const MOBILE_PLATFORM_HEADERS = ['ios', 'android'];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class Deferred {
|
|
2
|
+
constructor(payload) {
|
|
3
|
+
this.promise = new Promise((resolve, reject) => {
|
|
4
|
+
this.payload = payload;
|
|
5
|
+
this.reject = reject;
|
|
6
|
+
this.resolve = resolve;
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
resolveRequest(result) {
|
|
11
|
+
this.resolve(result);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default Deferred;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*global UserLeap*/
|
|
2
|
+
// singleton event emitter instance for UserLeap web events
|
|
3
|
+
// provides basic functionality such as subcribing to and emitting events
|
|
4
|
+
class ULEventEmitter {
|
|
5
|
+
constructor() {
|
|
6
|
+
if (!ULEventEmitter.instance) {
|
|
7
|
+
this._events = {};
|
|
8
|
+
ULEventEmitter.instance = this;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return ULEventEmitter.instance;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
subscribe(name, listener) {
|
|
15
|
+
if (!name || !listener) return;
|
|
16
|
+
if (!this._events[name]) this._events[name] = [];
|
|
17
|
+
this._events[name].push(listener);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
removeListener(name, listenerToRemove) {
|
|
21
|
+
if (!name || !listenerToRemove) return;
|
|
22
|
+
if (!this._events[name]) {
|
|
23
|
+
if (UserLeap.debugMode) console.log(`[DEBUG] ULEventEmitter: Can't remove a listener. Event "${name}" doesn't exist.`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const filterListeners = (listener) => listener !== listenerToRemove;
|
|
27
|
+
this._events[name] = this._events[name].filter(filterListeners);
|
|
28
|
+
}
|
|
29
|
+
removeAllListeners() {
|
|
30
|
+
Object.keys(this._events).map((key) => {
|
|
31
|
+
this._events[key] = [];
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
emit(name, data) {
|
|
36
|
+
if (!this._events[name] || this._events[name].length == 0) {
|
|
37
|
+
if (UserLeap.debugMode) console.log(`[DEBUG] ULEventEmitter: No listener registered for event "${name}".`, data);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const fireCallbacks = (callback) => {
|
|
42
|
+
callback(data);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
this._events[name].forEach(fireCallbacks);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const instance = new ULEventEmitter();
|
|
50
|
+
Object.freeze(instance);
|
|
51
|
+
|
|
52
|
+
export default instance;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/*global fetch */
|
|
2
|
+
import 'whatwg-fetch';
|
|
3
|
+
import { HEADERS, INSTALLATION_METHOD } from './constants';
|
|
4
|
+
import Deferred from './deferred';
|
|
5
|
+
const { delay, NETWORK_CONFIG } = require('./networkHelper');
|
|
6
|
+
|
|
7
|
+
let killswitch = false;
|
|
8
|
+
let killswitchReason = '';
|
|
9
|
+
let isRateLimited = false;
|
|
10
|
+
let pendingRequestQueue = [];
|
|
11
|
+
|
|
12
|
+
function getInstallationMethodHeader(Sprig) {
|
|
13
|
+
if (Sprig._config && Sprig._config.installationMethod) return Sprig._config.installationMethod;
|
|
14
|
+
if (Sprig._gtm) return INSTALLATION_METHOD.GTM;
|
|
15
|
+
if (Sprig._segment) return INSTALLATION_METHOD.SEGMENT;
|
|
16
|
+
return INSTALLATION_METHOD.SNIPPET;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function killNetworkRequests(reason) {
|
|
20
|
+
killswitch = true;
|
|
21
|
+
killswitchReason = reason;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getHttpHeaders(Sprig = {}) {
|
|
25
|
+
const headers = {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
'userleap-platform': 'web',
|
|
28
|
+
'x-ul-sdk-version': '2.14.0', //version here doesn't matter. it is string-replaced when compiled
|
|
29
|
+
};
|
|
30
|
+
if (Sprig.envId) headers[HEADERS.ENVIRONMENT_ID] = Sprig.envId;
|
|
31
|
+
if (Sprig.token) headers['Authorization'] = 'Bearer ' + Sprig.token;
|
|
32
|
+
if (Sprig.userId) headers[HEADERS.USER_ID] = Sprig.userId;
|
|
33
|
+
if (Sprig.visitorId) headers[HEADERS.VISITOR_ID] = Sprig.visitorId;
|
|
34
|
+
if (Sprig.partnerAnonymousId) headers[HEADERS.PARTNER_ANONYMOUS_ID] = Sprig.partnerAnonymousId;
|
|
35
|
+
if (Sprig.mobileHeadersJSON) {
|
|
36
|
+
const mobileHeaders = JSON.parse(Sprig.mobileHeadersJSON);
|
|
37
|
+
Object.assign(headers, mobileHeaders);
|
|
38
|
+
}
|
|
39
|
+
headers[HEADERS.INSTALLATION_METHOD] = getInstallationMethodHeader(Sprig);
|
|
40
|
+
if (Sprig.locale) headers['accept-language'] = Sprig.locale; // custom set locale overrides original header locales
|
|
41
|
+
|
|
42
|
+
return headers;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function dropOrQueueRequest(shouldDropOnRateLimit, isRateLimitRetry, requestInfo) {
|
|
46
|
+
if (shouldDropOnRateLimit) {
|
|
47
|
+
return { status: 429 };
|
|
48
|
+
} else {
|
|
49
|
+
const deferredRequest = new Deferred(requestInfo);
|
|
50
|
+
pendingRequestQueue.push(deferredRequest);
|
|
51
|
+
return deferredRequest.promise;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function ulFetch(url, options, attempt = 0, shouldDropOnRateLimit = false, shouldRetryRequest = false) {
|
|
56
|
+
// drop or queue request based on the current rate limit status
|
|
57
|
+
const requestInfo = { url, options, attempt, shouldDropOnRateLimit };
|
|
58
|
+
if (isRateLimited && !shouldRetryRequest) {
|
|
59
|
+
return dropOrQueueRequest(shouldDropOnRateLimit, shouldRetryRequest, requestInfo);
|
|
60
|
+
}
|
|
61
|
+
const killswitchResponse = { ok: false, reportError: false };
|
|
62
|
+
if (killswitch) {
|
|
63
|
+
console.log(`UserLeap - ${killswitchReason}`);
|
|
64
|
+
return killswitchResponse;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
options.headers = Object.assign(getHttpHeaders(), options.headers);
|
|
69
|
+
const result = await fetch(url, options);
|
|
70
|
+
if (result.status === 429) {
|
|
71
|
+
// retry if rate limit is not in effect yet and the request is not droppable
|
|
72
|
+
const shouldRetryCurrentRequest = (!isRateLimited && !shouldDropOnRateLimit) || shouldRetryRequest;
|
|
73
|
+
if (shouldRetryCurrentRequest) {
|
|
74
|
+
isRateLimited = true;
|
|
75
|
+
const rateLimitResetTime = result.headers.has('ratelimit-reset')
|
|
76
|
+
? Number(result.headers.get('ratelimit-reset'))
|
|
77
|
+
: NETWORK_CONFIG.RATELIMIT_RESET_DEFAULT;
|
|
78
|
+
return delay(rateLimitResetTime * 1000).then(async () => {
|
|
79
|
+
return ulFetch(url, options, 0, shouldDropOnRateLimit, true);
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
return dropOrQueueRequest(shouldDropOnRateLimit, false, requestInfo);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
isRateLimited = false;
|
|
86
|
+
if (pendingRequestQueue.length) {
|
|
87
|
+
pendingRequestQueue.map((deferredRequest) => {
|
|
88
|
+
const { url, options, attempt, shouldDropOnRateLimit } = deferredRequest.payload;
|
|
89
|
+
ulFetch(url, options, attempt, shouldDropOnRateLimit).then((result) => {
|
|
90
|
+
deferredRequest.resolveRequest(result);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
pendingRequestQueue = [];
|
|
94
|
+
}
|
|
95
|
+
if (result.ok) {
|
|
96
|
+
if (result.status === 249) {
|
|
97
|
+
killNetworkRequests();
|
|
98
|
+
return killswitchResponse;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const responseText = await result.text();
|
|
102
|
+
try {
|
|
103
|
+
if (responseText && responseText !== 'OK') {
|
|
104
|
+
result.json = JSON.parse(responseText);
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
} catch (err) {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
reportError: true,
|
|
111
|
+
error: new Error(`failed parsing response json for ${url} - ${responseText}`),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
} catch (err) {
|
|
117
|
+
//retry
|
|
118
|
+
const newAttempt = attempt + 1;
|
|
119
|
+
if (newAttempt > 2) {
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
reportError: false,
|
|
123
|
+
error: err,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return delay(newAttempt * 1000).then(() => {
|
|
127
|
+
return ulFetch(url, options, newAttempt);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get true or false for whether to direct embed from platform headers
|
|
3
|
+
* @param {Object} [options = {}]
|
|
4
|
+
* @return {Object} User meta information
|
|
5
|
+
*/
|
|
6
|
+
export default function shouldDirectEmbed({ 'userleap-platform': platform }) {
|
|
7
|
+
return platform !== 'web';
|
|
8
|
+
}
|
package/shared/tool.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// for shared functions across the sdk
|
|
2
|
+
|
|
3
|
+
import { CSS_CONSTANTS } from './constants';
|
|
4
|
+
|
|
5
|
+
export const overrideStyles = (document, styleString) => {
|
|
6
|
+
const overrideStyles = document.createElement('style');
|
|
7
|
+
overrideStyles.type = 'text/css';
|
|
8
|
+
overrideStyles.textContent = styleString;
|
|
9
|
+
overrideStyles.id = CSS_CONSTANTS.CUSTOM_STYLE_TAG_ID;
|
|
10
|
+
document.head.appendChild(overrideStyles);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const calculateFrameHeight = (document) => {
|
|
14
|
+
const container = document.querySelector(`.${CSS_CONSTANTS.CARD_CONTAINER_CLASS}`);
|
|
15
|
+
const containerHeight = container ? container.scrollHeight : 600;
|
|
16
|
+
const whiteSpace =
|
|
17
|
+
container && container.parentElement ? container.parentElement.clientHeight - container.clientHeight : 0;
|
|
18
|
+
return containerHeight + whiteSpace;
|
|
19
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// UserLeap event constants
|
|
2
|
+
// event convention
|
|
3
|
+
// event name: descriptive name of the event that happened
|
|
4
|
+
// payload: {
|
|
5
|
+
// name: 'event name',
|
|
6
|
+
// descriptiveDataName: data
|
|
7
|
+
// }
|
|
8
|
+
export const ulEvents = {
|
|
9
|
+
// passing into controller
|
|
10
|
+
SURVEY_LIFE_CYCLE: 'survey.lifeCycle',
|
|
11
|
+
SURVEY_DIMENSIONS: 'survey.dimensions',
|
|
12
|
+
SURVEY_HEIGHT: 'survey.height',
|
|
13
|
+
SURVEY_PRESENTED: 'survey.presented',
|
|
14
|
+
SURVEY_APPEARED: 'survey.appeared',
|
|
15
|
+
SURVEY_FADING_OUT: 'survey.fadingOut',
|
|
16
|
+
SURVEY_WILL_CLOSE: 'survey.willClose',
|
|
17
|
+
SURVEY_CLOSED: 'survey.closed',
|
|
18
|
+
SDK_READY: 'sdk.ready',
|
|
19
|
+
// passing into view
|
|
20
|
+
CLOSE_SURVEY_ON_OVERLAY_CLICK: 'close.survey.overlayClick',
|
|
21
|
+
VISITOR_ID_UPDATED: 'visitor.id.updated',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// events not emitted externally and not in the documentation
|
|
25
|
+
export const internalEvents = {
|
|
26
|
+
name: {
|
|
27
|
+
VERIFY_VIEW_VERSION: 'verify.view.version',
|
|
28
|
+
},
|
|
29
|
+
data: {
|
|
30
|
+
VIEW_VERSION: 'view.version',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const DISMISS_REASONS = {
|
|
35
|
+
CLOSED: 'close.click', // user clicked the close button
|
|
36
|
+
COMPLETE: 'survey.completed', // user answered all questions
|
|
37
|
+
PAGE_CHANGE: 'page.change', // productConfig.dismissOnPageChange == true and we detected a page change (excludes hash/query param changes)
|
|
38
|
+
API: 'api', // JS called Sprig('dismissActiveSurvey')
|
|
39
|
+
OVERRIDE: 'override', // JS called Sprig('displaySurvey', SURVEY_ID)
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const SURVEY_STATE = {
|
|
43
|
+
READY: 'ready',
|
|
44
|
+
NO_SURVEY: 'no survey',
|
|
45
|
+
};
|