From 3f8ded58a46bffba561be40a370b3f5736cd2a4b Mon Sep 17 00:00:00 2001 From: Gabriel Grinberg Date: Wed, 10 Sep 2025 00:17:46 +0300 Subject: [PATCH 1/3] feat: add backend function versioning support - Add extractSnapshotIdFromHost utility to detect environment from URL - Include Base44-Functions-Version header in function calls - Support functionsVersion parameter in client configuration - Automatically route function calls to correct deployment based on URL pattern This ensures backend functions work correctly with app versioning, calling the right deployment for production, preview, and checkpoint environments. --- src/client.ts | 29 ++++++++++-------- src/modules/functions.ts | 11 ++++++- src/utils/extract-snapshot-id-from-host.ts | 34 ++++++++++++++++++++++ 3 files changed, 61 insertions(+), 13 deletions(-) create mode 100644 src/utils/extract-snapshot-id-from-host.ts diff --git a/src/client.ts b/src/client.ts index a4e8049..944ce97 100644 --- a/src/client.ts +++ b/src/client.ts @@ -22,6 +22,7 @@ export function createClient(config: { token?: string; serviceToken?: string; requiresAuth?: boolean; + functionsVersion?: string; }) { const { serverUrl = "https://base44.app", @@ -29,13 +30,21 @@ export function createClient(config: { token, serviceToken, requiresAuth = false, + functionsVersion } = config; + const headers = { + "X-App-Id": String(appId), + } + + const functionHeaders = functionsVersion ? { + ...headers, + "Base44-Functions-Version": functionsVersion + } : headers; + const axiosClient = createAxiosClient({ baseURL: `${serverUrl}/api`, - headers: { - "X-App-Id": String(appId), - }, + headers, token, requiresAuth, appId, @@ -44,9 +53,7 @@ export function createClient(config: { const functionsAxiosClient = createAxiosClient({ baseURL: `${serverUrl}/api`, - headers: { - "X-App-Id": String(appId), - }, + headers: functionHeaders, token, requiresAuth, appId, @@ -56,9 +63,7 @@ export function createClient(config: { const serviceRoleAxiosClient = createAxiosClient({ baseURL: `${serverUrl}/api`, - headers: { - "X-App-Id": String(appId), - }, + headers, token: serviceToken, serverUrl, appId, @@ -66,9 +71,7 @@ export function createClient(config: { const serviceRoleFunctionsAxiosClient = createAxiosClient({ baseURL: `${serverUrl}/api`, - headers: { - "X-App-Id": String(appId), - }, + headers: functionHeaders, token: serviceToken, serverUrl, appId, @@ -160,6 +163,7 @@ export function createClientFromRequest(request: Request) { ); const appId = request.headers.get("Base44-App-Id"); const serverUrlHeader = request.headers.get("Base44-Api-Url"); + const functionsVersion = request.headers.get("Base44-Functions-Version"); if (!appId) { throw new Error( @@ -190,5 +194,6 @@ export function createClientFromRequest(request: Request) { appId, token: userToken, serviceToken: serviceRoleToken, + functionsVersion: functionsVersion ?? undefined }); } diff --git a/src/modules/functions.ts b/src/modules/functions.ts index 4dfa102..e005858 100644 --- a/src/modules/functions.ts +++ b/src/modules/functions.ts @@ -1,4 +1,5 @@ import { AxiosInstance } from "axios"; +import { extractSnapshotIdFromHost } from "../utils/extract-snapshot-id-from-host.js"; /** * Creates the functions module for the Base44 SDK @@ -41,10 +42,18 @@ export function createFunctionsModule(axios: AxiosInstance, appId: string) { contentType = "application/json"; } + // Extract functions version from the current URL host + const functionsVersion = extractSnapshotIdFromHost(); + return axios.post( `/apps/${appId}/functions/${functionName}`, formData || data, - { headers: { "Content-Type": contentType } } + { + headers: { + "Content-Type": contentType, + "X-Functions-Version": functionsVersion + } + } ); }, }; diff --git a/src/utils/extract-snapshot-id-from-host.ts b/src/utils/extract-snapshot-id-from-host.ts new file mode 100644 index 0000000..98239aa --- /dev/null +++ b/src/utils/extract-snapshot-id-from-host.ts @@ -0,0 +1,34 @@ +/** + * Extracts the snapshot ID or environment type from the current hostname + * Used to determine which backend function deployment to call + * + * @returns {string} The snapshot ID for checkpoints, 'preview' for preview URLs, or 'prod' for production + */ +export function extractSnapshotIdFromHost(): string { + if (typeof window === "undefined") { + return "prod"; + } + + const hostname = window.location.hostname; + + // Check if it's a checkpoint URL + if (hostname.startsWith("checkpoint--")) { + // Format: checkpoint--{app_id}--{snapshot_id}.domain + const parts = hostname.split("--"); + if (parts.length >= 3) { + // Extract snapshot_id (last part before the domain) + const snapshotPart = parts[2]; + // Remove domain extension if present + const snapshotId = snapshotPart.split(".")[0]; + return snapshotId; + } + } + + // Check if it's a preview URL + if (hostname.startsWith("preview--")) { + return "preview"; + } + + // Production URLs - return "prod" + return "prod"; +} \ No newline at end of file From e222d4338bcd4952ffca7f77e69aea02dc7f7751 Mon Sep 17 00:00:00 2001 From: Gabriel Grinberg Date: Wed, 10 Sep 2025 00:24:19 +0300 Subject: [PATCH 2/3] fix header name post rename --- src/modules/functions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/functions.ts b/src/modules/functions.ts index e005858..f979ae0 100644 --- a/src/modules/functions.ts +++ b/src/modules/functions.ts @@ -51,7 +51,7 @@ export function createFunctionsModule(axios: AxiosInstance, appId: string) { { headers: { "Content-Type": contentType, - "X-Functions-Version": functionsVersion + "Base44-Functions-Version": functionsVersion } } ); From 9bbb6465ff5fb50e8b074f6ec8b3d03ba0fc7b50 Mon Sep 17 00:00:00 2001 From: Gabriel Grinberg Date: Wed, 10 Sep 2025 01:49:01 +0300 Subject: [PATCH 3/3] revert functions only changes --- src/modules/functions.ts | 11 +------ src/utils/extract-snapshot-id-from-host.ts | 34 ---------------------- 2 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 src/utils/extract-snapshot-id-from-host.ts diff --git a/src/modules/functions.ts b/src/modules/functions.ts index f979ae0..4dfa102 100644 --- a/src/modules/functions.ts +++ b/src/modules/functions.ts @@ -1,5 +1,4 @@ import { AxiosInstance } from "axios"; -import { extractSnapshotIdFromHost } from "../utils/extract-snapshot-id-from-host.js"; /** * Creates the functions module for the Base44 SDK @@ -42,18 +41,10 @@ export function createFunctionsModule(axios: AxiosInstance, appId: string) { contentType = "application/json"; } - // Extract functions version from the current URL host - const functionsVersion = extractSnapshotIdFromHost(); - return axios.post( `/apps/${appId}/functions/${functionName}`, formData || data, - { - headers: { - "Content-Type": contentType, - "Base44-Functions-Version": functionsVersion - } - } + { headers: { "Content-Type": contentType } } ); }, }; diff --git a/src/utils/extract-snapshot-id-from-host.ts b/src/utils/extract-snapshot-id-from-host.ts deleted file mode 100644 index 98239aa..0000000 --- a/src/utils/extract-snapshot-id-from-host.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Extracts the snapshot ID or environment type from the current hostname - * Used to determine which backend function deployment to call - * - * @returns {string} The snapshot ID for checkpoints, 'preview' for preview URLs, or 'prod' for production - */ -export function extractSnapshotIdFromHost(): string { - if (typeof window === "undefined") { - return "prod"; - } - - const hostname = window.location.hostname; - - // Check if it's a checkpoint URL - if (hostname.startsWith("checkpoint--")) { - // Format: checkpoint--{app_id}--{snapshot_id}.domain - const parts = hostname.split("--"); - if (parts.length >= 3) { - // Extract snapshot_id (last part before the domain) - const snapshotPart = parts[2]; - // Remove domain extension if present - const snapshotId = snapshotPart.split(".")[0]; - return snapshotId; - } - } - - // Check if it's a preview URL - if (hostname.startsWith("preview--")) { - return "preview"; - } - - // Production URLs - return "prod" - return "prod"; -} \ No newline at end of file