Files
cal.diy/companion/services/webAuth.ts
T
Dhairyashil Shinde 18017b5320 feat(companion): TypeScript best practices improvements (#26206)
* feat(companion): TypeScript best practices improvements

- Add separate tsconfig.extension.json for extension code typechecking
- Add typecheck scripts: typecheck, typecheck:extension, typecheck:all
- Create ambient declarations for browser extension APIs (browser.d.ts)
- Create type definitions for attendee and google-calendar types
- Replace all @ts-ignore with @ts-expect-error or remove where ambient declarations cover them
- Replace any types with proper types in AuthContext, webAuth, buildPartialUpdatePayload
- Fix implicit any errors in extension content.ts
- Update deepEqual function to use unknown instead of any for better type safety

All typechecks now pass for both app and extension code.

* feat(companion): enable full TypeScript strict mode (#26208)

* feat(companion): enable strictNullChecks and strictFunctionTypes

Phase 1 of strict mode enablement for the companion app.

Changes:
- Enable strictNullChecks and strictFunctionTypes in tsconfig.json
- Fix null check for scheduleDetails in fetchScheduleDetails
- Add explicit type annotations to limits arrays in event-type-detail.tsx
- Filter conferencing options with null appId before passing to buildLocationOptions
- Update LimitsTab interface to use union type for field parameter
- Add optional chaining for onEdit, onDuplicate, onDelete in EventTypeListItem.ios.tsx
- Add explicit type annotations to options arrays in AvailabilityListScreen.tsx
- Add explicit type annotations to dateOptions and timeOptions in RescheduleScreen.tsx
- Fix type guard return types in calcom.ts to explicitly return boolean

All typechecks pass for both app and extension code.

* feat(companion): enable full strict mode and fix any types (Phase 2 + Step 7)

Phase 2: Enable full strict mode
- Replace strictNullChecks + strictFunctionTypes with strict: true
- Fix implicit any errors in Alert.prompt callbacks (BookingDetailScreen, useBookingActions)

Step 7: Fix metadata any types in type definition files
- Update UserProfile.metadata to Record<string, unknown>
- Update CreateEventTypeInput.metadata to Record<string, unknown>
- Update Booking.responses to Record<string, unknown>

All typechecks pass for both app and extension code with full strict mode enabled.

* use JSON.stringify()

* fix restores the recursive key comparison with sorted keys

* prevents false positives when objects have different keys

* feat(companion): add tree shaking optimization scripts (#26212)

* feat(companion): add tree shaking optimization scripts

Add npm scripts to enable Expo's experimental tree shaking for production builds:
- export: Basic expo export command
- export:ios / export:android: Platform-specific exports
- export:optimized: Export with tree shaking enabled (all platforms)
- export:optimized:ios / export:optimized:android: Platform-specific with tree shaking

Bundle size improvement measured:
- Baseline: 5.48 MB (1804 modules)
- With tree shaking: 5.25 MB (1831 modules)
- Reduction: ~230 KB (4.2% smaller)

Also documented tree shaking configuration options in .env.example for:
- npm scripts (recommended)
- Manual env var setting
- EAS build profile configuration

* revert .env.example file

* revert .env.example file
2025-12-26 17:13:50 +00:00

195 lines
5.4 KiB
TypeScript

import { Platform } from "react-native";
import type { UserProfile } from "./types/users.types";
export interface WebSessionInfo {
isLoggedIn: boolean;
accessToken?: string;
refreshToken?: string;
userInfo?: UserProfile;
}
export class WebAuthService {
// Check if running on web
static isWeb(): boolean {
return Platform.OS === "web";
}
// Get cookies from the browser
static getCookies(): { [key: string]: string } {
if (!this.isWeb()) return {};
const cookies: { [key: string]: string } = {};
if (typeof document !== "undefined") {
document.cookie.split(";").forEach((cookie) => {
const [name, value] = cookie.trim().split("=");
if (name && value) {
cookies[name] = decodeURIComponent(value);
}
});
}
return cookies;
}
// Check for Cal.com session cookies
static async checkCalComSession(): Promise<WebSessionInfo> {
if (!this.isWeb()) {
return { isLoggedIn: false };
}
try {
// Check for common Cal.com session cookies
const cookies = this.getCookies();
// Look for Cal.com authentication cookies
// Common patterns: next-auth.session-token, __Secure-next-auth.session-token, etc.
const sessionCookieNames = [
"next-auth.session-token",
"__Secure-next-auth.session-token",
"__Host-next-auth.session-token",
"next-auth.csrf-token",
"cal-session",
"session-token",
];
const foundSessionCookie = sessionCookieNames.find((name) => cookies[name]);
if (foundSessionCookie) {
// Try to validate the session with Cal.com API
const sessionInfo = await this.validateWebSession();
return sessionInfo;
}
return { isLoggedIn: false };
} catch (error) {
return { isLoggedIn: false };
}
}
// Validate web session by calling Cal.com API with cookies
static async validateWebSession(): Promise<WebSessionInfo> {
if (!this.isWeb()) {
return { isLoggedIn: false };
}
try {
// First try to call the NextAuth session endpoint
const sessionResponse = await fetch("https://app.cal.com/api/auth/session", {
method: "GET",
credentials: "include", // Include cookies
headers: {
"Content-Type": "application/json",
},
});
if (sessionResponse.ok) {
const sessionData = await sessionResponse.json();
if (sessionData && sessionData.user) {
// User is logged in via web session
return {
isLoggedIn: true,
userInfo: sessionData.user,
};
}
}
// Try the internal Cal.com me endpoint (this might work with cookies)
const meResponse = await fetch("https://app.cal.com/api/me", {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
});
if (meResponse.ok) {
const userData = await meResponse.json();
if (userData && (userData.id || userData.user || userData.data)) {
return {
isLoggedIn: true,
userInfo: userData.user || userData.data || userData,
};
}
}
// Try to check if user is logged in by attempting to access a protected page
const dashboardResponse = await fetch("https://app.cal.com/api/trpc/viewer.me", {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
});
if (dashboardResponse.ok) {
const dashboardData = await dashboardResponse.json();
if (dashboardData && dashboardData.result && dashboardData.result.data) {
return {
isLoggedIn: true,
userInfo: dashboardData.result.data,
};
}
}
return { isLoggedIn: false };
} catch (error) {
return { isLoggedIn: false };
}
}
// Try to get API tokens from web session
static async getTokensFromWebSession(): Promise<{ accessToken?: string; refreshToken?: string }> {
if (!this.isWeb()) {
return {};
}
try {
// Some Cal.com implementations might expose tokens via specific endpoints
// This is a fallback approach - in practice, web sessions might not expose tokens directly
// Check localStorage/sessionStorage for any stored tokens
if (typeof window !== "undefined") {
const localStorage = window.localStorage;
const sessionStorage = window.sessionStorage;
const possibleTokenKeys = [
"cal-access-token",
"cal-auth-token",
"next-auth.token",
"auth-token",
"access-token",
];
for (const key of possibleTokenKeys) {
const localToken = localStorage.getItem(key);
const sessionToken = sessionStorage.getItem(key);
if (localToken || sessionToken) {
return {
accessToken: localToken || sessionToken || undefined,
};
}
}
}
return {};
} catch (error) {
return {};
}
}
// Redirect to Cal.com web login instead of using WebView
static redirectToWebLogin(): void {
if (!this.isWeb()) return;
// For web, redirect directly to Cal.com login
const currentUrl = window.location.href;
const loginUrl = `https://app.cal.com/auth/signin?callbackUrl=${encodeURIComponent(currentUrl)}`;
window.location.href = loginUrl;
}
}