Skip to content

Commit d58dcb7

Browse files
committed
fix(demo): validate Cal embed configuration
1 parent 97392b5 commit d58dcb7

2 files changed

Lines changed: 65 additions & 9 deletions

File tree

apps/sim/app/(landing)/demo/components/demo-scheduler/demo-scheduler.test.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ vi.mock('@/lib/consent/tracking-consent', () => ({
2828
import {
2929
DemoScheduler,
3030
preloadCalEmbed,
31+
resolveCalEmbedConfig,
3132
} from '@/app/(landing)/demo/components/demo-scheduler/demo-scheduler'
3233

3334
const LEAD = {
@@ -70,7 +71,9 @@ describe('DemoScheduler', () => {
7071
expect.objectContaining({
7172
namespace: 'demo',
7273
calLink: 'team/sim/demo',
73-
style: { width: '100%', height: '100%', overflow: 'auto' },
74+
calOrigin: 'https://app.cal.com',
75+
embedJsUrl: 'https://app.cal.com/embed/embed.js',
76+
className: 'size-full overflow-auto',
7477
config: {
7578
name: LEAD.name,
7679
email: LEAD.email,
@@ -146,8 +149,26 @@ describe('DemoScheduler', () => {
146149
})
147150

148151
expect(mockGetCalApi).toHaveBeenCalledOnce()
149-
expect(mockGetCalApi).toHaveBeenCalledWith({ namespace: 'demo' })
152+
expect(mockGetCalApi).toHaveBeenCalledWith({
153+
namespace: 'demo',
154+
embedJsUrl: 'https://app.cal.com/embed/embed.js',
155+
})
150156
expect(mockCal).toHaveBeenCalledOnce()
151157
expect(mockCal).toHaveBeenCalledWith('preload', { calLink: 'team/sim/demo' })
152158
})
159+
160+
it('falls back from malformed Cal configuration and preserves valid custom origins', () => {
161+
expect(resolveCalEmbedConfig('javascript:alert(1)')).toEqual({
162+
calLink: 'team/sim/demo',
163+
calOrigin: 'https://app.cal.com',
164+
embedJsUrl: 'https://app.cal.com/embed/embed.js',
165+
})
166+
expect(resolveCalEmbedConfig('https://book.example.com/team/demo?theme=light#ignored')).toEqual(
167+
{
168+
calLink: 'team/demo?theme=light',
169+
calOrigin: 'https://book.example.com',
170+
embedJsUrl: 'https://book.example.com/embed/embed.js',
171+
}
172+
)
173+
})
153174
})

apps/sim/app/(landing)/demo/components/demo-scheduler/demo-scheduler.tsx

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,41 @@ import { useTrackingConsent } from '@/lib/consent/tracking-consent'
88
import type { DemoLead } from '@/app/(landing)/demo/components/demo-form'
99

1010
const CAL_NAMESPACE = 'demo'
11-
/** The Cal.com event the demo books - set `NEXT_PUBLIC_CAL_LINK` to override. */
12-
const CAL_LINK = process.env.NEXT_PUBLIC_CAL_LINK ?? 'team/sim/demo'
11+
const DEFAULT_CAL_ORIGIN = 'https://app.cal.com'
12+
const DEFAULT_CAL_LINK = 'team/sim/demo'
13+
14+
interface CalEmbedConfig {
15+
calLink: string
16+
calOrigin: string
17+
embedJsUrl: string
18+
}
19+
20+
function parseCalEmbedConfig(link: string): CalEmbedConfig {
21+
const url = new URL(link.replace(/^\/+/, ''), `${DEFAULT_CAL_ORIGIN}/`)
22+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) {
23+
throw new Error('Cal link must use HTTP(S) without embedded credentials')
24+
}
25+
26+
const calLink = `${url.pathname.replace(/^\/+/, '')}${url.search}`
27+
if (!calLink) throw new Error('Cal link must include an event path')
28+
29+
return {
30+
calLink,
31+
calOrigin: url.origin,
32+
embedJsUrl: `${url.origin}/embed/embed.js`,
33+
}
34+
}
35+
36+
/** Resolves the configured booker, falling back safely when the environment value is invalid. */
37+
export function resolveCalEmbedConfig(configuredLink?: string): CalEmbedConfig {
38+
try {
39+
return parseCalEmbedConfig(configuredLink?.trim() || DEFAULT_CAL_LINK)
40+
} catch {
41+
return parseCalEmbedConfig(DEFAULT_CAL_LINK)
42+
}
43+
}
44+
45+
const CAL_EMBED = resolveCalEmbedConfig(process.env.NEXT_PUBLIC_CAL_LINK)
1346

1447
/**
1548
* Sim's brand color, matching the `--brand-agent` token. The embed renders in a
@@ -37,9 +70,9 @@ let calEmbedPreloaded = false
3770
export function preloadCalEmbed(): void {
3871
if (calEmbedPreloaded) return
3972
calEmbedPreloaded = true
40-
getCalApi({ namespace: CAL_NAMESPACE })
73+
getCalApi({ namespace: CAL_NAMESPACE, embedJsUrl: CAL_EMBED.embedJsUrl })
4174
.then((cal) => {
42-
cal('preload', { calLink: CAL_LINK })
75+
cal('preload', { calLink: CAL_EMBED.calLink })
4376
})
4477
.catch(() => {
4578
calEmbedPreloaded = false
@@ -71,7 +104,7 @@ export function DemoScheduler({ lead }: DemoSchedulerProps) {
71104
}
72105
if (marketing) window.twq?.('event', X_DEMO_BOOKED_EVENT_ID, {})
73106
}
74-
const api = getCalApi({ namespace: CAL_NAMESPACE })
107+
const api = getCalApi({ namespace: CAL_NAMESPACE, embedJsUrl: CAL_EMBED.embedJsUrl })
75108
api
76109
.then((cal) => {
77110
if (cancelled) return
@@ -104,8 +137,10 @@ export function DemoScheduler({ lead }: DemoSchedulerProps) {
104137
<div className='mt-5 min-h-0 flex-1'>
105138
<Cal
106139
namespace={CAL_NAMESPACE}
107-
calLink={CAL_LINK}
108-
style={{ width: '100%', height: '100%', overflow: 'auto' }}
140+
calLink={CAL_EMBED.calLink}
141+
calOrigin={CAL_EMBED.calOrigin}
142+
embedJsUrl={CAL_EMBED.embedJsUrl}
143+
className='size-full overflow-auto'
109144
config={{
110145
name: lead.name,
111146
email: lead.email,

0 commit comments

Comments
 (0)