@walkeros/server-destination-datamanager 3.4.2 → 4.0.0-next-1777463920154
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/dev.js +1 -1
- package/dist/dev.js.map +1 -1
- package/dist/dev.mjs +1 -1
- package/dist/dev.mjs.map +1 -1
- package/dist/examples/index.js +8 -5
- package/dist/examples/index.mjs +8 -5
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/walkerOS.json +12 -47
- package/package.json +4 -4
package/dist/dev.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e,t=Object.defineProperty,a=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,i=Object.prototype.hasOwnProperty,r=(e,a)=>{for(var n in a)t(e,n,{get:a[n],enumerable:!0})},o={};r(o,{examples:()=>I,schemas:()=>s}),module.exports=(e=o,((e,r,o,s)=>{if(r&&"object"==typeof r||"function"==typeof r)for(let c of n(r))i.call(e,c)||c===o||t(e,c,{get:()=>r[c],enumerable:!(s=a(r,c))||s.enumerable});return e})(t({},"__esModule",{value:!0}),e));var s={};r(s,{mapping:()=>E,settings:()=>z});var c=require("@walkeros/core/dev"),d=c.z.enum(["GOOGLE_ADS","DISPLAY_VIDEO_ADVERTISER","DISPLAY_VIDEO_PARTNER","GOOGLE_ANALYTICS_PROPERTY"]),l=c.z.enum(["WEB","APP","IN_STORE","PHONE","OTHER"]),u=c.z.enum(["CONSENT_GRANTED","CONSENT_DENIED"]),p=c.z.object({adUserData:u.describe("Consent for data collection and use").optional(),adPersonalization:u.describe("Consent for ad personalization").optional()}),m=c.z.object({accountId:c.z.string().min(1).describe('Account ID (e.g., "123-456-7890" for Google Ads)'),accountType:d.describe("Type of account")}),v=c.z.object({operatingAccount:m.describe("Operating account details"),productDestinationId:c.z.string().min(1).describe("Product-specific destination ID (conversion action or user list)")}),g=require("@walkeros/core/dev"),b=g.z.object({client_email:g.z.string().email().describe("Service account email"),private_key:g.z.string().min(1).describe("Service account private key (PEM format)")}),f=g.z.object({credentials:b.optional().describe("Service account credentials (client_email + private_key). Recommended for serverless environments."),keyFilename:g.z.string().optional().describe("Path to service account JSON file. For local development or environments with filesystem access."),scopes:g.z.array(g.z.string()).optional().describe("OAuth scopes for Data Manager API. Defaults to datamanager scope."),destinations:g.z.array(v).min(1).max(10).describe("Array of destination accounts and conversion actions/user lists (max 10)"),eventSource:l.optional().default("WEB").describe("Event source for all events. Defaults to WEB. Values: WEB, APP, IN_STORE, PHONE, OTHER"),batchSize:g.z.number().int().min(1).max(2e3).describe("Maximum number of events to batch before sending (max 2000, like 100)").optional(),batchInterval:g.z.number().int().min(0).describe("Time in milliseconds to wait before auto-flushing batch (like 5000)").optional(),validateOnly:g.z.boolean().describe("If true, validate request without ingestion (testing mode)").optional(),url:g.z.string().url().describe("Override API endpoint for testing (like https://datamanager.googleapis.com/v1)").optional(),consent:p.describe("Request-level consent for all events").optional(),testEventCode:g.z.string().describe("Test event code for debugging (like TEST12345)").optional(),logLevel:g.z.enum(["debug","info","warn","error","none"]).describe("Log level for debugging (debug shows all API calls)").optional(),userData:g.z.record(g.z.string(),g.z.unknown()).describe("Guided helper: User data mapping for all events (like { email: 'user.id', phone: 'data.phone' })").optional(),userId:g.z.any().describe("Guided helper: First-party user ID for all events (like 'user.id')").optional(),clientId:g.z.any().describe("Guided helper: GA4 client ID for all events (like 'user.device')").optional(),sessionAttributes:g.z.any().describe("Guided helper: Privacy-safe attribution for all events (like 'context.sessionAttributes')").optional(),consentAdUserData:g.z.union([g.z.string(),g.z.boolean()]).describe("Consent mapping: Field name from event.consent (like 'marketing') or static boolean value").optional(),consentAdPersonalization:g.z.union([g.z.string(),g.z.boolean()]).describe("Consent mapping: Field name from event.consent (like 'targeting') or static boolean value").optional()}),y=require("@walkeros/core/dev").z.object({}),h=require("@walkeros/core/dev"),z=(0,h.zodToSchema)(f),E=(0,h.zodToSchema)(y),I={};r(I,{env:()=>O,step:()=>
|
|
1
|
+
"use strict";var e,t=Object.defineProperty,a=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,i=Object.prototype.hasOwnProperty,r=(e,a)=>{for(var n in a)t(e,n,{get:a[n],enumerable:!0})},o={};r(o,{examples:()=>I,schemas:()=>s}),module.exports=(e=o,((e,r,o,s)=>{if(r&&"object"==typeof r||"function"==typeof r)for(let c of n(r))i.call(e,c)||c===o||t(e,c,{get:()=>r[c],enumerable:!(s=a(r,c))||s.enumerable});return e})(t({},"__esModule",{value:!0}),e));var s={};r(s,{mapping:()=>E,settings:()=>z});var c=require("@walkeros/core/dev"),d=c.z.enum(["GOOGLE_ADS","DISPLAY_VIDEO_ADVERTISER","DISPLAY_VIDEO_PARTNER","GOOGLE_ANALYTICS_PROPERTY"]),l=c.z.enum(["WEB","APP","IN_STORE","PHONE","OTHER"]),u=c.z.enum(["CONSENT_GRANTED","CONSENT_DENIED"]),p=c.z.object({adUserData:u.describe("Consent for data collection and use").optional(),adPersonalization:u.describe("Consent for ad personalization").optional()}),m=c.z.object({accountId:c.z.string().min(1).describe('Account ID (e.g., "123-456-7890" for Google Ads)'),accountType:d.describe("Type of account")}),v=c.z.object({operatingAccount:m.describe("Operating account details"),productDestinationId:c.z.string().min(1).describe("Product-specific destination ID (conversion action or user list)")}),g=require("@walkeros/core/dev"),b=g.z.object({client_email:g.z.string().email().describe("Service account email"),private_key:g.z.string().min(1).describe("Service account private key (PEM format)")}),f=g.z.object({credentials:b.optional().describe("Service account credentials (client_email + private_key). Recommended for serverless environments."),keyFilename:g.z.string().optional().describe("Path to service account JSON file. For local development or environments with filesystem access."),scopes:g.z.array(g.z.string()).optional().describe("OAuth scopes for Data Manager API. Defaults to datamanager scope."),destinations:g.z.array(v).min(1).max(10).describe("Array of destination accounts and conversion actions/user lists (max 10)"),eventSource:l.optional().default("WEB").describe("Event source for all events. Defaults to WEB. Values: WEB, APP, IN_STORE, PHONE, OTHER"),batchSize:g.z.number().int().min(1).max(2e3).describe("Maximum number of events to batch before sending (max 2000, like 100)").optional(),batchInterval:g.z.number().int().min(0).describe("Time in milliseconds to wait before auto-flushing batch (like 5000)").optional(),validateOnly:g.z.boolean().describe("If true, validate request without ingestion (testing mode)").optional(),url:g.z.string().url().describe("Override API endpoint for testing (like https://datamanager.googleapis.com/v1)").optional(),consent:p.describe("Request-level consent for all events").optional(),testEventCode:g.z.string().describe("Test event code for debugging (like TEST12345)").optional(),logLevel:g.z.enum(["debug","info","warn","error","none"]).describe("Log level for debugging (debug shows all API calls)").optional(),userData:g.z.record(g.z.string(),g.z.unknown()).describe("Guided helper: User data mapping for all events (like { email: 'user.id', phone: 'data.phone' })").optional(),userId:g.z.any().describe("Guided helper: First-party user ID for all events (like 'user.id')").optional(),clientId:g.z.any().describe("Guided helper: GA4 client ID for all events (like 'user.device')").optional(),sessionAttributes:g.z.any().describe("Guided helper: Privacy-safe attribution for all events (like 'context.sessionAttributes')").optional(),consentAdUserData:g.z.union([g.z.string(),g.z.boolean()]).describe("Consent mapping: Field name from event.consent (like 'marketing') or static boolean value").optional(),consentAdPersonalization:g.z.union([g.z.string(),g.z.boolean()]).describe("Consent mapping: Field name from event.consent (like 'targeting') or static boolean value").optional()}),y=require("@walkeros/core/dev").z.object({}),h=require("@walkeros/core/dev"),z=(0,h.zodToSchema)(f),E=(0,h.zodToSchema)(y),I={};r(I,{env:()=>O,step:()=>S});var O={};r(O,{push:()=>A,simulation:()=>D});var A={fetch:async function(){return{ok:!0,status:200,json:async()=>({requestId:"mock-request-id",validationErrors:[]}),text:async()=>""}},authClient:{getAccessToken:async()=>({token:"ya29.c.test_token"})}},D=["fetch"],S={};r(S,{ga4PageView:()=>R,lead:()=>N,purchase:()=>w});var k=require("@walkeros/core"),P="https://datamanager.googleapis.com/v1/events:ingest",T=[{operatingAccount:{accountId:"123-456-7890",accountType:"GOOGLE_ADS"},productDestinationId:"AW-CONVERSION-123"}],_=e=>({method:"POST",headers:{Authorization:"Bearer ya29.c.test_token","Content-Type":"application/json"},body:e}),w={title:"Purchase",description:"A completed order is posted to Google Data Manager as a purchase conversion with hashed user identifiers.",in:(0,k.getEvent)("order complete",{id:"ev-1700000900000",timestamp:17000009e5,data:{id:"ORD-600",total:149.99,currency:"EUR"},user:{id:"user-abc",email:"buyer@example.com"},source:{type:"express",platform:"server"}}),mapping:{name:"purchase",data:{map:{transactionId:"data.id",conversionValue:"data.total",currency:{key:"data.currency",value:"USD"},eventName:{value:"purchase"},userId:"user.id",email:"user.email"}}},out:[["fetch",P,_(JSON.stringify({events:[{eventTimestamp:"2023-11-14T22:28:20.000Z",transactionId:"ORD-600",userId:"user-abc",userData:{userIdentifiers:[{emailAddress:"6a6c26195c3682faa816966af789717c3bfa834eee6c599d667d2b3429c27cfd"}]},conversionValue:149.99,currency:"EUR",eventName:"purchase",eventSource:"WEB"}],destinations:T}))]]},N={title:"Lead",description:"A demo request form submission is sent to Data Manager as a generate_lead conversion with a hashed email.",in:(0,k.getEvent)("form submit",{id:"ev-1700000901000",timestamp:1700000901e3,data:{type:"demo-request"},user:{email:"prospect@example.com"},source:{type:"express",platform:"server"}}),mapping:{name:"generate_lead",data:{map:{transactionId:"id",eventName:{value:"generate_lead"},conversionValue:{value:10},currency:{value:"USD"},email:"user.email"}}},out:[["fetch",P,_(JSON.stringify({events:[{eventTimestamp:"2023-11-14T22:28:21.000Z",transactionId:"ev-1700000901000",userData:{userIdentifiers:[{emailAddress:"395ec5f334be0ab5b28568a1e7f6ed5ea80e443fb1ce3d803340586a3df46642"}]},conversionValue:10,currency:"USD",eventName:"generate_lead",eventSource:"WEB"}],destinations:T}))]]},R={title:"Page view",description:"A page view is sent to Data Manager as a page_view event tied to the walker user id.",in:(0,k.getEvent)("page view",{id:"ev-1700000902000",timestamp:1700000902e3,data:{title:"Pricing",url:"https://example.com/pricing"},user:{id:"visitor-55"},source:{type:"express",platform:"server"}}),mapping:{name:"page_view",data:{map:{transactionId:"id",eventName:{value:"page_view"},userId:"user.id"}}},out:[["fetch",P,_(JSON.stringify({events:[{eventTimestamp:"2023-11-14T22:28:22.000Z",transactionId:"ev-1700000902000",userId:"visitor-55",eventName:"page_view",eventSource:"WEB"}],destinations:T}))]]};//# sourceMappingURL=dev.js.map
|
package/dist/dev.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/dev.ts","../src/schemas.ts","../src/schemas/primitives.ts","../src/schemas/settings.ts","../src/schemas/mapping.ts","../src/schemas/index.ts","../src/examples/index.ts","../src/examples/env.ts","../src/examples/step.ts"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\n","// Browser-safe schema-only exports\n// This file exports ONLY schemas without any Node.js dependencies\nexport { settings, mapping } from './schemas/index';\n","import { z } from '@walkeros/core/dev';\n\nexport const AccountTypeSchema = z.enum([\n 'GOOGLE_ADS',\n 'DISPLAY_VIDEO_ADVERTISER',\n 'DISPLAY_VIDEO_PARTNER',\n 'GOOGLE_ANALYTICS_PROPERTY',\n]);\n\nexport const EventSourceSchema = z.enum([\n 'WEB',\n 'APP',\n 'IN_STORE',\n 'PHONE',\n 'OTHER',\n]);\n\nexport const ConsentStatusSchema = z.enum([\n 'CONSENT_GRANTED',\n 'CONSENT_DENIED',\n]);\n\nexport const ConsentSchema = z.object({\n adUserData: ConsentStatusSchema.describe(\n 'Consent for data collection and use',\n ).optional(),\n adPersonalization: ConsentStatusSchema.describe(\n 'Consent for ad personalization',\n ).optional(),\n});\n\nexport const OperatingAccountSchema = z.object({\n accountId: z\n .string()\n .min(1)\n .describe('Account ID (e.g., \"123-456-7890\" for Google Ads)'),\n accountType: AccountTypeSchema.describe('Type of account'),\n});\n\nexport const DestinationSchema = z.object({\n operatingAccount: OperatingAccountSchema.describe(\n 'Operating account details',\n ),\n productDestinationId: z\n .string()\n .min(1)\n .describe(\n 'Product-specific destination ID (conversion action or user list)',\n ),\n});\n","import { z } from '@walkeros/core/dev';\nimport {\n DestinationSchema,\n EventSourceSchema,\n ConsentSchema,\n} from './primitives';\n\n/**\n * Service account credentials schema\n */\nconst CredentialsSchema = z.object({\n client_email: z.string().email().describe('Service account email'),\n private_key: z\n .string()\n .min(1)\n .describe('Service account private key (PEM format)'),\n});\n\nexport const SettingsSchema = z.object({\n credentials: CredentialsSchema.optional().describe(\n 'Service account credentials (client_email + private_key). Recommended for serverless environments.',\n ),\n keyFilename: z\n .string()\n .optional()\n .describe(\n 'Path to service account JSON file. For local development or environments with filesystem access.',\n ),\n scopes: z\n .array(z.string())\n .optional()\n .describe(\n 'OAuth scopes for Data Manager API. Defaults to datamanager scope.',\n ),\n destinations: z\n .array(DestinationSchema)\n .min(1)\n .max(10)\n .describe(\n 'Array of destination accounts and conversion actions/user lists (max 10)',\n ),\n eventSource: EventSourceSchema.optional()\n .default('WEB')\n .describe(\n 'Event source for all events. Defaults to WEB. Values: WEB, APP, IN_STORE, PHONE, OTHER',\n ),\n batchSize: z\n .number()\n .int()\n .min(1)\n .max(2000)\n .describe(\n 'Maximum number of events to batch before sending (max 2000, like 100)',\n )\n .optional(),\n batchInterval: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Time in milliseconds to wait before auto-flushing batch (like 5000)',\n )\n .optional(),\n validateOnly: z\n .boolean()\n .describe('If true, validate request without ingestion (testing mode)')\n .optional(),\n url: z\n .string()\n .url()\n .describe(\n 'Override API endpoint for testing (like https://datamanager.googleapis.com/v1)',\n )\n .optional(),\n consent: ConsentSchema.describe(\n 'Request-level consent for all events',\n ).optional(),\n testEventCode: z\n .string()\n .describe('Test event code for debugging (like TEST12345)')\n .optional(),\n logLevel: z\n .enum(['debug', 'info', 'warn', 'error', 'none'])\n .describe('Log level for debugging (debug shows all API calls)')\n .optional(),\n userData: z\n .record(z.string(), z.unknown())\n .describe(\n \"Guided helper: User data mapping for all events (like { email: 'user.id', phone: 'data.phone' })\",\n )\n .optional(),\n userId: z\n .any()\n .describe(\n \"Guided helper: First-party user ID for all events (like 'user.id')\",\n )\n .optional(),\n clientId: z\n .any()\n .describe(\n \"Guided helper: GA4 client ID for all events (like 'user.device')\",\n )\n .optional(),\n sessionAttributes: z\n .any()\n .describe(\n \"Guided helper: Privacy-safe attribution for all events (like 'context.sessionAttributes')\",\n )\n .optional(),\n consentAdUserData: z\n .union([z.string(), z.boolean()])\n .describe(\n \"Consent mapping: Field name from event.consent (like 'marketing') or static boolean value\",\n )\n .optional(),\n consentAdPersonalization: z\n .union([z.string(), z.boolean()])\n .describe(\n \"Consent mapping: Field name from event.consent (like 'targeting') or static boolean value\",\n )\n .optional(),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { z } from '@walkeros/core/dev';\n\n// Data Manager uses flexible mapping via walkerOS mapping system\n// No event-specific mapping schema needed (similar to Meta CAPI pattern)\nexport const MappingSchema = z.object({});\n\nexport type Mapping = z.infer<typeof MappingSchema>;\n","export * from './primitives';\nexport * from './settings';\nexport * from './mapping';\n\nimport { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\nimport { MappingSchema } from './mapping';\n\nexport const settings = zodToSchema(SettingsSchema);\nexport const mapping = zodToSchema(MappingSchema);\n","export * as env from './env';\nexport * as step from './step';\n","import type { OAuth2Client } from 'google-auth-library';\nimport type { Env } from '../types';\n\n/**\n * Example environment configurations for Google Data Manager destination.\n *\n * The destination invokes `env.fetch(url, { method, headers, body })` once\n * per push, with `Authorization: Bearer <accessToken>` obtained from\n * `env.authClient` via `getAccessToken`.\n *\n * For tests we use a stub `authClient` (jest mocks the `getAccessToken`\n * helper at module level) and a mock `fetch` returning a successful ingest\n * response.\n */\n\nasync function mockFetch(): Promise<Response> {\n return {\n ok: true,\n status: 200,\n json: async () => ({ requestId: 'mock-request-id', validationErrors: [] }),\n text: async () => '',\n } as unknown as Response;\n}\n\nconst mockAuthClient = {\n getAccessToken: async () => ({ token: 'ya29.c.test_token' }),\n} as unknown as OAuth2Client;\n\nexport const push: Env = {\n fetch: mockFetch,\n authClient: mockAuthClient,\n};\n\nexport const simulation = ['fetch'];\n","import type { Flow } from '@walkeros/core';\nimport { getEvent } from '@walkeros/core';\n\n/**\n * Google Data Manager step examples.\n *\n * At push time, the destination calls\n * `env.fetch(url, { method, headers, body })` where:\n * - `url` is `${settings.url}/events:ingest` (default\n * `https://datamanager.googleapis.com/v1`)\n * - `method` is `'POST'`\n * - `headers` is `{ Authorization: 'Bearer <accessToken>', 'Content-Type': 'application/json' }`\n * - `body` is the JSON-stringified `{ events: [dataManagerEvent], destinations }` payload\n *\n * The access token is obtained from `env.authClient` via `getAccessToken`.\n * The test fixture mocks both so the captured Authorization header is stable\n * (`Bearer ya29.c.test_token`).\n *\n * Event fields are emitted in the order the destination constructs them\n * (insertion order matters for `JSON.stringify` string equality):\n * eventTimestamp, transactionId?, clientId?, userId?, userData?,\n * adIdentifiers?, conversionValue?, currency?, cartData?, eventName?,\n * [eventSource is appended last by push.ts when not already present].\n *\n * Emails are normalized (trim, lowercase, strip dots for Gmail) and hashed\n * with SHA-256.\n */\nconst ENDPOINT = 'https://datamanager.googleapis.com/v1/events:ingest';\nconst DESTINATIONS = [\n {\n operatingAccount: {\n accountId: '123-456-7890',\n accountType: 'GOOGLE_ADS',\n },\n productDestinationId: 'AW-CONVERSION-123',\n },\n];\nconst INIT_OPTIONS = (body: string) => ({\n method: 'POST',\n headers: {\n Authorization: 'Bearer ya29.c.test_token',\n 'Content-Type': 'application/json',\n },\n body,\n});\n\nexport const purchase: Flow.StepExample = {\n title: 'Purchase',\n description:\n 'A completed order is posted to Google Data Manager as a purchase conversion with hashed user identifiers.',\n in: getEvent('order complete', {\n timestamp: 1700000900000,\n data: { id: 'ORD-600', total: 149.99, currency: 'EUR' },\n user: { id: 'user-abc', email: 'buyer@example.com' },\n source: { type: 'server', id: '', previous_id: '' },\n }),\n mapping: {\n name: 'purchase',\n data: {\n map: {\n transactionId: 'data.id',\n conversionValue: 'data.total',\n currency: { key: 'data.currency', value: 'USD' },\n eventName: { value: 'purchase' },\n userId: 'user.id',\n email: 'user.email',\n },\n },\n },\n out: [\n [\n 'fetch',\n ENDPOINT,\n INIT_OPTIONS(\n JSON.stringify({\n events: [\n {\n eventTimestamp: '2023-11-14T22:28:20.000Z',\n transactionId: 'ORD-600',\n userId: 'user-abc',\n userData: {\n userIdentifiers: [\n {\n // sha256('buyer@example.com')\n emailAddress:\n '6a6c26195c3682faa816966af789717c3bfa834eee6c599d667d2b3429c27cfd',\n },\n ],\n },\n conversionValue: 149.99,\n currency: 'EUR',\n eventName: 'purchase',\n eventSource: 'WEB',\n },\n ],\n destinations: DESTINATIONS,\n }),\n ),\n ],\n ],\n};\n\nexport const lead: Flow.StepExample = {\n title: 'Lead',\n description:\n 'A demo request form submission is sent to Data Manager as a generate_lead conversion with a hashed email.',\n in: getEvent('form submit', {\n timestamp: 1700000901000,\n data: { type: 'demo-request' },\n user: { email: 'prospect@example.com' },\n source: { type: 'server', id: '', previous_id: '' },\n }),\n mapping: {\n name: 'generate_lead',\n data: {\n map: {\n transactionId: 'id',\n eventName: { value: 'generate_lead' },\n conversionValue: { value: 10 },\n currency: { value: 'USD' },\n email: 'user.email',\n },\n },\n },\n out: [\n [\n 'fetch',\n ENDPOINT,\n INIT_OPTIONS(\n JSON.stringify({\n events: [\n {\n eventTimestamp: '2023-11-14T22:28:21.000Z',\n transactionId: '1700000901000-gr0up-1',\n userData: {\n userIdentifiers: [\n {\n // sha256('prospect@example.com')\n emailAddress:\n '395ec5f334be0ab5b28568a1e7f6ed5ea80e443fb1ce3d803340586a3df46642',\n },\n ],\n },\n conversionValue: 10,\n currency: 'USD',\n eventName: 'generate_lead',\n eventSource: 'WEB',\n },\n ],\n destinations: DESTINATIONS,\n }),\n ),\n ],\n ],\n};\n\nexport const ga4PageView: Flow.StepExample = {\n title: 'Page view',\n description:\n 'A page view is sent to Data Manager as a page_view event tied to the walker user id.',\n in: getEvent('page view', {\n timestamp: 1700000902000,\n data: { title: 'Pricing', url: 'https://example.com/pricing' },\n user: { id: 'visitor-55' },\n source: { type: 'server', id: '', previous_id: '' },\n }),\n mapping: {\n name: 'page_view',\n data: {\n map: {\n transactionId: 'id',\n eventName: { value: 'page_view' },\n userId: 'user.id',\n },\n },\n },\n out: [\n [\n 'fetch',\n ENDPOINT,\n INIT_OPTIONS(\n JSON.stringify({\n events: [\n {\n eventTimestamp: '2023-11-14T22:28:22.000Z',\n transactionId: '1700000902000-gr0up-1',\n userId: 'visitor-55',\n eventName: 'page_view',\n eventSource: 'WEB',\n },\n ],\n destinations: DESTINATIONS,\n }),\n ),\n ],\n ],\n};\n"],"mappings":"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","names":["import_dev","import_dev","import_dev"]}
|
|
1
|
+
{"version":3,"sources":["../src/dev.ts","../src/schemas.ts","../src/schemas/primitives.ts","../src/schemas/settings.ts","../src/schemas/mapping.ts","../src/schemas/index.ts","../src/examples/index.ts","../src/examples/env.ts","../src/examples/step.ts"],"sourcesContent":["export * as schemas from './schemas';\nexport * as examples from './examples';\n","// Browser-safe schema-only exports\n// This file exports ONLY schemas without any Node.js dependencies\nexport { settings, mapping } from './schemas/index';\n","import { z } from '@walkeros/core/dev';\n\nexport const AccountTypeSchema = z.enum([\n 'GOOGLE_ADS',\n 'DISPLAY_VIDEO_ADVERTISER',\n 'DISPLAY_VIDEO_PARTNER',\n 'GOOGLE_ANALYTICS_PROPERTY',\n]);\n\nexport const EventSourceSchema = z.enum([\n 'WEB',\n 'APP',\n 'IN_STORE',\n 'PHONE',\n 'OTHER',\n]);\n\nexport const ConsentStatusSchema = z.enum([\n 'CONSENT_GRANTED',\n 'CONSENT_DENIED',\n]);\n\nexport const ConsentSchema = z.object({\n adUserData: ConsentStatusSchema.describe(\n 'Consent for data collection and use',\n ).optional(),\n adPersonalization: ConsentStatusSchema.describe(\n 'Consent for ad personalization',\n ).optional(),\n});\n\nexport const OperatingAccountSchema = z.object({\n accountId: z\n .string()\n .min(1)\n .describe('Account ID (e.g., \"123-456-7890\" for Google Ads)'),\n accountType: AccountTypeSchema.describe('Type of account'),\n});\n\nexport const DestinationSchema = z.object({\n operatingAccount: OperatingAccountSchema.describe(\n 'Operating account details',\n ),\n productDestinationId: z\n .string()\n .min(1)\n .describe(\n 'Product-specific destination ID (conversion action or user list)',\n ),\n});\n","import { z } from '@walkeros/core/dev';\nimport {\n DestinationSchema,\n EventSourceSchema,\n ConsentSchema,\n} from './primitives';\n\n/**\n * Service account credentials schema\n */\nconst CredentialsSchema = z.object({\n client_email: z.string().email().describe('Service account email'),\n private_key: z\n .string()\n .min(1)\n .describe('Service account private key (PEM format)'),\n});\n\nexport const SettingsSchema = z.object({\n credentials: CredentialsSchema.optional().describe(\n 'Service account credentials (client_email + private_key). Recommended for serverless environments.',\n ),\n keyFilename: z\n .string()\n .optional()\n .describe(\n 'Path to service account JSON file. For local development or environments with filesystem access.',\n ),\n scopes: z\n .array(z.string())\n .optional()\n .describe(\n 'OAuth scopes for Data Manager API. Defaults to datamanager scope.',\n ),\n destinations: z\n .array(DestinationSchema)\n .min(1)\n .max(10)\n .describe(\n 'Array of destination accounts and conversion actions/user lists (max 10)',\n ),\n eventSource: EventSourceSchema.optional()\n .default('WEB')\n .describe(\n 'Event source for all events. Defaults to WEB. Values: WEB, APP, IN_STORE, PHONE, OTHER',\n ),\n batchSize: z\n .number()\n .int()\n .min(1)\n .max(2000)\n .describe(\n 'Maximum number of events to batch before sending (max 2000, like 100)',\n )\n .optional(),\n batchInterval: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Time in milliseconds to wait before auto-flushing batch (like 5000)',\n )\n .optional(),\n validateOnly: z\n .boolean()\n .describe('If true, validate request without ingestion (testing mode)')\n .optional(),\n url: z\n .string()\n .url()\n .describe(\n 'Override API endpoint for testing (like https://datamanager.googleapis.com/v1)',\n )\n .optional(),\n consent: ConsentSchema.describe(\n 'Request-level consent for all events',\n ).optional(),\n testEventCode: z\n .string()\n .describe('Test event code for debugging (like TEST12345)')\n .optional(),\n logLevel: z\n .enum(['debug', 'info', 'warn', 'error', 'none'])\n .describe('Log level for debugging (debug shows all API calls)')\n .optional(),\n userData: z\n .record(z.string(), z.unknown())\n .describe(\n \"Guided helper: User data mapping for all events (like { email: 'user.id', phone: 'data.phone' })\",\n )\n .optional(),\n userId: z\n .any()\n .describe(\n \"Guided helper: First-party user ID for all events (like 'user.id')\",\n )\n .optional(),\n clientId: z\n .any()\n .describe(\n \"Guided helper: GA4 client ID for all events (like 'user.device')\",\n )\n .optional(),\n sessionAttributes: z\n .any()\n .describe(\n \"Guided helper: Privacy-safe attribution for all events (like 'context.sessionAttributes')\",\n )\n .optional(),\n consentAdUserData: z\n .union([z.string(), z.boolean()])\n .describe(\n \"Consent mapping: Field name from event.consent (like 'marketing') or static boolean value\",\n )\n .optional(),\n consentAdPersonalization: z\n .union([z.string(), z.boolean()])\n .describe(\n \"Consent mapping: Field name from event.consent (like 'targeting') or static boolean value\",\n )\n .optional(),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { z } from '@walkeros/core/dev';\n\n// Data Manager uses flexible mapping via walkerOS mapping system\n// No event-specific mapping schema needed (similar to Meta CAPI pattern)\nexport const MappingSchema = z.object({});\n\nexport type Mapping = z.infer<typeof MappingSchema>;\n","export * from './primitives';\nexport * from './settings';\nexport * from './mapping';\n\nimport { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\nimport { MappingSchema } from './mapping';\n\nexport const settings = zodToSchema(SettingsSchema);\nexport const mapping = zodToSchema(MappingSchema);\n","export * as env from './env';\nexport * as step from './step';\n","import type { OAuth2Client } from 'google-auth-library';\nimport type { Env } from '../types';\n\n/**\n * Example environment configurations for Google Data Manager destination.\n *\n * The destination invokes `env.fetch(url, { method, headers, body })` once\n * per push, with `Authorization: Bearer <accessToken>` obtained from\n * `env.authClient` via `getAccessToken`.\n *\n * For tests we use a stub `authClient` (jest mocks the `getAccessToken`\n * helper at module level) and a mock `fetch` returning a successful ingest\n * response.\n */\n\nasync function mockFetch(): Promise<Response> {\n return {\n ok: true,\n status: 200,\n json: async () => ({ requestId: 'mock-request-id', validationErrors: [] }),\n text: async () => '',\n } as unknown as Response;\n}\n\nconst mockAuthClient = {\n getAccessToken: async () => ({ token: 'ya29.c.test_token' }),\n} as unknown as OAuth2Client;\n\nexport const push: Env = {\n fetch: mockFetch,\n authClient: mockAuthClient,\n};\n\nexport const simulation = ['fetch'];\n","import type { Flow } from '@walkeros/core';\nimport { getEvent } from '@walkeros/core';\n\n/**\n * Google Data Manager step examples.\n *\n * At push time, the destination calls\n * `env.fetch(url, { method, headers, body })` where:\n * - `url` is `${settings.url}/events:ingest` (default\n * `https://datamanager.googleapis.com/v1`)\n * - `method` is `'POST'`\n * - `headers` is `{ Authorization: 'Bearer <accessToken>', 'Content-Type': 'application/json' }`\n * - `body` is the JSON-stringified `{ events: [dataManagerEvent], destinations }` payload\n *\n * The access token is obtained from `env.authClient` via `getAccessToken`.\n * The test fixture mocks both so the captured Authorization header is stable\n * (`Bearer ya29.c.test_token`).\n *\n * Event fields are emitted in the order the destination constructs them\n * (insertion order matters for `JSON.stringify` string equality):\n * eventTimestamp, transactionId?, clientId?, userId?, userData?,\n * adIdentifiers?, conversionValue?, currency?, cartData?, eventName?,\n * [eventSource is appended last by push.ts when not already present].\n *\n * Emails are normalized (trim, lowercase, strip dots for Gmail) and hashed\n * with SHA-256.\n */\nconst ENDPOINT = 'https://datamanager.googleapis.com/v1/events:ingest';\nconst DESTINATIONS = [\n {\n operatingAccount: {\n accountId: '123-456-7890',\n accountType: 'GOOGLE_ADS',\n },\n productDestinationId: 'AW-CONVERSION-123',\n },\n];\nconst INIT_OPTIONS = (body: string) => ({\n method: 'POST',\n headers: {\n Authorization: 'Bearer ya29.c.test_token',\n 'Content-Type': 'application/json',\n },\n body,\n});\n\nexport const purchase: Flow.StepExample = {\n title: 'Purchase',\n description:\n 'A completed order is posted to Google Data Manager as a purchase conversion with hashed user identifiers.',\n in: getEvent('order complete', {\n id: 'ev-1700000900000',\n timestamp: 1700000900000,\n data: { id: 'ORD-600', total: 149.99, currency: 'EUR' },\n user: { id: 'user-abc', email: 'buyer@example.com' },\n source: { type: 'express', platform: 'server' },\n }),\n mapping: {\n name: 'purchase',\n data: {\n map: {\n transactionId: 'data.id',\n conversionValue: 'data.total',\n currency: { key: 'data.currency', value: 'USD' },\n eventName: { value: 'purchase' },\n userId: 'user.id',\n email: 'user.email',\n },\n },\n },\n out: [\n [\n 'fetch',\n ENDPOINT,\n INIT_OPTIONS(\n JSON.stringify({\n events: [\n {\n eventTimestamp: '2023-11-14T22:28:20.000Z',\n transactionId: 'ORD-600',\n userId: 'user-abc',\n userData: {\n userIdentifiers: [\n {\n // sha256('buyer@example.com')\n emailAddress:\n '6a6c26195c3682faa816966af789717c3bfa834eee6c599d667d2b3429c27cfd',\n },\n ],\n },\n conversionValue: 149.99,\n currency: 'EUR',\n eventName: 'purchase',\n eventSource: 'WEB',\n },\n ],\n destinations: DESTINATIONS,\n }),\n ),\n ],\n ],\n};\n\nexport const lead: Flow.StepExample = {\n title: 'Lead',\n description:\n 'A demo request form submission is sent to Data Manager as a generate_lead conversion with a hashed email.',\n in: getEvent('form submit', {\n id: 'ev-1700000901000',\n timestamp: 1700000901000,\n data: { type: 'demo-request' },\n user: { email: 'prospect@example.com' },\n source: { type: 'express', platform: 'server' },\n }),\n mapping: {\n name: 'generate_lead',\n data: {\n map: {\n transactionId: 'id',\n eventName: { value: 'generate_lead' },\n conversionValue: { value: 10 },\n currency: { value: 'USD' },\n email: 'user.email',\n },\n },\n },\n out: [\n [\n 'fetch',\n ENDPOINT,\n INIT_OPTIONS(\n JSON.stringify({\n events: [\n {\n eventTimestamp: '2023-11-14T22:28:21.000Z',\n transactionId: 'ev-1700000901000',\n userData: {\n userIdentifiers: [\n {\n // sha256('prospect@example.com')\n emailAddress:\n '395ec5f334be0ab5b28568a1e7f6ed5ea80e443fb1ce3d803340586a3df46642',\n },\n ],\n },\n conversionValue: 10,\n currency: 'USD',\n eventName: 'generate_lead',\n eventSource: 'WEB',\n },\n ],\n destinations: DESTINATIONS,\n }),\n ),\n ],\n ],\n};\n\nexport const ga4PageView: Flow.StepExample = {\n title: 'Page view',\n description:\n 'A page view is sent to Data Manager as a page_view event tied to the walker user id.',\n in: getEvent('page view', {\n id: 'ev-1700000902000',\n timestamp: 1700000902000,\n data: { title: 'Pricing', url: 'https://example.com/pricing' },\n user: { id: 'visitor-55' },\n source: { type: 'express', platform: 'server' },\n }),\n mapping: {\n name: 'page_view',\n data: {\n map: {\n transactionId: 'id',\n eventName: { value: 'page_view' },\n userId: 'user.id',\n },\n },\n },\n out: [\n [\n 'fetch',\n ENDPOINT,\n INIT_OPTIONS(\n JSON.stringify({\n events: [\n {\n eventTimestamp: '2023-11-14T22:28:22.000Z',\n transactionId: 'ev-1700000902000',\n userId: 'visitor-55',\n eventName: 'page_view',\n eventSource: 'WEB',\n },\n ],\n destinations: DESTINATIONS,\n }),\n ),\n ],\n ],\n};\n"],"mappings":"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","names":["import_dev","import_dev","import_dev"]}
|
package/dist/dev.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=Object.defineProperty,t=(t,a)=>{for(var i in a)e(t,i,{get:a[i],enumerable:!0})},a={};t(a,{mapping:()=>f,settings:()=>b});import{z as i}from"@walkeros/core/dev";var n=i.enum(["GOOGLE_ADS","DISPLAY_VIDEO_ADVERTISER","DISPLAY_VIDEO_PARTNER","GOOGLE_ANALYTICS_PROPERTY"]),r=i.enum(["WEB","APP","IN_STORE","PHONE","OTHER"]),o=i.enum(["CONSENT_GRANTED","CONSENT_DENIED"]),s=i.object({adUserData:o.describe("Consent for data collection and use").optional(),adPersonalization:o.describe("Consent for ad personalization").optional()}),c=i.object({accountId:i.string().min(1).describe('Account ID (e.g., "123-456-7890" for Google Ads)'),accountType:n.describe("Type of account")}),d=i.object({operatingAccount:c.describe("Operating account details"),productDestinationId:i.string().min(1).describe("Product-specific destination ID (conversion action or user list)")});import{z as l}from"@walkeros/core/dev";var u=l.object({client_email:l.string().email().describe("Service account email"),private_key:l.string().min(1).describe("Service account private key (PEM format)")}),
|
|
1
|
+
var e=Object.defineProperty,t=(t,a)=>{for(var i in a)e(t,i,{get:a[i],enumerable:!0})},a={};t(a,{mapping:()=>f,settings:()=>b});import{z as i}from"@walkeros/core/dev";var n=i.enum(["GOOGLE_ADS","DISPLAY_VIDEO_ADVERTISER","DISPLAY_VIDEO_PARTNER","GOOGLE_ANALYTICS_PROPERTY"]),r=i.enum(["WEB","APP","IN_STORE","PHONE","OTHER"]),o=i.enum(["CONSENT_GRANTED","CONSENT_DENIED"]),s=i.object({adUserData:o.describe("Consent for data collection and use").optional(),adPersonalization:o.describe("Consent for ad personalization").optional()}),c=i.object({accountId:i.string().min(1).describe('Account ID (e.g., "123-456-7890" for Google Ads)'),accountType:n.describe("Type of account")}),d=i.object({operatingAccount:c.describe("Operating account details"),productDestinationId:i.string().min(1).describe("Product-specific destination ID (conversion action or user list)")});import{z as l}from"@walkeros/core/dev";var u=l.object({client_email:l.string().email().describe("Service account email"),private_key:l.string().min(1).describe("Service account private key (PEM format)")}),m=l.object({credentials:u.optional().describe("Service account credentials (client_email + private_key). Recommended for serverless environments."),keyFilename:l.string().optional().describe("Path to service account JSON file. For local development or environments with filesystem access."),scopes:l.array(l.string()).optional().describe("OAuth scopes for Data Manager API. Defaults to datamanager scope."),destinations:l.array(d).min(1).max(10).describe("Array of destination accounts and conversion actions/user lists (max 10)"),eventSource:r.optional().default("WEB").describe("Event source for all events. Defaults to WEB. Values: WEB, APP, IN_STORE, PHONE, OTHER"),batchSize:l.number().int().min(1).max(2e3).describe("Maximum number of events to batch before sending (max 2000, like 100)").optional(),batchInterval:l.number().int().min(0).describe("Time in milliseconds to wait before auto-flushing batch (like 5000)").optional(),validateOnly:l.boolean().describe("If true, validate request without ingestion (testing mode)").optional(),url:l.string().url().describe("Override API endpoint for testing (like https://datamanager.googleapis.com/v1)").optional(),consent:s.describe("Request-level consent for all events").optional(),testEventCode:l.string().describe("Test event code for debugging (like TEST12345)").optional(),logLevel:l.enum(["debug","info","warn","error","none"]).describe("Log level for debugging (debug shows all API calls)").optional(),userData:l.record(l.string(),l.unknown()).describe("Guided helper: User data mapping for all events (like { email: 'user.id', phone: 'data.phone' })").optional(),userId:l.any().describe("Guided helper: First-party user ID for all events (like 'user.id')").optional(),clientId:l.any().describe("Guided helper: GA4 client ID for all events (like 'user.device')").optional(),sessionAttributes:l.any().describe("Guided helper: Privacy-safe attribution for all events (like 'context.sessionAttributes')").optional(),consentAdUserData:l.union([l.string(),l.boolean()]).describe("Consent mapping: Field name from event.consent (like 'marketing') or static boolean value").optional(),consentAdPersonalization:l.union([l.string(),l.boolean()]).describe("Consent mapping: Field name from event.consent (like 'targeting') or static boolean value").optional()});import{z as p}from"@walkeros/core/dev";var v=p.object({});import{zodToSchema as g}from"@walkeros/core/dev";var b=g(m),f=g(v),h={};t(h,{env:()=>y,step:()=>A});var y={};t(y,{push:()=>I,simulation:()=>E});var I={fetch:async function(){return{ok:!0,status:200,json:async()=>({requestId:"mock-request-id",validationErrors:[]}),text:async()=>""}},authClient:{getAccessToken:async()=>({token:"ya29.c.test_token"})}},E=["fetch"],A={};t(A,{ga4PageView:()=>_,lead:()=>P,purchase:()=>T});import{getEvent as D}from"@walkeros/core";var O="https://datamanager.googleapis.com/v1/events:ingest",k=[{operatingAccount:{accountId:"123-456-7890",accountType:"GOOGLE_ADS"},productDestinationId:"AW-CONVERSION-123"}],S=e=>({method:"POST",headers:{Authorization:"Bearer ya29.c.test_token","Content-Type":"application/json"},body:e}),T={title:"Purchase",description:"A completed order is posted to Google Data Manager as a purchase conversion with hashed user identifiers.",in:D("order complete",{id:"ev-1700000900000",timestamp:17000009e5,data:{id:"ORD-600",total:149.99,currency:"EUR"},user:{id:"user-abc",email:"buyer@example.com"},source:{type:"express",platform:"server"}}),mapping:{name:"purchase",data:{map:{transactionId:"data.id",conversionValue:"data.total",currency:{key:"data.currency",value:"USD"},eventName:{value:"purchase"},userId:"user.id",email:"user.email"}}},out:[["fetch",O,S(JSON.stringify({events:[{eventTimestamp:"2023-11-14T22:28:20.000Z",transactionId:"ORD-600",userId:"user-abc",userData:{userIdentifiers:[{emailAddress:"6a6c26195c3682faa816966af789717c3bfa834eee6c599d667d2b3429c27cfd"}]},conversionValue:149.99,currency:"EUR",eventName:"purchase",eventSource:"WEB"}],destinations:k}))]]},P={title:"Lead",description:"A demo request form submission is sent to Data Manager as a generate_lead conversion with a hashed email.",in:D("form submit",{id:"ev-1700000901000",timestamp:1700000901e3,data:{type:"demo-request"},user:{email:"prospect@example.com"},source:{type:"express",platform:"server"}}),mapping:{name:"generate_lead",data:{map:{transactionId:"id",eventName:{value:"generate_lead"},conversionValue:{value:10},currency:{value:"USD"},email:"user.email"}}},out:[["fetch",O,S(JSON.stringify({events:[{eventTimestamp:"2023-11-14T22:28:21.000Z",transactionId:"ev-1700000901000",userData:{userIdentifiers:[{emailAddress:"395ec5f334be0ab5b28568a1e7f6ed5ea80e443fb1ce3d803340586a3df46642"}]},conversionValue:10,currency:"USD",eventName:"generate_lead",eventSource:"WEB"}],destinations:k}))]]},_={title:"Page view",description:"A page view is sent to Data Manager as a page_view event tied to the walker user id.",in:D("page view",{id:"ev-1700000902000",timestamp:1700000902e3,data:{title:"Pricing",url:"https://example.com/pricing"},user:{id:"visitor-55"},source:{type:"express",platform:"server"}}),mapping:{name:"page_view",data:{map:{transactionId:"id",eventName:{value:"page_view"},userId:"user.id"}}},out:[["fetch",O,S(JSON.stringify({events:[{eventTimestamp:"2023-11-14T22:28:22.000Z",transactionId:"ev-1700000902000",userId:"visitor-55",eventName:"page_view",eventSource:"WEB"}],destinations:k}))]]};export{h as examples,a as schemas};//# sourceMappingURL=dev.mjs.map
|
package/dist/dev.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/schemas.ts","../src/schemas/primitives.ts","../src/schemas/settings.ts","../src/schemas/mapping.ts","../src/schemas/index.ts","../src/examples/index.ts","../src/examples/env.ts","../src/examples/step.ts"],"sourcesContent":["// Browser-safe schema-only exports\n// This file exports ONLY schemas without any Node.js dependencies\nexport { settings, mapping } from './schemas/index';\n","import { z } from '@walkeros/core/dev';\n\nexport const AccountTypeSchema = z.enum([\n 'GOOGLE_ADS',\n 'DISPLAY_VIDEO_ADVERTISER',\n 'DISPLAY_VIDEO_PARTNER',\n 'GOOGLE_ANALYTICS_PROPERTY',\n]);\n\nexport const EventSourceSchema = z.enum([\n 'WEB',\n 'APP',\n 'IN_STORE',\n 'PHONE',\n 'OTHER',\n]);\n\nexport const ConsentStatusSchema = z.enum([\n 'CONSENT_GRANTED',\n 'CONSENT_DENIED',\n]);\n\nexport const ConsentSchema = z.object({\n adUserData: ConsentStatusSchema.describe(\n 'Consent for data collection and use',\n ).optional(),\n adPersonalization: ConsentStatusSchema.describe(\n 'Consent for ad personalization',\n ).optional(),\n});\n\nexport const OperatingAccountSchema = z.object({\n accountId: z\n .string()\n .min(1)\n .describe('Account ID (e.g., \"123-456-7890\" for Google Ads)'),\n accountType: AccountTypeSchema.describe('Type of account'),\n});\n\nexport const DestinationSchema = z.object({\n operatingAccount: OperatingAccountSchema.describe(\n 'Operating account details',\n ),\n productDestinationId: z\n .string()\n .min(1)\n .describe(\n 'Product-specific destination ID (conversion action or user list)',\n ),\n});\n","import { z } from '@walkeros/core/dev';\nimport {\n DestinationSchema,\n EventSourceSchema,\n ConsentSchema,\n} from './primitives';\n\n/**\n * Service account credentials schema\n */\nconst CredentialsSchema = z.object({\n client_email: z.string().email().describe('Service account email'),\n private_key: z\n .string()\n .min(1)\n .describe('Service account private key (PEM format)'),\n});\n\nexport const SettingsSchema = z.object({\n credentials: CredentialsSchema.optional().describe(\n 'Service account credentials (client_email + private_key). Recommended for serverless environments.',\n ),\n keyFilename: z\n .string()\n .optional()\n .describe(\n 'Path to service account JSON file. For local development or environments with filesystem access.',\n ),\n scopes: z\n .array(z.string())\n .optional()\n .describe(\n 'OAuth scopes for Data Manager API. Defaults to datamanager scope.',\n ),\n destinations: z\n .array(DestinationSchema)\n .min(1)\n .max(10)\n .describe(\n 'Array of destination accounts and conversion actions/user lists (max 10)',\n ),\n eventSource: EventSourceSchema.optional()\n .default('WEB')\n .describe(\n 'Event source for all events. Defaults to WEB. Values: WEB, APP, IN_STORE, PHONE, OTHER',\n ),\n batchSize: z\n .number()\n .int()\n .min(1)\n .max(2000)\n .describe(\n 'Maximum number of events to batch before sending (max 2000, like 100)',\n )\n .optional(),\n batchInterval: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Time in milliseconds to wait before auto-flushing batch (like 5000)',\n )\n .optional(),\n validateOnly: z\n .boolean()\n .describe('If true, validate request without ingestion (testing mode)')\n .optional(),\n url: z\n .string()\n .url()\n .describe(\n 'Override API endpoint for testing (like https://datamanager.googleapis.com/v1)',\n )\n .optional(),\n consent: ConsentSchema.describe(\n 'Request-level consent for all events',\n ).optional(),\n testEventCode: z\n .string()\n .describe('Test event code for debugging (like TEST12345)')\n .optional(),\n logLevel: z\n .enum(['debug', 'info', 'warn', 'error', 'none'])\n .describe('Log level for debugging (debug shows all API calls)')\n .optional(),\n userData: z\n .record(z.string(), z.unknown())\n .describe(\n \"Guided helper: User data mapping for all events (like { email: 'user.id', phone: 'data.phone' })\",\n )\n .optional(),\n userId: z\n .any()\n .describe(\n \"Guided helper: First-party user ID for all events (like 'user.id')\",\n )\n .optional(),\n clientId: z\n .any()\n .describe(\n \"Guided helper: GA4 client ID for all events (like 'user.device')\",\n )\n .optional(),\n sessionAttributes: z\n .any()\n .describe(\n \"Guided helper: Privacy-safe attribution for all events (like 'context.sessionAttributes')\",\n )\n .optional(),\n consentAdUserData: z\n .union([z.string(), z.boolean()])\n .describe(\n \"Consent mapping: Field name from event.consent (like 'marketing') or static boolean value\",\n )\n .optional(),\n consentAdPersonalization: z\n .union([z.string(), z.boolean()])\n .describe(\n \"Consent mapping: Field name from event.consent (like 'targeting') or static boolean value\",\n )\n .optional(),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { z } from '@walkeros/core/dev';\n\n// Data Manager uses flexible mapping via walkerOS mapping system\n// No event-specific mapping schema needed (similar to Meta CAPI pattern)\nexport const MappingSchema = z.object({});\n\nexport type Mapping = z.infer<typeof MappingSchema>;\n","export * from './primitives';\nexport * from './settings';\nexport * from './mapping';\n\nimport { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\nimport { MappingSchema } from './mapping';\n\nexport const settings = zodToSchema(SettingsSchema);\nexport const mapping = zodToSchema(MappingSchema);\n","export * as env from './env';\nexport * as step from './step';\n","import type { OAuth2Client } from 'google-auth-library';\nimport type { Env } from '../types';\n\n/**\n * Example environment configurations for Google Data Manager destination.\n *\n * The destination invokes `env.fetch(url, { method, headers, body })` once\n * per push, with `Authorization: Bearer <accessToken>` obtained from\n * `env.authClient` via `getAccessToken`.\n *\n * For tests we use a stub `authClient` (jest mocks the `getAccessToken`\n * helper at module level) and a mock `fetch` returning a successful ingest\n * response.\n */\n\nasync function mockFetch(): Promise<Response> {\n return {\n ok: true,\n status: 200,\n json: async () => ({ requestId: 'mock-request-id', validationErrors: [] }),\n text: async () => '',\n } as unknown as Response;\n}\n\nconst mockAuthClient = {\n getAccessToken: async () => ({ token: 'ya29.c.test_token' }),\n} as unknown as OAuth2Client;\n\nexport const push: Env = {\n fetch: mockFetch,\n authClient: mockAuthClient,\n};\n\nexport const simulation = ['fetch'];\n","import type { Flow } from '@walkeros/core';\nimport { getEvent } from '@walkeros/core';\n\n/**\n * Google Data Manager step examples.\n *\n * At push time, the destination calls\n * `env.fetch(url, { method, headers, body })` where:\n * - `url` is `${settings.url}/events:ingest` (default\n * `https://datamanager.googleapis.com/v1`)\n * - `method` is `'POST'`\n * - `headers` is `{ Authorization: 'Bearer <accessToken>', 'Content-Type': 'application/json' }`\n * - `body` is the JSON-stringified `{ events: [dataManagerEvent], destinations }` payload\n *\n * The access token is obtained from `env.authClient` via `getAccessToken`.\n * The test fixture mocks both so the captured Authorization header is stable\n * (`Bearer ya29.c.test_token`).\n *\n * Event fields are emitted in the order the destination constructs them\n * (insertion order matters for `JSON.stringify` string equality):\n * eventTimestamp, transactionId?, clientId?, userId?, userData?,\n * adIdentifiers?, conversionValue?, currency?, cartData?, eventName?,\n * [eventSource is appended last by push.ts when not already present].\n *\n * Emails are normalized (trim, lowercase, strip dots for Gmail) and hashed\n * with SHA-256.\n */\nconst ENDPOINT = 'https://datamanager.googleapis.com/v1/events:ingest';\nconst DESTINATIONS = [\n {\n operatingAccount: {\n accountId: '123-456-7890',\n accountType: 'GOOGLE_ADS',\n },\n productDestinationId: 'AW-CONVERSION-123',\n },\n];\nconst INIT_OPTIONS = (body: string) => ({\n method: 'POST',\n headers: {\n Authorization: 'Bearer ya29.c.test_token',\n 'Content-Type': 'application/json',\n },\n body,\n});\n\nexport const purchase: Flow.StepExample = {\n title: 'Purchase',\n description:\n 'A completed order is posted to Google Data Manager as a purchase conversion with hashed user identifiers.',\n in: getEvent('order complete', {\n timestamp: 1700000900000,\n data: { id: 'ORD-600', total: 149.99, currency: 'EUR' },\n user: { id: 'user-abc', email: 'buyer@example.com' },\n source: { type: 'server', id: '', previous_id: '' },\n }),\n mapping: {\n name: 'purchase',\n data: {\n map: {\n transactionId: 'data.id',\n conversionValue: 'data.total',\n currency: { key: 'data.currency', value: 'USD' },\n eventName: { value: 'purchase' },\n userId: 'user.id',\n email: 'user.email',\n },\n },\n },\n out: [\n [\n 'fetch',\n ENDPOINT,\n INIT_OPTIONS(\n JSON.stringify({\n events: [\n {\n eventTimestamp: '2023-11-14T22:28:20.000Z',\n transactionId: 'ORD-600',\n userId: 'user-abc',\n userData: {\n userIdentifiers: [\n {\n // sha256('buyer@example.com')\n emailAddress:\n '6a6c26195c3682faa816966af789717c3bfa834eee6c599d667d2b3429c27cfd',\n },\n ],\n },\n conversionValue: 149.99,\n currency: 'EUR',\n eventName: 'purchase',\n eventSource: 'WEB',\n },\n ],\n destinations: DESTINATIONS,\n }),\n ),\n ],\n ],\n};\n\nexport const lead: Flow.StepExample = {\n title: 'Lead',\n description:\n 'A demo request form submission is sent to Data Manager as a generate_lead conversion with a hashed email.',\n in: getEvent('form submit', {\n timestamp: 1700000901000,\n data: { type: 'demo-request' },\n user: { email: 'prospect@example.com' },\n source: { type: 'server', id: '', previous_id: '' },\n }),\n mapping: {\n name: 'generate_lead',\n data: {\n map: {\n transactionId: 'id',\n eventName: { value: 'generate_lead' },\n conversionValue: { value: 10 },\n currency: { value: 'USD' },\n email: 'user.email',\n },\n },\n },\n out: [\n [\n 'fetch',\n ENDPOINT,\n INIT_OPTIONS(\n JSON.stringify({\n events: [\n {\n eventTimestamp: '2023-11-14T22:28:21.000Z',\n transactionId: '1700000901000-gr0up-1',\n userData: {\n userIdentifiers: [\n {\n // sha256('prospect@example.com')\n emailAddress:\n '395ec5f334be0ab5b28568a1e7f6ed5ea80e443fb1ce3d803340586a3df46642',\n },\n ],\n },\n conversionValue: 10,\n currency: 'USD',\n eventName: 'generate_lead',\n eventSource: 'WEB',\n },\n ],\n destinations: DESTINATIONS,\n }),\n ),\n ],\n ],\n};\n\nexport const ga4PageView: Flow.StepExample = {\n title: 'Page view',\n description:\n 'A page view is sent to Data Manager as a page_view event tied to the walker user id.',\n in: getEvent('page view', {\n timestamp: 1700000902000,\n data: { title: 'Pricing', url: 'https://example.com/pricing' },\n user: { id: 'visitor-55' },\n source: { type: 'server', id: '', previous_id: '' },\n }),\n mapping: {\n name: 'page_view',\n data: {\n map: {\n transactionId: 'id',\n eventName: { value: 'page_view' },\n userId: 'user.id',\n },\n },\n },\n out: [\n [\n 'fetch',\n ENDPOINT,\n INIT_OPTIONS(\n JSON.stringify({\n events: [\n {\n eventTimestamp: '2023-11-14T22:28:22.000Z',\n transactionId: '1700000902000-gr0up-1',\n userId: 'visitor-55',\n eventName: 'page_view',\n eventSource: 'WEB',\n },\n ],\n destinations: DESTINATIONS,\n }),\n ),\n ],\n ],\n};\n"],"mappings":"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","names":["z","z","z"]}
|
|
1
|
+
{"version":3,"sources":["../src/schemas.ts","../src/schemas/primitives.ts","../src/schemas/settings.ts","../src/schemas/mapping.ts","../src/schemas/index.ts","../src/examples/index.ts","../src/examples/env.ts","../src/examples/step.ts"],"sourcesContent":["// Browser-safe schema-only exports\n// This file exports ONLY schemas without any Node.js dependencies\nexport { settings, mapping } from './schemas/index';\n","import { z } from '@walkeros/core/dev';\n\nexport const AccountTypeSchema = z.enum([\n 'GOOGLE_ADS',\n 'DISPLAY_VIDEO_ADVERTISER',\n 'DISPLAY_VIDEO_PARTNER',\n 'GOOGLE_ANALYTICS_PROPERTY',\n]);\n\nexport const EventSourceSchema = z.enum([\n 'WEB',\n 'APP',\n 'IN_STORE',\n 'PHONE',\n 'OTHER',\n]);\n\nexport const ConsentStatusSchema = z.enum([\n 'CONSENT_GRANTED',\n 'CONSENT_DENIED',\n]);\n\nexport const ConsentSchema = z.object({\n adUserData: ConsentStatusSchema.describe(\n 'Consent for data collection and use',\n ).optional(),\n adPersonalization: ConsentStatusSchema.describe(\n 'Consent for ad personalization',\n ).optional(),\n});\n\nexport const OperatingAccountSchema = z.object({\n accountId: z\n .string()\n .min(1)\n .describe('Account ID (e.g., \"123-456-7890\" for Google Ads)'),\n accountType: AccountTypeSchema.describe('Type of account'),\n});\n\nexport const DestinationSchema = z.object({\n operatingAccount: OperatingAccountSchema.describe(\n 'Operating account details',\n ),\n productDestinationId: z\n .string()\n .min(1)\n .describe(\n 'Product-specific destination ID (conversion action or user list)',\n ),\n});\n","import { z } from '@walkeros/core/dev';\nimport {\n DestinationSchema,\n EventSourceSchema,\n ConsentSchema,\n} from './primitives';\n\n/**\n * Service account credentials schema\n */\nconst CredentialsSchema = z.object({\n client_email: z.string().email().describe('Service account email'),\n private_key: z\n .string()\n .min(1)\n .describe('Service account private key (PEM format)'),\n});\n\nexport const SettingsSchema = z.object({\n credentials: CredentialsSchema.optional().describe(\n 'Service account credentials (client_email + private_key). Recommended for serverless environments.',\n ),\n keyFilename: z\n .string()\n .optional()\n .describe(\n 'Path to service account JSON file. For local development or environments with filesystem access.',\n ),\n scopes: z\n .array(z.string())\n .optional()\n .describe(\n 'OAuth scopes for Data Manager API. Defaults to datamanager scope.',\n ),\n destinations: z\n .array(DestinationSchema)\n .min(1)\n .max(10)\n .describe(\n 'Array of destination accounts and conversion actions/user lists (max 10)',\n ),\n eventSource: EventSourceSchema.optional()\n .default('WEB')\n .describe(\n 'Event source for all events. Defaults to WEB. Values: WEB, APP, IN_STORE, PHONE, OTHER',\n ),\n batchSize: z\n .number()\n .int()\n .min(1)\n .max(2000)\n .describe(\n 'Maximum number of events to batch before sending (max 2000, like 100)',\n )\n .optional(),\n batchInterval: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Time in milliseconds to wait before auto-flushing batch (like 5000)',\n )\n .optional(),\n validateOnly: z\n .boolean()\n .describe('If true, validate request without ingestion (testing mode)')\n .optional(),\n url: z\n .string()\n .url()\n .describe(\n 'Override API endpoint for testing (like https://datamanager.googleapis.com/v1)',\n )\n .optional(),\n consent: ConsentSchema.describe(\n 'Request-level consent for all events',\n ).optional(),\n testEventCode: z\n .string()\n .describe('Test event code for debugging (like TEST12345)')\n .optional(),\n logLevel: z\n .enum(['debug', 'info', 'warn', 'error', 'none'])\n .describe('Log level for debugging (debug shows all API calls)')\n .optional(),\n userData: z\n .record(z.string(), z.unknown())\n .describe(\n \"Guided helper: User data mapping for all events (like { email: 'user.id', phone: 'data.phone' })\",\n )\n .optional(),\n userId: z\n .any()\n .describe(\n \"Guided helper: First-party user ID for all events (like 'user.id')\",\n )\n .optional(),\n clientId: z\n .any()\n .describe(\n \"Guided helper: GA4 client ID for all events (like 'user.device')\",\n )\n .optional(),\n sessionAttributes: z\n .any()\n .describe(\n \"Guided helper: Privacy-safe attribution for all events (like 'context.sessionAttributes')\",\n )\n .optional(),\n consentAdUserData: z\n .union([z.string(), z.boolean()])\n .describe(\n \"Consent mapping: Field name from event.consent (like 'marketing') or static boolean value\",\n )\n .optional(),\n consentAdPersonalization: z\n .union([z.string(), z.boolean()])\n .describe(\n \"Consent mapping: Field name from event.consent (like 'targeting') or static boolean value\",\n )\n .optional(),\n});\n\nexport type Settings = z.infer<typeof SettingsSchema>;\n","import { z } from '@walkeros/core/dev';\n\n// Data Manager uses flexible mapping via walkerOS mapping system\n// No event-specific mapping schema needed (similar to Meta CAPI pattern)\nexport const MappingSchema = z.object({});\n\nexport type Mapping = z.infer<typeof MappingSchema>;\n","export * from './primitives';\nexport * from './settings';\nexport * from './mapping';\n\nimport { zodToSchema } from '@walkeros/core/dev';\nimport { SettingsSchema } from './settings';\nimport { MappingSchema } from './mapping';\n\nexport const settings = zodToSchema(SettingsSchema);\nexport const mapping = zodToSchema(MappingSchema);\n","export * as env from './env';\nexport * as step from './step';\n","import type { OAuth2Client } from 'google-auth-library';\nimport type { Env } from '../types';\n\n/**\n * Example environment configurations for Google Data Manager destination.\n *\n * The destination invokes `env.fetch(url, { method, headers, body })` once\n * per push, with `Authorization: Bearer <accessToken>` obtained from\n * `env.authClient` via `getAccessToken`.\n *\n * For tests we use a stub `authClient` (jest mocks the `getAccessToken`\n * helper at module level) and a mock `fetch` returning a successful ingest\n * response.\n */\n\nasync function mockFetch(): Promise<Response> {\n return {\n ok: true,\n status: 200,\n json: async () => ({ requestId: 'mock-request-id', validationErrors: [] }),\n text: async () => '',\n } as unknown as Response;\n}\n\nconst mockAuthClient = {\n getAccessToken: async () => ({ token: 'ya29.c.test_token' }),\n} as unknown as OAuth2Client;\n\nexport const push: Env = {\n fetch: mockFetch,\n authClient: mockAuthClient,\n};\n\nexport const simulation = ['fetch'];\n","import type { Flow } from '@walkeros/core';\nimport { getEvent } from '@walkeros/core';\n\n/**\n * Google Data Manager step examples.\n *\n * At push time, the destination calls\n * `env.fetch(url, { method, headers, body })` where:\n * - `url` is `${settings.url}/events:ingest` (default\n * `https://datamanager.googleapis.com/v1`)\n * - `method` is `'POST'`\n * - `headers` is `{ Authorization: 'Bearer <accessToken>', 'Content-Type': 'application/json' }`\n * - `body` is the JSON-stringified `{ events: [dataManagerEvent], destinations }` payload\n *\n * The access token is obtained from `env.authClient` via `getAccessToken`.\n * The test fixture mocks both so the captured Authorization header is stable\n * (`Bearer ya29.c.test_token`).\n *\n * Event fields are emitted in the order the destination constructs them\n * (insertion order matters for `JSON.stringify` string equality):\n * eventTimestamp, transactionId?, clientId?, userId?, userData?,\n * adIdentifiers?, conversionValue?, currency?, cartData?, eventName?,\n * [eventSource is appended last by push.ts when not already present].\n *\n * Emails are normalized (trim, lowercase, strip dots for Gmail) and hashed\n * with SHA-256.\n */\nconst ENDPOINT = 'https://datamanager.googleapis.com/v1/events:ingest';\nconst DESTINATIONS = [\n {\n operatingAccount: {\n accountId: '123-456-7890',\n accountType: 'GOOGLE_ADS',\n },\n productDestinationId: 'AW-CONVERSION-123',\n },\n];\nconst INIT_OPTIONS = (body: string) => ({\n method: 'POST',\n headers: {\n Authorization: 'Bearer ya29.c.test_token',\n 'Content-Type': 'application/json',\n },\n body,\n});\n\nexport const purchase: Flow.StepExample = {\n title: 'Purchase',\n description:\n 'A completed order is posted to Google Data Manager as a purchase conversion with hashed user identifiers.',\n in: getEvent('order complete', {\n id: 'ev-1700000900000',\n timestamp: 1700000900000,\n data: { id: 'ORD-600', total: 149.99, currency: 'EUR' },\n user: { id: 'user-abc', email: 'buyer@example.com' },\n source: { type: 'express', platform: 'server' },\n }),\n mapping: {\n name: 'purchase',\n data: {\n map: {\n transactionId: 'data.id',\n conversionValue: 'data.total',\n currency: { key: 'data.currency', value: 'USD' },\n eventName: { value: 'purchase' },\n userId: 'user.id',\n email: 'user.email',\n },\n },\n },\n out: [\n [\n 'fetch',\n ENDPOINT,\n INIT_OPTIONS(\n JSON.stringify({\n events: [\n {\n eventTimestamp: '2023-11-14T22:28:20.000Z',\n transactionId: 'ORD-600',\n userId: 'user-abc',\n userData: {\n userIdentifiers: [\n {\n // sha256('buyer@example.com')\n emailAddress:\n '6a6c26195c3682faa816966af789717c3bfa834eee6c599d667d2b3429c27cfd',\n },\n ],\n },\n conversionValue: 149.99,\n currency: 'EUR',\n eventName: 'purchase',\n eventSource: 'WEB',\n },\n ],\n destinations: DESTINATIONS,\n }),\n ),\n ],\n ],\n};\n\nexport const lead: Flow.StepExample = {\n title: 'Lead',\n description:\n 'A demo request form submission is sent to Data Manager as a generate_lead conversion with a hashed email.',\n in: getEvent('form submit', {\n id: 'ev-1700000901000',\n timestamp: 1700000901000,\n data: { type: 'demo-request' },\n user: { email: 'prospect@example.com' },\n source: { type: 'express', platform: 'server' },\n }),\n mapping: {\n name: 'generate_lead',\n data: {\n map: {\n transactionId: 'id',\n eventName: { value: 'generate_lead' },\n conversionValue: { value: 10 },\n currency: { value: 'USD' },\n email: 'user.email',\n },\n },\n },\n out: [\n [\n 'fetch',\n ENDPOINT,\n INIT_OPTIONS(\n JSON.stringify({\n events: [\n {\n eventTimestamp: '2023-11-14T22:28:21.000Z',\n transactionId: 'ev-1700000901000',\n userData: {\n userIdentifiers: [\n {\n // sha256('prospect@example.com')\n emailAddress:\n '395ec5f334be0ab5b28568a1e7f6ed5ea80e443fb1ce3d803340586a3df46642',\n },\n ],\n },\n conversionValue: 10,\n currency: 'USD',\n eventName: 'generate_lead',\n eventSource: 'WEB',\n },\n ],\n destinations: DESTINATIONS,\n }),\n ),\n ],\n ],\n};\n\nexport const ga4PageView: Flow.StepExample = {\n title: 'Page view',\n description:\n 'A page view is sent to Data Manager as a page_view event tied to the walker user id.',\n in: getEvent('page view', {\n id: 'ev-1700000902000',\n timestamp: 1700000902000,\n data: { title: 'Pricing', url: 'https://example.com/pricing' },\n user: { id: 'visitor-55' },\n source: { type: 'express', platform: 'server' },\n }),\n mapping: {\n name: 'page_view',\n data: {\n map: {\n transactionId: 'id',\n eventName: { value: 'page_view' },\n userId: 'user.id',\n },\n },\n },\n out: [\n [\n 'fetch',\n ENDPOINT,\n INIT_OPTIONS(\n JSON.stringify({\n events: [\n {\n eventTimestamp: '2023-11-14T22:28:22.000Z',\n transactionId: 'ev-1700000902000',\n userId: 'visitor-55',\n eventName: 'page_view',\n eventSource: 'WEB',\n },\n ],\n destinations: DESTINATIONS,\n }),\n ),\n ],\n ],\n};\n"],"mappings":"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","names":["z","z","z"]}
|
package/dist/examples/index.js
CHANGED
|
@@ -78,10 +78,11 @@ var purchase = {
|
|
|
78
78
|
title: "Purchase",
|
|
79
79
|
description: "A completed order is posted to Google Data Manager as a purchase conversion with hashed user identifiers.",
|
|
80
80
|
in: (0, import_core.getEvent)("order complete", {
|
|
81
|
+
id: "ev-1700000900000",
|
|
81
82
|
timestamp: 17000009e5,
|
|
82
83
|
data: { id: "ORD-600", total: 149.99, currency: "EUR" },
|
|
83
84
|
user: { id: "user-abc", email: "buyer@example.com" },
|
|
84
|
-
source: { type: "
|
|
85
|
+
source: { type: "express", platform: "server" }
|
|
85
86
|
}),
|
|
86
87
|
mapping: {
|
|
87
88
|
name: "purchase",
|
|
@@ -131,10 +132,11 @@ var lead = {
|
|
|
131
132
|
title: "Lead",
|
|
132
133
|
description: "A demo request form submission is sent to Data Manager as a generate_lead conversion with a hashed email.",
|
|
133
134
|
in: (0, import_core.getEvent)("form submit", {
|
|
135
|
+
id: "ev-1700000901000",
|
|
134
136
|
timestamp: 1700000901e3,
|
|
135
137
|
data: { type: "demo-request" },
|
|
136
138
|
user: { email: "prospect@example.com" },
|
|
137
|
-
source: { type: "
|
|
139
|
+
source: { type: "express", platform: "server" }
|
|
138
140
|
}),
|
|
139
141
|
mapping: {
|
|
140
142
|
name: "generate_lead",
|
|
@@ -157,7 +159,7 @@ var lead = {
|
|
|
157
159
|
events: [
|
|
158
160
|
{
|
|
159
161
|
eventTimestamp: "2023-11-14T22:28:21.000Z",
|
|
160
|
-
transactionId: "1700000901000
|
|
162
|
+
transactionId: "ev-1700000901000",
|
|
161
163
|
userData: {
|
|
162
164
|
userIdentifiers: [
|
|
163
165
|
{
|
|
@@ -182,10 +184,11 @@ var ga4PageView = {
|
|
|
182
184
|
title: "Page view",
|
|
183
185
|
description: "A page view is sent to Data Manager as a page_view event tied to the walker user id.",
|
|
184
186
|
in: (0, import_core.getEvent)("page view", {
|
|
187
|
+
id: "ev-1700000902000",
|
|
185
188
|
timestamp: 1700000902e3,
|
|
186
189
|
data: { title: "Pricing", url: "https://example.com/pricing" },
|
|
187
190
|
user: { id: "visitor-55" },
|
|
188
|
-
source: { type: "
|
|
191
|
+
source: { type: "express", platform: "server" }
|
|
189
192
|
}),
|
|
190
193
|
mapping: {
|
|
191
194
|
name: "page_view",
|
|
@@ -206,7 +209,7 @@ var ga4PageView = {
|
|
|
206
209
|
events: [
|
|
207
210
|
{
|
|
208
211
|
eventTimestamp: "2023-11-14T22:28:22.000Z",
|
|
209
|
-
transactionId: "1700000902000
|
|
212
|
+
transactionId: "ev-1700000902000",
|
|
210
213
|
userId: "visitor-55",
|
|
211
214
|
eventName: "page_view",
|
|
212
215
|
eventSource: "WEB"
|
package/dist/examples/index.mjs
CHANGED
|
@@ -57,10 +57,11 @@ var purchase = {
|
|
|
57
57
|
title: "Purchase",
|
|
58
58
|
description: "A completed order is posted to Google Data Manager as a purchase conversion with hashed user identifiers.",
|
|
59
59
|
in: getEvent("order complete", {
|
|
60
|
+
id: "ev-1700000900000",
|
|
60
61
|
timestamp: 17000009e5,
|
|
61
62
|
data: { id: "ORD-600", total: 149.99, currency: "EUR" },
|
|
62
63
|
user: { id: "user-abc", email: "buyer@example.com" },
|
|
63
|
-
source: { type: "
|
|
64
|
+
source: { type: "express", platform: "server" }
|
|
64
65
|
}),
|
|
65
66
|
mapping: {
|
|
66
67
|
name: "purchase",
|
|
@@ -110,10 +111,11 @@ var lead = {
|
|
|
110
111
|
title: "Lead",
|
|
111
112
|
description: "A demo request form submission is sent to Data Manager as a generate_lead conversion with a hashed email.",
|
|
112
113
|
in: getEvent("form submit", {
|
|
114
|
+
id: "ev-1700000901000",
|
|
113
115
|
timestamp: 1700000901e3,
|
|
114
116
|
data: { type: "demo-request" },
|
|
115
117
|
user: { email: "prospect@example.com" },
|
|
116
|
-
source: { type: "
|
|
118
|
+
source: { type: "express", platform: "server" }
|
|
117
119
|
}),
|
|
118
120
|
mapping: {
|
|
119
121
|
name: "generate_lead",
|
|
@@ -136,7 +138,7 @@ var lead = {
|
|
|
136
138
|
events: [
|
|
137
139
|
{
|
|
138
140
|
eventTimestamp: "2023-11-14T22:28:21.000Z",
|
|
139
|
-
transactionId: "1700000901000
|
|
141
|
+
transactionId: "ev-1700000901000",
|
|
140
142
|
userData: {
|
|
141
143
|
userIdentifiers: [
|
|
142
144
|
{
|
|
@@ -161,10 +163,11 @@ var ga4PageView = {
|
|
|
161
163
|
title: "Page view",
|
|
162
164
|
description: "A page view is sent to Data Manager as a page_view event tied to the walker user id.",
|
|
163
165
|
in: getEvent("page view", {
|
|
166
|
+
id: "ev-1700000902000",
|
|
164
167
|
timestamp: 1700000902e3,
|
|
165
168
|
data: { title: "Pricing", url: "https://example.com/pricing" },
|
|
166
169
|
user: { id: "visitor-55" },
|
|
167
|
-
source: { type: "
|
|
170
|
+
source: { type: "express", platform: "server" }
|
|
168
171
|
}),
|
|
169
172
|
mapping: {
|
|
170
173
|
name: "page_view",
|
|
@@ -185,7 +188,7 @@ var ga4PageView = {
|
|
|
185
188
|
events: [
|
|
186
189
|
{
|
|
187
190
|
eventTimestamp: "2023-11-14T22:28:22.000Z",
|
|
188
|
-
transactionId: "1700000902000
|
|
191
|
+
transactionId: "ev-1700000902000",
|
|
189
192
|
userId: "visitor-55",
|
|
190
193
|
eventName: "page_view",
|
|
191
194
|
eventSource: "WEB"
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var mod,__defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__hasOwnProp=Object.prototype.hasOwnProperty,index_exports={};function getConfig(partialConfig={},logger){const settings=partialConfig.settings||{},{destinations:destinations,eventSource:eventSource="WEB"}=settings;destinations&&0!==destinations.length||logger.throw("Config settings destinations missing or empty");const settingsConfig={...settings,destinations:destinations,eventSource:eventSource};return{...partialConfig,settings:settingsConfig}}((target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})})(index_exports,{DestinationDataManager:()=>types_exports,default:()=>index_default,destinationDataManager:()=>destinationDataManager}),module.exports=(mod=index_exports,((to,from,except,desc)=>{if(from&&"object"==typeof from||"function"==typeof from)for(let key of __getOwnPropNames(from))__hasOwnProp.call(to,key)||key===except||__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to})(__defProp({},"__esModule",{value:!0}),mod));var import_core3=require("@walkeros/core"),import_core2=require("@walkeros/core"),import_core=require("@walkeros/core"),import_server_core=require("@walkeros/server-core");async function hashName(name,type="given"){if(!(0,import_core.isString)(name)||!name)return"";let normalized=name.trim().toLowerCase();if("given"===type){const prefixes=["mr.","mrs.","ms.","miss.","dr.","prof."];for(const prefix of prefixes)if(normalized.startsWith(prefix)){normalized=normalized.substring(prefix.length).trim();break}}if("family"===type){const suffixes=["jr.","sr.","iii","ii","iv","v"];for(const suffix of suffixes){const suffixWithSpace=` ${suffix}`;if(normalized.endsWith(suffixWithSpace)){normalized=normalized.substring(0,normalized.length-suffixWithSpace.length).trim();break}if(normalized.endsWith(suffix)){normalized=normalized.substring(0,normalized.length-suffix.length).trim();break}}}return(0,import_server_core.getHashServer)(normalized)}async function formatUserData(data){const identifiers=[];if((0,import_core2.isString)(data.email)&&data.email){const hashedEmail=await async function(email){if(!(0,import_core.isString)(email)||!email)return"";let normalized=email.trim().toLowerCase();if(normalized.endsWith("@gmail.com")||normalized.endsWith("@googlemail.com")){const[localPart,domain]=normalized.split("@");normalized=`${localPart.replace(/\./g,"")}@${domain}`}return(0,import_server_core.getHashServer)(normalized)}(data.email);hashedEmail&&identifiers.push({emailAddress:hashedEmail})}if((0,import_core2.isString)(data.phone)&&data.phone){const hashedPhone=await async function(phone){if(!(0,import_core.isString)(phone)||!phone)return"";let normalized=phone.trim();const hasPlus=normalized.startsWith("+");return normalized=normalized.replace(/\D/g,""),normalized=hasPlus||normalized.length>10?`+${normalized}`:`+1${normalized}`,(0,import_server_core.getHashServer)(normalized)}(data.phone);hashedPhone&&identifiers.push({phoneNumber:hashedPhone})}if(data.firstName||data.lastName||data.regionCode||data.postalCode){const address={};(0,import_core2.isString)(data.firstName)&&data.firstName&&(address.givenName=await hashName(data.firstName,"given")),(0,import_core2.isString)(data.lastName)&&data.lastName&&(address.familyName=await hashName(data.lastName,"family")),(0,import_core2.isString)(data.regionCode)&&data.regionCode&&(address.regionCode=data.regionCode.toUpperCase()),(0,import_core2.isString)(data.postalCode)&&data.postalCode&&(address.postalCode=data.postalCode),Object.keys(address).length>0&&identifiers.push({address:address})}if(0!==identifiers.length)return{userIdentifiers:identifiers.slice(0,10)}}async function formatEvent(event,mappedData){const dataManagerEvent={eventTimestamp:(timestamp=event.timestamp,new Date(timestamp).toISOString())};var timestamp;const data=mappedData||{};(0,import_core2.isString)(data.transactionId)&&data.transactionId&&(dataManagerEvent.transactionId=data.transactionId.substring(0,512)),(0,import_core2.isString)(data.clientId)&&data.clientId&&(dataManagerEvent.clientId=data.clientId.substring(0,255)),(0,import_core2.isString)(data.userId)&&data.userId&&(dataManagerEvent.userId=data.userId.substring(0,256));const userData=await formatUserData(data);userData&&(dataManagerEvent.userData=userData);const adIdentifiers=function(data){const identifiers={};return(0,import_core2.isString)(data.gclid)&&data.gclid&&(identifiers.gclid=data.gclid),(0,import_core2.isString)(data.gbraid)&&data.gbraid&&(identifiers.gbraid=data.gbraid),(0,import_core2.isString)(data.wbraid)&&data.wbraid&&(identifiers.wbraid=data.wbraid),(0,import_core2.isString)(data.sessionAttributes)&&data.sessionAttributes&&(identifiers.sessionAttributes=data.sessionAttributes),Object.keys(identifiers).length>0?identifiers:void 0}(data);adIdentifiers&&(dataManagerEvent.adIdentifiers=adIdentifiers),"number"==typeof data.conversionValue&&(dataManagerEvent.conversionValue=data.conversionValue),(0,import_core2.isString)(data.currency)&&data.currency&&(dataManagerEvent.currency=data.currency.substring(0,3).toUpperCase()),data.cartData&&"object"==typeof data.cartData&&(dataManagerEvent.cartData=data.cartData),(0,import_core2.isString)(data.eventName)&&data.eventName&&(dataManagerEvent.eventName=data.eventName.substring(0,40)),(0,import_core2.isString)(data.eventSource)&&data.eventSource&&(dataManagerEvent.eventSource=data.eventSource);const mappedConsent={};if("boolean"==typeof data.adUserData&&(mappedConsent.adUserData=data.adUserData?"CONSENT_GRANTED":"CONSENT_DENIED"),"boolean"==typeof data.adPersonalization&&(mappedConsent.adPersonalization=data.adPersonalization?"CONSENT_GRANTED":"CONSENT_DENIED"),0===Object.keys(mappedConsent).length){const eventConsent=function(walkerOSConsent){if(!walkerOSConsent)return;const consent={};return(0,import_core2.isDefined)(walkerOSConsent.marketing)&&(consent.adUserData=walkerOSConsent.marketing?"CONSENT_GRANTED":"CONSENT_DENIED"),(0,import_core2.isDefined)(walkerOSConsent.personalization)&&(consent.adPersonalization=walkerOSConsent.personalization?"CONSENT_GRANTED":"CONSENT_DENIED"),Object.keys(consent).length>0?consent:void 0}(event.consent);eventConsent&&(dataManagerEvent.consent=eventConsent)}else dataManagerEvent.consent=mappedConsent;return dataManagerEvent}var import_google_auth_library=require("google-auth-library"),DEFAULT_SCOPES=["https://www.googleapis.com/auth/datamanager"],AuthError=class extends Error{constructor(message,cause){super(message),this.cause=cause,this.name="DataManagerAuthError"}};var push=async function(event,{config:config,rule:rule,data:data,collector:collector,env:env,logger:logger}){const validatedConfig=getConfig(config,logger),{destinations:destinations,eventSource:eventSource,validateOnly:validateOnly=!1,url:url="https://datamanager.googleapis.com/v1",consent:requestConsent,testEventCode:testEventCode,userData:userData,userId:userId,clientId:clientId,sessionAttributes:sessionAttributes,consentAdUserData:consentAdUserData,consentAdPersonalization:consentAdPersonalization}=validatedConfig.settings,userDataMapped=userData?await(0,import_core3.getMappingValue)(event,{map:userData}):{},userIdMapped=userId?await(0,import_core3.getMappingValue)(event,userId):void 0,clientIdMapped=clientId?await(0,import_core3.getMappingValue)(event,clientId):void 0,sessionAttributesMapped=sessionAttributes?await(0,import_core3.getMappingValue)(event,sessionAttributes):void 0,consentAdUserDataValue="boolean"==typeof consentAdUserData?consentAdUserData:"string"==typeof consentAdUserData&&event.consent?event.consent[consentAdUserData]:void 0,consentAdPersonalizationValue="boolean"==typeof consentAdPersonalization?consentAdPersonalization:"string"==typeof consentAdPersonalization&&event.consent?event.consent[consentAdPersonalization]:void 0,settingsHelpers={};(0,import_core3.isObject)(userDataMapped)&&Object.assign(settingsHelpers,userDataMapped),void 0!==userIdMapped&&(settingsHelpers.userId=userIdMapped),void 0!==clientIdMapped&&(settingsHelpers.clientId=clientIdMapped),void 0!==sessionAttributesMapped&&(settingsHelpers.sessionAttributes=sessionAttributesMapped),void 0!==consentAdUserDataValue&&(settingsHelpers.adUserData=consentAdUserDataValue),void 0!==consentAdPersonalizationValue&&(settingsHelpers.adPersonalization=consentAdPersonalizationValue);const configData=validatedConfig.data?await(0,import_core3.getMappingValue)(event,validatedConfig.data):{},eventData=(0,import_core3.isObject)(data)?data:{},finalData={...settingsHelpers,...(0,import_core3.isObject)(configData)?configData:{},...eventData},dataManagerEvent=await formatEvent(event,finalData);dataManagerEvent.eventSource||(dataManagerEvent.eventSource=eventSource),!dataManagerEvent.consent&&requestConsent&&(dataManagerEvent.consent=requestConsent),dataManagerEvent.transactionId||logger.throw("transactionId is required");destinations.some(d=>{var _a;return"GOOGLE_ANALYTICS_PROPERTY"===(null==(_a=d.operatingAccount)?void 0:_a.accountType)})&&!dataManagerEvent.eventName&&logger.throw("eventName is required for GA4 destinations");const requestBody={events:[dataManagerEvent],destinations:destinations};requestConsent&&(requestBody.consent=requestConsent),validateOnly&&(requestBody.validateOnly=!0),testEventCode&&(requestBody.testEventCode=testEventCode);const authClient=null==env?void 0:env.authClient;if(!authClient)return logger.throw("Auth client not initialized. Ensure init() was called successfully.");let accessToken;try{accessToken=await async function(authClient){try{const tokenResponse=await authClient.getAccessToken();if(!tokenResponse.token)throw new AuthError("Auth client returned empty token");return tokenResponse.token}catch(error){throw new AuthError("Failed to obtain access token",error instanceof Error?error:void 0)}}(authClient)}catch(error){throw logger.error("Authentication failed",{error:error}),error}const fetchFn=(null==env?void 0:env.fetch)||fetch,endpoint=`${url}/events:ingest`;logger.debug("Sending to Data Manager API",{endpoint:endpoint,eventCount:requestBody.events.length,destinations:destinations.length,validateOnly:validateOnly});const response=await fetchFn(endpoint,{method:"POST",headers:{Authorization:`Bearer ${accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(requestBody)});if(!response.ok){const errorText=await response.text();logger.throw(`Data Manager API error (${response.status}): ${errorText}`)}const result=await response.json();logger.debug("API response",{status:response.status,requestId:result.requestId}),result.validationErrors&&result.validationErrors.length>0&&logger.throw(`Validation errors: ${JSON.stringify(result.validationErrors)}`)},types_exports={},destinationDataManager={type:"datamanager",config:{},async init({config:partialConfig,env:env,logger:logger}){const config=getConfig(partialConfig,logger);try{const authClient=await async function(settings){const{credentials:credentials,keyFilename:keyFilename,scopes:scopes=DEFAULT_SCOPES}=settings;try{if(credentials){const auth2=new import_google_auth_library.GoogleAuth({credentials:credentials,scopes:scopes});return await auth2.getClient()}if(keyFilename){const auth2=new import_google_auth_library.GoogleAuth({keyFilename:keyFilename,scopes:scopes});return await auth2.getClient()}const auth=new import_google_auth_library.GoogleAuth({scopes:scopes});return await auth.getClient()}catch(error){throw new AuthError("Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.",error instanceof Error?error:void 0)}}(config.settings);return logger.debug("Auth client created"),{...config,env:{...env,authClient:authClient}}}catch(error){logger.throw(`Data Manager authentication failed: ${error instanceof Error?error.message:"Unknown error"}`)}},push:async(event,context)=>await push(event,context)},index_default=destinationDataManager;//# sourceMappingURL=index.js.map
|
|
1
|
+
"use strict";var mod,__defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__hasOwnProp=Object.prototype.hasOwnProperty,index_exports={};function getConfig(partialConfig={},logger){const settings=partialConfig.settings||{},{destinations:destinations,eventSource:eventSource="WEB"}=settings;destinations&&0!==destinations.length||logger.throw("Config settings destinations missing or empty");const settingsConfig={...settings,destinations:destinations,eventSource:eventSource};return{...partialConfig,settings:settingsConfig}}((target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})})(index_exports,{DestinationDataManager:()=>types_exports,default:()=>index_default,destinationDataManager:()=>destinationDataManager}),module.exports=(mod=index_exports,((to,from,except,desc)=>{if(from&&"object"==typeof from||"function"==typeof from)for(let key of __getOwnPropNames(from))__hasOwnProp.call(to,key)||key===except||__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to})(__defProp({},"__esModule",{value:!0}),mod));var import_core3=require("@walkeros/core"),import_core2=require("@walkeros/core"),import_core=require("@walkeros/core"),import_server_core=require("@walkeros/server-core");async function hashName(name,type="given"){if(!(0,import_core.isString)(name)||!name)return"";let normalized=name.trim().toLowerCase();if("given"===type){const prefixes=["mr.","mrs.","ms.","miss.","dr.","prof."];for(const prefix of prefixes)if(normalized.startsWith(prefix)){normalized=normalized.substring(prefix.length).trim();break}}if("family"===type){const suffixes=["jr.","sr.","iii","ii","iv","v"];for(const suffix of suffixes){const suffixWithSpace=` ${suffix}`;if(normalized.endsWith(suffixWithSpace)){normalized=normalized.substring(0,normalized.length-suffixWithSpace.length).trim();break}if(normalized.endsWith(suffix)){normalized=normalized.substring(0,normalized.length-suffix.length).trim();break}}}return(0,import_server_core.getHashServer)(normalized)}async function formatUserData(data){const identifiers=[];if((0,import_core2.isString)(data.email)&&data.email){const hashedEmail=await async function(email){if(!(0,import_core.isString)(email)||!email)return"";let normalized=email.trim().toLowerCase();if(normalized.endsWith("@gmail.com")||normalized.endsWith("@googlemail.com")){const[localPart,domain]=normalized.split("@");normalized=`${localPart.replace(/\./g,"")}@${domain}`}return(0,import_server_core.getHashServer)(normalized)}(data.email);hashedEmail&&identifiers.push({emailAddress:hashedEmail})}if((0,import_core2.isString)(data.phone)&&data.phone){const hashedPhone=await async function(phone){if(!(0,import_core.isString)(phone)||!phone)return"";let normalized=phone.trim();const hasPlus=normalized.startsWith("+");return normalized=normalized.replace(/\D/g,""),normalized=hasPlus||normalized.length>10?`+${normalized}`:`+1${normalized}`,(0,import_server_core.getHashServer)(normalized)}(data.phone);hashedPhone&&identifiers.push({phoneNumber:hashedPhone})}if(data.firstName||data.lastName||data.regionCode||data.postalCode){const address={};(0,import_core2.isString)(data.firstName)&&data.firstName&&(address.givenName=await hashName(data.firstName,"given")),(0,import_core2.isString)(data.lastName)&&data.lastName&&(address.familyName=await hashName(data.lastName,"family")),(0,import_core2.isString)(data.regionCode)&&data.regionCode&&(address.regionCode=data.regionCode.toUpperCase()),(0,import_core2.isString)(data.postalCode)&&data.postalCode&&(address.postalCode=data.postalCode),Object.keys(address).length>0&&identifiers.push({address:address})}if(0!==identifiers.length)return{userIdentifiers:identifiers.slice(0,10)}}async function formatEvent(event,mappedData){const dataManagerEvent={eventTimestamp:(timestamp=event.timestamp,new Date(timestamp).toISOString())};var timestamp;const data=mappedData||{};(0,import_core2.isString)(data.transactionId)&&data.transactionId&&(dataManagerEvent.transactionId=data.transactionId.substring(0,512)),(0,import_core2.isString)(data.clientId)&&data.clientId&&(dataManagerEvent.clientId=data.clientId.substring(0,255)),(0,import_core2.isString)(data.userId)&&data.userId&&(dataManagerEvent.userId=data.userId.substring(0,256));const userData=await formatUserData(data);userData&&(dataManagerEvent.userData=userData);const adIdentifiers=function(data){const identifiers={};return(0,import_core2.isString)(data.gclid)&&data.gclid&&(identifiers.gclid=data.gclid),(0,import_core2.isString)(data.gbraid)&&data.gbraid&&(identifiers.gbraid=data.gbraid),(0,import_core2.isString)(data.wbraid)&&data.wbraid&&(identifiers.wbraid=data.wbraid),(0,import_core2.isString)(data.sessionAttributes)&&data.sessionAttributes&&(identifiers.sessionAttributes=data.sessionAttributes),Object.keys(identifiers).length>0?identifiers:void 0}(data);adIdentifiers&&(dataManagerEvent.adIdentifiers=adIdentifiers),"number"==typeof data.conversionValue&&(dataManagerEvent.conversionValue=data.conversionValue),(0,import_core2.isString)(data.currency)&&data.currency&&(dataManagerEvent.currency=data.currency.substring(0,3).toUpperCase()),data.cartData&&"object"==typeof data.cartData&&(dataManagerEvent.cartData=data.cartData),(0,import_core2.isString)(data.eventName)&&data.eventName&&(dataManagerEvent.eventName=data.eventName.substring(0,40)),(0,import_core2.isString)(data.eventSource)&&data.eventSource&&(dataManagerEvent.eventSource=data.eventSource);const mappedConsent={};if("boolean"==typeof data.adUserData&&(mappedConsent.adUserData=data.adUserData?"CONSENT_GRANTED":"CONSENT_DENIED"),"boolean"==typeof data.adPersonalization&&(mappedConsent.adPersonalization=data.adPersonalization?"CONSENT_GRANTED":"CONSENT_DENIED"),0===Object.keys(mappedConsent).length){const eventConsent=function(walkerOSConsent){if(!walkerOSConsent)return;const consent={};return(0,import_core2.isDefined)(walkerOSConsent.marketing)&&(consent.adUserData=walkerOSConsent.marketing?"CONSENT_GRANTED":"CONSENT_DENIED"),(0,import_core2.isDefined)(walkerOSConsent.personalization)&&(consent.adPersonalization=walkerOSConsent.personalization?"CONSENT_GRANTED":"CONSENT_DENIED"),Object.keys(consent).length>0?consent:void 0}(event.consent);eventConsent&&(dataManagerEvent.consent=eventConsent)}else dataManagerEvent.consent=mappedConsent;return dataManagerEvent}var import_google_auth_library=require("google-auth-library"),DEFAULT_SCOPES=["https://www.googleapis.com/auth/datamanager"],AuthError=class extends Error{constructor(message,cause){super(message),this.cause=cause,this.name="DataManagerAuthError"}};var push=async function(event,{config:config,rule:rule,data:data,collector:collector,env:env,logger:logger}){const validatedConfig=getConfig(config,logger),{destinations:destinations,eventSource:eventSource,validateOnly:validateOnly=!1,url:url="https://datamanager.googleapis.com/v1",consent:requestConsent,testEventCode:testEventCode,userData:userData,userId:userId,clientId:clientId,sessionAttributes:sessionAttributes,consentAdUserData:consentAdUserData,consentAdPersonalization:consentAdPersonalization}=validatedConfig.settings,userDataMapped=userData?await(0,import_core3.getMappingValue)(event,{map:userData}):{},userIdMapped=userId?await(0,import_core3.getMappingValue)(event,userId):void 0,clientIdMapped=clientId?await(0,import_core3.getMappingValue)(event,clientId):void 0,sessionAttributesMapped=sessionAttributes?await(0,import_core3.getMappingValue)(event,sessionAttributes):void 0,consentAdUserDataValue="boolean"==typeof consentAdUserData?consentAdUserData:"string"==typeof consentAdUserData&&event.consent?event.consent[consentAdUserData]:void 0,consentAdPersonalizationValue="boolean"==typeof consentAdPersonalization?consentAdPersonalization:"string"==typeof consentAdPersonalization&&event.consent?event.consent[consentAdPersonalization]:void 0,settingsHelpers={};(0,import_core3.isObject)(userDataMapped)&&Object.assign(settingsHelpers,userDataMapped),void 0!==userIdMapped&&(settingsHelpers.userId=userIdMapped),void 0!==clientIdMapped&&(settingsHelpers.clientId=clientIdMapped),void 0!==sessionAttributesMapped&&(settingsHelpers.sessionAttributes=sessionAttributesMapped),void 0!==consentAdUserDataValue&&(settingsHelpers.adUserData=consentAdUserDataValue),void 0!==consentAdPersonalizationValue&&(settingsHelpers.adPersonalization=consentAdPersonalizationValue);const configData=validatedConfig.data?await(0,import_core3.getMappingValue)(event,validatedConfig.data):{},eventData=(0,import_core3.isObject)(data)?data:{},finalData={...settingsHelpers,...(0,import_core3.isObject)(configData)?configData:{},...eventData},dataManagerEvent=await formatEvent(event,finalData);dataManagerEvent.eventSource||(dataManagerEvent.eventSource=eventSource),!dataManagerEvent.consent&&requestConsent&&(dataManagerEvent.consent=requestConsent),dataManagerEvent.transactionId||logger.throw("transactionId is required");destinations.some(d=>"GOOGLE_ANALYTICS_PROPERTY"===d.operatingAccount?.accountType)&&!dataManagerEvent.eventName&&logger.throw("eventName is required for GA4 destinations");const requestBody={events:[dataManagerEvent],destinations:destinations};requestConsent&&(requestBody.consent=requestConsent),validateOnly&&(requestBody.validateOnly=!0),testEventCode&&(requestBody.testEventCode=testEventCode);const authClient=env?.authClient;if(!authClient)return logger.throw("Auth client not initialized. Ensure init() was called successfully.");let accessToken;try{accessToken=await async function(authClient){try{const tokenResponse=await authClient.getAccessToken();if(!tokenResponse.token)throw new AuthError("Auth client returned empty token");return tokenResponse.token}catch(error){throw new AuthError("Failed to obtain access token",error instanceof Error?error:void 0)}}(authClient)}catch(error){throw logger.error("Authentication failed",{error:error}),error}const fetchFn=env?.fetch||fetch,endpoint=`${url}/events:ingest`;logger.debug("Sending to Data Manager API",{endpoint:endpoint,eventCount:requestBody.events.length,destinations:destinations.length,validateOnly:validateOnly});const response=await fetchFn(endpoint,{method:"POST",headers:{Authorization:`Bearer ${accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(requestBody)});if(!response.ok){const errorText=await response.text();logger.throw(`Data Manager API error (${response.status}): ${errorText}`)}const result=await response.json();logger.debug("API response",{status:response.status,requestId:result.requestId}),result.validationErrors&&result.validationErrors.length>0&&logger.throw(`Validation errors: ${JSON.stringify(result.validationErrors)}`)},types_exports={},destinationDataManager={type:"datamanager",config:{},async init({config:partialConfig,env:env,logger:logger}){const config=getConfig(partialConfig,logger);try{const authClient=await async function(settings){const{credentials:credentials,keyFilename:keyFilename,scopes:scopes=DEFAULT_SCOPES}=settings;try{if(credentials){const auth2=new import_google_auth_library.GoogleAuth({credentials:credentials,scopes:scopes});return await auth2.getClient()}if(keyFilename){const auth2=new import_google_auth_library.GoogleAuth({keyFilename:keyFilename,scopes:scopes});return await auth2.getClient()}const auth=new import_google_auth_library.GoogleAuth({scopes:scopes});return await auth.getClient()}catch(error){throw new AuthError("Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.",error instanceof Error?error:void 0)}}(config.settings);return logger.debug("Auth client created"),{...config,env:{...env,authClient:authClient}}}catch(error){logger.throw(`Data Manager authentication failed: ${error instanceof Error?error.message:"Unknown error"}`)}},push:async(event,context)=>await push(event,context)},index_default=destinationDataManager;//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/push.ts","../src/format.ts","../src/hash.ts","../src/auth.ts","../src/types/index.ts"],"sourcesContent":["import type { DestinationInterface } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\nimport { createAuthClient } from './auth';\n\nexport * as DestinationDataManager from './types';\n\nexport const destinationDataManager: DestinationInterface = {\n type: 'datamanager',\n\n config: {},\n\n async init({ config: partialConfig, env, logger }) {\n // getConfig validates required fields and returns ValidatedConfig\n const config = getConfig(partialConfig, logger);\n\n try {\n const authClient = await createAuthClient(config.settings);\n logger.debug('Auth client created');\n\n return {\n ...config,\n env: {\n ...env,\n authClient,\n },\n };\n } catch (error) {\n logger.throw(\n `Data Manager authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n },\n\n async push(event, context) {\n return await push(event, context);\n },\n};\n\nexport default destinationDataManager;\n","import type {\n ValidatedConfig,\n Settings,\n PartialConfig,\n EventSource,\n} from './types';\nimport type { Logger } from '@walkeros/core';\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): ValidatedConfig {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { destinations, eventSource = 'WEB' } = settings;\n\n if (!destinations || destinations.length === 0)\n logger.throw('Config settings destinations missing or empty');\n\n const settingsConfig: Settings & { eventSource: EventSource } = {\n ...settings,\n destinations,\n eventSource,\n };\n\n return { ...partialConfig, settings: settingsConfig };\n}\n","import type { PushFn } from './types';\nimport type { IngestEventsRequest, IngestEventsResponse } from './types';\nimport { getMappingValue, isObject } from '@walkeros/core';\nimport { formatEvent, formatConsent } from './format';\nimport { getAccessToken } from './auth';\nimport { getConfig } from './config';\n\nexport const push: PushFn = async function (\n event,\n { config, rule, data, collector, env, logger },\n) {\n // Validate config and get typed settings\n const validatedConfig = getConfig(config, logger);\n const {\n destinations,\n eventSource,\n validateOnly = false,\n url = 'https://datamanager.googleapis.com/v1',\n consent: requestConsent,\n testEventCode,\n userData,\n userId,\n clientId,\n sessionAttributes,\n consentAdUserData,\n consentAdPersonalization,\n } = validatedConfig.settings;\n\n // Extract Settings guided helpers\n const userDataMapped = userData\n ? await getMappingValue(event, { map: userData })\n : {};\n const userIdMapped = userId\n ? await getMappingValue(event, userId)\n : undefined;\n const clientIdMapped = clientId\n ? await getMappingValue(event, clientId)\n : undefined;\n const sessionAttributesMapped = sessionAttributes\n ? await getMappingValue(event, sessionAttributes)\n : undefined;\n\n // Extract consent from Settings\n const consentAdUserDataValue =\n typeof consentAdUserData === 'boolean'\n ? consentAdUserData\n : typeof consentAdUserData === 'string' && event.consent\n ? event.consent[consentAdUserData]\n : undefined;\n\n const consentAdPersonalizationValue =\n typeof consentAdPersonalization === 'boolean'\n ? consentAdPersonalization\n : typeof consentAdPersonalization === 'string' && event.consent\n ? event.consent[consentAdPersonalization]\n : undefined;\n\n // Build Settings helpers object\n const settingsHelpers: Record<string, unknown> = {};\n if (isObject(userDataMapped)) {\n Object.assign(settingsHelpers, userDataMapped);\n }\n if (userIdMapped !== undefined) settingsHelpers.userId = userIdMapped;\n if (clientIdMapped !== undefined) settingsHelpers.clientId = clientIdMapped;\n if (sessionAttributesMapped !== undefined)\n settingsHelpers.sessionAttributes = sessionAttributesMapped;\n if (consentAdUserDataValue !== undefined)\n settingsHelpers.adUserData = consentAdUserDataValue;\n if (consentAdPersonalizationValue !== undefined)\n settingsHelpers.adPersonalization = consentAdPersonalizationValue;\n\n // Get mapped data from destination config and event mapping\n const configData = validatedConfig.data\n ? await getMappingValue(event, validatedConfig.data)\n : {};\n const eventData = isObject(data) ? data : {};\n\n // Merge: Settings helpers < config.data < event mapping data\n const finalData = {\n ...settingsHelpers,\n ...(isObject(configData) ? configData : {}),\n ...eventData,\n };\n\n // Format event for Data Manager API\n const dataManagerEvent = await formatEvent(event, finalData);\n\n // Apply event source from settings (required)\n if (!dataManagerEvent.eventSource) {\n dataManagerEvent.eventSource = eventSource;\n }\n\n // Apply request-level consent if event doesn't have consent\n if (!dataManagerEvent.consent && requestConsent) {\n dataManagerEvent.consent = requestConsent;\n }\n\n // Validate required fields before API call\n if (!dataManagerEvent.transactionId) {\n logger.throw('transactionId is required');\n }\n\n // Check if any destination is GA4 (requires eventName)\n const hasGA4Destination = destinations.some(\n (d) => d.operatingAccount?.accountType === 'GOOGLE_ANALYTICS_PROPERTY',\n );\n\n if (hasGA4Destination && !dataManagerEvent.eventName) {\n logger.throw('eventName is required for GA4 destinations');\n }\n\n // Build API request\n const requestBody: IngestEventsRequest = {\n events: [dataManagerEvent],\n destinations,\n };\n\n // Add optional parameters\n if (requestConsent) {\n requestBody.consent = requestConsent;\n }\n\n if (validateOnly) {\n requestBody.validateOnly = true;\n }\n\n if (testEventCode) {\n requestBody.testEventCode = testEventCode;\n }\n\n const authClient = env?.authClient;\n if (!authClient) {\n return logger.throw(\n 'Auth client not initialized. Ensure init() was called successfully.',\n );\n }\n\n let accessToken: string;\n try {\n accessToken = await getAccessToken(authClient);\n } catch (error) {\n logger.error('Authentication failed', { error });\n throw error;\n }\n\n const fetchFn = env?.fetch || fetch;\n const endpoint = `${url}/events:ingest`;\n\n logger.debug('Sending to Data Manager API', {\n endpoint,\n eventCount: requestBody.events.length,\n destinations: destinations.length,\n validateOnly,\n });\n\n const response = await fetchFn(endpoint, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n logger.throw(`Data Manager API error (${response.status}): ${errorText}`);\n }\n\n const result: IngestEventsResponse = await response.json();\n\n logger.debug('API response', {\n status: response.status,\n requestId: result.requestId,\n });\n\n // If validation errors exist, throw them\n if (result.validationErrors && result.validationErrors.length > 0) {\n logger.throw(\n `Validation errors: ${JSON.stringify(result.validationErrors)}`,\n );\n }\n};\n","import type { WalkerOS } from '@walkeros/core';\nimport { isString, isDefined } from '@walkeros/core';\nimport type {\n Event,\n UserData,\n UserIdentifier,\n AdIdentifiers,\n Consent,\n ConsentStatus,\n} from './types';\nimport { hashEmail, hashPhone, hashName } from './hash';\n\n/**\n * Format walkerOS event timestamp to RFC 3339 format\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n *\n * walkerOS timestamp is in milliseconds, RFC 3339 format: \"2024-01-15T10:30:00Z\"\n */\nexport function formatTimestamp(timestamp: number): string {\n return new Date(timestamp).toISOString();\n}\n\n/**\n * Format user identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n *\n * User data must be explicitly mapped in the mapping configuration.\n * Max 10 identifiers per event\n */\nexport async function formatUserData(\n data: Record<string, unknown>,\n): Promise<UserData | undefined> {\n const identifiers: UserIdentifier[] = [];\n\n // Extract from mapped data only\n // Email\n if (isString(data.email) && data.email) {\n const hashedEmail = await hashEmail(data.email);\n if (hashedEmail) {\n identifiers.push({ emailAddress: hashedEmail });\n }\n }\n\n // Phone\n if (isString(data.phone) && data.phone) {\n const hashedPhone = await hashPhone(data.phone);\n if (hashedPhone) {\n identifiers.push({ phoneNumber: hashedPhone });\n }\n }\n\n // Address from mapped properties\n const hasAddress =\n data.firstName || data.lastName || data.regionCode || data.postalCode;\n\n if (hasAddress) {\n const address: Record<string, string> = {};\n\n if (isString(data.firstName) && data.firstName) {\n address.givenName = await hashName(data.firstName, 'given');\n }\n\n if (isString(data.lastName) && data.lastName) {\n address.familyName = await hashName(data.lastName, 'family');\n }\n\n // Region code is NOT hashed\n if (isString(data.regionCode) && data.regionCode) {\n address.regionCode = data.regionCode.toUpperCase();\n }\n\n // Postal code is NOT hashed\n if (isString(data.postalCode) && data.postalCode) {\n address.postalCode = data.postalCode;\n }\n\n if (Object.keys(address).length > 0) {\n identifiers.push({ address });\n }\n }\n\n // Limit to 10 identifiers\n if (identifiers.length === 0) return undefined;\n\n return {\n userIdentifiers: identifiers.slice(0, 10),\n };\n}\n\n/**\n * Extract and format attribution identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n *\n * Attribution identifiers should be mapped explicitly in the mapping configuration.\n * Example: { gclid: 'context.gclid', gbraid: 'context.gbraid' }\n */\nexport function formatAdIdentifiers(\n data: Record<string, unknown>,\n): AdIdentifiers | undefined {\n const identifiers: AdIdentifiers = {};\n\n // Extract from mapped data (already processed by mapping system)\n if (isString(data.gclid) && data.gclid) {\n identifiers.gclid = data.gclid;\n }\n if (isString(data.gbraid) && data.gbraid) {\n identifiers.gbraid = data.gbraid;\n }\n if (isString(data.wbraid) && data.wbraid) {\n identifiers.wbraid = data.wbraid;\n }\n if (isString(data.sessionAttributes) && data.sessionAttributes) {\n identifiers.sessionAttributes = data.sessionAttributes;\n }\n\n return Object.keys(identifiers).length > 0 ? identifiers : undefined;\n}\n\n/**\n * Map walkerOS consent to Data Manager consent format\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n *\n * walkerOS: { marketing: true, personalization: false }\n * Data Manager: { adUserData: 'CONSENT_GRANTED', adPersonalization: 'CONSENT_DENIED' }\n */\nexport function formatConsent(\n walkerOSConsent: WalkerOS.Consent | undefined,\n): Consent | undefined {\n if (!walkerOSConsent) return undefined;\n\n const consent: Consent = {};\n\n // Map marketing consent to adUserData\n if (isDefined(walkerOSConsent.marketing)) {\n consent.adUserData = walkerOSConsent.marketing\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // Map personalization consent to adPersonalization\n if (isDefined(walkerOSConsent.personalization)) {\n consent.adPersonalization = walkerOSConsent.personalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n return Object.keys(consent).length > 0 ? consent : undefined;\n}\n\n/**\n * Format complete event for Data Manager API\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport async function formatEvent(\n event: WalkerOS.Event,\n mappedData?: Record<string, unknown>,\n): Promise<Event> {\n const dataManagerEvent: Event = {\n eventTimestamp: formatTimestamp(event.timestamp),\n };\n\n // Use only mapped data (no fallback to event.data)\n const data = mappedData || {};\n\n // Transaction ID for deduplication\n if (isString(data.transactionId) && data.transactionId) {\n dataManagerEvent.transactionId = data.transactionId.substring(0, 512);\n }\n\n // Client ID (GA)\n if (isString(data.clientId) && data.clientId) {\n dataManagerEvent.clientId = data.clientId.substring(0, 255);\n }\n\n // User ID\n if (isString(data.userId) && data.userId) {\n dataManagerEvent.userId = data.userId.substring(0, 256);\n }\n\n // User data\n const userData = await formatUserData(data);\n if (userData) {\n dataManagerEvent.userData = userData;\n }\n\n // Attribution identifiers\n const adIdentifiers = formatAdIdentifiers(data);\n if (adIdentifiers) {\n dataManagerEvent.adIdentifiers = adIdentifiers;\n }\n\n // Conversion value\n if (typeof data.conversionValue === 'number') {\n dataManagerEvent.conversionValue = data.conversionValue;\n }\n\n // Currency\n if (isString(data.currency) && data.currency) {\n dataManagerEvent.currency = data.currency.substring(0, 3).toUpperCase();\n }\n\n // Cart data\n if (data.cartData && typeof data.cartData === 'object') {\n dataManagerEvent.cartData = data.cartData as Event['cartData'];\n }\n\n // Event name (for GA4)\n if (isString(data.eventName) && data.eventName) {\n dataManagerEvent.eventName = data.eventName.substring(0, 40);\n }\n\n // Event source\n if (isString(data.eventSource) && data.eventSource) {\n dataManagerEvent.eventSource = data.eventSource as Event['eventSource'];\n }\n\n // Consent - check mapped data first, then fallback to event.consent\n const mappedConsent: Consent = {};\n\n // Check for mapped consent values (from Settings or event mapping)\n if (typeof data.adUserData === 'boolean') {\n mappedConsent.adUserData = data.adUserData\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n if (typeof data.adPersonalization === 'boolean') {\n mappedConsent.adPersonalization = data.adPersonalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // If no mapped consent, fall back to event.consent\n if (Object.keys(mappedConsent).length === 0) {\n const eventConsent = formatConsent(event.consent);\n if (eventConsent) {\n dataManagerEvent.consent = eventConsent;\n }\n } else {\n dataManagerEvent.consent = mappedConsent;\n }\n\n return dataManagerEvent;\n}\n","import { isString } from '@walkeros/core';\nimport { getHashServer } from '@walkeros/server-core';\n\n/**\n * Normalize email address according to Google Data Manager requirements\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#email\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove dots (.) for gmail.com and googlemail.com\n * 4. SHA-256 hash\n */\nexport async function hashEmail(email: string): Promise<string> {\n if (!isString(email) || !email) return '';\n\n // Trim and lowercase\n let normalized = email.trim().toLowerCase();\n\n // Remove dots for Gmail addresses\n if (\n normalized.endsWith('@gmail.com') ||\n normalized.endsWith('@googlemail.com')\n ) {\n const [localPart, domain] = normalized.split('@');\n normalized = `${localPart.replace(/\\./g, '')}@${domain}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize phone number to E.164 format and hash\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#phone\n *\n * E.164 format: +[country code][number] (max 15 digits after +)\n * Example: +18005550100\n *\n * 1. Remove all non-digit characters except leading +\n * 2. Ensure it starts with +\n * 3. SHA-256 hash\n */\nexport async function hashPhone(phone: string): Promise<string> {\n if (!isString(phone) || !phone) return '';\n\n // Remove all non-digit characters except + at the start\n let normalized = phone.trim();\n\n // Extract country code if present\n const hasPlus = normalized.startsWith('+');\n\n // Remove all non-digits\n normalized = normalized.replace(/\\D/g, '');\n\n // Add + prefix if it was there or if number is long enough\n if (hasPlus || normalized.length > 10) {\n normalized = `+${normalized}`;\n } else {\n // Assume US number if no country code (default behavior)\n normalized = `+1${normalized}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize and hash a name (first or last name)\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#name\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove common prefixes (Mr., Mrs., Dr.) for first names\n * 4. Remove common suffixes (Jr., Sr., III) for last names\n * 5. SHA-256 hash\n */\nexport async function hashName(\n name: string,\n type: 'given' | 'family' = 'given',\n): Promise<string> {\n if (!isString(name) || !name) return '';\n\n // Trim and lowercase\n let normalized = name.trim().toLowerCase();\n\n // Remove prefixes for given names\n if (type === 'given') {\n const prefixes = ['mr.', 'mrs.', 'ms.', 'miss.', 'dr.', 'prof.'];\n for (const prefix of prefixes) {\n if (normalized.startsWith(prefix)) {\n normalized = normalized.substring(prefix.length).trim();\n break;\n }\n }\n }\n\n // Remove suffixes for family names (check with and without space)\n // Sort by length (longest first) to match \"iii\" before \"ii\"\n if (type === 'family') {\n const suffixes = ['jr.', 'sr.', 'iii', 'ii', 'iv', 'v'];\n for (const suffix of suffixes) {\n // Check for suffix with space before it (e.g., \" jr.\")\n const suffixWithSpace = ` ${suffix}`;\n if (normalized.endsWith(suffixWithSpace)) {\n normalized = normalized\n .substring(0, normalized.length - suffixWithSpace.length)\n .trim();\n break;\n }\n // Check for suffix without space\n if (normalized.endsWith(suffix)) {\n normalized = normalized\n .substring(0, normalized.length - suffix.length)\n .trim();\n break;\n }\n }\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Hash multiple email addresses\n */\nexport async function hashEmails(emails: string[]): Promise<string[]> {\n return Promise.all(emails.map((email) => hashEmail(email)));\n}\n\n/**\n * Hash multiple phone numbers\n */\nexport async function hashPhones(phones: string[]): Promise<string[]> {\n return Promise.all(phones.map((phone) => hashPhone(phone)));\n}\n","import { GoogleAuth, type OAuth2Client } from 'google-auth-library';\nimport type { Settings } from './types';\n\nconst DEFAULT_SCOPES = ['https://www.googleapis.com/auth/datamanager'];\n\n/**\n * Authentication error with cause tracking\n */\nexport class AuthError extends Error {\n constructor(\n message: string,\n public cause?: Error,\n ) {\n super(message);\n this.name = 'DataManagerAuthError';\n }\n}\n\n/**\n * Creates Google Auth client based on settings\n *\n * Authentication priority:\n * 1. credentials (inline service account) - if provided\n * 2. keyFilename (service account file) - if provided\n * 3. Application Default Credentials (ADC) - automatic fallback\n * - GOOGLE_APPLICATION_CREDENTIALS env var\n * - GCP metadata server (Cloud Functions, Cloud Run, GCE)\n *\n * @param settings - Configuration with auth options\n * @returns OAuth2Client for token retrieval\n * @throws AuthError if authentication fails\n */\nexport async function createAuthClient(\n settings: Settings,\n): Promise<OAuth2Client> {\n const { credentials, keyFilename, scopes = DEFAULT_SCOPES } = settings;\n\n try {\n if (credentials) {\n const auth = new GoogleAuth({\n credentials,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n if (keyFilename) {\n const auth = new GoogleAuth({\n keyFilename,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n const auth = new GoogleAuth({ scopes });\n return (await auth.getClient()) as OAuth2Client;\n } catch (error) {\n throw new AuthError(\n 'Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.',\n error instanceof Error ? error : undefined,\n );\n }\n}\n\n/**\n * Gets access token from auth client\n * Automatically returns cached token if valid or refreshes if expired\n *\n * @param authClient - OAuth2 client from createAuthClient()\n * @returns Fresh access token\n * @throws AuthError if token retrieval fails\n */\nexport async function getAccessToken(\n authClient: OAuth2Client,\n): Promise<string> {\n try {\n const tokenResponse = await authClient.getAccessToken();\n\n if (!tokenResponse.token) {\n throw new AuthError('Auth client returned empty token');\n }\n\n return tokenResponse.token;\n } catch (error) {\n throw new AuthError(\n 'Failed to obtain access token',\n error instanceof Error ? error : undefined,\n );\n }\n}\n","import type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationServer } from '@walkeros/server-core';\nimport type { OAuth2Client } from 'google-auth-library';\n\nexport interface Settings {\n /**\n * Service account credentials (client_email + private_key)\n * Recommended for serverless environments (AWS Lambda, Docker, etc.)\n */\n credentials?: {\n client_email: string;\n private_key: string;\n };\n\n /**\n * Path to service account JSON file\n * For local development or environments with filesystem access\n */\n keyFilename?: string;\n\n /**\n * OAuth scopes for Data Manager API\n * @default ['https://www.googleapis.com/auth/datamanager']\n */\n scopes?: string[];\n\n /** Array of destination accounts and conversion actions/user lists */\n destinations: Destination[];\n\n /** Event source for all events. Defaults to WEB if not specified */\n eventSource?: EventSource;\n\n /** Maximum number of events to batch before sending (max 2000) */\n batchSize?: number;\n\n /** Time in milliseconds to wait before auto-flushing batch */\n batchInterval?: number;\n\n /** If true, validate request without ingestion (testing mode) */\n validateOnly?: boolean;\n\n /** Override API endpoint (for testing) */\n url?: string;\n\n /** Request-level consent for all events */\n consent?: Consent;\n\n /** Test event code for debugging (optional) */\n testEventCode?: string;\n\n /** Guided helpers: User data mapping (applies to all events) */\n userData?: WalkerOSMapping.Map;\n\n /** Guided helper: First-party user ID */\n userId?: WalkerOSMapping.Value;\n\n /** Guided helper: GA4 client ID */\n clientId?: WalkerOSMapping.Value;\n\n /** Guided helper: Privacy-safe attribution (Google's sessionAttributes) */\n sessionAttributes?: WalkerOSMapping.Value;\n\n /** Consent mapping: Map consent field to adUserData (string = field name, boolean = static value) */\n consentAdUserData?: string | boolean;\n\n /** Consent mapping: Map consent field to adPersonalization (string = field name, boolean = static value) */\n consentAdPersonalization?: string | boolean;\n}\n\nexport interface Mapping {\n // Attribution identifiers (optional, for explicit mapping)\n gclid?: WalkerOSMapping.Value;\n gbraid?: WalkerOSMapping.Value;\n wbraid?: WalkerOSMapping.Value;\n sessionAttributes?: WalkerOSMapping.Value;\n}\n\nexport interface Env extends DestinationServer.Env {\n fetch?: typeof fetch;\n authClient?: OAuth2Client | null;\n}\n\nexport type InitSettings = Partial<Settings>;\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface DestinationInterface\n extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\n/**\n * Config after validation - settings is guaranteed to exist with required fields\n * Use this type after calling getConfig() to get proper type narrowing\n * After validation, eventSource is always set (defaults to 'WEB')\n */\nexport type ValidatedConfig = Omit<Config, 'settings'> & {\n settings: Settings & { eventSource: EventSource };\n};\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\n\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\n\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\n\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n\n// Google Data Manager API Types\n// https://developers.google.com/data-manager/api/reference\n\n/**\n * Destination account and product identifier\n * https://developers.google.com/data-manager/api/reference/rest/v1/Destination\n */\nexport interface Destination {\n /** Reference identifier for this destination */\n reference?: string;\n\n /** Login account (account initiating the request) */\n loginAccount?: ProductAccount;\n\n /** Linked account (child account linked to login account) */\n linkedAccount?: ProductAccount;\n\n /** Operating account (account where data is sent) */\n operatingAccount?: ProductAccount;\n\n /** Product-specific destination ID (conversion action or user list) */\n productDestinationId?: string;\n}\n\n/**\n * Product account information\n */\nexport interface ProductAccount {\n /** Account ID (e.g., \"123-456-7890\" for Google Ads) */\n accountId: string;\n\n /** Type of account */\n accountType: AccountType;\n}\n\nexport type AccountType =\n | 'ACCOUNT_TYPE_UNSPECIFIED'\n | 'GOOGLE_ADS'\n | 'DISPLAY_VIDEO_ADVERTISER'\n | 'DISPLAY_VIDEO_PARTNER'\n | 'GOOGLE_ANALYTICS_PROPERTY'\n | 'DATA_PARTNER';\n\nexport type EventSource = 'WEB' | 'APP' | 'IN_STORE' | 'PHONE' | 'OTHER';\n\n/**\n * Consent for Digital Markets Act (DMA) compliance\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n */\nexport interface Consent {\n /** Consent for data collection and use */\n adUserData?: ConsentStatus;\n\n /** Consent for ad personalization */\n adPersonalization?: ConsentStatus;\n}\n\nexport type ConsentStatus = 'CONSENT_GRANTED' | 'CONSENT_DENIED';\n\n/**\n * Request body for events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/events/ingest\n */\nexport interface IngestEventsRequest {\n /** Array of destinations for these events (max 10) */\n destinations: Destination[];\n\n /** Array of events to ingest (max 2000) */\n events: Event[];\n\n /** Request-level consent (overridden by event-level) */\n consent?: Consent;\n\n /** If true, validate without ingestion */\n validateOnly?: boolean;\n\n /** Test event code for debugging */\n testEventCode?: string;\n}\n\n/**\n * Single event for ingestion\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport interface Event {\n /** Destination references for routing */\n destinationReferences?: string[];\n\n /** Transaction ID for deduplication (max 512 chars) */\n transactionId?: string;\n\n /** Event timestamp in RFC 3339 format */\n eventTimestamp?: string;\n\n /** Last updated timestamp in RFC 3339 format */\n lastUpdatedTimestamp?: string;\n\n /** User data with identifiers (max 10 identifiers) */\n userData?: UserData;\n\n /** Event-level consent (overrides request-level) */\n consent?: Consent;\n\n /** Attribution identifiers */\n adIdentifiers?: AdIdentifiers;\n\n /** Currency code (ISO 4217, 3 chars) */\n currency?: string;\n\n /** Conversion value */\n conversionValue?: number;\n\n /** Source of the event */\n eventSource?: EventSource;\n\n /** Device information for the event */\n eventDeviceInfo?: DeviceInfo;\n\n /** Shopping cart data */\n cartData?: CartData;\n\n /** Custom variables for the event */\n customVariables?: CustomVariable[];\n\n /** Experimental fields (subject to change) */\n experimentalFields?: ExperimentalField[];\n\n /** User properties */\n userProperties?: UserProperties;\n\n /** Event name for GA4 (max 40 chars, required for GA4) */\n eventName?: string;\n\n /** Google Analytics client ID (max 255 chars) */\n clientId?: string;\n\n /** First-party user ID (max 256 chars) */\n userId?: string;\n\n /** Additional event parameters */\n additionalEventParameters?: EventParameter[];\n}\n\n/**\n * Device information\n */\nexport interface DeviceInfo {\n /** User agent string */\n userAgent?: string;\n}\n\n/**\n * Custom variable\n */\nexport interface CustomVariable {\n /** Variable name */\n name?: string;\n\n /** Variable value */\n value?: string;\n}\n\n/**\n * Experimental field\n */\nexport interface ExperimentalField {\n /** Field name */\n name?: string;\n\n /** Field value */\n value?: string;\n}\n\n/**\n * User properties\n */\nexport interface UserProperties {\n /** Property values */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Event parameter\n */\nexport interface EventParameter {\n /** Parameter name */\n name?: string;\n\n /** Parameter value */\n value?: string | number;\n}\n\n/**\n * User data with identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n */\nexport interface UserData {\n /** Array of user identifiers (max 10) */\n userIdentifiers: UserIdentifier[];\n}\n\n/**\n * User identifier (email, phone, or address)\n */\nexport type UserIdentifier =\n | { emailAddress: string }\n | { phoneNumber: string }\n | { address: Address };\n\n/**\n * Address for user identification\n * https://developers.google.com/data-manager/api/reference/rest/v1/Address\n */\nexport interface Address {\n /** Given name (first name) - SHA-256 hashed */\n givenName?: string;\n\n /** Family name (last name) - SHA-256 hashed */\n familyName?: string;\n\n /** ISO-3166-1 alpha-2 country code - NOT hashed (e.g., \"US\", \"GB\") */\n regionCode?: string;\n\n /** Postal code - NOT hashed */\n postalCode?: string;\n}\n\n/**\n * Attribution identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n */\nexport interface AdIdentifiers {\n /** Session attributes (privacy-safe attribution) */\n sessionAttributes?: string;\n\n /** Google Click ID (primary attribution) */\n gclid?: string;\n\n /** iOS attribution identifier (post-ATT) */\n gbraid?: string;\n\n /** Web-to-app attribution identifier */\n wbraid?: string;\n\n /** Device information for landing page */\n landingPageDeviceInfo?: DeviceInfo;\n}\n\n/**\n * Shopping cart data\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartData\n */\nexport interface CartData {\n /** Array of cart items (max 200) */\n items: CartItem[];\n}\n\n/**\n * Single cart item\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartItem\n */\nexport interface CartItem {\n /** Merchant product ID (max 127 chars) */\n merchantProductId?: string;\n\n /** Item price */\n price?: number;\n\n /** Item quantity */\n quantity?: number;\n}\n\n/**\n * Response from events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/IngestEventsResponse\n */\nexport interface IngestEventsResponse {\n /** Unique request ID for status checking */\n requestId: string;\n\n /** Validation errors (only if validateOnly=true) */\n validationErrors?: ValidationError[];\n}\n\n/**\n * Validation error\n */\nexport interface ValidationError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Field path that caused the error */\n fieldPath?: string;\n}\n\n/**\n * Request status response\n * https://developers.google.com/data-manager/api/reference/rest/v1/requestStatus/retrieve\n */\nexport interface RequestStatusResponse {\n /** Unique request ID */\n requestId: string;\n\n /** Processing state */\n state: RequestState;\n\n /** Number of events successfully ingested */\n eventsIngested?: number;\n\n /** Number of events that failed */\n eventsFailed?: number;\n\n /** Array of errors (if any) */\n errors?: RequestError[];\n}\n\nexport type RequestState =\n | 'STATE_UNSPECIFIED'\n | 'PENDING'\n | 'PROCESSING'\n | 'SUCCEEDED'\n | 'FAILED'\n | 'PARTIALLY_SUCCEEDED';\n\n/**\n * Request error\n */\nexport interface RequestError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Number of events affected by this error */\n eventCount?: number;\n}\n"],"mappings":"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","names":["import_core","import_core","auth"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/push.ts","../src/format.ts","../src/hash.ts","../src/auth.ts","../src/types/index.ts"],"sourcesContent":["import type { DestinationInterface } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\nimport { createAuthClient } from './auth';\n\nexport * as DestinationDataManager from './types';\n\nexport const destinationDataManager: DestinationInterface = {\n type: 'datamanager',\n\n config: {},\n\n async init({ config: partialConfig, env, logger }) {\n // getConfig validates required fields and returns ValidatedConfig\n const config = getConfig(partialConfig, logger);\n\n try {\n const authClient = await createAuthClient(config.settings);\n logger.debug('Auth client created');\n\n return {\n ...config,\n env: {\n ...env,\n authClient,\n },\n };\n } catch (error) {\n logger.throw(\n `Data Manager authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n },\n\n async push(event, context) {\n return await push(event, context);\n },\n};\n\nexport default destinationDataManager;\n","import type {\n ValidatedConfig,\n Settings,\n PartialConfig,\n EventSource,\n} from './types';\nimport type { Logger } from '@walkeros/core';\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): ValidatedConfig {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { destinations, eventSource = 'WEB' } = settings;\n\n if (!destinations || destinations.length === 0)\n logger.throw('Config settings destinations missing or empty');\n\n const settingsConfig: Settings & { eventSource: EventSource } = {\n ...settings,\n destinations,\n eventSource,\n };\n\n return { ...partialConfig, settings: settingsConfig };\n}\n","import type { PushFn } from './types';\nimport type { IngestEventsRequest, IngestEventsResponse } from './types';\nimport { getMappingValue, isObject } from '@walkeros/core';\nimport { formatEvent, formatConsent } from './format';\nimport { getAccessToken } from './auth';\nimport { getConfig } from './config';\n\nexport const push: PushFn = async function (\n event,\n { config, rule, data, collector, env, logger },\n) {\n // Validate config and get typed settings\n const validatedConfig = getConfig(config, logger);\n const {\n destinations,\n eventSource,\n validateOnly = false,\n url = 'https://datamanager.googleapis.com/v1',\n consent: requestConsent,\n testEventCode,\n userData,\n userId,\n clientId,\n sessionAttributes,\n consentAdUserData,\n consentAdPersonalization,\n } = validatedConfig.settings;\n\n // Extract Settings guided helpers\n const userDataMapped = userData\n ? await getMappingValue(event, { map: userData })\n : {};\n const userIdMapped = userId\n ? await getMappingValue(event, userId)\n : undefined;\n const clientIdMapped = clientId\n ? await getMappingValue(event, clientId)\n : undefined;\n const sessionAttributesMapped = sessionAttributes\n ? await getMappingValue(event, sessionAttributes)\n : undefined;\n\n // Extract consent from Settings\n const consentAdUserDataValue =\n typeof consentAdUserData === 'boolean'\n ? consentAdUserData\n : typeof consentAdUserData === 'string' && event.consent\n ? event.consent[consentAdUserData]\n : undefined;\n\n const consentAdPersonalizationValue =\n typeof consentAdPersonalization === 'boolean'\n ? consentAdPersonalization\n : typeof consentAdPersonalization === 'string' && event.consent\n ? event.consent[consentAdPersonalization]\n : undefined;\n\n // Build Settings helpers object\n const settingsHelpers: Record<string, unknown> = {};\n if (isObject(userDataMapped)) {\n Object.assign(settingsHelpers, userDataMapped);\n }\n if (userIdMapped !== undefined) settingsHelpers.userId = userIdMapped;\n if (clientIdMapped !== undefined) settingsHelpers.clientId = clientIdMapped;\n if (sessionAttributesMapped !== undefined)\n settingsHelpers.sessionAttributes = sessionAttributesMapped;\n if (consentAdUserDataValue !== undefined)\n settingsHelpers.adUserData = consentAdUserDataValue;\n if (consentAdPersonalizationValue !== undefined)\n settingsHelpers.adPersonalization = consentAdPersonalizationValue;\n\n // Get mapped data from destination config and event mapping\n const configData = validatedConfig.data\n ? await getMappingValue(event, validatedConfig.data)\n : {};\n const eventData = isObject(data) ? data : {};\n\n // Merge: Settings helpers < config.data < event mapping data\n const finalData = {\n ...settingsHelpers,\n ...(isObject(configData) ? configData : {}),\n ...eventData,\n };\n\n // Format event for Data Manager API\n const dataManagerEvent = await formatEvent(event, finalData);\n\n // Apply event source from settings (required)\n if (!dataManagerEvent.eventSource) {\n dataManagerEvent.eventSource = eventSource;\n }\n\n // Apply request-level consent if event doesn't have consent\n if (!dataManagerEvent.consent && requestConsent) {\n dataManagerEvent.consent = requestConsent;\n }\n\n // Validate required fields before API call\n if (!dataManagerEvent.transactionId) {\n logger.throw('transactionId is required');\n }\n\n // Check if any destination is GA4 (requires eventName)\n const hasGA4Destination = destinations.some(\n (d) => d.operatingAccount?.accountType === 'GOOGLE_ANALYTICS_PROPERTY',\n );\n\n if (hasGA4Destination && !dataManagerEvent.eventName) {\n logger.throw('eventName is required for GA4 destinations');\n }\n\n // Build API request\n const requestBody: IngestEventsRequest = {\n events: [dataManagerEvent],\n destinations,\n };\n\n // Add optional parameters\n if (requestConsent) {\n requestBody.consent = requestConsent;\n }\n\n if (validateOnly) {\n requestBody.validateOnly = true;\n }\n\n if (testEventCode) {\n requestBody.testEventCode = testEventCode;\n }\n\n const authClient = env?.authClient;\n if (!authClient) {\n return logger.throw(\n 'Auth client not initialized. Ensure init() was called successfully.',\n );\n }\n\n let accessToken: string;\n try {\n accessToken = await getAccessToken(authClient);\n } catch (error) {\n logger.error('Authentication failed', { error });\n throw error;\n }\n\n const fetchFn = env?.fetch || fetch;\n const endpoint = `${url}/events:ingest`;\n\n logger.debug('Sending to Data Manager API', {\n endpoint,\n eventCount: requestBody.events.length,\n destinations: destinations.length,\n validateOnly,\n });\n\n const response = await fetchFn(endpoint, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n logger.throw(`Data Manager API error (${response.status}): ${errorText}`);\n }\n\n const result: IngestEventsResponse = await response.json();\n\n logger.debug('API response', {\n status: response.status,\n requestId: result.requestId,\n });\n\n // If validation errors exist, throw them\n if (result.validationErrors && result.validationErrors.length > 0) {\n logger.throw(\n `Validation errors: ${JSON.stringify(result.validationErrors)}`,\n );\n }\n};\n","import type { WalkerOS } from '@walkeros/core';\nimport { isString, isDefined } from '@walkeros/core';\nimport type {\n Event,\n UserData,\n UserIdentifier,\n AdIdentifiers,\n Consent,\n ConsentStatus,\n} from './types';\nimport { hashEmail, hashPhone, hashName } from './hash';\n\n/**\n * Format walkerOS event timestamp to RFC 3339 format\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n *\n * walkerOS timestamp is in milliseconds, RFC 3339 format: \"2024-01-15T10:30:00Z\"\n */\nexport function formatTimestamp(timestamp: number): string {\n return new Date(timestamp).toISOString();\n}\n\n/**\n * Format user identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n *\n * User data must be explicitly mapped in the mapping configuration.\n * Max 10 identifiers per event\n */\nexport async function formatUserData(\n data: Record<string, unknown>,\n): Promise<UserData | undefined> {\n const identifiers: UserIdentifier[] = [];\n\n // Extract from mapped data only\n // Email\n if (isString(data.email) && data.email) {\n const hashedEmail = await hashEmail(data.email);\n if (hashedEmail) {\n identifiers.push({ emailAddress: hashedEmail });\n }\n }\n\n // Phone\n if (isString(data.phone) && data.phone) {\n const hashedPhone = await hashPhone(data.phone);\n if (hashedPhone) {\n identifiers.push({ phoneNumber: hashedPhone });\n }\n }\n\n // Address from mapped properties\n const hasAddress =\n data.firstName || data.lastName || data.regionCode || data.postalCode;\n\n if (hasAddress) {\n const address: Record<string, string> = {};\n\n if (isString(data.firstName) && data.firstName) {\n address.givenName = await hashName(data.firstName, 'given');\n }\n\n if (isString(data.lastName) && data.lastName) {\n address.familyName = await hashName(data.lastName, 'family');\n }\n\n // Region code is NOT hashed\n if (isString(data.regionCode) && data.regionCode) {\n address.regionCode = data.regionCode.toUpperCase();\n }\n\n // Postal code is NOT hashed\n if (isString(data.postalCode) && data.postalCode) {\n address.postalCode = data.postalCode;\n }\n\n if (Object.keys(address).length > 0) {\n identifiers.push({ address });\n }\n }\n\n // Limit to 10 identifiers\n if (identifiers.length === 0) return undefined;\n\n return {\n userIdentifiers: identifiers.slice(0, 10),\n };\n}\n\n/**\n * Extract and format attribution identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n *\n * Attribution identifiers should be mapped explicitly in the mapping configuration.\n * Example: { gclid: 'context.gclid', gbraid: 'context.gbraid' }\n */\nexport function formatAdIdentifiers(\n data: Record<string, unknown>,\n): AdIdentifiers | undefined {\n const identifiers: AdIdentifiers = {};\n\n // Extract from mapped data (already processed by mapping system)\n if (isString(data.gclid) && data.gclid) {\n identifiers.gclid = data.gclid;\n }\n if (isString(data.gbraid) && data.gbraid) {\n identifiers.gbraid = data.gbraid;\n }\n if (isString(data.wbraid) && data.wbraid) {\n identifiers.wbraid = data.wbraid;\n }\n if (isString(data.sessionAttributes) && data.sessionAttributes) {\n identifiers.sessionAttributes = data.sessionAttributes;\n }\n\n return Object.keys(identifiers).length > 0 ? identifiers : undefined;\n}\n\n/**\n * Map walkerOS consent to Data Manager consent format\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n *\n * walkerOS: { marketing: true, personalization: false }\n * Data Manager: { adUserData: 'CONSENT_GRANTED', adPersonalization: 'CONSENT_DENIED' }\n */\nexport function formatConsent(\n walkerOSConsent: WalkerOS.Consent | undefined,\n): Consent | undefined {\n if (!walkerOSConsent) return undefined;\n\n const consent: Consent = {};\n\n // Map marketing consent to adUserData\n if (isDefined(walkerOSConsent.marketing)) {\n consent.adUserData = walkerOSConsent.marketing\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // Map personalization consent to adPersonalization\n if (isDefined(walkerOSConsent.personalization)) {\n consent.adPersonalization = walkerOSConsent.personalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n return Object.keys(consent).length > 0 ? consent : undefined;\n}\n\n/**\n * Format complete event for Data Manager API\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport async function formatEvent(\n event: WalkerOS.Event,\n mappedData?: Record<string, unknown>,\n): Promise<Event> {\n const dataManagerEvent: Event = {\n eventTimestamp: formatTimestamp(event.timestamp),\n };\n\n // Use only mapped data (no fallback to event.data)\n const data = mappedData || {};\n\n // Transaction ID for deduplication\n if (isString(data.transactionId) && data.transactionId) {\n dataManagerEvent.transactionId = data.transactionId.substring(0, 512);\n }\n\n // Client ID (GA)\n if (isString(data.clientId) && data.clientId) {\n dataManagerEvent.clientId = data.clientId.substring(0, 255);\n }\n\n // User ID\n if (isString(data.userId) && data.userId) {\n dataManagerEvent.userId = data.userId.substring(0, 256);\n }\n\n // User data\n const userData = await formatUserData(data);\n if (userData) {\n dataManagerEvent.userData = userData;\n }\n\n // Attribution identifiers\n const adIdentifiers = formatAdIdentifiers(data);\n if (adIdentifiers) {\n dataManagerEvent.adIdentifiers = adIdentifiers;\n }\n\n // Conversion value\n if (typeof data.conversionValue === 'number') {\n dataManagerEvent.conversionValue = data.conversionValue;\n }\n\n // Currency\n if (isString(data.currency) && data.currency) {\n dataManagerEvent.currency = data.currency.substring(0, 3).toUpperCase();\n }\n\n // Cart data\n if (data.cartData && typeof data.cartData === 'object') {\n dataManagerEvent.cartData = data.cartData as Event['cartData'];\n }\n\n // Event name (for GA4)\n if (isString(data.eventName) && data.eventName) {\n dataManagerEvent.eventName = data.eventName.substring(0, 40);\n }\n\n // Event source\n if (isString(data.eventSource) && data.eventSource) {\n dataManagerEvent.eventSource = data.eventSource as Event['eventSource'];\n }\n\n // Consent - check mapped data first, then fallback to event.consent\n const mappedConsent: Consent = {};\n\n // Check for mapped consent values (from Settings or event mapping)\n if (typeof data.adUserData === 'boolean') {\n mappedConsent.adUserData = data.adUserData\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n if (typeof data.adPersonalization === 'boolean') {\n mappedConsent.adPersonalization = data.adPersonalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // If no mapped consent, fall back to event.consent\n if (Object.keys(mappedConsent).length === 0) {\n const eventConsent = formatConsent(event.consent);\n if (eventConsent) {\n dataManagerEvent.consent = eventConsent;\n }\n } else {\n dataManagerEvent.consent = mappedConsent;\n }\n\n return dataManagerEvent;\n}\n","import { isString } from '@walkeros/core';\nimport { getHashServer } from '@walkeros/server-core';\n\n/**\n * Normalize email address according to Google Data Manager requirements\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#email\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove dots (.) for gmail.com and googlemail.com\n * 4. SHA-256 hash\n */\nexport async function hashEmail(email: string): Promise<string> {\n if (!isString(email) || !email) return '';\n\n // Trim and lowercase\n let normalized = email.trim().toLowerCase();\n\n // Remove dots for Gmail addresses\n if (\n normalized.endsWith('@gmail.com') ||\n normalized.endsWith('@googlemail.com')\n ) {\n const [localPart, domain] = normalized.split('@');\n normalized = `${localPart.replace(/\\./g, '')}@${domain}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize phone number to E.164 format and hash\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#phone\n *\n * E.164 format: +[country code][number] (max 15 digits after +)\n * Example: +18005550100\n *\n * 1. Remove all non-digit characters except leading +\n * 2. Ensure it starts with +\n * 3. SHA-256 hash\n */\nexport async function hashPhone(phone: string): Promise<string> {\n if (!isString(phone) || !phone) return '';\n\n // Remove all non-digit characters except + at the start\n let normalized = phone.trim();\n\n // Extract country code if present\n const hasPlus = normalized.startsWith('+');\n\n // Remove all non-digits\n normalized = normalized.replace(/\\D/g, '');\n\n // Add + prefix if it was there or if number is long enough\n if (hasPlus || normalized.length > 10) {\n normalized = `+${normalized}`;\n } else {\n // Assume US number if no country code (default behavior)\n normalized = `+1${normalized}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize and hash a name (first or last name)\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#name\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove common prefixes (Mr., Mrs., Dr.) for first names\n * 4. Remove common suffixes (Jr., Sr., III) for last names\n * 5. SHA-256 hash\n */\nexport async function hashName(\n name: string,\n type: 'given' | 'family' = 'given',\n): Promise<string> {\n if (!isString(name) || !name) return '';\n\n // Trim and lowercase\n let normalized = name.trim().toLowerCase();\n\n // Remove prefixes for given names\n if (type === 'given') {\n const prefixes = ['mr.', 'mrs.', 'ms.', 'miss.', 'dr.', 'prof.'];\n for (const prefix of prefixes) {\n if (normalized.startsWith(prefix)) {\n normalized = normalized.substring(prefix.length).trim();\n break;\n }\n }\n }\n\n // Remove suffixes for family names (check with and without space)\n // Sort by length (longest first) to match \"iii\" before \"ii\"\n if (type === 'family') {\n const suffixes = ['jr.', 'sr.', 'iii', 'ii', 'iv', 'v'];\n for (const suffix of suffixes) {\n // Check for suffix with space before it (e.g., \" jr.\")\n const suffixWithSpace = ` ${suffix}`;\n if (normalized.endsWith(suffixWithSpace)) {\n normalized = normalized\n .substring(0, normalized.length - suffixWithSpace.length)\n .trim();\n break;\n }\n // Check for suffix without space\n if (normalized.endsWith(suffix)) {\n normalized = normalized\n .substring(0, normalized.length - suffix.length)\n .trim();\n break;\n }\n }\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Hash multiple email addresses\n */\nexport async function hashEmails(emails: string[]): Promise<string[]> {\n return Promise.all(emails.map((email) => hashEmail(email)));\n}\n\n/**\n * Hash multiple phone numbers\n */\nexport async function hashPhones(phones: string[]): Promise<string[]> {\n return Promise.all(phones.map((phone) => hashPhone(phone)));\n}\n","import { GoogleAuth, type OAuth2Client } from 'google-auth-library';\nimport type { Settings } from './types';\n\nconst DEFAULT_SCOPES = ['https://www.googleapis.com/auth/datamanager'];\n\n/**\n * Authentication error with cause tracking\n */\nexport class AuthError extends Error {\n constructor(\n message: string,\n public cause?: Error,\n ) {\n super(message);\n this.name = 'DataManagerAuthError';\n }\n}\n\n/**\n * Creates Google Auth client based on settings\n *\n * Authentication priority:\n * 1. credentials (inline service account) - if provided\n * 2. keyFilename (service account file) - if provided\n * 3. Application Default Credentials (ADC) - automatic fallback\n * - GOOGLE_APPLICATION_CREDENTIALS env var\n * - GCP metadata server (Cloud Functions, Cloud Run, GCE)\n *\n * @param settings - Configuration with auth options\n * @returns OAuth2Client for token retrieval\n * @throws AuthError if authentication fails\n */\nexport async function createAuthClient(\n settings: Settings,\n): Promise<OAuth2Client> {\n const { credentials, keyFilename, scopes = DEFAULT_SCOPES } = settings;\n\n try {\n if (credentials) {\n const auth = new GoogleAuth({\n credentials,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n if (keyFilename) {\n const auth = new GoogleAuth({\n keyFilename,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n const auth = new GoogleAuth({ scopes });\n return (await auth.getClient()) as OAuth2Client;\n } catch (error) {\n throw new AuthError(\n 'Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.',\n error instanceof Error ? error : undefined,\n );\n }\n}\n\n/**\n * Gets access token from auth client\n * Automatically returns cached token if valid or refreshes if expired\n *\n * @param authClient - OAuth2 client from createAuthClient()\n * @returns Fresh access token\n * @throws AuthError if token retrieval fails\n */\nexport async function getAccessToken(\n authClient: OAuth2Client,\n): Promise<string> {\n try {\n const tokenResponse = await authClient.getAccessToken();\n\n if (!tokenResponse.token) {\n throw new AuthError('Auth client returned empty token');\n }\n\n return tokenResponse.token;\n } catch (error) {\n throw new AuthError(\n 'Failed to obtain access token',\n error instanceof Error ? error : undefined,\n );\n }\n}\n","import type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationServer } from '@walkeros/server-core';\nimport type { OAuth2Client } from 'google-auth-library';\n\nexport interface Settings {\n /**\n * Service account credentials (client_email + private_key)\n * Recommended for serverless environments (AWS Lambda, Docker, etc.)\n */\n credentials?: {\n client_email: string;\n private_key: string;\n };\n\n /**\n * Path to service account JSON file\n * For local development or environments with filesystem access\n */\n keyFilename?: string;\n\n /**\n * OAuth scopes for Data Manager API\n * @default ['https://www.googleapis.com/auth/datamanager']\n */\n scopes?: string[];\n\n /** Array of destination accounts and conversion actions/user lists */\n destinations: Destination[];\n\n /** Event source for all events. Defaults to WEB if not specified */\n eventSource?: EventSource;\n\n /** Maximum number of events to batch before sending (max 2000) */\n batchSize?: number;\n\n /** Time in milliseconds to wait before auto-flushing batch */\n batchInterval?: number;\n\n /** If true, validate request without ingestion (testing mode) */\n validateOnly?: boolean;\n\n /** Override API endpoint (for testing) */\n url?: string;\n\n /** Request-level consent for all events */\n consent?: Consent;\n\n /** Test event code for debugging (optional) */\n testEventCode?: string;\n\n /** Guided helpers: User data mapping (applies to all events) */\n userData?: WalkerOSMapping.Map;\n\n /** Guided helper: First-party user ID */\n userId?: WalkerOSMapping.Value;\n\n /** Guided helper: GA4 client ID */\n clientId?: WalkerOSMapping.Value;\n\n /** Guided helper: Privacy-safe attribution (Google's sessionAttributes) */\n sessionAttributes?: WalkerOSMapping.Value;\n\n /** Consent mapping: Map consent field to adUserData (string = field name, boolean = static value) */\n consentAdUserData?: string | boolean;\n\n /** Consent mapping: Map consent field to adPersonalization (string = field name, boolean = static value) */\n consentAdPersonalization?: string | boolean;\n}\n\nexport interface Mapping {\n // Attribution identifiers (optional, for explicit mapping)\n gclid?: WalkerOSMapping.Value;\n gbraid?: WalkerOSMapping.Value;\n wbraid?: WalkerOSMapping.Value;\n sessionAttributes?: WalkerOSMapping.Value;\n}\n\nexport interface Env extends DestinationServer.Env {\n fetch?: typeof fetch;\n authClient?: OAuth2Client | null;\n}\n\nexport type InitSettings = Partial<Settings>;\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface DestinationInterface\n extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\n/**\n * Config after validation - settings is guaranteed to exist with required fields\n * Use this type after calling getConfig() to get proper type narrowing\n * After validation, eventSource is always set (defaults to 'WEB')\n */\nexport type ValidatedConfig = Omit<Config, 'settings'> & {\n settings: Settings & { eventSource: EventSource };\n};\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\n\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\n\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\n\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n\n// Google Data Manager API Types\n// https://developers.google.com/data-manager/api/reference\n\n/**\n * Destination account and product identifier\n * https://developers.google.com/data-manager/api/reference/rest/v1/Destination\n */\nexport interface Destination {\n /** Reference identifier for this destination */\n reference?: string;\n\n /** Login account (account initiating the request) */\n loginAccount?: ProductAccount;\n\n /** Linked account (child account linked to login account) */\n linkedAccount?: ProductAccount;\n\n /** Operating account (account where data is sent) */\n operatingAccount?: ProductAccount;\n\n /** Product-specific destination ID (conversion action or user list) */\n productDestinationId?: string;\n}\n\n/**\n * Product account information\n */\nexport interface ProductAccount {\n /** Account ID (e.g., \"123-456-7890\" for Google Ads) */\n accountId: string;\n\n /** Type of account */\n accountType: AccountType;\n}\n\nexport type AccountType =\n | 'ACCOUNT_TYPE_UNSPECIFIED'\n | 'GOOGLE_ADS'\n | 'DISPLAY_VIDEO_ADVERTISER'\n | 'DISPLAY_VIDEO_PARTNER'\n | 'GOOGLE_ANALYTICS_PROPERTY'\n | 'DATA_PARTNER';\n\nexport type EventSource = 'WEB' | 'APP' | 'IN_STORE' | 'PHONE' | 'OTHER';\n\n/**\n * Consent for Digital Markets Act (DMA) compliance\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n */\nexport interface Consent {\n /** Consent for data collection and use */\n adUserData?: ConsentStatus;\n\n /** Consent for ad personalization */\n adPersonalization?: ConsentStatus;\n}\n\nexport type ConsentStatus = 'CONSENT_GRANTED' | 'CONSENT_DENIED';\n\n/**\n * Request body for events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/events/ingest\n */\nexport interface IngestEventsRequest {\n /** Array of destinations for these events (max 10) */\n destinations: Destination[];\n\n /** Array of events to ingest (max 2000) */\n events: Event[];\n\n /** Request-level consent (overridden by event-level) */\n consent?: Consent;\n\n /** If true, validate without ingestion */\n validateOnly?: boolean;\n\n /** Test event code for debugging */\n testEventCode?: string;\n}\n\n/**\n * Single event for ingestion\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport interface Event {\n /** Destination references for routing */\n destinationReferences?: string[];\n\n /** Transaction ID for deduplication (max 512 chars) */\n transactionId?: string;\n\n /** Event timestamp in RFC 3339 format */\n eventTimestamp?: string;\n\n /** Last updated timestamp in RFC 3339 format */\n lastUpdatedTimestamp?: string;\n\n /** User data with identifiers (max 10 identifiers) */\n userData?: UserData;\n\n /** Event-level consent (overrides request-level) */\n consent?: Consent;\n\n /** Attribution identifiers */\n adIdentifiers?: AdIdentifiers;\n\n /** Currency code (ISO 4217, 3 chars) */\n currency?: string;\n\n /** Conversion value */\n conversionValue?: number;\n\n /** Source of the event */\n eventSource?: EventSource;\n\n /** Device information for the event */\n eventDeviceInfo?: DeviceInfo;\n\n /** Shopping cart data */\n cartData?: CartData;\n\n /** Custom variables for the event */\n customVariables?: CustomVariable[];\n\n /** Experimental fields (subject to change) */\n experimentalFields?: ExperimentalField[];\n\n /** User properties */\n userProperties?: UserProperties;\n\n /** Event name for GA4 (max 40 chars, required for GA4) */\n eventName?: string;\n\n /** Google Analytics client ID (max 255 chars) */\n clientId?: string;\n\n /** First-party user ID (max 256 chars) */\n userId?: string;\n\n /** Additional event parameters */\n additionalEventParameters?: EventParameter[];\n}\n\n/**\n * Device information\n */\nexport interface DeviceInfo {\n /** User agent string */\n userAgent?: string;\n}\n\n/**\n * Custom variable\n */\nexport interface CustomVariable {\n /** Variable name */\n name?: string;\n\n /** Variable value */\n value?: string;\n}\n\n/**\n * Experimental field\n */\nexport interface ExperimentalField {\n /** Field name */\n name?: string;\n\n /** Field value */\n value?: string;\n}\n\n/**\n * User properties\n */\nexport interface UserProperties {\n /** Property values */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Event parameter\n */\nexport interface EventParameter {\n /** Parameter name */\n name?: string;\n\n /** Parameter value */\n value?: string | number;\n}\n\n/**\n * User data with identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n */\nexport interface UserData {\n /** Array of user identifiers (max 10) */\n userIdentifiers: UserIdentifier[];\n}\n\n/**\n * User identifier (email, phone, or address)\n */\nexport type UserIdentifier =\n | { emailAddress: string }\n | { phoneNumber: string }\n | { address: Address };\n\n/**\n * Address for user identification\n * https://developers.google.com/data-manager/api/reference/rest/v1/Address\n */\nexport interface Address {\n /** Given name (first name) - SHA-256 hashed */\n givenName?: string;\n\n /** Family name (last name) - SHA-256 hashed */\n familyName?: string;\n\n /** ISO-3166-1 alpha-2 country code - NOT hashed (e.g., \"US\", \"GB\") */\n regionCode?: string;\n\n /** Postal code - NOT hashed */\n postalCode?: string;\n}\n\n/**\n * Attribution identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n */\nexport interface AdIdentifiers {\n /** Session attributes (privacy-safe attribution) */\n sessionAttributes?: string;\n\n /** Google Click ID (primary attribution) */\n gclid?: string;\n\n /** iOS attribution identifier (post-ATT) */\n gbraid?: string;\n\n /** Web-to-app attribution identifier */\n wbraid?: string;\n\n /** Device information for landing page */\n landingPageDeviceInfo?: DeviceInfo;\n}\n\n/**\n * Shopping cart data\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartData\n */\nexport interface CartData {\n /** Array of cart items (max 200) */\n items: CartItem[];\n}\n\n/**\n * Single cart item\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartItem\n */\nexport interface CartItem {\n /** Merchant product ID (max 127 chars) */\n merchantProductId?: string;\n\n /** Item price */\n price?: number;\n\n /** Item quantity */\n quantity?: number;\n}\n\n/**\n * Response from events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/IngestEventsResponse\n */\nexport interface IngestEventsResponse {\n /** Unique request ID for status checking */\n requestId: string;\n\n /** Validation errors (only if validateOnly=true) */\n validationErrors?: ValidationError[];\n}\n\n/**\n * Validation error\n */\nexport interface ValidationError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Field path that caused the error */\n fieldPath?: string;\n}\n\n/**\n * Request status response\n * https://developers.google.com/data-manager/api/reference/rest/v1/requestStatus/retrieve\n */\nexport interface RequestStatusResponse {\n /** Unique request ID */\n requestId: string;\n\n /** Processing state */\n state: RequestState;\n\n /** Number of events successfully ingested */\n eventsIngested?: number;\n\n /** Number of events that failed */\n eventsFailed?: number;\n\n /** Array of errors (if any) */\n errors?: RequestError[];\n}\n\nexport type RequestState =\n | 'STATE_UNSPECIFIED'\n | 'PENDING'\n | 'PROCESSING'\n | 'SUCCEEDED'\n | 'FAILED'\n | 'PARTIALLY_SUCCEEDED';\n\n/**\n * Request error\n */\nexport interface RequestError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Number of events affected by this error */\n eventCount?: number;\n}\n"],"mappings":"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","names":["import_core","import_core","auth"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function getConfig(partialConfig={},logger){const settings=partialConfig.settings||{},{destinations:destinations,eventSource:eventSource="WEB"}=settings;destinations&&0!==destinations.length||logger.throw("Config settings destinations missing or empty");const settingsConfig={...settings,destinations:destinations,eventSource:eventSource};return{...partialConfig,settings:settingsConfig}}import{getMappingValue,isObject}from"@walkeros/core";import{isString as isString2,isDefined}from"@walkeros/core";import{isString}from"@walkeros/core";import{getHashServer}from"@walkeros/server-core";async function hashName(name,type="given"){if(!isString(name)||!name)return"";let normalized=name.trim().toLowerCase();if("given"===type){const prefixes=["mr.","mrs.","ms.","miss.","dr.","prof."];for(const prefix of prefixes)if(normalized.startsWith(prefix)){normalized=normalized.substring(prefix.length).trim();break}}if("family"===type){const suffixes=["jr.","sr.","iii","ii","iv","v"];for(const suffix of suffixes){const suffixWithSpace=` ${suffix}`;if(normalized.endsWith(suffixWithSpace)){normalized=normalized.substring(0,normalized.length-suffixWithSpace.length).trim();break}if(normalized.endsWith(suffix)){normalized=normalized.substring(0,normalized.length-suffix.length).trim();break}}}return getHashServer(normalized)}async function formatUserData(data){const identifiers=[];if(isString2(data.email)&&data.email){const hashedEmail=await async function(email){if(!isString(email)||!email)return"";let normalized=email.trim().toLowerCase();if(normalized.endsWith("@gmail.com")||normalized.endsWith("@googlemail.com")){const[localPart,domain]=normalized.split("@");normalized=`${localPart.replace(/\./g,"")}@${domain}`}return getHashServer(normalized)}(data.email);hashedEmail&&identifiers.push({emailAddress:hashedEmail})}if(isString2(data.phone)&&data.phone){const hashedPhone=await async function(phone){if(!isString(phone)||!phone)return"";let normalized=phone.trim();const hasPlus=normalized.startsWith("+");return normalized=normalized.replace(/\D/g,""),normalized=hasPlus||normalized.length>10?`+${normalized}`:`+1${normalized}`,getHashServer(normalized)}(data.phone);hashedPhone&&identifiers.push({phoneNumber:hashedPhone})}if(data.firstName||data.lastName||data.regionCode||data.postalCode){const address={};isString2(data.firstName)&&data.firstName&&(address.givenName=await hashName(data.firstName,"given")),isString2(data.lastName)&&data.lastName&&(address.familyName=await hashName(data.lastName,"family")),isString2(data.regionCode)&&data.regionCode&&(address.regionCode=data.regionCode.toUpperCase()),isString2(data.postalCode)&&data.postalCode&&(address.postalCode=data.postalCode),Object.keys(address).length>0&&identifiers.push({address:address})}if(0!==identifiers.length)return{userIdentifiers:identifiers.slice(0,10)}}async function formatEvent(event,mappedData){const dataManagerEvent={eventTimestamp:(timestamp=event.timestamp,new Date(timestamp).toISOString())};var timestamp;const data=mappedData||{};isString2(data.transactionId)&&data.transactionId&&(dataManagerEvent.transactionId=data.transactionId.substring(0,512)),isString2(data.clientId)&&data.clientId&&(dataManagerEvent.clientId=data.clientId.substring(0,255)),isString2(data.userId)&&data.userId&&(dataManagerEvent.userId=data.userId.substring(0,256));const userData=await formatUserData(data);userData&&(dataManagerEvent.userData=userData);const adIdentifiers=function(data){const identifiers={};return isString2(data.gclid)&&data.gclid&&(identifiers.gclid=data.gclid),isString2(data.gbraid)&&data.gbraid&&(identifiers.gbraid=data.gbraid),isString2(data.wbraid)&&data.wbraid&&(identifiers.wbraid=data.wbraid),isString2(data.sessionAttributes)&&data.sessionAttributes&&(identifiers.sessionAttributes=data.sessionAttributes),Object.keys(identifiers).length>0?identifiers:void 0}(data);adIdentifiers&&(dataManagerEvent.adIdentifiers=adIdentifiers),"number"==typeof data.conversionValue&&(dataManagerEvent.conversionValue=data.conversionValue),isString2(data.currency)&&data.currency&&(dataManagerEvent.currency=data.currency.substring(0,3).toUpperCase()),data.cartData&&"object"==typeof data.cartData&&(dataManagerEvent.cartData=data.cartData),isString2(data.eventName)&&data.eventName&&(dataManagerEvent.eventName=data.eventName.substring(0,40)),isString2(data.eventSource)&&data.eventSource&&(dataManagerEvent.eventSource=data.eventSource);const mappedConsent={};if("boolean"==typeof data.adUserData&&(mappedConsent.adUserData=data.adUserData?"CONSENT_GRANTED":"CONSENT_DENIED"),"boolean"==typeof data.adPersonalization&&(mappedConsent.adPersonalization=data.adPersonalization?"CONSENT_GRANTED":"CONSENT_DENIED"),0===Object.keys(mappedConsent).length){const eventConsent=function(walkerOSConsent){if(!walkerOSConsent)return;const consent={};return isDefined(walkerOSConsent.marketing)&&(consent.adUserData=walkerOSConsent.marketing?"CONSENT_GRANTED":"CONSENT_DENIED"),isDefined(walkerOSConsent.personalization)&&(consent.adPersonalization=walkerOSConsent.personalization?"CONSENT_GRANTED":"CONSENT_DENIED"),Object.keys(consent).length>0?consent:void 0}(event.consent);eventConsent&&(dataManagerEvent.consent=eventConsent)}else dataManagerEvent.consent=mappedConsent;return dataManagerEvent}import{GoogleAuth}from"google-auth-library";var DEFAULT_SCOPES=["https://www.googleapis.com/auth/datamanager"],AuthError=class extends Error{constructor(message,cause){super(message),this.cause=cause,this.name="DataManagerAuthError"}};var push=async function(event,{config:config,rule:rule,data:data,collector:collector,env:env,logger:logger}){const validatedConfig=getConfig(config,logger),{destinations:destinations,eventSource:eventSource,validateOnly:validateOnly=!1,url:url="https://datamanager.googleapis.com/v1",consent:requestConsent,testEventCode:testEventCode,userData:userData,userId:userId,clientId:clientId,sessionAttributes:sessionAttributes,consentAdUserData:consentAdUserData,consentAdPersonalization:consentAdPersonalization}=validatedConfig.settings,userDataMapped=userData?await getMappingValue(event,{map:userData}):{},userIdMapped=userId?await getMappingValue(event,userId):void 0,clientIdMapped=clientId?await getMappingValue(event,clientId):void 0,sessionAttributesMapped=sessionAttributes?await getMappingValue(event,sessionAttributes):void 0,consentAdUserDataValue="boolean"==typeof consentAdUserData?consentAdUserData:"string"==typeof consentAdUserData&&event.consent?event.consent[consentAdUserData]:void 0,consentAdPersonalizationValue="boolean"==typeof consentAdPersonalization?consentAdPersonalization:"string"==typeof consentAdPersonalization&&event.consent?event.consent[consentAdPersonalization]:void 0,settingsHelpers={};isObject(userDataMapped)&&Object.assign(settingsHelpers,userDataMapped),void 0!==userIdMapped&&(settingsHelpers.userId=userIdMapped),void 0!==clientIdMapped&&(settingsHelpers.clientId=clientIdMapped),void 0!==sessionAttributesMapped&&(settingsHelpers.sessionAttributes=sessionAttributesMapped),void 0!==consentAdUserDataValue&&(settingsHelpers.adUserData=consentAdUserDataValue),void 0!==consentAdPersonalizationValue&&(settingsHelpers.adPersonalization=consentAdPersonalizationValue);const configData=validatedConfig.data?await getMappingValue(event,validatedConfig.data):{},eventData=isObject(data)?data:{},finalData={...settingsHelpers,...isObject(configData)?configData:{},...eventData},dataManagerEvent=await formatEvent(event,finalData);dataManagerEvent.eventSource||(dataManagerEvent.eventSource=eventSource),!dataManagerEvent.consent&&requestConsent&&(dataManagerEvent.consent=requestConsent),dataManagerEvent.transactionId||logger.throw("transactionId is required");destinations.some(d=>{var _a;return"GOOGLE_ANALYTICS_PROPERTY"===(null==(_a=d.operatingAccount)?void 0:_a.accountType)})&&!dataManagerEvent.eventName&&logger.throw("eventName is required for GA4 destinations");const requestBody={events:[dataManagerEvent],destinations:destinations};requestConsent&&(requestBody.consent=requestConsent),validateOnly&&(requestBody.validateOnly=!0),testEventCode&&(requestBody.testEventCode=testEventCode);const authClient=null==env?void 0:env.authClient;if(!authClient)return logger.throw("Auth client not initialized. Ensure init() was called successfully.");let accessToken;try{accessToken=await async function(authClient){try{const tokenResponse=await authClient.getAccessToken();if(!tokenResponse.token)throw new AuthError("Auth client returned empty token");return tokenResponse.token}catch(error){throw new AuthError("Failed to obtain access token",error instanceof Error?error:void 0)}}(authClient)}catch(error){throw logger.error("Authentication failed",{error:error}),error}const fetchFn=(null==env?void 0:env.fetch)||fetch,endpoint=`${url}/events:ingest`;logger.debug("Sending to Data Manager API",{endpoint:endpoint,eventCount:requestBody.events.length,destinations:destinations.length,validateOnly:validateOnly});const response=await fetchFn(endpoint,{method:"POST",headers:{Authorization:`Bearer ${accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(requestBody)});if(!response.ok){const errorText=await response.text();logger.throw(`Data Manager API error (${response.status}): ${errorText}`)}const result=await response.json();logger.debug("API response",{status:response.status,requestId:result.requestId}),result.validationErrors&&result.validationErrors.length>0&&logger.throw(`Validation errors: ${JSON.stringify(result.validationErrors)}`)},types_exports={},destinationDataManager={type:"datamanager",config:{},async init({config:partialConfig,env:env,logger:logger}){const config=getConfig(partialConfig,logger);try{const authClient=await async function(settings){const{credentials:credentials,keyFilename:keyFilename,scopes:scopes=DEFAULT_SCOPES}=settings;try{if(credentials){const auth2=new GoogleAuth({credentials:credentials,scopes:scopes});return await auth2.getClient()}if(keyFilename){const auth2=new GoogleAuth({keyFilename:keyFilename,scopes:scopes});return await auth2.getClient()}const auth=new GoogleAuth({scopes:scopes});return await auth.getClient()}catch(error){throw new AuthError("Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.",error instanceof Error?error:void 0)}}(config.settings);return logger.debug("Auth client created"),{...config,env:{...env,authClient:authClient}}}catch(error){logger.throw(`Data Manager authentication failed: ${error instanceof Error?error.message:"Unknown error"}`)}},push:async(event,context)=>await push(event,context)},index_default=destinationDataManager;export{types_exports as DestinationDataManager,index_default as default,destinationDataManager};//# sourceMappingURL=index.mjs.map
|
|
1
|
+
function getConfig(partialConfig={},logger){const settings=partialConfig.settings||{},{destinations:destinations,eventSource:eventSource="WEB"}=settings;destinations&&0!==destinations.length||logger.throw("Config settings destinations missing or empty");const settingsConfig={...settings,destinations:destinations,eventSource:eventSource};return{...partialConfig,settings:settingsConfig}}import{getMappingValue,isObject}from"@walkeros/core";import{isString as isString2,isDefined}from"@walkeros/core";import{isString}from"@walkeros/core";import{getHashServer}from"@walkeros/server-core";async function hashName(name,type="given"){if(!isString(name)||!name)return"";let normalized=name.trim().toLowerCase();if("given"===type){const prefixes=["mr.","mrs.","ms.","miss.","dr.","prof."];for(const prefix of prefixes)if(normalized.startsWith(prefix)){normalized=normalized.substring(prefix.length).trim();break}}if("family"===type){const suffixes=["jr.","sr.","iii","ii","iv","v"];for(const suffix of suffixes){const suffixWithSpace=` ${suffix}`;if(normalized.endsWith(suffixWithSpace)){normalized=normalized.substring(0,normalized.length-suffixWithSpace.length).trim();break}if(normalized.endsWith(suffix)){normalized=normalized.substring(0,normalized.length-suffix.length).trim();break}}}return getHashServer(normalized)}async function formatUserData(data){const identifiers=[];if(isString2(data.email)&&data.email){const hashedEmail=await async function(email){if(!isString(email)||!email)return"";let normalized=email.trim().toLowerCase();if(normalized.endsWith("@gmail.com")||normalized.endsWith("@googlemail.com")){const[localPart,domain]=normalized.split("@");normalized=`${localPart.replace(/\./g,"")}@${domain}`}return getHashServer(normalized)}(data.email);hashedEmail&&identifiers.push({emailAddress:hashedEmail})}if(isString2(data.phone)&&data.phone){const hashedPhone=await async function(phone){if(!isString(phone)||!phone)return"";let normalized=phone.trim();const hasPlus=normalized.startsWith("+");return normalized=normalized.replace(/\D/g,""),normalized=hasPlus||normalized.length>10?`+${normalized}`:`+1${normalized}`,getHashServer(normalized)}(data.phone);hashedPhone&&identifiers.push({phoneNumber:hashedPhone})}if(data.firstName||data.lastName||data.regionCode||data.postalCode){const address={};isString2(data.firstName)&&data.firstName&&(address.givenName=await hashName(data.firstName,"given")),isString2(data.lastName)&&data.lastName&&(address.familyName=await hashName(data.lastName,"family")),isString2(data.regionCode)&&data.regionCode&&(address.regionCode=data.regionCode.toUpperCase()),isString2(data.postalCode)&&data.postalCode&&(address.postalCode=data.postalCode),Object.keys(address).length>0&&identifiers.push({address:address})}if(0!==identifiers.length)return{userIdentifiers:identifiers.slice(0,10)}}async function formatEvent(event,mappedData){const dataManagerEvent={eventTimestamp:(timestamp=event.timestamp,new Date(timestamp).toISOString())};var timestamp;const data=mappedData||{};isString2(data.transactionId)&&data.transactionId&&(dataManagerEvent.transactionId=data.transactionId.substring(0,512)),isString2(data.clientId)&&data.clientId&&(dataManagerEvent.clientId=data.clientId.substring(0,255)),isString2(data.userId)&&data.userId&&(dataManagerEvent.userId=data.userId.substring(0,256));const userData=await formatUserData(data);userData&&(dataManagerEvent.userData=userData);const adIdentifiers=function(data){const identifiers={};return isString2(data.gclid)&&data.gclid&&(identifiers.gclid=data.gclid),isString2(data.gbraid)&&data.gbraid&&(identifiers.gbraid=data.gbraid),isString2(data.wbraid)&&data.wbraid&&(identifiers.wbraid=data.wbraid),isString2(data.sessionAttributes)&&data.sessionAttributes&&(identifiers.sessionAttributes=data.sessionAttributes),Object.keys(identifiers).length>0?identifiers:void 0}(data);adIdentifiers&&(dataManagerEvent.adIdentifiers=adIdentifiers),"number"==typeof data.conversionValue&&(dataManagerEvent.conversionValue=data.conversionValue),isString2(data.currency)&&data.currency&&(dataManagerEvent.currency=data.currency.substring(0,3).toUpperCase()),data.cartData&&"object"==typeof data.cartData&&(dataManagerEvent.cartData=data.cartData),isString2(data.eventName)&&data.eventName&&(dataManagerEvent.eventName=data.eventName.substring(0,40)),isString2(data.eventSource)&&data.eventSource&&(dataManagerEvent.eventSource=data.eventSource);const mappedConsent={};if("boolean"==typeof data.adUserData&&(mappedConsent.adUserData=data.adUserData?"CONSENT_GRANTED":"CONSENT_DENIED"),"boolean"==typeof data.adPersonalization&&(mappedConsent.adPersonalization=data.adPersonalization?"CONSENT_GRANTED":"CONSENT_DENIED"),0===Object.keys(mappedConsent).length){const eventConsent=function(walkerOSConsent){if(!walkerOSConsent)return;const consent={};return isDefined(walkerOSConsent.marketing)&&(consent.adUserData=walkerOSConsent.marketing?"CONSENT_GRANTED":"CONSENT_DENIED"),isDefined(walkerOSConsent.personalization)&&(consent.adPersonalization=walkerOSConsent.personalization?"CONSENT_GRANTED":"CONSENT_DENIED"),Object.keys(consent).length>0?consent:void 0}(event.consent);eventConsent&&(dataManagerEvent.consent=eventConsent)}else dataManagerEvent.consent=mappedConsent;return dataManagerEvent}import{GoogleAuth}from"google-auth-library";var DEFAULT_SCOPES=["https://www.googleapis.com/auth/datamanager"],AuthError=class extends Error{constructor(message,cause){super(message),this.cause=cause,this.name="DataManagerAuthError"}};var push=async function(event,{config:config,rule:rule,data:data,collector:collector,env:env,logger:logger}){const validatedConfig=getConfig(config,logger),{destinations:destinations,eventSource:eventSource,validateOnly:validateOnly=!1,url:url="https://datamanager.googleapis.com/v1",consent:requestConsent,testEventCode:testEventCode,userData:userData,userId:userId,clientId:clientId,sessionAttributes:sessionAttributes,consentAdUserData:consentAdUserData,consentAdPersonalization:consentAdPersonalization}=validatedConfig.settings,userDataMapped=userData?await getMappingValue(event,{map:userData}):{},userIdMapped=userId?await getMappingValue(event,userId):void 0,clientIdMapped=clientId?await getMappingValue(event,clientId):void 0,sessionAttributesMapped=sessionAttributes?await getMappingValue(event,sessionAttributes):void 0,consentAdUserDataValue="boolean"==typeof consentAdUserData?consentAdUserData:"string"==typeof consentAdUserData&&event.consent?event.consent[consentAdUserData]:void 0,consentAdPersonalizationValue="boolean"==typeof consentAdPersonalization?consentAdPersonalization:"string"==typeof consentAdPersonalization&&event.consent?event.consent[consentAdPersonalization]:void 0,settingsHelpers={};isObject(userDataMapped)&&Object.assign(settingsHelpers,userDataMapped),void 0!==userIdMapped&&(settingsHelpers.userId=userIdMapped),void 0!==clientIdMapped&&(settingsHelpers.clientId=clientIdMapped),void 0!==sessionAttributesMapped&&(settingsHelpers.sessionAttributes=sessionAttributesMapped),void 0!==consentAdUserDataValue&&(settingsHelpers.adUserData=consentAdUserDataValue),void 0!==consentAdPersonalizationValue&&(settingsHelpers.adPersonalization=consentAdPersonalizationValue);const configData=validatedConfig.data?await getMappingValue(event,validatedConfig.data):{},eventData=isObject(data)?data:{},finalData={...settingsHelpers,...isObject(configData)?configData:{},...eventData},dataManagerEvent=await formatEvent(event,finalData);dataManagerEvent.eventSource||(dataManagerEvent.eventSource=eventSource),!dataManagerEvent.consent&&requestConsent&&(dataManagerEvent.consent=requestConsent),dataManagerEvent.transactionId||logger.throw("transactionId is required");destinations.some(d=>"GOOGLE_ANALYTICS_PROPERTY"===d.operatingAccount?.accountType)&&!dataManagerEvent.eventName&&logger.throw("eventName is required for GA4 destinations");const requestBody={events:[dataManagerEvent],destinations:destinations};requestConsent&&(requestBody.consent=requestConsent),validateOnly&&(requestBody.validateOnly=!0),testEventCode&&(requestBody.testEventCode=testEventCode);const authClient=env?.authClient;if(!authClient)return logger.throw("Auth client not initialized. Ensure init() was called successfully.");let accessToken;try{accessToken=await async function(authClient){try{const tokenResponse=await authClient.getAccessToken();if(!tokenResponse.token)throw new AuthError("Auth client returned empty token");return tokenResponse.token}catch(error){throw new AuthError("Failed to obtain access token",error instanceof Error?error:void 0)}}(authClient)}catch(error){throw logger.error("Authentication failed",{error:error}),error}const fetchFn=env?.fetch||fetch,endpoint=`${url}/events:ingest`;logger.debug("Sending to Data Manager API",{endpoint:endpoint,eventCount:requestBody.events.length,destinations:destinations.length,validateOnly:validateOnly});const response=await fetchFn(endpoint,{method:"POST",headers:{Authorization:`Bearer ${accessToken}`,"Content-Type":"application/json"},body:JSON.stringify(requestBody)});if(!response.ok){const errorText=await response.text();logger.throw(`Data Manager API error (${response.status}): ${errorText}`)}const result=await response.json();logger.debug("API response",{status:response.status,requestId:result.requestId}),result.validationErrors&&result.validationErrors.length>0&&logger.throw(`Validation errors: ${JSON.stringify(result.validationErrors)}`)},types_exports={},destinationDataManager={type:"datamanager",config:{},async init({config:partialConfig,env:env,logger:logger}){const config=getConfig(partialConfig,logger);try{const authClient=await async function(settings){const{credentials:credentials,keyFilename:keyFilename,scopes:scopes=DEFAULT_SCOPES}=settings;try{if(credentials){const auth2=new GoogleAuth({credentials:credentials,scopes:scopes});return await auth2.getClient()}if(keyFilename){const auth2=new GoogleAuth({keyFilename:keyFilename,scopes:scopes});return await auth2.getClient()}const auth=new GoogleAuth({scopes:scopes});return await auth.getClient()}catch(error){throw new AuthError("Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.",error instanceof Error?error:void 0)}}(config.settings);return logger.debug("Auth client created"),{...config,env:{...env,authClient:authClient}}}catch(error){logger.throw(`Data Manager authentication failed: ${error instanceof Error?error.message:"Unknown error"}`)}},push:async(event,context)=>await push(event,context)},index_default=destinationDataManager;export{types_exports as DestinationDataManager,index_default as default,destinationDataManager};//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/push.ts","../src/format.ts","../src/hash.ts","../src/auth.ts","../src/types/index.ts","../src/index.ts"],"sourcesContent":["import type {\n ValidatedConfig,\n Settings,\n PartialConfig,\n EventSource,\n} from './types';\nimport type { Logger } from '@walkeros/core';\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): ValidatedConfig {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { destinations, eventSource = 'WEB' } = settings;\n\n if (!destinations || destinations.length === 0)\n logger.throw('Config settings destinations missing or empty');\n\n const settingsConfig: Settings & { eventSource: EventSource } = {\n ...settings,\n destinations,\n eventSource,\n };\n\n return { ...partialConfig, settings: settingsConfig };\n}\n","import type { PushFn } from './types';\nimport type { IngestEventsRequest, IngestEventsResponse } from './types';\nimport { getMappingValue, isObject } from '@walkeros/core';\nimport { formatEvent, formatConsent } from './format';\nimport { getAccessToken } from './auth';\nimport { getConfig } from './config';\n\nexport const push: PushFn = async function (\n event,\n { config, rule, data, collector, env, logger },\n) {\n // Validate config and get typed settings\n const validatedConfig = getConfig(config, logger);\n const {\n destinations,\n eventSource,\n validateOnly = false,\n url = 'https://datamanager.googleapis.com/v1',\n consent: requestConsent,\n testEventCode,\n userData,\n userId,\n clientId,\n sessionAttributes,\n consentAdUserData,\n consentAdPersonalization,\n } = validatedConfig.settings;\n\n // Extract Settings guided helpers\n const userDataMapped = userData\n ? await getMappingValue(event, { map: userData })\n : {};\n const userIdMapped = userId\n ? await getMappingValue(event, userId)\n : undefined;\n const clientIdMapped = clientId\n ? await getMappingValue(event, clientId)\n : undefined;\n const sessionAttributesMapped = sessionAttributes\n ? await getMappingValue(event, sessionAttributes)\n : undefined;\n\n // Extract consent from Settings\n const consentAdUserDataValue =\n typeof consentAdUserData === 'boolean'\n ? consentAdUserData\n : typeof consentAdUserData === 'string' && event.consent\n ? event.consent[consentAdUserData]\n : undefined;\n\n const consentAdPersonalizationValue =\n typeof consentAdPersonalization === 'boolean'\n ? consentAdPersonalization\n : typeof consentAdPersonalization === 'string' && event.consent\n ? event.consent[consentAdPersonalization]\n : undefined;\n\n // Build Settings helpers object\n const settingsHelpers: Record<string, unknown> = {};\n if (isObject(userDataMapped)) {\n Object.assign(settingsHelpers, userDataMapped);\n }\n if (userIdMapped !== undefined) settingsHelpers.userId = userIdMapped;\n if (clientIdMapped !== undefined) settingsHelpers.clientId = clientIdMapped;\n if (sessionAttributesMapped !== undefined)\n settingsHelpers.sessionAttributes = sessionAttributesMapped;\n if (consentAdUserDataValue !== undefined)\n settingsHelpers.adUserData = consentAdUserDataValue;\n if (consentAdPersonalizationValue !== undefined)\n settingsHelpers.adPersonalization = consentAdPersonalizationValue;\n\n // Get mapped data from destination config and event mapping\n const configData = validatedConfig.data\n ? await getMappingValue(event, validatedConfig.data)\n : {};\n const eventData = isObject(data) ? data : {};\n\n // Merge: Settings helpers < config.data < event mapping data\n const finalData = {\n ...settingsHelpers,\n ...(isObject(configData) ? configData : {}),\n ...eventData,\n };\n\n // Format event for Data Manager API\n const dataManagerEvent = await formatEvent(event, finalData);\n\n // Apply event source from settings (required)\n if (!dataManagerEvent.eventSource) {\n dataManagerEvent.eventSource = eventSource;\n }\n\n // Apply request-level consent if event doesn't have consent\n if (!dataManagerEvent.consent && requestConsent) {\n dataManagerEvent.consent = requestConsent;\n }\n\n // Validate required fields before API call\n if (!dataManagerEvent.transactionId) {\n logger.throw('transactionId is required');\n }\n\n // Check if any destination is GA4 (requires eventName)\n const hasGA4Destination = destinations.some(\n (d) => d.operatingAccount?.accountType === 'GOOGLE_ANALYTICS_PROPERTY',\n );\n\n if (hasGA4Destination && !dataManagerEvent.eventName) {\n logger.throw('eventName is required for GA4 destinations');\n }\n\n // Build API request\n const requestBody: IngestEventsRequest = {\n events: [dataManagerEvent],\n destinations,\n };\n\n // Add optional parameters\n if (requestConsent) {\n requestBody.consent = requestConsent;\n }\n\n if (validateOnly) {\n requestBody.validateOnly = true;\n }\n\n if (testEventCode) {\n requestBody.testEventCode = testEventCode;\n }\n\n const authClient = env?.authClient;\n if (!authClient) {\n return logger.throw(\n 'Auth client not initialized. Ensure init() was called successfully.',\n );\n }\n\n let accessToken: string;\n try {\n accessToken = await getAccessToken(authClient);\n } catch (error) {\n logger.error('Authentication failed', { error });\n throw error;\n }\n\n const fetchFn = env?.fetch || fetch;\n const endpoint = `${url}/events:ingest`;\n\n logger.debug('Sending to Data Manager API', {\n endpoint,\n eventCount: requestBody.events.length,\n destinations: destinations.length,\n validateOnly,\n });\n\n const response = await fetchFn(endpoint, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n logger.throw(`Data Manager API error (${response.status}): ${errorText}`);\n }\n\n const result: IngestEventsResponse = await response.json();\n\n logger.debug('API response', {\n status: response.status,\n requestId: result.requestId,\n });\n\n // If validation errors exist, throw them\n if (result.validationErrors && result.validationErrors.length > 0) {\n logger.throw(\n `Validation errors: ${JSON.stringify(result.validationErrors)}`,\n );\n }\n};\n","import type { WalkerOS } from '@walkeros/core';\nimport { isString, isDefined } from '@walkeros/core';\nimport type {\n Event,\n UserData,\n UserIdentifier,\n AdIdentifiers,\n Consent,\n ConsentStatus,\n} from './types';\nimport { hashEmail, hashPhone, hashName } from './hash';\n\n/**\n * Format walkerOS event timestamp to RFC 3339 format\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n *\n * walkerOS timestamp is in milliseconds, RFC 3339 format: \"2024-01-15T10:30:00Z\"\n */\nexport function formatTimestamp(timestamp: number): string {\n return new Date(timestamp).toISOString();\n}\n\n/**\n * Format user identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n *\n * User data must be explicitly mapped in the mapping configuration.\n * Max 10 identifiers per event\n */\nexport async function formatUserData(\n data: Record<string, unknown>,\n): Promise<UserData | undefined> {\n const identifiers: UserIdentifier[] = [];\n\n // Extract from mapped data only\n // Email\n if (isString(data.email) && data.email) {\n const hashedEmail = await hashEmail(data.email);\n if (hashedEmail) {\n identifiers.push({ emailAddress: hashedEmail });\n }\n }\n\n // Phone\n if (isString(data.phone) && data.phone) {\n const hashedPhone = await hashPhone(data.phone);\n if (hashedPhone) {\n identifiers.push({ phoneNumber: hashedPhone });\n }\n }\n\n // Address from mapped properties\n const hasAddress =\n data.firstName || data.lastName || data.regionCode || data.postalCode;\n\n if (hasAddress) {\n const address: Record<string, string> = {};\n\n if (isString(data.firstName) && data.firstName) {\n address.givenName = await hashName(data.firstName, 'given');\n }\n\n if (isString(data.lastName) && data.lastName) {\n address.familyName = await hashName(data.lastName, 'family');\n }\n\n // Region code is NOT hashed\n if (isString(data.regionCode) && data.regionCode) {\n address.regionCode = data.regionCode.toUpperCase();\n }\n\n // Postal code is NOT hashed\n if (isString(data.postalCode) && data.postalCode) {\n address.postalCode = data.postalCode;\n }\n\n if (Object.keys(address).length > 0) {\n identifiers.push({ address });\n }\n }\n\n // Limit to 10 identifiers\n if (identifiers.length === 0) return undefined;\n\n return {\n userIdentifiers: identifiers.slice(0, 10),\n };\n}\n\n/**\n * Extract and format attribution identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n *\n * Attribution identifiers should be mapped explicitly in the mapping configuration.\n * Example: { gclid: 'context.gclid', gbraid: 'context.gbraid' }\n */\nexport function formatAdIdentifiers(\n data: Record<string, unknown>,\n): AdIdentifiers | undefined {\n const identifiers: AdIdentifiers = {};\n\n // Extract from mapped data (already processed by mapping system)\n if (isString(data.gclid) && data.gclid) {\n identifiers.gclid = data.gclid;\n }\n if (isString(data.gbraid) && data.gbraid) {\n identifiers.gbraid = data.gbraid;\n }\n if (isString(data.wbraid) && data.wbraid) {\n identifiers.wbraid = data.wbraid;\n }\n if (isString(data.sessionAttributes) && data.sessionAttributes) {\n identifiers.sessionAttributes = data.sessionAttributes;\n }\n\n return Object.keys(identifiers).length > 0 ? identifiers : undefined;\n}\n\n/**\n * Map walkerOS consent to Data Manager consent format\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n *\n * walkerOS: { marketing: true, personalization: false }\n * Data Manager: { adUserData: 'CONSENT_GRANTED', adPersonalization: 'CONSENT_DENIED' }\n */\nexport function formatConsent(\n walkerOSConsent: WalkerOS.Consent | undefined,\n): Consent | undefined {\n if (!walkerOSConsent) return undefined;\n\n const consent: Consent = {};\n\n // Map marketing consent to adUserData\n if (isDefined(walkerOSConsent.marketing)) {\n consent.adUserData = walkerOSConsent.marketing\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // Map personalization consent to adPersonalization\n if (isDefined(walkerOSConsent.personalization)) {\n consent.adPersonalization = walkerOSConsent.personalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n return Object.keys(consent).length > 0 ? consent : undefined;\n}\n\n/**\n * Format complete event for Data Manager API\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport async function formatEvent(\n event: WalkerOS.Event,\n mappedData?: Record<string, unknown>,\n): Promise<Event> {\n const dataManagerEvent: Event = {\n eventTimestamp: formatTimestamp(event.timestamp),\n };\n\n // Use only mapped data (no fallback to event.data)\n const data = mappedData || {};\n\n // Transaction ID for deduplication\n if (isString(data.transactionId) && data.transactionId) {\n dataManagerEvent.transactionId = data.transactionId.substring(0, 512);\n }\n\n // Client ID (GA)\n if (isString(data.clientId) && data.clientId) {\n dataManagerEvent.clientId = data.clientId.substring(0, 255);\n }\n\n // User ID\n if (isString(data.userId) && data.userId) {\n dataManagerEvent.userId = data.userId.substring(0, 256);\n }\n\n // User data\n const userData = await formatUserData(data);\n if (userData) {\n dataManagerEvent.userData = userData;\n }\n\n // Attribution identifiers\n const adIdentifiers = formatAdIdentifiers(data);\n if (adIdentifiers) {\n dataManagerEvent.adIdentifiers = adIdentifiers;\n }\n\n // Conversion value\n if (typeof data.conversionValue === 'number') {\n dataManagerEvent.conversionValue = data.conversionValue;\n }\n\n // Currency\n if (isString(data.currency) && data.currency) {\n dataManagerEvent.currency = data.currency.substring(0, 3).toUpperCase();\n }\n\n // Cart data\n if (data.cartData && typeof data.cartData === 'object') {\n dataManagerEvent.cartData = data.cartData as Event['cartData'];\n }\n\n // Event name (for GA4)\n if (isString(data.eventName) && data.eventName) {\n dataManagerEvent.eventName = data.eventName.substring(0, 40);\n }\n\n // Event source\n if (isString(data.eventSource) && data.eventSource) {\n dataManagerEvent.eventSource = data.eventSource as Event['eventSource'];\n }\n\n // Consent - check mapped data first, then fallback to event.consent\n const mappedConsent: Consent = {};\n\n // Check for mapped consent values (from Settings or event mapping)\n if (typeof data.adUserData === 'boolean') {\n mappedConsent.adUserData = data.adUserData\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n if (typeof data.adPersonalization === 'boolean') {\n mappedConsent.adPersonalization = data.adPersonalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // If no mapped consent, fall back to event.consent\n if (Object.keys(mappedConsent).length === 0) {\n const eventConsent = formatConsent(event.consent);\n if (eventConsent) {\n dataManagerEvent.consent = eventConsent;\n }\n } else {\n dataManagerEvent.consent = mappedConsent;\n }\n\n return dataManagerEvent;\n}\n","import { isString } from '@walkeros/core';\nimport { getHashServer } from '@walkeros/server-core';\n\n/**\n * Normalize email address according to Google Data Manager requirements\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#email\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove dots (.) for gmail.com and googlemail.com\n * 4. SHA-256 hash\n */\nexport async function hashEmail(email: string): Promise<string> {\n if (!isString(email) || !email) return '';\n\n // Trim and lowercase\n let normalized = email.trim().toLowerCase();\n\n // Remove dots for Gmail addresses\n if (\n normalized.endsWith('@gmail.com') ||\n normalized.endsWith('@googlemail.com')\n ) {\n const [localPart, domain] = normalized.split('@');\n normalized = `${localPart.replace(/\\./g, '')}@${domain}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize phone number to E.164 format and hash\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#phone\n *\n * E.164 format: +[country code][number] (max 15 digits after +)\n * Example: +18005550100\n *\n * 1. Remove all non-digit characters except leading +\n * 2. Ensure it starts with +\n * 3. SHA-256 hash\n */\nexport async function hashPhone(phone: string): Promise<string> {\n if (!isString(phone) || !phone) return '';\n\n // Remove all non-digit characters except + at the start\n let normalized = phone.trim();\n\n // Extract country code if present\n const hasPlus = normalized.startsWith('+');\n\n // Remove all non-digits\n normalized = normalized.replace(/\\D/g, '');\n\n // Add + prefix if it was there or if number is long enough\n if (hasPlus || normalized.length > 10) {\n normalized = `+${normalized}`;\n } else {\n // Assume US number if no country code (default behavior)\n normalized = `+1${normalized}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize and hash a name (first or last name)\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#name\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove common prefixes (Mr., Mrs., Dr.) for first names\n * 4. Remove common suffixes (Jr., Sr., III) for last names\n * 5. SHA-256 hash\n */\nexport async function hashName(\n name: string,\n type: 'given' | 'family' = 'given',\n): Promise<string> {\n if (!isString(name) || !name) return '';\n\n // Trim and lowercase\n let normalized = name.trim().toLowerCase();\n\n // Remove prefixes for given names\n if (type === 'given') {\n const prefixes = ['mr.', 'mrs.', 'ms.', 'miss.', 'dr.', 'prof.'];\n for (const prefix of prefixes) {\n if (normalized.startsWith(prefix)) {\n normalized = normalized.substring(prefix.length).trim();\n break;\n }\n }\n }\n\n // Remove suffixes for family names (check with and without space)\n // Sort by length (longest first) to match \"iii\" before \"ii\"\n if (type === 'family') {\n const suffixes = ['jr.', 'sr.', 'iii', 'ii', 'iv', 'v'];\n for (const suffix of suffixes) {\n // Check for suffix with space before it (e.g., \" jr.\")\n const suffixWithSpace = ` ${suffix}`;\n if (normalized.endsWith(suffixWithSpace)) {\n normalized = normalized\n .substring(0, normalized.length - suffixWithSpace.length)\n .trim();\n break;\n }\n // Check for suffix without space\n if (normalized.endsWith(suffix)) {\n normalized = normalized\n .substring(0, normalized.length - suffix.length)\n .trim();\n break;\n }\n }\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Hash multiple email addresses\n */\nexport async function hashEmails(emails: string[]): Promise<string[]> {\n return Promise.all(emails.map((email) => hashEmail(email)));\n}\n\n/**\n * Hash multiple phone numbers\n */\nexport async function hashPhones(phones: string[]): Promise<string[]> {\n return Promise.all(phones.map((phone) => hashPhone(phone)));\n}\n","import { GoogleAuth, type OAuth2Client } from 'google-auth-library';\nimport type { Settings } from './types';\n\nconst DEFAULT_SCOPES = ['https://www.googleapis.com/auth/datamanager'];\n\n/**\n * Authentication error with cause tracking\n */\nexport class AuthError extends Error {\n constructor(\n message: string,\n public cause?: Error,\n ) {\n super(message);\n this.name = 'DataManagerAuthError';\n }\n}\n\n/**\n * Creates Google Auth client based on settings\n *\n * Authentication priority:\n * 1. credentials (inline service account) - if provided\n * 2. keyFilename (service account file) - if provided\n * 3. Application Default Credentials (ADC) - automatic fallback\n * - GOOGLE_APPLICATION_CREDENTIALS env var\n * - GCP metadata server (Cloud Functions, Cloud Run, GCE)\n *\n * @param settings - Configuration with auth options\n * @returns OAuth2Client for token retrieval\n * @throws AuthError if authentication fails\n */\nexport async function createAuthClient(\n settings: Settings,\n): Promise<OAuth2Client> {\n const { credentials, keyFilename, scopes = DEFAULT_SCOPES } = settings;\n\n try {\n if (credentials) {\n const auth = new GoogleAuth({\n credentials,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n if (keyFilename) {\n const auth = new GoogleAuth({\n keyFilename,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n const auth = new GoogleAuth({ scopes });\n return (await auth.getClient()) as OAuth2Client;\n } catch (error) {\n throw new AuthError(\n 'Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.',\n error instanceof Error ? error : undefined,\n );\n }\n}\n\n/**\n * Gets access token from auth client\n * Automatically returns cached token if valid or refreshes if expired\n *\n * @param authClient - OAuth2 client from createAuthClient()\n * @returns Fresh access token\n * @throws AuthError if token retrieval fails\n */\nexport async function getAccessToken(\n authClient: OAuth2Client,\n): Promise<string> {\n try {\n const tokenResponse = await authClient.getAccessToken();\n\n if (!tokenResponse.token) {\n throw new AuthError('Auth client returned empty token');\n }\n\n return tokenResponse.token;\n } catch (error) {\n throw new AuthError(\n 'Failed to obtain access token',\n error instanceof Error ? error : undefined,\n );\n }\n}\n","import type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationServer } from '@walkeros/server-core';\nimport type { OAuth2Client } from 'google-auth-library';\n\nexport interface Settings {\n /**\n * Service account credentials (client_email + private_key)\n * Recommended for serverless environments (AWS Lambda, Docker, etc.)\n */\n credentials?: {\n client_email: string;\n private_key: string;\n };\n\n /**\n * Path to service account JSON file\n * For local development or environments with filesystem access\n */\n keyFilename?: string;\n\n /**\n * OAuth scopes for Data Manager API\n * @default ['https://www.googleapis.com/auth/datamanager']\n */\n scopes?: string[];\n\n /** Array of destination accounts and conversion actions/user lists */\n destinations: Destination[];\n\n /** Event source for all events. Defaults to WEB if not specified */\n eventSource?: EventSource;\n\n /** Maximum number of events to batch before sending (max 2000) */\n batchSize?: number;\n\n /** Time in milliseconds to wait before auto-flushing batch */\n batchInterval?: number;\n\n /** If true, validate request without ingestion (testing mode) */\n validateOnly?: boolean;\n\n /** Override API endpoint (for testing) */\n url?: string;\n\n /** Request-level consent for all events */\n consent?: Consent;\n\n /** Test event code for debugging (optional) */\n testEventCode?: string;\n\n /** Guided helpers: User data mapping (applies to all events) */\n userData?: WalkerOSMapping.Map;\n\n /** Guided helper: First-party user ID */\n userId?: WalkerOSMapping.Value;\n\n /** Guided helper: GA4 client ID */\n clientId?: WalkerOSMapping.Value;\n\n /** Guided helper: Privacy-safe attribution (Google's sessionAttributes) */\n sessionAttributes?: WalkerOSMapping.Value;\n\n /** Consent mapping: Map consent field to adUserData (string = field name, boolean = static value) */\n consentAdUserData?: string | boolean;\n\n /** Consent mapping: Map consent field to adPersonalization (string = field name, boolean = static value) */\n consentAdPersonalization?: string | boolean;\n}\n\nexport interface Mapping {\n // Attribution identifiers (optional, for explicit mapping)\n gclid?: WalkerOSMapping.Value;\n gbraid?: WalkerOSMapping.Value;\n wbraid?: WalkerOSMapping.Value;\n sessionAttributes?: WalkerOSMapping.Value;\n}\n\nexport interface Env extends DestinationServer.Env {\n fetch?: typeof fetch;\n authClient?: OAuth2Client | null;\n}\n\nexport type InitSettings = Partial<Settings>;\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface DestinationInterface\n extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\n/**\n * Config after validation - settings is guaranteed to exist with required fields\n * Use this type after calling getConfig() to get proper type narrowing\n * After validation, eventSource is always set (defaults to 'WEB')\n */\nexport type ValidatedConfig = Omit<Config, 'settings'> & {\n settings: Settings & { eventSource: EventSource };\n};\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\n\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\n\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\n\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n\n// Google Data Manager API Types\n// https://developers.google.com/data-manager/api/reference\n\n/**\n * Destination account and product identifier\n * https://developers.google.com/data-manager/api/reference/rest/v1/Destination\n */\nexport interface Destination {\n /** Reference identifier for this destination */\n reference?: string;\n\n /** Login account (account initiating the request) */\n loginAccount?: ProductAccount;\n\n /** Linked account (child account linked to login account) */\n linkedAccount?: ProductAccount;\n\n /** Operating account (account where data is sent) */\n operatingAccount?: ProductAccount;\n\n /** Product-specific destination ID (conversion action or user list) */\n productDestinationId?: string;\n}\n\n/**\n * Product account information\n */\nexport interface ProductAccount {\n /** Account ID (e.g., \"123-456-7890\" for Google Ads) */\n accountId: string;\n\n /** Type of account */\n accountType: AccountType;\n}\n\nexport type AccountType =\n | 'ACCOUNT_TYPE_UNSPECIFIED'\n | 'GOOGLE_ADS'\n | 'DISPLAY_VIDEO_ADVERTISER'\n | 'DISPLAY_VIDEO_PARTNER'\n | 'GOOGLE_ANALYTICS_PROPERTY'\n | 'DATA_PARTNER';\n\nexport type EventSource = 'WEB' | 'APP' | 'IN_STORE' | 'PHONE' | 'OTHER';\n\n/**\n * Consent for Digital Markets Act (DMA) compliance\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n */\nexport interface Consent {\n /** Consent for data collection and use */\n adUserData?: ConsentStatus;\n\n /** Consent for ad personalization */\n adPersonalization?: ConsentStatus;\n}\n\nexport type ConsentStatus = 'CONSENT_GRANTED' | 'CONSENT_DENIED';\n\n/**\n * Request body for events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/events/ingest\n */\nexport interface IngestEventsRequest {\n /** Array of destinations for these events (max 10) */\n destinations: Destination[];\n\n /** Array of events to ingest (max 2000) */\n events: Event[];\n\n /** Request-level consent (overridden by event-level) */\n consent?: Consent;\n\n /** If true, validate without ingestion */\n validateOnly?: boolean;\n\n /** Test event code for debugging */\n testEventCode?: string;\n}\n\n/**\n * Single event for ingestion\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport interface Event {\n /** Destination references for routing */\n destinationReferences?: string[];\n\n /** Transaction ID for deduplication (max 512 chars) */\n transactionId?: string;\n\n /** Event timestamp in RFC 3339 format */\n eventTimestamp?: string;\n\n /** Last updated timestamp in RFC 3339 format */\n lastUpdatedTimestamp?: string;\n\n /** User data with identifiers (max 10 identifiers) */\n userData?: UserData;\n\n /** Event-level consent (overrides request-level) */\n consent?: Consent;\n\n /** Attribution identifiers */\n adIdentifiers?: AdIdentifiers;\n\n /** Currency code (ISO 4217, 3 chars) */\n currency?: string;\n\n /** Conversion value */\n conversionValue?: number;\n\n /** Source of the event */\n eventSource?: EventSource;\n\n /** Device information for the event */\n eventDeviceInfo?: DeviceInfo;\n\n /** Shopping cart data */\n cartData?: CartData;\n\n /** Custom variables for the event */\n customVariables?: CustomVariable[];\n\n /** Experimental fields (subject to change) */\n experimentalFields?: ExperimentalField[];\n\n /** User properties */\n userProperties?: UserProperties;\n\n /** Event name for GA4 (max 40 chars, required for GA4) */\n eventName?: string;\n\n /** Google Analytics client ID (max 255 chars) */\n clientId?: string;\n\n /** First-party user ID (max 256 chars) */\n userId?: string;\n\n /** Additional event parameters */\n additionalEventParameters?: EventParameter[];\n}\n\n/**\n * Device information\n */\nexport interface DeviceInfo {\n /** User agent string */\n userAgent?: string;\n}\n\n/**\n * Custom variable\n */\nexport interface CustomVariable {\n /** Variable name */\n name?: string;\n\n /** Variable value */\n value?: string;\n}\n\n/**\n * Experimental field\n */\nexport interface ExperimentalField {\n /** Field name */\n name?: string;\n\n /** Field value */\n value?: string;\n}\n\n/**\n * User properties\n */\nexport interface UserProperties {\n /** Property values */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Event parameter\n */\nexport interface EventParameter {\n /** Parameter name */\n name?: string;\n\n /** Parameter value */\n value?: string | number;\n}\n\n/**\n * User data with identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n */\nexport interface UserData {\n /** Array of user identifiers (max 10) */\n userIdentifiers: UserIdentifier[];\n}\n\n/**\n * User identifier (email, phone, or address)\n */\nexport type UserIdentifier =\n | { emailAddress: string }\n | { phoneNumber: string }\n | { address: Address };\n\n/**\n * Address for user identification\n * https://developers.google.com/data-manager/api/reference/rest/v1/Address\n */\nexport interface Address {\n /** Given name (first name) - SHA-256 hashed */\n givenName?: string;\n\n /** Family name (last name) - SHA-256 hashed */\n familyName?: string;\n\n /** ISO-3166-1 alpha-2 country code - NOT hashed (e.g., \"US\", \"GB\") */\n regionCode?: string;\n\n /** Postal code - NOT hashed */\n postalCode?: string;\n}\n\n/**\n * Attribution identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n */\nexport interface AdIdentifiers {\n /** Session attributes (privacy-safe attribution) */\n sessionAttributes?: string;\n\n /** Google Click ID (primary attribution) */\n gclid?: string;\n\n /** iOS attribution identifier (post-ATT) */\n gbraid?: string;\n\n /** Web-to-app attribution identifier */\n wbraid?: string;\n\n /** Device information for landing page */\n landingPageDeviceInfo?: DeviceInfo;\n}\n\n/**\n * Shopping cart data\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartData\n */\nexport interface CartData {\n /** Array of cart items (max 200) */\n items: CartItem[];\n}\n\n/**\n * Single cart item\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartItem\n */\nexport interface CartItem {\n /** Merchant product ID (max 127 chars) */\n merchantProductId?: string;\n\n /** Item price */\n price?: number;\n\n /** Item quantity */\n quantity?: number;\n}\n\n/**\n * Response from events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/IngestEventsResponse\n */\nexport interface IngestEventsResponse {\n /** Unique request ID for status checking */\n requestId: string;\n\n /** Validation errors (only if validateOnly=true) */\n validationErrors?: ValidationError[];\n}\n\n/**\n * Validation error\n */\nexport interface ValidationError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Field path that caused the error */\n fieldPath?: string;\n}\n\n/**\n * Request status response\n * https://developers.google.com/data-manager/api/reference/rest/v1/requestStatus/retrieve\n */\nexport interface RequestStatusResponse {\n /** Unique request ID */\n requestId: string;\n\n /** Processing state */\n state: RequestState;\n\n /** Number of events successfully ingested */\n eventsIngested?: number;\n\n /** Number of events that failed */\n eventsFailed?: number;\n\n /** Array of errors (if any) */\n errors?: RequestError[];\n}\n\nexport type RequestState =\n | 'STATE_UNSPECIFIED'\n | 'PENDING'\n | 'PROCESSING'\n | 'SUCCEEDED'\n | 'FAILED'\n | 'PARTIALLY_SUCCEEDED';\n\n/**\n * Request error\n */\nexport interface RequestError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Number of events affected by this error */\n eventCount?: number;\n}\n","import type { DestinationInterface } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\nimport { createAuthClient } from './auth';\n\nexport * as DestinationDataManager from './types';\n\nexport const destinationDataManager: DestinationInterface = {\n type: 'datamanager',\n\n config: {},\n\n async init({ config: partialConfig, env, logger }) {\n // getConfig validates required fields and returns ValidatedConfig\n const config = getConfig(partialConfig, logger);\n\n try {\n const authClient = await createAuthClient(config.settings);\n logger.debug('Auth client created');\n\n return {\n ...config,\n env: {\n ...env,\n authClient,\n },\n };\n } catch (error) {\n logger.throw(\n `Data Manager authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n },\n\n async push(event, context) {\n return await push(event, context);\n },\n};\n\nexport default destinationDataManager;\n"],"mappings":"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","names":["isString","isString","auth"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/push.ts","../src/format.ts","../src/hash.ts","../src/auth.ts","../src/types/index.ts","../src/index.ts"],"sourcesContent":["import type {\n ValidatedConfig,\n Settings,\n PartialConfig,\n EventSource,\n} from './types';\nimport type { Logger } from '@walkeros/core';\n\nexport function getConfig(\n partialConfig: PartialConfig = {},\n logger: Logger.Instance,\n): ValidatedConfig {\n const settings = (partialConfig.settings || {}) as Partial<Settings>;\n const { destinations, eventSource = 'WEB' } = settings;\n\n if (!destinations || destinations.length === 0)\n logger.throw('Config settings destinations missing or empty');\n\n const settingsConfig: Settings & { eventSource: EventSource } = {\n ...settings,\n destinations,\n eventSource,\n };\n\n return { ...partialConfig, settings: settingsConfig };\n}\n","import type { PushFn } from './types';\nimport type { IngestEventsRequest, IngestEventsResponse } from './types';\nimport { getMappingValue, isObject } from '@walkeros/core';\nimport { formatEvent, formatConsent } from './format';\nimport { getAccessToken } from './auth';\nimport { getConfig } from './config';\n\nexport const push: PushFn = async function (\n event,\n { config, rule, data, collector, env, logger },\n) {\n // Validate config and get typed settings\n const validatedConfig = getConfig(config, logger);\n const {\n destinations,\n eventSource,\n validateOnly = false,\n url = 'https://datamanager.googleapis.com/v1',\n consent: requestConsent,\n testEventCode,\n userData,\n userId,\n clientId,\n sessionAttributes,\n consentAdUserData,\n consentAdPersonalization,\n } = validatedConfig.settings;\n\n // Extract Settings guided helpers\n const userDataMapped = userData\n ? await getMappingValue(event, { map: userData })\n : {};\n const userIdMapped = userId\n ? await getMappingValue(event, userId)\n : undefined;\n const clientIdMapped = clientId\n ? await getMappingValue(event, clientId)\n : undefined;\n const sessionAttributesMapped = sessionAttributes\n ? await getMappingValue(event, sessionAttributes)\n : undefined;\n\n // Extract consent from Settings\n const consentAdUserDataValue =\n typeof consentAdUserData === 'boolean'\n ? consentAdUserData\n : typeof consentAdUserData === 'string' && event.consent\n ? event.consent[consentAdUserData]\n : undefined;\n\n const consentAdPersonalizationValue =\n typeof consentAdPersonalization === 'boolean'\n ? consentAdPersonalization\n : typeof consentAdPersonalization === 'string' && event.consent\n ? event.consent[consentAdPersonalization]\n : undefined;\n\n // Build Settings helpers object\n const settingsHelpers: Record<string, unknown> = {};\n if (isObject(userDataMapped)) {\n Object.assign(settingsHelpers, userDataMapped);\n }\n if (userIdMapped !== undefined) settingsHelpers.userId = userIdMapped;\n if (clientIdMapped !== undefined) settingsHelpers.clientId = clientIdMapped;\n if (sessionAttributesMapped !== undefined)\n settingsHelpers.sessionAttributes = sessionAttributesMapped;\n if (consentAdUserDataValue !== undefined)\n settingsHelpers.adUserData = consentAdUserDataValue;\n if (consentAdPersonalizationValue !== undefined)\n settingsHelpers.adPersonalization = consentAdPersonalizationValue;\n\n // Get mapped data from destination config and event mapping\n const configData = validatedConfig.data\n ? await getMappingValue(event, validatedConfig.data)\n : {};\n const eventData = isObject(data) ? data : {};\n\n // Merge: Settings helpers < config.data < event mapping data\n const finalData = {\n ...settingsHelpers,\n ...(isObject(configData) ? configData : {}),\n ...eventData,\n };\n\n // Format event for Data Manager API\n const dataManagerEvent = await formatEvent(event, finalData);\n\n // Apply event source from settings (required)\n if (!dataManagerEvent.eventSource) {\n dataManagerEvent.eventSource = eventSource;\n }\n\n // Apply request-level consent if event doesn't have consent\n if (!dataManagerEvent.consent && requestConsent) {\n dataManagerEvent.consent = requestConsent;\n }\n\n // Validate required fields before API call\n if (!dataManagerEvent.transactionId) {\n logger.throw('transactionId is required');\n }\n\n // Check if any destination is GA4 (requires eventName)\n const hasGA4Destination = destinations.some(\n (d) => d.operatingAccount?.accountType === 'GOOGLE_ANALYTICS_PROPERTY',\n );\n\n if (hasGA4Destination && !dataManagerEvent.eventName) {\n logger.throw('eventName is required for GA4 destinations');\n }\n\n // Build API request\n const requestBody: IngestEventsRequest = {\n events: [dataManagerEvent],\n destinations,\n };\n\n // Add optional parameters\n if (requestConsent) {\n requestBody.consent = requestConsent;\n }\n\n if (validateOnly) {\n requestBody.validateOnly = true;\n }\n\n if (testEventCode) {\n requestBody.testEventCode = testEventCode;\n }\n\n const authClient = env?.authClient;\n if (!authClient) {\n return logger.throw(\n 'Auth client not initialized. Ensure init() was called successfully.',\n );\n }\n\n let accessToken: string;\n try {\n accessToken = await getAccessToken(authClient);\n } catch (error) {\n logger.error('Authentication failed', { error });\n throw error;\n }\n\n const fetchFn = env?.fetch || fetch;\n const endpoint = `${url}/events:ingest`;\n\n logger.debug('Sending to Data Manager API', {\n endpoint,\n eventCount: requestBody.events.length,\n destinations: destinations.length,\n validateOnly,\n });\n\n const response = await fetchFn(endpoint, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n logger.throw(`Data Manager API error (${response.status}): ${errorText}`);\n }\n\n const result: IngestEventsResponse = await response.json();\n\n logger.debug('API response', {\n status: response.status,\n requestId: result.requestId,\n });\n\n // If validation errors exist, throw them\n if (result.validationErrors && result.validationErrors.length > 0) {\n logger.throw(\n `Validation errors: ${JSON.stringify(result.validationErrors)}`,\n );\n }\n};\n","import type { WalkerOS } from '@walkeros/core';\nimport { isString, isDefined } from '@walkeros/core';\nimport type {\n Event,\n UserData,\n UserIdentifier,\n AdIdentifiers,\n Consent,\n ConsentStatus,\n} from './types';\nimport { hashEmail, hashPhone, hashName } from './hash';\n\n/**\n * Format walkerOS event timestamp to RFC 3339 format\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n *\n * walkerOS timestamp is in milliseconds, RFC 3339 format: \"2024-01-15T10:30:00Z\"\n */\nexport function formatTimestamp(timestamp: number): string {\n return new Date(timestamp).toISOString();\n}\n\n/**\n * Format user identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n *\n * User data must be explicitly mapped in the mapping configuration.\n * Max 10 identifiers per event\n */\nexport async function formatUserData(\n data: Record<string, unknown>,\n): Promise<UserData | undefined> {\n const identifiers: UserIdentifier[] = [];\n\n // Extract from mapped data only\n // Email\n if (isString(data.email) && data.email) {\n const hashedEmail = await hashEmail(data.email);\n if (hashedEmail) {\n identifiers.push({ emailAddress: hashedEmail });\n }\n }\n\n // Phone\n if (isString(data.phone) && data.phone) {\n const hashedPhone = await hashPhone(data.phone);\n if (hashedPhone) {\n identifiers.push({ phoneNumber: hashedPhone });\n }\n }\n\n // Address from mapped properties\n const hasAddress =\n data.firstName || data.lastName || data.regionCode || data.postalCode;\n\n if (hasAddress) {\n const address: Record<string, string> = {};\n\n if (isString(data.firstName) && data.firstName) {\n address.givenName = await hashName(data.firstName, 'given');\n }\n\n if (isString(data.lastName) && data.lastName) {\n address.familyName = await hashName(data.lastName, 'family');\n }\n\n // Region code is NOT hashed\n if (isString(data.regionCode) && data.regionCode) {\n address.regionCode = data.regionCode.toUpperCase();\n }\n\n // Postal code is NOT hashed\n if (isString(data.postalCode) && data.postalCode) {\n address.postalCode = data.postalCode;\n }\n\n if (Object.keys(address).length > 0) {\n identifiers.push({ address });\n }\n }\n\n // Limit to 10 identifiers\n if (identifiers.length === 0) return undefined;\n\n return {\n userIdentifiers: identifiers.slice(0, 10),\n };\n}\n\n/**\n * Extract and format attribution identifiers from mapped data\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n *\n * Attribution identifiers should be mapped explicitly in the mapping configuration.\n * Example: { gclid: 'context.gclid', gbraid: 'context.gbraid' }\n */\nexport function formatAdIdentifiers(\n data: Record<string, unknown>,\n): AdIdentifiers | undefined {\n const identifiers: AdIdentifiers = {};\n\n // Extract from mapped data (already processed by mapping system)\n if (isString(data.gclid) && data.gclid) {\n identifiers.gclid = data.gclid;\n }\n if (isString(data.gbraid) && data.gbraid) {\n identifiers.gbraid = data.gbraid;\n }\n if (isString(data.wbraid) && data.wbraid) {\n identifiers.wbraid = data.wbraid;\n }\n if (isString(data.sessionAttributes) && data.sessionAttributes) {\n identifiers.sessionAttributes = data.sessionAttributes;\n }\n\n return Object.keys(identifiers).length > 0 ? identifiers : undefined;\n}\n\n/**\n * Map walkerOS consent to Data Manager consent format\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n *\n * walkerOS: { marketing: true, personalization: false }\n * Data Manager: { adUserData: 'CONSENT_GRANTED', adPersonalization: 'CONSENT_DENIED' }\n */\nexport function formatConsent(\n walkerOSConsent: WalkerOS.Consent | undefined,\n): Consent | undefined {\n if (!walkerOSConsent) return undefined;\n\n const consent: Consent = {};\n\n // Map marketing consent to adUserData\n if (isDefined(walkerOSConsent.marketing)) {\n consent.adUserData = walkerOSConsent.marketing\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // Map personalization consent to adPersonalization\n if (isDefined(walkerOSConsent.personalization)) {\n consent.adPersonalization = walkerOSConsent.personalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n return Object.keys(consent).length > 0 ? consent : undefined;\n}\n\n/**\n * Format complete event for Data Manager API\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport async function formatEvent(\n event: WalkerOS.Event,\n mappedData?: Record<string, unknown>,\n): Promise<Event> {\n const dataManagerEvent: Event = {\n eventTimestamp: formatTimestamp(event.timestamp),\n };\n\n // Use only mapped data (no fallback to event.data)\n const data = mappedData || {};\n\n // Transaction ID for deduplication\n if (isString(data.transactionId) && data.transactionId) {\n dataManagerEvent.transactionId = data.transactionId.substring(0, 512);\n }\n\n // Client ID (GA)\n if (isString(data.clientId) && data.clientId) {\n dataManagerEvent.clientId = data.clientId.substring(0, 255);\n }\n\n // User ID\n if (isString(data.userId) && data.userId) {\n dataManagerEvent.userId = data.userId.substring(0, 256);\n }\n\n // User data\n const userData = await formatUserData(data);\n if (userData) {\n dataManagerEvent.userData = userData;\n }\n\n // Attribution identifiers\n const adIdentifiers = formatAdIdentifiers(data);\n if (adIdentifiers) {\n dataManagerEvent.adIdentifiers = adIdentifiers;\n }\n\n // Conversion value\n if (typeof data.conversionValue === 'number') {\n dataManagerEvent.conversionValue = data.conversionValue;\n }\n\n // Currency\n if (isString(data.currency) && data.currency) {\n dataManagerEvent.currency = data.currency.substring(0, 3).toUpperCase();\n }\n\n // Cart data\n if (data.cartData && typeof data.cartData === 'object') {\n dataManagerEvent.cartData = data.cartData as Event['cartData'];\n }\n\n // Event name (for GA4)\n if (isString(data.eventName) && data.eventName) {\n dataManagerEvent.eventName = data.eventName.substring(0, 40);\n }\n\n // Event source\n if (isString(data.eventSource) && data.eventSource) {\n dataManagerEvent.eventSource = data.eventSource as Event['eventSource'];\n }\n\n // Consent - check mapped data first, then fallback to event.consent\n const mappedConsent: Consent = {};\n\n // Check for mapped consent values (from Settings or event mapping)\n if (typeof data.adUserData === 'boolean') {\n mappedConsent.adUserData = data.adUserData\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n if (typeof data.adPersonalization === 'boolean') {\n mappedConsent.adPersonalization = data.adPersonalization\n ? 'CONSENT_GRANTED'\n : 'CONSENT_DENIED';\n }\n\n // If no mapped consent, fall back to event.consent\n if (Object.keys(mappedConsent).length === 0) {\n const eventConsent = formatConsent(event.consent);\n if (eventConsent) {\n dataManagerEvent.consent = eventConsent;\n }\n } else {\n dataManagerEvent.consent = mappedConsent;\n }\n\n return dataManagerEvent;\n}\n","import { isString } from '@walkeros/core';\nimport { getHashServer } from '@walkeros/server-core';\n\n/**\n * Normalize email address according to Google Data Manager requirements\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#email\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove dots (.) for gmail.com and googlemail.com\n * 4. SHA-256 hash\n */\nexport async function hashEmail(email: string): Promise<string> {\n if (!isString(email) || !email) return '';\n\n // Trim and lowercase\n let normalized = email.trim().toLowerCase();\n\n // Remove dots for Gmail addresses\n if (\n normalized.endsWith('@gmail.com') ||\n normalized.endsWith('@googlemail.com')\n ) {\n const [localPart, domain] = normalized.split('@');\n normalized = `${localPart.replace(/\\./g, '')}@${domain}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize phone number to E.164 format and hash\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#phone\n *\n * E.164 format: +[country code][number] (max 15 digits after +)\n * Example: +18005550100\n *\n * 1. Remove all non-digit characters except leading +\n * 2. Ensure it starts with +\n * 3. SHA-256 hash\n */\nexport async function hashPhone(phone: string): Promise<string> {\n if (!isString(phone) || !phone) return '';\n\n // Remove all non-digit characters except + at the start\n let normalized = phone.trim();\n\n // Extract country code if present\n const hasPlus = normalized.startsWith('+');\n\n // Remove all non-digits\n normalized = normalized.replace(/\\D/g, '');\n\n // Add + prefix if it was there or if number is long enough\n if (hasPlus || normalized.length > 10) {\n normalized = `+${normalized}`;\n } else {\n // Assume US number if no country code (default behavior)\n normalized = `+1${normalized}`;\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Normalize and hash a name (first or last name)\n * https://developers.google.com/data-manager/api/devguides/concepts/formatting#name\n *\n * 1. Trim whitespace\n * 2. Convert to lowercase\n * 3. Remove common prefixes (Mr., Mrs., Dr.) for first names\n * 4. Remove common suffixes (Jr., Sr., III) for last names\n * 5. SHA-256 hash\n */\nexport async function hashName(\n name: string,\n type: 'given' | 'family' = 'given',\n): Promise<string> {\n if (!isString(name) || !name) return '';\n\n // Trim and lowercase\n let normalized = name.trim().toLowerCase();\n\n // Remove prefixes for given names\n if (type === 'given') {\n const prefixes = ['mr.', 'mrs.', 'ms.', 'miss.', 'dr.', 'prof.'];\n for (const prefix of prefixes) {\n if (normalized.startsWith(prefix)) {\n normalized = normalized.substring(prefix.length).trim();\n break;\n }\n }\n }\n\n // Remove suffixes for family names (check with and without space)\n // Sort by length (longest first) to match \"iii\" before \"ii\"\n if (type === 'family') {\n const suffixes = ['jr.', 'sr.', 'iii', 'ii', 'iv', 'v'];\n for (const suffix of suffixes) {\n // Check for suffix with space before it (e.g., \" jr.\")\n const suffixWithSpace = ` ${suffix}`;\n if (normalized.endsWith(suffixWithSpace)) {\n normalized = normalized\n .substring(0, normalized.length - suffixWithSpace.length)\n .trim();\n break;\n }\n // Check for suffix without space\n if (normalized.endsWith(suffix)) {\n normalized = normalized\n .substring(0, normalized.length - suffix.length)\n .trim();\n break;\n }\n }\n }\n\n return getHashServer(normalized);\n}\n\n/**\n * Hash multiple email addresses\n */\nexport async function hashEmails(emails: string[]): Promise<string[]> {\n return Promise.all(emails.map((email) => hashEmail(email)));\n}\n\n/**\n * Hash multiple phone numbers\n */\nexport async function hashPhones(phones: string[]): Promise<string[]> {\n return Promise.all(phones.map((phone) => hashPhone(phone)));\n}\n","import { GoogleAuth, type OAuth2Client } from 'google-auth-library';\nimport type { Settings } from './types';\n\nconst DEFAULT_SCOPES = ['https://www.googleapis.com/auth/datamanager'];\n\n/**\n * Authentication error with cause tracking\n */\nexport class AuthError extends Error {\n constructor(\n message: string,\n public cause?: Error,\n ) {\n super(message);\n this.name = 'DataManagerAuthError';\n }\n}\n\n/**\n * Creates Google Auth client based on settings\n *\n * Authentication priority:\n * 1. credentials (inline service account) - if provided\n * 2. keyFilename (service account file) - if provided\n * 3. Application Default Credentials (ADC) - automatic fallback\n * - GOOGLE_APPLICATION_CREDENTIALS env var\n * - GCP metadata server (Cloud Functions, Cloud Run, GCE)\n *\n * @param settings - Configuration with auth options\n * @returns OAuth2Client for token retrieval\n * @throws AuthError if authentication fails\n */\nexport async function createAuthClient(\n settings: Settings,\n): Promise<OAuth2Client> {\n const { credentials, keyFilename, scopes = DEFAULT_SCOPES } = settings;\n\n try {\n if (credentials) {\n const auth = new GoogleAuth({\n credentials,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n if (keyFilename) {\n const auth = new GoogleAuth({\n keyFilename,\n scopes,\n });\n return (await auth.getClient()) as OAuth2Client;\n }\n\n const auth = new GoogleAuth({ scopes });\n return (await auth.getClient()) as OAuth2Client;\n } catch (error) {\n throw new AuthError(\n 'Failed to create auth client. Check credentials configuration or ensure GOOGLE_APPLICATION_CREDENTIALS is set.',\n error instanceof Error ? error : undefined,\n );\n }\n}\n\n/**\n * Gets access token from auth client\n * Automatically returns cached token if valid or refreshes if expired\n *\n * @param authClient - OAuth2 client from createAuthClient()\n * @returns Fresh access token\n * @throws AuthError if token retrieval fails\n */\nexport async function getAccessToken(\n authClient: OAuth2Client,\n): Promise<string> {\n try {\n const tokenResponse = await authClient.getAccessToken();\n\n if (!tokenResponse.token) {\n throw new AuthError('Auth client returned empty token');\n }\n\n return tokenResponse.token;\n } catch (error) {\n throw new AuthError(\n 'Failed to obtain access token',\n error instanceof Error ? error : undefined,\n );\n }\n}\n","import type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationServer } from '@walkeros/server-core';\nimport type { OAuth2Client } from 'google-auth-library';\n\nexport interface Settings {\n /**\n * Service account credentials (client_email + private_key)\n * Recommended for serverless environments (AWS Lambda, Docker, etc.)\n */\n credentials?: {\n client_email: string;\n private_key: string;\n };\n\n /**\n * Path to service account JSON file\n * For local development or environments with filesystem access\n */\n keyFilename?: string;\n\n /**\n * OAuth scopes for Data Manager API\n * @default ['https://www.googleapis.com/auth/datamanager']\n */\n scopes?: string[];\n\n /** Array of destination accounts and conversion actions/user lists */\n destinations: Destination[];\n\n /** Event source for all events. Defaults to WEB if not specified */\n eventSource?: EventSource;\n\n /** Maximum number of events to batch before sending (max 2000) */\n batchSize?: number;\n\n /** Time in milliseconds to wait before auto-flushing batch */\n batchInterval?: number;\n\n /** If true, validate request without ingestion (testing mode) */\n validateOnly?: boolean;\n\n /** Override API endpoint (for testing) */\n url?: string;\n\n /** Request-level consent for all events */\n consent?: Consent;\n\n /** Test event code for debugging (optional) */\n testEventCode?: string;\n\n /** Guided helpers: User data mapping (applies to all events) */\n userData?: WalkerOSMapping.Map;\n\n /** Guided helper: First-party user ID */\n userId?: WalkerOSMapping.Value;\n\n /** Guided helper: GA4 client ID */\n clientId?: WalkerOSMapping.Value;\n\n /** Guided helper: Privacy-safe attribution (Google's sessionAttributes) */\n sessionAttributes?: WalkerOSMapping.Value;\n\n /** Consent mapping: Map consent field to adUserData (string = field name, boolean = static value) */\n consentAdUserData?: string | boolean;\n\n /** Consent mapping: Map consent field to adPersonalization (string = field name, boolean = static value) */\n consentAdPersonalization?: string | boolean;\n}\n\nexport interface Mapping {\n // Attribution identifiers (optional, for explicit mapping)\n gclid?: WalkerOSMapping.Value;\n gbraid?: WalkerOSMapping.Value;\n wbraid?: WalkerOSMapping.Value;\n sessionAttributes?: WalkerOSMapping.Value;\n}\n\nexport interface Env extends DestinationServer.Env {\n fetch?: typeof fetch;\n authClient?: OAuth2Client | null;\n}\n\nexport type InitSettings = Partial<Settings>;\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport interface DestinationInterface\n extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: Settings;\n} & DestinationServer.Config<Types>;\n\n/**\n * Config after validation - settings is guaranteed to exist with required fields\n * Use this type after calling getConfig() to get proper type narrowing\n * After validation, eventSource is always set (defaults to 'WEB')\n */\nexport type ValidatedConfig = Omit<Config, 'settings'> & {\n settings: Settings & { eventSource: EventSource };\n};\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\n\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\n\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\n\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n\n// Google Data Manager API Types\n// https://developers.google.com/data-manager/api/reference\n\n/**\n * Destination account and product identifier\n * https://developers.google.com/data-manager/api/reference/rest/v1/Destination\n */\nexport interface Destination {\n /** Reference identifier for this destination */\n reference?: string;\n\n /** Login account (account initiating the request) */\n loginAccount?: ProductAccount;\n\n /** Linked account (child account linked to login account) */\n linkedAccount?: ProductAccount;\n\n /** Operating account (account where data is sent) */\n operatingAccount?: ProductAccount;\n\n /** Product-specific destination ID (conversion action or user list) */\n productDestinationId?: string;\n}\n\n/**\n * Product account information\n */\nexport interface ProductAccount {\n /** Account ID (e.g., \"123-456-7890\" for Google Ads) */\n accountId: string;\n\n /** Type of account */\n accountType: AccountType;\n}\n\nexport type AccountType =\n | 'ACCOUNT_TYPE_UNSPECIFIED'\n | 'GOOGLE_ADS'\n | 'DISPLAY_VIDEO_ADVERTISER'\n | 'DISPLAY_VIDEO_PARTNER'\n | 'GOOGLE_ANALYTICS_PROPERTY'\n | 'DATA_PARTNER';\n\nexport type EventSource = 'WEB' | 'APP' | 'IN_STORE' | 'PHONE' | 'OTHER';\n\n/**\n * Consent for Digital Markets Act (DMA) compliance\n * https://developers.google.com/data-manager/api/devguides/concepts/dma\n */\nexport interface Consent {\n /** Consent for data collection and use */\n adUserData?: ConsentStatus;\n\n /** Consent for ad personalization */\n adPersonalization?: ConsentStatus;\n}\n\nexport type ConsentStatus = 'CONSENT_GRANTED' | 'CONSENT_DENIED';\n\n/**\n * Request body for events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/events/ingest\n */\nexport interface IngestEventsRequest {\n /** Array of destinations for these events (max 10) */\n destinations: Destination[];\n\n /** Array of events to ingest (max 2000) */\n events: Event[];\n\n /** Request-level consent (overridden by event-level) */\n consent?: Consent;\n\n /** If true, validate without ingestion */\n validateOnly?: boolean;\n\n /** Test event code for debugging */\n testEventCode?: string;\n}\n\n/**\n * Single event for ingestion\n * https://developers.google.com/data-manager/api/reference/rest/v1/Event\n */\nexport interface Event {\n /** Destination references for routing */\n destinationReferences?: string[];\n\n /** Transaction ID for deduplication (max 512 chars) */\n transactionId?: string;\n\n /** Event timestamp in RFC 3339 format */\n eventTimestamp?: string;\n\n /** Last updated timestamp in RFC 3339 format */\n lastUpdatedTimestamp?: string;\n\n /** User data with identifiers (max 10 identifiers) */\n userData?: UserData;\n\n /** Event-level consent (overrides request-level) */\n consent?: Consent;\n\n /** Attribution identifiers */\n adIdentifiers?: AdIdentifiers;\n\n /** Currency code (ISO 4217, 3 chars) */\n currency?: string;\n\n /** Conversion value */\n conversionValue?: number;\n\n /** Source of the event */\n eventSource?: EventSource;\n\n /** Device information for the event */\n eventDeviceInfo?: DeviceInfo;\n\n /** Shopping cart data */\n cartData?: CartData;\n\n /** Custom variables for the event */\n customVariables?: CustomVariable[];\n\n /** Experimental fields (subject to change) */\n experimentalFields?: ExperimentalField[];\n\n /** User properties */\n userProperties?: UserProperties;\n\n /** Event name for GA4 (max 40 chars, required for GA4) */\n eventName?: string;\n\n /** Google Analytics client ID (max 255 chars) */\n clientId?: string;\n\n /** First-party user ID (max 256 chars) */\n userId?: string;\n\n /** Additional event parameters */\n additionalEventParameters?: EventParameter[];\n}\n\n/**\n * Device information\n */\nexport interface DeviceInfo {\n /** User agent string */\n userAgent?: string;\n}\n\n/**\n * Custom variable\n */\nexport interface CustomVariable {\n /** Variable name */\n name?: string;\n\n /** Variable value */\n value?: string;\n}\n\n/**\n * Experimental field\n */\nexport interface ExperimentalField {\n /** Field name */\n name?: string;\n\n /** Field value */\n value?: string;\n}\n\n/**\n * User properties\n */\nexport interface UserProperties {\n /** Property values */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Event parameter\n */\nexport interface EventParameter {\n /** Parameter name */\n name?: string;\n\n /** Parameter value */\n value?: string | number;\n}\n\n/**\n * User data with identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/UserData\n */\nexport interface UserData {\n /** Array of user identifiers (max 10) */\n userIdentifiers: UserIdentifier[];\n}\n\n/**\n * User identifier (email, phone, or address)\n */\nexport type UserIdentifier =\n | { emailAddress: string }\n | { phoneNumber: string }\n | { address: Address };\n\n/**\n * Address for user identification\n * https://developers.google.com/data-manager/api/reference/rest/v1/Address\n */\nexport interface Address {\n /** Given name (first name) - SHA-256 hashed */\n givenName?: string;\n\n /** Family name (last name) - SHA-256 hashed */\n familyName?: string;\n\n /** ISO-3166-1 alpha-2 country code - NOT hashed (e.g., \"US\", \"GB\") */\n regionCode?: string;\n\n /** Postal code - NOT hashed */\n postalCode?: string;\n}\n\n/**\n * Attribution identifiers\n * https://developers.google.com/data-manager/api/reference/rest/v1/AdIdentifiers\n */\nexport interface AdIdentifiers {\n /** Session attributes (privacy-safe attribution) */\n sessionAttributes?: string;\n\n /** Google Click ID (primary attribution) */\n gclid?: string;\n\n /** iOS attribution identifier (post-ATT) */\n gbraid?: string;\n\n /** Web-to-app attribution identifier */\n wbraid?: string;\n\n /** Device information for landing page */\n landingPageDeviceInfo?: DeviceInfo;\n}\n\n/**\n * Shopping cart data\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartData\n */\nexport interface CartData {\n /** Array of cart items (max 200) */\n items: CartItem[];\n}\n\n/**\n * Single cart item\n * https://developers.google.com/data-manager/api/reference/rest/v1/CartItem\n */\nexport interface CartItem {\n /** Merchant product ID (max 127 chars) */\n merchantProductId?: string;\n\n /** Item price */\n price?: number;\n\n /** Item quantity */\n quantity?: number;\n}\n\n/**\n * Response from events.ingest API\n * https://developers.google.com/data-manager/api/reference/rest/v1/IngestEventsResponse\n */\nexport interface IngestEventsResponse {\n /** Unique request ID for status checking */\n requestId: string;\n\n /** Validation errors (only if validateOnly=true) */\n validationErrors?: ValidationError[];\n}\n\n/**\n * Validation error\n */\nexport interface ValidationError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Field path that caused the error */\n fieldPath?: string;\n}\n\n/**\n * Request status response\n * https://developers.google.com/data-manager/api/reference/rest/v1/requestStatus/retrieve\n */\nexport interface RequestStatusResponse {\n /** Unique request ID */\n requestId: string;\n\n /** Processing state */\n state: RequestState;\n\n /** Number of events successfully ingested */\n eventsIngested?: number;\n\n /** Number of events that failed */\n eventsFailed?: number;\n\n /** Array of errors (if any) */\n errors?: RequestError[];\n}\n\nexport type RequestState =\n | 'STATE_UNSPECIFIED'\n | 'PENDING'\n | 'PROCESSING'\n | 'SUCCEEDED'\n | 'FAILED'\n | 'PARTIALLY_SUCCEEDED';\n\n/**\n * Request error\n */\nexport interface RequestError {\n /** Error code */\n code: string;\n\n /** Human-readable error message */\n message: string;\n\n /** Number of events affected by this error */\n eventCount?: number;\n}\n","import type { DestinationInterface } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\nimport { createAuthClient } from './auth';\n\nexport * as DestinationDataManager from './types';\n\nexport const destinationDataManager: DestinationInterface = {\n type: 'datamanager',\n\n config: {},\n\n async init({ config: partialConfig, env, logger }) {\n // getConfig validates required fields and returns ValidatedConfig\n const config = getConfig(partialConfig, logger);\n\n try {\n const authClient = await createAuthClient(config.settings);\n logger.debug('Auth client created');\n\n return {\n ...config,\n env: {\n ...env,\n authClient,\n },\n };\n } catch (error) {\n logger.throw(\n `Data Manager authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n },\n\n async push(event, context) {\n return await push(event, context);\n },\n};\n\nexport default destinationDataManager;\n"],"mappings":"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","names":["isString","isString","auth"]}
|
package/dist/walkerOS.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$meta": {
|
|
3
3
|
"package": "@walkeros/server-destination-datamanager",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.0-next-1777463920154",
|
|
5
5
|
"type": "destination",
|
|
6
6
|
"platform": [
|
|
7
7
|
"server"
|
|
@@ -264,35 +264,21 @@
|
|
|
264
264
|
"entity": "child",
|
|
265
265
|
"data": {
|
|
266
266
|
"is": "subordinated"
|
|
267
|
-
},
|
|
268
|
-
"nested": [],
|
|
269
|
-
"context": {
|
|
270
|
-
"element": [
|
|
271
|
-
"child",
|
|
272
|
-
0
|
|
273
|
-
]
|
|
274
267
|
}
|
|
275
268
|
}
|
|
276
269
|
],
|
|
277
270
|
"consent": {
|
|
278
271
|
"functional": true
|
|
279
272
|
},
|
|
280
|
-
"id": "1700000902000
|
|
273
|
+
"id": "ev-1700000902000",
|
|
281
274
|
"trigger": "load",
|
|
282
275
|
"entity": "page",
|
|
283
276
|
"action": "view",
|
|
284
277
|
"timestamp": 1700000902000,
|
|
285
278
|
"timing": 3.14,
|
|
286
|
-
"group": "gr0up",
|
|
287
|
-
"count": 1,
|
|
288
|
-
"version": {
|
|
289
|
-
"source": "3.4.2",
|
|
290
|
-
"tagging": 1
|
|
291
|
-
},
|
|
292
279
|
"source": {
|
|
293
|
-
"type": "
|
|
294
|
-
"
|
|
295
|
-
"previous_id": ""
|
|
280
|
+
"type": "express",
|
|
281
|
+
"platform": "server"
|
|
296
282
|
}
|
|
297
283
|
},
|
|
298
284
|
"mapping": {
|
|
@@ -317,7 +303,7 @@
|
|
|
317
303
|
"Authorization": "Bearer ya29.c.test_token",
|
|
318
304
|
"Content-Type": "application/json"
|
|
319
305
|
},
|
|
320
|
-
"body": "{\"events\":[{\"eventTimestamp\":\"2023-11-14T22:28:22.000Z\",\"transactionId\":\"1700000902000
|
|
306
|
+
"body": "{\"events\":[{\"eventTimestamp\":\"2023-11-14T22:28:22.000Z\",\"transactionId\":\"ev-1700000902000\",\"userId\":\"visitor-55\",\"eventName\":\"page_view\",\"eventSource\":\"WEB\"}],\"destinations\":[{\"operatingAccount\":{\"accountId\":\"123-456-7890\",\"accountType\":\"GOOGLE_ADS\"},\"productDestinationId\":\"AW-CONVERSION-123\"}]}"
|
|
321
307
|
}
|
|
322
308
|
]
|
|
323
309
|
]
|
|
@@ -350,35 +336,21 @@
|
|
|
350
336
|
"entity": "child",
|
|
351
337
|
"data": {
|
|
352
338
|
"is": "subordinated"
|
|
353
|
-
},
|
|
354
|
-
"nested": [],
|
|
355
|
-
"context": {
|
|
356
|
-
"element": [
|
|
357
|
-
"child",
|
|
358
|
-
0
|
|
359
|
-
]
|
|
360
339
|
}
|
|
361
340
|
}
|
|
362
341
|
],
|
|
363
342
|
"consent": {
|
|
364
343
|
"functional": true
|
|
365
344
|
},
|
|
366
|
-
"id": "1700000901000
|
|
345
|
+
"id": "ev-1700000901000",
|
|
367
346
|
"trigger": "test",
|
|
368
347
|
"entity": "form",
|
|
369
348
|
"action": "submit",
|
|
370
349
|
"timestamp": 1700000901000,
|
|
371
350
|
"timing": 3.14,
|
|
372
|
-
"group": "gr0up",
|
|
373
|
-
"count": 1,
|
|
374
|
-
"version": {
|
|
375
|
-
"source": "3.4.2",
|
|
376
|
-
"tagging": 1
|
|
377
|
-
},
|
|
378
351
|
"source": {
|
|
379
|
-
"type": "
|
|
380
|
-
"
|
|
381
|
-
"previous_id": ""
|
|
352
|
+
"type": "express",
|
|
353
|
+
"platform": "server"
|
|
382
354
|
}
|
|
383
355
|
},
|
|
384
356
|
"mapping": {
|
|
@@ -409,7 +381,7 @@
|
|
|
409
381
|
"Authorization": "Bearer ya29.c.test_token",
|
|
410
382
|
"Content-Type": "application/json"
|
|
411
383
|
},
|
|
412
|
-
"body": "{\"events\":[{\"eventTimestamp\":\"2023-11-14T22:28:21.000Z\",\"transactionId\":\"1700000901000
|
|
384
|
+
"body": "{\"events\":[{\"eventTimestamp\":\"2023-11-14T22:28:21.000Z\",\"transactionId\":\"ev-1700000901000\",\"userData\":{\"userIdentifiers\":[{\"emailAddress\":\"395ec5f334be0ab5b28568a1e7f6ed5ea80e443fb1ce3d803340586a3df46642\"}]},\"conversionValue\":10,\"currency\":\"USD\",\"eventName\":\"generate_lead\",\"eventSource\":\"WEB\"}],\"destinations\":[{\"operatingAccount\":{\"accountId\":\"123-456-7890\",\"accountType\":\"GOOGLE_ADS\"},\"productDestinationId\":\"AW-CONVERSION-123\"}]}"
|
|
413
385
|
}
|
|
414
386
|
]
|
|
415
387
|
]
|
|
@@ -491,22 +463,15 @@
|
|
|
491
463
|
"consent": {
|
|
492
464
|
"functional": true
|
|
493
465
|
},
|
|
494
|
-
"id": "1700000900000
|
|
466
|
+
"id": "ev-1700000900000",
|
|
495
467
|
"trigger": "load",
|
|
496
468
|
"entity": "order",
|
|
497
469
|
"action": "complete",
|
|
498
470
|
"timestamp": 1700000900000,
|
|
499
471
|
"timing": 3.14,
|
|
500
|
-
"group": "gr0up",
|
|
501
|
-
"count": 1,
|
|
502
|
-
"version": {
|
|
503
|
-
"source": "3.4.2",
|
|
504
|
-
"tagging": 1
|
|
505
|
-
},
|
|
506
472
|
"source": {
|
|
507
|
-
"type": "
|
|
508
|
-
"
|
|
509
|
-
"previous_id": ""
|
|
473
|
+
"type": "express",
|
|
474
|
+
"platform": "server"
|
|
510
475
|
}
|
|
511
476
|
},
|
|
512
477
|
"mapping": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walkeros/server-destination-datamanager",
|
|
3
3
|
"description": "Google Data Manager server destination for walkerOS",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.0-next-1777463920154",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
"update": "npx npm-check-updates -u && npm update"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@walkeros/core": "
|
|
38
|
-
"@walkeros/server-core": "
|
|
37
|
+
"@walkeros/core": "4.0.0-next-1777463920154",
|
|
38
|
+
"@walkeros/server-core": "4.0.0-next-1777463920154",
|
|
39
39
|
"google-auth-library": "^10.5.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@walkeros/collector": "
|
|
42
|
+
"@walkeros/collector": "4.0.0-next-1777463920154"
|
|
43
43
|
},
|
|
44
44
|
"repository": {
|
|
45
45
|
"url": "git+https://github.com/elbwalker/walkerOS.git",
|