Skip to content

Context

Ahmed Abbas edited this page Apr 23, 2025 · 2 revisions

Chapter 2: Context

Welcome back! In Chapter 1: ConvertSDK / Core, we learned how to initialize the main ConvertSDK object – our orchestra conductor. We finished by creating a special object for a single website visitor using convert.createContext(visitorId, visitorAttributes).

But what is this Context object, and what can we do with it? Let's find out!

The Problem: Managing Individual Visitors

Imagine your website is like a busy theme park. The ConvertSDK object is the park management – it knows about all the rides (experiments) and attractions (features). However, park management doesn't interact directly with every single visitor.

When a visitor arrives ('user123' lands on your homepage), you need a way to:

  1. Identify them uniquely: Know this is 'user123'.
  2. Give them the right experience: If you're testing two different welcome messages (A/B test), ensure 'user123' consistently sees one version throughout their visit.
  3. Track their actions: If 'user123' signs up after seeing a specific welcome message, you need to record that success for that specific message and that specific user.

How do you manage all this personalized information and interaction for just one visitor among potentially thousands?

What is Context? Your Visitor's Personal Guide

The Context object is the answer! Think of it as a personalized guide and wristband given to each visitor when they enter your theme park (website).

  • Identification: The guide knows the visitor's unique ID (visitorId).
  • Personalization: It keeps track of which specific version of an experiment (like which welcome message) the visitor should see.
  • Interaction Point: You use this guide to ask questions like "Which experiment variation should this visitor see?" or to tell it "This visitor just completed a goal!".
  • State: It can hold information about the visitor (like browser: 'chrome' or country: 'USA') which might influence which experiments or features they are eligible for.

Every action you perform using a specific Context object is tied directly to the visitorId it was created with. If you create one Context for 'user123' and another for 'visitor456', their activities remain separate and personalized.

Using the Context: Running an Experiment

Let's go back to the A/B test from Chapter 1: testing two headlines ('headline-A' vs. 'headline-B') to see which gets more sign-ups. We already initialized the SDK and created a context for 'user123'.

// Remember from Chapter 1:
// 'convert' is the initialized ConvertSDK instance
// 'visitorContext' is the Context object for 'user123'
const visitorContext = convert.createContext('user123', { device: 'mobile' });

// Now, let's use the context!
// Assume your headline A/B test is called 'headline-test' in Convert
const experienceKey = 'headline-test';

// Ask the context which variation 'user123' should see
const decision = visitorContext.runExperience(experienceKey);

console.log(decision);
  • visitorContext.runExperience(experienceKey) is the core action here. We're asking the personal guide (visitorContext) which specific variation of the 'headline-test' ride this visitor (user123) should experience.
  • experienceKey: This is the unique identifier for your experiment (you set this up in your Convert account).

What does runExperience give back?

It usually returns a small object describing the variation the visitor should see. It might look something like this (simplified):

// Example possible output for 'decision':
{
  "experienceKey": "headline-test",
  "key": "variation-B", // The ID of the chosen variation
  "id": "10023457", // Convert ID for the variation
  "status": "running",
  // ... other details
}

This tells you that for user123, the SDK decided they should see variation-B (maybe this corresponds to your second headline). Now your website code can use this information to display the correct headline.

If something goes wrong (e.g., the experience key doesn't exist, or the visitor doesn't qualify for the experiment based on rules), runExperience might return an error code instead.

Other Context Actions:

The Context object isn't just for A/B tests. You'll use it for other common tasks too:

  • runFeature(featureKey, attributes): Checks if a feature flag is enabled for this visitor and gets its details. (FeatureManager handles the logic).
  • trackConversion(goalKey, attributes): Records that this visitor completed a specific goal (like clicking a sign-up button). (DataManager helps store this).
  • runExperiences(attributes): Runs all active experiments the visitor qualifies for at once.
  • runFeatures(attributes): Checks all relevant feature flags for the visitor.

All these methods work similarly: you call them on the specific visitor's Context object, and the action is tied to that visitor's ID.

Under the Hood: How runExperience Works

When you call visitorContext.runExperience('headline-test'), what happens inside the SDK?

  1. Check Visitor ID: The Context object confirms it has a valid visitorId ('user123').
  2. Gather Properties: It looks at the visitor's properties. This includes properties provided when creating the context (like { device: 'mobile' }) and potentially other stored properties for this visitor that the SDK might know about (managed by the DataManager). It also includes any temporary properties you might pass directly to runExperience.
  3. Delegate to ExperienceManager: The Context itself doesn't decide the variation. It passes the visitorId, the experienceKey ('headline-test'), and all the gathered properties to the ExperienceManager.
  4. ExperienceManager Decides: The ExperienceManager is the specialist. It checks if the experiment exists, if it's running, if the visitor meets any targeting rules (like "only show to mobile users"), and then uses a process called "bucketing" (managed by the BucketingManager) to consistently assign the visitor to a variation.
  5. Return Result: The ExperienceManager sends the result (the chosen variation details or an error) back to the Context.
  6. Fire Event (Optional): The Context might notify other parts of the system that a bucketing decision was made by firing an event using the EventManager. This is useful for logging or integrations.
  7. Return to You: The Context returns the final result (the variation object or error) to your code.

Here's a simplified diagram:

sequenceDiagram
    participant YourCode as Your Website Code
    participant Context as Visitor Context ('user123')
    participant ExpManager as ExperienceManager
    participant DataManager as DataManager
    participant EventManager as EventManager

    YourCode->>+Context: runExperience('headline-test')
    Context->>+DataManager: Get stored properties for 'user123'
    DataManager-->>-Context: Return properties (e.g., {})
    Note over Context: Combine with initial properties (e.g., {device: 'mobile'})
    Context->>+ExpManager: selectVariation('user123', 'headline-test', {device: 'mobile'})
    Note right of ExpManager: Checks rules, performs bucketing...
    ExpManager-->>-Context: Return variation object (e.g., variation-B)
    Context->>+EventManager: fire(BUCKETING, {details...})
    EventManager-->>-Context: Context-->>-YourCode: Return variation object
Loading

Code Peek:

Let's look at simplified snippets from the SDK's context.ts file.

1. The Constructor (context.ts)

When convert.createContext() is called, it runs this constructor to set up the Context object.

// File: packages/js-sdk/src/context.ts (Simplified)

export class Context implements ContextInterface {
  private _visitorId: string;
  private _visitorProperties: Record<string, any>;
  private _experienceManager: ExperienceManagerInterface; // Will use this!
  private _featureManager: FeatureManagerInterface;
  private _dataManager: DataManagerInterface; // Will use this!
  private _eventManager: EventManagerInterface; // Will use this!
  // ... other managers and properties

  constructor(
    config: Config,
    visitorId: string,
    managers: { // SDK passes all needed managers
      eventManager: EventManagerInterface;
      experienceManager: ExperienceManagerInterface;
      featureManager: FeatureManagerInterface;
      dataManager: DataManagerInterface;
      // ... other managers
    },
    visitorProperties?: Record<string, any> // e.g., { device: 'mobile' }
  ) {
    this._visitorId = visitorId; // Store the visitor's ID
    this._visitorProperties = visitorProperties || {}; // Store initial properties
    // Store references to the managers passed in
    this._experienceManager = managers.experienceManager;
    this._featureManager = managers.featureManager;
    this._dataManager = managers.dataManager;
    this._eventManager = managers.eventManager;
    // ...
  }

  // ... methods like runExperience, runFeature, etc. ...
}
  • The constructor simply stores the visitorId, any initial visitorProperties, and references to all the helper managers it needs to delegate tasks to.

2. The runExperience Method (context.ts)

This is the method called when you write visitorContext.runExperience(...).

// File: packages/js-sdk/src/context.ts (Simplified)

// Inside the Context class:
  runExperience(
    experienceKey: string,
    attributes?: BucketingAttributes // Optional extra attributes
  ): BucketedVariation | RuleError | BucketingError {
    if (!this._visitorId) {
      // Handle error: Visitor ID is missing
      return; // Simplified error handling
    }

    // Combine stored properties with any temporary ones from 'attributes'
    const visitorProperties = this.getVisitorProperties(
      attributes?.visitorProperties
    ); // Uses _visitorProperties and DataManager

    // === The Core Delegation ===
    // Ask ExperienceManager to figure out the variation
    const bucketedVariation = this._experienceManager.selectVariation(
      this._visitorId,
      experienceKey,
      {
        visitorProperties: visitorProperties, // Pass combined properties
        locationProperties: attributes?.locationProperties, // Pass location info if any
        // ... other options
      }
    );

    // Check if the result is an error (RuleError, BucketingError)
    if (/* result is an error */) {
      return bucketedVariation; // Return the error
    }

    // If successful, fire an event (optional step)
    if (bucketedVariation) {
      this._eventManager.fire(
        SystemEvents.BUCKETING, // Type of event
        { // Event details
          visitorId: this._visitorId,
          experienceKey: experienceKey,
          variationKey: (bucketedVariation as BucketedVariation).key
        }
      );
    }

    // Return the successful result (the variation object)
    return bucketedVariation as BucketedVariation;
  }

  // Helper method to combine properties (simplified)
  private getVisitorProperties(newAttributes?: Record<string, any>): Record<string, any> {
     const storedProperties = this._dataManager.getData(this._visitorId)?.segments || {};
     const initialProperties = this._visitorProperties || {};
     // Merge stored, initial, and new properties (simplified logic)
     return { ...storedProperties, ...initialProperties, ...(newAttributes || {}) };
  }
  • It first checks for the visitorId.
  • It calls getVisitorProperties which merges any properties passed now (attributes) with properties provided initially and potentially others stored by the DataManager.
  • The key step is this._experienceManager.selectVariation(...), where it delegates the actual decision-making.
  • It fires a BUCKETING event via the EventManager on success.
  • Finally, it returns the result from the ExperienceManager.

Conclusion

The Context object is your primary tool for interacting with the Convert SDK on behalf of a single visitor. It acts as a personal guide, holding the visitor's ID and properties, and providing methods like runExperience, runFeature, and trackConversion. Every action taken through a Context is tied specifically to that visitor, enabling personalized experiences and accurate tracking.

You now understand:

  1. Why Context is necessary for managing individual user sessions.
  2. What the Context object represents (a visitor's personal guide).
  3. How to use context.runExperience() to get the correct A/B test variation for a visitor.
  4. That Context delegates the complex decision-making logic to specialized managers like the ExperienceManager.

We saw that Context relies heavily on the ExperienceManager to actually choose the variation in runExperience. How does that manager work its magic? Let's explore that in the next chapter!

Ready to learn more? Head over to Chapter 3: ExperienceManager!

Clone this wiki locally