diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d08eb82..e6a7bab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,3 +22,4 @@ jobs: - run: npm run lint --if-present - run: npm run typecheck --if-present - run: npm run bundle --if-present + - run: npm run test --if-present diff --git a/.npmignore b/.npmignore index e831038..c185877 100644 --- a/.npmignore +++ b/.npmignore @@ -1 +1,8 @@ -src \ No newline at end of file +src +.all-contributorsrc +.eslintrc.js +.github/workflows +.prettierrc +esbuild.js +tsconfig.build.json +tsconfig.json \ No newline at end of file diff --git a/assets/pinterest.png b/assets/pinterest.png index 7ade056..8c55527 100644 Binary files a/assets/pinterest.png and b/assets/pinterest.png differ diff --git a/src/index.test.ts b/src/index.test.ts index d42172c..acd4bd3 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,13 +1,12 @@ import { MCEvent } from '@managed-components/types' import { getEventData } from '.' describe('getEventData', () => { - it('generates a compliant request payload', () => { + it('generates a compliant request payload', async () => { const expectedResult = { event_name: 'lead', action_source: 'brie', - event_time: 1692890759111, event_id: 'mytest133r4524', - event_source_url: 'http://localhost:1337/', + event_source_url: undefined, opt_out: true, partner_name: 'Me', app_id: 'wow1234', @@ -18,15 +17,26 @@ describe('getEventData', () => { os_version: '10.15.7', wifi: true, language: 'en', - user_properties: { + user_data: { client_ip_address: '::1', client_user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36', + click_id: '1531262624', em: 'f660ab912ec121d1b1e928a0bb4bc61b15f5ad44d5efdc4e1c92a25e99b8e44a', hashed_maids: '5e8a5ed735739e9281b7324b75bc96b37784f75cad8ab1ccffb61f87a098c06a', ph: 'a231e771cb4993085107fb4a6c067b850466eeccaa3c58d446ae48a5ee83b7a9', - ge: '5856e02f6fd0853c17f99778b1663b833e0aaac36f4ab09de76e5518db518a1e', + ge: '0d248e82c62c9386878327d491c762a002152d42ab2c391a31c44d9f62675ddf', + bd: '01203006fa1afc3a4647e6c77c1bdf09450a65ad9be9b46523f25482c6017873', + ln: 'bb359aed64ef3d28926979e36f6ee43a8880c44dad1f3079a8b74c0aefb2687f', + fn: '9ae4bc0e32db0e3484cd398459d20f9b4f79cce36667428181bf037131a3c987', + ct: '8374c43b01b9243ca119c7495391cbe6700cec66c4453b45d7790c82849bac37', + st: 'd1121c939cdad7e34c57c1991132059e7c94ae002e61849cc83dce98d2acf6c6', + zp: 'b281bc2c616cb3c3a097215fdc9397ae87e6e06b156cc34e656be7a1a9ce8839', + country: + '3e049d78d958038ac6bd5dcf038075cc73362b4956aaf7308c3a69c8eca76297', + external_id: + 'da379b7950f9b50f9fd247a8700038393cb4dfec33cb92ca460a250135db509b', }, custom_data: { search_string: 'string', @@ -76,8 +86,8 @@ describe('getEventData', () => { type: 'event', } as unknown as MCEvent - const result = getEventData(mockMCEvent, false) - + const result = await getEventData(mockMCEvent, false) + delete (result as Record).event_time expect(result).toEqual(expectedResult) }) }) diff --git a/src/index.ts b/src/index.ts index 806cfbf..3fb9b0d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,7 @@ import { export const getEventData = async ( event: MCEvent, pageview: boolean, - ecomPayload?: object + ecomPayload?: Record ) => { const { client } = event const parsedUserAgent = UAParser(client.userAgent) diff --git a/src/utils.ts b/src/utils.ts index f97dc80..88431b5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,6 @@ import { MCEvent } from '@managed-components/types' -export const checkEventName = (event: MCEvent) => { +export function checkEventName(event: MCEvent): MCEvent | undefined { const allowedEvents = [ 'custom', 'lead', @@ -34,9 +34,8 @@ export async function sha256(data: string): Promise { } export async function hashPayload( - payload: any -): Promise> { - const hashedData: Record = {} + payload: Record +): Promise> { const fieldsToHash = [ 'em', 'hashed_maids', @@ -52,18 +51,23 @@ export async function hashPayload( 'external_id', ] - for (const field of fieldsToHash) { - if (payload[field]) { - const hashedValue = await sha256(payload[field].toLowerCase()) - if (hashedValue) { - hashedData[field] = [hashedValue] - } + const results: Record = {} + const promises: Promise[] = [] + + for (const key in payload) { + if (fieldsToHash.includes(key)) { + const hashPromise = sha256(payload[key].toString()).then(hash => { + results[key] = hash + }) + promises.push(hashPromise) } } - return hashedData + + await Promise.all(promises) + return results } -export async function pushEventData(payload: any) { +export async function pushEventData(payload: Record) { const eventDataKeys = [ 'partner_name', 'app_id', @@ -71,7 +75,7 @@ export async function pushEventData(payload: any) { 'app_version', 'wifi', ] - const eventDataResult: { [key: string]: any } = {} + const eventDataResult: { [key: string]: unknown } = {} for (const key of eventDataKeys) { if (Object.prototype.hasOwnProperty.call(payload, key)) { @@ -81,7 +85,7 @@ export async function pushEventData(payload: any) { return eventDataResult } -export async function pushCustomData(payload: any) { +export async function pushCustomData(payload: Record) { const customDataKeys = [ 'search_string', 'opt_out_type', @@ -96,7 +100,7 @@ export async function pushCustomData(payload: any) { 'contents', 'num_items', ] - const customDataResult: { [key: string]: any } = {} + const customDataResult: { [key: string]: unknown } = {} for (const key of customDataKeys) { if (Object.prototype.hasOwnProperty.call(payload, key)) { switch (key) { @@ -105,7 +109,11 @@ export async function pushCustomData(payload: any) { customDataResult[key] = String(payload[key]) break case 'num_items': - customDataResult[key] = parseInt(payload[key], 10) // Convert to integer + if (typeof payload[key] === 'string') { + customDataResult[key] = parseInt(payload[key] as string, 10) // Convert to integer in case sent as string + } else { + customDataResult[key] = payload[key] + } break case 'content_ids': customDataResult[key] = [payload[key]] @@ -129,44 +137,45 @@ export async function getEcommercePayload(event: MCEvent) { const { type, name } = event let { payload } = event payload = { ...payload, ...payload.ecommerce } - if (type === 'ecommerce') { - const mapEventName = (name: string | undefined) => { - if (name === 'Product Added') { - // Fixed the assignment (=) to comparison (===) - return 'add_to_cart' - } else if (name === 'Order Completed') { - return 'checkout' - } - } - - payload.name = mapEventName(name) - if (Array.isArray(payload.products)) { - payload.content_ids = payload.products - .map((product: any) => product.product_id) - .join() - payload.content_name = payload.products - .map((product: any) => product.name) - .join() - payload.content_category = payload.products - .map((product: any) => product.category) - .join() - payload.content_brand = payload.products - .map((product: any) => product.brand) - .join() - payload.contents = payload.products.map((product: any) => ({ - id: product.product_id, - item_price: product.price.toString(), - quantity: product.quantity, - })) - payload.num_items = - payload.quantity || - payload.products.reduce( - (sum: any, product: any) => sum + parseInt(product.quantity, 10), - 0 - ) + if (type !== 'ecommerce') { + return + } + const mapEventName = (name: string | undefined) => { + if (name === 'Product Added') { + // Fixed the assignment (=) to comparison (===) + return 'add_to_cart' + } else if (name === 'Order Completed') { + return 'checkout' } + } - payload.value = payload.revenue || payload.total || payload.value + payload.name = mapEventName(name) + if (Array.isArray(payload.products)) { + payload.content_ids = payload.products + .map((product: any) => product.product_id) + .join() + payload.content_name = payload.products + .map((product: any) => product.name) + .join() + payload.content_category = payload.products + .map((product: any) => product.category) + .join() + payload.content_brand = payload.products + .map((product: any) => product.brand) + .join() + payload.contents = payload.products.map((product: any) => ({ + id: product.product_id, + item_price: product.price.toString(), + quantity: product.quantity, + })) + payload.num_items = + payload.quantity || + payload.products.reduce( + (sum: any, product: any) => sum + parseInt(product.quantity, 10), + 0 + ) } + + payload.value = payload.revenue || payload.total || payload.value return payload }