Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions apps/extension/entrypoints/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ export default defineBackground(() => {

recordTokenOperation();
if (storageAPI?.local) {
storageAPI.local.remove(["cal_oauth_tokens", "oauth_state"], () => {
storageAPI.local.remove(["cal_oauth_tokens", "oauth_state", "cal_region"], () => {
const runtime = getRuntimeAPI();
if (runtime?.lastError) {
devLog.error("Failed to clear OAuth tokens:", runtime.lastError.message);
Expand Down Expand Up @@ -861,7 +861,24 @@ async function validateOAuthState(state: string): Promise<void> {
}
}

const API_BASE_URL = "https://api.cal.com/v2";
const REGION_STORAGE_KEY = "cal_region";

async function getStoredRegion(): Promise<"us" | "eu"> {
const storageAPI = getStorageAPI();
if (!storageAPI?.local) return "us";
try {
const result = await storageAPI.local.get([REGION_STORAGE_KEY]);
const value = result[REGION_STORAGE_KEY];
return value === "eu" ? "eu" : "us";
} catch {
return "us";
}
}

async function getApiBaseUrl(): Promise<string> {
const region = await getStoredRegion();
return region === "eu" ? "https://api.cal.eu/v2" : "https://api.cal.com/v2";
}

const tokenOperationTimestamps: number[] = [];
const TOKEN_RATE_LIMIT_WINDOW_MS = 60000;
Expand All @@ -888,7 +905,8 @@ async function validateTokens(tokens: OAuthTokens): Promise<boolean> {
}

try {
const response = await fetchWithTimeout(`${API_BASE_URL}/me`, {
const apiBaseUrl = await getApiBaseUrl();
const response = await fetchWithTimeout(`${apiBaseUrl}/me`, {
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
"Content-Type": "application/json",
Expand Down Expand Up @@ -934,9 +952,10 @@ async function getAuthHeader(): Promise<string> {
async function fetchEventTypes(): Promise<unknown[]> {
const authHeader = await getAuthHeader();

const apiBaseUrl = await getApiBaseUrl();
// For authenticated users, no username/orgSlug params needed - API uses auth token
// This also ensures hidden event types are returned (they're filtered out when username is provided)
const response = await fetchWithTimeout(`${API_BASE_URL}/event-types`, {
const response = await fetchWithTimeout(`${apiBaseUrl}/event-types`, {
headers: {
Authorization: authHeader,
"Content-Type": "application/json",
Expand Down Expand Up @@ -992,7 +1011,8 @@ async function checkAuthStatus(): Promise<boolean> {
async function getBookingStatus(bookingUid: string): Promise<Booking> {
const authHeader = await getAuthHeader();

const response = await fetchWithTimeout(`${API_BASE_URL}/bookings/${bookingUid}`, {
const apiBaseUrl = await getApiBaseUrl();
const response = await fetchWithTimeout(`${apiBaseUrl}/bookings/${bookingUid}`, {
method: "GET",
headers: {
Authorization: authHeader,
Expand Down Expand Up @@ -1039,7 +1059,8 @@ async function markAttendeeNoShow(
): Promise<Booking> {
const authHeader = await getAuthHeader();

const response = await fetchWithTimeout(`${API_BASE_URL}/bookings/${bookingUid}/mark-absent`, {
const apiBaseUrl = await getApiBaseUrl();
const response = await fetchWithTimeout(`${apiBaseUrl}/bookings/${bookingUid}/mark-absent`, {
method: "POST",
headers: {
Authorization: authHeader,
Expand Down
5 changes: 3 additions & 2 deletions apps/extension/entrypoints/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export default defineContentScript({
);
return;
}
handleTokenSyncRequest(event.data.tokens, iframe.contentWindow);
handleTokenSyncRequest(event.data.tokens, event.data.region, iframe.contentWindow);
} else if (event.data.type === "cal-extension-clear-tokens") {
if (!validateSessionToken(event.data.sessionToken)) {
iframe.contentWindow?.postMessage(
Expand Down Expand Up @@ -281,9 +281,10 @@ export default defineContentScript({

function handleTokenSyncRequest(
tokens: { accessToken?: string; refreshToken?: string; expiresAt?: number },
region: "us" | "eu" | undefined,
iframeWindow: Window | null
) {
chrome.runtime.sendMessage({ action: "sync-oauth-tokens", tokens }, (response) => {
chrome.runtime.sendMessage({ action: "sync-oauth-tokens", tokens, region }, (response) => {
if (chrome.runtime.lastError) {
devLog.error(
"Failed to communicate with background script:",
Expand Down
18 changes: 18 additions & 0 deletions apps/extension/wxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,48 @@ const browserTarget = process.env.BROWSER_TARGET || "chrome";
/**
* Get browser-specific OAuth configuration.
* Falls back to default (Chrome) config if browser-specific config is not set.
*
* Returns both US (default) and EU values so the runtime can pick the right
* one based on the data region selected on the login screen.
*/
function getOAuthConfig() {
const defaultClientId = process.env.EXPO_PUBLIC_CALCOM_OAUTH_CLIENT_ID || "";
const defaultRedirectUri = process.env.EXPO_PUBLIC_CALCOM_OAUTH_REDIRECT_URI || "";
const defaultClientIdEu = process.env.EXPO_PUBLIC_CALCOM_OAUTH_CLIENT_ID_EU || "";
const defaultRedirectUriEu = process.env.EXPO_PUBLIC_CALCOM_OAUTH_REDIRECT_URI_EU || "";

switch (browserTarget) {
case "firefox":
return {
clientId: process.env.EXPO_PUBLIC_CALCOM_OAUTH_CLIENT_ID_FIREFOX || defaultClientId,
redirectUri:
process.env.EXPO_PUBLIC_CALCOM_OAUTH_REDIRECT_URI_FIREFOX || defaultRedirectUri,
clientIdEu: process.env.EXPO_PUBLIC_CALCOM_OAUTH_CLIENT_ID_FIREFOX_EU || defaultClientIdEu,
redirectUriEu:
process.env.EXPO_PUBLIC_CALCOM_OAUTH_REDIRECT_URI_FIREFOX_EU || defaultRedirectUriEu,
};
case "safari":
return {
clientId: process.env.EXPO_PUBLIC_CALCOM_OAUTH_CLIENT_ID_SAFARI || defaultClientId,
redirectUri: process.env.EXPO_PUBLIC_CALCOM_OAUTH_REDIRECT_URI_SAFARI || defaultRedirectUri,
clientIdEu: process.env.EXPO_PUBLIC_CALCOM_OAUTH_CLIENT_ID_SAFARI_EU || defaultClientIdEu,
redirectUriEu:
process.env.EXPO_PUBLIC_CALCOM_OAUTH_REDIRECT_URI_SAFARI_EU || defaultRedirectUriEu,
};
case "edge":
return {
clientId: process.env.EXPO_PUBLIC_CALCOM_OAUTH_CLIENT_ID_EDGE || defaultClientId,
redirectUri: process.env.EXPO_PUBLIC_CALCOM_OAUTH_REDIRECT_URI_EDGE || defaultRedirectUri,
clientIdEu: process.env.EXPO_PUBLIC_CALCOM_OAUTH_CLIENT_ID_EDGE_EU || defaultClientIdEu,
redirectUriEu:
process.env.EXPO_PUBLIC_CALCOM_OAUTH_REDIRECT_URI_EDGE_EU || defaultRedirectUriEu,
};
default:
return {
clientId: defaultClientId,
redirectUri: defaultRedirectUri,
clientIdEu: defaultClientIdEu,
redirectUriEu: defaultRedirectUriEu,
};
}
}
Expand Down Expand Up @@ -71,6 +87,8 @@ export default defineConfig({
"https://companion.cal.com/*",
"https://api.cal.com/*",
"https://app.cal.com/*",
"https://api.cal.eu/*",
"https://app.cal.eu/*",
"https://mail.google.com/*",
"https://calendar.google.com/*",
// Include localhost permission for dev builds (needed for iframe to load)
Expand Down
17 changes: 12 additions & 5 deletions apps/mobile/app/(tabs)/(event-types)/event-type-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
showSuccessAlert,
} from "@/utils/alerts";
import { openInAppBrowser } from "@/utils/browser";
import { getCalAppUrl } from "@/utils/region";
import {
buildLocationOptions,
mapApiLocationToItem,
Expand Down Expand Up @@ -1526,7 +1527,10 @@ export default function EventTypeDetail() {
<Text className="mb-5 text-center text-[19px] font-bold text-black dark:text-white">
Select Available Durations
</Text>
<ScrollView style={{ maxHeight: 400, marginBottom: 20 }} showsVerticalScrollIndicator={false}>
<ScrollView
style={{ maxHeight: 400, marginBottom: 20 }}
showsVerticalScrollIndicator={false}
>
{availableDurations.map((duration, index) => (
<TouchableOpacity
key={duration}
Expand Down Expand Up @@ -1571,7 +1575,10 @@ export default function EventTypeDetail() {
<Text className="mb-5 text-center text-[19px] font-bold text-[#333] dark:text-white">
Select Available Durations
</Text>
<ScrollView style={{ maxHeight: 400, marginBottom: 20 }} showsVerticalScrollIndicator={false}>
<ScrollView
style={{ maxHeight: 400, marginBottom: 20 }}
showsVerticalScrollIndicator={false}
>
{availableDurations.map((duration, index) => (
<TouchableOpacity
key={duration}
Expand Down Expand Up @@ -2343,7 +2350,7 @@ export default function EventTypeDetail() {
showNotAvailableAlert();
} else {
openInAppBrowser(
`https://app.cal.com/event-types/${id}?tabName=apps`,
`${getCalAppUrl()}/event-types/${id}?tabName=apps`,
"Apps settings"
);
}
Expand Down Expand Up @@ -2378,7 +2385,7 @@ export default function EventTypeDetail() {
showNotAvailableAlert();
} else {
openInAppBrowser(
`https://app.cal.com/event-types/${id}?tabName=workflows`,
`${getCalAppUrl()}/event-types/${id}?tabName=workflows`,
"Workflows settings"
);
}
Expand Down Expand Up @@ -2413,7 +2420,7 @@ export default function EventTypeDetail() {
showNotAvailableAlert();
} else {
openInAppBrowser(
`https://app.cal.com/event-types/${id}?tabName=webhooks`,
`${getCalAppUrl()}/event-types/${id}?tabName=webhooks`,
"Webhooks settings"
);
}
Expand Down
5 changes: 4 additions & 1 deletion apps/mobile/app/(tabs)/(more)/index.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { type LandingPage, useUserPreferences } from "@/hooks/useUserPreferences
import { showErrorAlert, showNotAvailableAlert, showSilentSuccessAlert } from "@/utils/alerts";
import { openInAppBrowser } from "@/utils/browser";
import { getAvatarUrl } from "@/utils/getAvatarUrl";
import { getCalAppUrl } from "@/utils/region";

interface MoreMenuItem {
name: string;
Expand Down Expand Up @@ -336,7 +337,9 @@ export default function More() {
>
The companion app is an extension of the web application.{"\n"}
For advanced features, visit{" "}
<Text style={{ color: isDark ? "#D1D5DB" : "#1F2937" }}>app.cal.com</Text>
<Text style={{ color: isDark ? "#D1D5DB" : "#1F2937" }}>
{getCalAppUrl().replace(/^https?:\/\//, "")}
</Text>
</Text>
</ScrollView>

Expand Down
16 changes: 9 additions & 7 deletions apps/mobile/app/(tabs)/(more)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useQueryContext } from "@/contexts/QueryContext";
import { type LandingPage, useUserPreferences } from "@/hooks/useUserPreferences";
import { showErrorAlert, showSilentSuccessAlert } from "@/utils/alerts";
import { openInAppBrowser } from "@/utils/browser";
import { getCalAppUrl } from "@/utils/region";

interface MoreMenuItem {
name: string;
Expand Down Expand Up @@ -79,30 +80,31 @@ export default function More() {
}
};

const calAppUrl = getCalAppUrl();
const menuItems: MoreMenuItem[] = [
{
name: "Apps",
icon: "grid-outline",
isExternal: true,
onPress: () => openInAppBrowser("https://app.cal.com/apps", "Apps page"),
onPress: () => openInAppBrowser(`${calAppUrl}/apps`, "Apps page"),
},
{
name: "Routing",
icon: "git-branch-outline",
isExternal: true,
onPress: () => openInAppBrowser("https://app.cal.com/routing", "Routing page"),
onPress: () => openInAppBrowser(`${calAppUrl}/routing`, "Routing page"),
},
{
name: "Workflows",
icon: "flash-outline",
isExternal: true,
onPress: () => openInAppBrowser("https://app.cal.com/workflows", "Workflows page"),
onPress: () => openInAppBrowser(`${calAppUrl}/workflows`, "Workflows page"),
},
{
name: "Insights",
icon: "bar-chart-outline",
isExternal: true,
onPress: () => openInAppBrowser("https://app.cal.com/insights", "Insights page"),
onPress: () => openInAppBrowser(`${calAppUrl}/insights`, "Insights page"),
},
{
name: "Support",
Expand Down Expand Up @@ -202,7 +204,7 @@ export default function More() {
>
<TouchableOpacity
onPress={() =>
openInAppBrowser("https://app.cal.com/settings/my-account/profile", "Delete Account")
openInAppBrowser(`${getCalAppUrl()}/settings/my-account/profile`, "Delete Account")
}
className="flex-row items-center justify-between bg-white px-5 py-4 active:bg-red-50"
style={{ backgroundColor: theme.backgroundSecondary }}
Expand Down Expand Up @@ -243,9 +245,9 @@ export default function More() {
For advanced features, visit{" "}
<Text
style={{ color: theme.text }}
onPress={() => openInAppBrowser("https://app.cal.com", "Cal.com")}
onPress={() => openInAppBrowser(calAppUrl, "Cal.com")}
>
app.cal.com
{calAppUrl.replace(/^https?:\/\//, "")}
</Text>
</Text>
</ScrollView>
Expand Down
20 changes: 9 additions & 11 deletions apps/mobile/app/profile-sheet.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useUserProfile } from "@/hooks";
import { showSuccessAlert } from "@/utils/alerts";
import { openInAppBrowser } from "@/utils/browser";
import { getAvatarUrl } from "@/utils/getAvatarUrl";
import { getCalAppUrl, getCalWebUrl } from "@/utils/region";

interface ProfileMenuItem {
id: string;
Expand Down Expand Up @@ -57,34 +58,31 @@ export default function ProfileSheet() {
avatarText: isDark ? "#FFFFFF" : "#4B5563",
};

const publicPageUrl = userProfile?.username ? `https://cal.com/${userProfile.username}` : null;
const calAppUrl = getCalAppUrl();
const calWebUrl = getCalWebUrl();
const publicPageUrl = userProfile?.username ? `${calWebUrl}/${userProfile.username}` : null;

const menuItems: ProfileMenuItem[] = [
{
id: "profile",
label: "My Profile",
icon: "person-outline",
onPress: () =>
openInAppBrowser("https://app.cal.com/settings/my-account/profile", "Profile page"),
onPress: () => openInAppBrowser(`${calAppUrl}/settings/my-account/profile`, "Profile page"),
external: true,
},
{
id: "settings",
label: "My Settings",
icon: "settings-outline",
onPress: () =>
openInAppBrowser("https://app.cal.com/settings/my-account/general", "Settings page"),
onPress: () => openInAppBrowser(`${calAppUrl}/settings/my-account/general`, "Settings page"),
external: true,
},
{
id: "outOfOffice",
label: "Out of Office",
icon: "moon-outline",
onPress: () =>
openInAppBrowser(
"https://app.cal.com/settings/my-account/out-of-office",
"Out of Office page"
),
openInAppBrowser(`${calAppUrl}/settings/my-account/out-of-office`, "Out of Office page"),
external: true,
},

Expand All @@ -105,14 +103,14 @@ export default function ProfileSheet() {
id: "roadmap",
label: "Roadmap",
icon: "map-outline",
onPress: () => openInAppBrowser("https://cal.com/roadmap", "Roadmap page"),
onPress: () => openInAppBrowser(`${calWebUrl}/roadmap`, "Roadmap page"),
external: true,
},
{
id: "help",
label: "Help",
icon: "help-circle-outline",
onPress: () => openInAppBrowser("https://cal.com/help", "Help page"),
onPress: () => openInAppBrowser(`${calWebUrl}/help`, "Help page"),
external: true,
},
];
Expand Down
Loading
Loading