@walkeros/server-destination-bing 4.0.0-next-1777463920154 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var mod,__defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__hasOwnProp=Object.prototype.hasOwnProperty,index_exports={};((target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})})(index_exports,{DestinationBing:()=>types_exports,default:()=>index_default,destinationBing:()=>destinationBing}),module.exports=(mod=index_exports,((to,from,except,desc)=>{if(from&&"object"==typeof from||"function"==typeof from)for(let key of __getOwnPropNames(from))__hasOwnProp.call(to,key)||key===except||__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to})(__defProp({},"__esModule",{value:!0}),mod));var import_core=require("@walkeros/core"),import_server_core2=require("@walkeros/server-core"),import_server_core=require("@walkeros/server-core"),keysToHash=["em","ph"];async function hashUserData(userData,doNotHash=[]){const entries=await Promise.all(Object.entries(userData).map(async([key,value])=>{if(void 0===value)return[key,value];if(function(key,doNotHash=[]){return keysToHash.includes(key)&&!doNotHash.includes(key)}(key,doNotHash)){const normalized=function(key,value){return"em"===key?function(value){const trimmed=value.trim().toLowerCase(),atIndex=trimmed.lastIndexOf("@");if(atIndex<0)return trimmed;const user=trimmed.slice(0,atIndex),domain=trimmed.slice(atIndex+1),aliasIndex=user.indexOf("+");return`${(aliasIndex<0?user:user.slice(0,aliasIndex)).replace(/\./g,"")}@${domain}`}(value):"ph"===key?function(value){return value.trim()}(value):value}(key,String(value));return[key,await(0,import_server_core.getHashServer)(normalized)]}return[key,value]}));return Object.fromEntries(entries)}var types_exports={},destinationBing={type:"bing",config:{},async init({config:partialConfig,logger:logger}){const config=function(partialConfig={},logger){const settings=partialConfig.settings||{},{accessToken:accessToken,tagId:tagId}=settings;accessToken||logger.throw("Config settings accessToken missing"),tagId||logger.throw("Config settings tagId missing");const settingsConfig={url:"https://capi.uet.microsoft.com/v1/",dataProvider:"walkerOS",...settings,accessToken:accessToken,tagId:tagId};return{...partialConfig,settings:settingsConfig}}(partialConfig,logger);return config},push:async(event,context)=>await async function(event,{config:config,rule:rule,data:data,env:env,logger:logger}){const{accessToken:accessToken,tagId:tagId,url:url="https://capi.uet.microsoft.com/v1/",doNotHash:doNotHash,user_data:user_data,dataProvider:dataProvider="walkerOS",continueOnValidationError:continueOnValidationError}=config.settings,eventData=(0,import_core.isObject)(data)?data:{},configData=config.data?await(0,import_core.getMappingValue)(event,config.data):{},userDataCustom=user_data?await(0,import_core.getMappingValue)(event,{map:user_data}):{},ruleSettings=rule?.settings,eventType="pageLoad"===ruleSettings?.eventType?"pageLoad":"custom",eventMappedUserData=(0,import_core.isObject)(eventData.userData)?eventData.userData:{},userData={...(0,import_core.isObject)(configData)&&(0,import_core.isObject)(configData.userData)?configData.userData:{},...(0,import_core.isObject)(userDataCustom)?userDataCustom:{},...eventMappedUserData},hashedUserData=await hashUserData(userData,doNotHash),customData={};(0,import_core.isObject)(eventData.customData)&&Object.assign(customData,eventData.customData);for(const[key,value]of Object.entries(eventData))"userData"!==key&&"customData"!==key&&(customData[key]=value);const capiEvent={eventType:eventType,eventId:event.id,eventTime:Math.round((event.timestamp||Date.now())/1e3),adStorageConsent:"G",userData:hashedUserData};"custom"===eventType&&(capiEvent.eventName=rule?.name||event.name),event.source?.url&&(capiEvent.eventSourceUrl=event.source.url),Object.keys(customData).length>0&&(capiEvent.customData=customData);const body={data:[capiEvent],dataProvider:dataProvider};void 0!==continueOnValidationError&&(body.continueOnValidationError=continueOnValidationError);const endpoint=`${url}${tagId}/events`;logger.debug("Calling Bing UET CAPI",{endpoint:endpoint,method:"POST",eventType:capiEvent.eventType,eventName:capiEvent.eventName,eventId:capiEvent.eventId});const sendServerFn=env?.sendServer||import_server_core2.sendServer,result=await sendServerFn(endpoint,JSON.stringify(body),{headers:{Authorization:`Bearer ${accessToken}`,"Content-Type":"application/json"}});logger.debug("Bing UET CAPI response",{ok:!(0,import_core.isObject)(result)||result.ok}),(0,import_core.isObject)(result)&&!1===result.ok&&logger.throw(`Bing UET CAPI error: ${JSON.stringify(result)}`)}(event,context)},index_default=destinationBing;//# sourceMappingURL=index.js.map
1
+ "use strict";var mod,__defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__hasOwnProp=Object.prototype.hasOwnProperty,index_exports={};((target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})})(index_exports,{DestinationBing:()=>types_exports,default:()=>index_default,destinationBing:()=>destinationBing}),module.exports=(mod=index_exports,((to,from,except,desc)=>{if(from&&"object"==typeof from||"function"==typeof from)for(let key of __getOwnPropNames(from))__hasOwnProp.call(to,key)||key===except||__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to})(__defProp({},"__esModule",{value:!0}),mod));var import_core=require("@walkeros/core"),import_server_core2=require("@walkeros/server-core"),import_server_core=require("@walkeros/server-core"),keysToHash=["em","ph"];async function hashUserData(userData,doNotHash=[]){const entries=await Promise.all(Object.entries(userData).map(async([key,value])=>{if(void 0===value)return[key,value];if(function(key,doNotHash=[]){return keysToHash.includes(key)&&!doNotHash.includes(key)}(key,doNotHash)){const normalized=function(key,value){return"em"===key?function(value){const trimmed=value.trim().toLowerCase(),atIndex=trimmed.lastIndexOf("@");if(atIndex<0)return trimmed;const user=trimmed.slice(0,atIndex),domain=trimmed.slice(atIndex+1),aliasIndex=user.indexOf("+");return`${(aliasIndex<0?user:user.slice(0,aliasIndex)).replace(/\./g,"")}@${domain}`}(value):"ph"===key?function(value){return value.trim()}(value):value}(key,String(value));return[key,await(0,import_server_core.getHashServer)(normalized)]}return[key,value]}));return Object.fromEntries(entries)}var types_exports={},destinationBing={type:"bing",config:{},async init({config:partialConfig,logger:logger}){const config=function(partialConfig={},logger){const settings=partialConfig.settings||{},{accessToken:accessToken,tagId:tagId}=settings;accessToken||logger.throw("Config settings accessToken missing"),tagId||logger.throw("Config settings tagId missing");const settingsConfig={url:"https://capi.uet.microsoft.com/v1/",dataProvider:"walkerOS",...settings,accessToken:accessToken,tagId:tagId};return{...partialConfig,settings:settingsConfig}}(partialConfig,logger);return config},push:async(event,context)=>await async function(event,{config:config,rule:rule,data:data,env:env,logger:logger,collector:collector}){const{accessToken:accessToken,tagId:tagId,url:url="https://capi.uet.microsoft.com/v1/",doNotHash:doNotHash,user_data:user_data,dataProvider:dataProvider="walkerOS",continueOnValidationError:continueOnValidationError}=config.settings,eventData=(0,import_core.isObject)(data)?data:{},configData=config.data?await(0,import_core.getMappingValue)(event,config.data,{collector:collector}):{},userDataCustom=user_data?await(0,import_core.getMappingValue)(event,{map:user_data},{collector:collector}):{},ruleSettings=rule?.settings,eventType="pageLoad"===ruleSettings?.eventType?"pageLoad":"custom",eventMappedUserData=(0,import_core.isObject)(eventData.userData)?eventData.userData:{},userData={...(0,import_core.isObject)(configData)&&(0,import_core.isObject)(configData.userData)?configData.userData:{},...(0,import_core.isObject)(userDataCustom)?userDataCustom:{},...eventMappedUserData},hashedUserData=await hashUserData(userData,doNotHash),customData={};(0,import_core.isObject)(eventData.customData)&&Object.assign(customData,eventData.customData);for(const[key,value]of Object.entries(eventData))"userData"!==key&&"customData"!==key&&(customData[key]=value);const capiEvent={eventType:eventType,eventId:event.id,eventTime:Math.round((event.timestamp||Date.now())/1e3),adStorageConsent:"G",userData:hashedUserData};"custom"===eventType&&(capiEvent.eventName=rule?.name||event.name),event.source?.url&&(capiEvent.eventSourceUrl=event.source.url),Object.keys(customData).length>0&&(capiEvent.customData=customData);const body={data:[capiEvent],dataProvider:dataProvider};void 0!==continueOnValidationError&&(body.continueOnValidationError=continueOnValidationError);const endpoint=`${url}${tagId}/events`;logger.debug("Calling Bing UET CAPI",{endpoint:endpoint,method:"POST",eventType:capiEvent.eventType,eventName:capiEvent.eventName,eventId:capiEvent.eventId});const sendServerFn=env?.sendServer||import_server_core2.sendServer,result=await sendServerFn(endpoint,JSON.stringify(body),{headers:{Authorization:`Bearer ${accessToken}`,"Content-Type":"application/json"}});logger.debug("Bing UET CAPI response",{ok:!(0,import_core.isObject)(result)||result.ok}),(0,import_core.isObject)(result)&&!1===result.ok&&logger.throw(`Bing UET CAPI error: ${JSON.stringify(result)}`)}(event,context)},index_default=destinationBing;//# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/push.ts","../src/hash.ts","../src/types/index.ts"],"sourcesContent":["import type { Destination } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\n\n// Types\nexport * as DestinationBing from './types';\n\nexport const destinationBing: Destination = {\n type: 'bing',\n\n config: {},\n\n async init({ config: partialConfig, logger }) {\n const config = getConfig(partialConfig, logger);\n return config;\n },\n\n async push(event, context) {\n return await push(event, context);\n },\n};\n\nexport default destinationBing;\n","import type { Config, Settings, PartialConfig } from './types';\nimport type { Logger } from '@walkeros/core';\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): Config {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { accessToken, tagId } = settings;\n\n if (!accessToken) logger.throw('Config settings accessToken missing');\n if (!tagId) logger.throw('Config settings tagId missing');\n\n const settingsConfig: Settings = {\n url: 'https://capi.uet.microsoft.com/v1/',\n dataProvider: 'walkerOS',\n ...settings,\n accessToken,\n tagId,\n };\n\n return { ...partialConfig, settings: settingsConfig };\n}\n","import type {\n CAPIEvent,\n CAPIRequestBody,\n CustomData,\n Env,\n EventType,\n PushFn,\n Settings,\n UserData,\n} from './types';\nimport { getMappingValue, isObject } from '@walkeros/core';\nimport { sendServer } from '@walkeros/server-core';\nimport { hashUserData } from './hash';\n\nexport const push: PushFn = async function (\n event,\n { config, rule, data, env, logger },\n) {\n const {\n accessToken,\n tagId,\n url = 'https://capi.uet.microsoft.com/v1/',\n doNotHash,\n user_data,\n dataProvider = 'walkerOS',\n continueOnValidationError,\n } = config.settings as Settings;\n\n const eventData = isObject(data) ? data : {};\n const configData = config.data\n ? await getMappingValue(event, config.data)\n : {};\n const userDataCustom = user_data\n ? await getMappingValue(event, { map: user_data })\n : {};\n\n // Resolve mapping-level eventType override (rule.settings.eventType)\n const ruleSettings = rule?.settings as { eventType?: EventType } | undefined;\n const eventType: EventType =\n ruleSettings?.eventType === 'pageLoad' ? 'pageLoad' : 'custom';\n\n // Build user_data from merge sources (priority: later overrides earlier)\n const eventMappedUserData = isObject(eventData.userData)\n ? (eventData.userData as UserData)\n : {};\n\n const userData: UserData = {\n ...(isObject(configData) && isObject(configData.userData)\n ? (configData.userData as UserData)\n : {}),\n ...(isObject(userDataCustom) ? (userDataCustom as UserData) : {}),\n ...eventMappedUserData,\n };\n\n // Hash identity fields (em, ph only; with Microsoft email normalization)\n const hashedUserData = await hashUserData(userData, doNotHash);\n\n // Build customData from mapped event data\n const customData: CustomData = {};\n if (isObject(eventData.customData)) {\n Object.assign(customData, eventData.customData);\n }\n for (const [key, value] of Object.entries(eventData)) {\n if (key === 'userData' || key === 'customData') continue;\n customData[key] = value;\n }\n\n // Build Bing CAPI event\n const capiEvent: CAPIEvent = {\n eventType,\n eventId: event.id,\n eventTime: Math.round((event.timestamp || Date.now()) / 1000),\n adStorageConsent: 'G',\n userData: hashedUserData,\n };\n\n if (eventType === 'custom') {\n capiEvent.eventName = rule?.name || event.name;\n }\n\n if (event.source?.url) {\n capiEvent.eventSourceUrl = event.source.url;\n }\n\n if (Object.keys(customData).length > 0) {\n capiEvent.customData = customData;\n }\n\n const body: CAPIRequestBody = {\n data: [capiEvent],\n dataProvider,\n };\n\n if (continueOnValidationError !== undefined) {\n body.continueOnValidationError = continueOnValidationError;\n }\n\n const endpoint = `${url}${tagId}/events`;\n\n logger.debug('Calling Bing UET CAPI', {\n endpoint,\n method: 'POST',\n eventType: capiEvent.eventType,\n eventName: capiEvent.eventName,\n eventId: capiEvent.eventId,\n });\n\n const sendServerFn = (env as Env)?.sendServer || sendServer;\n const result = await sendServerFn(endpoint, JSON.stringify(body), {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n });\n\n logger.debug('Bing UET CAPI response', {\n ok: isObject(result) ? result.ok : true,\n });\n\n if (isObject(result) && result.ok === false) {\n logger.throw(`Bing UET CAPI error: ${JSON.stringify(result)}`);\n }\n};\n","import type { UserData } from './types';\nimport { getHashServer } from '@walkeros/server-core';\n\n/**\n * Bing UET CAPI user data fields that must be SHA-256 hashed before sending.\n * Only `em` and `ph` are hashable; all other identity fields pass through\n * unhashed (anonymousId, externalId, msclkid, clientIpAddress,\n * clientUserAgent, idfa, gaid).\n * https://learn.microsoft.com/en-us/advertising/guides/universal-event-tracking-capi\n */\nconst keysToHash = ['em', 'ph'];\n\n/**\n * Microsoft-specific email normalization:\n * - Trim whitespace\n * - Lowercase\n * - Split at `@`\n * - Remove dots from the user portion\n * - Strip `+alias` suffix from the user portion\n * - Rejoin\n */\nexport function normalizeEmail(value: string): string {\n const trimmed = value.trim().toLowerCase();\n const atIndex = trimmed.lastIndexOf('@');\n if (atIndex < 0) return trimmed;\n\n const user = trimmed.slice(0, atIndex);\n const domain = trimmed.slice(atIndex + 1);\n\n const aliasIndex = user.indexOf('+');\n const beforeAlias = aliasIndex < 0 ? user : user.slice(0, aliasIndex);\n const normalizedUser = beforeAlias.replace(/\\./g, '');\n\n return `${normalizedUser}@${domain}`;\n}\n\nfunction normalizePhone(value: string): string {\n return value.trim();\n}\n\nfunction shouldBeHashed(key: string, doNotHash: string[] = []): boolean {\n return keysToHash.includes(key) && !doNotHash.includes(key);\n}\n\nfunction normalizeForKey(key: string, value: string): string {\n if (key === 'em') return normalizeEmail(value);\n if (key === 'ph') return normalizePhone(value);\n return value;\n}\n\nexport async function hashUserData(\n userData: UserData,\n doNotHash: string[] = [],\n): Promise<UserData> {\n const entries = await Promise.all(\n Object.entries(userData).map(async ([key, value]) => {\n if (value === undefined) return [key, value];\n if (shouldBeHashed(key, doNotHash)) {\n const normalized = normalizeForKey(key, String(value));\n return [key, await getHashServer(normalized)];\n }\n return [key, value];\n }),\n );\n\n return Object.fromEntries(entries) as UserData;\n}\n","import type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationServer, sendServer } from '@walkeros/server-core';\n\nexport interface Settings {\n accessToken: string;\n tagId: string;\n url?: string;\n doNotHash?: string[];\n user_data?: WalkerOSMapping.Map;\n dataProvider?: string;\n continueOnValidationError?: boolean;\n}\n\nexport type InitSettings = Partial<Settings>;\n\nexport interface Mapping {\n eventType?: EventType;\n}\n\nexport interface Env extends DestinationServer.Env {\n sendServer?: typeof sendServer;\n}\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface Destination extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\n\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\n\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\n\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n\n/**\n * Microsoft Advertising (Bing) UET Conversions API\n * https://learn.microsoft.com/en-us/advertising/guides/universal-event-tracking-capi\n */\nexport type EventType = 'pageLoad' | 'custom';\n\nexport type AdStorageConsent = 'G' | 'D';\n\nexport interface CAPIRequestBody {\n data: CAPIEvent[];\n dataProvider?: string;\n continueOnValidationError?: boolean;\n}\n\nexport interface CAPIEvent {\n eventType: EventType;\n eventId?: string;\n eventName?: string;\n eventTime: number;\n eventSourceUrl?: string;\n pageLoadId?: string;\n referrerUrl?: string;\n pageTitle?: string;\n keywords?: string;\n adStorageConsent?: AdStorageConsent;\n userData?: UserData;\n customData?: CustomData;\n}\n\n// User identity fields. Hashable: em, ph. Non-hashable: anonymousId,\n// externalId, msclkid, clientIpAddress, clientUserAgent, idfa, gaid.\nexport interface UserData {\n /** Anonymous ID. Do NOT hash. */\n anonymousId?: string;\n /** External/customer ID. Do NOT hash. */\n externalId?: string;\n /** Email, SHA-256 hashed, normalized (dots/alias stripped, lowercased) */\n em?: string;\n /** Phone number, SHA-256 hashed, trimmed */\n ph?: string;\n /** Microsoft click ID. Do NOT hash. */\n msclkid?: string;\n /** Client IP address (IPv4 or IPv6). Do NOT hash. */\n clientIpAddress?: string;\n /** Client user agent. Do NOT hash. */\n clientUserAgent?: string;\n /** iOS IDFA. Do NOT hash. */\n idfa?: string;\n /** Android advertising ID (GAID). Do NOT hash. */\n gaid?: string;\n}\n\nexport interface CustomData {\n eventCategory?: string;\n eventLabel?: string;\n eventValue?: number;\n searchTerm?: string;\n transactionId?: string;\n value?: number;\n currency?: string;\n items?: ItemData[];\n itemIds?: string[];\n pageType?: string;\n ecommTotalValue?: number;\n ecommCategory?: string;\n hotelData?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface ItemData {\n id?: string;\n quantity?: number;\n price?: number;\n name?: string;\n}\n\nexport interface ResponseBody {\n status?: string;\n requestId?: string;\n}\n"],"mappings":"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","names":["import_server_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/push.ts","../src/hash.ts","../src/types/index.ts"],"sourcesContent":["import type { Destination } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\n\n// Types\nexport * as DestinationBing from './types';\n\nexport const destinationBing: Destination = {\n type: 'bing',\n\n config: {},\n\n async init({ config: partialConfig, logger }) {\n const config = getConfig(partialConfig, logger);\n return config;\n },\n\n async push(event, context) {\n return await push(event, context);\n },\n};\n\nexport default destinationBing;\n","import type { Config, Settings, PartialConfig } from './types';\nimport type { Logger } from '@walkeros/core';\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): Config {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { accessToken, tagId } = settings;\n\n if (!accessToken) logger.throw('Config settings accessToken missing');\n if (!tagId) logger.throw('Config settings tagId missing');\n\n const settingsConfig: Settings = {\n url: 'https://capi.uet.microsoft.com/v1/',\n dataProvider: 'walkerOS',\n ...settings,\n accessToken,\n tagId,\n };\n\n return { ...partialConfig, settings: settingsConfig };\n}\n","import type {\n CAPIEvent,\n CAPIRequestBody,\n CustomData,\n Env,\n EventType,\n PushFn,\n Settings,\n UserData,\n} from './types';\nimport { getMappingValue, isObject } from '@walkeros/core';\nimport { sendServer } from '@walkeros/server-core';\nimport { hashUserData } from './hash';\n\nexport const push: PushFn = async function (\n event,\n { config, rule, data, env, logger, collector },\n) {\n const {\n accessToken,\n tagId,\n url = 'https://capi.uet.microsoft.com/v1/',\n doNotHash,\n user_data,\n dataProvider = 'walkerOS',\n continueOnValidationError,\n } = config.settings as Settings;\n\n const eventData = isObject(data) ? data : {};\n const configData = config.data\n ? await getMappingValue(event, config.data, { collector })\n : {};\n const userDataCustom = user_data\n ? await getMappingValue(event, { map: user_data }, { collector })\n : {};\n\n // Resolve mapping-level eventType override (rule.settings.eventType)\n const ruleSettings = rule?.settings as { eventType?: EventType } | undefined;\n const eventType: EventType =\n ruleSettings?.eventType === 'pageLoad' ? 'pageLoad' : 'custom';\n\n // Build user_data from merge sources (priority: later overrides earlier)\n const eventMappedUserData = isObject(eventData.userData)\n ? (eventData.userData as UserData)\n : {};\n\n const userData: UserData = {\n ...(isObject(configData) && isObject(configData.userData)\n ? (configData.userData as UserData)\n : {}),\n ...(isObject(userDataCustom) ? (userDataCustom as UserData) : {}),\n ...eventMappedUserData,\n };\n\n // Hash identity fields (em, ph only; with Microsoft email normalization)\n const hashedUserData = await hashUserData(userData, doNotHash);\n\n // Build customData from mapped event data\n const customData: CustomData = {};\n if (isObject(eventData.customData)) {\n Object.assign(customData, eventData.customData);\n }\n for (const [key, value] of Object.entries(eventData)) {\n if (key === 'userData' || key === 'customData') continue;\n customData[key] = value;\n }\n\n // Build Bing CAPI event\n const capiEvent: CAPIEvent = {\n eventType,\n eventId: event.id,\n eventTime: Math.round((event.timestamp || Date.now()) / 1000),\n adStorageConsent: 'G',\n userData: hashedUserData,\n };\n\n if (eventType === 'custom') {\n capiEvent.eventName = rule?.name || event.name;\n }\n\n if (event.source?.url) {\n capiEvent.eventSourceUrl = event.source.url;\n }\n\n if (Object.keys(customData).length > 0) {\n capiEvent.customData = customData;\n }\n\n const body: CAPIRequestBody = {\n data: [capiEvent],\n dataProvider,\n };\n\n if (continueOnValidationError !== undefined) {\n body.continueOnValidationError = continueOnValidationError;\n }\n\n const endpoint = `${url}${tagId}/events`;\n\n logger.debug('Calling Bing UET CAPI', {\n endpoint,\n method: 'POST',\n eventType: capiEvent.eventType,\n eventName: capiEvent.eventName,\n eventId: capiEvent.eventId,\n });\n\n const sendServerFn = (env as Env)?.sendServer || sendServer;\n const result = await sendServerFn(endpoint, JSON.stringify(body), {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n });\n\n logger.debug('Bing UET CAPI response', {\n ok: isObject(result) ? result.ok : true,\n });\n\n if (isObject(result) && result.ok === false) {\n logger.throw(`Bing UET CAPI error: ${JSON.stringify(result)}`);\n }\n};\n","import type { UserData } from './types';\nimport { getHashServer } from '@walkeros/server-core';\n\n/**\n * Bing UET CAPI user data fields that must be SHA-256 hashed before sending.\n * Only `em` and `ph` are hashable; all other identity fields pass through\n * unhashed (anonymousId, externalId, msclkid, clientIpAddress,\n * clientUserAgent, idfa, gaid).\n * https://learn.microsoft.com/en-us/advertising/guides/universal-event-tracking-capi\n */\nconst keysToHash = ['em', 'ph'];\n\n/**\n * Microsoft-specific email normalization:\n * - Trim whitespace\n * - Lowercase\n * - Split at `@`\n * - Remove dots from the user portion\n * - Strip `+alias` suffix from the user portion\n * - Rejoin\n */\nexport function normalizeEmail(value: string): string {\n const trimmed = value.trim().toLowerCase();\n const atIndex = trimmed.lastIndexOf('@');\n if (atIndex < 0) return trimmed;\n\n const user = trimmed.slice(0, atIndex);\n const domain = trimmed.slice(atIndex + 1);\n\n const aliasIndex = user.indexOf('+');\n const beforeAlias = aliasIndex < 0 ? user : user.slice(0, aliasIndex);\n const normalizedUser = beforeAlias.replace(/\\./g, '');\n\n return `${normalizedUser}@${domain}`;\n}\n\nfunction normalizePhone(value: string): string {\n return value.trim();\n}\n\nfunction shouldBeHashed(key: string, doNotHash: string[] = []): boolean {\n return keysToHash.includes(key) && !doNotHash.includes(key);\n}\n\nfunction normalizeForKey(key: string, value: string): string {\n if (key === 'em') return normalizeEmail(value);\n if (key === 'ph') return normalizePhone(value);\n return value;\n}\n\nexport async function hashUserData(\n userData: UserData,\n doNotHash: string[] = [],\n): Promise<UserData> {\n const entries = await Promise.all(\n Object.entries(userData).map(async ([key, value]) => {\n if (value === undefined) return [key, value];\n if (shouldBeHashed(key, doNotHash)) {\n const normalized = normalizeForKey(key, String(value));\n return [key, await getHashServer(normalized)];\n }\n return [key, value];\n }),\n );\n\n return Object.fromEntries(entries) as UserData;\n}\n","import type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationServer, sendServer } from '@walkeros/server-core';\n\nexport interface Settings {\n accessToken: string;\n tagId: string;\n url?: string;\n doNotHash?: string[];\n user_data?: WalkerOSMapping.Map;\n dataProvider?: string;\n continueOnValidationError?: boolean;\n}\n\nexport type InitSettings = Partial<Settings>;\n\nexport interface Mapping {\n eventType?: EventType;\n}\n\nexport interface Env extends DestinationServer.Env {\n sendServer?: typeof sendServer;\n}\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface Destination extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\n\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\n\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\n\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n\n/**\n * Microsoft Advertising (Bing) UET Conversions API\n * https://learn.microsoft.com/en-us/advertising/guides/universal-event-tracking-capi\n */\nexport type EventType = 'pageLoad' | 'custom';\n\nexport type AdStorageConsent = 'G' | 'D';\n\nexport interface CAPIRequestBody {\n data: CAPIEvent[];\n dataProvider?: string;\n continueOnValidationError?: boolean;\n}\n\nexport interface CAPIEvent {\n eventType: EventType;\n eventId?: string;\n eventName?: string;\n eventTime: number;\n eventSourceUrl?: string;\n pageLoadId?: string;\n referrerUrl?: string;\n pageTitle?: string;\n keywords?: string;\n adStorageConsent?: AdStorageConsent;\n userData?: UserData;\n customData?: CustomData;\n}\n\n// User identity fields. Hashable: em, ph. Non-hashable: anonymousId,\n// externalId, msclkid, clientIpAddress, clientUserAgent, idfa, gaid.\nexport interface UserData {\n /** Anonymous ID. Do NOT hash. */\n anonymousId?: string;\n /** External/customer ID. Do NOT hash. */\n externalId?: string;\n /** Email, SHA-256 hashed, normalized (dots/alias stripped, lowercased) */\n em?: string;\n /** Phone number, SHA-256 hashed, trimmed */\n ph?: string;\n /** Microsoft click ID. Do NOT hash. */\n msclkid?: string;\n /** Client IP address (IPv4 or IPv6). Do NOT hash. */\n clientIpAddress?: string;\n /** Client user agent. Do NOT hash. */\n clientUserAgent?: string;\n /** iOS IDFA. Do NOT hash. */\n idfa?: string;\n /** Android advertising ID (GAID). Do NOT hash. */\n gaid?: string;\n}\n\nexport interface CustomData {\n eventCategory?: string;\n eventLabel?: string;\n eventValue?: number;\n searchTerm?: string;\n transactionId?: string;\n value?: number;\n currency?: string;\n items?: ItemData[];\n itemIds?: string[];\n pageType?: string;\n ecommTotalValue?: number;\n ecommCategory?: string;\n hotelData?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface ItemData {\n id?: string;\n quantity?: number;\n price?: number;\n name?: string;\n}\n\nexport interface ResponseBody {\n status?: string;\n requestId?: string;\n}\n"],"mappings":"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","names":["import_server_core"]}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{getMappingValue,isObject}from"@walkeros/core";import{sendServer}from"@walkeros/server-core";import{getHashServer}from"@walkeros/server-core";var keysToHash=["em","ph"];async function hashUserData(userData,doNotHash=[]){const entries=await Promise.all(Object.entries(userData).map(async([key,value])=>{if(void 0===value)return[key,value];if(function(key,doNotHash=[]){return keysToHash.includes(key)&&!doNotHash.includes(key)}(key,doNotHash)){const normalized=function(key,value){return"em"===key?function(value){const trimmed=value.trim().toLowerCase(),atIndex=trimmed.lastIndexOf("@");if(atIndex<0)return trimmed;const user=trimmed.slice(0,atIndex),domain=trimmed.slice(atIndex+1),aliasIndex=user.indexOf("+");return`${(aliasIndex<0?user:user.slice(0,aliasIndex)).replace(/\./g,"")}@${domain}`}(value):"ph"===key?function(value){return value.trim()}(value):value}(key,String(value));return[key,await getHashServer(normalized)]}return[key,value]}));return Object.fromEntries(entries)}var types_exports={},destinationBing={type:"bing",config:{},async init({config:partialConfig,logger:logger}){const config=function(partialConfig={},logger){const settings=partialConfig.settings||{},{accessToken:accessToken,tagId:tagId}=settings;accessToken||logger.throw("Config settings accessToken missing"),tagId||logger.throw("Config settings tagId missing");const settingsConfig={url:"https://capi.uet.microsoft.com/v1/",dataProvider:"walkerOS",...settings,accessToken:accessToken,tagId:tagId};return{...partialConfig,settings:settingsConfig}}(partialConfig,logger);return config},push:async(event,context)=>await async function(event,{config:config,rule:rule,data:data,env:env,logger:logger}){const{accessToken:accessToken,tagId:tagId,url:url="https://capi.uet.microsoft.com/v1/",doNotHash:doNotHash,user_data:user_data,dataProvider:dataProvider="walkerOS",continueOnValidationError:continueOnValidationError}=config.settings,eventData=isObject(data)?data:{},configData=config.data?await getMappingValue(event,config.data):{},userDataCustom=user_data?await getMappingValue(event,{map:user_data}):{},ruleSettings=rule?.settings,eventType="pageLoad"===ruleSettings?.eventType?"pageLoad":"custom",eventMappedUserData=isObject(eventData.userData)?eventData.userData:{},userData={...isObject(configData)&&isObject(configData.userData)?configData.userData:{},...isObject(userDataCustom)?userDataCustom:{},...eventMappedUserData},hashedUserData=await hashUserData(userData,doNotHash),customData={};isObject(eventData.customData)&&Object.assign(customData,eventData.customData);for(const[key,value]of Object.entries(eventData))"userData"!==key&&"customData"!==key&&(customData[key]=value);const capiEvent={eventType:eventType,eventId:event.id,eventTime:Math.round((event.timestamp||Date.now())/1e3),adStorageConsent:"G",userData:hashedUserData};"custom"===eventType&&(capiEvent.eventName=rule?.name||event.name),event.source?.url&&(capiEvent.eventSourceUrl=event.source.url),Object.keys(customData).length>0&&(capiEvent.customData=customData);const body={data:[capiEvent],dataProvider:dataProvider};void 0!==continueOnValidationError&&(body.continueOnValidationError=continueOnValidationError);const endpoint=`${url}${tagId}/events`;logger.debug("Calling Bing UET CAPI",{endpoint:endpoint,method:"POST",eventType:capiEvent.eventType,eventName:capiEvent.eventName,eventId:capiEvent.eventId});const sendServerFn=env?.sendServer||sendServer,result=await sendServerFn(endpoint,JSON.stringify(body),{headers:{Authorization:`Bearer ${accessToken}`,"Content-Type":"application/json"}});logger.debug("Bing UET CAPI response",{ok:!isObject(result)||result.ok}),isObject(result)&&!1===result.ok&&logger.throw(`Bing UET CAPI error: ${JSON.stringify(result)}`)}(event,context)},index_default=destinationBing;export{types_exports as DestinationBing,index_default as default,destinationBing};//# sourceMappingURL=index.mjs.map
1
+ import{getMappingValue,isObject}from"@walkeros/core";import{sendServer}from"@walkeros/server-core";import{getHashServer}from"@walkeros/server-core";var keysToHash=["em","ph"];async function hashUserData(userData,doNotHash=[]){const entries=await Promise.all(Object.entries(userData).map(async([key,value])=>{if(void 0===value)return[key,value];if(function(key,doNotHash=[]){return keysToHash.includes(key)&&!doNotHash.includes(key)}(key,doNotHash)){const normalized=function(key,value){return"em"===key?function(value){const trimmed=value.trim().toLowerCase(),atIndex=trimmed.lastIndexOf("@");if(atIndex<0)return trimmed;const user=trimmed.slice(0,atIndex),domain=trimmed.slice(atIndex+1),aliasIndex=user.indexOf("+");return`${(aliasIndex<0?user:user.slice(0,aliasIndex)).replace(/\./g,"")}@${domain}`}(value):"ph"===key?function(value){return value.trim()}(value):value}(key,String(value));return[key,await getHashServer(normalized)]}return[key,value]}));return Object.fromEntries(entries)}var types_exports={},destinationBing={type:"bing",config:{},async init({config:partialConfig,logger:logger}){const config=function(partialConfig={},logger){const settings=partialConfig.settings||{},{accessToken:accessToken,tagId:tagId}=settings;accessToken||logger.throw("Config settings accessToken missing"),tagId||logger.throw("Config settings tagId missing");const settingsConfig={url:"https://capi.uet.microsoft.com/v1/",dataProvider:"walkerOS",...settings,accessToken:accessToken,tagId:tagId};return{...partialConfig,settings:settingsConfig}}(partialConfig,logger);return config},push:async(event,context)=>await async function(event,{config:config,rule:rule,data:data,env:env,logger:logger,collector:collector}){const{accessToken:accessToken,tagId:tagId,url:url="https://capi.uet.microsoft.com/v1/",doNotHash:doNotHash,user_data:user_data,dataProvider:dataProvider="walkerOS",continueOnValidationError:continueOnValidationError}=config.settings,eventData=isObject(data)?data:{},configData=config.data?await getMappingValue(event,config.data,{collector:collector}):{},userDataCustom=user_data?await getMappingValue(event,{map:user_data},{collector:collector}):{},ruleSettings=rule?.settings,eventType="pageLoad"===ruleSettings?.eventType?"pageLoad":"custom",eventMappedUserData=isObject(eventData.userData)?eventData.userData:{},userData={...isObject(configData)&&isObject(configData.userData)?configData.userData:{},...isObject(userDataCustom)?userDataCustom:{},...eventMappedUserData},hashedUserData=await hashUserData(userData,doNotHash),customData={};isObject(eventData.customData)&&Object.assign(customData,eventData.customData);for(const[key,value]of Object.entries(eventData))"userData"!==key&&"customData"!==key&&(customData[key]=value);const capiEvent={eventType:eventType,eventId:event.id,eventTime:Math.round((event.timestamp||Date.now())/1e3),adStorageConsent:"G",userData:hashedUserData};"custom"===eventType&&(capiEvent.eventName=rule?.name||event.name),event.source?.url&&(capiEvent.eventSourceUrl=event.source.url),Object.keys(customData).length>0&&(capiEvent.customData=customData);const body={data:[capiEvent],dataProvider:dataProvider};void 0!==continueOnValidationError&&(body.continueOnValidationError=continueOnValidationError);const endpoint=`${url}${tagId}/events`;logger.debug("Calling Bing UET CAPI",{endpoint:endpoint,method:"POST",eventType:capiEvent.eventType,eventName:capiEvent.eventName,eventId:capiEvent.eventId});const sendServerFn=env?.sendServer||sendServer,result=await sendServerFn(endpoint,JSON.stringify(body),{headers:{Authorization:`Bearer ${accessToken}`,"Content-Type":"application/json"}});logger.debug("Bing UET CAPI response",{ok:!isObject(result)||result.ok}),isObject(result)&&!1===result.ok&&logger.throw(`Bing UET CAPI error: ${JSON.stringify(result)}`)}(event,context)},index_default=destinationBing;export{types_exports as DestinationBing,index_default as default,destinationBing};//# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts","../src/push.ts","../src/hash.ts","../src/types/index.ts","../src/index.ts"],"sourcesContent":["import type { Config, Settings, PartialConfig } from './types';\nimport type { Logger } from '@walkeros/core';\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): Config {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { accessToken, tagId } = settings;\n\n if (!accessToken) logger.throw('Config settings accessToken missing');\n if (!tagId) logger.throw('Config settings tagId missing');\n\n const settingsConfig: Settings = {\n url: 'https://capi.uet.microsoft.com/v1/',\n dataProvider: 'walkerOS',\n ...settings,\n accessToken,\n tagId,\n };\n\n return { ...partialConfig, settings: settingsConfig };\n}\n","import type {\n CAPIEvent,\n CAPIRequestBody,\n CustomData,\n Env,\n EventType,\n PushFn,\n Settings,\n UserData,\n} from './types';\nimport { getMappingValue, isObject } from '@walkeros/core';\nimport { sendServer } from '@walkeros/server-core';\nimport { hashUserData } from './hash';\n\nexport const push: PushFn = async function (\n event,\n { config, rule, data, env, logger },\n) {\n const {\n accessToken,\n tagId,\n url = 'https://capi.uet.microsoft.com/v1/',\n doNotHash,\n user_data,\n dataProvider = 'walkerOS',\n continueOnValidationError,\n } = config.settings as Settings;\n\n const eventData = isObject(data) ? data : {};\n const configData = config.data\n ? await getMappingValue(event, config.data)\n : {};\n const userDataCustom = user_data\n ? await getMappingValue(event, { map: user_data })\n : {};\n\n // Resolve mapping-level eventType override (rule.settings.eventType)\n const ruleSettings = rule?.settings as { eventType?: EventType } | undefined;\n const eventType: EventType =\n ruleSettings?.eventType === 'pageLoad' ? 'pageLoad' : 'custom';\n\n // Build user_data from merge sources (priority: later overrides earlier)\n const eventMappedUserData = isObject(eventData.userData)\n ? (eventData.userData as UserData)\n : {};\n\n const userData: UserData = {\n ...(isObject(configData) && isObject(configData.userData)\n ? (configData.userData as UserData)\n : {}),\n ...(isObject(userDataCustom) ? (userDataCustom as UserData) : {}),\n ...eventMappedUserData,\n };\n\n // Hash identity fields (em, ph only; with Microsoft email normalization)\n const hashedUserData = await hashUserData(userData, doNotHash);\n\n // Build customData from mapped event data\n const customData: CustomData = {};\n if (isObject(eventData.customData)) {\n Object.assign(customData, eventData.customData);\n }\n for (const [key, value] of Object.entries(eventData)) {\n if (key === 'userData' || key === 'customData') continue;\n customData[key] = value;\n }\n\n // Build Bing CAPI event\n const capiEvent: CAPIEvent = {\n eventType,\n eventId: event.id,\n eventTime: Math.round((event.timestamp || Date.now()) / 1000),\n adStorageConsent: 'G',\n userData: hashedUserData,\n };\n\n if (eventType === 'custom') {\n capiEvent.eventName = rule?.name || event.name;\n }\n\n if (event.source?.url) {\n capiEvent.eventSourceUrl = event.source.url;\n }\n\n if (Object.keys(customData).length > 0) {\n capiEvent.customData = customData;\n }\n\n const body: CAPIRequestBody = {\n data: [capiEvent],\n dataProvider,\n };\n\n if (continueOnValidationError !== undefined) {\n body.continueOnValidationError = continueOnValidationError;\n }\n\n const endpoint = `${url}${tagId}/events`;\n\n logger.debug('Calling Bing UET CAPI', {\n endpoint,\n method: 'POST',\n eventType: capiEvent.eventType,\n eventName: capiEvent.eventName,\n eventId: capiEvent.eventId,\n });\n\n const sendServerFn = (env as Env)?.sendServer || sendServer;\n const result = await sendServerFn(endpoint, JSON.stringify(body), {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n });\n\n logger.debug('Bing UET CAPI response', {\n ok: isObject(result) ? result.ok : true,\n });\n\n if (isObject(result) && result.ok === false) {\n logger.throw(`Bing UET CAPI error: ${JSON.stringify(result)}`);\n }\n};\n","import type { UserData } from './types';\nimport { getHashServer } from '@walkeros/server-core';\n\n/**\n * Bing UET CAPI user data fields that must be SHA-256 hashed before sending.\n * Only `em` and `ph` are hashable; all other identity fields pass through\n * unhashed (anonymousId, externalId, msclkid, clientIpAddress,\n * clientUserAgent, idfa, gaid).\n * https://learn.microsoft.com/en-us/advertising/guides/universal-event-tracking-capi\n */\nconst keysToHash = ['em', 'ph'];\n\n/**\n * Microsoft-specific email normalization:\n * - Trim whitespace\n * - Lowercase\n * - Split at `@`\n * - Remove dots from the user portion\n * - Strip `+alias` suffix from the user portion\n * - Rejoin\n */\nexport function normalizeEmail(value: string): string {\n const trimmed = value.trim().toLowerCase();\n const atIndex = trimmed.lastIndexOf('@');\n if (atIndex < 0) return trimmed;\n\n const user = trimmed.slice(0, atIndex);\n const domain = trimmed.slice(atIndex + 1);\n\n const aliasIndex = user.indexOf('+');\n const beforeAlias = aliasIndex < 0 ? user : user.slice(0, aliasIndex);\n const normalizedUser = beforeAlias.replace(/\\./g, '');\n\n return `${normalizedUser}@${domain}`;\n}\n\nfunction normalizePhone(value: string): string {\n return value.trim();\n}\n\nfunction shouldBeHashed(key: string, doNotHash: string[] = []): boolean {\n return keysToHash.includes(key) && !doNotHash.includes(key);\n}\n\nfunction normalizeForKey(key: string, value: string): string {\n if (key === 'em') return normalizeEmail(value);\n if (key === 'ph') return normalizePhone(value);\n return value;\n}\n\nexport async function hashUserData(\n userData: UserData,\n doNotHash: string[] = [],\n): Promise<UserData> {\n const entries = await Promise.all(\n Object.entries(userData).map(async ([key, value]) => {\n if (value === undefined) return [key, value];\n if (shouldBeHashed(key, doNotHash)) {\n const normalized = normalizeForKey(key, String(value));\n return [key, await getHashServer(normalized)];\n }\n return [key, value];\n }),\n );\n\n return Object.fromEntries(entries) as UserData;\n}\n","import type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationServer, sendServer } from '@walkeros/server-core';\n\nexport interface Settings {\n accessToken: string;\n tagId: string;\n url?: string;\n doNotHash?: string[];\n user_data?: WalkerOSMapping.Map;\n dataProvider?: string;\n continueOnValidationError?: boolean;\n}\n\nexport type InitSettings = Partial<Settings>;\n\nexport interface Mapping {\n eventType?: EventType;\n}\n\nexport interface Env extends DestinationServer.Env {\n sendServer?: typeof sendServer;\n}\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface Destination extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\n\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\n\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\n\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n\n/**\n * Microsoft Advertising (Bing) UET Conversions API\n * https://learn.microsoft.com/en-us/advertising/guides/universal-event-tracking-capi\n */\nexport type EventType = 'pageLoad' | 'custom';\n\nexport type AdStorageConsent = 'G' | 'D';\n\nexport interface CAPIRequestBody {\n data: CAPIEvent[];\n dataProvider?: string;\n continueOnValidationError?: boolean;\n}\n\nexport interface CAPIEvent {\n eventType: EventType;\n eventId?: string;\n eventName?: string;\n eventTime: number;\n eventSourceUrl?: string;\n pageLoadId?: string;\n referrerUrl?: string;\n pageTitle?: string;\n keywords?: string;\n adStorageConsent?: AdStorageConsent;\n userData?: UserData;\n customData?: CustomData;\n}\n\n// User identity fields. Hashable: em, ph. Non-hashable: anonymousId,\n// externalId, msclkid, clientIpAddress, clientUserAgent, idfa, gaid.\nexport interface UserData {\n /** Anonymous ID. Do NOT hash. */\n anonymousId?: string;\n /** External/customer ID. Do NOT hash. */\n externalId?: string;\n /** Email, SHA-256 hashed, normalized (dots/alias stripped, lowercased) */\n em?: string;\n /** Phone number, SHA-256 hashed, trimmed */\n ph?: string;\n /** Microsoft click ID. Do NOT hash. */\n msclkid?: string;\n /** Client IP address (IPv4 or IPv6). Do NOT hash. */\n clientIpAddress?: string;\n /** Client user agent. Do NOT hash. */\n clientUserAgent?: string;\n /** iOS IDFA. Do NOT hash. */\n idfa?: string;\n /** Android advertising ID (GAID). Do NOT hash. */\n gaid?: string;\n}\n\nexport interface CustomData {\n eventCategory?: string;\n eventLabel?: string;\n eventValue?: number;\n searchTerm?: string;\n transactionId?: string;\n value?: number;\n currency?: string;\n items?: ItemData[];\n itemIds?: string[];\n pageType?: string;\n ecommTotalValue?: number;\n ecommCategory?: string;\n hotelData?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface ItemData {\n id?: string;\n quantity?: number;\n price?: number;\n name?: string;\n}\n\nexport interface ResponseBody {\n status?: string;\n requestId?: string;\n}\n","import type { Destination } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\n\n// Types\nexport * as DestinationBing from './types';\n\nexport const destinationBing: Destination = {\n type: 'bing',\n\n config: {},\n\n async init({ config: partialConfig, logger }) {\n const config = getConfig(partialConfig, logger);\n return config;\n },\n\n async push(event, context) {\n return await push(event, context);\n },\n};\n\nexport default destinationBing;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/config.ts","../src/push.ts","../src/hash.ts","../src/types/index.ts","../src/index.ts"],"sourcesContent":["import type { Config, Settings, PartialConfig } from './types';\nimport type { Logger } from '@walkeros/core';\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): Config {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { accessToken, tagId } = settings;\n\n if (!accessToken) logger.throw('Config settings accessToken missing');\n if (!tagId) logger.throw('Config settings tagId missing');\n\n const settingsConfig: Settings = {\n url: 'https://capi.uet.microsoft.com/v1/',\n dataProvider: 'walkerOS',\n ...settings,\n accessToken,\n tagId,\n };\n\n return { ...partialConfig, settings: settingsConfig };\n}\n","import type {\n CAPIEvent,\n CAPIRequestBody,\n CustomData,\n Env,\n EventType,\n PushFn,\n Settings,\n UserData,\n} from './types';\nimport { getMappingValue, isObject } from '@walkeros/core';\nimport { sendServer } from '@walkeros/server-core';\nimport { hashUserData } from './hash';\n\nexport const push: PushFn = async function (\n event,\n { config, rule, data, env, logger, collector },\n) {\n const {\n accessToken,\n tagId,\n url = 'https://capi.uet.microsoft.com/v1/',\n doNotHash,\n user_data,\n dataProvider = 'walkerOS',\n continueOnValidationError,\n } = config.settings as Settings;\n\n const eventData = isObject(data) ? data : {};\n const configData = config.data\n ? await getMappingValue(event, config.data, { collector })\n : {};\n const userDataCustom = user_data\n ? await getMappingValue(event, { map: user_data }, { collector })\n : {};\n\n // Resolve mapping-level eventType override (rule.settings.eventType)\n const ruleSettings = rule?.settings as { eventType?: EventType } | undefined;\n const eventType: EventType =\n ruleSettings?.eventType === 'pageLoad' ? 'pageLoad' : 'custom';\n\n // Build user_data from merge sources (priority: later overrides earlier)\n const eventMappedUserData = isObject(eventData.userData)\n ? (eventData.userData as UserData)\n : {};\n\n const userData: UserData = {\n ...(isObject(configData) && isObject(configData.userData)\n ? (configData.userData as UserData)\n : {}),\n ...(isObject(userDataCustom) ? (userDataCustom as UserData) : {}),\n ...eventMappedUserData,\n };\n\n // Hash identity fields (em, ph only; with Microsoft email normalization)\n const hashedUserData = await hashUserData(userData, doNotHash);\n\n // Build customData from mapped event data\n const customData: CustomData = {};\n if (isObject(eventData.customData)) {\n Object.assign(customData, eventData.customData);\n }\n for (const [key, value] of Object.entries(eventData)) {\n if (key === 'userData' || key === 'customData') continue;\n customData[key] = value;\n }\n\n // Build Bing CAPI event\n const capiEvent: CAPIEvent = {\n eventType,\n eventId: event.id,\n eventTime: Math.round((event.timestamp || Date.now()) / 1000),\n adStorageConsent: 'G',\n userData: hashedUserData,\n };\n\n if (eventType === 'custom') {\n capiEvent.eventName = rule?.name || event.name;\n }\n\n if (event.source?.url) {\n capiEvent.eventSourceUrl = event.source.url;\n }\n\n if (Object.keys(customData).length > 0) {\n capiEvent.customData = customData;\n }\n\n const body: CAPIRequestBody = {\n data: [capiEvent],\n dataProvider,\n };\n\n if (continueOnValidationError !== undefined) {\n body.continueOnValidationError = continueOnValidationError;\n }\n\n const endpoint = `${url}${tagId}/events`;\n\n logger.debug('Calling Bing UET CAPI', {\n endpoint,\n method: 'POST',\n eventType: capiEvent.eventType,\n eventName: capiEvent.eventName,\n eventId: capiEvent.eventId,\n });\n\n const sendServerFn = (env as Env)?.sendServer || sendServer;\n const result = await sendServerFn(endpoint, JSON.stringify(body), {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n });\n\n logger.debug('Bing UET CAPI response', {\n ok: isObject(result) ? result.ok : true,\n });\n\n if (isObject(result) && result.ok === false) {\n logger.throw(`Bing UET CAPI error: ${JSON.stringify(result)}`);\n }\n};\n","import type { UserData } from './types';\nimport { getHashServer } from '@walkeros/server-core';\n\n/**\n * Bing UET CAPI user data fields that must be SHA-256 hashed before sending.\n * Only `em` and `ph` are hashable; all other identity fields pass through\n * unhashed (anonymousId, externalId, msclkid, clientIpAddress,\n * clientUserAgent, idfa, gaid).\n * https://learn.microsoft.com/en-us/advertising/guides/universal-event-tracking-capi\n */\nconst keysToHash = ['em', 'ph'];\n\n/**\n * Microsoft-specific email normalization:\n * - Trim whitespace\n * - Lowercase\n * - Split at `@`\n * - Remove dots from the user portion\n * - Strip `+alias` suffix from the user portion\n * - Rejoin\n */\nexport function normalizeEmail(value: string): string {\n const trimmed = value.trim().toLowerCase();\n const atIndex = trimmed.lastIndexOf('@');\n if (atIndex < 0) return trimmed;\n\n const user = trimmed.slice(0, atIndex);\n const domain = trimmed.slice(atIndex + 1);\n\n const aliasIndex = user.indexOf('+');\n const beforeAlias = aliasIndex < 0 ? user : user.slice(0, aliasIndex);\n const normalizedUser = beforeAlias.replace(/\\./g, '');\n\n return `${normalizedUser}@${domain}`;\n}\n\nfunction normalizePhone(value: string): string {\n return value.trim();\n}\n\nfunction shouldBeHashed(key: string, doNotHash: string[] = []): boolean {\n return keysToHash.includes(key) && !doNotHash.includes(key);\n}\n\nfunction normalizeForKey(key: string, value: string): string {\n if (key === 'em') return normalizeEmail(value);\n if (key === 'ph') return normalizePhone(value);\n return value;\n}\n\nexport async function hashUserData(\n userData: UserData,\n doNotHash: string[] = [],\n): Promise<UserData> {\n const entries = await Promise.all(\n Object.entries(userData).map(async ([key, value]) => {\n if (value === undefined) return [key, value];\n if (shouldBeHashed(key, doNotHash)) {\n const normalized = normalizeForKey(key, String(value));\n return [key, await getHashServer(normalized)];\n }\n return [key, value];\n }),\n );\n\n return Object.fromEntries(entries) as UserData;\n}\n","import type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationServer, sendServer } from '@walkeros/server-core';\n\nexport interface Settings {\n accessToken: string;\n tagId: string;\n url?: string;\n doNotHash?: string[];\n user_data?: WalkerOSMapping.Map;\n dataProvider?: string;\n continueOnValidationError?: boolean;\n}\n\nexport type InitSettings = Partial<Settings>;\n\nexport interface Mapping {\n eventType?: EventType;\n}\n\nexport interface Env extends DestinationServer.Env {\n sendServer?: typeof sendServer;\n}\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface Destination extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\n\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\n\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\n\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n\n/**\n * Microsoft Advertising (Bing) UET Conversions API\n * https://learn.microsoft.com/en-us/advertising/guides/universal-event-tracking-capi\n */\nexport type EventType = 'pageLoad' | 'custom';\n\nexport type AdStorageConsent = 'G' | 'D';\n\nexport interface CAPIRequestBody {\n data: CAPIEvent[];\n dataProvider?: string;\n continueOnValidationError?: boolean;\n}\n\nexport interface CAPIEvent {\n eventType: EventType;\n eventId?: string;\n eventName?: string;\n eventTime: number;\n eventSourceUrl?: string;\n pageLoadId?: string;\n referrerUrl?: string;\n pageTitle?: string;\n keywords?: string;\n adStorageConsent?: AdStorageConsent;\n userData?: UserData;\n customData?: CustomData;\n}\n\n// User identity fields. Hashable: em, ph. Non-hashable: anonymousId,\n// externalId, msclkid, clientIpAddress, clientUserAgent, idfa, gaid.\nexport interface UserData {\n /** Anonymous ID. Do NOT hash. */\n anonymousId?: string;\n /** External/customer ID. Do NOT hash. */\n externalId?: string;\n /** Email, SHA-256 hashed, normalized (dots/alias stripped, lowercased) */\n em?: string;\n /** Phone number, SHA-256 hashed, trimmed */\n ph?: string;\n /** Microsoft click ID. Do NOT hash. */\n msclkid?: string;\n /** Client IP address (IPv4 or IPv6). Do NOT hash. */\n clientIpAddress?: string;\n /** Client user agent. Do NOT hash. */\n clientUserAgent?: string;\n /** iOS IDFA. Do NOT hash. */\n idfa?: string;\n /** Android advertising ID (GAID). Do NOT hash. */\n gaid?: string;\n}\n\nexport interface CustomData {\n eventCategory?: string;\n eventLabel?: string;\n eventValue?: number;\n searchTerm?: string;\n transactionId?: string;\n value?: number;\n currency?: string;\n items?: ItemData[];\n itemIds?: string[];\n pageType?: string;\n ecommTotalValue?: number;\n ecommCategory?: string;\n hotelData?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface ItemData {\n id?: string;\n quantity?: number;\n price?: number;\n name?: string;\n}\n\nexport interface ResponseBody {\n status?: string;\n requestId?: string;\n}\n","import type { Destination } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\n\n// Types\nexport * as DestinationBing from './types';\n\nexport const destinationBing: Destination = {\n type: 'bing',\n\n config: {},\n\n async init({ config: partialConfig, logger }) {\n const config = getConfig(partialConfig, logger);\n return config;\n },\n\n async push(event, context) {\n return await push(event, context);\n },\n};\n\nexport default destinationBing;\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$meta": {
3
3
  "package": "@walkeros/server-destination-bing",
4
- "version": "4.0.0-next-1777463920154",
4
+ "version": "4.0.0",
5
5
  "type": "destination",
6
6
  "platform": [
7
7
  "server"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@walkeros/server-destination-bing",
3
3
  "description": "Microsoft Advertising (Bing UET CAPI) server destination for walkerOS",
4
- "version": "4.0.0-next-1777463920154",
4
+ "version": "4.0.0",
5
5
  "license": "MIT",
6
6
  "exports": {
7
7
  ".": {
@@ -34,11 +34,11 @@
34
34
  "update": "npx npm-check-updates -u && npm update"
35
35
  },
36
36
  "dependencies": {
37
- "@walkeros/core": "4.0.0-next-1777463920154",
38
- "@walkeros/server-core": "4.0.0-next-1777463920154"
37
+ "@walkeros/core": "4.0.0",
38
+ "@walkeros/server-core": "4.0.0"
39
39
  },
40
40
  "devDependencies": {
41
- "@walkeros/collector": "4.0.0-next-1777463920154"
41
+ "@walkeros/collector": "4.0.0"
42
42
  },
43
43
  "repository": {
44
44
  "url": "git+https://github.com/elbwalker/walkerOS.git",