mcp-scraper 0.96.3 → 0.97.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/README.md +12 -18
  3. package/dist/{actions-FKAX2PYT.js → actions-NCZNPYEY.js} +1 -1
  4. package/dist/bin/api-server.js +1 -1
  5. package/dist/bin/mcp-scraper-cli.js +26 -27
  6. package/dist/bin/mcp-scraper-core.js +1 -1
  7. package/dist/bin/mcp-scraper-install.js +1 -1
  8. package/dist/bin/mcp-stdio-server.js +1 -1
  9. package/dist/{bundle-7JMACIIR.js → bundle-XR6XKYJM.js} +1 -1
  10. package/dist/chunk-4P3LZNVA.js +954 -0
  11. package/dist/chunk-6FAMBNHY.js +1 -0
  12. package/dist/{chunk-3HXMW2UJ.js → chunk-A4UASMEQ.js} +1 -1
  13. package/dist/chunk-AMYPXA2F.js +930 -0
  14. package/dist/chunk-B5Y26E3A.js +1 -0
  15. package/dist/{chunk-WP3QS4CJ.js → chunk-EYGA6LOU.js} +1 -1
  16. package/dist/{chunk-P55O4IDC.js → chunk-JNPM3U3L.js} +1 -1
  17. package/dist/chunk-ND4WIBXT.js +16 -0
  18. package/dist/{chunk-PANT6VEM.js → chunk-NULRGHJ4.js} +1 -1
  19. package/dist/{chunk-42SLBGDI.js → chunk-OOB3RKWD.js} +2 -2
  20. package/dist/{chunk-DJT2547J.js → chunk-OSKCHEVM.js} +1 -1
  21. package/dist/{chunk-U7BZ5KTE.js → chunk-PD7BI7TR.js} +1 -1
  22. package/dist/{chunk-WBJ6Y7WP.js → chunk-PQRT4T2G.js} +2 -2
  23. package/dist/chunk-U3NV72AL.js +8 -0
  24. package/dist/{chunk-4KHDV5E7.js → chunk-WHVK6FUB.js} +1 -1
  25. package/dist/{chunk-E7I3OQHQ.js → chunk-XYUJTIPE.js} +1 -1
  26. package/dist/{chunk-JREMJRSS.js → chunk-YJPFEL2E.js} +1 -1
  27. package/dist/{chunk-CFZXZKFT.js → chunk-Z4EHBBL4.js} +1 -1
  28. package/dist/{core-schema-423BSTE6.js → core-schema-Y47YFKH2.js} +7 -44
  29. package/dist/{dispatch-E6VTNHHS.js → dispatch-ILBBKOSW.js} +1 -1
  30. package/dist/{queue-4XHYUV7J.js → queue-VOGXQDPR.js} +1 -1
  31. package/dist/{reconciliation-43CSC3XD.js → reconciliation-OXBG7JA2.js} +1 -1
  32. package/dist/{server-74VGVXA4.js → server-6HUBN3OR.js} +700 -1016
  33. package/dist/settlement-3PS32LQO.js +1 -0
  34. package/dist/{worker-L5NISZQV.js → worker-NXRU3TT3.js} +1 -1
  35. package/package.json +2 -2
  36. package/dist/chunk-2IATUJO6.js +0 -1
  37. package/dist/chunk-7ZR5IO3B.js +0 -16
  38. package/dist/chunk-JGGQ3UX5.js +0 -1
  39. package/dist/chunk-MJOXIDKF.js +0 -935
  40. package/dist/chunk-OY24ERPV.js +0 -8
  41. package/dist/chunk-QYPSFZK3.js +0 -955
  42. package/dist/settlement-I5IBIK6U.js +0 -1
@@ -1,935 +0,0 @@
1
- import{H as sn,_ as an,b as ut,c as je,e as tn,t as nn,z as rn}from"./chunk-WBJ6Y7WP.js";import{B as gn,a as f,b as v,c as ue,d as Oe,e as yt,f as ln,g as un,h as U,i as $,l as _n,m as mn,n as pn,p as gt,u as B,v as Q,z as yn}from"./chunk-NNOX3FYG.js";import{a as qe}from"./chunk-NALXKTUU.js";import{a as Kt,b as Jt,f as zt}from"./chunk-DJT2547J.js";import{d as on,g as He,i as We,k as _t,m as mt,o as cn,r as Be,t as dn,u as pt}from"./chunk-2IATUJO6.js";import{$ as en,A as Yt,F as Gt,J as ct,N as L,O as Xt,X as Qt,Y as Zt,Z as Re,_ as dt,aa as lt,ca as te,ea as le,f as Ue,z as Fe}from"./chunk-OY24ERPV.js";import{a as I}from"./chunk-WNUXMPVX.js";import{randomUUID as Fi}from"crypto";async function fn(e,t,n){return(await I().execute({sql:`INSERT OR IGNORE INTO stripe_events
2
- (event_id, event_type, stripe_created_at, status, attempt_count, received_at, next_attempt_at, processed_at, updated_at)
3
- VALUES (?, ?, ?, 'pending', 0, datetime('now'), datetime('now'), datetime('now'), datetime('now'))`,args:[e,t,n]})).rowsAffected===1?"queued":"duplicate"}async function ra(e=50,t=300){let n=Math.max(1,Math.min(500,Math.trunc(e))),i=await I().execute({sql:`SELECT event_id
4
- FROM stripe_events
5
- WHERE (status IN ('pending', 'retryable') AND COALESCE(next_attempt_at, received_at) <= datetime('now'))
6
- OR (status = 'processing' AND claimed_at <= datetime('now', '-' || ? || ' seconds'))
7
- ORDER BY COALESCE(next_attempt_at, received_at), received_at
8
- LIMIT ?`,args:[t,n]}),r=[];for(let s of i.rows){let a=String(s.event_id),o=Fi();if((await I().execute({sql:`UPDATE stripe_events
9
- SET status = 'processing', attempt_count = attempt_count + 1,
10
- claim_token = ?, claimed_at = datetime('now'), last_error = NULL,
11
- last_error_code = NULL, updated_at = datetime('now')
12
- WHERE event_id = ?
13
- AND ((status IN ('pending', 'retryable') AND COALESCE(next_attempt_at, received_at) <= datetime('now'))
14
- OR (status = 'processing' AND claimed_at <= datetime('now', '-' || ? || ' seconds')))`,args:[o,a,t]})).rowsAffected!==1)continue;let l=await I().execute({sql:"SELECT event_type, attempt_count FROM stripe_events WHERE event_id = ? LIMIT 1",args:[a]});r.push({eventId:a,eventType:l.rows[0]?.event_type!=null?String(l.rows[0].event_type):null,attemptCount:Number(l.rows[0]?.attempt_count??1),token:o})}return r}async function sa(e,t){return(await I().execute({sql:`UPDATE stripe_events
15
- SET status = 'processed',
16
- claim_token = NULL,
17
- claimed_at = NULL,
18
- next_attempt_at = NULL,
19
- completed_at = datetime('now'),
20
- last_error = NULL,
21
- processed_at = datetime('now'),
22
- updated_at = datetime('now')
23
- WHERE event_id = ? AND status = 'processing' AND claim_token = ?`,args:[e,t]})).rowsAffected===1}async function aa(e,t,n,i,r){return(await I().execute({sql:`UPDATE stripe_events
24
- SET status = 'retryable',
25
- claim_token = NULL,
26
- claimed_at = NULL,
27
- last_error = ?,
28
- last_error_code = ?,
29
- next_attempt_at = datetime('now', '+' || ? || ' seconds'),
30
- updated_at = datetime('now')
31
- WHERE event_id = ? AND status = 'processing' AND claim_token = ?`,args:[n.slice(0,500),i.slice(0,100),Math.max(0,Math.trunc(r)),e,t]})).rowsAffected===1}async function oa(e,t,n,i){return(await I().execute({sql:`UPDATE stripe_events
32
- SET status = 'dead_letter', claim_token = NULL, claimed_at = NULL,
33
- last_error = ?, last_error_code = ?, next_attempt_at = NULL, updated_at = datetime('now')
34
- WHERE event_id = ? AND status = 'processing' AND claim_token = ?`,args:[n.slice(0,500),i.slice(0,100),e,t]})).rowsAffected===1}async function ca(){let t=(await I().execute(`
35
- SELECT
36
- SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending,
37
- SUM(CASE WHEN status = 'processing' THEN 1 ELSE 0 END) AS processing,
38
- SUM(CASE WHEN status = 'retryable' THEN 1 ELSE 0 END) AS retryable,
39
- SUM(CASE WHEN status = 'dead_letter' THEN 1 ELSE 0 END) AS dead_letter,
40
- MIN(CASE WHEN status IN ('pending', 'retryable') THEN COALESCE(next_attempt_at, received_at) END) AS oldest_due_at
41
- FROM stripe_events
42
- `)).rows[0];return{pending:Number(t?.pending??0),processing:Number(t?.processing??0),retryable:Number(t?.retryable??0),dead_letter:Number(t?.dead_letter??0),oldest_due_at:t?.oldest_due_at!=null?String(t.oldest_due_at):null}}import zs from"stripe";import{Hono as Ys}from"hono";import{waitUntil as Gs}from"@vercel/functions";async function P(e){let t=await I().execute({sql:"SELECT * FROM users WHERE stripe_customer_id = ? AND active = 1",args:[e]});return t.rows[0]?ut(t.rows[0]):void 0}async function Ve(e,t){await I().execute({sql:"UPDATE users SET stripe_customer_id = ? WHERE id = ?",args:[t,e]})}async function ft(e,t){if(!Number.isSafeInteger(t)||t<0)throw new Error("concurrency pack quantity must be a non-negative safe integer");await I().execute({sql:"UPDATE users SET concurrency_pack_quantity = ?, extra_concurrency_slots = ? WHERE id = ?",args:[t,t*2,e]})}async function vt(e,t){await I().execute({sql:"UPDATE users SET concurrency_stripe_sub_id = ? WHERE id = ?",args:[t,e]})}async function Et(e,t,n,i){await I().execute({sql:"UPDATE users SET subscription_tier = ?, subscription_concurrency = ?, subscription_id = ? WHERE id = ?",args:[t,Math.max(0,Math.round(n)),i,e]})}async function vn(e){if(!e.invoiceId||e.invoiceId.length>500)throw new Error("invoice id is required and must be bounded");if(!Number.isSafeInteger(e.creditsMc)||e.creditsMc<=0)throw new Error("subscription credit amount must be a positive integer");await Jt(e.userId);let t=I(),n=e.neverExpires?"9999-12-31 23:59:59":null,i=await t.batch([{sql:`INSERT OR IGNORE INTO ledger (user_id, amount_mc, operation, description, stripe_pi)
43
- VALUES (?, ?, 'subscription', ?, ?)`,args:[e.userId,e.creditsMc,e.description,e.invoiceId]},{sql:`INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, stripe_pi, expires_at, never_expires)
44
- SELECT ?, ?, ?, 'subscription', ?, COALESCE(?, datetime('now', ?)), ?
45
- WHERE changes() = 1`,args:[e.userId,e.creditsMc,e.creditsMc,e.invoiceId,n,Kt,e.neverExpires?1:0]},{sql:"UPDATE users SET balance_mc = balance_mc + ? WHERE id = ? AND changes() = 1",args:[e.creditsMc,e.userId]},{sql:`UPDATE users
46
- SET subscription_tier = ?, subscription_concurrency = ?, subscription_id = ?,
47
- memory_plan = CASE WHEN ? IS NOT NULL THEN ? ELSE memory_plan END,
48
- memory_subscription_id = CASE WHEN ? IS NOT NULL THEN NULL ELSE memory_subscription_id END
49
- WHERE id = ?
50
- AND EXISTS (
51
- SELECT 1 FROM ledger
52
- WHERE stripe_pi = ? AND user_id = ? AND amount_mc = ? AND operation = 'subscription'
53
- )`,args:[e.tier,Math.max(0,Math.round(e.concurrency)),e.subscriptionId,e.includedMemoryPlan,e.includedMemoryPlan,e.includedMemoryPlan,e.userId,e.invoiceId,e.userId,e.creditsMc]}],"write"),s=(await t.execute({sql:`SELECT l.user_id, l.amount_mc, l.operation, u.balance_mc
54
- FROM ledger l JOIN users u ON u.id = l.user_id
55
- WHERE l.stripe_pi = ? LIMIT 1`,args:[e.invoiceId]})).rows[0];if(!s||Number(s.user_id)!==e.userId||Number(s.amount_mc)!==e.creditsMc||String(s.operation)!=="subscription")throw new Error("invoice idempotency key conflicts with an existing grant");return{granted:i[0]?.rowsAffected===1,balance_mc:Number(s.balance_mc??0)}}async function _e(e,t,n){await I().execute({sql:"UPDATE users SET memory_plan = ?, memory_subscription_id = ? WHERE id = ?",args:[t,n,e]})}async function ma(e){let n=(await I().execute({sql:"SELECT memory_provisioned FROM users WHERE id = ?",args:[e]})).rows[0];return Number(n?.memory_provisioned??0)===1}async function En(e,t){await I().execute({sql:"UPDATE users SET memory_provisioned = ? WHERE id = ?",args:[t?1:0,e]})}async function bt(e){let t=await I().execute({sql:"SELECT * FROM users WHERE memory_subscription_id = ? AND active = 1",args:[e]});return t.rows[0]?ut(t.rows[0]):void 0}import{createCipheriv as Ji,createDecipheriv as zi,randomBytes as Yi,scryptSync as Gi}from"crypto";import{setScheduleEntitlementTool as Xi}from"mcpscraper-memory-tools/tools/schedule/set-schedule-entitlement";import{createHash as ji,randomUUID as qi}from"crypto";import{exportJWK as Hi,importPKCS8 as Wi,SignJWT as Bi}from"jose";function Vi(e=process.env.OAUTH_ISSUER){let t=e?.trim()||"https://mcpscraper.dev",n=new URL(t);if(n.protocol!=="https:"||n.username||n.password||n.search||n.hash||n.pathname!=="/"&&n.pathname!=="")throw new Error("OAUTH_ISSUER must be an HTTPS origin without credentials, path, query, or fragment");return n.origin}var Ki=Vi(),Ke=null;async function bn(){if(Ke)return Ke;let e=process.env.OAUTH_SIGNING_KEY;if(!e?.trim())throw new Error("OAUTH_SIGNING_KEY is not set");let t=e.includes("BEGIN")?e:Buffer.from(e,"base64").toString("utf8"),n=await Wi(t,"RS256",{extractable:!0}),i=await Hi(n),r={kty:i.kty,n:i.n,e:i.e},s=ji("sha256").update(JSON.stringify({e:r.e,kty:r.kty,n:r.n})).digest("base64url").slice(0,16);return r.kid=s,r.alg="RS256",r.use="sig",Ke={privateKey:n,publicJwk:r,kid:s},Ke}async function fa(){return(await bn()).publicJwk}async function In(e){let{privateKey:t,kid:n}=await bn();return new Bi({scope:e.scope,plan:e.plan}).setProtectedHeader({alg:"RS256",kid:n}).setIssuer(Ki).setSubject(e.identity).setAudience(e.audience).setIssuedAt().setJti(qi()).setExpirationTime(`${e.ttlSeconds}s`).sign(t)}var Qi=()=>(process.env.MCP_MEMORY_BASE_URL??"https://memory.mcpscraper.dev").replace(/\/$/,""),Zi=()=>(process.env.MCP_MEMORY_RESOURCE_URL??"https://memory.mcpscraper.dev/mcp").replace(/\/$/,""),er=()=>process.env.MCP_MEMORY_ADMIN_KEY?.trim()??"",tr=300;function nr(e){let t=e.split(`
56
- `).find(i=>i.startsWith("data:")),n=t?t.slice(5).trim():e;return JSON.parse(n)}function It(e){return e?(process.env.MEMORY_OPERATOR_IDENTITIES??"").split(",").map(n=>n.trim().toLowerCase()).filter(Boolean).includes(e.trim().toLowerCase()):!1}function An(){return Gi(qe(),"mcp-memory-key-v1",32)}function Na(e){let t=Yi(12),n=Ji("aes-256-gcm",An(),t),i=Buffer.concat([n.update(e,"utf8"),n.final()]),r=n.getAuthTag();return`${t.toString("base64")}:${r.toString("base64")}:${i.toString("base64")}`}function Ca(e){try{let[t,n,i]=e.split(":"),r=zi("aes-256-gcm",An(),Buffer.from(t,"base64"));return r.setAuthTag(Buffer.from(n,"base64")),Buffer.concat([r.update(Buffer.from(i,"base64")),r.final()]).toString("utf8")}catch{return null}}async function Sn(e,t,n){try{let i=await fetch(`${Qi()}/mcp`,{method:"POST",headers:{"content-type":"application/json",accept:"application/json, text/event-stream",authorization:`Bearer ${n}`},body:JSON.stringify({jsonrpc:"2.0",id:1,method:"tools/call",params:{name:e,arguments:t}})}),r=await i.text();if(!i.ok)return{ok:!1,error:`memory ${e} failed (${i.status})`};let s=nr(r);if(s.error)return{ok:!1,error:s.error.message??`memory ${e} failed`};if(s.result?.structuredContent)return s.result.structuredContent;let a=s.result?.content?.find(o=>o.type==="text")?.text;if(a)try{return JSON.parse(a)}catch{return{ok:!1,error:a}}return{ok:!1,error:`memory ${e} returned no result`}}catch(i){return{ok:!1,error:i?.message??"memory call failed"}}}function hn(e){return e.email}function ir(e){return hn(e)}function wa(e){return It(e.email)?"unlimited":e.memory_plan==="pro"||e.memory_plan==="team"?e.memory_plan:"free"}async function Ra(e){try{let{key:t}=await Nn(e);if(!t)return;(await Sn("listVaultsTool",{},t)).ok&&await En(e.id,!0)}catch{}}function rr(e){return e?!!Xt[e]?.includes_memory:!1}function V(e){return e.memory_plan==="team"?"team":rr(e.subscription_tier)||e.memory_plan==="pro"?"pro":"free"}async function Nn(e,t){try{let n=It(e.email),i=n?"unlimited":t??V(e),r=n?"memory.read memory.write memory.admin":"memory.read memory.write";return{key:await In({identity:hn(e),scope:r,plan:i,audience:Zi(),ttlSeconds:tr})}}catch(n){return{key:null,error:n instanceof Error?n.message:"memory service authorization unavailable"}}}async function Z(e,t){let n=It(e.email)?"unlimited":t,{key:i,error:r}=await Nn(e,n);if(!i)return{ok:!1,error:r??"memory unavailable"};let s=await Sn("listVaultsTool",{},i);return{ok:s.ok,error:s.error}}async function Je(e){let t=er();if(!t)return{ok:!1,error:"memory not configured (MCP_MEMORY_ADMIN_KEY missing)"};let n=ir(e),i=await Xi.execute({apiKey:t,granteeIdentity:n,mcpScraperApiKey:e.api_key},{});return{ok:i.ok,error:i.error}}var Cn=["active","trialing","past_due"];function De(e){return Cn.includes(e)}function wn(e,t){for(let n of Cn){let i=e.find(r=>r.status===n&&t(r));if(i)return i}}var sr="price_1TtYPAS8aAcsk3TGHOgrDZiQ";function ar(e=new Date){return`${e.getUTCFullYear()}-${String(e.getUTCMonth()+1).padStart(2,"0")}`}var or=new Set(["connected","needs_reauth"]);function cr(e){return or.has(String(e.lifecycleStatus??""))}async function dr(e,t){let n=await I().execute({sql:"SELECT connection_id FROM connected_account_charges WHERE user_id = ? AND period_start = ?",args:[Number(e),t]});return new Set(n.rows.map(i=>String(i.connection_id)))}async function lr(e,t,n){await sn();let i=Number(e),r=I();return await r.batch([{sql:`
57
- INSERT OR IGNORE INTO connected_account_charge_transfers (
58
- user_id, period_start, from_connection_id, to_connection_id, provider_config_key
59
- )
60
- SELECT ?, ?, previous.id, replacement.id, replacement.provider_config_key
61
- FROM service_connections AS replacement
62
- JOIN service_connections AS previous
63
- ON previous.user_id = replacement.user_id
64
- AND previous.transport = 'nango'
65
- AND previous.provider_config_key = replacement.provider_config_key
66
- AND previous.lifecycle_status = 'disconnected'
67
- JOIN connected_account_charges AS charge
68
- ON charge.user_id = previous.user_id
69
- AND charge.connection_id = previous.id
70
- AND charge.period_start = ?
71
- WHERE replacement.id = ?
72
- AND replacement.user_id = ?
73
- AND replacement.transport = 'nango'
74
- AND replacement.lifecycle_status IN ('connected', 'needs_reauth')
75
- AND NOT EXISTS (
76
- SELECT 1 FROM connected_account_charges AS existing
77
- WHERE existing.user_id = replacement.user_id
78
- AND existing.connection_id = replacement.id
79
- AND existing.period_start = ?
80
- )
81
- ORDER BY previous.updated_at DESC, previous.id ASC
82
- LIMIT 1
83
- `,args:[i,n,n,t,i,n]},{sql:`
84
- UPDATE connected_account_charges
85
- SET connection_id = ?
86
- WHERE user_id = ?
87
- AND period_start = ?
88
- AND connection_id = (
89
- SELECT transfer.from_connection_id
90
- FROM connected_account_charge_transfers AS transfer
91
- JOIN connected_account_charges AS current_charge
92
- ON current_charge.user_id = transfer.user_id
93
- AND current_charge.period_start = transfer.period_start
94
- AND current_charge.connection_id = transfer.from_connection_id
95
- WHERE transfer.user_id = ?
96
- AND transfer.period_start = ?
97
- AND transfer.to_connection_id = ?
98
- ORDER BY transfer.transferred_at DESC, transfer.id DESC
99
- LIMIT 1
100
- )
101
- AND NOT EXISTS (
102
- SELECT 1 FROM connected_account_charges AS existing
103
- WHERE existing.user_id = ?
104
- AND existing.connection_id = ?
105
- AND existing.period_start = ?
106
- )
107
- `,args:[t,i,n,i,n,t,i,t,n]}],"write"),(await r.execute({sql:"SELECT 1 FROM connected_account_charges WHERE user_id = ? AND connection_id = ? AND period_start = ? LIMIT 1",args:[i,t,n]})).rows.length>0}async function ht(e){let t=await I().execute({sql:"SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1",args:[Number(e)]});return t.rows[0]?t.rows[0]:null}async function At(e){await I().execute({sql:`INSERT INTO connected_account_billing
108
- (user_id, stripe_subscription_id, stripe_subscription_item_id, price_id, quantity, status, last_error_code, checked_at, synced_at, updated_at)
109
- VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), CASE WHEN ? THEN datetime('now') ELSE NULL END, datetime('now'))
110
- ON CONFLICT(user_id) DO UPDATE SET
111
- stripe_subscription_id = excluded.stripe_subscription_id,
112
- stripe_subscription_item_id = excluded.stripe_subscription_item_id,
113
- price_id = excluded.price_id,
114
- quantity = excluded.quantity,
115
- status = excluded.status,
116
- last_error_code = excluded.last_error_code,
117
- checked_at = datetime('now'),
118
- synced_at = CASE WHEN ? THEN datetime('now') ELSE connected_account_billing.synced_at END,
119
- updated_at = datetime('now')`,args:[Number(e.userId),e.stripeSubscriptionId,e.stripeSubscriptionItemId,e.priceId,e.quantity,e.status,e.lastErrorCode,e.synced?1:0,e.synced?1:0]});let t=await ht(e.userId);if(!t)throw new Error("connected account billing state was not persisted");return t}var K=class extends Error{constructor(n,i,r=409,s){super(n);this.code=i;this.status=r;this.details=s;this.name="ConnectedAccountBillingError"}code;status;details};function Nt(){let e=process.env.CONNECTED_ACCOUNT_PRICE_ID?.trim();return e||sr||null}function St(e,t){let n=Math.max(0,Math.round(e));return{provider:"nango",billingMode:"flat_recurring_credits",priceId:Nt(),unitAmountCredits:Fe,interval:"month",quantity:n,projectedMonthlyCredits:n*Fe,status:t?.status??"pending",lastErrorCode:t?.last_error_code??null,syncedAt:t?.synced_at??null,usage:Gt.usage}}function me(e){return e.items.data.find(t=>!!t.price?.id&&t.price.id in L)}function Ma(e){return wn(e,t=>!!me(t))}function ur(e){return e instanceof K?e.code:"stripe_connection_billing_failed"}async function ze(e,t,n){await At({userId:e.id,stripeSubscriptionId:e.subscription_id,stripeSubscriptionItemId:null,priceId:Nt(),quantity:t,status:n instanceof K&&n.code!=="stripe_connection_billing_failed"?"blocked":"error",lastErrorCode:ur(n),synced:!1})}async function Ua(e,t){let n=[...new Set(t.filter(cr).map(c=>c.connectionId))],i=n.length,r=Nt();if(!r){let c=new K("Connected-account billing is not configured.","connected_account_price_not_configured",503);throw await ze(e,i,c),c}if(!e.subscription_id||!(e.subscription_tier??"")||!Object.values(L).some(c=>c.tier===e.subscription_tier)){if(i===0){let l=await At({userId:e.id,stripeSubscriptionId:null,stripeSubscriptionItemId:null,priceId:r,quantity:0,status:"synced",lastErrorCode:null,synced:!0});return St(0,l)}let c=new K("An active paid plan is required before connected accounts can be billed.","paid_plan_required",403);throw await ze(e,i,c),c}let s=await ht(e.id),a=Math.max(0,s?.quantity??0),o=ar();try{let c=await dr(e.id,o),l=n.filter(d=>!c.has(d));for(let d of l){if(await lr(e.id,d,o))continue;let p=Yt,E=await zt(e.id,p,Ue.CONNECTED_ACCOUNT_FEE,`1 connected account at ${Fe} credits/month (${o})`,`connection-fee:${d}:${o}`);if(!E.ok){let b=new K(`Insufficient credits to connect. Balance: ${te(E.balance_mc)} credits. Connecting requires ${te(p)} credits (${te(p-E.balance_mc)} short). Upgrade your plan or wait for your credits to refresh.`,"insufficient_credits",402,{balanceCredits:te(E.balance_mc),requiredCredits:te(p),shortByCredits:te(Math.max(0,p-E.balance_mc))});throw await ze(e,a,b),b}await I().execute({sql:`INSERT INTO connected_account_charges (user_id, connection_id, period_start, amount_mc)
120
- VALUES (?, ?, ?, ?) ON CONFLICT(user_id, connection_id, period_start) DO NOTHING`,args:[Number(e.id),d,o,p]}),E.duplicate||await rn(e.id,1)}let _=await At({userId:e.id,stripeSubscriptionId:e.subscription_id,stripeSubscriptionItemId:null,priceId:r,quantity:i,status:"synced",lastErrorCode:null,synced:!0});return St(i,_)}catch(c){throw c instanceof K?c:(await ze(e,a,c),new K("Unable to update connected-account billing.","stripe_connection_billing_failed",503))}}async function Fa(e,t){return St(t,await ht(e))}import{createHash as Bc,createHmac as jt,generateKeyPairSync as Ss,randomUUID as de,sign as hs}from"crypto";import Ns from"twilio";import{z as u}from"zod";import{z as R}from"zod";var _r=new Set(["AED","AFN","ALL","AMD","ANG","AOA","ARS","AUD","AWG","AZN","BAM","BBD","BDT","BGN","BHD","BIF","BMD","BND","BOB","BOV","BRL","BSD","BTN","BWP","BYN","BZD","CAD","CDF","CHE","CHF","CHW","CLF","CLP","CNY","COP","COU","CRC","CUP","CVE","CZK","DJF","DKK","DOP","DZD","EGP","ERN","ETB","EUR","FJD","FKP","GBP","GEL","GHS","GIP","GMD","GNF","GTQ","GYD","HKD","HNL","HTG","HUF","IDR","ILS","INR","IQD","IRR","ISK","JMD","JOD","JPY","KES","KGS","KHR","KMF","KPW","KRW","KWD","KYD","KZT","LAK","LBP","LKR","LRD","LSL","LYD","MAD","MDL","MGA","MKD","MMK","MNT","MOP","MRU","MUR","MVR","MWK","MXN","MXV","MYR","MZN","NAD","NGN","NIO","NOK","NPR","NZD","OMR","PAB","PEN","PGK","PHP","PKR","PLN","PYG","QAR","RON","RSD","RUB","RWF","SAR","SBD","SCR","SDG","SEK","SGD","SHP","SLE","SOS","SRD","SSP","STN","SVC","SYP","SZL","THB","TJS","TMT","TND","TOP","TRY","TTD","TWD","TZS","UAH","UGX","USD","USN","UYI","UYU","UYW","UZS","VED","VES","VND","VUV","WST","XAF","XAG","XAU","XBA","XBB","XBC","XBD","XCD","XDR","XOF","XPD","XPF","XPT","XSU","XTS","XUA","XXX","YER","ZAR","ZMW","ZWG"]),Te=R.string().trim().length(3).transform(e=>e.toUpperCase()).refine(e=>_r.has(e),"Provide a valid ISO-4217 currency."),Ye=R.number().int("Money must be represented as integer minor units.").min(0,"Money cannot be negative.").max(Number.MAX_SAFE_INTEGER),Ge=R.string().trim().min(1).max(80).default("1"),mr=R.enum(["semantic_markup","source_event","crm_field","transaction"]),pr=R.object({mode:R.literal("none"),ruleVersion:Ge}).strict(),yr=R.object({mode:R.literal("fixed"),valueMinor:Ye,currency:Te,ruleVersion:Ge}).strict(),gr=R.object({mode:R.literal("dynamic"),source:mr,currency:Te,fallbackValueMinor:Ye.nullable().default(null),ruleVersion:Ge}).strict(),Rn=R.discriminatedUnion("mode",[pr,yr,gr]),fr=R.object({linkedEventName:R.string().trim().regex(/^[a-z][a-z0-9_]{1,79}$/),relation:R.enum(["before","after","either"]),intervalSeconds:R.number().int().min(0).max(10*365*24*60*60).nullable(),overrideValueMinor:Ye,currency:Te}).strict(),Xe=R.object({targetEventName:R.string().trim().regex(/^[a-z][a-z0-9_]{1,79}$/),base:Rn,conditionalOverride:fr.nullable().default(null),ruleVersion:Ge}).strict().superRefine((e,t)=>{let n=e.conditionalOverride;n&&e.base.mode!=="none"&&n.currency!==e.base.currency&&t.addIssue({code:"custom",path:["conditionalOverride","currency"],message:"Conditional override currency must match the base value currency."})});function ne(e,t){let n=Date.parse(e);if(!Number.isFinite(n))throw new Error(`${t} must be an ISO timestamp.`);return n}function vr(e,t){let n=Ye.parse(e.valueMinor),i=On(t,e.currency);return ne(e.observedAt,"observedAt"),{valueMinor:n,currency:i,observedAt:new Date(e.observedAt).toISOString()}}function Er(e){return Rn.parse(e)}function On(e,t){let i=(Array.isArray(e)?e:[e,t]).filter(r=>typeof r=="string"&&r.trim().length>0).map(r=>Te.parse(r));if(i.length===0)throw new Error("A currency is required for a monetary value.");if(new Set(i).size!==1)throw new Error(`Mixed currencies are not compatible: ${[...new Set(i)].join(", ")}.`);return i[0]}function br(e,t={}){let n=Er(e);if(n.mode==="none")return{valueMinor:null,currency:null,provenance:{mode:"none",source:null,observedAt:null,fallback:!1,ruleVersion:n.ruleVersion,resolution:"absent",baseValueMinor:null,resolvedValueMinor:null,currency:null,linkedEvent:null}};if(n.mode==="fixed"){let s=t.observedAt?new Date(ne(t.observedAt,"observedAt")).toISOString():null;return{valueMinor:n.valueMinor,currency:n.currency,provenance:{mode:"fixed",source:null,observedAt:s,fallback:!1,ruleVersion:n.ruleVersion,resolution:"fixed",baseValueMinor:n.valueMinor,resolvedValueMinor:n.valueMinor,currency:n.currency,linkedEvent:null}}}let i=t.observations?.[n.source];if(i){let s=vr(i,n.currency);return{valueMinor:s.valueMinor,currency:s.currency,provenance:{mode:"dynamic",source:n.source,observedAt:s.observedAt,fallback:!1,ruleVersion:n.ruleVersion,resolution:"dynamic",baseValueMinor:s.valueMinor,resolvedValueMinor:s.valueMinor,currency:s.currency,linkedEvent:null}}}let r=n.fallbackValueMinor;return{valueMinor:r,currency:r===null?null:n.currency,provenance:{mode:"dynamic",source:n.source,observedAt:null,fallback:r!==null,ruleVersion:n.ruleVersion,resolution:r===null?"absent":"fallback",baseValueMinor:r,resolvedValueMinor:r,currency:r===null?null:n.currency,linkedEvent:null}}}function Ir(e,t,n){let i=ne(t.occurredAt,"targetEvent.occurredAt"),r=n.filter(s=>{if(s.eventName!==e.linkedEventName)return!1;let o=(ne(s.occurredAt,"linkedEvent.occurredAt")-i)/1e3;return e.relation==="before"&&o>0||e.relation==="after"&&o<0?!1:e.intervalSeconds===null||Math.abs(o)<=e.intervalSeconds});return r.sort((s,a)=>{let o=Math.abs(ne(s.occurredAt,"linkedEvent.occurredAt")-i),c=Math.abs(ne(a.occurredAt,"linkedEvent.occurredAt")-i);return o-c||s.eventId.localeCompare(a.eventId)}),r[0]??null}function Dn(e,t){let n=Xe.parse(e);if(t.targetEvent.eventName!==n.targetEventName)throw new Error(`Value rule targets ${n.targetEventName}, not ${t.targetEvent.eventName}.`);let i=br({...n.base,ruleVersion:n.ruleVersion},t);if(!n.conditionalOverride)return i;let r=Ir(n.conditionalOverride,t.targetEvent,t.linkedEvents);if(!r)return i;if(t.providerOperation==="restatement"&&r.identityTier!=="confirmed")throw new Error("Candidate-only evidence cannot be used for a provider restatement.");let s=i.currency?On(i.currency,n.conditionalOverride.currency):Te.parse(n.conditionalOverride.currency);return{valueMinor:n.conditionalOverride.overrideValueMinor,currency:s,provenance:{...i.provenance,ruleVersion:n.ruleVersion,resolution:"conditional_override",resolvedValueMinor:n.conditionalOverride.overrideValueMinor,currency:s,linkedEvent:{eventId:r.eventId,eventName:r.eventName,occurredAt:new Date(ne(r.occurredAt,"linkedEvent.occurredAt")).toISOString(),identityTier:r.identityTier??"candidate"}}}}var Tn=u.object({gclid:u.string().trim().max(500).optional(),gbraid:u.string().trim().max(500).optional(),wbraid:u.string().trim().max(500).optional(),fbclid:u.string().trim().max(500).optional(),fbp:u.string().trim().max(500).optional(),fbc:u.string().trim().max(500).optional(),ttclid:u.string().trim().max(500).optional(),rdt_cid:u.string().trim().max(500).optional(),msclkid:u.string().trim().max(500).optional(),li_fat_id:u.string().trim().max(500).optional(),snapclid:u.string().trim().max(500).optional()}).strict(),Ar=u.object({site_analytics:u.enum(["granted","denied","unknown"]),enhanced_matching:u.enum(["granted","denied","unknown"]),advertising_measurement:u.enum(["granted","denied","unknown"])}).strict(),Sr=u.object({analytics:u.enum(["granted","denied","unknown"]).default("unknown"),adUserData:u.enum(["granted","denied","unknown"]).default("unknown"),adPersonalization:u.enum(["granted","denied","unknown"]).default("unknown"),globalPrivacyControl:u.boolean().default(!1),occurredAt:u.string().datetime(),receiptId:u.string().uuid().optional(),policyVersion:u.number().int().positive().optional(),purposes:Ar.optional()}).strict().transform(e=>({...e,purposes:e.purposes??{site_analytics:e.analytics,enhanced_matching:e.adUserData,advertising_measurement:e.adPersonalization}})),xn=u.enum(["browser","form","call","crm","transaction","custom_server","conversion","delivery"]),hr=u.object({schemaVersion:u.literal(1).default(1),eventId:u.string().trim().min(8).max(200),sourceEventId:u.string().trim().min(1).max(300).optional(),source:u.string().trim().min(1).max(80).default("xray"),sourceAccountRef:u.string().trim().max(240).optional(),eventKind:xn,eventName:u.string().trim().regex(/^[a-z][a-z0-9_]{1,79}$/),occurredAt:u.string().datetime(),visitorId:u.string().trim().max(160).optional(),sessionId:u.string().trim().max(160).optional(),personRef:u.string().trim().max(240).optional(),orderId:u.string().trim().max(240).optional(),callId:u.string().trim().max(240).optional(),dealId:u.string().trim().max(240).optional(),path:u.string().trim().max(2e3).optional(),canonicalUrl:u.string().url().max(3e3).optional(),sourceName:u.string().trim().max(180).optional(),medium:u.string().trim().max(180).optional(),campaign:u.string().trim().max(240).optional(),clickIds:Tn.optional(),consent:Sr.optional(),valueMinor:u.number().int().min(0).max(Number.MAX_SAFE_INTEGER).optional(),currency:u.string().trim().regex(/^[A-Za-z]{3}$/).optional(),properties:u.record(u.string().max(80),u.unknown()).default({})}).strict(),Qe=hr.extend({sourceEventId:u.string().trim().min(1).max(300),source:u.string().trim().min(1).max(80),sourceAccountRef:u.string().trim().min(1).max(240)}),Va=u.object({sourceEventId:u.string().trim().min(1).max(300),source:u.string().trim().min(1).max(80),sourceAccountRef:u.string().trim().min(1).max(240),contactId:u.string().trim().max(240).optional(),dealId:u.string().trim().min(1).max(240),pipelineId:u.string().trim().max(240).optional(),stageId:u.string().trim().min(1).max(240),stageName:u.string().trim().max(240).optional(),occurredAt:u.string().datetime(),valueMinor:u.number().int().min(0).max(Number.MAX_SAFE_INTEGER).optional(),currency:u.string().trim().regex(/^[A-Za-z]{3}$/).optional(),visitorId:u.string().trim().max(160).optional(),sessionId:u.string().trim().max(160).optional(),clickIds:Tn.optional(),email:u.string().email().max(320).optional(),phone:u.string().trim().max(80).optional(),orderId:u.string().trim().max(240).optional(),status:u.enum(["open","won","lost","spam","unknown"]).default("unknown")}).strict(),Nr=u.object({eventName:u.string().regex(/^[a-z][a-z0-9_]{1,79}$/).optional(),eventKind:xn.optional(),source:u.string().trim().max(80).optional(),stageId:u.string().trim().max(240).optional(),status:u.enum(["open","won","lost","spam","unknown"]).optional(),answered:u.boolean().optional(),minimumCallDurationSeconds:u.number().int().min(0).max(86400).optional(),excludedTags:u.array(u.string().trim().max(80)).max(30).default([]),requiredTags:u.array(u.string().trim().max(80)).max(30).default([])}).strict(),Ln=u.object({name:u.string().trim().min(1).max(120),conversionKind:u.string().trim().regex(/^[a-z][a-z0-9_]{1,79}$/),condition:Nr,defaultValueMinor:u.number().int().min(0).max(Number.MAX_SAFE_INTEGER).optional(),defaultCurrency:u.string().trim().regex(/^[A-Za-z]{3}$/).default("USD"),valueRule:Xe.optional(),enabled:u.boolean().default(!0)}).strict();function Cr(e){let t=e.trim().toLowerCase().replace(/\.$/,"");if(!t||t.includes("://")||/[/?#]/.test(t))return"";try{let n=new URL(`https://${t}`).hostname.toLowerCase().replace(/\.$/,"");return!n||n.length>253||n!=="localhost"&&!n.includes(".")||n.split(".").some(i=>!i||i.length>63||!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(i))?"":n}catch{return""}}var wr=u.string().trim().max(253).transform(Cr).refine(Boolean,"Provide a valid bare hostname."),xe=u.string().trim().max(80).regex(/^[A-Za-z][A-Za-z0-9_-]{0,79}$/).refine(e=>(e.match(/\d/g)?.length??0)<7,"Semantic dimensions cannot contain phone-like values.").transform(e=>e.toLowerCase()),Rr=u.object({value:u.number().finite().min(0).max(1e12).optional(),currency:u.string().trim().regex(/^[A-Za-z]{3}$/).transform(e=>e.toUpperCase()).optional(),orderId:u.string().trim().min(1).max(200).optional(),contentId:u.string().trim().min(1).max(200).optional(),contentType:u.string().trim().min(1).max(80).optional(),placement:xe.optional(),component:xe.optional(),action:xe.optional(),destination:xe.optional(),plan:xe.optional()}).strict(),Ct=u.object({name:u.string().trim().min(1).max(120),eventName:u.string().trim().regex(/^[a-z][a-z0-9_]{1,79}$/),triggerKind:u.enum(["page","click","form","semantic"]),path:u.string().trim().startsWith("/").max(2e3).optional(),pathMatch:u.enum(["exact","prefix"]).default("exact"),selector:u.string().trim().min(1).max(500).optional(),hostnames:u.array(wr).max(25).transform(e=>[...new Set(e)]).default([]),metadata:Rr.optional(),valueRule:Xe.optional(),oncePerSession:u.boolean().default(!1),enabled:u.boolean().default(!0)}).strict().superRefine((e,t)=>{e.triggerKind==="page"&&!e.path&&t.addIssue({code:"custom",path:["path"],message:"Page triggers require a path."}),["click","form"].includes(e.triggerKind)&&!e.selector&&t.addIssue({code:"custom",path:["selector"],message:"Click and form triggers require a selector."}),e.triggerKind==="semantic"&&(e.path||e.selector)&&t.addIssue({code:"custom",path:["triggerKind"],message:"Semantic triggers use data-xray-event and cannot include a path or selector."})});import{createHmac as Or,randomUUID as wt}from"crypto";import io from"ipaddr.js";import{randomUUID as $n}from"crypto";async function Ga(e){return(await v().query(`SELECT id, site_id, name, status, version, approved_at
121
- FROM analytics_identity_namespaces
122
- WHERE site_id=$1 AND status IN ('draft','approved','disabled')
123
- ORDER BY CASE status WHEN 'approved' THEN 0 WHEN 'draft' THEN 1 ELSE 2 END,
124
- version DESC, name, id`,[e.siteId])).rows.map(n=>({id:n.id,siteId:n.site_id,name:n.name,status:n.status,version:n.version,approvedAt:n.approved_at===null?null:new Date(n.approved_at).toISOString()}))}var kn={domain:"host",application:"server",phone:"call",crm:"crm"},Pn={host:"domain",server:"application",call:"phone",crm:"crm"};function Mn(e,t){let n=t.trim().toLowerCase();if(!n||n.length>500)throw new f("analytics_namespace_source_invalid","Provide a valid namespace source.",400);if(e==="domain"&&(n.includes("://")||/[/?#]/.test(n)||!n.includes(".")))throw new f("analytics_namespace_domain_invalid","Provide a bare approved domain.",400);return n}async function Xa(e){let t=e.name.trim();if(!t||t.length>160)throw new f("analytics_namespace_name_invalid","Provide a namespace name.",400);let n=await v().connect();await n.query("BEGIN");try{await n.query("SELECT pg_advisory_xact_lock(hashtext($1))",[e.siteId]);let r=(await n.query(`SELECT id, site_id, name, status, version, approved_at
125
- FROM analytics_identity_namespaces
126
- WHERE site_id=$1 AND status IN ('draft','approved','disabled')
127
- ORDER BY version DESC LIMIT 1`,[e.siteId])).rows[0];if(r){if(r.name!==t)throw new f("analytics_namespace_already_exists","This site already belongs to an identity namespace.",409);return await n.query("COMMIT"),{id:r.id,siteId:r.site_id,name:r.name,status:r.status,version:r.version,approvedAt:r.approved_at?.toISOString()??null}}let a=(await n.query(`INSERT INTO analytics_identity_namespaces(
128
- id, site_id, name, status, approved_hosts, approved_sources, version, approved_by_user_id
129
- ) VALUES ($1,$2,$3,'draft','[]'::jsonb,'[]'::jsonb,1,$4)
130
- RETURNING id, site_id, name, status, version, approved_at`,[$n(),e.siteId,t,e.createdByUserId])).rows[0];return await n.query("COMMIT"),{id:a.id,siteId:a.site_id,name:a.name,status:a.status,version:a.version,approvedAt:a.approved_at?.toISOString()??null}}catch(i){throw await n.query("ROLLBACK"),i}finally{n.release()}}async function Qa(e){let t=Mn(e.sourceKind,e.sourceRef),n=await v().connect();await n.query("BEGIN");try{let i=await n.query(`SELECT id, status FROM analytics_identity_namespaces
131
- WHERE id=$1 AND site_id=$2 FOR UPDATE`,[e.namespaceId,e.siteId]);if(!i.rows[0])throw new f("analytics_namespace_not_found","Identity namespace not found.",404);if(["archived","disabled"].includes(i.rows[0].status))throw new f("analytics_namespace_inactive","Identity namespace is not active.",409);let r=kn[e.sourceKind],s=await n.query(`INSERT INTO analytics_identity_sources(id, namespace_id, site_id, source_kind, source_ref, status)
132
- VALUES ($1,$2,$3,$4,$5,'approved')
133
- ON CONFLICT(namespace_id, source_kind, source_ref) DO UPDATE
134
- SET status='approved', revoked_at=NULL
135
- RETURNING id, namespace_id, site_id, source_kind, source_ref, status, created_at, revoked_at`,[$n(),e.namespaceId,e.siteId,r,t]);await n.query(`UPDATE analytics_identity_namespaces
136
- SET status='approved', approved_by_user_id=$3, approved_at=COALESCE(approved_at, now()),
137
- approved_hosts=CASE WHEN $4='host'
138
- THEN (SELECT jsonb_agg(DISTINCT value) FROM jsonb_array_elements_text(approved_hosts || to_jsonb($5::text)) value)
139
- ELSE approved_hosts END,
140
- approved_sources=(SELECT jsonb_agg(DISTINCT value) FROM jsonb_array_elements_text(approved_sources || to_jsonb(($4 || ':' || $5)::text)) value),
141
- updated_at=now()
142
- WHERE id=$1 AND site_id=$2`,[e.namespaceId,e.siteId,e.approvedByUserId,r,t]),await n.query("COMMIT");let a=s.rows[0];return{id:a.id,namespaceId:a.namespace_id,siteId:a.site_id,sourceKind:Pn[a.source_kind],sourceRef:a.source_ref,status:a.status,createdAt:a.created_at instanceof Date?a.created_at.toISOString():new Date(a.created_at).toISOString(),revokedAt:a.revoked_at==null?null:a.revoked_at instanceof Date?a.revoked_at.toISOString():new Date(a.revoked_at).toISOString()}}catch(i){throw await n.query("ROLLBACK"),i}finally{n.release()}}async function Ze(e){let t=v();if(!(await t.query(`SELECT id FROM analytics_identity_namespaces
143
- WHERE id=$1 AND site_id=$2 AND status='approved'`,[e.namespaceId,e.siteId])).rows[0])throw new f("analytics_namespace_boundary","The identity namespace is not approved for this site.",403);let i=[...new Set(e.identityNodeIds??[])];if(i.length&&(await t.query(`SELECT id FROM analytics_identity_nodes
144
- WHERE site_id=$1 AND namespace_id=$2 AND id = ANY($3::uuid[])`,[e.siteId,e.namespaceId,i])).rows.length!==i.length)throw new f("analytics_namespace_boundary","Identity evidence cannot cross namespaces.",403);if(e.personId&&!(await t.query("SELECT id FROM analytics_people WHERE id=$1 AND site_id=$2 AND namespace_id=$3",[e.personId,e.siteId,e.namespaceId])).rows[0])throw new f("analytics_namespace_boundary","The person does not belong to this namespace.",403);if(e.source){let r=Mn(e.source.kind,e.source.ref);if(!(await t.query(`SELECT id FROM analytics_identity_sources
145
- WHERE namespace_id=$1 AND site_id=$2 AND source_kind=$3 AND source_ref=$4 AND status='approved'`,[e.namespaceId,e.siteId,kn[e.source.kind],r])).rows[0])throw new f("analytics_namespace_source_unapproved","This identity source is not approved.",403)}}async function Za(e){return await Ze(e),(await v().query(`SELECT id, namespace_id, site_id, source_kind, source_ref, status, created_at, revoked_at
146
- FROM analytics_identity_sources
147
- WHERE namespace_id=$1 AND site_id=$2
148
- ORDER BY source_kind, source_ref`,[e.namespaceId,e.siteId])).rows.map(n=>({id:n.id,namespaceId:n.namespace_id,siteId:n.site_id,sourceKind:Pn[n.source_kind],sourceRef:n.source_ref,status:n.status,createdAt:n.created_at.toISOString(),revokedAt:n.revoked_at?.toISOString()??null}))}function Dr(){let e=process.env.ANALYTICS_IDENTITY_SECRET?.trim()||process.env.SESSION_SECRET?.trim();if(!e)throw new f("analytics_identity_secret_missing","Candidate identity derivation is not configured.",503);return e}function Rt(e,t){return Or("sha256",Dr()).update(`xray-candidate:v1:${e}:${t}`).digest("hex")}async function oo(e){await Ze(e);let t=Math.max(1,Math.min(200,Math.floor(e.limit??50)));return(await v().query(`SELECT a.id, a.model_version, a.first_observed_at, a.last_observed_at, a.expires_at,
149
- array_remove(array_agg(DISTINCT e.feature_kind) FILTER (WHERE e.contribution > 0), NULL) supporting,
150
- array_remove(array_agg(DISTINCT e.feature_kind) FILTER (WHERE e.contribution < 0), NULL) conflicting
151
- FROM analytics_candidate_associations a
152
- LEFT JOIN analytics_candidate_evidence e ON e.association_id=a.id AND e.namespace_id=a.namespace_id
153
- WHERE a.site_id=$1 AND a.namespace_id=$2 AND a.status='active' AND a.confidence_band='medium'
154
- AND a.expires_at > $3::timestamptz
155
- GROUP BY a.id
156
- ORDER BY a.updated_at DESC LIMIT $4`,[e.siteId,e.namespaceId,e.now??new Date().toISOString(),t])).rows.map(i=>({associationId:i.id,band:"medium",supportingFeatures:i.supporting??[],conflictingFeatures:i.conflicting??[],modelVersion:i.model_version,firstObservedAt:i.first_observed_at.toISOString(),lastObservedAt:i.last_observed_at.toISOString(),expiresAt:i.expires_at.toISOString()}))}async function Un(e){await Ze({siteId:e.siteId,namespaceId:e.namespaceId,personId:e.personId});let t=new Date(e.occurredAt??Date.now());if(!e.evidenceRef.trim())throw new f("analytics_candidate_promotion_evidence_required","Deterministic evidence is required.",400);let n=e.promotedBy??"system";if(n==="user"&&!e.promotedByUserId)throw new f("analytics_candidate_promotion_actor_required","User-confirmed promotion requires an actor.",400);let i=Rt("journey_pattern",`${e.evidenceKind}:${e.evidenceRef.trim().toLowerCase()}`),s=await v().connect();await s.query("BEGIN");try{let a=await s.query(`UPDATE analytics_candidate_associations
157
- SET status='promoted', promoted_person_id=$4, promoted_at=$5, updated_at=now()
158
- WHERE id=$1 AND site_id=$2 AND namespace_id=$3
159
- AND (status IN ('active','expired') OR (status='promoted' AND promoted_person_id=$4))
160
- RETURNING id,left_identity_node_id,right_identity_node_id`,[e.associationId,e.siteId,e.namespaceId,e.personId,t.toISOString()]);if(!a.rows[0])throw new f("analytics_candidate_not_found","Candidate association not found in this namespace.",404);return await s.query(`INSERT INTO analytics_candidate_promotions(
161
- id, site_id, namespace_id, association_id, person_id, evidence_kind,
162
- evidence_ref_hmac, promoted_by, promoted_by_user_id, promoted_at
163
- ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)
164
- ON CONFLICT(association_id, person_id) DO NOTHING`,[wt(),e.siteId,e.namespaceId,e.associationId,e.personId,e.evidenceKind,i,n,e.promotedByUserId??null,t.toISOString()]),await s.query(`INSERT INTO analytics_privacy_audits(
165
- id, site_id, namespace_ref, subject_ref, action, idempotency_key,
166
- actor_user_id, result_code, affected_counts, completed_at
167
- ) VALUES ($1,$2,$3,$4,'candidate_promoted',$5,$6,'promoted',$7::jsonb,$8)
168
- ON CONFLICT(site_id, idempotency_key) DO NOTHING`,[wt(),e.siteId,e.namespaceId,e.associationId,`candidate-promotion:${e.associationId}:${e.personId}`,e.promotedByUserId??null,JSON.stringify({candidateAssociations:1,deterministicPeople:1}),t.toISOString()]),await s.query(`INSERT INTO analytics_identity_promotion_receipts(
169
- id,site_id,namespace_id,canonical_person_id,canonical_person_ref_hmac,
170
- evidence_kind,evidence_ref_hmac,prior_person_ids,prior_identity_node_ids,
171
- candidate_association_ids,affected_history_counts,schema_version,idempotency_key,promoted_at
172
- ) VALUES($1,$2,$3,$4,$5,$6,$7,'{}'::uuid[],$8::uuid[],$9::uuid[],$10::jsonb,
173
- 'xray.identity-promotion.v1',$11,$12)
174
- ON CONFLICT(site_id,idempotency_key) DO NOTHING`,[wt(),e.siteId,e.namespaceId,e.personId,Rt("journey_pattern",`person:${e.personId}`),e.evidenceKind,i,[a.rows[0].left_identity_node_id,a.rows[0].right_identity_node_id],[e.associationId],JSON.stringify({events:0,sessions:0,touches:0,conversions:0,candidateTouches:0}),Rt("journey_pattern",`promotion:${e.siteId}:${e.associationId}:${e.personId}:${e.evidenceKind}:${e.evidenceRef}`),t.toISOString()]),await s.query("COMMIT"),{associationId:e.associationId,personId:e.personId,promotedAt:t.toISOString(),reason:e.evidenceKind}}catch(a){throw await s.query("ROLLBACK"),a}finally{s.release()}}import{createHash as zr}from"crypto";import{z}from"zod";import{createHash as Tr,randomUUID as ie}from"crypto";function S(e){return e??v()}function et(e){return Tr("sha256").update(JSON.stringify(e)).digest("hex")}async function re(e){let t=await S(e.db).query("SELECT id::text AS id FROM analytics_connections WHERE id::text=$1 AND site_id=$2",[e.connectionId,e.siteId]);if(!t.rowCount)throw new f("analytics_crm_resource_not_found","CRM resource was not found.",404);return t.rows[0].id}async function po(e){let t=on.parse(e.capability),n=S(e.db),i=await re({siteId:e.siteId,connectionId:e.connectionId,db:n});await n.query(`INSERT INTO analytics_crm_connection_capabilities(
175
- site_id,connection_id,provider,contract_version,mode,schema_fingerprint,allowed_operations,evidence,verified_at,updated_at
176
- ) VALUES($1,$2,$3,1,$4,$5,$6::jsonb,$7::jsonb,$8,now())
177
- ON CONFLICT(connection_id,contract_version) DO UPDATE SET
178
- mode=EXCLUDED.mode,schema_fingerprint=EXCLUDED.schema_fingerprint,
179
- allowed_operations=EXCLUDED.allowed_operations,evidence=EXCLUDED.evidence,
180
- verified_at=EXCLUDED.verified_at,updated_at=now()`,[e.siteId,i,t.provider,t.mode,t.evidence.tenantSchemaFingerprint,JSON.stringify([...new Set(e.allowedOperations)].sort()),JSON.stringify(t.evidence),t.evidence.evaluatedAt])}async function yo(e){let t=He.parse(e.plan),n=S(e.db),i=await re({siteId:e.siteId,connectionId:t.connectionId,db:n}),r=await n.query("SELECT plan,request_fingerprint FROM analytics_crm_provisioning_plans WHERE site_id=$1 AND public_plan_id=$2",[e.siteId,t.planId]);if(r.rowCount){if(r.rows[0].request_fingerprint!==t.requestFingerprint)throw new f("idempotency_conflict","This plan identifier was already used for different arguments.",409);return{plan:He.parse(r.rows[0].plan),reused:!0}}return await n.query(`INSERT INTO analytics_crm_provisioning_plans(
181
- id,site_id,connection_id,provider,contract_version,tenant_schema_version,plan,status,
182
- created_by_user_id,public_plan_id,schema_fingerprint,request_fingerprint,created_at,updated_at
183
- ) VALUES($1,$2,$3,$4,1,$5,$6::jsonb,'preview',$7,$8,$9,$10,$11,$11)`,[ie(),e.siteId,i,t.provider,t.schemaFingerprint,JSON.stringify(t),e.createdByUserId,t.planId,t.schemaFingerprint,t.requestFingerprint,t.createdAt]),{plan:t,reused:!1}}async function go(e){let t=await S(e.db).query("SELECT plan FROM analytics_crm_provisioning_plans WHERE site_id=$1 AND public_plan_id=$2",[e.siteId,e.planId]);if(!t.rowCount)throw new f("analytics_crm_resource_not_found","CRM resource was not found.",404);return He.parse(t.rows[0].plan)}async function fo(e){let t=We.parse(e.receipt),n=S(e.db),i=await n.query("SELECT id::text AS id,request_fingerprint FROM analytics_crm_provisioning_plans WHERE site_id=$1 AND public_plan_id=$2",[e.siteId,t.planId]);if(!i.rowCount)throw new f("analytics_crm_resource_not_found","CRM resource was not found.",404);if(i.rows[0].request_fingerprint!==t.planFingerprint)throw new f("idempotency_conflict","The approved plan no longer matches the persisted plan.",409);let r=await n.query(`SELECT r.receipt FROM analytics_crm_provisioning_receipts r
184
- JOIN analytics_crm_provisioning_plans p ON p.id=r.plan_id
185
- WHERE p.site_id=$1 AND r.public_receipt_id=$2`,[e.siteId,t.receiptId]);if(r.rowCount&&We.parse(r.rows[0].receipt).planFingerprint!==t.planFingerprint)throw new f("idempotency_conflict","This receipt identifier was already used for different arguments.",409);let s=t.status==="tested"?"test_write":"create_field",a=t.status==="failed"?"failed":"accepted";return await n.query(`INSERT INTO analytics_crm_provisioning_receipts(
186
- id,plan_id,operation,status,safe_error_code,warnings,created_at,public_receipt_id,receipt
187
- ) VALUES($1,$2,$3,$4,$5,'[]'::jsonb,$6,$7,$8::jsonb)
188
- ON CONFLICT (public_receipt_id) WHERE public_receipt_id IS NOT NULL
189
- DO UPDATE SET operation=EXCLUDED.operation,status=EXCLUDED.status,safe_error_code=EXCLUDED.safe_error_code,receipt=EXCLUDED.receipt`,[ie(),i.rows[0].id,s,a,t.errorCode??null,t.occurredAt,t.receiptId,JSON.stringify(t)]),await n.query(`UPDATE analytics_crm_provisioning_plans
190
- SET status=$3,approved_by_user_id=$4,approved_at=COALESCE(approved_at,$5),
191
- approved_field_ids=$6::jsonb,updated_at=$5
192
- WHERE site_id=$1 AND public_plan_id=$2`,[e.siteId,t.planId,t.status==="tested"?"verified":t.status==="failed"?"failed":"approved",e.approvedByUserId,t.occurredAt,JSON.stringify([...new Set(e.approvedFieldInternalNames)].sort())]),t}async function vo(e){let t=await S(e.db).query(`SELECT r.receipt FROM analytics_crm_provisioning_receipts r
193
- JOIN analytics_crm_provisioning_plans p ON p.id=r.plan_id
194
- WHERE p.site_id=$1 AND r.public_receipt_id=$2`,[e.siteId,e.receiptId]);if(!t.rowCount)throw new f("analytics_crm_resource_not_found","CRM resource was not found.",404);return We.parse(t.rows[0].receipt)}var Ot=e=>e instanceof Date?e.toISOString():e;function Dt(e){return cn.parse({syncId:e.sync_id,siteId:e.site_id,connectionId:e.connection_id,provider:e.provider,state:e.state,mode:e.mode,cursor:typeof e.cursor=="string"?e.cursor:e.cursor==null?null:JSON.stringify(e.cursor),lastStartedAt:Ot(e.last_started_at),lastSuccessAt:Ot(e.last_success_at),nextRunAt:Ot(e.next_run_at),imported:Number(e.imported),deduplicated:Number(e.deduplicated),rejected:Number(e.rejected),lastErrorCode:e.last_error_code,requestFingerprint:e.request_fingerprint})}async function Eo(e){let t=S(e.db),n=await re({siteId:e.siteId,connectionId:e.connectionId,db:t}),i=et({siteId:e.siteId,connectionId:n,provider:e.provider,mode:e.mode,mapping:e.mapping,mappingVersion:e.mappingVersion??1,capabilityEvidence:e.capabilityEvidence??{},backfillFrom:e.backfillFrom??null}),r=await t.query("SELECT * FROM analytics_crm_sync_runs WHERE site_id=$1 AND idempotency_key=$2",[e.siteId,e.idempotencyKey]);if(r.rowCount){if(r.rows[0].request_fingerprint!==i)throw new f("idempotency_conflict","This Idempotency-Key was already used for different CRM sync arguments.",409);return{reused:!0,status:Dt(r.rows[0])}}let s=await t.query(`INSERT INTO analytics_crm_sync_runs(
195
- sync_id,site_id,connection_id,provider,mode,state,mapping,mapping_version,capability_evidence,idempotency_key,request_fingerprint,last_started_at
196
- ) VALUES($1,$2,$3,$4,$5,'queued',$6::jsonb,$7,$8::jsonb,$9,$10,now()) RETURNING *`,[e.syncId,e.siteId,n,e.provider,e.mode,JSON.stringify(e.mapping),e.mappingVersion??1,JSON.stringify(e.capabilityEvidence??{}),e.idempotencyKey,i]);return await t.query(`INSERT INTO analytics_crm_sync_state(connection_id,status,last_started_at,updated_at)
197
- VALUES($1,'pending',now(),now()) ON CONFLICT(connection_id)
198
- DO UPDATE SET status='pending',last_started_at=now(),updated_at=now()`,[n]),{reused:!1,status:Dt(s.rows[0])}}async function bo(e){let t=await S(e.db).query("SELECT * FROM analytics_crm_sync_runs WHERE site_id=$1 AND sync_id=$2",[e.siteId,e.syncId]);return t.rowCount?Dt(t.rows[0]):null}async function Io(e){let t=S(e.db);return(await t.query(`UPDATE analytics_crm_sync_runs SET next_run_at=$4,interval_seconds=$5,updated_at=now()
199
- WHERE site_id=$1 AND sync_id=$2 AND connection_id::text=$3 AND state <> 'cancelled'`,[e.siteId,e.syncId,e.connectionId,e.nextRunAt,e.intervalSeconds])).rowCount?(e.intervalSeconds===null?await t.query(`DELETE FROM analytics_reconciliation_jobs
200
- WHERE connection_id::text=$1 AND status='pending'`,[e.connectionId]):await t.query(`INSERT INTO analytics_reconciliation_jobs(id,connection_id,status,next_attempt_at)
201
- VALUES($1,$2,'pending',$3)
202
- ON CONFLICT(connection_id) DO UPDATE SET
203
- status=CASE WHEN analytics_reconciliation_jobs.status='leased' THEN 'leased' ELSE 'pending' END,
204
- next_attempt_at=CASE WHEN analytics_reconciliation_jobs.status='leased'
205
- THEN analytics_reconciliation_jobs.next_attempt_at ELSE EXCLUDED.next_attempt_at END,
206
- updated_at=now()`,[ie(),e.connectionId,e.nextRunAt]),!0):!1}async function Ao(e){let t=S(e.db),n=await t.query(`UPDATE analytics_crm_sync_runs SET state='cancelled',next_run_at=NULL,updated_at=now()
207
- WHERE site_id=$1 AND sync_id=$2 AND state <> 'cancelled' RETURNING connection_id::text AS connection_id`,[e.siteId,e.syncId]);return n.rowCount?(await t.query("UPDATE analytics_crm_sync_state SET status='disabled',next_sync_at=NULL,updated_at=now() WHERE connection_id::text=$1",[n.rows[0].connection_id]),!0):!1}async function So(e){let t=S(e.db);await re({siteId:e.siteId,connectionId:e.connectionId,db:t}),await t.query(`INSERT INTO analytics_crm_sync_state(connection_id,direction,status,last_started_at,updated_at)
208
- VALUES($1,'inbound','running',now(),now())
209
- ON CONFLICT(connection_id) DO UPDATE SET
210
- direction=CASE WHEN analytics_crm_sync_state.direction='outbound' THEN 'bidirectional' ELSE analytics_crm_sync_state.direction END,
211
- status='running',last_started_at=now(),last_error_code=NULL,updated_at=now()`,[e.connectionId]),await t.query(`UPDATE analytics_crm_sync_runs SET state='running',last_started_at=now(),last_error_code=NULL,updated_at=now()
212
- WHERE sync_id=(SELECT sync_id FROM analytics_crm_sync_runs
213
- WHERE site_id=$1 AND connection_id::text=$2 AND state NOT IN ('cancelled','webhook_only')
214
- ORDER BY created_at DESC LIMIT 1)`,[e.siteId,e.connectionId])}async function ho(e){let t=S(e.db);await re({siteId:e.siteId,connectionId:e.connectionId,db:t}),await t.query(`INSERT INTO analytics_crm_sync_state(
215
- connection_id,cursor,direction,status,last_started_at,last_completed_at,last_success_at,next_sync_at,updated_at
216
- ) VALUES($1,$2::jsonb,'inbound','healthy',now(),now(),now(),$3,now())
217
- ON CONFLICT(connection_id) DO UPDATE SET
218
- cursor=EXCLUDED.cursor,
219
- direction=CASE WHEN analytics_crm_sync_state.direction='outbound' THEN 'bidirectional' ELSE analytics_crm_sync_state.direction END,
220
- status='healthy',last_completed_at=now(),last_success_at=now(),last_error_code=NULL,
221
- next_sync_at=EXCLUDED.next_sync_at,updated_at=now()`,[e.connectionId,JSON.stringify(e.cursor??null),e.nextRunAt]),await t.query(`UPDATE analytics_crm_sync_runs SET
222
- state=$3,cursor=$4::jsonb,last_success_at=now(),next_run_at=$5,
223
- imported=imported+$6,deduplicated=deduplicated+$7,rejected=rejected+$8,
224
- last_error_code=NULL,updated_at=now()
225
- WHERE sync_id=(SELECT sync_id FROM analytics_crm_sync_runs
226
- WHERE site_id=$1 AND connection_id::text=$2 AND state <> 'cancelled'
227
- ORDER BY created_at DESC LIMIT 1)`,[e.siteId,e.connectionId,e.hasMore?"queued":"completed",JSON.stringify(e.cursor??null),e.nextRunAt,e.imported,e.deduplicated,e.rejected]),await t.query(`INSERT INTO analytics_crm_sync_receipts(
228
- id,site_id,connection_id,direction,operation,subject_ref,external_receipt_id,status,occurred_at
229
- ) VALUES($1,$2,$3,'inbound','historical_pull',NULL,$4,'accepted',now())`,[ie(),e.siteId,e.connectionId,e.receiptFingerprint])}async function No(e){let t=S(e.db);await re({siteId:e.siteId,connectionId:e.connectionId,db:t});let n=e.terminal?"failed":"retrying";await t.query(`INSERT INTO analytics_crm_sync_state(connection_id,direction,status,last_completed_at,last_error_code,next_sync_at,updated_at)
230
- VALUES($1,'inbound','degraded',now(),$2,$3,now())
231
- ON CONFLICT(connection_id) DO UPDATE SET
232
- direction=CASE WHEN analytics_crm_sync_state.direction='outbound' THEN 'bidirectional' ELSE analytics_crm_sync_state.direction END,
233
- status='degraded',last_completed_at=now(),last_error_code=EXCLUDED.last_error_code,
234
- next_sync_at=EXCLUDED.next_sync_at,updated_at=now()`,[e.connectionId,e.safeErrorCode,e.nextRunAt]),await t.query(`UPDATE analytics_crm_sync_runs SET state='degraded',last_error_code=$3,next_run_at=$4,
235
- rejected=rejected+1,updated_at=now()
236
- WHERE sync_id=(SELECT sync_id FROM analytics_crm_sync_runs
237
- WHERE site_id=$1 AND connection_id::text=$2 AND state <> 'cancelled'
238
- ORDER BY created_at DESC LIMIT 1)`,[e.siteId,e.connectionId,e.safeErrorCode,e.nextRunAt]),await t.query(`INSERT INTO analytics_crm_sync_receipts(
239
- id,site_id,connection_id,direction,operation,subject_ref,external_receipt_id,status,safe_error_code,occurred_at
240
- ) VALUES($1,$2,$3,'inbound','historical_pull',NULL,$4,$5,$6,now())`,[ie(),e.siteId,e.connectionId,e.receiptFingerprint,n,e.safeErrorCode])}function Fn(e){return Be.parse({policyId:e.policy_id,siteId:e.site_id,provider:e.provider,connectionId:e.connection_id,kind:e.kind,enabled:e.enabled,version:Number(e.version),provisioningReceiptId:e.provisioning_receipt_id,schemaFingerprint:e.schema_fingerprint,mappingVersion:e.mapping_version==null?null:Number(e.mapping_version),mapping:e.mapping??null,allowedFields:e.allowed_fields,selectedEvents:e.selected_events,purpose:e.purpose,updatedAt:e.updated_at instanceof Date?e.updated_at.toISOString():new Date(e.updated_at).toISOString()})}async function Co(e){let t=Be.parse(e.policy),n=S(e.db),i=await re({siteId:t.siteId,connectionId:t.connectionId,db:n});if(t.enabled){let a=(await n.query(`SELECT r.receipt->>'status' receipt_status,p.status plan_status,p.schema_fingerprint plan_schema_fingerprint,
241
- c.schema_fingerprint current_schema_fingerprint
242
- FROM analytics_crm_provisioning_receipts r
243
- JOIN analytics_crm_provisioning_plans p ON p.id=r.plan_id
244
- LEFT JOIN analytics_crm_connection_capabilities c ON c.connection_id=p.connection_id AND c.contract_version=1
245
- WHERE p.site_id=$1 AND p.connection_id=$2 AND r.public_receipt_id=$3`,[t.siteId,i,t.provisioningReceiptId])).rows[0];if(!a||a.receipt_status!=="tested"||a.plan_status!=="verified")throw new f("analytics_crm_outbound_test_required","A successful provisioning test is required before outbound automation can be enabled.",409);if(a.plan_schema_fingerprint!==t.schemaFingerprint||a.current_schema_fingerprint!==t.schemaFingerprint)throw new f("analytics_crm_schema_changed","The CRM schema changed after testing. Test the exact schema again before enabling outbound automation.",409)}if(t.kind==="pipeline_event"&&(!t.mapping||t.mapping.version!==t.mappingVersion))throw new f("analytics_crm_mapping_version_mismatch","The pipeline mapping and mapping version must match exactly.",409);let r=await n.query(`INSERT INTO analytics_crm_outbound_policies(
246
- policy_id,site_id,connection_id,provider,kind,enabled,version,provisioning_receipt_id,
247
- schema_fingerprint,mapping_version,mapping,allowed_fields,selected_events,purpose,created_by_user_id,updated_at
248
- ) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12::jsonb,$13::jsonb,$14,$15,now())
249
- ON CONFLICT(policy_id) DO UPDATE SET
250
- enabled=EXCLUDED.enabled,provisioning_receipt_id=EXCLUDED.provisioning_receipt_id,
251
- schema_fingerprint=EXCLUDED.schema_fingerprint,mapping_version=EXCLUDED.mapping_version,
252
- mapping=EXCLUDED.mapping,allowed_fields=EXCLUDED.allowed_fields,selected_events=EXCLUDED.selected_events,
253
- purpose=EXCLUDED.purpose,updated_at=now()
254
- WHERE analytics_crm_outbound_policies.site_id=EXCLUDED.site_id
255
- AND analytics_crm_outbound_policies.connection_id=EXCLUDED.connection_id
256
- AND analytics_crm_outbound_policies.kind=EXCLUDED.kind
257
- AND analytics_crm_outbound_policies.version=EXCLUDED.version
258
- RETURNING *`,[t.policyId,t.siteId,i,t.provider,t.kind,t.enabled,t.version,t.provisioningReceiptId,t.schemaFingerprint,t.mappingVersion,JSON.stringify(t.mapping),JSON.stringify([...new Set(t.allowedFields)].sort()),JSON.stringify([...new Set(t.selectedEvents)].sort()),t.purpose,e.createdByUserId]);if(!r.rowCount)throw new f("idempotency_conflict","This policy identifier is already bound to another immutable policy version.",409);return Fn(r.rows[0])}async function jn(e){return(await S(e.db).query("SELECT * FROM analytics_crm_outbound_policies WHERE site_id=$1 ORDER BY updated_at DESC,policy_id",[e.siteId])).rows.map(Fn)}function Tt(e){return dn.parse({jobId:e.job_id,policyId:e.policy_id,siteId:e.site_id,personId:e.person_id,kind:e.kind,state:e.state,policyVersion:Number(e.policy_version),mappingVersion:e.mapping_version==null?null:Number(e.mapping_version),attempts:Number(e.attempts),nextAttemptAt:e.next_attempt_at==null?null:e.next_attempt_at instanceof Date?e.next_attempt_at.toISOString():e.next_attempt_at,safeErrorCode:e.safe_error_code,requestFingerprint:e.request_fingerprint})}async function qn(e){let t=Be.parse(e.policy);if(!t.enabled)throw new f("analytics_crm_outbound_policy_disabled","The CRM outbound policy is disabled.",409);let n=S(e.db);if(!(await n.query(`SELECT p.id::text id FROM analytics_people p
259
- WHERE p.id=$1 AND p.site_id=$2 AND
260
- (p.crm_person_ref IS NOT NULL OR EXISTS (
261
- SELECT 1 FROM analytics_identity_edges e JOIN analytics_identity_nodes n ON n.id=e.identity_node_id
262
- WHERE e.person_id=p.id AND n.kind IN ('email','phone','customer_id') AND e.confidence >= 0.99))`,[e.personId,t.siteId])).rowCount)throw new f("analytics_crm_outbound_confirmed_person_required","CRM outbound delivery requires a confirmed person.",409);let r=await n.query("SELECT * FROM analytics_crm_outbound_jobs WHERE site_id=$1 AND idempotency_key=$2",[t.siteId,e.idempotencyKey]);if(r.rowCount){if(r.rows[0].request_fingerprint!==e.requestFingerprint)throw new f("idempotency_conflict","This idempotency key was already used for another CRM outbound request.",409);return{job:Tt(r.rows[0]),reused:!0}}let s=await n.query(`INSERT INTO analytics_crm_outbound_jobs(
263
- job_id,policy_id,site_id,person_id,kind,policy_version,mapping_version,source_event_id,
264
- payload_ciphertext,idempotency_key,request_fingerprint,next_attempt_at
265
- ) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,now()) RETURNING *`,[e.jobId,t.policyId,t.siteId,e.personId,t.kind,t.version,t.mappingVersion,e.sourceEventId??null,e.payloadCiphertext,e.idempotencyKey,e.requestFingerprint]);return{job:Tt(s.rows[0]),reused:!1}}async function Hn(e){let t=await S(e.db).query(`WITH due AS (
266
- SELECT j.job_id FROM analytics_crm_outbound_jobs j
267
- JOIN analytics_crm_outbound_policies p ON p.policy_id=j.policy_id
268
- WHERE p.enabled=true AND p.provider NOT IN ('salesforce','pipedrive')
269
- AND ((j.state IN ('pending','retrying') AND j.next_attempt_at <= now()) OR
270
- (j.state='running' AND j.lease_expires_at < now()))
271
- ORDER BY coalesce(j.next_attempt_at,j.lease_expires_at),j.created_at FOR UPDATE OF j SKIP LOCKED LIMIT $1
272
- )
273
- UPDATE analytics_crm_outbound_jobs j SET state='running',attempts=j.attempts+1,
274
- lease_expires_at=now()+($2::text || ' seconds')::interval,updated_at=now()
275
- FROM due,analytics_crm_outbound_policies p,analytics_sites s
276
- WHERE j.job_id=due.job_id AND p.policy_id=j.policy_id AND s.id=j.site_id
277
- RETURNING j.*,p.provider,p.connection_id::text,p.mapping,p.allowed_fields,p.purpose,s.owner_user_id,
278
- EXISTS(SELECT 1 FROM analytics_crm_connection_capabilities c
279
- WHERE c.connection_id=p.connection_id AND c.contract_version=1 AND c.schema_fingerprint=p.schema_fingerprint) schema_current`,[Math.max(1,Math.min(50,e.limit)),e.leaseSeconds??90]);for(let n of t.rows)await S(e.db).query(`INSERT INTO analytics_crm_outbound_attempts(id,job_id,attempt,status,started_at)
280
- VALUES($1,$2,$3,'started',now()) ON CONFLICT(job_id,attempt) DO NOTHING`,[ie(),n.job_id,Number(n.attempts)]);return t.rows.map(n=>({job:Tt(n),provider:n.provider,connectionId:n.connection_id,ownerUserId:Number(n.owner_user_id),payloadCiphertext:n.payload_ciphertext,mapping:n.mapping,allowedFields:n.allowed_fields,purpose:n.purpose,schemaCurrent:n.schema_current}))}async function Wn(e){return(await S(e.db).query(`SELECT EXISTS(
281
- SELECT 1
282
- FROM analytics_crm_outbound_jobs j
283
- JOIN analytics_crm_outbound_policies p ON p.policy_id=j.policy_id
284
- JOIN analytics_people person ON person.id=j.person_id AND person.site_id=j.site_id
285
- WHERE j.job_id=$1 AND j.state='running' AND j.attempts=$2
286
- AND j.lease_expires_at>now() AND j.payload_ciphertext<>''
287
- AND p.enabled=true AND p.version=j.policy_version AND p.purpose=$3
288
- AND EXISTS (
289
- SELECT 1 FROM analytics_purpose_policies purpose_policy
290
- WHERE purpose_policy.site_id=j.site_id
291
- AND purpose_policy.purpose=CASE $3
292
- WHEN 'necessary_form_fulfillment' THEN 'necessary'
293
- WHEN 'site_analytics' THEN 'site_analytics'
294
- ELSE 'advertising_measurement' END
295
- AND purpose_policy.enabled=true AND purpose_policy.retired_at IS NULL
296
- )
297
- AND NOT EXISTS (
298
- SELECT 1 FROM analytics_identity_tombstones tombstone
299
- WHERE tombstone.site_id=j.site_id AND tombstone.person_id=j.person_id
300
- )
301
- AND ($3='necessary_form_fulfillment' OR EXISTS (
302
- SELECT 1
303
- FROM analytics_consent_current_state consent
304
- JOIN analytics_consent_receipts receipt
305
- ON receipt.id=consent.latest_receipt_id AND receipt.site_id=consent.site_id
306
- WHERE consent.site_id=j.site_id
307
- AND consent.latest_receipt_id::text=$4
308
- AND consent.gpc=false
309
- AND receipt.action NOT IN ('withdraw','reject_all','gpc')
310
- AND (consent.expires_at IS NULL OR consent.expires_at>now())
311
- AND EXISTS (
312
- SELECT 1 FROM analytics_purpose_policies current_policy
313
- WHERE current_policy.site_id=consent.site_id
314
- AND current_policy.purpose=CASE $3
315
- WHEN 'site_analytics' THEN 'site_analytics'
316
- ELSE 'advertising_measurement' END
317
- AND current_policy.jurisdiction_profile=consent.jurisdiction_profile
318
- AND current_policy.enabled=true AND current_policy.retired_at IS NULL
319
- )
320
- AND (($3='site_analytics' AND consent.site_analytics=true) OR
321
- ($3='advertising_measurement' AND consent.advertising_measurement=true))
322
- ))
323
- ) authorized`,[e.jobId,e.attempt,e.purpose,e.consentReceiptRef])).rows[0]?.authorized===!0}async function Bn(e){let t=S(e.db);return(await t.query(`UPDATE analytics_crm_outbound_jobs
324
- SET state='cancelled',payload_ciphertext='',next_attempt_at=NULL,
325
- lease_expires_at=NULL,cancelled_at=now(),safe_error_code=$3,updated_at=now()
326
- WHERE job_id=$1 AND state='running' AND attempts=$2`,[e.jobId,e.attempt,e.safeErrorCode.slice(0,80)])).rowCount?(await t.query(`UPDATE analytics_crm_outbound_attempts
327
- SET status='cancelled',safe_error_code=$3,completed_at=now()
328
- WHERE job_id=$1 AND attempt=$2 AND status='started'`,[e.jobId,e.attempt,e.safeErrorCode.slice(0,80)]),!0):!1}async function tt(e){let t=S(e.db),n=await t.query("SELECT attempts,max_attempts FROM analytics_crm_outbound_jobs WHERE job_id=$1",[e.jobId]);if(!n.rowCount)throw new f("analytics_crm_resource_not_found","CRM outbound job was not found.",404);let i=Number(n.rows[0].attempts),r=e.status!=="retrying"||i>=Number(n.rows[0].max_attempts),s=e.status==="accepted"?"delivered":e.status==="retrying"&&!r?"retrying":"dead_letter",a=`crmout_${et({jobId:e.jobId,attempt:i,status:e.status}).slice(0,24)}`;return await t.query(`UPDATE analytics_crm_outbound_jobs SET state=$2,safe_error_code=$3,next_attempt_at=$4,
329
- lease_expires_at=NULL,delivered_at=CASE WHEN $2='delivered' THEN now() ELSE delivered_at END,updated_at=now()
330
- WHERE job_id=$1 AND state='running'`,[e.jobId,s,e.safeErrorCode??null,s==="retrying"?e.retryAt??null:null]),await t.query(`INSERT INTO analytics_crm_outbound_attempts(id,job_id,attempt,status,safe_error_code,completed_at)
331
- VALUES($1,$2,$3,$4,$5,now()) ON CONFLICT(job_id,attempt) DO UPDATE SET status=EXCLUDED.status,safe_error_code=EXCLUDED.safe_error_code,completed_at=now()`,[ie(),e.jobId,i,e.status==="accepted"?"accepted":s==="retrying"?"retrying":"failed",e.safeErrorCode??null]),await t.query(`INSERT INTO analytics_crm_outbound_receipts(receipt_id,job_id,status,external_receipt_id,safe_error_code)
332
- VALUES($1,$2,$3,$4,$5) ON CONFLICT(receipt_id) DO NOTHING`,[a,e.jobId,s==="retrying"?"retrying":e.status,e.externalReceiptId??null,e.safeErrorCode??null]),pt.parse({receiptId:a,jobId:e.jobId,status:s==="retrying"?"retrying":e.status,...e.externalReceiptId?{externalReceiptId:e.externalReceiptId}:{},...e.safeErrorCode?{safeErrorCode:e.safeErrorCode}:{},occurredAt:new Date().toISOString()})}async function wo(e){return(await S(e.db).query(`SELECT r.* FROM analytics_crm_outbound_receipts r
333
- JOIN analytics_crm_outbound_jobs j ON j.job_id=r.job_id
334
- JOIN analytics_crm_outbound_policies p ON p.policy_id=j.policy_id
335
- WHERE j.site_id=$1 AND p.provider NOT IN ('salesforce','pipedrive')
336
- ORDER BY r.occurred_at DESC LIMIT $2`,[e.siteId,Math.max(1,Math.min(100,e.limit??25))])).rows.map(n=>pt.parse({receiptId:n.receipt_id,jobId:n.job_id,status:n.status,...n.external_receipt_id?{externalReceiptId:n.external_receipt_id}:{},...n.safe_error_code?{safeErrorCode:n.safe_error_code}:{},occurredAt:n.occurred_at instanceof Date?n.occurred_at.toISOString():n.occurred_at}))}import{createHash as Kn}from"crypto";var Lr=["hubspot","salesforce","highlevel","zoho","pipedrive","keap"],Ro=["hubspot","highlevel","zoho","keap"];var J=e=>{let t={describe:!1,discover_schema:!1,incremental_read:!1,webhook:!1,create_fields:!1,upsert_person:!1,append_activity:!1,test_write:!1,list_outcomes:!1,pipeline_write:!1};for(let n of e)t[n]=!0;return Object.freeze(t)},pe=["describe","discover_schema","incremental_read","webhook","upsert_person","append_activity","test_write","list_outcomes","pipeline_write"],Vn=Object.freeze({hubspot:{provider:"hubspot",displayName:"HubSpot",providerConfigKeys:["hubspot"],auth:"oauth2",operations:J([...pe,"create_fields"]),personObject:"contacts",pipelineObject:"deals",activityObject:"notes",supportedObjects:["contacts","companies","deals","notes"],metadataEndpoint:"/crm/v3/properties/{objectType}",customFieldEndpoint:"/crm/v3/properties/{objectType}",externalIdStrategy:"email or an approved unique contact property",writablePersonFields:["email","firstname","lastname","phone"],requiredPersonFields:[],stageField:"dealstage",valueField:"amount",associations:["contact_to_company","contact_to_deal","note_to_contact"],pagination:"cursor",batchLimit:100,rateLimitHeaders:["x-hubspot-ratelimit-remaining","retry-after"],caveats:["Effective properties and writable scopes are tenant-specific."],safeFallback:"note"},salesforce:{provider:"salesforce",displayName:"Salesforce",providerConfigKeys:["salesforce"],auth:"oauth2",operations:J([...pe,"create_fields"]),personObject:"Contact",pipelineObject:"Opportunity",activityObject:"Task",supportedObjects:["Lead","Contact","Account","Opportunity","Task"],metadataEndpoint:"/services/data/{version}/sobjects/{object}/describe",customFieldEndpoint:"Metadata API",externalIdStrategy:"approved External ID field",writablePersonFields:["Email","FirstName","LastName","Phone"],requiredPersonFields:["LastName"],stageField:"StageName",valueField:"Amount",associations:["Contact.AccountId","Opportunity.AccountId","OpportunityContactRole"],pagination:"cursor",batchLimit:200,rateLimitHeaders:["sforce-limit-info","retry-after"],caveats:["Required fields, validation rules, record types and field-level security are tenant-specific."],safeFallback:"activity"},highlevel:{provider:"highlevel",displayName:"HighLevel",providerConfigKeys:["highlevel","gohighlevel"],auth:"oauth2",operations:J([...pe,"create_fields"]),personObject:"contacts",pipelineObject:"opportunities",activityObject:"notes",supportedObjects:["contacts","companies","opportunities","notes"],metadataEndpoint:"/locations/{locationId}/customFields",customFieldEndpoint:"/locations/{locationId}/customFields",externalIdStrategy:"location-scoped email/phone or approved custom field",writablePersonFields:["email","firstName","lastName","phone"],requiredPersonFields:[],stageField:"pipelineStageId",valueField:"monetaryValue",associations:["contact.location","opportunity.contact"],pagination:"cursor",batchLimit:100,rateLimitHeaders:["x-ratelimit-remaining","retry-after"],caveats:["Location context and installed OAuth scopes determine effective access."],safeFallback:"note"},zoho:{provider:"zoho",displayName:"Zoho CRM",providerConfigKeys:["zoho","zoho-crm"],auth:"oauth2",operations:J([...pe,"create_fields"]),personObject:"Contacts",pipelineObject:"Deals",activityObject:"Notes",supportedObjects:["Leads","Contacts","Accounts","Deals","Notes"],metadataEndpoint:"/crm/v7/settings/fields",customFieldEndpoint:"/crm/v7/settings/fields",externalIdStrategy:"approved unique field and duplicate-check preference",writablePersonFields:["Email","First_Name","Last_Name","Phone"],requiredPersonFields:["Last_Name"],stageField:"Stage",valueField:"Amount",associations:["Contact_Name","Account_Name","Parent_Id"],pagination:"page",batchLimit:100,rateLimitHeaders:["x-api-credits-remaining","retry-after"],caveats:["Custom field APIs, editions and API credits vary by tenant."],safeFallback:"note"},pipedrive:{provider:"pipedrive",displayName:"Pipedrive",providerConfigKeys:["pipedrive"],auth:"oauth2",operations:J([...pe,"create_fields"]),personObject:"persons",pipelineObject:"deals",activityObject:"notes",supportedObjects:["persons","organizations","deals","activities","notes"],metadataEndpoint:"/v1/personFields",customFieldEndpoint:"/v1/personFields",externalIdStrategy:"approved namespaced custom field; custom-field keys are tenant-specific",writablePersonFields:["name","email","phone"],requiredPersonFields:["name"],stageField:"stage_id",valueField:"value",associations:["person_id","org_id","deal_id"],pagination:"cursor",batchLimit:100,rateLimitHeaders:["x-ratelimit-remaining","retry-after"],caveats:["Custom-field hashes must be discovered from the connected tenant."],safeFallback:"note"},keap:{provider:"keap",displayName:"Keap",providerConfigKeys:["keap","infusionsoft"],auth:"oauth2",operations:J([...pe,"create_fields"]),personObject:"contacts",pipelineObject:"opportunities",activityObject:"notes",supportedObjects:["contacts","companies","opportunities","notes","tags"],metadataEndpoint:"/v1/contactCustomFields",customFieldEndpoint:"/v1/contactCustomFields",externalIdStrategy:"email or approved custom field",writablePersonFields:["email_addresses","given_name","family_name","phone_numbers"],requiredPersonFields:[],stageField:"stage_id",valueField:"projected_revenue",associations:["contact.company","opportunity.contact"],pagination:"offset",batchLimit:100,rateLimitHeaders:["x-ratelimit-remaining","retry-after"],caveats:["Custom fields, opportunity access and throttling depend on account entitlements."],safeFallback:"note"},callrail:{provider:"callrail",displayName:"CallRail",providerConfigKeys:["callrail"],auth:"api_key",operations:J(["describe","incremental_read","webhook","list_outcomes"]),personObject:null,pipelineObject:null,activityObject:"calls",supportedObjects:["calls"],metadataEndpoint:null,customFieldEndpoint:null,externalIdStrategy:null,writablePersonFields:[],requiredPersonFields:[],stageField:null,valueField:null,associations:[],pagination:"cursor",batchLimit:100,rateLimitHeaders:["x-ratelimit-remaining","retry-after"],caveats:["Read and webhook call outcomes only; no CRM person or pipeline writes."],safeFallback:"webhook_only"},twilio:{provider:"twilio",displayName:"Twilio",providerConfigKeys:["twilio"],auth:"account_token",operations:J(["describe","incremental_read","webhook","list_outcomes"]),personObject:null,pipelineObject:null,activityObject:"calls",supportedObjects:["calls"],metadataEndpoint:null,customFieldEndpoint:null,externalIdStrategy:null,writablePersonFields:[],requiredPersonFields:[],stageField:null,valueField:null,associations:[],pagination:"page",batchLimit:100,rateLimitHeaders:["twilio-concurrent-requests","retry-after"],caveats:["Read and webhook call outcomes only; no CRM person or pipeline writes."],safeFallback:"webhook_only"},ctm:{provider:"ctm",displayName:"CallTrackingMetrics",providerConfigKeys:["ctm","calltrackingmetrics"],auth:"api_key",operations:J(["describe","webhook"]),personObject:null,pipelineObject:null,activityObject:"calls",supportedObjects:["calls"],metadataEndpoint:null,customFieldEndpoint:null,externalIdStrategy:null,writablePersonFields:[],requiredPersonFields:[],stageField:null,valueField:null,associations:[],pagination:"webhook_only",batchLimit:null,rateLimitHeaders:["retry-after"],caveats:["Webhook-only until incremental cursor semantics are documented and proven."],safeFallback:"webhook_only"}}),$r=Object.freeze({gohighlevel:"highlevel",ghl:"highlevel","high-level":"highlevel",infusionsoft:"keap","zoho-crm":"zoho",calltrackingmetrics:"ctm","call-tracking-metrics":"ctm"});function se(e){let t=e.trim().toLowerCase().replace(/[ _]+/g,"-"),n=$r[t]??t;if(!(n in Vn))throw new Error("analytics_provider_unsupported");return n}function nt(e){return Vn[se(e)]}function ye(e,t){let n=nt(e);if(!n.operations[t])throw new Error(`analytics_provider_operation_unsupported:${n.provider}:${t}`);return n}var F=Object.freeze({describe:"describe-xray-crm-capabilities",discoverSchema:"discover-xray-crm-schema",upsertPerson:"upsert-xray-person",appendActivity:"append-xray-journey-activity",testWrite:"test-xray-write",listOutcomes:"list-xray-outcomes",createFields:"create-xray-custom-fields",pipelineEvent:"apply-xray-pipeline-event"}),kr=Object.freeze({hubspot:["crm.objects.deals.read"],salesforce:["api"],highlevel:["opportunities.readonly"],zoho:["ZohoCRM.modules.deals.READ"],pipedrive:["deals:read"],keap:["full"]});function Oo(e){let t=se(e.provider);if(!Lr.includes(t))throw new Error("analytics_crm_provider_required");let n=t,i=nt(n),r=new Set(e.compiledActions??[]),s=new Set(e.advertisedTools??[]),a=new Set(e.grantedScopes??[]),o=e.documented===!0,c=e.normalizedAdapter===!0,l=F.listOutcomes,_=c&&r.has(l),d=s.has(l),p=kr[n],E=p.every(D=>a.has(D)),b=new Set(e.tenantObjects??[]),k=!!(i.pipelineObject&&b.has(i.pipelineObject)),x=o&&_&&d&&E&&k,h=i.operations.webhook&&e.webhookAdvertised===!0,C=[...o?[]:["official_documentation"],...c?[]:["normalized_adapter"],...r.has(l)?[]:[`compiled_action:${l}`],...d?[]:[`connection_tool:${l}`],...p.filter(D=>!a.has(D)).map(D=>`scope:${D}`),...!k&&i.pipelineObject?[`tenant_object:${i.pipelineObject}`]:[]],m=x&&h?"incremental_and_webhook":x?"incremental_only":h?"webhook_only":e.manualBackfillAvailable===!0?"manual_backfill":"unavailable",X=(D,A)=>i.operations[D]&&r.has(A)&&s.has(A);return{provider:n,mode:m,canSyncNow:x,canSchedule:x,canProvisionFields:X("create_fields",F.createFields),canUpsertPerson:X("upsert_person",F.upsertPerson),canApplyPipelineEvent:X("pipeline_write",F.pipelineEvent),supportedObjects:e.tenantObjects?i.supportedObjects.filter(D=>b.has(D)):[],evidence:{documented:o,normalizedAdapter:c,advertisedTools:[...s].sort(),grantedScopes:[...a].sort(),missingRequirements:C,tenantSchemaFingerprint:e.tenantSchemaFingerprint??null,evaluatedAt:e.evaluatedAt??new Date().toISOString()},caveats:i.caveats}}var Jn=[{internalName:"xray_person_id",label:"X-Ray Person ID",type:"string",writable:!0},{internalName:"xray_journey_url",label:"X-Ray Detailed Journey",type:"url",writable:!0},{internalName:"xray_first_touch",label:"X-Ray First Touch",type:"string",writable:!0},{internalName:"xray_last_non_direct_touch",label:"X-Ray Last Non-Direct Touch",type:"string",writable:!0},{internalName:"xray_converting_touch",label:"X-Ray Converting Touch",type:"string",writable:!0},{internalName:"xray_landing_page",label:"X-Ray Landing Page",type:"url",writable:!0},{internalName:"xray_conversion_page",label:"X-Ray Conversion Page",type:"url",writable:!0},{internalName:"xray_session_count",label:"X-Ray Session Count",type:"number",writable:!0},{internalName:"xray_event_count",label:"X-Ray Event Count",type:"number",writable:!0},{internalName:"xray_consent_receipt_ref",label:"X-Ray Consent Receipt",type:"string",writable:!0}];function ae(e,t){return`${e}_${Kn("sha256").update(JSON.stringify(t)).digest("hex").slice(0,24)}`}function Lt(e){return Kn("sha256").update(JSON.stringify(e)).digest("hex")}function Pr(e){return Lt([...e].sort((t,n)=>`${t.object}:${t.internalName}`.localeCompare(`${n.object}:${n.internalName}`)))}function w(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function O(e,t=300){return typeof e=="string"&&e.trim()?e.trim().slice(0,t):void 0}function Mr(e){let t=O(e,80)?.toLowerCase()??"";return["number","integer","double","currency","decimal","percent","monetary"].some(n=>t.includes(n))?"number":["datetime","date","timestamp"].some(n=>t.includes(n))?"datetime":["bool","checkbox"].some(n=>t.includes(n))?"boolean":["url","uri"].some(n=>t.includes(n))?"url":"string"}function ge(e){let t=O(e.object,100),n=O(e.internalName,160),i=O(e.label,160);return!t||!n||!i?null:{object:t,internalName:n,label:i,type:Mr(e.type),writable:e.writable,...e.required?{required:!0}:{}}}function xt(e){return Array.isArray(e)?e.flatMap(t=>{if(!w(t))return[];let n=O(t.id,240),i=O(t.name??t.label,240);if(!n||!i)return[];let r=Array.isArray(t.stages)?t.stages.flatMap(s=>{if(!w(s))return[];let a=O(s.id,240),o=O(s.name??s.label,240);return a&&o?[{id:a,name:o}]:[]}):[];return[{id:n,name:i,stages:r}]}):[]}function Ur(e,t){let n=w(t)?t:{},i=[],r=[],s=xt(n.pipelines),a=o=>{o&&i.push(o)};if(e==="hubspot"){for(let[o,c]of[["contacts","contacts"],["deals","deals"]])if(Array.isArray(n[o])){r.push(c);for(let l of n[o])w(l)&&a(ge({object:c,internalName:l.name,label:l.label,type:l.type,writable:l.archived!==!0&&l.providerDefined!==!0}))}}else if(e==="salesforce"){if(Array.isArray(n.objects))for(let o of n.objects){if(!w(o))continue;let c=O(o.object,100);if(!c)continue;r.push(c);let l=w(o.permissions)?o.permissions:{};if(Array.isArray(o.fields))for(let _ of o.fields)w(_)&&a(ge({object:c,internalName:_.name,label:_.label,type:_.type,writable:l.update===!0&&_.updateable===!0,required:_.required===!0}))}}else if(e==="highlevel"){if(Array.isArray(n.customFields)){for(let o of n.customFields)if(w(o)){let c=o.model==="opportunity"?"opportunities":"contacts";r.push(c),a(ge({object:c,internalName:o.fieldKey??o.id,label:o.name,type:o.dataType,writable:!0}))}}}else if(e==="zoho"){let o=Array.isArray(n.moduleResults)?n.moduleResults:[n];for(let c of o){if(!w(c))continue;let l=O(c.module,100);if(l&&(r.push(l),Array.isArray(c.fields)))for(let _ of c.fields)w(_)&&a(ge({object:l,internalName:_.apiName,label:_.label,type:_.dataType,writable:_.writable===!0,required:_.required===!0}))}}else if(e==="pipedrive"){if(Array.isArray(n.fields)){for(let c of n.fields)if(w(c)){let l=c.objectType==="deal"?"deals":"persons";r.push(l),a(ge({object:l,internalName:c.code,label:c.name,type:c.fieldType,writable:c.writable===!0}))}}let o=Array.isArray(n.stages)?n.stages:[];s=xt(Array.isArray(n.pipelines)?n.pipelines.map(c=>w(c)?{...c,id:String(c.id??""),stages:o.filter(l=>w(l)&&String(l.pipelineId)===String(c.id)).map(l=>({...l,id:String(l.id??"")}))}:c):[])}else if(e==="keap"){if(r.push("contacts"),Array.isArray(n.contactFields))for(let c of n.contactFields)w(c)&&a(ge({object:"contacts",internalName:c.fieldName??c.id,label:c.label,type:c.fieldType,writable:!0}));let o=Array.isArray(n.opportunityStages)?n.opportunityStages:[];o.length&&(s=[{id:"opportunities",name:"Opportunities",stages:xt([{id:"opportunities",name:"Opportunities",stages:o}])[0]?.stages??[]}])}return{fields:i,objects:[...new Set(r)],pipelines:s,...Array.isArray(n.allowedOperations)?{allowedOperations:n.allowedOperations.filter(o=>typeof o=="string")}:{}}}function Fr(e){return!w(e)||!Array.isArray(e.fields)?[]:e.fields.flatMap(t=>{if(!w(t))return[];let n=O(t.object,100),i=O(t.internalName??t.internal_name,160);if(!n||!i)return[];let r=O(t.id??t.providerFieldId,240);return[{object:n,internalName:i,...r?{providerFieldId:r}:{}}]})}function it(e){let t=se(e);if(t==="callrail"||t==="twilio"||t==="ctm")throw new Error("analytics_crm_provider_required");return t}function Lo(e){let t=it(e.provider),n=ye(t,"discover_schema");if(!n.personObject||!e.connectionId.trim())throw new Error("analytics_crm_provisioning_invalid_scope");let i={},r=[];for(let l of Jn){let _=e.fields.find(d=>d.object===n.personObject&&(d.internalName.toLowerCase()===l.internalName||d.label.toLowerCase()===l.label.toLowerCase()));_?_.writable&&(i[l.internalName]=_.internalName):r.push({...l,object:n.personObject})}let s=e.now??new Date().toISOString(),a=Pr(e.fields),o={provider:t,connectionId:e.connectionId,existingMappings:i,proposedFields:r,schemaFingerprint:a},c=Lt(o);return{planId:ae("crmplan",o),provider:t,connectionId:e.connectionId,personObject:n.personObject,existingMappings:i,proposedFields:r,fallback:n.safeFallback==="activity"?"activity":"note",permissionRequirements:["schema:read",...r.length?["schema:write"]:[],"records:write"],schemaFingerprint:a,requestFingerprint:c,createdAt:s}}async function $o(e){let t=ye(e.plan.provider,"create_fields"),n=ae("crmplan",{provider:e.plan.provider,connectionId:e.plan.connectionId,existingMappings:e.plan.existingMappings,proposedFields:e.plan.proposedFields,schemaFingerprint:e.plan.schemaFingerprint}),i=Lt({provider:e.plan.provider,connectionId:e.plan.connectionId,existingMappings:e.plan.existingMappings,proposedFields:e.plan.proposedFields,schemaFingerprint:e.plan.schemaFingerprint});if(e.plan.planId!==n||e.plan.personObject!==t.personObject||e.plan.requestFingerprint!==i)throw new Error("analytics_crm_provisioning_plan_tampered");let r=new Set(Jn.map(c=>c.internalName));if(e.plan.proposedFields.some(c=>c.object!==t.personObject||!r.has(c.internalName)))throw new Error("analytics_crm_provisioning_plan_tampered");if(!Number.isInteger(e.approvedByUserId)||e.approvedByUserId<=0)throw new Error("analytics_crm_provisioning_approval_required");let s=new Set(e.approvedFieldInternalNames);if([...s].some(c=>!e.plan.proposedFields.some(l=>l.internalName===c)))throw new Error("analytics_crm_provisioning_field_not_in_plan");let a=e.plan.proposedFields.filter(c=>s.has(c.internalName)),o=ae("crmreceipt",{planId:e.plan.planId,fields:[...s].sort()});try{let c=a.length?await e.dependencies.callAction({connectionId:e.plan.connectionId,action:F.createFields,payload:{provider:e.plan.provider,fields:a},idempotencyKey:o}):{fields:[]};return{receiptId:o,planId:e.plan.planId,provider:e.plan.provider,connectionId:e.plan.connectionId,status:"approved",createdFields:Fr(c),existingMappings:e.plan.existingMappings,cleanup:{supported:!1,instructions:"Created empty X-Ray fields may be removed manually after confirming they are not populated."},planFingerprint:e.plan.requestFingerprint,occurredAt:e.now??new Date().toISOString()}}catch(c){return{receiptId:o,planId:e.plan.planId,provider:e.plan.provider,connectionId:e.plan.connectionId,status:"failed",createdFields:[],existingMappings:e.plan.existingMappings,cleanup:{supported:!1,instructions:"No cleanup was attempted."},planFingerprint:e.plan.requestFingerprint,occurredAt:e.now??new Date().toISOString(),errorCode:O(c instanceof Error?c.message:c,80)?.replace(/[^a-zA-Z0-9_:-]/g,"_")||"analytics_crm_provisioning_failed"}}}async function ko(e){if(e.receipt.status!=="approved")throw new Error("analytics_crm_provisioning_not_approved");ye(e.receipt.provider,"test_write");let t=ae("crmtest",e.receipt.receiptId),n=await e.dependencies.callAction({connectionId:e.receipt.connectionId,action:F.testWrite,payload:{provider:e.receipt.provider,marker:"X-Ray reversible connection test",cleanupRequested:!0},idempotencyKey:t});if(!w(n)||n.ok!==!0)throw new Error("analytics_crm_test_write_not_verified");return{...e.receipt,status:"tested",occurredAt:e.now??new Date().toISOString()}}async function zn(e){let t=it(e.provider);if(ye(t,"upsert_person"),e.person.identityTier!=="confirmed"||!e.person.deterministicIdentity.value.trim())throw new Error("analytics_crm_person_requires_confirmed_identity");let n=ae("crmperson",{provider:t,connectionId:e.connectionId,personId:e.person.personId,identity:e.person.deterministicIdentity,consent:e.person.consentReceiptRef}),i={provider:t,identity:e.person.deterministicIdentity,contact:e.person.contact,journey:{personId:e.person.personId,firstTouch:e.person.firstTouch,lastNonDirectTouch:e.person.lastNonDirectTouch,convertingTouch:e.person.convertingTouch,landingPage:e.person.landingPage,conversionPage:e.person.conversionPage,sessionCount:e.person.sessionCount,eventCount:e.person.eventCount,campaignClickCoverage:e.person.campaignClickCoverage,consentReceiptRef:e.person.consentReceiptRef,detailedJourneyUrl:e.person.detailedJourneyUrl},mapping:e.mapping??{},createDeal:!1},r=await e.dependencies.callAction({connectionId:e.connectionId,action:F.upsertPerson,payload:i,idempotencyKey:n}),s=w(r)?O(r.recordRef??r.id,240):void 0;return{receiptId:n,provider:t,operation:"person_upsert",dealCreated:!1,...s?{providerRecordRef:s}:{}}}async function Yn(e){let t=it(e.provider);if(ye(t,"pipeline_write"),e.person.identityTier!=="confirmed")throw new Error("analytics_crm_pipeline_requires_confirmed_identity");if(e.event.eventName!==e.mapping.eventName)throw new Error("analytics_crm_pipeline_event_not_mapped");if(e.mapping.valueSource==="verified_revenue"&&(!Number.isSafeInteger(e.event.verifiedRevenueMinor)||e.event.verifiedRevenueMinor<0))throw new Error("analytics_crm_pipeline_verified_revenue_required");let n=ae("crmpipeline",{provider:t,connectionId:e.connectionId,eventId:e.event.eventId,mappingVersion:e.mapping.version});return await e.dependencies.callAction({connectionId:e.connectionId,action:F.pipelineEvent,payload:{provider:t,command:"apply_pipeline_event",person:{personId:e.person.personId,identity:e.person.deterministicIdentity},event:e.event,mapping:e.mapping},idempotencyKey:n}),{receiptId:n,provider:t,operation:"pipeline_event",dealCreatedOrUpdated:!0}}async function Po(e){let t=it(e.provider),n=nt(t),i=t==="highlevel"?{locationId:e.discoveryContext?.locationId}:t==="zoho"?{module:e.discoveryContext?.zohoModules?.[0]??"Contacts"}:{};if(t==="highlevel"&&!O(i.locationId,128))throw new Error("analytics_crm_highlevel_location_required");let r=(p,E)=>e.dependencies.callAction({connectionId:e.connectionId,action:F.discoverSchema,payload:p,idempotencyKey:ae("crmdiscover",{provider:t,connectionId:e.connectionId,actionPayload:p,suffix:E})}),s=await r(i);if(t==="zoho"){let p=[...new Set(e.discoveryContext?.zohoModules??["Contacts","Deals"])],E=[s];for(let b of p.slice(1))E.push(await r({module:b},b));s={moduleResults:E}}let a=Ur(t,s),o=a.fields,c=Object.entries(n.operations).filter(([,p])=>p).map(([p])=>p),l=a.allowedOperations?new Set(a.allowedOperations.map(p=>O(p,80)).filter(p=>!!p)):null,_=l?c.filter(p=>l.has(p)):c,d=new Set(n.supportedObjects);return{provider:t,publicProfile:n,connectionId:e.connectionId,fields:o,objects:a.objects.length?a.objects.filter(p=>d.has(p)):n.supportedObjects,pipelines:a.pipelines,allowedOperations:_,discoveredAt:e.now??new Date().toISOString()}}import{createHash as qr,randomUUID as q}from"crypto";var jr=[[/perplexity/i,"llm","Perplexity"],[/chatgpt|chat\.openai|openai/i,"llm","ChatGPT"],[/claude|anthropic/i,"llm","Claude"],[/\bgrok\b|xai/i,"llm","Grok"],[/ai[ _-]?overview|google[ _-]?ai|\bsge\b|gemini/i,"llm","Google AI Overview"],[/instagram/i,"social","Instagram"],[/facebook|\bfb\b|meta/i,"social","Facebook"],[/linkedin/i,"social","LinkedIn"],[/tiktok/i,"social","TikTok"],[/youtube|youtu\.be/i,"social","YouTube"],[/twitter|x\.com/i,"social","X"],[/\bg2\b|g2\.com/i,"review","G2"],[/trustpilot/i,"review","Trustpilot"],[/\bbbb\b|better business bureau/i,"review","BBB"],[/capterra/i,"review","Capterra"],[/yelp/i,"review","Yelp"],[/google|bing|duckduckgo|yahoo/i,"search","Search"]];function j(e,t=240){return typeof e=="string"&&e.trim()?e.trim().slice(0,t):null}function Gn(e){try{return e?new URL(e).hostname.replace(/^www\./,""):""}catch{return""}}function Xn(e){let t=e.properties??{},n=j(e.source,180)||Gn(e.referrer)||"(direct)",i=j(e.medium,180)||(n==="(direct)"?"(none)":"referral"),r=e.clickIds??{},s=`${n} ${i} ${Gn(e.referrer)}`,a=n==="(direct)"?"direct":/email|newsletter/i.test(i)?"email":"referral",o=n==="(direct)"?"Direct":n;for(let[c,l,_]of jr)if(c.test(s)){a=l,o=_;break}return r.fbclid?(a="social",o=/instagram/i.test(s)?"Instagram":"Facebook"):r.ttclid?(a="social",o="TikTok"):r.rdt_cid?(a="social",o="Reddit"):r.gclid||r.gbraid||r.wbraid?(a="search",o="Google"):r.msclkid&&(a="search",o="Microsoft Ads"),{channelFamily:a,platform:o,source:n,medium:i,campaign:j(e.campaign),campaignId:j(t.campaign_id??t.utm_id),adSetId:j(t.adset_id??t.ad_set_id??t.adgroup_id??t.ad_group_id),adId:j(t.ad_id),creativeId:j(t.creative_id),placement:j(t.placement),term:j(t.utm_term),content:j(t.utm_content)}}import{randomBytes as Qn}from"crypto";function jo(e){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,80)||`site-${Qn(4).toString("hex")}`}function Zn(e){if(!e||e==="null")return null;try{let t=new URL(e);return t.protocol!=="http:"&&t.protocol!=="https:"?null:t.hostname.toLowerCase().replace(/\.$/,"")}catch{return null}}function qo(){return`px_${Qn(18).toString("base64url")}`}var Hr=1800*1e3,Xo=1e4;function Wr(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0?Math.min(Math.trunc(e),Hr):null}function Br(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0?Math.min(Math.trunc(e),100):null}var Vr=/(email|phone|password|token|secret|authorization|cookie|name|address|form|body)/i;function ei(e){if(!e)return"/";try{return new URL(e,"https://analytics.invalid").pathname.slice(0,2e3)||"/"}catch{return"/"}}function fe(e){if(!e)return null;try{let t=new URL(e);return t.protocol!=="http:"&&t.protocol!=="https:"?null:(t.search="",t.hash="",t.toString().slice(0,3e3))}catch{return null}}function Kr(e){let t={};for(let[n,i]of Object.entries(e??{}).slice(0,24))!/^[a-z][a-z0-9_]{0,63}$/i.test(n)||Vr.test(n)||(i===null||typeof i=="boolean"||typeof i=="number"?t[n]=i:typeof i=="string"&&(t[n]=i.slice(0,300)));return t}async function Jr(e){if(!e.event.visitorId||!e.event.sessionId)return;let t=$("visitor_id",e.event.visitorId),n=$("session_id",e.event.sessionId),i=new Date(e.event.occurredAt);if(!Number.isFinite(i.getTime()))return;let r=ei(e.event.path||e.event.canonicalUrl),s=await e.client.query(`INSERT INTO analytics_sessions(
337
- id, site_id, pixel_id, visitor_hmac, session_hmac, person_id,
338
- started_at, last_active_at, landing_path, exit_path, pageview_count, event_count
339
- ) VALUES ($1,$2,$3,$4,$5,$6,$7,$7,$8,$8,$9,1)
340
- ON CONFLICT(site_id, session_hmac) DO UPDATE SET
341
- person_id = COALESCE(analytics_sessions.person_id, EXCLUDED.person_id),
342
- last_active_at = GREATEST(analytics_sessions.last_active_at, EXCLUDED.last_active_at),
343
- ended_at = GREATEST(COALESCE(analytics_sessions.ended_at, EXCLUDED.last_active_at), EXCLUDED.last_active_at),
344
- duration_ms = GREATEST(
345
- analytics_sessions.duration_ms,
346
- LEAST(1800000, (EXTRACT(EPOCH FROM (GREATEST(analytics_sessions.last_active_at, EXCLUDED.last_active_at) - analytics_sessions.started_at)) * 1000)::bigint)
347
- ),
348
- exit_path = EXCLUDED.exit_path,
349
- pageview_count = analytics_sessions.pageview_count + EXCLUDED.pageview_count,
350
- event_count = analytics_sessions.event_count + 1,
351
- updated_at = now()
352
- RETURNING id`,[q(),e.siteId,e.pixelId,t,n,e.personId??null,e.event.occurredAt,r,e.event.eventName==="page_view"?1:0]),a=U(e.event.clickIds);if(!!!(e.event.source||e.event.medium||e.event.campaign||e.event.referrer||Object.keys(a).length))return;let c=qr("sha256").update(JSON.stringify({siteId:e.siteId,sessionHmac:n,source:e.event.source??null,medium:e.event.medium??null,campaign:e.event.campaign??null,referrer:fe(e.event.referrer),clickIds:a,path:r})).digest("hex"),l=await e.client.query(`INSERT INTO analytics_attribution_touches(
353
- id, site_id, session_id, person_id, visitor_hmac, occurred_at,
354
- source, medium, campaign, referrer, click_ids, landing_path, touch_hash
355
- ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13)
356
- ON CONFLICT(site_id, touch_hash) DO UPDATE SET
357
- person_id = COALESCE(analytics_attribution_touches.person_id, EXCLUDED.person_id)
358
- RETURNING id`,[q(),e.siteId,s.rows[0].id,e.personId??null,t,e.event.occurredAt,e.event.source?.slice(0,180)??null,e.event.medium?.slice(0,180)??null,e.event.campaign?.slice(0,240)??null,fe(e.event.referrer),JSON.stringify(a),r,c]);await e.client.query(`UPDATE analytics_sessions
359
- SET first_touch_id = COALESCE(first_touch_id, $2), last_touch_id = $2, updated_at = now()
360
- WHERE id = $1`,[s.rows[0].id,l.rows[0].id])}async function Qo(e){let t=v(),n=`air_${q()}`,i=Zn(e.origin),s=(await t.query(`SELECT p.id, p.site_id, p.status,
361
- COALESCE(
362
- (e.eligible AND e.access_expires_at > now()) OR e.trial_ends_at > now(),
363
- false
364
- ) AS entitled
365
- FROM analytics_pixels p
366
- JOIN analytics_sites s ON s.id = p.site_id
367
- LEFT JOIN analytics_thorbit_entitlements e ON e.user_id = s.owner_user_id
368
- WHERE p.public_id = $1`,[e.publicPixelId])).rows[0];if(!s||s.status!=="active"){let d=[s?"pixel_disabled":"pixel_unknown"];return await t.query(`INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, hostname, rejected_count, reason_codes)
369
- VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,[q(),n,s?.site_id??null,s?.id??null,i,e.events.length,JSON.stringify(d)]),{requestId:n,accepted:0,rejected:e.events.length,reasonCodes:d,hostname:i,siteId:s?.site_id??null,pixelId:s?.id??null}}if(!s.entitled){let d=["thorbit_entitlement_required"];return await t.query(`INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, hostname, rejected_count, reason_codes)
370
- VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,[q(),n,s.site_id,s.id,i,e.events.length,JSON.stringify(d)]),{requestId:n,accepted:0,rejected:e.events.length,reasonCodes:d,hostname:i,siteId:s.site_id,pixelId:s.id}}if(!i){let d=["origin_missing_or_invalid"];return await t.query(`INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, rejected_count, reason_codes)
371
- VALUES ($1, $2, $3, $4, $5, $6::jsonb)`,[q(),n,s.site_id,s.id,e.events.length,JSON.stringify(d)]),{requestId:n,accepted:0,rejected:e.events.length,reasonCodes:d,hostname:i,siteId:s.site_id,pixelId:s.id}}if((await t.query(`INSERT INTO analytics_pixel_domains(id, pixel_id, hostname, state, rejected_count)
372
- VALUES ($1, $2, $3, 'observed_unverified', 0)
373
- ON CONFLICT(pixel_id, hostname) DO UPDATE
374
- SET last_seen_at = now(), updated_at = now()
375
- RETURNING state`,[q(),s.id,i])).rows[0]?.state!=="approved"){await t.query(`UPDATE analytics_pixel_domains
376
- SET rejected_count = rejected_count + $3, last_seen_at = now(), updated_at = now()
377
- WHERE pixel_id = $1 AND hostname = $2`,[s.id,i,e.events.length]);let d=["domain_unapproved"];return await t.query(`INSERT INTO analytics_ingestion_receipts(id, request_id, site_id, pixel_id, hostname, rejected_count, reason_codes)
378
- VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)`,[q(),n,s.site_id,s.id,i,e.events.length,JSON.stringify(d)]),{requestId:n,accepted:0,rejected:e.events.length,reasonCodes:d,hostname:i,siteId:s.site_id,pixelId:s.id}}let o=await t.connect(),c=0,l=0,_=new Set;try{await o.query("BEGIN");for(let d of e.events)try{await pn(o,{siteId:s.site_id,pixelId:s.id,event:d});let p=Xn(d),E=d.identity,b=null,k=null;if(d.eventDefinitionId&&d.eventDefinitionVersion){let C=await o.query(`SELECT id, version FROM analytics_event_definitions
379
- WHERE id=$1 AND site_id=$2 AND version=$3 AND event_name=$4 AND status='active'
380
- LIMIT 1`,[d.eventDefinitionId,s.site_id,d.eventDefinitionVersion,d.eventName]);C.rows[0]&&(b=C.rows[0].id,k=C.rows[0].version)}let x=_n(d)&&(d.visitorId||d.sessionId)?await o.query(`SELECT e.person_id FROM analytics_identity_nodes n JOIN analytics_identity_edges e ON e.identity_node_id = n.id
381
- WHERE n.site_id = $1 AND ((n.kind = 'visitor_id' AND n.value_hmac = $2) OR (n.kind = 'session_id' AND n.value_hmac = $3))
382
- ORDER BY e.confidence DESC, e.last_seen_at DESC LIMIT 1`,[s.site_id,d.visitorId?$("visitor_id",d.visitorId):"",d.sessionId?$("session_id",d.sessionId):""]):{rows:[]};if((await o.query(`INSERT INTO analytics_events(
383
- id, event_id, site_id, pixel_id, hostname, visitor_id, session_id, event_name,
384
- occurred_at, path, canonical_url, title, referrer, source, medium, campaign,
385
- device_class, properties, country_code, region_code, click_ids, person_id,
386
- channel_family, platform, campaign_id, ad_set_id, ad_id, creative_id, placement, utm_term, utm_content,
387
- engaged_ms, scroll_depth, visitor_hmac, session_hmac, event_definition_id, event_definition_version
388
- ) VALUES (
389
- $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18::jsonb, $19, $20, $21::jsonb, $22,
390
- $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37
391
- ) ON CONFLICT(site_id, event_id) DO NOTHING`,[q(),d.eventId,s.site_id,s.id,i,d.visitorId??null,d.sessionId??null,d.eventName,d.occurredAt,ei(d.path||d.canonicalUrl),fe(d.canonicalUrl),d.title?.slice(0,500)??null,fe(d.referrer),d.source?.slice(0,180)??null,d.medium?.slice(0,180)??null,d.campaign?.slice(0,240)??null,d.deviceClass??null,JSON.stringify(Kr(d.properties)),gt(e.geography?.countryCode,2),gt(e.geography?.regionCode,8),JSON.stringify(U(d.clickIds)),x.rows[0]?.person_id??null,p.channelFamily,p.platform,p.campaignId,p.adSetId,p.adId,p.creativeId,p.placement,p.term,p.content,Wr(d.engagedMs),Br(d.scrollDepth),d.visitorId?$("visitor_id",d.visitorId):null,d.sessionId?$("session_id",d.sessionId):null,b,k])).rowCount){c+=1;let C=x.rows[0]?.person_id;await Jr({client:o,siteId:s.site_id,pixelId:s.id,personId:C,event:d}),C&&d.eventName==="identify"&&await yn(o,{siteId:s.site_id,personId:C,visitorId:d.visitorId,sessionId:d.sessionId}),E&&mn(d)&&await gn(o,{siteId:s.site_id,visitorId:d.visitorId,sessionId:d.sessionId,email:E.email,phone:E.phone,customerId:E.customerId,orderId:E.orderId,clickIds:d.clickIds,occurredAt:d.occurredAt})}else l+=1,_.add("duplicate_event")}catch{l+=1,_.add("event_insert_failed")}await o.query(`UPDATE analytics_pixel_domains
392
- SET accepted_count = accepted_count + $3,
393
- rejected_count = rejected_count + $4,
394
- last_seen_at = now(), updated_at = now()
395
- WHERE pixel_id = $1 AND hostname = $2`,[s.id,i,c,l]),await o.query(`INSERT INTO analytics_ingestion_receipts(
396
- id, request_id, site_id, pixel_id, hostname, accepted_count, rejected_count, reason_codes
397
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb)`,[q(),n,s.site_id,s.id,i,c,l,JSON.stringify([..._])]),await o.query("COMMIT")}catch(d){throw await o.query("ROLLBACK"),d}finally{o.release()}return{requestId:n,accepted:c,rejected:l,reasonCodes:[..._],hostname:i,siteId:s.site_id,pixelId:s.id}}var Yr=25,Gr=25e3,ti=z.object({person:_t,event:mt.nullable(),mapping:z.object({version:z.number().int().positive(),eventName:z.string(),pipelineId:z.string(),stageId:z.string(),operation:z.enum(["create_or_update_deal","update_stage"]),valueSource:z.enum(["none","verified_revenue"]),activationEligible:z.boolean()}).strict().nullable()}).strict();function Xr(e,t){return`${e}_${zr("sha256").update(JSON.stringify(t)).digest("hex").slice(0,24)}`}function rt(e){return fe(e)??void 0}function Qr(e,t,n){return e.purpose==="necessary_form_fulfillment"?t.eventKind==="form":e.purpose==="site_analytics"?n.siteAnalytics:n.advertisingMeasurement}function H(e,t){return e.has(t)}function Zr(e){let t=new Set(e.policy.allowedFields),n=e.contact?{...H(t,"contact.email")&&e.contact.email?{email:e.contact.email}:{},...H(t,"contact.phone")&&e.contact.phone?{phone:e.contact.phone}:{},...H(t,"contact.firstName")&&e.contact.firstName?{firstName:e.contact.firstName}:{},...H(t,"contact.lastName")&&e.contact.lastName?{lastName:e.contact.lastName}:{}}:void 0,i=[e.event.sourceName??e.event.source,e.event.medium,e.event.campaign].filter(Boolean).map(a=>String(a).slice(0,240)).join(" / "),r=e.policy.purpose==="advertising_measurement"&&e.consent.advertisingMeasurement?U(e.event.clickIds):{},s=Object.entries(r).filter(([a])=>H(t,`clickIds.${a}`)).map(([a])=>a).sort().join(",");return _t.parse({identityTier:"confirmed",personId:e.personId,deterministicIdentity:{kind:"crm_id",value:e.crmPersonRef},...n&&Object.keys(n).length?{contact:n}:{},...H(t,"attribution.firstTouch")&&i?{firstTouch:i}:{},...H(t,"attribution.lastNonDirectTouch")&&i?{lastNonDirectTouch:i}:{},...H(t,"attribution.convertingTouch")&&i?{convertingTouch:i}:{},...H(t,"attribution.landingPage")&&rt(e.event.canonicalUrl)?{landingPage:rt(e.event.canonicalUrl)}:{},...H(t,"attribution.conversionPage")&&rt(e.event.canonicalUrl)?{conversionPage:rt(e.event.canonicalUrl)}:{},sessionCount:e.sessionCount,eventCount:e.eventCount,...s?{campaignClickCoverage:s}:{},consentReceiptRef:e.consent.receiptId??`purpose:${e.policy.purpose}`,detailedJourneyUrl:`https://mcpscraper.dev/dashboard/analytics/people/${encodeURIComponent(e.personId)}`})}async function ni(e){let t=(await jn({siteId:e.siteId,db:e.db})).filter(s=>s.enabled&&Qr(s,e.event,e.consent)).filter(s=>s.selectedEvents.length===0||s.selectedEvents.includes(e.event.eventName));if(!t.length)return{queued:0,reused:0};let n=await e.db.query(`SELECT p.crm_person_ref,
398
- (SELECT count(DISTINCT s.id)::text FROM analytics_sessions s WHERE s.site_id=p.site_id AND s.person_id=p.id) session_count,
399
- ((SELECT count(*) FROM analytics_events e WHERE e.site_id=p.site_id AND e.person_id=p.id)+
400
- (SELECT count(*) FROM analytics_external_events x WHERE x.site_id=p.site_id AND x.person_id=p.id))::text event_count
401
- FROM analytics_people p WHERE p.site_id=$1 AND p.id=$2`,[e.siteId,e.personId]);if(!n.rows[0])return{queued:0,reused:0};let i=0,r=0;for(let s of t){if(s.kind==="pipeline_event"&&(!s.mapping||s.mapping.eventName!==e.event.eventName))continue;let a=Zr({personId:e.personId,crmPersonRef:n.rows[0].crm_person_ref,event:e.event,policy:s,consent:e.consent,sessionCount:Number(n.rows[0].session_count),eventCount:Number(n.rows[0].event_count)}),o=s.kind==="pipeline_event"?mt.parse({eventId:e.canonicalEventId,eventName:e.event.eventName,occurredAt:e.event.occurredAt,...s.mapping?.valueSource==="verified_revenue"&&e.event.valueMinor!==void 0?{verifiedRevenueMinor:e.event.valueMinor,currency:e.event.currency?.toUpperCase()}:{}}):null;if(s.kind==="pipeline_event"&&s.mapping?.valueSource==="verified_revenue"&&o?.verifiedRevenueMinor===void 0)continue;let c=ti.parse({person:a,event:o,mapping:s.mapping}),l=`${s.policyId}:${s.version}:${e.canonicalEventId}`,_=et({policy:s,personId:e.personId,event:o,projection:a});(await qn({jobId:Xr("crmjob",{idempotencyKey:l}),policy:s,personId:e.personId,sourceEventId:e.canonicalEventId,payloadCiphertext:ue(JSON.stringify(c),"crm-outbound-payload"),idempotencyKey:l,requestFingerprint:_,db:e.db})).reused?r+=1:i+=1}return{queued:i,reused:r}}var st={async resolveIdentity(e){return(await tn(e))?.email??null},async callAction(e){return an({identity:e.identity,connectionId:e.connectionId,tool:e.action,input:e.payload,classification:"action",operationKind:"sync",requestId:e.idempotencyKey})},authorizeSend:e=>Wn(e),cancelUnauthorized:e=>Bn(e)};function es(e){let t=e instanceof Error?e.message:String(e);return/schema_changed|mapping_version|not_mapped|verified_revenue|required|disabled/i.test(t)?{code:"crm_outbound_contract_invalid",retryable:!1,rateLimited:!1}:/429|rate.?limit|too many/i.test(t)?{code:"crm_outbound_rate_limited",retryable:!0,rateLimited:!0}:/401|403|scope|permission|unauthorized|forbidden/i.test(t)?{code:"crm_outbound_authorization_required",retryable:!1,rateLimited:!1}:{code:"crm_outbound_provider_unavailable",retryable:!0,rateLimited:!1}}async function uc(e=10,t=Date.now()+Gr,n=st){let i=Math.max(1,Math.min(Yr,e)),r=v();await r.query(`INSERT INTO analytics_crm_outbound_worker_state(worker_key,last_started_at,last_error_code,updated_at)
402
- VALUES('default',now(),NULL,now()) ON CONFLICT(worker_key) DO UPDATE SET last_started_at=now(),last_error_code=NULL,updated_at=now()`);try{let s=await Hn({limit:i,leaseSeconds:90}),a=0,o=0,c=0;for(let d of s){if(Date.now()>=t){await tt({jobId:d.job.jobId,status:"retrying",safeErrorCode:"crm_outbound_tick_deadline",retryAt:new Date(Date.now()+6e4).toISOString()}),o+=1;continue}try{if(!d.schemaCurrent)throw new Error("analytics_crm_schema_changed");let p=Oe(d.payloadCiphertext,"crm-outbound-payload");if(!p)throw new Error("crm_outbound_payload_unreadable");let E=ti.parse(JSON.parse(p)),b=await n.resolveIdentity(d.ownerUserId);if(!b)throw new Error("crm_outbound_owner_identity_unavailable");if(!await(n.authorizeSend??(n===st?st.authorizeSend:async()=>!0))({jobId:d.job.jobId,attempt:d.job.attempts,purpose:d.purpose,consentReceiptRef:E.person.consentReceiptRef})){await(n.cancelUnauthorized??st.cancelUnauthorized)({jobId:d.job.jobId,attempt:d.job.attempts,safeErrorCode:"crm_outbound_privacy_fence_closed"}),c+=1;continue}let h={callAction:({connectionId:m,action:X,payload:D,idempotencyKey:A})=>n.callAction({identity:b,connectionId:m,action:X,payload:D,idempotencyKey:A})},C=d.job.kind==="person_summary"?await zn({provider:d.provider,connectionId:d.connectionId,person:E.person,dependencies:h}):await Yn({provider:d.provider,connectionId:d.connectionId,person:E.person,event:E.event,mapping:E.mapping,dependencies:h});await tt({jobId:d.job.jobId,status:"accepted",externalReceiptId:C.receiptId}),a+=1}catch(p){let E=es(p),b=new Date(Date.now()+(E.rateLimited?30*6e4:Math.min(360*6e4,3e4*2**d.job.attempts))).toISOString();await tt({jobId:d.job.jobId,status:E.retryable?"retrying":"failed",safeErrorCode:E.code,...E.retryable?{retryAt:b}:{}}),E.retryable&&d.job.attempts<5?o+=1:c+=1}}let l=await r.query("SELECT count(*)::int due FROM analytics_crm_outbound_jobs WHERE state IN ('pending','retrying') AND next_attempt_at <= now()"),_={claimed:s.length,delivered:a,retried:o,deadLettered:c,remainingDue:Number(l.rows[0]?.due??0)};return await r.query(`UPDATE analytics_crm_outbound_worker_state SET last_completed_at=now(),last_success_at=now(),
403
- last_error_code=NULL,last_claimed=$1,last_remaining_due=$2,updated_at=now() WHERE worker_key='default'`,[_.claimed,_.remainingDue]),_}catch(s){throw await r.query(`UPDATE analytics_crm_outbound_worker_state SET last_completed_at=now(),last_error_code='crm_outbound_worker_failed',updated_at=now()
404
- WHERE worker_key='default'`).catch(()=>{}),s}}import{createHash as ts,randomUUID as ii}from"crypto";import{z as g}from"zod";var oe=g.string().trim().min(1).max(160).regex(/^[a-zA-Z0-9_.:-]+$/),$t=g.string().trim().min(1).max(160).regex(/^[a-zA-Z][a-zA-Z0-9_.:-]*$/),ns=g.discriminatedUnion("kind",[g.object({kind:g.literal("event_completed"),eventName:oe,withinDays:g.number().int().min(1).max(3650).optional()}).strict(),g.object({kind:g.literal("event_frequency"),eventName:oe,minimum:g.number().int().min(1).max(1e3),withinDays:g.number().int().min(1).max(3650).optional()}).strict(),g.object({kind:g.literal("event_recency"),eventName:oe,withinDays:g.number().int().min(1).max(3650)}).strict(),g.object({kind:g.literal("page_visited"),path:g.string().trim().startsWith("/").max(2e3),withinDays:g.number().int().min(1).max(3650).optional()}).strict(),g.object({kind:g.literal("referrer_equals"),value:g.string().trim().min(1).max(500)}).strict(),g.object({kind:g.literal("source_equals"),value:g.string().trim().min(1).max(180)}).strict(),g.object({kind:g.literal("call_outcome"),outcome:oe,withinDays:g.number().int().min(1).max(3650).optional()}).strict(),g.object({kind:g.literal("crm_stage"),stage:oe}).strict(),g.object({kind:g.literal("property_equals"),property:$t,value:g.union([g.string().max(500),g.number().finite(),g.boolean()])}).strict(),g.object({kind:g.literal("property_present"),property:$t}).strict()]),is=g.object({id:oe,label:g.string().trim().min(1).max(160),operation:g.enum(["add","subtract"]),points:g.number().int().min(0).max(1e5),cap:g.number().int().min(0).max(1e5).optional(),predicates:g.array(ns).min(1).max(50)}).strict(),ri=g.object({namespaceId:oe,name:g.string().trim().min(1).max(160),kind:g.enum(["fit","engagement","combined"]),targetObject:g.enum(["person","visitor"]),minimum:g.number().int().min(-1e6).max(1e6),maximum:g.number().int().min(-1e6).max(1e6),groups:g.array(is).min(1).max(100),permittedProperties:g.array($t).max(200).default([]),enabled:g.boolean().default(!0)}).strict().superRefine((e,t)=>{e.minimum>=e.maximum&&t.addIssue({code:"custom",path:["maximum"],message:"maximum must be greater than minimum"});let n=new Set(e.permittedProperties);for(let[i,r]of e.groups.entries())for(let[s,a]of r.predicates.entries())(a.kind==="property_equals"||a.kind==="property_present")&&!n.has(a.property)&&t.addIssue({code:"custom",path:["groups",i,"predicates",s,"property"],message:"property is not allowlisted"})});function si(){return{definitions:new Map,evaluations:new Map,evaluationHistory:[]}}var ee=class extends Error{constructor(n,i){super(i);this.code=n;this.name="AnalyticsLeadScoringError"}code};function kt(e){let t=e instanceof Date?e.toISOString():e??new Date().toISOString();if(!Number.isFinite(Date.parse(t)))throw new ee("analytics_score_invalid_time","A valid evaluation time is required.");return new Date(t).toISOString()}function Le(e,t){return`${e}:${t}`}function Pt(e){return{...e,groups:e.groups.map(t=>({...t,predicates:t.predicates.map(n=>({...n}))})),permittedProperties:[...e.permittedProperties]}}function rs(e,t,n){let i=Date.parse(e),r=Date.parse(t);return!Number.isFinite(i)||i>r?!1:n===void 0||r-i<=n*864e5}function ss(e,t,n){let i=(r,s,a)=>(r??[]).filter(o=>o.name===s&&rs(o.occurredAt,n,a));switch(e.kind){case"event_completed":{let r=i(t.events,e.eventName,e.withinDays);return r.length?r.map(s=>s.ref):null}case"event_frequency":{let r=i(t.events,e.eventName,e.withinDays);return r.length>=e.minimum?r.map(s=>s.ref):null}case"event_recency":{let r=i(t.events,e.eventName,e.withinDays);return r.length?r.map(s=>s.ref):null}case"page_visited":{let r=i(t.pages,e.path,e.withinDays);return r.length?r.map(s=>s.ref):null}case"referrer_equals":{let r=(t.referrers??[]).filter(s=>s.name===e.value);return r.length?r.map(s=>s.ref):null}case"source_equals":{let r=(t.sources??[]).filter(s=>s.name===e.value);return r.length?r.map(s=>s.ref):null}case"call_outcome":{let r=i(t.calls,e.outcome,e.withinDays);return r.length?r.map(s=>s.ref):null}case"crm_stage":{let r=i(t.crmStages,e.stage);return r.length?r.map(s=>s.ref):null}case"property_equals":return t.permittedProperties?.[e.property]===e.value?[`property:${e.property}`]:null;case"property_present":{let r=t.permittedProperties?.[e.property];return r!=null?[`property:${e.property}`]:null}}}function as(e){let t=n=>Array.isArray(n)?n.map(t):n&&typeof n=="object"?Object.fromEntries(Object.entries(n).sort(([i],[r])=>i.localeCompare(r)).map(([i,r])=>[i,t(r)])):n;return ts("sha256").update(JSON.stringify(t(e))).digest("hex")}function ai(e){let t=ri.parse(e.definition),n=e.id??ii();if(e.tables.definitions.has(Le(t.namespaceId,n)))throw new ee("analytics_score_exists","A lead score with this ID already exists.");let i=kt(e.now),r={id:n,...t,groups:t.groups.map(s=>({...s,predicates:s.predicates.map(a=>({...a}))})),version:1,createdAt:i,updatedAt:i};return e.tables.definitions.set(Le(t.namespaceId,n),r),Pt(r)}function yc(e){let t=e.tables.definitions.get(Le(e.namespaceId,e.scoreId));if(!t)throw new ee("analytics_score_not_found","Lead score not found.");if(t.version!==e.expectedVersion)throw new ee("analytics_score_version_conflict","The lead score changed; reload before updating.");let n=ri.parse(e.definition);if(n.namespaceId!==e.namespaceId)throw new ee("analytics_score_namespace_mismatch","Lead scores cannot move between namespaces.");let i={...t,...n,groups:n.groups.map(r=>({...r,predicates:r.predicates.map(s=>({...s}))})),version:t.version+1,updatedAt:kt(e.now)};return e.tables.definitions.set(Le(e.namespaceId,e.scoreId),i),Pt(i)}function gc(e){return[...e.tables.definitions.values()].filter(t=>t.namespaceId===e.namespaceId).sort((t,n)=>t.name.localeCompare(n.name)).map(Pt)}function oi(e){let t=e.tables.definitions.get(Le(e.namespaceId,e.scoreId));if(!t||!t.enabled)throw new ee("analytics_score_unavailable","Lead score is missing or disabled.");let n=kt(e.now),i=t.groups.map(_=>{let d=_.predicates.map(b=>ss(b,e.facts,n)),p=d.every(b=>b!==null),E=p?Math.min(_.points,_.cap??_.points):0;return{groupId:_.id,operation:_.operation,matched:p,points:_.operation==="subtract"?-E:E,evidenceRefs:p?[...new Set(d.flatMap(b=>b??[]))].sort():[]}}),r=i.reduce((_,d)=>_+d.points,0),s=Math.max(t.minimum,Math.min(t.maximum,r)),a=[...new Set(i.flatMap(_=>_.evidenceRefs))].sort(),o=as({namespaceId:e.namespaceId,scoreId:e.scoreId,version:t.version,subjectRef:e.subjectRef,identityTier:e.identityTier,components:i,value:s}),c=e.tables.evaluations.get(o);if(c)return{...c,components:c.components.map(_=>({..._,evidenceRefs:[..._.evidenceRefs]})),evidenceRefs:[...c.evidenceRefs]};let l={id:ii(),namespaceId:e.namespaceId,subjectRef:e.subjectRef,identityTier:e.identityTier,scoreId:t.id,scoreVersion:t.version,scoreKind:t.kind,value:s,components:i,evidenceRefs:a,evaluatedAt:n,idempotencyKey:o,providerEligible:e.identityTier==="confirmed"};return e.tables.evaluations.set(o,l),e.tables.evaluationHistory.push(l),{...l,components:l.components.map(_=>({..._,evidenceRefs:[..._.evidenceRefs]})),evidenceRefs:[...l.evidenceRefs]}}import{createHmac as os,timingSafeEqual as cs}from"crypto";function y(e,t=500){if(typeof e!="string"&&typeof e!="number")return;let n=String(e).trim();return n?n.slice(0,t):void 0}function Y(e){let t=Number(e);return Number.isFinite(t)?t:void 0}function ce(e){if(typeof e=="boolean")return e;if(e==="true"||e==="1"||e===1)return!0;if(e==="false"||e==="0"||e===0)return!1}function $e(e){if(Array.isArray(e))return e.map(n=>y(n,80)).filter(n=>!!n).slice(0,30);let t=y(e,1e3);return t?t.split(",").map(n=>n.trim()).filter(Boolean).slice(0,30):[]}function G(...e){for(let t of e){let n=y(t,100);if(!n)continue;let i=new Date(n);if(Number.isFinite(i.getTime()))return i.toISOString()}return new Date().toISOString()}function ve(e,t,n){return os(e,t).update(n).digest("base64")}function Ee(e,t){if(!e||e.length!==t.length)return!1;try{return cs(Buffer.from(e),Buffer.from(t))}catch{return!1}}function be(e,t=Date.now(),n=300*1e3){let i=y(e,100);if(!i)return!1;let r=Number(i),s=Number.isFinite(r)?r>1e10?r:r*1e3:Date.parse(i);return Number.isFinite(s)&&Math.abs(t-s)<=n}function Ec(e){let t=new URL(e.requestUrl);for(let s of e.internalRewriteQueryKeys??[])t.searchParams.delete(s);let n=e.publicOrigin?.trim();if(n){let s=new URL(n);if(s.protocol!=="https:"||s.username||s.password)throw new Error("analytics webhook public origin must be an HTTPS origin");return s.pathname=t.pathname,s.search=t.search,s.hash="",s.toString()}let i=e.forwardedProto?.split(",")[0]?.trim(),r=e.forwardedHost?.split(",")[0]?.trim();return i&&(t.protocol=`${i}:`),r&&(t.host=r),t.toString()}function ci(e){return be(e.payloadTimestamp,e.now)&&Ee(e.signature,ve("sha1",e.secret,e.rawBody))}function di(e){let t=e.payload,n=y(t.resource_id??t.id,240)||"unknown",i=G(t.timestamp,t.start_time,t.created_at),r=$e(t.tags??t.tag),s=y(t.lead_status,80),a=s==="not_a_lead"||r.some(c=>c.toLowerCase()==="spam")||ce(t.spam)===!0,o={...y(t.gclid)?{gclid:y(t.gclid)}:{},...y(t.gbraid)?{gbraid:y(t.gbraid)}:{},...y(t.wbraid)?{wbraid:y(t.wbraid)}:{},...y(t.fbclid)?{fbclid:y(t.fbclid)}:{},...y(t.msclkid)?{msclkid:y(t.msclkid)}:{}};return{schemaVersion:1,eventId:`callrail_${n}_${e.phase}`,sourceEventId:`${n}:${e.phase}:${i}`,source:"callrail",sourceAccountRef:e.sourceAccountRef,eventKind:"call",eventName:e.phase==="pre_call"?"call_started":e.phase==="routing_complete"?"call_routed":e.phase==="call_modified"?"call_modified":"call_completed",occurredAt:i,callId:n,personRef:y(t.person_resource_id,240),visitorId:y(t.visitor_id??t.browser_id??t.ga,160),canonicalUrl:y(t.landing_page_url,3e3),sourceName:y(t.utm_source??t.source_name,180),medium:y(t.utm_medium??t.medium,180),campaign:y(t.utm_campaign??t.campaign,240),clickIds:o,properties:{answered:ce(t.answered)??!1,durationSeconds:Y(t.duration)??0,status:a?"spam":s||"unknown",tags:r,direction:y(t.direction,40)||"unknown",trackingNumber:y(t.tracking_phone_number,80)||null,changes:$e(t.changes)}}}function li(e){return be(e.requestTime,e.now)&&Ee(e.signature.trim(),ve("sha1",e.secret,`${e.requestTime}${e.rawBody}`))}function ui(e){let t=e.payload,n=y(t.activity_id??t.call_id??t.id,240)||"unknown",i=G(t.timestamp,t.event_time,t.started_at,t.created_at),r=$e(t.tags??t.tag_list),s=y(t.status??t.disposition,80)||"unknown",a=s.toLowerCase().includes("spam")||r.some(o=>o.toLowerCase()==="spam");return{schemaVersion:1,eventId:`ctm_${n}_${e.phase}`,sourceEventId:`${n}:${e.phase}:${i}`,source:"calltrackingmetrics",sourceAccountRef:e.sourceAccountRef,eventKind:"call",eventName:e.phase.includes("start")?"call_started":e.phase.includes("answer")?"call_answered":"call_completed",occurredAt:i,callId:n,personRef:y(t.contact_id??t.visitor_id,240),visitorId:y(t.visitor_id??t.cookie_id,160),sessionId:y(t.session_id,160),canonicalUrl:y(t.landing_page_url??t.landing_url,3e3),sourceName:y(t.source??t.utm_source,180),medium:y(t.medium??t.utm_medium,180),campaign:y(t.campaign??t.utm_campaign,240),clickIds:{...y(t.gclid)?{gclid:y(t.gclid)}:{},...y(t.fbclid)?{fbclid:y(t.fbclid)}:{},...y(t.msclkid)?{msclkid:y(t.msclkid)}:{}},properties:{answered:ce(t.answered)??e.phase.includes("answer"),durationSeconds:Y(t.duration??t.talk_time)??0,status:a?"spam":s,tags:r,direction:y(t.direction,40)||"unknown",trackingNumber:y(t.tracking_number??t.dialed_number,80)||null,score:Y(t.score)??null}}}import ds from"twilio";function _i(e){return ds.validateRequest(e.authToken,e.signature,e.url,e.params)}function mi(e){let t=e.params,n=y(t.CallSid,240)||"unknown",i=(y(t.CallStatus,80)||"unknown").toLowerCase(),r=G(t.Timestamp,t.CallTimestamp,t.StartTime),s=["completed","busy","failed","no-answer","canceled"].includes(i),a=["in-progress","answered","completed"].includes(i);return{schemaVersion:1,eventId:`twilio_${n}_${i}`,sourceEventId:`${n}:${i}`,source:"twilio",sourceAccountRef:e.sourceAccountRef,eventKind:"call",eventName:s?"call_completed":a?"call_answered":"call_started",occurredAt:r,callId:n,visitorId:y(t.XRayVisitorId,160),sessionId:y(t.XRaySessionId,160),personRef:y(t.ContactId,240),properties:{answered:ce(t.Answered)??a,durationSeconds:Y(t.CallDuration??t.Duration)??0,status:i,direction:y(t.Direction,40)||"unknown",trackingNumber:y(t.To,80)||null,attributionLink:t.XRaySessionId?"explicit_session":"unattributed"}}}import{createCipheriv as ls,createDecipheriv as us,createHash as _s,createHmac as ms}from"crypto";var ke={session:"xrs_",visitor:"xrv_",person:"xrp_",cursor:"xrc_",candidate:"xri_"};function Ut(e){return _s("sha256").update(`xray-report:${e}:`).update(qe()).digest()}function T(e,t,n){let i=Ut("reference"),r=Buffer.from(`v1:${e}:${t}`),s=Buffer.from(n),a=ms("sha256",Ut("reference-iv")).update(r).update(s).digest().subarray(0,12),o=ls("aes-256-gcm",i,a);o.setAAD(r);let c=Buffer.concat([o.update(s),o.final()]);return`${ke[e]}${Buffer.concat([a,c,o.getAuthTag()]).toString("base64url")}`}function at(e,t,n){let i=ke[e];if(!n.startsWith(i))throw new Error("analytics_reference_invalid");try{let r=Buffer.from(n.slice(i.length),"base64url");if(r.length<29)throw new Error("short reference");let s=r.subarray(0,12),a=r.subarray(r.length-16),o=r.subarray(12,r.length-16),c=us("aes-256-gcm",Ut("reference"),s);return c.setAAD(Buffer.from(`v1:${e}:${t}`)),c.setAuthTag(a),Buffer.concat([c.update(o),c.final()]).toString("utf8")}catch{throw new Error("analytics_reference_invalid")}}function ps(e,t){return T("cursor",e,JSON.stringify(t))}function Ae(e,t){if(!t)return null;try{let n=JSON.parse(at("cursor",e,t));if(!n.at||!n.id||Number.isNaN(Date.parse(n.at)))throw new Error("invalid");return n}catch{throw new Error("analytics_cursor_invalid")}}function Se(e){if(e===void 0)return 50;if(!Number.isInteger(e)||e<1||e>100)throw new Error("analytics_limit_invalid");return e}function Mt(e){if(typeof e!="string"||!e)return null;try{let t=new URL(e);return`${t.origin}${t.pathname}`.slice(0,2e3)}catch{return null}}function Ie(e){if(e==null)return null;let t=Number(e);return Number.isFinite(t)?t:null}function N(e){let t=e instanceof Date?e:new Date(e);if(!Number.isFinite(t.getTime()))throw new Error("analytics_timestamp_invalid");return t.toISOString()}function pi(e){return e===null?null:N(e)}function he(e,t,n,i){let r=t.length>n,s=t.slice(0,n),a=s.at(-1);return{items:s.map(i),pageInfo:{hasNextPage:r,nextCursor:r&&a?ps(e,{at:a._cursorAt,id:a._cursorId}):null}}}async function Ne(e,t){await B(t,e.siteId,e.userId)}async function Lc(e,t=v()){await Ne(e,t);let n=Se(e.limit),i=Ae(e.siteId,e.cursor),s=(await t.query(`/* analyticsSessions:keyset */
405
- WITH ranked AS (
406
- SELECT s.*,
407
- row_number() OVER (PARTITION BY s.visitor_hmac ORDER BY s.started_at, s.id)::int AS session_number
408
- FROM analytics_sessions s
409
- WHERE s.site_id = $1
410
- ), facts AS (
411
- SELECT DISTINCT ON (e.session_hmac)
412
- e.session_hmac, e.country_code, e.region_code, e.device_class,
413
- e.source, e.medium, e.campaign
414
- FROM analytics_events e
415
- WHERE e.site_id = $1
416
- ORDER BY e.session_hmac, e.occurred_at
417
- ), conversions AS (
418
- SELECT c.session_hmac, count(*)::int AS conversion_count,
419
- sum(c.value_minor)::bigint AS revenue_minor,
420
- CASE WHEN count(DISTINCT c.currency) = 1 THEN min(c.currency) END AS currency
421
- FROM analytics_conversions c WHERE c.site_id = $1 GROUP BY c.session_hmac
422
- )
423
- SELECT r.id AS internal_id, r.visitor_hmac, r.person_id, r.started_at, r.last_active_at,
424
- r.ended_at, r.duration_ms, r.landing_path, r.exit_path, r.pageview_count,
425
- r.event_count, r.session_number, f.country_code, f.region_code, f.device_class,
426
- f.source, f.medium, f.campaign, COALESCE(c.conversion_count, 0)::int AS conversion_count,
427
- c.revenue_minor, c.currency
428
- FROM ranked r
429
- LEFT JOIN facts f ON f.session_hmac = r.session_hmac
430
- LEFT JOIN conversions c ON c.session_hmac = r.session_hmac
431
- WHERE ($2::timestamptz IS NULL OR (r.started_at, r.id) < ($2::timestamptz, $3::uuid))
432
- AND ($4::boolean IS NULL OR (r.session_number > 1) = $4)
433
- AND ($5::boolean IS NULL OR (COALESCE(c.conversion_count, 0) > 0) = $5)
434
- AND ($6::bigint IS NULL OR r.duration_ms >= $6)
435
- AND ($7::bigint IS NULL OR r.duration_ms <= $7)
436
- AND ($8::text IS NULL OR EXISTS (
437
- SELECT 1 FROM analytics_events ee
438
- WHERE ee.site_id = $1 AND ee.session_hmac = r.session_hmac AND ee.event_name = $8
439
- ))
440
- ORDER BY r.started_at DESC, r.id DESC LIMIT $9`,[e.siteId,i?.at??null,i?.id??null,e.returning??null,e.converted??null,e.minimumDurationMs??null,e.maximumDurationMs??null,e.eventName?.slice(0,160)??null,n+1])).rows.map(a=>({...a,_cursorAt:N(a.started_at),_cursorId:a.internal_id}));return he(e.siteId,s,n,a=>({sessionRef:T("session",e.siteId,a.internal_id),visitorRef:T("visitor",e.siteId,`visitor:${a.visitor_hmac}`),personRef:a.person_id?T("person",e.siteId,a.person_id):null,returning:a.session_number>1,sessionNumber:a.session_number,startedAt:N(a.started_at),lastActiveAt:N(a.last_active_at),endedAt:pi(a.ended_at),durationMs:Ie(a.duration_ms),landingPath:a.landing_path,exitPath:a.exit_path,pages:a.pageview_count,events:a.event_count,device:a.device_class,location:{country:a.country_code,region:a.region_code},acquisition:{source:a.source,medium:a.medium,campaign:a.campaign},conversions:a.conversion_count,revenue:a.revenue_minor===null?null:{valueMinor:Number(a.revenue_minor),currency:a.currency}}))}async function $c(e,t=v()){await Ne(e,t);let n=Se(e.limit),i=Ae(e.siteId,e.cursor),s=(await t.query(`/* analyticsVisitors:keyset */
441
- WITH visitor_sessions AS (
442
- SELECT COALESCE(s.person_id::text, s.visitor_hmac) AS identity_key, s.person_id,
443
- min(s.started_at) AS first_seen_at, max(s.last_active_at) AS last_seen_at,
444
- count(*)::int AS session_count, sum(s.duration_ms)::bigint AS duration_ms,
445
- sum(s.pageview_count)::int AS pageview_count, sum(s.event_count)::int AS event_count,
446
- (array_agg(s.id ORDER BY s.last_active_at DESC, s.id DESC))[1] AS last_session_id
447
- FROM analytics_sessions s WHERE s.site_id = $1
448
- GROUP BY COALESCE(s.person_id::text, s.visitor_hmac), s.person_id
449
- ), conversions AS (
450
- SELECT COALESCE(c.person_id::text, s.visitor_hmac) AS identity_key,
451
- count(*)::int AS conversion_count, sum(c.value_minor)::bigint AS revenue_minor,
452
- CASE WHEN count(DISTINCT c.currency) = 1 THEN min(c.currency) END AS currency
453
- FROM analytics_conversions c
454
- LEFT JOIN analytics_sessions s ON s.site_id=c.site_id AND s.session_hmac=c.session_hmac
455
- WHERE c.site_id=$1 GROUP BY COALESCE(c.person_id::text, s.visitor_hmac)
456
- )
457
- SELECT v.*, COALESCE(c.conversion_count,0)::int AS conversion_count, c.revenue_minor, c.currency
458
- FROM visitor_sessions v LEFT JOIN conversions c USING(identity_key)
459
- WHERE ($2::timestamptz IS NULL OR (v.last_seen_at, v.last_session_id) < ($2::timestamptz, $3::uuid))
460
- AND ($4::boolean IS NULL OR (v.session_count > 1) = $4)
461
- AND ($5::boolean IS NULL OR (COALESCE(c.conversion_count,0) > 0) = $5)
462
- ORDER BY v.last_seen_at DESC, v.last_session_id DESC LIMIT $6`,[e.siteId,i?.at??null,i?.id??null,e.returning??null,e.converted??null,n+1])).rows.map(a=>({...a,_cursorAt:N(a.last_seen_at),_cursorId:a.last_session_id}));return he(e.siteId,s,n,a=>({visitorRef:T("visitor",e.siteId,a.person_id?`person:${a.person_id}`:`visitor:${a.identity_key}`),personRef:a.person_id?T("person",e.siteId,a.person_id):null,known:!!a.person_id,returning:a.session_count>1,sessions:a.session_count,firstSeenAt:N(a.first_seen_at),lastSeenAt:N(a.last_seen_at),durationMs:Ie(a.duration_ms),pages:a.pageview_count,events:a.event_count,conversions:a.conversion_count,revenue:a.revenue_minor===null?null:{valueMinor:Number(a.revenue_minor),currency:a.currency}}))}async function ys(e,t){if(t.startsWith(ke.person))return{kind:"person",personId:at("person",e,t),visitorHmac:null,sessionId:null};if(t.startsWith(ke.visitor)){let n=at("visitor",e,t);if(n.startsWith("person:"))return{kind:"visitor",personId:n.slice(7),visitorHmac:null,sessionId:null};if(n.startsWith("visitor:"))return{kind:"visitor",personId:null,visitorHmac:n.slice(8),sessionId:null};throw new Error("analytics_reference_invalid")}if(t.startsWith(ke.session))return{kind:"session",personId:null,visitorHmac:null,sessionId:at("session",e,t)};throw new Error("analytics_reference_invalid")}var gs=2160*60*60*1e3,fs=366*24*60*60*1e3;function vs(e,t){let n=t===void 0?Date.now():Date.parse(t),i=e===void 0?n-gs:Date.parse(e);if(!Number.isFinite(i)||!Number.isFinite(n)||i>=n||n-i>fs)throw new f("analytics_journey_date_range_invalid","Choose a valid from-inclusive, to-exclusive date range of at most 366 days.",400);return{from:new Date(i).toISOString(),to:new Date(n).toISOString()}}async function kc(e,t=v()){await Ne(e,t);let n=Se(e.limit),i=Ae(e.siteId,e.cursor),r;try{r=await ys(e.siteId,e.reference)}catch{throw new f("analytics_journey_not_found","Journey not found.",404)}if(!(await t.query(`/* analyticsEnhancedJourney:subject */
463
- SELECT 1 WHERE
464
- ($2::uuid IS NOT NULL AND EXISTS (SELECT 1 FROM analytics_people p WHERE p.site_id=$1 AND p.id=$2))
465
- OR ($3::text IS NOT NULL AND EXISTS (SELECT 1 FROM analytics_sessions s WHERE s.site_id=$1 AND s.visitor_hmac=$3))
466
- OR ($4::uuid IS NOT NULL AND EXISTS (SELECT 1 FROM analytics_sessions s WHERE s.site_id=$1 AND s.id=$4))
467
- LIMIT 1`,[e.siteId,r.personId,r.visitorHmac,r.sessionId])).rowCount)throw new f("analytics_journey_not_found","Journey not found.",404);let a=vs(e.from,e.to),o=[e.siteId,r.personId,r.visitorHmac,r.sessionId,a.from,a.to],c=`
468
- SELECT s.id, s.session_hmac, s.visitor_hmac, s.person_id, s.started_at,
469
- s.last_active_at, s.ended_at, s.duration_ms, s.landing_path, s.exit_path
470
- FROM analytics_sessions s
471
- WHERE s.site_id=$1
472
- AND ($2::uuid IS NULL OR s.person_id=$2)
473
- AND ($3::text IS NULL OR s.visitor_hmac=$3 OR s.person_id=(SELECT person_id FROM analytics_sessions WHERE site_id=$1 AND visitor_hmac=$3 AND person_id IS NOT NULL LIMIT 1))
474
- AND ($4::uuid IS NULL OR s.id=$4)`,[l,_,d,p,E,b]=await Promise.all([t.query(`/* analyticsSessionTimeline:keyset */
475
- WITH subject_sessions AS (
476
- ${c}
477
- ), entries AS (
478
- SELECT s.started_at AS occurred_at, s.id::text||':start' AS stable_id,
479
- 'session' AS entry_type, 'session_started' AS name, s.landing_path AS path,
480
- NULL::text AS referrer, NULL::text AS source, NULL::text AS medium,
481
- NULL::text AS campaign, NULL::bigint AS value_minor, NULL::text AS currency,
482
- 'deterministic'::text AS evidence_class, 'persisted_session_bounds'::text AS provenance
483
- FROM subject_sessions s
484
- UNION ALL
485
- SELECT COALESCE(s.ended_at,s.last_active_at), s.id::text||':end', 'session', 'session_ended',
486
- s.exit_path, NULL, NULL, NULL, NULL, NULL, NULL, 'deterministic', 'persisted_session_bounds'
487
- FROM analytics_sessions s JOIN subject_sessions ss ON ss.id=s.id
488
- UNION ALL
489
- SELECT e.occurred_at, e.id::text AS stable_id,
490
- CASE WHEN e.event_name='page_view' THEN 'page' ELSE 'event' END AS entry_type,
491
- e.event_name AS name, e.path, e.referrer, e.source, e.medium, e.campaign,
492
- NULL::bigint AS value_minor, NULL::text AS currency,
493
- 'deterministic'::text AS evidence_class, 'pixel_event'::text AS provenance
494
- FROM analytics_events e JOIN subject_sessions s ON s.session_hmac=e.session_hmac AND e.site_id=$1
495
- UNION ALL
496
- SELECT t.occurred_at, t.id::text, 'touch', COALESCE(t.touch_kind,'touch'), t.landing_path,
497
- t.referrer, t.source, t.medium, t.campaign, NULL, NULL, 'deterministic', 'attribution_touch'
498
- FROM analytics_attribution_touches t JOIN subject_sessions s ON s.id=t.session_id AND t.site_id=$1
499
- UNION ALL
500
- SELECT x.occurred_at, x.id::text, x.event_kind, x.event_name, NULL, NULL, x.source, NULL, NULL,
501
- x.value_minor, x.currency, 'deterministic', 'verified_external_event'
502
- FROM analytics_external_events x
503
- WHERE x.site_id=$1 AND (x.person_id=$2 OR x.session_hmac IN (SELECT session_hmac FROM subject_sessions))
504
- UNION ALL
505
- SELECT c.occurred_at, c.id::text, 'conversion', c.conversion_kind, NULL, NULL, NULL, NULL, NULL,
506
- c.value_minor, c.currency, 'deterministic', COALESCE(c.value_source,'conversion')
507
- FROM analytics_conversions c
508
- WHERE c.site_id=$1 AND (c.person_id=$2 OR c.session_hmac IN (SELECT session_hmac FROM subject_sessions))
509
- UNION ALL
510
- SELECT e.occurred_at, ve.id::text, 'value', e.event_name, e.path, NULL, e.source, e.medium,
511
- e.campaign, ve.value_minor, ve.currency, 'deterministic', ve.value_source
512
- FROM analytics_event_value_evaluations ve
513
- JOIN analytics_events e ON e.id=ve.source_event_id AND e.site_id=ve.site_id
514
- JOIN subject_sessions s ON s.session_hmac=e.session_hmac
515
- WHERE ve.site_id=$1
516
- UNION ALL
517
- SELECT se.evaluated_at, se.id::text, 'score', sd.name, NULL, NULL, NULL, NULL, NULL,
518
- se.score::bigint, NULL, 'deterministic', 'lead_score:'||sd.score_kind
519
- FROM analytics_score_evaluations se JOIN analytics_score_definitions sd ON sd.id=se.definition_id
520
- WHERE se.site_id=$1 AND se.person_id=$2
521
- UNION ALL
522
- SELECT j.created_at, j.id::text, 'receipt', j.status, NULL, NULL, d.platform, NULL, NULL,
523
- NULL, NULL, 'deterministic', COALESCE(j.response_category,'delivery_receipt')
524
- FROM analytics_activation_jobs j JOIN analytics_activation_destinations d ON d.id=j.destination_id
525
- WHERE d.site_id=$1 AND j.person_id=$2
526
- UNION ALL
527
- SELECT ca.promoted_at, ca.id::text, 'identity_transition', 'candidate_promoted_to_confirmed', NULL, NULL,
528
- NULL, NULL, NULL, NULL, NULL, 'deterministic', 'later_deterministic_evidence'
529
- FROM analytics_candidate_associations ca
530
- WHERE ca.site_id=$1 AND ca.promoted_person_id=$2 AND ca.status='promoted'
531
- )
532
- SELECT * FROM entries
533
- WHERE occurred_at IS NOT NULL
534
- AND occurred_at >= $5::timestamptz AND occurred_at < $6::timestamptz
535
- AND ($7::timestamptz IS NULL OR (occurred_at, stable_id) > ($7::timestamptz, $8::text))
536
- ORDER BY occurred_at, stable_id LIMIT $9`,[...o,i?.at??null,i?.id??null,n+1]),t.query(`/* analyticsEnhancedJourney:summary */
537
- WITH subject_sessions AS (${c}), ranged_sessions AS (
538
- SELECT * FROM subject_sessions WHERE started_at >= $5::timestamptz AND started_at < $6::timestamptz
539
- ), ranged_events AS (
540
- SELECT e.* FROM analytics_events e JOIN subject_sessions s ON s.session_hmac=e.session_hmac
541
- WHERE e.site_id=$1 AND e.occurred_at >= $5::timestamptz AND e.occurred_at < $6::timestamptz
542
- ), ranged_conversions AS (
543
- SELECT c.* FROM analytics_conversions c
544
- WHERE c.site_id=$1 AND c.occurred_at >= $5::timestamptz AND c.occurred_at < $6::timestamptz
545
- AND (c.person_id=$2 OR c.session_hmac IN (SELECT session_hmac FROM subject_sessions))
546
- )
547
- SELECT (SELECT (array_agg(person_id ORDER BY started_at) FILTER (WHERE person_id IS NOT NULL))[1]::text FROM subject_sessions) AS resolved_person_id,
548
- (SELECT count(*)::int FROM ranged_sessions) AS sessions,
549
- (SELECT count(*)::int FROM ranged_events WHERE event_name='page_view') AS pageviews,
550
- (SELECT count(*)::int FROM ranged_events) AS events,
551
- (SELECT count(*)::int FROM ranged_conversions) AS conversions,
552
- (SELECT sum(value_minor)::bigint FROM ranged_conversions) AS revenue_minor,
553
- (SELECT CASE WHEN count(DISTINCT currency)=1 THEN min(currency) END FROM ranged_conversions WHERE value_minor IS NOT NULL) AS currency,
554
- (SELECT sum(engaged_ms)::bigint FROM ranged_events WHERE event_name='page_engagement' AND engaged_ms IS NOT NULL) AS total_engagement_ms,
555
- (SELECT round(avg(engaged_ms))::bigint FROM ranged_events WHERE event_name='page_engagement' AND engaged_ms IS NOT NULL) AS average_engagement_ms`,[...o]),t.query(`/* analyticsEnhancedJourney:pages */
556
- WITH subject_sessions AS (${c}), pageviews AS (
557
- SELECT e.path, e.session_id, count(*)::int AS visits
558
- FROM analytics_events e JOIN subject_sessions s ON s.session_hmac=e.session_hmac
559
- WHERE e.site_id=$1 AND e.event_name='page_view' AND e.path IS NOT NULL
560
- AND e.occurred_at >= $5::timestamptz AND e.occurred_at < $6::timestamptz
561
- GROUP BY e.path,e.session_id
562
- ), engagement AS (
563
- SELECT e.path, sum(e.engaged_ms)::bigint AS engaged_ms
564
- FROM analytics_events e JOIN subject_sessions s ON s.session_hmac=e.session_hmac
565
- WHERE e.site_id=$1 AND e.event_name='page_engagement' AND e.path IS NOT NULL AND e.engaged_ms IS NOT NULL
566
- AND e.occurred_at >= $5::timestamptz AND e.occurred_at < $6::timestamptz GROUP BY e.path
567
- ), conversion_pages AS (
568
- SELECT latest.path, count(*)::int AS conversions FROM analytics_conversions c
569
- JOIN LATERAL (
570
- SELECT e.path FROM analytics_events e
571
- WHERE e.site_id=$1 AND e.session_id=c.session_id AND e.event_name='page_view'
572
- AND e.occurred_at <= c.occurred_at ORDER BY e.occurred_at DESC,e.id DESC LIMIT 1
573
- ) latest ON true
574
- WHERE c.site_id=$1 AND c.occurred_at >= $5::timestamptz AND c.occurred_at < $6::timestamptz
575
- AND (c.person_id=$2 OR c.session_hmac IN (SELECT session_hmac FROM subject_sessions))
576
- GROUP BY latest.path
577
- )
578
- SELECT p.path,sum(p.visits)::int AS visits,count(*)::int AS unique_sessions,e.engaged_ms,
579
- COALESCE(c.conversions,0)::int AS conversions
580
- FROM pageviews p LEFT JOIN engagement e USING(path) LEFT JOIN conversion_pages c USING(path)
581
- GROUP BY p.path,e.engaged_ms,c.conversions ORDER BY visits DESC,p.path LIMIT 20`,[...o]),t.query(`/* analyticsEnhancedJourney:acquisition */
582
- WITH subject_sessions AS (${c}), first_touch AS (
583
- SELECT DISTINCT ON (e.session_hmac) e.session_hmac,e.source,e.medium,e.campaign,e.referrer
584
- FROM analytics_events e JOIN subject_sessions s ON s.session_hmac=e.session_hmac
585
- WHERE e.site_id=$1 AND e.occurred_at >= $5::timestamptz AND e.occurred_at < $6::timestamptz
586
- ORDER BY e.session_hmac,e.occurred_at,e.id
587
- ), event_counts AS (
588
- SELECT e.session_hmac,count(*)::int AS events FROM analytics_events e JOIN subject_sessions s ON s.session_hmac=e.session_hmac
589
- WHERE e.site_id=$1 AND e.occurred_at >= $5::timestamptz AND e.occurred_at < $6::timestamptz GROUP BY e.session_hmac
590
- ), conversion_counts AS (
591
- SELECT c.session_hmac,count(*)::int AS conversions FROM analytics_conversions c
592
- WHERE c.site_id=$1 AND c.occurred_at >= $5::timestamptz AND c.occurred_at < $6::timestamptz
593
- AND (c.person_id=$2 OR c.session_hmac IN (SELECT session_hmac FROM subject_sessions)) GROUP BY c.session_hmac
594
- )
595
- SELECT f.source,f.medium,f.campaign,f.referrer,count(*)::int AS sessions,
596
- sum(COALESCE(e.events,0))::int AS events,sum(COALESCE(c.conversions,0))::int AS conversions
597
- FROM first_touch f LEFT JOIN event_counts e USING(session_hmac) LEFT JOIN conversion_counts c USING(session_hmac)
598
- GROUP BY f.source,f.medium,f.campaign,f.referrer ORDER BY sessions DESC,f.source NULLS LAST LIMIT 20`,[...o]),t.query(`/* analyticsEnhancedJourney:events */
599
- WITH subject_sessions AS (${c}), event_groups AS (
600
- SELECT e.event_name AS name,count(*)::int AS count,count(DISTINCT e.session_id)::int AS unique_sessions,
601
- array_agg(DISTINCT e.session_id) AS session_ids
602
- FROM analytics_events e JOIN subject_sessions s ON s.session_hmac=e.session_hmac
603
- WHERE e.site_id=$1 AND e.occurred_at >= $5::timestamptz AND e.occurred_at < $6::timestamptz
604
- GROUP BY e.event_name
605
- )
606
- SELECT g.name,g.count,g.unique_sessions,
607
- (SELECT count(*)::int FROM analytics_conversions c WHERE c.site_id=$1
608
- AND c.occurred_at >= $5::timestamptz AND c.occurred_at < $6::timestamptz
609
- AND c.session_id=ANY(g.session_ids)) AS conversions
610
- FROM event_groups g ORDER BY g.count DESC,g.name LIMIT 50`,[...o]),t.query(`/* analyticsEnhancedJourney:sessions */
611
- WITH subject_sessions AS (${c}), ranged_sessions AS (
612
- SELECT * FROM subject_sessions WHERE started_at >= $5::timestamptz AND started_at < $6::timestamptz
613
- ), event_stats AS (
614
- SELECT e.session_hmac,count(*) FILTER (WHERE e.event_name='page_view')::int AS pageviews,count(*)::int AS events
615
- FROM analytics_events e JOIN ranged_sessions s ON s.session_hmac=e.session_hmac
616
- WHERE e.site_id=$1 AND e.occurred_at >= $5::timestamptz AND e.occurred_at < $6::timestamptz GROUP BY e.session_hmac
617
- ), first_touch AS (
618
- SELECT DISTINCT ON (e.session_hmac) e.session_hmac,e.source,e.medium,e.campaign,e.referrer
619
- FROM analytics_events e JOIN ranged_sessions s ON s.session_hmac=e.session_hmac
620
- WHERE e.site_id=$1 ORDER BY e.session_hmac,e.occurred_at,e.id
621
- ), conversion_stats AS (
622
- SELECT c.session_hmac,count(*)::int AS conversions,sum(c.value_minor)::bigint AS revenue_minor,
623
- CASE WHEN count(DISTINCT c.currency)=1 THEN min(c.currency) END AS currency
624
- FROM analytics_conversions c JOIN ranged_sessions s ON s.session_hmac=c.session_hmac
625
- WHERE c.site_id=$1 AND c.occurred_at >= $5::timestamptz AND c.occurred_at < $6::timestamptz GROUP BY c.session_hmac
626
- )
627
- SELECT s.id AS internal_id,s.started_at,s.ended_at,s.duration_ms,s.landing_path,s.exit_path,
628
- COALESCE(e.pageviews,0)::int AS pageviews,COALESCE(e.events,0)::int AS events,
629
- COALESCE(c.conversions,0)::int AS conversions,c.revenue_minor,c.currency,
630
- f.source,f.medium,f.campaign,f.referrer
631
- FROM ranged_sessions s LEFT JOIN event_stats e ON e.session_hmac=s.session_hmac
632
- LEFT JOIN first_touch f ON f.session_hmac=s.session_hmac LEFT JOIN conversion_stats c ON c.session_hmac=s.session_hmac
633
- ORDER BY s.started_at DESC,s.id DESC LIMIT 50`,[...o])]),k=l.rows.map(m=>({...m,_cursorAt:N(m.occurred_at),_cursorId:m.stable_id})),x=he(e.siteId,k,n,m=>({occurredAt:N(m.occurred_at),type:m.entry_type,name:m.name,path:m.path,referrer:Mt(m.referrer),acquisition:m.source||m.medium||m.campaign?{source:m.source,medium:m.medium,campaign:m.campaign}:null,value:m.value_minor===null?null:{valueMinor:Number(m.value_minor),currency:m.currency},evidenceClass:m.evidence_class,provenance:m.provenance})),h=_.rows[0]??{resolved_person_id:null,sessions:0,pageviews:0,events:0,conversions:0,revenue_minor:null,currency:null,total_engagement_ms:null,average_engagement_ms:null},C=h.resolved_person_id??r.personId;return{subject:{kind:C?"person":r.kind,reference:e.reference,personRef:C?T("person",e.siteId,C):null},range:{...a,fromInclusive:!0,toExclusive:!0},journeyTier:"confirmed",candidateAssisted:null,summary:{sessions:Number(h.sessions),pageviews:Number(h.pageviews),events:Number(h.events),conversions:Number(h.conversions),revenue:h.revenue_minor===null?null:{valueMinor:Number(h.revenue_minor),currency:h.currency},totalEngagementMs:Ie(h.total_engagement_ms),averageEngagementMs:Ie(h.average_engagement_ms)},commonPages:d.rows.map(m=>({path:m.path,visits:Number(m.visits),uniqueSessions:Number(m.unique_sessions),engagedMs:Ie(m.engaged_ms),conversions:Number(m.conversions)})),acquisition:p.rows.map(m=>({source:m.source,medium:m.medium,campaign:m.campaign,referrer:Mt(m.referrer),sessions:Number(m.sessions),events:Number(m.events),conversions:Number(m.conversions)})),eventBreakdown:E.rows.map(m=>({name:m.name,count:Number(m.count),uniqueSessions:Number(m.unique_sessions),conversions:Number(m.conversions)})),sessions:b.rows.map(m=>({sessionRef:T("session",e.siteId,m.internal_id),startedAt:N(m.started_at),endedAt:pi(m.ended_at),durationMs:Ie(m.duration_ms),pageviews:Number(m.pageviews),events:Number(m.events),conversions:Number(m.conversions),revenue:m.revenue_minor===null?null:{valueMinor:Number(m.revenue_minor),currency:m.currency},landingPath:m.landing_path,exitPath:m.exit_path,acquisition:m.source||m.medium||m.campaign||m.referrer?{source:m.source,medium:m.medium,campaign:m.campaign,referrer:Mt(m.referrer)}:null})),...x}}async function Pc(e,t=v()){await Ne(e,t);let n=Se(e.limit),i=Ae(e.siteId,e.cursor),s=(await t.query(`/* analyticsConfirmedJourneys:keyset */
634
- SELECT p.id AS person_id, p.namespace_id, sessions.first_seen_at,
635
- sessions.last_seen_at, COALESCE(sessions.session_count,0)::int AS session_count,
636
- COALESCE(events.event_count,0)::int AS event_count,
637
- COALESCE(conversions.conversion_count,0)::int AS conversion_count,
638
- conversions.revenue_minor, conversions.currency
639
- FROM analytics_people p
640
- JOIN analytics_identity_namespaces n ON n.id=p.namespace_id AND n.site_id=p.site_id
641
- LEFT JOIN LATERAL (
642
- SELECT min(s.started_at) AS first_seen_at, max(s.last_active_at) AS last_seen_at,
643
- count(*)::int AS session_count
644
- FROM analytics_sessions s WHERE s.site_id=p.site_id AND s.person_id=p.id
645
- ) sessions ON true
646
- LEFT JOIN LATERAL (
647
- SELECT count(*)::int AS event_count
648
- FROM analytics_events e WHERE e.site_id=p.site_id AND e.person_id=p.id
649
- ) events ON true
650
- LEFT JOIN LATERAL (
651
- SELECT count(*)::int AS conversion_count, sum(c.value_minor)::bigint AS revenue_minor,
652
- CASE WHEN count(DISTINCT c.currency)=1 THEN min(c.currency) END AS currency
653
- FROM analytics_conversions c WHERE c.site_id=p.site_id AND c.person_id=p.id
654
- ) conversions ON true
655
- WHERE p.site_id=$1 AND n.status='approved'
656
- AND ($2::timestamptz IS NULL OR (sessions.last_seen_at,p.id) < ($2::timestamptz,$3::uuid))
657
- ORDER BY last_seen_at DESC, p.id DESC LIMIT $4`,[e.siteId,i?.at??null,i?.id??null,n+1])).rows.map(o=>({...o,_cursorAt:N(o.last_seen_at),_cursorId:o.person_id}));return{journeyTier:"confirmed",...he(e.siteId,s,n,o=>({journeyTier:"confirmed",personRef:T("person",e.siteId,o.person_id),firstSeenAt:N(o.first_seen_at),lastSeenAt:N(o.last_seen_at),sessions:o.session_count,events:o.event_count,conversions:o.conversion_count,revenue:o.revenue_minor===null?null:{valueMinor:Number(o.revenue_minor),currency:o.currency},evidenceClass:"deterministic"}))}}async function Mc(e,t=v()){await Ne(e,t);let n=Se(e.limit),i=Ae(e.siteId,e.cursor),s=(await t.query(`/* analyticsBestGuessJourneys:keyset */
658
- SELECT ca.id AS association_id, ca.namespace_id, ca.model_version, ca.expires_at, ca.updated_at,
659
- ca.left_identity_node_id AS left_node_id, ca.right_identity_node_id AS right_node_id,
660
- COALESCE(stats.confirmed_sessions,0)::int AS confirmed_sessions,
661
- COALESCE(stats.inferred_sessions,0)::int AS inferred_sessions,
662
- COALESCE(stats.confirmed_events,0)::int AS confirmed_events,
663
- COALESCE(stats.inferred_events,0)::int AS inferred_events,
664
- COALESCE(stats.confirmed_conversions,0)::int AS confirmed_conversions,
665
- COALESCE(stats.inferred_conversions,0)::int AS inferred_conversions,
666
- stats.confirmed_revenue_minor, stats.inferred_revenue_minor, stats.currency,
667
- COALESCE(ev.feature_kinds, ARRAY[]::text[]) AS feature_kinds
668
- FROM analytics_candidate_associations ca
669
- JOIN analytics_identity_namespaces n ON n.id=ca.namespace_id AND n.site_id=ca.site_id
670
- LEFT JOIN LATERAL (
671
- SELECT array_agg(DISTINCT ce.feature_kind ORDER BY ce.feature_kind) AS feature_kinds
672
- FROM analytics_candidate_evidence ce
673
- WHERE ce.association_id=ca.id AND ce.namespace_id=ca.namespace_id AND ce.expires_at > now()
674
- ) ev ON true
675
- LEFT JOIN LATERAL (
676
- SELECT
677
- sum(node_stats.session_count) FILTER (WHERE node_stats.node_id=ca.left_identity_node_id)::int AS confirmed_sessions,
678
- sum(node_stats.session_count) FILTER (WHERE node_stats.node_id=ca.right_identity_node_id)::int AS inferred_sessions,
679
- sum(node_stats.event_count) FILTER (WHERE node_stats.node_id=ca.left_identity_node_id)::int AS confirmed_events,
680
- sum(node_stats.event_count) FILTER (WHERE node_stats.node_id=ca.right_identity_node_id)::int AS inferred_events,
681
- sum(node_stats.conversion_count) FILTER (WHERE node_stats.node_id=ca.left_identity_node_id)::int AS confirmed_conversions,
682
- sum(node_stats.conversion_count) FILTER (WHERE node_stats.node_id=ca.right_identity_node_id)::int AS inferred_conversions,
683
- sum(node_stats.revenue_minor) FILTER (WHERE node_stats.node_id=ca.left_identity_node_id)::bigint AS confirmed_revenue_minor,
684
- sum(node_stats.revenue_minor) FILTER (WHERE node_stats.node_id=ca.right_identity_node_id)::bigint AS inferred_revenue_minor,
685
- CASE WHEN count(DISTINCT node_stats.currency)=1 THEN min(node_stats.currency) END AS currency
686
- FROM (
687
- SELECT inode.id AS node_id,
688
- (SELECT count(*) FROM analytics_sessions s
689
- WHERE s.site_id=ca.site_id AND s.visitor_hmac=inode.value_hmac)::int AS session_count,
690
- (SELECT count(*) FROM analytics_events e JOIN analytics_sessions s
691
- ON s.site_id=e.site_id AND s.session_hmac=e.session_hmac
692
- WHERE e.site_id=ca.site_id AND s.visitor_hmac=inode.value_hmac)::int AS event_count,
693
- (SELECT count(*) FROM analytics_conversions c JOIN analytics_sessions s
694
- ON s.site_id=c.site_id AND s.session_hmac=c.session_hmac
695
- WHERE c.site_id=ca.site_id AND s.visitor_hmac=inode.value_hmac)::int AS conversion_count,
696
- (SELECT sum(c.value_minor) FROM analytics_conversions c JOIN analytics_sessions s
697
- ON s.site_id=c.site_id AND s.session_hmac=c.session_hmac
698
- WHERE c.site_id=ca.site_id AND s.visitor_hmac=inode.value_hmac)::bigint AS revenue_minor,
699
- (SELECT CASE WHEN count(DISTINCT c.currency)=1 THEN min(c.currency) END
700
- FROM analytics_conversions c JOIN analytics_sessions s
701
- ON s.site_id=c.site_id AND s.session_hmac=c.session_hmac
702
- WHERE c.site_id=ca.site_id AND s.visitor_hmac=inode.value_hmac) AS currency
703
- FROM analytics_identity_nodes inode
704
- WHERE inode.site_id=ca.site_id AND inode.namespace_id=ca.namespace_id
705
- AND inode.id IN (ca.left_identity_node_id,ca.right_identity_node_id)
706
- ) node_stats
707
- ) stats ON true
708
- WHERE ca.site_id=$1 AND n.status='approved' AND ca.status='active'
709
- AND ca.confidence_band='high' AND ca.expires_at > now()
710
- AND ($2::timestamptz IS NULL OR (ca.updated_at,ca.id) < ($2::timestamptz,$3::uuid))
711
- ORDER BY ca.updated_at DESC, ca.id DESC LIMIT $4`,[e.siteId,i?.at??null,i?.id??null,n+1])).rows.map(o=>({...o,_cursorAt:N(o.updated_at),_cursorId:o.association_id}));return{journeyTier:"candidate_assisted",totalsAreSeparated:!0,...he(e.siteId,s,n,o=>({journeyTier:"candidate_assisted",inferenceRef:T("candidate",e.siteId,o.association_id),confirmedTotals:{sessions:o.confirmed_sessions,events:o.confirmed_events,conversions:o.confirmed_conversions,revenue:o.confirmed_revenue_minor===null?null:{valueMinor:Number(o.confirmed_revenue_minor),currency:o.currency}},inferredIncrementalTotals:{sessions:o.inferred_sessions,events:o.inferred_events,conversions:o.inferred_conversions,revenue:o.inferred_revenue_minor===null?null:{valueMinor:Number(o.inferred_revenue_minor),currency:o.currency}},candidateCount:1,confidenceBand:"high",modelVersion:o.model_version,featureClasses:o.feature_kinds,coverage:o.inferred_sessions>0?"incremental_sessions_available":"no_incremental_sessions",expiresAt:N(o.expires_at),provenance:"consented_high_confidence_candidate_association"}))}}async function Uc(e,t=v()){await Ne(e,t);let n=Se(e.limit),i=Ae(e.siteId,e.cursor),s=(await t.query(`/* analyticsInferenceReview:keyset */
712
- SELECT ca.id AS association_id, ca.updated_at, ca.first_observed_at, ca.last_observed_at,
713
- ca.expires_at, ca.model_version, ca.confidence_band,
714
- COALESCE(array_agg(DISTINCT ce.feature_kind ORDER BY ce.feature_kind)
715
- FILTER (WHERE ce.id IS NOT NULL), ARRAY[]::text[]) AS feature_kinds
716
- FROM analytics_candidate_associations ca
717
- JOIN analytics_identity_namespaces n ON n.id=ca.namespace_id AND n.site_id=ca.site_id
718
- LEFT JOIN analytics_candidate_evidence ce ON ce.association_id=ca.id
719
- AND ce.namespace_id=ca.namespace_id AND ce.expires_at > now()
720
- WHERE ca.site_id=$1 AND n.status='approved' AND ca.status='active'
721
- AND ca.confidence_band='medium' AND ca.expires_at > now()
722
- AND ($2::timestamptz IS NULL OR (ca.updated_at,ca.id) < ($2::timestamptz,$3::uuid))
723
- GROUP BY ca.id
724
- ORDER BY ca.updated_at DESC, ca.id DESC LIMIT $4`,[e.siteId,i?.at??null,i?.id??null,n+1])).rows.map(a=>({...a,_cursorAt:N(a.updated_at),_cursorId:a.association_id}));return he(e.siteId,s,n,a=>({inferenceRef:T("candidate",e.siteId,a.association_id),confidenceBand:a.confidence_band,modelVersion:a.model_version,featureClasses:a.feature_kinds,firstObservedAt:N(a.first_observed_at),lastObservedAt:N(a.last_observed_at),expiresAt:N(a.expires_at),action:"diagnostic_only",eligibleForConfirmedMetrics:!1,eligibleForCrmOrActivation:!1}))}import{createHash as Es,createVerify as bs,verify as Is}from"crypto";function yi(e){if(!be(e.timestamp,e.now))return!1;let t=`${e.method.toUpperCase()}${e.url}${e.rawBody}${e.timestamp}`;return Ee(e.signature,ve("sha256",e.clientSecret,t))}var As=`-----BEGIN PUBLIC KEY-----
725
- MCowBQYDK2VwAyEAi2HR1srL4o18O8BRa7gVJY7G7bupbN3H9AwJrHCDiOg=
726
- -----END PUBLIC KEY-----`;function gi(e){try{if(e.ghlSignature&&e.ghlSignature!=="N/A")return Is(null,Buffer.from(e.rawBody,"utf8"),e.ed25519PublicKey||As,Buffer.from(e.ghlSignature,"base64"));if(e.legacySignature&&e.legacySignature!=="N/A"&&e.legacyRsaPublicKey){let t=bs("SHA256");return t.update(e.rawBody),t.verify(e.legacyRsaPublicKey,e.legacySignature,"base64")}return!1}catch{return!1}}function Ft(e){return{schemaVersion:1,eventId:`crm_${Es("sha256").update(`${e.source}:${e.sourceEventId}`).digest("hex").slice(0,24)}`,sourceEventId:e.sourceEventId,source:e.source,sourceAccountRef:e.sourceAccountRef,eventKind:"crm",eventName:"crm_stage_changed",occurredAt:e.occurredAt,visitorId:e.visitorId,sessionId:e.sessionId,personRef:e.contactId,dealId:e.dealId,orderId:e.orderId,valueMinor:e.valueMinor,currency:e.currency,clickIds:e.clickIds,properties:{pipelineId:e.pipelineId||null,stageId:e.stageId,stageName:e.stageName||null,status:e.status}}}function fi(e){let t=e.item,n=y(t.objectId,240)||"unknown",i=y(t.propertyValue,240)||"unknown";return{sourceEventId:`deal:${n}:stage:${i}:${y(t.occurredAt,100)||"unknown"}`,source:"hubspot",sourceAccountRef:e.sourceAccountRef,dealId:n,stageId:i,stageName:e.stageName,occurredAt:G(t.occurredAt),valueMinor:e.valueMinor,currency:e.currency,status:/closed.?won/i.test(i)?"won":/closed.?lost/i.test(i)?"lost":"open"}}function vi(e){let t=e.payload,n=t.opportunity&&typeof t.opportunity=="object"?t.opportunity:t,i=y(n.id??t.id,240)||"unknown",r=y(n.pipelineStageId??t.pipelineStageId,240)||"unknown",s=(y(n.status??t.status,80)||"open").toLowerCase();return{sourceEventId:`opportunity:${i}:stage:${r}:${y(t.timestamp??t.dateAdded,100)||"unknown"}`,source:"highlevel",sourceAccountRef:e.sourceAccountRef,contactId:y(n.contactId??t.contactId,240),dealId:i,pipelineId:y(n.pipelineId??t.pipelineId,240),stageId:r,stageName:y(n.pipelineStageName??t.pipelineStageName,240),occurredAt:G(t.timestamp,t.dateAdded,n.updatedAt),valueMinor:Math.round((Y(n.monetaryValue??t.monetaryValue)??0)*100),currency:y(n.currency??t.currency,3)?.toUpperCase(),status:s.includes("won")?"won":s.includes("lost")?"lost":s.includes("spam")?"spam":"open"}}var Cs=90;function bi(e){let t=e.consent;if(!t)return{necessary:!0,siteAnalytics:!1,enhancedMatching:!1,advertisingMeasurement:!1,receiptId:null,reason:"missing"};if(t.globalPrivacyControl)return{necessary:!0,siteAnalytics:!1,enhancedMatching:!1,advertisingMeasurement:!1,receiptId:t.receiptId??null,reason:"gpc"};let n=t.purposes,i={necessary:!0,siteAnalytics:n.site_analytics==="granted",enhancedMatching:n.enhanced_matching==="granted",advertisingMeasurement:n.advertising_measurement==="granted",receiptId:t.receiptId??null};return{...i,reason:i.siteAnalytics||i.enhancedMatching||i.advertisingMeasurement?"granted":"denied"}}function ws(e){let t=[];return e.confirmedPeople<e.minimumConfirmedPeople&&t.push(`confirmed_people:${e.confirmedPeople}/${e.minimumConfirmedPeople}`),e.positiveOutcomes<e.minimumPositiveOutcomes&&t.push(`positive_outcomes:${e.positiveOutcomes}/${e.minimumPositiveOutcomes}`),e.validatedManifest||t.push("validated_calibrated_manifest"),{...e,eligible:t.length===0,missingEvidence:t}}var Rs={callrail:{expectedEventTypes:["call"],expectedEventPhases:["pre_call","routing_complete","post_call","call_modified"],signatureMode:"callrail_hmac_sha1_exact_body",freshnessSlaSeconds:86400,supportsReconciliation:!0},calltrackingmetrics:{expectedEventTypes:["call"],expectedEventPhases:["call_started","call_answered","call_complete","call_modified"],signatureMode:"ctm_hmac_sha1_time_and_exact_body",freshnessSlaSeconds:86400,supportsReconciliation:!1},twilio:{expectedEventTypes:["call_status"],expectedEventPhases:["initiated","ringing","in-progress","completed","busy","failed","no-answer","canceled"],signatureMode:"twilio_sdk_exact_url_and_form_body",freshnessSlaSeconds:86400,supportsReconciliation:!0},hubspot:{expectedEventTypes:["deal.propertyChange"],expectedEventPhases:["dealstage"],signatureMode:"hubspot_v3_hmac_sha256_exact_url_body_timestamp",freshnessSlaSeconds:86400,supportsReconciliation:!1},highlevel:{expectedEventTypes:["OpportunityStageUpdate"],expectedEventPhases:["open","won","lost","spam"],signatureMode:"highlevel_ed25519_or_legacy_rsa",freshnessSlaSeconds:86400,supportsReconciliation:!1},generic_crm:{expectedEventTypes:["crm_stage_changed","transaction","custom_server"],expectedEventPhases:["open","qualified","won","lost","spam"],signatureMode:"xray_hmac_sha256_timestamp_and_exact_body",freshnessSlaSeconds:86400,supportsReconciliation:!1}},Os=/(^|_)(api_?key|access_?key|api_?token|auth_?token|token|password|private_?key|secret|signing_?key)($|_)/i;function Ds(e){let t=(n,i)=>{if(!(!n||typeof n!="object"))for(let[r,s]of Object.entries(n)){let a=i?`${i}.${r}`:r,o=r.replace(/([a-z0-9])([A-Z])/g,"$1_$2").toLowerCase();if(Os.test(o)&&!/public_?key/i.test(o))throw new f("analytics_connection_config_contains_credential",`Connection config cannot contain credentials (${a}). Use the encrypted webhook secret or service connection instead.`,400);t(s,a)}};t(e,"")}function cd(e){return e instanceof Error&&e.message==="webhook_secret_unavailable"?"webhook_secret_unavailable":e instanceof f?e.code.slice(0,80):"invalid_webhook_payload"}function qt(e){let t=e.provider,n=Rs[t];if(!n)throw new f("analytics_connection_provider_unavailable","This analytics connection is not available in the current public release.",404);return{callbackPath:`/analytics/v1/webhooks/${t}/${e.connectionId}`,expectedEventTypes:[...n.expectedEventTypes],expectedEventPhases:[...n.expectedEventPhases],signatureMode:n.signatureMode,freshnessSlaSeconds:n.freshnessSlaSeconds,reconciliationCapability:n.supportsReconciliation?e.serviceConnectionRef?"available":"connection_required":"webhook_only"}}function dd(e,t){let n=String(e.id),i=String(e.provider),r=qt({provider:i,connectionId:n,serviceConnectionRef:typeof e.service_connection_ref=="string"?e.service_connection_ref:null}),s=String(e.readiness??"configured_unverified"),a=e.verification_evidence==="provider"||e.verification_evidence==="synthetic"?e.verification_evidence:null,o=!!e.webhook_secret_configured,c=e.last_event_at?String(e.last_event_at):null,l=s==="live"&&c?Date.now()-Date.parse(c)>r.freshnessSlaSeconds*1e3:!1;return{...e,callbackUrl:new URL(r.callbackPath,`${t.replace(/\/$/,"")}/`).toString(),expectedEventTypes:r.expectedEventTypes,expectedEventPhases:r.expectedEventPhases,signatureMode:r.signatureMode,freshnessSlaSeconds:r.freshnessSlaSeconds,freshness:l?"stale":c?"fresh":"awaiting_first_event",reconciliationCapability:r.reconciliationCapability,lastVerifiedReceipt:e.last_verified_receipt_id?{receiptId:String(e.last_verified_receipt_id),evidence:a,verifiedAt:e.last_verified_at?String(e.last_verified_at):null}:a?{receiptId:null,evidence:a,verifiedAt:e.last_verified_at?String(e.last_verified_at):null}:null,safeError:e.last_error_code?{code:String(e.last_error_code)}:null,setupChecklist:{callbackUrlGenerated:!0,callbackConfigured:a==="provider",signatureConfigured:i==="highlevel"||o,syntheticTestPassed:a==="synthetic"||a==="provider",providerReceiptReceived:a==="provider"},evidenceStillMissing:a==="provider"?[]:["Send a real provider-signed event to the callback URL to mark this connection live."]}}async function ld(e){let t=Ct.parse(e.definition),n=v();await Q(n,e.siteId,e.userId);let i=t.enabled?"active":"disabled";return(await n.query(`INSERT INTO analytics_event_definitions(
727
- id, site_id, name, event_name, trigger_kind, definition, status, created_by_user_id
728
- ) VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7,$8)
729
- ON CONFLICT(site_id, name) DO UPDATE SET event_name=EXCLUDED.event_name,
730
- trigger_kind=EXCLUDED.trigger_kind, definition=EXCLUDED.definition,
731
- status=EXCLUDED.status, version=analytics_event_definitions.version+1, updated_at=now()
732
- RETURNING id, name, event_name, trigger_kind, definition, version, status, created_at, updated_at`,[de(),e.siteId,t.name,t.eventName,t.triggerKind,JSON.stringify(t),i,e.userId])).rows[0]}async function ud(e,t){let n=v();return await B(n,e,t),(await n.query(`SELECT id, name, event_name, trigger_kind, definition, version, status, created_at, updated_at
733
- FROM analytics_event_definitions WHERE site_id=$1 AND status <> 'archived'
734
- ORDER BY created_at DESC`,[e])).rows}async function Ht(e){let t=v();e.requireEditor?await Q(t,e.siteId,e.userId):await B(t,e.siteId,e.userId);let i=(await t.query(`SELECT id, definition, version, status FROM analytics_event_definitions
735
- WHERE id=$1 AND site_id=$2`,[e.definitionId,e.siteId])).rows[0];if(!i||i.status==="archived")throw new f("analytics_event_definition_not_found","Event definition not found.",404);return{db:t,row:i}}async function _d(e){let{db:t,row:n}=await Ht({...e,requireEditor:!0}),i=Ct.parse({...n.definition,...e.changes}),r=i.enabled?"active":"disabled";return(await t.query(`UPDATE analytics_event_definitions SET
736
- name=$3, event_name=$4, trigger_kind=$5, definition=$6::jsonb,
737
- status=$7, version=version+1, updated_at=now()
738
- WHERE id=$1 AND site_id=$2
739
- RETURNING id, name, event_name, trigger_kind, definition, version, status, created_at, updated_at`,[e.definitionId,e.siteId,i.name,i.eventName,i.triggerKind,JSON.stringify(i),r])).rows[0]}async function md(e){let t=v();await Q(t,e.siteId,e.userId);let n=await t.query(`UPDATE analytics_event_definitions SET status='archived', updated_at=now()
740
- WHERE id=$1 AND site_id=$2
741
- RETURNING id, status`,[e.definitionId,e.siteId]);if(!n.rows[0])throw new f("analytics_event_definition_not_found","Event definition not found.",404);return n.rows[0]}function Ts(e,t,n){let i=t.hostnames.length===0||t.hostnames.includes(n.hostname),r=t.triggerKind===n.triggerKind,s=t.triggerKind==="page"?t.pathMatch==="prefix"?n.path.startsWith(t.path??"/"):n.path===t.path:null,a=t.triggerKind==="semantic"?n.tagEventName===t.eventName:null,o=t.triggerKind==="click"||t.triggerKind==="form"?n.selectorMatched===!0:null;return{definitionId:e,eventName:t.eventName,triggerKind:t.triggerKind,matched:t.enabled&&i&&r&&(s??a??o??!1),checks:{hostname:i,triggerKind:r,path:s,tag:a,selector:o}}}async function pd(e){let{row:t}=await Ht({...e,requireEditor:!1});return Ts(e.definitionId,t.definition,e.preview)}async function yd(e){let{db:t,row:n}=await Ht({...e,requireEditor:!1}),i=Math.max(0,Math.min(15e3,Math.trunc(e.timeoutMs??0))),r=Math.max(1,Math.min(86400,Math.trunc(e.maxAgeSeconds??300))),s=Date.now()+i;do{let o=(await t.query(`SELECT event_id, received_at
742
- FROM analytics_events
743
- WHERE site_id=$1 AND event_definition_id=$2 AND event_definition_version=$3
744
- AND received_at >= now() - ($4 * interval '1 second')
745
- ORDER BY received_at DESC, id DESC
746
- LIMIT 1`,[e.siteId,e.definitionId,n.version,r])).rows[0];if(o)return{status:"observed_live_match",definitionId:e.definitionId,definitionVersion:n.version,eventId:o.event_id,observedAt:new Date(o.received_at).toISOString(),maxAgeSeconds:r};let c=s-Date.now();if(c<=0)break;await new Promise(l=>setTimeout(l,Math.min(500,c)))}while(Date.now()<=s);return{status:"not_observed",definitionId:e.definitionId,definitionVersion:n.version,eventId:null,observedAt:null,maxAgeSeconds:r}}async function gd(e){let t=await v().query(`SELECT s.owner_user_id, s.id AS site_id, d.id, d.version, d.definition
747
- FROM analytics_pixels p JOIN analytics_sites s ON s.id=p.site_id
748
- LEFT JOIN analytics_event_definitions d ON d.site_id=s.id AND d.status='active'
749
- WHERE p.public_id=$1 AND p.status='active'`,[e]);return{ownerUserId:t.rows[0]?Number(t.rows[0].owner_user_id):null,siteId:t.rows[0]?.site_id??null,definitions:t.rows.filter(n=>n.id&&n.version&&n.definition).map(n=>({...n.definition,id:n.id,version:n.version}))}}async function fd(e){Ds(e.config??{});let t=v();await Q(t,e.siteId,e.userId);let n=e.webhookSecret?ue(e.webhookSecret,`connection-secret:${e.provider}:${e.sourceAccountRef}`):null;return(await t.query(`INSERT INTO analytics_connections(
750
- id, site_id, provider, name, source_account_ref, service_connection_ref,
751
- secret_ciphertext, config, created_by_user_id
752
- ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8::jsonb,$9)
753
- ON CONFLICT(site_id, provider, source_account_ref) DO UPDATE SET
754
- name = EXCLUDED.name,
755
- service_connection_ref = COALESCE(EXCLUDED.service_connection_ref, analytics_connections.service_connection_ref),
756
- secret_ciphertext = COALESCE(EXCLUDED.secret_ciphertext, analytics_connections.secret_ciphertext),
757
- config = EXCLUDED.config,
758
- readiness = CASE WHEN analytics_connections.readiness = 'live' THEN 'live' ELSE 'configured_unverified' END,
759
- updated_at = now()
760
- RETURNING id, site_id, provider, name, source_account_ref, service_connection_ref,
761
- readiness, last_verified_at, last_event_at, last_reconciled_at, last_error_code,
762
- verification_evidence, last_verified_receipt_id,
763
- (secret_ciphertext IS NOT NULL) AS webhook_secret_configured,
764
- created_at, updated_at`,[de(),e.siteId,e.provider.trim().toLowerCase().slice(0,80),e.name.trim().slice(0,120),e.sourceAccountRef.trim().slice(0,240),e.serviceConnectionRef?.trim().slice(0,240)||null,n,JSON.stringify(e.config??{}),e.userId])).rows[0]}async function vd(e,t){let n=v();return await B(n,e,t),(await n.query(`SELECT id, site_id, provider, name, source_account_ref, service_connection_ref,
765
- readiness, last_verified_at, last_event_at, last_reconciled_at,
766
- last_error_code, verification_evidence, last_verified_receipt_id,
767
- created_at, updated_at,
768
- (secret_ciphertext IS NOT NULL) AS webhook_secret_configured
769
- FROM analytics_connections WHERE site_id = $1 ORDER BY created_at`,[e])).rows}async function Ed(e){let n=(await v().query(`SELECT id, site_id, provider, source_account_ref, secret_ciphertext,
770
- readiness, config
771
- FROM analytics_connections WHERE id = $1 AND readiness <> 'disabled'`,[e])).rows[0];if(!n)return null;let i=`connection-secret:${n.provider}:${n.source_account_ref}`;return{id:n.id,siteId:n.site_id,provider:n.provider,sourceAccountRef:n.source_account_ref,secret:n.secret_ciphertext?Oe(n.secret_ciphertext,i):null,readiness:n.readiness,config:n.config||{}}}async function Ii(e){let t=v();await Q(t,e.siteId,e.userId);let i=(await t.query(`SELECT id, site_id, provider, source_account_ref, service_connection_ref,
772
- secret_ciphertext, readiness, config
773
- FROM analytics_connections
774
- WHERE id=$1 AND site_id=$2 AND readiness <> 'disabled'`,[e.connectionId,e.siteId])).rows[0];if(!i)throw new f("analytics_connection_not_found","Analytics connection not found.",404);return i}function xs(e){return e.secret_ciphertext?Oe(e.secret_ciphertext,`connection-secret:${e.provider}:${e.source_account_ref}`):null}function Ls(e){let t=e.now??Date.now(),n=new Date(t).toISOString(),i=e.secret||"xray-ephemeral-synthetic-fixture-secret";if(e.provider==="callrail"){let c={resource_id:"xray-test-call",timestamp:n,answered:!0,duration:30},l=JSON.stringify(c);return{signatureVerified:ci({rawBody:l,signature:e.tamperSignature?"invalid":jt("sha1",i).update(l).digest("base64"),secret:i,payloadTimestamp:n,now:t}),event:di({payload:c,sourceAccountRef:e.sourceAccountRef,phase:"post_call"})}}if(e.provider==="calltrackingmetrics"){let c={activity_id:"xray-test-call",timestamp:n,status:"completed"},l=JSON.stringify(c),_=String(Math.floor(t/1e3));return{signatureVerified:li({rawBody:l,requestTime:_,signature:e.tamperSignature?"invalid":jt("sha1",i).update(_+l).digest("base64"),secret:i,now:t}),event:ui({payload:c,sourceAccountRef:e.sourceAccountRef,phase:"call_complete"})}}if(e.provider==="twilio"){let c={CallSid:"CA_XRAY_TEST",CallStatus:"completed",Timestamp:n},l=e.tamperSignature?"invalid":Ns.getExpectedTwilioSignature(i,e.callbackUrl,c);return{signatureVerified:_i({authToken:i,signature:l,url:e.callbackUrl,params:c}),event:mi({params:c,sourceAccountRef:e.sourceAccountRef})}}if(e.provider==="hubspot"){let c={subscriptionType:"deal.propertyChange",propertyName:"dealstage",objectId:"xray-test-deal",propertyValue:"qualified",occurredAt:String(t)},l=JSON.stringify([c]),_=String(t),d=jt("sha256",i).update(`POST${e.callbackUrl}${l}${_}`).digest("base64"),p=e.tamperSignature?"invalid":d;return{signatureVerified:yi({clientSecret:i,signature:p,timestamp:_,method:"POST",url:e.callbackUrl,rawBody:l,now:t}),event:Ft(fi({item:c,sourceAccountRef:e.sourceAccountRef}))}}if(e.provider==="highlevel"){let c={timestamp:n,opportunity:{id:"xray-test-opportunity",pipelineStageId:"qualified",status:"open"}},l=JSON.stringify(c),{privateKey:_,publicKey:d}=Ss("ed25519"),p=e.tamperSignature?Buffer.alloc(64).toString("base64"):hs(null,Buffer.from(l),_).toString("base64");return{signatureVerified:gi({rawBody:l,ghlSignature:p,ed25519PublicKey:d.export({type:"spki",format:"pem"}).toString()}),event:Ft(vi({payload:c,sourceAccountRef:e.sourceAccountRef}))}}let r=Qe.parse({schemaVersion:1,eventId:"generic_xray_test",sourceEventId:"xray-test-event",source:"generic_crm",sourceAccountRef:e.sourceAccountRef,eventKind:"crm",eventName:"crm_stage_changed",occurredAt:n,dealId:"xray-test-deal",properties:{stageId:"qualified",status:"open"}}),s=JSON.stringify(r),a=String(t),o=ln(e.provider,a,s,i);return{signatureVerified:un(e.tamperSignature?`${o.startsWith("0")?"1":"0"}${o.slice(1)}`:o,o),event:r}}async function bd(e){let t=await Ii(e),n=xs(t);if(t.provider!=="highlevel"&&!n)throw new f("analytics_connection_secret_unavailable","Configure the provider webhook validation secret before running a connection test.",409);let i=qt({provider:t.provider,connectionId:t.id,serviceConnectionRef:t.service_connection_ref}),r=new URL(i.callbackPath,`${e.requestOrigin.replace(/\/$/,"")}/`).toString(),s=Ls({provider:t.provider,sourceAccountRef:t.source_account_ref,callbackUrl:r,secret:n});if(!s.signatureVerified)throw await Ei({connectionId:t.id,readiness:"degraded",errorCode:"synthetic_signature_test_failed"}),new f("analytics_connection_test_failed","Connection signature test failed.",409);let a=await Ei({connectionId:t.id,readiness:"verified",evidence:"synthetic"}),o=a?.verification_evidence??"synthetic";return{status:a?.readiness??"verified",evidence:o,provider:t.provider,eventType:s.event.eventKind,eventPhase:s.event.eventName,connection:a,evidenceStillMissing:o==="provider"?[]:["A real provider-signed receipt is required before this connection becomes live."]}}async function Id(e){let t=await Ii(e),n=qt({provider:t.provider,connectionId:t.id,serviceConnectionRef:t.service_connection_ref});return{capability:n.reconciliationCapability,started:!1,evidence:null,nextAction:n.reconciliationCapability==="available"?"Reconciliation is available; the reconciliation worker must claim and execute this request.":n.reconciliationCapability==="connection_required"?"Connect the provider read API before reconciliation can run.":"This provider is webhook only; use webhook retries or historical backfill."}}async function Ad(e){let t=de(),n=e.rawBody?ue(e.rawBody,"webhook-payload"):null,i=await v().query(`INSERT INTO analytics_webhook_receipts(
775
- id, site_id, connection_id, provider, source_account_ref, source_event_id,
776
- signature_status, payload_ciphertext, payload_expires_at
777
- ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,now() + interval '7 days')
778
- ON CONFLICT(site_id, provider, source_account_ref, source_event_id) DO UPDATE SET
779
- normalization_status = CASE
780
- WHEN analytics_webhook_receipts.normalization_status = 'accepted' THEN 'replayed'
781
- ELSE analytics_webhook_receipts.normalization_status
782
- END
783
- RETURNING id, normalization_status`,[t,e.siteId,e.connectionId,e.provider,e.sourceAccountRef,e.sourceEventId,e.signatureStatus,n]);return{id:i.rows[0].id,duplicate:i.rows[0].id!==t,status:i.rows[0].normalization_status}}async function Sd(e){await v().query(`UPDATE analytics_webhook_receipts SET
784
- normalization_status = $2,
785
- error_code = $3,
786
- normalized_at = now()
787
- WHERE id = $1`,[e.receiptId,e.accepted?"accepted":"rejected",e.errorCode??null])}async function Ei(e){return(await v().query(`UPDATE analytics_connections SET
788
- readiness = CASE WHEN readiness='live' AND $2='verified' THEN 'live' ELSE $2 END,
789
- last_error_code = $3,
790
- last_verified_at = CASE WHEN $2 IN ('verified','live') THEN now() ELSE last_verified_at END,
791
- last_event_at = CASE WHEN $4 THEN now() ELSE last_event_at END,
792
- last_reconciled_at = CASE WHEN $5 THEN now() ELSE last_reconciled_at END,
793
- verification_evidence = CASE
794
- WHEN verification_evidence='provider' THEN 'provider'
795
- ELSE COALESCE($6, verification_evidence)
796
- END,
797
- last_verified_receipt_id = COALESCE($7::uuid, last_verified_receipt_id),
798
- updated_at = now()
799
- WHERE id = $1
800
- RETURNING id, readiness, last_verified_at, last_event_at, last_reconciled_at,
801
- last_error_code, verification_evidence, last_verified_receipt_id`,[e.connectionId,e.readiness,e.errorCode??null,!!e.receipt,!!e.reconciled,e.evidence??null,e.receiptId??null])).rows[0]??null}async function hd(e){let t=Ln.parse(e.rule),n=v();return await Q(n,e.siteId,e.userId),(await n.query(`INSERT INTO analytics_conversion_rules(
802
- id, site_id, name, conversion_kind, condition, default_value_minor,
803
- default_currency, status, created_by_user_id
804
- ) VALUES ($1,$2,$3,$4,$5::jsonb,$6,$7,$8,$9)
805
- ON CONFLICT(site_id, name) DO UPDATE SET
806
- conversion_kind = EXCLUDED.conversion_kind,
807
- condition = EXCLUDED.condition,
808
- default_value_minor = EXCLUDED.default_value_minor,
809
- default_currency = EXCLUDED.default_currency,
810
- status = EXCLUDED.status,
811
- version = analytics_conversion_rules.version + 1,
812
- updated_at = now()
813
- RETURNING *`,[de(),e.siteId,t.name,t.conversionKind,JSON.stringify({...t.condition,...t.valueRule?{valueRule:t.valueRule}:{}}),t.defaultValueMinor??null,t.defaultCurrency.toUpperCase(),t.enabled?"active":"disabled",e.userId])).rows[0]}async function Nd(e,t){let n=v();return await B(n,e,t),(await n.query(`SELECT id, name, conversion_kind, condition, default_value_minor,
814
- default_currency, version, status, created_at, updated_at
815
- FROM analytics_conversion_rules WHERE site_id = $1 AND status <> 'archived'
816
- ORDER BY created_at`,[e])).rows.map(r=>{let s=r.condition&&typeof r.condition=="object"?r.condition:{},{valueRule:a,...o}=s;return{...r,condition:o,value_rule:a??null}})}function $s(e){let t=e.properties.tags;return Array.isArray(t)?t.filter(n=>typeof n=="string"):[]}function ks(e,t){let n=[],i=e.condition;i.eventName&&i.eventName!==t.eventName&&n.push("event_name_mismatch"),i.eventKind&&i.eventKind!==t.eventKind&&n.push("event_kind_mismatch"),i.source&&i.source!==t.source&&n.push("source_mismatch"),i.stageId&&i.stageId!==t.properties.stageId&&n.push("stage_mismatch"),i.status&&i.status!==t.properties.status&&n.push("status_mismatch"),i.answered!==void 0&&i.answered!==!!t.properties.answered&&n.push("answered_mismatch"),i.minimumCallDurationSeconds!==void 0&&Number(t.properties.durationSeconds??0)<i.minimumCallDurationSeconds&&n.push("call_too_short");let r=$s(t);return i.excludedTags.some(s=>r.includes(s))&&n.push("excluded_tag"),i.requiredTags.some(s=>!r.includes(s))&&n.push("required_tag_missing"),(t.properties.status==="spam"||r.includes("spam"))&&n.push("spam_excluded"),{matches:n.length===0,reasons:n}}async function Ps(e,t,n){if(!bi(n).enhancedMatching)return{personId:null,namespaceId:null,identityTier:"unlinked"};let s=(await e.query(`SELECT id FROM analytics_identity_namespaces
817
- WHERE site_id=$1 AND status='approved' ORDER BY version DESC LIMIT 1`,[t])).rows[0]?.id??null,a=[];n.visitorId&&a.push(["visitor_id",n.visitorId]),n.sessionId&&a.push(["session_id",n.sessionId]),n.personRef&&a.push(["customer_id",n.personRef]);for(let[o,c]of Object.entries(U(n.clickIds)))a.push(["click_id",`${o}:${c}`]);for(let[o,c]of a){let l=await e.query(`SELECT e.person_id FROM analytics_identity_nodes n
818
- JOIN analytics_identity_edges e ON e.identity_node_id = n.id
819
- WHERE n.site_id = $1 AND n.kind = $2 AND n.value_hmac = $3
820
- AND ($4::uuid IS NULL OR (n.namespace_id=$4 AND e.namespace_id=$4))
821
- ORDER BY e.confidence DESC, e.last_seen_at DESC LIMIT 1`,[t,o,$(o,c),s]);if(l.rows[0])return{personId:l.rows[0].person_id,namespaceId:s,identityTier:"confirmed"}}return{personId:null,namespaceId:s,identityTier:"unlinked"}}async function Ms(e,t){let i=(await e.query(`SELECT m.person_id::text, m.namespace_id::text,
822
- c.provider AS connection_provider,
823
- s.latest_receipt_id::text AS receipt_id, s.policy_version,
824
- s.site_analytics, s.enhanced_matching, s.advertising_measurement,
825
- s.gpc, r.action, s.effective_at, s.expires_at
826
- FROM analytics_crm_person_mappings m
827
- JOIN analytics_connections c ON c.id=m.connection_id AND c.site_id=m.site_id
828
- JOIN analytics_people p ON p.id=m.person_id AND p.site_id=m.site_id AND p.namespace_id=m.namespace_id
829
- JOIN analytics_identity_namespaces n ON n.id=m.namespace_id AND n.site_id=m.site_id AND n.status='approved'
830
- LEFT JOIN analytics_consent_current_state s
831
- ON s.site_id=m.site_id AND s.subject_ref=p.public_ref
832
- LEFT JOIN analytics_consent_receipts r
833
- ON r.id=s.latest_receipt_id AND r.site_id=s.site_id
834
- WHERE m.site_id=$1 AND m.connection_id::text=$2
835
- AND m.provider_person_ref=$3 AND m.person_id::text=$4
836
- LIMIT 1`,[t.siteId,t.context.connectionId,t.context.providerContactId,t.context.expectedPersonId])).rows[0];if(!i)return{personId:null,namespaceId:null,consent:void 0,reason:"mapping_unavailable"};let r,s;try{r=se(t.context.provider),s=se(i.connection_provider)}catch{return{personId:null,namespaceId:null,consent:void 0,reason:"provider_mismatch"}}if(r!==s)return{personId:null,namespaceId:null,consent:void 0,reason:"provider_mismatch"};if(!i.receipt_id||!i.effective_at)return{personId:null,namespaceId:null,consent:void 0,reason:"consent_missing"};if(i.action==="withdraw")return{personId:null,namespaceId:null,consent:void 0,reason:"consent_withdrawn"};if(i.gpc||i.action==="gpc")return{personId:null,namespaceId:null,consent:void 0,reason:"consent_gpc"};let a=new Date(t.at??Date.now()).getTime();if(i.expires_at&&new Date(i.expires_at).getTime()<=a)return{personId:null,namespaceId:null,consent:void 0,reason:"consent_expired"};if(!i.enhanced_matching)return{personId:null,namespaceId:null,consent:void 0,reason:"enhanced_matching_denied"};if(!i.advertising_measurement)return{personId:null,namespaceId:null,consent:void 0,reason:"advertising_measurement_denied"};let o=new Date(i.effective_at).toISOString();return{personId:i.person_id,namespaceId:i.namespace_id,reason:"eligible",consent:{analytics:i.site_analytics?"granted":"denied",adUserData:"granted",adPersonalization:"granted",globalPrivacyControl:!1,occurredAt:o,receiptId:i.receipt_id,policyVersion:i.policy_version??void 0,purposes:{site_analytics:i.site_analytics?"granted":"denied",enhanced_matching:"granted",advertising_measurement:"granted"}}}}async function Us(e,t){let n=U(t.eventClickIds),i=await e.query(`SELECT click_ids FROM analytics_attribution_touches
837
- WHERE site_id=$1 AND person_id=$2
838
- AND occurred_at <= $3::timestamptz
839
- AND occurred_at >= $3::timestamptz - ($4::text || ' days')::interval
840
- AND click_ids <> '{}'::jsonb
841
- ORDER BY occurred_at DESC, id DESC`,[t.siteId,t.personId,t.occurredAt,Cs]),r={};for(let s of i.rows)for(let[a,o]of Object.entries(U(s.click_ids)))a in r||(r[a]=o);return{...r,...n}}function Fs(e,t){let n=e.valueMinor??Number(t.default_value_minor??0),i=e.valueMinor!==void 0?"dynamic":(t.default_value_minor!==null,"fixed"),r=e.valueMinor!==void 0?"source_event":null,s={mode:e.valueMinor!==void 0?"dynamic":"fixed",source:r,observedAt:e.valueMinor!==void 0?e.occurredAt:null,fallback:!1,ruleVersion:String(t.version),resolution:i,baseValueMinor:n,resolvedValueMinor:n,currency:(e.currency||t.default_currency||"USD").toUpperCase(),linkedEvent:null};return{valueMinor:n,currency:s.currency,provenance:s}}async function js(e,t){if(!t.rule.value_rule)return Fs(t.event,t.rule);let n=t.rule.value_rule.conditionalOverride?.linkedEventName,i=n&&t.personId?(await e.query(`SELECT id, event_name, occurred_at FROM analytics_external_events
842
- WHERE site_id=$1 AND person_id=$2 AND event_name=$3 AND id<>$4
843
- ORDER BY occurred_at DESC LIMIT 100`,[t.siteId,t.personId,n,t.insertedEventId])).rows.map(s=>({eventId:s.id,eventName:s.event_name,occurredAt:s.occurred_at.toISOString(),identityTier:"confirmed"})):[],r=t.event.valueMinor===void 0?void 0:{source_event:{valueMinor:t.event.valueMinor,currency:(t.event.currency||t.rule.default_currency||"USD").toUpperCase(),observedAt:t.event.occurredAt},crm_field:t.event.source.includes("crm")?{valueMinor:t.event.valueMinor,currency:(t.event.currency||t.rule.default_currency||"USD").toUpperCase(),observedAt:t.event.occurredAt}:void 0,transaction:t.event.eventKind==="transaction"?{valueMinor:t.event.valueMinor,currency:(t.event.currency||t.rule.default_currency||"USD").toUpperCase(),observedAt:t.event.occurredAt}:void 0};return Dn(t.rule.value_rule,{targetEvent:{eventId:t.insertedEventId,eventName:t.event.eventName,occurredAt:t.event.occurredAt,identityTier:t.personId?"confirmed":"candidate"},linkedEvents:i,observations:r,observedAt:t.event.occurredAt,providerOperation:"new_conversion"})}async function qs(e){if(!e.namespaceId||!e.personId)return[];let t=await e.client.query(`SELECT id, name, score_kind, rules, minimum_score, maximum_score, version
844
- FROM analytics_score_definitions WHERE site_id=$1 AND status='active'`,[e.siteId]);if(!t.rowCount)return[];let n=await e.client.query(`SELECT id, event_name, event_kind, occurred_at, source, properties
845
- FROM analytics_external_events WHERE site_id=$1 AND person_id=$2
846
- ORDER BY occurred_at DESC LIMIT 5000`,[e.siteId,e.personId]),i={events:n.rows.map(s=>({ref:s.id,name:s.event_name,occurredAt:s.occurred_at.toISOString()})),pages:n.rows.filter(s=>typeof s.properties.path=="string").map(s=>({ref:s.id,name:String(s.properties.path),occurredAt:s.occurred_at.toISOString()})),sources:n.rows.map(s=>({ref:s.id,name:s.source,occurredAt:s.occurred_at.toISOString()})),calls:n.rows.filter(s=>s.event_kind==="call").map(s=>({ref:s.id,name:String(s.properties.outcome??s.properties.status??s.event_name),occurredAt:s.occurred_at.toISOString()})),crmStages:n.rows.filter(s=>s.event_kind==="crm").map(s=>({ref:s.id,name:String(s.properties.stageId??s.properties.stageName??s.event_name),occurredAt:s.occurred_at.toISOString()}))},r=[];for(let s of t.rows){let a=s.rules&&typeof s.rules=="object"&&!Array.isArray(s.rules)?s.rules:{},o=Array.isArray(s.rules)?s.rules:a.groups;if(!Array.isArray(o)||o.length===0)continue;let c=Array.isArray(a.permittedProperties)?a.permittedProperties.filter(l=>typeof l=="string"):[];try{let l=si();ai({tables:l,id:s.id,definition:{namespaceId:e.namespaceId,name:s.name,kind:s.score_kind,targetObject:"person",minimum:s.minimum_score,maximum:s.maximum_score,groups:o,permittedProperties:c,enabled:!0},now:e.occurredAt});let _=oi({tables:l,namespaceId:e.namespaceId,scoreId:s.id,subjectRef:e.personId,identityTier:"confirmed",facts:i,now:e.occurredAt});await e.client.query(`INSERT INTO analytics_score_evaluations(
847
- id, site_id, namespace_id, definition_id, definition_version,
848
- person_id, score, contributions, evaluated_at
849
- ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8::jsonb,$9)`,[_.id,e.siteId,e.namespaceId,s.id,s.version,e.personId,_.value,JSON.stringify(_.components),_.evaluatedAt]),r.push(_.id)}catch{}}return r}async function Hs(e){return e.namespaceId?(await e.client.query(`SELECT t.id, d.event_name, t.minimum_confirmed_people, t.minimum_positive_outcomes,
850
- (SELECT count(DISTINCT p.id)::text FROM analytics_people p
851
- WHERE p.site_id=t.site_id AND p.namespace_id=t.namespace_id) AS confirmed_people,
852
- (SELECT count(DISTINCT e.person_id)::text FROM analytics_external_events e
853
- JOIN analytics_people p ON p.id=e.person_id AND p.site_id=e.site_id
854
- WHERE e.site_id=t.site_id AND p.namespace_id=t.namespace_id AND e.event_name=d.event_name) AS positive_outcomes,
855
- EXISTS(SELECT 1 FROM analytics_prediction_manifests m
856
- WHERE m.target_id=t.id AND m.status IN ('validated','active')) AS validated_manifest
857
- FROM analytics_prediction_targets t
858
- JOIN analytics_event_definitions d ON d.id=t.target_event_definition_id
859
- WHERE t.site_id=$1 AND t.namespace_id=$2 AND t.status<>'disabled'`,[e.siteId,e.namespaceId])).rows.map(n=>ws({targetId:n.id,confirmedPeople:Number(n.confirmed_people),positiveOutcomes:Number(n.positive_outcomes),minimumConfirmedPeople:n.minimum_confirmed_people,minimumPositiveOutcomes:n.minimum_positive_outcomes,validatedManifest:n.validated_manifest})):[]}async function Ai(e){let t=Qe.parse(e.event),n=e.db??v();e.userId!==void 0&&await Q(n,e.siteId,e.userId);let i=!e.transactionClient,r=e.transactionClient??await n.connect();try{i&&await r.query("BEGIN");let s=e.trustedCrmContext?await Ms(r,{siteId:e.siteId,context:e.trustedCrmContext}):null,a=e.trustedCrmContext?Qe.parse({...t,personRef:void 0,consent:s?.consent,properties:{...t.properties,_xrayCrmLinkReason:s?.reason??"mapping_unavailable"}}):t,o=bi(a),c=s?.personId&&s.namespaceId?{personId:s.personId,namespaceId:s.namespaceId,identityTier:"confirmed"}:e.trustedCrmContext?{personId:null,namespaceId:null,identityTier:"unlinked"}:await Ps(r,e.siteId,a),l=c.personId,_=await r.query(`INSERT INTO analytics_external_events(
860
- id, site_id, receipt_id, person_id, source, source_account_ref,
861
- source_event_id, event_kind, event_name, occurred_at, visitor_hmac,
862
- session_hmac, call_id, deal_id, order_id, value_minor, currency,
863
- click_ids, properties
864
- ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18::jsonb,$19::jsonb)
865
- ON CONFLICT(site_id, source, source_account_ref, source_event_id) DO NOTHING
866
- RETURNING id`,[de(),e.siteId,e.receiptId??null,l,a.source,a.sourceAccountRef,a.sourceEventId,a.eventKind,a.eventName,a.occurredAt,a.visitorId?yt("visitor_id",a.visitorId):null,a.sessionId?yt("session_id",a.sessionId):null,a.callId??null,a.dealId??null,a.orderId??null,a.valueMinor??null,a.currency?.toUpperCase()??null,JSON.stringify(U(a.clickIds)),JSON.stringify({...a.properties,_xrayConsentEligibility:o,_xrayIdentityTier:c.identityTier,...c.namespaceId?{_xrayNamespaceId:c.namespaceId}:{}})]),d=_.rows[0]?.id??(await r.query(`SELECT id FROM analytics_external_events
867
- WHERE site_id=$1 AND source=$2 AND source_account_ref=$3 AND source_event_id=$4`,[e.siteId,a.source,a.sourceAccountRef,a.sourceEventId])).rows[0]?.id;if(!d)throw new f("analytics_external_event_conflict","The canonical event could not be resolved.",409);let E=(await r.query(`SELECT id, name, conversion_kind, condition, default_value_minor,
868
- default_currency, version
869
- FROM analytics_conversion_rules WHERE site_id = $1 AND status = 'active'`,[e.siteId])).rows.filter(A=>ks({name:A.name,conversionKind:A.conversion_kind,condition:(({valueRule:W,...Me})=>Me)(A.condition),defaultValueMinor:A.default_value_minor?Number(A.default_value_minor):void 0,defaultCurrency:A.default_currency,valueRule:A.condition.valueRule,enabled:!0},a).matches),b=[],k={};for(let A of E){let W=await js(r,{siteId:e.siteId,personId:l,insertedEventId:d,event:a,rule:{value_rule:A.condition.valueRule,default_value_minor:A.default_value_minor,default_currency:A.default_currency,version:A.version}}),Me=W.valueMinor??0,Vt=(W.currency||A.default_currency||"USD").toUpperCase(),Li=W.provenance.resolution==="absent"?"absent":W.provenance.resolution==="fallback"?"dynamic":W.provenance.resolution,$i=de(),we=await r.query(`INSERT INTO analytics_conversions(
870
- id, source_event_id, site_id, session_id, conversion_kind,
871
- value_minor, currency, occurred_at, person_id, rule_id, rule_version, order_id,
872
- value_source, value_provenance, session_hmac
873
- ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14::jsonb,$15)
874
- ON CONFLICT(site_id, source_event_id) DO NOTHING RETURNING id`,[$i,`rule:${A.id}:${a.source}:${a.sourceEventId}`,e.siteId,a.sessionId??null,A.conversion_kind,Me,Vt,a.occurredAt,l,A.id,A.version,a.orderId??null,Li,JSON.stringify(W.provenance),a.sessionId?$("session_id",a.sessionId):null]);if(!we.rowCount||(b.push(we.rows[0].id),k[we.rows[0].id]=W.provenance,!l||!o.advertisingMeasurement))continue;let ki=await r.query(`SELECT id FROM analytics_activation_destinations
875
- WHERE site_id = $1 AND status = 'active' AND readiness IN ('verified','live')`,[e.siteId]),Pi=await Us(r,{siteId:e.siteId,personId:l,occurredAt:a.occurredAt,eventClickIds:a.clickIds}),Mi=ue(JSON.stringify({eventId:we.rows[0].id,eventName:A.conversion_kind,eventTime:a.occurredAt,valueMinor:Me,currency:Vt,valueProvenance:W.provenance,attributionModelProvenance:{model:typeof a.properties.attributionModel=="string"?a.properties.attributionModel:"unassigned",evidenceTier:"confirmed"},orderId:a.orderId,clickIds:Pi,source:a.source,personId:l,identityTier:"confirmed",namespaceId:c.namespaceId,consent:{receiptId:o.receiptId,advertisingMeasurement:!0}}),"activation-payload");for(let Ui of ki.rows)await r.query(`INSERT INTO analytics_activation_jobs(
876
- id, destination_id, conversion_id, person_id, payload_ciphertext
877
- ) VALUES ($1,$2,$3,$4,$5)
878
- ON CONFLICT(destination_id, conversion_id) DO NOTHING`,[de(),Ui.id,we.rows[0].id,l,Mi])}let x=await qs({client:r,siteId:e.siteId,namespaceId:c.namespaceId,personId:l,occurredAt:a.occurredAt}),h=await Hs({client:r,siteId:e.siteId,namespaceId:c.namespaceId});l&&await ni({siteId:e.siteId,personId:l,canonicalEventId:d,event:a,consent:{...o.receiptId?{receiptId:o.receiptId}:{},siteAnalytics:o.siteAnalytics,advertisingMeasurement:o.advertisingMeasurement},db:r}),i&&await r.query("COMMIT");let C=null,m=typeof a.properties.candidateAssociationId=="string"?a.properties.candidateAssociationId:null,X=typeof a.properties.deterministicEvidenceRef=="string"?a.properties.deterministicEvidenceRef:null,D=a.properties.deterministicEvidenceKind;return l&&c.namespaceId&&m&&X&&["verified_email","verified_phone","crm_contact","signed_linker","authenticated_user"].includes(String(D))&&(C=await Un({siteId:e.siteId,namespaceId:c.namespaceId,associationId:m,personId:l,evidenceKind:D,evidenceRef:X,occurredAt:a.occurredAt}).catch(()=>null)),{id:_.rows[0]?.id??null,created:!!_.rowCount,personId:l,identityTier:c.identityTier,namespaceId:c.namespaceId,conversionIds:b,consentEligibility:o,valueProvenance:k,scoreEvaluationIds:x,predictionEvidence:h,candidatePromotion:C}}catch(s){throw i&&await r.query("ROLLBACK"),s}finally{i&&r.release()}}async function Cd(e,t,n){let i=v();if(await B(i,e,t),!(await i.query("SELECT 1 FROM analytics_people WHERE id = $1 AND site_id = $2",[n,e])).rowCount)throw new f("analytics_person_not_found","Attributed person not found.",404);let[s,a,o,c,l]=await Promise.all([i.query(`SELECT id, started_at, last_active_at, ended_at, duration_ms, landing_path,
879
- exit_path, pageview_count, event_count
880
- FROM analytics_sessions WHERE site_id = $1 AND person_id = $2 ORDER BY started_at`,[e,n]),i.query(`SELECT occurred_at, source, medium, campaign, referrer, landing_path
881
- FROM analytics_attribution_touches WHERE site_id = $1 AND person_id = $2 ORDER BY occurred_at`,[e,n]),i.query(`SELECT source, event_kind, event_name, occurred_at, value_minor, currency
882
- FROM analytics_external_events WHERE site_id = $1 AND person_id = $2 ORDER BY occurred_at`,[e,n]),i.query(`SELECT conversion_kind, value_minor, currency, occurred_at, rule_version
883
- FROM analytics_conversions WHERE site_id = $1 AND person_id = $2 ORDER BY occurred_at`,[e,n]),i.query(`SELECT j.status, j.attempts, j.response_category, j.created_at, j.delivered_at,
884
- d.platform, d.name AS destination_name
885
- FROM analytics_activation_jobs j
886
- JOIN analytics_activation_destinations d ON d.id = j.destination_id
887
- WHERE d.site_id = $1 AND j.person_id = $2 ORDER BY j.created_at`,[e,n])]),_=d=>{if(typeof d!="string"||!d)return null;try{let p=new URL(d);return`${p.origin}${p.pathname}`.slice(0,2e3)}catch{return null}};return{personRef:T("person",e,n),sessions:s.rows.map(d=>({sessionRef:T("session",e,String(d.id)),started_at:d.started_at,last_active_at:d.last_active_at,ended_at:d.ended_at,duration_ms:d.duration_ms,landing_path:d.landing_path,exit_path:d.exit_path,pageview_count:d.pageview_count,event_count:d.event_count})),touches:a.rows.map(d=>({occurred_at:d.occurred_at,source:d.source,medium:d.medium,campaign:d.campaign,referrer:_(d.referrer),landing_path:d.landing_path})),externalEvents:o.rows.map(d=>({source:d.source,event_kind:d.event_kind,event_name:d.event_name,occurred_at:d.occurred_at,value_minor:d.value_minor,currency:d.currency})),conversions:c.rows.map(d=>({conversion_kind:d.conversion_kind,value_minor:d.value_minor,currency:d.currency,occurred_at:d.occurred_at,rule_version:d.rule_version})),deliveries:l.rows.map(d=>({status:d.status,attempts:d.attempts,response_category:d.response_category,created_at:d.created_at,delivered_at:d.delivered_at,platform:d.platform,destination_name:d.destination_name}))}}async function wd(e,t){let n=v();await B(n,e,t);let r=(await n.query(`SELECT
888
- count(*)::int AS total_events,
889
- count(*) FILTER (WHERE person_id IS NOT NULL)::int AS identified_events,
890
- count(*) FILTER (WHERE click_ids <> '{}'::jsonb)::int AS events_with_click_id,
891
- count(*) FILTER (WHERE event_kind = 'call')::int AS call_events,
892
- count(*) FILTER (WHERE event_kind = 'call' AND person_id IS NOT NULL)::int AS joined_call_events,
893
- count(*) FILTER (WHERE event_kind = 'crm')::int AS crm_events,
894
- count(*) FILTER (WHERE event_kind = 'crm' AND person_id IS NOT NULL)::int AS joined_crm_events,
895
- count(*) FILTER (WHERE properties->>'status' = 'spam' OR properties->'tags' ? 'spam')::int AS spam_events
896
- FROM analytics_external_events WHERE site_id = $1`,[e])).rows[0],s=(a,o)=>o?a/o:null;return{...r,identifiedRate:s(r.identified_events,r.total_events),clickIdCaptureRate:s(r.events_with_click_id,r.total_events),phoneJoinRate:s(r.joined_call_events,r.call_events),crmStageJoinRate:s(r.joined_crm_events,r.crm_events)}}var Pe="mcp_scraper_subscription_paid";function Ws(e){return/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(e)}async function Bs(e,t){return(await e.query(`SELECT p.id::text AS person_id, p.namespace_id::text AS namespace_id,
897
- cs.latest_receipt_id::text AS receipt_id, cs.policy_version,
898
- cs.site_analytics, cs.enhanced_matching, cs.advertising_measurement,
899
- cs.effective_at
900
- FROM analytics_identity_nodes node
901
- JOIN analytics_identity_edges edge
902
- ON edge.identity_node_id=node.id AND edge.site_id=node.site_id
903
- JOIN analytics_people p
904
- ON p.id=edge.person_id AND p.site_id=node.site_id
905
- JOIN analytics_identity_namespaces ns
906
- ON ns.id=p.namespace_id AND ns.site_id=p.site_id AND ns.status='approved'
907
- JOIN analytics_sites site
908
- ON site.id=p.site_id AND site.status='active'
909
- JOIN analytics_restricted_data profile
910
- ON profile.site_id=p.site_id AND profile.person_id=p.id
911
- AND profile.purpose='enhanced_matching' AND profile.expires_at>now()
912
- JOIN analytics_consent_receipts provenance
913
- ON provenance.id=profile.consent_receipt_id AND provenance.site_id=profile.site_id
914
- JOIN analytics_consent_current_state cs
915
- ON cs.site_id=p.site_id AND cs.subject_ref=provenance.subject_ref
916
- JOIN analytics_consent_receipts receipt
917
- ON receipt.id=cs.latest_receipt_id AND receipt.site_id=cs.site_id
918
- WHERE node.site_id=$1 AND node.kind='customer_id' AND node.value_hmac=$2
919
- AND p.public_ref IS NOT NULL
920
- AND cs.site_analytics=true AND cs.enhanced_matching=true
921
- AND cs.gpc=false AND receipt.action NOT IN ('withdraw','gpc')
922
- AND cs.effective_at <= now()
923
- AND (cs.expires_at IS NULL OR cs.expires_at > now())
924
- AND NOT EXISTS (
925
- SELECT 1 FROM analytics_identity_tombstones tombstone
926
- WHERE tombstone.site_id=p.site_id AND tombstone.person_id=p.id
927
- )
928
- AND NOT EXISTS (
929
- SELECT 1 FROM analytics_privacy_audits audit
930
- WHERE audit.site_id=p.site_id AND audit.subject_ref=p.public_ref
931
- AND audit.action='deletion_requested' AND audit.result_code='running'
932
- )
933
- ORDER BY edge.confidence DESC, edge.last_seen_at DESC
934
- LIMIT 1
935
- FOR SHARE OF p, ns, cs, receipt`,[t.siteId,$("customer_id",t.stableUserId)])).rows[0]??null}async function Wt(e,t){let n=process.env.MCP_SCRAPER_XRAY_SITE_ID?.trim();if(!n)return{status:"skipped",eventName:Pe,reason:"site_not_configured"};if(!Ws(n))return{status:"skipped",eventName:Pe,reason:"site_id_invalid"};let i=String(e.userId).trim();if(!i||!e.invoiceId.trim()||!Number.isSafeInteger(e.amountPaidMinor)||e.amountPaidMinor<0||!/^[a-z]{3}$/i.test(e.currency)||!Number.isFinite(new Date(e.occurredAt).getTime()))throw new Error("analytics_first_party_outcome_invalid");let r=t??{db:v(),record:(a,o)=>Ai({...a,transactionClient:o})},s=await r.db.connect();try{await s.query("BEGIN");let a=await Bs(s,{siteId:n,stableUserId:i});if(!a)return await s.query("ROLLBACK"),{status:"skipped",eventName:Pe,reason:"identity_or_consent_unavailable"};let o={schemaVersion:1,eventId:`mcp_invoice_${e.invoiceId}`.slice(0,200),sourceEventId:`stripe:invoice:${e.invoiceId}`.slice(0,300),source:"mcp_scraper",sourceAccountRef:"first_party_billing",eventKind:"transaction",eventName:Pe,occurredAt:e.occurredAt,personRef:i,orderId:e.invoiceId,valueMinor:e.amountPaidMinor,currency:e.currency.toUpperCase(),consent:{analytics:"granted",adUserData:"granted",adPersonalization:a.advertising_measurement?"granted":"denied",globalPrivacyControl:!1,occurredAt:new Date(a.effective_at).toISOString(),receiptId:a.receipt_id,policyVersion:a.policy_version,purposes:{site_analytics:"granted",enhanced_matching:"granted",advertising_measurement:a.advertising_measurement?"granted":"denied"}},properties:{product:"mcp_scraper",billingKind:"base_plan_paid_invoice",revenueProvenance:"stripe_paid_invoice"}},c=await r.record({siteId:n,event:o},s);return await s.query("COMMIT"),{status:"recorded",eventName:Pe,reason:"recorded",created:c.created,conversionIds:c.conversionIds}}catch(a){throw await s.query("ROLLBACK").catch(()=>{}),a}finally{s.release()}}function Si(e,t){return e&&(t??0)>0}function hi(e){let t=e;return t?.price?.id??t?.pricing?.price_details?.price??t?.plan?.id}function Ce(e){if(typeof e=="string")return e;if(e&&typeof e=="object"&&"id"in e)return String(e.id)}function Vs(e){let t=e.subscription;if(t)return Ce(t);let n=e.parent;return Ce(n?.subscription_details?.subscription)}async function Ks(e,t){let n=await P(e);return n||!t||(n=await je(t),n&&await Ve(n.id,e)),n}async function Js(e,t,n){let i=t.memory_subscription_id;if(!(!i||i===n))try{let r=await e.subscriptions.retrieve(i);r.status!=="canceled"&&!r.cancel_at_period_end&&await e.subscriptions.update(i,{cancel_at_period_end:!0})}catch(r){console.error("[stripe] could not wind down standalone memory subscription",i,r instanceof Error?r.message:r)}}async function Bt(e,t,n){if(!t.id||t.status!=="paid")return{status:"not_applicable",invoiceId:t.id??null};let i=t.lines.data.some(p=>{let E=hi(p);return!!E&&E in L});if(!Si(i,t.total))return{status:"not_applicable",invoiceId:t.id};let r=Vs(t),a=(r?me(await e.subscriptions.retrieve(r))?.price?.id:void 0)??t.lines.data.map(hi).find(p=>!!p&&p in L),o=a?L[a]:void 0;if(!o)return{status:"not_applicable",invoiceId:t.id};let c=Ce(t.customer);if(!c)throw new Error("Paid subscription invoice has no Stripe customer");let l=await Ks(c,t.customer_email);if(!l)throw new Error("Paid subscription invoice does not map to an MCP Scraper account");let _=await vn({invoiceId:t.id,userId:l.id,subscriptionId:r??l.subscription_id,tier:o.tier,concurrency:o.concurrency,creditsMc:o.credits_mc,description:`${o.label} subscription credits`,neverExpires:!!o.credits_never_expire,includedMemoryPlan:o.includes_memory?"pro":null});o.includes_memory&&(await Js(e,l,r??l.subscription_id),await Z(l,V({...l,memory_plan:"pro",subscription_tier:o.tier})));let d=await Je(l);d.ok||console.warn("[stripe] scheduled-action credential sync failed:",d.error??"unknown error");try{await Wt({userId:l.id,invoiceId:t.id,amountPaidMinor:t.amount_paid,currency:t.currency,occurredAt:new Date(((t.status_transitions?.paid_at??n)||Math.floor(Date.now()/1e3))*1e3).toISOString()})}catch(p){console.error("[stripe] X-Ray paid outcome failed",{invoiceId:t.id,error:p instanceof Error?p.message:String(p)})}return{status:_.granted?"fulfilled":"already_fulfilled",invoiceId:t.id,userId:l.id,tier:o.tier}}async function Ni(e,t,n){let i=await e.checkout.sessions.retrieve(t,{expand:["invoice"]});if(i.mode!=="subscription")throw new Error("Checkout session is not a subscription purchase");if(i.payment_status!=="paid"&&i.payment_status!=="no_payment_required")return{status:"not_applicable",invoiceId:Ce(i.invoice)??null};let r=Ce(i.customer);if(n&&r!==n)throw new Error("Checkout session does not belong to this billing account");let s=typeof i.invoice=="object"&&i.invoice&&!("deleted"in i.invoice)?i.invoice:await e.invoices.retrieve(Ce(i.invoice)??"");if(!s?.id)throw new Error("Paid subscription checkout has no invoice");return Bt(e,s,i.created)}var Ci="scale_credit_pack";function ot(e){if(typeof e=="string")return e;if(e&&typeof e=="object"&&"id"in e)return String(e.id)}function wi(e){let t=Number(e);if(!Number.isSafeInteger(t)||t<1||t>lt)throw new Error(`quantity must be a whole number from 1 to ${lt}`);return t}function Vd(e){return e.subscription_tier==="scale"}function Kd(e,t){let n=wi(t);return{purchase_kind:Ci,account_user_id:String(e),eligible_tier:"scale",increment_quantity:String(n),increment_usd:String(Re),credits_per_increment:String(dt)}}async function Ri(e,t,n){let i=await e.checkout.sessions.retrieve(t);if(i.mode!=="payment"||i.metadata?.purchase_kind!==Ci)return{status:"not_applicable",paymentIntentId:ot(i.payment_intent)??null};if(i.payment_status!=="paid")return{status:"not_applicable",paymentIntentId:ot(i.payment_intent)??null};let r=ot(i.customer);if(!r)throw new Error("Paid Scale credit-pack checkout has no Stripe customer");if(n&&r!==n)throw new Error("Checkout session does not belong to this billing account");let s=wi(i.metadata.increment_quantity),a=s*Re*100;if(i.currency!=="usd"||i.amount_subtotal!==a)throw new Error("Scale credit-pack checkout amount does not match the product contract");let o=await e.checkout.sessions.listLineItems(i.id,{limit:2}),c=o.data[0];if(o.data.length!==1||c?.quantity!==s||c.price?.currency!=="usd"||c.price?.unit_amount!==Re*100)throw new Error("Scale credit-pack checkout line item does not match the product contract");let l=await P(r);if(!l)throw new Error("Paid Scale credit-pack checkout does not map to an MCP Scraper account");if(i.metadata.account_user_id!==String(l.id)||i.metadata.eligible_tier!=="scale")throw new Error("Scale credit-pack checkout account metadata does not match the billing account");let _=ot(i.payment_intent);if(!_)throw new Error("Paid Scale credit-pack checkout has no payment intent");let d=s*dt;return{status:(await nn(l.id,s*en,Ue.TOPUP,`$${s*Re} Scale credit pack (${d.toLocaleString("en-US")} credits)`,`stripe-payment-intent:${_}`,{neverExpires:!1})).duplicate?"already_fulfilled":"fulfilled",paymentIntentId:_,userId:l.id,quantity:s,credits:d}}var M=new zs(process.env.STRIPE_SECRET_KEY,{apiVersion:"2026-02-25.clover"});async function Xs(e,t){if(!(!e||e===t))try{let n=await M.subscriptions.retrieve(e),i=n.items.data[0]?.price?.id,r=n.status!=="canceled"&&!n.cancel_at_period_end;i&&i in le&&r&&await M.subscriptions.update(e,{cancel_at_period_end:!0})}catch(n){console.error("[stripe] could not wind down standalone memory subscription",e,n instanceof Error?n.message:n)}}var Qs=new Ys;Qs.post("/webhooks",async e=>{let t=e.req.header("stripe-signature"),n=await e.req.text(),i;try{i=M.webhooks.constructEvent(n,t,process.env.STRIPE_WEBHOOK_SECRET)}catch{return e.json({error:"Invalid signature"},400)}try{let r=await fn(i.id,i.type,i.created);return r==="queued"&&Zs(),e.json({received:!0,disposition:r})}catch(r){let s=r instanceof Error?r.message:String(r);return console.error("[stripe] webhook enqueue failed",i.id,s),e.json({error:"Webhook enqueue failed"},500)}});function Zs(){let e=import("./queue-4XHYUV7J.js").then(({drainStripeEventQueue:t})=>t({limit:10,leaseSeconds:300})).catch(t=>{console.error("[stripe] immediate queue drain failed; the scheduled drain will retry",t instanceof Error?t.message:String(t))});Gs(e)}async function ol(e){if(e.type==="checkout.session.completed"||e.type==="checkout.session.async_payment_succeeded"){let t=e.data.object;t.mode==="payment"?await Ri(M,t.id):await Ni(M,t.id);return}if(e.type==="invoice.created"){let t=e.data.object;Oi(t)&&t.status==="draft"&&await M.invoices.update(t.id,{description:Qt,footer:Zt,custom_fields:[{name:"Pack benefit",value:"2 browsers per $5 monthly pack"},{name:"Billing cadence",value:"Monthly"}]});return}if(e.type==="invoice.paid"||e.type==="invoice.payment_succeeded"){let t=e.data.object;if(Oi(t)){let i=t.customer_email?void 0:await Ti(t.customer,void 0),r=t.customer_email??i?.email;if(!r)throw new Error("Paid concurrency invoice has no customer receipt email");await ea(t.id,r);return}if(t.lines.data.map(xi).find(i=>i&&i in le))return;await Bt(M,t,e.created)}if(e.type==="customer.subscription.created"||e.type==="customer.subscription.updated"){let t=e.data.object,n=Di(t);if(n){let s=await P(t.customer);if(!s)return;let a=De(t.status);await ft(s.id,a?ta(n):0),await vt(s.id,a?t.id:null);return}let i=me(t)?.price?.id??t.items.data[0]?.price?.id;if(i&&i in le){let s=le[i],a=await Ti(t.customer,t.customer_email);if(a){let o=De(t.status),c=o?s.plan:"free";await _e(a.id,c,o?t.id:null),await Z(a,V({...a,memory_plan:c}))}return}let r=await P(t.customer);if(!r)return;if(i&&i in L){let s=L[i],a=De(t.status),o=a?s.tier:null;if(await Et(r.id,o,a?s.concurrency:0,a?t.id:null),s.includes_memory){let c=a?"pro":"free";a&&await Xs(r.memory_subscription_id,t.id),await _e(r.id,c,null),await Z(r,V({...r,memory_plan:c}))}else r.memory_subscription_id===t.id&&(await _e(r.id,"free",null),await Z(r,V({...r,memory_plan:"free"})));if(a){let c=await Je(r);c.ok||console.warn("[stripe] scheduled-action credential sync failed:",c.error??"unknown error")}}}if(e.type==="customer.subscription.deleted"){let t=e.data.object;if(Di(t)){let i=await P(t.customer);i&&(await ft(i.id,0),await vt(i.id,null));return}let n=me(t)?.price?.id??t.items.data[0]?.price?.id;if(n&&n in L){let i=L[n],r=await P(t.customer);r&&(await Et(r.id,null,0,null),(i.includes_memory||r.memory_subscription_id===t.id)&&(await _e(r.id,"free",null),await Z(r,V({...r,memory_plan:"free"}))));return}if(n&&n in le||await bt(t.id)){let i=await bt(t.id)??await P(t.customer);i&&(await _e(i.id,"free",null),await Z(i,V({...i,memory_plan:"free"})));return}}}function Oi(e){return e.lines.data.some(t=>xi(t)===ct)}async function ea(e,t){let i=(await M.invoicePayments.list({invoice:e,status:"paid",limit:10})).data.find(a=>a.status==="paid");if(!i)throw new Error("Paid concurrency invoice has no paid Stripe payment");let r;if(i.payment.type==="charge")r=i.payment.charge;else if(i.payment.type==="payment_intent"&&i.payment.payment_intent){let a=typeof i.payment.payment_intent=="string"?i.payment.payment_intent:i.payment.payment_intent.id;r=(await M.paymentIntents.retrieve(a,{expand:["latest_charge"]})).latest_charge}if(!r)throw new Error("Paid concurrency invoice has no receipt-capable charge");let s=typeof r=="string"?await M.charges.retrieve(r):r;s.receipt_email!==t&&await M.charges.update(s.id,{receipt_email:t})}function Di(e){return e.items.data.find(t=>t.price?.id===ct)}function ta(e){let t=e.quantity??1;if(!Number.isSafeInteger(t)||t<1)throw new Error("Stripe concurrency pack quantity must be a positive safe integer");return t}function xi(e){let t=e;return t?.price?.id??t?.pricing?.price_details?.price??t?.plan?.id}async function Ti(e,t){let n=await P(e);if(n)return n;if(t)return n=await je(t),n&&await Ve(n.id,e),n}export{Ve as a,ft as b,vt as c,Et as d,_e as e,ma as f,Ki as g,fa as h,In as i,It as j,Na as k,Ca as l,Sn as m,ir as n,wa as o,Ra as p,rr as q,Nn as r,Je as s,ra as t,sa as u,aa as v,oa as w,ca as x,ht as y,K as z,me as A,Ma as B,Ua as C,Fa as D,Xe as E,Qe as F,Ln as G,Cr as H,Rr as I,Ct as J,Ga as K,Xa as L,Qa as M,Za as N,oo as O,Un as P,po as Q,yo as R,go as S,fo as T,vo as U,Eo as V,bo as W,Io as X,Ao as Y,So as Z,ho as _,No as $,Co as aa,jn as ba,wo as ca,Lr as da,Ro as ea,se as fa,nt as ga,F as ha,Oo as ia,Pr as ja,Lo as ka,$o as la,ko as ma,zn as na,Yn as oa,Po as pa,jo as qa,Zn as ra,qo as sa,Hr as ta,Xo as ua,fe as va,Qo as wa,uc as xa,si as ya,ai as za,yc as Aa,gc as Ba,oi as Ca,Ec as Da,ci as Ea,di as Fa,li as Ga,ui as Ha,_i as Ia,mi as Ja,at as Ka,Lc as La,$c as Ma,kc as Na,Pc as Oa,Mc as Pa,Uc as Qa,yi as Ra,gi as Sa,Ft as Ta,fi as Ua,vi as Va,Ds as Wa,cd as Xa,dd as Ya,ld as Za,ud as _a,_d as $a,md as ab,pd as bb,yd as cb,gd as db,fd as eb,vd as fb,Ed as gb,bd as hb,Id as ib,Ad as jb,Sd as kb,Ei as lb,hd as mb,Nd as nb,Ai as ob,Cd as pb,wd as qb,Bt as rb,Ni as sb,wi as tb,Vd as ub,Kd as vb,Ri as wb,M as xb,Qs as yb,ol as zb};