Shared SDK: The NinetailedApiClient

Work with events and types common to all Ninetailed implementations.

The Shared SDK exposes Ninetailed data objects, types, and methods applicable across runtimes. The Shared SDK allows you to create an API Client that facilitates constructing and sending Experience API events. It also handles retrying requests and request errors.

Elements of the shared SDK are composed to create the JavaScript SDK, which is then composed into Ninetailed's React-based SDKs.

Want to browse the code? Check out the shared SDK in our open source repository.

Instantiation

import { NinetailedApiClient, fetchImpl} from "@ninetailed/experience.js-shared"

const ninetailedApiClient = new NinetailedApiClient({ clientId, environment, url, fetchImpl, }: NinetailedApiClientOptions)

type NinetailedApiClientOptions = {
    clientId: string;
    environment?: string;
    url?: string;
    fetchImpl?: FetchImpl;
}

Profile Methods

A NinetailedAPIClient maps one function for each Experience API endpoint. upsertProfile is also provided to conveniently switch between create and update functions.

The returned response of each method is the same as that of the Experience API; a data structure indicating the complete representation of the profile(s) and the Ninetailed Experiences & variants that the Experience API has selected for the profile(s).

Request Options

All of profile methods accept request options in addition to the supplied events. These are used to control request timeout & retry behaviour, performance, localization, and location resolution.

type RequestOptions = {
    /**
     * A timeout after which a request will get cancelled
     */
    timeout?: number;
    /**
     * Return a profile as though events have been submitted without actually storing the profile state
     * Useful in ESR or SSR contexts
     */
    preflight?: boolean;
    /**
     * Determines the locale to which to localize location.city & location.country properties
     */
    locale?: string;
    /**
     * An IP address to override the IP lookup behaviour, if using also supplying "ip-enrichment" as an enabled feature
     * Used in ESR or SSR environments to proxy a client's IP instead of using the server's IP
     */
    ip?: string;
    /**
     * Sending requests as plain-text bypasses the need for a CORS preflight request, making for a faster request.
     */
    plainText?: boolean;
    /**
     * The maximum amount of retries for a request.
     * Only 503 errors will be retried. The Ninetailed API is aware of which requests are retryable and send a 503 error.
     *
     * @default 1
     */
    retries?: number;
    /**
     * The maximum amount of time in ms to wait between retries.
     * By default the retry will be sent immediately as the Ninetailed API is serverless and sends a 503 error if it could not recover a request internally.
     *
     * @default 0 (no delay)
     */
    minRetryTimeout?: number;
    /**
     * Activated features which the API should use for this request.
     */
    enabledFeatures?: Feature[];
};

/**
 * "location" = Attempt to resolve the location of the user based on the IP of the request and where it ingresses to the Experience API
 * "ip-enrichment" = Attempt to resolve firmographic data with a connected Albacross API Key. See Setup => Customer Data => Albacross
 */
type Feature = "location" | "ip-enrichment"

Event Building Functions

Each of the profile methods above accepts an array of events. Event building helper functions facilitate generating the required payload for page, track, and identify events. These builder methods take care of parsing the supplied ctx.url and populating context properties on events commonly used by Audience rules, including context.page and context.campaign.

function buildPageEvent(data: {
    messageId: string; // A UUID
    timestamp: number; // Typically assigned as Date.now()
    ctx: {
        url: string; // Supply the whole URL, including the protocol, domain, path, and any query parameters
        referrer: string;
        locale: string;
        userAgent: string;
        document?: {
            title: string;
        } | undefined;
    }
    // Optionally supply an object to merge with API-resolved location
    location?: Geolocation;
    properties: Record<string, any>; // JSON
})

function buildTrackEvent(data: {
    messageId: string; // A UUID
    timestamp: number; // Typically assigned as Date.now()
    ctx: {
        url: string; // Supply the whole URL, including the protocol, domain, path, and any query parameters
        referrer: string;
        locale: string;
        userAgent: string;
        document?: {
            title: string;
        } | undefined;
    }
    // Optionally supply an object to merge with API-resolved location
    location?: Geolocation;
    event: string; // the name of the event
    properties: Record<string, any>; // JSON
})

function buildIdentifyEvent(data: {
    messageId: string; // A UUID
    timestamp: number; // Typically assigned as Date.now()
    ctx: {
        url: string; // Supply the whole URL, including the protocol, domain, path, and any query parameters
        referrer: string;
        locale: string;
        userAgent: string;
        document?: {
            title: string;
        } | undefined;
    }
    // Optionally supply an object to merge with API-resolved location
    location?: Geolocation;
    userId: string; // An alias. If you don't want to set one, use an empty string ""
    traits: Record<string, any>; // JSON
})

type Geolocation = {
      coordinates?: {
        latitude: number;
        longitude: number;
      },
      city?: string; // Use proper capitalization of the city name
      postalCode?: string;
      region?: string;
      regionCode?: string; // ISO 3166-2
      country?: string;
      countryCode?: string; // ISO 3166-2
      continent?: string;
      timezone?: string;
}

Example Shared SDK Usage

This is a short example of using the buildPageEvent helper to create a well-formatted event of type page and using it to upsert a profile.

Middleware Example
import {buildPageEvent, buildTrackEvent, buildIdentifyEvent, NinetailedApiClient, NINETAILED_ANONYMOUS_ID_COOKIE } from "@ninetailed/experience.js-shared"

import { v4 as uuidv4 } from 'uuid';

const clientID = "YOUR_NINETAILED_CLIENT_ID" || undefined;
const apiClient = new NinetailedApiClient({ clientId });

const middleware = (req: Request) => {
  buildPageEvent({
    // Hardcoded strings are for example purposes
    // You'd populate these from the incoming Request object
    ctx: { 
      url: 'https://www.example.com/path/?query=test'
      locale: 'en-US',
      referrer: 'https://www.google.com/',
      userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
    }
    messageId: uuidv4(),
    timestamp: Date.now(),
    // Resolve your original request geolocation info and pass it along
    location: {
      city: request.geo?.city,
      region: request.geo?.region,
      country: request.geo?.country,
      continent: requqest.geo?.continent,
    } 
    properties: {},
  });
  
  const apiResponse = await apiClient.upsertProfile(
    {
      // Use cookies to store and read a Ninetailed profile ID
      // `upsertProfile` takes care of case where this isn't present
      profileId: req.cookies.get(NINETAILED_ANONYMOUS_ID_COOKIE)?.value 
      events: [{ pageEvent }],
    },
    { ip: req.ip } // Pass original IP address
  );
  
  // If no profile ID cookie present, be sure to write it in a response
  return apiResponse;
}

Despite the brevity of this example, this is all it takes to get started working with Ninetailed in edge functions and middleware.

Last updated