@walkeros/server-destination-datamanager 0.6.0 → 0.8.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={};function getConfig(partialConfig={},logger){const settings=partialConfig.settings||{},{destinations:destinations,eventSource:eventSource="WEB"}=settings;destinations&&0!==destinations.length||logger.throw("Config settings destinations missing or empty");const settingsConfig={...settings,destinations:destinations,eventSource:eventSource};return{...partialConfig,settings:settingsConfig}}((target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})})(index_exports,{DestinationDataManager:()=>types_exports,default:()=>index_default,destinationDataManager:()=>destinationDataManager}),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_core3=require("@walkeros/core"),import_core2=require("@walkeros/core"),import_core=require("@walkeros/core"),import_server_core=require("@walkeros/server-core");async function hashName(name,type="given"){if(!(0,import_core.isString)(name)||!name)return"";let normalized=name.trim().toLowerCase();if("given"===type){const prefixes=["mr.","mrs.","ms.","miss.","dr.","prof."];for(const prefix of prefixes)if(normalized.startsWith(prefix)){normalized=normalized.substring(prefix.length).trim();break}}if("family"===type){const suffixes=["jr.","sr.","iii","ii","iv","v"];for(const suffix of suffixes){const suffixWithSpace=` ${suffix}`;if(normalized.endsWith(suffixWithSpace)){normalized=normalized.substring(0,normalized.length-suffixWithSpace.length).trim();break}if(normalized.endsWith(suffix)){normalized=normalized.substring(0,normalized.length-suffix.length).trim();break}}}return(0,import_server_core.getHashServer)(normalized)}async function formatUserData(data){const identifiers=[];if((0,import_core2.isString)(data.email)&&data.email){const hashedEmail=await async function(email){if(!(0,import_core.isString)(email)||!email)return"";let normalized=email.trim().toLowerCase();if(normalized.endsWith("@gmail.com")||normalized.endsWith("@googlemail.com")){const[localPart,domain]=normalized.split("@");normalized=`${localPart.replace(/\./g,"")}@${domain}`}return(0,import_server_core.getHashServer)(normalized)}(data.email);hashedEmail&&identifiers.push({emailAddress:hashedEmail})}if((0,import_core2.isString)(data.phone)&&data.phone){const hashedPhone=await async function(phone){if(!(0,import_core.isString)(phone)||!phone)return"";let normalized=phone.trim();const hasPlus=normalized.startsWith("+");return normalized=normalized.replace(/\D/g,""),normalized=hasPlus||normalized.length>10?`+${normalized}`:`+1${normalized}`,(0,import_server_core.getHashServer)(normalized)}(data.phone);hashedPhone&&identifiers.push({phoneNumber:hashedPhone})}if(data.firstName||data.lastName||data.regionCode||data.postalCode){const address={};(0,import_core2.isString)(data.firstName)&&data.firstName&&(address.givenName=await hashName(data.firstName,"given")),(0,import_core2.isString)(data.lastName)&&data.lastName&&(address.familyName=await hashName(data.lastName,"family")),(0,import_core2.isString)(data.regionCode)&&data.regionCode&&(address.regionCode=data.regionCode.toUpperCase()),(0,import_core2.isString)(data.postalCode)&&data.postalCode&&(address.postalCode=data.postalCode),Object.keys(address).length>0&&identifiers.push({address:address})}if(0!==identifiers.length)return{userIdentifiers:identifiers.slice(0,10)}}async function formatEvent(event,mappedData){const dataManagerEvent={eventTimestamp:(timestamp=event.timestamp,new Date(timestamp).toISOString())};var timestamp;const data=mappedData||{};(0,import_core2.isString)(data.transactionId)&&data.transactionId&&(dataManagerEvent.transactionId=data.transactionId.substring(0,512)),(0,import_core2.isString)(data.clientId)&&data.clientId&&(dataManagerEvent.clientId=data.clientId.substring(0,255)),(0,import_core2.isString)(data.userId)&&data.userId&&(dataManagerEvent.userId=data.userId.substring(0,256));const userData=await formatUserData(data);userData&&(dataManagerEvent.userData=userData);const adIdentifiers=function(data){const identifiers={};return(0,import_core2.isString)(data.gclid)&&data.gclid&&(identifiers.gclid=data.gclid),(0,import_core2.isString)(data.gbraid)&&data.gbraid&&(identifiers.gbraid=data.gbraid),(0,import_core2.isString)(data.wbraid)&&data.wbraid&&(identifiers.wbraid=data.wbraid),(0,import_core2.isString)(data.sessionAttributes)&&data.sessionAttributes&&(identifiers.sessionAttributes=data.sessionAttributes),Object.keys(identifiers).length>0?identifiers:void 0}(data);adIdentifiers&&(dataManagerEvent.adIdentifiers=adIdentifiers),"number"==typeof data.conversionValue&&(dataManagerEvent.conversionValue=data.conversionValue),(0,import_core2.isString)(data.currency)&&data.currency&&(dataManagerEvent.currency=data.currency.substring(0,3).toUpperCase()),data.cartData&&"object"==typeof data.cartData&&(dataManagerEvent.cartData=data.cartData),(0,import_core2.isString)(data.eventName)&&data.eventName&&(dataManagerEvent.eventName=data.eventName.substring(0,40)),(0,import_core2.isString)(data.eventSource)&&data.eventSource&&(dataManagerEvent.eventSource=data.eventSource);const mappedConsent={};if("boolean"==typeof data.adUserData&&(mappedConsent.adUserData=data.adUserData?"CONSENT_GRANTED":"CONSENT_DENIED"),"boolean"==typeof data.adPersonalization&&(mappedConsent.adPersonalization=data.adPersonalization?"CONSENT_GRANTED":"CONSENT_DENIED"),0===Object.keys(mappedConsent).length){const eventConsent=function(walkerOSConsent){if(!walkerOSConsent)return;const consent={};return(0,import_core2.isDefined)(walkerOSConsent.marketing)&&(consent.adUserData=walkerOSConsent.marketing?"CONSENT_GRANTED":"CONSENT_DENIED"),(0,import_core2.isDefined)(walkerOSConsent.personalization)&&(consent.adPersonalization=walkerOSConsent.personalization?"CONSENT_GRANTED":"CONSENT_DENIED"),Object.keys(consent).length>0?consent:void 0}(event.consent);eventConsent&&(dataManagerEvent.consent=eventConsent)}else dataManagerEvent.consent=mappedConsent;return dataManagerEvent}var import_google_auth_library=require("google-auth-library"),DEFAULT_SCOPES=["https://www.googleapis.com/auth/datamanager"],AuthError=class extends Error{constructor(message,cause){super(message),this.cause=cause,this.name="DataManagerAuthError"}};var push=async function(event,{config:config,mapping:mapping,data:data,collector:collector,env:env,logger:logger}){const validatedConfig=getConfig(config,logger),{destinations:destinations,eventSource:eventSource,validateOnly:validateOnly=!1,url:url="https://datamanager.googleapis.com/v1",consent:requestConsent,testEventCode:testEventCode,userData:userData,userId:userId,clientId:clientId,sessionAttributes:sessionAttributes,consentAdUserData:consentAdUserData,consentAdPersonalization:consentAdPersonalization}=validatedConfig.settings,userDataMapped=userData?await(0,import_core3.getMappingValue)(event,{map:userData}):{},userIdMapped=userId?await(0,import_core3.getMappingValue)(event,userId):void 0,clientIdMapped=clientId?await(0,import_core3.getMappingValue)(event,clientId):void 0,sessionAttributesMapped=sessionAttributes?await(0,import_core3.getMappingValue)(event,sessionAttributes):void 0,consentAdUserDataValue="boolean"==typeof consentAdUserData?consentAdUserData:"string"==typeof consentAdUserData&&event.consent?event.consent[consentAdUserData]:void 0,consentAdPersonalizationValue="boolean"==typeof consentAdPersonalization?consentAdPersonalization:"string"==typeof consentAdPersonalization&&event.consent?event.consent[consentAdPersonalization]:void 0,settingsHelpers={};(0,import_core3.isObject)(userDataMapped)&&Object.assign(settingsHelpers,userDataMapped),void 0!==userIdMapped&&(settingsHelpers.userId=userIdMapped),void 0!==clientIdMapped&&(settingsHelpers.clientId=clientIdMapped),void 0!==sessionAttributesMapped&&(settingsHelpers.sessionAttributes=sessionAttributesMapped),void 0!==consentAdUserDataValue&&(settingsHelpers.adUserData=consentAdUserDataValue),void 0!==consentAdPersonalizationValue&&(settingsHelpers.adPersonalization=consentAdPersonalizationValue);const configData=validatedConfig.data?await(0,import_core3.getMappingValue)(event,validatedConfig.data):{},eventData=(0,import_core3.isObject)(data)?data:{},finalData={...settingsHelpers,...(0,import_core3.isObject)(configData)?configData:{},...eventData},dataManagerEvent=await formatEvent(event,finalData);dataManagerEvent.eventSource||(dataManagerEvent.eventSource=eventSource),!dataManagerEvent.consent&&requestConsent&&(dataManagerEvent.consent=requestConsent),dataManagerEvent.transactionId||logger.throw("transactionId is required");destinations.some(d=>{var _a;return"GOOGLE_ANALYTICS_PROPERTY"===(null==(_a=d.operatingAccount)?void 0:_a.accountType)})&&!dataManagerEvent.eventName&&logger.throw("eventName is required for GA4 destinations");const requestBody={events:[dataManagerEvent],destinations:destinations};requestConsent&&(requestBody.consent=requestConsent),validateOnly&&(requestBody.validateOnly=!0),testEventCode&&(requestBody.testEventCode=testEventCode);const authClient=null==env?void 0:env.authClient;if(!authClient)return logger.throw("Auth client not initialized. Ensure init() was called successfully.");let accessToken;try{accessToken=await async function(authClient){try{const tokenResponse=await authClient.getAccessToken();if(!tokenResponse.token)throw new AuthError("Auth client returned empty token");return tokenResponse.token}catch(error){throw new AuthError("Failed to obtain access token",error instanceof Error?error:void 0)}}(authClient)}catch(error){throw logger.error("Authentication failed",{error:error}),error}const fetchFn=(null==env?void 0:env.fetch)||fetch,endpoint=`${url}/events:ingest`;logger.debug("Sending to Data Manager API",{endpoint:endpoint,eventCount:requestBody.events.length,destinations:destinations.length,validateOnly:validateOnly});const response=await fetchFn(endpoint,{method:"POST",headers:{Authorization:`Bearer ${accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(requestBody)});if(!response.ok){const errorText=await response.text();logger.throw(`Data Manager API error (${response.status}): ${errorText}`)}const result=await response.json();logger.debug("API response",{status:response.status,requestId:result.requestId}),result.validationErrors&&result.validationErrors.length>0&&logger.throw(`Validation errors: ${JSON.stringify(result.validationErrors)}`)},types_exports={},destinationDataManager={type:"datamanager",config:{},async init({config:partialConfig,env:env,logger:logger}){const config=getConfig(partialConfig,logger);try{const authClient=await async function(settings){const{credentials:credentials,keyFilename:keyFilename,scopes:scopes=DEFAULT_SCOPES}=settings;try{if(credentials){const auth2=new import_google_auth_library.GoogleAuth({credentials:credentials,scopes:scopes});return await auth2.getClient()}if(keyFilename){const auth2=new import_google_auth_library.GoogleAuth({keyFilename:keyFilename,scopes:scopes});return await auth2.getClient()}const auth=new import_google_auth_library.GoogleAuth({scopes:scopes});return await auth.getClient()}catch(error){throw new AuthError("Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.",error instanceof Error?error:void 0)}}(config.settings);return logger.debug("Auth client created"),{...config,env:{...env,authClient:authClient}}}catch(error){logger.throw(`Data Manager authentication failed: ${error instanceof Error?error.message:"Unknown error"}`)}},push:async(event,{config:config,mapping:mapping,data:data,collector:collector,env:env,logger:logger})=>await push(event,{config:config,mapping:mapping,data:data,collector:collector,env:env,logger:logger})},index_default=destinationDataManager;//# sourceMappingURL=index.js.map
1
+ "use strict";var mod,__defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__hasOwnProp=Object.prototype.hasOwnProperty,index_exports={};function getConfig(partialConfig={},logger){const settings=partialConfig.settings||{},{destinations:destinations,eventSource:eventSource="WEB"}=settings;destinations&&0!==destinations.length||logger.throw("Config settings destinations missing or empty");const settingsConfig={...settings,destinations:destinations,eventSource:eventSource};return{...partialConfig,settings:settingsConfig}}((target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})})(index_exports,{DestinationDataManager:()=>types_exports,default:()=>index_default,destinationDataManager:()=>destinationDataManager}),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_core3=require("@walkeros/core"),import_core2=require("@walkeros/core"),import_core=require("@walkeros/core"),import_server_core=require("@walkeros/server-core");async function hashName(name,type="given"){if(!(0,import_core.isString)(name)||!name)return"";let normalized=name.trim().toLowerCase();if("given"===type){const prefixes=["mr.","mrs.","ms.","miss.","dr.","prof."];for(const prefix of prefixes)if(normalized.startsWith(prefix)){normalized=normalized.substring(prefix.length).trim();break}}if("family"===type){const suffixes=["jr.","sr.","iii","ii","iv","v"];for(const suffix of suffixes){const suffixWithSpace=` ${suffix}`;if(normalized.endsWith(suffixWithSpace)){normalized=normalized.substring(0,normalized.length-suffixWithSpace.length).trim();break}if(normalized.endsWith(suffix)){normalized=normalized.substring(0,normalized.length-suffix.length).trim();break}}}return(0,import_server_core.getHashServer)(normalized)}async function formatUserData(data){const identifiers=[];if((0,import_core2.isString)(data.email)&&data.email){const hashedEmail=await async function(email){if(!(0,import_core.isString)(email)||!email)return"";let normalized=email.trim().toLowerCase();if(normalized.endsWith("@gmail.com")||normalized.endsWith("@googlemail.com")){const[localPart,domain]=normalized.split("@");normalized=`${localPart.replace(/\./g,"")}@${domain}`}return(0,import_server_core.getHashServer)(normalized)}(data.email);hashedEmail&&identifiers.push({emailAddress:hashedEmail})}if((0,import_core2.isString)(data.phone)&&data.phone){const hashedPhone=await async function(phone){if(!(0,import_core.isString)(phone)||!phone)return"";let normalized=phone.trim();const hasPlus=normalized.startsWith("+");return normalized=normalized.replace(/\D/g,""),normalized=hasPlus||normalized.length>10?`+${normalized}`:`+1${normalized}`,(0,import_server_core.getHashServer)(normalized)}(data.phone);hashedPhone&&identifiers.push({phoneNumber:hashedPhone})}if(data.firstName||data.lastName||data.regionCode||data.postalCode){const address={};(0,import_core2.isString)(data.firstName)&&data.firstName&&(address.givenName=await hashName(data.firstName,"given")),(0,import_core2.isString)(data.lastName)&&data.lastName&&(address.familyName=await hashName(data.lastName,"family")),(0,import_core2.isString)(data.regionCode)&&data.regionCode&&(address.regionCode=data.regionCode.toUpperCase()),(0,import_core2.isString)(data.postalCode)&&data.postalCode&&(address.postalCode=data.postalCode),Object.keys(address).length>0&&identifiers.push({address:address})}if(0!==identifiers.length)return{userIdentifiers:identifiers.slice(0,10)}}async function formatEvent(event,mappedData){const dataManagerEvent={eventTimestamp:(timestamp=event.timestamp,new Date(timestamp).toISOString())};var timestamp;const data=mappedData||{};(0,import_core2.isString)(data.transactionId)&&data.transactionId&&(dataManagerEvent.transactionId=data.transactionId.substring(0,512)),(0,import_core2.isString)(data.clientId)&&data.clientId&&(dataManagerEvent.clientId=data.clientId.substring(0,255)),(0,import_core2.isString)(data.userId)&&data.userId&&(dataManagerEvent.userId=data.userId.substring(0,256));const userData=await formatUserData(data);userData&&(dataManagerEvent.userData=userData);const adIdentifiers=function(data){const identifiers={};return(0,import_core2.isString)(data.gclid)&&data.gclid&&(identifiers.gclid=data.gclid),(0,import_core2.isString)(data.gbraid)&&data.gbraid&&(identifiers.gbraid=data.gbraid),(0,import_core2.isString)(data.wbraid)&&data.wbraid&&(identifiers.wbraid=data.wbraid),(0,import_core2.isString)(data.sessionAttributes)&&data.sessionAttributes&&(identifiers.sessionAttributes=data.sessionAttributes),Object.keys(identifiers).length>0?identifiers:void 0}(data);adIdentifiers&&(dataManagerEvent.adIdentifiers=adIdentifiers),"number"==typeof data.conversionValue&&(dataManagerEvent.conversionValue=data.conversionValue),(0,import_core2.isString)(data.currency)&&data.currency&&(dataManagerEvent.currency=data.currency.substring(0,3).toUpperCase()),data.cartData&&"object"==typeof data.cartData&&(dataManagerEvent.cartData=data.cartData),(0,import_core2.isString)(data.eventName)&&data.eventName&&(dataManagerEvent.eventName=data.eventName.substring(0,40)),(0,import_core2.isString)(data.eventSource)&&data.eventSource&&(dataManagerEvent.eventSource=data.eventSource);const mappedConsent={};if("boolean"==typeof data.adUserData&&(mappedConsent.adUserData=data.adUserData?"CONSENT_GRANTED":"CONSENT_DENIED"),"boolean"==typeof data.adPersonalization&&(mappedConsent.adPersonalization=data.adPersonalization?"CONSENT_GRANTED":"CONSENT_DENIED"),0===Object.keys(mappedConsent).length){const eventConsent=function(walkerOSConsent){if(!walkerOSConsent)return;const consent={};return(0,import_core2.isDefined)(walkerOSConsent.marketing)&&(consent.adUserData=walkerOSConsent.marketing?"CONSENT_GRANTED":"CONSENT_DENIED"),(0,import_core2.isDefined)(walkerOSConsent.personalization)&&(consent.adPersonalization=walkerOSConsent.personalization?"CONSENT_GRANTED":"CONSENT_DENIED"),Object.keys(consent).length>0?consent:void 0}(event.consent);eventConsent&&(dataManagerEvent.consent=eventConsent)}else dataManagerEvent.consent=mappedConsent;return dataManagerEvent}var import_google_auth_library=require("google-auth-library"),DEFAULT_SCOPES=["https://www.googleapis.com/auth/datamanager"],AuthError=class extends Error{constructor(message,cause){super(message),this.cause=cause,this.name="DataManagerAuthError"}};var push=async function(event,{config:config,rule:rule,data:data,collector:collector,env:env,logger:logger}){const validatedConfig=getConfig(config,logger),{destinations:destinations,eventSource:eventSource,validateOnly:validateOnly=!1,url:url="https://datamanager.googleapis.com/v1",consent:requestConsent,testEventCode:testEventCode,userData:userData,userId:userId,clientId:clientId,sessionAttributes:sessionAttributes,consentAdUserData:consentAdUserData,consentAdPersonalization:consentAdPersonalization}=validatedConfig.settings,userDataMapped=userData?await(0,import_core3.getMappingValue)(event,{map:userData}):{},userIdMapped=userId?await(0,import_core3.getMappingValue)(event,userId):void 0,clientIdMapped=clientId?await(0,import_core3.getMappingValue)(event,clientId):void 0,sessionAttributesMapped=sessionAttributes?await(0,import_core3.getMappingValue)(event,sessionAttributes):void 0,consentAdUserDataValue="boolean"==typeof consentAdUserData?consentAdUserData:"string"==typeof consentAdUserData&&event.consent?event.consent[consentAdUserData]:void 0,consentAdPersonalizationValue="boolean"==typeof consentAdPersonalization?consentAdPersonalization:"string"==typeof consentAdPersonalization&&event.consent?event.consent[consentAdPersonalization]:void 0,settingsHelpers={};(0,import_core3.isObject)(userDataMapped)&&Object.assign(settingsHelpers,userDataMapped),void 0!==userIdMapped&&(settingsHelpers.userId=userIdMapped),void 0!==clientIdMapped&&(settingsHelpers.clientId=clientIdMapped),void 0!==sessionAttributesMapped&&(settingsHelpers.sessionAttributes=sessionAttributesMapped),void 0!==consentAdUserDataValue&&(settingsHelpers.adUserData=consentAdUserDataValue),void 0!==consentAdPersonalizationValue&&(settingsHelpers.adPersonalization=consentAdPersonalizationValue);const configData=validatedConfig.data?await(0,import_core3.getMappingValue)(event,validatedConfig.data):{},eventData=(0,import_core3.isObject)(data)?data:{},finalData={...settingsHelpers,...(0,import_core3.isObject)(configData)?configData:{},...eventData},dataManagerEvent=await formatEvent(event,finalData);dataManagerEvent.eventSource||(dataManagerEvent.eventSource=eventSource),!dataManagerEvent.consent&&requestConsent&&(dataManagerEvent.consent=requestConsent),dataManagerEvent.transactionId||logger.throw("transactionId is required");destinations.some(d=>{var _a;return"GOOGLE_ANALYTICS_PROPERTY"===(null==(_a=d.operatingAccount)?void 0:_a.accountType)})&&!dataManagerEvent.eventName&&logger.throw("eventName is required for GA4 destinations");const requestBody={events:[dataManagerEvent],destinations:destinations};requestConsent&&(requestBody.consent=requestConsent),validateOnly&&(requestBody.validateOnly=!0),testEventCode&&(requestBody.testEventCode=testEventCode);const authClient=null==env?void 0:env.authClient;if(!authClient)return logger.throw("Auth client not initialized. Ensure init() was called successfully.");let accessToken;try{accessToken=await async function(authClient){try{const tokenResponse=await authClient.getAccessToken();if(!tokenResponse.token)throw new AuthError("Auth client returned empty token");return tokenResponse.token}catch(error){throw new AuthError("Failed to obtain access token",error instanceof Error?error:void 0)}}(authClient)}catch(error){throw logger.error("Authentication failed",{error:error}),error}const fetchFn=(null==env?void 0:env.fetch)||fetch,endpoint=`${url}/events:ingest`;logger.debug("Sending to Data Manager API",{endpoint:endpoint,eventCount:requestBody.events.length,destinations:destinations.length,validateOnly:validateOnly});const response=await fetchFn(endpoint,{method:"POST",headers:{Authorization:`Bearer ${accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(requestBody)});if(!response.ok){const errorText=await response.text();logger.throw(`Data Manager API error (${response.status}): ${errorText}`)}const result=await response.json();logger.debug("API response",{status:response.status,requestId:result.requestId}),result.validationErrors&&result.validationErrors.length>0&&logger.throw(`Validation errors: ${JSON.stringify(result.validationErrors)}`)},types_exports={},destinationDataManager={type:"datamanager",config:{},async init({config:partialConfig,env:env,logger:logger}){const config=getConfig(partialConfig,logger);try{const authClient=await async function(settings){const{credentials:credentials,keyFilename:keyFilename,scopes:scopes=DEFAULT_SCOPES}=settings;try{if(credentials){const auth2=new import_google_auth_library.GoogleAuth({credentials:credentials,scopes:scopes});return await auth2.getClient()}if(keyFilename){const auth2=new import_google_auth_library.GoogleAuth({keyFilename:keyFilename,scopes:scopes});return await auth2.getClient()}const auth=new import_google_auth_library.GoogleAuth({scopes:scopes});return await auth.getClient()}catch(error){throw new AuthError("Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.",error instanceof Error?error:void 0)}}(config.settings);return logger.debug("Auth client created"),{...config,env:{...env,authClient:authClient}}}catch(error){logger.throw(`Data Manager authentication failed: ${error instanceof Error?error.message:"Unknown error"}`)}},push:async(event,context)=>await push(event,context)},index_default=destinationDataManager;//# 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/format.ts","../src/hash.ts","../src/auth.ts","../src/types/index.ts"],"sourcesContent":["import type { DestinationInterface } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\nimport { createAuthClient } from './auth';\n\nexport * as DestinationDataManager from './types';\n\nexport const destinationDataManager: DestinationInterface = {\n type: 'datamanager',\n\n config: {},\n\n async init({ config: partialConfig, env, logger }) {\n // getConfig validates required fields and returns ValidatedConfig\n const config = getConfig(partialConfig, logger);\n\n try {\n const authClient = await createAuthClient(config.settings);\n logger.debug('Auth client created');\n\n return {\n ...config,\n env: {\n ...env,\n authClient,\n },\n };\n } catch (error) {\n logger.throw(\n `Data Manager authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n },\n\n async push(event, { config, mapping, data, collector, env, logger }) {\n return await push(event, { config, mapping, data, collector, env, logger });\n },\n};\n\nexport default destinationDataManager;\n","import type {\n ValidatedConfig,\n Settings,\n PartialConfig,\n EventSource,\n} from './types';\nimport type { Logger } from '@walkeros/core';\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): ValidatedConfig {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { destinations, eventSource = 'WEB' } = settings;\n\n if (!destinations || destinations.length === 0)\n logger.throw('Config settings destinations missing or empty');\n\n const settingsConfig: Settings & { eventSource: EventSource } = {\n ...settings,\n destinations,\n eventSource,\n };\n\n return { ...partialConfig, settings: settingsConfig };\n}\n","import type { PushFn } from './types';\nimport type { IngestEventsRequest, IngestEventsResponse } from './types';\nimport { getMappingValue, isObject } from '@walkeros/core';\nimport { formatEvent, formatConsent } from './format';\nimport { getAccessToken } from './auth';\nimport { getConfig } from './config';\n\nexport const push: PushFn = async function (\n event,\n { config, mapping, data, collector, env, logger },\n) {\n // Validate config and get typed settings\n const validatedConfig = getConfig(config, logger);\n const {\n destinations,\n eventSource,\n validateOnly = false,\n url = 'https://datamanager.googleapis.com/v1',\n consent: requestConsent,\n testEventCode,\n userData,\n userId,\n clientId,\n sessionAttributes,\n consentAdUserData,\n consentAdPersonalization,\n } = validatedConfig.settings;\n\n // Extract Settings guided helpers\n const userDataMapped = userData\n ? await getMappingValue(event, { map: userData })\n : {};\n const userIdMapped = userId\n ? await getMappingValue(event, userId)\n : undefined;\n const clientIdMapped = clientId\n ? await getMappingValue(event, clientId)\n : undefined;\n const sessionAttributesMapped = sessionAttributes\n ? await getMappingValue(event, sessionAttributes)\n : undefined;\n\n // Extract consent from Settings\n const consentAdUserDataValue =\n typeof consentAdUserData === 'boolean'\n ? consentAdUserData\n : typeof consentAdUserData === 'string' && event.consent\n ? event.consent[consentAdUserData]\n : undefined;\n\n const consentAdPersonalizationValue =\n typeof consentAdPersonalization === 'boolean'\n ? consentAdPersonalization\n : typeof consentAdPersonalization === 'string' && event.consent\n ? event.consent[consentAdPersonalization]\n : undefined;\n\n // Build Settings helpers object\n const settingsHelpers: Record<string, unknown> = {};\n if (isObject(userDataMapped)) {\n Object.assign(settingsHelpers, userDataMapped);\n }\n if (userIdMapped !== undefined) settingsHelpers.userId = userIdMapped;\n if (clientIdMapped !== undefined) settingsHelpers.clientId = clientIdMapped;\n if (sessionAttributesMapped !== undefined)\n settingsHelpers.sessionAttributes = sessionAttributesMapped;\n if (consentAdUserDataValue !== undefined)\n settingsHelpers.adUserData = consentAdUserDataValue;\n if (consentAdPersonalizationValue !== undefined)\n settingsHelpers.adPersonalization = consentAdPersonalizationValue;\n\n // Get mapped data from destination config and event mapping\n const configData = validatedConfig.data\n ? await getMappingValue(event, validatedConfig.data)\n : {};\n const eventData = isObject(data) ? data : {};\n\n // Merge: Settings helpers < config.data < event mapping data\n const finalData = {\n ...settingsHelpers,\n ...(isObject(configData) ? configData : {}),\n ...eventData,\n };\n\n // Format event for Data Manager API\n const dataManagerEvent = await formatEvent(event, finalData);\n\n // Apply event source from settings (required)\n if (!dataManagerEvent.eventSource) {\n dataManagerEvent.eventSource = eventSource;\n }\n\n // Apply request-level consent if event doesn't have consent\n if (!dataManagerEvent.consent && requestConsent) {\n dataManagerEvent.consent = requestConsent;\n }\n\n // Validate required fields before API call\n if (!dataManagerEvent.transactionId) {\n logger.throw('transactionId is required');\n }\n\n // Check if any destination is GA4 (requires eventName)\n const hasGA4Destination = destinations.some(\n (d) => d.operatingAccount?.accountType === 'GOOGLE_ANALYTICS_PROPERTY',\n );\n\n if (hasGA4Destination && !dataManagerEvent.eventName) {\n logger.throw('eventName is required for GA4 destinations');\n }\n\n // Build API request\n const requestBody: IngestEventsRequest = {\n events: [dataManagerEvent],\n destinations,\n };\n\n // Add optional parameters\n if (requestConsent) {\n requestBody.consent = requestConsent;\n }\n\n if (validateOnly) {\n requestBody.validateOnly = true;\n }\n\n if (testEventCode) {\n requestBody.testEventCode = testEventCode;\n }\n\n const authClient = env?.authClient;\n if (!authClient) {\n return logger.throw(\n 'Auth client not initialized. Ensure init() was called successfully.',\n );\n }\n\n let accessToken: string;\n try {\n accessToken = await getAccessToken(authClient);\n } catch (error) {\n logger.error('Authentication failed', { error });\n throw error;\n }\n\n const fetchFn = env?.fetch || fetch;\n const endpoint = `${url}/events:ingest`;\n\n logger.debug('Sending to Data Manager API', {\n endpoint,\n eventCount: requestBody.events.length,\n destinations: destinations.length,\n validateOnly,\n });\n\n const response = await fetchFn(endpoint, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n logger.throw(`Data Manager API error (${response.status}): ${errorText}`);\n }\n\n const result: IngestEventsResponse = await response.json();\n\n logger.debug('API response', {\n status: response.status,\n requestId: result.requestId,\n });\n\n // If validation errors exist, throw them\n if (result.validationErrors && result.validationErrors.length > 0) {\n logger.throw(\n `Validation errors: ${JSON.stringify(result.validationErrors)}`,\n );\n }\n};\n","import type { WalkerOS } from '@walkeros/core';\nimport { isString, isDefined } from '@walkeros/core';\nimport type {\n Event,\n UserData,\n UserIdentifier,\n AdIdentifiers,\n Consent,\n ConsentStatus,\n} from './types';\nimport { hashEmail, hashPhone, hashName } from './hash';\n\n/**\n * Format walkerOS event timestamp to RFC 3339 format\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n *\n * walkerOS timestamp is in milliseconds, RFC 3339 format: \"2024-01-15T10:30:00Z\"\n */\nexport function formatTimestamp(timestamp: number): string {\n return new Date(timestamp).toISOString();\n}\n\n/**\n * Format user identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n *\n * User data must be explicitly mapped in the mapping configuration.\n * Max 10 identifiers per event\n */\nexport async function formatUserData(\n data: Record<string, unknown>,\n): Promise<UserData | undefined> {\n const identifiers: UserIdentifier[] = [];\n\n // Extract from mapped data only\n // Email\n if (isString(data.email) && data.email) {\n const hashedEmail = await hashEmail(data.email);\n if (hashedEmail) {\n identifiers.push({ emailAddress: hashedEmail });\n }\n }\n\n // Phone\n if (isString(data.phone) && data.phone) {\n const hashedPhone = await hashPhone(data.phone);\n if (hashedPhone) {\n identifiers.push({ phoneNumber: hashedPhone });\n }\n }\n\n // Address from mapped properties\n const hasAddress =\n data.firstName || data.lastName || data.regionCode || data.postalCode;\n\n if (hasAddress) {\n const address: Record<string, string> = {};\n\n if (isString(data.firstName) && data.firstName) {\n address.givenName = await hashName(data.firstName, 'given');\n }\n\n if (isString(data.lastName) && data.lastName) {\n address.familyName = await hashName(data.lastName, 'family');\n }\n\n // Region code is NOT hashed\n if (isString(data.regionCode) && data.regionCode) {\n address.regionCode = data.regionCode.toUpperCase();\n }\n\n // Postal code is NOT hashed\n if (isString(data.postalCode) && data.postalCode) {\n address.postalCode = data.postalCode;\n }\n\n if (Object.keys(address).length > 0) {\n identifiers.push({ address });\n }\n }\n\n // Limit to 10 identifiers\n if (identifiers.length === 0) return undefined;\n\n return {\n userIdentifiers: identifiers.slice(0, 10),\n };\n}\n\n/**\n * Extract and format attribution identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n *\n * Attribution identifiers should be mapped explicitly in the mapping configuration.\n * Example: { gclid: 'context.gclid', gbraid: 'context.gbraid' }\n */\nexport function formatAdIdentifiers(\n data: Record<string, unknown>,\n): AdIdentifiers | undefined {\n const identifiers: AdIdentifiers = {};\n\n // Extract from mapped data (already processed by mapping system)\n if (isString(data.gclid) && data.gclid) {\n identifiers.gclid = data.gclid;\n }\n if (isString(data.gbraid) && data.gbraid) {\n identifiers.gbraid = data.gbraid;\n }\n if (isString(data.wbraid) && data.wbraid) {\n identifiers.wbraid = data.wbraid;\n }\n if (isString(data.sessionAttributes) && data.sessionAttributes) {\n identifiers.sessionAttributes = data.sessionAttributes;\n }\n\n return Object.keys(identifiers).length > 0 ? identifiers : undefined;\n}\n\n/**\n * Map walkerOS consent to Data Manager consent format\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n *\n * walkerOS: { marketing: true, personalization: false }\n * Data Manager: { adUserData: 'CONSENT_GRANTED', adPersonalization: 'CONSENT_DENIED' }\n */\nexport function formatConsent(\n walkerOSConsent: WalkerOS.Consent | undefined,\n): Consent | undefined {\n if (!walkerOSConsent) return undefined;\n\n const consent: Consent = {};\n\n // Map marketing consent to adUserData\n if (isDefined(walkerOSConsent.marketing)) {\n consent.adUserData = walkerOSConsent.marketing\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // Map personalization consent to adPersonalization\n if (isDefined(walkerOSConsent.personalization)) {\n consent.adPersonalization = walkerOSConsent.personalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n return Object.keys(consent).length > 0 ? consent : undefined;\n}\n\n/**\n * Format complete event for Data Manager API\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport async function formatEvent(\n event: WalkerOS.Event,\n mappedData?: Record<string, unknown>,\n): Promise<Event> {\n const dataManagerEvent: Event = {\n eventTimestamp: formatTimestamp(event.timestamp),\n };\n\n // Use only mapped data (no fallback to event.data)\n const data = mappedData || {};\n\n // Transaction ID for deduplication\n if (isString(data.transactionId) && data.transactionId) {\n dataManagerEvent.transactionId = data.transactionId.substring(0, 512);\n }\n\n // Client ID (GA)\n if (isString(data.clientId) && data.clientId) {\n dataManagerEvent.clientId = data.clientId.substring(0, 255);\n }\n\n // User ID\n if (isString(data.userId) && data.userId) {\n dataManagerEvent.userId = data.userId.substring(0, 256);\n }\n\n // User data\n const userData = await formatUserData(data);\n if (userData) {\n dataManagerEvent.userData = userData;\n }\n\n // Attribution identifiers\n const adIdentifiers = formatAdIdentifiers(data);\n if (adIdentifiers) {\n dataManagerEvent.adIdentifiers = adIdentifiers;\n }\n\n // Conversion value\n if (typeof data.conversionValue === 'number') {\n dataManagerEvent.conversionValue = data.conversionValue;\n }\n\n // Currency\n if (isString(data.currency) && data.currency) {\n dataManagerEvent.currency = data.currency.substring(0, 3).toUpperCase();\n }\n\n // Cart data\n if (data.cartData && typeof data.cartData === 'object') {\n dataManagerEvent.cartData = data.cartData as Event['cartData'];\n }\n\n // Event name (for GA4)\n if (isString(data.eventName) && data.eventName) {\n dataManagerEvent.eventName = data.eventName.substring(0, 40);\n }\n\n // Event source\n if (isString(data.eventSource) && data.eventSource) {\n dataManagerEvent.eventSource = data.eventSource as Event['eventSource'];\n }\n\n // Consent - check mapped data first, then fallback to event.consent\n const mappedConsent: Consent = {};\n\n // Check for mapped consent values (from Settings or event mapping)\n if (typeof data.adUserData === 'boolean') {\n mappedConsent.adUserData = data.adUserData\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n if (typeof data.adPersonalization === 'boolean') {\n mappedConsent.adPersonalization = data.adPersonalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // If no mapped consent, fall back to event.consent\n if (Object.keys(mappedConsent).length === 0) {\n const eventConsent = formatConsent(event.consent);\n if (eventConsent) {\n dataManagerEvent.consent = eventConsent;\n }\n } else {\n dataManagerEvent.consent = mappedConsent;\n }\n\n return dataManagerEvent;\n}\n","import { isString } from '@walkeros/core';\nimport { getHashServer } from '@walkeros/server-core';\n\n/**\n * Normalize email address according to Google Data Manager requirements\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#email\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove dots (.) for gmail.com and googlemail.com\n * 4. SHA-256 hash\n */\nexport async function hashEmail(email: string): Promise<string> {\n if (!isString(email) || !email) return '';\n\n // Trim and lowercase\n let normalized = email.trim().toLowerCase();\n\n // Remove dots for Gmail addresses\n if (\n normalized.endsWith('@gmail.com') ||\n normalized.endsWith('@googlemail.com')\n ) {\n const [localPart, domain] = normalized.split('@');\n normalized = `${localPart.replace(/\\./g, '')}@${domain}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize phone number to E.164 format and hash\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#phone\n *\n * E.164 format: +[country code][number] (max 15 digits after +)\n * Example: +18005550100\n *\n * 1. Remove all non-digit characters except leading +\n * 2. Ensure it starts with +\n * 3. SHA-256 hash\n */\nexport async function hashPhone(phone: string): Promise<string> {\n if (!isString(phone) || !phone) return '';\n\n // Remove all non-digit characters except + at the start\n let normalized = phone.trim();\n\n // Extract country code if present\n const hasPlus = normalized.startsWith('+');\n\n // Remove all non-digits\n normalized = normalized.replace(/\\D/g, '');\n\n // Add + prefix if it was there or if number is long enough\n if (hasPlus || normalized.length > 10) {\n normalized = `+${normalized}`;\n } else {\n // Assume US number if no country code (default behavior)\n normalized = `+1${normalized}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize and hash a name (first or last name)\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#name\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove common prefixes (Mr., Mrs., Dr.) for first names\n * 4. Remove common suffixes (Jr., Sr., III) for last names\n * 5. SHA-256 hash\n */\nexport async function hashName(\n name: string,\n type: 'given' | 'family' = 'given',\n): Promise<string> {\n if (!isString(name) || !name) return '';\n\n // Trim and lowercase\n let normalized = name.trim().toLowerCase();\n\n // Remove prefixes for given names\n if (type === 'given') {\n const prefixes = ['mr.', 'mrs.', 'ms.', 'miss.', 'dr.', 'prof.'];\n for (const prefix of prefixes) {\n if (normalized.startsWith(prefix)) {\n normalized = normalized.substring(prefix.length).trim();\n break;\n }\n }\n }\n\n // Remove suffixes for family names (check with and without space)\n // Sort by length (longest first) to match \"iii\" before \"ii\"\n if (type === 'family') {\n const suffixes = ['jr.', 'sr.', 'iii', 'ii', 'iv', 'v'];\n for (const suffix of suffixes) {\n // Check for suffix with space before it (e.g., \" jr.\")\n const suffixWithSpace = ` ${suffix}`;\n if (normalized.endsWith(suffixWithSpace)) {\n normalized = normalized\n .substring(0, normalized.length - suffixWithSpace.length)\n .trim();\n break;\n }\n // Check for suffix without space\n if (normalized.endsWith(suffix)) {\n normalized = normalized\n .substring(0, normalized.length - suffix.length)\n .trim();\n break;\n }\n }\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Hash multiple email addresses\n */\nexport async function hashEmails(emails: string[]): Promise<string[]> {\n return Promise.all(emails.map((email) => hashEmail(email)));\n}\n\n/**\n * Hash multiple phone numbers\n */\nexport async function hashPhones(phones: string[]): Promise<string[]> {\n return Promise.all(phones.map((phone) => hashPhone(phone)));\n}\n","import { GoogleAuth, type OAuth2Client } from 'google-auth-library';\nimport type { Settings } from './types';\n\nconst DEFAULT_SCOPES = ['https://www.googleapis.com/auth/datamanager'];\n\n/**\n * Authentication error with cause tracking\n */\nexport class AuthError extends Error {\n constructor(\n message: string,\n public cause?: Error,\n ) {\n super(message);\n this.name = 'DataManagerAuthError';\n }\n}\n\n/**\n * Creates Google Auth client based on settings\n *\n * Authentication priority:\n * 1. credentials (inline service account) - if provided\n * 2. keyFilename (service account file) - if provided\n * 3. Application Default Credentials (ADC) - automatic fallback\n * - GOOGLE_APPLICATION_CREDENTIALS env var\n * - GCP metadata server (Cloud Functions, Cloud Run, GCE)\n *\n * @param settings - Configuration with auth options\n * @returns OAuth2Client for token retrieval\n * @throws AuthError if authentication fails\n */\nexport async function createAuthClient(\n settings: Settings,\n): Promise<OAuth2Client> {\n const { credentials, keyFilename, scopes = DEFAULT_SCOPES } = settings;\n\n try {\n if (credentials) {\n const auth = new GoogleAuth({\n credentials,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n if (keyFilename) {\n const auth = new GoogleAuth({\n keyFilename,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n const auth = new GoogleAuth({ scopes });\n return (await auth.getClient()) as OAuth2Client;\n } catch (error) {\n throw new AuthError(\n 'Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.',\n error instanceof Error ? error : undefined,\n );\n }\n}\n\n/**\n * Gets access token from auth client\n * Automatically returns cached token if valid or refreshes if expired\n *\n * @param authClient - OAuth2 client from createAuthClient()\n * @returns Fresh access token\n * @throws AuthError if token retrieval fails\n */\nexport async function getAccessToken(\n authClient: OAuth2Client,\n): Promise<string> {\n try {\n const tokenResponse = await authClient.getAccessToken();\n\n if (!tokenResponse.token) {\n throw new AuthError('Auth client returned empty token');\n }\n\n return tokenResponse.token;\n } catch (error) {\n throw new AuthError(\n 'Failed to obtain access token',\n error instanceof Error ? error : undefined,\n );\n }\n}\n","import type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationServer } from '@walkeros/server-core';\nimport type { OAuth2Client } from 'google-auth-library';\n\nexport interface Settings {\n /**\n * Service account credentials (client_email + private_key)\n * Recommended for serverless environments (AWS Lambda, Docker, etc.)\n */\n credentials?: {\n client_email: string;\n private_key: string;\n };\n\n /**\n * Path to service account JSON file\n * For local development or environments with filesystem access\n */\n keyFilename?: string;\n\n /**\n * OAuth scopes for Data Manager API\n * @default ['https://www.googleapis.com/auth/datamanager']\n */\n scopes?: string[];\n\n /** Array of destination accounts and conversion actions/user lists */\n destinations: Destination[];\n\n /** Event source for all events. Defaults to WEB if not specified */\n eventSource?: EventSource;\n\n /** Maximum number of events to batch before sending (max 2000) */\n batchSize?: number;\n\n /** Time in milliseconds to wait before auto-flushing batch */\n batchInterval?: number;\n\n /** If true, validate request without ingestion (testing mode) */\n validateOnly?: boolean;\n\n /** Override API endpoint (for testing) */\n url?: string;\n\n /** Request-level consent for all events */\n consent?: Consent;\n\n /** Test event code for debugging (optional) */\n testEventCode?: string;\n\n /** Guided helpers: User data mapping (applies to all events) */\n userData?: WalkerOSMapping.Map;\n\n /** Guided helper: First-party user ID */\n userId?: WalkerOSMapping.Value;\n\n /** Guided helper: GA4 client ID */\n clientId?: WalkerOSMapping.Value;\n\n /** Guided helper: Privacy-safe attribution (Google's sessionAttributes) */\n sessionAttributes?: WalkerOSMapping.Value;\n\n /** Consent mapping: Map consent field to adUserData (string = field name, boolean = static value) */\n consentAdUserData?: string | boolean;\n\n /** Consent mapping: Map consent field to adPersonalization (string = field name, boolean = static value) */\n consentAdPersonalization?: string | boolean;\n}\n\nexport interface Mapping {\n // Attribution identifiers (optional, for explicit mapping)\n gclid?: WalkerOSMapping.Value;\n gbraid?: WalkerOSMapping.Value;\n wbraid?: WalkerOSMapping.Value;\n sessionAttributes?: WalkerOSMapping.Value;\n}\n\nexport interface Env extends DestinationServer.Env {\n fetch?: typeof fetch;\n authClient?: OAuth2Client | null;\n}\n\nexport type InitSettings = Partial<Settings>;\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface DestinationInterface\n extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\n/**\n * Config after validation - settings is guaranteed to exist with required fields\n * Use this type after calling getConfig() to get proper type narrowing\n * After validation, eventSource is always set (defaults to 'WEB')\n */\nexport type ValidatedConfig = Omit<Config, 'settings'> & {\n settings: Settings & { eventSource: EventSource };\n};\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// Google Data Manager API Types\n// https://developers.google.com/data-manager/api/reference\n\n/**\n * Destination account and product identifier\n * https://developers.google.com/data-manager/api/reference/rest/v1/Destination\n */\nexport interface Destination {\n /** Reference identifier for this destination */\n reference?: string;\n\n /** Login account (account initiating the request) */\n loginAccount?: ProductAccount;\n\n /** Linked account (child account linked to login account) */\n linkedAccount?: ProductAccount;\n\n /** Operating account (account where data is sent) */\n operatingAccount?: ProductAccount;\n\n /** Product-specific destination ID (conversion action or user list) */\n productDestinationId?: string;\n}\n\n/**\n * Product account information\n */\nexport interface ProductAccount {\n /** Account ID (e.g., \"123-456-7890\" for Google Ads) */\n accountId: string;\n\n /** Type of account */\n accountType: AccountType;\n}\n\nexport type AccountType =\n | 'ACCOUNT_TYPE_UNSPECIFIED'\n | 'GOOGLE_ADS'\n | 'DISPLAY_VIDEO_ADVERTISER'\n | 'DISPLAY_VIDEO_PARTNER'\n | 'GOOGLE_ANALYTICS_PROPERTY'\n | 'DATA_PARTNER';\n\nexport type EventSource = 'WEB' | 'APP' | 'IN_STORE' | 'PHONE' | 'OTHER';\n\n/**\n * Consent for Digital Markets Act (DMA) compliance\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n */\nexport interface Consent {\n /** Consent for data collection and use */\n adUserData?: ConsentStatus;\n\n /** Consent for ad personalization */\n adPersonalization?: ConsentStatus;\n}\n\nexport type ConsentStatus = 'CONSENT_GRANTED' | 'CONSENT_DENIED';\n\n/**\n * Request body for events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/events/ingest\n */\nexport interface IngestEventsRequest {\n /** Array of destinations for these events (max 10) */\n destinations: Destination[];\n\n /** Array of events to ingest (max 2000) */\n events: Event[];\n\n /** Request-level consent (overridden by event-level) */\n consent?: Consent;\n\n /** If true, validate without ingestion */\n validateOnly?: boolean;\n\n /** Test event code for debugging */\n testEventCode?: string;\n}\n\n/**\n * Single event for ingestion\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport interface Event {\n /** Destination references for routing */\n destinationReferences?: string[];\n\n /** Transaction ID for deduplication (max 512 chars) */\n transactionId?: string;\n\n /** Event timestamp in RFC 3339 format */\n eventTimestamp?: string;\n\n /** Last updated timestamp in RFC 3339 format */\n lastUpdatedTimestamp?: string;\n\n /** User data with identifiers (max 10 identifiers) */\n userData?: UserData;\n\n /** Event-level consent (overrides request-level) */\n consent?: Consent;\n\n /** Attribution identifiers */\n adIdentifiers?: AdIdentifiers;\n\n /** Currency code (ISO 4217, 3 chars) */\n currency?: string;\n\n /** Conversion value */\n conversionValue?: number;\n\n /** Source of the event */\n eventSource?: EventSource;\n\n /** Device information for the event */\n eventDeviceInfo?: DeviceInfo;\n\n /** Shopping cart data */\n cartData?: CartData;\n\n /** Custom variables for the event */\n customVariables?: CustomVariable[];\n\n /** Experimental fields (subject to change) */\n experimentalFields?: ExperimentalField[];\n\n /** User properties */\n userProperties?: UserProperties;\n\n /** Event name for GA4 (max 40 chars, required for GA4) */\n eventName?: string;\n\n /** Google Analytics client ID (max 255 chars) */\n clientId?: string;\n\n /** First-party user ID (max 256 chars) */\n userId?: string;\n\n /** Additional event parameters */\n additionalEventParameters?: EventParameter[];\n}\n\n/**\n * Device information\n */\nexport interface DeviceInfo {\n /** User agent string */\n userAgent?: string;\n}\n\n/**\n * Custom variable\n */\nexport interface CustomVariable {\n /** Variable name */\n name?: string;\n\n /** Variable value */\n value?: string;\n}\n\n/**\n * Experimental field\n */\nexport interface ExperimentalField {\n /** Field name */\n name?: string;\n\n /** Field value */\n value?: string;\n}\n\n/**\n * User properties\n */\nexport interface UserProperties {\n /** Property values */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Event parameter\n */\nexport interface EventParameter {\n /** Parameter name */\n name?: string;\n\n /** Parameter value */\n value?: string | number;\n}\n\n/**\n * User data with identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n */\nexport interface UserData {\n /** Array of user identifiers (max 10) */\n userIdentifiers: UserIdentifier[];\n}\n\n/**\n * User identifier (email, phone, or address)\n */\nexport type UserIdentifier =\n | { emailAddress: string }\n | { phoneNumber: string }\n | { address: Address };\n\n/**\n * Address for user identification\n * https://developers.google.com/data-manager/api/reference/rest/v1/Address\n */\nexport interface Address {\n /** Given name (first name) - SHA-256 hashed */\n givenName?: string;\n\n /** Family name (last name) - SHA-256 hashed */\n familyName?: string;\n\n /** ISO-3166-1 alpha-2 country code - NOT hashed (e.g., \"US\", \"GB\") */\n regionCode?: string;\n\n /** Postal code - NOT hashed */\n postalCode?: string;\n}\n\n/**\n * Attribution identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n */\nexport interface AdIdentifiers {\n /** Session attributes (privacy-safe attribution) */\n sessionAttributes?: string;\n\n /** Google Click ID (primary attribution) */\n gclid?: string;\n\n /** iOS attribution identifier (post-ATT) */\n gbraid?: string;\n\n /** Web-to-app attribution identifier */\n wbraid?: string;\n\n /** Device information for landing page */\n landingPageDeviceInfo?: DeviceInfo;\n}\n\n/**\n * Shopping cart data\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartData\n */\nexport interface CartData {\n /** Array of cart items (max 200) */\n items: CartItem[];\n}\n\n/**\n * Single cart item\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartItem\n */\nexport interface CartItem {\n /** Merchant product ID (max 127 chars) */\n merchantProductId?: string;\n\n /** Item price */\n price?: number;\n\n /** Item quantity */\n quantity?: number;\n}\n\n/**\n * Response from events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/IngestEventsResponse\n */\nexport interface IngestEventsResponse {\n /** Unique request ID for status checking */\n requestId: string;\n\n /** Validation errors (only if validateOnly=true) */\n validationErrors?: ValidationError[];\n}\n\n/**\n * Validation error\n */\nexport interface ValidationError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Field path that caused the error */\n fieldPath?: string;\n}\n\n/**\n * Request status response\n * https://developers.google.com/data-manager/api/reference/rest/v1/requestStatus/retrieve\n */\nexport interface RequestStatusResponse {\n /** Unique request ID */\n requestId: string;\n\n /** Processing state */\n state: RequestState;\n\n /** Number of events successfully ingested */\n eventsIngested?: number;\n\n /** Number of events that failed */\n eventsFailed?: number;\n\n /** Array of errors (if any) */\n errors?: RequestError[];\n}\n\nexport type RequestState =\n | 'STATE_UNSPECIFIED'\n | 'PENDING'\n | 'PROCESSING'\n | 'SUCCEEDED'\n | 'FAILED'\n | 'PARTIALLY_SUCCEEDED';\n\n/**\n * Request error\n */\nexport interface RequestError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Number of events affected by this error */\n eventCount?: number;\n}\n"],"mappings":"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","names":["import_core","import_core","auth"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/push.ts","../src/format.ts","../src/hash.ts","../src/auth.ts","../src/types/index.ts"],"sourcesContent":["import type { DestinationInterface } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\nimport { createAuthClient } from './auth';\n\nexport * as DestinationDataManager from './types';\n\nexport const destinationDataManager: DestinationInterface = {\n type: 'datamanager',\n\n config: {},\n\n async init({ config: partialConfig, env, logger }) {\n // getConfig validates required fields and returns ValidatedConfig\n const config = getConfig(partialConfig, logger);\n\n try {\n const authClient = await createAuthClient(config.settings);\n logger.debug('Auth client created');\n\n return {\n ...config,\n env: {\n ...env,\n authClient,\n },\n };\n } catch (error) {\n logger.throw(\n `Data Manager authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n },\n\n async push(event, context) {\n return await push(event, context);\n },\n};\n\nexport default destinationDataManager;\n","import type {\n ValidatedConfig,\n Settings,\n PartialConfig,\n EventSource,\n} from './types';\nimport type { Logger } from '@walkeros/core';\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): ValidatedConfig {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { destinations, eventSource = 'WEB' } = settings;\n\n if (!destinations || destinations.length === 0)\n logger.throw('Config settings destinations missing or empty');\n\n const settingsConfig: Settings & { eventSource: EventSource } = {\n ...settings,\n destinations,\n eventSource,\n };\n\n return { ...partialConfig, settings: settingsConfig };\n}\n","import type { PushFn } from './types';\nimport type { IngestEventsRequest, IngestEventsResponse } from './types';\nimport { getMappingValue, isObject } from '@walkeros/core';\nimport { formatEvent, formatConsent } from './format';\nimport { getAccessToken } from './auth';\nimport { getConfig } from './config';\n\nexport const push: PushFn = async function (\n event,\n { config, rule, data, collector, env, logger },\n) {\n // Validate config and get typed settings\n const validatedConfig = getConfig(config, logger);\n const {\n destinations,\n eventSource,\n validateOnly = false,\n url = 'https://datamanager.googleapis.com/v1',\n consent: requestConsent,\n testEventCode,\n userData,\n userId,\n clientId,\n sessionAttributes,\n consentAdUserData,\n consentAdPersonalization,\n } = validatedConfig.settings;\n\n // Extract Settings guided helpers\n const userDataMapped = userData\n ? await getMappingValue(event, { map: userData })\n : {};\n const userIdMapped = userId\n ? await getMappingValue(event, userId)\n : undefined;\n const clientIdMapped = clientId\n ? await getMappingValue(event, clientId)\n : undefined;\n const sessionAttributesMapped = sessionAttributes\n ? await getMappingValue(event, sessionAttributes)\n : undefined;\n\n // Extract consent from Settings\n const consentAdUserDataValue =\n typeof consentAdUserData === 'boolean'\n ? consentAdUserData\n : typeof consentAdUserData === 'string' && event.consent\n ? event.consent[consentAdUserData]\n : undefined;\n\n const consentAdPersonalizationValue =\n typeof consentAdPersonalization === 'boolean'\n ? consentAdPersonalization\n : typeof consentAdPersonalization === 'string' && event.consent\n ? event.consent[consentAdPersonalization]\n : undefined;\n\n // Build Settings helpers object\n const settingsHelpers: Record<string, unknown> = {};\n if (isObject(userDataMapped)) {\n Object.assign(settingsHelpers, userDataMapped);\n }\n if (userIdMapped !== undefined) settingsHelpers.userId = userIdMapped;\n if (clientIdMapped !== undefined) settingsHelpers.clientId = clientIdMapped;\n if (sessionAttributesMapped !== undefined)\n settingsHelpers.sessionAttributes = sessionAttributesMapped;\n if (consentAdUserDataValue !== undefined)\n settingsHelpers.adUserData = consentAdUserDataValue;\n if (consentAdPersonalizationValue !== undefined)\n settingsHelpers.adPersonalization = consentAdPersonalizationValue;\n\n // Get mapped data from destination config and event mapping\n const configData = validatedConfig.data\n ? await getMappingValue(event, validatedConfig.data)\n : {};\n const eventData = isObject(data) ? data : {};\n\n // Merge: Settings helpers < config.data < event mapping data\n const finalData = {\n ...settingsHelpers,\n ...(isObject(configData) ? configData : {}),\n ...eventData,\n };\n\n // Format event for Data Manager API\n const dataManagerEvent = await formatEvent(event, finalData);\n\n // Apply event source from settings (required)\n if (!dataManagerEvent.eventSource) {\n dataManagerEvent.eventSource = eventSource;\n }\n\n // Apply request-level consent if event doesn't have consent\n if (!dataManagerEvent.consent && requestConsent) {\n dataManagerEvent.consent = requestConsent;\n }\n\n // Validate required fields before API call\n if (!dataManagerEvent.transactionId) {\n logger.throw('transactionId is required');\n }\n\n // Check if any destination is GA4 (requires eventName)\n const hasGA4Destination = destinations.some(\n (d) => d.operatingAccount?.accountType === 'GOOGLE_ANALYTICS_PROPERTY',\n );\n\n if (hasGA4Destination && !dataManagerEvent.eventName) {\n logger.throw('eventName is required for GA4 destinations');\n }\n\n // Build API request\n const requestBody: IngestEventsRequest = {\n events: [dataManagerEvent],\n destinations,\n };\n\n // Add optional parameters\n if (requestConsent) {\n requestBody.consent = requestConsent;\n }\n\n if (validateOnly) {\n requestBody.validateOnly = true;\n }\n\n if (testEventCode) {\n requestBody.testEventCode = testEventCode;\n }\n\n const authClient = env?.authClient;\n if (!authClient) {\n return logger.throw(\n 'Auth client not initialized. Ensure init() was called successfully.',\n );\n }\n\n let accessToken: string;\n try {\n accessToken = await getAccessToken(authClient);\n } catch (error) {\n logger.error('Authentication failed', { error });\n throw error;\n }\n\n const fetchFn = env?.fetch || fetch;\n const endpoint = `${url}/events:ingest`;\n\n logger.debug('Sending to Data Manager API', {\n endpoint,\n eventCount: requestBody.events.length,\n destinations: destinations.length,\n validateOnly,\n });\n\n const response = await fetchFn(endpoint, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n logger.throw(`Data Manager API error (${response.status}): ${errorText}`);\n }\n\n const result: IngestEventsResponse = await response.json();\n\n logger.debug('API response', {\n status: response.status,\n requestId: result.requestId,\n });\n\n // If validation errors exist, throw them\n if (result.validationErrors && result.validationErrors.length > 0) {\n logger.throw(\n `Validation errors: ${JSON.stringify(result.validationErrors)}`,\n );\n }\n};\n","import type { WalkerOS } from '@walkeros/core';\nimport { isString, isDefined } from '@walkeros/core';\nimport type {\n Event,\n UserData,\n UserIdentifier,\n AdIdentifiers,\n Consent,\n ConsentStatus,\n} from './types';\nimport { hashEmail, hashPhone, hashName } from './hash';\n\n/**\n * Format walkerOS event timestamp to RFC 3339 format\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n *\n * walkerOS timestamp is in milliseconds, RFC 3339 format: \"2024-01-15T10:30:00Z\"\n */\nexport function formatTimestamp(timestamp: number): string {\n return new Date(timestamp).toISOString();\n}\n\n/**\n * Format user identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n *\n * User data must be explicitly mapped in the mapping configuration.\n * Max 10 identifiers per event\n */\nexport async function formatUserData(\n data: Record<string, unknown>,\n): Promise<UserData | undefined> {\n const identifiers: UserIdentifier[] = [];\n\n // Extract from mapped data only\n // Email\n if (isString(data.email) && data.email) {\n const hashedEmail = await hashEmail(data.email);\n if (hashedEmail) {\n identifiers.push({ emailAddress: hashedEmail });\n }\n }\n\n // Phone\n if (isString(data.phone) && data.phone) {\n const hashedPhone = await hashPhone(data.phone);\n if (hashedPhone) {\n identifiers.push({ phoneNumber: hashedPhone });\n }\n }\n\n // Address from mapped properties\n const hasAddress =\n data.firstName || data.lastName || data.regionCode || data.postalCode;\n\n if (hasAddress) {\n const address: Record<string, string> = {};\n\n if (isString(data.firstName) && data.firstName) {\n address.givenName = await hashName(data.firstName, 'given');\n }\n\n if (isString(data.lastName) && data.lastName) {\n address.familyName = await hashName(data.lastName, 'family');\n }\n\n // Region code is NOT hashed\n if (isString(data.regionCode) && data.regionCode) {\n address.regionCode = data.regionCode.toUpperCase();\n }\n\n // Postal code is NOT hashed\n if (isString(data.postalCode) && data.postalCode) {\n address.postalCode = data.postalCode;\n }\n\n if (Object.keys(address).length > 0) {\n identifiers.push({ address });\n }\n }\n\n // Limit to 10 identifiers\n if (identifiers.length === 0) return undefined;\n\n return {\n userIdentifiers: identifiers.slice(0, 10),\n };\n}\n\n/**\n * Extract and format attribution identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n *\n * Attribution identifiers should be mapped explicitly in the mapping configuration.\n * Example: { gclid: 'context.gclid', gbraid: 'context.gbraid' }\n */\nexport function formatAdIdentifiers(\n data: Record<string, unknown>,\n): AdIdentifiers | undefined {\n const identifiers: AdIdentifiers = {};\n\n // Extract from mapped data (already processed by mapping system)\n if (isString(data.gclid) && data.gclid) {\n identifiers.gclid = data.gclid;\n }\n if (isString(data.gbraid) && data.gbraid) {\n identifiers.gbraid = data.gbraid;\n }\n if (isString(data.wbraid) && data.wbraid) {\n identifiers.wbraid = data.wbraid;\n }\n if (isString(data.sessionAttributes) && data.sessionAttributes) {\n identifiers.sessionAttributes = data.sessionAttributes;\n }\n\n return Object.keys(identifiers).length > 0 ? identifiers : undefined;\n}\n\n/**\n * Map walkerOS consent to Data Manager consent format\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n *\n * walkerOS: { marketing: true, personalization: false }\n * Data Manager: { adUserData: 'CONSENT_GRANTED', adPersonalization: 'CONSENT_DENIED' }\n */\nexport function formatConsent(\n walkerOSConsent: WalkerOS.Consent | undefined,\n): Consent | undefined {\n if (!walkerOSConsent) return undefined;\n\n const consent: Consent = {};\n\n // Map marketing consent to adUserData\n if (isDefined(walkerOSConsent.marketing)) {\n consent.adUserData = walkerOSConsent.marketing\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // Map personalization consent to adPersonalization\n if (isDefined(walkerOSConsent.personalization)) {\n consent.adPersonalization = walkerOSConsent.personalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n return Object.keys(consent).length > 0 ? consent : undefined;\n}\n\n/**\n * Format complete event for Data Manager API\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport async function formatEvent(\n event: WalkerOS.Event,\n mappedData?: Record<string, unknown>,\n): Promise<Event> {\n const dataManagerEvent: Event = {\n eventTimestamp: formatTimestamp(event.timestamp),\n };\n\n // Use only mapped data (no fallback to event.data)\n const data = mappedData || {};\n\n // Transaction ID for deduplication\n if (isString(data.transactionId) && data.transactionId) {\n dataManagerEvent.transactionId = data.transactionId.substring(0, 512);\n }\n\n // Client ID (GA)\n if (isString(data.clientId) && data.clientId) {\n dataManagerEvent.clientId = data.clientId.substring(0, 255);\n }\n\n // User ID\n if (isString(data.userId) && data.userId) {\n dataManagerEvent.userId = data.userId.substring(0, 256);\n }\n\n // User data\n const userData = await formatUserData(data);\n if (userData) {\n dataManagerEvent.userData = userData;\n }\n\n // Attribution identifiers\n const adIdentifiers = formatAdIdentifiers(data);\n if (adIdentifiers) {\n dataManagerEvent.adIdentifiers = adIdentifiers;\n }\n\n // Conversion value\n if (typeof data.conversionValue === 'number') {\n dataManagerEvent.conversionValue = data.conversionValue;\n }\n\n // Currency\n if (isString(data.currency) && data.currency) {\n dataManagerEvent.currency = data.currency.substring(0, 3).toUpperCase();\n }\n\n // Cart data\n if (data.cartData && typeof data.cartData === 'object') {\n dataManagerEvent.cartData = data.cartData as Event['cartData'];\n }\n\n // Event name (for GA4)\n if (isString(data.eventName) && data.eventName) {\n dataManagerEvent.eventName = data.eventName.substring(0, 40);\n }\n\n // Event source\n if (isString(data.eventSource) && data.eventSource) {\n dataManagerEvent.eventSource = data.eventSource as Event['eventSource'];\n }\n\n // Consent - check mapped data first, then fallback to event.consent\n const mappedConsent: Consent = {};\n\n // Check for mapped consent values (from Settings or event mapping)\n if (typeof data.adUserData === 'boolean') {\n mappedConsent.adUserData = data.adUserData\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n if (typeof data.adPersonalization === 'boolean') {\n mappedConsent.adPersonalization = data.adPersonalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // If no mapped consent, fall back to event.consent\n if (Object.keys(mappedConsent).length === 0) {\n const eventConsent = formatConsent(event.consent);\n if (eventConsent) {\n dataManagerEvent.consent = eventConsent;\n }\n } else {\n dataManagerEvent.consent = mappedConsent;\n }\n\n return dataManagerEvent;\n}\n","import { isString } from '@walkeros/core';\nimport { getHashServer } from '@walkeros/server-core';\n\n/**\n * Normalize email address according to Google Data Manager requirements\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#email\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove dots (.) for gmail.com and googlemail.com\n * 4. SHA-256 hash\n */\nexport async function hashEmail(email: string): Promise<string> {\n if (!isString(email) || !email) return '';\n\n // Trim and lowercase\n let normalized = email.trim().toLowerCase();\n\n // Remove dots for Gmail addresses\n if (\n normalized.endsWith('@gmail.com') ||\n normalized.endsWith('@googlemail.com')\n ) {\n const [localPart, domain] = normalized.split('@');\n normalized = `${localPart.replace(/\\./g, '')}@${domain}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize phone number to E.164 format and hash\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#phone\n *\n * E.164 format: +[country code][number] (max 15 digits after +)\n * Example: +18005550100\n *\n * 1. Remove all non-digit characters except leading +\n * 2. Ensure it starts with +\n * 3. SHA-256 hash\n */\nexport async function hashPhone(phone: string): Promise<string> {\n if (!isString(phone) || !phone) return '';\n\n // Remove all non-digit characters except + at the start\n let normalized = phone.trim();\n\n // Extract country code if present\n const hasPlus = normalized.startsWith('+');\n\n // Remove all non-digits\n normalized = normalized.replace(/\\D/g, '');\n\n // Add + prefix if it was there or if number is long enough\n if (hasPlus || normalized.length > 10) {\n normalized = `+${normalized}`;\n } else {\n // Assume US number if no country code (default behavior)\n normalized = `+1${normalized}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize and hash a name (first or last name)\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#name\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove common prefixes (Mr., Mrs., Dr.) for first names\n * 4. Remove common suffixes (Jr., Sr., III) for last names\n * 5. SHA-256 hash\n */\nexport async function hashName(\n name: string,\n type: 'given' | 'family' = 'given',\n): Promise<string> {\n if (!isString(name) || !name) return '';\n\n // Trim and lowercase\n let normalized = name.trim().toLowerCase();\n\n // Remove prefixes for given names\n if (type === 'given') {\n const prefixes = ['mr.', 'mrs.', 'ms.', 'miss.', 'dr.', 'prof.'];\n for (const prefix of prefixes) {\n if (normalized.startsWith(prefix)) {\n normalized = normalized.substring(prefix.length).trim();\n break;\n }\n }\n }\n\n // Remove suffixes for family names (check with and without space)\n // Sort by length (longest first) to match \"iii\" before \"ii\"\n if (type === 'family') {\n const suffixes = ['jr.', 'sr.', 'iii', 'ii', 'iv', 'v'];\n for (const suffix of suffixes) {\n // Check for suffix with space before it (e.g., \" jr.\")\n const suffixWithSpace = ` ${suffix}`;\n if (normalized.endsWith(suffixWithSpace)) {\n normalized = normalized\n .substring(0, normalized.length - suffixWithSpace.length)\n .trim();\n break;\n }\n // Check for suffix without space\n if (normalized.endsWith(suffix)) {\n normalized = normalized\n .substring(0, normalized.length - suffix.length)\n .trim();\n break;\n }\n }\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Hash multiple email addresses\n */\nexport async function hashEmails(emails: string[]): Promise<string[]> {\n return Promise.all(emails.map((email) => hashEmail(email)));\n}\n\n/**\n * Hash multiple phone numbers\n */\nexport async function hashPhones(phones: string[]): Promise<string[]> {\n return Promise.all(phones.map((phone) => hashPhone(phone)));\n}\n","import { GoogleAuth, type OAuth2Client } from 'google-auth-library';\nimport type { Settings } from './types';\n\nconst DEFAULT_SCOPES = ['https://www.googleapis.com/auth/datamanager'];\n\n/**\n * Authentication error with cause tracking\n */\nexport class AuthError extends Error {\n constructor(\n message: string,\n public cause?: Error,\n ) {\n super(message);\n this.name = 'DataManagerAuthError';\n }\n}\n\n/**\n * Creates Google Auth client based on settings\n *\n * Authentication priority:\n * 1. credentials (inline service account) - if provided\n * 2. keyFilename (service account file) - if provided\n * 3. Application Default Credentials (ADC) - automatic fallback\n * - GOOGLE_APPLICATION_CREDENTIALS env var\n * - GCP metadata server (Cloud Functions, Cloud Run, GCE)\n *\n * @param settings - Configuration with auth options\n * @returns OAuth2Client for token retrieval\n * @throws AuthError if authentication fails\n */\nexport async function createAuthClient(\n settings: Settings,\n): Promise<OAuth2Client> {\n const { credentials, keyFilename, scopes = DEFAULT_SCOPES } = settings;\n\n try {\n if (credentials) {\n const auth = new GoogleAuth({\n credentials,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n if (keyFilename) {\n const auth = new GoogleAuth({\n keyFilename,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n const auth = new GoogleAuth({ scopes });\n return (await auth.getClient()) as OAuth2Client;\n } catch (error) {\n throw new AuthError(\n 'Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.',\n error instanceof Error ? error : undefined,\n );\n }\n}\n\n/**\n * Gets access token from auth client\n * Automatically returns cached token if valid or refreshes if expired\n *\n * @param authClient - OAuth2 client from createAuthClient()\n * @returns Fresh access token\n * @throws AuthError if token retrieval fails\n */\nexport async function getAccessToken(\n authClient: OAuth2Client,\n): Promise<string> {\n try {\n const tokenResponse = await authClient.getAccessToken();\n\n if (!tokenResponse.token) {\n throw new AuthError('Auth client returned empty token');\n }\n\n return tokenResponse.token;\n } catch (error) {\n throw new AuthError(\n 'Failed to obtain access token',\n error instanceof Error ? error : undefined,\n );\n }\n}\n","import type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationServer } from '@walkeros/server-core';\nimport type { OAuth2Client } from 'google-auth-library';\n\nexport interface Settings {\n /**\n * Service account credentials (client_email + private_key)\n * Recommended for serverless environments (AWS Lambda, Docker, etc.)\n */\n credentials?: {\n client_email: string;\n private_key: string;\n };\n\n /**\n * Path to service account JSON file\n * For local development or environments with filesystem access\n */\n keyFilename?: string;\n\n /**\n * OAuth scopes for Data Manager API\n * @default ['https://www.googleapis.com/auth/datamanager']\n */\n scopes?: string[];\n\n /** Array of destination accounts and conversion actions/user lists */\n destinations: Destination[];\n\n /** Event source for all events. Defaults to WEB if not specified */\n eventSource?: EventSource;\n\n /** Maximum number of events to batch before sending (max 2000) */\n batchSize?: number;\n\n /** Time in milliseconds to wait before auto-flushing batch */\n batchInterval?: number;\n\n /** If true, validate request without ingestion (testing mode) */\n validateOnly?: boolean;\n\n /** Override API endpoint (for testing) */\n url?: string;\n\n /** Request-level consent for all events */\n consent?: Consent;\n\n /** Test event code for debugging (optional) */\n testEventCode?: string;\n\n /** Guided helpers: User data mapping (applies to all events) */\n userData?: WalkerOSMapping.Map;\n\n /** Guided helper: First-party user ID */\n userId?: WalkerOSMapping.Value;\n\n /** Guided helper: GA4 client ID */\n clientId?: WalkerOSMapping.Value;\n\n /** Guided helper: Privacy-safe attribution (Google's sessionAttributes) */\n sessionAttributes?: WalkerOSMapping.Value;\n\n /** Consent mapping: Map consent field to adUserData (string = field name, boolean = static value) */\n consentAdUserData?: string | boolean;\n\n /** Consent mapping: Map consent field to adPersonalization (string = field name, boolean = static value) */\n consentAdPersonalization?: string | boolean;\n}\n\nexport interface Mapping {\n // Attribution identifiers (optional, for explicit mapping)\n gclid?: WalkerOSMapping.Value;\n gbraid?: WalkerOSMapping.Value;\n wbraid?: WalkerOSMapping.Value;\n sessionAttributes?: WalkerOSMapping.Value;\n}\n\nexport interface Env extends DestinationServer.Env {\n fetch?: typeof fetch;\n authClient?: OAuth2Client | null;\n}\n\nexport type InitSettings = Partial<Settings>;\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface DestinationInterface\n extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\n/**\n * Config after validation - settings is guaranteed to exist with required fields\n * Use this type after calling getConfig() to get proper type narrowing\n * After validation, eventSource is always set (defaults to 'WEB')\n */\nexport type ValidatedConfig = Omit<Config, 'settings'> & {\n settings: Settings & { eventSource: EventSource };\n};\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// Google Data Manager API Types\n// https://developers.google.com/data-manager/api/reference\n\n/**\n * Destination account and product identifier\n * https://developers.google.com/data-manager/api/reference/rest/v1/Destination\n */\nexport interface Destination {\n /** Reference identifier for this destination */\n reference?: string;\n\n /** Login account (account initiating the request) */\n loginAccount?: ProductAccount;\n\n /** Linked account (child account linked to login account) */\n linkedAccount?: ProductAccount;\n\n /** Operating account (account where data is sent) */\n operatingAccount?: ProductAccount;\n\n /** Product-specific destination ID (conversion action or user list) */\n productDestinationId?: string;\n}\n\n/**\n * Product account information\n */\nexport interface ProductAccount {\n /** Account ID (e.g., \"123-456-7890\" for Google Ads) */\n accountId: string;\n\n /** Type of account */\n accountType: AccountType;\n}\n\nexport type AccountType =\n | 'ACCOUNT_TYPE_UNSPECIFIED'\n | 'GOOGLE_ADS'\n | 'DISPLAY_VIDEO_ADVERTISER'\n | 'DISPLAY_VIDEO_PARTNER'\n | 'GOOGLE_ANALYTICS_PROPERTY'\n | 'DATA_PARTNER';\n\nexport type EventSource = 'WEB' | 'APP' | 'IN_STORE' | 'PHONE' | 'OTHER';\n\n/**\n * Consent for Digital Markets Act (DMA) compliance\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n */\nexport interface Consent {\n /** Consent for data collection and use */\n adUserData?: ConsentStatus;\n\n /** Consent for ad personalization */\n adPersonalization?: ConsentStatus;\n}\n\nexport type ConsentStatus = 'CONSENT_GRANTED' | 'CONSENT_DENIED';\n\n/**\n * Request body for events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/events/ingest\n */\nexport interface IngestEventsRequest {\n /** Array of destinations for these events (max 10) */\n destinations: Destination[];\n\n /** Array of events to ingest (max 2000) */\n events: Event[];\n\n /** Request-level consent (overridden by event-level) */\n consent?: Consent;\n\n /** If true, validate without ingestion */\n validateOnly?: boolean;\n\n /** Test event code for debugging */\n testEventCode?: string;\n}\n\n/**\n * Single event for ingestion\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport interface Event {\n /** Destination references for routing */\n destinationReferences?: string[];\n\n /** Transaction ID for deduplication (max 512 chars) */\n transactionId?: string;\n\n /** Event timestamp in RFC 3339 format */\n eventTimestamp?: string;\n\n /** Last updated timestamp in RFC 3339 format */\n lastUpdatedTimestamp?: string;\n\n /** User data with identifiers (max 10 identifiers) */\n userData?: UserData;\n\n /** Event-level consent (overrides request-level) */\n consent?: Consent;\n\n /** Attribution identifiers */\n adIdentifiers?: AdIdentifiers;\n\n /** Currency code (ISO 4217, 3 chars) */\n currency?: string;\n\n /** Conversion value */\n conversionValue?: number;\n\n /** Source of the event */\n eventSource?: EventSource;\n\n /** Device information for the event */\n eventDeviceInfo?: DeviceInfo;\n\n /** Shopping cart data */\n cartData?: CartData;\n\n /** Custom variables for the event */\n customVariables?: CustomVariable[];\n\n /** Experimental fields (subject to change) */\n experimentalFields?: ExperimentalField[];\n\n /** User properties */\n userProperties?: UserProperties;\n\n /** Event name for GA4 (max 40 chars, required for GA4) */\n eventName?: string;\n\n /** Google Analytics client ID (max 255 chars) */\n clientId?: string;\n\n /** First-party user ID (max 256 chars) */\n userId?: string;\n\n /** Additional event parameters */\n additionalEventParameters?: EventParameter[];\n}\n\n/**\n * Device information\n */\nexport interface DeviceInfo {\n /** User agent string */\n userAgent?: string;\n}\n\n/**\n * Custom variable\n */\nexport interface CustomVariable {\n /** Variable name */\n name?: string;\n\n /** Variable value */\n value?: string;\n}\n\n/**\n * Experimental field\n */\nexport interface ExperimentalField {\n /** Field name */\n name?: string;\n\n /** Field value */\n value?: string;\n}\n\n/**\n * User properties\n */\nexport interface UserProperties {\n /** Property values */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Event parameter\n */\nexport interface EventParameter {\n /** Parameter name */\n name?: string;\n\n /** Parameter value */\n value?: string | number;\n}\n\n/**\n * User data with identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n */\nexport interface UserData {\n /** Array of user identifiers (max 10) */\n userIdentifiers: UserIdentifier[];\n}\n\n/**\n * User identifier (email, phone, or address)\n */\nexport type UserIdentifier =\n | { emailAddress: string }\n | { phoneNumber: string }\n | { address: Address };\n\n/**\n * Address for user identification\n * https://developers.google.com/data-manager/api/reference/rest/v1/Address\n */\nexport interface Address {\n /** Given name (first name) - SHA-256 hashed */\n givenName?: string;\n\n /** Family name (last name) - SHA-256 hashed */\n familyName?: string;\n\n /** ISO-3166-1 alpha-2 country code - NOT hashed (e.g., \"US\", \"GB\") */\n regionCode?: string;\n\n /** Postal code - NOT hashed */\n postalCode?: string;\n}\n\n/**\n * Attribution identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n */\nexport interface AdIdentifiers {\n /** Session attributes (privacy-safe attribution) */\n sessionAttributes?: string;\n\n /** Google Click ID (primary attribution) */\n gclid?: string;\n\n /** iOS attribution identifier (post-ATT) */\n gbraid?: string;\n\n /** Web-to-app attribution identifier */\n wbraid?: string;\n\n /** Device information for landing page */\n landingPageDeviceInfo?: DeviceInfo;\n}\n\n/**\n * Shopping cart data\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartData\n */\nexport interface CartData {\n /** Array of cart items (max 200) */\n items: CartItem[];\n}\n\n/**\n * Single cart item\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartItem\n */\nexport interface CartItem {\n /** Merchant product ID (max 127 chars) */\n merchantProductId?: string;\n\n /** Item price */\n price?: number;\n\n /** Item quantity */\n quantity?: number;\n}\n\n/**\n * Response from events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/IngestEventsResponse\n */\nexport interface IngestEventsResponse {\n /** Unique request ID for status checking */\n requestId: string;\n\n /** Validation errors (only if validateOnly=true) */\n validationErrors?: ValidationError[];\n}\n\n/**\n * Validation error\n */\nexport interface ValidationError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Field path that caused the error */\n fieldPath?: string;\n}\n\n/**\n * Request status response\n * https://developers.google.com/data-manager/api/reference/rest/v1/requestStatus/retrieve\n */\nexport interface RequestStatusResponse {\n /** Unique request ID */\n requestId: string;\n\n /** Processing state */\n state: RequestState;\n\n /** Number of events successfully ingested */\n eventsIngested?: number;\n\n /** Number of events that failed */\n eventsFailed?: number;\n\n /** Array of errors (if any) */\n errors?: RequestError[];\n}\n\nexport type RequestState =\n | 'STATE_UNSPECIFIED'\n | 'PENDING'\n | 'PROCESSING'\n | 'SUCCEEDED'\n | 'FAILED'\n | 'PARTIALLY_SUCCEEDED';\n\n/**\n * Request error\n */\nexport interface RequestError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Number of events affected by this error */\n eventCount?: number;\n}\n"],"mappings":"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","names":["import_core","import_core","auth"]}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- function getConfig(partialConfig={},logger){const settings=partialConfig.settings||{},{destinations:destinations,eventSource:eventSource="WEB"}=settings;destinations&&0!==destinations.length||logger.throw("Config settings destinations missing or empty");const settingsConfig={...settings,destinations:destinations,eventSource:eventSource};return{...partialConfig,settings:settingsConfig}}import{getMappingValue,isObject}from"@walkeros/core";import{isString as isString2,isDefined}from"@walkeros/core";import{isString}from"@walkeros/core";import{getHashServer}from"@walkeros/server-core";async function hashName(name,type="given"){if(!isString(name)||!name)return"";let normalized=name.trim().toLowerCase();if("given"===type){const prefixes=["mr.","mrs.","ms.","miss.","dr.","prof."];for(const prefix of prefixes)if(normalized.startsWith(prefix)){normalized=normalized.substring(prefix.length).trim();break}}if("family"===type){const suffixes=["jr.","sr.","iii","ii","iv","v"];for(const suffix of suffixes){const suffixWithSpace=` ${suffix}`;if(normalized.endsWith(suffixWithSpace)){normalized=normalized.substring(0,normalized.length-suffixWithSpace.length).trim();break}if(normalized.endsWith(suffix)){normalized=normalized.substring(0,normalized.length-suffix.length).trim();break}}}return getHashServer(normalized)}async function formatUserData(data){const identifiers=[];if(isString2(data.email)&&data.email){const hashedEmail=await async function(email){if(!isString(email)||!email)return"";let normalized=email.trim().toLowerCase();if(normalized.endsWith("@gmail.com")||normalized.endsWith("@googlemail.com")){const[localPart,domain]=normalized.split("@");normalized=`${localPart.replace(/\./g,"")}@${domain}`}return getHashServer(normalized)}(data.email);hashedEmail&&identifiers.push({emailAddress:hashedEmail})}if(isString2(data.phone)&&data.phone){const hashedPhone=await async function(phone){if(!isString(phone)||!phone)return"";let normalized=phone.trim();const hasPlus=normalized.startsWith("+");return normalized=normalized.replace(/\D/g,""),normalized=hasPlus||normalized.length>10?`+${normalized}`:`+1${normalized}`,getHashServer(normalized)}(data.phone);hashedPhone&&identifiers.push({phoneNumber:hashedPhone})}if(data.firstName||data.lastName||data.regionCode||data.postalCode){const address={};isString2(data.firstName)&&data.firstName&&(address.givenName=await hashName(data.firstName,"given")),isString2(data.lastName)&&data.lastName&&(address.familyName=await hashName(data.lastName,"family")),isString2(data.regionCode)&&data.regionCode&&(address.regionCode=data.regionCode.toUpperCase()),isString2(data.postalCode)&&data.postalCode&&(address.postalCode=data.postalCode),Object.keys(address).length>0&&identifiers.push({address:address})}if(0!==identifiers.length)return{userIdentifiers:identifiers.slice(0,10)}}async function formatEvent(event,mappedData){const dataManagerEvent={eventTimestamp:(timestamp=event.timestamp,new Date(timestamp).toISOString())};var timestamp;const data=mappedData||{};isString2(data.transactionId)&&data.transactionId&&(dataManagerEvent.transactionId=data.transactionId.substring(0,512)),isString2(data.clientId)&&data.clientId&&(dataManagerEvent.clientId=data.clientId.substring(0,255)),isString2(data.userId)&&data.userId&&(dataManagerEvent.userId=data.userId.substring(0,256));const userData=await formatUserData(data);userData&&(dataManagerEvent.userData=userData);const adIdentifiers=function(data){const identifiers={};return isString2(data.gclid)&&data.gclid&&(identifiers.gclid=data.gclid),isString2(data.gbraid)&&data.gbraid&&(identifiers.gbraid=data.gbraid),isString2(data.wbraid)&&data.wbraid&&(identifiers.wbraid=data.wbraid),isString2(data.sessionAttributes)&&data.sessionAttributes&&(identifiers.sessionAttributes=data.sessionAttributes),Object.keys(identifiers).length>0?identifiers:void 0}(data);adIdentifiers&&(dataManagerEvent.adIdentifiers=adIdentifiers),"number"==typeof data.conversionValue&&(dataManagerEvent.conversionValue=data.conversionValue),isString2(data.currency)&&data.currency&&(dataManagerEvent.currency=data.currency.substring(0,3).toUpperCase()),data.cartData&&"object"==typeof data.cartData&&(dataManagerEvent.cartData=data.cartData),isString2(data.eventName)&&data.eventName&&(dataManagerEvent.eventName=data.eventName.substring(0,40)),isString2(data.eventSource)&&data.eventSource&&(dataManagerEvent.eventSource=data.eventSource);const mappedConsent={};if("boolean"==typeof data.adUserData&&(mappedConsent.adUserData=data.adUserData?"CONSENT_GRANTED":"CONSENT_DENIED"),"boolean"==typeof data.adPersonalization&&(mappedConsent.adPersonalization=data.adPersonalization?"CONSENT_GRANTED":"CONSENT_DENIED"),0===Object.keys(mappedConsent).length){const eventConsent=function(walkerOSConsent){if(!walkerOSConsent)return;const consent={};return isDefined(walkerOSConsent.marketing)&&(consent.adUserData=walkerOSConsent.marketing?"CONSENT_GRANTED":"CONSENT_DENIED"),isDefined(walkerOSConsent.personalization)&&(consent.adPersonalization=walkerOSConsent.personalization?"CONSENT_GRANTED":"CONSENT_DENIED"),Object.keys(consent).length>0?consent:void 0}(event.consent);eventConsent&&(dataManagerEvent.consent=eventConsent)}else dataManagerEvent.consent=mappedConsent;return dataManagerEvent}import{GoogleAuth}from"google-auth-library";var DEFAULT_SCOPES=["https://www.googleapis.com/auth/datamanager"],AuthError=class extends Error{constructor(message,cause){super(message),this.cause=cause,this.name="DataManagerAuthError"}};var push=async function(event,{config:config,mapping:mapping,data:data,collector:collector,env:env,logger:logger}){const validatedConfig=getConfig(config,logger),{destinations:destinations,eventSource:eventSource,validateOnly:validateOnly=!1,url:url="https://datamanager.googleapis.com/v1",consent:requestConsent,testEventCode:testEventCode,userData:userData,userId:userId,clientId:clientId,sessionAttributes:sessionAttributes,consentAdUserData:consentAdUserData,consentAdPersonalization:consentAdPersonalization}=validatedConfig.settings,userDataMapped=userData?await getMappingValue(event,{map:userData}):{},userIdMapped=userId?await getMappingValue(event,userId):void 0,clientIdMapped=clientId?await getMappingValue(event,clientId):void 0,sessionAttributesMapped=sessionAttributes?await getMappingValue(event,sessionAttributes):void 0,consentAdUserDataValue="boolean"==typeof consentAdUserData?consentAdUserData:"string"==typeof consentAdUserData&&event.consent?event.consent[consentAdUserData]:void 0,consentAdPersonalizationValue="boolean"==typeof consentAdPersonalization?consentAdPersonalization:"string"==typeof consentAdPersonalization&&event.consent?event.consent[consentAdPersonalization]:void 0,settingsHelpers={};isObject(userDataMapped)&&Object.assign(settingsHelpers,userDataMapped),void 0!==userIdMapped&&(settingsHelpers.userId=userIdMapped),void 0!==clientIdMapped&&(settingsHelpers.clientId=clientIdMapped),void 0!==sessionAttributesMapped&&(settingsHelpers.sessionAttributes=sessionAttributesMapped),void 0!==consentAdUserDataValue&&(settingsHelpers.adUserData=consentAdUserDataValue),void 0!==consentAdPersonalizationValue&&(settingsHelpers.adPersonalization=consentAdPersonalizationValue);const configData=validatedConfig.data?await getMappingValue(event,validatedConfig.data):{},eventData=isObject(data)?data:{},finalData={...settingsHelpers,...isObject(configData)?configData:{},...eventData},dataManagerEvent=await formatEvent(event,finalData);dataManagerEvent.eventSource||(dataManagerEvent.eventSource=eventSource),!dataManagerEvent.consent&&requestConsent&&(dataManagerEvent.consent=requestConsent),dataManagerEvent.transactionId||logger.throw("transactionId is required");destinations.some(d=>{var _a;return"GOOGLE_ANALYTICS_PROPERTY"===(null==(_a=d.operatingAccount)?void 0:_a.accountType)})&&!dataManagerEvent.eventName&&logger.throw("eventName is required for GA4 destinations");const requestBody={events:[dataManagerEvent],destinations:destinations};requestConsent&&(requestBody.consent=requestConsent),validateOnly&&(requestBody.validateOnly=!0),testEventCode&&(requestBody.testEventCode=testEventCode);const authClient=null==env?void 0:env.authClient;if(!authClient)return logger.throw("Auth client not initialized. Ensure init() was called successfully.");let accessToken;try{accessToken=await async function(authClient){try{const tokenResponse=await authClient.getAccessToken();if(!tokenResponse.token)throw new AuthError("Auth client returned empty token");return tokenResponse.token}catch(error){throw new AuthError("Failed to obtain access token",error instanceof Error?error:void 0)}}(authClient)}catch(error){throw logger.error("Authentication failed",{error:error}),error}const fetchFn=(null==env?void 0:env.fetch)||fetch,endpoint=`${url}/events:ingest`;logger.debug("Sending to Data Manager API",{endpoint:endpoint,eventCount:requestBody.events.length,destinations:destinations.length,validateOnly:validateOnly});const response=await fetchFn(endpoint,{method:"POST",headers:{Authorization:`Bearer ${accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(requestBody)});if(!response.ok){const errorText=await response.text();logger.throw(`Data Manager API error (${response.status}): ${errorText}`)}const result=await response.json();logger.debug("API response",{status:response.status,requestId:result.requestId}),result.validationErrors&&result.validationErrors.length>0&&logger.throw(`Validation errors: ${JSON.stringify(result.validationErrors)}`)},types_exports={},destinationDataManager={type:"datamanager",config:{},async init({config:partialConfig,env:env,logger:logger}){const config=getConfig(partialConfig,logger);try{const authClient=await async function(settings){const{credentials:credentials,keyFilename:keyFilename,scopes:scopes=DEFAULT_SCOPES}=settings;try{if(credentials){const auth2=new GoogleAuth({credentials:credentials,scopes:scopes});return await auth2.getClient()}if(keyFilename){const auth2=new GoogleAuth({keyFilename:keyFilename,scopes:scopes});return await auth2.getClient()}const auth=new GoogleAuth({scopes:scopes});return await auth.getClient()}catch(error){throw new AuthError("Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.",error instanceof Error?error:void 0)}}(config.settings);return logger.debug("Auth client created"),{...config,env:{...env,authClient:authClient}}}catch(error){logger.throw(`Data Manager authentication failed: ${error instanceof Error?error.message:"Unknown error"}`)}},push:async(event,{config:config,mapping:mapping,data:data,collector:collector,env:env,logger:logger})=>await push(event,{config:config,mapping:mapping,data:data,collector:collector,env:env,logger:logger})},index_default=destinationDataManager;export{types_exports as DestinationDataManager,index_default as default,destinationDataManager};//# sourceMappingURL=index.mjs.map
1
+ function getConfig(partialConfig={},logger){const settings=partialConfig.settings||{},{destinations:destinations,eventSource:eventSource="WEB"}=settings;destinations&&0!==destinations.length||logger.throw("Config settings destinations missing or empty");const settingsConfig={...settings,destinations:destinations,eventSource:eventSource};return{...partialConfig,settings:settingsConfig}}import{getMappingValue,isObject}from"@walkeros/core";import{isString as isString2,isDefined}from"@walkeros/core";import{isString}from"@walkeros/core";import{getHashServer}from"@walkeros/server-core";async function hashName(name,type="given"){if(!isString(name)||!name)return"";let normalized=name.trim().toLowerCase();if("given"===type){const prefixes=["mr.","mrs.","ms.","miss.","dr.","prof."];for(const prefix of prefixes)if(normalized.startsWith(prefix)){normalized=normalized.substring(prefix.length).trim();break}}if("family"===type){const suffixes=["jr.","sr.","iii","ii","iv","v"];for(const suffix of suffixes){const suffixWithSpace=` ${suffix}`;if(normalized.endsWith(suffixWithSpace)){normalized=normalized.substring(0,normalized.length-suffixWithSpace.length).trim();break}if(normalized.endsWith(suffix)){normalized=normalized.substring(0,normalized.length-suffix.length).trim();break}}}return getHashServer(normalized)}async function formatUserData(data){const identifiers=[];if(isString2(data.email)&&data.email){const hashedEmail=await async function(email){if(!isString(email)||!email)return"";let normalized=email.trim().toLowerCase();if(normalized.endsWith("@gmail.com")||normalized.endsWith("@googlemail.com")){const[localPart,domain]=normalized.split("@");normalized=`${localPart.replace(/\./g,"")}@${domain}`}return getHashServer(normalized)}(data.email);hashedEmail&&identifiers.push({emailAddress:hashedEmail})}if(isString2(data.phone)&&data.phone){const hashedPhone=await async function(phone){if(!isString(phone)||!phone)return"";let normalized=phone.trim();const hasPlus=normalized.startsWith("+");return normalized=normalized.replace(/\D/g,""),normalized=hasPlus||normalized.length>10?`+${normalized}`:`+1${normalized}`,getHashServer(normalized)}(data.phone);hashedPhone&&identifiers.push({phoneNumber:hashedPhone})}if(data.firstName||data.lastName||data.regionCode||data.postalCode){const address={};isString2(data.firstName)&&data.firstName&&(address.givenName=await hashName(data.firstName,"given")),isString2(data.lastName)&&data.lastName&&(address.familyName=await hashName(data.lastName,"family")),isString2(data.regionCode)&&data.regionCode&&(address.regionCode=data.regionCode.toUpperCase()),isString2(data.postalCode)&&data.postalCode&&(address.postalCode=data.postalCode),Object.keys(address).length>0&&identifiers.push({address:address})}if(0!==identifiers.length)return{userIdentifiers:identifiers.slice(0,10)}}async function formatEvent(event,mappedData){const dataManagerEvent={eventTimestamp:(timestamp=event.timestamp,new Date(timestamp).toISOString())};var timestamp;const data=mappedData||{};isString2(data.transactionId)&&data.transactionId&&(dataManagerEvent.transactionId=data.transactionId.substring(0,512)),isString2(data.clientId)&&data.clientId&&(dataManagerEvent.clientId=data.clientId.substring(0,255)),isString2(data.userId)&&data.userId&&(dataManagerEvent.userId=data.userId.substring(0,256));const userData=await formatUserData(data);userData&&(dataManagerEvent.userData=userData);const adIdentifiers=function(data){const identifiers={};return isString2(data.gclid)&&data.gclid&&(identifiers.gclid=data.gclid),isString2(data.gbraid)&&data.gbraid&&(identifiers.gbraid=data.gbraid),isString2(data.wbraid)&&data.wbraid&&(identifiers.wbraid=data.wbraid),isString2(data.sessionAttributes)&&data.sessionAttributes&&(identifiers.sessionAttributes=data.sessionAttributes),Object.keys(identifiers).length>0?identifiers:void 0}(data);adIdentifiers&&(dataManagerEvent.adIdentifiers=adIdentifiers),"number"==typeof data.conversionValue&&(dataManagerEvent.conversionValue=data.conversionValue),isString2(data.currency)&&data.currency&&(dataManagerEvent.currency=data.currency.substring(0,3).toUpperCase()),data.cartData&&"object"==typeof data.cartData&&(dataManagerEvent.cartData=data.cartData),isString2(data.eventName)&&data.eventName&&(dataManagerEvent.eventName=data.eventName.substring(0,40)),isString2(data.eventSource)&&data.eventSource&&(dataManagerEvent.eventSource=data.eventSource);const mappedConsent={};if("boolean"==typeof data.adUserData&&(mappedConsent.adUserData=data.adUserData?"CONSENT_GRANTED":"CONSENT_DENIED"),"boolean"==typeof data.adPersonalization&&(mappedConsent.adPersonalization=data.adPersonalization?"CONSENT_GRANTED":"CONSENT_DENIED"),0===Object.keys(mappedConsent).length){const eventConsent=function(walkerOSConsent){if(!walkerOSConsent)return;const consent={};return isDefined(walkerOSConsent.marketing)&&(consent.adUserData=walkerOSConsent.marketing?"CONSENT_GRANTED":"CONSENT_DENIED"),isDefined(walkerOSConsent.personalization)&&(consent.adPersonalization=walkerOSConsent.personalization?"CONSENT_GRANTED":"CONSENT_DENIED"),Object.keys(consent).length>0?consent:void 0}(event.consent);eventConsent&&(dataManagerEvent.consent=eventConsent)}else dataManagerEvent.consent=mappedConsent;return dataManagerEvent}import{GoogleAuth}from"google-auth-library";var DEFAULT_SCOPES=["https://www.googleapis.com/auth/datamanager"],AuthError=class extends Error{constructor(message,cause){super(message),this.cause=cause,this.name="DataManagerAuthError"}};var push=async function(event,{config:config,rule:rule,data:data,collector:collector,env:env,logger:logger}){const validatedConfig=getConfig(config,logger),{destinations:destinations,eventSource:eventSource,validateOnly:validateOnly=!1,url:url="https://datamanager.googleapis.com/v1",consent:requestConsent,testEventCode:testEventCode,userData:userData,userId:userId,clientId:clientId,sessionAttributes:sessionAttributes,consentAdUserData:consentAdUserData,consentAdPersonalization:consentAdPersonalization}=validatedConfig.settings,userDataMapped=userData?await getMappingValue(event,{map:userData}):{},userIdMapped=userId?await getMappingValue(event,userId):void 0,clientIdMapped=clientId?await getMappingValue(event,clientId):void 0,sessionAttributesMapped=sessionAttributes?await getMappingValue(event,sessionAttributes):void 0,consentAdUserDataValue="boolean"==typeof consentAdUserData?consentAdUserData:"string"==typeof consentAdUserData&&event.consent?event.consent[consentAdUserData]:void 0,consentAdPersonalizationValue="boolean"==typeof consentAdPersonalization?consentAdPersonalization:"string"==typeof consentAdPersonalization&&event.consent?event.consent[consentAdPersonalization]:void 0,settingsHelpers={};isObject(userDataMapped)&&Object.assign(settingsHelpers,userDataMapped),void 0!==userIdMapped&&(settingsHelpers.userId=userIdMapped),void 0!==clientIdMapped&&(settingsHelpers.clientId=clientIdMapped),void 0!==sessionAttributesMapped&&(settingsHelpers.sessionAttributes=sessionAttributesMapped),void 0!==consentAdUserDataValue&&(settingsHelpers.adUserData=consentAdUserDataValue),void 0!==consentAdPersonalizationValue&&(settingsHelpers.adPersonalization=consentAdPersonalizationValue);const configData=validatedConfig.data?await getMappingValue(event,validatedConfig.data):{},eventData=isObject(data)?data:{},finalData={...settingsHelpers,...isObject(configData)?configData:{},...eventData},dataManagerEvent=await formatEvent(event,finalData);dataManagerEvent.eventSource||(dataManagerEvent.eventSource=eventSource),!dataManagerEvent.consent&&requestConsent&&(dataManagerEvent.consent=requestConsent),dataManagerEvent.transactionId||logger.throw("transactionId is required");destinations.some(d=>{var _a;return"GOOGLE_ANALYTICS_PROPERTY"===(null==(_a=d.operatingAccount)?void 0:_a.accountType)})&&!dataManagerEvent.eventName&&logger.throw("eventName is required for GA4 destinations");const requestBody={events:[dataManagerEvent],destinations:destinations};requestConsent&&(requestBody.consent=requestConsent),validateOnly&&(requestBody.validateOnly=!0),testEventCode&&(requestBody.testEventCode=testEventCode);const authClient=null==env?void 0:env.authClient;if(!authClient)return logger.throw("Auth client not initialized. Ensure init() was called successfully.");let accessToken;try{accessToken=await async function(authClient){try{const tokenResponse=await authClient.getAccessToken();if(!tokenResponse.token)throw new AuthError("Auth client returned empty token");return tokenResponse.token}catch(error){throw new AuthError("Failed to obtain access token",error instanceof Error?error:void 0)}}(authClient)}catch(error){throw logger.error("Authentication failed",{error:error}),error}const fetchFn=(null==env?void 0:env.fetch)||fetch,endpoint=`${url}/events:ingest`;logger.debug("Sending to Data Manager API",{endpoint:endpoint,eventCount:requestBody.events.length,destinations:destinations.length,validateOnly:validateOnly});const response=await fetchFn(endpoint,{method:"POST",headers:{Authorization:`Bearer ${accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(requestBody)});if(!response.ok){const errorText=await response.text();logger.throw(`Data Manager API error (${response.status}): ${errorText}`)}const result=await response.json();logger.debug("API response",{status:response.status,requestId:result.requestId}),result.validationErrors&&result.validationErrors.length>0&&logger.throw(`Validation errors: ${JSON.stringify(result.validationErrors)}`)},types_exports={},destinationDataManager={type:"datamanager",config:{},async init({config:partialConfig,env:env,logger:logger}){const config=getConfig(partialConfig,logger);try{const authClient=await async function(settings){const{credentials:credentials,keyFilename:keyFilename,scopes:scopes=DEFAULT_SCOPES}=settings;try{if(credentials){const auth2=new GoogleAuth({credentials:credentials,scopes:scopes});return await auth2.getClient()}if(keyFilename){const auth2=new GoogleAuth({keyFilename:keyFilename,scopes:scopes});return await auth2.getClient()}const auth=new GoogleAuth({scopes:scopes});return await auth.getClient()}catch(error){throw new AuthError("Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.",error instanceof Error?error:void 0)}}(config.settings);return logger.debug("Auth client created"),{...config,env:{...env,authClient:authClient}}}catch(error){logger.throw(`Data Manager authentication failed: ${error instanceof Error?error.message:"Unknown error"}`)}},push:async(event,context)=>await push(event,context)},index_default=destinationDataManager;export{types_exports as DestinationDataManager,index_default as default,destinationDataManager};//# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts","../src/push.ts","../src/format.ts","../src/hash.ts","../src/auth.ts","../src/types/index.ts","../src/index.ts"],"sourcesContent":["import type {\n ValidatedConfig,\n Settings,\n PartialConfig,\n EventSource,\n} from './types';\nimport type { Logger } from '@walkeros/core';\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): ValidatedConfig {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { destinations, eventSource = 'WEB' } = settings;\n\n if (!destinations || destinations.length === 0)\n logger.throw('Config settings destinations missing or empty');\n\n const settingsConfig: Settings & { eventSource: EventSource } = {\n ...settings,\n destinations,\n eventSource,\n };\n\n return { ...partialConfig, settings: settingsConfig };\n}\n","import type { PushFn } from './types';\nimport type { IngestEventsRequest, IngestEventsResponse } from './types';\nimport { getMappingValue, isObject } from '@walkeros/core';\nimport { formatEvent, formatConsent } from './format';\nimport { getAccessToken } from './auth';\nimport { getConfig } from './config';\n\nexport const push: PushFn = async function (\n event,\n { config, mapping, data, collector, env, logger },\n) {\n // Validate config and get typed settings\n const validatedConfig = getConfig(config, logger);\n const {\n destinations,\n eventSource,\n validateOnly = false,\n url = 'https://datamanager.googleapis.com/v1',\n consent: requestConsent,\n testEventCode,\n userData,\n userId,\n clientId,\n sessionAttributes,\n consentAdUserData,\n consentAdPersonalization,\n } = validatedConfig.settings;\n\n // Extract Settings guided helpers\n const userDataMapped = userData\n ? await getMappingValue(event, { map: userData })\n : {};\n const userIdMapped = userId\n ? await getMappingValue(event, userId)\n : undefined;\n const clientIdMapped = clientId\n ? await getMappingValue(event, clientId)\n : undefined;\n const sessionAttributesMapped = sessionAttributes\n ? await getMappingValue(event, sessionAttributes)\n : undefined;\n\n // Extract consent from Settings\n const consentAdUserDataValue =\n typeof consentAdUserData === 'boolean'\n ? consentAdUserData\n : typeof consentAdUserData === 'string' && event.consent\n ? event.consent[consentAdUserData]\n : undefined;\n\n const consentAdPersonalizationValue =\n typeof consentAdPersonalization === 'boolean'\n ? consentAdPersonalization\n : typeof consentAdPersonalization === 'string' && event.consent\n ? event.consent[consentAdPersonalization]\n : undefined;\n\n // Build Settings helpers object\n const settingsHelpers: Record<string, unknown> = {};\n if (isObject(userDataMapped)) {\n Object.assign(settingsHelpers, userDataMapped);\n }\n if (userIdMapped !== undefined) settingsHelpers.userId = userIdMapped;\n if (clientIdMapped !== undefined) settingsHelpers.clientId = clientIdMapped;\n if (sessionAttributesMapped !== undefined)\n settingsHelpers.sessionAttributes = sessionAttributesMapped;\n if (consentAdUserDataValue !== undefined)\n settingsHelpers.adUserData = consentAdUserDataValue;\n if (consentAdPersonalizationValue !== undefined)\n settingsHelpers.adPersonalization = consentAdPersonalizationValue;\n\n // Get mapped data from destination config and event mapping\n const configData = validatedConfig.data\n ? await getMappingValue(event, validatedConfig.data)\n : {};\n const eventData = isObject(data) ? data : {};\n\n // Merge: Settings helpers < config.data < event mapping data\n const finalData = {\n ...settingsHelpers,\n ...(isObject(configData) ? configData : {}),\n ...eventData,\n };\n\n // Format event for Data Manager API\n const dataManagerEvent = await formatEvent(event, finalData);\n\n // Apply event source from settings (required)\n if (!dataManagerEvent.eventSource) {\n dataManagerEvent.eventSource = eventSource;\n }\n\n // Apply request-level consent if event doesn't have consent\n if (!dataManagerEvent.consent && requestConsent) {\n dataManagerEvent.consent = requestConsent;\n }\n\n // Validate required fields before API call\n if (!dataManagerEvent.transactionId) {\n logger.throw('transactionId is required');\n }\n\n // Check if any destination is GA4 (requires eventName)\n const hasGA4Destination = destinations.some(\n (d) => d.operatingAccount?.accountType === 'GOOGLE_ANALYTICS_PROPERTY',\n );\n\n if (hasGA4Destination && !dataManagerEvent.eventName) {\n logger.throw('eventName is required for GA4 destinations');\n }\n\n // Build API request\n const requestBody: IngestEventsRequest = {\n events: [dataManagerEvent],\n destinations,\n };\n\n // Add optional parameters\n if (requestConsent) {\n requestBody.consent = requestConsent;\n }\n\n if (validateOnly) {\n requestBody.validateOnly = true;\n }\n\n if (testEventCode) {\n requestBody.testEventCode = testEventCode;\n }\n\n const authClient = env?.authClient;\n if (!authClient) {\n return logger.throw(\n 'Auth client not initialized. Ensure init() was called successfully.',\n );\n }\n\n let accessToken: string;\n try {\n accessToken = await getAccessToken(authClient);\n } catch (error) {\n logger.error('Authentication failed', { error });\n throw error;\n }\n\n const fetchFn = env?.fetch || fetch;\n const endpoint = `${url}/events:ingest`;\n\n logger.debug('Sending to Data Manager API', {\n endpoint,\n eventCount: requestBody.events.length,\n destinations: destinations.length,\n validateOnly,\n });\n\n const response = await fetchFn(endpoint, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n logger.throw(`Data Manager API error (${response.status}): ${errorText}`);\n }\n\n const result: IngestEventsResponse = await response.json();\n\n logger.debug('API response', {\n status: response.status,\n requestId: result.requestId,\n });\n\n // If validation errors exist, throw them\n if (result.validationErrors && result.validationErrors.length > 0) {\n logger.throw(\n `Validation errors: ${JSON.stringify(result.validationErrors)}`,\n );\n }\n};\n","import type { WalkerOS } from '@walkeros/core';\nimport { isString, isDefined } from '@walkeros/core';\nimport type {\n Event,\n UserData,\n UserIdentifier,\n AdIdentifiers,\n Consent,\n ConsentStatus,\n} from './types';\nimport { hashEmail, hashPhone, hashName } from './hash';\n\n/**\n * Format walkerOS event timestamp to RFC 3339 format\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n *\n * walkerOS timestamp is in milliseconds, RFC 3339 format: \"2024-01-15T10:30:00Z\"\n */\nexport function formatTimestamp(timestamp: number): string {\n return new Date(timestamp).toISOString();\n}\n\n/**\n * Format user identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n *\n * User data must be explicitly mapped in the mapping configuration.\n * Max 10 identifiers per event\n */\nexport async function formatUserData(\n data: Record<string, unknown>,\n): Promise<UserData | undefined> {\n const identifiers: UserIdentifier[] = [];\n\n // Extract from mapped data only\n // Email\n if (isString(data.email) && data.email) {\n const hashedEmail = await hashEmail(data.email);\n if (hashedEmail) {\n identifiers.push({ emailAddress: hashedEmail });\n }\n }\n\n // Phone\n if (isString(data.phone) && data.phone) {\n const hashedPhone = await hashPhone(data.phone);\n if (hashedPhone) {\n identifiers.push({ phoneNumber: hashedPhone });\n }\n }\n\n // Address from mapped properties\n const hasAddress =\n data.firstName || data.lastName || data.regionCode || data.postalCode;\n\n if (hasAddress) {\n const address: Record<string, string> = {};\n\n if (isString(data.firstName) && data.firstName) {\n address.givenName = await hashName(data.firstName, 'given');\n }\n\n if (isString(data.lastName) && data.lastName) {\n address.familyName = await hashName(data.lastName, 'family');\n }\n\n // Region code is NOT hashed\n if (isString(data.regionCode) && data.regionCode) {\n address.regionCode = data.regionCode.toUpperCase();\n }\n\n // Postal code is NOT hashed\n if (isString(data.postalCode) && data.postalCode) {\n address.postalCode = data.postalCode;\n }\n\n if (Object.keys(address).length > 0) {\n identifiers.push({ address });\n }\n }\n\n // Limit to 10 identifiers\n if (identifiers.length === 0) return undefined;\n\n return {\n userIdentifiers: identifiers.slice(0, 10),\n };\n}\n\n/**\n * Extract and format attribution identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n *\n * Attribution identifiers should be mapped explicitly in the mapping configuration.\n * Example: { gclid: 'context.gclid', gbraid: 'context.gbraid' }\n */\nexport function formatAdIdentifiers(\n data: Record<string, unknown>,\n): AdIdentifiers | undefined {\n const identifiers: AdIdentifiers = {};\n\n // Extract from mapped data (already processed by mapping system)\n if (isString(data.gclid) && data.gclid) {\n identifiers.gclid = data.gclid;\n }\n if (isString(data.gbraid) && data.gbraid) {\n identifiers.gbraid = data.gbraid;\n }\n if (isString(data.wbraid) && data.wbraid) {\n identifiers.wbraid = data.wbraid;\n }\n if (isString(data.sessionAttributes) && data.sessionAttributes) {\n identifiers.sessionAttributes = data.sessionAttributes;\n }\n\n return Object.keys(identifiers).length > 0 ? identifiers : undefined;\n}\n\n/**\n * Map walkerOS consent to Data Manager consent format\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n *\n * walkerOS: { marketing: true, personalization: false }\n * Data Manager: { adUserData: 'CONSENT_GRANTED', adPersonalization: 'CONSENT_DENIED' }\n */\nexport function formatConsent(\n walkerOSConsent: WalkerOS.Consent | undefined,\n): Consent | undefined {\n if (!walkerOSConsent) return undefined;\n\n const consent: Consent = {};\n\n // Map marketing consent to adUserData\n if (isDefined(walkerOSConsent.marketing)) {\n consent.adUserData = walkerOSConsent.marketing\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // Map personalization consent to adPersonalization\n if (isDefined(walkerOSConsent.personalization)) {\n consent.adPersonalization = walkerOSConsent.personalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n return Object.keys(consent).length > 0 ? consent : undefined;\n}\n\n/**\n * Format complete event for Data Manager API\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport async function formatEvent(\n event: WalkerOS.Event,\n mappedData?: Record<string, unknown>,\n): Promise<Event> {\n const dataManagerEvent: Event = {\n eventTimestamp: formatTimestamp(event.timestamp),\n };\n\n // Use only mapped data (no fallback to event.data)\n const data = mappedData || {};\n\n // Transaction ID for deduplication\n if (isString(data.transactionId) && data.transactionId) {\n dataManagerEvent.transactionId = data.transactionId.substring(0, 512);\n }\n\n // Client ID (GA)\n if (isString(data.clientId) && data.clientId) {\n dataManagerEvent.clientId = data.clientId.substring(0, 255);\n }\n\n // User ID\n if (isString(data.userId) && data.userId) {\n dataManagerEvent.userId = data.userId.substring(0, 256);\n }\n\n // User data\n const userData = await formatUserData(data);\n if (userData) {\n dataManagerEvent.userData = userData;\n }\n\n // Attribution identifiers\n const adIdentifiers = formatAdIdentifiers(data);\n if (adIdentifiers) {\n dataManagerEvent.adIdentifiers = adIdentifiers;\n }\n\n // Conversion value\n if (typeof data.conversionValue === 'number') {\n dataManagerEvent.conversionValue = data.conversionValue;\n }\n\n // Currency\n if (isString(data.currency) && data.currency) {\n dataManagerEvent.currency = data.currency.substring(0, 3).toUpperCase();\n }\n\n // Cart data\n if (data.cartData && typeof data.cartData === 'object') {\n dataManagerEvent.cartData = data.cartData as Event['cartData'];\n }\n\n // Event name (for GA4)\n if (isString(data.eventName) && data.eventName) {\n dataManagerEvent.eventName = data.eventName.substring(0, 40);\n }\n\n // Event source\n if (isString(data.eventSource) && data.eventSource) {\n dataManagerEvent.eventSource = data.eventSource as Event['eventSource'];\n }\n\n // Consent - check mapped data first, then fallback to event.consent\n const mappedConsent: Consent = {};\n\n // Check for mapped consent values (from Settings or event mapping)\n if (typeof data.adUserData === 'boolean') {\n mappedConsent.adUserData = data.adUserData\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n if (typeof data.adPersonalization === 'boolean') {\n mappedConsent.adPersonalization = data.adPersonalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // If no mapped consent, fall back to event.consent\n if (Object.keys(mappedConsent).length === 0) {\n const eventConsent = formatConsent(event.consent);\n if (eventConsent) {\n dataManagerEvent.consent = eventConsent;\n }\n } else {\n dataManagerEvent.consent = mappedConsent;\n }\n\n return dataManagerEvent;\n}\n","import { isString } from '@walkeros/core';\nimport { getHashServer } from '@walkeros/server-core';\n\n/**\n * Normalize email address according to Google Data Manager requirements\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#email\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove dots (.) for gmail.com and googlemail.com\n * 4. SHA-256 hash\n */\nexport async function hashEmail(email: string): Promise<string> {\n if (!isString(email) || !email) return '';\n\n // Trim and lowercase\n let normalized = email.trim().toLowerCase();\n\n // Remove dots for Gmail addresses\n if (\n normalized.endsWith('@gmail.com') ||\n normalized.endsWith('@googlemail.com')\n ) {\n const [localPart, domain] = normalized.split('@');\n normalized = `${localPart.replace(/\\./g, '')}@${domain}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize phone number to E.164 format and hash\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#phone\n *\n * E.164 format: +[country code][number] (max 15 digits after +)\n * Example: +18005550100\n *\n * 1. Remove all non-digit characters except leading +\n * 2. Ensure it starts with +\n * 3. SHA-256 hash\n */\nexport async function hashPhone(phone: string): Promise<string> {\n if (!isString(phone) || !phone) return '';\n\n // Remove all non-digit characters except + at the start\n let normalized = phone.trim();\n\n // Extract country code if present\n const hasPlus = normalized.startsWith('+');\n\n // Remove all non-digits\n normalized = normalized.replace(/\\D/g, '');\n\n // Add + prefix if it was there or if number is long enough\n if (hasPlus || normalized.length > 10) {\n normalized = `+${normalized}`;\n } else {\n // Assume US number if no country code (default behavior)\n normalized = `+1${normalized}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize and hash a name (first or last name)\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#name\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove common prefixes (Mr., Mrs., Dr.) for first names\n * 4. Remove common suffixes (Jr., Sr., III) for last names\n * 5. SHA-256 hash\n */\nexport async function hashName(\n name: string,\n type: 'given' | 'family' = 'given',\n): Promise<string> {\n if (!isString(name) || !name) return '';\n\n // Trim and lowercase\n let normalized = name.trim().toLowerCase();\n\n // Remove prefixes for given names\n if (type === 'given') {\n const prefixes = ['mr.', 'mrs.', 'ms.', 'miss.', 'dr.', 'prof.'];\n for (const prefix of prefixes) {\n if (normalized.startsWith(prefix)) {\n normalized = normalized.substring(prefix.length).trim();\n break;\n }\n }\n }\n\n // Remove suffixes for family names (check with and without space)\n // Sort by length (longest first) to match \"iii\" before \"ii\"\n if (type === 'family') {\n const suffixes = ['jr.', 'sr.', 'iii', 'ii', 'iv', 'v'];\n for (const suffix of suffixes) {\n // Check for suffix with space before it (e.g., \" jr.\")\n const suffixWithSpace = ` ${suffix}`;\n if (normalized.endsWith(suffixWithSpace)) {\n normalized = normalized\n .substring(0, normalized.length - suffixWithSpace.length)\n .trim();\n break;\n }\n // Check for suffix without space\n if (normalized.endsWith(suffix)) {\n normalized = normalized\n .substring(0, normalized.length - suffix.length)\n .trim();\n break;\n }\n }\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Hash multiple email addresses\n */\nexport async function hashEmails(emails: string[]): Promise<string[]> {\n return Promise.all(emails.map((email) => hashEmail(email)));\n}\n\n/**\n * Hash multiple phone numbers\n */\nexport async function hashPhones(phones: string[]): Promise<string[]> {\n return Promise.all(phones.map((phone) => hashPhone(phone)));\n}\n","import { GoogleAuth, type OAuth2Client } from 'google-auth-library';\nimport type { Settings } from './types';\n\nconst DEFAULT_SCOPES = ['https://www.googleapis.com/auth/datamanager'];\n\n/**\n * Authentication error with cause tracking\n */\nexport class AuthError extends Error {\n constructor(\n message: string,\n public cause?: Error,\n ) {\n super(message);\n this.name = 'DataManagerAuthError';\n }\n}\n\n/**\n * Creates Google Auth client based on settings\n *\n * Authentication priority:\n * 1. credentials (inline service account) - if provided\n * 2. keyFilename (service account file) - if provided\n * 3. Application Default Credentials (ADC) - automatic fallback\n * - GOOGLE_APPLICATION_CREDENTIALS env var\n * - GCP metadata server (Cloud Functions, Cloud Run, GCE)\n *\n * @param settings - Configuration with auth options\n * @returns OAuth2Client for token retrieval\n * @throws AuthError if authentication fails\n */\nexport async function createAuthClient(\n settings: Settings,\n): Promise<OAuth2Client> {\n const { credentials, keyFilename, scopes = DEFAULT_SCOPES } = settings;\n\n try {\n if (credentials) {\n const auth = new GoogleAuth({\n credentials,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n if (keyFilename) {\n const auth = new GoogleAuth({\n keyFilename,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n const auth = new GoogleAuth({ scopes });\n return (await auth.getClient()) as OAuth2Client;\n } catch (error) {\n throw new AuthError(\n 'Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.',\n error instanceof Error ? error : undefined,\n );\n }\n}\n\n/**\n * Gets access token from auth client\n * Automatically returns cached token if valid or refreshes if expired\n *\n * @param authClient - OAuth2 client from createAuthClient()\n * @returns Fresh access token\n * @throws AuthError if token retrieval fails\n */\nexport async function getAccessToken(\n authClient: OAuth2Client,\n): Promise<string> {\n try {\n const tokenResponse = await authClient.getAccessToken();\n\n if (!tokenResponse.token) {\n throw new AuthError('Auth client returned empty token');\n }\n\n return tokenResponse.token;\n } catch (error) {\n throw new AuthError(\n 'Failed to obtain access token',\n error instanceof Error ? error : undefined,\n );\n }\n}\n","import type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationServer } from '@walkeros/server-core';\nimport type { OAuth2Client } from 'google-auth-library';\n\nexport interface Settings {\n /**\n * Service account credentials (client_email + private_key)\n * Recommended for serverless environments (AWS Lambda, Docker, etc.)\n */\n credentials?: {\n client_email: string;\n private_key: string;\n };\n\n /**\n * Path to service account JSON file\n * For local development or environments with filesystem access\n */\n keyFilename?: string;\n\n /**\n * OAuth scopes for Data Manager API\n * @default ['https://www.googleapis.com/auth/datamanager']\n */\n scopes?: string[];\n\n /** Array of destination accounts and conversion actions/user lists */\n destinations: Destination[];\n\n /** Event source for all events. Defaults to WEB if not specified */\n eventSource?: EventSource;\n\n /** Maximum number of events to batch before sending (max 2000) */\n batchSize?: number;\n\n /** Time in milliseconds to wait before auto-flushing batch */\n batchInterval?: number;\n\n /** If true, validate request without ingestion (testing mode) */\n validateOnly?: boolean;\n\n /** Override API endpoint (for testing) */\n url?: string;\n\n /** Request-level consent for all events */\n consent?: Consent;\n\n /** Test event code for debugging (optional) */\n testEventCode?: string;\n\n /** Guided helpers: User data mapping (applies to all events) */\n userData?: WalkerOSMapping.Map;\n\n /** Guided helper: First-party user ID */\n userId?: WalkerOSMapping.Value;\n\n /** Guided helper: GA4 client ID */\n clientId?: WalkerOSMapping.Value;\n\n /** Guided helper: Privacy-safe attribution (Google's sessionAttributes) */\n sessionAttributes?: WalkerOSMapping.Value;\n\n /** Consent mapping: Map consent field to adUserData (string = field name, boolean = static value) */\n consentAdUserData?: string | boolean;\n\n /** Consent mapping: Map consent field to adPersonalization (string = field name, boolean = static value) */\n consentAdPersonalization?: string | boolean;\n}\n\nexport interface Mapping {\n // Attribution identifiers (optional, for explicit mapping)\n gclid?: WalkerOSMapping.Value;\n gbraid?: WalkerOSMapping.Value;\n wbraid?: WalkerOSMapping.Value;\n sessionAttributes?: WalkerOSMapping.Value;\n}\n\nexport interface Env extends DestinationServer.Env {\n fetch?: typeof fetch;\n authClient?: OAuth2Client | null;\n}\n\nexport type InitSettings = Partial<Settings>;\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface DestinationInterface\n extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\n/**\n * Config after validation - settings is guaranteed to exist with required fields\n * Use this type after calling getConfig() to get proper type narrowing\n * After validation, eventSource is always set (defaults to 'WEB')\n */\nexport type ValidatedConfig = Omit<Config, 'settings'> & {\n settings: Settings & { eventSource: EventSource };\n};\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// Google Data Manager API Types\n// https://developers.google.com/data-manager/api/reference\n\n/**\n * Destination account and product identifier\n * https://developers.google.com/data-manager/api/reference/rest/v1/Destination\n */\nexport interface Destination {\n /** Reference identifier for this destination */\n reference?: string;\n\n /** Login account (account initiating the request) */\n loginAccount?: ProductAccount;\n\n /** Linked account (child account linked to login account) */\n linkedAccount?: ProductAccount;\n\n /** Operating account (account where data is sent) */\n operatingAccount?: ProductAccount;\n\n /** Product-specific destination ID (conversion action or user list) */\n productDestinationId?: string;\n}\n\n/**\n * Product account information\n */\nexport interface ProductAccount {\n /** Account ID (e.g., \"123-456-7890\" for Google Ads) */\n accountId: string;\n\n /** Type of account */\n accountType: AccountType;\n}\n\nexport type AccountType =\n | 'ACCOUNT_TYPE_UNSPECIFIED'\n | 'GOOGLE_ADS'\n | 'DISPLAY_VIDEO_ADVERTISER'\n | 'DISPLAY_VIDEO_PARTNER'\n | 'GOOGLE_ANALYTICS_PROPERTY'\n | 'DATA_PARTNER';\n\nexport type EventSource = 'WEB' | 'APP' | 'IN_STORE' | 'PHONE' | 'OTHER';\n\n/**\n * Consent for Digital Markets Act (DMA) compliance\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n */\nexport interface Consent {\n /** Consent for data collection and use */\n adUserData?: ConsentStatus;\n\n /** Consent for ad personalization */\n adPersonalization?: ConsentStatus;\n}\n\nexport type ConsentStatus = 'CONSENT_GRANTED' | 'CONSENT_DENIED';\n\n/**\n * Request body for events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/events/ingest\n */\nexport interface IngestEventsRequest {\n /** Array of destinations for these events (max 10) */\n destinations: Destination[];\n\n /** Array of events to ingest (max 2000) */\n events: Event[];\n\n /** Request-level consent (overridden by event-level) */\n consent?: Consent;\n\n /** If true, validate without ingestion */\n validateOnly?: boolean;\n\n /** Test event code for debugging */\n testEventCode?: string;\n}\n\n/**\n * Single event for ingestion\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport interface Event {\n /** Destination references for routing */\n destinationReferences?: string[];\n\n /** Transaction ID for deduplication (max 512 chars) */\n transactionId?: string;\n\n /** Event timestamp in RFC 3339 format */\n eventTimestamp?: string;\n\n /** Last updated timestamp in RFC 3339 format */\n lastUpdatedTimestamp?: string;\n\n /** User data with identifiers (max 10 identifiers) */\n userData?: UserData;\n\n /** Event-level consent (overrides request-level) */\n consent?: Consent;\n\n /** Attribution identifiers */\n adIdentifiers?: AdIdentifiers;\n\n /** Currency code (ISO 4217, 3 chars) */\n currency?: string;\n\n /** Conversion value */\n conversionValue?: number;\n\n /** Source of the event */\n eventSource?: EventSource;\n\n /** Device information for the event */\n eventDeviceInfo?: DeviceInfo;\n\n /** Shopping cart data */\n cartData?: CartData;\n\n /** Custom variables for the event */\n customVariables?: CustomVariable[];\n\n /** Experimental fields (subject to change) */\n experimentalFields?: ExperimentalField[];\n\n /** User properties */\n userProperties?: UserProperties;\n\n /** Event name for GA4 (max 40 chars, required for GA4) */\n eventName?: string;\n\n /** Google Analytics client ID (max 255 chars) */\n clientId?: string;\n\n /** First-party user ID (max 256 chars) */\n userId?: string;\n\n /** Additional event parameters */\n additionalEventParameters?: EventParameter[];\n}\n\n/**\n * Device information\n */\nexport interface DeviceInfo {\n /** User agent string */\n userAgent?: string;\n}\n\n/**\n * Custom variable\n */\nexport interface CustomVariable {\n /** Variable name */\n name?: string;\n\n /** Variable value */\n value?: string;\n}\n\n/**\n * Experimental field\n */\nexport interface ExperimentalField {\n /** Field name */\n name?: string;\n\n /** Field value */\n value?: string;\n}\n\n/**\n * User properties\n */\nexport interface UserProperties {\n /** Property values */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Event parameter\n */\nexport interface EventParameter {\n /** Parameter name */\n name?: string;\n\n /** Parameter value */\n value?: string | number;\n}\n\n/**\n * User data with identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n */\nexport interface UserData {\n /** Array of user identifiers (max 10) */\n userIdentifiers: UserIdentifier[];\n}\n\n/**\n * User identifier (email, phone, or address)\n */\nexport type UserIdentifier =\n | { emailAddress: string }\n | { phoneNumber: string }\n | { address: Address };\n\n/**\n * Address for user identification\n * https://developers.google.com/data-manager/api/reference/rest/v1/Address\n */\nexport interface Address {\n /** Given name (first name) - SHA-256 hashed */\n givenName?: string;\n\n /** Family name (last name) - SHA-256 hashed */\n familyName?: string;\n\n /** ISO-3166-1 alpha-2 country code - NOT hashed (e.g., \"US\", \"GB\") */\n regionCode?: string;\n\n /** Postal code - NOT hashed */\n postalCode?: string;\n}\n\n/**\n * Attribution identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n */\nexport interface AdIdentifiers {\n /** Session attributes (privacy-safe attribution) */\n sessionAttributes?: string;\n\n /** Google Click ID (primary attribution) */\n gclid?: string;\n\n /** iOS attribution identifier (post-ATT) */\n gbraid?: string;\n\n /** Web-to-app attribution identifier */\n wbraid?: string;\n\n /** Device information for landing page */\n landingPageDeviceInfo?: DeviceInfo;\n}\n\n/**\n * Shopping cart data\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartData\n */\nexport interface CartData {\n /** Array of cart items (max 200) */\n items: CartItem[];\n}\n\n/**\n * Single cart item\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartItem\n */\nexport interface CartItem {\n /** Merchant product ID (max 127 chars) */\n merchantProductId?: string;\n\n /** Item price */\n price?: number;\n\n /** Item quantity */\n quantity?: number;\n}\n\n/**\n * Response from events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/IngestEventsResponse\n */\nexport interface IngestEventsResponse {\n /** Unique request ID for status checking */\n requestId: string;\n\n /** Validation errors (only if validateOnly=true) */\n validationErrors?: ValidationError[];\n}\n\n/**\n * Validation error\n */\nexport interface ValidationError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Field path that caused the error */\n fieldPath?: string;\n}\n\n/**\n * Request status response\n * https://developers.google.com/data-manager/api/reference/rest/v1/requestStatus/retrieve\n */\nexport interface RequestStatusResponse {\n /** Unique request ID */\n requestId: string;\n\n /** Processing state */\n state: RequestState;\n\n /** Number of events successfully ingested */\n eventsIngested?: number;\n\n /** Number of events that failed */\n eventsFailed?: number;\n\n /** Array of errors (if any) */\n errors?: RequestError[];\n}\n\nexport type RequestState =\n | 'STATE_UNSPECIFIED'\n | 'PENDING'\n | 'PROCESSING'\n | 'SUCCEEDED'\n | 'FAILED'\n | 'PARTIALLY_SUCCEEDED';\n\n/**\n * Request error\n */\nexport interface RequestError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Number of events affected by this error */\n eventCount?: number;\n}\n","import type { DestinationInterface } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\nimport { createAuthClient } from './auth';\n\nexport * as DestinationDataManager from './types';\n\nexport const destinationDataManager: DestinationInterface = {\n type: 'datamanager',\n\n config: {},\n\n async init({ config: partialConfig, env, logger }) {\n // getConfig validates required fields and returns ValidatedConfig\n const config = getConfig(partialConfig, logger);\n\n try {\n const authClient = await createAuthClient(config.settings);\n logger.debug('Auth client created');\n\n return {\n ...config,\n env: {\n ...env,\n authClient,\n },\n };\n } catch (error) {\n logger.throw(\n `Data Manager authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n },\n\n async push(event, { config, mapping, data, collector, env, logger }) {\n return await push(event, { config, mapping, data, collector, env, logger });\n },\n};\n\nexport default destinationDataManager;\n"],"mappings":"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","names":["isString","isString","auth"]}
1
+ {"version":3,"sources":["../src/config.ts","../src/push.ts","../src/format.ts","../src/hash.ts","../src/auth.ts","../src/types/index.ts","../src/index.ts"],"sourcesContent":["import type {\n ValidatedConfig,\n Settings,\n PartialConfig,\n EventSource,\n} from './types';\nimport type { Logger } from '@walkeros/core';\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): ValidatedConfig {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { destinations, eventSource = 'WEB' } = settings;\n\n if (!destinations || destinations.length === 0)\n logger.throw('Config settings destinations missing or empty');\n\n const settingsConfig: Settings & { eventSource: EventSource } = {\n ...settings,\n destinations,\n eventSource,\n };\n\n return { ...partialConfig, settings: settingsConfig };\n}\n","import type { PushFn } from './types';\nimport type { IngestEventsRequest, IngestEventsResponse } from './types';\nimport { getMappingValue, isObject } from '@walkeros/core';\nimport { formatEvent, formatConsent } from './format';\nimport { getAccessToken } from './auth';\nimport { getConfig } from './config';\n\nexport const push: PushFn = async function (\n event,\n { config, rule, data, collector, env, logger },\n) {\n // Validate config and get typed settings\n const validatedConfig = getConfig(config, logger);\n const {\n destinations,\n eventSource,\n validateOnly = false,\n url = 'https://datamanager.googleapis.com/v1',\n consent: requestConsent,\n testEventCode,\n userData,\n userId,\n clientId,\n sessionAttributes,\n consentAdUserData,\n consentAdPersonalization,\n } = validatedConfig.settings;\n\n // Extract Settings guided helpers\n const userDataMapped = userData\n ? await getMappingValue(event, { map: userData })\n : {};\n const userIdMapped = userId\n ? await getMappingValue(event, userId)\n : undefined;\n const clientIdMapped = clientId\n ? await getMappingValue(event, clientId)\n : undefined;\n const sessionAttributesMapped = sessionAttributes\n ? await getMappingValue(event, sessionAttributes)\n : undefined;\n\n // Extract consent from Settings\n const consentAdUserDataValue =\n typeof consentAdUserData === 'boolean'\n ? consentAdUserData\n : typeof consentAdUserData === 'string' && event.consent\n ? event.consent[consentAdUserData]\n : undefined;\n\n const consentAdPersonalizationValue =\n typeof consentAdPersonalization === 'boolean'\n ? consentAdPersonalization\n : typeof consentAdPersonalization === 'string' && event.consent\n ? event.consent[consentAdPersonalization]\n : undefined;\n\n // Build Settings helpers object\n const settingsHelpers: Record<string, unknown> = {};\n if (isObject(userDataMapped)) {\n Object.assign(settingsHelpers, userDataMapped);\n }\n if (userIdMapped !== undefined) settingsHelpers.userId = userIdMapped;\n if (clientIdMapped !== undefined) settingsHelpers.clientId = clientIdMapped;\n if (sessionAttributesMapped !== undefined)\n settingsHelpers.sessionAttributes = sessionAttributesMapped;\n if (consentAdUserDataValue !== undefined)\n settingsHelpers.adUserData = consentAdUserDataValue;\n if (consentAdPersonalizationValue !== undefined)\n settingsHelpers.adPersonalization = consentAdPersonalizationValue;\n\n // Get mapped data from destination config and event mapping\n const configData = validatedConfig.data\n ? await getMappingValue(event, validatedConfig.data)\n : {};\n const eventData = isObject(data) ? data : {};\n\n // Merge: Settings helpers < config.data < event mapping data\n const finalData = {\n ...settingsHelpers,\n ...(isObject(configData) ? configData : {}),\n ...eventData,\n };\n\n // Format event for Data Manager API\n const dataManagerEvent = await formatEvent(event, finalData);\n\n // Apply event source from settings (required)\n if (!dataManagerEvent.eventSource) {\n dataManagerEvent.eventSource = eventSource;\n }\n\n // Apply request-level consent if event doesn't have consent\n if (!dataManagerEvent.consent && requestConsent) {\n dataManagerEvent.consent = requestConsent;\n }\n\n // Validate required fields before API call\n if (!dataManagerEvent.transactionId) {\n logger.throw('transactionId is required');\n }\n\n // Check if any destination is GA4 (requires eventName)\n const hasGA4Destination = destinations.some(\n (d) => d.operatingAccount?.accountType === 'GOOGLE_ANALYTICS_PROPERTY',\n );\n\n if (hasGA4Destination && !dataManagerEvent.eventName) {\n logger.throw('eventName is required for GA4 destinations');\n }\n\n // Build API request\n const requestBody: IngestEventsRequest = {\n events: [dataManagerEvent],\n destinations,\n };\n\n // Add optional parameters\n if (requestConsent) {\n requestBody.consent = requestConsent;\n }\n\n if (validateOnly) {\n requestBody.validateOnly = true;\n }\n\n if (testEventCode) {\n requestBody.testEventCode = testEventCode;\n }\n\n const authClient = env?.authClient;\n if (!authClient) {\n return logger.throw(\n 'Auth client not initialized. Ensure init() was called successfully.',\n );\n }\n\n let accessToken: string;\n try {\n accessToken = await getAccessToken(authClient);\n } catch (error) {\n logger.error('Authentication failed', { error });\n throw error;\n }\n\n const fetchFn = env?.fetch || fetch;\n const endpoint = `${url}/events:ingest`;\n\n logger.debug('Sending to Data Manager API', {\n endpoint,\n eventCount: requestBody.events.length,\n destinations: destinations.length,\n validateOnly,\n });\n\n const response = await fetchFn(endpoint, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n logger.throw(`Data Manager API error (${response.status}): ${errorText}`);\n }\n\n const result: IngestEventsResponse = await response.json();\n\n logger.debug('API response', {\n status: response.status,\n requestId: result.requestId,\n });\n\n // If validation errors exist, throw them\n if (result.validationErrors && result.validationErrors.length > 0) {\n logger.throw(\n `Validation errors: ${JSON.stringify(result.validationErrors)}`,\n );\n }\n};\n","import type { WalkerOS } from '@walkeros/core';\nimport { isString, isDefined } from '@walkeros/core';\nimport type {\n Event,\n UserData,\n UserIdentifier,\n AdIdentifiers,\n Consent,\n ConsentStatus,\n} from './types';\nimport { hashEmail, hashPhone, hashName } from './hash';\n\n/**\n * Format walkerOS event timestamp to RFC 3339 format\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n *\n * walkerOS timestamp is in milliseconds, RFC 3339 format: \"2024-01-15T10:30:00Z\"\n */\nexport function formatTimestamp(timestamp: number): string {\n return new Date(timestamp).toISOString();\n}\n\n/**\n * Format user identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n *\n * User data must be explicitly mapped in the mapping configuration.\n * Max 10 identifiers per event\n */\nexport async function formatUserData(\n data: Record<string, unknown>,\n): Promise<UserData | undefined> {\n const identifiers: UserIdentifier[] = [];\n\n // Extract from mapped data only\n // Email\n if (isString(data.email) && data.email) {\n const hashedEmail = await hashEmail(data.email);\n if (hashedEmail) {\n identifiers.push({ emailAddress: hashedEmail });\n }\n }\n\n // Phone\n if (isString(data.phone) && data.phone) {\n const hashedPhone = await hashPhone(data.phone);\n if (hashedPhone) {\n identifiers.push({ phoneNumber: hashedPhone });\n }\n }\n\n // Address from mapped properties\n const hasAddress =\n data.firstName || data.lastName || data.regionCode || data.postalCode;\n\n if (hasAddress) {\n const address: Record<string, string> = {};\n\n if (isString(data.firstName) && data.firstName) {\n address.givenName = await hashName(data.firstName, 'given');\n }\n\n if (isString(data.lastName) && data.lastName) {\n address.familyName = await hashName(data.lastName, 'family');\n }\n\n // Region code is NOT hashed\n if (isString(data.regionCode) && data.regionCode) {\n address.regionCode = data.regionCode.toUpperCase();\n }\n\n // Postal code is NOT hashed\n if (isString(data.postalCode) && data.postalCode) {\n address.postalCode = data.postalCode;\n }\n\n if (Object.keys(address).length > 0) {\n identifiers.push({ address });\n }\n }\n\n // Limit to 10 identifiers\n if (identifiers.length === 0) return undefined;\n\n return {\n userIdentifiers: identifiers.slice(0, 10),\n };\n}\n\n/**\n * Extract and format attribution identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n *\n * Attribution identifiers should be mapped explicitly in the mapping configuration.\n * Example: { gclid: 'context.gclid', gbraid: 'context.gbraid' }\n */\nexport function formatAdIdentifiers(\n data: Record<string, unknown>,\n): AdIdentifiers | undefined {\n const identifiers: AdIdentifiers = {};\n\n // Extract from mapped data (already processed by mapping system)\n if (isString(data.gclid) && data.gclid) {\n identifiers.gclid = data.gclid;\n }\n if (isString(data.gbraid) && data.gbraid) {\n identifiers.gbraid = data.gbraid;\n }\n if (isString(data.wbraid) && data.wbraid) {\n identifiers.wbraid = data.wbraid;\n }\n if (isString(data.sessionAttributes) && data.sessionAttributes) {\n identifiers.sessionAttributes = data.sessionAttributes;\n }\n\n return Object.keys(identifiers).length > 0 ? identifiers : undefined;\n}\n\n/**\n * Map walkerOS consent to Data Manager consent format\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n *\n * walkerOS: { marketing: true, personalization: false }\n * Data Manager: { adUserData: 'CONSENT_GRANTED', adPersonalization: 'CONSENT_DENIED' }\n */\nexport function formatConsent(\n walkerOSConsent: WalkerOS.Consent | undefined,\n): Consent | undefined {\n if (!walkerOSConsent) return undefined;\n\n const consent: Consent = {};\n\n // Map marketing consent to adUserData\n if (isDefined(walkerOSConsent.marketing)) {\n consent.adUserData = walkerOSConsent.marketing\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // Map personalization consent to adPersonalization\n if (isDefined(walkerOSConsent.personalization)) {\n consent.adPersonalization = walkerOSConsent.personalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n return Object.keys(consent).length > 0 ? consent : undefined;\n}\n\n/**\n * Format complete event for Data Manager API\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport async function formatEvent(\n event: WalkerOS.Event,\n mappedData?: Record<string, unknown>,\n): Promise<Event> {\n const dataManagerEvent: Event = {\n eventTimestamp: formatTimestamp(event.timestamp),\n };\n\n // Use only mapped data (no fallback to event.data)\n const data = mappedData || {};\n\n // Transaction ID for deduplication\n if (isString(data.transactionId) && data.transactionId) {\n dataManagerEvent.transactionId = data.transactionId.substring(0, 512);\n }\n\n // Client ID (GA)\n if (isString(data.clientId) && data.clientId) {\n dataManagerEvent.clientId = data.clientId.substring(0, 255);\n }\n\n // User ID\n if (isString(data.userId) && data.userId) {\n dataManagerEvent.userId = data.userId.substring(0, 256);\n }\n\n // User data\n const userData = await formatUserData(data);\n if (userData) {\n dataManagerEvent.userData = userData;\n }\n\n // Attribution identifiers\n const adIdentifiers = formatAdIdentifiers(data);\n if (adIdentifiers) {\n dataManagerEvent.adIdentifiers = adIdentifiers;\n }\n\n // Conversion value\n if (typeof data.conversionValue === 'number') {\n dataManagerEvent.conversionValue = data.conversionValue;\n }\n\n // Currency\n if (isString(data.currency) && data.currency) {\n dataManagerEvent.currency = data.currency.substring(0, 3).toUpperCase();\n }\n\n // Cart data\n if (data.cartData && typeof data.cartData === 'object') {\n dataManagerEvent.cartData = data.cartData as Event['cartData'];\n }\n\n // Event name (for GA4)\n if (isString(data.eventName) && data.eventName) {\n dataManagerEvent.eventName = data.eventName.substring(0, 40);\n }\n\n // Event source\n if (isString(data.eventSource) && data.eventSource) {\n dataManagerEvent.eventSource = data.eventSource as Event['eventSource'];\n }\n\n // Consent - check mapped data first, then fallback to event.consent\n const mappedConsent: Consent = {};\n\n // Check for mapped consent values (from Settings or event mapping)\n if (typeof data.adUserData === 'boolean') {\n mappedConsent.adUserData = data.adUserData\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n if (typeof data.adPersonalization === 'boolean') {\n mappedConsent.adPersonalization = data.adPersonalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // If no mapped consent, fall back to event.consent\n if (Object.keys(mappedConsent).length === 0) {\n const eventConsent = formatConsent(event.consent);\n if (eventConsent) {\n dataManagerEvent.consent = eventConsent;\n }\n } else {\n dataManagerEvent.consent = mappedConsent;\n }\n\n return dataManagerEvent;\n}\n","import { isString } from '@walkeros/core';\nimport { getHashServer } from '@walkeros/server-core';\n\n/**\n * Normalize email address according to Google Data Manager requirements\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#email\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove dots (.) for gmail.com and googlemail.com\n * 4. SHA-256 hash\n */\nexport async function hashEmail(email: string): Promise<string> {\n if (!isString(email) || !email) return '';\n\n // Trim and lowercase\n let normalized = email.trim().toLowerCase();\n\n // Remove dots for Gmail addresses\n if (\n normalized.endsWith('@gmail.com') ||\n normalized.endsWith('@googlemail.com')\n ) {\n const [localPart, domain] = normalized.split('@');\n normalized = `${localPart.replace(/\\./g, '')}@${domain}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize phone number to E.164 format and hash\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#phone\n *\n * E.164 format: +[country code][number] (max 15 digits after +)\n * Example: +18005550100\n *\n * 1. Remove all non-digit characters except leading +\n * 2. Ensure it starts with +\n * 3. SHA-256 hash\n */\nexport async function hashPhone(phone: string): Promise<string> {\n if (!isString(phone) || !phone) return '';\n\n // Remove all non-digit characters except + at the start\n let normalized = phone.trim();\n\n // Extract country code if present\n const hasPlus = normalized.startsWith('+');\n\n // Remove all non-digits\n normalized = normalized.replace(/\\D/g, '');\n\n // Add + prefix if it was there or if number is long enough\n if (hasPlus || normalized.length > 10) {\n normalized = `+${normalized}`;\n } else {\n // Assume US number if no country code (default behavior)\n normalized = `+1${normalized}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize and hash a name (first or last name)\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#name\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove common prefixes (Mr., Mrs., Dr.) for first names\n * 4. Remove common suffixes (Jr., Sr., III) for last names\n * 5. SHA-256 hash\n */\nexport async function hashName(\n name: string,\n type: 'given' | 'family' = 'given',\n): Promise<string> {\n if (!isString(name) || !name) return '';\n\n // Trim and lowercase\n let normalized = name.trim().toLowerCase();\n\n // Remove prefixes for given names\n if (type === 'given') {\n const prefixes = ['mr.', 'mrs.', 'ms.', 'miss.', 'dr.', 'prof.'];\n for (const prefix of prefixes) {\n if (normalized.startsWith(prefix)) {\n normalized = normalized.substring(prefix.length).trim();\n break;\n }\n }\n }\n\n // Remove suffixes for family names (check with and without space)\n // Sort by length (longest first) to match \"iii\" before \"ii\"\n if (type === 'family') {\n const suffixes = ['jr.', 'sr.', 'iii', 'ii', 'iv', 'v'];\n for (const suffix of suffixes) {\n // Check for suffix with space before it (e.g., \" jr.\")\n const suffixWithSpace = ` ${suffix}`;\n if (normalized.endsWith(suffixWithSpace)) {\n normalized = normalized\n .substring(0, normalized.length - suffixWithSpace.length)\n .trim();\n break;\n }\n // Check for suffix without space\n if (normalized.endsWith(suffix)) {\n normalized = normalized\n .substring(0, normalized.length - suffix.length)\n .trim();\n break;\n }\n }\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Hash multiple email addresses\n */\nexport async function hashEmails(emails: string[]): Promise<string[]> {\n return Promise.all(emails.map((email) => hashEmail(email)));\n}\n\n/**\n * Hash multiple phone numbers\n */\nexport async function hashPhones(phones: string[]): Promise<string[]> {\n return Promise.all(phones.map((phone) => hashPhone(phone)));\n}\n","import { GoogleAuth, type OAuth2Client } from 'google-auth-library';\nimport type { Settings } from './types';\n\nconst DEFAULT_SCOPES = ['https://www.googleapis.com/auth/datamanager'];\n\n/**\n * Authentication error with cause tracking\n */\nexport class AuthError extends Error {\n constructor(\n message: string,\n public cause?: Error,\n ) {\n super(message);\n this.name = 'DataManagerAuthError';\n }\n}\n\n/**\n * Creates Google Auth client based on settings\n *\n * Authentication priority:\n * 1. credentials (inline service account) - if provided\n * 2. keyFilename (service account file) - if provided\n * 3. Application Default Credentials (ADC) - automatic fallback\n * - GOOGLE_APPLICATION_CREDENTIALS env var\n * - GCP metadata server (Cloud Functions, Cloud Run, GCE)\n *\n * @param settings - Configuration with auth options\n * @returns OAuth2Client for token retrieval\n * @throws AuthError if authentication fails\n */\nexport async function createAuthClient(\n settings: Settings,\n): Promise<OAuth2Client> {\n const { credentials, keyFilename, scopes = DEFAULT_SCOPES } = settings;\n\n try {\n if (credentials) {\n const auth = new GoogleAuth({\n credentials,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n if (keyFilename) {\n const auth = new GoogleAuth({\n keyFilename,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n const auth = new GoogleAuth({ scopes });\n return (await auth.getClient()) as OAuth2Client;\n } catch (error) {\n throw new AuthError(\n 'Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.',\n error instanceof Error ? error : undefined,\n );\n }\n}\n\n/**\n * Gets access token from auth client\n * Automatically returns cached token if valid or refreshes if expired\n *\n * @param authClient - OAuth2 client from createAuthClient()\n * @returns Fresh access token\n * @throws AuthError if token retrieval fails\n */\nexport async function getAccessToken(\n authClient: OAuth2Client,\n): Promise<string> {\n try {\n const tokenResponse = await authClient.getAccessToken();\n\n if (!tokenResponse.token) {\n throw new AuthError('Auth client returned empty token');\n }\n\n return tokenResponse.token;\n } catch (error) {\n throw new AuthError(\n 'Failed to obtain access token',\n error instanceof Error ? error : undefined,\n );\n }\n}\n","import type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationServer } from '@walkeros/server-core';\nimport type { OAuth2Client } from 'google-auth-library';\n\nexport interface Settings {\n /**\n * Service account credentials (client_email + private_key)\n * Recommended for serverless environments (AWS Lambda, Docker, etc.)\n */\n credentials?: {\n client_email: string;\n private_key: string;\n };\n\n /**\n * Path to service account JSON file\n * For local development or environments with filesystem access\n */\n keyFilename?: string;\n\n /**\n * OAuth scopes for Data Manager API\n * @default ['https://www.googleapis.com/auth/datamanager']\n */\n scopes?: string[];\n\n /** Array of destination accounts and conversion actions/user lists */\n destinations: Destination[];\n\n /** Event source for all events. Defaults to WEB if not specified */\n eventSource?: EventSource;\n\n /** Maximum number of events to batch before sending (max 2000) */\n batchSize?: number;\n\n /** Time in milliseconds to wait before auto-flushing batch */\n batchInterval?: number;\n\n /** If true, validate request without ingestion (testing mode) */\n validateOnly?: boolean;\n\n /** Override API endpoint (for testing) */\n url?: string;\n\n /** Request-level consent for all events */\n consent?: Consent;\n\n /** Test event code for debugging (optional) */\n testEventCode?: string;\n\n /** Guided helpers: User data mapping (applies to all events) */\n userData?: WalkerOSMapping.Map;\n\n /** Guided helper: First-party user ID */\n userId?: WalkerOSMapping.Value;\n\n /** Guided helper: GA4 client ID */\n clientId?: WalkerOSMapping.Value;\n\n /** Guided helper: Privacy-safe attribution (Google's sessionAttributes) */\n sessionAttributes?: WalkerOSMapping.Value;\n\n /** Consent mapping: Map consent field to adUserData (string = field name, boolean = static value) */\n consentAdUserData?: string | boolean;\n\n /** Consent mapping: Map consent field to adPersonalization (string = field name, boolean = static value) */\n consentAdPersonalization?: string | boolean;\n}\n\nexport interface Mapping {\n // Attribution identifiers (optional, for explicit mapping)\n gclid?: WalkerOSMapping.Value;\n gbraid?: WalkerOSMapping.Value;\n wbraid?: WalkerOSMapping.Value;\n sessionAttributes?: WalkerOSMapping.Value;\n}\n\nexport interface Env extends DestinationServer.Env {\n fetch?: typeof fetch;\n authClient?: OAuth2Client | null;\n}\n\nexport type InitSettings = Partial<Settings>;\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface DestinationInterface\n extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\n/**\n * Config after validation - settings is guaranteed to exist with required fields\n * Use this type after calling getConfig() to get proper type narrowing\n * After validation, eventSource is always set (defaults to 'WEB')\n */\nexport type ValidatedConfig = Omit<Config, 'settings'> & {\n settings: Settings & { eventSource: EventSource };\n};\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// Google Data Manager API Types\n// https://developers.google.com/data-manager/api/reference\n\n/**\n * Destination account and product identifier\n * https://developers.google.com/data-manager/api/reference/rest/v1/Destination\n */\nexport interface Destination {\n /** Reference identifier for this destination */\n reference?: string;\n\n /** Login account (account initiating the request) */\n loginAccount?: ProductAccount;\n\n /** Linked account (child account linked to login account) */\n linkedAccount?: ProductAccount;\n\n /** Operating account (account where data is sent) */\n operatingAccount?: ProductAccount;\n\n /** Product-specific destination ID (conversion action or user list) */\n productDestinationId?: string;\n}\n\n/**\n * Product account information\n */\nexport interface ProductAccount {\n /** Account ID (e.g., \"123-456-7890\" for Google Ads) */\n accountId: string;\n\n /** Type of account */\n accountType: AccountType;\n}\n\nexport type AccountType =\n | 'ACCOUNT_TYPE_UNSPECIFIED'\n | 'GOOGLE_ADS'\n | 'DISPLAY_VIDEO_ADVERTISER'\n | 'DISPLAY_VIDEO_PARTNER'\n | 'GOOGLE_ANALYTICS_PROPERTY'\n | 'DATA_PARTNER';\n\nexport type EventSource = 'WEB' | 'APP' | 'IN_STORE' | 'PHONE' | 'OTHER';\n\n/**\n * Consent for Digital Markets Act (DMA) compliance\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n */\nexport interface Consent {\n /** Consent for data collection and use */\n adUserData?: ConsentStatus;\n\n /** Consent for ad personalization */\n adPersonalization?: ConsentStatus;\n}\n\nexport type ConsentStatus = 'CONSENT_GRANTED' | 'CONSENT_DENIED';\n\n/**\n * Request body for events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/events/ingest\n */\nexport interface IngestEventsRequest {\n /** Array of destinations for these events (max 10) */\n destinations: Destination[];\n\n /** Array of events to ingest (max 2000) */\n events: Event[];\n\n /** Request-level consent (overridden by event-level) */\n consent?: Consent;\n\n /** If true, validate without ingestion */\n validateOnly?: boolean;\n\n /** Test event code for debugging */\n testEventCode?: string;\n}\n\n/**\n * Single event for ingestion\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport interface Event {\n /** Destination references for routing */\n destinationReferences?: string[];\n\n /** Transaction ID for deduplication (max 512 chars) */\n transactionId?: string;\n\n /** Event timestamp in RFC 3339 format */\n eventTimestamp?: string;\n\n /** Last updated timestamp in RFC 3339 format */\n lastUpdatedTimestamp?: string;\n\n /** User data with identifiers (max 10 identifiers) */\n userData?: UserData;\n\n /** Event-level consent (overrides request-level) */\n consent?: Consent;\n\n /** Attribution identifiers */\n adIdentifiers?: AdIdentifiers;\n\n /** Currency code (ISO 4217, 3 chars) */\n currency?: string;\n\n /** Conversion value */\n conversionValue?: number;\n\n /** Source of the event */\n eventSource?: EventSource;\n\n /** Device information for the event */\n eventDeviceInfo?: DeviceInfo;\n\n /** Shopping cart data */\n cartData?: CartData;\n\n /** Custom variables for the event */\n customVariables?: CustomVariable[];\n\n /** Experimental fields (subject to change) */\n experimentalFields?: ExperimentalField[];\n\n /** User properties */\n userProperties?: UserProperties;\n\n /** Event name for GA4 (max 40 chars, required for GA4) */\n eventName?: string;\n\n /** Google Analytics client ID (max 255 chars) */\n clientId?: string;\n\n /** First-party user ID (max 256 chars) */\n userId?: string;\n\n /** Additional event parameters */\n additionalEventParameters?: EventParameter[];\n}\n\n/**\n * Device information\n */\nexport interface DeviceInfo {\n /** User agent string */\n userAgent?: string;\n}\n\n/**\n * Custom variable\n */\nexport interface CustomVariable {\n /** Variable name */\n name?: string;\n\n /** Variable value */\n value?: string;\n}\n\n/**\n * Experimental field\n */\nexport interface ExperimentalField {\n /** Field name */\n name?: string;\n\n /** Field value */\n value?: string;\n}\n\n/**\n * User properties\n */\nexport interface UserProperties {\n /** Property values */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Event parameter\n */\nexport interface EventParameter {\n /** Parameter name */\n name?: string;\n\n /** Parameter value */\n value?: string | number;\n}\n\n/**\n * User data with identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n */\nexport interface UserData {\n /** Array of user identifiers (max 10) */\n userIdentifiers: UserIdentifier[];\n}\n\n/**\n * User identifier (email, phone, or address)\n */\nexport type UserIdentifier =\n | { emailAddress: string }\n | { phoneNumber: string }\n | { address: Address };\n\n/**\n * Address for user identification\n * https://developers.google.com/data-manager/api/reference/rest/v1/Address\n */\nexport interface Address {\n /** Given name (first name) - SHA-256 hashed */\n givenName?: string;\n\n /** Family name (last name) - SHA-256 hashed */\n familyName?: string;\n\n /** ISO-3166-1 alpha-2 country code - NOT hashed (e.g., \"US\", \"GB\") */\n regionCode?: string;\n\n /** Postal code - NOT hashed */\n postalCode?: string;\n}\n\n/**\n * Attribution identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n */\nexport interface AdIdentifiers {\n /** Session attributes (privacy-safe attribution) */\n sessionAttributes?: string;\n\n /** Google Click ID (primary attribution) */\n gclid?: string;\n\n /** iOS attribution identifier (post-ATT) */\n gbraid?: string;\n\n /** Web-to-app attribution identifier */\n wbraid?: string;\n\n /** Device information for landing page */\n landingPageDeviceInfo?: DeviceInfo;\n}\n\n/**\n * Shopping cart data\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartData\n */\nexport interface CartData {\n /** Array of cart items (max 200) */\n items: CartItem[];\n}\n\n/**\n * Single cart item\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartItem\n */\nexport interface CartItem {\n /** Merchant product ID (max 127 chars) */\n merchantProductId?: string;\n\n /** Item price */\n price?: number;\n\n /** Item quantity */\n quantity?: number;\n}\n\n/**\n * Response from events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/IngestEventsResponse\n */\nexport interface IngestEventsResponse {\n /** Unique request ID for status checking */\n requestId: string;\n\n /** Validation errors (only if validateOnly=true) */\n validationErrors?: ValidationError[];\n}\n\n/**\n * Validation error\n */\nexport interface ValidationError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Field path that caused the error */\n fieldPath?: string;\n}\n\n/**\n * Request status response\n * https://developers.google.com/data-manager/api/reference/rest/v1/requestStatus/retrieve\n */\nexport interface RequestStatusResponse {\n /** Unique request ID */\n requestId: string;\n\n /** Processing state */\n state: RequestState;\n\n /** Number of events successfully ingested */\n eventsIngested?: number;\n\n /** Number of events that failed */\n eventsFailed?: number;\n\n /** Array of errors (if any) */\n errors?: RequestError[];\n}\n\nexport type RequestState =\n | 'STATE_UNSPECIFIED'\n | 'PENDING'\n | 'PROCESSING'\n | 'SUCCEEDED'\n | 'FAILED'\n | 'PARTIALLY_SUCCEEDED';\n\n/**\n * Request error\n */\nexport interface RequestError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Number of events affected by this error */\n eventCount?: number;\n}\n","import type { DestinationInterface } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\nimport { createAuthClient } from './auth';\n\nexport * as DestinationDataManager from './types';\n\nexport const destinationDataManager: DestinationInterface = {\n type: 'datamanager',\n\n config: {},\n\n async init({ config: partialConfig, env, logger }) {\n // getConfig validates required fields and returns ValidatedConfig\n const config = getConfig(partialConfig, logger);\n\n try {\n const authClient = await createAuthClient(config.settings);\n logger.debug('Auth client created');\n\n return {\n ...config,\n env: {\n ...env,\n authClient,\n },\n };\n } catch (error) {\n logger.throw(\n `Data Manager authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n },\n\n async push(event, context) {\n return await push(event, context);\n },\n};\n\nexport default destinationDataManager;\n"],"mappings":";AAQO,SAAS,UACd,gBAA+B,CAAC,GAChC,QACiB;AACjB,QAAM,WAAY,cAAc,YAAY,CAAC;AAC7C,QAAM,EAAE,cAAc,cAAc,MAAM,IAAI;AAE9C,MAAI,CAAC,gBAAgB,aAAa,WAAW;AAC3C,WAAO,MAAM,+CAA+C;AAE9D,QAAM,iBAA0D;AAAA,IAC9D,GAAG;AAAA,IACH;AAAA,IACA;AAAA,EACF;AAEA,SAAO,EAAE,GAAG,eAAe,UAAU,eAAe;AACtD;;;ACvBA,SAAS,iBAAiB,gBAAgB;;;ACD1C,SAAS,YAAAA,WAAU,iBAAiB;;;ACDpC,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AAW9B,eAAsB,UAAU,OAAgC;AAC9D,MAAI,CAAC,SAAS,KAAK,KAAK,CAAC,MAAO,QAAO;AAGvC,MAAI,aAAa,MAAM,KAAK,EAAE,YAAY;AAG1C,MACE,WAAW,SAAS,YAAY,KAChC,WAAW,SAAS,iBAAiB,GACrC;AACA,UAAM,CAAC,WAAW,MAAM,IAAI,WAAW,MAAM,GAAG;AAChD,iBAAa,GAAG,UAAU,QAAQ,OAAO,EAAE,CAAC,IAAI,MAAM;AAAA,EACxD;AAEA,SAAO,cAAc,UAAU;AACjC;AAaA,eAAsB,UAAU,OAAgC;AAC9D,MAAI,CAAC,SAAS,KAAK,KAAK,CAAC,MAAO,QAAO;AAGvC,MAAI,aAAa,MAAM,KAAK;AAG5B,QAAM,UAAU,WAAW,WAAW,GAAG;AAGzC,eAAa,WAAW,QAAQ,OAAO,EAAE;AAGzC,MAAI,WAAW,WAAW,SAAS,IAAI;AACrC,iBAAa,IAAI,UAAU;AAAA,EAC7B,OAAO;AAEL,iBAAa,KAAK,UAAU;AAAA,EAC9B;AAEA,SAAO,cAAc,UAAU;AACjC;AAYA,eAAsB,SACpB,MACA,OAA2B,SACV;AACjB,MAAI,CAAC,SAAS,IAAI,KAAK,CAAC,KAAM,QAAO;AAGrC,MAAI,aAAa,KAAK,KAAK,EAAE,YAAY;AAGzC,MAAI,SAAS,SAAS;AACpB,UAAM,WAAW,CAAC,OAAO,QAAQ,OAAO,SAAS,OAAO,OAAO;AAC/D,eAAW,UAAU,UAAU;AAC7B,UAAI,WAAW,WAAW,MAAM,GAAG;AACjC,qBAAa,WAAW,UAAU,OAAO,MAAM,EAAE,KAAK;AACtD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAIA,MAAI,SAAS,UAAU;AACrB,UAAM,WAAW,CAAC,OAAO,OAAO,OAAO,MAAM,MAAM,GAAG;AACtD,eAAW,UAAU,UAAU;AAE7B,YAAM,kBAAkB,IAAI,MAAM;AAClC,UAAI,WAAW,SAAS,eAAe,GAAG;AACxC,qBAAa,WACV,UAAU,GAAG,WAAW,SAAS,gBAAgB,MAAM,EACvD,KAAK;AACR;AAAA,MACF;AAEA,UAAI,WAAW,SAAS,MAAM,GAAG;AAC/B,qBAAa,WACV,UAAU,GAAG,WAAW,SAAS,OAAO,MAAM,EAC9C,KAAK;AACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,cAAc,UAAU;AACjC;;;ADpGO,SAAS,gBAAgB,WAA2B;AACzD,SAAO,IAAI,KAAK,SAAS,EAAE,YAAY;AACzC;AASA,eAAsB,eACpB,MAC+B;AAC/B,QAAM,cAAgC,CAAC;AAIvC,MAAIC,UAAS,KAAK,KAAK,KAAK,KAAK,OAAO;AACtC,UAAM,cAAc,MAAM,UAAU,KAAK,KAAK;AAC9C,QAAI,aAAa;AACf,kBAAY,KAAK,EAAE,cAAc,YAAY,CAAC;AAAA,IAChD;AAAA,EACF;AAGA,MAAIA,UAAS,KAAK,KAAK,KAAK,KAAK,OAAO;AACtC,UAAM,cAAc,MAAM,UAAU,KAAK,KAAK;AAC9C,QAAI,aAAa;AACf,kBAAY,KAAK,EAAE,aAAa,YAAY,CAAC;AAAA,IAC/C;AAAA,EACF;AAGA,QAAM,aACJ,KAAK,aAAa,KAAK,YAAY,KAAK,cAAc,KAAK;AAE7D,MAAI,YAAY;AACd,UAAM,UAAkC,CAAC;AAEzC,QAAIA,UAAS,KAAK,SAAS,KAAK,KAAK,WAAW;AAC9C,cAAQ,YAAY,MAAM,SAAS,KAAK,WAAW,OAAO;AAAA,IAC5D;AAEA,QAAIA,UAAS,KAAK,QAAQ,KAAK,KAAK,UAAU;AAC5C,cAAQ,aAAa,MAAM,SAAS,KAAK,UAAU,QAAQ;AAAA,IAC7D;AAGA,QAAIA,UAAS,KAAK,UAAU,KAAK,KAAK,YAAY;AAChD,cAAQ,aAAa,KAAK,WAAW,YAAY;AAAA,IACnD;AAGA,QAAIA,UAAS,KAAK,UAAU,KAAK,KAAK,YAAY;AAChD,cAAQ,aAAa,KAAK;AAAA,IAC5B;AAEA,QAAI,OAAO,KAAK,OAAO,EAAE,SAAS,GAAG;AACnC,kBAAY,KAAK,EAAE,QAAQ,CAAC;AAAA,IAC9B;AAAA,EACF;AAGA,MAAI,YAAY,WAAW,EAAG,QAAO;AAErC,SAAO;AAAA,IACL,iBAAiB,YAAY,MAAM,GAAG,EAAE;AAAA,EAC1C;AACF;AASO,SAAS,oBACd,MAC2B;AAC3B,QAAM,cAA6B,CAAC;AAGpC,MAAIA,UAAS,KAAK,KAAK,KAAK,KAAK,OAAO;AACtC,gBAAY,QAAQ,KAAK;AAAA,EAC3B;AACA,MAAIA,UAAS,KAAK,MAAM,KAAK,KAAK,QAAQ;AACxC,gBAAY,SAAS,KAAK;AAAA,EAC5B;AACA,MAAIA,UAAS,KAAK,MAAM,KAAK,KAAK,QAAQ;AACxC,gBAAY,SAAS,KAAK;AAAA,EAC5B;AACA,MAAIA,UAAS,KAAK,iBAAiB,KAAK,KAAK,mBAAmB;AAC9D,gBAAY,oBAAoB,KAAK;AAAA,EACvC;AAEA,SAAO,OAAO,KAAK,WAAW,EAAE,SAAS,IAAI,cAAc;AAC7D;AASO,SAAS,cACd,iBACqB;AACrB,MAAI,CAAC,gBAAiB,QAAO;AAE7B,QAAM,UAAmB,CAAC;AAG1B,MAAI,UAAU,gBAAgB,SAAS,GAAG;AACxC,YAAQ,aAAa,gBAAgB,YACjC,oBACA;AAAA,EACN;AAGA,MAAI,UAAU,gBAAgB,eAAe,GAAG;AAC9C,YAAQ,oBAAoB,gBAAgB,kBACxC,oBACA;AAAA,EACN;AAEA,SAAO,OAAO,KAAK,OAAO,EAAE,SAAS,IAAI,UAAU;AACrD;AAMA,eAAsB,YACpB,OACA,YACgB;AAChB,QAAM,mBAA0B;AAAA,IAC9B,gBAAgB,gBAAgB,MAAM,SAAS;AAAA,EACjD;AAGA,QAAM,OAAO,cAAc,CAAC;AAG5B,MAAIA,UAAS,KAAK,aAAa,KAAK,KAAK,eAAe;AACtD,qBAAiB,gBAAgB,KAAK,cAAc,UAAU,GAAG,GAAG;AAAA,EACtE;AAGA,MAAIA,UAAS,KAAK,QAAQ,KAAK,KAAK,UAAU;AAC5C,qBAAiB,WAAW,KAAK,SAAS,UAAU,GAAG,GAAG;AAAA,EAC5D;AAGA,MAAIA,UAAS,KAAK,MAAM,KAAK,KAAK,QAAQ;AACxC,qBAAiB,SAAS,KAAK,OAAO,UAAU,GAAG,GAAG;AAAA,EACxD;AAGA,QAAM,WAAW,MAAM,eAAe,IAAI;AAC1C,MAAI,UAAU;AACZ,qBAAiB,WAAW;AAAA,EAC9B;AAGA,QAAM,gBAAgB,oBAAoB,IAAI;AAC9C,MAAI,eAAe;AACjB,qBAAiB,gBAAgB;AAAA,EACnC;AAGA,MAAI,OAAO,KAAK,oBAAoB,UAAU;AAC5C,qBAAiB,kBAAkB,KAAK;AAAA,EAC1C;AAGA,MAAIA,UAAS,KAAK,QAAQ,KAAK,KAAK,UAAU;AAC5C,qBAAiB,WAAW,KAAK,SAAS,UAAU,GAAG,CAAC,EAAE,YAAY;AAAA,EACxE;AAGA,MAAI,KAAK,YAAY,OAAO,KAAK,aAAa,UAAU;AACtD,qBAAiB,WAAW,KAAK;AAAA,EACnC;AAGA,MAAIA,UAAS,KAAK,SAAS,KAAK,KAAK,WAAW;AAC9C,qBAAiB,YAAY,KAAK,UAAU,UAAU,GAAG,EAAE;AAAA,EAC7D;AAGA,MAAIA,UAAS,KAAK,WAAW,KAAK,KAAK,aAAa;AAClD,qBAAiB,cAAc,KAAK;AAAA,EACtC;AAGA,QAAM,gBAAyB,CAAC;AAGhC,MAAI,OAAO,KAAK,eAAe,WAAW;AACxC,kBAAc,aAAa,KAAK,aAC5B,oBACA;AAAA,EACN;AACA,MAAI,OAAO,KAAK,sBAAsB,WAAW;AAC/C,kBAAc,oBAAoB,KAAK,oBACnC,oBACA;AAAA,EACN;AAGA,MAAI,OAAO,KAAK,aAAa,EAAE,WAAW,GAAG;AAC3C,UAAM,eAAe,cAAc,MAAM,OAAO;AAChD,QAAI,cAAc;AAChB,uBAAiB,UAAU;AAAA,IAC7B;AAAA,EACF,OAAO;AACL,qBAAiB,UAAU;AAAA,EAC7B;AAEA,SAAO;AACT;;;AElPA,SAAS,kBAAqC;AAG9C,IAAM,iBAAiB,CAAC,6CAA6C;AAK9D,IAAM,YAAN,cAAwB,MAAM;AAAA,EACnC,YACE,SACO,OACP;AACA,UAAM,OAAO;AAFN;AAGP,SAAK,OAAO;AAAA,EACd;AACF;AAgBA,eAAsB,iBACpB,UACuB;AACvB,QAAM,EAAE,aAAa,aAAa,SAAS,eAAe,IAAI;AAE9D,MAAI;AACF,QAAI,aAAa;AACf,YAAMC,QAAO,IAAI,WAAW;AAAA,QAC1B;AAAA,QACA;AAAA,MACF,CAAC;AACD,aAAQ,MAAMA,MAAK,UAAU;AAAA,IAC/B;AAEA,QAAI,aAAa;AACf,YAAMA,QAAO,IAAI,WAAW;AAAA,QAC1B;AAAA,QACA;AAAA,MACF,CAAC;AACD,aAAQ,MAAMA,MAAK,UAAU;AAAA,IAC/B;AAEA,UAAM,OAAO,IAAI,WAAW,EAAE,OAAO,CAAC;AACtC,WAAQ,MAAM,KAAK,UAAU;AAAA,EAC/B,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR;AAAA,MACA,iBAAiB,QAAQ,QAAQ;AAAA,IACnC;AAAA,EACF;AACF;AAUA,eAAsB,eACpB,YACiB;AACjB,MAAI;AACF,UAAM,gBAAgB,MAAM,WAAW,eAAe;AAEtD,QAAI,CAAC,cAAc,OAAO;AACxB,YAAM,IAAI,UAAU,kCAAkC;AAAA,IACxD;AAEA,WAAO,cAAc;AAAA,EACvB,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR;AAAA,MACA,iBAAiB,QAAQ,QAAQ;AAAA,IACnC;AAAA,EACF;AACF;;;AHlFO,IAAM,OAAe,eAC1B,OACA,EAAE,QAAQ,MAAM,MAAM,WAAW,KAAK,OAAO,GAC7C;AAEA,QAAM,kBAAkB,UAAU,QAAQ,MAAM;AAChD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf,MAAM;AAAA,IACN,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,gBAAgB;AAGpB,QAAM,iBAAiB,WACnB,MAAM,gBAAgB,OAAO,EAAE,KAAK,SAAS,CAAC,IAC9C,CAAC;AACL,QAAM,eAAe,SACjB,MAAM,gBAAgB,OAAO,MAAM,IACnC;AACJ,QAAM,iBAAiB,WACnB,MAAM,gBAAgB,OAAO,QAAQ,IACrC;AACJ,QAAM,0BAA0B,oBAC5B,MAAM,gBAAgB,OAAO,iBAAiB,IAC9C;AAGJ,QAAM,yBACJ,OAAO,sBAAsB,YACzB,oBACA,OAAO,sBAAsB,YAAY,MAAM,UAC7C,MAAM,QAAQ,iBAAiB,IAC/B;AAER,QAAM,gCACJ,OAAO,6BAA6B,YAChC,2BACA,OAAO,6BAA6B,YAAY,MAAM,UACpD,MAAM,QAAQ,wBAAwB,IACtC;AAGR,QAAM,kBAA2C,CAAC;AAClD,MAAI,SAAS,cAAc,GAAG;AAC5B,WAAO,OAAO,iBAAiB,cAAc;AAAA,EAC/C;AACA,MAAI,iBAAiB,OAAW,iBAAgB,SAAS;AACzD,MAAI,mBAAmB,OAAW,iBAAgB,WAAW;AAC7D,MAAI,4BAA4B;AAC9B,oBAAgB,oBAAoB;AACtC,MAAI,2BAA2B;AAC7B,oBAAgB,aAAa;AAC/B,MAAI,kCAAkC;AACpC,oBAAgB,oBAAoB;AAGtC,QAAM,aAAa,gBAAgB,OAC/B,MAAM,gBAAgB,OAAO,gBAAgB,IAAI,IACjD,CAAC;AACL,QAAM,YAAY,SAAS,IAAI,IAAI,OAAO,CAAC;AAG3C,QAAM,YAAY;AAAA,IAChB,GAAG;AAAA,IACH,GAAI,SAAS,UAAU,IAAI,aAAa,CAAC;AAAA,IACzC,GAAG;AAAA,EACL;AAGA,QAAM,mBAAmB,MAAM,YAAY,OAAO,SAAS;AAG3D,MAAI,CAAC,iBAAiB,aAAa;AACjC,qBAAiB,cAAc;AAAA,EACjC;AAGA,MAAI,CAAC,iBAAiB,WAAW,gBAAgB;AAC/C,qBAAiB,UAAU;AAAA,EAC7B;AAGA,MAAI,CAAC,iBAAiB,eAAe;AACnC,WAAO,MAAM,2BAA2B;AAAA,EAC1C;AAGA,QAAM,oBAAoB,aAAa;AAAA,IACrC,CAAC,MAAG;AAxGR;AAwGW,sBAAE,qBAAF,mBAAoB,iBAAgB;AAAA;AAAA,EAC7C;AAEA,MAAI,qBAAqB,CAAC,iBAAiB,WAAW;AACpD,WAAO,MAAM,4CAA4C;AAAA,EAC3D;AAGA,QAAM,cAAmC;AAAA,IACvC,QAAQ,CAAC,gBAAgB;AAAA,IACzB;AAAA,EACF;AAGA,MAAI,gBAAgB;AAClB,gBAAY,UAAU;AAAA,EACxB;AAEA,MAAI,cAAc;AAChB,gBAAY,eAAe;AAAA,EAC7B;AAEA,MAAI,eAAe;AACjB,gBAAY,gBAAgB;AAAA,EAC9B;AAEA,QAAM,aAAa,2BAAK;AACxB,MAAI,CAAC,YAAY;AACf,WAAO,OAAO;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,kBAAc,MAAM,eAAe,UAAU;AAAA,EAC/C,SAAS,OAAO;AACd,WAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC;AAC/C,UAAM;AAAA,EACR;AAEA,QAAM,WAAU,2BAAK,UAAS;AAC9B,QAAM,WAAW,GAAG,GAAG;AAEvB,SAAO,MAAM,+BAA+B;AAAA,IAC1C;AAAA,IACA,YAAY,YAAY,OAAO;AAAA,IAC/B,cAAc,aAAa;AAAA,IAC3B;AAAA,EACF,CAAC;AAED,QAAM,WAAW,MAAM,QAAQ,UAAU;AAAA,IACvC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,eAAe,UAAU,WAAW;AAAA,MACpC,gBAAgB;AAAA,IAClB;AAAA,IACA,MAAM,KAAK,UAAU,WAAW;AAAA,EAClC,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,YAAY,MAAM,SAAS,KAAK;AACtC,WAAO,MAAM,2BAA2B,SAAS,MAAM,MAAM,SAAS,EAAE;AAAA,EAC1E;AAEA,QAAM,SAA+B,MAAM,SAAS,KAAK;AAEzD,SAAO,MAAM,gBAAgB;AAAA,IAC3B,QAAQ,SAAS;AAAA,IACjB,WAAW,OAAO;AAAA,EACpB,CAAC;AAGD,MAAI,OAAO,oBAAoB,OAAO,iBAAiB,SAAS,GAAG;AACjE,WAAO;AAAA,MACL,sBAAsB,KAAK,UAAU,OAAO,gBAAgB,CAAC;AAAA,IAC/D;AAAA,EACF;AACF;;;AItLA;;;ACOO,IAAM,yBAA+C;AAAA,EAC1D,MAAM;AAAA,EAEN,QAAQ,CAAC;AAAA,EAET,MAAM,KAAK,EAAE,QAAQ,eAAe,KAAK,OAAO,GAAG;AAEjD,UAAM,SAAS,UAAU,eAAe,MAAM;AAE9C,QAAI;AACF,YAAM,aAAa,MAAM,iBAAiB,OAAO,QAAQ;AACzD,aAAO,MAAM,qBAAqB;AAElC,aAAO;AAAA,QACL,GAAG;AAAA,QACH,KAAK;AAAA,UACH,GAAG;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,aAAO;AAAA,QACL,uCAAuC,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,MACjG;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,OAAO,SAAS;AACzB,WAAO,MAAM,KAAK,OAAO,OAAO;AAAA,EAClC;AACF;AAEA,IAAO,gBAAQ;","names":["isString","isString","auth"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@walkeros/server-destination-datamanager",
3
3
  "description": "Google Data Manager server destination for walkerOS",
4
- "version": "0.6.0",
4
+ "version": "0.8.0",
5
5
  "license": "MIT",
6
6
  "exports": {
7
7
  ".": {
@@ -32,8 +32,8 @@
32
32
  "update": "npx npm-check-updates -u && npm update"
33
33
  },
34
34
  "dependencies": {
35
- "@walkeros/core": "0.6.0",
36
- "@walkeros/server-core": "0.6.0",
35
+ "@walkeros/core": "0.8.0",
36
+ "@walkeros/server-core": "0.8.0",
37
37
  "google-auth-library": "^10.5.0"
38
38
  },
39
39
  "devDependencies": {},