|t(rPeoyuj;LVCXkQ``g~`ZgBOi
zu=AOm@r#u7psDR>YN-In)Bpeg8FW%kQvfqN?focT7z69*#;D(^+iS)~d&|{GNw_qj
z0*U|t0Jup+K~xyiHILa6f-nq4gOs))pn%IHt!3X_|Nrj{bv$qPOq1RK0O&~ONu0_^
zgHOl&fHxFEWa*KeIw9h1c|B)qtk^4v63+cVG&pNa7C{hh4;rFoWDy6j+eBLv)n4qp
zKztKDjPWAyJLcE(?tUYR{~Wt!8k+87%~^rYv7g8@EH>qC61SBbwG)!7s-j*0f_pL7
zWPWt(+D-TP0v;J+-#TYv`OBco0{OUoso{^R4q4^r;R~4r6Bp
z6Cgx@G{a;ABePT>%h=SLUDT#0SfONT5nC0O}VJbn-$ql>h($07*qoM6N<$f?x{n
A3IG5A
literal 0
HcmV?d00001
diff --git a/website/sandbox/favicon-32x32.png b/website/sandbox/favicon-32x32.png
new file mode 100644
index 0000000000000000000000000000000000000000..915c22c2f5823de18464001a03448f1647cce8cc
GIT binary patch
literal 748
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvI6;x#X;^)
z4C~IxyaaNp1AIbUeKHH5N-I2-R(vWY4`e)%0J0UHDXD=3K_c=X5*YzSq!k|VC_Oha
z{r~^}b3MZ+!b*=g<$oMG_R2Hp-`B5iVv}AvdOVRc{B`yE3ro8{PoF*0wE2Da{@=In
zKDP9HUApS?w3+XVtG;j9sr)j85$Ft|k|4ie2Htn7>wjJQ`_RBqs@JXE~J?*VP
z2lsipIEGl9E}eXHSF-|7%j!_Z?O_wn?p^DB|34>xN5I>S#k2o=JI&Y`Q?~0K(|!KD
z>!)YhlyE1UkYHe7Gk+qx+H;ziT{2Iy8|PAnAFi`hcOMH}(d{J@ylCmd4C!aAIjgm7
zQ*=2CeHN&zPSoE1c=M{Zo$Av%zs%6yQff18bsD>aE%VQHj_D2SHa7H&2((AfHP>g}
zlaXGZcjg9lW}heQ2WD?QdfC@N`4Rtt
zr4@T!e03A1Z7F~7Bk+2z{_NC=E$`k}SxMT>WDl-b__<1M@@B%&lJv0R~`C_gPTCsm=OvLICRr~FT!@q8M@z^u%zm&`4!EbKj5gjra@rNQKI3bXR&5QWn>uADe>=7`J@_R|d>
c3%v9gUWp5qd~!0K3bca3)78&qol`;+0Pk){*Z=?k
literal 0
HcmV?d00001
diff --git a/website/sandbox/favicon.ico b/website/sandbox/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..53c4c7a1a63c565251586c04e78588e6c5a7f7fa
GIT binary patch
literal 15086
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
-
- User-agent test
+
+
+
+
+
+
+
+ User-agent test
-
- User-agent test
-
-
-
+
+ // highlight the code blocks
+ typeof hljs !== 'undefined' && hljs.highlightAll()
+ }
+
+ /**
+ * The history items array. It's a proxy object to handle the array changes and render the items.
+ *
+ * @type {Array- }
+ */
+ const historyItems = new Proxy([], {
+ deleteProperty: function (target, property) {
+ delete target[property]
+
+ renderHistoryItems(historyList, target)
+
+ return true
+ },
+ set: function (target, property, value) {
+ target[property] = value
+
+ renderHistoryItems(historyList, target)
+
+ return true
+ },
+ })
+
+ /**
+ * Check the user-agent and add it to the history.
+ *
+ * @return {Promise}
+ */
+ const check = async () => {
+ const emptyPlaceholder = ''
+ const timestamp = Date.now()
+
+ const navigatorBrands = navigator?.userAgentData?.brands
+ const userAgentDataAsJson = navigator?.userAgentData?.toJSON()
+ const highEntropyValues = await navigator?.userAgentData?.getHighEntropyValues([
+ 'architecture',
+ 'bitness',
+ 'formFactor',
+ 'fullVersionList',
+ 'model',
+ 'platformVersion',
+ 'uaFullVersion',
+ 'wow64',
+ ])
+
+ // capture everything, including the high-entropy values
+ const data = {
+ navigator: {
+ userAgent: navigator.userAgent ?? emptyPlaceholder,
+ platform: navigator.platform ?? emptyPlaceholder,
+ oscpu: navigator?.oscpu ?? emptyPlaceholder,
+ vendor: navigator?.vendor ?? emptyPlaceholder,
+ userAgentData: {
+ brands: navigatorBrands ? JSON.stringify(navigatorBrands) : emptyPlaceholder,
+ mobile: navigator?.userAgentData?.mobile ?? emptyPlaceholder,
+ platform: navigator?.userAgentData?.platform ?? emptyPlaceholder,
+ toJSON: userAgentDataAsJson ? JSON.stringify(userAgentDataAsJson) : emptyPlaceholder,
+ getHighEntropyValues: highEntropyValues ? JSON.stringify(highEntropyValues) : emptyPlaceholder,
+ },
+ },
+ timestamp: 0, // set the timestamp later (we need zero for the comparison)
+ }
+
+ // if the last array item is the same as the current data, don't add it (compare without the timestamp, of course)
+ if (historyItems.length > 0) {
+ const lastItem = {...historyItems[historyItems.length - 1]}
+ lastItem.timestamp = 0
+
+ if (JSON.stringify(lastItem) === JSON.stringify(data)) {
+ return
+ }
+ }
+
+ // push the data to the history
+ historyItems.push({...data, timestamp})
+
+ // and sort the history by the timestamp
+ historyItems.sort((a, b) => a.timestamp - b.timestamp)
+ }
+
+ // initial check
+ check().then(() => console.log('The initial check is done')).catch(console.error)
+
+ // start periodically checking the user-agent
+ setInterval(() => check().catch(console.error), 5)
+
+ console.log('To get the actual property/method values, you can use one of the following code snippet:\n' + [
+ 'navigator.userAgent',
+ 'navigator.platform',
+ 'navigator.oscpu',
+ 'navigator.vendor',
+ 'navigator.userAgentData.brands',
+ 'navigator.userAgentData.mobile',
+ 'navigator.userAgentData.platform',
+ 'navigator.userAgentData.toJSON()',
+ "await navigator.userAgentData.getHighEntropyValues(['architecture', 'bitness', 'formFactor', 'fullVersionList', 'model', 'platformVersion', 'uaFullVersion', 'wow64'])",
+ ].map((s) => '\t ๐ก ' + s).join('\n'))
+
+ // check the highlight.js availability and highlight the code blocks (initially)
+ const t = setInterval(() => {
+ if (typeof hljs !== 'undefined' && hljs.highlightAll) {
+ clearInterval(t)
+
+ hljs.configure({cssSelector: '.key, .value'})
+ hljs.highlightAll()
+ }
+ }, 100)
+
+
+