Plugin Framework
The plugin framework provides the capability to include custom enhancements or modifications within Heap's event pipeline, empowering you to execute specific enrichment, redaction, or other modifications that would not otherwise be handled by Heap's existing APIs.
The new SDK is structured into three distinct layers: Adapters, Transformers, and Networkers. You can add custom functionality within the Adapters and Transformers layers, as described below.
Plugin Persistence
Adapters and Transformers plugins are stored in memory, so these APIs will need to be called on each page where you want to use the plugin framework.
Adapters
Adapters are modules executed during initialization of the web SDK, acting as the entry point for essential functionalities within their respective contexts. They play a crucial role in initializing features to capture and support relevant client-side actions.
These modules are executed prior to events being sent to Heap and have the ability to block our event processing pipeline until execution is complete, or 5 seconds has passed. As such, the primary use case for custom adapters is to perform asynchronous setup calls that your events may be dependent on.
addAdapter
Adds a custom adapter to the Heap Web SDK registry
var adapterName = 'myCustomAdapter';
var adapterFn = function() {
return executeAdapterFn()
.then(function() {
heap.markAdapterAsResolved(adapterName);
});
};
var adapter = {
name: adapterName,
fn: adapterFn
};
var config = {
isBlocking: true,
blockDurationInMs: 3500
};
heap.addAdapter(adapter, config);
const adapterName = 'myCustomAdapter';
const adapterFn = async () => {
await executeAdapterFn();
heap.markAdapterAsResolved(adapterName);
}
const adapter: { name: string; fn: Function } = {
name: adapterName,
fn: adapterFn,
}
const config: { isBlocking?: boolean; blockDurationInMs?: number } = {
isBlocking: true,
blockDurationInMs: 3500,
}
heap.addAdapter(adapter, config);
Arguments
-
(required) adapter
: adapter object containingname
andfn
to be executed at heap initialization timeAdapter Property Type Description name
string
Required.
Unique identifier for the custom adapterfn
Function
Required.
Adapter function to be executed at heap initialization time -
(optional) config
: configuration object for the corresponding adapterConfig Property Type Description Default isBlocking
boolean
Optional.
Sets whether event processing should be blocked until adapter has resolved, or 5000 milliseconds has passed.false
blockDurationInMs
number
Optional.
Sets the duration to block the queue. Max duration is 5000 milliseconds.5000
markAdapterAsResolved
Flags the adapter with the provided name as resolved. Should be used within the adapter function to unblock the Heap event queue once the adapter's asynchronous blocking step is complete.
Default Adapter Resolution
If the adapter takes longer than 5000 milliseconds to call
markAdapterAsResolved
, the Heap SDK will automatically mark the adapter as resolved.If the adapter is non-blocking, then this API is unnecessary.
var adapterName = 'myCustomAdapter';
var adapterFn = function() {
return executeAdapterFn()
.then(function() {
heap.markAdapterAsResolved(adapterName);
});
};
var adapter = {
name: adapterName,
fn: adapterFn
};
const adapterName = 'myCustomAdapter';
const adapterFn = async () => {
await executeAdapterFn();
heap.markAdapterAsResolved(adapterName);
}
const adapter: { name: string; fn: Function } = {
name: adapterName,
fn: adapterFn,
}
Arguments
(required) adapterName
: name of the custom adapter to resolve
Transformers
Using Transformers
It is important to understand Heap's event schema to use transformers correctly. Read more about the structure of our events in Plugin Framework - Heap Event Schema.
Because Heap events have a defined schema, we recommend using custom transformers primarily for data redaction and transformation. For adding additional event properties, you should default to using our
addEventProperties
andaddUserProperties
APIs detailed in API Reference.
Transformers are isolated services responsible for the enrichment, transformation, and redaction of heap event messages. A transformer pipeline is a predefined sequence of steps that each transformer follows. The pipeline is identified based on the type of event.
Transformer Pipelines
Message Type | Pipeline |
---|---|
Track | general |
User | metadata |
Session | metadata |
Pageview | metadata |
addTransformerFn
Adds custom transformer to the registry. By default, your transformer will be added to the general
pipeline, unless otherwise specified.
var name = 'redactTrackMessageTransformer';
var fn = function(messages) {
return redactTrackMessages(messages);
};
var pipeline = 'general';
heap.addTransformerFn(name, fn, pipeline);
const name: string = 'redactTrackMessageTransformer';
const fn = (messages: Array<EventMessage>): Array<EventMessage> => {
return redactTrackMessages(messages);
};
const pipeline: 'general' | 'metadata' = 'general';
heap.addTransformerFn(name, fn, pipeline);
Arguments
(required) name
: name of the custom transformer(required) fn
: custom transformation function to modify the Heap event message.- takes in an array of Heap event messages, and returns an array of the transformed event messages
(optional) pipeline
:general
|metadata
- determines which type of message to apply transformations to, defaulting togeneral
pipeline (track events)
removeTransformer
Removes specified transformer from the registry.
heap.removeTransformer('myCustomTransformer');
heap.removeTransformer('myCustomTransformer');
Arguments
(required) name
: name of transformer (string)
resetTransformers
Removes all custom transformers from the registry.
heap.resetTransformers();
heap.resetTransformers();
Examples
Adding event properties from an asynchronous source
If you want to add event properties that are dependent on an asynchronous call to another library or data warehouse, adapters will allow you to pause the event queue while you fetch that data. This ensures that all events will contain your properties of interest.
var adapterName = 'asyncSourcePropertiesAdapter';
var addAdditionalPropertiesFromAsyncSource = function() {
return fetchProperties()
.then(function(properties) {
heap.addEventProperties(properties);
heap.markAdapterAsResolved(adapterName);
});
};
var adapter = { name: adapterName, fn: addAdditionalPropertiesFromAsyncSource };
var config = { isBlocking: true, blockDurationInMs: 5000 };
heap.addAdapter(adapter, config);
const adapterName = 'asyncSourcePropertiesAdapter';
const addAdditionalPropertiesFromAsyncSource = async () => {
const properties = await fetchProperties();
heap.addEventProperties(properties);
heap.markAdapterAsResolved(adapterName);
}
const adapter = { name: adapterName, fn: addAdditionalPropertiesFromAsyncSource };
const config = { isBlocking: true, blockDurationInMs: 5000 };
heap.addAdapter(adapter, config);
Redacting URL query params from all events
To remove query params for all events, we will need to transform the metadata events to redact query params. Because events inherit properties from the pageview and session events, redaction of the page information on those events will also be applied to associated track events.
var transformerName = 'redactUrlQueryFromAllEventsTransformer';
var transformerFn = function(messages) {
var redactUrlQuery = function(message) {
try {
// Event is a pageview event
if (message.type === 'meta_pageview') {
message.data.pageview_info.url.query = '****';
}
// Event is a session event
if (message.type === 'meta_session') {
message.data.initial_pageview_info.url.query = '****';
}
// Event is a user event
if (message.type === 'meta_user') {
message.data.initial_session_info.initial_pageview_info.url.query = '****';
}
return message;
} catch (e) {
return message;
}
};
return messages.map(redactUrlQuery);
};
// Using metadata pipeline since we are modifying user, session, and pageview messages
var pipeline = 'metadata';
heap.addTransformerFn(transformerName, transformerFn, pipeline);
const transformerName = 'redactUrlQueryFromAllEventsTransformer';
const transformerFn = (messages: Array<EventMessage>): Array<EventMessage> => {
const redactUrlQuery = (message: EventMessage) => {
try {
// Event is a pageview event
if (message.type === 'meta_pageview') {
message.data.pageview_info.url.query = '****';
}
// Event is a session event
if (message.type === 'meta_session') {
message.data.initial_pageview_info.url.query = '****';
}
// Event is a user event
if (message.type === 'meta_user') {
message.data.initial_session_info.initial_pageview_info.url.query = '****';
}
return message;
} catch (e) {
return message;
}
}
return messages.map(redactUrlQuery);
}
// Using metadata pipeline since we are modifying user, session, and pageview messages
const pipeline = 'metadata';
heap.addTransformerFn(transformerName, transformerFn, pipeline);
Redacting target text on interaction events for a subset of users
Assume you want to redact target text on interaction track events for users that reside in Europe. To do so, we would use adapters to call some asynchronous API to determine whether that client is from Europe and set the client state accordingly. Transformers would then consume from that client state to determine whether to redact target text.
// Create adapter to set state on whether user is from Europe
var adapterName = 'isUserInEuropeAdapter';
var isUserInEurope = function () {
return fetchIsUserInEurope()
.then(function (isUserInEurope) {
window.isUserInEurope = isUserInEurope;
heap.markAdapterAsResolved(adapterName);
});
};
// Defaults to 5 seconds of blocking
var config = { isBlocking: true };
heap.addAdapter({ name: adapterName, fn: isUserInEurope }, config);
// Create transformer to redact target text from users in Europe
var transformerName = 'redactTargetTextForUsersInEuropeTransformer';
var transformerFn = function (messages) {
if (!window.isUserInEurope) {
return messages;
}
var redactTargetTextForUsersInEurope = function (message) {
try {
// Check if message is an interaction event before redacting
if (message.data.event.interaction) {
message.data.event.interaction.nodes[0].targetText = '****';
}
return message;
} catch (e) {
return message;
}
};
return messages.map(redactTargetTextForUsersInEurope);
};
// Using default general pipeline since we are modifying track events
heap.addTransformerFn(transformerName, transformerFn);
// Create adapter to set state on whether user is from Europe
const adapterName = 'isUserInEuropeAdapter';
const isUserInEurope = async () => {
const isUserInEurope = await fetchIsUserInEurope();
window.isUserInEurope = isUserInEurope;
heap.markAdapterAsResolved(adapterName);
}
// Defaults to 5 seconds of blocking
const config = { isBlocking: true };
heap.addAdapter({ name: adapterName, fn: isUserInEurope }, config);
// Create transformer to redact target text from users in Europe
const transformerName = 'redactTargetTextForUsersInEuropeTransformer';
const transformerFn = (messages: Array<EventMessage>): Array<EventMessage> => {
if (!window.isUserInEurope) {
return messages;
}
const redactTargetTextForUsersInEurope = (message: EventMessage) => {
try {
// Check if message is an interaction event before redactiong
if (message.data.event.interaction) {
message.data.event.interaction.nodes[0].targetText = '****';
}
return message;
} catch (e) {
return message;
}
}
return messages.map(redactTargetTextForUsersInEurope);
}
// Using default general pipeline since we are modifying track events
heap.addTransformerFn(transformerName, transformerFn);
Updated 4 months ago