-
Notifications
You must be signed in to change notification settings - Fork 307
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
343 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
import { FullContext, JitsuFunction } from "@jitsu/protocols/functions"; | ||
import { RetryError } from "@jitsu/functions-lib"; | ||
import type { AnalyticsServerEvent } from "@jitsu/protocols/analytics"; | ||
import { BrazeCredentials } from "../meta"; | ||
import { eventTimeSafeMs } from "./lib"; | ||
import omit from "lodash/omit"; | ||
import { pick } from "lodash"; | ||
import { identify } from "../../__tests__/lib/datalayout-test-data"; | ||
|
||
const endpoints = { | ||
"US-01 : dashboard-01.braze.com": "https://rest.iad-01.braze.com", | ||
"US-02 : dashboard-02.braze.com": "https://rest.iad-02.braze.com", | ||
"US-03 : dashboard-03.braze.com": "https://rest.iad-03.braze.com", | ||
"US-04 : dashboard-04.braze.com": "https://rest.iad-04.braze.com", | ||
"US-05 : dashboard-05.braze.com": "https://rest.iad-05.braze.com", | ||
"US-06 : dashboard-06.braze.com": "https://rest.iad-06.braze.com", | ||
"US-07 : dashboard-07.braze.com": "https://rest.iad-07.braze.com", | ||
"US-08 : dashboard-08.braze.com": "https://rest.iad-08.braze.com", | ||
"US-09 : dashboard-09.braze.com": "https://rest.iad-09.braze.com", | ||
"EU-01 : dashboard-01.braze.eu": "https://rest.fra-01.braze.eu", | ||
"EU-02 : dashboard-02.braze.eu": "https://rest.fra-02.braze.eu", | ||
}; | ||
export type HttpRequest = { | ||
method?: string; | ||
url: string; | ||
payload?: any; | ||
headers?: Record<string, string>; | ||
}; | ||
|
||
function toBrazeGender(gender: string | null | undefined): string | null | undefined { | ||
if (!gender) { | ||
return gender; | ||
} | ||
|
||
const genders: Record<string, string[]> = { | ||
M: ["man", "male", "m"], | ||
F: ["woman", "female", "w", "f"], | ||
O: ["other", "o"], | ||
N: ["not applicable", "n"], | ||
P: ["prefer not to say", "p"], | ||
}; | ||
|
||
const brazeGender = Object.keys(genders).find(key => genders[key].includes(gender.toLowerCase())); | ||
return brazeGender || gender; | ||
} | ||
|
||
function getAnonymousIdAlias(event: AnalyticsServerEvent, ctx: FullContext<BrazeCredentials>) { | ||
if (ctx.props.useJitsuAnonymousIdAlias && event.anonymousId) { | ||
return { | ||
alias_name: event.anonymousId, | ||
alias_label: "anonymous_id", | ||
}; | ||
} | ||
} | ||
|
||
function getIdPart(event: AnalyticsServerEvent, ctx: FullContext<BrazeCredentials>) { | ||
let idPart = {} as any; | ||
const traits = event.traits || event.context?.traits || {}; | ||
const user_alias = traits.user_alias || event.properties?.user_alias || getAnonymousIdAlias(event, ctx); | ||
const braze_id = traits.braze_id || event.properties?.braze_id; | ||
if (event.userId) { | ||
idPart.external_id = event.userId; | ||
} else if (user_alias) { | ||
idPart.user_alias = user_alias; | ||
} else if (braze_id) { | ||
idPart.braze_id = braze_id; | ||
} | ||
idPart.email = traits.email; | ||
idPart.phone = traits.phone; | ||
if (Object.keys(idPart).length === 0) { | ||
throw new Error('One of "external_id", "user_alias", "braze_id", "email" or "phone" is required.'); | ||
} | ||
return idPart; | ||
} | ||
|
||
function trackEvent(event: AnalyticsServerEvent, ctx: FullContext<BrazeCredentials>): any { | ||
return { | ||
events: [ | ||
{ | ||
...getIdPart(event, ctx), | ||
app_id: ctx.props.appId, | ||
name: event.event, | ||
time: new Date(eventTimeSafeMs(event)).toISOString(), | ||
properties: event.properties, | ||
_update_existing_only: false, | ||
}, | ||
], | ||
}; | ||
} | ||
|
||
function trackPurchase(event: AnalyticsServerEvent, ctx: FullContext<BrazeCredentials>): any { | ||
const products = event.properties?.products as any[]; | ||
if (!products || !products.length) { | ||
return; | ||
} | ||
const reservedKeys = ["product_id", "currency", "price", "quantity"]; | ||
const event_properties = omit(event.properties, ["products"]); | ||
const base = { | ||
...getIdPart(event, ctx), | ||
app_id: ctx.props.appId, | ||
time: new Date(eventTimeSafeMs(event)).toISOString(), | ||
_update_existing_only: false, | ||
}; | ||
return { | ||
purchases: products.map(product => ({ | ||
...base, | ||
product_id: product.product_id, | ||
currency: product.currency ?? "USD", | ||
price: product.price, | ||
quantity: product.quantity, | ||
properties: { | ||
...omit(product, reservedKeys), | ||
...event_properties, | ||
}, | ||
})), | ||
}; | ||
} | ||
|
||
function updateUserProfile(event: AnalyticsServerEvent, ctx: FullContext<BrazeCredentials>): any { | ||
const geo = ctx.geo || ({} as any); | ||
const traits = event.traits || ({} as any); | ||
const knownProps = [ | ||
"country", | ||
"current_location", | ||
"date_of_first_session", | ||
"date_of_last_session", | ||
"dob", | ||
"email", | ||
"email_subscribe", | ||
"email_open_tracking_disabled", | ||
"email_click_tracking_disabled", | ||
"facebook", | ||
"first_name", | ||
"home_city", | ||
"image_url", | ||
"language", | ||
"last_name", | ||
"marked_email_as_spam_at", | ||
"phone", | ||
"push_subscribe", | ||
"push_tokens", | ||
"time_zone", | ||
"twitter", | ||
"subscription_groups", | ||
]; | ||
return { | ||
attributes: [ | ||
{ | ||
...getIdPart(event, ctx), | ||
country: ctx.geo?.country?.name, | ||
current_location: | ||
geo.location?.latitude && geo.location?.longitude | ||
? { | ||
latitude: geo.location?.latitude, | ||
longitude: geo.location?.longitude, | ||
} | ||
: undefined, | ||
first_name: traits.firstName, | ||
last_name: traits.lastName, | ||
home_city: traits.address?.city, | ||
image_url: traits.avatar, | ||
time_zone: geo.location?.timezone, | ||
gender: toBrazeGender(traits.gender), | ||
...pick(traits, knownProps), | ||
custom_attributes: omit(traits, knownProps), | ||
_update_existing_only: false, | ||
}, | ||
], | ||
}; | ||
} | ||
|
||
function identifyUser(event: AnalyticsServerEvent, ctx: FullContext<BrazeCredentials>): any { | ||
const external_id = event.userId; | ||
const user_alias = event.traits?.user_alias || getAnonymousIdAlias(event, ctx); | ||
if (!external_id || !user_alias) { | ||
return; | ||
} | ||
return { | ||
aliases_to_identify: [ | ||
{ | ||
external_id, | ||
user_alias, | ||
}, | ||
], | ||
merge_behavior: "merge", | ||
}; | ||
} | ||
|
||
const BrazeDestination: JitsuFunction<AnalyticsServerEvent, BrazeCredentials> = async (event, ctx) => { | ||
const endpoint = endpoints[ctx.props.endpoint]; | ||
if (!endpoint) { | ||
throw new Error(`Unknown endpoint ${ctx.props.endpoint}`); | ||
} | ||
try { | ||
let httpRequests: HttpRequest[] = []; | ||
const headers = { | ||
"Content-type": "application/json", | ||
Authorization: `Bearer ${ctx.props.apiKey}`, | ||
}; | ||
const url = `${endpoint}/users/track`; | ||
try { | ||
if (event.type === "identify") { | ||
httpRequests.push({ | ||
url, | ||
payload: updateUserProfile(event, ctx), | ||
headers, | ||
}); | ||
const identify = identifyUser(event, ctx); | ||
if (identify) { | ||
httpRequests.push({ | ||
url: `${endpoint}/users/identify`, | ||
payload: identify, | ||
headers, | ||
}); | ||
} | ||
} else if (event.type === "track" && event.event != "Order Completed") { | ||
httpRequests.push({ | ||
url, | ||
payload: trackEvent(event, ctx), | ||
headers, | ||
}); | ||
} else if (event.type === "track" && event.event === "Order Completed") { | ||
httpRequests.push({ | ||
url, | ||
payload: trackPurchase(event, ctx), | ||
headers, | ||
}); | ||
} else if ((event.type === "page" || event.type === "screen") && ctx.props.sendPageEvents) { | ||
const track = { ...event }; | ||
track.event = event.type; | ||
const props = { ...event.properties }; | ||
if (event.name) { | ||
props[`${event.type}_name`] = event.name; | ||
} | ||
if (event.category) { | ||
props[`${event.type}_category`] = event.category; | ||
} | ||
track.properties = props; | ||
httpRequests.push({ | ||
url, | ||
payload: trackEvent(track, ctx), | ||
headers, | ||
}); | ||
} | ||
} catch (e: any) { | ||
ctx.log.error(e); | ||
return false; | ||
} | ||
|
||
for (const httpRequest of httpRequests) { | ||
if (httpRequest.payload) { | ||
const method = httpRequest.method || "POST"; | ||
const result = await ctx.fetch(httpRequest.url, { | ||
method, | ||
headers: httpRequest.headers, | ||
...(httpRequest.payload ? { body: JSON.stringify(httpRequest.payload) } : {}), | ||
}); | ||
if (result.status !== 200 && result.status !== 201) { | ||
throw new Error( | ||
`Braze ${method} ${httpRequest.url}:${ | ||
httpRequest.payload ? `${JSON.stringify(httpRequest.payload)} --> ` : "" | ||
}${result.status} ${await result.text()}` | ||
); | ||
} else { | ||
ctx.log.debug(`Braze ${method} ${httpRequest.url}: ${result.status} ${await result.text()}`); | ||
} | ||
} | ||
} | ||
} catch (e: any) { | ||
throw new RetryError(e.message); | ||
} | ||
}; | ||
|
||
BrazeDestination.displayName = "braze-destination"; | ||
|
||
BrazeDestination.description = "This functions covers jitsu events and sends them to Braze"; | ||
|
||
export default BrazeDestination; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export default ( | ||
<svg width="100%" height="100%" viewBox="0 0 256 256" version="1.1" preserveAspectRatio="xMidYMid"> | ||
<g> | ||
<path | ||
d="M115.665455,146.618182 C117.76,126.603636 130.327273,110.312727 144.989091,110.312727 C159.650909,110.312727 167.563636,126.603636 165.469091,146.618182 C163.374545,166.632727 151.272727,183.156364 137.541818,183.156364 C123.810909,183.156364 112.64,170.589091 115.665455,146.618182 M128,237.381818 C95.4203166,237.428273 64.5231317,222.918545 43.7527273,197.818182 C61.9054545,203.636364 82.1527273,198.516364 97.9781818,182.923636 C99.6494284,181.247143 101.204543,179.458761 102.632727,177.570909 C109.847273,193.163636 123.810909,201.076364 137.774545,201.076364 C161.512727,201.076364 181.527273,176.64 183.854545,146.385455 C186.181818,116.130909 169.192727,92.16 146.618182,92.16 C138.426818,92.046441 130.366511,94.2227238 123.345455,98.4436364 L130.327273,57.7163636 C131.023255,54.4563454 130.257956,51.0550185 128.232727,48.4072727 C126.138182,45.1490909 124.276364,43.7527273 121.483636,43.7527273 L88.9018182,43.7527273 C86.9755948,43.8531012 85.4024904,45.3278866 85.1781818,47.2436364 C84.3124445,51.0792109 83.6905928,54.9657841 83.3163636,58.88 C83.0836364,61.44 84.9454545,62.3709091 87.04,62.3709091 L110.778182,62.3709091 C107.054545,85.6436364 99.6072727,130.792727 97.5127273,142.196364 C95.3170885,152.990461 90.066367,162.926442 82.3854545,170.821818 C69.3527273,183.854545 45.3818182,186.88 31.1854545,172.683636 C26.2981818,167.563636 18.6181818,154.996364 18.6181818,128 C18.6181818,67.5900899 67.5900899,18.6181818 128,18.6181818 C188.40991,18.6181818 237.381818,67.5900899 237.381818,128 C237.381818,188.40991 188.40991,237.381818 128,237.381818 M128,0 C57.307552,-4.32866401e-15 8.65732801e-15,57.307552 0,128 C-8.65732801e-15,198.692448 57.307552,256 128,256 C198.692448,256 256,198.692448 256,128 C256,94.0522893 242.514324,61.4949884 218.509668,37.490332 C194.505012,13.4856756 161.947711,2.07869776e-15 128,0" | ||
fill="#212124" | ||
fill-rule="nonzero" | ||
></path> | ||
</g> | ||
</svg> | ||
); |