Skip to content
Merged
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
23 changes: 5 additions & 18 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type Base44Client = ReturnType<typeof createClient>;
* Create a Base44 client instance
* @param {Object} config - Client configuration
* @param {string} [config.serverUrl='https://base44.app'] - API server URL
* @param {string} [config.appBaseUrl] - Application base URL
* @param {string|number} config.appId - Application ID
* @param {string} [config.token] - Authentication token
* @param {string} [config.serviceToken] - Service role authentication token
Expand All @@ -26,24 +27,24 @@ export type Base44Client = ReturnType<typeof createClient>;
*/
export function createClient(config: {
serverUrl?: string;
appBaseUrl?: string;
appId: string;
token?: string;
serviceToken?: string;
requiresAuth?: boolean;
functionsVersion?: string;
headers?: Record<string, string>;
options?: CreateClientOptions;
onRedirectToLogin?: () => void;
}) {
const {
serverUrl = "https://base44.app",
appId,
token,
serviceToken,
requiresAuth = false,
appBaseUrl,
options,
functionsVersion,
onRedirectToLogin,
headers: optionalHeaders,
} = config;

Expand Down Expand Up @@ -75,50 +76,36 @@ export function createClient(config: {
baseURL: `${serverUrl}/api`,
headers,
token,
requiresAuth,
appId,
serverUrl,
onError: options?.onError,
onRedirectToLogin,
});

const functionsAxiosClient = createAxiosClient({
baseURL: `${serverUrl}/api`,
headers: functionHeaders,
token,
requiresAuth,
appId,
serverUrl,
interceptResponses: false,
onError: options?.onError,
onRedirectToLogin,
});

const serviceRoleAxiosClient = createAxiosClient({
baseURL: `${serverUrl}/api`,
headers,
token: serviceToken,
serverUrl,
appId,
onError: options?.onError,
onRedirectToLogin,
});

const serviceRoleFunctionsAxiosClient = createAxiosClient({
baseURL: `${serverUrl}/api`,
headers: functionHeaders,
token: serviceToken,
serverUrl,
appId,
interceptResponses: false,
onRedirectToLogin,
});

const userModules = {
entities: createEntitiesModule(axiosClient, appId),
integrations: createIntegrationsModule(axiosClient, appId),
auth: createAuthModule(axiosClient, functionsAxiosClient, appId, {
onRedirectToLogin,
appBaseUrl,
serverUrl,
}),
functions: createFunctionsModule(functionsAxiosClient, appId),
Expand All @@ -144,7 +131,7 @@ export function createClient(config: {
socket,
appId,
serverUrl,
token
token,
}),
cleanup: () => {
socket.disconnect();
Expand Down
10 changes: 3 additions & 7 deletions src/modules/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function createAuthModule(
appId: string,
options: {
serverUrl: string;
onRedirectToLogin?: () => void;
appBaseUrl?: string;
}
) {
return {
Expand Down Expand Up @@ -46,19 +46,15 @@ export function createAuthModule(
);
}

if (options.onRedirectToLogin) {
options.onRedirectToLogin();
return;
}
// If nextUrl is not provided, use the current URL
const redirectUrl = nextUrl
? new URL(nextUrl, window.location.origin).toString()
: window.location.href;

// Build the login URL
const loginUrl = `${
options.serverUrl
}/login?from_url=${encodeURIComponent(redirectUrl)}&app_id=${appId}`;
options.appBaseUrl ?? ""
}/login?from_url=${encodeURIComponent(redirectUrl)}`;

// Redirect to the login page
window.location.href = loginUrl;
Expand Down
43 changes: 0 additions & 43 deletions src/utils/axios-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,6 @@ function safeErrorLog(prefix: string, error: unknown) {
}
}

/**
* Redirects to the login page with the current URL as return destination
* @param {string} serverUrl - Base server URL
* @param {string|number} appId - Application ID
*/
function redirectToLogin(serverUrl: string, appId: string) {
if (typeof window === "undefined") {
return; // Can't redirect in non-browser environment
}

const currentUrl = encodeURIComponent(window.location.href);
const loginUrl = `${serverUrl}/login?from_url=${currentUrl}&app_id=${appId}`;
window.location.href = loginUrl;
}

/**
* Creates an axios client with default configuration and interceptors
* @param {Object} options - Client configuration options
Expand All @@ -87,22 +72,14 @@ export function createAxiosClient({
baseURL,
headers = {},
token,
requiresAuth = false,
appId,
serverUrl,
interceptResponses = true,
onError,
onRedirectToLogin,
}: {
baseURL: string;
headers?: Record<string, string>;
token?: string;
requiresAuth?: boolean;
appId: string;
serverUrl: string;
interceptResponses?: boolean;
onError?: (error: Error) => void;
onRedirectToLogin?: () => void;
}) {
const client = axios.create({
baseURL,
Expand Down Expand Up @@ -193,26 +170,6 @@ export function createAxiosClient({
safeErrorLog("[Base44 SDK Error]", base44Error);
}

// Check for 403 Forbidden (authentication required) and redirect to login if requiresAuth is true
console.log(
requiresAuth,
error.response?.status,
typeof window !== "undefined"
);
if (
requiresAuth &&
error.response?.status === 403 &&
typeof window !== "undefined"
) {
console.log("Authentication required. Redirecting to login...");
// Use a slight delay to allow the error to propagate first
setTimeout(() => {
onRedirectToLogin
? onRedirectToLogin()
: redirectToLogin(serverUrl, appId);
}, 100);
}

onError?.(base44Error);

return Promise.reject(base44Error);
Expand Down
57 changes: 52 additions & 5 deletions tests/unit/auth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ describe('Auth Module', () => {

// Verify the redirect URL was set correctly
expect(mockLocation.href).toBe(
`${serverUrl}/login?from_url=${encodeURIComponent(nextUrl)}&app_id=${appId}`
`/login?from_url=${encodeURIComponent(nextUrl)}`
);

// Restore window
Expand All @@ -164,14 +164,61 @@ describe('Auth Module', () => {
global.window = {
location: mockLocation
};

base44.auth.redirectToLogin();

// Verify the redirect URL uses current URL
expect(mockLocation.href).toBe(
`${serverUrl}/login?from_url=${encodeURIComponent(currentUrl)}&app_id=${appId}`
`/login?from_url=${encodeURIComponent(currentUrl)}`
);


// Restore window
global.window = originalWindow;
});

test('should use appBaseUrl for login redirect when provided', () => {
const customAppBaseUrl = 'https://custom-app.example.com';
const clientWithCustomUrl = createClient({
serverUrl,
appId,
appBaseUrl: customAppBaseUrl,
});

// Mock window.location
const originalWindow = global.window;
const mockLocation = { href: '' };
global.window = {
location: mockLocation
};

const nextUrl = 'https://example.com/dashboard';
clientWithCustomUrl.auth.redirectToLogin(nextUrl);

// Verify the redirect URL uses the custom appBaseUrl
expect(mockLocation.href).toBe(
`${customAppBaseUrl}/login?from_url=${encodeURIComponent(nextUrl)}`
);

// Restore window
global.window = originalWindow;
});

test('should use relative URL for login redirect when appBaseUrl is not provided', () => {
// Mock window.location
const originalWindow = global.window;
const mockLocation = { href: '', origin: 'https://current-app.com' };
global.window = {
location: mockLocation
};

const nextUrl = 'https://example.com/dashboard';
base44.auth.redirectToLogin(nextUrl);

// Verify the redirect URL uses a relative path (no appBaseUrl prefix)
expect(mockLocation.href).toBe(
`/login?from_url=${encodeURIComponent(nextUrl)}`
);

// Restore window
global.window = originalWindow;
});
Expand Down