@nodii/onboarding 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -2
- package/dist/chunk-7NS7ENJV.js +232 -0
- package/dist/chunk-7NS7ENJV.js.map +1 -0
- package/dist/chunk-AZE3NPSY.js +186 -0
- package/dist/chunk-AZE3NPSY.js.map +1 -0
- package/dist/chunk-BMQINH6O.js +401 -0
- package/dist/chunk-BMQINH6O.js.map +1 -0
- package/dist/chunk-F75ZJERM.js +199 -0
- package/dist/chunk-F75ZJERM.js.map +1 -0
- package/dist/chunk-FIRSA7EH.js +3 -0
- package/dist/chunk-FIRSA7EH.js.map +1 -0
- package/dist/chunk-IPAKPLIF.js +1660 -0
- package/dist/chunk-IPAKPLIF.js.map +1 -0
- package/dist/chunk-JFDQZ3AC.js +3 -0
- package/dist/chunk-JFDQZ3AC.js.map +1 -0
- package/dist/chunk-JFIONONR.js +3 -0
- package/dist/chunk-JFIONONR.js.map +1 -0
- package/dist/chunk-KQTS47FC.js +546 -0
- package/dist/chunk-KQTS47FC.js.map +1 -0
- package/dist/chunk-LVJM4CTG.js +306 -0
- package/dist/chunk-LVJM4CTG.js.map +1 -0
- package/dist/chunk-SEKNVHD3.js +59 -0
- package/dist/chunk-SEKNVHD3.js.map +1 -0
- package/dist/chunk-SIMLMQPT.js +37 -0
- package/dist/chunk-SIMLMQPT.js.map +1 -0
- package/dist/chunk-U56GQITG.js +79 -0
- package/dist/chunk-U56GQITG.js.map +1 -0
- package/dist/chunk-U6AXKGYQ.js +101 -0
- package/dist/chunk-U6AXKGYQ.js.map +1 -0
- package/dist/chunk-VH6FWDWI.js +3 -0
- package/dist/chunk-VH6FWDWI.js.map +1 -0
- package/dist/chunk-XJTJPBJB.js +191 -0
- package/dist/chunk-XJTJPBJB.js.map +1 -0
- package/dist/chunk-Z4PA6C2Z.js +3 -0
- package/dist/chunk-Z4PA6C2Z.js.map +1 -0
- package/dist/client-CqE2dF9A.d.ts +86 -0
- package/dist/consent/index.d.ts +59 -0
- package/dist/consent/index.js +7 -0
- package/dist/consent/index.js.map +1 -0
- package/dist/controller-BQb62D0N.d.ts +177 -0
- package/dist/dynamic/index.d.ts +235 -0
- package/dist/dynamic/index.js +6 -0
- package/dist/dynamic/index.js.map +1 -0
- package/dist/errors-vJkye4a0.d.ts +110 -0
- package/dist/form/index.d.ts +64 -0
- package/dist/form/index.js +5 -0
- package/dist/form/index.js.map +1 -0
- package/dist/index-Dqfa890U.d.ts +83 -0
- package/dist/index.d.ts +16 -2
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -0
- package/dist/model-ESLSEbi0.d.ts +640 -0
- package/dist/model-mnjAEtKe.d.ts +253 -0
- package/dist/recaptcha/index.d.ts +102 -0
- package/dist/recaptcha/index.js +4 -0
- package/dist/recaptcha/index.js.map +1 -0
- package/dist/render-DC7P2BGQ.js +5 -0
- package/dist/render-DC7P2BGQ.js.map +1 -0
- package/dist/schema-BrXb9gnh.d.ts +267 -0
- package/dist/signup-client-BL5DfarJ.d.ts +392 -0
- package/dist/spec/index.d.ts +17 -0
- package/dist/spec/index.js +5 -0
- package/dist/spec/index.js.map +1 -0
- package/dist/submit/index.d.ts +42 -0
- package/dist/submit/index.js +6 -0
- package/dist/submit/index.js.map +1 -0
- package/dist/viewer/index.d.ts +4 -0
- package/dist/viewer/index.js +5 -0
- package/dist/viewer/index.js.map +1 -0
- package/dist/widget/index.d.ts +262 -0
- package/dist/widget/index.js +12 -0
- package/dist/widget/index.js.map +1 -0
- package/package.json +96 -6
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { contactFormSchema } from './chunk-SIMLMQPT.js';
|
|
2
|
+
|
|
3
|
+
// src/form/state.ts
|
|
4
|
+
function zodIssuesToFieldErrors(issues) {
|
|
5
|
+
return issues.map((issue) => ({
|
|
6
|
+
path: issue.path.join("."),
|
|
7
|
+
message: issue.message
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
var INITIAL_VALUES = {
|
|
11
|
+
name: void 0,
|
|
12
|
+
companyName: "",
|
|
13
|
+
contactName: "",
|
|
14
|
+
email: "",
|
|
15
|
+
phone: void 0,
|
|
16
|
+
message: void 0,
|
|
17
|
+
interestModules: [],
|
|
18
|
+
sizeBand: void 0,
|
|
19
|
+
attribution: void 0,
|
|
20
|
+
privacyNoticeAcknowledged: false,
|
|
21
|
+
marketingOptIn: false,
|
|
22
|
+
intakeKind: "enquiry",
|
|
23
|
+
website: void 0
|
|
24
|
+
};
|
|
25
|
+
function createContactFormState() {
|
|
26
|
+
let state = {
|
|
27
|
+
values: { ...INITIAL_VALUES },
|
|
28
|
+
errors: [],
|
|
29
|
+
serverError: null,
|
|
30
|
+
status: "idle",
|
|
31
|
+
dirty: false
|
|
32
|
+
};
|
|
33
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
34
|
+
function emit() {
|
|
35
|
+
for (const listener of listeners) {
|
|
36
|
+
listener({ ...state, values: { ...state.values } });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function runValidation() {
|
|
40
|
+
const result = contactFormSchema.safeParse(state.values);
|
|
41
|
+
if (result.success) return [];
|
|
42
|
+
return zodIssuesToFieldErrors(result.error.issues);
|
|
43
|
+
}
|
|
44
|
+
let hasValidated = false;
|
|
45
|
+
return {
|
|
46
|
+
getState() {
|
|
47
|
+
return { ...state, values: { ...state.values } };
|
|
48
|
+
},
|
|
49
|
+
setValue(field, value) {
|
|
50
|
+
state = {
|
|
51
|
+
...state,
|
|
52
|
+
values: { ...state.values, [field]: value },
|
|
53
|
+
dirty: true
|
|
54
|
+
};
|
|
55
|
+
if (hasValidated) {
|
|
56
|
+
state = { ...state, errors: runValidation() };
|
|
57
|
+
}
|
|
58
|
+
emit();
|
|
59
|
+
},
|
|
60
|
+
validate() {
|
|
61
|
+
hasValidated = true;
|
|
62
|
+
const errors = runValidation();
|
|
63
|
+
state = { ...state, errors, dirty: true };
|
|
64
|
+
emit();
|
|
65
|
+
return errors.length === 0;
|
|
66
|
+
},
|
|
67
|
+
setSubmitting() {
|
|
68
|
+
state = { ...state, status: "submitting", serverError: null, errors: [] };
|
|
69
|
+
emit();
|
|
70
|
+
},
|
|
71
|
+
setSuccess() {
|
|
72
|
+
state = { ...state, status: "success", serverError: null };
|
|
73
|
+
emit();
|
|
74
|
+
},
|
|
75
|
+
setError(message) {
|
|
76
|
+
state = { ...state, status: "error", serverError: message };
|
|
77
|
+
emit();
|
|
78
|
+
},
|
|
79
|
+
reset() {
|
|
80
|
+
hasValidated = false;
|
|
81
|
+
state = {
|
|
82
|
+
values: { ...INITIAL_VALUES },
|
|
83
|
+
errors: [],
|
|
84
|
+
serverError: null,
|
|
85
|
+
status: "idle",
|
|
86
|
+
dirty: false
|
|
87
|
+
};
|
|
88
|
+
emit();
|
|
89
|
+
},
|
|
90
|
+
subscribe(listener) {
|
|
91
|
+
listeners.add(listener);
|
|
92
|
+
return () => {
|
|
93
|
+
listeners.delete(listener);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { createContactFormState };
|
|
100
|
+
//# sourceMappingURL=chunk-U6AXKGYQ.js.map
|
|
101
|
+
//# sourceMappingURL=chunk-U6AXKGYQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/form/state.ts"],"names":[],"mappings":"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","file":"chunk-U6AXKGYQ.js","sourcesContent":["/**\n * Headless form state manager for the contact form.\n *\n * Designed to be framework-agnostic — callers supply a change callback\n * and call the returned mutators. The widget layer uses this internally;\n * framework integrations (React, Vue, etc.) can wrap it in their own hooks.\n */\n\nimport type { ZodIssue } from \"zod/v3\";\nimport { contactFormSchema, type ContactFormValues } from \"./schema.js\";\n\nexport interface FieldError {\n path: string;\n message: string;\n}\n\nexport type FormStatus = \"idle\" | \"submitting\" | \"success\" | \"error\";\n\nexport interface ContactFormState {\n values: ContactFormValues;\n /** Per-field validation errors from the last validate() call. */\n errors: FieldError[];\n /** Cross-field / server error message. */\n serverError: string | null;\n status: FormStatus;\n /** True if values have been touched at least once. */\n dirty: boolean;\n}\n\nexport type ContactFormStateListener = (state: ContactFormState) => void;\n\nexport interface ContactFormControl {\n /** Current snapshot of form state. */\n getState(): ContactFormState;\n /** Update a single field value and re-validate if dirty. */\n setValue<K extends keyof ContactFormValues>(\n field: K,\n value: ContactFormValues[K],\n ): void;\n /**\n * Run full client-side validation. Returns true if valid.\n * Also marks the form dirty so future setValue calls auto-validate.\n */\n validate(): boolean;\n /** Mark the form as submitting. Called by the submit client. */\n setSubmitting(): void;\n /** Mark the form as successfully submitted. */\n setSuccess(): void;\n /** Set a server-side error (e.g. 429 rate limited). */\n setError(message: string): void;\n /** Reset to initial idle state. */\n reset(): void;\n /** Subscribe to state changes. Returns unsubscribe function. */\n subscribe(listener: ContactFormStateListener): () => void;\n}\n\nfunction zodIssuesToFieldErrors(issues: ZodIssue[]): FieldError[] {\n return issues.map((issue) => ({\n path: issue.path.join(\".\"),\n message: issue.message,\n }));\n}\n\nconst INITIAL_VALUES: ContactFormValues = {\n name: undefined,\n companyName: \"\",\n contactName: \"\",\n email: \"\",\n phone: undefined,\n message: undefined,\n interestModules: [],\n sizeBand: undefined,\n attribution: undefined,\n privacyNoticeAcknowledged: false,\n marketingOptIn: false,\n intakeKind: \"enquiry\",\n website: undefined,\n};\n\n/**\n * Create a new headless contact form state controller.\n *\n * @example\n * ```ts\n * const form = createContactFormState();\n * form.subscribe((state) => renderUI(state));\n * form.setValue(\"name\", \"Alice\");\n * const valid = form.validate();\n * if (valid) await submitContact(form.getState().values, botToken);\n * ```\n */\nexport function createContactFormState(): ContactFormControl {\n let state: ContactFormState = {\n values: { ...INITIAL_VALUES },\n errors: [],\n serverError: null,\n status: \"idle\",\n dirty: false,\n };\n\n const listeners = new Set<ContactFormStateListener>();\n\n function emit(): void {\n for (const listener of listeners) {\n listener({ ...state, values: { ...state.values } });\n }\n }\n\n function runValidation(): FieldError[] {\n const result = contactFormSchema.safeParse(state.values);\n if (result.success) return [];\n return zodIssuesToFieldErrors(result.error.issues);\n }\n\n // Once validate() has run at least once, EVERY subsequent setValue\n // revalidates — errors both clear when a field is fixed AND reappear\n // when a fixed field is broken again. (Previously the revalidation was\n // gated on errors.length > 0, so fix-then-rebreak silently showed no\n // error until the next explicit validate().)\n let hasValidated = false;\n\n return {\n getState() {\n return { ...state, values: { ...state.values } };\n },\n\n setValue(field, value) {\n state = {\n ...state,\n values: { ...state.values, [field]: value },\n dirty: true,\n };\n if (hasValidated) {\n state = { ...state, errors: runValidation() };\n }\n emit();\n },\n\n validate() {\n hasValidated = true;\n const errors = runValidation();\n state = { ...state, errors, dirty: true };\n emit();\n return errors.length === 0;\n },\n\n setSubmitting() {\n state = { ...state, status: \"submitting\", serverError: null, errors: [] };\n emit();\n },\n\n setSuccess() {\n state = { ...state, status: \"success\", serverError: null };\n emit();\n },\n\n setError(message) {\n state = { ...state, status: \"error\", serverError: message };\n emit();\n },\n\n reset() {\n hasValidated = false;\n state = {\n values: { ...INITIAL_VALUES },\n errors: [],\n serverError: null,\n status: \"idle\",\n dirty: false,\n };\n emit();\n },\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-VH6FWDWI.js"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// src/submit/errors.ts
|
|
2
|
+
var OnboardingError = class extends Error {
|
|
3
|
+
kind;
|
|
4
|
+
statusCode;
|
|
5
|
+
constructor(kind, message, statusCode = null) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "OnboardingError";
|
|
8
|
+
this.kind = kind;
|
|
9
|
+
this.statusCode = statusCode;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var OnboardingBotChallengeError = class extends OnboardingError {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(
|
|
15
|
+
"bot_challenge_failed",
|
|
16
|
+
message ?? "Bot challenge verification failed (403 bot_challenge_failed). Refresh the CAPTCHA and try again.",
|
|
17
|
+
403
|
|
18
|
+
);
|
|
19
|
+
this.name = "OnboardingBotChallengeError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var OnboardingConflictError = class extends OnboardingError {
|
|
23
|
+
constructor(statusCode = 409, message) {
|
|
24
|
+
super(
|
|
25
|
+
"conflict",
|
|
26
|
+
message ?? `Request conflicted with the current server state (${statusCode}).`,
|
|
27
|
+
statusCode
|
|
28
|
+
);
|
|
29
|
+
this.name = "OnboardingConflictError";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var OnboardingSubdomainTakenError = class extends OnboardingError {
|
|
33
|
+
constructor() {
|
|
34
|
+
super(
|
|
35
|
+
"subdomain_taken",
|
|
36
|
+
"That workspace address is already taken. Choose another.",
|
|
37
|
+
409
|
|
38
|
+
);
|
|
39
|
+
this.name = "OnboardingSubdomainTakenError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var OnboardingRateLimitError = class extends OnboardingError {
|
|
43
|
+
retryAfterSeconds;
|
|
44
|
+
constructor(retryAfterSeconds = null) {
|
|
45
|
+
super(
|
|
46
|
+
"rate_limited",
|
|
47
|
+
retryAfterSeconds !== null ? `Rate limited. Retry after ${retryAfterSeconds}s.` : "Rate limited. Please wait before retrying.",
|
|
48
|
+
429
|
|
49
|
+
);
|
|
50
|
+
this.name = "OnboardingRateLimitError";
|
|
51
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var OnboardingSpecStaleError = class extends OnboardingError {
|
|
55
|
+
currentSpecVersion;
|
|
56
|
+
constructor(currentSpecVersion) {
|
|
57
|
+
super(
|
|
58
|
+
"spec_stale",
|
|
59
|
+
`The onboarding form has been updated (new version: ${currentSpecVersion}). Please refresh and try again.`,
|
|
60
|
+
409
|
|
61
|
+
);
|
|
62
|
+
this.name = "OnboardingSpecStaleError";
|
|
63
|
+
this.currentSpecVersion = currentSpecVersion;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var OnboardingConsentStaleError = class extends OnboardingError {
|
|
67
|
+
/**
|
|
68
|
+
* The documents the server reports as changed (may be empty when the
|
|
69
|
+
* server does not enumerate them — the caller then refetches + diffs).
|
|
70
|
+
*/
|
|
71
|
+
staleDocuments;
|
|
72
|
+
constructor(staleDocuments = []) {
|
|
73
|
+
const which = staleDocuments.length > 0 ? ` (${staleDocuments.map((d) => d.docType ?? d.legalDocumentId ?? "a document").join(", ")})` : "";
|
|
74
|
+
super(
|
|
75
|
+
"consent_stale",
|
|
76
|
+
`One or more legal documents were updated${which}. Please review and re-accept the updated documents, then try again.`,
|
|
77
|
+
409
|
|
78
|
+
);
|
|
79
|
+
this.name = "OnboardingConsentStaleError";
|
|
80
|
+
this.staleDocuments = staleDocuments;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var OnboardingConsentIncompleteError = class extends OnboardingError {
|
|
84
|
+
constructor(message) {
|
|
85
|
+
super(
|
|
86
|
+
"consent_incomplete",
|
|
87
|
+
message ?? "Consent is incomplete (400 consent_incomplete). One or more required documents were not accepted at their required level.",
|
|
88
|
+
400
|
|
89
|
+
);
|
|
90
|
+
this.name = "OnboardingConsentIncompleteError";
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var OnboardingServerError = class extends OnboardingError {
|
|
94
|
+
constructor(statusCode, message) {
|
|
95
|
+
super(
|
|
96
|
+
"server_error",
|
|
97
|
+
message ?? `Server error (${statusCode}). The request will be retried.`,
|
|
98
|
+
statusCode
|
|
99
|
+
);
|
|
100
|
+
this.name = "OnboardingServerError";
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var OnboardingNetworkError = class extends OnboardingError {
|
|
104
|
+
constructor(cause) {
|
|
105
|
+
super(
|
|
106
|
+
"network_error",
|
|
107
|
+
"Network error \u2014 check your connection and try again.",
|
|
108
|
+
null
|
|
109
|
+
);
|
|
110
|
+
this.name = "OnboardingNetworkError";
|
|
111
|
+
if (cause instanceof Error) {
|
|
112
|
+
this.cause = cause;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var OnboardingValidationError = class extends OnboardingError {
|
|
117
|
+
constructor(message) {
|
|
118
|
+
super("validation_error", message, 422);
|
|
119
|
+
this.name = "OnboardingValidationError";
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var OnboardingSpecInvalidError = class extends OnboardingError {
|
|
123
|
+
constructor(detail) {
|
|
124
|
+
super(
|
|
125
|
+
"spec_invalid",
|
|
126
|
+
`The onboarding spec response does not match the D540 contract: ${detail}`,
|
|
127
|
+
null
|
|
128
|
+
);
|
|
129
|
+
this.name = "OnboardingSpecInvalidError";
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// src/submit/http.ts
|
|
134
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
|
|
135
|
+
async function fetchWithTimeout(url, options = {}) {
|
|
136
|
+
const {
|
|
137
|
+
timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS,
|
|
138
|
+
fetchImpl = globalThis.fetch,
|
|
139
|
+
signal: callerSignal,
|
|
140
|
+
...init
|
|
141
|
+
} = options;
|
|
142
|
+
const controller = new AbortController();
|
|
143
|
+
const timer = setTimeout(() => {
|
|
144
|
+
controller.abort(new Error(`request timed out after ${timeoutMs}ms`));
|
|
145
|
+
}, timeoutMs);
|
|
146
|
+
if (callerSignal) {
|
|
147
|
+
if (callerSignal.aborted) {
|
|
148
|
+
clearTimeout(timer);
|
|
149
|
+
controller.abort(callerSignal.reason);
|
|
150
|
+
} else {
|
|
151
|
+
callerSignal.addEventListener(
|
|
152
|
+
"abort",
|
|
153
|
+
() => controller.abort(callerSignal.reason),
|
|
154
|
+
{ once: true }
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
return await fetchImpl(url, { ...init, signal: controller.signal });
|
|
160
|
+
} finally {
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function withTimeout(op, timeoutMs, onTimeout) {
|
|
165
|
+
return new Promise((resolve, reject) => {
|
|
166
|
+
let settled = false;
|
|
167
|
+
const timer = setTimeout(() => {
|
|
168
|
+
if (settled) return;
|
|
169
|
+
settled = true;
|
|
170
|
+
reject(onTimeout());
|
|
171
|
+
}, timeoutMs);
|
|
172
|
+
op.then(
|
|
173
|
+
(v) => {
|
|
174
|
+
if (settled) return;
|
|
175
|
+
settled = true;
|
|
176
|
+
clearTimeout(timer);
|
|
177
|
+
resolve(v);
|
|
178
|
+
},
|
|
179
|
+
(err) => {
|
|
180
|
+
if (settled) return;
|
|
181
|
+
settled = true;
|
|
182
|
+
clearTimeout(timer);
|
|
183
|
+
reject(err);
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export { DEFAULT_REQUEST_TIMEOUT_MS, OnboardingBotChallengeError, OnboardingConflictError, OnboardingConsentIncompleteError, OnboardingConsentStaleError, OnboardingError, OnboardingNetworkError, OnboardingRateLimitError, OnboardingServerError, OnboardingSpecInvalidError, OnboardingSpecStaleError, OnboardingSubdomainTakenError, OnboardingValidationError, fetchWithTimeout, withTimeout };
|
|
190
|
+
//# sourceMappingURL=chunk-XJTJPBJB.js.map
|
|
191
|
+
//# sourceMappingURL=chunk-XJTJPBJB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/submit/errors.ts","../src/submit/http.ts"],"names":[],"mappings":"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","file":"chunk-XJTJPBJB.js","sourcesContent":["/**\n * Error taxonomy for the onboarding submit client.\n *\n * Maps HTTP status codes and server error bodies to typed errors:\n * 403 bot_challenge_failed → OnboardingBotChallengeError\n * 409 spec_stale (SUBMIT) → OnboardingSpecStaleError (submit endpoint only,\n * body { error:true, code:\"spec_stale\",\n * currentSpecVersion })\n * 409 (other / LEADS) → OnboardingConflictError (generic conflict)\n * 429 → OnboardingRateLimitError\n * 5xx → OnboardingServerError (retryable)\n * network / timeout → OnboardingNetworkError\n */\n\nexport type OnboardingErrorKind =\n | \"bot_challenge_failed\"\n | \"spec_stale\"\n | \"spec_invalid\"\n | \"consent_stale\"\n | \"consent_incomplete\"\n | \"subdomain_taken\"\n | \"conflict\"\n | \"rate_limited\"\n | \"server_error\"\n | \"network_error\"\n | \"validation_error\";\n\nexport class OnboardingError extends Error {\n public readonly kind: OnboardingErrorKind;\n public readonly statusCode: number | null;\n\n constructor(\n kind: OnboardingErrorKind,\n message: string,\n statusCode: number | null = null,\n ) {\n super(message);\n this.name = \"OnboardingError\";\n this.kind = kind;\n this.statusCode = statusCode;\n }\n}\n\nexport class OnboardingBotChallengeError extends OnboardingError {\n constructor(message?: string) {\n super(\n \"bot_challenge_failed\",\n message ??\n \"Bot challenge verification failed (403 bot_challenge_failed). Refresh the CAPTCHA and try again.\",\n 403,\n );\n this.name = \"OnboardingBotChallengeError\";\n }\n}\n\n/**\n * A 409 that is NOT spec_stale. On the /leads path there is no spec_stale\n * concept (the payload carries no specVersion); on /submit, a 409 that\n * lacks `code:\"spec_stale\"` also lands here. Distinct from\n * OnboardingSpecStaleError so headless consumers switching on `kind` get the\n * correct diagnosis (a generic conflict, e.g. a D538 same-key concurrent\n * race) rather than a \"form has been updated, refresh\" recovery message.\n */\nexport class OnboardingConflictError extends OnboardingError {\n constructor(statusCode = 409, message?: string) {\n super(\n \"conflict\",\n message ??\n `Request conflicted with the current server state (${statusCode}).`,\n statusCode,\n );\n this.name = \"OnboardingConflictError\";\n }\n}\n\nexport class OnboardingSubdomainTakenError extends OnboardingError {\n constructor() {\n super(\n \"subdomain_taken\",\n \"That workspace address is already taken. Choose another.\",\n 409,\n );\n this.name = \"OnboardingSubdomainTakenError\";\n }\n}\n\nexport class OnboardingRateLimitError extends OnboardingError {\n public readonly retryAfterSeconds: number | null;\n\n constructor(retryAfterSeconds: number | null = null) {\n super(\n \"rate_limited\",\n retryAfterSeconds !== null\n ? `Rate limited. Retry after ${retryAfterSeconds}s.`\n : \"Rate limited. Please wait before retrying.\",\n 429,\n );\n this.name = \"OnboardingRateLimitError\";\n this.retryAfterSeconds = retryAfterSeconds;\n }\n}\n\n/**\n * A /submit 409 whose body carries `code:\"spec_stale\"` (D540 reject-stale).\n * The PINNED body shape (confirmed against the crm intake) is exactly:\n * { error: true, code: \"spec_stale\", currentSpecVersion: string }\n * The version is read ONLY from `currentSpecVersion` — no field hedging.\n * Submit-endpoint only; /leads 409s are OnboardingConflictError.\n */\nexport class OnboardingSpecStaleError extends OnboardingError {\n public readonly currentSpecVersion: string;\n\n constructor(currentSpecVersion: string) {\n super(\n \"spec_stale\",\n `The onboarding form has been updated (new version: ${currentSpecVersion}). Please refresh and try again.`,\n 409,\n );\n this.name = \"OnboardingSpecStaleError\";\n this.currentSpecVersion = currentSpecVersion;\n }\n}\n\n/**\n * A /submit 409 whose body carries `code:\"consent_stale\"` (D543 §4 —\n * SIBLING to spec_stale). Raised when a required legal document's live\n * version/content_hash changed between spec-fetch and submit, so the echoed\n * consent no longer matches the live version.\n *\n * PINNED body shape (D543 §4, mirroring spec_stale):\n * { error: true, code: \"consent_stale\", staleDocuments?: [...] }\n * where each staleDocuments entry identifies a changed doc so the caller can\n * tell the user WHICH docs changed. The caller refetches the spec and\n * REQUIRES re-acceptance of the changed docs — the SDK NEVER auto-accepts a\n * changed doc (D543 §4). Submit-endpoint only.\n */\nexport interface StaleConsentDocument {\n /** The server registry id of the changed doc (D543 §3 legal_documents.id). */\n legalDocumentId?: string;\n /** doc_type of the changed doc, when the server includes it. */\n docType?: string;\n /** The new live version_no, when the server includes it. */\n currentVersionNo?: number;\n}\n\nexport class OnboardingConsentStaleError extends OnboardingError {\n /**\n * The documents the server reports as changed (may be empty when the\n * server does not enumerate them — the caller then refetches + diffs).\n */\n public readonly staleDocuments: StaleConsentDocument[];\n\n constructor(staleDocuments: StaleConsentDocument[] = []) {\n const which =\n staleDocuments.length > 0\n ? ` (${staleDocuments\n .map((d) => d.docType ?? d.legalDocumentId ?? \"a document\")\n .join(\", \")})`\n : \"\";\n super(\n \"consent_stale\",\n `One or more legal documents were updated${which}. Please review and re-accept the updated documents, then try again.`,\n 409,\n );\n this.name = \"OnboardingConsentStaleError\";\n this.staleDocuments = staleDocuments;\n }\n}\n\n/**\n * A /submit 400 whose body carries `code:\"consent_incomplete\"` (D543 §4).\n * The server's authoritative re-validation found a required live document\n * that was missing, not accepted, or whose evidence did not satisfy its\n * acceptance_level gate. Distinct from a generic 422 so headless consumers\n * switching on `kind` route the user back to the consent step specifically.\n */\nexport class OnboardingConsentIncompleteError extends OnboardingError {\n constructor(message?: string) {\n super(\n \"consent_incomplete\",\n message ??\n \"Consent is incomplete (400 consent_incomplete). One or more required documents were not accepted at their required level.\",\n 400,\n );\n this.name = \"OnboardingConsentIncompleteError\";\n }\n}\n\nexport class OnboardingServerError extends OnboardingError {\n constructor(statusCode: number, message?: string) {\n super(\n \"server_error\",\n message ?? `Server error (${statusCode}). The request will be retried.`,\n statusCode,\n );\n this.name = \"OnboardingServerError\";\n }\n}\n\nexport class OnboardingNetworkError extends OnboardingError {\n constructor(cause?: unknown) {\n super(\n \"network_error\",\n \"Network error — check your connection and try again.\",\n null,\n );\n this.name = \"OnboardingNetworkError\";\n if (cause instanceof Error) {\n this.cause = cause;\n }\n }\n}\n\nexport class OnboardingValidationError extends OnboardingError {\n constructor(message: string) {\n super(\"validation_error\", message, 422);\n this.name = \"OnboardingValidationError\";\n }\n}\n\n/**\n * The spec endpoint returned a body that does not conform to the LOCKED\n * D540 §1 response shape. This is a server-side contract violation — the\n * SDK refuses to render a form from a malformed spec (a half-parsed spec\n * could silently drop required fields).\n */\nexport class OnboardingSpecInvalidError extends OnboardingError {\n constructor(detail: string) {\n super(\n \"spec_invalid\",\n `The onboarding spec response does not match the D540 contract: ${detail}`,\n null,\n );\n this.name = \"OnboardingSpecInvalidError\";\n }\n}\n","/**\n * Shared HTTP helper: fetch bounded by a timeout via AbortController.\n *\n * Every SDK wire call (spec fetch, leads submit, signup submit) MUST be\n * bounded — the widgets flip to a disabled/\"Sending…\"/\"Loading…\" state\n * BEFORE the await, so an intake that accepts the connection and then stalls\n * would otherwise hang the UI forever. On timeout the fetch is aborted and a\n * typed OnboardingNetworkError is raised by the caller (which maps abort/\n * network failures uniformly).\n */\n\n/** Default per-request timeout. Generous enough for a slow-but-alive intake. */\nexport const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;\n\nexport interface TimeoutFetchOptions extends RequestInit {\n /** Per-request timeout in ms. Default DEFAULT_REQUEST_TIMEOUT_MS. */\n timeoutMs?: number;\n /** The fetch implementation to use (defaults to globalThis.fetch). */\n fetchImpl?: typeof globalThis.fetch;\n}\n\n/**\n * fetch() with a hard timeout. Aborts the request when the deadline elapses;\n * the resulting AbortError propagates to the caller (who wraps it as an\n * OnboardingNetworkError). A caller-supplied `signal` is respected too — if\n * either the caller aborts OR the timeout fires, the request is aborted.\n */\nexport async function fetchWithTimeout(\n url: string,\n options: TimeoutFetchOptions = {},\n): Promise<Response> {\n const {\n timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS,\n fetchImpl = globalThis.fetch,\n signal: callerSignal,\n ...init\n } = options;\n\n const controller = new AbortController();\n const timer = setTimeout(() => {\n controller.abort(new Error(`request timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n // Forward a caller abort into our controller.\n if (callerSignal) {\n if (callerSignal.aborted) {\n clearTimeout(timer);\n controller.abort(callerSignal.reason);\n } else {\n callerSignal.addEventListener(\n \"abort\",\n () => controller.abort(callerSignal.reason),\n { once: true },\n );\n }\n }\n\n try {\n return await fetchImpl(url, { ...init, signal: controller.signal });\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Run a promise with a timeout. On timeout, rejects with `onTimeout()`'s\n * error. Used to bound calls that don't take an AbortSignal (grecaptcha\n * .execute()). The underlying op is NOT cancelled (grecaptcha has no abort),\n * but the caller no longer waits on it — the widget re-enables.\n */\nexport function withTimeout<T>(\n op: Promise<T>,\n timeoutMs: number,\n onTimeout: () => Error,\n): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n let settled = false;\n const timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n reject(onTimeout());\n }, timeoutMs);\n op.then(\n (v) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(v);\n },\n (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(err);\n },\n );\n });\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-Z4PA6C2Z.js"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { a as OnboardingSpec } from './model-ESLSEbi0.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Config-fetch client for GET /api/v1/public/onboarding/spec (D540 §1/§3).
|
|
5
|
+
*
|
|
6
|
+
* - The onboarder is derived SERVER-side from the request Host (D536) —
|
|
7
|
+
* it is NOT sent by the client. The `onboarder` value here is purely the
|
|
8
|
+
* CLIENT-side cache key (D540 §3: "cache key (onboarder, specVersion)").
|
|
9
|
+
* - The spec response is validated against the LOCKED D540 shape before
|
|
10
|
+
* it is returned; a malformed spec throws OnboardingSpecInvalidError.
|
|
11
|
+
* - Cache semantics: every successful fetch is stored under
|
|
12
|
+
* (onboarder, specVersion) and as the onboarder's "latest". The versioned
|
|
13
|
+
* entries let the 409 spec_stale flow diff the previously-served version
|
|
14
|
+
* against the refetched one (see `diffSpecFields` + the signup widget's
|
|
15
|
+
* handleSpecStale, which reads the cached prior version before refetching).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* In-memory spec cache keyed by (onboarder, specVersion), with a "latest"
|
|
20
|
+
* pointer per onboarder. Purely client-side; no TTL — the reject-stale
|
|
21
|
+
* policy (409 spec_stale on submit) is the authoritative staleness signal,
|
|
22
|
+
* and a refetch overwrites "latest".
|
|
23
|
+
*/
|
|
24
|
+
declare class OnboardingSpecCache {
|
|
25
|
+
private readonly byVersion;
|
|
26
|
+
private readonly latest;
|
|
27
|
+
/**
|
|
28
|
+
* Collision-proof composite key. Uses JSON.stringify of the tuple so an
|
|
29
|
+
* onboarder or version containing the delimiter can never alias another
|
|
30
|
+
* entry — and, critically, contains NO control characters (an earlier
|
|
31
|
+
* NUL-separator made git treat this whole file as binary/undiffable).
|
|
32
|
+
*/
|
|
33
|
+
private static versionKey;
|
|
34
|
+
get(onboarder: string, specVersion: string): OnboardingSpec | undefined;
|
|
35
|
+
getLatest(onboarder: string): OnboardingSpec | undefined;
|
|
36
|
+
set(onboarder: string, spec: OnboardingSpec): void;
|
|
37
|
+
clear(): void;
|
|
38
|
+
}
|
|
39
|
+
/** Module-level default cache (one per page). Injectable for tests/apps. */
|
|
40
|
+
declare const defaultSpecCache: OnboardingSpecCache;
|
|
41
|
+
/**
|
|
42
|
+
* Diff two spec versions' signup field sets — used by the 409 spec_stale
|
|
43
|
+
* flow to tell the user WHAT changed. Compares by field key across all
|
|
44
|
+
* signup steps.
|
|
45
|
+
*/
|
|
46
|
+
interface SpecFieldDiff {
|
|
47
|
+
added: string[];
|
|
48
|
+
removed: string[];
|
|
49
|
+
changed: string[];
|
|
50
|
+
}
|
|
51
|
+
declare function diffSpecFields(prev: OnboardingSpec, next: OnboardingSpec): SpecFieldDiff;
|
|
52
|
+
interface SpecFetchOptions {
|
|
53
|
+
baseUrl: string;
|
|
54
|
+
/**
|
|
55
|
+
* Client-side cache key ONLY (never transmitted; trust is host-derived
|
|
56
|
+
* per D536). Use the onboarder slug/id your embed is configured with.
|
|
57
|
+
*/
|
|
58
|
+
onboarder: string;
|
|
59
|
+
/** Custom fetch implementation (defaults to globalThis.fetch). */
|
|
60
|
+
fetch?: typeof globalThis.fetch;
|
|
61
|
+
/** Cache to read/write (defaults to the module-level cache). */
|
|
62
|
+
cache?: OnboardingSpecCache;
|
|
63
|
+
/**
|
|
64
|
+
* Request timeout in ms (default 15s). A stalled intake aborts with
|
|
65
|
+
* OnboardingNetworkError so the boot doesn't hang on "Loading signup form…".
|
|
66
|
+
*/
|
|
67
|
+
timeoutMs?: number;
|
|
68
|
+
/**
|
|
69
|
+
* When true, return the cached "latest" for this onboarder if present
|
|
70
|
+
* WITHOUT a network round-trip. Default false (spec fetches are cheap
|
|
71
|
+
* and the server bumps specVersion on any change). The signup widget's
|
|
72
|
+
* boot passes preferCached:true so a remount reuses the loaded spec.
|
|
73
|
+
*/
|
|
74
|
+
preferCached?: boolean;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Fetch (and validate, and cache) the onboarding spec.
|
|
78
|
+
*
|
|
79
|
+
* @throws {OnboardingSpecInvalidError} response does not match D540 §1
|
|
80
|
+
* @throws {OnboardingRateLimitError} on 429
|
|
81
|
+
* @throws {OnboardingServerError} on any other non-2xx status
|
|
82
|
+
* @throws {OnboardingNetworkError} on network failure OR timeout
|
|
83
|
+
*/
|
|
84
|
+
declare function fetchOnboardingSpec(options: SpecFetchOptions): Promise<OnboardingSpec>;
|
|
85
|
+
|
|
86
|
+
export { OnboardingSpecCache as O, type SpecFetchOptions as S, type SpecFieldDiff as a, diffSpecFields as b, defaultSpecCache as d, fetchOnboardingSpec as f };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { b as ConsentDocument } from '../model-mnjAEtKe.js';
|
|
2
|
+
export { A as ACCEPTANCE_LEVELS, a as AcceptanceLevel, C as ConsentDocType, c as ConsentPack, D as DOC_TYPES, d as acceptanceLevelSchema, e as consentDocKey, f as consentDocumentSchema, g as consentPackSchema } from '../model-mnjAEtKe.js';
|
|
3
|
+
export { C as ConsentController, a as ConsentDocState, b as ConsentEvidence, c as ConsentState, d as ConsentStateListener, e as ConsentSubmitDocument, S as SCROLL_END_THRESHOLD, f as createConsentController, i as initialGateSatisfied } from '../controller-BQb62D0N.js';
|
|
4
|
+
import { d as OnboardingError } from '../errors-vJkye4a0.js';
|
|
5
|
+
import 'zod/v3';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Headless document-content fetch helper (D543 §5).
|
|
9
|
+
*
|
|
10
|
+
* The consent controller is DOM-free; document RENDERING is the consumer's
|
|
11
|
+
* responsibility (TanStack model — you render the row, we own the state). For
|
|
12
|
+
* `scroll_then_accept` gating to work the doc must be rendered SAME-ORIGIN and
|
|
13
|
+
* inline (a cross-origin iframe cannot report scroll position), so consumers
|
|
14
|
+
* fetch the presigned artifact and render its bytes/text themselves.
|
|
15
|
+
*
|
|
16
|
+
* This helper does the fetch + classification so a consumer doesn't wrestle
|
|
17
|
+
* CORS/content-type detection. It returns the artifact's content-type plus the
|
|
18
|
+
* body as text (for HTML/plain) or a Blob (for PDF/binary). It NEVER renders
|
|
19
|
+
* or sanitizes — the consumer (or the default component) decides how to display
|
|
20
|
+
* it (e.g. sanitize+inject HTML, or PDF.js a blob, or degrade to open-in-tab).
|
|
21
|
+
*
|
|
22
|
+
* A short-TTL presigned GET may fail (expired URL, CORS, offline); the caller
|
|
23
|
+
* gets a typed OnboardingDocumentFetchError so a failed preview degrades
|
|
24
|
+
* gracefully (e.g. fall back to an "open in a new tab" affordance) without
|
|
25
|
+
* breaking the consent gate.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** How the fetched artifact should be treated by a renderer. */
|
|
29
|
+
type ConsentDocumentKind = "html" | "text" | "pdf" | "binary";
|
|
30
|
+
interface FetchedConsentDocument {
|
|
31
|
+
/** The raw `Content-Type` header (lower-cased, params stripped). */
|
|
32
|
+
contentType: string;
|
|
33
|
+
/** Classification for the renderer. */
|
|
34
|
+
kind: ConsentDocumentKind;
|
|
35
|
+
/** Present for kind "html" | "text". */
|
|
36
|
+
text?: string;
|
|
37
|
+
/** Present for kind "pdf" | "binary". */
|
|
38
|
+
blob?: Blob;
|
|
39
|
+
}
|
|
40
|
+
interface FetchDocumentContentOptions {
|
|
41
|
+
fetch?: typeof globalThis.fetch;
|
|
42
|
+
/** Per-request timeout (default 15s) — a stalled CDN never hangs the UI. */
|
|
43
|
+
timeoutMs?: number;
|
|
44
|
+
}
|
|
45
|
+
/** A failed presigned artifact fetch (expired URL, CORS, offline, non-2xx). */
|
|
46
|
+
declare class OnboardingDocumentFetchError extends OnboardingError {
|
|
47
|
+
readonly url: string;
|
|
48
|
+
constructor(url: string, detail: string);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Fetch a consent document's presigned artifact and return its content-type +
|
|
52
|
+
* body (text for HTML/plain, Blob for PDF/binary).
|
|
53
|
+
*
|
|
54
|
+
* @throws {OnboardingDocumentFetchError} on network failure, timeout, CORS,
|
|
55
|
+
* or a non-2xx response — the caller degrades gracefully (open-in-tab).
|
|
56
|
+
*/
|
|
57
|
+
declare function fetchDocumentContent(doc: Pick<ConsentDocument, "downloadUrl">, options?: FetchDocumentContentOptions): Promise<FetchedConsentDocument>;
|
|
58
|
+
|
|
59
|
+
export { ConsentDocument, type ConsentDocumentKind, type FetchDocumentContentOptions, type FetchedConsentDocument, OnboardingDocumentFetchError, fetchDocumentContent };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import '../chunk-FIRSA7EH.js';
|
|
2
|
+
export { SCROLL_END_THRESHOLD, createConsentController, initialGateSatisfied } from '../chunk-AZE3NPSY.js';
|
|
3
|
+
export { ACCEPTANCE_LEVELS, DOC_TYPES, acceptanceLevelSchema, consentDocKey, consentDocumentSchema, consentPackSchema } from '../chunk-U56GQITG.js';
|
|
4
|
+
export { OnboardingDocumentFetchError, fetchDocumentContent } from '../chunk-SEKNVHD3.js';
|
|
5
|
+
import '../chunk-XJTJPBJB.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|