@walkeros/server-destination-datamanager 0.4.1 → 0.5.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.d.mts CHANGED
@@ -2,8 +2,6 @@ import { Destination as Destination$1, Mapping as Mapping$1 } from '@walkeros/co
2
2
  import { DestinationServer } from '@walkeros/server-core';
3
3
  import { OAuth2Client } from 'google-auth-library';
4
4
 
5
- type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
6
-
7
5
  interface Settings {
8
6
  /**
9
7
  * Service account credentials (client_email + private_key)
@@ -25,7 +23,7 @@ interface Settings {
25
23
  scopes?: string[];
26
24
  /** Array of destination accounts and conversion actions/user lists */
27
25
  destinations: Destination[];
28
- /** Default event source if not specified per event */
26
+ /** Event source for all events. Defaults to WEB if not specified */
29
27
  eventSource?: EventSource;
30
28
  /** Maximum number of events to batch before sending (max 2000) */
31
29
  batchSize?: number;
@@ -39,8 +37,6 @@ interface Settings {
39
37
  consent?: Consent;
40
38
  /** Test event code for debugging (optional) */
41
39
  testEventCode?: string;
42
- /** Log level for debugging (optional) */
43
- logLevel?: LogLevel;
44
40
  /** Guided helpers: User data mapping (applies to all events) */
45
41
  userData?: Mapping$1.Map;
46
42
  /** Guided helper: First-party user ID */
@@ -72,6 +68,16 @@ interface DestinationInterface extends DestinationServer.Destination<Types> {
72
68
  type Config = {
73
69
  settings: Settings;
74
70
  } & DestinationServer.Config<Types>;
71
+ /**
72
+ * Config after validation - settings is guaranteed to exist with required fields
73
+ * Use this type after calling getConfig() to get proper type narrowing
74
+ * After validation, eventSource is always set (defaults to 'WEB')
75
+ */
76
+ type ValidatedConfig = Omit<Config, 'settings'> & {
77
+ settings: Settings & {
78
+ eventSource: EventSource;
79
+ };
80
+ };
75
81
  type InitFn = DestinationServer.InitFn<Types>;
76
82
  type PushFn = DestinationServer.PushFn<Types>;
77
83
  type PartialConfig = DestinationServer.PartialConfig<Types>;
@@ -371,9 +377,10 @@ type index_Types = Types;
371
377
  type index_UserData = UserData;
372
378
  type index_UserIdentifier = UserIdentifier;
373
379
  type index_UserProperties = UserProperties;
380
+ type index_ValidatedConfig = ValidatedConfig;
374
381
  type index_ValidationError = ValidationError;
375
382
  declare namespace index {
376
- export type { index_AccountType as AccountType, index_AdIdentifiers as AdIdentifiers, index_Address as Address, index_CartData as CartData, index_CartItem as CartItem, index_Config as Config, index_Consent as Consent, index_ConsentStatus as ConsentStatus, index_CustomVariable as CustomVariable, index_Destination as Destination, index_DestinationInterface as DestinationInterface, index_DeviceInfo as DeviceInfo, index_Env as Env, index_Event as Event, index_EventParameter as EventParameter, index_EventSource as EventSource, index_ExperimentalField as ExperimentalField, index_IngestEventsRequest as IngestEventsRequest, index_IngestEventsResponse as IngestEventsResponse, index_InitFn as InitFn, index_InitSettings as InitSettings, index_Mapping as Mapping, index_PartialConfig as PartialConfig, index_ProductAccount as ProductAccount, index_PushEvents as PushEvents, index_PushFn as PushFn, index_RequestError as RequestError, index_RequestState as RequestState, index_RequestStatusResponse as RequestStatusResponse, index_Rule as Rule, index_Rules as Rules, index_Settings as Settings, index_Types as Types, index_UserData as UserData, index_UserIdentifier as UserIdentifier, index_UserProperties as UserProperties, index_ValidationError as ValidationError };
383
+ export type { index_AccountType as AccountType, index_AdIdentifiers as AdIdentifiers, index_Address as Address, index_CartData as CartData, index_CartItem as CartItem, index_Config as Config, index_Consent as Consent, index_ConsentStatus as ConsentStatus, index_CustomVariable as CustomVariable, index_Destination as Destination, index_DestinationInterface as DestinationInterface, index_DeviceInfo as DeviceInfo, index_Env as Env, index_Event as Event, index_EventParameter as EventParameter, index_EventSource as EventSource, index_ExperimentalField as ExperimentalField, index_IngestEventsRequest as IngestEventsRequest, index_IngestEventsResponse as IngestEventsResponse, index_InitFn as InitFn, index_InitSettings as InitSettings, index_Mapping as Mapping, index_PartialConfig as PartialConfig, index_ProductAccount as ProductAccount, index_PushEvents as PushEvents, index_PushFn as PushFn, index_RequestError as RequestError, index_RequestState as RequestState, index_RequestStatusResponse as RequestStatusResponse, index_Rule as Rule, index_Rules as Rules, index_Settings as Settings, index_Types as Types, index_UserData as UserData, index_UserIdentifier as UserIdentifier, index_UserProperties as UserProperties, index_ValidatedConfig as ValidatedConfig, index_ValidationError as ValidationError };
377
384
  }
378
385
 
379
386
  declare const destinationDataManager: DestinationInterface;
package/dist/index.d.ts CHANGED
@@ -2,8 +2,6 @@ import { Destination as Destination$1, Mapping as Mapping$1 } from '@walkeros/co
2
2
  import { DestinationServer } from '@walkeros/server-core';
3
3
  import { OAuth2Client } from 'google-auth-library';
4
4
 
5
- type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
6
-
7
5
  interface Settings {
8
6
  /**
9
7
  * Service account credentials (client_email + private_key)
@@ -25,7 +23,7 @@ interface Settings {
25
23
  scopes?: string[];
26
24
  /** Array of destination accounts and conversion actions/user lists */
27
25
  destinations: Destination[];
28
- /** Default event source if not specified per event */
26
+ /** Event source for all events. Defaults to WEB if not specified */
29
27
  eventSource?: EventSource;
30
28
  /** Maximum number of events to batch before sending (max 2000) */
31
29
  batchSize?: number;
@@ -39,8 +37,6 @@ interface Settings {
39
37
  consent?: Consent;
40
38
  /** Test event code for debugging (optional) */
41
39
  testEventCode?: string;
42
- /** Log level for debugging (optional) */
43
- logLevel?: LogLevel;
44
40
  /** Guided helpers: User data mapping (applies to all events) */
45
41
  userData?: Mapping$1.Map;
46
42
  /** Guided helper: First-party user ID */
@@ -72,6 +68,16 @@ interface DestinationInterface extends DestinationServer.Destination<Types> {
72
68
  type Config = {
73
69
  settings: Settings;
74
70
  } & DestinationServer.Config<Types>;
71
+ /**
72
+ * Config after validation - settings is guaranteed to exist with required fields
73
+ * Use this type after calling getConfig() to get proper type narrowing
74
+ * After validation, eventSource is always set (defaults to 'WEB')
75
+ */
76
+ type ValidatedConfig = Omit<Config, 'settings'> & {
77
+ settings: Settings & {
78
+ eventSource: EventSource;
79
+ };
80
+ };
75
81
  type InitFn = DestinationServer.InitFn<Types>;
76
82
  type PushFn = DestinationServer.PushFn<Types>;
77
83
  type PartialConfig = DestinationServer.PartialConfig<Types>;
@@ -371,9 +377,10 @@ type index_Types = Types;
371
377
  type index_UserData = UserData;
372
378
  type index_UserIdentifier = UserIdentifier;
373
379
  type index_UserProperties = UserProperties;
380
+ type index_ValidatedConfig = ValidatedConfig;
374
381
  type index_ValidationError = ValidationError;
375
382
  declare namespace index {
376
- export type { index_AccountType as AccountType, index_AdIdentifiers as AdIdentifiers, index_Address as Address, index_CartData as CartData, index_CartItem as CartItem, index_Config as Config, index_Consent as Consent, index_ConsentStatus as ConsentStatus, index_CustomVariable as CustomVariable, index_Destination as Destination, index_DestinationInterface as DestinationInterface, index_DeviceInfo as DeviceInfo, index_Env as Env, index_Event as Event, index_EventParameter as EventParameter, index_EventSource as EventSource, index_ExperimentalField as ExperimentalField, index_IngestEventsRequest as IngestEventsRequest, index_IngestEventsResponse as IngestEventsResponse, index_InitFn as InitFn, index_InitSettings as InitSettings, index_Mapping as Mapping, index_PartialConfig as PartialConfig, index_ProductAccount as ProductAccount, index_PushEvents as PushEvents, index_PushFn as PushFn, index_RequestError as RequestError, index_RequestState as RequestState, index_RequestStatusResponse as RequestStatusResponse, index_Rule as Rule, index_Rules as Rules, index_Settings as Settings, index_Types as Types, index_UserData as UserData, index_UserIdentifier as UserIdentifier, index_UserProperties as UserProperties, index_ValidationError as ValidationError };
383
+ export type { index_AccountType as AccountType, index_AdIdentifiers as AdIdentifiers, index_Address as Address, index_CartData as CartData, index_CartItem as CartItem, index_Config as Config, index_Consent as Consent, index_ConsentStatus as ConsentStatus, index_CustomVariable as CustomVariable, index_Destination as Destination, index_DestinationInterface as DestinationInterface, index_DeviceInfo as DeviceInfo, index_Env as Env, index_Event as Event, index_EventParameter as EventParameter, index_EventSource as EventSource, index_ExperimentalField as ExperimentalField, index_IngestEventsRequest as IngestEventsRequest, index_IngestEventsResponse as IngestEventsResponse, index_InitFn as InitFn, index_InitSettings as InitSettings, index_Mapping as Mapping, index_PartialConfig as PartialConfig, index_ProductAccount as ProductAccount, index_PushEvents as PushEvents, index_PushFn as PushFn, index_RequestError as RequestError, index_RequestState as RequestState, index_RequestStatusResponse as RequestStatusResponse, index_Rule as Rule, index_Rules as Rules, index_Settings as Settings, index_Types as Types, index_UserData as UserData, index_UserIdentifier as UserIdentifier, index_UserProperties as UserProperties, index_ValidatedConfig as ValidatedConfig, index_ValidationError as ValidationError };
377
384
  }
378
385
 
379
386
  declare const destinationDataManager: DestinationInterface;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.prototype.hasOwnProperty,a={};((e,n)=>{for(var r in n)t(e,r,{get:n[r],enumerable:!0})})(a,{DestinationDataManager:()=>w,default:()=>y,destinationDataManager:()=>b}),module.exports=(e=a,((e,a,s,o)=>{if(a&&"object"==typeof a||"function"==typeof a)for(let c of r(a))i.call(e,c)||c===s||t(e,c,{get:()=>a[c],enumerable:!(o=n(a,c))||o.enumerable});return e})(t({},"__esModule",{value:!0}),e));var s=require("@walkeros/core");var o=require("@walkeros/core"),c=require("@walkeros/core"),l=require("@walkeros/core"),g=require("@walkeros/server-core");async function d(e,t="given"){if(!(0,l.isString)(e)||!e)return"";let n=e.trim().toLowerCase();if("given"===t){const e=["mr.","mrs.","ms.","miss.","dr.","prof."];for(const t of e)if(n.startsWith(t)){n=n.substring(t.length).trim();break}}if("family"===t){const e=["jr.","sr.","iii","ii","iv","v"];for(const t of e){const e=` ${t}`;if(n.endsWith(e)){n=n.substring(0,n.length-e.length).trim();break}if(n.endsWith(t)){n=n.substring(0,n.length-t.length).trim();break}}}return(0,g.getHashServer)(n)}async function u(e){const t=[];if((0,c.isString)(e.email)&&e.email){const n=await async function(e){if(!(0,l.isString)(e)||!e)return"";let t=e.trim().toLowerCase();if(t.endsWith("@gmail.com")||t.endsWith("@googlemail.com")){const[e,n]=t.split("@");t=`${e.replace(/\./g,"")}@${n}`}return(0,g.getHashServer)(t)}(e.email);n&&t.push({emailAddress:n})}if((0,c.isString)(e.phone)&&e.phone){const n=await async function(e){if(!(0,l.isString)(e)||!e)return"";let t=e.trim();const n=t.startsWith("+");return t=t.replace(/\D/g,""),t=n||t.length>10?`+${t}`:`+1${t}`,(0,g.getHashServer)(t)}(e.phone);n&&t.push({phoneNumber:n})}if(e.firstName||e.lastName||e.regionCode||e.postalCode){const n={};(0,c.isString)(e.firstName)&&e.firstName&&(n.givenName=await d(e.firstName,"given")),(0,c.isString)(e.lastName)&&e.lastName&&(n.familyName=await d(e.lastName,"family")),(0,c.isString)(e.regionCode)&&e.regionCode&&(n.regionCode=e.regionCode.toUpperCase()),(0,c.isString)(e.postalCode)&&e.postalCode&&(n.postalCode=e.postalCode),Object.keys(n).length>0&&t.push({address:n})}if(0!==t.length)return{userIdentifiers:t.slice(0,10)}}async function f(e,t){const n={eventTimestamp:(r=e.timestamp,new Date(r).toISOString())};var r;const i=t||{};(0,c.isString)(i.transactionId)&&i.transactionId&&(n.transactionId=i.transactionId.substring(0,512)),(0,c.isString)(i.clientId)&&i.clientId&&(n.clientId=i.clientId.substring(0,255)),(0,c.isString)(i.userId)&&i.userId&&(n.userId=i.userId.substring(0,256));const a=await u(i);a&&(n.userData=a);const s=function(e){const t={};return(0,c.isString)(e.gclid)&&e.gclid&&(t.gclid=e.gclid),(0,c.isString)(e.gbraid)&&e.gbraid&&(t.gbraid=e.gbraid),(0,c.isString)(e.wbraid)&&e.wbraid&&(t.wbraid=e.wbraid),(0,c.isString)(e.sessionAttributes)&&e.sessionAttributes&&(t.sessionAttributes=e.sessionAttributes),Object.keys(t).length>0?t:void 0}(i);s&&(n.adIdentifiers=s),"number"==typeof i.conversionValue&&(n.conversionValue=i.conversionValue),(0,c.isString)(i.currency)&&i.currency&&(n.currency=i.currency.substring(0,3).toUpperCase()),i.cartData&&"object"==typeof i.cartData&&(n.cartData=i.cartData),(0,c.isString)(i.eventName)&&i.eventName&&(n.eventName=i.eventName.substring(0,40)),(0,c.isString)(i.eventSource)&&i.eventSource&&(n.eventSource=i.eventSource);const o={};if("boolean"==typeof i.adUserData&&(o.adUserData=i.adUserData?"CONSENT_GRANTED":"CONSENT_DENIED"),"boolean"==typeof i.adPersonalization&&(o.adPersonalization=i.adPersonalization?"CONSENT_GRANTED":"CONSENT_DENIED"),0===Object.keys(o).length){const t=function(e){if(!e)return;const t={};return(0,c.isDefined)(e.marketing)&&(t.adUserData=e.marketing?"CONSENT_GRANTED":"CONSENT_DENIED"),(0,c.isDefined)(e.personalization)&&(t.adPersonalization=e.personalization?"CONSENT_GRANTED":"CONSENT_DENIED"),Object.keys(t).length>0?t:void 0}(e.consent);t&&(n.consent=t)}else n.consent=o;return n}var p=require("google-auth-library"),h=["https://www.googleapis.com/auth/datamanager"],m=class extends Error{constructor(e,t){super(e),this.cause=t,this.name="DataManagerAuthError"}};var v=async function(e,{config:t,mapping:n,data:r,collector:i,env:a}){const{destinations:s,eventSource:c,validateOnly:l=!1,url:g="https://datamanager.googleapis.com/v1",consent:d,testEventCode:u,logLevel:p="none",userData:h,userId:v,clientId:w,sessionAttributes:b,consentAdUserData:y,consentAdPersonalization:S}=t.settings,E=function(e="none"){const t={debug:0,info:1,warn:2,error:3,none:4}[e];return{debug:(e,n)=>{t<=0&&console.log(`[DataManager] ${e}`,n||"")},info:(e,n)=>{t<=1&&console.log(`[DataManager] ${e}`,n||"")},warn:(e,n)=>{t<=2&&console.warn(`[DataManager] ${e}`,n||"")},error:(e,n)=>{t<=3&&console.error(`[DataManager] ${e}`,n||"")}}}(p),N=h?await(0,o.getMappingValue)(e,{map:h}):{},D=v?await(0,o.getMappingValue)(e,v):void 0,C=w?await(0,o.getMappingValue)(e,w):void 0,I=b?await(0,o.getMappingValue)(e,b):void 0,O="boolean"==typeof y?y:"string"==typeof y&&e.consent?e.consent[y]:void 0,A="boolean"==typeof S?S:"string"==typeof S&&e.consent?e.consent[S]:void 0,k={};(0,o.isObject)(N)&&Object.assign(k,N),void 0!==D&&(k.userId=D),void 0!==C&&(k.clientId=C),void 0!==I&&(k.sessionAttributes=I),void 0!==O&&(k.adUserData=O),void 0!==A&&(k.adPersonalization=A);const P=t.data?await(0,o.getMappingValue)(e,t.data):{},T=(0,o.isObject)(r)?r:{},j={...k,...(0,o.isObject)(P)?P:{},...T},M=await f(e,j);if(E.debug("Processing event",{name:e.name,id:e.id,timestamp:e.timestamp}),!M.eventSource&&c&&(M.eventSource=c),!M.consent&&d&&(M.consent=d),!s||0===s.length)throw new Error("Destinations are required");const $={events:[M],destinations:s};d&&($.consent=d),l&&($.validateOnly=!0),u&&($.testEventCode=u);const q=null==a?void 0:a.authClient;if(!q)throw new Error("Auth client not initialized. Ensure init() was called successfully.");let _;try{_=await async function(e){try{const t=await e.getAccessToken();if(!t.token)throw new m("Auth client returned empty token");return t.token}catch(e){throw new m("Failed to obtain access token",e instanceof Error?e:void 0)}}(q)}catch(e){throw E.error("Authentication failed",{error:e}),e}const z=(null==a?void 0:a.fetch)||fetch,V=`${g}/events:ingest`;E.debug("Sending to Data Manager API",{endpoint:V,eventCount:$.events.length,destinations:s.length,validateOnly:l});const G=await z(V,{method:"POST",headers:{Authorization:`Bearer ${_}`,"Content-Type":"application/json"},body:JSON.stringify($)});if(!G.ok){const e=await G.text();throw E.error("API request failed",{status:G.status,error:e}),new Error(`Data Manager API error (${G.status}): ${e}`)}const U=await G.json();if(E.debug("API response",{status:G.status,requestId:U.requestId}),U.validationErrors&&U.validationErrors.length>0)throw E.error("Validation errors",{errors:U.validationErrors}),new Error(`Validation errors: ${JSON.stringify(U.validationErrors)}`);E.info("Event processed successfully",{requestId:U.requestId})},w={},b={type:"datamanager",config:{},async init({config:e,env:t}){const n=function(e={}){const t=e.settings||{},{destinations:n}=t;n&&0!==n.length||(0,s.throwError)("Config settings destinations missing or empty");const r={...t,destinations:n};return{...e,settings:r}}(e);if(!n.settings)throw new Error("Settings required for Data Manager destination");if(!n.settings.destinations||0===n.settings.destinations.length)throw new Error("At least one destination required in settings");try{const e=await async function(e){const{credentials:t,keyFilename:n,scopes:r=h}=e;try{if(t){const e=new p.GoogleAuth({credentials:t,scopes:r});return await e.getClient()}if(n){const e=new p.GoogleAuth({keyFilename:n,scopes:r});return await e.getClient()}const e=new p.GoogleAuth({scopes:r});return await e.getClient()}catch(e){throw new m("Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.",e instanceof Error?e:void 0)}}(n.settings);return{...n,env:{...t,authClient:e}}}catch(e){throw new Error(`Data Manager authentication failed: ${e instanceof Error?e.message:"Unknown error"}`)}},push:async(e,{config:t,mapping:n,data:r,collector:i,env:a})=>await v(e,{config:t,mapping:n,data:r,collector:i,env:a})},y=b;//# sourceMappingURL=index.js.map
1
+ "use strict";var e,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.prototype.hasOwnProperty,r={};function s(e={},t){const n=e.settings||{},{destinations:i,eventSource:a="WEB"}=n;i&&0!==i.length||t.throw("Config settings destinations missing or empty");const r={...n,destinations:i,eventSource:a};return{...e,settings:r}}((e,n)=>{for(var i in n)t(e,i,{get:n[i],enumerable:!0})})(r,{DestinationDataManager:()=>w,default:()=>b,destinationDataManager:()=>y}),module.exports=(e=r,((e,r,s,o)=>{if(r&&"object"==typeof r||"function"==typeof r)for(let c of i(r))a.call(e,c)||c===s||t(e,c,{get:()=>r[c],enumerable:!(o=n(r,c))||o.enumerable});return e})(t({},"__esModule",{value:!0}),e));var o=require("@walkeros/core"),c=require("@walkeros/core"),l=require("@walkeros/core"),g=require("@walkeros/server-core");async function d(e,t="given"){if(!(0,l.isString)(e)||!e)return"";let n=e.trim().toLowerCase();if("given"===t){const e=["mr.","mrs.","ms.","miss.","dr.","prof."];for(const t of e)if(n.startsWith(t)){n=n.substring(t.length).trim();break}}if("family"===t){const e=["jr.","sr.","iii","ii","iv","v"];for(const t of e){const e=` ${t}`;if(n.endsWith(e)){n=n.substring(0,n.length-e.length).trim();break}if(n.endsWith(t)){n=n.substring(0,n.length-t.length).trim();break}}}return(0,g.getHashServer)(n)}async function u(e){const t=[];if((0,c.isString)(e.email)&&e.email){const n=await async function(e){if(!(0,l.isString)(e)||!e)return"";let t=e.trim().toLowerCase();if(t.endsWith("@gmail.com")||t.endsWith("@googlemail.com")){const[e,n]=t.split("@");t=`${e.replace(/\./g,"")}@${n}`}return(0,g.getHashServer)(t)}(e.email);n&&t.push({emailAddress:n})}if((0,c.isString)(e.phone)&&e.phone){const n=await async function(e){if(!(0,l.isString)(e)||!e)return"";let t=e.trim();const n=t.startsWith("+");return t=t.replace(/\D/g,""),t=n||t.length>10?`+${t}`:`+1${t}`,(0,g.getHashServer)(t)}(e.phone);n&&t.push({phoneNumber:n})}if(e.firstName||e.lastName||e.regionCode||e.postalCode){const n={};(0,c.isString)(e.firstName)&&e.firstName&&(n.givenName=await d(e.firstName,"given")),(0,c.isString)(e.lastName)&&e.lastName&&(n.familyName=await d(e.lastName,"family")),(0,c.isString)(e.regionCode)&&e.regionCode&&(n.regionCode=e.regionCode.toUpperCase()),(0,c.isString)(e.postalCode)&&e.postalCode&&(n.postalCode=e.postalCode),Object.keys(n).length>0&&t.push({address:n})}if(0!==t.length)return{userIdentifiers:t.slice(0,10)}}async function f(e,t){const n={eventTimestamp:(i=e.timestamp,new Date(i).toISOString())};var i;const a=t||{};(0,c.isString)(a.transactionId)&&a.transactionId&&(n.transactionId=a.transactionId.substring(0,512)),(0,c.isString)(a.clientId)&&a.clientId&&(n.clientId=a.clientId.substring(0,255)),(0,c.isString)(a.userId)&&a.userId&&(n.userId=a.userId.substring(0,256));const r=await u(a);r&&(n.userData=r);const s=function(e){const t={};return(0,c.isString)(e.gclid)&&e.gclid&&(t.gclid=e.gclid),(0,c.isString)(e.gbraid)&&e.gbraid&&(t.gbraid=e.gbraid),(0,c.isString)(e.wbraid)&&e.wbraid&&(t.wbraid=e.wbraid),(0,c.isString)(e.sessionAttributes)&&e.sessionAttributes&&(t.sessionAttributes=e.sessionAttributes),Object.keys(t).length>0?t:void 0}(a);s&&(n.adIdentifiers=s),"number"==typeof a.conversionValue&&(n.conversionValue=a.conversionValue),(0,c.isString)(a.currency)&&a.currency&&(n.currency=a.currency.substring(0,3).toUpperCase()),a.cartData&&"object"==typeof a.cartData&&(n.cartData=a.cartData),(0,c.isString)(a.eventName)&&a.eventName&&(n.eventName=a.eventName.substring(0,40)),(0,c.isString)(a.eventSource)&&a.eventSource&&(n.eventSource=a.eventSource);const o={};if("boolean"==typeof a.adUserData&&(o.adUserData=a.adUserData?"CONSENT_GRANTED":"CONSENT_DENIED"),"boolean"==typeof a.adPersonalization&&(o.adPersonalization=a.adPersonalization?"CONSENT_GRANTED":"CONSENT_DENIED"),0===Object.keys(o).length){const t=function(e){if(!e)return;const t={};return(0,c.isDefined)(e.marketing)&&(t.adUserData=e.marketing?"CONSENT_GRANTED":"CONSENT_DENIED"),(0,c.isDefined)(e.personalization)&&(t.adPersonalization=e.personalization?"CONSENT_GRANTED":"CONSENT_DENIED"),Object.keys(t).length>0?t:void 0}(e.consent);t&&(n.consent=t)}else n.consent=o;return n}var p=require("google-auth-library"),h=["https://www.googleapis.com/auth/datamanager"],v=class extends Error{constructor(e,t){super(e),this.cause=t,this.name="DataManagerAuthError"}};var m=async function(e,{config:t,mapping:n,data:i,collector:a,env:r,logger:c}){const l=s(t,c),{destinations:g,eventSource:d,validateOnly:u=!1,url:p="https://datamanager.googleapis.com/v1",consent:h,testEventCode:m,userData:w,userId:y,clientId:b,sessionAttributes:N,consentAdUserData:S,consentAdPersonalization:D}=l.settings,E=w?await(0,o.getMappingValue)(e,{map:w}):{},C=y?await(0,o.getMappingValue)(e,y):void 0,O=b?await(0,o.getMappingValue)(e,b):void 0,I=N?await(0,o.getMappingValue)(e,N):void 0,A="boolean"==typeof S?S:"string"==typeof S&&e.consent?e.consent[S]:void 0,k="boolean"==typeof D?D:"string"==typeof D&&e.consent?e.consent[D]:void 0,T={};(0,o.isObject)(E)&&Object.assign(T,E),void 0!==C&&(T.userId=C),void 0!==O&&(T.clientId=O),void 0!==I&&(T.sessionAttributes=I),void 0!==A&&(T.adUserData=A),void 0!==k&&(T.adPersonalization=k);const P=l.data?await(0,o.getMappingValue)(e,l.data):{},j=(0,o.isObject)(i)?i:{},_={...T,...(0,o.isObject)(P)?P:{},...j},G=await f(e,_);G.eventSource||(G.eventSource=d),!G.consent&&h&&(G.consent=h),G.transactionId||c.throw("transactionId is required");g.some(e=>{var t;return"GOOGLE_ANALYTICS_PROPERTY"===(null==(t=e.operatingAccount)?void 0:t.accountType)})&&!G.eventName&&c.throw("eventName is required for GA4 destinations");const M={events:[G],destinations:g};h&&(M.consent=h),u&&(M.validateOnly=!0),m&&(M.testEventCode=m);const $=null==r?void 0:r.authClient;if(!$)return c.throw("Auth client not initialized. Ensure init() was called successfully.");let z;try{z=await async function(e){try{const t=await e.getAccessToken();if(!t.token)throw new v("Auth client returned empty token");return t.token}catch(e){throw new v("Failed to obtain access token",e instanceof Error?e:void 0)}}($)}catch(e){throw c.error("Authentication failed",{error:e}),e}const q=(null==r?void 0:r.fetch)||fetch,U=`${p}/events:ingest`;c.debug("Sending to Data Manager API",{endpoint:U,eventCount:M.events.length,destinations:g.length,validateOnly:u});const V=await q(U,{method:"POST",headers:{Authorization:`Bearer ${z}`,"Content-Type":"application/json"},body:JSON.stringify(M)});if(!V.ok){const e=await V.text();c.throw(`Data Manager API error (${V.status}): ${e}`)}const L=await V.json();c.debug("API response",{status:V.status,requestId:L.requestId}),L.validationErrors&&L.validationErrors.length>0&&c.throw(`Validation errors: ${JSON.stringify(L.validationErrors)}`)},w={},y={type:"datamanager",config:{},async init({config:e,env:t,logger:n}){const i=s(e,n);try{const e=await async function(e){const{credentials:t,keyFilename:n,scopes:i=h}=e;try{if(t){const e=new p.GoogleAuth({credentials:t,scopes:i});return await e.getClient()}if(n){const e=new p.GoogleAuth({keyFilename:n,scopes:i});return await e.getClient()}const e=new p.GoogleAuth({scopes:i});return await e.getClient()}catch(e){throw new v("Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.",e instanceof Error?e:void 0)}}(i.settings);return n.debug("Auth client created"),{...i,env:{...t,authClient:e}}}catch(e){n.throw(`Data Manager authentication failed: ${e instanceof Error?e.message:"Unknown error"}`)}},push:async(e,{config:t,mapping:n,data:i,collector:a,env:r,logger:s})=>await m(e,{config:t,mapping:n,data:i,collector:a,env:r,logger:s})},b=y;//# 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/utils.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 }) {\n const config = getConfig(partialConfig);\n\n if (!config.settings) {\n throw new Error('Settings required for Data Manager destination');\n }\n\n if (\n !config.settings.destinations ||\n config.settings.destinations.length === 0\n ) {\n throw new Error('At least one destination required in settings');\n }\n\n try {\n const authClient = await createAuthClient(config.settings);\n\n return {\n ...config,\n env: {\n ...env,\n authClient,\n },\n };\n } catch (error) {\n throw new Error(\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 }) {\n return await push(event, { config, mapping, data, collector, env });\n },\n};\n\nexport default destinationDataManager;\n","import type { Config, Settings, PartialConfig } from './types';\nimport { throwError } from '@walkeros/core';\n\nexport function getConfig(partialConfig: PartialConfig = {}): Config {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { destinations } = settings;\n\n if (!destinations || destinations.length === 0)\n throwError('Config settings destinations missing or empty');\n\n const settingsConfig: Settings = {\n ...settings,\n destinations,\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 { createLogger } from './utils';\nimport { getAccessToken } from './auth';\n\nexport const push: PushFn = async function (\n event,\n { config, mapping, data, collector, env },\n) {\n const {\n destinations,\n eventSource,\n validateOnly = false,\n url = 'https://datamanager.googleapis.com/v1',\n consent: requestConsent,\n testEventCode,\n logLevel = 'none',\n userData,\n userId,\n clientId,\n sessionAttributes,\n consentAdUserData,\n consentAdPersonalization,\n } = config.settings!;\n\n const logger = createLogger(logLevel);\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 = config.data\n ? await getMappingValue(event, config.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 logger.debug('Processing event', {\n name: event.name,\n id: event.id,\n timestamp: event.timestamp,\n });\n\n // Apply event source from settings if not set\n if (!dataManagerEvent.eventSource && 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 if (!destinations || destinations.length === 0) {\n throw new Error('Destinations are required');\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 throw new Error(\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.error('API request failed', {\n status: response.status,\n error: errorText,\n });\n throw new Error(\n `Data Manager API error (${response.status}): ${errorText}`,\n );\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.error('Validation errors', {\n errors: result.validationErrors,\n });\n throw new Error(\n `Validation errors: ${JSON.stringify(result.validationErrors)}`,\n );\n }\n\n logger.info('Event processed successfully', {\n requestId: result.requestId,\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","export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';\n\n/* eslint-disable no-console */\nexport function createLogger(level: LogLevel = 'none') {\n const levels = { debug: 0, info: 1, warn: 2, error: 3, none: 4 };\n const currentLevel = levels[level];\n\n return {\n debug: (message: string, data?: unknown) => {\n if (currentLevel <= 0)\n console.log(`[DataManager] ${message}`, data || '');\n },\n info: (message: string, data?: unknown) => {\n if (currentLevel <= 1)\n console.log(`[DataManager] ${message}`, data || '');\n },\n warn: (message: string, data?: unknown) => {\n if (currentLevel <= 2)\n console.warn(`[DataManager] ${message}`, data || '');\n },\n error: (message: string, data?: unknown) => {\n if (currentLevel <= 3)\n console.error(`[DataManager] ${message}`, data || '');\n },\n };\n}\n/* eslint-enable no-console */\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';\nimport type { LogLevel } from '../utils';\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 /** Default event source if not specified per event */\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 /** Log level for debugging (optional) */\n logLevel?: LogLevel;\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\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","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, { 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"]}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{throwError as t}from"@walkeros/core";import{getMappingValue as e,isObject as n}from"@walkeros/core";import{isString as a,isDefined as r}from"@walkeros/core";import{isString as o}from"@walkeros/core";import{getHashServer as s}from"@walkeros/server-core";async function i(t,e="given"){if(!o(t)||!t)return"";let n=t.trim().toLowerCase();if("given"===e){const t=["mr.","mrs.","ms.","miss.","dr.","prof."];for(const e of t)if(n.startsWith(e)){n=n.substring(e.length).trim();break}}if("family"===e){const t=["jr.","sr.","iii","ii","iv","v"];for(const e of t){const t=` ${e}`;if(n.endsWith(t)){n=n.substring(0,n.length-t.length).trim();break}if(n.endsWith(e)){n=n.substring(0,n.length-e.length).trim();break}}}return s(n)}async function c(t){const e=[];if(a(t.email)&&t.email){const n=await async function(t){if(!o(t)||!t)return"";let e=t.trim().toLowerCase();if(e.endsWith("@gmail.com")||e.endsWith("@googlemail.com")){const[t,n]=e.split("@");e=`${t.replace(/\./g,"")}@${n}`}return s(e)}(t.email);n&&e.push({emailAddress:n})}if(a(t.phone)&&t.phone){const n=await async function(t){if(!o(t)||!t)return"";let e=t.trim();const n=e.startsWith("+");return e=e.replace(/\D/g,""),e=n||e.length>10?`+${e}`:`+1${e}`,s(e)}(t.phone);n&&e.push({phoneNumber:n})}if(t.firstName||t.lastName||t.regionCode||t.postalCode){const n={};a(t.firstName)&&t.firstName&&(n.givenName=await i(t.firstName,"given")),a(t.lastName)&&t.lastName&&(n.familyName=await i(t.lastName,"family")),a(t.regionCode)&&t.regionCode&&(n.regionCode=t.regionCode.toUpperCase()),a(t.postalCode)&&t.postalCode&&(n.postalCode=t.postalCode),Object.keys(n).length>0&&e.push({address:n})}if(0!==e.length)return{userIdentifiers:e.slice(0,10)}}async function d(t,e){const n={eventTimestamp:(o=t.timestamp,new Date(o).toISOString())};var o;const s=e||{};a(s.transactionId)&&s.transactionId&&(n.transactionId=s.transactionId.substring(0,512)),a(s.clientId)&&s.clientId&&(n.clientId=s.clientId.substring(0,255)),a(s.userId)&&s.userId&&(n.userId=s.userId.substring(0,256));const i=await c(s);i&&(n.userData=i);const d=function(t){const e={};return a(t.gclid)&&t.gclid&&(e.gclid=t.gclid),a(t.gbraid)&&t.gbraid&&(e.gbraid=t.gbraid),a(t.wbraid)&&t.wbraid&&(e.wbraid=t.wbraid),a(t.sessionAttributes)&&t.sessionAttributes&&(e.sessionAttributes=t.sessionAttributes),Object.keys(e).length>0?e:void 0}(s);d&&(n.adIdentifiers=d),"number"==typeof s.conversionValue&&(n.conversionValue=s.conversionValue),a(s.currency)&&s.currency&&(n.currency=s.currency.substring(0,3).toUpperCase()),s.cartData&&"object"==typeof s.cartData&&(n.cartData=s.cartData),a(s.eventName)&&s.eventName&&(n.eventName=s.eventName.substring(0,40)),a(s.eventSource)&&s.eventSource&&(n.eventSource=s.eventSource);const l={};if("boolean"==typeof s.adUserData&&(l.adUserData=s.adUserData?"CONSENT_GRANTED":"CONSENT_DENIED"),"boolean"==typeof s.adPersonalization&&(l.adPersonalization=s.adPersonalization?"CONSENT_GRANTED":"CONSENT_DENIED"),0===Object.keys(l).length){const e=function(t){if(!t)return;const e={};return r(t.marketing)&&(e.adUserData=t.marketing?"CONSENT_GRANTED":"CONSENT_DENIED"),r(t.personalization)&&(e.adPersonalization=t.personalization?"CONSENT_GRANTED":"CONSENT_DENIED"),Object.keys(e).length>0?e:void 0}(t.consent);e&&(n.consent=e)}else n.consent=l;return n}import{GoogleAuth as l}from"google-auth-library";var u=["https://www.googleapis.com/auth/datamanager"],g=class extends Error{constructor(t,e){super(t),this.cause=e,this.name="DataManagerAuthError"}};var f=async function(t,{config:a,mapping:r,data:o,collector:s,env:i}){const{destinations:c,eventSource:l,validateOnly:u=!1,url:f="https://datamanager.googleapis.com/v1",consent:m,testEventCode:p,logLevel:h="none",userData:w,userId:v,clientId:y,sessionAttributes:b,consentAdUserData:E,consentAdPersonalization:N}=a.settings,D=function(t="none"){const e={debug:0,info:1,warn:2,error:3,none:4}[t];return{debug:(t,n)=>{e<=0&&console.log(`[DataManager] ${t}`,n||"")},info:(t,n)=>{e<=1&&console.log(`[DataManager] ${t}`,n||"")},warn:(t,n)=>{e<=2&&console.warn(`[DataManager] ${t}`,n||"")},error:(t,n)=>{e<=3&&console.error(`[DataManager] ${t}`,n||"")}}}(h),C=w?await e(t,{map:w}):{},I=v?await e(t,v):void 0,A=y?await e(t,y):void 0,k=b?await e(t,b):void 0,S="boolean"==typeof E?E:"string"==typeof E&&t.consent?t.consent[E]:void 0,O="boolean"==typeof N?N:"string"==typeof N&&t.consent?t.consent[N]:void 0,T={};n(C)&&Object.assign(T,C),void 0!==I&&(T.userId=I),void 0!==A&&(T.clientId=A),void 0!==k&&(T.sessionAttributes=k),void 0!==S&&(T.adUserData=S),void 0!==O&&(T.adPersonalization=O);const $=a.data?await e(t,a.data):{},P=n(o)?o:{},z={...T,...n($)?$:{},...P},M=await d(t,z);if(D.debug("Processing event",{name:t.name,id:t.id,timestamp:t.timestamp}),!M.eventSource&&l&&(M.eventSource=l),!M.consent&&m&&(M.consent=m),!c||0===c.length)throw new Error("Destinations are required");const _={events:[M],destinations:c};m&&(_.consent=m),u&&(_.validateOnly=!0),p&&(_.testEventCode=p);const j=null==i?void 0:i.authClient;if(!j)throw new Error("Auth client not initialized. Ensure init() was called successfully.");let U;try{U=await async function(t){try{const e=await t.getAccessToken();if(!e.token)throw new g("Auth client returned empty token");return e.token}catch(t){throw new g("Failed to obtain access token",t instanceof Error?t:void 0)}}(j)}catch(t){throw D.error("Authentication failed",{error:t}),t}const q=(null==i?void 0:i.fetch)||fetch,G=`${f}/events:ingest`;D.debug("Sending to Data Manager API",{endpoint:G,eventCount:_.events.length,destinations:c.length,validateOnly:u});const L=await q(G,{method:"POST",headers:{Authorization:`Bearer ${U}`,"Content-Type":"application/json"},body:JSON.stringify(_)});if(!L.ok){const t=await L.text();throw D.error("API request failed",{status:L.status,error:t}),new Error(`Data Manager API error (${L.status}): ${t}`)}const W=await L.json();if(D.debug("API response",{status:L.status,requestId:W.requestId}),W.validationErrors&&W.validationErrors.length>0)throw D.error("Validation errors",{errors:W.validationErrors}),new Error(`Validation errors: ${JSON.stringify(W.validationErrors)}`);D.info("Event processed successfully",{requestId:W.requestId})},m={},p={type:"datamanager",config:{},async init({config:e,env:n}){const a=function(e={}){const n=e.settings||{},{destinations:a}=n;a&&0!==a.length||t("Config settings destinations missing or empty");const r={...n,destinations:a};return{...e,settings:r}}(e);if(!a.settings)throw new Error("Settings required for Data Manager destination");if(!a.settings.destinations||0===a.settings.destinations.length)throw new Error("At least one destination required in settings");try{const t=await async function(t){const{credentials:e,keyFilename:n,scopes:a=u}=t;try{if(e){const t=new l({credentials:e,scopes:a});return await t.getClient()}if(n){const t=new l({keyFilename:n,scopes:a});return await t.getClient()}const t=new l({scopes:a});return await t.getClient()}catch(t){throw new g("Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.",t instanceof Error?t:void 0)}}(a.settings);return{...a,env:{...n,authClient:t}}}catch(t){throw new Error(`Data Manager authentication failed: ${t instanceof Error?t.message:"Unknown error"}`)}},push:async(t,{config:e,mapping:n,data:a,collector:r,env:o})=>await f(t,{config:e,mapping:n,data:a,collector:r,env:o})},h=p;export{m as DestinationDataManager,h as default,p as destinationDataManager};//# sourceMappingURL=index.mjs.map
1
+ function t(t={},e){const n=t.settings||{},{destinations:a,eventSource:o="WEB"}=n;a&&0!==a.length||e.throw("Config settings destinations missing or empty");const i={...n,destinations:a,eventSource:o};return{...t,settings:i}}import{getMappingValue as e,isObject as n}from"@walkeros/core";import{isString as a,isDefined as o}from"@walkeros/core";import{isString as i}from"@walkeros/core";import{getHashServer as s}from"@walkeros/server-core";async function r(t,e="given"){if(!i(t)||!t)return"";let n=t.trim().toLowerCase();if("given"===e){const t=["mr.","mrs.","ms.","miss.","dr.","prof."];for(const e of t)if(n.startsWith(e)){n=n.substring(e.length).trim();break}}if("family"===e){const t=["jr.","sr.","iii","ii","iv","v"];for(const e of t){const t=` ${e}`;if(n.endsWith(t)){n=n.substring(0,n.length-t.length).trim();break}if(n.endsWith(e)){n=n.substring(0,n.length-e.length).trim();break}}}return s(n)}async function c(t){const e=[];if(a(t.email)&&t.email){const n=await async function(t){if(!i(t)||!t)return"";let e=t.trim().toLowerCase();if(e.endsWith("@gmail.com")||e.endsWith("@googlemail.com")){const[t,n]=e.split("@");e=`${t.replace(/\./g,"")}@${n}`}return s(e)}(t.email);n&&e.push({emailAddress:n})}if(a(t.phone)&&t.phone){const n=await async function(t){if(!i(t)||!t)return"";let e=t.trim();const n=e.startsWith("+");return e=e.replace(/\D/g,""),e=n||e.length>10?`+${e}`:`+1${e}`,s(e)}(t.phone);n&&e.push({phoneNumber:n})}if(t.firstName||t.lastName||t.regionCode||t.postalCode){const n={};a(t.firstName)&&t.firstName&&(n.givenName=await r(t.firstName,"given")),a(t.lastName)&&t.lastName&&(n.familyName=await r(t.lastName,"family")),a(t.regionCode)&&t.regionCode&&(n.regionCode=t.regionCode.toUpperCase()),a(t.postalCode)&&t.postalCode&&(n.postalCode=t.postalCode),Object.keys(n).length>0&&e.push({address:n})}if(0!==e.length)return{userIdentifiers:e.slice(0,10)}}async function d(t,e){const n={eventTimestamp:(i=t.timestamp,new Date(i).toISOString())};var i;const s=e||{};a(s.transactionId)&&s.transactionId&&(n.transactionId=s.transactionId.substring(0,512)),a(s.clientId)&&s.clientId&&(n.clientId=s.clientId.substring(0,255)),a(s.userId)&&s.userId&&(n.userId=s.userId.substring(0,256));const r=await c(s);r&&(n.userData=r);const d=function(t){const e={};return a(t.gclid)&&t.gclid&&(e.gclid=t.gclid),a(t.gbraid)&&t.gbraid&&(e.gbraid=t.gbraid),a(t.wbraid)&&t.wbraid&&(e.wbraid=t.wbraid),a(t.sessionAttributes)&&t.sessionAttributes&&(e.sessionAttributes=t.sessionAttributes),Object.keys(e).length>0?e:void 0}(s);d&&(n.adIdentifiers=d),"number"==typeof s.conversionValue&&(n.conversionValue=s.conversionValue),a(s.currency)&&s.currency&&(n.currency=s.currency.substring(0,3).toUpperCase()),s.cartData&&"object"==typeof s.cartData&&(n.cartData=s.cartData),a(s.eventName)&&s.eventName&&(n.eventName=s.eventName.substring(0,40)),a(s.eventSource)&&s.eventSource&&(n.eventSource=s.eventSource);const l={};if("boolean"==typeof s.adUserData&&(l.adUserData=s.adUserData?"CONSENT_GRANTED":"CONSENT_DENIED"),"boolean"==typeof s.adPersonalization&&(l.adPersonalization=s.adPersonalization?"CONSENT_GRANTED":"CONSENT_DENIED"),0===Object.keys(l).length){const e=function(t){if(!t)return;const e={};return o(t.marketing)&&(e.adUserData=t.marketing?"CONSENT_GRANTED":"CONSENT_DENIED"),o(t.personalization)&&(e.adPersonalization=t.personalization?"CONSENT_GRANTED":"CONSENT_DENIED"),Object.keys(e).length>0?e:void 0}(t.consent);e&&(n.consent=e)}else n.consent=l;return n}import{GoogleAuth as l}from"google-auth-library";var u=["https://www.googleapis.com/auth/datamanager"],g=class extends Error{constructor(t,e){super(t),this.cause=e,this.name="DataManagerAuthError"}};var f=async function(a,{config:o,mapping:i,data:s,collector:r,env:c,logger:l}){const u=t(o,l),{destinations:f,eventSource:m,validateOnly:h=!1,url:p="https://datamanager.googleapis.com/v1",consent:v,testEventCode:w,userData:y,userId:N,clientId:b,sessionAttributes:E,consentAdUserData:C,consentAdPersonalization:I}=u.settings,D=y?await e(a,{map:y}):{},A=N?await e(a,N):void 0,O=b?await e(a,b):void 0,S=E?await e(a,E):void 0,k="boolean"==typeof C?C:"string"==typeof C&&a.consent?a.consent[C]:void 0,T="boolean"==typeof I?I:"string"==typeof I&&a.consent?a.consent[I]:void 0,P={};n(D)&&Object.assign(P,D),void 0!==A&&(P.userId=A),void 0!==O&&(P.clientId=O),void 0!==S&&(P.sessionAttributes=S),void 0!==k&&(P.adUserData=k),void 0!==T&&(P.adPersonalization=T);const _=u.data?await e(a,u.data):{},$=n(s)?s:{},z={...P,...n(_)?_:{},...$},j=await d(a,z);j.eventSource||(j.eventSource=m),!j.consent&&v&&(j.consent=v),j.transactionId||l.throw("transactionId is required");f.some(t=>{var e;return"GOOGLE_ANALYTICS_PROPERTY"===(null==(e=t.operatingAccount)?void 0:e.accountType)})&&!j.eventName&&l.throw("eventName is required for GA4 destinations");const G={events:[j],destinations:f};v&&(G.consent=v),h&&(G.validateOnly=!0),w&&(G.testEventCode=w);const U=null==c?void 0:c.authClient;if(!U)return l.throw("Auth client not initialized. Ensure init() was called successfully.");let L;try{L=await async function(t){try{const e=await t.getAccessToken();if(!e.token)throw new g("Auth client returned empty token");return e.token}catch(t){throw new g("Failed to obtain access token",t instanceof Error?t:void 0)}}(U)}catch(t){throw l.error("Authentication failed",{error:t}),t}const R=(null==c?void 0:c.fetch)||fetch,W=`${p}/events:ingest`;l.debug("Sending to Data Manager API",{endpoint:W,eventCount:G.events.length,destinations:f.length,validateOnly:h});const M=await R(W,{method:"POST",headers:{Authorization:`Bearer ${L}`,"Content-Type":"application/json"},body:JSON.stringify(G)});if(!M.ok){const t=await M.text();l.throw(`Data Manager API error (${M.status}): ${t}`)}const q=await M.json();l.debug("API response",{status:M.status,requestId:q.requestId}),q.validationErrors&&q.validationErrors.length>0&&l.throw(`Validation errors: ${JSON.stringify(q.validationErrors)}`)},m={},h={type:"datamanager",config:{},async init({config:e,env:n,logger:a}){const o=t(e,a);try{const t=await async function(t){const{credentials:e,keyFilename:n,scopes:a=u}=t;try{if(e){const t=new l({credentials:e,scopes:a});return await t.getClient()}if(n){const t=new l({keyFilename:n,scopes:a});return await t.getClient()}const t=new l({scopes:a});return await t.getClient()}catch(t){throw new g("Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.",t instanceof Error?t:void 0)}}(o.settings);return a.debug("Auth client created"),{...o,env:{...n,authClient:t}}}catch(t){a.throw(`Data Manager authentication failed: ${t instanceof Error?t.message:"Unknown error"}`)}},push:async(t,{config:e,mapping:n,data:a,collector:o,env:i,logger:s})=>await f(t,{config:e,mapping:n,data:a,collector:o,env:i,logger:s})},p=h;export{m as DestinationDataManager,p as default,h as destinationDataManager};//# sourceMappingURL=index.mjs.map