-
Notifications
You must be signed in to change notification settings - Fork 490
feat: add cloud gtm injection #8311
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
65bc3b1
43f6251
ef1c55b
c43d4aa
c1cb37c
35d81c6
3d17edd
a0e840c
bb0abe7
b0b1f6f
8d1ff4e
51e7291
02a4d3e
cea03a5
7bdd527
cba42e2
523f1c4
62968f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import type { TierKey } from '@/platform/cloud/subscription/constants/tierPricing' | ||
| import type { BillingCycle } from './subscriptionTierRank' | ||
|
|
||
| type PendingSubscriptionPurchase = { | ||
| tierKey: TierKey | ||
| billingCycle: BillingCycle | ||
| timestamp: number | ||
| } | ||
|
|
||
| const STORAGE_KEY = 'pending_subscription_purchase' | ||
| const MAX_AGE_MS = 24 * 60 * 60 * 1000 // 24 hours | ||
| const VALID_TIERS: TierKey[] = ['standard', 'creator', 'pro', 'founder'] | ||
| const VALID_CYCLES: BillingCycle[] = ['monthly', 'yearly'] | ||
|
|
||
| const safeRemove = (): void => { | ||
| try { | ||
| localStorage.removeItem(STORAGE_KEY) | ||
| } catch { | ||
| // Ignore storage errors (e.g. private browsing mode) | ||
| } | ||
| } | ||
|
|
||
| export function startSubscriptionPurchaseTracking( | ||
| tierKey: TierKey, | ||
| billingCycle: BillingCycle | ||
| ): void { | ||
| if (typeof window === 'undefined') return | ||
| try { | ||
| const payload: PendingSubscriptionPurchase = { | ||
| tierKey, | ||
| billingCycle, | ||
| timestamp: Date.now() | ||
| } | ||
| localStorage.setItem(STORAGE_KEY, JSON.stringify(payload)) | ||
| } catch { | ||
| // Ignore storage errors (e.g. private browsing mode) | ||
| } | ||
| } | ||
|
|
||
| export function getPendingSubscriptionPurchase(): PendingSubscriptionPurchase | null { | ||
| if (typeof window === 'undefined') return null | ||
|
|
||
| try { | ||
| const raw = localStorage.getItem(STORAGE_KEY) | ||
| if (!raw) return null | ||
|
|
||
| const parsed = JSON.parse(raw) as PendingSubscriptionPurchase | ||
| if (!parsed || typeof parsed !== 'object') { | ||
| safeRemove() | ||
| return null | ||
| } | ||
|
|
||
| const { tierKey, billingCycle, timestamp } = parsed | ||
| if ( | ||
| !VALID_TIERS.includes(tierKey) || | ||
| !VALID_CYCLES.includes(billingCycle) || | ||
| typeof timestamp !== 'number' | ||
| ) { | ||
| safeRemove() | ||
| return null | ||
| } | ||
|
|
||
| if (Date.now() - timestamp > MAX_AGE_MS) { | ||
| safeRemove() | ||
| return null | ||
| } | ||
|
|
||
| return parsed | ||
| } catch { | ||
| safeRemove() | ||
| return null | ||
| } | ||
| } | ||
|
|
||
| export function clearPendingSubscriptionPurchase(): void { | ||
| if (typeof window === 'undefined') return | ||
| safeRemove() | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,43 @@ | ||||||||||||||||||||||||||||||||||||||||
| import { isCloud } from '@/platform/distribution/types' | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const GTM_CONTAINER_ID = 'GTM-NP9JM6K7' | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| let isInitialized = false | ||||||||||||||||||||||||||||||||||||||||
| let initPromise: Promise<void> | null = null | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| export function initGtm(): void { | ||||||||||||||||||||||||||||||||||||||||
| if (!isCloud || typeof window === 'undefined') return | ||||||||||||||||||||||||||||||||||||||||
| if (typeof document === 'undefined') return | ||||||||||||||||||||||||||||||||||||||||
| if (isInitialized) return | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if (!initPromise) { | ||||||||||||||||||||||||||||||||||||||||
| initPromise = new Promise((resolve) => { | ||||||||||||||||||||||||||||||||||||||||
| const dataLayer = window.dataLayer ?? (window.dataLayer = []) | ||||||||||||||||||||||||||||||||||||||||
| dataLayer.push({ | ||||||||||||||||||||||||||||||||||||||||
| 'gtm.start': Date.now(), | ||||||||||||||||||||||||||||||||||||||||
| event: 'gtm.js' | ||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const script = document.createElement('script') | ||||||||||||||||||||||||||||||||||||||||
| script.async = true | ||||||||||||||||||||||||||||||||||||||||
| script.src = `https://www.googletagmanager.com/gtm.js?id=${GTM_CONTAINER_ID}` | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const finalize = () => { | ||||||||||||||||||||||||||||||||||||||||
| isInitialized = true | ||||||||||||||||||||||||||||||||||||||||
| resolve() | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| script.addEventListener('load', finalize, { once: true }) | ||||||||||||||||||||||||||||||||||||||||
| script.addEventListener('error', finalize, { once: true }) | ||||||||||||||||||||||||||||||||||||||||
| document.head?.appendChild(script) | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+25
to
+32
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Script load errors are silently swallowed. When GTM script fails to load (e.g., network error, blocked by ad-blocker), ♻️ Optional: Add error logging const finalize = () => {
isInitialized = true
resolve()
}
script.addEventListener('load', finalize, { once: true })
- script.addEventListener('error', finalize, { once: true })
+ script.addEventListener('error', (e) => {
+ console.warn('GTM script failed to load:', e)
+ finalize()
+ }, { once: true })
document.head?.appendChild(script)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| void initPromise | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| export function pushDataLayerEvent(event: Record<string, unknown>): void { | ||||||||||||||||||||||||||||||||||||||||
| if (!isCloud || typeof window === 'undefined') return | ||||||||||||||||||||||||||||||||||||||||
| const dataLayer = window.dataLayer ?? (window.dataLayer = []) | ||||||||||||||||||||||||||||||||||||||||
| dataLayer.push(event) | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,6 +9,7 @@ import type { RouteLocationNormalized } from 'vue-router' | |||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useFeatureFlags } from '@/composables/useFeatureFlags' | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { isCloud } from '@/platform/distribution/types' | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { pushDataLayerEvent } from '@/platform/telemetry/gtm' | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useDialogService } from '@/services/dialogService' | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore' | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useUserStore } from '@/stores/userStore' | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -36,6 +37,16 @@ function getBasePath(): string { | |||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const basePath = getBasePath() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| function pushPageView(): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isCloud || typeof window === 'undefined') return | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| pushDataLayerEvent({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| event: 'page_view', | ||||||||||||||||||||||||||||||||||||||||||||||||||
| page_location: window.location.href, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| page_title: document.title | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+44
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
40
to
48
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential timing issue with When Consider using 🔧 Suggested fix using nextTick+import { nextTick } from 'vue'
+
function pushPageView(): void {
if (!isCloud) return
- const dataLayer = window.dataLayer ?? (window.dataLayer = [])
- dataLayer.push({
- event: 'page_view',
- page_location: window.location.href,
- page_title: document.title
+ nextTick(() => {
+ const dataLayer = window.dataLayer ?? (window.dataLayer = [])
+ dataLayer.push({
+ event: 'page_view',
+ page_location: window.location.href,
+ page_title: document.title
+ })
})
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ahh, we'll see what marketing/gtm wants. we pagetitle is practically only ever going to be ComfyUI because we only change it for workflow name, unsaved indicator, and node/progress status.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
✏️ Learnings added
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const router = createRouter({ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| history: isFileProtocol | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ? createWebHashHistory() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -93,6 +104,10 @@ installPreservedQueryTracker(router, [ | |||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| router.afterEach(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| pushPageView() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isCloud) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const { flags } = useFeatureFlags() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const PUBLIC_ROUTE_NAMES = new Set([ | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍