fix: merge syftc config and guard against Syft disabled-mode client

This commit is contained in:
Benjamin Lu
2026-07-02 20:22:33 -07:00
parent 411b15df12
commit a8b60c6607
3 changed files with 58 additions and 14 deletions

9
global.d.ts vendored
View File

@@ -59,6 +59,11 @@ interface SyftDataClient {
fetchID?: (...args: unknown[]) => Promise<unknown>
}
/** Installed by the Syft UMD instead of SyftDataClient when telemetry is opted out */
interface SyftDisabledClient {
enable: () => void
}
interface Window {
__CONFIG__: {
gtm_container_id?: string
@@ -96,8 +101,8 @@ interface Window {
}
dataLayer?: Array<Record<string, unknown>>
gtag?: GtagFunction
syft?: SyftDataClient
syftc?: { sourceId: string }
syft?: SyftDataClient | SyftDisabledClient
syftc?: { sourceId?: string; enabled?: boolean }
ire_o?: string
ire?: ImpactQueueFunction
rewardful?: RewardfulQueueFunction

View File

@@ -43,6 +43,14 @@ function mockScriptAppend() {
.mockImplementation(<T extends Node>(node: T) => node)
}
function syftStub(): SyftDataClient {
const syft = window.syft
if (!syft || !('identify' in syft)) {
throw new Error('Expected a full Syft client on window')
}
return syft
}
function failScript(
appendChild: ReturnType<typeof mockScriptAppend>,
index: number
@@ -109,7 +117,7 @@ describe('SyftTelemetryProvider', () => {
})
expect(appendChild).toHaveBeenCalledTimes(2)
expect(window.syft?.q).toContainEqual([
expect(syftStub().q).toContainEqual([
'identify',
'retry@example.com',
{ source: 'login', method: 'email' }
@@ -131,7 +139,7 @@ describe('SyftTelemetryProvider', () => {
await Promise.resolve()
expect(appendChild).toHaveBeenCalledTimes(2)
expect(window.syft?.q).toContainEqual([
expect(syftStub().q).toContainEqual([
'identify',
'new@example.com',
{ source: 'signup', method: 'google' }
@@ -158,7 +166,7 @@ describe('SyftTelemetryProvider', () => {
provider.trackUserLoggedIn()
expect(appendChild).toHaveBeenCalledTimes(3)
expect(window.syft?.q).toContainEqual([
expect(syftStub().q).toContainEqual([
'identify',
'restored@example.com',
{ source: 'login' }
@@ -191,7 +199,7 @@ describe('SyftTelemetryProvider', () => {
const SyftTelemetryProvider = await importProvider()
new SyftTelemetryProvider()
const pending = window.syft?.fetchID?.('anonymousId')
const pending = syftStub().fetchID?.('anonymousId')
failScript(appendChild, 0)
@@ -215,13 +223,43 @@ describe('SyftTelemetryProvider', () => {
expect(appendChild).toHaveBeenCalledTimes(1)
expect(window.syftc).toEqual({ sourceId: 'src-123' })
expect(window.syft?.q).toContainEqual([
expect(syftStub().q).toContainEqual([
'identify',
'late@example.com',
{ source: 'login', method: 'email' }
])
})
it('preserves an existing opt-out flag when writing the source id', async () => {
mockRemoteConfig.value = { syftdata_source_id: 'src-123' }
window.syftc = { enabled: false }
mockScriptAppend()
const SyftTelemetryProvider = await importProvider()
new SyftTelemetryProvider()
expect(window.syftc).toEqual({ enabled: false, sourceId: 'src-123' })
})
it('skips identify when Syft installed its disabled-mode client', async () => {
mockRemoteConfig.value = { syftdata_source_id: 'src-123' }
const appendChild = mockScriptAppend()
const disabledClient = { enable: vi.fn() }
window.syft = disabledClient
const SyftTelemetryProvider = await importProvider()
const provider = new SyftTelemetryProvider()
expect(() =>
provider.trackAuth({
email: 'optedout@example.com',
is_new_user: false,
method: 'email'
})
).not.toThrow()
expect(window.syft).toBe(disabledClient)
expect(appendChild).not.toHaveBeenCalled()
})
it('does not touch the current user store during construction', async () => {
mockRemoteConfig.value = { syftdata_source_id: 'src-123' }
mockScriptAppend()
@@ -255,7 +293,7 @@ describe('SyftTelemetryProvider', () => {
method: 'google'
})
expect(window.syft?.q).toContainEqual([
expect(syftStub().q).toContainEqual([
'identify',
'new@example.com',
{ source: 'signup', method: 'google' }
@@ -316,7 +354,7 @@ describe('SyftTelemetryProvider', () => {
new SyftTelemetryProvider().trackUserLoggedIn()
expect(mockCurrentUser.useCurrentUser).toHaveBeenCalled()
expect(window.syft?.q).toContainEqual([
expect(syftStub().q).toContainEqual([
'identify',
'restored@example.com',
{ source: 'login' }

View File

@@ -12,8 +12,9 @@ let lastIdentifiedEmail: string | null = null
let pendingIdentify: { email: string; traits: SyftDataTraits } | null = null
let hasReplayedIdentify = false
const loadSyftSdk = createScriptLoader<SyftDataClient>(SYFT_SRC, () =>
window.syft && window.syft !== currentStub ? window.syft : null
const loadSyftSdk = createScriptLoader<SyftDataClient | SyftDisabledClient>(
SYFT_SRC,
() => (window.syft && window.syft !== currentStub ? window.syft : null)
)
function createTraits(
@@ -50,11 +51,11 @@ function createSyftStub(): SyftDataClient {
}
}
function ensureSyftClient(): SyftDataClient | null {
function ensureSyftClient(): SyftDataClient | SyftDisabledClient | null {
const sourceId = remoteConfig.value.syftdata_source_id
if (!sourceId) return window.syft ?? null
window.syftc = { sourceId }
window.syftc = { ...window.syftc, sourceId }
if (window.syft) return window.syft
const stub = createSyftStub()
@@ -92,7 +93,7 @@ function replayPendingIdentify(): void {
function identifyUser(email: string, traits: SyftDataTraits): void {
const syft = ensureSyftClient()
if (!syft) return
if (!syft || !('identify' in syft)) return
syft.identify(email, traits)
lastIdentifiedEmail = email