-
Notifications
You must be signed in to change notification settings - Fork 66
/
utils.js
129 lines (114 loc) · 3.29 KB
/
utils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import Base64 from 'Base64';
// See http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
export function btoa(s) {
return Base64.btoa(unescape(encodeURIComponent(s)));
}
export function base64URLEncode(s) {
return (
btoa(s)
// eslint-disable-next-line
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_')
);
}
export function clone(obj) {
return JSON.parse(JSON.stringify(obj));
}
// Events emitted in LDClient's initialize method will happen before the consumer
// can register a listener, so defer them to next tick.
export function onNextTick(cb) {
setTimeout(cb, 0);
}
/**
* Wrap a promise to invoke an optional callback upon resolution or rejection.
*
* This function assumes the callback follows the Node.js callback type: (err, value) => void
*
* If a callback is provided:
* - if the promise is resolved, invoke the callback with (null, value)
* - if the promise is rejected, invoke the callback with (error, null)
*
* @param {Promise<any>} promise
* @param {Function} callback
* @returns Promise<any> | undefined
*/
export function wrapPromiseCallback(promise, callback) {
const ret = promise.then(
value => {
if (callback) {
setTimeout(() => {
callback(null, value);
}, 0);
}
return value;
},
error => {
if (callback) {
setTimeout(() => {
callback(error, null);
}, 0);
} else {
return Promise.reject(error);
}
}
);
return !callback ? ret : undefined;
}
/**
* Takes a map of flag keys to values, and returns the more verbose structure used by the
* client stream.
*/
export function transformValuesToVersionedValues(flags) {
const ret = {};
for (const key in flags) {
if (flags.hasOwnProperty(key)) {
ret[key] = { value: flags[key], version: 0 };
}
}
return ret;
}
/**
* Returns an array of event groups each of which can be safely URL-encoded
* without hitting the safe maximum URL length of certain browsers.
*
* @param {number} maxLength maximum URL length targeted
* @param {Array[Object}]} events queue of events to divide
* @returns Array[Array[Object]]
*/
export function chunkUserEventsForUrl(maxLength, events) {
const allEvents = events.slice(0);
const allChunks = [];
let remainingSpace = maxLength;
let chunk;
while (allEvents.length > 0) {
chunk = [];
while (remainingSpace > 0) {
const event = allEvents.shift();
if (!event) {
break;
}
remainingSpace = remainingSpace - base64URLEncode(JSON.stringify(event)).length;
// If we are over the max size, put this one back on the queue
// to try in the next round, unless this event alone is larger
// than the limit, in which case, screw it, and try it anyway.
if (remainingSpace < 0 && chunk.length > 0) {
allEvents.unshift(event);
} else {
chunk.push(event);
}
}
remainingSpace = maxLength;
allChunks.push(chunk);
}
return allChunks;
}
export function getLDUserAgentString() {
return 'JSClient/' + VERSION;
}
export function addLDHeaders(xhr) {
xhr.setRequestHeader('X-LaunchDarkly-User-Agent', getLDUserAgentString());
}
export function extend(...objects) {
return objects.reduce((acc, obj) => ({ ...acc, ...obj }), {});
}