fiftyone.pipeline.core 4.4.104 → 4.4.105
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.
|
@@ -20,6 +20,9 @@ fiftyoneDegreesManager = function() {
|
|
|
20
20
|
// Set to true when the JSON object is complete.
|
|
21
21
|
var completed = false;
|
|
22
22
|
|
|
23
|
+
// Set to true when the `catchError` is called.
|
|
24
|
+
let failed = false;
|
|
25
|
+
|
|
23
26
|
// changeFuncs is an array of functions. When onChange is called and passed
|
|
24
27
|
// a function, the function is registered and is called when processing is
|
|
25
28
|
// complete.
|
|
@@ -79,13 +82,13 @@ fiftyoneDegreesManager = function() {
|
|
|
79
82
|
// and return the data as key value pairs. This method is needed to extract
|
|
80
83
|
// stored values for inclusion in the GET or POST request for situations
|
|
81
84
|
// where CORS will prevent them from being sent to third parties.
|
|
82
|
-
var getFodSavedValues = function(){
|
|
85
|
+
var getFodSavedValues = function() {
|
|
83
86
|
let fodValues = {};
|
|
84
87
|
{{#_enableCookies}}
|
|
85
88
|
{
|
|
86
89
|
let keyValuePairs = document.cookie.split(/; */);
|
|
87
90
|
for(let nextPair of keyValuePairs) {
|
|
88
|
-
let firstEqualsLocation = nextPair.indexOf(
|
|
91
|
+
let firstEqualsLocation = nextPair.indexOf("=");
|
|
89
92
|
let name = nextPair.substring(0, firstEqualsLocation);
|
|
90
93
|
if(startsWith(name, "51D_")){
|
|
91
94
|
let value = nextPair.substring(firstEqualsLocation+1);
|
|
@@ -112,17 +115,17 @@ fiftyoneDegreesManager = function() {
|
|
|
112
115
|
|
|
113
116
|
// Extract key value pairs from the '51D_' prefixed values and concatenates
|
|
114
117
|
// them to form a query string for the subsequent json refresh.
|
|
115
|
-
var getParametersFromStorage = function(){
|
|
118
|
+
var getParametersFromStorage = function() {
|
|
116
119
|
var fodValues = getFodSavedValues();
|
|
117
120
|
var keyValuePairs = [];
|
|
118
121
|
for (var key in fodValues) {
|
|
119
122
|
if (fodValues.hasOwnProperty(key)) {
|
|
120
123
|
// Url encode the value.
|
|
121
|
-
// This is done to ensure that invalid characters (e.g. = chars at the end of
|
|
124
|
+
// This is done to ensure that invalid characters (e.g. = chars at the end of
|
|
122
125
|
// base 64 encoded strings) reach the server intact.
|
|
123
|
-
// The server will automatically decode the value before passing it into the
|
|
126
|
+
// The server will automatically decode the value before passing it into the
|
|
124
127
|
// Pipeline API.
|
|
125
|
-
keyValuePairs.push(key+"="+encodeURIComponent(fodValues[key]));
|
|
128
|
+
keyValuePairs.push(key + "=" + encodeURIComponent(fodValues[key]));
|
|
126
129
|
}
|
|
127
130
|
}
|
|
128
131
|
return keyValuePairs;
|
|
@@ -133,21 +136,21 @@ fiftyoneDegreesManager = function() {
|
|
|
133
136
|
// returned rather than letting an exception occur.
|
|
134
137
|
var getFromJson = function(key, allowObjects, allowBooleans) {
|
|
135
138
|
var result = undefined;
|
|
136
|
-
if(typeof allowObjects ===
|
|
137
|
-
if(typeof allowBooleans ===
|
|
139
|
+
if (typeof allowObjects === "undefined") { allowObjects = false; }
|
|
140
|
+
if (typeof allowBooleans === "undefined") { allowBooleans = false; }
|
|
138
141
|
|
|
139
|
-
if (typeof(key) ===
|
|
142
|
+
if (typeof(key) === "string") {
|
|
140
143
|
var functions = json;
|
|
141
144
|
var segments = key.split('.');
|
|
142
145
|
var i = 0;
|
|
143
146
|
while (functions !== undefined && i < segments.length) {
|
|
144
147
|
functions = functions[segments[i++]];
|
|
145
148
|
}
|
|
146
|
-
if (typeof
|
|
149
|
+
if (typeof functions === "string") {
|
|
147
150
|
result = functions;
|
|
148
|
-
} else if (allowBooleans && typeof
|
|
151
|
+
} else if (allowBooleans && typeof functions === "boolean") {
|
|
149
152
|
result = functions;
|
|
150
|
-
} else if (allowObjects && typeof functions ===
|
|
153
|
+
} else if (allowObjects && typeof functions === "object" && functions !== null) {
|
|
151
154
|
result = functions;
|
|
152
155
|
}
|
|
153
156
|
}
|
|
@@ -183,7 +186,6 @@ fiftyoneDegreesManager = function() {
|
|
|
183
186
|
var executeCallback = true;
|
|
184
187
|
var started = 0;
|
|
185
188
|
var cached = 0;
|
|
186
|
-
var cachedResponse = undefined;
|
|
187
189
|
var toProcess = 0;
|
|
188
190
|
|
|
189
191
|
// If there is no cached response and there are JavaScript code snippets
|
|
@@ -195,87 +197,113 @@ fiftyoneDegreesManager = function() {
|
|
|
195
197
|
let session51DataPrefix = sessionKey + "_data_";
|
|
196
198
|
let sessionSetPatch = 'window.sessionStorage["' + session51DataPrefix + '$3$6"]=$4$7';
|
|
197
199
|
{{/_enableCookies}}
|
|
198
|
-
|
|
200
|
+
|
|
199
201
|
// Execute each of the JavaScript property code snippets using the
|
|
200
202
|
// index of the value to access the value to avoid problems with
|
|
201
203
|
// JavaScript returning erroneous values.
|
|
202
204
|
for (var index = 0; index < jsProperties.length; index++) {
|
|
203
205
|
var name = jsProperties[index];
|
|
204
|
-
if (jsPropertiesStarted.indexOf(name)
|
|
205
|
-
|
|
206
|
+
if (jsPropertiesStarted.indexOf(name) !== -1) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
var body = getFromJson(name);
|
|
210
|
+
|
|
211
|
+
// If there is a body then this property should be processed.
|
|
212
|
+
if (body) {
|
|
213
|
+
toProcess++;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
var isCached = sessionStorage && sessionStorage.getItem(sessionKey + "_property_" + name);
|
|
206
217
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
// If the property has already been processed then skip it.
|
|
219
|
+
if (isCached) {
|
|
220
|
+
cached++;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Create new function bound to this instance and execute it.
|
|
225
|
+
// This is needed to ensure the scope of the function is
|
|
226
|
+
// associated with this instance if any members are altered or
|
|
227
|
+
// added. Avoids global scoped variables.
|
|
228
|
+
|
|
229
|
+
var delay = getFromJson(name + 'delayexecution', false, true);
|
|
230
|
+
|
|
231
|
+
if (
|
|
232
|
+
(ignoreDelayFlag || delay === undefined || delay === false) &&
|
|
233
|
+
typeof body === "string" &&
|
|
234
|
+
body.length
|
|
235
|
+
) {
|
|
236
|
+
var func = undefined;
|
|
237
|
+
var searchString = '// 51D replace this comment with callback function.';
|
|
238
|
+
completed = false;
|
|
239
|
+
jsPropertiesStarted.push(name);
|
|
240
|
+
started++;
|
|
241
|
+
|
|
242
|
+
{{^_enableCookies}}
|
|
243
|
+
body = body.replaceAll(valueSetPrefix, sessionSetPatch);
|
|
244
|
+
{{/_enableCookies}}
|
|
245
|
+
|
|
246
|
+
if (body.indexOf(searchString) !== -1){
|
|
247
|
+
callbackCounter++;
|
|
248
|
+
body = body.replace(/\/\/ 51D replace this comment with callback function./g, 'callbackFunc(resolveFunc, rejectFunc);');
|
|
249
|
+
func = new Function('callbackFunc', 'resolveFunc', 'rejectFunc',
|
|
250
|
+
"try {\n" +
|
|
251
|
+
body + "\n" +
|
|
252
|
+
"} catch (err) {\n" +
|
|
253
|
+
"console.log(err);" +
|
|
254
|
+
"}"
|
|
255
|
+
);
|
|
256
|
+
func(completedCallback, resolve, reject);
|
|
257
|
+
executeCallback = false;
|
|
258
|
+
} else {
|
|
259
|
+
func = new Function(
|
|
260
|
+
"try {\n" +
|
|
261
|
+
body + "\n" +
|
|
262
|
+
"} catch (err) {\n" +
|
|
263
|
+
"console.log(err);" +
|
|
264
|
+
"}"
|
|
265
|
+
);
|
|
266
|
+
func();
|
|
210
267
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
var
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
started++;
|
|
228
|
-
|
|
229
|
-
{{^_enableCookies}}
|
|
230
|
-
body = body.replaceAll(valueSetPrefix, sessionSetPatch);
|
|
231
|
-
{{/_enableCookies}}
|
|
232
|
-
|
|
233
|
-
if (body.indexOf(searchString) !== -1){
|
|
234
|
-
callbackCounter++;
|
|
235
|
-
body = body.replace(/\/\/ 51D replace this comment with callback function./g, 'callbackFunc(resolveFunc, rejectFunc);');
|
|
236
|
-
func = new Function('callbackFunc', 'resolveFunc', 'rejectFunc',
|
|
237
|
-
"try {\n" +
|
|
238
|
-
body + "\n" +
|
|
239
|
-
"} catch (err) {\n" +
|
|
240
|
-
"console.log(err);" +
|
|
241
|
-
"}"
|
|
242
|
-
);
|
|
243
|
-
func(completedCallback, resolve, reject);
|
|
244
|
-
executeCallback = false;
|
|
245
|
-
} else {
|
|
246
|
-
func = new Function(
|
|
247
|
-
"try {\n" +
|
|
248
|
-
body + "\n" +
|
|
249
|
-
"} catch (err) {\n" +
|
|
250
|
-
"console.log(err);" +
|
|
251
|
-
"}"
|
|
252
|
-
);
|
|
253
|
-
func();
|
|
254
|
-
}
|
|
255
|
-
if (sessionStorage) {
|
|
256
|
-
sessionStorage.setItem(sessionKey + "_property_" + name, true)
|
|
268
|
+
|
|
269
|
+
if (sessionStorage) {
|
|
270
|
+
sessionStorage.setItem(sessionKey + "_property_" + name, true)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// If the property is `javascripthardwareprofile` then check if the
|
|
274
|
+
// profile has been set. If not then remove current property from
|
|
275
|
+
// the list of properties that have started to prevent the
|
|
276
|
+
// 2nd request from being made.
|
|
277
|
+
if (name === "device.javascripthardwareprofile") {
|
|
278
|
+
var hrw = getFodSavedValues();
|
|
279
|
+
if (hrw && !hrw["51D_ProfileIds"]) {
|
|
280
|
+
// find and remove name from jsPropertiesStarted
|
|
281
|
+
var propIndex = jsPropertiesStarted.indexOf(name);
|
|
282
|
+
if (propIndex > -1) {
|
|
283
|
+
jsPropertiesStarted.splice(index, 1);
|
|
257
284
|
}
|
|
285
|
+
started--;
|
|
286
|
+
toProcess--;
|
|
258
287
|
}
|
|
259
|
-
} else {
|
|
260
|
-
cached++;
|
|
261
288
|
}
|
|
262
289
|
}
|
|
263
290
|
}
|
|
264
291
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
292
|
+
|
|
293
|
+
if ((cached === toProcess || started === 0) && sessionStorage) {
|
|
294
|
+
var cachedResponse = sessionStorage.getItem(sessionKey);
|
|
295
|
+
if (cachedResponse) {
|
|
296
|
+
loadJSON(resolve, reject, cachedResponse);
|
|
297
|
+
executeCallback = false;
|
|
272
298
|
}
|
|
273
299
|
}
|
|
274
300
|
|
|
275
301
|
if (started === 0) {
|
|
276
302
|
executeCallback = false;
|
|
303
|
+
failed = false;
|
|
277
304
|
completed = true;
|
|
278
305
|
}
|
|
306
|
+
|
|
279
307
|
if (executeCallback) {
|
|
280
308
|
callbackCounter = 1;
|
|
281
309
|
completedCallback(resolve, reject);
|
|
@@ -343,7 +371,13 @@ fiftyoneDegreesManager = function() {
|
|
|
343
371
|
{{#_updateEnabled}}
|
|
344
372
|
// Process the response as json and call the resolve method.
|
|
345
373
|
var loadJSON = function(resolve, reject, responseText) {
|
|
346
|
-
|
|
374
|
+
try {
|
|
375
|
+
json = JSON.parse(responseText);
|
|
376
|
+
} catch(err) {
|
|
377
|
+
clearCache();
|
|
378
|
+
reject(new Error("Invalid JSON - the endpoint is likely setup incorrectly", { cause: err }));
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
347
381
|
|
|
348
382
|
if (hasJSFunctions()) {
|
|
349
383
|
// json updated so fire 'on change' functions
|
|
@@ -352,6 +386,7 @@ fiftyoneDegreesManager = function() {
|
|
|
352
386
|
fireChangeFuncs(json);
|
|
353
387
|
process(resolve, reject);
|
|
354
388
|
} else {
|
|
389
|
+
failed = false;
|
|
355
390
|
completed = true;
|
|
356
391
|
// json updated so fire 'on change' functions
|
|
357
392
|
// This must happen after completed = true in order
|
|
@@ -476,7 +511,28 @@ fiftyoneDegreesManager = function() {
|
|
|
476
511
|
|
|
477
512
|
// Function logs errors, used to 'reject' a promise or for error callbacks.
|
|
478
513
|
var catchError = function(value) {
|
|
479
|
-
|
|
514
|
+
failed = true;
|
|
515
|
+
function errorToDesc(subError) {
|
|
516
|
+
let msg = subError.message;
|
|
517
|
+
if (!msg) {
|
|
518
|
+
msg = String(subError);
|
|
519
|
+
}
|
|
520
|
+
let cause = subError.cause;
|
|
521
|
+
if (cause) {
|
|
522
|
+
msg += '\n--- caused by ---\n';
|
|
523
|
+
msg += errorToDesc(cause);
|
|
524
|
+
}
|
|
525
|
+
return msg;
|
|
526
|
+
}
|
|
527
|
+
let errorDesc = errorToDesc(value);
|
|
528
|
+
console.log(errorDesc);
|
|
529
|
+
|
|
530
|
+
if (json.errors === null || json.errors === undefined) {
|
|
531
|
+
json.errors = [errorDesc];
|
|
532
|
+
} else if (Array.isArray(json.errors)) {
|
|
533
|
+
json.errors.push(errorDesc);
|
|
534
|
+
}
|
|
535
|
+
fireChangeFuncs(json);
|
|
480
536
|
}
|
|
481
537
|
|
|
482
538
|
// Populate this instance of the FOD object with getters to access the
|
|
@@ -588,11 +644,11 @@ fiftyoneDegreesManager = function() {
|
|
|
588
644
|
}
|
|
589
645
|
|
|
590
646
|
{{/_hasDelayedProperties}}
|
|
591
|
-
if(completed){
|
|
647
|
+
if(completed || failed){
|
|
592
648
|
resolve(json);
|
|
593
649
|
}else{
|
|
594
650
|
this.onChange(function(data) {
|
|
595
|
-
if(completed){
|
|
651
|
+
if(completed || failed){
|
|
596
652
|
resolve(data);
|
|
597
653
|
}
|
|
598
654
|
})
|
|
@@ -606,6 +662,7 @@ fiftyoneDegreesManager = function() {
|
|
|
606
662
|
this.promise.then(function(value) {
|
|
607
663
|
// JSON has been updated so replace the current instance.
|
|
608
664
|
update.call(parent, value);
|
|
665
|
+
failed = false;
|
|
609
666
|
completed = true;
|
|
610
667
|
}).catch(catchError);
|
|
611
668
|
{{/_supportsPromises}}
|
|
@@ -6,6 +6,53 @@ Store shared (mustache) templates to be used by the implementation of `JavaScrip
|
|
|
6
6
|
|
|
7
7
|
The [language-independent specification](https://github.com/51Degrees/specifications) describes how JavaScriptBuilderElement is used [here](https://github.com/51Degrees/specifications/blob/36ff732360acb49221dc81237281264dac4eb897/pipeline-specification/pipeline-elements/javascript-builder.md). The mechanics is: javascript file is requested from the server (on-premise web integration) and is created from this mustache template by the JavaScriptBuilderElement. It then collects more evidence, sends it to the server and upon response calls a callback function providing the client with more precise device data.
|
|
8
8
|
|
|
9
|
+
## Cookies -> Session storage Transformation
|
|
10
|
+
|
|
11
|
+
The processJsProperties function in javascript template has a section that uses regex to search the injected JavaScript for any commands that set cookie values to the browser's document object. It then transforms this command so that it sets session storage values instead. This format should be accounted for when writing JavaScript properties.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
### Cookie Transformation regular expression rules
|
|
16
|
+
|
|
17
|
+
#### Valid Format for Cookie Statements:
|
|
18
|
+
- **Cookie Assignment**: The expression should start with `document.cookie = `
|
|
19
|
+
- **Spaces**: Spaces around the first `=` sign are optional
|
|
20
|
+
- **Cookie Name**: The name of the cookie should only contain alphanumeric characters, underscores, and must not have spaces
|
|
21
|
+
- **Assignment with Double Quotes**: The cookie value assignment can use double quotes, and the value should be set programmatically by concatenating a string with a variable or expression
|
|
22
|
+
- **Assignment with Backticks**: The cookie value assignment can use backticks for template literals, and the value can be set programmatically using expressions inside `${}`
|
|
23
|
+
- **No Direct Value Assignment**: Directly setting a value within the string is not allowed; values must be set programmatically
|
|
24
|
+
|
|
25
|
+
#### Regular Expression:
|
|
26
|
+
```javascript
|
|
27
|
+
/document\.cookie\s*=\s*(("([A-Za-z0-9_"\s\+]+)\s*=\s*"\s*\+\s*([^\s};]+))|(`([A-Za-z0-9_]+)\s*=\s*\$\{([^}]+)\}`))/g
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### Valid Examples:
|
|
31
|
+
```javascript
|
|
32
|
+
document.cookie="51D_PropertyName="+"True"; // No spaces around the equals and/or plus sign
|
|
33
|
+
document.cookie = "51D_PropertyName=" + "True"; // Spaces around the equals sign
|
|
34
|
+
document.cookie = "51D_PropertyName=" + screen.height; // Assigning a value using a variable
|
|
35
|
+
document.cookie="51D_PropertyName="+screen.height; // No spaces, variable assignment
|
|
36
|
+
document.cookie=`51D_PropertyName=${btoa(JSON.stringify(value))}` // Using a template literal with an expression
|
|
37
|
+
document.cookie="51D_PropertyName="+profileIds.join("|") // Assigning a value using a joined string of variables
|
|
38
|
+
document.cookie = `51D_PropertyName=${"True"}`; // Using backticks for programmatic value assignment
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### Invalid Examples:
|
|
42
|
+
```javascript
|
|
43
|
+
document.cookie = "51D_PropertyName=True"; // Direct assignment within the string is not allowed
|
|
44
|
+
document.cookie = "51D_PropertyName=" + profileIds.join(" ") // Spaces within the expression are not allowed
|
|
45
|
+
document.cookie = " 51D_PropertyName = " + "True"; // Spaces inside the cookie name are not allowed
|
|
46
|
+
document.cookie = ` 51D_PropertyName =${"True"}`; // Spaces inside the template literal are not allowed
|
|
47
|
+
document.cookie = `51D_PropertyName=START${window.middle}END`; // Concatenating strings directly within template literals is not allowed
|
|
48
|
+
```
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## CSP Considerations
|
|
52
|
+
[Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) is an added layer of security to mitigate cross-site and other types of attacks. CSP limits which 3rd party resources are loaded and what these resources are allowed to do. 51Degrees JavaScript produced from the template is usually such a 3rd party resource when hosted on [51Degrees cloud](https://cloud.51degrees.com/api-docs/index.html). If CSP header specifies [script-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src) it has to list 51Degrees cloud origin as a source and also add 'unsafe-eval' as a source.
|
|
53
|
+
|
|
54
|
+
`'unsafe-eval'` source is needed because the template loads and executes dynamic javascript code snippets relying on JavaScript Function API which is in the eval() family. The snippets are part of the data file and are frequently updated to support latest changes in the browsers. Snippet execution may cause multiple server calls to load more dynamic code (in theory, in practice it usually comes down to a single server call) - thus this code can not be statically included in the template and has to be loaded dynamically as part of the JSON response of the server.
|
|
55
|
+
|
|
9
56
|
## Shipping / Deployment
|
|
10
57
|
|
|
11
58
|
This repo is not a stand-alone package, but is shipped as part of and used by each of the following repositories / packages:
|