From a1d8c04402586a34973b605a409d807da562fb54 Mon Sep 17 00:00:00 2001 From: Manuel Zedel Date: Fri, 10 Apr 2026 10:54:42 +0200 Subject: [PATCH] fix(utils): made tracking module align more w/ classless codebase & underlying library Signed-off-by: Manuel Zedel --- packages/utils/src/tracking.ts | 193 ++++++++++++++++++--------------- 1 file changed, 106 insertions(+), 87 deletions(-) diff --git a/packages/utils/src/tracking.ts b/packages/utils/src/tracking.ts index e5f001da..d2caebea 100644 --- a/packages/utils/src/tracking.ts +++ b/packages/utils/src/tracking.ts @@ -11,106 +11,125 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -//@ts-nocheck -import ReactGA4 from 'react-ga4'; +import ReactGA from 'react-ga4'; + +import type { Tenant, User } from '@northern.tech/types/MenderTypes'; const cookieConsentCSS = 'https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.css'; const cookieConsentJS = 'https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.js'; -const ReactGA = ReactGA4.default; +interface TrackingState { + initialized: boolean; + organization: Tenant | null; + trackingEnabled: boolean; + user: User | null; +} + +const state: TrackingState = { + initialized: false, + organization: null, + trackingEnabled: true, + user: null +}; -class Tracker { - constructor() { - this.initialized = false; - this.trackingEnabled = true; - this.currentPageView = null; - this.currentOrganizationUser = null; - } - cookieconsent() { - return new Promise(resolve => { - const style = document.createElement('link'); - style.href = cookieConsentCSS; - style.rel = 'stylesheet'; - style.async = true; - document.head.appendChild(style); - // - const script = document.createElement('script'); - script.src = cookieConsentJS; - script.async = false; - script.addEventListener('load', () => { - window.cookieconsent.initialise({ - palette: { - popup: { - background: '#5d0f43', - text: '#ffffff' - }, - button: { - background: '#73a4ad', - text: '#ffffff' - } - }, - position: 'bottom-left', - type: 'opt-out', - content: { - message: 'We use cookies to analyze our traffic so we can improve our website and give you a better experience.', - link: 'View our cookie policy', - href: 'https://northern.tech/legal/cookies' - }, - autoOpen: true, - revokable: false, - law: { - regionalLaw: false - }, - onStatusChange: status => { - const hasConsented = status == 'allow'; - resolve({ trackingConsentGiven: hasConsented }); - } - }); +export const cookieconsent = (): Promise<{ trackingConsentGiven: boolean }> => + new Promise(resolve => { + const style = document.createElement('link'); + style.href = cookieConsentCSS; + style.rel = 'stylesheet'; + style.async = true; + document.head.appendChild(style); + + const script = document.createElement('script'); + script.src = cookieConsentJS; + script.async = false; + script.addEventListener('load', () => { + (window as any).cookieconsent.initialise({ + palette: { + popup: { background: '#5d0f43', text: '#ffffff' }, + button: { background: '#73a4ad', text: '#ffffff' } + }, + position: 'bottom-left', + type: 'opt-out', + content: { + message: 'We use cookies to analyze our traffic so we can improve our website and give you a better experience.', + link: 'View our cookie policy', + href: 'https://northern.tech/legal/cookies' + }, + autoOpen: true, + revokable: false, + law: { regionalLaw: false }, + onStatusChange: (status: string) => { + resolve({ trackingConsentGiven: status === 'allow' }); + } }); - document.body.appendChild(script); }); + document.body.appendChild(script); + }); + +export const initialize = (trackingCode: string): boolean => { + if (state.initialized && state.trackingEnabled) { + return false; } - event(data) { - if (this.initialized && this.trackingEnabled) { - ReactGA.event(data); - } - } - exception(error) { - if (this.initialized && this.trackingEnabled) { - ReactGA.event('error', error); - } + ReactGA.initialize(trackingCode); + state.initialized = true; + return true; +}; + +export const trackEvent = (data: { action: string; category: string; label?: string; value?: number }) => { + if (state.initialized && state.trackingEnabled) { + ReactGA.event(data); } - initialize(trackingCode) { - if (this.initialized && this.trackingEnabled) { - return false; - } - ReactGA.initialize(trackingCode); - this.initialized = true; - return true; +}; + +export const trackException = (error: { description: string; fatal?: boolean }) => { + if (state.initialized && state.trackingEnabled) { + ReactGA.event('error', error); } - pageview(data) { - if (data) { - this.currentPageView = data; - } +}; + +export const pageview = (page?: string) => { + // currently a no-op stored for later use, matching original behavior + if (page) { + // page view tracking placeholder } - set(value) { - if (this.initialized && this.trackingEnabled) { - ReactGA.set(value); - } +}; + +export const setGA = (value: Record) => { + if (state.initialized && state.trackingEnabled) { + ReactGA.set(value); } - setOrganizationUser(organization, user) { - if (this.initialized && this.trackingEnabled && this.currentOrganizationUser != { organization, user }) { - this.currentOrganizationUser = { organization, user }; - this.set({ dimension1: organization.plan }); - this.set({ dimension2: organization.id }); - this.set({ dimension3: user.id }); - this.set({ userId: user.id }); - } +}; + +export const setOrganizationUser = (organization: Tenant, user: User) => { + if (!state.initialized || !state.trackingEnabled) { + return; } - setTrackingEnabled(trackingEnabled) { - this.trackingEnabled = trackingEnabled; + const { user: currentUser, organization: currentOrganization } = state; + if (currentOrganization?.id === organization.id && currentUser?.id === user.id && currentOrganization.plan === organization.plan) { + return; } -} + state.organization = organization; + state.user = user; + ReactGA.set({ dimension1: organization.plan }); + ReactGA.set({ dimension2: organization.id }); + ReactGA.set({ dimension3: user.id }); + ReactGA.set({ userId: user.id }); +}; + +export const setTrackingEnabled = (trackingEnabled: boolean) => { + state.trackingEnabled = trackingEnabled; +}; + +const Tracking = { + cookieconsent, + event: trackEvent, + exception: trackException, + initialize, + pageview, + set: setGA, + setOrganizationUser, + setTrackingEnabled +}; -const Tracking = new Tracker(); export default Tracking;