@parag.more.withsurface.com/forms-sdk 0.1.4
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/README.md +63 -0
- package/dist/index.cjs +2737 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.mjs +2714 -0
- package/dist/index.mjs.map +7 -0
- package/dist/react/index.cjs +44 -0
- package/dist/react/index.cjs.map +7 -0
- package/dist/react/index.mjs +21 -0
- package/dist/react/index.mjs.map +7 -0
- package/dist/surface-forms-sdk.min.js +2 -0
- package/dist/surface-forms-sdk.min.js.map +7 -0
- package/dist/types/bind/bindForm.d.ts +43 -0
- package/dist/types/bind/bindForm.d.ts.map +1 -0
- package/dist/types/bind/bindForm.test.d.ts +2 -0
- package/dist/types/bind/bindForm.test.d.ts.map +1 -0
- package/dist/types/bind/bookingWidget.d.ts +35 -0
- package/dist/types/bind/bookingWidget.d.ts.map +1 -0
- package/dist/types/bind/bookingWidget.test.d.ts +2 -0
- package/dist/types/bind/bookingWidget.test.d.ts.map +1 -0
- package/dist/types/core/client/emailValidation.d.ts +23 -0
- package/dist/types/core/client/emailValidation.d.ts.map +1 -0
- package/dist/types/core/client/emailValidation.test.d.ts +2 -0
- package/dist/types/core/client/emailValidation.test.d.ts.map +1 -0
- package/dist/types/core/client/responses.d.ts +35 -0
- package/dist/types/core/client/responses.d.ts.map +1 -0
- package/dist/types/core/client/responses.test.d.ts +2 -0
- package/dist/types/core/client/responses.test.d.ts.map +1 -0
- package/dist/types/core/client/runtimeConfig.contract.test.d.ts +2 -0
- package/dist/types/core/client/runtimeConfig.contract.test.d.ts.map +1 -0
- package/dist/types/core/client/runtimeConfig.d.ts +72 -0
- package/dist/types/core/client/runtimeConfig.d.ts.map +1 -0
- package/dist/types/core/client/runtimeConfig.test.d.ts +2 -0
- package/dist/types/core/client/runtimeConfig.test.d.ts.map +1 -0
- package/dist/types/core/client/scheduler.contract.test.d.ts +2 -0
- package/dist/types/core/client/scheduler.contract.test.d.ts.map +1 -0
- package/dist/types/core/client/scheduler.d.ts +121 -0
- package/dist/types/core/client/scheduler.d.ts.map +1 -0
- package/dist/types/core/client/scheduler.test.d.ts +2 -0
- package/dist/types/core/client/scheduler.test.d.ts.map +1 -0
- package/dist/types/core/client/transport.d.ts +56 -0
- package/dist/types/core/client/transport.d.ts.map +1 -0
- package/dist/types/core/client/transport.test.d.ts +2 -0
- package/dist/types/core/client/transport.test.d.ts.map +1 -0
- package/dist/types/core/engine/answerAudit.d.ts +21 -0
- package/dist/types/core/engine/answerAudit.d.ts.map +1 -0
- package/dist/types/core/engine/answerAudit.test.d.ts +2 -0
- package/dist/types/core/engine/answerAudit.test.d.ts.map +1 -0
- package/dist/types/core/engine/answerStates.contract.test.d.ts +2 -0
- package/dist/types/core/engine/answerStates.contract.test.d.ts.map +1 -0
- package/dist/types/core/engine/answerStates.d.ts +92 -0
- package/dist/types/core/engine/answerStates.d.ts.map +1 -0
- package/dist/types/core/engine/formEngine.d.ts +145 -0
- package/dist/types/core/engine/formEngine.d.ts.map +1 -0
- package/dist/types/core/engine/formEngine.test.d.ts +2 -0
- package/dist/types/core/engine/formEngine.test.d.ts.map +1 -0
- package/dist/types/core/engine/redirect.d.ts +36 -0
- package/dist/types/core/engine/redirect.d.ts.map +1 -0
- package/dist/types/core/engine/redirect.test.d.ts +2 -0
- package/dist/types/core/engine/redirect.test.d.ts.map +1 -0
- package/dist/types/core/engine/responsePayload.contract.test.d.ts +2 -0
- package/dist/types/core/engine/responsePayload.contract.test.d.ts.map +1 -0
- package/dist/types/core/engine/responsePayload.d.ts +74 -0
- package/dist/types/core/engine/responsePayload.d.ts.map +1 -0
- package/dist/types/core/engine/responsePayload.test.d.ts +2 -0
- package/dist/types/core/engine/responsePayload.test.d.ts.map +1 -0
- package/dist/types/core/engine/urlData.contract.test.d.ts +2 -0
- package/dist/types/core/engine/urlData.contract.test.d.ts.map +1 -0
- package/dist/types/core/engine/urlData.d.ts +30 -0
- package/dist/types/core/engine/urlData.d.ts.map +1 -0
- package/dist/types/core/engine/urlData.test.d.ts +2 -0
- package/dist/types/core/engine/urlData.test.d.ts.map +1 -0
- package/dist/types/core/engine/urlPrefill.d.ts +21 -0
- package/dist/types/core/engine/urlPrefill.d.ts.map +1 -0
- package/dist/types/core/engine/urlPrefill.test.d.ts +2 -0
- package/dist/types/core/engine/urlPrefill.test.d.ts.map +1 -0
- package/dist/types/core/events/dropoffDetection.d.ts +21 -0
- package/dist/types/core/events/dropoffDetection.d.ts.map +1 -0
- package/dist/types/core/events/dropoffDetection.test.d.ts +2 -0
- package/dist/types/core/events/dropoffDetection.test.d.ts.map +1 -0
- package/dist/types/core/events/emitter.d.ts +23 -0
- package/dist/types/core/events/emitter.d.ts.map +1 -0
- package/dist/types/core/events/emitter.test.d.ts +2 -0
- package/dist/types/core/events/emitter.test.d.ts.map +1 -0
- package/dist/types/core/events/formJourneyTracker.contract.test.d.ts +2 -0
- package/dist/types/core/events/formJourneyTracker.contract.test.d.ts.map +1 -0
- package/dist/types/core/events/formJourneyTracker.d.ts +69 -0
- package/dist/types/core/events/formJourneyTracker.d.ts.map +1 -0
- package/dist/types/core/events/formJourneyTracker.test.d.ts +2 -0
- package/dist/types/core/events/formJourneyTracker.test.d.ts.map +1 -0
- package/dist/types/core/events/pushEvents.contract.test.d.ts +2 -0
- package/dist/types/core/events/pushEvents.contract.test.d.ts.map +1 -0
- package/dist/types/core/events/pushEvents.d.ts +62 -0
- package/dist/types/core/events/pushEvents.d.ts.map +1 -0
- package/dist/types/core/events/pushEvents.test.d.ts +2 -0
- package/dist/types/core/events/pushEvents.test.d.ts.map +1 -0
- package/dist/types/core/events/startedDetection.d.ts +11 -0
- package/dist/types/core/events/startedDetection.d.ts.map +1 -0
- package/dist/types/core/events/startedDetection.test.d.ts +2 -0
- package/dist/types/core/events/startedDetection.test.d.ts.map +1 -0
- package/dist/types/core/events/viewDetection.d.ts +15 -0
- package/dist/types/core/events/viewDetection.d.ts.map +1 -0
- package/dist/types/core/events/viewDetection.test.d.ts +2 -0
- package/dist/types/core/events/viewDetection.test.d.ts.map +1 -0
- package/dist/types/core/identity/browserFingerprint.d.ts +40 -0
- package/dist/types/core/identity/browserFingerprint.d.ts.map +1 -0
- package/dist/types/core/identity/browserFingerprint.test.d.ts +2 -0
- package/dist/types/core/identity/browserFingerprint.test.d.ts.map +1 -0
- package/dist/types/core/identity/ensureIdentified.d.ts +27 -0
- package/dist/types/core/identity/ensureIdentified.d.ts.map +1 -0
- package/dist/types/core/identity/ensureIdentified.test.d.ts +2 -0
- package/dist/types/core/identity/ensureIdentified.test.d.ts.map +1 -0
- package/dist/types/core/identity/identify.contract.test.d.ts +2 -0
- package/dist/types/core/identity/identify.contract.test.d.ts.map +1 -0
- package/dist/types/core/identity/identify.d.ts +48 -0
- package/dist/types/core/identity/identify.d.ts.map +1 -0
- package/dist/types/core/identity/identify.test.d.ts +2 -0
- package/dist/types/core/identity/identify.test.d.ts.map +1 -0
- package/dist/types/core/identity/journey.contract.test.d.ts +2 -0
- package/dist/types/core/identity/journey.contract.test.d.ts.map +1 -0
- package/dist/types/core/identity/journey.d.ts +28 -0
- package/dist/types/core/identity/journey.d.ts.map +1 -0
- package/dist/types/core/identity/journey.test.d.ts +2 -0
- package/dist/types/core/identity/journey.test.d.ts.map +1 -0
- package/dist/types/core/identity/journey.testStubs.d.ts +5 -0
- package/dist/types/core/identity/journey.testStubs.d.ts.map +1 -0
- package/dist/types/core/identity/leadDataCache.d.ts +27 -0
- package/dist/types/core/identity/leadDataCache.d.ts.map +1 -0
- package/dist/types/core/identity/leadDataCache.test.d.ts +2 -0
- package/dist/types/core/identity/leadDataCache.test.d.ts.map +1 -0
- package/dist/types/enrichment/attachEnrichment.d.ts +35 -0
- package/dist/types/enrichment/attachEnrichment.d.ts.map +1 -0
- package/dist/types/enrichment/attachEnrichment.test.d.ts +2 -0
- package/dist/types/enrichment/attachEnrichment.test.d.ts.map +1 -0
- package/dist/types/index.d.ts +28 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/react/index.d.ts +2 -0
- package/dist/types/react/index.d.ts.map +1 -0
- package/dist/types/react/ssr.test.d.ts +2 -0
- package/dist/types/react/ssr.test.d.ts.map +1 -0
- package/dist/types/react/useSurfaceForm.d.ts +25 -0
- package/dist/types/react/useSurfaceForm.d.ts.map +1 -0
- package/dist/types/react/useSurfaceForm.test.d.ts +2 -0
- package/dist/types/react/useSurfaceForm.test.d.ts.map +1 -0
- package/dist/types/surfaceForms.d.ts +120 -0
- package/dist/types/surfaceForms.d.ts.map +1 -0
- package/dist/types/surfaceForms.test.d.ts +2 -0
- package/dist/types/surfaceForms.test.d.ts.map +1 -0
- package/dist/types/tracking/attachTracking.d.ts +35 -0
- package/dist/types/tracking/attachTracking.d.ts.map +1 -0
- package/dist/types/tracking/attachTracking.test.d.ts +2 -0
- package/dist/types/tracking/attachTracking.test.d.ts.map +1 -0
- package/dist/types/tracking/conversionRules.contract.test.d.ts +2 -0
- package/dist/types/tracking/conversionRules.contract.test.d.ts.map +1 -0
- package/dist/types/tracking/conversionRules.d.ts +87 -0
- package/dist/types/tracking/conversionRules.d.ts.map +1 -0
- package/dist/types/tracking/conversionRules.test.d.ts +2 -0
- package/dist/types/tracking/conversionRules.test.d.ts.map +1 -0
- package/dist/types/tracking/vendors.d.ts +46 -0
- package/dist/types/tracking/vendors.d.ts.map +1 -0
- package/dist/types/tracking/vendors.test.d.ts +2 -0
- package/dist/types/tracking/vendors.test.d.ts.map +1 -0
- package/dist/types/validation/attachEmailValidation.d.ts +8 -0
- package/dist/types/validation/attachEmailValidation.d.ts.map +1 -0
- package/dist/types/validation/attachEmailValidation.test.d.ts +2 -0
- package/dist/types/validation/attachEmailValidation.test.d.ts.map +1 -0
- package/docs/api.md +310 -0
- package/docs/reference/binding.md +116 -0
- package/docs/reference/clients.md +212 -0
- package/docs/reference/engine.md +193 -0
- package/docs/reference/events.md +93 -0
- package/docs/reference/identity.md +116 -0
- package/llms.txt +317 -0
- package/package.json +25 -0
- package/skills/surface-forms-sdk/SKILL.md +153 -0
- package/skills/surface-forms-sdk/debugging.md +64 -0
- package/skills/surface-forms-sdk/editing.md +67 -0
- package/skills/surface-forms-sdk/patterns.md +222 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { FormEngine } from "../core/engine/formEngine";
|
|
2
|
+
import { type AdConversionRule } from "./conversionRules";
|
|
3
|
+
import { type VendorSettings } from "./vendors";
|
|
4
|
+
/**
|
|
5
|
+
* Phase-2 tracking runtime (forms-sdk-plan.md rows 51–55): binds one engine
|
|
6
|
+
* instance to the customer-tag lifecycle events and the ad-conversion rule
|
|
7
|
+
* engine. Trigger moments mirror `useConversionRules`' semantics translated
|
|
8
|
+
* from React effects to engine events:
|
|
9
|
+
*
|
|
10
|
+
* - `form_started`: the response row landing (first `saved`) in a session that
|
|
11
|
+
* began without a responseId — a resume never refires.
|
|
12
|
+
* - `step_submitted`: the first `stepCompleted` of the session.
|
|
13
|
+
* - `reached_step`: every step that becomes current (including the initial
|
|
14
|
+
* one), once per step — back-nav never refires.
|
|
15
|
+
* - terminal (`reached_qualified` + `form_submitted` union, deduped by rule):
|
|
16
|
+
* the `completed` event; a disqualified finish never fires.
|
|
17
|
+
* - `email_provided` / `meeting_booked`: first non-empty email answer / first
|
|
18
|
+
* scheduler state with `eventScheduled` — detected via `subscribe`.
|
|
19
|
+
*
|
|
20
|
+
* Every rule fires once per instance; `responseId` stays the platform-side
|
|
21
|
+
* dedupe key, so moments reached before the first write queue and flush when
|
|
22
|
+
* `saved` delivers the id (native's pending-refs, minus the React plumbing).
|
|
23
|
+
* All state is instance-scoped (§2.7) — two forms on one page track separately.
|
|
24
|
+
*/
|
|
25
|
+
export type TrackingSettings = VendorSettings & {
|
|
26
|
+
adsConversions?: AdConversionRule[];
|
|
27
|
+
};
|
|
28
|
+
export type AttachTrackingOptions = {
|
|
29
|
+
engine: FormEngine;
|
|
30
|
+
formId: string;
|
|
31
|
+
settings: TrackingSettings;
|
|
32
|
+
win?: Window;
|
|
33
|
+
};
|
|
34
|
+
export declare const attachTracking: ({ engine, formId, settings, win }: AttachTrackingOptions) => (() => void);
|
|
35
|
+
//# sourceMappingURL=attachTracking.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachTracking.d.ts","sourceRoot":"","sources":["../../../src/tracking/attachTracking.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAGL,KAAK,gBAAgB,EAItB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAA8C,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAE5F;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG;IAAE,cAAc,CAAC,EAAE,gBAAgB,EAAE,CAAA;CAAE,CAAC;AAExF,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAmDF,eAAO,MAAM,cAAc,sCAAuC,qBAAqB,KAAG,CAAC,MAAM,IAAI,CAkKpG,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachTracking.test.d.ts","sourceRoot":"","sources":["../../../src/tracking/attachTracking.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversionRules.contract.test.d.ts","sourceRoot":"","sources":["../../../src/tracking/conversionRules.contract.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ad-conversion rule engine (forms-sdk-plan.md row 55): a dependency-free port
|
|
3
|
+
* of form-render's `adConversions/{evaluateRules,fireConversion}.ts` and the
|
|
4
|
+
* `ZAdConversionRule` settings schema. The SDK always runs first-party, so the
|
|
5
|
+
* native parent-page/postMessage/ack hop disappears — matching rules fire
|
|
6
|
+
* directly on this window. Rule semantics, provider payloads, and the
|
|
7
|
+
* `responseId` dedupe key are identical by construction; the contract test
|
|
8
|
+
* validates these local types against the real Zod schema.
|
|
9
|
+
*/
|
|
10
|
+
export type AdConversionProvider = "x" | "meta" | "openai" | "ga4" | "linkedin";
|
|
11
|
+
export type AdConversionTrigger = "reached_qualified" | "form_submitted" | "reached_step" | "form_started" | "step_submitted" | "email_provided" | "meeting_booked";
|
|
12
|
+
export type AdConversionCondition = {
|
|
13
|
+
param: string;
|
|
14
|
+
operator: "equals_any" | "contains" | "is_present";
|
|
15
|
+
values?: string[];
|
|
16
|
+
};
|
|
17
|
+
export type AdConversionRule = {
|
|
18
|
+
id: string;
|
|
19
|
+
provider: AdConversionProvider;
|
|
20
|
+
trigger?: AdConversionTrigger;
|
|
21
|
+
triggerStepId?: string;
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
conditionsOperator?: "and" | "or";
|
|
24
|
+
conditions?: AdConversionCondition[];
|
|
25
|
+
eventId?: string;
|
|
26
|
+
pixelId?: string;
|
|
27
|
+
measurementId?: string;
|
|
28
|
+
eventName?: string;
|
|
29
|
+
partnerId?: string;
|
|
30
|
+
conversionId?: string;
|
|
31
|
+
};
|
|
32
|
+
export type TrackingParams = Record<string, string>;
|
|
33
|
+
export type ConversionClickIds = {
|
|
34
|
+
twclid?: string;
|
|
35
|
+
fbclid?: string;
|
|
36
|
+
gclid?: string;
|
|
37
|
+
li_fat_id?: string;
|
|
38
|
+
};
|
|
39
|
+
export type FireContext = {
|
|
40
|
+
responseId: string;
|
|
41
|
+
email?: string;
|
|
42
|
+
} & ConversionClickIds;
|
|
43
|
+
export type ConversionEvent = {
|
|
44
|
+
provider: "x";
|
|
45
|
+
event: {
|
|
46
|
+
event_id: string;
|
|
47
|
+
pixel_id: string;
|
|
48
|
+
};
|
|
49
|
+
} | {
|
|
50
|
+
provider: "meta";
|
|
51
|
+
event: {
|
|
52
|
+
pixel_id: string;
|
|
53
|
+
event_name: string;
|
|
54
|
+
};
|
|
55
|
+
} | {
|
|
56
|
+
provider: "openai";
|
|
57
|
+
event: {
|
|
58
|
+
pixel_id: string;
|
|
59
|
+
event_name: string;
|
|
60
|
+
};
|
|
61
|
+
} | {
|
|
62
|
+
provider: "ga4";
|
|
63
|
+
event: {
|
|
64
|
+
measurement_id: string;
|
|
65
|
+
event_name: string;
|
|
66
|
+
};
|
|
67
|
+
} | {
|
|
68
|
+
provider: "linkedin";
|
|
69
|
+
event: {
|
|
70
|
+
partner_id: string;
|
|
71
|
+
conversion_id: string;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
export declare const DEFAULT_META_EVENT_NAME = "Lead";
|
|
75
|
+
export declare const DEFAULT_GA4_EVENT_NAME = "generate_lead";
|
|
76
|
+
export declare const DEFAULT_OPENAI_EVENT_NAME = "lead_created";
|
|
77
|
+
export declare const matchesCondition: (condition: AdConversionCondition, params: TrackingParams) => boolean;
|
|
78
|
+
export declare const matchesConditions: (conditions: AdConversionCondition[], params: TrackingParams, operator?: "and" | "or") => boolean;
|
|
79
|
+
export declare const ruleToConversionEvent: (rule: AdConversionRule) => ConversionEvent | null;
|
|
80
|
+
/** Rules firing for this trigger given the page's tracking params; `reached_step` also needs the step id to match. */
|
|
81
|
+
export declare const selectFiringEvents: (rules: AdConversionRule[], trigger: AdConversionTrigger, params: TrackingParams, stepId?: string) => {
|
|
82
|
+
rule: AdConversionRule;
|
|
83
|
+
conversion: ConversionEvent;
|
|
84
|
+
}[];
|
|
85
|
+
/** Fire one conversion on this window. Best-effort: silent under ad blockers, never throws into the form. */
|
|
86
|
+
export declare const fireConversion: (win: Window, conversion: ConversionEvent, ctx: FireContext) => boolean;
|
|
87
|
+
//# sourceMappingURL=conversionRules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversionRules.d.ts","sourceRoot":"","sources":["../../../src/tracking/conversionRules.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversionRules.test.d.ts","sourceRoot":"","sources":["../../../src/tracking/conversionRules.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Customer-tag bootstrap + lifecycle emission (forms-sdk-plan.md rows 51–54,
|
|
3
|
+
* 58): GTM container, GA4, Meta Pixel, and HubSpot from the form's settings,
|
|
4
|
+
* with the same don't-double-inject guards as the native runtime and the
|
|
5
|
+
* parent tag. No React, no react-gtm-module / react-facebook-pixel — direct
|
|
6
|
+
* global bootstraps like the parent tag's providers.
|
|
7
|
+
*
|
|
8
|
+
* Three native quirks are FIXED here by decision (2026-08-10, memory +
|
|
9
|
+
* plan rows 51–52) — the parity harness whitelists them as intentional diffs:
|
|
10
|
+
* - GTM `SurfaceFormSubmitEvent` carries `formId` (native omits it).
|
|
11
|
+
* - `stepId` in `SurfaceFormStepSubmitted` is the real step id (native sends
|
|
12
|
+
* the step *name*) — migration note for name-keyed GTM triggers.
|
|
13
|
+
* - GA4 config does NOT hardcode `debug_mode: true`.
|
|
14
|
+
*/
|
|
15
|
+
export type LifecycleUrlData = {
|
|
16
|
+
url: string;
|
|
17
|
+
urlParams: Record<string, string>;
|
|
18
|
+
};
|
|
19
|
+
export type VendorSettings = {
|
|
20
|
+
gtmId?: string;
|
|
21
|
+
gaMeasurementId?: string;
|
|
22
|
+
metaPixelId?: string;
|
|
23
|
+
hubspotTracking?: {
|
|
24
|
+
trackingId?: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export type StepSubmittedPayload = LifecycleUrlData & {
|
|
28
|
+
/** Quirk fix: the real step id — native sends the step name here. */
|
|
29
|
+
stepId: string;
|
|
30
|
+
formId: string;
|
|
31
|
+
emailProvided: boolean;
|
|
32
|
+
meetingBooked: boolean;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* One instance per SurfaceForms.init (no module-level singletons — §2.7):
|
|
36
|
+
* injects the configured vendors immediately, exposes the three lifecycle
|
|
37
|
+
* emitters with native event names/payload keys (they are customers' GTM
|
|
38
|
+
* trigger contracts).
|
|
39
|
+
*/
|
|
40
|
+
export declare const createVendorTracker: (win: Window, settings: VendorSettings) => {
|
|
41
|
+
formStarted: (formId: string, urlData: LifecycleUrlData) => void;
|
|
42
|
+
stepSubmitted: (payload: StepSubmittedPayload) => void;
|
|
43
|
+
formCompleted: (formId: string, urlData: LifecycleUrlData) => void;
|
|
44
|
+
};
|
|
45
|
+
export type VendorTracker = ReturnType<typeof createVendorTracker>;
|
|
46
|
+
//# sourceMappingURL=vendors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vendors.d.ts","sourceRoot":"","sources":["../../../src/tracking/vendors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,MAAM,MAAM,gBAAgB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC;AAElF,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C,CAAC;AAwEF,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG;IACpD,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IAGf,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,QAAS,MAAM,YAAY,cAAc;IAsBrE,WAAW,WAAW,MAAM,WAAW,gBAAgB;IAKvD,aAAa,YAAY,oBAAoB;IAK7C,aAAa,WAAW,MAAM,WAAW,gBAAgB;CAO5D,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vendors.test.d.ts","sourceRoot":"","sources":["../../../src/tracking/vendors.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EmailValidator } from "../core/client/emailValidation";
|
|
2
|
+
import type { FormEngine } from "../core/engine/formEngine";
|
|
3
|
+
export type AttachEmailValidationOptions = {
|
|
4
|
+
engine: FormEngine;
|
|
5
|
+
validator: EmailValidator;
|
|
6
|
+
};
|
|
7
|
+
export declare const attachEmailValidation: ({ engine, validator }: AttachEmailValidationOptions) => (() => void);
|
|
8
|
+
//# sourceMappingURL=attachEmailValidation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachEmailValidation.d.ts","sourceRoot":"","sources":["../../../src/validation/attachEmailValidation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAyB,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAC5F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AA0B5D,MAAM,MAAM,4BAA4B,GAAG;IACzC,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,cAAc,CAAC;CAC3B,CAAC;AAEF,eAAO,MAAM,qBAAqB,0BAA2B,4BAA4B,KAAG,CAAC,MAAM,IAAI,CA0FtG,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachEmailValidation.test.d.ts","sourceRoot":"","sources":["../../../src/validation/attachEmailValidation.test.ts"],"names":[],"mappings":""}
|
package/docs/api.md
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Surface Forms SDK — API reference
|
|
2
|
+
|
|
3
|
+
Every public export of the package, with semantics — so nothing requires reading
|
|
4
|
+
the bundle. Companion docs: `llms.txt` (binding contract + boot, read it first),
|
|
5
|
+
`skills/surface-forms-sdk/` (workflow, HTML patterns, editing, debugging).
|
|
6
|
+
Import everything from the package name you installed; React bits from the
|
|
7
|
+
`/react` subpath. All types below also ship as `.d.ts`.
|
|
8
|
+
|
|
9
|
+
Layering: **`SurfaceForms.init` composes everything in this file for you.**
|
|
10
|
+
Sections 1–6 are the surface most integrations touch. Section 7 documents the
|
|
11
|
+
building blocks individually for advanced/offline callers assembling their own
|
|
12
|
+
stack (custom transport, server-side tests, partial adoption) — using them
|
|
13
|
+
directly is never required.
|
|
14
|
+
|
|
15
|
+
This file is the complete MAP (every export appears here); the exhaustive
|
|
16
|
+
per-domain detail — full type bodies, option tables, exact runtime semantics —
|
|
17
|
+
lives in `docs/reference/`. Open the one matching your question:
|
|
18
|
+
|
|
19
|
+
| Deep reference | Covers |
|
|
20
|
+
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| [`reference/engine.md`](reference/engine.md) | `createFormEngine` (options, methods, events, write-queue/delivery semantics), all typed answer states, response-payload builders, urlData/urlPrefill/redirect |
|
|
22
|
+
| [`reference/clients.md`](reference/clients.md) | transport, runtime-config fetch + helpers, response senders, the full `SchedulerClient`, email validation (client + watcher), enrichment watcher |
|
|
23
|
+
| [`reference/identity.md`](reference/identity.md) | fingerprint (full type + wire contract), identify payload/result, tag-compatible lead cache, `ensureIdentified`, page-view journey |
|
|
24
|
+
| [`reference/events.md`](reference/events.md) | pushEvent pipeline wire types, form-journey telemetry, view/started/drop-off detectors, the emitter primitive |
|
|
25
|
+
| [`reference/binding.md`](reference/binding.md) | `bindForm`'s exact resolution algorithm + choice-group builders, `mountBookingWidget` (all options, DOM tree, behavior), React adapter |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 1. Boot — `SurfaceForms.init(options): Promise<SurfaceForm>`
|
|
30
|
+
|
|
31
|
+
Fetches the form record, identifies the visitor, creates the engine, and wires
|
|
32
|
+
every enabled module (DOM binding, tracking, enrichment, email validation,
|
|
33
|
+
journey, drop-off). Throws if the record fetch fails — the error names the
|
|
34
|
+
exact URL and host it tried.
|
|
35
|
+
|
|
36
|
+
### `SurfaceFormsInitOptions`
|
|
37
|
+
|
|
38
|
+
| Option | Type | Meaning |
|
|
39
|
+
| --------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
40
|
+
| `environmentId` | `string` | Required. From `get_sdk_binding_map`. |
|
|
41
|
+
| `formId` | `string` | Required. |
|
|
42
|
+
| `apiBaseUrl` | `string?` | The environment's host. **Required unless the environment is on `https://forms.withsurface.com`** — `get_sdk_binding_map` returns it. |
|
|
43
|
+
| `container` | `Element?` | Enables the `data-*` binding layer + view detection on this root (§4). Omit for fully headless use. |
|
|
44
|
+
| `steps` | `Array<{id, endStepKind?}>?` | Caller-provided step order — **skips the record fetch entirely** (offline mode). Normally omitted. |
|
|
45
|
+
| `settings` | object? | Behavioral gates + tracking config (`trackPartialResponses`, `trackCookies`, `gtmId`, `gaMeasurementId`, `metaPixelId`, `hubspotTracking`, `adsConversions`). Caller values win over the record's. |
|
|
46
|
+
| `expectedQuestionIds` | `string[]?` | Overrides the record-derived list behind the unbound-question console warning. |
|
|
47
|
+
| `resolveNextStep` | `ResolveNextStep?` | Routing hook for `next()`/`submit()` (§7.3); defaults to linear record order. |
|
|
48
|
+
| `urlDataStateId` | `string?` | Seeds a `URL_DATA` attribution pseudo-answer under this id (rides every save). |
|
|
49
|
+
| `source` | `LeadSource?` | Lead source for identify; defaults `"sdkForm"`. |
|
|
50
|
+
| `journey` | `boolean?` | Page-view tracking module (§7.6), default off. |
|
|
51
|
+
| `emailValidation` | `boolean?` | Automatic waterfall email validation (§5), default off — verdicts bill the form owner's validator quota. |
|
|
52
|
+
| `preview` | `boolean?` | Blocks ALL network writes (identify, responses, events, journey, enrichment, tracking). Scheduler returns synthetic bookings. |
|
|
53
|
+
| `previewToken` | `string?` | With `preview: true`, serves a never-published form's DRAFT config. From `create_form` / `get_sdk_binding_map`. |
|
|
54
|
+
| `resume` | `{responseId, resumeToken}?` | Continue a stored response (§3). Ignored in preview. |
|
|
55
|
+
| `debug` | `boolean?` | Logs lifecycle + audits every answer against the record; warns on states the server would reject and on inert config (declared enrichment source, no provider). Dev only. |
|
|
56
|
+
| `meta` | `Record<string, unknown>?` | Seed response meta. `surfaceTagStatus` is always overwritten to `"sdk"`. |
|
|
57
|
+
|
|
58
|
+
## 2. The form handle — `SurfaceForm`
|
|
59
|
+
|
|
60
|
+
Everything the engine exposes (§7.3) minus internals, plus init-level helpers.
|
|
61
|
+
Full method semantics, event payloads, and delivery guarantees:
|
|
62
|
+
[`reference/engine.md`](reference/engine.md).
|
|
63
|
+
|
|
64
|
+
| Member | Signature | Semantics |
|
|
65
|
+
| ------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
66
|
+
| `setAnswer` | `(questionId, state) => void` | Merges a partial state into the question's answer. Use the typed answer states (§6) — wrong shapes are the classic production 400. |
|
|
67
|
+
| `next` / `back` | `() => Promise<void>` / `() => void` | Linear advance (saves progress; the step being LEFT is stamped on the write) / rewind (never writes). |
|
|
68
|
+
| `goToStep` | `(stepId) => Promise<void>` | Customer-owned routing: forward jumps save like `next()`; jumps to a visited step rewind like `back()`. Rejects unknown ids. |
|
|
69
|
+
| `submit` | `() => Promise<void>` | Finishes the form. **The terminal write is awaited** — when this resolves (and `saved` fires with `finished: true`), the server has the finished response; a delivery failure emits `error` instead. Then reveals the record's `thank_you` step. |
|
|
70
|
+
| `disqualify` | `() => Promise<void>` | Finishes as disqualified (`invalidResponse: true`); reveals the `disqualified` ending. |
|
|
71
|
+
| `savePartial` | `() => Promise<void>` | Explicit mid-form save (fire-and-forget beacon delivery). |
|
|
72
|
+
| `flush` | `() => Promise<void>` | Resolves when every queued write (including post-finish amends) has settled. Await before programmatic teardown. |
|
|
73
|
+
| `markViewed` | `() => void` | Emits `form_viewed` + first-step `form_step_viewed`, once. The bind layer calls it via IntersectionObserver; headless callers call it themselves. |
|
|
74
|
+
| `state` | `() => FormEngineState` | `{ currentStepId, answers, externalStates, responseId, resumeToken, finished }`. Referentially stable between mutations (safe for `useSyncExternalStore`). |
|
|
75
|
+
| `subscribe` | `(cb) => Unsubscribe` | Store subscription (fires on any state change). |
|
|
76
|
+
| `on` | `(event, cb) => Unsubscribe` | Lifecycle events: `viewed`, `started`, `stepChanged {fromStepId,toStepId}`, `stepCompleted {stepId}`, `completed {formId}`, `disqualified {stepId}`, `saved {responseId, finished, resumeToken}`, `error {scope, error}`. |
|
|
77
|
+
| `setExternalState` | `(id, {type, ...}) => void` | Writes a pseudo-answer that rides every save (enrichment results, custom attribution). Post-finish it amends the stored response. |
|
|
78
|
+
| `mergeMeta` | `(partial) => void` | Records caller-computed values (qualification score, experiment bucket) onto response `meta`; rides the next save or amends post-finish. Reserved SDK keys (`surfaceTagStatus`, email-validation verdicts) are dropped with a warning. |
|
|
79
|
+
| `identify` | `() => Promise<unknown>` | Re-runs visitor identify (SPA route change); later writes attribute to the fresh session. |
|
|
80
|
+
| `validateEmail` | `(email) => Promise<EmailValidationResult>` | On-demand deliverability verdict (§5). |
|
|
81
|
+
| `capabilities` | `{enrichment, emailValidation, tracking}` | What actually runs in THIS environment — a record can declare features the environment doesn't back. |
|
|
82
|
+
| `runtimeConfig` | `RuntimeConfig?` | The fetched record (undefined when `steps` was caller-provided). |
|
|
83
|
+
| `destroy` | `() => void` | Tears down every module binding. One engine per page-life — re-`init` without `destroy` creates duplicate responses. |
|
|
84
|
+
|
|
85
|
+
## 3. Resume
|
|
86
|
+
|
|
87
|
+
The first save returns `resumeToken` (on `saved` and in `state()`). Persist
|
|
88
|
+
`{responseId, resumeToken}` and pass as `init({ resume })`: boot fetches the
|
|
89
|
+
stored answers through the token-gated read-back, seeds the engine (writes
|
|
90
|
+
become updates, stored answers ride every resend), and `state().answers` holds
|
|
91
|
+
them for prefill. A failed fetch warns and starts fresh. Never build resume
|
|
92
|
+
links from responseIds alone — the token is the authorization.
|
|
93
|
+
|
|
94
|
+
## 4. DOM binding — `bindForm({ engine, root, expectedQuestionIds? }): () => void`
|
|
95
|
+
|
|
96
|
+
Exact resolution algorithm and choice-group builders: [`reference/binding.md`](reference/binding.md).
|
|
97
|
+
|
|
98
|
+
What `init` wires when `container` is passed; exported for callers composing
|
|
99
|
+
their own engine. Discovers `data-question-id` / `data-field-name` /
|
|
100
|
+
`data-question-type` / `data-step-id` and the navigation markers, captures
|
|
101
|
+
input events into `setAnswer` (seeding values already present in the DOM —
|
|
102
|
+
autofill/SSR), builds choice-group answers from radio/checkbox groups, shows
|
|
103
|
+
one step container at a time, and warns on record questionIds with no bound
|
|
104
|
+
element. Returns a teardown. Full attribute contract: `llms.txt`.
|
|
105
|
+
|
|
106
|
+
## 5. Email validation
|
|
107
|
+
|
|
108
|
+
- **`createEmailValidator({ transport, environmentId, preview? }): EmailValidator`** —
|
|
109
|
+
`validate(email)` / `validateBatch(emails)` against
|
|
110
|
+
`POST /api/v1/edge/emailValidation` (provider waterfall + the environment's
|
|
111
|
+
spam blacklist; keys stay server-side). Verdicts are cached per address for
|
|
112
|
+
the validator's lifetime — including failures — so nothing is billed twice.
|
|
113
|
+
**Fail-open**: when no validator can run the result is
|
|
114
|
+
`{ isValid: true, failed: true, status: "unknown" }`; the same address can
|
|
115
|
+
later resolve to a definitive verdict. Preview returns synthetic valid
|
|
116
|
+
verdicts with no network.
|
|
117
|
+
- **`EmailValidationResult`** — `{ email, isValid, status?, reason?, blacklistEntryIds?, failed? }`.
|
|
118
|
+
- **`attachEmailValidation({ engine, validator }): () => void`** — the
|
|
119
|
+
`emailValidation: true` watcher: debounced (300 ms, native's) validation of
|
|
120
|
+
email-bearing answers (`email`, `emailAddress`, `workEmailAddress`), flushed
|
|
121
|
+
on step completion, writing native's response-meta verdict keys
|
|
122
|
+
(`isEmailValid`, `emailValidation`, `emailValidationFailed`) with native
|
|
123
|
+
aggregation (any valid email wins; fail-open counts valid; the window
|
|
124
|
+
follows the live answers).
|
|
125
|
+
|
|
126
|
+
## 6. Typed answer states
|
|
127
|
+
|
|
128
|
+
Full type bodies: [`reference/engine.md`](reference/engine.md).
|
|
129
|
+
|
|
130
|
+
Local mirrors of each component's wire schema, contract-tested against the
|
|
131
|
+
server's Zod schemas. Annotate headless `setAnswer` calls with them:
|
|
132
|
+
|
|
133
|
+
`ShortInputAnswer` `{type:"ShortInput", input}` · `LongInputAnswer` ·
|
|
134
|
+
`EmailFormAnswer` `{type:"EmailForm", email, subscriptionCheck?}` ·
|
|
135
|
+
`DropdownAnswer` `{type:"Dropdown", selected}` · `DisclaimerAnswer` ·
|
|
136
|
+
`FileUploaderAnswer` `{type:"FileUploader", fileUrl?}` ·
|
|
137
|
+
`MultipleOptionsAnswer` `{type, choices: [{key, value, index}, …]}` (the ENTIRE
|
|
138
|
+
option list, one entry per record option) · `MMChoiceAnswer` /
|
|
139
|
+
`MultipleChoiceAnswer` `{type, choices: [{key, value}, …]}` ·
|
|
140
|
+
`GraphicOptionsAnswer` `{type, value, index}` ·
|
|
141
|
+
`MultipleChoiceSingleAnswerAnswer` `{type, value, index}` ·
|
|
142
|
+
`IdentityInfoAnswer` (keys from `IDENTITY_INFO_ANSWER_KEYS`:
|
|
143
|
+
`firstName`, `lastName`, `fullName`, `emailAddress`, `workEmailAddress`,
|
|
144
|
+
`phoneNumber`, `companyName`, `numberOfEmployees`, `websiteUrl`, `title`, …) ·
|
|
145
|
+
`SchedulerAnswer` `{type:"CalendlyScreen"|"DynamicScheduler", eventScheduled, uri, …}` ·
|
|
146
|
+
union **`KnownAnswerState`**.
|
|
147
|
+
|
|
148
|
+
The five choice types have DIFFERENT shapes — trust the binding map's
|
|
149
|
+
`answerShape`, never a remembered one.
|
|
150
|
+
|
|
151
|
+
## 7. Building blocks (what `init` composes)
|
|
152
|
+
|
|
153
|
+
### 7.1 Transport
|
|
154
|
+
|
|
155
|
+
- **`createTransport({ apiBaseUrl? }): Transport`** — `get`/`post` (typed
|
|
156
|
+
`TransportResult<T>` = `{ok:true,data} | {ok:false,error:TransportError}`,
|
|
157
|
+
never throws; `post` opts: `keepalive`, `credentials`) and `beacon`
|
|
158
|
+
(sendBeacon-first, `fetch(keepalive)` fallback, failures swallowed). Every
|
|
159
|
+
request carries `X-Surface-Sdk-Version` (beacons: `?sdv=` param).
|
|
160
|
+
- **`DEFAULT_API_BASE_URL`** — `"https://forms.withsurface.com"`.
|
|
161
|
+
|
|
162
|
+
### 7.2 Runtime config
|
|
163
|
+
|
|
164
|
+
- **`fetchRuntimeConfig(transport, formId, { preview?, previewToken? })`** —
|
|
165
|
+
`GET /api/v1/public/forms/{formId}/runtime-config` (the same payload the
|
|
166
|
+
hosted page reads; `settings.password` never arrives). `?preview=1&pt=`
|
|
167
|
+
serves a never-published form's draft.
|
|
168
|
+
- Helpers over the fetched `RuntimeConfig`: **`stepsOf(config)`** (ordered
|
|
169
|
+
`{id, endStepKind?}`), **`statefulQuestionsOf`** / **`statefulQuestionIdsOf`**
|
|
170
|
+
(answer-recording questions), **`enrichmentSourcesOf`** (questionId +
|
|
171
|
+
fieldNames that can trigger enrichment), **`hasEnabledEnrichmentProvider(settings)`**.
|
|
172
|
+
|
|
173
|
+
### 7.3 Engine — `createFormEngine(options): FormEngine`
|
|
174
|
+
|
|
175
|
+
Full option table, method semantics, and event payloads: [`reference/engine.md`](reference/engine.md).
|
|
176
|
+
|
|
177
|
+
The headless core: answers, traversal, response writes, lifecycle events.
|
|
178
|
+
Options: `formId`, `environmentId?`, `steps` (required), `transport`,
|
|
179
|
+
`resolveNextStep?`, `trackPartialResponses?` (default true),
|
|
180
|
+
`trackCookies?`, `externalStates?`, `meta?`, `identity?` (a GETTER, read at
|
|
181
|
+
write time), `pushEvents?`, `formJourney?`, `preview?`, `resume?`.
|
|
182
|
+
|
|
183
|
+
Semantics that matter:
|
|
184
|
+
|
|
185
|
+
- Partial saves happen on forward movement only, stamped with the step being
|
|
186
|
+
left; the first forward move creates the response, later moves update it.
|
|
187
|
+
- Every write-initiating call runs through one queue — double-clicks advance
|
|
188
|
+
once, a `submit()` during an in-flight `next()` queues behind it, nothing
|
|
189
|
+
writes after the finishing submit.
|
|
190
|
+
- The FINISHING write is an awaited `fetch(keepalive)`; partial updates are
|
|
191
|
+
beacons. A failed terminal write emits `error` (with a best-effort beacon
|
|
192
|
+
fallback) and never fires `saved`.
|
|
193
|
+
- `setExternalState` / `mergeMeta` after finish amend the stored response by
|
|
194
|
+
re-issuing the terminal update (full-answer-set resends are idempotent).
|
|
195
|
+
- `resolveNextStep(currentStepId, answers) => {stepId, reason?} | "END" | "INVALID"`
|
|
196
|
+
— `"END"` finishes, `"INVALID"` disqualifies; default is linear order.
|
|
197
|
+
- **`ResolvedNextStep`**, **`ResolveNextStep`**, **`FormEngineState`**,
|
|
198
|
+
**`FormEngineEvents`** types exported.
|
|
199
|
+
|
|
200
|
+
### 7.4 Response wire
|
|
201
|
+
|
|
202
|
+
- **`submitResponse(transport, payload)`** — `POST /api/v1/responses/`
|
|
203
|
+
(credentialed: reads/refreshes the `{formId}_pa` repeat-visitor cookie);
|
|
204
|
+
returns `{responseId, leadId, resumeToken}`.
|
|
205
|
+
- **`updateResponse(transport, payload)`** — beacon to `/api/v1/responses/update`.
|
|
206
|
+
- **`createSubmitResponsePayload`** / **`createUpdateResponsePayload`** /
|
|
207
|
+
**`buildResponsesArray`** — payload builders mirroring the native runtime:
|
|
208
|
+
full answer-set resend, external-state pseudo-answers, scheduler-availability
|
|
209
|
+
skip, booking detection, `responseShapeVersion: "2.0"`. Types:
|
|
210
|
+
`FormResponsePayload`, `FormUpdateResponsePayload`, `SingleFormResponse`,
|
|
211
|
+
`AnswerStates`, `ExternalStates`.
|
|
212
|
+
|
|
213
|
+
### 7.5 Identity
|
|
214
|
+
|
|
215
|
+
- **`getBrowserFingerprint(environmentId): Promise<Fingerprint>`** /
|
|
216
|
+
**`getHash(input)`** — the canonical fingerprint; serialization is a wire
|
|
217
|
+
contract (do not derive your own).
|
|
218
|
+
- **`identifyLead(transport, payload, opts?)`** / **`buildIdentifyPayload`** —
|
|
219
|
+
`POST /api/v1/lead/identify`; null on any failure. **`LeadSource`** union
|
|
220
|
+
(SDK default `"sdkForm"`). **`IdentifyPayload`**, **`IdentifyResult`**.
|
|
221
|
+
- **`ensureIdentified({ transport, environmentId, formId, source?, preview? })`**
|
|
222
|
+
— the boot step: fresh cache skips the network; otherwise fingerprints,
|
|
223
|
+
carries cached ids forward, caches the result.
|
|
224
|
+
- **`readLeadData()`** / **`writeLeadData(data)`** +
|
|
225
|
+
**`LEAD_DATA_STORAGE_KEY`** (`"surfaceLeadData"`), **`LEAD_DATA_TTL_MS`**
|
|
226
|
+
(10 min) — the tag-compatible identity cache: on a page that also runs the
|
|
227
|
+
Surface tag, both read the same entry and agree on the visitor.
|
|
228
|
+
|
|
229
|
+
### 7.6 Analytics / events
|
|
230
|
+
|
|
231
|
+
- **`createPushEventClient({ transport, preview? })`** — the internal event
|
|
232
|
+
pipeline (`POST /api/v1/pushEvent`): bot filter, drop-on-invalid,
|
|
233
|
+
beacon-first. **`buildStepTransitionEventBatch`** builds the
|
|
234
|
+
`form_step_submitted` + `form_step_viewed` pair. Wire types exported
|
|
235
|
+
(`PushEventPayload`, `PushEventBatch`, `SurfaceEventType`, …).
|
|
236
|
+
- **`createFormJourneyTracker({ transport, preview? })`** — step-journey
|
|
237
|
+
telemetry (`POST /api/v1/form-journey/track`): visit ordinals, 50-visit cap,
|
|
238
|
+
terminal freeze, microtask coalescing. Types: `FormJourneyTracker`,
|
|
239
|
+
`FormJourneyOutcome`, `FormJourneyTransitionReason`, ….
|
|
240
|
+
- **`createJourneyTracker({ transport, environmentId })`** +
|
|
241
|
+
**`JOURNEY_COOKIE_NAME`** / **`RECENT_VISIT_COOKIE_NAME`** — the `journey:
|
|
242
|
+
true` page-view module (tag-compatible cookies, duplicate-URL skip, SPA
|
|
243
|
+
history patching; `observeRouteChanges()` returns a teardown).
|
|
244
|
+
- **`observeFormView(element, onViewed)`** — IntersectionObserver, fires once
|
|
245
|
+
(immediately when IO is unavailable).
|
|
246
|
+
- **`observeDropoff(pushEvents, getState)`** — `form_step_dropoff` on
|
|
247
|
+
`pagehide` (never for bfcache suspensions, only after the form started).
|
|
248
|
+
- **`createStartedDetector(initialAnswers)`** — returns a checker that fires
|
|
249
|
+
true exactly once, on the first deep-inequality vs the snapshot.
|
|
250
|
+
- **`createEmitter<EventMap>()`** — the typed, instance-scoped emitter
|
|
251
|
+
primitive (`Emitter`, `Unsubscribe`); handler exceptions are isolated.
|
|
252
|
+
|
|
253
|
+
### 7.7 Page context
|
|
254
|
+
|
|
255
|
+
- **`buildUrlData({ trackCookies?, defaultUrl? }): UrlData`** +
|
|
256
|
+
**`constructUrlParamsObject`** / **`parseCookiesObject`** — the `URL_DATA`
|
|
257
|
+
attribution entry (fullUrl/params/referrer/cookies) read from the page.
|
|
258
|
+
- **`parsePrefillParams(search)`** / **`parseQuestionId`** — the
|
|
259
|
+
`?<questionId>_<fieldName>=value` URL-prefill syntax (splits on the FIRST
|
|
260
|
+
underscore; `url`/`referrer`/`cookies` reserved).
|
|
261
|
+
- **`buildRedirectUrl(url, { leadId?, sessionId? })`** /
|
|
262
|
+
**`performRedirect({ url, type, preview? })`** +
|
|
263
|
+
**`DEFAULT_REDIRECT_TIMEOUT_MS`** (400 ms), **`RedirectType`**
|
|
264
|
+
(`"NewTab" | "CurrentWindow"`) — ending-redirect helpers with the identity
|
|
265
|
+
handoff query params; preview never navigates.
|
|
266
|
+
|
|
267
|
+
### 7.8 Scheduler + enrichment
|
|
268
|
+
|
|
269
|
+
Full `SchedulerClient` signatures and widget options: [`reference/clients.md`](reference/clients.md) + [`reference/binding.md`](reference/binding.md).
|
|
270
|
+
|
|
271
|
+
- **`createSchedulerClient({ transport, preview? }): SchedulerClient`** —
|
|
272
|
+
public Surface-scheduler endpoints: `getPublicEventType`,
|
|
273
|
+
`getAvailabilitySummary`, `getMonthAvailability` (`YYYY-MM`), `getSlots`,
|
|
274
|
+
`book` (threads `responseId`/`leadSessionId`/`fingerprint` into the booking
|
|
275
|
+
meta for booking↔response linkage), `reschedule`. Preview fabricates
|
|
276
|
+
bookings without network. Types: `Booking`, `TimeSlot`,
|
|
277
|
+
`AvailabilitySummary`, `BookingAttendee`.
|
|
278
|
+
- **`toSchedulerAnswerState(booking)`** — the persisted scheduler answer
|
|
279
|
+
(emits `type: "DynamicScheduler"`; override `type` to the record's component
|
|
280
|
+
type).
|
|
281
|
+
- **`mountBookingWidget(options)`** — see `llms.txt` (DOM tree, labels,
|
|
282
|
+
`formatters`, `data-value`).
|
|
283
|
+
- **`attachEnrichment({ engine, transport, formId, sources })`** +
|
|
284
|
+
**`ENRICHMENT_STATE_ID`** (`"selfServeEnrichment"`), **`EnrichmentSource`** —
|
|
285
|
+
the real-time enrichment watcher: valid email/domain stable for the debounce
|
|
286
|
+
fires `POST /api/v1/services/enrichment/realtime` once per value; the result
|
|
287
|
+
becomes an external state that rides every save as `enrichedFields`.
|
|
288
|
+
|
|
289
|
+
## 8. React — `import { useSurfaceForm } from "<package>/react"`
|
|
290
|
+
|
|
291
|
+
**`useSurfaceForm(engine): UseSurfaceFormResult`** — a `useSyncExternalStore`
|
|
292
|
+
adapter over any `FormEngine` (including the handle from `init`): returns
|
|
293
|
+
`{ state, setAnswer, next, back, goToStep, submit, disqualify, savePartial,
|
|
294
|
+
markViewed, on }`. `state` is referentially stable between engine mutations —
|
|
295
|
+
render controlled inputs from it directly. React is an optional peer
|
|
296
|
+
dependency; the main entry is React-free.
|
|
297
|
+
|
|
298
|
+
## 9. Cross-cutting guarantees
|
|
299
|
+
|
|
300
|
+
- **Zero runtime dependencies**; SSR-safe module scope; instance-scoped state
|
|
301
|
+
(multi-form pages work).
|
|
302
|
+
- **Never break the host page**: transports resolve typed results instead of
|
|
303
|
+
throwing; event-handler exceptions are isolated.
|
|
304
|
+
- **Preview writes nothing**: identify, responses, events, journey, enrichment,
|
|
305
|
+
tracking, and email validation (synthetic verdicts) are all blocked.
|
|
306
|
+
- **Delivery**: creates + the finishing update are awaited; mid-form updates
|
|
307
|
+
and analytics are beacon-first fire-and-forget. `flush()` before
|
|
308
|
+
programmatic teardown.
|
|
309
|
+
- Send `X-Surface-Sdk-Version` on everything — visible server-side for rate
|
|
310
|
+
limit observability.
|