1(function(window){ 2 var STORAGE = { 3 anonymousId: 'hw_anonymous_id', 4 onboardingSessionId: 'hw_onboarding_session_id', 5 onboardingSessionMeta: 'hw_onboarding_session_meta', 6 onboardingFirstSession: 'hw_onboarding_first_session_v2', 7 sentEvents: 'hw_onboarding_sent_events_v2', 8 debugEvents: 'hw_onboarding_debug_events_v1' 9 }; 10 11 function safeGet(key) { 12 try { return localStorage.getItem(key); } catch (e) { return null; } 13 } 14 function safeSet(key, value) { 15 try { localStorage.setItem(key, value); } catch (e) {} 16 } 17 function safeRemove(key) { 18 try { localStorage.removeItem(key); } catch (e) {} 19 } 20 function parseJson(raw, fallback) { 21 if (!raw) return fallback; 22 try { return JSON.parse(raw); } catch (e) { return fallback; } 23 } 24 function uuid(prefix) { 25 prefix = prefix || 'evt'; 26 return prefix + '_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 10); 27 } 28 function getAnonymousId() { 29 var id = safeGet(STORAGE.anonymousId); 30 if (!id) { 31 id = uuid('anon'); 32 safeSet(STORAGE.anonymousId, id); 33 } 34 return id; 35 } 36 function getOnboardingVersion() { 37 return 'onboarding_v2'; 38 } 39 function getEntryVariant(explicit) { 40 if (explicit) return explicit; 41 var meta = parseJson(safeGet(STORAGE.onboardingSessionMeta), null); 42 return (meta && meta.entry_variant) || 'fallback_unknown'; 43 } 44 function getSessionMeta() { 45 return parseJson(safeGet(STORAGE.onboardingSessionMeta), {}) || {}; 46 } 47 function setSessionMeta(meta) { 48 safeSet(STORAGE.onboardingSessionMeta, JSON.stringify(meta || {})); 49 } 50 function getOrCreateOnboardingSession(entryVariant) { 51 var id = safeGet(STORAGE.onboardingSessionId); 52 var meta = getSessionMeta(); 53 if (!id || meta.ended_at) { 54 id = uuid('obs'); 55 meta = { 56 onboarding_session_id: id, 57 started_at: new Date().toISOString(), 58 entry_variant: getEntryVariant(entryVariant), 59 is_first_session: !safeGet(STORAGE.onboardingFirstSession), 60 ended_at: null 61 }; 62 safeSet(STORAGE.onboardingSessionId, id); 63 setSessionMeta(meta); 64 } else if (entryVariant && !meta.entry_variant) { 65 meta.entry_variant = entryVariant; 66 setSessionMeta(meta); 67 } 68 return { id: id, meta: meta }; 69 } 70 function endOnboardingSession(reason) { 71 var meta = getSessionMeta(); 72 if (!meta || !meta.onboarding_session_id) return; 73 meta.ended_at = new Date().toISOString(); 74 meta.flow_result = reason || 'completed'; 75 setSessionMeta(meta); 76 } 77 function getSentMap() { 78 return parseJson(safeGet(STORAGE.sentEvents), {}) || {}; 79 } 80 function hasSentOnce(key) { 81 var map = getSentMap(); 82 return !!map[key]; 83 } 84 function markSentOnce(key) { 85 var map = getSentMap(); 86 map[key] = new Date().toISOString(); 87 safeSet(STORAGE.sentEvents, JSON.stringify(map)); 88 } 89 function buildLevelPayload(extra) { 90 extra = extra || {}; 91 return { 92 initial_entry_level: extra.initial_entry_level == null ? null : extra.initial_entry_level, 93 placement_result_level: extra.placement_result_level == null ? null : extra.placement_result_level, 94 effective_start_level: extra.effective_start_level == null ? null : extra.effective_start_level 95 }; 96 } 97 function buildContext(extra) { 98 extra = extra || {}; 99 var session = getOrCreateOnboardingSession(extra.entry_variant); 100 var meta = session.meta || {}; 101 var payload = { 102 anonymous_id: getAnonymousId(), 103 onboarding_session_id: session.id, 104 event_id: uuid('evt'), 105 event_ts: new Date().toISOString(), 106 onboarding_version: getOnboardingVersion(), 107 entry_variant: getEntryVariant(extra.entry_variant), 108 is_first_session: !!meta.is_first_session, 109 flow_result: extra.flow_result == null ? null : extra.flow_result, 110 page_path: (window.location && window.location.pathname) || '/', 111 platform: 'web_pwa' 112 }; 113 for (var k in extra) { 114 if (Object.prototype.hasOwnProperty.call(extra, k) && extra[k] !== undefined) payload[k] = extra[k]; 115 } 116 return payload; 117 } 118 function validatePayload(eventName, payload) { 119 if (!payload.anonymous_id || !payload.onboarding_session_id) { 120 console.warn('[HiwordAnalytics] missing ids for', eventName, payload); 121 return false;
122 } 123 return true; 124 } 125 function appendDebugEvent(eventName, payload) { 126 var current = parseJson(safeGet(STORAGE.debugEvents), []); 127 if (!Array.isArray(current)) current = []; 128 current.push(Object.assign({ event_name: eventName }, payload)); 129 if (current.length > 200) current = current.slice(current.length - 200); 130 safeSet(STORAGE.debugEvents, JSON.stringify(current)); 131 } 132 function flushQueuedEvents() { 133 var queue = parseJson(safeGet('hw_onboarding_pending_events_v1'), []); 134 if (!Array.isArray(queue) || !queue.length) return Promise.resolve(false); 135 return sendEvents(queue).then(function(ok) { 136 if (ok) safeRemove('hw_onboarding_pending_events_v1'); 137 return ok; 138 }).catch(function() { return false; }); 139 } 140 function queuePendingEvent(payload) { 141 var queue = parseJson(safeGet('hw_onboarding_pending_events_v1'), []); 142 if (!Array.isArray(queue)) queue = []; 143 queue.push(payload); 144 if (queue.length > 200) queue = queue.slice(queue.length - 200); 145 safeSet('hw_onboarding_pending_events_v1', JSON.stringify(queue)); 146 } 147 function sendEvents(events) { 148 if (!window.fetch || !Array.isArray(events) || !events.length) return Promise.resolve(false); 149 return window.fetch('/api/onboarding-analytics', { 150 method: 'POST', 151 headers: { 'Content-Type': 'application/json' }, 152 body: JSON.stringify(events) 153 }).then(function(res) { 154 return !!(res && res.ok); 155 }).catch(function() { 156 return false; 157 }); 158 } 159 function trackEvent(eventName, extra, options) { 160 extra = extra || {}; 161 options = options || {}; 162 var payload = buildContext(extra); 163 var onceKey = options.onceKey || null; 164 if (onceKey && hasSentOnce(onceKey)) return false; 165 if (!validatePayload(eventName, payload)) return false; 166 appendDebugEvent(eventName, payload); 167 sendEvents([Object.assign({ event_name: eventName }, payload)]).then(function(ok) { 168 if (!ok) queuePendingEvent(Object.assign({ event_name: eventName }, payload)); 169 }); 170 if (typeof window.gtag === 'function') { 171 window.gtag('event', eventName, payload); 172 } 173 if (onceKey) markSentOnce(onceKey); 174 return payload; 175 } 176 function trackLegacyCompatible(eventName, legacyEventName, extra, options) { 177 var payload = trackEvent(eventName, extra, options); 178 if (!payload) return false; 179 if (legacyEventName && typeof window.gtag === 'function') { 180 var legacyPayload = Object.assign({ event_category: 'engagement' }, payload); 181 window.gtag('event', legacyEventName, legacyPayload); 182 } 183 return payload; 184 } 185 function freezeFirstOnboardingSession(payload) { 186 if (safeGet(STORAGE.onboardingFirstSession)) return; 187 safeSet(STORAGE.onboardingFirstSession, JSON.stringify(payload || {})); 188 } 189 function getFirstOnboardingSession() { 190 return parseJson(safeGet(STORAGE.onboardingFirstSession), null); 191 } 192 function startOnboardingSession(entryVariant, extra) { 193 var session = getOrCreateOnboardingSession(entryVariant); 194 var onceKey = 'session_start:' + session.id; 195 return trackEvent('onboarding_session_start', Object.assign({}, extra || {}, { 196 entry_variant: getEntryVariant(entryVariant) 197 }), { onceKey: onceKey }); 198 } 199 function trackOnboardingSkip(reason, extra) { 200 extra = extra || {}; 201 var meta = getSessionMeta(); 202 var payload = trackEvent('onboarding_skip', Object.assign({}, extra, { 203 skip_reason: reason || 'user_skip', 204 flow_result: 'skipped' 205 }), { onceKey: 'onboarding_skip:' + (meta.onboarding_session_id || 'none') }); 206 endOnboardingSession('skipped'); 207 return payload; 208 } 209 function completeActivation(extra) { 210 extra = extra || {}; 211 var meta = getSessionMeta(); 212 var payload = trackEvent('onboarding_activation_complete', Object.assign({}, extra, { 213 flow_result: 'completed' 214 }), { onceKey: 'onboarding_activation_complete:' + (meta.onboarding_session_id || 'none') }); 215 if (payload) { 216 freezeFirstOnboardingSession(payload); 217 endOnboardingSession('completed'); 218 } 219 return payload; 220 } 221 222 window.HiwordAnalytics = { 223 initAnalytics: function() { getAnonymousId(); flushQueuedEvents(); return true; }, 224 getAnonymousId: getAnonymousId, 225 getOrCreateOnboardingSession: getOrCreateOnboardingSession, 226 getSessionMeta: getSessionMeta, 227 startOnboardingSession: startOnboardingSession, 228 endOnboardingSession: endOnboardingSession, 229 buildLevelPayload: buildLevelPayload, 230 buildContext: buildContext, 231 trackEvent: trackEvent, 232 trackLegacyCompatible: trackLegacyCompatible, 233 freezeFirstOnboardingSession: freezeFirstOnboardingSession, 234 getFirstOnboardingSession: getFirstOnboardingSession, 235 trackOnboardingSkip: trackOnboardingSkip, 236 completeActivation: completeActivation, 237 hasSentOnce: hasSentOnce, 238 markSentOnce: markSentOnce, 239 getDebugEvents: function() { return parseJson(safeGet(STORAGE.debugEvents), []); }, 240 clearDebugEvents: function() { safeRemove(STORAGE.debugEvents); } 241 }; 242})(window);
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.