PageSourceSearch

https://www.sveakbt.se/wp-content/plugins/svea2/src/Features/ABTest/assets/ab-split.js?ver=1790922055

js sveakbt.se collected 2026-10-02 16:37:20 UTC 5,082 bytes, 132 lines download raw bytes

1/*!
2 * Svea A/B client split — deterministic per-visitor arm assignment, in the browser.
3 *
4 * The server-side A/B mode swaps CONTENT, which needs a singular main query and
5 * cannot survive full-page caching. A `client_split` test varies nothing the server
6 * rendered: every visitor gets byte-identical HTML, this script is on the page for
7 * all of them, and the arms only diverge at the moment somebody clicks the control
8 * being tested. That is what makes it cache-safe.
9 *
10 * The bucket function is PHP's, character for character:
11 *
12 *     abs(crc32(uuid . ':' . test_id)) % 100 < traffic_percent  ->  'variant'
13 *
14 * CRC-32/ISO-HDLC (reflected polynomial 0xEDB88320, init and final xor
15 * 0xFFFFFFFF) over the UTF-8 BYTES of the string, which is what PHP's crc32()
16 * hashes. `>>> 0` is the JS half of PHP's abs(): PHP returns the value unsigned on
17 * 64-bit, JS's bitwise ops return it signed, and a signed remainder would put a
18 * chunk of visitors in the wrong arm rather than merely disagreeing about a few.
19 *
20 * Parity is not assumed - tools/fixtures/ab-crc32-vectors.json holds PHP-generated
21 * vectors across the bucket boundaries and tools/ab-rig.mjs replays every one of
22 * them through THIS file. A crc32 implementation with one wrong table entry still
23 * agrees with PHP on most inputs, so "it looked right on a few uuids" proves
24 * nothing.
25 *
26 * It writes nothing, touches no DOM and reads no state it did not find already
27 * present. A visitor with no `svea_visitor` value gets null - the control arm and
28 * no measurement - which is the same posture the server-side system takes, and the
29 * only honest one: an unidentified visitor cannot be counted in a denominator.
30 */
31(function () {
32    if (typeof window === 'undefined' || typeof document === 'undefined') return;
33
34    var COOKIE_NAME = 'svea_visitor';
35    var LS_KEY = 'svea_visitor';
36
37    var CRC_TABLE = null;
38
39    function crcTable() {
40        if (CRC_TABLE) return CRC_TABLE;
41        var table = new Int32Array(256);
42        for (var n = 0; n < 256; n++) {
43            var c = n;
44            for (var k = 0; k < 8; k++) {
45                c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
46            }
47            table[n] = c;
48        }
49        CRC_TABLE = table;
50        return CRC_TABLE;
51    }
52
53    /**
54     * UTF-8 bytes of a string. PHP hashes bytes, JS strings are UTF-16 code units,
55     * and the difference is invisible for the ASCII a uuid is made of - which is
56     * exactly why it has to be done properly here rather than discovered later by
57     * the one test id or reason string that is not ASCII.
58     */
59    function utf8Bytes(str) {
60        if (typeof TextEncoder === 'function') {
61            return new TextEncoder().encode(str);
62        }
63        var utf8 = unescape(encodeURIComponent(str));
64        var bytes = new Uint8Array(utf8.length);
65        for (var i = 0; i < utf8.length; i++) bytes[i] = utf8.charCodeAt(i) & 0xff;
66        return bytes;
67    }
68
69    function crc32(str) {
70        var table = crcTable();
71        var bytes = utf8Bytes(str);
72        var crc = -1;
73        for (var i = 0; i < bytes.length; i++) {
74            crc = (crc >>> 8) ^ table[(crc ^ bytes[i]) & 0xff];
75        }
76        return (crc ^ -1) >>> 0;
77    }
78
79    function readCookie(name) {
80        var re = new RegExp('(?:^|; )' + name.replace(/([.*+?^$|(){}[\]\\])/g, '\\$1') + '=([^;]*)');
81        var match = document.cookie.match(re);
82        return match ? decodeURIComponent(match[1]) : null;
83    }
84
85    /** The visitor id, read exactly the way tracker.js reads it: cookie, then localStorage. */
86    function visitorUuid() {
87        var fromCookie = readCookie(COOKIE_NAME);
88        if (fromCookie) return fromCookie;
89        try {
90            return (window.localStorage && localStorage.getItem(LS_KEY)) || null;
91        } catch (e) {
92            return null;
93        }
94    }
95
96    function findTest(testId) {
97        var config = window.sveaAbSplit;
98        var tests = config && Object.prototype.toString.call(config.tests) === '[object Array]' ? config.tests : null;
99        if (!tests) return null;
100        for (var i = 0; i < tests.length; i++) {
101            if (tests[i] && Number(tests[i].id) === Number(testId)) return tests[i];
102        }
103        return null;
104    }
105
106    /**
107     * @returns {'original'|'variant'|null} null = no config for this test, or no
108     *   visitor id to hash. Both mean "behave exactly as if no test existed".
109     */
110    function assign(testId) {
111        var test = findTest(testId);
112        if (!test) return null;
113
114        var percent = Number(test.traffic_percent);
115        if (!isFinite(percent)) return null;
116
117        var uuid = visitorUuid();
118        if (!uuid) return null;
119
120        var bucket = crc32(uuid + ':' + Number(testId)) % 100;
121
122        return bucket < percent ? 'variant' : 'original';
123    }
124
125    window.SveaAB = {
126        assign: assign,
127        uuid: visitorUuid,
128        // Exposed so the parity rig can hash the PHP-generated vectors through the
129        // real implementation instead of a copy of it.
130        _crc32: crc32,
131    };
132})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.