Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
beefproject
GitHub Repository: beefproject/beef
Path: blob/master/core/main/client/lib/platform.js
1154 views
1
/*!
2
* Platform.js
3
* Copyright 2014-2020 Benjamin Tan
4
* Copyright 2011-2013 John-David Dalton
5
* Available under MIT license
6
*/
7
;(function() {
8
'use strict';
9
10
/** Used to determine if values are of the language type `Object`. */
11
var objectTypes = {
12
'function': true,
13
'object': true
14
};
15
16
/** Used as a reference to the global object. */
17
var root = (objectTypes[typeof window] && window) || this;
18
19
/** Backup possible global object. */
20
var oldRoot = root;
21
22
/** Detect free variable `exports`. */
23
var freeExports = objectTypes[typeof exports] && exports;
24
25
/** Detect free variable `module`. */
26
var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
27
28
/** Detect free variable `global` from Node.js or Browserified code and use it as `root`. */
29
var freeGlobal = freeExports && freeModule && typeof global == 'object' && global;
30
if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal || freeGlobal.self === freeGlobal)) {
31
root = freeGlobal;
32
}
33
34
/**
35
* Used as the maximum length of an array-like object.
36
* See the [ES6 spec](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength)
37
* for more details.
38
*/
39
var maxSafeInteger = Math.pow(2, 53) - 1;
40
41
/** Regular expression to detect Opera. */
42
var reOpera = /\bOpera/;
43
44
/** Possible global object. */
45
var thisBinding = this;
46
47
/** Used for native method references. */
48
var objectProto = Object.prototype;
49
50
/** Used to check for own properties of an object. */
51
var hasOwnProperty = objectProto.hasOwnProperty;
52
53
/** Used to resolve the internal `[[Class]]` of values. */
54
var toString = objectProto.toString;
55
56
/*--------------------------------------------------------------------------*/
57
58
/**
59
* Capitalizes a string value.
60
*
61
* @private
62
* @param {string} string The string to capitalize.
63
* @returns {string} The capitalized string.
64
*/
65
function capitalize(string) {
66
string = String(string);
67
return string.charAt(0).toUpperCase() + string.slice(1);
68
}
69
70
/**
71
* A utility function to clean up the OS name.
72
*
73
* @private
74
* @param {string} os The OS name to clean up.
75
* @param {string} [pattern] A `RegExp` pattern matching the OS name.
76
* @param {string} [label] A label for the OS.
77
*/
78
function cleanupOS(os, pattern, label) {
79
// Platform tokens are defined at:
80
// http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx
81
// http://web.archive.org/web/20081122053950/http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx
82
var data = {
83
'10.0': '10',
84
'6.4': '10 Technical Preview',
85
'6.3': '8.1',
86
'6.2': '8',
87
'6.1': 'Server 2008 R2 / 7',
88
'6.0': 'Server 2008 / Vista',
89
'5.2': 'Server 2003 / XP 64-bit',
90
'5.1': 'XP',
91
'5.01': '2000 SP1',
92
'5.0': '2000',
93
'4.0': 'NT',
94
'4.90': 'ME'
95
};
96
// Detect Windows version from platform tokens.
97
if (pattern && label && /^Win/i.test(os) && !/^Windows Phone /i.test(os) &&
98
(data = data[/[\d.]+$/.exec(os)])) {
99
os = 'Windows ' + data;
100
}
101
// Correct character case and cleanup string.
102
os = String(os);
103
104
if (pattern && label) {
105
os = os.replace(RegExp(pattern, 'i'), label);
106
}
107
108
os = format(
109
os.replace(/ ce$/i, ' CE')
110
.replace(/\bhpw/i, 'web')
111
.replace(/\bMacintosh\b/, 'Mac OS')
112
.replace(/_PowerPC\b/i, ' OS')
113
.replace(/\b(OS X) [^ \d]+/i, '$1')
114
.replace(/\bMac (OS X)\b/, '$1')
115
.replace(/\/(\d)/, ' $1')
116
.replace(/_/g, '.')
117
.replace(/(?: BePC|[ .]*fc[ \d.]+)$/i, '')
118
.replace(/\bx86\.64\b/gi, 'x86_64')
119
.replace(/\b(Windows Phone) OS\b/, '$1')
120
.replace(/\b(Chrome OS \w+) [\d.]+\b/, '$1')
121
.split(' on ')[0]
122
);
123
124
return os;
125
}
126
127
/**
128
* An iteration utility for arrays and objects.
129
*
130
* @private
131
* @param {Array|Object} object The object to iterate over.
132
* @param {Function} callback The function called per iteration.
133
*/
134
function each(object, callback) {
135
var index = -1,
136
length = object ? object.length : 0;
137
138
if (typeof length == 'number' && length > -1 && length <= maxSafeInteger) {
139
while (++index < length) {
140
callback(object[index], index, object);
141
}
142
} else {
143
forOwn(object, callback);
144
}
145
}
146
147
/**
148
* Trim and conditionally capitalize string values.
149
*
150
* @private
151
* @param {string} string The string to format.
152
* @returns {string} The formatted string.
153
*/
154
function format(string) {
155
string = trim(string);
156
return /^(?:webOS|i(?:OS|P))/.test(string)
157
? string
158
: capitalize(string);
159
}
160
161
/**
162
* Iterates over an object's own properties, executing the `callback` for each.
163
*
164
* @private
165
* @param {Object} object The object to iterate over.
166
* @param {Function} callback The function executed per own property.
167
*/
168
function forOwn(object, callback) {
169
for (var key in object) {
170
if (hasOwnProperty.call(object, key)) {
171
callback(object[key], key, object);
172
}
173
}
174
}
175
176
/**
177
* Gets the internal `[[Class]]` of a value.
178
*
179
* @private
180
* @param {*} value The value.
181
* @returns {string} The `[[Class]]`.
182
*/
183
function getClassOf(value) {
184
return value == null
185
? capitalize(value)
186
: toString.call(value).slice(8, -1);
187
}
188
189
/**
190
* Host objects can return type values that are different from their actual
191
* data type. The objects we are concerned with usually return non-primitive
192
* types of "object", "function", or "unknown".
193
*
194
* @private
195
* @param {*} object The owner of the property.
196
* @param {string} property The property to check.
197
* @returns {boolean} Returns `true` if the property value is a non-primitive, else `false`.
198
*/
199
function isHostType(object, property) {
200
var type = object != null ? typeof object[property] : 'number';
201
return !/^(?:boolean|number|string|undefined)$/.test(type) &&
202
(type == 'object' ? !!object[property] : true);
203
}
204
205
/**
206
* Prepares a string for use in a `RegExp` by making hyphens and spaces optional.
207
*
208
* @private
209
* @param {string} string The string to qualify.
210
* @returns {string} The qualified string.
211
*/
212
function qualify(string) {
213
return String(string).replace(/([ -])(?!$)/g, '$1?');
214
}
215
216
/**
217
* A bare-bones `Array#reduce` like utility function.
218
*
219
* @private
220
* @param {Array} array The array to iterate over.
221
* @param {Function} callback The function called per iteration.
222
* @returns {*} The accumulated result.
223
*/
224
function reduce(array, callback) {
225
var accumulator = null;
226
each(array, function(value, index) {
227
accumulator = callback(accumulator, value, index, array);
228
});
229
return accumulator;
230
}
231
232
/**
233
* Removes leading and trailing whitespace from a string.
234
*
235
* @private
236
* @param {string} string The string to trim.
237
* @returns {string} The trimmed string.
238
*/
239
function trim(string) {
240
return String(string).replace(/^ +| +$/g, '');
241
}
242
243
/*--------------------------------------------------------------------------*/
244
245
/**
246
* Creates a new platform object.
247
*
248
* @memberOf platform
249
* @param {Object|string} [ua=navigator.userAgent] The user agent string or
250
* context object.
251
* @returns {Object} A platform object.
252
*/
253
function parse(ua) {
254
255
/** The environment context object. */
256
var context = root;
257
258
/** Used to flag when a custom context is provided. */
259
var isCustomContext = ua && typeof ua == 'object' && getClassOf(ua) != 'String';
260
261
// Juggle arguments.
262
if (isCustomContext) {
263
context = ua;
264
ua = null;
265
}
266
267
/** Browser navigator object. */
268
var nav = context.navigator || {};
269
270
/** Browser user agent string. */
271
var userAgent = nav.userAgent || '';
272
273
ua || (ua = userAgent);
274
275
/** Used to flag when `thisBinding` is the [ModuleScope]. */
276
var isModuleScope = isCustomContext || thisBinding == oldRoot;
277
278
/** Used to detect if browser is like Chrome. */
279
var likeChrome = isCustomContext
280
? !!nav.likeChrome
281
: /\bChrome\b/.test(ua) && !/internal|\n/i.test(toString.toString());
282
283
/** Internal `[[Class]]` value shortcuts. */
284
var objectClass = 'Object',
285
airRuntimeClass = isCustomContext ? objectClass : 'ScriptBridgingProxyObject',
286
enviroClass = isCustomContext ? objectClass : 'Environment',
287
javaClass = (isCustomContext && context.java) ? 'JavaPackage' : getClassOf(context.java),
288
phantomClass = isCustomContext ? objectClass : 'RuntimeObject';
289
290
/** Detect Java environments. */
291
var java = /\bJava/.test(javaClass) && context.java;
292
293
/** Detect Rhino. */
294
var rhino = java && getClassOf(context.environment) == enviroClass;
295
296
/** A character to represent alpha. */
297
var alpha = java ? 'a' : '\u03b1';
298
299
/** A character to represent beta. */
300
var beta = java ? 'b' : '\u03b2';
301
302
/** Browser document object. */
303
var doc = context.document || {};
304
305
/**
306
* Detect Opera browser (Presto-based).
307
* http://www.howtocreate.co.uk/operaStuff/operaObject.html
308
* http://dev.opera.com/articles/view/opera-mini-web-content-authoring-guidelines/#operamini
309
*/
310
var opera = context.operamini || context.opera;
311
312
/** Opera `[[Class]]`. */
313
var operaClass = reOpera.test(operaClass = (isCustomContext && opera) ? opera['[[Class]]'] : getClassOf(opera))
314
? operaClass
315
: (opera = null);
316
317
/*------------------------------------------------------------------------*/
318
319
/** Temporary variable used over the script's lifetime. */
320
var data;
321
322
/** The CPU architecture. */
323
var arch = ua;
324
325
/** Platform description array. */
326
var description = [];
327
328
/** Platform alpha/beta indicator. */
329
var prerelease = null;
330
331
/** A flag to indicate that environment features should be used to resolve the platform. */
332
var useFeatures = ua == userAgent;
333
334
/** The browser/environment version. */
335
var version = useFeatures && opera && typeof opera.version == 'function' && opera.version();
336
337
/** A flag to indicate if the OS ends with "/ Version" */
338
var isSpecialCasedOS;
339
340
/* Detectable layout engines (order is important). */
341
var layout = getLayout([
342
{ 'label': 'EdgeHTML', 'pattern': 'Edge' },
343
'Trident',
344
{ 'label': 'WebKit', 'pattern': 'AppleWebKit' },
345
'iCab',
346
'Presto',
347
'NetFront',
348
'Tasman',
349
'KHTML',
350
'Gecko'
351
]);
352
353
/* Detectable browser names (order is important). */
354
var name = getName([
355
'Adobe AIR',
356
'Arora',
357
'Avant Browser',
358
'Breach',
359
'Camino',
360
'Electron',
361
'Epiphany',
362
'Fennec',
363
'Flock',
364
'Galeon',
365
'GreenBrowser',
366
'iCab',
367
'Iceweasel',
368
'K-Meleon',
369
'Konqueror',
370
'Lunascape',
371
'Maxthon',
372
{ 'label': 'Microsoft Edge', 'pattern': '(?:Edge|Edg|EdgA|EdgiOS)' },
373
'Midori',
374
'Nook Browser',
375
'PaleMoon',
376
'PhantomJS',
377
'Raven',
378
'Rekonq',
379
'RockMelt',
380
{ 'label': 'Samsung Internet', 'pattern': 'SamsungBrowser' },
381
'SeaMonkey',
382
{ 'label': 'Silk', 'pattern': '(?:Cloud9|Silk-Accelerated)' },
383
'Sleipnir',
384
'SlimBrowser',
385
{ 'label': 'SRWare Iron', 'pattern': 'Iron' },
386
'Sunrise',
387
'Swiftfox',
388
'Vivaldi',
389
'Waterfox',
390
'WebPositive',
391
{ 'label': 'Yandex Browser', 'pattern': 'YaBrowser' },
392
{ 'label': 'UC Browser', 'pattern': 'UCBrowser' },
393
'Opera Mini',
394
{ 'label': 'Opera Mini', 'pattern': 'OPiOS' },
395
'Opera',
396
{ 'label': 'Opera', 'pattern': 'OPR' },
397
'Chromium',
398
'Chrome',
399
{ 'label': 'Chrome', 'pattern': '(?:HeadlessChrome)' },
400
{ 'label': 'Chrome Mobile', 'pattern': '(?:CriOS|CrMo)' },
401
{ 'label': 'Firefox', 'pattern': '(?:Firefox|Minefield)' },
402
{ 'label': 'Firefox for iOS', 'pattern': 'FxiOS' },
403
{ 'label': 'IE', 'pattern': 'IEMobile' },
404
{ 'label': 'IE', 'pattern': 'MSIE' },
405
'Safari'
406
]);
407
408
/* Detectable products (order is important). */
409
var product = getProduct([
410
{ 'label': 'BlackBerry', 'pattern': 'BB10' },
411
'BlackBerry',
412
{ 'label': 'Galaxy S', 'pattern': 'GT-I9000' },
413
{ 'label': 'Galaxy S2', 'pattern': 'GT-I9100' },
414
{ 'label': 'Galaxy S3', 'pattern': 'GT-I9300' },
415
{ 'label': 'Galaxy S4', 'pattern': 'GT-I9500' },
416
{ 'label': 'Galaxy S5', 'pattern': 'SM-G900' },
417
{ 'label': 'Galaxy S6', 'pattern': 'SM-G920' },
418
{ 'label': 'Galaxy S6 Edge', 'pattern': 'SM-G925' },
419
{ 'label': 'Galaxy S7', 'pattern': 'SM-G930' },
420
{ 'label': 'Galaxy S7 Edge', 'pattern': 'SM-G935' },
421
'Google TV',
422
'Lumia',
423
'iPad',
424
'iPod',
425
'iPhone',
426
'Kindle',
427
{ 'label': 'Kindle Fire', 'pattern': '(?:Cloud9|Silk-Accelerated)' },
428
'Nexus',
429
'Nook',
430
'PlayBook',
431
'PlayStation Vita',
432
'PlayStation',
433
'TouchPad',
434
'Transformer',
435
{ 'label': 'Wii U', 'pattern': 'WiiU' },
436
'Wii',
437
'Xbox One',
438
{ 'label': 'Xbox 360', 'pattern': 'Xbox' },
439
'Xoom'
440
]);
441
442
/* Detectable manufacturers. */
443
var manufacturer = getManufacturer({
444
'Apple': { 'iPad': 1, 'iPhone': 1, 'iPod': 1 },
445
'Alcatel': {},
446
'Archos': {},
447
'Amazon': { 'Kindle': 1, 'Kindle Fire': 1 },
448
'Asus': { 'Transformer': 1 },
449
'Barnes & Noble': { 'Nook': 1 },
450
'BlackBerry': { 'PlayBook': 1 },
451
'Google': { 'Google TV': 1, 'Nexus': 1 },
452
'HP': { 'TouchPad': 1 },
453
'HTC': {},
454
'Huawei': {},
455
'Lenovo': {},
456
'LG': {},
457
'Microsoft': { 'Xbox': 1, 'Xbox One': 1 },
458
'Motorola': { 'Xoom': 1 },
459
'Nintendo': { 'Wii U': 1, 'Wii': 1 },
460
'Nokia': { 'Lumia': 1 },
461
'Oppo': {},
462
'Samsung': { 'Galaxy S': 1, 'Galaxy S2': 1, 'Galaxy S3': 1, 'Galaxy S4': 1 },
463
'Sony': { 'PlayStation': 1, 'PlayStation Vita': 1 },
464
'Xiaomi': { 'Mi': 1, 'Redmi': 1 }
465
});
466
467
/* Detectable operating systems (order is important). */
468
var os = getOS([
469
'Windows Phone',
470
'KaiOS',
471
'Android',
472
'CentOS',
473
{ 'label': 'Chrome OS', 'pattern': 'CrOS' },
474
'Debian',
475
{ 'label': 'DragonFly BSD', 'pattern': 'DragonFly' },
476
'Fedora',
477
'FreeBSD',
478
'Gentoo',
479
'Haiku',
480
'Kubuntu',
481
'Linux Mint',
482
'OpenBSD',
483
'Red Hat',
484
'SuSE',
485
'Ubuntu',
486
'Xubuntu',
487
'Cygwin',
488
'Symbian OS',
489
'hpwOS',
490
'webOS ',
491
'webOS',
492
'Tablet OS',
493
'Tizen',
494
'Linux',
495
'Mac OS X',
496
'Macintosh',
497
'Mac',
498
'Windows 98;',
499
'Windows '
500
]);
501
502
/*------------------------------------------------------------------------*/
503
504
/**
505
* Picks the layout engine from an array of guesses.
506
*
507
* @private
508
* @param {Array} guesses An array of guesses.
509
* @returns {null|string} The detected layout engine.
510
*/
511
function getLayout(guesses) {
512
return reduce(guesses, function(result, guess) {
513
return result || RegExp('\\b' + (
514
guess.pattern || qualify(guess)
515
) + '\\b', 'i').exec(ua) && (guess.label || guess);
516
});
517
}
518
519
/**
520
* Picks the manufacturer from an array of guesses.
521
*
522
* @private
523
* @param {Array} guesses An object of guesses.
524
* @returns {null|string} The detected manufacturer.
525
*/
526
function getManufacturer(guesses) {
527
return reduce(guesses, function(result, value, key) {
528
// Lookup the manufacturer by product or scan the UA for the manufacturer.
529
return result || (
530
value[product] ||
531
value[/^[a-z]+(?: +[a-z]+\b)*/i.exec(product)] ||
532
RegExp('\\b' + qualify(key) + '(?:\\b|\\w*\\d)', 'i').exec(ua)
533
) && key;
534
});
535
}
536
537
/**
538
* Picks the browser name from an array of guesses.
539
*
540
* @private
541
* @param {Array} guesses An array of guesses.
542
* @returns {null|string} The detected browser name.
543
*/
544
function getName(guesses) {
545
return reduce(guesses, function(result, guess) {
546
return result || RegExp('\\b' + (
547
guess.pattern || qualify(guess)
548
) + '\\b', 'i').exec(ua) && (guess.label || guess);
549
});
550
}
551
552
/**
553
* Picks the OS name from an array of guesses.
554
*
555
* @private
556
* @param {Array} guesses An array of guesses.
557
* @returns {null|string} The detected OS name.
558
*/
559
function getOS(guesses) {
560
return reduce(guesses, function(result, guess) {
561
var pattern = guess.pattern || qualify(guess);
562
if (!result && (result =
563
RegExp('\\b' + pattern + '(?:/[\\d.]+|[ \\w.]*)', 'i').exec(ua)
564
)) {
565
result = cleanupOS(result, pattern, guess.label || guess);
566
}
567
return result;
568
});
569
}
570
571
/**
572
* Picks the product name from an array of guesses.
573
*
574
* @private
575
* @param {Array} guesses An array of guesses.
576
* @returns {null|string} The detected product name.
577
*/
578
function getProduct(guesses) {
579
return reduce(guesses, function(result, guess) {
580
var pattern = guess.pattern || qualify(guess);
581
if (!result && (result =
582
RegExp('\\b' + pattern + ' *\\d+[.\\w_]*', 'i').exec(ua) ||
583
RegExp('\\b' + pattern + ' *\\w+-[\\w]*', 'i').exec(ua) ||
584
RegExp('\\b' + pattern + '(?:; *(?:[a-z]+[_-])?[a-z]+\\d+|[^ ();-]*)', 'i').exec(ua)
585
)) {
586
// Split by forward slash and append product version if needed.
587
if ((result = String((guess.label && !RegExp(pattern, 'i').test(guess.label)) ? guess.label : result).split('/'))[1] && !/[\d.]+/.test(result[0])) {
588
result[0] += ' ' + result[1];
589
}
590
// Correct character case and cleanup string.
591
guess = guess.label || guess;
592
result = format(result[0]
593
.replace(RegExp(pattern, 'i'), guess)
594
.replace(RegExp('; *(?:' + guess + '[_-])?', 'i'), ' ')
595
.replace(RegExp('(' + guess + ')[-_.]?(\\w)', 'i'), '$1 $2'));
596
}
597
return result;
598
});
599
}
600
601
/**
602
* Resolves the version using an array of UA patterns.
603
*
604
* @private
605
* @param {Array} patterns An array of UA patterns.
606
* @returns {null|string} The detected version.
607
*/
608
function getVersion(patterns) {
609
return reduce(patterns, function(result, pattern) {
610
return result || (RegExp(pattern +
611
'(?:-[\\d.]+/|(?: for [\\w-]+)?[ /-])([\\d.]+[^ ();/_-]*)', 'i').exec(ua) || 0)[1] || null;
612
});
613
}
614
615
/**
616
* Returns `platform.description` when the platform object is coerced to a string.
617
*
618
* @name toString
619
* @memberOf platform
620
* @returns {string} Returns `platform.description` if available, else an empty string.
621
*/
622
function toStringPlatform() {
623
return this.description || '';
624
}
625
626
/*------------------------------------------------------------------------*/
627
628
// Convert layout to an array so we can add extra details.
629
layout && (layout = [layout]);
630
631
// Detect Android products.
632
// Browsers on Android devices typically provide their product IDS after "Android;"
633
// up to "Build" or ") AppleWebKit".
634
// Example:
635
// "Mozilla/5.0 (Linux; Android 8.1.0; Moto G (5) Plus) AppleWebKit/537.36
636
// (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36"
637
if (/\bAndroid\b/.test(os) && !product &&
638
(data = /\bAndroid[^;]*;(.*?)(?:Build|\) AppleWebKit)\b/i.exec(ua))) {
639
product = trim(data[1])
640
// Replace any language codes (eg. "en-US").
641
.replace(/^[a-z]{2}-[a-z]{2};\s*/i, '')
642
|| null;
643
}
644
// Detect product names that contain their manufacturer's name.
645
if (manufacturer && !product) {
646
product = getProduct([manufacturer]);
647
} else if (manufacturer && product) {
648
product = product
649
.replace(RegExp('^(' + qualify(manufacturer) + ')[-_.\\s]', 'i'), manufacturer + ' ')
650
.replace(RegExp('^(' + qualify(manufacturer) + ')[-_.]?(\\w)', 'i'), manufacturer + ' $2');
651
}
652
// Clean up Google TV.
653
if ((data = /\bGoogle TV\b/.exec(product))) {
654
product = data[0];
655
}
656
// Detect simulators.
657
if (/\bSimulator\b/i.test(ua)) {
658
product = (product ? product + ' ' : '') + 'Simulator';
659
}
660
// Detect Opera Mini 8+ running in Turbo/Uncompressed mode on iOS.
661
if (name == 'Opera Mini' && /\bOPiOS\b/.test(ua)) {
662
description.push('running in Turbo/Uncompressed mode');
663
}
664
// Detect IE Mobile 11.
665
if (name == 'IE' && /\blike iPhone OS\b/.test(ua)) {
666
data = parse(ua.replace(/like iPhone OS/, ''));
667
manufacturer = data.manufacturer;
668
product = data.product;
669
}
670
// Detect iOS.
671
else if (/^iP/.test(product)) {
672
name || (name = 'Safari');
673
os = 'iOS' + ((data = / OS ([\d_]+)/i.exec(ua))
674
? ' ' + data[1].replace(/_/g, '.')
675
: '');
676
}
677
// Detect Kubuntu.
678
else if (name == 'Konqueror' && /^Linux\b/i.test(os)) {
679
os = 'Kubuntu';
680
}
681
// Detect Android browsers.
682
else if ((manufacturer && manufacturer != 'Google' &&
683
((/Chrome/.test(name) && !/\bMobile Safari\b/i.test(ua)) || /\bVita\b/.test(product))) ||
684
(/\bAndroid\b/.test(os) && /^Chrome/.test(name) && /\bVersion\//i.test(ua))) {
685
name = 'Android Browser';
686
os = /\bAndroid\b/.test(os) ? os : 'Android';
687
}
688
// Detect Silk desktop/accelerated modes.
689
else if (name == 'Silk') {
690
if (!/\bMobi/i.test(ua)) {
691
os = 'Android';
692
description.unshift('desktop mode');
693
}
694
if (/Accelerated *= *true/i.test(ua)) {
695
description.unshift('accelerated');
696
}
697
}
698
// Detect UC Browser speed mode.
699
else if (name == 'UC Browser' && /\bUCWEB\b/.test(ua)) {
700
description.push('speed mode');
701
}
702
// Detect PaleMoon identifying as Firefox.
703
else if (name == 'PaleMoon' && (data = /\bFirefox\/([\d.]+)\b/.exec(ua))) {
704
description.push('identifying as Firefox ' + data[1]);
705
}
706
// Detect Firefox OS and products running Firefox.
707
else if (name == 'Firefox' && (data = /\b(Mobile|Tablet|TV)\b/i.exec(ua))) {
708
os || (os = 'Firefox OS');
709
product || (product = data[1]);
710
}
711
// Detect false positives for Firefox/Safari.
712
else if (!name || (data = !/\bMinefield\b/i.test(ua) && /\b(?:Firefox|Safari)\b/.exec(name))) {
713
// Escape the `/` for Firefox 1.
714
if (name && !product && /[\/,]|^[^(]+?\)/.test(ua.slice(ua.indexOf(data + '/') + 8))) {
715
// Clear name of false positives.
716
name = null;
717
}
718
// Reassign a generic name.
719
if ((data = product || manufacturer || os) &&
720
(product || manufacturer || /\b(?:Android|Symbian OS|Tablet OS|webOS)\b/.test(os))) {
721
name = /[a-z]+(?: Hat)?/i.exec(/\bAndroid\b/.test(os) ? os : data) + ' Browser';
722
}
723
}
724
// Add Chrome version to description for Electron.
725
else if (name == 'Electron' && (data = (/\bChrome\/([\d.]+)\b/.exec(ua) || 0)[1])) {
726
description.push('Chromium ' + data);
727
}
728
// Detect non-Opera (Presto-based) versions (order is important).
729
if (!version) {
730
version = getVersion([
731
'(?:Cloud9|CriOS|CrMo|Edge|Edg|EdgA|EdgiOS|FxiOS|HeadlessChrome|IEMobile|Iron|Opera ?Mini|OPiOS|OPR|Raven|SamsungBrowser|Silk(?!/[\\d.]+$)|UCBrowser|YaBrowser)',
732
'Version',
733
qualify(name),
734
'(?:Firefox|Minefield|NetFront)'
735
]);
736
}
737
// Detect stubborn layout engines.
738
if ((data =
739
layout == 'iCab' && parseFloat(version) > 3 && 'WebKit' ||
740
/\bOpera\b/.test(name) && (/\bOPR\b/.test(ua) ? 'Blink' : 'Presto') ||
741
/\b(?:Midori|Nook|Safari)\b/i.test(ua) && !/^(?:Trident|EdgeHTML)$/.test(layout) && 'WebKit' ||
742
!layout && /\bMSIE\b/i.test(ua) && (os == 'Mac OS' ? 'Tasman' : 'Trident') ||
743
layout == 'WebKit' && /\bPlayStation\b(?! Vita\b)/i.test(name) && 'NetFront'
744
)) {
745
layout = [data];
746
}
747
// Detect Windows Phone 7 desktop mode.
748
if (name == 'IE' && (data = (/; *(?:XBLWP|ZuneWP)(\d+)/i.exec(ua) || 0)[1])) {
749
name += ' Mobile';
750
os = 'Windows Phone ' + (/\+$/.test(data) ? data : data + '.x');
751
description.unshift('desktop mode');
752
}
753
// Detect Windows Phone 8.x desktop mode.
754
else if (/\bWPDesktop\b/i.test(ua)) {
755
name = 'IE Mobile';
756
os = 'Windows Phone 8.x';
757
description.unshift('desktop mode');
758
version || (version = (/\brv:([\d.]+)/.exec(ua) || 0)[1]);
759
}
760
// Detect IE 11 identifying as other browsers.
761
else if (name != 'IE' && layout == 'Trident' && (data = /\brv:([\d.]+)/.exec(ua))) {
762
if (name) {
763
description.push('identifying as ' + name + (version ? ' ' + version : ''));
764
}
765
name = 'IE';
766
version = data[1];
767
}
768
// Leverage environment features.
769
if (useFeatures) {
770
// Detect server-side environments.
771
// Rhino has a global function while others have a global object.
772
if (isHostType(context, 'global')) {
773
if (java) {
774
data = java.lang.System;
775
arch = data.getProperty('os.arch');
776
os = os || data.getProperty('os.name') + ' ' + data.getProperty('os.version');
777
}
778
if (rhino) {
779
try {
780
version = context.require('ringo/engine').version.join('.');
781
name = 'RingoJS';
782
} catch(e) {
783
if ((data = context.system) && data.global.system == context.system) {
784
name = 'Narwhal';
785
os || (os = data[0].os || null);
786
}
787
}
788
if (!name) {
789
name = 'Rhino';
790
}
791
}
792
else if (
793
typeof context.process == 'object' && !context.process.browser &&
794
(data = context.process)
795
) {
796
if (typeof data.versions == 'object') {
797
if (typeof data.versions.electron == 'string') {
798
description.push('Node ' + data.versions.node);
799
name = 'Electron';
800
version = data.versions.electron;
801
} else if (typeof data.versions.nw == 'string') {
802
description.push('Chromium ' + version, 'Node ' + data.versions.node);
803
name = 'NW.js';
804
version = data.versions.nw;
805
}
806
}
807
if (!name) {
808
name = 'Node.js';
809
arch = data.arch;
810
os = data.platform;
811
version = /[\d.]+/.exec(data.version);
812
version = version ? version[0] : null;
813
}
814
}
815
}
816
// Detect Adobe AIR.
817
else if (getClassOf((data = context.runtime)) == airRuntimeClass) {
818
name = 'Adobe AIR';
819
os = data.flash.system.Capabilities.os;
820
}
821
// Detect PhantomJS.
822
else if (getClassOf((data = context.phantom)) == phantomClass) {
823
name = 'PhantomJS';
824
version = (data = data.version || null) && (data.major + '.' + data.minor + '.' + data.patch);
825
}
826
// Detect IE compatibility modes.
827
else if (typeof doc.documentMode == 'number' && (data = /\bTrident\/(\d+)/i.exec(ua))) {
828
// We're in compatibility mode when the Trident version + 4 doesn't
829
// equal the document mode.
830
version = [version, doc.documentMode];
831
if ((data = +data[1] + 4) != version[1]) {
832
description.push('IE ' + version[1] + ' mode');
833
layout && (layout[1] = '');
834
version[1] = data;
835
}
836
version = name == 'IE' ? String(version[1].toFixed(1)) : version[0];
837
}
838
// Detect IE 11 masking as other browsers.
839
else if (typeof doc.documentMode == 'number' && /^(?:Chrome|Firefox)\b/.test(name)) {
840
description.push('masking as ' + name + ' ' + version);
841
name = 'IE';
842
version = '11.0';
843
layout = ['Trident'];
844
os = 'Windows';
845
}
846
os = os && format(os);
847
}
848
// Detect prerelease phases.
849
if (version && (data =
850
/(?:[ab]|dp|pre|[ab]\d+pre)(?:\d+\+?)?$/i.exec(version) ||
851
/(?:alpha|beta)(?: ?\d)?/i.exec(ua + ';' + (useFeatures && nav.appMinorVersion)) ||
852
/\bMinefield\b/i.test(ua) && 'a'
853
)) {
854
prerelease = /b/i.test(data) ? 'beta' : 'alpha';
855
version = version.replace(RegExp(data + '\\+?$'), '') +
856
(prerelease == 'beta' ? beta : alpha) + (/\d+\+?/.exec(data) || '');
857
}
858
// Detect Firefox Mobile.
859
if (name == 'Fennec' || name == 'Firefox' && /\b(?:Android|Firefox OS|KaiOS)\b/.test(os)) {
860
name = 'Firefox Mobile';
861
}
862
// Obscure Maxthon's unreliable version.
863
else if (name == 'Maxthon' && version) {
864
version = version.replace(/\.[\d.]+/, '.x');
865
}
866
// Detect Xbox 360 and Xbox One.
867
else if (/\bXbox\b/i.test(product)) {
868
if (product == 'Xbox 360') {
869
os = null;
870
}
871
if (product == 'Xbox 360' && /\bIEMobile\b/.test(ua)) {
872
description.unshift('mobile mode');
873
}
874
}
875
// Add mobile postfix.
876
else if ((/^(?:Chrome|IE|Opera)$/.test(name) || name && !product && !/Browser|Mobi/.test(name)) &&
877
(os == 'Windows CE' || /Mobi/i.test(ua))) {
878
name += ' Mobile';
879
}
880
// Detect IE platform preview.
881
else if (name == 'IE' && useFeatures) {
882
try {
883
if (context.external === null) {
884
description.unshift('platform preview');
885
}
886
} catch(e) {
887
description.unshift('embedded');
888
}
889
}
890
// Detect BlackBerry OS version.
891
// http://docs.blackberry.com/en/developers/deliverables/18169/HTTP_headers_sent_by_BB_Browser_1234911_11.jsp
892
else if ((/\bBlackBerry\b/.test(product) || /\bBB10\b/.test(ua)) && (data =
893
(RegExp(product.replace(/ +/g, ' *') + '/([.\\d]+)', 'i').exec(ua) || 0)[1] ||
894
version
895
)) {
896
data = [data, /BB10/.test(ua)];
897
os = (data[1] ? (product = null, manufacturer = 'BlackBerry') : 'Device Software') + ' ' + data[0];
898
version = null;
899
}
900
// Detect Opera identifying/masking itself as another browser.
901
// http://www.opera.com/support/kb/view/843/
902
else if (this != forOwn && product != 'Wii' && (
903
(useFeatures && opera) ||
904
(/Opera/.test(name) && /\b(?:MSIE|Firefox)\b/i.test(ua)) ||
905
(name == 'Firefox' && /\bOS X (?:\d+\.){2,}/.test(os)) ||
906
(name == 'IE' && (
907
(os && !/^Win/.test(os) && version > 5.5) ||
908
/\bWindows XP\b/.test(os) && version > 8 ||
909
version == 8 && !/\bTrident\b/.test(ua)
910
))
911
) && !reOpera.test((data = parse.call(forOwn, ua.replace(reOpera, '') + ';'))) && data.name) {
912
// When "identifying", the UA contains both Opera and the other browser's name.
913
data = 'ing as ' + data.name + ((data = data.version) ? ' ' + data : '');
914
if (reOpera.test(name)) {
915
if (/\bIE\b/.test(data) && os == 'Mac OS') {
916
os = null;
917
}
918
data = 'identify' + data;
919
}
920
// When "masking", the UA contains only the other browser's name.
921
else {
922
data = 'mask' + data;
923
if (operaClass) {
924
name = format(operaClass.replace(/([a-z])([A-Z])/g, '$1 $2'));
925
} else {
926
name = 'Opera';
927
}
928
if (/\bIE\b/.test(data)) {
929
os = null;
930
}
931
if (!useFeatures) {
932
version = null;
933
}
934
}
935
layout = ['Presto'];
936
description.push(data);
937
}
938
// Detect WebKit Nightly and approximate Chrome/Safari versions.
939
if ((data = (/\bAppleWebKit\/([\d.]+\+?)/i.exec(ua) || 0)[1])) {
940
// Correct build number for numeric comparison.
941
// (e.g. "532.5" becomes "532.05")
942
data = [parseFloat(data.replace(/\.(\d)$/, '.0$1')), data];
943
// Nightly builds are postfixed with a "+".
944
if (name == 'Safari' && data[1].slice(-1) == '+') {
945
name = 'WebKit Nightly';
946
prerelease = 'alpha';
947
version = data[1].slice(0, -1);
948
}
949
// Clear incorrect browser versions.
950
else if (version == data[1] ||
951
version == (data[2] = (/\bSafari\/([\d.]+\+?)/i.exec(ua) || 0)[1])) {
952
version = null;
953
}
954
// Use the full Chrome version when available.
955
data[1] = (/\b(?:Headless)?Chrome\/([\d.]+)/i.exec(ua) || 0)[1];
956
// Detect Blink layout engine.
957
if (data[0] == 537.36 && data[2] == 537.36 && parseFloat(data[1]) >= 28 && layout == 'WebKit') {
958
layout = ['Blink'];
959
}
960
// Detect JavaScriptCore.
961
// http://stackoverflow.com/questions/6768474/how-can-i-detect-which-javascript-engine-v8-or-jsc-is-used-at-runtime-in-androi
962
if (!useFeatures || (!likeChrome && !data[1])) {
963
layout && (layout[1] = 'like Safari');
964
data = (data = data[0], data < 400 ? 1 : data < 500 ? 2 : data < 526 ? 3 : data < 533 ? 4 : data < 534 ? '4+' : data < 535 ? 5 : data < 537 ? 6 : data < 538 ? 7 : data < 601 ? 8 : data < 602 ? 9 : data < 604 ? 10 : data < 606 ? 11 : data < 608 ? 12 : '12');
965
} else {
966
layout && (layout[1] = 'like Chrome');
967
data = data[1] || (data = data[0], data < 530 ? 1 : data < 532 ? 2 : data < 532.05 ? 3 : data < 533 ? 4 : data < 534.03 ? 5 : data < 534.07 ? 6 : data < 534.10 ? 7 : data < 534.13 ? 8 : data < 534.16 ? 9 : data < 534.24 ? 10 : data < 534.30 ? 11 : data < 535.01 ? 12 : data < 535.02 ? '13+' : data < 535.07 ? 15 : data < 535.11 ? 16 : data < 535.19 ? 17 : data < 536.05 ? 18 : data < 536.10 ? 19 : data < 537.01 ? 20 : data < 537.11 ? '21+' : data < 537.13 ? 23 : data < 537.18 ? 24 : data < 537.24 ? 25 : data < 537.36 ? 26 : layout != 'Blink' ? '27' : '28');
968
}
969
// Add the postfix of ".x" or "+" for approximate versions.
970
layout && (layout[1] += ' ' + (data += typeof data == 'number' ? '.x' : /[.+]/.test(data) ? '' : '+'));
971
// Obscure version for some Safari 1-2 releases.
972
if (name == 'Safari' && (!version || parseInt(version) > 45)) {
973
version = data;
974
} else if (name == 'Chrome' && /\bHeadlessChrome/i.test(ua)) {
975
description.unshift('headless');
976
}
977
}
978
// Detect Opera desktop modes.
979
if (name == 'Opera' && (data = /\bzbov|zvav$/.exec(os))) {
980
name += ' ';
981
description.unshift('desktop mode');
982
if (data == 'zvav') {
983
name += 'Mini';
984
version = null;
985
} else {
986
name += 'Mobile';
987
}
988
os = os.replace(RegExp(' *' + data + '$'), '');
989
}
990
// Detect Chrome desktop mode.
991
else if (name == 'Safari' && /\bChrome\b/.exec(layout && layout[1])) {
992
description.unshift('desktop mode');
993
name = 'Chrome Mobile';
994
version = null;
995
996
if (/\bOS X\b/.test(os)) {
997
manufacturer = 'Apple';
998
os = 'iOS 4.3+';
999
} else {
1000
os = null;
1001
}
1002
}
1003
// Newer versions of SRWare Iron uses the Chrome tag to indicate its version number.
1004
else if (/\bSRWare Iron\b/.test(name) && !version) {
1005
version = getVersion('Chrome');
1006
}
1007
// Strip incorrect OS versions.
1008
if (version && version.indexOf((data = /[\d.]+$/.exec(os))) == 0 &&
1009
ua.indexOf('/' + data + '-') > -1) {
1010
os = trim(os.replace(data, ''));
1011
}
1012
// Ensure OS does not include the browser name.
1013
if (os && os.indexOf(name) != -1 && !RegExp(name + ' OS').test(os)) {
1014
os = os.replace(RegExp(' *' + qualify(name) + ' *'), '');
1015
}
1016
// Add layout engine.
1017
if (layout && !/\b(?:Avant|Nook)\b/.test(name) && (
1018
/Browser|Lunascape|Maxthon/.test(name) ||
1019
name != 'Safari' && /^iOS/.test(os) && /\bSafari\b/.test(layout[1]) ||
1020
/^(?:Adobe|Arora|Breach|Midori|Opera|Phantom|Rekonq|Rock|Samsung Internet|Sleipnir|SRWare Iron|Vivaldi|Web)/.test(name) && layout[1])) {
1021
// Don't add layout details to description if they are falsey.
1022
(data = layout[layout.length - 1]) && description.push(data);
1023
}
1024
// Combine contextual information.
1025
if (description.length) {
1026
description = ['(' + description.join('; ') + ')'];
1027
}
1028
// Append manufacturer to description.
1029
if (manufacturer && product && product.indexOf(manufacturer) < 0) {
1030
description.push('on ' + manufacturer);
1031
}
1032
// Append product to description.
1033
if (product) {
1034
description.push((/^on /.test(description[description.length - 1]) ? '' : 'on ') + product);
1035
}
1036
// Parse the OS into an object.
1037
if (os) {
1038
data = / ([\d.+]+)$/.exec(os);
1039
isSpecialCasedOS = data && os.charAt(os.length - data[0].length - 1) == '/';
1040
os = {
1041
'architecture': 32,
1042
'family': (data && !isSpecialCasedOS) ? os.replace(data[0], '') : os,
1043
'version': data ? data[1] : null,
1044
'toString': function() {
1045
var version = this.version;
1046
return this.family + ((version && !isSpecialCasedOS) ? ' ' + version : '') + (this.architecture == 64 ? ' 64-bit' : '');
1047
}
1048
};
1049
}
1050
// Add browser/OS architecture.
1051
if ((data = /\b(?:AMD|IA|Win|WOW|x86_|x)64\b/i.exec(arch)) && !/\bi686\b/i.test(arch)) {
1052
if (os) {
1053
os.architecture = 64;
1054
os.family = os.family.replace(RegExp(' *' + data), '');
1055
}
1056
if (
1057
name && (/\bWOW64\b/i.test(ua) ||
1058
(useFeatures && /\w(?:86|32)$/.test(nav.cpuClass || nav.platform) && !/\bWin64; x64\b/i.test(ua)))
1059
) {
1060
description.unshift('32-bit');
1061
}
1062
}
1063
// Chrome 39 and above on OS X is always 64-bit.
1064
else if (
1065
os && /^OS X/.test(os.family) &&
1066
name == 'Chrome' && parseFloat(version) >= 39
1067
) {
1068
os.architecture = 64;
1069
}
1070
1071
ua || (ua = null);
1072
1073
/*------------------------------------------------------------------------*/
1074
1075
/**
1076
* The platform object.
1077
*
1078
* @name platform
1079
* @type Object
1080
*/
1081
var platform = {};
1082
1083
/**
1084
* The platform description.
1085
*
1086
* @memberOf platform
1087
* @type string|null
1088
*/
1089
platform.description = ua;
1090
1091
/**
1092
* The name of the browser's layout engine.
1093
*
1094
* The list of common layout engines include:
1095
* "Blink", "EdgeHTML", "Gecko", "Trident" and "WebKit"
1096
*
1097
* @memberOf platform
1098
* @type string|null
1099
*/
1100
platform.layout = layout && layout[0];
1101
1102
/**
1103
* The name of the product's manufacturer.
1104
*
1105
* The list of manufacturers include:
1106
* "Apple", "Archos", "Amazon", "Asus", "Barnes & Noble", "BlackBerry",
1107
* "Google", "HP", "HTC", "LG", "Microsoft", "Motorola", "Nintendo",
1108
* "Nokia", "Samsung" and "Sony"
1109
*
1110
* @memberOf platform
1111
* @type string|null
1112
*/
1113
platform.manufacturer = manufacturer;
1114
1115
/**
1116
* The name of the browser/environment.
1117
*
1118
* The list of common browser names include:
1119
* "Chrome", "Electron", "Firefox", "Firefox for iOS", "IE",
1120
* "Microsoft Edge", "PhantomJS", "Safari", "SeaMonkey", "Silk",
1121
* "Opera Mini" and "Opera"
1122
*
1123
* Mobile versions of some browsers have "Mobile" appended to their name:
1124
* eg. "Chrome Mobile", "Firefox Mobile", "IE Mobile" and "Opera Mobile"
1125
*
1126
* @memberOf platform
1127
* @type string|null
1128
*/
1129
platform.name = name;
1130
1131
/**
1132
* The alpha/beta release indicator.
1133
*
1134
* @memberOf platform
1135
* @type string|null
1136
*/
1137
platform.prerelease = prerelease;
1138
1139
/**
1140
* The name of the product hosting the browser.
1141
*
1142
* The list of common products include:
1143
*
1144
* "BlackBerry", "Galaxy S4", "Lumia", "iPad", "iPod", "iPhone", "Kindle",
1145
* "Kindle Fire", "Nexus", "Nook", "PlayBook", "TouchPad" and "Transformer"
1146
*
1147
* @memberOf platform
1148
* @type string|null
1149
*/
1150
platform.product = product;
1151
1152
/**
1153
* The browser's user agent string.
1154
*
1155
* @memberOf platform
1156
* @type string|null
1157
*/
1158
platform.ua = ua;
1159
1160
/**
1161
* The browser/environment version.
1162
*
1163
* @memberOf platform
1164
* @type string|null
1165
*/
1166
platform.version = name && version;
1167
1168
/**
1169
* The name of the operating system.
1170
*
1171
* @memberOf platform
1172
* @type Object
1173
*/
1174
platform.os = os || {
1175
1176
/**
1177
* The CPU architecture the OS is built for.
1178
*
1179
* @memberOf platform.os
1180
* @type number|null
1181
*/
1182
'architecture': null,
1183
1184
/**
1185
* The family of the OS.
1186
*
1187
* Common values include:
1188
* "Windows", "Windows Server 2008 R2 / 7", "Windows Server 2008 / Vista",
1189
* "Windows XP", "OS X", "Linux", "Ubuntu", "Debian", "Fedora", "Red Hat",
1190
* "SuSE", "Android", "iOS" and "Windows Phone"
1191
*
1192
* @memberOf platform.os
1193
* @type string|null
1194
*/
1195
'family': null,
1196
1197
/**
1198
* The version of the OS.
1199
*
1200
* @memberOf platform.os
1201
* @type string|null
1202
*/
1203
'version': null,
1204
1205
/**
1206
* Returns the OS string.
1207
*
1208
* @memberOf platform.os
1209
* @returns {string} The OS string.
1210
*/
1211
'toString': function() { return 'null'; }
1212
};
1213
1214
platform.parse = parse;
1215
platform.toString = toStringPlatform;
1216
1217
if (platform.version) {
1218
description.unshift(version);
1219
}
1220
if (platform.name) {
1221
description.unshift(name);
1222
}
1223
if (os && name && !(os == String(os).split(' ')[0] && (os == name.split(' ')[0] || product))) {
1224
description.push(product ? '(' + os + ')' : 'on ' + os);
1225
}
1226
if (description.length) {
1227
platform.description = description.join(' ');
1228
}
1229
return platform;
1230
}
1231
1232
/*--------------------------------------------------------------------------*/
1233
1234
// Export platform.
1235
var platform = parse();
1236
1237
// Some AMD build optimizers, like r.js, check for condition patterns like the following:
1238
if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
1239
// Expose platform on the global object to prevent errors when platform is
1240
// loaded by a script tag in the presence of an AMD loader.
1241
// See http://requirejs.org/docs/errors.html#mismatch for more details.
1242
root.platform = platform;
1243
1244
// Define as an anonymous module so platform can be aliased through path mapping.
1245
define(function() {
1246
return platform;
1247
});
1248
}
1249
// Check for `exports` after `define` in case a build optimizer adds an `exports` object.
1250
else if (freeExports && freeModule) {
1251
// Export for CommonJS support.
1252
forOwn(platform, function(value, key) {
1253
freeExports[key] = value;
1254
});
1255
}
1256
else {
1257
// Export to the global object.
1258
root.platform = platform;
1259
}
1260
}.call(this));
1261
1262