Skip to content

Instantly share code, notes, and snippets.

@LRENZ
Last active May 13, 2025 10:02
Show Gist options
  • Save LRENZ/ba10f38825fd56c15452aefb8b9a760f to your computer and use it in GitHub Desktop.
Save LRENZ/ba10f38825fd56c15452aefb8b9a760f to your computer and use it in GitHub Desktop.
GET GA4 client id
function generateUUID() { // Public Domain/MIT
var d = new Date().getTime(); //Timestamp
var d2 = (typeof performance !== 'undefined' && performance.now && (performance.now() * 1000)) || 0; //Time in microseconds since page-load or 0 if unsupported
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16; //random number between 0 and 16
if (d > 0) { //Use timestamp until depleted
r = (d + r) % 16 | 0;
d = Math.floor(d / 16);
} else { //Use microseconds since page-load if supported
r = (d2 + r) % 16 | 0;
d2 = Math.floor(d2 / 16);
}
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
function get_ga4_client_id(mid) { // Renamed for consistency if you have get_ga4_session_id
return new Promise((resolve) => {
let clientId = '';
let resolved = false; // 标记是否已解决,防止多次解决
// 辅助函数:尝试从 _ga cookie 获取 client_id
const tryGetFromGaCookie = () => {
const cookies = document.cookie.split(';').reduce((acc, cookie) => {
const parts = cookie.split('=').map(c => c.trim());
acc[parts[0]] = parts[1];
return acc;
}, {});
// _ga cookie format is typically GA1.X.YYYYYYYYYY.ZZZZZZZZZZ
// where YYYYYYYYYY.ZZZZZZZZZZ is the client ID.
// The prefix "GA1.X." is usually 6 characters long (e.g., "GA1.2.").
// So, substring(6) is a common way.
// A more robust way might be to split by '.' and take parts[2] + '.' + parts[3]
// if the "X" part can vary in length, but for _ga it's usually fixed.
if (cookies._ga) {
// Example: _ga=GA1.2.123456789.1234567890
// Client ID: 123456789.1234567890
const cookieValue = cookies._ga;
const parts = cookieValue.split('.');
if (parts.length === 4 && parts[0] === 'GA1') { // Basic validation
console.log("get client id from _ga cookie");
return `${parts[2]}.${parts[3]}`;
} else if (cookieValue.length > 6 && cookieValue.startsWith('GA1.')) {
// Fallback to simpler substring if split logic fails but looks like _ga
console.log("get client id from _ga cookie (substring method)");
return cookieValue.substring(6); // This was in your original code
} else {
console.warn("_ga cookie found, but format is unexpected:", cookieValue);
}
}
return null;
};
// 辅助函数:回退到生成 UUID
const fallbackToUUID = () => {
console.log("generate client id using UUID");
return generateUUID();
};
const resolveClientId = (id, source) => {
if (!resolved) {
resolved = true;
console.log(`客户端ID (来源: ${source}):`, id);
resolve(id);
}
};
// 1. 尝试使用 gtag API
if (typeof gtag === 'function') {
let gtagTimedOut = false;
const gtagTimeout = setTimeout(() => {
if (resolved) return;
gtagTimedOut = true;
console.warn("gtag 'get' client_id timed out. Falling back.");
clientId = tryGetFromGaCookie();
if (clientId) {
resolveClientId(clientId, "cookies (after gtag timeout)");
} else {
resolveClientId(fallbackToUUID(), "generated UUID (after gtag timeout)");
}
}, 1500); // 设置一个超时时间,例如1.5秒
try {
gtag('get', mid, 'client_id', (client_id_from_api) => {
clearTimeout(gtagTimeout);
if (resolved || gtagTimedOut) return;
if (client_id_from_api) {
console.log("get client id from gtag API");
resolveClientId(client_id_from_api, "gtag API");
} else {
console.log("gtag API did not return client_id. Falling back to cookies.");
clientId = tryGetFromGaCookie();
if (clientId) {
resolveClientId(clientId, "cookies (after gtag empty)");
} else {
resolveClientId(fallbackToUUID(), "generated UUID (after gtag empty & cookies empty)");
}
}
});
} catch (error) {
clearTimeout(gtagTimeout);
if (resolved) return;
console.error("Error calling gtag 'get' for client_id:", error, "Falling back.");
clientId = tryGetFromGaCookie();
if (clientId) {
resolveClientId(clientId, "cookies (after gtag error)");
} else {
resolveClientId(fallbackToUUID(), "generated UUID (after gtag error & cookies empty)");
}
}
} else {
// gtag API 不可用,直接尝试 cookies
console.log("gtag is not a function. Falling back to cookies for client_id.");
clientId = tryGetFromGaCookie();
if (clientId) {
resolveClientId(clientId, "cookies (gtag not available)");
} else {
// cookies 中也没有,则生成 UUID
resolveClientId(fallbackToUUID(), "generated UUID (gtag not available & cookies empty)");
}
}
});
}
function get_ga4_session_id(mid) {
return new Promise((resolve) => {
let sessionId = '';
let resolved = false; // 标记是否已解决,防止多次解决
// 辅助函数:尝试从 cookies 获取
const tryGetFromCookies = () => {
const cookies = document.cookie.split(';').reduce((acc, cookie) => {
const parts = cookie.split('=').map(c => c.trim());
acc[parts[0]] = parts[1];
return acc;
}, {});
const measurementIdSuffix = mid.startsWith('G-') ? mid.substring(2) : mid;
if (!measurementIdSuffix) {
console.warn("Invalid mid format for cookie lookup:", mid);
return null;
}
const cookieKey = '_ga_' + measurementIdSuffix;
const ga4CookieValue = cookies[cookieKey];
if (ga4CookieValue) {
console.log("Found GA4 cookie " + cookieKey + ":", ga4CookieValue);
const parts = ga4CookieValue.split('.');
// GA4 session ID is typically in the third segment (index 2)
// Format examples:
// Old/Simple: GS1.1.1680000000.1.0... (session_id is 1680000000)
// New "s" prefix: GS1.1.s1747128224$o8... (session_id is 1747128224)
// New "s" prefix (GA4 property ID in cookie): _ga_R12345=GS1.1.s1700000000$stuff...
if (parts.length >= 3) {
let sessionInfoPart = parts[2];
// Check for the new format like "s1747128224$o8..."
if (sessionInfoPart.startsWith('s') && sessionInfoPart.includes('$')) {
const sessionTimestamp = sessionInfoPart.substring(1).split('$')[0];
if (/^\d+$/.test(sessionTimestamp)) { // Validate it's a number
console.log("get session id from cookies (new 's...$' format)");
return sessionTimestamp;
} else {
console.warn("Cookie " + cookieKey + " ('s...$' format) session part is not a valid timestamp:", sessionInfoPart);
}
}
// Else, assume it's the old/simple format (just a timestamp)
else if (/^\d+$/.test(sessionInfoPart)) { // Validate it's a number
console.log("get session id from cookies (simple timestamp format)");
return sessionInfoPart;
} else {
console.warn("Cookie " + cookieKey + " found, but session part (parts[2]) format is unexpected:", sessionInfoPart);
}
} else {
console.warn("Cookie " + cookieKey + " found, but format (dot parts) is unexpected:", ga4CookieValue);
}
} else {
console.log("Cookie " + cookieKey + " not found.");
}
return null;
};
// 辅助函数:回退到时间戳
const fallbackToTimestamp = () => {
console.log("get session id from current timestamp");
return Date.now().toString();
};
const resolveSessionId = (id, source) => {
if (!resolved) {
resolved = true;
// GA4 session IDs are numbers, but gtag API might return them as strings.
// Timestamps from cookies or Date.now() are numbers. Standardize to string.
console.log(`会话ID (来源: ${source}):`, String(id));
resolve(String(id));
}
};
// 1. 尝试使用 gtag API
if (typeof gtag === 'function') {
let gtagTimedOut = false;
const gtagTimeout = setTimeout(() => {
if (resolved) return;
gtagTimedOut = true;
console.warn("gtag 'get' session_id timed out. Falling back.");
sessionId = tryGetFromCookies();
if (sessionId) {
resolveSessionId(sessionId, "cookies (after gtag timeout)");
} else {
resolveSessionId(fallbackToTimestamp(), "timestamp (after gtag timeout)");
}
}, 1500); // 设置一个超时时间,例如1.5秒
try {
gtag('get', mid, 'session_id', (session_id_from_api) => {
clearTimeout(gtagTimeout);
if (resolved || gtagTimedOut) return;
if (session_id_from_api) {
console.log("get session id from gtag API");
resolveSessionId(session_id_from_api, "gtag API");
} else {
console.log("gtag API did not return session_id. Falling back to cookies.");
sessionId = tryGetFromCookies();
if (sessionId) {
resolveSessionId(sessionId, "cookies (after gtag empty)");
} else {
resolveSessionId(fallbackToTimestamp(), "timestamp (after gtag empty & cookies empty)");
}
}
});
} catch (error) {
clearTimeout(gtagTimeout);
if (resolved) return;
console.error("Error calling gtag 'get' for session_id:", error, "Falling back.");
sessionId = tryGetFromCookies();
if (sessionId) {
resolveSessionId(sessionId, "cookies (after gtag error)");
} else {
resolveSessionId(fallbackToTimestamp(), "timestamp (after gtag error & cookies empty)");
}
}
} else {
console.log("gtag is not a function. Falling back to cookies.");
sessionId = tryGetFromCookies();
if (sessionId) {
resolveSessionId(sessionId, "cookies (gtag not available)");
} else {
resolveSessionId(fallbackToTimestamp(), "timestamp (gtag not available & cookies empty)");
}
}
});
}
async function getCookiesPayload() {
try {
const payload = {
"event": "store_cookies",
"cart_token": event.data.checkout.token,
"fbp": await browser.cookie.get('_fbp'),
"fbc": await browser.cookie.get('_fbc'),
"_ga": await browser.cookie.get('_ga'),
"_ga_YGX1C71PZB": await browser.cookie.get('_ga_YGX1C71PZB'),
"ga_client_id": get_ga4_client_id("G-YGX1C71PZB"),
"ga_session_id": get_ga4_session_id("G-YGX1C71PZB"),
"shopify_customer_id": await browser.cookie.get('shopify_customer_id')
};
return payload;
} catch (error) {
console.error('Error retrieving cookies:', error);
return null;
}
}
async function sendCookiesToSgtm() {
const payload = await getCookiesPayload();
if (payload) {
fetch("https://pmnbqnod.apc.stape.io/data", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload),
keepalive: true
}).then(response => {
console.log('Payload sent successfully:', response);
}).catch(error => {
console.error('Error sending payload:', error);
});
}
}
sendCookiesToSgtm();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment