///
///
/* eslint-disable no-undef */
import { Fingerprinter } from "@zywave/zywave-analytics/dist/fingerprinter.js";
import { assert } from "@esm-bundle/chai";
import type { FingerprintAttributes } from "@zywave/zywave-analytics/dist/fingerprinter.js";
async function sha256(source: string) {
const sourceBytes = new TextEncoder().encode(source);
const digest = await crypto.subtle.digest("SHA-256", sourceBytes);
const resultBytes = [...new Uint8Array(digest)];
return resultBytes.map((x) => x.toString(16).padStart(2, "0")).join("");
}
function buildTestCanvas() {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d")!;
const txt = "ZYWAVE";
context.textBaseline = "top";
context.textBaseline = "top";
context.font = "14px 'Arial'";
context.textBaseline = "alphabetic";
context.fillStyle = "#f60";
context.fillRect(125, 1, 62, 20);
context.fillStyle = "#069";
context.fillText(txt, 2, 15);
context.fillStyle = "rgba(102, 204, 0, 0.7)";
context.fillText(txt, 4, 17);
return { canvas, context };
}
suite("Fingerprinter", () => {
let attributes: FingerprintAttributes;
const storageTtlDays = 1;
const cookieDomain = ".example.com";
setup(async () => {
attributes = await Fingerprinter.getAttributes({ storageTtlDays, cookieDomain });
});
test("attributes are defined", () => {
assert.isDefined(attributes.canvas);
assert.isDefined(attributes.storage);
assert.isDefined(attributes.screenWidth);
assert.isDefined(attributes.screenHeight);
assert.isDefined(attributes.timeZone);
assert.isDefined(attributes.doNotTrack);
assert.isDefined(attributes.gpc);
assert.isDefined(attributes.prefersReducedMotion);
assert.isDefined(attributes.prefersColorScheme);
});
test("canvas fingerprint is expected canvas drawing", async () => {
const { canvas } = buildTestCanvas();
const dataUrl = canvas.toDataURL();
const data = dataUrl.split(",")[1];
const expectedCanvasFingerprint = `v1|${await sha256(data)}`;
assert.equal(attributes.canvas, expectedCanvasFingerprint);
});
// localhost testing and Secure cookies don't mix well
test.skip("storage fingerprint sets cookie and localStorage", () => {
const key = "zapi:bf";
const value = `; ${document.cookie}`;
const parts = value.split(`; ${key}=`);
const cookieValue = parts.length === 2 ? parts.pop()?.split(";").shift() : null;
assert.equal(cookieValue, attributes.storage);
const localStorageValue = localStorage.getItem(key);
assert.isNotNull(localStorageValue);
assert.isDefined(localStorageValue);
const localStorageParsedValue = JSON.parse(localStorageValue!) as ExpirableLocalStorageItem;
assert.isDefined(localStorageParsedValue.expires);
assert.equal(localStorageParsedValue.value, attributes.storage);
});
test("timeZone is pulled from Intl", () => {
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
assert.equal(attributes.timeZone, timeZone);
});
test("screenWidth and screenHeight are pulled from Screen", () => {
assert.equal(attributes.screenWidth, window.screen.width);
assert.equal(attributes.screenHeight, window.screen.height);
});
test("doNotTrack is pulled from Navigator", () => {
assert.equal(attributes.doNotTrack, navigator.doNotTrack);
});
test("gpc is pulled from Navigator", () => {
assert.equal(attributes.gpc, navigator.globalPrivacyControl);
});
test("prefersReducedMotion is pulled from matchMedia", () => {
assert.equal(attributes.prefersReducedMotion, window.matchMedia("(prefers-reduced-motion: reduce)").matches);
});
test("prefersColorScheme is pulled from matchMedia", () => {
const prefersColorScheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
assert.equal(attributes.prefersColorScheme, prefersColorScheme);
});
});
type ExpirableLocalStorageItem = {
value: string;
expires: number;
};