PageSourceSearch

https://hiword.ai/src/lib/analytics-helper.js

js hiword.ai collected 2026-10-05 12:02:11 UTC 9,354 bytes, 242 lines download raw bytes

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.