-
Notifications
You must be signed in to change notification settings - Fork 2
ApiManager
Welcome back! In Chapter 7: RuleManager, we saw how the SDK acts like a bouncer, checking if visitors meet specific criteria before they can participate in an experiment. We've covered how the SDK manages visitors, experiments, features, data, bucketing, and rules internally.
But how does the SDK get the initial setup information (like the details of your experiments and those rules) from Convert's servers? And how does it report back what happened (like which variation a visitor saw, or if they completed a goal)?
Imagine the Convert SDK running in your visitor's browser is like a local branch office. It needs to:
- Get Instructions: Receive the latest operational plan (your project configuration, including experiments, features, audiences) from the headquarters (Convert servers).
- Send Reports: Send status updates (tracking events like "visitor A saw variation B", "visitor C completed goal X") back to headquarters so you can see the results in your Convert dashboard.
How does this local branch office (the SDK) communicate reliably with headquarters (Convert servers)?
Meet the ApiManager
! Think of it as the dedicated messenger or communication link between the Convert SDK running on your website and the Convert backend servers.
Its primary jobs are:
-
Fetching the Project Plan (
getConfig
): When the SDK starts up (Chapter 1: ConvertSDK / Core), if you provided ansdkKey
, theApiManager
is responsible for contacting the Convert servers and downloading the necessary project configuration data (all your experiments, features, rules, etc.). -
Sending Tracking Reports (
enqueue
,releaseQueue
): When the SDK makes a decision (like assigning a visitor to a variation using the BucketingManager) or tracks a conversion, the DataManager tells theApiManager
about it. TheApiManager
doesn't send a separate message for every single event. Instead, it acts efficiently:- Collects Messages: It gathers these small tracking reports into a queue.
- Sends in Batches: It periodically sends these collected reports back to the Convert tracking servers in batches. This is much more efficient than sending dozens of tiny messages individually.
In short, ApiManager
handles all the "talking" the SDK needs to do with the external Convert platform.
Like several other managers (ExperienceManager, BucketingManager, RuleManager), you'll rarely interact directly with the ApiManager
. It's mainly used internally by other core components:
-
By
ConvertSDK / Core
: During initialization, theCore
class uses theApiManager
'sgetConfig()
method to fetch the project configuration if ansdkKey
was provided.// Simplified conceptual code inside Core.fetchConfig() async fetchConfig() { try { // Tell the ApiManager to get the data const data = await this._apiManager.getConfig(); // ... process the received data ... } catch (error) { /* Handle error */ } }
-
By
DataManager
: When theDataManager
records a bucketing decision or a conversion event, it doesn't send it immediately. It tells theApiManager
to add the event to its queue usingenqueue()
.// Simplified conceptual code inside DataManager._retrieveBucketing() _retrieveBucketing(visitorId, experience, /*...*/) { // ... bucketing logic determines chosenVariationId ... // Store the decision locally... this.putData(visitorId, { bucketing: { /*...*/ } }); // Tell ApiManager to queue a tracking event for this decision if (enableTracking) { const eventData = { type: 'bucketing', /* other details */ }; this._apiManager.enqueue(visitorId, eventData); } // Return the variation details... }
So, ApiManager
is the behind-the-scenes worker handling the communication initiated by other parts of the SDK.
Let's look at the two main tasks: fetching data and sending tracking.
1. Fetching Configuration (getConfig
)
- When
Core.fetchConfig()
callsapiManager.getConfig()
, theApiManager
prepares and sends a simple HTTP GET request. -
Target: It sends the request to the Convert configuration endpoint URL (which it gets from the SDK settings, like
https://cdn.convert.com
). The URL includes your uniquesdkKey
(e.g.,https://cdn.convert.com/config/YOUR_SDK_KEY
). - Response: The Convert server responds with a large JSON object containing all the details about your project (experiences, features, audiences, goals, etc.).
-
Result: The
ApiManager
receives this JSON data and passes it back to theCore
, which then gives it to the DataManager for storage.
sequenceDiagram
participant Core as ConvertSDK / Core
participant ApiMgr as ApiManager
participant ConvertAPI as Convert Config API
Core->>+ApiMgr: getConfig()
Note over ApiMgr: Prepare HTTP GET request for /config/YOUR_SDK_KEY
ApiMgr->>+ConvertAPI: GET /config/YOUR_SDK_KEY
ConvertAPI-->>-ApiMgr: Respond with Project JSON data
ApiMgr-->>-Core: Return Project JSON data
2. Sending Tracking Events (enqueue
and releaseQueue
)
This is a bit more complex because of the batching mechanism.
-
Step 1: Enqueue: When
DataManager
callsapiManager.enqueue(visitorId, eventData)
, theApiManager
doesn't send anything yet. It simply adds theeventData
(like{type: 'conversion', goalId: '123'}
) to an internal list (_requestsQueue.items
) associated with thevisitorId
. It also increments a counter (_requestsQueue.length
). -
Step 2: Check Batch Conditions: After adding the event,
ApiManager
checks two things:- Did the queue size just reach the
batchSize
limit (e.g., 10 events)? - Was this the first event added to an empty queue?
- Did the queue size just reach the
-
Step 3: Release Queue (if needed):
-
Batch Size Reached: If the queue is full (
length === batchSize
), theApiManager
immediately calls its ownreleaseQueue('size')
method. -
First Event Added: If this was the first event, the
ApiManager
starts a timer (startQueue()
). This timer is set for thereleaseInterval
(e.g., 10 seconds). If the timer runs out before the batch size is reached, it will triggerreleaseQueue('timeout')
.
-
Batch Size Reached: If the queue is full (
-
Step 4: Send Batch (
releaseQueue
): WhenreleaseQueue
is called (either by batch size or timeout):- It stops any active timer (
stopQueue()
). - It copies the current contents of the queue (
_requestsQueue.items
). - It resets the internal queue (
_requestsQueue.reset()
) so new events can be collected. - It packages the copied events into a payload suitable for the Convert tracking API.
- It sends this payload as an HTTP POST request to the Convert tracking endpoint URL (like
https://track.convert.com/track/YOUR_SDK_KEY
). -
Optimization: In browsers, it might try using
navigator.sendBeacon()
for this POST request, which is more reliable if the user navigates away quickly.
- It stops any active timer (
sequenceDiagram
participant DataMgr as DataManager
participant ApiMgr as ApiManager
participant ConvertAPI as Convert Track API
DataMgr->>+ApiMgr: enqueue(visitor1, event1)
Note over ApiMgr: Add event1 to queue. Queue size = 1.\nStart 10s timer.
DataMgr->>+ApiMgr: enqueue(visitor2, event2)
Note over ApiMgr: Add event2 to queue. Queue size = 2.
DataMgr->>+ApiMgr: enqueue(visitor1, event3)
Note over ApiMgr: Add event3 to visitor1's events. Queue size = 3.
loop Every releaseInterval (e.g., 10s) OR when Queue fills up
ApiMgr->>ApiMgr: releaseQueue() Triggered (e.g., by timeout)
Note over ApiMgr: Stop timer. Copy queue items. Reset queue.
Note over ApiMgr: Prepare HTTP POST payload with events 1, 2, 3.
ApiMgr->>+ConvertAPI: POST /track/YOUR_SDK_KEY (payload: [event1, event2, event3])
ConvertAPI-->>-ApiMgr: Acknowledge receipt (e.g., HTTP 200 OK)
ApiMgr-->>ApiMgr: Handle response (log success/error)
end
This batching ensures the SDK doesn't overload the network with too many small requests, improving performance.
Let's look at simplified snippets from packages/api/src/api-manager.ts
.
1. The Constructor
Sets up endpoints, batching parameters, and initial tracking data structure.
// File: packages/api/src/api-manager.ts (Simplified)
import { HttpClient, HttpRequest, HttpResponse } from '@convertcom/js-sdk-utils';
// ... other imports ...
const DEFAULT_BATCH_SIZE = 10;
const DEFAULT_RELEASE_INTERVAL = 10000; // 10 seconds
const DEFAULT_CONFIG_ENDPOINT = 'https://cdn.convert.com';
const DEFAULT_TRACK_ENDPOINT = 'https://track.convert.com';
export class ApiManager implements ApiManagerInterface {
private _requestsQueue: VisitorsQueue;
private _requestsQueueTimerID: number; // For the release interval timer
private readonly _configEndpoint: string;
private readonly _trackEndpoint: string;
private _sdkKey: string;
private _projectId: string;
private _trackingEvent: TrackingEvent; // Base structure for tracking payload
private _trackingEnabled: boolean; // Can be turned off in config
readonly batchSize: number;
readonly releaseInterval: number;
constructor(config?: Config, { /* dependencies */ }) {
// Get URLs from config or use defaults
this._configEndpoint = config?.api?.endpoint?.config || DEFAULT_CONFIG_ENDPOINT;
this._trackEndpoint = config?.api?.endpoint?.track || DEFAULT_TRACK_ENDPOINT;
// Get batch settings from config or use defaults
this.batchSize = Number(config?.events?.batch_size) || DEFAULT_BATCH_SIZE;
this.releaseInterval = Number(config?.events?.release_interval) || DEFAULT_RELEASE_INTERVAL;
// SDK Key needed for API calls
this._sdkKey = config?.sdkKey || '';
this._projectId = config?.data?.project?.id; // May be set later via setData
// Tracking enabled by default, can be disabled
this._trackingEnabled = config?.network?.tracking !== false;
// Base structure for the payload sent to the tracking API
this._trackingEvent = {
accountId: config?.data?.account_id,
projectId: this._projectId,
visitors: []
// ... other fields like enrichData, source ...
};
// Initialize the queue object with methods to push/reset
this._requestsQueue = {
length: 0,
items: [],
push(/* ... implementation ... */){ /* Adds item and increments length */ },
reset(){ this.items = []; this.length = 0; }
};
// ... store loggerManager, eventManager ...
}
// ... other methods ...
}
- Reads configuration for API endpoints (
_configEndpoint
,_trackEndpoint
). - Reads batching parameters (
batchSize
,releaseInterval
). - Stores the
sdkKey
needed for API paths. - Sets up the internal
_requestsQueue
object and the base_trackingEvent
payload.
2. Fetching Configuration (getConfig
)
Makes the GET request to the config endpoint.
// File: packages/api/src/api-manager.ts (Simplified)
// Inside ApiManager class:
getConfig(): Promise<ConfigResponseData> {
this._loggerManager?.trace?.('ApiManager.getConfig()');
// Basic request structure
const path: Path = {
base: this._configEndpoint,
route: `/config/${this._sdkKey}` // Append SDK key to path
// Note: Actual code might add query params like '?environment=...'
};
// Use the internal request method (which uses HttpClient)
return new Promise((resolve, reject) => {
this.request('get', path)
.then(({ data }) => resolve(data)) // Extract data from response
.catch(reject);
});
}
// Internal helper using HttpClient (Highly Simplified)
private async request(
method: string, path: Path, data: Record<string, any> = {}
): Promise<HttpResponse> {
const requestConfig: HttpRequest = {
method: <HttpMethod>method,
path: path.route,
baseURL: path.base,
data: data, // Body for POST requests
headers: { 'Content-Type': 'application/json' /* ... other headers */ }
};
// Assume HttpClient makes the actual network call
return HttpClient.request(requestConfig);
}
- Constructs the
path
object with the base URL and the specific route/config/YOUR_SDK_KEY
. - Calls the internal
request
helper (which uses theHttpClient
utility we saw referenced before) to make the GET request. - Returns the
data
part of the successful response.
3. Enqueuing Tracking Events (enqueue
)
Adds an event to the queue and checks if the queue should be released.
// File: packages/api/src/api-manager.ts (Simplified)
// Inside ApiManager class:
enqueue(
visitorId: string,
eventRequest: VisitorTrackingEvents, // The event data {type, ...}
segments?: VisitorSegments // Optional visitor segment info
): void {
this._loggerManager?.trace?.('ApiManager.enqueue()', { eventRequest });
// Add the event to the internal queue list
this._requestsQueue.push(visitorId, eventRequest, segments);
// Only manage queue timing/release if tracking is enabled
if (this._trackingEnabled) {
// If queue is now full, release it immediately
if (this._requestsQueue.length >= this.batchSize) {
this.releaseQueue('size'); // Release due to size limit
}
// If this was the *first* item added (queue was empty before)
// start the timer for the release interval.
else if (this._requestsQueue.length === 1) {
this.startQueue(); // Start the timeout timer
}
}
}
// Helper to start the release timer
private startQueue(): void {
// Clear any existing timer first
clearTimeout(this._requestsQueueTimerID);
// Set a new timer
this._requestsQueueTimerID = setTimeout(() => {
this.releaseQueue('timeout'); // Release due to timeout
}, this.releaseInterval) as any;
}
// Helper to stop the release timer
private stopQueue(): void {
clearTimeout(this._requestsQueueTimerID);
}
- Calls
this._requestsQueue.push
to add the event data. - Checks if tracking is enabled.
- Checks if the queue length has reached
batchSize
, callingreleaseQueue
if it has. - Checks if the queue length is now 1 (meaning it was empty before), calling
startQueue
to begin the timeout if it is.
4. Releasing the Queue (releaseQueue
)
Sends the batched events to the tracking endpoint.
// File: packages/api/src/api-manager.ts (Simplified)
// Inside ApiManager class:
async releaseQueue(reason?: string): Promise<any> {
// Do nothing if queue is empty or tracking is off
if (!this._requestsQueue.length || !this._trackingEnabled) return;
this._loggerManager?.info?.('ApiManager.releaseQueue()', { reason });
// Stop the interval timer since we are sending now
this.stopQueue();
// Prepare the payload: Copy current queue items into the tracking structure
const payload: TrackingEvent = {
...this._trackingEvent, // Base info (accountId, projectId)
visitors: this._requestsQueue.items.slice() // Copy visitors and their events
// source: 'js-sdk' // Add source info
};
// Reset the queue for the next batch BEFORE sending
this._requestsQueue.reset();
// Define the tracking API path
const path: Path = {
base: this._trackEndpoint,
route: `/track/${this._sdkKey}`
};
try {
// Send the payload via POST request
// NOTE: Actual implementation might try navigator.sendBeacon first
const result = await this.request('post', path, payload);
// ... logging success, maybe fire event ...
return result;
} catch (error) {
// TODO: Handle errors (e.g., retry logic, exponential backoff)
this._loggerManager?.error?.('ApiManager.releaseQueue() failed', { error });
// Consider re-adding items to queue or alternative error handling
// For simplicity here, we just log the error.
throw error; // Re-throw for caller to potentially handle
}
}
- Checks if the queue has items and tracking is enabled.
- Stops the timeout timer (
stopQueue
). - Copies the queue items (
this._requestsQueue.items.slice()
) into thepayload
. -
Important: Resets the queue (
_requestsQueue.reset()
) immediately before the network request, so new events arriving during the request are added to a fresh queue. - Calls
this.request
to send thepayload
via HTTP POST to the/track/YOUR_SDK_KEY
endpoint. - Includes basic error handling (logging).
The ApiManager
is the vital communication link between the Convert SDK operating in the user's environment and the Convert backend servers. It acts as the messenger, responsible for:
-
Fetching the project configuration (
getConfig
) when the SDK initializes (if using ansdkKey
). -
Collecting and Batching tracking events (
enqueue
). -
Sending these batched events (
releaseQueue
) efficiently back to Convert for reporting.
While you don't usually call its methods directly, understanding the ApiManager
helps clarify how the SDK gets its instructions and reports results back to your Convert dashboard. It relies on batching and timers to do this efficiently.
We've now seen how most of the core managers interact and rely on each other. But how do these components signal to each other when something important happens, like when the configuration data has been successfully fetched and the SDK is ready? For internal communication, the SDK uses an event system.
Let's explore this internal notification system in the next chapter: Chapter 9: EventManager!
Copyrights © 2025 All Rights Reserved by Convert Insights, Inc.
Core Modules
- ConvertSDK / Core
- Context
- ExperienceManager
- FeatureManager
- DataManager
- BucketingManager
- RuleManager
- ApiManager
- EventManager
- Config / Types
Integration Guides