@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 +14 -28
- package/dist/index.d.ts +14 -28
- package/dist/index.js +21 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +21 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -1
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
|
|
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
|
|
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`
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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 && !
|
|
164
|
+
if (!hasCustomerId && !hasStripeCustomerId) {
|
|
165
165
|
throw new Error(
|
|
166
|
-
"Billing methods require customerId or
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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 && !
|
|
116
|
+
if (!hasCustomerId && !hasStripeCustomerId) {
|
|
117
117
|
throw new Error(
|
|
118
|
-
"Billing methods require customerId or
|
|
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.
|
|
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.
|
|
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
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -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.
|
|
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",
|