mixpanel
Version:
A simple server-side API for mixpanel
111 lines (97 loc) • 2.98 kB
JavaScript
/**
* Utility functions for Mixpanel feature flags
*/
const crypto = require("crypto");
// Constants
const EXPOSURE_EVENT = "$experiment_started";
const REQUEST_HEADERS = {
"Content-Type": "application/json",
};
/**
* FNV-1a 64-bit hash function used for consistent variant assignment
* https://www.ietf.org/archive/id/draft-eastlake-fnv-21.html#section-6.1.2
* @param {Buffer} data - Data to hash
* @returns {BigInt} - Hash value as BigInt
*/
function _fnv1a64(data) {
const FNV_PRIME = BigInt("0x100000001B3");
let hash = BigInt("0xCBF29CE484222325");
for (let i = 0; i < data.length; i++) {
hash ^= BigInt(data[i]);
hash *= FNV_PRIME;
hash &= BigInt("0xFFFFFFFFFFFFFFFF");
}
return hash;
}
/**
* Normalized hash function that returns a value between 0.0 and 1.0
* Used for variant assignment based on rollout percentages
* @param {string} key - The key to hash (usually distinct_id or other identifier)
* @param {string} salt - Salt value (usually flag-specific hash_salt)
* @returns {number} - Hash value normalized to the non-inclusive range, [0.0, 1.0)
*/
function normalizedHash(key, salt) {
const combined = Buffer.from(key + salt, "utf-8");
const hashValue = _fnv1a64(combined);
return Number(hashValue % BigInt(100)) / 100.0;
}
/**
* Prepare common query parameters for feature flags API requests
* @param {string} token - Mixpanel project token
* @param {string} sdkVersion - SDK version string
* @returns {Object} - Query parameters object
*/
function prepareCommonQueryParams(token, sdkVersion) {
return {
mp_lib: "node",
$lib_version: sdkVersion,
token: token,
};
}
/**
* Generate W3C traceparent header for distributed tracing
* Format: 00-{trace-id}-{parent-id}-{trace-flags}
* @returns {string} - traceparent header value
*/
function generateTraceparent() {
const version = "00";
const traceId = crypto.randomBytes(16).toString("hex");
const parentId = crypto.randomBytes(8).toString("hex");
const traceFlags = "01"; // sampled
return `${version}-${traceId}-${parentId}-${traceFlags}`;
}
function lowercaseJson(obj, lowercaseKeys) {
if (obj === null || obj === undefined) {
return obj;
} else if (typeof obj === "string") {
return obj.toLowerCase();
} else if (typeof obj === "object") {
if (Array.isArray(obj)) {
return obj.map((item) => lowercaseJson(item, lowercaseKeys));
} else {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => [
lowercaseKeys ? k.toLowerCase() : k,
lowercaseJson(v, lowercaseKeys),
]),
);
}
} else {
return obj;
}
}
function lowercaseAllKeysAndValues(obj) {
return lowercaseJson(obj, true);
}
function lowercaseLeafNodes(obj) {
return lowercaseJson(obj, false);
}
module.exports = {
EXPOSURE_EVENT,
REQUEST_HEADERS,
normalizedHash,
prepareCommonQueryParams,
generateTraceparent,
lowercaseAllKeysAndValues,
lowercaseLeafNodes,
};