Shared SDK
Shared SDK: The NinetailedApiClient
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.
For more information about the code, see 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;
}
Parameter | Type Signature | Description |
---|---|---|
clientId | clientId: string | The organization ID/API key of a Ninetailed account. |
environment | environment?: string | The environment key of a Ninetailed account. Typically either main or development , depending on the environment in which you have configured your content source connections. your you have configured Defaults to main if unsupplied. |
url | url?: string | Specify the base URL of the Experience API. This should usually be left unspecified. Defaults to the production Experience API base URL of https://experience.ninetailed.co when unspecified. |
fetchImpl | fetchImpl?: FetchImpl | A NinetailedApiClient can be used in different JavaScript runtimes including the browser, Node.js, and edge workers. However, a implementation of the fetch() method available in the browser may not be available within all of those run times. This option allows you to provide a fetch() implementation of your own should the runtime not expose one automatically. |
Profile Methods
A NinetailedAPIClient
maps one function for each Experience API endpoint. upsertProfile
is also provided to conveniently switch between create and update functions.
Method | Type Signature | Description |
---|---|---|
upsertProfile | upsertProfile({ profileId, events }: { profileId?: string; events: Event[];} options?: RequestOptions): Promise<ProfileWithSelectedVariants> | If a profile ID is not supplied, calls updateProfile . Otherwise, calls createProfile . |
createProfile | createProfile({ events }: { events: Event[];}, options?: RequestOptions): Promise<ProfileWithSelectedVariants> | Creates a profile with the specified profile ID. Interfaces with the create profile endpoint. |
updateProfile | createProfile({ profileId, events }: { profileId: string, events: Event[];}, options?: RequestOptions): Promise<ProfileWithSelectedVariants> | Creates a profile at the specified profile ID. Interfaces with the update profile endpoint. |
getProfile | getProfile(id: string, options?: Omit<RequestOptions, 'preflight' | 'plaintext'> ): Promise<ProfileWithSelectedVariants> | Get the profile with the specified profile ID. Interfaces with the get profile endpoint. |
upsertManyProfiles | upsertManyProfiles({events} : {events: Event[], options?: { timeout?: number | undefined; enabledFeatures?: Feature[] | undefined; }}): Promise<ProfileWithSelectedVariants[]> | Supply an array of events to update many profiles at once. Responds with the representation of each upserted profile. Interfaces with the batch upsert profile endpoint. |
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 and 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.
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.