353 lines
14 KiB
JavaScript
353 lines
14 KiB
JavaScript
import { createAxiosClient } from "./utils/axios-client.js";
|
|
import { createEntitiesModule } from "./modules/entities.js";
|
|
import { createIntegrationsModule } from "./modules/integrations.js";
|
|
import { createAuthModule } from "./modules/auth.js";
|
|
import { createSsoModule } from "./modules/sso.js";
|
|
import { createConnectorsModule } from "./modules/connectors.js";
|
|
import { getAccessToken } from "./utils/auth-utils.js";
|
|
import { createFunctionsModule } from "./modules/functions.js";
|
|
import { createAgentsModule } from "./modules/agents.js";
|
|
import { createAppLogsModule } from "./modules/app-logs.js";
|
|
import { createUsersModule } from "./modules/users.js";
|
|
import { RoomsSocket } from "./utils/socket-utils.js";
|
|
import { createAnalyticsModule } from "./modules/analytics.js";
|
|
/**
|
|
* Creates a Base44 client.
|
|
*
|
|
* This is the main entry point for the Base44 SDK. It creates a client that provides access to the SDK's modules, such as {@linkcode EntitiesModule | entities}, {@linkcode AuthModule | auth}, and {@linkcode FunctionsModule | functions}.
|
|
*
|
|
* How you get a client depends on your context:
|
|
* - **Inside a Base44 app:** The client is automatically created and configured for you. Import it from `@/api/base44Client`.
|
|
* - **External app using Base44 as a backend:** Call `createClient()` directly in your code to create and configure the client.
|
|
*
|
|
* The client supports three authentication modes:
|
|
* - **Anonymous**: Access modules without authentication using `base44.moduleName`. Operations are scoped to public data and permissions.
|
|
* - **User authentication**: Access modules with user-level permissions using `base44.moduleName`. Operations are scoped to the authenticated user's data and permissions. Use `base44.auth.loginViaEmailPassword()` or other auth methods to get a token.
|
|
* - **Service role authentication**: Access modules with elevated permissions using `base44.asServiceRole.moduleName`. Operations can access any data available to the app's admin. Only available in Base44-hosted backend functions. Create a client with service role authentication using {@linkcode createClientFromRequest | createClientFromRequest()}.
|
|
*
|
|
* For example, when using the {@linkcode EntitiesModule | entities} module:
|
|
* - **Anonymous**: Can only read public data.
|
|
* - **User authentication**: Can access the current user's data.
|
|
* - **Service role authentication**: Can access all data that admins can access.
|
|
*
|
|
* Most modules are available in all three modes, but with different permission levels. However, some modules are only available in specific authentication modes.
|
|
*
|
|
* @param config - Configuration object for the client.
|
|
* @returns A configured Base44 client instance with access to all SDK modules.
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* // Create a client for your app
|
|
* import { createClient } from '@base44/sdk';
|
|
*
|
|
* const base44 = createClient({
|
|
* appId: 'my-app-id'
|
|
* });
|
|
*
|
|
* // Use the client to access your data
|
|
* const products = await base44.entities.Products.list();
|
|
* ```
|
|
*/
|
|
export function createClient(config) {
|
|
const { serverUrl = "https://base44.app", appId, token, serviceToken, requiresAuth = false, appBaseUrl, options, functionsVersion, headers: optionalHeaders, } = config;
|
|
// Normalize appBaseUrl to always be a string (empty if not provided or invalid)
|
|
const normalizedAppBaseUrl = typeof appBaseUrl === "string" ? appBaseUrl : "";
|
|
const socketConfig = {
|
|
serverUrl,
|
|
mountPath: "/ws-user-apps/socket.io/",
|
|
transports: ["websocket"],
|
|
appId,
|
|
token,
|
|
};
|
|
let socket = null;
|
|
const getSocket = () => {
|
|
if (!socket) {
|
|
socket = RoomsSocket({
|
|
config: socketConfig,
|
|
});
|
|
}
|
|
return socket;
|
|
};
|
|
const headers = {
|
|
...optionalHeaders,
|
|
"X-App-Id": String(appId),
|
|
};
|
|
const functionHeaders = functionsVersion
|
|
? {
|
|
...headers,
|
|
"Base44-Functions-Version": functionsVersion,
|
|
}
|
|
: headers;
|
|
const axiosClient = createAxiosClient({
|
|
baseURL: `${serverUrl}/api`,
|
|
headers,
|
|
token,
|
|
onError: options === null || options === void 0 ? void 0 : options.onError,
|
|
});
|
|
const functionsAxiosClient = createAxiosClient({
|
|
baseURL: `${serverUrl}/api`,
|
|
headers: functionHeaders,
|
|
token,
|
|
interceptResponses: false,
|
|
onError: options === null || options === void 0 ? void 0 : options.onError,
|
|
});
|
|
const serviceRoleAxiosClient = createAxiosClient({
|
|
baseURL: `${serverUrl}/api`,
|
|
headers,
|
|
token: serviceToken,
|
|
onError: options === null || options === void 0 ? void 0 : options.onError,
|
|
});
|
|
const serviceRoleFunctionsAxiosClient = createAxiosClient({
|
|
baseURL: `${serverUrl}/api`,
|
|
headers: functionHeaders,
|
|
token: serviceToken,
|
|
interceptResponses: false,
|
|
});
|
|
const userAuthModule = createAuthModule(axiosClient, functionsAxiosClient, appId, {
|
|
appBaseUrl: normalizedAppBaseUrl,
|
|
serverUrl,
|
|
});
|
|
const userModules = {
|
|
entities: createEntitiesModule({
|
|
axios: axiosClient,
|
|
appId,
|
|
getSocket,
|
|
}),
|
|
integrations: createIntegrationsModule(axiosClient, appId),
|
|
auth: userAuthModule,
|
|
functions: createFunctionsModule(functionsAxiosClient, appId),
|
|
agents: createAgentsModule({
|
|
axios: axiosClient,
|
|
getSocket,
|
|
appId,
|
|
serverUrl,
|
|
token,
|
|
}),
|
|
appLogs: createAppLogsModule(axiosClient, appId),
|
|
users: createUsersModule(axiosClient, appId),
|
|
analytics: createAnalyticsModule({
|
|
axiosClient,
|
|
serverUrl,
|
|
appId,
|
|
userAuthModule,
|
|
}),
|
|
cleanup: () => {
|
|
userModules.analytics.cleanup();
|
|
if (socket) {
|
|
socket.disconnect();
|
|
}
|
|
},
|
|
};
|
|
const serviceRoleModules = {
|
|
entities: createEntitiesModule({
|
|
axios: serviceRoleAxiosClient,
|
|
appId,
|
|
getSocket,
|
|
}),
|
|
integrations: createIntegrationsModule(serviceRoleAxiosClient, appId),
|
|
sso: createSsoModule(serviceRoleAxiosClient, appId, token),
|
|
connectors: createConnectorsModule(serviceRoleAxiosClient, appId),
|
|
functions: createFunctionsModule(serviceRoleFunctionsAxiosClient, appId),
|
|
agents: createAgentsModule({
|
|
axios: serviceRoleAxiosClient,
|
|
getSocket,
|
|
appId,
|
|
serverUrl,
|
|
token,
|
|
}),
|
|
appLogs: createAppLogsModule(serviceRoleAxiosClient, appId),
|
|
cleanup: () => {
|
|
if (socket) {
|
|
socket.disconnect();
|
|
}
|
|
},
|
|
};
|
|
// Always try to get token from localStorage or URL parameters
|
|
if (typeof window !== "undefined") {
|
|
// Get token from URL or localStorage
|
|
const accessToken = token || getAccessToken();
|
|
if (accessToken) {
|
|
userModules.auth.setToken(accessToken);
|
|
}
|
|
}
|
|
// If authentication is required, verify token and redirect to login if needed
|
|
if (requiresAuth && typeof window !== "undefined") {
|
|
// We perform this check asynchronously to not block client creation
|
|
setTimeout(async () => {
|
|
try {
|
|
const isAuthenticated = await userModules.auth.isAuthenticated();
|
|
if (!isAuthenticated) {
|
|
userModules.auth.redirectToLogin(window.location.href);
|
|
}
|
|
}
|
|
catch (error) {
|
|
console.error("Authentication check failed:", error);
|
|
userModules.auth.redirectToLogin(window.location.href);
|
|
}
|
|
}, 0);
|
|
}
|
|
// Assemble and return the client
|
|
const client = {
|
|
...userModules,
|
|
/**
|
|
* Sets a new authentication token for all subsequent requests.
|
|
*
|
|
* @param newToken - The new authentication token
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* // Update token after login
|
|
* const { access_token } = await base44.auth.loginViaEmailPassword(
|
|
* 'user@example.com',
|
|
* 'password'
|
|
* );
|
|
* base44.setToken(access_token);
|
|
* ```
|
|
*/
|
|
setToken(newToken) {
|
|
userModules.auth.setToken(newToken);
|
|
if (socket) {
|
|
socket.updateConfig({
|
|
token: newToken,
|
|
});
|
|
}
|
|
socketConfig.token = newToken;
|
|
},
|
|
/**
|
|
* Gets the current client configuration.
|
|
*
|
|
* @internal
|
|
*/
|
|
getConfig() {
|
|
return {
|
|
serverUrl,
|
|
appId,
|
|
requiresAuth,
|
|
};
|
|
},
|
|
/**
|
|
* Provides access to service role modules.
|
|
*
|
|
* Service role authentication provides elevated permissions for backend operations. Unlike user authentication, which is scoped to a specific user's permissions, service role authentication has access to the data and operations available to the app's admin.
|
|
*
|
|
* @throws {Error} When accessed without providing a serviceToken during client creation.
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const base44 = createClient({
|
|
* appId: 'my-app-id',
|
|
* serviceToken: 'service-role-token'
|
|
* });
|
|
*
|
|
* // Also access a module with elevated permissions
|
|
* const allUsers = await base44.asServiceRole.entities.User.list();
|
|
* ```
|
|
*/
|
|
get asServiceRole() {
|
|
if (!serviceToken) {
|
|
throw new Error("Service token is required to use asServiceRole. Please provide a serviceToken when creating the client.");
|
|
}
|
|
return serviceRoleModules;
|
|
},
|
|
};
|
|
return client;
|
|
}
|
|
/**
|
|
* Creates a Base44 client from an HTTP request.
|
|
*
|
|
* This function is designed for use in Base44-hosted backend functions. For frontends and external backends, use {@linkcode createClient | createClient()} instead.
|
|
*
|
|
* When used in a Base44-hosted backend function, `createClientFromRequest()` automatically extracts authentication tokens from the request headers that Base44 injects when forwarding requests. The returned client includes service role access using `base44.asServiceRole`, which provides admin-level permissions.
|
|
*
|
|
* To learn more about the Base44 client, see {@linkcode createClient | createClient()}.
|
|
*
|
|
* @param request - The incoming HTTP request object containing Base44 authentication headers.
|
|
* @returns A configured Base44 client instance with authentication from the incoming request.
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* // User authentication in backend function
|
|
* import { createClientFromRequest } from 'npm:@base44/sdk';
|
|
*
|
|
* Deno.serve(async (req) => {
|
|
* try {
|
|
* const base44 = createClientFromRequest(req);
|
|
* const user = await base44.auth.me();
|
|
*
|
|
* if (!user) {
|
|
* return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
* }
|
|
*
|
|
* // Access user's data
|
|
* const userOrders = await base44.entities.Orders.filter({ userId: user.id });
|
|
* return Response.json({ orders: userOrders });
|
|
* } catch (error) {
|
|
* return Response.json({ error: error.message }, { status: 500 });
|
|
* }
|
|
* });
|
|
* ```
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* // Service role authentication in backend function
|
|
* import { createClientFromRequest } from 'npm:@base44/sdk';
|
|
*
|
|
* Deno.serve(async (req) => {
|
|
* try {
|
|
* const base44 = createClientFromRequest(req);
|
|
*
|
|
* // Access admin data with service role permissions
|
|
* const recentOrders = await base44.asServiceRole.entities.Orders.list('-created_at', 50);
|
|
*
|
|
* return Response.json({ orders: recentOrders });
|
|
* } catch (error) {
|
|
* return Response.json({ error: error.message }, { status: 500 });
|
|
* }
|
|
* });
|
|
* ```
|
|
*
|
|
*/
|
|
export function createClientFromRequest(request) {
|
|
const authHeader = request.headers.get("Authorization");
|
|
const serviceRoleAuthHeader = request.headers.get("Base44-Service-Authorization");
|
|
const appId = request.headers.get("Base44-App-Id");
|
|
const serverUrlHeader = request.headers.get("Base44-Api-Url");
|
|
const functionsVersion = request.headers.get("Base44-Functions-Version");
|
|
const stateHeader = request.headers.get("Base44-State");
|
|
if (!appId) {
|
|
throw new Error("Base44-App-Id header is required, but is was not found on the request");
|
|
}
|
|
// Validate authorization header formats
|
|
let serviceRoleToken;
|
|
let userToken;
|
|
if (serviceRoleAuthHeader !== null) {
|
|
if (serviceRoleAuthHeader === "" ||
|
|
!serviceRoleAuthHeader.startsWith("Bearer ") ||
|
|
serviceRoleAuthHeader.split(" ").length !== 2) {
|
|
throw new Error('Invalid authorization header format. Expected "Bearer <token>"');
|
|
}
|
|
serviceRoleToken = serviceRoleAuthHeader.split(" ")[1];
|
|
}
|
|
if (authHeader !== null) {
|
|
if (authHeader === "" ||
|
|
!authHeader.startsWith("Bearer ") ||
|
|
authHeader.split(" ").length !== 2) {
|
|
throw new Error('Invalid authorization header format. Expected "Bearer <token>"');
|
|
}
|
|
userToken = authHeader.split(" ")[1];
|
|
}
|
|
// Prepare additional headers to propagate
|
|
const additionalHeaders = {};
|
|
if (stateHeader) {
|
|
additionalHeaders["Base44-State"] = stateHeader;
|
|
}
|
|
return createClient({
|
|
serverUrl: serverUrlHeader || "https://base44.app",
|
|
appId,
|
|
token: userToken,
|
|
serviceToken: serviceRoleToken,
|
|
functionsVersion: functionsVersion !== null && functionsVersion !== void 0 ? functionsVersion : undefined,
|
|
headers: additionalHeaders,
|
|
});
|
|
}
|