@outlit/core 1.4.5 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -29,8 +29,11 @@ interface BrowserIdentifyOptions extends CustomerAttribution {
29
29
  }
30
30
  /**
31
31
  * Base server-side user identity.
32
- * Track calls may use customerId/customerDomain without these fields.
32
+ * Track calls may use customerId without these fields.
33
33
  * Identify calls still require email or userId at runtime.
34
+ * `customerId`-only track calls are valid immediately, but they stay provisional
35
+ * until the same customer/account/workspace later appears on identify() with
36
+ * the matching customerId and an email.
34
37
  *
35
38
  * - fingerprint: Device identifier for anonymous tracking (can be linked later)
36
39
  * - email: User's email address (definitive identity, resolves immediately)
@@ -45,8 +48,6 @@ interface ServerIdentity {
45
48
  interface CustomerAttribution {
46
49
  /** Your system-owned customer/account/workspace ID. */
47
50
  customerId?: string;
48
- /** Public customer/account domain used for billing and attribution. */
49
- customerDomain?: string;
50
51
  }
51
52
  interface CustomerTraits {
52
53
  /** Customer's billing plan */
@@ -73,21 +74,17 @@ interface ServerIdentifyOptions extends ServerIdentity, CustomerAttribution {
73
74
  }
74
75
  /**
75
76
  * Customer identity for SDK billing methods.
76
- * Public billing calls should use `customerId` and/or `customerDomain`.
77
+ * Public billing calls should use `customerId`.
77
78
  */
78
79
  interface CustomerIdentifier extends CustomerAttribution {
79
- /**
80
- * @deprecated Use `customerDomain` instead.
81
- * Legacy alias kept for backward compatibility.
82
- */
83
- domain?: string;
84
80
  /**
85
81
  * @deprecated Stripe customer identifier.
86
- * Billing attribution should use `customerId` and/or `customerDomain` publicly.
82
+ * Billing attribution should use `customerId` publicly.
87
83
  */
88
84
  stripeCustomerId?: string;
89
85
  }
90
86
  interface BaseEvent {
87
+ uuid?: string;
91
88
  type: EventType;
92
89
  timestamp: number;
93
90
  url: string;
@@ -110,7 +107,6 @@ interface IdentifyEvent extends BaseEvent {
110
107
  userId?: string;
111
108
  fingerprint?: string;
112
109
  customerId?: string;
113
- customerDomain?: string;
114
110
  customerTraits?: CustomerTraits;
115
111
  traits?: IdentifyTraits;
116
112
  }
@@ -121,7 +117,6 @@ interface CustomEvent extends BaseEvent {
121
117
  userId?: string;
122
118
  fingerprint?: string;
123
119
  customerId?: string;
124
- customerDomain?: string;
125
120
  properties?: Record<string, string | number | boolean | null>;
126
121
  }
127
122
  interface CalendarEvent extends BaseEvent {
@@ -158,9 +153,6 @@ interface BillingEvent extends BaseEvent {
158
153
  status: BillingStatus;
159
154
  /** Optional customer identifiers */
160
155
  customerId?: string;
161
- customerDomain?: string;
162
- /** @deprecated Legacy alias for `customerDomain`. */
163
- domain?: string;
164
156
  stripeCustomerId?: string;
165
157
  /** Optional properties for context */
166
158
  properties?: Record<string, string | number | boolean | null>;
@@ -182,11 +174,6 @@ interface PayloadUserIdentity {
182
174
  * Kept for one compatibility window while callers migrate.
183
175
  */
184
176
  customerId?: string;
185
- /**
186
- * @deprecated Use payload-level `customerIdentity.customerDomain` instead.
187
- * Kept for one compatibility window while callers migrate.
188
- */
189
- customerDomain?: string;
190
177
  /**
191
178
  * @deprecated Use payload-level `customerIdentity.customerTraits` instead.
192
179
  * Kept for one compatibility window while callers migrate.
@@ -196,6 +183,8 @@ interface PayloadUserIdentity {
196
183
  /**
197
184
  * Customer identity for payload-level attribution.
198
185
  * Used by browser SDK to attach account/workspace context to a batch.
186
+ * `customerId`-only attribution is valid and remains provisional until a later
187
+ * identify(email, customerId) call links that external account/workspace to a resolved customer.
199
188
  */
200
189
  interface PayloadCustomerIdentity extends CustomerAttribution {
201
190
  /** Customer/account traits. */
@@ -273,7 +262,6 @@ declare function buildIdentifyEvent(params: BaseEventParams & {
273
262
  fingerprint?: string;
274
263
  traits?: IdentifyTraits;
275
264
  customerId?: string;
276
- customerDomain?: string;
277
265
  customerTraits?: CustomerTraits;
278
266
  }): IdentifyEvent;
279
267
  /**
@@ -286,7 +274,6 @@ declare function buildCustomEvent(params: BaseEventParams & {
286
274
  userId?: string;
287
275
  fingerprint?: string;
288
276
  customerId?: string;
289
- customerDomain?: string;
290
277
  }): CustomEvent;
291
278
  /**
292
279
  * Build a calendar booking event.
@@ -326,9 +313,7 @@ declare function buildStageEvent(params: BaseEventParams & {
326
313
  declare function buildBillingEvent(params: BaseEventParams & {
327
314
  status: BillingStatus;
328
315
  customerId?: string;
329
- customerDomain?: string;
330
316
  stripeCustomerId?: string;
331
- domain?: string;
332
317
  properties?: Record<string, string | number | boolean | null>;
333
318
  }): BillingEvent;
334
319
  /**
@@ -340,7 +325,9 @@ declare function buildBillingEvent(params: BaseEventParams & {
340
325
  * @param userIdentity - Optional user identity for immediate resolution (from setUser in SPA)
341
326
  * @param sessionId - Optional session ID for grouping events (browser SDK only)
342
327
  * @param fingerprint - Optional device identifier for server-side anonymous tracking
343
- * @param customerIdentity - Optional customer identity for batch attribution
328
+ * @param customerIdentity - Optional customer identity for batch attribution. `customerId`-only
329
+ * batches are valid and remain provisional until a later identify(email, customerId) call
330
+ * links that external account/workspace to a resolved customer.
344
331
  */
345
332
  declare function buildIngestPayload(visitorId: string, source: SourceType, events: TrackerEvent[], userIdentity?: PayloadUserIdentity, sessionId?: string, fingerprint?: string, customerIdentity?: PayloadCustomerIdentity): IngestPayload;
346
333
  /**
@@ -378,13 +365,12 @@ declare function sanitizeFormFields(fields: Record<string, string> | undefined,
378
365
  * - email: User's email (definitive identity)
379
366
  * - userId: Your system-owned user/contact ID
380
367
  * - customerId: Your system-owned customer/account/workspace ID
381
- * - customerDomain: Public customer/account domain used for billing and attribution
382
368
  */
383
- declare function validateServerIdentity(fingerprint?: string, email?: string, userId?: string, customerId?: string, customerDomain?: string): void;
369
+ declare function validateServerIdentity(fingerprint?: string, email?: string, userId?: string, customerId?: string): void;
384
370
  /**
385
371
  * Validate that at least one customer identifier is provided for billing calls.
386
372
  */
387
- declare function validateCustomerIdentity(customerId?: string, customerDomain?: string, domain?: string, stripeCustomerId?: string): void;
373
+ declare function validateCustomerIdentity(customerId?: string, stripeCustomerId?: string): void;
388
374
  /**
389
375
  * Validate that a string looks like a valid email address.
390
376
  */
package/dist/index.d.ts CHANGED
@@ -29,8 +29,11 @@ interface BrowserIdentifyOptions extends CustomerAttribution {
29
29
  }
30
30
  /**
31
31
  * Base server-side user identity.
32
- * Track calls may use customerId/customerDomain without these fields.
32
+ * Track calls may use customerId without these fields.
33
33
  * Identify calls still require email or userId at runtime.
34
+ * `customerId`-only track calls are valid immediately, but they stay provisional
35
+ * until the same customer/account/workspace later appears on identify() with
36
+ * the matching customerId and an email.
34
37
  *
35
38
  * - fingerprint: Device identifier for anonymous tracking (can be linked later)
36
39
  * - email: User's email address (definitive identity, resolves immediately)
@@ -45,8 +48,6 @@ interface ServerIdentity {
45
48
  interface CustomerAttribution {
46
49
  /** Your system-owned customer/account/workspace ID. */
47
50
  customerId?: string;
48
- /** Public customer/account domain used for billing and attribution. */
49
- customerDomain?: string;
50
51
  }
51
52
  interface CustomerTraits {
52
53
  /** Customer's billing plan */
@@ -73,21 +74,17 @@ interface ServerIdentifyOptions extends ServerIdentity, CustomerAttribution {
73
74
  }
74
75
  /**
75
76
  * Customer identity for SDK billing methods.
76
- * Public billing calls should use `customerId` and/or `customerDomain`.
77
+ * Public billing calls should use `customerId`.
77
78
  */
78
79
  interface CustomerIdentifier extends CustomerAttribution {
79
- /**
80
- * @deprecated Use `customerDomain` instead.
81
- * Legacy alias kept for backward compatibility.
82
- */
83
- domain?: string;
84
80
  /**
85
81
  * @deprecated Stripe customer identifier.
86
- * Billing attribution should use `customerId` and/or `customerDomain` publicly.
82
+ * Billing attribution should use `customerId` publicly.
87
83
  */
88
84
  stripeCustomerId?: string;
89
85
  }
90
86
  interface BaseEvent {
87
+ uuid?: string;
91
88
  type: EventType;
92
89
  timestamp: number;
93
90
  url: string;
@@ -110,7 +107,6 @@ interface IdentifyEvent extends BaseEvent {
110
107
  userId?: string;
111
108
  fingerprint?: string;
112
109
  customerId?: string;
113
- customerDomain?: string;
114
110
  customerTraits?: CustomerTraits;
115
111
  traits?: IdentifyTraits;
116
112
  }
@@ -121,7 +117,6 @@ interface CustomEvent extends BaseEvent {
121
117
  userId?: string;
122
118
  fingerprint?: string;
123
119
  customerId?: string;
124
- customerDomain?: string;
125
120
  properties?: Record<string, string | number | boolean | null>;
126
121
  }
127
122
  interface CalendarEvent extends BaseEvent {
@@ -158,9 +153,6 @@ interface BillingEvent extends BaseEvent {
158
153
  status: BillingStatus;
159
154
  /** Optional customer identifiers */
160
155
  customerId?: string;
161
- customerDomain?: string;
162
- /** @deprecated Legacy alias for `customerDomain`. */
163
- domain?: string;
164
156
  stripeCustomerId?: string;
165
157
  /** Optional properties for context */
166
158
  properties?: Record<string, string | number | boolean | null>;
@@ -182,11 +174,6 @@ interface PayloadUserIdentity {
182
174
  * Kept for one compatibility window while callers migrate.
183
175
  */
184
176
  customerId?: string;
185
- /**
186
- * @deprecated Use payload-level `customerIdentity.customerDomain` instead.
187
- * Kept for one compatibility window while callers migrate.
188
- */
189
- customerDomain?: string;
190
177
  /**
191
178
  * @deprecated Use payload-level `customerIdentity.customerTraits` instead.
192
179
  * Kept for one compatibility window while callers migrate.
@@ -196,6 +183,8 @@ interface PayloadUserIdentity {
196
183
  /**
197
184
  * Customer identity for payload-level attribution.
198
185
  * Used by browser SDK to attach account/workspace context to a batch.
186
+ * `customerId`-only attribution is valid and remains provisional until a later
187
+ * identify(email, customerId) call links that external account/workspace to a resolved customer.
199
188
  */
200
189
  interface PayloadCustomerIdentity extends CustomerAttribution {
201
190
  /** Customer/account traits. */
@@ -273,7 +262,6 @@ declare function buildIdentifyEvent(params: BaseEventParams & {
273
262
  fingerprint?: string;
274
263
  traits?: IdentifyTraits;
275
264
  customerId?: string;
276
- customerDomain?: string;
277
265
  customerTraits?: CustomerTraits;
278
266
  }): IdentifyEvent;
279
267
  /**
@@ -286,7 +274,6 @@ declare function buildCustomEvent(params: BaseEventParams & {
286
274
  userId?: string;
287
275
  fingerprint?: string;
288
276
  customerId?: string;
289
- customerDomain?: string;
290
277
  }): CustomEvent;
291
278
  /**
292
279
  * Build a calendar booking event.
@@ -326,9 +313,7 @@ declare function buildStageEvent(params: BaseEventParams & {
326
313
  declare function buildBillingEvent(params: BaseEventParams & {
327
314
  status: BillingStatus;
328
315
  customerId?: string;
329
- customerDomain?: string;
330
316
  stripeCustomerId?: string;
331
- domain?: string;
332
317
  properties?: Record<string, string | number | boolean | null>;
333
318
  }): BillingEvent;
334
319
  /**
@@ -340,7 +325,9 @@ declare function buildBillingEvent(params: BaseEventParams & {
340
325
  * @param userIdentity - Optional user identity for immediate resolution (from setUser in SPA)
341
326
  * @param sessionId - Optional session ID for grouping events (browser SDK only)
342
327
  * @param fingerprint - Optional device identifier for server-side anonymous tracking
343
- * @param customerIdentity - Optional customer identity for batch attribution
328
+ * @param customerIdentity - Optional customer identity for batch attribution. `customerId`-only
329
+ * batches are valid and remain provisional until a later identify(email, customerId) call
330
+ * links that external account/workspace to a resolved customer.
344
331
  */
345
332
  declare function buildIngestPayload(visitorId: string, source: SourceType, events: TrackerEvent[], userIdentity?: PayloadUserIdentity, sessionId?: string, fingerprint?: string, customerIdentity?: PayloadCustomerIdentity): IngestPayload;
346
333
  /**
@@ -378,13 +365,12 @@ declare function sanitizeFormFields(fields: Record<string, string> | undefined,
378
365
  * - email: User's email (definitive identity)
379
366
  * - userId: Your system-owned user/contact ID
380
367
  * - customerId: Your system-owned customer/account/workspace ID
381
- * - customerDomain: Public customer/account domain used for billing and attribution
382
368
  */
383
- declare function validateServerIdentity(fingerprint?: string, email?: string, userId?: string, customerId?: string, customerDomain?: string): void;
369
+ declare function validateServerIdentity(fingerprint?: string, email?: string, userId?: string, customerId?: string): void;
384
370
  /**
385
371
  * Validate that at least one customer identifier is provided for billing calls.
386
372
  */
387
- declare function validateCustomerIdentity(customerId?: string, customerDomain?: string, domain?: string, stripeCustomerId?: string): void;
373
+ declare function validateCustomerIdentity(customerId?: string, stripeCustomerId?: string): void;
388
374
  /**
389
375
  * Validate that a string looks like a valid email address.
390
376
  */
package/dist/index.js CHANGED
@@ -46,6 +46,9 @@ __export(src_exports, {
46
46
  });
47
47
  module.exports = __toCommonJS(src_exports);
48
48
 
49
+ // src/payload.ts
50
+ var import_uuid = require("uuid");
51
+
49
52
  // src/types.ts
50
53
  var DEFAULT_API_HOST = "https://app.outlit.ai";
51
54
  var DEFAULT_DENIED_FORM_FIELDS = [
@@ -144,26 +147,23 @@ function sanitizeFormFields(fields, customDenylist) {
144
147
  }
145
148
  return Object.keys(sanitized).length > 0 ? sanitized : void 0;
146
149
  }
147
- function validateServerIdentity(fingerprint, email, userId, customerId, customerDomain) {
150
+ function validateServerIdentity(fingerprint, email, userId, customerId) {
148
151
  const hasFingerprint = fingerprint && fingerprint.trim().length > 0;
149
152
  const hasEmail = email && email.trim().length > 0;
150
153
  const hasUserId = userId && userId.trim().length > 0;
151
154
  const hasCustomerId = customerId && customerId.trim().length > 0;
152
- const hasCustomerDomain = customerDomain && customerDomain.trim().length > 0;
153
- if (!hasFingerprint && !hasEmail && !hasUserId && !hasCustomerId && !hasCustomerDomain) {
155
+ if (!hasFingerprint && !hasEmail && !hasUserId && !hasCustomerId) {
154
156
  throw new Error(
155
- "Server SDK requires at least one of: fingerprint, email, userId, customerId, or customerDomain for track calls. Use fingerprint/email/userId for user-scoped tracking or customerId/customerDomain for customer-only tracking."
157
+ "Server SDK requires at least one of: fingerprint, email, userId, or customerId for track calls. Use fingerprint/email/userId for user-scoped tracking or customerId for customer-only tracking."
156
158
  );
157
159
  }
158
160
  }
159
- function validateCustomerIdentity(customerId, customerDomain, domain, stripeCustomerId) {
161
+ function validateCustomerIdentity(customerId, stripeCustomerId) {
160
162
  const hasCustomerId = customerId && customerId.trim().length > 0;
161
- const hasCustomerDomain = customerDomain && customerDomain.trim().length > 0;
162
- const hasDomain = domain && domain.trim().length > 0;
163
163
  const hasStripeCustomerId = stripeCustomerId && stripeCustomerId.trim().length > 0;
164
- if (!hasCustomerId && !hasCustomerDomain && !hasDomain && !hasStripeCustomerId) {
164
+ if (!hasCustomerId && !hasStripeCustomerId) {
165
165
  throw new Error(
166
- "Billing methods require customerId or customerDomain. Use customerId for your system-owned account ID and customerDomain for the public account domain."
166
+ "Billing methods require customerId or stripeCustomerId. Use customerId for your system-owned account ID."
167
167
  );
168
168
  }
169
169
  }
@@ -279,6 +279,7 @@ function extractIdentityFromForm(fields, inputTypes) {
279
279
  function buildPageviewEvent(params) {
280
280
  const { url, referrer, timestamp, title } = params;
281
281
  return {
282
+ uuid: (0, import_uuid.v7)(),
282
283
  type: "pageview",
283
284
  timestamp: timestamp ?? Date.now(),
284
285
  url,
@@ -291,6 +292,7 @@ function buildPageviewEvent(params) {
291
292
  function buildFormEvent(params) {
292
293
  const { url, referrer, timestamp, formId, formFields } = params;
293
294
  return {
295
+ uuid: (0, import_uuid.v7)(),
294
296
  type: "form",
295
297
  timestamp: timestamp ?? Date.now(),
296
298
  url,
@@ -311,10 +313,10 @@ function buildIdentifyEvent(params) {
311
313
  fingerprint,
312
314
  traits,
313
315
  customerId,
314
- customerDomain,
315
316
  customerTraits
316
317
  } = params;
317
318
  return {
319
+ uuid: (0, import_uuid.v7)(),
318
320
  type: "identify",
319
321
  timestamp: timestamp ?? Date.now(),
320
322
  url,
@@ -325,7 +327,6 @@ function buildIdentifyEvent(params) {
325
327
  userId,
326
328
  fingerprint,
327
329
  customerId,
328
- customerDomain,
329
330
  customerTraits,
330
331
  traits
331
332
  };
@@ -340,10 +341,10 @@ function buildCustomEvent(params) {
340
341
  email,
341
342
  userId,
342
343
  fingerprint,
343
- customerId,
344
- customerDomain
344
+ customerId
345
345
  } = params;
346
346
  return {
347
+ uuid: (0, import_uuid.v7)(),
347
348
  type: "custom",
348
349
  timestamp: timestamp ?? Date.now(),
349
350
  url,
@@ -355,7 +356,6 @@ function buildCustomEvent(params) {
355
356
  userId,
356
357
  fingerprint,
357
358
  customerId,
358
- customerDomain,
359
359
  properties
360
360
  };
361
361
  }
@@ -374,6 +374,7 @@ function buildCalendarEvent(params) {
374
374
  inviteeName
375
375
  } = params;
376
376
  return {
377
+ uuid: (0, import_uuid.v7)(),
377
378
  type: "calendar",
378
379
  timestamp: timestamp ?? Date.now(),
379
380
  url,
@@ -393,6 +394,7 @@ function buildCalendarEvent(params) {
393
394
  function buildEngagementEvent(params) {
394
395
  const { url, referrer, timestamp, activeTimeMs, totalTimeMs, sessionId } = params;
395
396
  return {
397
+ uuid: (0, import_uuid.v7)(),
396
398
  type: "engagement",
397
399
  timestamp: timestamp ?? Date.now(),
398
400
  url,
@@ -407,6 +409,7 @@ function buildEngagementEvent(params) {
407
409
  function buildStageEvent(params) {
408
410
  const { url, referrer, timestamp, stage, properties } = params;
409
411
  return {
412
+ uuid: (0, import_uuid.v7)(),
410
413
  type: "stage",
411
414
  timestamp: timestamp ?? Date.now(),
412
415
  url,
@@ -418,18 +421,9 @@ function buildStageEvent(params) {
418
421
  };
419
422
  }
420
423
  function buildBillingEvent(params) {
421
- const {
422
- url,
423
- referrer,
424
- timestamp,
425
- status,
426
- customerId,
427
- customerDomain,
428
- stripeCustomerId,
429
- domain,
430
- properties
431
- } = params;
424
+ const { url, referrer, timestamp, status, customerId, stripeCustomerId, properties } = params;
432
425
  return {
426
+ uuid: (0, import_uuid.v7)(),
433
427
  type: "billing",
434
428
  timestamp: timestamp ?? Date.now(),
435
429
  url,
@@ -438,8 +432,6 @@ function buildBillingEvent(params) {
438
432
  utm: extractUtmParams(url),
439
433
  status,
440
434
  customerId,
441
- customerDomain: customerDomain ?? domain,
442
- domain: domain ?? customerDomain,
443
435
  stripeCustomerId,
444
436
  properties
445
437
  };
@@ -450,9 +442,8 @@ function buildIngestPayload(visitorId, source, events, userIdentity, sessionId,
450
442
  source,
451
443
  events
452
444
  };
453
- const legacyCustomerIdentity = customerIdentity === void 0 && userIdentity && (userIdentity.customerId || userIdentity.customerDomain || userIdentity.customerTraits) ? {
445
+ const legacyCustomerIdentity = customerIdentity === void 0 && userIdentity && (userIdentity.customerId || userIdentity.customerTraits) ? {
454
446
  ...userIdentity.customerId && { customerId: userIdentity.customerId },
455
- ...userIdentity.customerDomain && { customerDomain: userIdentity.customerDomain },
456
447
  ...userIdentity.customerTraits && { customerTraits: userIdentity.customerTraits }
457
448
  } : void 0;
458
449
  const resolvedCustomerIdentity = customerIdentity ?? legacyCustomerIdentity;
@@ -469,14 +460,11 @@ function buildIngestPayload(visitorId, source, events, userIdentity, sessionId,
469
460
  ...userIdentity.traits && { traits: userIdentity.traits }
470
461
  };
471
462
  }
472
- if (resolvedCustomerIdentity && (resolvedCustomerIdentity.customerId || resolvedCustomerIdentity.customerDomain || resolvedCustomerIdentity.customerTraits)) {
463
+ if (resolvedCustomerIdentity && (resolvedCustomerIdentity.customerId || resolvedCustomerIdentity.customerTraits)) {
473
464
  payload.customerIdentity = {
474
465
  ...resolvedCustomerIdentity.customerId && {
475
466
  customerId: resolvedCustomerIdentity.customerId
476
467
  },
477
- ...resolvedCustomerIdentity.customerDomain && {
478
- customerDomain: resolvedCustomerIdentity.customerDomain
479
- },
480
468
  ...resolvedCustomerIdentity.customerTraits && {
481
469
  customerTraits: resolvedCustomerIdentity.customerTraits
482
470
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/utils.ts","../src/payload.ts"],"sourcesContent":["// Types\n\n// Payload builders\nexport {\n batchEvents,\n buildBillingEvent,\n buildCalendarEvent,\n buildCustomEvent,\n buildEngagementEvent,\n buildFormEvent,\n buildIdentifyEvent,\n buildIngestPayload,\n buildPageviewEvent,\n buildStageEvent,\n MAX_BATCH_SIZE,\n} from \"./payload\"\nexport type {\n BillingEvent,\n BillingStatus,\n BrowserIdentifyOptions,\n BrowserTrackOptions,\n CalendarEvent,\n CalendarProvider,\n CustomEvent,\n CustomerAttribution,\n CustomerIdentifier,\n CustomerTraits,\n EngagementEvent,\n EventType,\n ExplicitJourneyStage,\n FormEvent,\n IdentifyEvent,\n IdentifyTraits,\n IngestPayload,\n IngestResponse,\n PageviewEvent,\n PayloadCustomerIdentity,\n PayloadUserIdentity,\n ServerIdentifyOptions,\n ServerIdentity,\n ServerTrackOptions,\n SourceType,\n StageEvent,\n TrackerConfig,\n TrackerEvent,\n UtmParams,\n} from \"./types\"\n// Constants\nexport { DEFAULT_API_HOST, DEFAULT_DENIED_FORM_FIELDS } from \"./types\"\n\n// Auto-identify types\nexport type { ExtractedIdentity } from \"./utils\"\n// Utilities\nexport {\n extractIdentityFromForm,\n extractPathFromUrl,\n extractUtmParams,\n findEmailField,\n findNameFields,\n isFieldDenied,\n // Auto-identify utilities\n isValidEmail,\n sanitizeFormFields,\n validateCustomerIdentity,\n validateServerIdentity,\n} from \"./utils\"\n","// ============================================\n// EVENT TYPES\n// ============================================\n\nexport type EventType =\n | \"pageview\"\n | \"form\"\n | \"identify\"\n | \"custom\"\n | \"calendar\"\n | \"engagement\"\n | \"stage\"\n | \"billing\"\n\n// Only explicit stages - discovered/signed_up are inferred from identify calls\nexport type ExplicitJourneyStage = \"activated\" | \"engaged\" | \"inactive\"\n\nexport type BillingStatus = \"trialing\" | \"paid\" | \"churned\"\n\nexport type CalendarProvider = \"cal.com\" | \"calendly\" | \"unknown\"\n\nexport type SourceType = \"client\" | \"server\" | \"integration\"\n\n// ============================================\n// UTM PARAMETERS\n// ============================================\n\nexport interface UtmParams {\n source?: string\n medium?: string\n campaign?: string\n term?: string\n content?: string\n}\n\n// ============================================\n// TRACKER CONFIGURATION\n// ============================================\n\nexport interface TrackerConfig {\n publicKey: string\n apiHost?: string // default: 'https://app.outlit.ai'\n}\n\n// ============================================\n// BROWSER-SPECIFIC TYPES (anonymous allowed)\n// visitorId is auto-managed by the browser SDK\n// ============================================\n\nexport interface BrowserTrackOptions {\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface BrowserIdentifyOptions extends CustomerAttribution {\n email?: string\n /** Your system-owned user/contact ID. */\n userId?: string\n /** User/contact traits. */\n traits?: IdentifyTraits\n /** Customer/account-level traits. */\n customerTraits?: CustomerTraits\n}\n\n// ============================================\n// SERVER-SPECIFIC TYPES\n// Track calls can use user identity, customer attribution, or both.\n// Identify calls remain user-scoped and require email or userId.\n// ============================================\n\n/**\n * Base server-side user identity.\n * Track calls may use customerId/customerDomain without these fields.\n * Identify calls still require email or userId at runtime.\n *\n * - fingerprint: Device identifier for anonymous tracking (can be linked later)\n * - email: User's email address (definitive identity, resolves immediately)\n * - userId: Your system-owned user/contact ID\n */\nexport interface ServerIdentity {\n fingerprint?: string\n email?: string\n /** Your system-owned user/contact ID. */\n userId?: string\n}\n\nexport interface CustomerAttribution {\n /** Your system-owned customer/account/workspace ID. */\n customerId?: string\n /** Public customer/account domain used for billing and attribution. */\n customerDomain?: string\n}\n\n// ============================================\n// TRAITS\n// ============================================\n\nexport interface CustomerTraits {\n /** Customer's billing plan */\n plan?: string\n /** Allow additional custom properties */\n [key: string]: string | number | boolean | null | undefined\n}\n\n/**\n * Traits for identify calls.\n * These are user/contact traits, not customer/account traits.\n */\nexport interface IdentifyTraits {\n /** User-level traits */\n [key: string]: string | number | boolean | null | undefined\n}\n\nexport interface ServerTrackOptions extends ServerIdentity, CustomerAttribution {\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n timestamp?: number\n}\n\nexport interface ServerIdentifyOptions extends ServerIdentity, CustomerAttribution {\n traits?: IdentifyTraits\n customerTraits?: CustomerTraits\n}\n\n/**\n * Customer identity for SDK billing methods.\n * Public billing calls should use `customerId` and/or `customerDomain`.\n */\nexport interface CustomerIdentifier extends CustomerAttribution {\n /**\n * @deprecated Use `customerDomain` instead.\n * Legacy alias kept for backward compatibility.\n */\n domain?: string\n /**\n * @deprecated Stripe customer identifier.\n * Billing attribution should use `customerId` and/or `customerDomain` publicly.\n */\n stripeCustomerId?: string\n}\n\n// ============================================\n// INTERNAL EVENT TYPES\n// These are the full event objects sent to the API\n// ============================================\n\ninterface BaseEvent {\n type: EventType\n timestamp: number // Unix timestamp in milliseconds\n url: string\n path: string\n referrer?: string\n utm?: UtmParams\n}\n\nexport interface PageviewEvent extends BaseEvent {\n type: \"pageview\"\n title?: string\n}\n\nexport interface FormEvent extends BaseEvent {\n type: \"form\"\n formId?: string\n formFields?: Record<string, string>\n}\n\nexport interface IdentifyEvent extends BaseEvent {\n type: \"identify\"\n email?: string\n userId?: string\n fingerprint?: string\n customerId?: string\n customerDomain?: string\n customerTraits?: CustomerTraits\n traits?: IdentifyTraits\n}\n\nexport interface CustomEvent extends BaseEvent {\n type: \"custom\"\n eventName: string\n email?: string\n userId?: string\n fingerprint?: string\n customerId?: string\n customerDomain?: string\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface CalendarEvent extends BaseEvent {\n type: \"calendar\"\n provider: CalendarProvider\n eventType?: string // e.g., \"30 Minute Meeting\"\n startTime?: string // ISO timestamp\n endTime?: string // ISO timestamp\n duration?: number // Duration in minutes\n isRecurring?: boolean\n /** Available when identity is passed via webhooks or manual integration */\n inviteeEmail?: string\n inviteeName?: string\n}\n\nexport interface EngagementEvent extends BaseEvent {\n type: \"engagement\"\n /** Time in milliseconds the user was actively engaged (visible tab + user interactions) */\n activeTimeMs: number\n /** Total wall-clock time in milliseconds on the page */\n totalTimeMs: number\n /** Session ID for grouping engagement events. Resets after 30 min of inactivity or tab close. */\n sessionId: string\n}\n\nexport interface StageEvent extends BaseEvent {\n type: \"stage\"\n /** The journey stage to set (only explicit stages, discovered/signed_up are inferred) */\n stage: ExplicitJourneyStage\n /** Optional properties for context */\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface BillingEvent extends BaseEvent {\n type: \"billing\"\n /** The billing status to set for a customer */\n status: BillingStatus\n /** Optional customer identifiers */\n customerId?: string\n customerDomain?: string\n /** @deprecated Legacy alias for `customerDomain`. */\n domain?: string\n stripeCustomerId?: string\n /** Optional properties for context */\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport type TrackerEvent =\n | PageviewEvent\n | FormEvent\n | IdentifyEvent\n | CustomEvent\n | CalendarEvent\n | EngagementEvent\n | StageEvent\n | BillingEvent\n\n// ============================================\n// INGEST PAYLOAD\n// This is what gets sent to the API\n// ============================================\n\n/**\n * User identity for payload-level resolution.\n * Used by browser SDK when user is logged in (via setUser).\n * Customer attribution is carried separately in `customerIdentity`.\n */\nexport interface PayloadUserIdentity {\n email?: string\n /** Your system-owned user/contact ID. */\n userId?: string\n /** User/contact traits. */\n traits?: IdentifyTraits\n /**\n * @deprecated Use payload-level `customerIdentity.customerId` instead.\n * Kept for one compatibility window while callers migrate.\n */\n customerId?: string\n /**\n * @deprecated Use payload-level `customerIdentity.customerDomain` instead.\n * Kept for one compatibility window while callers migrate.\n */\n customerDomain?: string\n /**\n * @deprecated Use payload-level `customerIdentity.customerTraits` instead.\n * Kept for one compatibility window while callers migrate.\n */\n customerTraits?: CustomerTraits\n}\n\n/**\n * Customer identity for payload-level attribution.\n * Used by browser SDK to attach account/workspace context to a batch.\n */\nexport interface PayloadCustomerIdentity extends CustomerAttribution {\n /** Customer/account traits. */\n customerTraits?: CustomerTraits\n}\n\nexport interface IngestPayload {\n visitorId?: string // Required for pixel, optional for server\n source: SourceType\n events: TrackerEvent[]\n /**\n * Device identifier for anonymous tracking.\n * Events with fingerprint can be linked to users later via identify.\n * Only present for server-side events.\n */\n fingerprint?: string\n /**\n * Session ID for grouping all events in this batch.\n * Only present for browser (client) source events.\n * Used to correlate pageviews, forms, custom events, and engagement\n * within the same browsing session.\n */\n sessionId?: string\n /**\n * User identity for this batch of events.\n * When present, the server can resolve directly to CustomerContact\n * instead of relying on anonymous visitor flow.\n *\n * This is set by the browser SDK when setUser() has been called,\n * allowing immediate identity resolution for SPA/React apps.\n */\n userIdentity?: PayloadUserIdentity\n /**\n * Customer/account identity for this batch of events.\n * Used to attribute browser batches to a customer/workspace even when\n * the customer fields are not present on every event.\n */\n customerIdentity?: PayloadCustomerIdentity\n}\n\n// ============================================\n// API RESPONSE\n// ============================================\n\nexport interface IngestResponse {\n success: boolean\n processed: number\n errors?: Array<{\n index: number\n message: string\n }>\n}\n\n// ============================================\n// CONSTANTS\n// ============================================\n\nexport const DEFAULT_API_HOST = \"https://app.outlit.ai\"\n\n// Re-export for convenience\nexport type { PayloadUserIdentity as UserIdentity }\n\nexport const DEFAULT_DENIED_FORM_FIELDS = [\n \"password\",\n \"passwd\",\n \"pass\",\n \"pwd\",\n \"token\",\n \"secret\",\n \"api_key\",\n \"apikey\",\n \"api-key\",\n \"credit_card\",\n \"creditcard\",\n \"credit-card\",\n \"cc_number\",\n \"ccnumber\",\n \"card_number\",\n \"cardnumber\",\n \"cvv\",\n \"cvc\",\n \"ssn\",\n \"social_security\",\n \"socialsecurity\",\n \"bank_account\",\n \"bankaccount\",\n \"routing_number\",\n \"routingnumber\",\n]\n","import { DEFAULT_DENIED_FORM_FIELDS, type UtmParams } from \"./types\"\n\n// ============================================\n// UTM EXTRACTION\n// ============================================\n\n/**\n * Extract UTM parameters from a URL.\n */\nexport function extractUtmParams(url: string): UtmParams | undefined {\n try {\n const urlObj = new URL(url)\n const params = urlObj.searchParams\n\n const utm: UtmParams = {}\n\n if (params.has(\"utm_source\")) utm.source = params.get(\"utm_source\") ?? undefined\n if (params.has(\"utm_medium\")) utm.medium = params.get(\"utm_medium\") ?? undefined\n if (params.has(\"utm_campaign\")) utm.campaign = params.get(\"utm_campaign\") ?? undefined\n if (params.has(\"utm_term\")) utm.term = params.get(\"utm_term\") ?? undefined\n if (params.has(\"utm_content\")) utm.content = params.get(\"utm_content\") ?? undefined\n\n return Object.keys(utm).length > 0 ? utm : undefined\n } catch (error) {\n console.warn(`[Outlit] Failed to parse URL for UTM extraction: \"${url}\"`, error)\n return undefined\n }\n}\n\n/**\n * Extract path from a URL.\n */\nexport function extractPathFromUrl(url: string): string {\n try {\n const urlObj = new URL(url)\n let path = urlObj.pathname\n const hash = urlObj.hash\n\n // For file:// or custom protocol URLs with no real hostname (common in Electron),\n // the meaningful route is typically stored in the hash fragment.\n if (hash && hash.length > 1 && (!urlObj.hostname || urlObj.protocol === \"file:\")) {\n path = hash.replace(/^#/, \"\")\n }\n // For web hash-routing SPAs (e.g. \"/#/settings\"), prefer hash when the\n // pathname is an SPA entry point.\n else if (\n hash.startsWith(\"#/\") &&\n (urlObj.pathname === \"/\" || urlObj.pathname.endsWith(\"/index.html\"))\n ) {\n path = hash.slice(1)\n }\n\n if (!path) return \"/\"\n if (!path.startsWith(\"/\")) return `/${path}`\n\n return path\n } catch (error) {\n console.warn(\n `[Outlit] Failed to parse URL for path extraction: \"${url}\", defaulting to \"/\"`,\n error,\n )\n return \"/\"\n }\n}\n\n// ============================================\n// FORM FIELD SANITIZATION\n// ============================================\n\n/**\n * Check if a field name should be denied (case-insensitive).\n */\nexport function isFieldDenied(fieldName: string, denylist: string[]): boolean {\n const normalizedName = fieldName.toLowerCase().replace(/[-_\\s]/g, \"\")\n return denylist.some((denied) => {\n const normalizedDenied = denied.toLowerCase().replace(/[-_\\s]/g, \"\")\n return normalizedName.includes(normalizedDenied)\n })\n}\n\n/**\n * Check if a value looks like sensitive data (e.g., credit card number).\n */\nfunction looksLikeSensitiveValue(value: string): boolean {\n // Remove spaces and dashes\n const cleaned = value.replace(/[\\s-]/g, \"\")\n\n // Check for credit card patterns (13-19 digits)\n if (/^\\d{13,19}$/.test(cleaned)) {\n return true\n }\n\n // Check for SSN pattern (9 digits)\n if (/^\\d{9}$/.test(cleaned) || /^\\d{3}-\\d{2}-\\d{4}$/.test(value)) {\n return true\n }\n\n return false\n}\n\n/**\n * Sanitize form fields by removing sensitive data.\n * Returns a new object with denied fields removed.\n */\nexport function sanitizeFormFields(\n fields: Record<string, string> | undefined,\n customDenylist?: string[],\n): Record<string, string> | undefined {\n if (!fields) return undefined\n\n const denylist = customDenylist ?? DEFAULT_DENIED_FORM_FIELDS\n const sanitized: Record<string, string> = {}\n\n for (const [key, value] of Object.entries(fields)) {\n if (!isFieldDenied(key, denylist)) {\n // Also check for credit card patterns in values\n if (!looksLikeSensitiveValue(value)) {\n sanitized[key] = value\n }\n }\n }\n\n return Object.keys(sanitized).length > 0 ? sanitized : undefined\n}\n\n// ============================================\n// VISITOR ID DERIVATION (for server SDK)\n// ============================================\n\n/**\n * Derive a deterministic visitor ID from email and/or userId.\n * This is used by the server SDK to create consistent IDs for API compatibility.\n *\n * Uses a simple hash to create a UUID-like string that will be consistent\n * for the same email/userId combination.\n */\nexport function deriveVisitorIdFromIdentity(email?: string, userId?: string): string {\n const identity = [email?.toLowerCase(), userId].filter(Boolean).join(\"|\")\n if (!identity) {\n throw new Error(\"Either email or userId must be provided\")\n }\n\n // Simple hash function to create a deterministic UUID-like string\n let hash = 0\n for (let i = 0; i < identity.length; i++) {\n const char = identity.charCodeAt(i)\n hash = (hash << 5) - hash + char\n hash = hash & hash // Convert to 32-bit integer\n }\n\n // Convert to hex and format as UUID-like string\n const hex = Math.abs(hash).toString(16).padStart(8, \"0\")\n const part1 = hex.slice(0, 8)\n const part2 = identity.length.toString(16).padStart(4, \"0\")\n const part3 = \"4000\" // Version 4 UUID marker\n const part4 = (((hash >>> 16) & 0x0fff) | 0x8000).toString(16)\n const part5 = Math.abs(hash * 31)\n .toString(16)\n .padStart(12, \"0\")\n .slice(0, 12)\n\n return `${part1}-${part2}-${part3}-${part4}-${part5}`\n}\n\n// ============================================\n// VALIDATION\n// ============================================\n\n/**\n * Validate that at least one identity field is provided.\n * Used by the server SDK to enforce identity requirements.\n *\n * Valid identities:\n * - fingerprint: Device identifier (for anonymous tracking, can be linked later)\n * - email: User's email (definitive identity)\n * - userId: Your system-owned user/contact ID\n * - customerId: Your system-owned customer/account/workspace ID\n * - customerDomain: Public customer/account domain used for billing and attribution\n */\nexport function validateServerIdentity(\n fingerprint?: string,\n email?: string,\n userId?: string,\n customerId?: string,\n customerDomain?: string,\n): void {\n const hasFingerprint = fingerprint && fingerprint.trim().length > 0\n const hasEmail = email && email.trim().length > 0\n const hasUserId = userId && userId.trim().length > 0\n const hasCustomerId = customerId && customerId.trim().length > 0\n const hasCustomerDomain = customerDomain && customerDomain.trim().length > 0\n\n if (!hasFingerprint && !hasEmail && !hasUserId && !hasCustomerId && !hasCustomerDomain) {\n throw new Error(\n \"Server SDK requires at least one of: fingerprint, email, userId, customerId, or customerDomain for track calls. \" +\n \"Use fingerprint/email/userId for user-scoped tracking or customerId/customerDomain for customer-only tracking.\",\n )\n }\n}\n\n/**\n * Validate that at least one customer identifier is provided for billing calls.\n */\nexport function validateCustomerIdentity(\n customerId?: string,\n customerDomain?: string,\n domain?: string,\n stripeCustomerId?: string,\n): void {\n const hasCustomerId = customerId && customerId.trim().length > 0\n const hasCustomerDomain = customerDomain && customerDomain.trim().length > 0\n const hasDomain = domain && domain.trim().length > 0\n const hasStripeCustomerId = stripeCustomerId && stripeCustomerId.trim().length > 0\n\n if (!hasCustomerId && !hasCustomerDomain && !hasDomain && !hasStripeCustomerId) {\n throw new Error(\n \"Billing methods require customerId or customerDomain. \" +\n \"Use customerId for your system-owned account ID and customerDomain for the public account domain.\",\n )\n }\n}\n\n// ============================================\n// AUTO-IDENTIFY: EMAIL & NAME EXTRACTION\n// ============================================\n\n/**\n * Validate that a string looks like a valid email address.\n */\nexport function isValidEmail(value: string): boolean {\n if (!value || typeof value !== \"string\") return false\n // Basic email regex - intentionally permissive to avoid false negatives\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n return emailRegex.test(value.trim())\n}\n\n/**\n * Email field name patterns (case-insensitive, normalized).\n * Order matters - more specific patterns first.\n */\nconst EMAIL_FIELD_PATTERNS = [\n /^e?-?mail$/i,\n /^email[_-]?address$/i,\n /^user[_-]?email$/i,\n /^work[_-]?email$/i,\n /^contact[_-]?email$/i,\n /^primary[_-]?email$/i,\n /^business[_-]?email$/i,\n]\n\n/**\n * Full name field patterns.\n */\nconst FULL_NAME_PATTERNS = [\n /^name$/i,\n /^full[_-]?name$/i,\n /^your[_-]?name$/i,\n /^customer[_-]?name$/i,\n /^contact[_-]?name$/i,\n /^display[_-]?name$/i,\n]\n\n/**\n * First name field patterns.\n */\nconst FIRST_NAME_PATTERNS = [\n /^first[_-]?name$/i,\n /^firstname$/i,\n /^first$/i,\n /^fname$/i,\n /^given[_-]?name$/i,\n /^forename$/i,\n]\n\n/**\n * Last name field patterns.\n */\nconst LAST_NAME_PATTERNS = [\n /^last[_-]?name$/i,\n /^lastname$/i,\n /^last$/i,\n /^lname$/i,\n /^surname$/i,\n /^family[_-]?name$/i,\n]\n\n/**\n * Check if a field name matches any of the given patterns.\n */\nfunction matchesPatterns(fieldName: string, patterns: RegExp[]): boolean {\n const normalized = fieldName.trim()\n return patterns.some((pattern) => pattern.test(normalized))\n}\n\n/**\n * Find an email value from form fields.\n *\n * Priority:\n * 1. Fields with input type=\"email\" (if inputTypes map provided)\n * 2. Field names matching email patterns\n * 3. Any field with a value that looks like an email\n *\n * @param fields - Form field key-value pairs\n * @param inputTypes - Optional map of field names to input types\n * @returns The email value if found, undefined otherwise\n */\nexport function findEmailField(\n fields: Record<string, string>,\n inputTypes?: Map<string, string>,\n): string | undefined {\n // Priority 1: Check fields with type=\"email\"\n if (inputTypes) {\n for (const [fieldName, inputType] of inputTypes.entries()) {\n if (inputType === \"email\") {\n const value = fields[fieldName]\n if (value && isValidEmail(value)) {\n return value.trim()\n }\n }\n }\n }\n\n // Priority 2: Check field names matching email patterns\n for (const [fieldName, value] of Object.entries(fields)) {\n if (matchesPatterns(fieldName, EMAIL_FIELD_PATTERNS) && isValidEmail(value)) {\n return value.trim()\n }\n }\n\n // Priority 3: Any field with email-like value (fallback)\n for (const value of Object.values(fields)) {\n if (isValidEmail(value)) {\n return value.trim()\n }\n }\n\n return undefined\n}\n\n/**\n * Extract name fields from form data.\n *\n * Looks for:\n * - Full name fields (name, full_name, etc.)\n * - First name fields (first_name, fname, etc.)\n * - Last name fields (last_name, lname, etc.)\n *\n * If only first/last names are found, combines them into a full name.\n *\n * @param fields - Form field key-value pairs\n * @returns Object with name, firstName, and/or lastName if found\n */\nexport function findNameFields(fields: Record<string, string>): {\n name?: string\n firstName?: string\n lastName?: string\n} {\n let fullName: string | undefined\n let firstName: string | undefined\n let lastName: string | undefined\n\n for (const [fieldName, value] of Object.entries(fields)) {\n const trimmedValue = value?.trim()\n if (!trimmedValue) continue\n\n // Check for full name\n if (!fullName && matchesPatterns(fieldName, FULL_NAME_PATTERNS)) {\n fullName = trimmedValue\n }\n\n // Check for first name\n if (!firstName && matchesPatterns(fieldName, FIRST_NAME_PATTERNS)) {\n firstName = trimmedValue\n }\n\n // Check for last name\n if (!lastName && matchesPatterns(fieldName, LAST_NAME_PATTERNS)) {\n lastName = trimmedValue\n }\n }\n\n const result: { name?: string; firstName?: string; lastName?: string } = {}\n\n // If we have a full name, use it\n if (fullName) {\n result.name = fullName\n }\n // If we have first and last, combine them\n else if (firstName && lastName) {\n result.name = `${firstName} ${lastName}`\n result.firstName = firstName\n result.lastName = lastName\n }\n // If we only have first name\n else if (firstName) {\n result.firstName = firstName\n }\n // If we only have last name\n else if (lastName) {\n result.lastName = lastName\n }\n\n return result\n}\n\n/**\n * Identity extracted from a form submission.\n */\nexport interface ExtractedIdentity {\n email: string\n name?: string\n firstName?: string\n lastName?: string\n}\n\n/**\n * Extract identity information (email + name) from form fields.\n *\n * Returns undefined if no valid email is found (email is required for identification).\n *\n * @param fields - Form field key-value pairs\n * @param inputTypes - Optional map of field names to input types\n * @returns Extracted identity with email and optional name fields, or undefined\n */\nexport function extractIdentityFromForm(\n fields: Record<string, string>,\n inputTypes?: Map<string, string>,\n): ExtractedIdentity | undefined {\n const email = findEmailField(fields, inputTypes)\n\n // Email is required for identification\n if (!email) {\n return undefined\n }\n\n const nameFields = findNameFields(fields)\n\n return {\n email,\n ...nameFields,\n }\n}\n","import type {\n BillingEvent,\n BillingStatus,\n CalendarEvent,\n CalendarProvider,\n CustomEvent,\n CustomerTraits,\n EngagementEvent,\n ExplicitJourneyStage,\n FormEvent,\n IdentifyEvent,\n IdentifyTraits,\n IngestPayload,\n PageviewEvent,\n PayloadCustomerIdentity,\n PayloadUserIdentity,\n SourceType,\n StageEvent,\n TrackerEvent,\n} from \"./types\"\nimport { extractPathFromUrl, extractUtmParams } from \"./utils\"\n\n// ============================================\n// EVENT BUILDERS\n// ============================================\n\ninterface BaseEventParams {\n url: string\n referrer?: string\n timestamp?: number\n}\n\n/**\n * Build a pageview event.\n */\nexport function buildPageviewEvent(params: BaseEventParams & { title?: string }): PageviewEvent {\n const { url, referrer, timestamp, title } = params\n return {\n type: \"pageview\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n title,\n }\n}\n\n/**\n * Build a form event.\n */\nexport function buildFormEvent(\n params: BaseEventParams & {\n formId?: string\n formFields?: Record<string, string>\n },\n): FormEvent {\n const { url, referrer, timestamp, formId, formFields } = params\n return {\n type: \"form\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n formId,\n formFields,\n }\n}\n\n/**\n * Build an identify event.\n */\nexport function buildIdentifyEvent(\n params: BaseEventParams & {\n email?: string\n userId?: string\n fingerprint?: string\n traits?: IdentifyTraits\n customerId?: string\n customerDomain?: string\n customerTraits?: CustomerTraits\n },\n): IdentifyEvent {\n const {\n url,\n referrer,\n timestamp,\n email,\n userId,\n fingerprint,\n traits,\n customerId,\n customerDomain,\n customerTraits,\n } = params\n return {\n type: \"identify\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n email,\n userId,\n fingerprint,\n customerId,\n customerDomain,\n customerTraits,\n traits,\n }\n}\n\n/**\n * Build a custom event.\n */\nexport function buildCustomEvent(\n params: BaseEventParams & {\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n email?: string\n userId?: string\n fingerprint?: string\n customerId?: string\n customerDomain?: string\n },\n): CustomEvent {\n const {\n url,\n referrer,\n timestamp,\n eventName,\n properties,\n email,\n userId,\n fingerprint,\n customerId,\n customerDomain,\n } = params\n return {\n type: \"custom\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n eventName,\n email,\n userId,\n fingerprint,\n customerId,\n customerDomain,\n properties,\n }\n}\n\n/**\n * Build a calendar booking event.\n */\nexport function buildCalendarEvent(\n params: BaseEventParams & {\n provider: CalendarProvider\n eventType?: string\n startTime?: string\n endTime?: string\n duration?: number\n isRecurring?: boolean\n inviteeEmail?: string\n inviteeName?: string\n },\n): CalendarEvent {\n const {\n url,\n referrer,\n timestamp,\n provider,\n eventType,\n startTime,\n endTime,\n duration,\n isRecurring,\n inviteeEmail,\n inviteeName,\n } = params\n return {\n type: \"calendar\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n provider,\n eventType,\n startTime,\n endTime,\n duration,\n isRecurring,\n inviteeEmail,\n inviteeName,\n }\n}\n\n/**\n * Build an engagement event.\n * Captures active time on page for session analytics.\n */\nexport function buildEngagementEvent(\n params: BaseEventParams & {\n activeTimeMs: number\n totalTimeMs: number\n sessionId: string\n },\n): EngagementEvent {\n const { url, referrer, timestamp, activeTimeMs, totalTimeMs, sessionId } = params\n return {\n type: \"engagement\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n activeTimeMs,\n totalTimeMs,\n sessionId,\n }\n}\n\n/**\n * Build a stage event.\n * Used to explicitly set customer journey stage (activated, engaged, inactive).\n * discovered/signed_up stages are inferred from identify calls.\n */\nexport function buildStageEvent(\n params: BaseEventParams & {\n stage: ExplicitJourneyStage\n properties?: Record<string, string | number | boolean | null>\n },\n): StageEvent {\n const { url, referrer, timestamp, stage, properties } = params\n return {\n type: \"stage\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n stage,\n properties,\n }\n}\n\n/**\n * Build a billing event.\n * Used to set customer billing status (trialing, paid, churned).\n */\nexport function buildBillingEvent(\n params: BaseEventParams & {\n status: BillingStatus\n customerId?: string\n customerDomain?: string\n stripeCustomerId?: string\n domain?: string\n properties?: Record<string, string | number | boolean | null>\n },\n): BillingEvent {\n const {\n url,\n referrer,\n timestamp,\n status,\n customerId,\n customerDomain,\n stripeCustomerId,\n domain,\n properties,\n } = params\n return {\n type: \"billing\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n status,\n customerId,\n customerDomain: customerDomain ?? domain,\n domain: domain ?? customerDomain,\n stripeCustomerId,\n properties,\n }\n}\n\n// ============================================\n// PAYLOAD BUILDER\n// ============================================\n\n/**\n * Build an ingest payload from events.\n *\n * @param visitorId - The anonymous visitor ID from browser cookie/storage\n * @param source - The event source (client, server, integration)\n * @param events - Array of events to send\n * @param userIdentity - Optional user identity for immediate resolution (from setUser in SPA)\n * @param sessionId - Optional session ID for grouping events (browser SDK only)\n * @param fingerprint - Optional device identifier for server-side anonymous tracking\n * @param customerIdentity - Optional customer identity for batch attribution\n */\nexport function buildIngestPayload(\n visitorId: string,\n source: SourceType,\n events: TrackerEvent[],\n userIdentity?: PayloadUserIdentity,\n sessionId?: string,\n fingerprint?: string,\n customerIdentity?: PayloadCustomerIdentity,\n): IngestPayload {\n const payload: IngestPayload = {\n visitorId,\n source,\n events,\n }\n const legacyCustomerIdentity =\n customerIdentity === undefined &&\n userIdentity &&\n (userIdentity.customerId || userIdentity.customerDomain || userIdentity.customerTraits)\n ? {\n ...(userIdentity.customerId && { customerId: userIdentity.customerId }),\n ...(userIdentity.customerDomain && { customerDomain: userIdentity.customerDomain }),\n ...(userIdentity.customerTraits && { customerTraits: userIdentity.customerTraits }),\n }\n : undefined\n const resolvedCustomerIdentity = customerIdentity ?? legacyCustomerIdentity\n\n // Only include fingerprint if provided (server SDK only)\n if (fingerprint) {\n payload.fingerprint = fingerprint\n }\n\n // Only include sessionId if provided (browser SDK only)\n if (sessionId) {\n payload.sessionId = sessionId\n }\n\n // Only include userIdentity if it has actual values\n if (userIdentity && (userIdentity.email || userIdentity.userId || userIdentity.traits)) {\n payload.userIdentity = {\n ...(userIdentity.email && { email: userIdentity.email }),\n ...(userIdentity.userId && { userId: userIdentity.userId }),\n ...(userIdentity.traits && { traits: userIdentity.traits }),\n }\n }\n\n if (\n resolvedCustomerIdentity &&\n (resolvedCustomerIdentity.customerId ||\n resolvedCustomerIdentity.customerDomain ||\n resolvedCustomerIdentity.customerTraits)\n ) {\n payload.customerIdentity = {\n ...(resolvedCustomerIdentity.customerId && {\n customerId: resolvedCustomerIdentity.customerId,\n }),\n ...(resolvedCustomerIdentity.customerDomain && {\n customerDomain: resolvedCustomerIdentity.customerDomain,\n }),\n ...(resolvedCustomerIdentity.customerTraits && {\n customerTraits: resolvedCustomerIdentity.customerTraits,\n }),\n }\n }\n\n return payload\n}\n\n// ============================================\n// BATCH HELPERS\n// ============================================\n\n/**\n * Maximum number of events in a single batch.\n */\nexport const MAX_BATCH_SIZE = 100\n\n/**\n * Split events into batches of MAX_BATCH_SIZE.\n */\nexport function batchEvents(events: TrackerEvent[]): TrackerEvent[][] {\n const batches: TrackerEvent[][] = []\n for (let i = 0; i < events.length; i += MAX_BATCH_SIZE) {\n batches.push(events.slice(i, i + MAX_BATCH_SIZE))\n }\n return batches\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/payload.ts","../src/types.ts","../src/utils.ts"],"sourcesContent":["// Types\n\n// Payload builders\nexport {\n batchEvents,\n buildBillingEvent,\n buildCalendarEvent,\n buildCustomEvent,\n buildEngagementEvent,\n buildFormEvent,\n buildIdentifyEvent,\n buildIngestPayload,\n buildPageviewEvent,\n buildStageEvent,\n MAX_BATCH_SIZE,\n} from \"./payload\"\nexport type {\n BillingEvent,\n BillingStatus,\n BrowserIdentifyOptions,\n BrowserTrackOptions,\n CalendarEvent,\n CalendarProvider,\n CustomEvent,\n CustomerAttribution,\n CustomerIdentifier,\n CustomerTraits,\n EngagementEvent,\n EventType,\n ExplicitJourneyStage,\n FormEvent,\n IdentifyEvent,\n IdentifyTraits,\n IngestPayload,\n IngestResponse,\n PageviewEvent,\n PayloadCustomerIdentity,\n PayloadUserIdentity,\n ServerIdentifyOptions,\n ServerIdentity,\n ServerTrackOptions,\n SourceType,\n StageEvent,\n TrackerConfig,\n TrackerEvent,\n UtmParams,\n} from \"./types\"\n// Constants\nexport { DEFAULT_API_HOST, DEFAULT_DENIED_FORM_FIELDS } from \"./types\"\n\n// Auto-identify types\nexport type { ExtractedIdentity } from \"./utils\"\n// Utilities\nexport {\n extractIdentityFromForm,\n extractPathFromUrl,\n extractUtmParams,\n findEmailField,\n findNameFields,\n isFieldDenied,\n // Auto-identify utilities\n isValidEmail,\n sanitizeFormFields,\n validateCustomerIdentity,\n validateServerIdentity,\n} from \"./utils\"\n","import { v7 as uuidv7 } from \"uuid\"\nimport type {\n BillingEvent,\n BillingStatus,\n CalendarEvent,\n CalendarProvider,\n CustomEvent,\n CustomerTraits,\n EngagementEvent,\n ExplicitJourneyStage,\n FormEvent,\n IdentifyEvent,\n IdentifyTraits,\n IngestPayload,\n PageviewEvent,\n PayloadCustomerIdentity,\n PayloadUserIdentity,\n SourceType,\n StageEvent,\n TrackerEvent,\n} from \"./types\"\nimport { extractPathFromUrl, extractUtmParams } from \"./utils\"\n\n// ============================================\n// EVENT BUILDERS\n// ============================================\n\ninterface BaseEventParams {\n url: string\n referrer?: string\n timestamp?: number\n}\n\n/**\n * Build a pageview event.\n */\nexport function buildPageviewEvent(params: BaseEventParams & { title?: string }): PageviewEvent {\n const { url, referrer, timestamp, title } = params\n return {\n uuid: uuidv7(),\n type: \"pageview\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n title,\n }\n}\n\n/**\n * Build a form event.\n */\nexport function buildFormEvent(\n params: BaseEventParams & {\n formId?: string\n formFields?: Record<string, string>\n },\n): FormEvent {\n const { url, referrer, timestamp, formId, formFields } = params\n return {\n uuid: uuidv7(),\n type: \"form\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n formId,\n formFields,\n }\n}\n\n/**\n * Build an identify event.\n */\nexport function buildIdentifyEvent(\n params: BaseEventParams & {\n email?: string\n userId?: string\n fingerprint?: string\n traits?: IdentifyTraits\n customerId?: string\n customerTraits?: CustomerTraits\n },\n): IdentifyEvent {\n const {\n url,\n referrer,\n timestamp,\n email,\n userId,\n fingerprint,\n traits,\n customerId,\n customerTraits,\n } = params\n return {\n uuid: uuidv7(),\n type: \"identify\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n email,\n userId,\n fingerprint,\n customerId,\n customerTraits,\n traits,\n }\n}\n\n/**\n * Build a custom event.\n */\nexport function buildCustomEvent(\n params: BaseEventParams & {\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n email?: string\n userId?: string\n fingerprint?: string\n customerId?: string\n },\n): CustomEvent {\n const {\n url,\n referrer,\n timestamp,\n eventName,\n properties,\n email,\n userId,\n fingerprint,\n customerId,\n } = params\n return {\n uuid: uuidv7(),\n type: \"custom\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n eventName,\n email,\n userId,\n fingerprint,\n customerId,\n properties,\n }\n}\n\n/**\n * Build a calendar booking event.\n */\nexport function buildCalendarEvent(\n params: BaseEventParams & {\n provider: CalendarProvider\n eventType?: string\n startTime?: string\n endTime?: string\n duration?: number\n isRecurring?: boolean\n inviteeEmail?: string\n inviteeName?: string\n },\n): CalendarEvent {\n const {\n url,\n referrer,\n timestamp,\n provider,\n eventType,\n startTime,\n endTime,\n duration,\n isRecurring,\n inviteeEmail,\n inviteeName,\n } = params\n return {\n uuid: uuidv7(),\n type: \"calendar\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n provider,\n eventType,\n startTime,\n endTime,\n duration,\n isRecurring,\n inviteeEmail,\n inviteeName,\n }\n}\n\n/**\n * Build an engagement event.\n * Captures active time on page for session analytics.\n */\nexport function buildEngagementEvent(\n params: BaseEventParams & {\n activeTimeMs: number\n totalTimeMs: number\n sessionId: string\n },\n): EngagementEvent {\n const { url, referrer, timestamp, activeTimeMs, totalTimeMs, sessionId } = params\n return {\n uuid: uuidv7(),\n type: \"engagement\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n activeTimeMs,\n totalTimeMs,\n sessionId,\n }\n}\n\n/**\n * Build a stage event.\n * Used to explicitly set customer journey stage (activated, engaged, inactive).\n * discovered/signed_up stages are inferred from identify calls.\n */\nexport function buildStageEvent(\n params: BaseEventParams & {\n stage: ExplicitJourneyStage\n properties?: Record<string, string | number | boolean | null>\n },\n): StageEvent {\n const { url, referrer, timestamp, stage, properties } = params\n return {\n uuid: uuidv7(),\n type: \"stage\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n stage,\n properties,\n }\n}\n\n/**\n * Build a billing event.\n * Used to set customer billing status (trialing, paid, churned).\n */\nexport function buildBillingEvent(\n params: BaseEventParams & {\n status: BillingStatus\n customerId?: string\n stripeCustomerId?: string\n properties?: Record<string, string | number | boolean | null>\n },\n): BillingEvent {\n const { url, referrer, timestamp, status, customerId, stripeCustomerId, properties } = params\n return {\n uuid: uuidv7(),\n type: \"billing\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n status,\n customerId,\n stripeCustomerId,\n properties,\n }\n}\n\n// ============================================\n// PAYLOAD BUILDER\n// ============================================\n\n/**\n * Build an ingest payload from events.\n *\n * @param visitorId - The anonymous visitor ID from browser cookie/storage\n * @param source - The event source (client, server, integration)\n * @param events - Array of events to send\n * @param userIdentity - Optional user identity for immediate resolution (from setUser in SPA)\n * @param sessionId - Optional session ID for grouping events (browser SDK only)\n * @param fingerprint - Optional device identifier for server-side anonymous tracking\n * @param customerIdentity - Optional customer identity for batch attribution. `customerId`-only\n * batches are valid and remain provisional until a later identify(email, customerId) call\n * links that external account/workspace to a resolved customer.\n */\nexport function buildIngestPayload(\n visitorId: string,\n source: SourceType,\n events: TrackerEvent[],\n userIdentity?: PayloadUserIdentity,\n sessionId?: string,\n fingerprint?: string,\n customerIdentity?: PayloadCustomerIdentity,\n): IngestPayload {\n const payload: IngestPayload = {\n visitorId,\n source,\n events,\n }\n const legacyCustomerIdentity =\n customerIdentity === undefined &&\n userIdentity &&\n (userIdentity.customerId || userIdentity.customerTraits)\n ? {\n ...(userIdentity.customerId && { customerId: userIdentity.customerId }),\n ...(userIdentity.customerTraits && { customerTraits: userIdentity.customerTraits }),\n }\n : undefined\n const resolvedCustomerIdentity = customerIdentity ?? legacyCustomerIdentity\n\n // Only include fingerprint if provided (server SDK only)\n if (fingerprint) {\n payload.fingerprint = fingerprint\n }\n\n // Only include sessionId if provided (browser SDK only)\n if (sessionId) {\n payload.sessionId = sessionId\n }\n\n // Only include userIdentity if it has actual values\n if (userIdentity && (userIdentity.email || userIdentity.userId || userIdentity.traits)) {\n payload.userIdentity = {\n ...(userIdentity.email && { email: userIdentity.email }),\n ...(userIdentity.userId && { userId: userIdentity.userId }),\n ...(userIdentity.traits && { traits: userIdentity.traits }),\n }\n }\n\n if (\n resolvedCustomerIdentity &&\n (resolvedCustomerIdentity.customerId || resolvedCustomerIdentity.customerTraits)\n ) {\n payload.customerIdentity = {\n ...(resolvedCustomerIdentity.customerId && {\n customerId: resolvedCustomerIdentity.customerId,\n }),\n ...(resolvedCustomerIdentity.customerTraits && {\n customerTraits: resolvedCustomerIdentity.customerTraits,\n }),\n }\n }\n\n return payload\n}\n\n// ============================================\n// BATCH HELPERS\n// ============================================\n\n/**\n * Maximum number of events in a single batch.\n */\nexport const MAX_BATCH_SIZE = 100\n\n/**\n * Split events into batches of MAX_BATCH_SIZE.\n */\nexport function batchEvents(events: TrackerEvent[]): TrackerEvent[][] {\n const batches: TrackerEvent[][] = []\n for (let i = 0; i < events.length; i += MAX_BATCH_SIZE) {\n batches.push(events.slice(i, i + MAX_BATCH_SIZE))\n }\n return batches\n}\n","// ============================================\n// EVENT TYPES\n// ============================================\n\nexport type EventType =\n | \"pageview\"\n | \"form\"\n | \"identify\"\n | \"custom\"\n | \"calendar\"\n | \"engagement\"\n | \"stage\"\n | \"billing\"\n\n// Only explicit stages - discovered/signed_up are inferred from identify calls\nexport type ExplicitJourneyStage = \"activated\" | \"engaged\" | \"inactive\"\n\nexport type BillingStatus = \"trialing\" | \"paid\" | \"churned\"\n\nexport type CalendarProvider = \"cal.com\" | \"calendly\" | \"unknown\"\n\nexport type SourceType = \"client\" | \"server\" | \"integration\"\n\n// ============================================\n// UTM PARAMETERS\n// ============================================\n\nexport interface UtmParams {\n source?: string\n medium?: string\n campaign?: string\n term?: string\n content?: string\n}\n\n// ============================================\n// TRACKER CONFIGURATION\n// ============================================\n\nexport interface TrackerConfig {\n publicKey: string\n apiHost?: string // default: 'https://app.outlit.ai'\n}\n\n// ============================================\n// BROWSER-SPECIFIC TYPES (anonymous allowed)\n// visitorId is auto-managed by the browser SDK\n// ============================================\n\nexport interface BrowserTrackOptions {\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface BrowserIdentifyOptions extends CustomerAttribution {\n email?: string\n /** Your system-owned user/contact ID. */\n userId?: string\n /** User/contact traits. */\n traits?: IdentifyTraits\n /** Customer/account-level traits. */\n customerTraits?: CustomerTraits\n}\n\n// ============================================\n// SERVER-SPECIFIC TYPES\n// Track calls can use user identity, customer attribution, or both.\n// Identify calls remain user-scoped and require email or userId.\n// ============================================\n\n/**\n * Base server-side user identity.\n * Track calls may use customerId without these fields.\n * Identify calls still require email or userId at runtime.\n * `customerId`-only track calls are valid immediately, but they stay provisional\n * until the same customer/account/workspace later appears on identify() with\n * the matching customerId and an email.\n *\n * - fingerprint: Device identifier for anonymous tracking (can be linked later)\n * - email: User's email address (definitive identity, resolves immediately)\n * - userId: Your system-owned user/contact ID\n */\nexport interface ServerIdentity {\n fingerprint?: string\n email?: string\n /** Your system-owned user/contact ID. */\n userId?: string\n}\n\nexport interface CustomerAttribution {\n /** Your system-owned customer/account/workspace ID. */\n customerId?: string\n}\n\n// ============================================\n// TRAITS\n// ============================================\n\nexport interface CustomerTraits {\n /** Customer's billing plan */\n plan?: string\n /** Allow additional custom properties */\n [key: string]: string | number | boolean | null | undefined\n}\n\n/**\n * Traits for identify calls.\n * These are user/contact traits, not customer/account traits.\n */\nexport interface IdentifyTraits {\n /** User-level traits */\n [key: string]: string | number | boolean | null | undefined\n}\n\nexport interface ServerTrackOptions extends ServerIdentity, CustomerAttribution {\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n timestamp?: number\n}\n\nexport interface ServerIdentifyOptions extends ServerIdentity, CustomerAttribution {\n traits?: IdentifyTraits\n customerTraits?: CustomerTraits\n}\n\n/**\n * Customer identity for SDK billing methods.\n * Public billing calls should use `customerId`.\n */\nexport interface CustomerIdentifier extends CustomerAttribution {\n /**\n * @deprecated Stripe customer identifier.\n * Billing attribution should use `customerId` publicly.\n */\n stripeCustomerId?: string\n}\n\n// ============================================\n// INTERNAL EVENT TYPES\n// These are the full event objects sent to the API\n// ============================================\n\ninterface BaseEvent {\n uuid?: string\n type: EventType\n timestamp: number // Unix timestamp in milliseconds\n url: string\n path: string\n referrer?: string\n utm?: UtmParams\n}\n\nexport interface PageviewEvent extends BaseEvent {\n type: \"pageview\"\n title?: string\n}\n\nexport interface FormEvent extends BaseEvent {\n type: \"form\"\n formId?: string\n formFields?: Record<string, string>\n}\n\nexport interface IdentifyEvent extends BaseEvent {\n type: \"identify\"\n email?: string\n userId?: string\n fingerprint?: string\n customerId?: string\n customerTraits?: CustomerTraits\n traits?: IdentifyTraits\n}\n\nexport interface CustomEvent extends BaseEvent {\n type: \"custom\"\n eventName: string\n email?: string\n userId?: string\n fingerprint?: string\n customerId?: string\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface CalendarEvent extends BaseEvent {\n type: \"calendar\"\n provider: CalendarProvider\n eventType?: string // e.g., \"30 Minute Meeting\"\n startTime?: string // ISO timestamp\n endTime?: string // ISO timestamp\n duration?: number // Duration in minutes\n isRecurring?: boolean\n /** Available when identity is passed via webhooks or manual integration */\n inviteeEmail?: string\n inviteeName?: string\n}\n\nexport interface EngagementEvent extends BaseEvent {\n type: \"engagement\"\n /** Time in milliseconds the user was actively engaged (visible tab + user interactions) */\n activeTimeMs: number\n /** Total wall-clock time in milliseconds on the page */\n totalTimeMs: number\n /** Session ID for grouping engagement events. Resets after 30 min of inactivity or tab close. */\n sessionId: string\n}\n\nexport interface StageEvent extends BaseEvent {\n type: \"stage\"\n /** The journey stage to set (only explicit stages, discovered/signed_up are inferred) */\n stage: ExplicitJourneyStage\n /** Optional properties for context */\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface BillingEvent extends BaseEvent {\n type: \"billing\"\n /** The billing status to set for a customer */\n status: BillingStatus\n /** Optional customer identifiers */\n customerId?: string\n stripeCustomerId?: string\n /** Optional properties for context */\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport type TrackerEvent =\n | PageviewEvent\n | FormEvent\n | IdentifyEvent\n | CustomEvent\n | CalendarEvent\n | EngagementEvent\n | StageEvent\n | BillingEvent\n\n// ============================================\n// INGEST PAYLOAD\n// This is what gets sent to the API\n// ============================================\n\n/**\n * User identity for payload-level resolution.\n * Used by browser SDK when user is logged in (via setUser).\n * Customer attribution is carried separately in `customerIdentity`.\n */\nexport interface PayloadUserIdentity {\n email?: string\n /** Your system-owned user/contact ID. */\n userId?: string\n /** User/contact traits. */\n traits?: IdentifyTraits\n /**\n * @deprecated Use payload-level `customerIdentity.customerId` instead.\n * Kept for one compatibility window while callers migrate.\n */\n customerId?: string\n /**\n * @deprecated Use payload-level `customerIdentity.customerTraits` instead.\n * Kept for one compatibility window while callers migrate.\n */\n customerTraits?: CustomerTraits\n}\n\n/**\n * Customer identity for payload-level attribution.\n * Used by browser SDK to attach account/workspace context to a batch.\n * `customerId`-only attribution is valid and remains provisional until a later\n * identify(email, customerId) call links that external account/workspace to a resolved customer.\n */\nexport interface PayloadCustomerIdentity extends CustomerAttribution {\n /** Customer/account traits. */\n customerTraits?: CustomerTraits\n}\n\nexport interface IngestPayload {\n visitorId?: string // Required for pixel, optional for server\n source: SourceType\n events: TrackerEvent[]\n /**\n * Device identifier for anonymous tracking.\n * Events with fingerprint can be linked to users later via identify.\n * Only present for server-side events.\n */\n fingerprint?: string\n /**\n * Session ID for grouping all events in this batch.\n * Only present for browser (client) source events.\n * Used to correlate pageviews, forms, custom events, and engagement\n * within the same browsing session.\n */\n sessionId?: string\n /**\n * User identity for this batch of events.\n * When present, the server can resolve directly to CustomerContact\n * instead of relying on anonymous visitor flow.\n *\n * This is set by the browser SDK when setUser() has been called,\n * allowing immediate identity resolution for SPA/React apps.\n */\n userIdentity?: PayloadUserIdentity\n /**\n * Customer/account identity for this batch of events.\n * Used to attribute browser batches to a customer/workspace even when\n * the customer fields are not present on every event.\n */\n customerIdentity?: PayloadCustomerIdentity\n}\n\n// ============================================\n// API RESPONSE\n// ============================================\n\nexport interface IngestResponse {\n success: boolean\n processed: number\n errors?: Array<{\n index: number\n message: string\n }>\n}\n\n// ============================================\n// CONSTANTS\n// ============================================\n\nexport const DEFAULT_API_HOST = \"https://app.outlit.ai\"\n\n// Re-export for convenience\nexport type { PayloadUserIdentity as UserIdentity }\n\nexport const DEFAULT_DENIED_FORM_FIELDS = [\n \"password\",\n \"passwd\",\n \"pass\",\n \"pwd\",\n \"token\",\n \"secret\",\n \"api_key\",\n \"apikey\",\n \"api-key\",\n \"credit_card\",\n \"creditcard\",\n \"credit-card\",\n \"cc_number\",\n \"ccnumber\",\n \"card_number\",\n \"cardnumber\",\n \"cvv\",\n \"cvc\",\n \"ssn\",\n \"social_security\",\n \"socialsecurity\",\n \"bank_account\",\n \"bankaccount\",\n \"routing_number\",\n \"routingnumber\",\n]\n","import { DEFAULT_DENIED_FORM_FIELDS, type UtmParams } from \"./types\"\n\n// ============================================\n// UTM EXTRACTION\n// ============================================\n\n/**\n * Extract UTM parameters from a URL.\n */\nexport function extractUtmParams(url: string): UtmParams | undefined {\n try {\n const urlObj = new URL(url)\n const params = urlObj.searchParams\n\n const utm: UtmParams = {}\n\n if (params.has(\"utm_source\")) utm.source = params.get(\"utm_source\") ?? undefined\n if (params.has(\"utm_medium\")) utm.medium = params.get(\"utm_medium\") ?? undefined\n if (params.has(\"utm_campaign\")) utm.campaign = params.get(\"utm_campaign\") ?? undefined\n if (params.has(\"utm_term\")) utm.term = params.get(\"utm_term\") ?? undefined\n if (params.has(\"utm_content\")) utm.content = params.get(\"utm_content\") ?? undefined\n\n return Object.keys(utm).length > 0 ? utm : undefined\n } catch (error) {\n console.warn(`[Outlit] Failed to parse URL for UTM extraction: \"${url}\"`, error)\n return undefined\n }\n}\n\n/**\n * Extract path from a URL.\n */\nexport function extractPathFromUrl(url: string): string {\n try {\n const urlObj = new URL(url)\n let path = urlObj.pathname\n const hash = urlObj.hash\n\n // For file:// or custom protocol URLs with no real hostname (common in Electron),\n // the meaningful route is typically stored in the hash fragment.\n if (hash && hash.length > 1 && (!urlObj.hostname || urlObj.protocol === \"file:\")) {\n path = hash.replace(/^#/, \"\")\n }\n // For web hash-routing SPAs (e.g. \"/#/settings\"), prefer hash when the\n // pathname is an SPA entry point.\n else if (\n hash.startsWith(\"#/\") &&\n (urlObj.pathname === \"/\" || urlObj.pathname.endsWith(\"/index.html\"))\n ) {\n path = hash.slice(1)\n }\n\n if (!path) return \"/\"\n if (!path.startsWith(\"/\")) return `/${path}`\n\n return path\n } catch (error) {\n console.warn(\n `[Outlit] Failed to parse URL for path extraction: \"${url}\", defaulting to \"/\"`,\n error,\n )\n return \"/\"\n }\n}\n\n// ============================================\n// FORM FIELD SANITIZATION\n// ============================================\n\n/**\n * Check if a field name should be denied (case-insensitive).\n */\nexport function isFieldDenied(fieldName: string, denylist: string[]): boolean {\n const normalizedName = fieldName.toLowerCase().replace(/[-_\\s]/g, \"\")\n return denylist.some((denied) => {\n const normalizedDenied = denied.toLowerCase().replace(/[-_\\s]/g, \"\")\n return normalizedName.includes(normalizedDenied)\n })\n}\n\n/**\n * Check if a value looks like sensitive data (e.g., credit card number).\n */\nfunction looksLikeSensitiveValue(value: string): boolean {\n // Remove spaces and dashes\n const cleaned = value.replace(/[\\s-]/g, \"\")\n\n // Check for credit card patterns (13-19 digits)\n if (/^\\d{13,19}$/.test(cleaned)) {\n return true\n }\n\n // Check for SSN pattern (9 digits)\n if (/^\\d{9}$/.test(cleaned) || /^\\d{3}-\\d{2}-\\d{4}$/.test(value)) {\n return true\n }\n\n return false\n}\n\n/**\n * Sanitize form fields by removing sensitive data.\n * Returns a new object with denied fields removed.\n */\nexport function sanitizeFormFields(\n fields: Record<string, string> | undefined,\n customDenylist?: string[],\n): Record<string, string> | undefined {\n if (!fields) return undefined\n\n const denylist = customDenylist ?? DEFAULT_DENIED_FORM_FIELDS\n const sanitized: Record<string, string> = {}\n\n for (const [key, value] of Object.entries(fields)) {\n if (!isFieldDenied(key, denylist)) {\n // Also check for credit card patterns in values\n if (!looksLikeSensitiveValue(value)) {\n sanitized[key] = value\n }\n }\n }\n\n return Object.keys(sanitized).length > 0 ? sanitized : undefined\n}\n\n// ============================================\n// VISITOR ID DERIVATION (for server SDK)\n// ============================================\n\n/**\n * Derive a deterministic visitor ID from email and/or userId.\n * This is used by the server SDK to create consistent IDs for API compatibility.\n *\n * Uses a simple hash to create a UUID-like string that will be consistent\n * for the same email/userId combination.\n */\nexport function deriveVisitorIdFromIdentity(email?: string, userId?: string): string {\n const identity = [email?.toLowerCase(), userId].filter(Boolean).join(\"|\")\n if (!identity) {\n throw new Error(\"Either email or userId must be provided\")\n }\n\n // Simple hash function to create a deterministic UUID-like string\n let hash = 0\n for (let i = 0; i < identity.length; i++) {\n const char = identity.charCodeAt(i)\n hash = (hash << 5) - hash + char\n hash = hash & hash // Convert to 32-bit integer\n }\n\n // Convert to hex and format as UUID-like string\n const hex = Math.abs(hash).toString(16).padStart(8, \"0\")\n const part1 = hex.slice(0, 8)\n const part2 = identity.length.toString(16).padStart(4, \"0\")\n const part3 = \"4000\" // Version 4 UUID marker\n const part4 = (((hash >>> 16) & 0x0fff) | 0x8000).toString(16)\n const part5 = Math.abs(hash * 31)\n .toString(16)\n .padStart(12, \"0\")\n .slice(0, 12)\n\n return `${part1}-${part2}-${part3}-${part4}-${part5}`\n}\n\n// ============================================\n// VALIDATION\n// ============================================\n\n/**\n * Validate that at least one identity field is provided.\n * Used by the server SDK to enforce identity requirements.\n *\n * Valid identities:\n * - fingerprint: Device identifier (for anonymous tracking, can be linked later)\n * - email: User's email (definitive identity)\n * - userId: Your system-owned user/contact ID\n * - customerId: Your system-owned customer/account/workspace ID\n */\nexport function validateServerIdentity(\n fingerprint?: string,\n email?: string,\n userId?: string,\n customerId?: string,\n): void {\n const hasFingerprint = fingerprint && fingerprint.trim().length > 0\n const hasEmail = email && email.trim().length > 0\n const hasUserId = userId && userId.trim().length > 0\n const hasCustomerId = customerId && customerId.trim().length > 0\n\n if (!hasFingerprint && !hasEmail && !hasUserId && !hasCustomerId) {\n throw new Error(\n \"Server SDK requires at least one of: fingerprint, email, userId, or customerId for track calls. \" +\n \"Use fingerprint/email/userId for user-scoped tracking or customerId for customer-only tracking.\",\n )\n }\n}\n\n/**\n * Validate that at least one customer identifier is provided for billing calls.\n */\nexport function validateCustomerIdentity(customerId?: string, stripeCustomerId?: string): void {\n const hasCustomerId = customerId && customerId.trim().length > 0\n const hasStripeCustomerId = stripeCustomerId && stripeCustomerId.trim().length > 0\n\n if (!hasCustomerId && !hasStripeCustomerId) {\n throw new Error(\n \"Billing methods require customerId or stripeCustomerId. \" +\n \"Use customerId for your system-owned account ID.\",\n )\n }\n}\n\n// ============================================\n// AUTO-IDENTIFY: EMAIL & NAME EXTRACTION\n// ============================================\n\n/**\n * Validate that a string looks like a valid email address.\n */\nexport function isValidEmail(value: string): boolean {\n if (!value || typeof value !== \"string\") return false\n // Basic email regex - intentionally permissive to avoid false negatives\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n return emailRegex.test(value.trim())\n}\n\n/**\n * Email field name patterns (case-insensitive, normalized).\n * Order matters - more specific patterns first.\n */\nconst EMAIL_FIELD_PATTERNS = [\n /^e?-?mail$/i,\n /^email[_-]?address$/i,\n /^user[_-]?email$/i,\n /^work[_-]?email$/i,\n /^contact[_-]?email$/i,\n /^primary[_-]?email$/i,\n /^business[_-]?email$/i,\n]\n\n/**\n * Full name field patterns.\n */\nconst FULL_NAME_PATTERNS = [\n /^name$/i,\n /^full[_-]?name$/i,\n /^your[_-]?name$/i,\n /^customer[_-]?name$/i,\n /^contact[_-]?name$/i,\n /^display[_-]?name$/i,\n]\n\n/**\n * First name field patterns.\n */\nconst FIRST_NAME_PATTERNS = [\n /^first[_-]?name$/i,\n /^firstname$/i,\n /^first$/i,\n /^fname$/i,\n /^given[_-]?name$/i,\n /^forename$/i,\n]\n\n/**\n * Last name field patterns.\n */\nconst LAST_NAME_PATTERNS = [\n /^last[_-]?name$/i,\n /^lastname$/i,\n /^last$/i,\n /^lname$/i,\n /^surname$/i,\n /^family[_-]?name$/i,\n]\n\n/**\n * Check if a field name matches any of the given patterns.\n */\nfunction matchesPatterns(fieldName: string, patterns: RegExp[]): boolean {\n const normalized = fieldName.trim()\n return patterns.some((pattern) => pattern.test(normalized))\n}\n\n/**\n * Find an email value from form fields.\n *\n * Priority:\n * 1. Fields with input type=\"email\" (if inputTypes map provided)\n * 2. Field names matching email patterns\n * 3. Any field with a value that looks like an email\n *\n * @param fields - Form field key-value pairs\n * @param inputTypes - Optional map of field names to input types\n * @returns The email value if found, undefined otherwise\n */\nexport function findEmailField(\n fields: Record<string, string>,\n inputTypes?: Map<string, string>,\n): string | undefined {\n // Priority 1: Check fields with type=\"email\"\n if (inputTypes) {\n for (const [fieldName, inputType] of inputTypes.entries()) {\n if (inputType === \"email\") {\n const value = fields[fieldName]\n if (value && isValidEmail(value)) {\n return value.trim()\n }\n }\n }\n }\n\n // Priority 2: Check field names matching email patterns\n for (const [fieldName, value] of Object.entries(fields)) {\n if (matchesPatterns(fieldName, EMAIL_FIELD_PATTERNS) && isValidEmail(value)) {\n return value.trim()\n }\n }\n\n // Priority 3: Any field with email-like value (fallback)\n for (const value of Object.values(fields)) {\n if (isValidEmail(value)) {\n return value.trim()\n }\n }\n\n return undefined\n}\n\n/**\n * Extract name fields from form data.\n *\n * Looks for:\n * - Full name fields (name, full_name, etc.)\n * - First name fields (first_name, fname, etc.)\n * - Last name fields (last_name, lname, etc.)\n *\n * If only first/last names are found, combines them into a full name.\n *\n * @param fields - Form field key-value pairs\n * @returns Object with name, firstName, and/or lastName if found\n */\nexport function findNameFields(fields: Record<string, string>): {\n name?: string\n firstName?: string\n lastName?: string\n} {\n let fullName: string | undefined\n let firstName: string | undefined\n let lastName: string | undefined\n\n for (const [fieldName, value] of Object.entries(fields)) {\n const trimmedValue = value?.trim()\n if (!trimmedValue) continue\n\n // Check for full name\n if (!fullName && matchesPatterns(fieldName, FULL_NAME_PATTERNS)) {\n fullName = trimmedValue\n }\n\n // Check for first name\n if (!firstName && matchesPatterns(fieldName, FIRST_NAME_PATTERNS)) {\n firstName = trimmedValue\n }\n\n // Check for last name\n if (!lastName && matchesPatterns(fieldName, LAST_NAME_PATTERNS)) {\n lastName = trimmedValue\n }\n }\n\n const result: { name?: string; firstName?: string; lastName?: string } = {}\n\n // If we have a full name, use it\n if (fullName) {\n result.name = fullName\n }\n // If we have first and last, combine them\n else if (firstName && lastName) {\n result.name = `${firstName} ${lastName}`\n result.firstName = firstName\n result.lastName = lastName\n }\n // If we only have first name\n else if (firstName) {\n result.firstName = firstName\n }\n // If we only have last name\n else if (lastName) {\n result.lastName = lastName\n }\n\n return result\n}\n\n/**\n * Identity extracted from a form submission.\n */\nexport interface ExtractedIdentity {\n email: string\n name?: string\n firstName?: string\n lastName?: string\n}\n\n/**\n * Extract identity information (email + name) from form fields.\n *\n * Returns undefined if no valid email is found (email is required for identification).\n *\n * @param fields - Form field key-value pairs\n * @param inputTypes - Optional map of field names to input types\n * @returns Extracted identity with email and optional name fields, or undefined\n */\nexport function extractIdentityFromForm(\n fields: Record<string, string>,\n inputTypes?: Map<string, string>,\n): ExtractedIdentity | undefined {\n const email = findEmailField(fields, inputTypes)\n\n // Email is required for identification\n if (!email) {\n return undefined\n }\n\n const nameFields = findNameFields(fields)\n\n return {\n email,\n ...nameFields,\n }\n}\n"],"mappings":"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","names":["uuidv7"]}
package/dist/index.mjs CHANGED
@@ -1,3 +1,6 @@
1
+ // src/payload.ts
2
+ import { v7 as uuidv7 } from "uuid";
3
+
1
4
  // src/types.ts
2
5
  var DEFAULT_API_HOST = "https://app.outlit.ai";
3
6
  var DEFAULT_DENIED_FORM_FIELDS = [
@@ -96,26 +99,23 @@ function sanitizeFormFields(fields, customDenylist) {
96
99
  }
97
100
  return Object.keys(sanitized).length > 0 ? sanitized : void 0;
98
101
  }
99
- function validateServerIdentity(fingerprint, email, userId, customerId, customerDomain) {
102
+ function validateServerIdentity(fingerprint, email, userId, customerId) {
100
103
  const hasFingerprint = fingerprint && fingerprint.trim().length > 0;
101
104
  const hasEmail = email && email.trim().length > 0;
102
105
  const hasUserId = userId && userId.trim().length > 0;
103
106
  const hasCustomerId = customerId && customerId.trim().length > 0;
104
- const hasCustomerDomain = customerDomain && customerDomain.trim().length > 0;
105
- if (!hasFingerprint && !hasEmail && !hasUserId && !hasCustomerId && !hasCustomerDomain) {
107
+ if (!hasFingerprint && !hasEmail && !hasUserId && !hasCustomerId) {
106
108
  throw new Error(
107
- "Server SDK requires at least one of: fingerprint, email, userId, customerId, or customerDomain for track calls. Use fingerprint/email/userId for user-scoped tracking or customerId/customerDomain for customer-only tracking."
109
+ "Server SDK requires at least one of: fingerprint, email, userId, or customerId for track calls. Use fingerprint/email/userId for user-scoped tracking or customerId for customer-only tracking."
108
110
  );
109
111
  }
110
112
  }
111
- function validateCustomerIdentity(customerId, customerDomain, domain, stripeCustomerId) {
113
+ function validateCustomerIdentity(customerId, stripeCustomerId) {
112
114
  const hasCustomerId = customerId && customerId.trim().length > 0;
113
- const hasCustomerDomain = customerDomain && customerDomain.trim().length > 0;
114
- const hasDomain = domain && domain.trim().length > 0;
115
115
  const hasStripeCustomerId = stripeCustomerId && stripeCustomerId.trim().length > 0;
116
- if (!hasCustomerId && !hasCustomerDomain && !hasDomain && !hasStripeCustomerId) {
116
+ if (!hasCustomerId && !hasStripeCustomerId) {
117
117
  throw new Error(
118
- "Billing methods require customerId or customerDomain. Use customerId for your system-owned account ID and customerDomain for the public account domain."
118
+ "Billing methods require customerId or stripeCustomerId. Use customerId for your system-owned account ID."
119
119
  );
120
120
  }
121
121
  }
@@ -231,6 +231,7 @@ function extractIdentityFromForm(fields, inputTypes) {
231
231
  function buildPageviewEvent(params) {
232
232
  const { url, referrer, timestamp, title } = params;
233
233
  return {
234
+ uuid: uuidv7(),
234
235
  type: "pageview",
235
236
  timestamp: timestamp ?? Date.now(),
236
237
  url,
@@ -243,6 +244,7 @@ function buildPageviewEvent(params) {
243
244
  function buildFormEvent(params) {
244
245
  const { url, referrer, timestamp, formId, formFields } = params;
245
246
  return {
247
+ uuid: uuidv7(),
246
248
  type: "form",
247
249
  timestamp: timestamp ?? Date.now(),
248
250
  url,
@@ -263,10 +265,10 @@ function buildIdentifyEvent(params) {
263
265
  fingerprint,
264
266
  traits,
265
267
  customerId,
266
- customerDomain,
267
268
  customerTraits
268
269
  } = params;
269
270
  return {
271
+ uuid: uuidv7(),
270
272
  type: "identify",
271
273
  timestamp: timestamp ?? Date.now(),
272
274
  url,
@@ -277,7 +279,6 @@ function buildIdentifyEvent(params) {
277
279
  userId,
278
280
  fingerprint,
279
281
  customerId,
280
- customerDomain,
281
282
  customerTraits,
282
283
  traits
283
284
  };
@@ -292,10 +293,10 @@ function buildCustomEvent(params) {
292
293
  email,
293
294
  userId,
294
295
  fingerprint,
295
- customerId,
296
- customerDomain
296
+ customerId
297
297
  } = params;
298
298
  return {
299
+ uuid: uuidv7(),
299
300
  type: "custom",
300
301
  timestamp: timestamp ?? Date.now(),
301
302
  url,
@@ -307,7 +308,6 @@ function buildCustomEvent(params) {
307
308
  userId,
308
309
  fingerprint,
309
310
  customerId,
310
- customerDomain,
311
311
  properties
312
312
  };
313
313
  }
@@ -326,6 +326,7 @@ function buildCalendarEvent(params) {
326
326
  inviteeName
327
327
  } = params;
328
328
  return {
329
+ uuid: uuidv7(),
329
330
  type: "calendar",
330
331
  timestamp: timestamp ?? Date.now(),
331
332
  url,
@@ -345,6 +346,7 @@ function buildCalendarEvent(params) {
345
346
  function buildEngagementEvent(params) {
346
347
  const { url, referrer, timestamp, activeTimeMs, totalTimeMs, sessionId } = params;
347
348
  return {
349
+ uuid: uuidv7(),
348
350
  type: "engagement",
349
351
  timestamp: timestamp ?? Date.now(),
350
352
  url,
@@ -359,6 +361,7 @@ function buildEngagementEvent(params) {
359
361
  function buildStageEvent(params) {
360
362
  const { url, referrer, timestamp, stage, properties } = params;
361
363
  return {
364
+ uuid: uuidv7(),
362
365
  type: "stage",
363
366
  timestamp: timestamp ?? Date.now(),
364
367
  url,
@@ -370,18 +373,9 @@ function buildStageEvent(params) {
370
373
  };
371
374
  }
372
375
  function buildBillingEvent(params) {
373
- const {
374
- url,
375
- referrer,
376
- timestamp,
377
- status,
378
- customerId,
379
- customerDomain,
380
- stripeCustomerId,
381
- domain,
382
- properties
383
- } = params;
376
+ const { url, referrer, timestamp, status, customerId, stripeCustomerId, properties } = params;
384
377
  return {
378
+ uuid: uuidv7(),
385
379
  type: "billing",
386
380
  timestamp: timestamp ?? Date.now(),
387
381
  url,
@@ -390,8 +384,6 @@ function buildBillingEvent(params) {
390
384
  utm: extractUtmParams(url),
391
385
  status,
392
386
  customerId,
393
- customerDomain: customerDomain ?? domain,
394
- domain: domain ?? customerDomain,
395
387
  stripeCustomerId,
396
388
  properties
397
389
  };
@@ -402,9 +394,8 @@ function buildIngestPayload(visitorId, source, events, userIdentity, sessionId,
402
394
  source,
403
395
  events
404
396
  };
405
- const legacyCustomerIdentity = customerIdentity === void 0 && userIdentity && (userIdentity.customerId || userIdentity.customerDomain || userIdentity.customerTraits) ? {
397
+ const legacyCustomerIdentity = customerIdentity === void 0 && userIdentity && (userIdentity.customerId || userIdentity.customerTraits) ? {
406
398
  ...userIdentity.customerId && { customerId: userIdentity.customerId },
407
- ...userIdentity.customerDomain && { customerDomain: userIdentity.customerDomain },
408
399
  ...userIdentity.customerTraits && { customerTraits: userIdentity.customerTraits }
409
400
  } : void 0;
410
401
  const resolvedCustomerIdentity = customerIdentity ?? legacyCustomerIdentity;
@@ -421,14 +412,11 @@ function buildIngestPayload(visitorId, source, events, userIdentity, sessionId,
421
412
  ...userIdentity.traits && { traits: userIdentity.traits }
422
413
  };
423
414
  }
424
- if (resolvedCustomerIdentity && (resolvedCustomerIdentity.customerId || resolvedCustomerIdentity.customerDomain || resolvedCustomerIdentity.customerTraits)) {
415
+ if (resolvedCustomerIdentity && (resolvedCustomerIdentity.customerId || resolvedCustomerIdentity.customerTraits)) {
425
416
  payload.customerIdentity = {
426
417
  ...resolvedCustomerIdentity.customerId && {
427
418
  customerId: resolvedCustomerIdentity.customerId
428
419
  },
429
- ...resolvedCustomerIdentity.customerDomain && {
430
- customerDomain: resolvedCustomerIdentity.customerDomain
431
- },
432
420
  ...resolvedCustomerIdentity.customerTraits && {
433
421
  customerTraits: resolvedCustomerIdentity.customerTraits
434
422
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/utils.ts","../src/payload.ts"],"sourcesContent":["// ============================================\n// EVENT TYPES\n// ============================================\n\nexport type EventType =\n | \"pageview\"\n | \"form\"\n | \"identify\"\n | \"custom\"\n | \"calendar\"\n | \"engagement\"\n | \"stage\"\n | \"billing\"\n\n// Only explicit stages - discovered/signed_up are inferred from identify calls\nexport type ExplicitJourneyStage = \"activated\" | \"engaged\" | \"inactive\"\n\nexport type BillingStatus = \"trialing\" | \"paid\" | \"churned\"\n\nexport type CalendarProvider = \"cal.com\" | \"calendly\" | \"unknown\"\n\nexport type SourceType = \"client\" | \"server\" | \"integration\"\n\n// ============================================\n// UTM PARAMETERS\n// ============================================\n\nexport interface UtmParams {\n source?: string\n medium?: string\n campaign?: string\n term?: string\n content?: string\n}\n\n// ============================================\n// TRACKER CONFIGURATION\n// ============================================\n\nexport interface TrackerConfig {\n publicKey: string\n apiHost?: string // default: 'https://app.outlit.ai'\n}\n\n// ============================================\n// BROWSER-SPECIFIC TYPES (anonymous allowed)\n// visitorId is auto-managed by the browser SDK\n// ============================================\n\nexport interface BrowserTrackOptions {\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface BrowserIdentifyOptions extends CustomerAttribution {\n email?: string\n /** Your system-owned user/contact ID. */\n userId?: string\n /** User/contact traits. */\n traits?: IdentifyTraits\n /** Customer/account-level traits. */\n customerTraits?: CustomerTraits\n}\n\n// ============================================\n// SERVER-SPECIFIC TYPES\n// Track calls can use user identity, customer attribution, or both.\n// Identify calls remain user-scoped and require email or userId.\n// ============================================\n\n/**\n * Base server-side user identity.\n * Track calls may use customerId/customerDomain without these fields.\n * Identify calls still require email or userId at runtime.\n *\n * - fingerprint: Device identifier for anonymous tracking (can be linked later)\n * - email: User's email address (definitive identity, resolves immediately)\n * - userId: Your system-owned user/contact ID\n */\nexport interface ServerIdentity {\n fingerprint?: string\n email?: string\n /** Your system-owned user/contact ID. */\n userId?: string\n}\n\nexport interface CustomerAttribution {\n /** Your system-owned customer/account/workspace ID. */\n customerId?: string\n /** Public customer/account domain used for billing and attribution. */\n customerDomain?: string\n}\n\n// ============================================\n// TRAITS\n// ============================================\n\nexport interface CustomerTraits {\n /** Customer's billing plan */\n plan?: string\n /** Allow additional custom properties */\n [key: string]: string | number | boolean | null | undefined\n}\n\n/**\n * Traits for identify calls.\n * These are user/contact traits, not customer/account traits.\n */\nexport interface IdentifyTraits {\n /** User-level traits */\n [key: string]: string | number | boolean | null | undefined\n}\n\nexport interface ServerTrackOptions extends ServerIdentity, CustomerAttribution {\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n timestamp?: number\n}\n\nexport interface ServerIdentifyOptions extends ServerIdentity, CustomerAttribution {\n traits?: IdentifyTraits\n customerTraits?: CustomerTraits\n}\n\n/**\n * Customer identity for SDK billing methods.\n * Public billing calls should use `customerId` and/or `customerDomain`.\n */\nexport interface CustomerIdentifier extends CustomerAttribution {\n /**\n * @deprecated Use `customerDomain` instead.\n * Legacy alias kept for backward compatibility.\n */\n domain?: string\n /**\n * @deprecated Stripe customer identifier.\n * Billing attribution should use `customerId` and/or `customerDomain` publicly.\n */\n stripeCustomerId?: string\n}\n\n// ============================================\n// INTERNAL EVENT TYPES\n// These are the full event objects sent to the API\n// ============================================\n\ninterface BaseEvent {\n type: EventType\n timestamp: number // Unix timestamp in milliseconds\n url: string\n path: string\n referrer?: string\n utm?: UtmParams\n}\n\nexport interface PageviewEvent extends BaseEvent {\n type: \"pageview\"\n title?: string\n}\n\nexport interface FormEvent extends BaseEvent {\n type: \"form\"\n formId?: string\n formFields?: Record<string, string>\n}\n\nexport interface IdentifyEvent extends BaseEvent {\n type: \"identify\"\n email?: string\n userId?: string\n fingerprint?: string\n customerId?: string\n customerDomain?: string\n customerTraits?: CustomerTraits\n traits?: IdentifyTraits\n}\n\nexport interface CustomEvent extends BaseEvent {\n type: \"custom\"\n eventName: string\n email?: string\n userId?: string\n fingerprint?: string\n customerId?: string\n customerDomain?: string\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface CalendarEvent extends BaseEvent {\n type: \"calendar\"\n provider: CalendarProvider\n eventType?: string // e.g., \"30 Minute Meeting\"\n startTime?: string // ISO timestamp\n endTime?: string // ISO timestamp\n duration?: number // Duration in minutes\n isRecurring?: boolean\n /** Available when identity is passed via webhooks or manual integration */\n inviteeEmail?: string\n inviteeName?: string\n}\n\nexport interface EngagementEvent extends BaseEvent {\n type: \"engagement\"\n /** Time in milliseconds the user was actively engaged (visible tab + user interactions) */\n activeTimeMs: number\n /** Total wall-clock time in milliseconds on the page */\n totalTimeMs: number\n /** Session ID for grouping engagement events. Resets after 30 min of inactivity or tab close. */\n sessionId: string\n}\n\nexport interface StageEvent extends BaseEvent {\n type: \"stage\"\n /** The journey stage to set (only explicit stages, discovered/signed_up are inferred) */\n stage: ExplicitJourneyStage\n /** Optional properties for context */\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface BillingEvent extends BaseEvent {\n type: \"billing\"\n /** The billing status to set for a customer */\n status: BillingStatus\n /** Optional customer identifiers */\n customerId?: string\n customerDomain?: string\n /** @deprecated Legacy alias for `customerDomain`. */\n domain?: string\n stripeCustomerId?: string\n /** Optional properties for context */\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport type TrackerEvent =\n | PageviewEvent\n | FormEvent\n | IdentifyEvent\n | CustomEvent\n | CalendarEvent\n | EngagementEvent\n | StageEvent\n | BillingEvent\n\n// ============================================\n// INGEST PAYLOAD\n// This is what gets sent to the API\n// ============================================\n\n/**\n * User identity for payload-level resolution.\n * Used by browser SDK when user is logged in (via setUser).\n * Customer attribution is carried separately in `customerIdentity`.\n */\nexport interface PayloadUserIdentity {\n email?: string\n /** Your system-owned user/contact ID. */\n userId?: string\n /** User/contact traits. */\n traits?: IdentifyTraits\n /**\n * @deprecated Use payload-level `customerIdentity.customerId` instead.\n * Kept for one compatibility window while callers migrate.\n */\n customerId?: string\n /**\n * @deprecated Use payload-level `customerIdentity.customerDomain` instead.\n * Kept for one compatibility window while callers migrate.\n */\n customerDomain?: string\n /**\n * @deprecated Use payload-level `customerIdentity.customerTraits` instead.\n * Kept for one compatibility window while callers migrate.\n */\n customerTraits?: CustomerTraits\n}\n\n/**\n * Customer identity for payload-level attribution.\n * Used by browser SDK to attach account/workspace context to a batch.\n */\nexport interface PayloadCustomerIdentity extends CustomerAttribution {\n /** Customer/account traits. */\n customerTraits?: CustomerTraits\n}\n\nexport interface IngestPayload {\n visitorId?: string // Required for pixel, optional for server\n source: SourceType\n events: TrackerEvent[]\n /**\n * Device identifier for anonymous tracking.\n * Events with fingerprint can be linked to users later via identify.\n * Only present for server-side events.\n */\n fingerprint?: string\n /**\n * Session ID for grouping all events in this batch.\n * Only present for browser (client) source events.\n * Used to correlate pageviews, forms, custom events, and engagement\n * within the same browsing session.\n */\n sessionId?: string\n /**\n * User identity for this batch of events.\n * When present, the server can resolve directly to CustomerContact\n * instead of relying on anonymous visitor flow.\n *\n * This is set by the browser SDK when setUser() has been called,\n * allowing immediate identity resolution for SPA/React apps.\n */\n userIdentity?: PayloadUserIdentity\n /**\n * Customer/account identity for this batch of events.\n * Used to attribute browser batches to a customer/workspace even when\n * the customer fields are not present on every event.\n */\n customerIdentity?: PayloadCustomerIdentity\n}\n\n// ============================================\n// API RESPONSE\n// ============================================\n\nexport interface IngestResponse {\n success: boolean\n processed: number\n errors?: Array<{\n index: number\n message: string\n }>\n}\n\n// ============================================\n// CONSTANTS\n// ============================================\n\nexport const DEFAULT_API_HOST = \"https://app.outlit.ai\"\n\n// Re-export for convenience\nexport type { PayloadUserIdentity as UserIdentity }\n\nexport const DEFAULT_DENIED_FORM_FIELDS = [\n \"password\",\n \"passwd\",\n \"pass\",\n \"pwd\",\n \"token\",\n \"secret\",\n \"api_key\",\n \"apikey\",\n \"api-key\",\n \"credit_card\",\n \"creditcard\",\n \"credit-card\",\n \"cc_number\",\n \"ccnumber\",\n \"card_number\",\n \"cardnumber\",\n \"cvv\",\n \"cvc\",\n \"ssn\",\n \"social_security\",\n \"socialsecurity\",\n \"bank_account\",\n \"bankaccount\",\n \"routing_number\",\n \"routingnumber\",\n]\n","import { DEFAULT_DENIED_FORM_FIELDS, type UtmParams } from \"./types\"\n\n// ============================================\n// UTM EXTRACTION\n// ============================================\n\n/**\n * Extract UTM parameters from a URL.\n */\nexport function extractUtmParams(url: string): UtmParams | undefined {\n try {\n const urlObj = new URL(url)\n const params = urlObj.searchParams\n\n const utm: UtmParams = {}\n\n if (params.has(\"utm_source\")) utm.source = params.get(\"utm_source\") ?? undefined\n if (params.has(\"utm_medium\")) utm.medium = params.get(\"utm_medium\") ?? undefined\n if (params.has(\"utm_campaign\")) utm.campaign = params.get(\"utm_campaign\") ?? undefined\n if (params.has(\"utm_term\")) utm.term = params.get(\"utm_term\") ?? undefined\n if (params.has(\"utm_content\")) utm.content = params.get(\"utm_content\") ?? undefined\n\n return Object.keys(utm).length > 0 ? utm : undefined\n } catch (error) {\n console.warn(`[Outlit] Failed to parse URL for UTM extraction: \"${url}\"`, error)\n return undefined\n }\n}\n\n/**\n * Extract path from a URL.\n */\nexport function extractPathFromUrl(url: string): string {\n try {\n const urlObj = new URL(url)\n let path = urlObj.pathname\n const hash = urlObj.hash\n\n // For file:// or custom protocol URLs with no real hostname (common in Electron),\n // the meaningful route is typically stored in the hash fragment.\n if (hash && hash.length > 1 && (!urlObj.hostname || urlObj.protocol === \"file:\")) {\n path = hash.replace(/^#/, \"\")\n }\n // For web hash-routing SPAs (e.g. \"/#/settings\"), prefer hash when the\n // pathname is an SPA entry point.\n else if (\n hash.startsWith(\"#/\") &&\n (urlObj.pathname === \"/\" || urlObj.pathname.endsWith(\"/index.html\"))\n ) {\n path = hash.slice(1)\n }\n\n if (!path) return \"/\"\n if (!path.startsWith(\"/\")) return `/${path}`\n\n return path\n } catch (error) {\n console.warn(\n `[Outlit] Failed to parse URL for path extraction: \"${url}\", defaulting to \"/\"`,\n error,\n )\n return \"/\"\n }\n}\n\n// ============================================\n// FORM FIELD SANITIZATION\n// ============================================\n\n/**\n * Check if a field name should be denied (case-insensitive).\n */\nexport function isFieldDenied(fieldName: string, denylist: string[]): boolean {\n const normalizedName = fieldName.toLowerCase().replace(/[-_\\s]/g, \"\")\n return denylist.some((denied) => {\n const normalizedDenied = denied.toLowerCase().replace(/[-_\\s]/g, \"\")\n return normalizedName.includes(normalizedDenied)\n })\n}\n\n/**\n * Check if a value looks like sensitive data (e.g., credit card number).\n */\nfunction looksLikeSensitiveValue(value: string): boolean {\n // Remove spaces and dashes\n const cleaned = value.replace(/[\\s-]/g, \"\")\n\n // Check for credit card patterns (13-19 digits)\n if (/^\\d{13,19}$/.test(cleaned)) {\n return true\n }\n\n // Check for SSN pattern (9 digits)\n if (/^\\d{9}$/.test(cleaned) || /^\\d{3}-\\d{2}-\\d{4}$/.test(value)) {\n return true\n }\n\n return false\n}\n\n/**\n * Sanitize form fields by removing sensitive data.\n * Returns a new object with denied fields removed.\n */\nexport function sanitizeFormFields(\n fields: Record<string, string> | undefined,\n customDenylist?: string[],\n): Record<string, string> | undefined {\n if (!fields) return undefined\n\n const denylist = customDenylist ?? DEFAULT_DENIED_FORM_FIELDS\n const sanitized: Record<string, string> = {}\n\n for (const [key, value] of Object.entries(fields)) {\n if (!isFieldDenied(key, denylist)) {\n // Also check for credit card patterns in values\n if (!looksLikeSensitiveValue(value)) {\n sanitized[key] = value\n }\n }\n }\n\n return Object.keys(sanitized).length > 0 ? sanitized : undefined\n}\n\n// ============================================\n// VISITOR ID DERIVATION (for server SDK)\n// ============================================\n\n/**\n * Derive a deterministic visitor ID from email and/or userId.\n * This is used by the server SDK to create consistent IDs for API compatibility.\n *\n * Uses a simple hash to create a UUID-like string that will be consistent\n * for the same email/userId combination.\n */\nexport function deriveVisitorIdFromIdentity(email?: string, userId?: string): string {\n const identity = [email?.toLowerCase(), userId].filter(Boolean).join(\"|\")\n if (!identity) {\n throw new Error(\"Either email or userId must be provided\")\n }\n\n // Simple hash function to create a deterministic UUID-like string\n let hash = 0\n for (let i = 0; i < identity.length; i++) {\n const char = identity.charCodeAt(i)\n hash = (hash << 5) - hash + char\n hash = hash & hash // Convert to 32-bit integer\n }\n\n // Convert to hex and format as UUID-like string\n const hex = Math.abs(hash).toString(16).padStart(8, \"0\")\n const part1 = hex.slice(0, 8)\n const part2 = identity.length.toString(16).padStart(4, \"0\")\n const part3 = \"4000\" // Version 4 UUID marker\n const part4 = (((hash >>> 16) & 0x0fff) | 0x8000).toString(16)\n const part5 = Math.abs(hash * 31)\n .toString(16)\n .padStart(12, \"0\")\n .slice(0, 12)\n\n return `${part1}-${part2}-${part3}-${part4}-${part5}`\n}\n\n// ============================================\n// VALIDATION\n// ============================================\n\n/**\n * Validate that at least one identity field is provided.\n * Used by the server SDK to enforce identity requirements.\n *\n * Valid identities:\n * - fingerprint: Device identifier (for anonymous tracking, can be linked later)\n * - email: User's email (definitive identity)\n * - userId: Your system-owned user/contact ID\n * - customerId: Your system-owned customer/account/workspace ID\n * - customerDomain: Public customer/account domain used for billing and attribution\n */\nexport function validateServerIdentity(\n fingerprint?: string,\n email?: string,\n userId?: string,\n customerId?: string,\n customerDomain?: string,\n): void {\n const hasFingerprint = fingerprint && fingerprint.trim().length > 0\n const hasEmail = email && email.trim().length > 0\n const hasUserId = userId && userId.trim().length > 0\n const hasCustomerId = customerId && customerId.trim().length > 0\n const hasCustomerDomain = customerDomain && customerDomain.trim().length > 0\n\n if (!hasFingerprint && !hasEmail && !hasUserId && !hasCustomerId && !hasCustomerDomain) {\n throw new Error(\n \"Server SDK requires at least one of: fingerprint, email, userId, customerId, or customerDomain for track calls. \" +\n \"Use fingerprint/email/userId for user-scoped tracking or customerId/customerDomain for customer-only tracking.\",\n )\n }\n}\n\n/**\n * Validate that at least one customer identifier is provided for billing calls.\n */\nexport function validateCustomerIdentity(\n customerId?: string,\n customerDomain?: string,\n domain?: string,\n stripeCustomerId?: string,\n): void {\n const hasCustomerId = customerId && customerId.trim().length > 0\n const hasCustomerDomain = customerDomain && customerDomain.trim().length > 0\n const hasDomain = domain && domain.trim().length > 0\n const hasStripeCustomerId = stripeCustomerId && stripeCustomerId.trim().length > 0\n\n if (!hasCustomerId && !hasCustomerDomain && !hasDomain && !hasStripeCustomerId) {\n throw new Error(\n \"Billing methods require customerId or customerDomain. \" +\n \"Use customerId for your system-owned account ID and customerDomain for the public account domain.\",\n )\n }\n}\n\n// ============================================\n// AUTO-IDENTIFY: EMAIL & NAME EXTRACTION\n// ============================================\n\n/**\n * Validate that a string looks like a valid email address.\n */\nexport function isValidEmail(value: string): boolean {\n if (!value || typeof value !== \"string\") return false\n // Basic email regex - intentionally permissive to avoid false negatives\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n return emailRegex.test(value.trim())\n}\n\n/**\n * Email field name patterns (case-insensitive, normalized).\n * Order matters - more specific patterns first.\n */\nconst EMAIL_FIELD_PATTERNS = [\n /^e?-?mail$/i,\n /^email[_-]?address$/i,\n /^user[_-]?email$/i,\n /^work[_-]?email$/i,\n /^contact[_-]?email$/i,\n /^primary[_-]?email$/i,\n /^business[_-]?email$/i,\n]\n\n/**\n * Full name field patterns.\n */\nconst FULL_NAME_PATTERNS = [\n /^name$/i,\n /^full[_-]?name$/i,\n /^your[_-]?name$/i,\n /^customer[_-]?name$/i,\n /^contact[_-]?name$/i,\n /^display[_-]?name$/i,\n]\n\n/**\n * First name field patterns.\n */\nconst FIRST_NAME_PATTERNS = [\n /^first[_-]?name$/i,\n /^firstname$/i,\n /^first$/i,\n /^fname$/i,\n /^given[_-]?name$/i,\n /^forename$/i,\n]\n\n/**\n * Last name field patterns.\n */\nconst LAST_NAME_PATTERNS = [\n /^last[_-]?name$/i,\n /^lastname$/i,\n /^last$/i,\n /^lname$/i,\n /^surname$/i,\n /^family[_-]?name$/i,\n]\n\n/**\n * Check if a field name matches any of the given patterns.\n */\nfunction matchesPatterns(fieldName: string, patterns: RegExp[]): boolean {\n const normalized = fieldName.trim()\n return patterns.some((pattern) => pattern.test(normalized))\n}\n\n/**\n * Find an email value from form fields.\n *\n * Priority:\n * 1. Fields with input type=\"email\" (if inputTypes map provided)\n * 2. Field names matching email patterns\n * 3. Any field with a value that looks like an email\n *\n * @param fields - Form field key-value pairs\n * @param inputTypes - Optional map of field names to input types\n * @returns The email value if found, undefined otherwise\n */\nexport function findEmailField(\n fields: Record<string, string>,\n inputTypes?: Map<string, string>,\n): string | undefined {\n // Priority 1: Check fields with type=\"email\"\n if (inputTypes) {\n for (const [fieldName, inputType] of inputTypes.entries()) {\n if (inputType === \"email\") {\n const value = fields[fieldName]\n if (value && isValidEmail(value)) {\n return value.trim()\n }\n }\n }\n }\n\n // Priority 2: Check field names matching email patterns\n for (const [fieldName, value] of Object.entries(fields)) {\n if (matchesPatterns(fieldName, EMAIL_FIELD_PATTERNS) && isValidEmail(value)) {\n return value.trim()\n }\n }\n\n // Priority 3: Any field with email-like value (fallback)\n for (const value of Object.values(fields)) {\n if (isValidEmail(value)) {\n return value.trim()\n }\n }\n\n return undefined\n}\n\n/**\n * Extract name fields from form data.\n *\n * Looks for:\n * - Full name fields (name, full_name, etc.)\n * - First name fields (first_name, fname, etc.)\n * - Last name fields (last_name, lname, etc.)\n *\n * If only first/last names are found, combines them into a full name.\n *\n * @param fields - Form field key-value pairs\n * @returns Object with name, firstName, and/or lastName if found\n */\nexport function findNameFields(fields: Record<string, string>): {\n name?: string\n firstName?: string\n lastName?: string\n} {\n let fullName: string | undefined\n let firstName: string | undefined\n let lastName: string | undefined\n\n for (const [fieldName, value] of Object.entries(fields)) {\n const trimmedValue = value?.trim()\n if (!trimmedValue) continue\n\n // Check for full name\n if (!fullName && matchesPatterns(fieldName, FULL_NAME_PATTERNS)) {\n fullName = trimmedValue\n }\n\n // Check for first name\n if (!firstName && matchesPatterns(fieldName, FIRST_NAME_PATTERNS)) {\n firstName = trimmedValue\n }\n\n // Check for last name\n if (!lastName && matchesPatterns(fieldName, LAST_NAME_PATTERNS)) {\n lastName = trimmedValue\n }\n }\n\n const result: { name?: string; firstName?: string; lastName?: string } = {}\n\n // If we have a full name, use it\n if (fullName) {\n result.name = fullName\n }\n // If we have first and last, combine them\n else if (firstName && lastName) {\n result.name = `${firstName} ${lastName}`\n result.firstName = firstName\n result.lastName = lastName\n }\n // If we only have first name\n else if (firstName) {\n result.firstName = firstName\n }\n // If we only have last name\n else if (lastName) {\n result.lastName = lastName\n }\n\n return result\n}\n\n/**\n * Identity extracted from a form submission.\n */\nexport interface ExtractedIdentity {\n email: string\n name?: string\n firstName?: string\n lastName?: string\n}\n\n/**\n * Extract identity information (email + name) from form fields.\n *\n * Returns undefined if no valid email is found (email is required for identification).\n *\n * @param fields - Form field key-value pairs\n * @param inputTypes - Optional map of field names to input types\n * @returns Extracted identity with email and optional name fields, or undefined\n */\nexport function extractIdentityFromForm(\n fields: Record<string, string>,\n inputTypes?: Map<string, string>,\n): ExtractedIdentity | undefined {\n const email = findEmailField(fields, inputTypes)\n\n // Email is required for identification\n if (!email) {\n return undefined\n }\n\n const nameFields = findNameFields(fields)\n\n return {\n email,\n ...nameFields,\n }\n}\n","import type {\n BillingEvent,\n BillingStatus,\n CalendarEvent,\n CalendarProvider,\n CustomEvent,\n CustomerTraits,\n EngagementEvent,\n ExplicitJourneyStage,\n FormEvent,\n IdentifyEvent,\n IdentifyTraits,\n IngestPayload,\n PageviewEvent,\n PayloadCustomerIdentity,\n PayloadUserIdentity,\n SourceType,\n StageEvent,\n TrackerEvent,\n} from \"./types\"\nimport { extractPathFromUrl, extractUtmParams } from \"./utils\"\n\n// ============================================\n// EVENT BUILDERS\n// ============================================\n\ninterface BaseEventParams {\n url: string\n referrer?: string\n timestamp?: number\n}\n\n/**\n * Build a pageview event.\n */\nexport function buildPageviewEvent(params: BaseEventParams & { title?: string }): PageviewEvent {\n const { url, referrer, timestamp, title } = params\n return {\n type: \"pageview\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n title,\n }\n}\n\n/**\n * Build a form event.\n */\nexport function buildFormEvent(\n params: BaseEventParams & {\n formId?: string\n formFields?: Record<string, string>\n },\n): FormEvent {\n const { url, referrer, timestamp, formId, formFields } = params\n return {\n type: \"form\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n formId,\n formFields,\n }\n}\n\n/**\n * Build an identify event.\n */\nexport function buildIdentifyEvent(\n params: BaseEventParams & {\n email?: string\n userId?: string\n fingerprint?: string\n traits?: IdentifyTraits\n customerId?: string\n customerDomain?: string\n customerTraits?: CustomerTraits\n },\n): IdentifyEvent {\n const {\n url,\n referrer,\n timestamp,\n email,\n userId,\n fingerprint,\n traits,\n customerId,\n customerDomain,\n customerTraits,\n } = params\n return {\n type: \"identify\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n email,\n userId,\n fingerprint,\n customerId,\n customerDomain,\n customerTraits,\n traits,\n }\n}\n\n/**\n * Build a custom event.\n */\nexport function buildCustomEvent(\n params: BaseEventParams & {\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n email?: string\n userId?: string\n fingerprint?: string\n customerId?: string\n customerDomain?: string\n },\n): CustomEvent {\n const {\n url,\n referrer,\n timestamp,\n eventName,\n properties,\n email,\n userId,\n fingerprint,\n customerId,\n customerDomain,\n } = params\n return {\n type: \"custom\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n eventName,\n email,\n userId,\n fingerprint,\n customerId,\n customerDomain,\n properties,\n }\n}\n\n/**\n * Build a calendar booking event.\n */\nexport function buildCalendarEvent(\n params: BaseEventParams & {\n provider: CalendarProvider\n eventType?: string\n startTime?: string\n endTime?: string\n duration?: number\n isRecurring?: boolean\n inviteeEmail?: string\n inviteeName?: string\n },\n): CalendarEvent {\n const {\n url,\n referrer,\n timestamp,\n provider,\n eventType,\n startTime,\n endTime,\n duration,\n isRecurring,\n inviteeEmail,\n inviteeName,\n } = params\n return {\n type: \"calendar\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n provider,\n eventType,\n startTime,\n endTime,\n duration,\n isRecurring,\n inviteeEmail,\n inviteeName,\n }\n}\n\n/**\n * Build an engagement event.\n * Captures active time on page for session analytics.\n */\nexport function buildEngagementEvent(\n params: BaseEventParams & {\n activeTimeMs: number\n totalTimeMs: number\n sessionId: string\n },\n): EngagementEvent {\n const { url, referrer, timestamp, activeTimeMs, totalTimeMs, sessionId } = params\n return {\n type: \"engagement\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n activeTimeMs,\n totalTimeMs,\n sessionId,\n }\n}\n\n/**\n * Build a stage event.\n * Used to explicitly set customer journey stage (activated, engaged, inactive).\n * discovered/signed_up stages are inferred from identify calls.\n */\nexport function buildStageEvent(\n params: BaseEventParams & {\n stage: ExplicitJourneyStage\n properties?: Record<string, string | number | boolean | null>\n },\n): StageEvent {\n const { url, referrer, timestamp, stage, properties } = params\n return {\n type: \"stage\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n stage,\n properties,\n }\n}\n\n/**\n * Build a billing event.\n * Used to set customer billing status (trialing, paid, churned).\n */\nexport function buildBillingEvent(\n params: BaseEventParams & {\n status: BillingStatus\n customerId?: string\n customerDomain?: string\n stripeCustomerId?: string\n domain?: string\n properties?: Record<string, string | number | boolean | null>\n },\n): BillingEvent {\n const {\n url,\n referrer,\n timestamp,\n status,\n customerId,\n customerDomain,\n stripeCustomerId,\n domain,\n properties,\n } = params\n return {\n type: \"billing\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n status,\n customerId,\n customerDomain: customerDomain ?? domain,\n domain: domain ?? customerDomain,\n stripeCustomerId,\n properties,\n }\n}\n\n// ============================================\n// PAYLOAD BUILDER\n// ============================================\n\n/**\n * Build an ingest payload from events.\n *\n * @param visitorId - The anonymous visitor ID from browser cookie/storage\n * @param source - The event source (client, server, integration)\n * @param events - Array of events to send\n * @param userIdentity - Optional user identity for immediate resolution (from setUser in SPA)\n * @param sessionId - Optional session ID for grouping events (browser SDK only)\n * @param fingerprint - Optional device identifier for server-side anonymous tracking\n * @param customerIdentity - Optional customer identity for batch attribution\n */\nexport function buildIngestPayload(\n visitorId: string,\n source: SourceType,\n events: TrackerEvent[],\n userIdentity?: PayloadUserIdentity,\n sessionId?: string,\n fingerprint?: string,\n customerIdentity?: PayloadCustomerIdentity,\n): IngestPayload {\n const payload: IngestPayload = {\n visitorId,\n source,\n events,\n }\n const legacyCustomerIdentity =\n customerIdentity === undefined &&\n userIdentity &&\n (userIdentity.customerId || userIdentity.customerDomain || userIdentity.customerTraits)\n ? {\n ...(userIdentity.customerId && { customerId: userIdentity.customerId }),\n ...(userIdentity.customerDomain && { customerDomain: userIdentity.customerDomain }),\n ...(userIdentity.customerTraits && { customerTraits: userIdentity.customerTraits }),\n }\n : undefined\n const resolvedCustomerIdentity = customerIdentity ?? legacyCustomerIdentity\n\n // Only include fingerprint if provided (server SDK only)\n if (fingerprint) {\n payload.fingerprint = fingerprint\n }\n\n // Only include sessionId if provided (browser SDK only)\n if (sessionId) {\n payload.sessionId = sessionId\n }\n\n // Only include userIdentity if it has actual values\n if (userIdentity && (userIdentity.email || userIdentity.userId || userIdentity.traits)) {\n payload.userIdentity = {\n ...(userIdentity.email && { email: userIdentity.email }),\n ...(userIdentity.userId && { userId: userIdentity.userId }),\n ...(userIdentity.traits && { traits: userIdentity.traits }),\n }\n }\n\n if (\n resolvedCustomerIdentity &&\n (resolvedCustomerIdentity.customerId ||\n resolvedCustomerIdentity.customerDomain ||\n resolvedCustomerIdentity.customerTraits)\n ) {\n payload.customerIdentity = {\n ...(resolvedCustomerIdentity.customerId && {\n customerId: resolvedCustomerIdentity.customerId,\n }),\n ...(resolvedCustomerIdentity.customerDomain && {\n customerDomain: resolvedCustomerIdentity.customerDomain,\n }),\n ...(resolvedCustomerIdentity.customerTraits && {\n customerTraits: resolvedCustomerIdentity.customerTraits,\n }),\n }\n }\n\n return payload\n}\n\n// ============================================\n// BATCH HELPERS\n// ============================================\n\n/**\n * Maximum number of events in a single batch.\n */\nexport const MAX_BATCH_SIZE = 100\n\n/**\n * Split events into batches of MAX_BATCH_SIZE.\n */\nexport function batchEvents(events: TrackerEvent[]): TrackerEvent[][] {\n const batches: TrackerEvent[][] = []\n for (let i = 0; i < events.length; i += MAX_BATCH_SIZE) {\n batches.push(events.slice(i, i + MAX_BATCH_SIZE))\n }\n return batches\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/payload.ts","../src/types.ts","../src/utils.ts"],"sourcesContent":["import { v7 as uuidv7 } from \"uuid\"\nimport type {\n BillingEvent,\n BillingStatus,\n CalendarEvent,\n CalendarProvider,\n CustomEvent,\n CustomerTraits,\n EngagementEvent,\n ExplicitJourneyStage,\n FormEvent,\n IdentifyEvent,\n IdentifyTraits,\n IngestPayload,\n PageviewEvent,\n PayloadCustomerIdentity,\n PayloadUserIdentity,\n SourceType,\n StageEvent,\n TrackerEvent,\n} from \"./types\"\nimport { extractPathFromUrl, extractUtmParams } from \"./utils\"\n\n// ============================================\n// EVENT BUILDERS\n// ============================================\n\ninterface BaseEventParams {\n url: string\n referrer?: string\n timestamp?: number\n}\n\n/**\n * Build a pageview event.\n */\nexport function buildPageviewEvent(params: BaseEventParams & { title?: string }): PageviewEvent {\n const { url, referrer, timestamp, title } = params\n return {\n uuid: uuidv7(),\n type: \"pageview\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n title,\n }\n}\n\n/**\n * Build a form event.\n */\nexport function buildFormEvent(\n params: BaseEventParams & {\n formId?: string\n formFields?: Record<string, string>\n },\n): FormEvent {\n const { url, referrer, timestamp, formId, formFields } = params\n return {\n uuid: uuidv7(),\n type: \"form\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n formId,\n formFields,\n }\n}\n\n/**\n * Build an identify event.\n */\nexport function buildIdentifyEvent(\n params: BaseEventParams & {\n email?: string\n userId?: string\n fingerprint?: string\n traits?: IdentifyTraits\n customerId?: string\n customerTraits?: CustomerTraits\n },\n): IdentifyEvent {\n const {\n url,\n referrer,\n timestamp,\n email,\n userId,\n fingerprint,\n traits,\n customerId,\n customerTraits,\n } = params\n return {\n uuid: uuidv7(),\n type: \"identify\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n email,\n userId,\n fingerprint,\n customerId,\n customerTraits,\n traits,\n }\n}\n\n/**\n * Build a custom event.\n */\nexport function buildCustomEvent(\n params: BaseEventParams & {\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n email?: string\n userId?: string\n fingerprint?: string\n customerId?: string\n },\n): CustomEvent {\n const {\n url,\n referrer,\n timestamp,\n eventName,\n properties,\n email,\n userId,\n fingerprint,\n customerId,\n } = params\n return {\n uuid: uuidv7(),\n type: \"custom\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n eventName,\n email,\n userId,\n fingerprint,\n customerId,\n properties,\n }\n}\n\n/**\n * Build a calendar booking event.\n */\nexport function buildCalendarEvent(\n params: BaseEventParams & {\n provider: CalendarProvider\n eventType?: string\n startTime?: string\n endTime?: string\n duration?: number\n isRecurring?: boolean\n inviteeEmail?: string\n inviteeName?: string\n },\n): CalendarEvent {\n const {\n url,\n referrer,\n timestamp,\n provider,\n eventType,\n startTime,\n endTime,\n duration,\n isRecurring,\n inviteeEmail,\n inviteeName,\n } = params\n return {\n uuid: uuidv7(),\n type: \"calendar\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n provider,\n eventType,\n startTime,\n endTime,\n duration,\n isRecurring,\n inviteeEmail,\n inviteeName,\n }\n}\n\n/**\n * Build an engagement event.\n * Captures active time on page for session analytics.\n */\nexport function buildEngagementEvent(\n params: BaseEventParams & {\n activeTimeMs: number\n totalTimeMs: number\n sessionId: string\n },\n): EngagementEvent {\n const { url, referrer, timestamp, activeTimeMs, totalTimeMs, sessionId } = params\n return {\n uuid: uuidv7(),\n type: \"engagement\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n activeTimeMs,\n totalTimeMs,\n sessionId,\n }\n}\n\n/**\n * Build a stage event.\n * Used to explicitly set customer journey stage (activated, engaged, inactive).\n * discovered/signed_up stages are inferred from identify calls.\n */\nexport function buildStageEvent(\n params: BaseEventParams & {\n stage: ExplicitJourneyStage\n properties?: Record<string, string | number | boolean | null>\n },\n): StageEvent {\n const { url, referrer, timestamp, stage, properties } = params\n return {\n uuid: uuidv7(),\n type: \"stage\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n stage,\n properties,\n }\n}\n\n/**\n * Build a billing event.\n * Used to set customer billing status (trialing, paid, churned).\n */\nexport function buildBillingEvent(\n params: BaseEventParams & {\n status: BillingStatus\n customerId?: string\n stripeCustomerId?: string\n properties?: Record<string, string | number | boolean | null>\n },\n): BillingEvent {\n const { url, referrer, timestamp, status, customerId, stripeCustomerId, properties } = params\n return {\n uuid: uuidv7(),\n type: \"billing\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n status,\n customerId,\n stripeCustomerId,\n properties,\n }\n}\n\n// ============================================\n// PAYLOAD BUILDER\n// ============================================\n\n/**\n * Build an ingest payload from events.\n *\n * @param visitorId - The anonymous visitor ID from browser cookie/storage\n * @param source - The event source (client, server, integration)\n * @param events - Array of events to send\n * @param userIdentity - Optional user identity for immediate resolution (from setUser in SPA)\n * @param sessionId - Optional session ID for grouping events (browser SDK only)\n * @param fingerprint - Optional device identifier for server-side anonymous tracking\n * @param customerIdentity - Optional customer identity for batch attribution. `customerId`-only\n * batches are valid and remain provisional until a later identify(email, customerId) call\n * links that external account/workspace to a resolved customer.\n */\nexport function buildIngestPayload(\n visitorId: string,\n source: SourceType,\n events: TrackerEvent[],\n userIdentity?: PayloadUserIdentity,\n sessionId?: string,\n fingerprint?: string,\n customerIdentity?: PayloadCustomerIdentity,\n): IngestPayload {\n const payload: IngestPayload = {\n visitorId,\n source,\n events,\n }\n const legacyCustomerIdentity =\n customerIdentity === undefined &&\n userIdentity &&\n (userIdentity.customerId || userIdentity.customerTraits)\n ? {\n ...(userIdentity.customerId && { customerId: userIdentity.customerId }),\n ...(userIdentity.customerTraits && { customerTraits: userIdentity.customerTraits }),\n }\n : undefined\n const resolvedCustomerIdentity = customerIdentity ?? legacyCustomerIdentity\n\n // Only include fingerprint if provided (server SDK only)\n if (fingerprint) {\n payload.fingerprint = fingerprint\n }\n\n // Only include sessionId if provided (browser SDK only)\n if (sessionId) {\n payload.sessionId = sessionId\n }\n\n // Only include userIdentity if it has actual values\n if (userIdentity && (userIdentity.email || userIdentity.userId || userIdentity.traits)) {\n payload.userIdentity = {\n ...(userIdentity.email && { email: userIdentity.email }),\n ...(userIdentity.userId && { userId: userIdentity.userId }),\n ...(userIdentity.traits && { traits: userIdentity.traits }),\n }\n }\n\n if (\n resolvedCustomerIdentity &&\n (resolvedCustomerIdentity.customerId || resolvedCustomerIdentity.customerTraits)\n ) {\n payload.customerIdentity = {\n ...(resolvedCustomerIdentity.customerId && {\n customerId: resolvedCustomerIdentity.customerId,\n }),\n ...(resolvedCustomerIdentity.customerTraits && {\n customerTraits: resolvedCustomerIdentity.customerTraits,\n }),\n }\n }\n\n return payload\n}\n\n// ============================================\n// BATCH HELPERS\n// ============================================\n\n/**\n * Maximum number of events in a single batch.\n */\nexport const MAX_BATCH_SIZE = 100\n\n/**\n * Split events into batches of MAX_BATCH_SIZE.\n */\nexport function batchEvents(events: TrackerEvent[]): TrackerEvent[][] {\n const batches: TrackerEvent[][] = []\n for (let i = 0; i < events.length; i += MAX_BATCH_SIZE) {\n batches.push(events.slice(i, i + MAX_BATCH_SIZE))\n }\n return batches\n}\n","// ============================================\n// EVENT TYPES\n// ============================================\n\nexport type EventType =\n | \"pageview\"\n | \"form\"\n | \"identify\"\n | \"custom\"\n | \"calendar\"\n | \"engagement\"\n | \"stage\"\n | \"billing\"\n\n// Only explicit stages - discovered/signed_up are inferred from identify calls\nexport type ExplicitJourneyStage = \"activated\" | \"engaged\" | \"inactive\"\n\nexport type BillingStatus = \"trialing\" | \"paid\" | \"churned\"\n\nexport type CalendarProvider = \"cal.com\" | \"calendly\" | \"unknown\"\n\nexport type SourceType = \"client\" | \"server\" | \"integration\"\n\n// ============================================\n// UTM PARAMETERS\n// ============================================\n\nexport interface UtmParams {\n source?: string\n medium?: string\n campaign?: string\n term?: string\n content?: string\n}\n\n// ============================================\n// TRACKER CONFIGURATION\n// ============================================\n\nexport interface TrackerConfig {\n publicKey: string\n apiHost?: string // default: 'https://app.outlit.ai'\n}\n\n// ============================================\n// BROWSER-SPECIFIC TYPES (anonymous allowed)\n// visitorId is auto-managed by the browser SDK\n// ============================================\n\nexport interface BrowserTrackOptions {\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface BrowserIdentifyOptions extends CustomerAttribution {\n email?: string\n /** Your system-owned user/contact ID. */\n userId?: string\n /** User/contact traits. */\n traits?: IdentifyTraits\n /** Customer/account-level traits. */\n customerTraits?: CustomerTraits\n}\n\n// ============================================\n// SERVER-SPECIFIC TYPES\n// Track calls can use user identity, customer attribution, or both.\n// Identify calls remain user-scoped and require email or userId.\n// ============================================\n\n/**\n * Base server-side user identity.\n * Track calls may use customerId without these fields.\n * Identify calls still require email or userId at runtime.\n * `customerId`-only track calls are valid immediately, but they stay provisional\n * until the same customer/account/workspace later appears on identify() with\n * the matching customerId and an email.\n *\n * - fingerprint: Device identifier for anonymous tracking (can be linked later)\n * - email: User's email address (definitive identity, resolves immediately)\n * - userId: Your system-owned user/contact ID\n */\nexport interface ServerIdentity {\n fingerprint?: string\n email?: string\n /** Your system-owned user/contact ID. */\n userId?: string\n}\n\nexport interface CustomerAttribution {\n /** Your system-owned customer/account/workspace ID. */\n customerId?: string\n}\n\n// ============================================\n// TRAITS\n// ============================================\n\nexport interface CustomerTraits {\n /** Customer's billing plan */\n plan?: string\n /** Allow additional custom properties */\n [key: string]: string | number | boolean | null | undefined\n}\n\n/**\n * Traits for identify calls.\n * These are user/contact traits, not customer/account traits.\n */\nexport interface IdentifyTraits {\n /** User-level traits */\n [key: string]: string | number | boolean | null | undefined\n}\n\nexport interface ServerTrackOptions extends ServerIdentity, CustomerAttribution {\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n timestamp?: number\n}\n\nexport interface ServerIdentifyOptions extends ServerIdentity, CustomerAttribution {\n traits?: IdentifyTraits\n customerTraits?: CustomerTraits\n}\n\n/**\n * Customer identity for SDK billing methods.\n * Public billing calls should use `customerId`.\n */\nexport interface CustomerIdentifier extends CustomerAttribution {\n /**\n * @deprecated Stripe customer identifier.\n * Billing attribution should use `customerId` publicly.\n */\n stripeCustomerId?: string\n}\n\n// ============================================\n// INTERNAL EVENT TYPES\n// These are the full event objects sent to the API\n// ============================================\n\ninterface BaseEvent {\n uuid?: string\n type: EventType\n timestamp: number // Unix timestamp in milliseconds\n url: string\n path: string\n referrer?: string\n utm?: UtmParams\n}\n\nexport interface PageviewEvent extends BaseEvent {\n type: \"pageview\"\n title?: string\n}\n\nexport interface FormEvent extends BaseEvent {\n type: \"form\"\n formId?: string\n formFields?: Record<string, string>\n}\n\nexport interface IdentifyEvent extends BaseEvent {\n type: \"identify\"\n email?: string\n userId?: string\n fingerprint?: string\n customerId?: string\n customerTraits?: CustomerTraits\n traits?: IdentifyTraits\n}\n\nexport interface CustomEvent extends BaseEvent {\n type: \"custom\"\n eventName: string\n email?: string\n userId?: string\n fingerprint?: string\n customerId?: string\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface CalendarEvent extends BaseEvent {\n type: \"calendar\"\n provider: CalendarProvider\n eventType?: string // e.g., \"30 Minute Meeting\"\n startTime?: string // ISO timestamp\n endTime?: string // ISO timestamp\n duration?: number // Duration in minutes\n isRecurring?: boolean\n /** Available when identity is passed via webhooks or manual integration */\n inviteeEmail?: string\n inviteeName?: string\n}\n\nexport interface EngagementEvent extends BaseEvent {\n type: \"engagement\"\n /** Time in milliseconds the user was actively engaged (visible tab + user interactions) */\n activeTimeMs: number\n /** Total wall-clock time in milliseconds on the page */\n totalTimeMs: number\n /** Session ID for grouping engagement events. Resets after 30 min of inactivity or tab close. */\n sessionId: string\n}\n\nexport interface StageEvent extends BaseEvent {\n type: \"stage\"\n /** The journey stage to set (only explicit stages, discovered/signed_up are inferred) */\n stage: ExplicitJourneyStage\n /** Optional properties for context */\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface BillingEvent extends BaseEvent {\n type: \"billing\"\n /** The billing status to set for a customer */\n status: BillingStatus\n /** Optional customer identifiers */\n customerId?: string\n stripeCustomerId?: string\n /** Optional properties for context */\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport type TrackerEvent =\n | PageviewEvent\n | FormEvent\n | IdentifyEvent\n | CustomEvent\n | CalendarEvent\n | EngagementEvent\n | StageEvent\n | BillingEvent\n\n// ============================================\n// INGEST PAYLOAD\n// This is what gets sent to the API\n// ============================================\n\n/**\n * User identity for payload-level resolution.\n * Used by browser SDK when user is logged in (via setUser).\n * Customer attribution is carried separately in `customerIdentity`.\n */\nexport interface PayloadUserIdentity {\n email?: string\n /** Your system-owned user/contact ID. */\n userId?: string\n /** User/contact traits. */\n traits?: IdentifyTraits\n /**\n * @deprecated Use payload-level `customerIdentity.customerId` instead.\n * Kept for one compatibility window while callers migrate.\n */\n customerId?: string\n /**\n * @deprecated Use payload-level `customerIdentity.customerTraits` instead.\n * Kept for one compatibility window while callers migrate.\n */\n customerTraits?: CustomerTraits\n}\n\n/**\n * Customer identity for payload-level attribution.\n * Used by browser SDK to attach account/workspace context to a batch.\n * `customerId`-only attribution is valid and remains provisional until a later\n * identify(email, customerId) call links that external account/workspace to a resolved customer.\n */\nexport interface PayloadCustomerIdentity extends CustomerAttribution {\n /** Customer/account traits. */\n customerTraits?: CustomerTraits\n}\n\nexport interface IngestPayload {\n visitorId?: string // Required for pixel, optional for server\n source: SourceType\n events: TrackerEvent[]\n /**\n * Device identifier for anonymous tracking.\n * Events with fingerprint can be linked to users later via identify.\n * Only present for server-side events.\n */\n fingerprint?: string\n /**\n * Session ID for grouping all events in this batch.\n * Only present for browser (client) source events.\n * Used to correlate pageviews, forms, custom events, and engagement\n * within the same browsing session.\n */\n sessionId?: string\n /**\n * User identity for this batch of events.\n * When present, the server can resolve directly to CustomerContact\n * instead of relying on anonymous visitor flow.\n *\n * This is set by the browser SDK when setUser() has been called,\n * allowing immediate identity resolution for SPA/React apps.\n */\n userIdentity?: PayloadUserIdentity\n /**\n * Customer/account identity for this batch of events.\n * Used to attribute browser batches to a customer/workspace even when\n * the customer fields are not present on every event.\n */\n customerIdentity?: PayloadCustomerIdentity\n}\n\n// ============================================\n// API RESPONSE\n// ============================================\n\nexport interface IngestResponse {\n success: boolean\n processed: number\n errors?: Array<{\n index: number\n message: string\n }>\n}\n\n// ============================================\n// CONSTANTS\n// ============================================\n\nexport const DEFAULT_API_HOST = \"https://app.outlit.ai\"\n\n// Re-export for convenience\nexport type { PayloadUserIdentity as UserIdentity }\n\nexport const DEFAULT_DENIED_FORM_FIELDS = [\n \"password\",\n \"passwd\",\n \"pass\",\n \"pwd\",\n \"token\",\n \"secret\",\n \"api_key\",\n \"apikey\",\n \"api-key\",\n \"credit_card\",\n \"creditcard\",\n \"credit-card\",\n \"cc_number\",\n \"ccnumber\",\n \"card_number\",\n \"cardnumber\",\n \"cvv\",\n \"cvc\",\n \"ssn\",\n \"social_security\",\n \"socialsecurity\",\n \"bank_account\",\n \"bankaccount\",\n \"routing_number\",\n \"routingnumber\",\n]\n","import { DEFAULT_DENIED_FORM_FIELDS, type UtmParams } from \"./types\"\n\n// ============================================\n// UTM EXTRACTION\n// ============================================\n\n/**\n * Extract UTM parameters from a URL.\n */\nexport function extractUtmParams(url: string): UtmParams | undefined {\n try {\n const urlObj = new URL(url)\n const params = urlObj.searchParams\n\n const utm: UtmParams = {}\n\n if (params.has(\"utm_source\")) utm.source = params.get(\"utm_source\") ?? undefined\n if (params.has(\"utm_medium\")) utm.medium = params.get(\"utm_medium\") ?? undefined\n if (params.has(\"utm_campaign\")) utm.campaign = params.get(\"utm_campaign\") ?? undefined\n if (params.has(\"utm_term\")) utm.term = params.get(\"utm_term\") ?? undefined\n if (params.has(\"utm_content\")) utm.content = params.get(\"utm_content\") ?? undefined\n\n return Object.keys(utm).length > 0 ? utm : undefined\n } catch (error) {\n console.warn(`[Outlit] Failed to parse URL for UTM extraction: \"${url}\"`, error)\n return undefined\n }\n}\n\n/**\n * Extract path from a URL.\n */\nexport function extractPathFromUrl(url: string): string {\n try {\n const urlObj = new URL(url)\n let path = urlObj.pathname\n const hash = urlObj.hash\n\n // For file:// or custom protocol URLs with no real hostname (common in Electron),\n // the meaningful route is typically stored in the hash fragment.\n if (hash && hash.length > 1 && (!urlObj.hostname || urlObj.protocol === \"file:\")) {\n path = hash.replace(/^#/, \"\")\n }\n // For web hash-routing SPAs (e.g. \"/#/settings\"), prefer hash when the\n // pathname is an SPA entry point.\n else if (\n hash.startsWith(\"#/\") &&\n (urlObj.pathname === \"/\" || urlObj.pathname.endsWith(\"/index.html\"))\n ) {\n path = hash.slice(1)\n }\n\n if (!path) return \"/\"\n if (!path.startsWith(\"/\")) return `/${path}`\n\n return path\n } catch (error) {\n console.warn(\n `[Outlit] Failed to parse URL for path extraction: \"${url}\", defaulting to \"/\"`,\n error,\n )\n return \"/\"\n }\n}\n\n// ============================================\n// FORM FIELD SANITIZATION\n// ============================================\n\n/**\n * Check if a field name should be denied (case-insensitive).\n */\nexport function isFieldDenied(fieldName: string, denylist: string[]): boolean {\n const normalizedName = fieldName.toLowerCase().replace(/[-_\\s]/g, \"\")\n return denylist.some((denied) => {\n const normalizedDenied = denied.toLowerCase().replace(/[-_\\s]/g, \"\")\n return normalizedName.includes(normalizedDenied)\n })\n}\n\n/**\n * Check if a value looks like sensitive data (e.g., credit card number).\n */\nfunction looksLikeSensitiveValue(value: string): boolean {\n // Remove spaces and dashes\n const cleaned = value.replace(/[\\s-]/g, \"\")\n\n // Check for credit card patterns (13-19 digits)\n if (/^\\d{13,19}$/.test(cleaned)) {\n return true\n }\n\n // Check for SSN pattern (9 digits)\n if (/^\\d{9}$/.test(cleaned) || /^\\d{3}-\\d{2}-\\d{4}$/.test(value)) {\n return true\n }\n\n return false\n}\n\n/**\n * Sanitize form fields by removing sensitive data.\n * Returns a new object with denied fields removed.\n */\nexport function sanitizeFormFields(\n fields: Record<string, string> | undefined,\n customDenylist?: string[],\n): Record<string, string> | undefined {\n if (!fields) return undefined\n\n const denylist = customDenylist ?? DEFAULT_DENIED_FORM_FIELDS\n const sanitized: Record<string, string> = {}\n\n for (const [key, value] of Object.entries(fields)) {\n if (!isFieldDenied(key, denylist)) {\n // Also check for credit card patterns in values\n if (!looksLikeSensitiveValue(value)) {\n sanitized[key] = value\n }\n }\n }\n\n return Object.keys(sanitized).length > 0 ? sanitized : undefined\n}\n\n// ============================================\n// VISITOR ID DERIVATION (for server SDK)\n// ============================================\n\n/**\n * Derive a deterministic visitor ID from email and/or userId.\n * This is used by the server SDK to create consistent IDs for API compatibility.\n *\n * Uses a simple hash to create a UUID-like string that will be consistent\n * for the same email/userId combination.\n */\nexport function deriveVisitorIdFromIdentity(email?: string, userId?: string): string {\n const identity = [email?.toLowerCase(), userId].filter(Boolean).join(\"|\")\n if (!identity) {\n throw new Error(\"Either email or userId must be provided\")\n }\n\n // Simple hash function to create a deterministic UUID-like string\n let hash = 0\n for (let i = 0; i < identity.length; i++) {\n const char = identity.charCodeAt(i)\n hash = (hash << 5) - hash + char\n hash = hash & hash // Convert to 32-bit integer\n }\n\n // Convert to hex and format as UUID-like string\n const hex = Math.abs(hash).toString(16).padStart(8, \"0\")\n const part1 = hex.slice(0, 8)\n const part2 = identity.length.toString(16).padStart(4, \"0\")\n const part3 = \"4000\" // Version 4 UUID marker\n const part4 = (((hash >>> 16) & 0x0fff) | 0x8000).toString(16)\n const part5 = Math.abs(hash * 31)\n .toString(16)\n .padStart(12, \"0\")\n .slice(0, 12)\n\n return `${part1}-${part2}-${part3}-${part4}-${part5}`\n}\n\n// ============================================\n// VALIDATION\n// ============================================\n\n/**\n * Validate that at least one identity field is provided.\n * Used by the server SDK to enforce identity requirements.\n *\n * Valid identities:\n * - fingerprint: Device identifier (for anonymous tracking, can be linked later)\n * - email: User's email (definitive identity)\n * - userId: Your system-owned user/contact ID\n * - customerId: Your system-owned customer/account/workspace ID\n */\nexport function validateServerIdentity(\n fingerprint?: string,\n email?: string,\n userId?: string,\n customerId?: string,\n): void {\n const hasFingerprint = fingerprint && fingerprint.trim().length > 0\n const hasEmail = email && email.trim().length > 0\n const hasUserId = userId && userId.trim().length > 0\n const hasCustomerId = customerId && customerId.trim().length > 0\n\n if (!hasFingerprint && !hasEmail && !hasUserId && !hasCustomerId) {\n throw new Error(\n \"Server SDK requires at least one of: fingerprint, email, userId, or customerId for track calls. \" +\n \"Use fingerprint/email/userId for user-scoped tracking or customerId for customer-only tracking.\",\n )\n }\n}\n\n/**\n * Validate that at least one customer identifier is provided for billing calls.\n */\nexport function validateCustomerIdentity(customerId?: string, stripeCustomerId?: string): void {\n const hasCustomerId = customerId && customerId.trim().length > 0\n const hasStripeCustomerId = stripeCustomerId && stripeCustomerId.trim().length > 0\n\n if (!hasCustomerId && !hasStripeCustomerId) {\n throw new Error(\n \"Billing methods require customerId or stripeCustomerId. \" +\n \"Use customerId for your system-owned account ID.\",\n )\n }\n}\n\n// ============================================\n// AUTO-IDENTIFY: EMAIL & NAME EXTRACTION\n// ============================================\n\n/**\n * Validate that a string looks like a valid email address.\n */\nexport function isValidEmail(value: string): boolean {\n if (!value || typeof value !== \"string\") return false\n // Basic email regex - intentionally permissive to avoid false negatives\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n return emailRegex.test(value.trim())\n}\n\n/**\n * Email field name patterns (case-insensitive, normalized).\n * Order matters - more specific patterns first.\n */\nconst EMAIL_FIELD_PATTERNS = [\n /^e?-?mail$/i,\n /^email[_-]?address$/i,\n /^user[_-]?email$/i,\n /^work[_-]?email$/i,\n /^contact[_-]?email$/i,\n /^primary[_-]?email$/i,\n /^business[_-]?email$/i,\n]\n\n/**\n * Full name field patterns.\n */\nconst FULL_NAME_PATTERNS = [\n /^name$/i,\n /^full[_-]?name$/i,\n /^your[_-]?name$/i,\n /^customer[_-]?name$/i,\n /^contact[_-]?name$/i,\n /^display[_-]?name$/i,\n]\n\n/**\n * First name field patterns.\n */\nconst FIRST_NAME_PATTERNS = [\n /^first[_-]?name$/i,\n /^firstname$/i,\n /^first$/i,\n /^fname$/i,\n /^given[_-]?name$/i,\n /^forename$/i,\n]\n\n/**\n * Last name field patterns.\n */\nconst LAST_NAME_PATTERNS = [\n /^last[_-]?name$/i,\n /^lastname$/i,\n /^last$/i,\n /^lname$/i,\n /^surname$/i,\n /^family[_-]?name$/i,\n]\n\n/**\n * Check if a field name matches any of the given patterns.\n */\nfunction matchesPatterns(fieldName: string, patterns: RegExp[]): boolean {\n const normalized = fieldName.trim()\n return patterns.some((pattern) => pattern.test(normalized))\n}\n\n/**\n * Find an email value from form fields.\n *\n * Priority:\n * 1. Fields with input type=\"email\" (if inputTypes map provided)\n * 2. Field names matching email patterns\n * 3. Any field with a value that looks like an email\n *\n * @param fields - Form field key-value pairs\n * @param inputTypes - Optional map of field names to input types\n * @returns The email value if found, undefined otherwise\n */\nexport function findEmailField(\n fields: Record<string, string>,\n inputTypes?: Map<string, string>,\n): string | undefined {\n // Priority 1: Check fields with type=\"email\"\n if (inputTypes) {\n for (const [fieldName, inputType] of inputTypes.entries()) {\n if (inputType === \"email\") {\n const value = fields[fieldName]\n if (value && isValidEmail(value)) {\n return value.trim()\n }\n }\n }\n }\n\n // Priority 2: Check field names matching email patterns\n for (const [fieldName, value] of Object.entries(fields)) {\n if (matchesPatterns(fieldName, EMAIL_FIELD_PATTERNS) && isValidEmail(value)) {\n return value.trim()\n }\n }\n\n // Priority 3: Any field with email-like value (fallback)\n for (const value of Object.values(fields)) {\n if (isValidEmail(value)) {\n return value.trim()\n }\n }\n\n return undefined\n}\n\n/**\n * Extract name fields from form data.\n *\n * Looks for:\n * - Full name fields (name, full_name, etc.)\n * - First name fields (first_name, fname, etc.)\n * - Last name fields (last_name, lname, etc.)\n *\n * If only first/last names are found, combines them into a full name.\n *\n * @param fields - Form field key-value pairs\n * @returns Object with name, firstName, and/or lastName if found\n */\nexport function findNameFields(fields: Record<string, string>): {\n name?: string\n firstName?: string\n lastName?: string\n} {\n let fullName: string | undefined\n let firstName: string | undefined\n let lastName: string | undefined\n\n for (const [fieldName, value] of Object.entries(fields)) {\n const trimmedValue = value?.trim()\n if (!trimmedValue) continue\n\n // Check for full name\n if (!fullName && matchesPatterns(fieldName, FULL_NAME_PATTERNS)) {\n fullName = trimmedValue\n }\n\n // Check for first name\n if (!firstName && matchesPatterns(fieldName, FIRST_NAME_PATTERNS)) {\n firstName = trimmedValue\n }\n\n // Check for last name\n if (!lastName && matchesPatterns(fieldName, LAST_NAME_PATTERNS)) {\n lastName = trimmedValue\n }\n }\n\n const result: { name?: string; firstName?: string; lastName?: string } = {}\n\n // If we have a full name, use it\n if (fullName) {\n result.name = fullName\n }\n // If we have first and last, combine them\n else if (firstName && lastName) {\n result.name = `${firstName} ${lastName}`\n result.firstName = firstName\n result.lastName = lastName\n }\n // If we only have first name\n else if (firstName) {\n result.firstName = firstName\n }\n // If we only have last name\n else if (lastName) {\n result.lastName = lastName\n }\n\n return result\n}\n\n/**\n * Identity extracted from a form submission.\n */\nexport interface ExtractedIdentity {\n email: string\n name?: string\n firstName?: string\n lastName?: string\n}\n\n/**\n * Extract identity information (email + name) from form fields.\n *\n * Returns undefined if no valid email is found (email is required for identification).\n *\n * @param fields - Form field key-value pairs\n * @param inputTypes - Optional map of field names to input types\n * @returns Extracted identity with email and optional name fields, or undefined\n */\nexport function extractIdentityFromForm(\n fields: Record<string, string>,\n inputTypes?: Map<string, string>,\n): ExtractedIdentity | undefined {\n const email = findEmailField(fields, inputTypes)\n\n // Email is required for identification\n if (!email) {\n return undefined\n }\n\n const nameFields = findNameFields(fields)\n\n return {\n email,\n ...nameFields,\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outlit/core",
3
- "version": "1.4.5",
3
+ "version": "1.5.1",
4
4
  "description": "Shared types and utilities for Outlit tracking SDKs",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Outlit AI",
@@ -45,6 +45,9 @@
45
45
  "typecheck": "tsc --noEmit",
46
46
  "test": "vitest run"
47
47
  },
48
+ "dependencies": {
49
+ "uuid": "^14.0.0"
50
+ },
48
51
  "devDependencies": {
49
52
  "@outlit/typescript-config": "workspace:*",
50
53
  "tsup": "^8.5.1",