Last active
May 13, 2025 10:02
-
-
Save LRENZ/ba10f38825fd56c15452aefb8b9a760f to your computer and use it in GitHub Desktop.
GET GA4 client id
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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