1/*! 2 * QuickRouter ææ¡£ç«å¹¿åå½å 模å (channel.js) 3 * 4 * 两å¥ç¬ç«å½å ï¼å¹¶åäºä¸å¹²æ°ï¼ 5 * 6 * [A] ç¾åº¦/ææ¡£æ¸ éå½å ï¼åæï¼ä¸åï¼ 7 * - Cookie/localStorage key: quickrouter_attribution 8 * - ä¸é¦é¡µ src/lib/attribution.ts å¯¹é½ 9 * - URL åå ¥åªå 许 7 ä¸ªææ¡£æ¸ éï¼å ±äº« Cookie 读åå è®¸å ¨é¨ 10 ä¸ªæ¸ é 10 * 11 * [B] Google Ads å½å ï¼æ°å¢ï¼ 12 * - Cookie/localStorage key: quickrouter_google_attribution 13 * - åæ®µç½ååï¼gclid/gbraid/wbraid/gad_source/utmç³»å/campaignid/adgroupid/ 14 * creative/targetid/matchtype/device/network/keyword 15 * - éå å é¨å段ï¼source(doc.quickrouter.ai)/landingPath/capturedAt/expiresAt/version(2) 16 * - 触åï¼URL å« gclid/gbraid/wbraid æ utm_source=google æè§ä¸ºæ° Google ç¹å» 17 * - æ´ç»è¦çï¼ç¦æ¢éé¡¹æ¼æ¥ï¼æ®é访é®ä¸æ¸ é¤ 18 * 19 * 注å龿¥ï¼/go-registerï¼åæ¶æºå¸¦ [A] å [B] çç½åååæ°ã 20 * ä¸ä¿®æ¹ UIãææ¡ãå¸å±ãæç¨æ£æãç»å½æé®ã/consoleã/pricingãAPI æ ¹ååæ®é龿¥ã 21 * ä¸å¼å ¥ç¬¬ä¸æ¹ä¾èµãåç JSï¼æ§æµè§å¨å ¼å®¹ã 22 */ 23(function () { 24 'use strict'; 25 26 // ä» æµè§å¨æ§è¡ï¼æå»º/SSR å®å ¨ï¼ 27 if (typeof window === 'undefined' || typeof document === 'undefined') { 28 return; 29 } 30 31 // ============================================================ 32 // å ±äº«å¸¸éä¸å·¥å · 33 // ============================================================ 34 35 var COOKIE_MAX_AGE = 2592000; // 30 天 36 var COOKIE_DOMAIN = '.quickrouter.ai'; 37 var REGISTER_BASE_URL = 'https://quickrouter.ai/go-register'; 38 var REGISTER_BTN_SELECTOR = 'a.header-register, a[data-register-link]'; 39 var GOOGLE_COOKIE_MAX_BYTES = 3500; 40 var GOOGLE_VERSION = 2; 41 var DOC_SOURCE = 'doc.quickrouter.ai'; 42 43 function isProductionHost() { 44 var h = window.location.hostname || ''; 45 return h.indexOf('quickrouter.ai') !== -1; 46 } 47 48 function isLocalhost() { 49 var h = window.location.hostname || ''; 50 return h === 'localhost' || h === '127.0.0.1' || h === '::1'; 51 } 52 53 // éç¨ Cookie åå ¥ï¼localhost ä¸è®¾ Domain/Secureï¼ 54 function setCookie(name, value) { 55 var parts = [name + '=' + value, 'path=/', 'max-age=' + COOKIE_MAX_AGE, 'SameSite=Lax']; 56 if (isProductionHost()) { 57 parts.push('Domain=' + COOKIE_DOMAIN, 'Secure'); 58 } 59 document.cookie = parts.join('; '); 60 } 61 62 function deleteCookie(name) { 63 var lines = [name + '=; path=/; max-age=0']; 64 if (isProductionHost()) { 65 lines.push(name + '=; path=/; max-age=0; Domain=' + COOKIE_DOMAIN); 66 } 67 for (var i = 0; i < lines.length; i++) document.cookie = lines[i]; 68 } 69 70 // éç¨ï¼æ§å¶åç¬¦è¿æ»¤ + trim + æå段é¿åº¦æªæ 71 function sanitizeValue(value, maxLen) { 72 if (value === null || value === undefined) return null; 73 var cleaned = String(value).replace(/[\x00-\x1f\x7f]/g, '').replace(/^\s+|\s+$/g, ''); 74 if (!cleaned) return null; 75 if (maxLen && cleaned.length > maxLen) { 76 cleaned = cleaned.slice(0, maxLen); 77 } 78 return cleaned; 79 } 80 81 // éç¨ï¼ä» document.cookie 读åå¹¶ JSON è§£ç 82 function readCookieJSON(name) { 83 try { 84 var cookies = document.cookie ? document.cookie.split('; ') : []; 85 for (var i = 0; i < cookies.length; i++) { 86 if (cookies[i].indexOf(name + '=') === 0) { 87 var raw = cookies[i].split('=').slice(1).join('='); 88 return JSON.parse(decodeURIComponent(raw)); 89 } 90 } 91 } catch (e) {} 92 return null; 93 } 94 95 // éç¨ï¼ä» localStorage 读åå¹¶ JSON è§£ç 96 function readStorageJSON(key) { 97 try { 98 var raw = window.localStorage.getItem(key); 99 if (!raw) return null; 100 return JSON.parse(raw); 101 } catch (e) {} 102 return null; 103 } 104 105 // ============================================================ 106 // [A] ç¾åº¦/ææ¡£æ¸ éå½å ï¼åæé»è¾ï¼ä¿æä¸åï¼ 107 // ============================================================ 108 109 var DOC_CHANNELS = [ 110 'c_k0fxd6d2', 'c_onaqud4z', 'c_4wmiketg', 'c_pe0gzbkc', 111 'c_gbxim5yt', 'c_zhcy4sth', 'c_pxh0vohq' 112 ]; 113 var ALL_CHANNELS = [ 114 'c_dngxpufq', 'c_zia4gxbb', 'c_ake3gman', 115 'c_k0fxd6d2', 'c_onaqud4z', 'c_4wmiketg', 'c_pe0gzbkc', 116 'c_gbxim5yt', 'c_zhcy4sth', 'c_pxh0vohq' 117 ]; 118 var TRACKING_KEYS = [ 119 'bd_vid', 'planid', 'unitid', 'creative', 'keywordid', 120 'matchtype', 'equipment_type', 'userid', 'longtail' 121 ]; 122 var TRACKING_MAX_LENGTH = { 123 bd_vid: 128, planid: 64, unitid: 64, creative: 64, keywordid: 128, 124 matchtype: 8, equipment_type: 8, userid: 64, longtail: 256 125 }; 126 127 var ATTRIBUTION_COOKIE_NAME = 'quickrouter_attribution'; 128 var ATTRIBUTION_STORAGE_KEY = 'quickrouter_attribution'; 129 130 var DOC_CHANNEL_SET = {}; 131 for (var i = 0; i < DOC_CHANNELS.length; i++) DOC_CHANNEL_SET[DOC_CHANNELS[i]] = true; 132 var ALL_CHANNEL_SET = {}; 133 for (var j = 0; j < ALL_CHANNELS.length; j++) ALL_CHANNEL_SET[ALL_CHANNELS[j]] = true; 134
135 function isDocChannel(v) { 136 return !!v && Object.prototype.hasOwnProperty.call(DOC_CHANNEL_SET, v); 137 } 138 function isAnyChannel(v) { 139 return !!v && Object.prototype.hasOwnProperty.call(ALL_CHANNEL_SET, v); 140 } 141 142 // ç¾åº¦å段ä¸ç¨ sanitizeï¼æ TRACKING_MAX_LENGTHï¼ 143 function sanitizeBaidu(key, value) { 144 if (!value) return null; 145 var cleaned = String(value).replace(/[\x00-\x1f\x7f]/g, '').replace(/^\s+|\s+$/g, ''); 146 if (!cleaned) return null; 147 var maxLen = TRACKING_MAX_LENGTH[key]; 148 if (maxLen && cleaned.length > maxLen) cleaned = cleaned.slice(0, maxLen); 149 return cleaned; 150 } 151 152 function readAttributionFromSearchParams(params) { 153 var channel = null; 154 try { channel = params.get('channel'); } catch (e) { return null; } 155 if (!isDocChannel(channel)) return null; 156 var attribution = { channel: channel }; 157 for (var i = 0; i < TRACKING_KEYS.length; i++) { 158 var key = TRACKING_KEYS[i]; 159 var cleaned = sanitizeBaidu(key, params.get(key)); 160 if (cleaned) attribution[key] = cleaned; 161 } 162 return attribution; 163 } 164 165 function validateAttribution(obj) { 166 if (!obj || typeof obj !== 'object') return null; 167 var channel = typeof obj.channel === 'string' ? obj.channel : null; 168 if (!isAnyChannel(channel)) return null; 169 var attribution = { channel: channel }; 170 for (var i = 0; i < TRACKING_KEYS.length; i++) { 171 var key = TRACKING_KEYS[i]; 172 var raw = obj[key]; 173 var cleaned = sanitizeBaidu(key, typeof raw === 'string' ? raw : null); 174 if (cleaned) attribution[key] = cleaned; 175 } 176 return attribution; 177 } 178 179 function clearAttribution() { 180 deleteCookie(ATTRIBUTION_COOKIE_NAME); 181 deleteCookie('quickrouter_channel'); 182 for (var i = 0; i < TRACKING_KEYS.length; i++) { 183 deleteCookie('qr_track_' + TRACKING_KEYS[i]); 184 } 185 try { 186 window.localStorage.removeItem(ATTRIBUTION_STORAGE_KEY); 187 window.localStorage.removeItem('quickrouter_channel'); 188 window.localStorage.removeItem('qr_tracking_params'); 189 } catch (e) {} 190 } 191 192 function writeAttribution(attribution) { 193 clearAttribution(); 194 var json = JSON.stringify(attribution);
195 setCookie(ATTRIBUTION_COOKIE_NAME, encodeURIComponent(json)); 196 try { window.localStorage.setItem(ATTRIBUTION_STORAGE_KEY, json); } catch (e) {} 197 } 198 199 function readAttributionFromClientCookie() { 200 var obj = readCookieJSON(ATTRIBUTION_COOKIE_NAME); 201 return validateAttribution(obj); 202 } 203 204 function readAttributionFromStorage() { 205 return validateAttribution(readStorageJSON(ATTRIBUTION_STORAGE_KEY)); 206 } 207 208 function resolveAttribution() { 209 var urlAttrib = null; 210 try { 211 urlAttrib = readAttributionFromSearchParams(new URLSearchParams(window.location.search)); 212 } catch (e) {} 213 if (urlAttrib) return urlAttrib; 214 var c = readAttributionFromClientCookie(); 215 if (c) return c; 216 return readAttributionFromStorage(); 217 } 218 219 // ============================================================ 220 // [B] Google Ads å½å ï¼æ°å¢ï¼ 221 // ============================================================ 222 223 var GOOGLE_COOKIE_NAME = 'quickrouter_google_attribution'; 224 var GOOGLE_STORAGE_KEY = 'quickrouter_google_attribution'; 225 226 // Google ç½åååæ®µåå ¶æå¤§é¿åº¦ï¼ä¸ä¸»ç«ä¸è´ï¼ 227 var GOOGLE_FIELDS = [ 228 'gclid', 'gbraid', 'wbraid', 'gad_source', 229 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id', 230 'campaignid', 'adgroupid', 'creative', 'targetid', 'matchtype', 'device', 'network', 'keyword' 231 ]; 232 var GOOGLE_MAX_LENGTH = { 233 gclid: 256, gbraid: 256, wbraid: 256, gad_source: 64, 234 utm_source: 128, utm_medium: 128, utm_campaign: 128, utm_term: 256, utm_content: 256, utm_id: 64, 235 campaignid: 64, adgroupid: 64, creative: 64, targetid: 256, matchtype: 8, device: 8, network: 16, keyword: 256 236 }; 237 // ä¸éå å°æ³¨å龿¥çå é¨å段 238 var GOOGLE_INTERNAL_FIELDS = ['source', 'landingPath', 'capturedAt', 'expiresAt', 'version']; 239 240 // Cookie è¶ 3500 åèæ¶çåæ®µå é¤é¡ºåºï¼ä»åå°å便¬¡å ï¼æ ¸å¿å段ä¸å¨å ¶ä¸ï¼ 241 var GOOGLE_TRIM_ORDER = [ 242 'landingPath', 'keyword', 'utm_content', 'utm_term', 'utm_id', 243 'utm_campaign', 'utm_medium', 'utm_source', 'gad_source', 244 'targetid', 'creative', 'network', 'device', 'matchtype' 245 ]; 246 // ç»ä¸è½å é¤çç¹å»æ è¯ 247 var GOOGLE_CLICK_IDS = ['gclid', 'gbraid', 'wbraid']; 248 // æ ¸å¿å段ï¼è£åªåå¿ é¡»ä¿çï¼campaignid/adgroupid ä¹å±æ ¸å¿ä½ä¸å¨å é¤é¡ºåºéï¼å¤©ç¶ä¿çï¼ 249 var GOOGLE_CORE_FIELDS = ['campaignid', 'adgroupid', 'capturedAt', 'expiresAt', 'version', 'source']; 250 // æªæé¶æ®µï¼ç¹å»æ è¯æå¤ 256ï¼campaignid/adgroupid æå¤ 64 251 var GOOGLE_TRUNCATE_LIMITS = { gclid: 256, gbraid: 256, wbraid: 256, campaignid: 64, adgroupid: 64 }; 252 253 // 夿æ¯å¦ä¸ºæ°ç Google ç¹å»ï¼å« gclid/gbraid/wbraid æ utm_source=google 254 function isGoogleClick(params) { 255 try { 256 if (params.get('gclid') || params.get('gbraid') || params.get('wbraid')) return true; 257 var utmSource = params.get('utm_source'); 258 if (utmSource && utmSource.toLowerCase() === 'google') return true; 259 } catch (e) {} 260 return false; 261 } 262 263 // ä» URLSearchParams æå»º Google å½å å¯¹è±¡ï¼æ´ç»ï¼åªåç½åååæ®µ + å é¨åæ®µï¼ 264 function readGoogleFromSearchParams(params) { 265 if (!isGoogleClick(params)) return null; 266 var obj = {}; 267 for (var i = 0; i < GOOGLE_FIELDS.length; i++) { 268 var key = GOOGLE_FIELDS[i]; 269 var cleaned = sanitizeValue(params.get(key), GOOGLE_MAX_LENGTH[key]); 270 if (cleaned) obj[key] = cleaned; 271 } 272 // éå å é¨å段 273 obj.source = DOC_SOURCE; 274 try { obj.landingPath = window.location.pathname; } catch (e) { obj.landingPath = ''; } 275 var now = Date.now(); 276 obj.capturedAt = now; 277 obj.expiresAt = now + COOKIE_MAX_AGE * 1000; 278 obj.version = GOOGLE_VERSION; 279 return obj; 280 } 281 282 // éªè¯ä»åå¨è¯»åºç Google 对象ï¼åªä¿çç½åå + åæ³å é¨åæ®µï¼ 283 function validateGoogle(obj) { 284 if (!obj || typeof obj !== 'object') return null; 285 // å¿ é¡»è³å°æä¸ä¸ª Google 广ååæ®µï¼å¦åè§ä¸ºæ æ 286 var hasField = false; 287 var out = {}; 288 for (var i = 0; i < GOOGLE_FIELDS.length; i++) { 289 var key = GOOGLE_FIELDS[i]; 290 var raw = obj[key];
291 var cleaned = sanitizeValue(typeof raw === 'string' ? raw : null, GOOGLE_MAX_LENGTH[key]); 292 if (cleaned) { out[key] = cleaned; hasField = true; } 293 } 294 if (!hasField) return null; 295 // ä¿çå é¨å段ï¼è¥åå¨ï¼ 296 if (obj.source) out.source = obj.source; 297 if (obj.landingPath) out.landingPath = obj.landingPath; 298 if (typeof obj.capturedAt === 'number') out.capturedAt = obj.capturedAt; 299 if (typeof obj.expiresAt === 'number') out.expiresAt = obj.expiresAt; 300 if (obj.version) out.version = obj.version; 301 return out; 302 } 303 304 // 对象深æ·è´ï¼ç®åçï¼ä» ç¨äºæå¹³ Google å¯¹è±¡ï¼ 305 function cloneGoogleObj(obj) { 306 var out = {}; 307 for (var k in obj) { 308 if (Object.prototype.hasOwnProperty.call(obj, k)) out[k] = obj[k]; 309 } 310 return out; 311 } 312 313 // æªæè¶ é¿åç¬¦ä¸²åæ®µï¼ç¹å»æ è¯<=256ï¼campaignid/adgroupid<=64ï¼ 314 function truncateCoreFields(obj) { 315 for (var field in GOOGLE_TRUNCATE_LIMITS) { 316 if (Object.prototype.hasOwnProperty.call(GOOGLE_TRUNCATE_LIMITS, field) && obj[field]) { 317 var limit = GOOGLE_TRUNCATE_LIMITS[field]; 318 var val = String(obj[field]); 319 if (val.length > limit) obj[field] = val.slice(0, limit); 320 } 321 } 322 } 323 324 // å° Google 对象ç¼ç åæ§å¶å¨ <= maxBytesï¼å§ç»è¿åå¯è§£æç encoded å符串 325 function encodeForCookie(obj, maxBytes) { 326 var working = cloneGoogleObj(obj); 327 328 // é¶æ®µ 1ï¼å°è¯åå§å¯¹è±¡ 329 var encoded = encodeURIComponent(JSON.stringify(working)); 330 if (encoded.length <= maxBytes) return encoded; 331 332 // é¶æ®µ 2ï¼æ GOOGLE_TRIM_ORDER 便¬¡å é¤éæ ¸å¿å段 333 for (var i = 0; i < GOOGLE_TRIM_ORDER.length; i++) { 334 var field = GOOGLE_TRIM_ORDER[i]; 335 if (working[field] !== undefined) { 336 delete working[field]; 337 encoded = encodeURIComponent(JSON.stringify(working)); 338 if (encoded.length <= maxBytes) return encoded; 339 } 340 } 341 342 // é¶æ®µ 3ï¼æªææ ¸å¿åç¬¦ä¸²åæ®µ 343 truncateCoreFields(working); 344 encoded = encodeURIComponent(JSON.stringify(working)); 345 // æç»å åºï¼æç«¯æ åµä¸ä»è¶ éï¼å¼ºå¶ä¿è¯å¯è§£æï¼æ¤æ¶å·²æ¯æå¤§è£åªï¼ 346 return encoded; 347 } 348 349 function writeGoogle(googleObj) { 350 // localStorage ä¿ååå§å®æ´å¯¹è±¡ 351 var fullJson = JSON.stringify(googleObj); 352 try { window.localStorage.setItem(GOOGLE_STORAGE_KEY, fullJson); } catch (e) {} 353 354 // ç¶å Cookie å¿ é¡»å§ç»åå ¥ï¼è¶ éæ¶æä¼å 级è£åªå段ï¼ç»ä¸é级为åªå localStorage 355 var encoded = encodeForCookie(googleObj, GOOGLE_COOKIE_MAX_BYTES);
356 setCookie(GOOGLE_COOKIE_NAME, encoded); 357 } 358 359 function readGoogleFromClientCookie() { 360 return validateGoogle(readCookieJSON(GOOGLE_COOKIE_NAME)); 361 } 362 363 function readGoogleFromStorage() { 364 return validateGoogle(readStorageJSON(GOOGLE_STORAGE_KEY)); 365 } 366 367 // 读åä¼å 级ï¼URL(æ° Google ç¹å») > Cookie > localStorage 368 function resolveGoogle() { 369 var urlGoogle = null; 370 try { 371 urlGoogle = readGoogleFromSearchParams(new URLSearchParams(window.location.search)); 372 } catch (e) {} 373 if (urlGoogle) return urlGoogle; 374 var c = readGoogleFromClientCookie(); 375 if (c) return c; 376 return readGoogleFromStorage(); 377 } 378 379 // ============================================================ 380 // 注å龿¥çæï¼åå¹¶ç¾åº¦ + Google ç½åååæ°ï¼ 381 // ============================================================ 382 383 // ç¨ URL + URLSearchParams çæï¼åæ¶éå ç¾åº¦æ¸ éåæ°å Google ç½åååæ° 384 // ä¸éå Google å é¨å段ï¼source/landingPath/capturedAt/expiresAt/versionï¼ 385 function buildRegisterUrl(attribution, google) { 386 var url = new URL(REGISTER_BASE_URL); 387 388 // ç¾åº¦æ¸ éåæ°
389 if (attribution && isAnyChannel(attribution.channel)) { 390 url.searchParams.set('channel', attribution.channel); 391 for (var i = 0; i < TRACKING_KEYS.length; i++) { 392 var bkey = TRACKING_KEYS[i]; 393 if (attribution[bkey]) url.searchParams.set(bkey, attribution[bkey]); 394 } 395 } 396 397 // Google ç½åååæ°ï¼æé¤å é¨åæ®µï¼ 398 if (google) { 399 for (var k = 0; k < GOOGLE_FIELDS.length; k++) { 400 var gkey = GOOGLE_FIELDS[k]; 401 if (google[gkey]) url.searchParams.set(gkey, google[gkey]); 402 } 403 } 404 405 return url.toString(); 406 } 407 408 function updateRegisterButtons(attribution, google) { 409 var btns = document.querySelectorAll(REGISTER_BTN_SELECTOR); 410 if (!btns || !btns.length) return; 411 var href = buildRegisterUrl(attribution, google); 412 for (var i = 0; i < btns.length; i++) { 413 btns[i].setAttribute('href', href); 414 } 415 } 416 417 // ============================================================ 418 // 主æµç¨ 419 // ============================================================ 420 421 function init() { 422 var params = null; 423 try { params = new URLSearchParams(window.location.search); } catch (e) {} 424 425 // [A] ç¾åº¦æ¸ éï¼URL å«åæ³ææ¡£ channel â æ´ç»è¦ç 426 var urlAttrib = null; 427 if (params) { 428 try { urlAttrib = readAttributionFromSearchParams(params); } catch (e) {} 429 } 430 if (urlAttrib) { 431 writeAttribution(urlAttrib); 432 } 433 // æ®éè®¿é®æ æ°åæ³ channelï¼ä¸æ¸ é¤ãä¸è¦ç 434 435 // [B] Googleï¼URL æ¯æ° Google ç¹å» â æ´ç»è¦çï¼ç¬ç«äºç¾åº¦ï¼ 436 var urlGoogle = null; 437 if (params) { 438 try { urlGoogle = readGoogleFromSearchParams(params); } catch (e) {} 439 } 440 if (urlGoogle) { 441 writeGoogle(urlGoogle); 442 } 443 // æ®é访é®ä¸æ¯ Google ç¹å»ï¼ä¸æ¸ é¤ Google å½å 444 445 // è§£æå½åçæç两å¥å½å 446 var currentAttrib = resolveAttribution(); 447 var currentGoogle = resolveGoogle(); 448 449 // æ¹å header 注åæé®ï¼åæ¶æºå¸¦ä¸¤å¥åæ°ï¼ 450 updateRegisterButtons(currentAttrib, currentGoogle); 451 } 452 453 // ============================================================ 454 // è°è¯ API 455 // ============================================================ 456 457 window.QRChannel = { 458 // ç¾åº¦æ¸ é 459 resolve: resolveAttribution, 460 // Google å½å 461 resolveGoogle: resolveGoogle, 462 // 注å龿¥çæï¼æ åæ¶ç¨å½åçæå½å ï¼ 463 buildRegisterUrl: function (attribution, google) { 464 if (arguments.length === 0) { 465 return buildRegisterUrl(resolveAttribution(), resolveGoogle()); 466 } 467 return buildRegisterUrl(attribution || null, google || null); 468 }, 469 docChannels: DOC_CHANNELS.slice(), 470 allChannels: ALL_CHANNELS.slice(), 471 trackingKeys: TRACKING_KEYS.slice(), 472 googleFields: GOOGLE_FIELDS.slice() 473 }; 474 475 // èæ¬ä½äº <body> æ«å°¾ï¼DOM ä¸è¬å·²å°±ç»ªï¼åéå åº 476 if (document.readyState === 'loading') { 477 document.addEventListener('DOMContentLoaded', init); 478 } else { 479 init(); 480 } 481})();
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.