@unifygtm/intent-client
Version:
JavaScript client for interacting with the Unify Intent API in the browser.
146 lines (131 loc) • 4.69 kB
text/typescript
import type { PageProperties, UserAgentDataType } from '../../types';
const EMAIL_REGEX = /^[A-Za-z0-9._+%-]+@[A-Za-z0-9.-]+[.][A-Za-z]+$/;
const TEST_CLIENT_PROPERTIES = ['identify', 'page'];
/**
* Helper function to check if a value is an instance of the global Unify
* Intent Client. The following checks are performend:
*
* 1. Is the value an object? The intent client will always be.
* 2. Is the value defined? For obvious reasons.
* 3. Is the value NOT an array? We temporarily stub the intent client as
* an array in the global context to queue method calls before the client loads.
* 4. Are the expected properties in the object? This is to protect against
* a bug where customers accidentally override the global variable we
* normally store the intent client at by setting the `id` of an element
* in the DOM equal to the same name. For example, creating a `<script>`
* tag with `id="unify"` will override `window.unify` to be the `<script>`
* HTML element.
*
* @param value - the value to check is an instance of the intent client
* @returns `true` if the value is an instance of the intent client, or else `false`
*/
export function isIntentClient(value: unknown) {
return (
!!value &&
typeof value === 'object' &&
!Array.isArray(value) &&
TEST_CLIENT_PROPERTIES.every((property) => property in value)
);
}
/**
* Gets a milliseconds since epoch for `minutes` in the future.
*
* @param minutes - number of minutes in the future
* @param fromTime - the relative time to calculate in the future from
* @returns the corresponding milliseconds since epoch
*/
export function getTimeForMinutesInFuture(
minutes: number,
fromTime?: Date,
): number {
return (fromTime?.getTime() ?? new Date().getTime()) + minutes * 60 * 1000;
}
/**
* Gets the properties of the current page used in intent logging.
*
* @param pathname - optional pathname to use instead of the current pathname
* when constructing page properties. This allows supporting logging
* a page other than the one the user is currently on.
* @returns a set of properties for the specified or current page
*/
export const getCurrentPageProperties = (
pathname?: string,
): PageProperties => ({
path: pathname ?? window.location.pathname,
query: parseUrlQueryParams(window.location.href),
referrer: document.referrer,
title: document.title,
url:
pathname !== undefined
? getLocationHrefWithCustomPath({ location: window.location, pathname })
: window.location.href,
});
/**
* This function will replace the pathname for a given `location.href`
* with the specified custom pathname. This is more complicated than
* a simple `replace` because the pathname can be simply "/" which can
* occur in the URL before the pathname (i.e. after the protocol).
*
* @param location - the location containing the `href` to replace the
* pathname for
* @param pathname - the custom pathname to replace the existing one with
* @returns the `location.href` with `pathname` replacing the existing pathname
*/
export function getLocationHrefWithCustomPath({
location,
pathname,
}: {
location: Location;
pathname: string;
}) {
const url = new URL(location.href);
url.pathname = pathname;
return url.toString();
}
/**
* Gets the current user agent data used in intent logging.
*/
export const getCurrentUserAgentData = (): UserAgentDataType => ({
userAgent: window.navigator.userAgent,
userAgentData:
typeof navigator.userAgentData !== 'undefined'
? navigator.userAgentData
: undefined,
});
/**
* Validates a user-entered email address.
*
* @param email - the email address to validate
* @returns the email address if valid, otherwise `undefined`
*/
export const validateEmail = (email: string): string | undefined => {
if (!EMAIL_REGEX.test(email)) {
return undefined;
}
return email;
};
/**
* Extract the key-value query parameters from a URL.
*
* @param url - URL to parse.
* @returns Object containing the query parameters as key-value pairs.
*/
export const parseUrlQueryParams = (url: string): Record<string, string> => {
const params = new URL(url).searchParams;
const queryParams: Record<string, string> = {};
for (const [key, value] of params.entries()) {
queryParams[key] = value;
}
return queryParams;
};
export function getDomainForUrl(url: string): string | null {
try {
const urlObj = new URL(url.startsWith('http') ? url : `https://${url}`);
return urlObj.hostname;
} catch {
return null;
}
}
export function getDomainForEmail(email: string): string | null {
return email.split('@').at(1) ?? null;
}