diff --git a/Readme.md b/Readme.md index 7217b28..3472ab0 100644 --- a/Readme.md +++ b/Readme.md @@ -36,6 +36,7 @@ this repo as the set of examples. - [Zendesk](./destinations/zendesk) - Create new Zendesk tickets triggered by events that you send - [Datadog](./destinations/datadog) - Sends a metric to datadog with high level message/event type as tags - [Optimizely](./destinations/optimizely) - Sends conversion metrix to optimizely. +- [Radar](./destinations/radar) - Send events to Radar for enrichment with location context ## Development diff --git a/destinations/radar/Readme.md b/destinations/radar/Readme.md new file mode 100644 index 0000000..d3e5661 --- /dev/null +++ b/destinations/radar/Readme.md @@ -0,0 +1,9 @@ +# Radar Custom Destination Function + +This function forwards events to Radar as a [`track` event](https://radar.com/documentation/api#track) for both anonymous and named users. + +The function requires coordinates to be sent up following the common fields spec. + +## Settings + +- `radarPublishableKey` (string): [Radar Publishable Key](https://radar.com/documentation/) \ No newline at end of file diff --git a/destinations/radar/handler.js b/destinations/radar/handler.js new file mode 100644 index 0000000..95487e3 --- /dev/null +++ b/destinations/radar/handler.js @@ -0,0 +1,155 @@ +// This example uses the Radar track API + +/** + * Handle track event + * @param {SegmentTrackEvent} event + * @param {FunctionSettings} settings + */ + async function onTrack(event, settings) { + const endpoint = 'https://api.radar.io/v1/track'; + let response; + + try { + if (hasRequiredIdentifiers(event) && hasRequiredLocationContext(event)) { + radarTrackPayload = createPayload(event); + setIfDefined(event,'anonymousId',radarTrackPayload,'metadata.segmentAnonymousId'); + setIfDefined(event, 'userId', radarTrackPayload, 'userId'); + setIfDefined(event, 'context.os.version', radarTrackPayload, 'deviceOS'); + setIfDefined(event,'context.device.manufacturer',radarTrackPayload,'deviceMake'); + setIfDefined(event,'context.device.model',radarTrackPayload,'deviceModel'); + response = await fetch(endpoint, { + method: 'POST', + headers: { + Authorization: `${settings.radarPublishableKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(radarTrackPayload) + }); + } else { + throw new InvalidEventPayload('Missing required attributes'); + } + } catch (error) { + // Retry on connection error + throw new RetryError(error.message); + } + + if (response.status >= 500 || response.status === 429) { + // Retry on 5xx (server errors) and 429s (rate limits) + throw new RetryError(`Failed with ${response.status}`); + } +} + +const setIfDefined = ( + sourceObject, + sourcePath, + destObject, + destPath, + defaultValue +) => { + let value = _.get(sourceObject, sourcePath); + if (value !== undefined) { + _.set(destObject, destPath, value); + } else if (defaultValue !== undefined) { + _.set(destObject, destPath, defaultValue); + } +}; + +/** + * Handle identify event + * @param {SegmentIdentifyEvent} event + * @param {FunctionSettings} settings + */ +async function onIdentify(event, settings) { + throw new EventNotSupported('identify is not supported'); +} + +/** + * Handle group event + * @param {SegmentGroupEvent} event + * @param {FunctionSettings} settings + */ +async function onGroup(event, settings) { + throw new EventNotSupported('group is not supported'); +} + +/** + * Handle page event + * @param {SegmentPageEvent} event + * @param {FunctionSettings} settings + */ +async function onPage(event, settings) { + throw new EventNotSupported('page is not supported'); +} + +/** + * Handle screen event + * @param {SegmentScreenEvent} event + * @param {FunctionSettings} settings + */ +async function onScreen(event, settings) { + throw new EventNotSupported('screen is not supported'); +} + +/** + * Handle alias event + * @param {SegmentAliasEvent} event + * @param {FunctionSettings} settings + */ +async function onAlias(event, settings) { + throw new EventNotSupported('alias is not supported'); +} + +/** + * Handle delete event + * @param {SegmentDeleteEvent} event + * @param {FunctionSettings} settings + */ +async function onDelete(event, settings) { + throw new EventNotSupported('delete is not supported'); +} + +function hasRequiredLocationContext(event) { + if (event.context?.location?.latitude && event.context?.location?.longitude) { + return true; + } else { + return false; + } +} + +function hasRequiredIdentifiers(event) { + if (event.context?.device?.id || event.anonymousId) { + return true; + } else { + return false; + } +} + +function deviceTypeTransform(deviceType) { + if (deviceType) { + switch (deviceType.toLowerCase()) { + case 'ios': + return 'iOS'; + case 'android': + return 'Android'; + default: + return 'Web'; + } + } else { + return 'Web'; + } +} + +const createPayload = e => { + var date = new Date(); + return { + deviceId: e.context.device?.id ?? e.anonymousId, + latitude: e.context.location.latitude, + longitude: e.context.location.longitude, + accuracy: e.context.location.accuracy ?? 65, + deviceType: deviceTypeTransform(e.context.device?.type), + foreground: true, + stopped: true, + metadata: {}, + updatedAt: e.sentAt ?? date.toISOString() + }; +};