Skip to content
120 changes: 114 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import { createClient } from '@base44/sdk';
// Create a client instance
const base44 = createClient({
serverUrl: 'https://base44.app', // Optional, defaults to 'https://base44.app'
appId: 'your-app-id', // Required
env: 'prod', // Optional, defaults to 'prod'
token: 'your-token', // Optional
autoInitAuth: true, // Optional, defaults to true - auto-detects tokens from URL or localStorage
appId: 'your-app-id', // Required
token: 'your-user-token', // Optional, for user authentication
serviceToken: 'your-service-token', // Optional, for service role authentication
autoInitAuth: true, // Optional, defaults to true - auto-detects tokens from URL or localStorage
});
```

Expand Down Expand Up @@ -63,6 +63,77 @@ const newProducts = await base44.entities.Product.bulkCreate([
]);
```

### Service Role Authentication

Service role authentication allows server-side applications to perform operations with elevated privileges. This is useful for administrative tasks, background jobs, and server-to-server communication.

```javascript
import { createClient } from '@base44/sdk';

// Create a client with service role token
const base44 = createClient({
appId: 'your-app-id',
token: 'user-token', // For user operations
serviceToken: 'service-token' // For service role operations
});

// User operations (uses user token)
const userEntities = await base44.entities.User.list();

// Service role operations (uses service token)
const allEntities = await base44.asServiceRole.entities.User.list();

// Service role has access to:
// - base44.asServiceRole.entities
// - base44.asServiceRole.integrations
// - base44.asServiceRole.functions
// Note: Service role does NOT have access to auth module for security

// If no service token is provided, accessing asServiceRole throws an error
const clientWithoutService = createClient({ appId: 'your-app-id' });
try {
await clientWithoutService.asServiceRole.entities.User.list();
} catch (error) {
// Error: Service token is required to use asServiceRole
}
```

### Server-Side Usage

For server-side applications, you can create a client from incoming HTTP requests:

```javascript
import { createClientFromRequest } from '@base44/sdk';

// In your server handler (Express, Next.js, etc.)
app.get('/api/data', async (req, res) => {
try {
// Extract client configuration from request headers
const base44 = createClientFromRequest(req);

// Headers used:
// - Authorization: Bearer <user-token>
// - Base44-Service-Authorization: Bearer <service-token>
// - Base44-App-Id: <app-id>
// - Base44-Api-Url: <custom-api-url> (optional)

// Use appropriate authentication based on available tokens
let data;
if (base44.asServiceRole) {
// Service token available - use elevated privileges
data = await base44.asServiceRole.entities.SensitiveData.list();
} else {
// Only user token available - use user permissions
data = await base44.entities.PublicData.list();
}

res.json(data);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
```

### Working with Integrations

```javascript
Expand Down Expand Up @@ -103,7 +174,7 @@ import { getAccessToken } from '@base44/sdk/utils/auth-utils';
// Create a client with authentication
const base44 = createClient({
appId: 'your-app-id',
accessToken: getAccessToken() // Automatically retrieves token from localStorage or URL
token: getAccessToken() // Automatically retrieves token from localStorage or URL
});

// Check authentication status
Expand Down Expand Up @@ -167,7 +238,7 @@ function AuthProvider({ children }) {
const [client] = useState(() =>
createClient({
appId: 'your-app-id',
accessToken: getAccessToken()
token: getAccessToken()
})
);

Expand Down Expand Up @@ -347,6 +418,24 @@ async function fetchProducts() {
}
}

// Service role operations with TypeScript
async function adminOperations() {
const base44 = createClient({
appId: 'your-app-id',
serviceToken: 'service-token'
});

// TypeScript knows asServiceRole requires a service token
try {
const allUsers: Entity[] = await base44.asServiceRole.entities.User.list();
console.log(`Total users: ${allUsers.length}`);
} catch (error) {
if (error instanceof Error) {
console.error(error.message); // Service token is required to use asServiceRole
}
}
}

// Authentication with TypeScript
async function handleAuth(auth: AuthModule) {
// Check authentication
Expand Down Expand Up @@ -456,6 +545,25 @@ try {
}
```

## Functions

The SDK supports invoking custom functions:

```javascript
// Invoke a function without parameters
const result = await base44.functions.myFunction();

// Invoke a function with parameters
const result = await base44.functions.calculateTotal({
items: ['item1', 'item2'],
discount: 0.1
});

// Functions are automatically authenticated with the user token
// Service role can also invoke functions
const serviceResult = await base44.asServiceRole.functions.adminFunction();
```

## Testing

The SDK includes comprehensive tests to ensure reliability.
Expand Down
124 changes: 99 additions & 25 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,41 @@ import { createFunctionsModule } from "./modules/functions.js";
* @param {Object} config - Client configuration
* @param {string} [config.serverUrl='https://base44.app'] - API server URL
* @param {string|number} config.appId - Application ID
* @param {string} [config.env='prod'] - Environment ('prod' or 'dev')
* @param {string} [config.token] - Authentication token
* @param {string} [config.serviceToken] - Service role authentication token
* @param {boolean} [config.requiresAuth=false] - Whether the app requires authentication
* @returns {Object} Base44 client instance
*/
export function createClient(config: {
serverUrl?: string;
appId: string;
env?: string;
token?: string;
serviceToken?: string;
requiresAuth?: boolean;
}) {
const {
serverUrl = "https://base44.app",
appId,
env = "prod",
token,
serviceToken,
requiresAuth = false,
} = config;

// Create the base axios client
const axiosClient = createAxiosClient({
baseURL: `${serverUrl}/api`,
headers: {
"X-App-Id": String(appId),
"X-Environment": env,
},
token,
requiresAuth, // Pass requiresAuth to axios client
appId, // Pass appId for login redirect
serverUrl, // Pass serverUrl for login redirect
requiresAuth,
appId,
serverUrl,
});

const functionsAxiosClient = createAxiosClient({
baseURL: `${serverUrl}/api`,
headers: {
"X-App-Id": String(appId),
"X-Environment": env,
},
token,
requiresAuth,
Expand All @@ -56,18 +53,46 @@ export function createClient(config: {
interceptResponses: false,
});

// Create modules
const entities = createEntitiesModule(axiosClient, appId);
const integrations = createIntegrationsModule(axiosClient, appId);
const auth = createAuthModule(axiosClient, appId, serverUrl);
const functions = createFunctionsModule(functionsAxiosClient, appId);
const serviceRoleAxiosClient = createAxiosClient({
baseURL: `${serverUrl}/api`,
headers: {
"X-App-Id": String(appId),
},
token: serviceToken,
serverUrl,
appId,
});

const serviceRoleFunctionsAxiosClient = createAxiosClient({
baseURL: `${serverUrl}/api`,
headers: {
"X-App-Id": String(appId),
},
token: serviceToken,
serverUrl,
appId,
interceptResponses: false,
});

const userModules = {
entities: createEntitiesModule(axiosClient, appId),
integrations: createIntegrationsModule(axiosClient, appId),
auth: createAuthModule(axiosClient, functionsAxiosClient, appId),
functions: createFunctionsModule(functionsAxiosClient, appId),
};

const serviceRoleModules = {
entities: createEntitiesModule(serviceRoleAxiosClient, appId),
integrations: createIntegrationsModule(serviceRoleAxiosClient, appId),
functions: createFunctionsModule(serviceRoleFunctionsAxiosClient, appId),
};

// 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) {
auth.setToken(accessToken);
userModules.auth.setToken(accessToken);
}
}

Expand All @@ -76,30 +101,27 @@ export function createClient(config: {
// We perform this check asynchronously to not block client creation
setTimeout(async () => {
try {
const isAuthenticated = await auth.isAuthenticated();
const isAuthenticated = await userModules.auth.isAuthenticated();
if (!isAuthenticated) {
auth.redirectToLogin(window.location.href);
userModules.auth.redirectToLogin(window.location.href);
}
} catch (error) {
console.error("Authentication check failed:", error);
auth.redirectToLogin(window.location.href);
userModules.auth.redirectToLogin(window.location.href);
}
}, 0);
}

// Assemble and return the client
return {
entities,
integrations,
auth,
functions,
const client = {
...userModules,

/**
* Set authentication token for all requests
* @param {string} newToken - New auth token
*/
setToken(newToken: string) {
auth.setToken(newToken);
userModules.auth.setToken(newToken);
},

/**
Expand All @@ -110,9 +132,61 @@ export function createClient(config: {
return {
serverUrl,
appId,
env,
requiresAuth,
};
},

/**
* Access service role modules - throws error if no service token was provided
* @throws {Error} When accessed without a service token
*/
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;
}

export function createClientFromRequest(request: 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");

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: string | undefined;
let userToken: string | undefined;

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];
}

return createClient({
serverUrl: serverUrlHeader || "https://base44.app",
appId,
token: userToken,
serviceToken: serviceRoleToken,
});
}
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createClient } from "./client.js";
import { createClient, createClientFromRequest } from "./client.js";
import { Base44Error } from "./utils/axios-client.js";
import {
getAccessToken,
Expand All @@ -9,8 +9,8 @@ import {

export {
createClient,
createClientFromRequest,
Base44Error,
// Export auth utilities for easier access
getAccessToken,
saveAccessToken,
removeAccessToken,
Expand Down
Loading