@nodii/onboarding 0.1.0-dev.0 → 0.2.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 +124 -0
- package/dist/{chunk-4SWOVHZ2.js → chunk-A3DE2IAH.js} +155 -18
- package/dist/chunk-A3DE2IAH.js.map +1 -0
- package/dist/{chunk-AFCJLUPK.js → chunk-DWFZA5T6.js} +19 -8
- package/dist/chunk-DWFZA5T6.js.map +1 -0
- package/dist/chunk-FIRSA7EH.js +3 -0
- package/dist/{chunk-ZDY2I6KY.js.map → chunk-FIRSA7EH.js.map} +1 -1
- package/dist/chunk-JFDQZ3AC.js +3 -0
- package/dist/chunk-JFDQZ3AC.js.map +1 -0
- package/dist/{chunk-34BQX2KR.js → chunk-KFHVZGX6.js} +61 -6
- package/dist/chunk-KFHVZGX6.js.map +1 -0
- package/dist/chunk-NDSBIB3N.js +99 -0
- package/dist/chunk-NDSBIB3N.js.map +1 -0
- package/dist/chunk-OLWR5COK.js +10 -0
- package/dist/chunk-OLWR5COK.js.map +1 -0
- package/dist/{chunk-SWMIIUXR.js → chunk-OVULPWNZ.js} +917 -158
- package/dist/chunk-OVULPWNZ.js.map +1 -0
- package/dist/chunk-SEKNVHD3.js +59 -0
- package/dist/chunk-SEKNVHD3.js.map +1 -0
- package/dist/chunk-T7EWURTF.js +321 -0
- package/dist/chunk-T7EWURTF.js.map +1 -0
- package/dist/{chunk-BQCTDU5V.js → chunk-UHOFGWI7.js} +90 -15
- package/dist/chunk-UHOFGWI7.js.map +1 -0
- package/dist/chunk-WKN4U7QF.js +35 -0
- package/dist/chunk-WKN4U7QF.js.map +1 -0
- package/dist/{chunk-AW7W3HAC.js → chunk-XJTJPBJB.js} +40 -3
- package/dist/chunk-XJTJPBJB.js.map +1 -0
- package/dist/chunk-ZR6X3GSM.js +186 -0
- package/dist/chunk-ZR6X3GSM.js.map +1 -0
- package/dist/client-NvqPpfQP.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-BLH3wnxl.d.ts +177 -0
- package/dist/dynamic/index.d.ts +65 -6
- package/dist/dynamic/index.js +5 -2
- package/dist/errors-vJkye4a0.d.ts +110 -0
- package/dist/form/index.d.ts +12 -4
- package/dist/form/index.js +2 -2
- package/dist/index-BMSbQGWP.d.ts +103 -0
- package/dist/index.d.ts +13 -7
- package/dist/index.js +13 -8
- package/dist/model-BI_I-D_V.d.ts +267 -0
- package/dist/{model-IEyDI54w.d.ts → model-CEwvHlWA.d.ts} +212 -46
- package/dist/render-EWQXF73I.js +5 -0
- package/dist/render-EWQXF73I.js.map +1 -0
- package/dist/schema-BrtjpXfL.d.ts +257 -0
- package/dist/{signup-client-Bl6eUrxR.d.ts → signup-client-CS95nkVO.d.ts} +152 -19
- package/dist/spec/index.d.ts +10 -81
- package/dist/spec/index.js +3 -2
- package/dist/submit/index.d.ts +7 -71
- package/dist/submit/index.js +4 -3
- 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 +102 -20
- package/dist/widget/index.js +10 -7
- package/package.json +21 -2
- package/dist/chunk-34BQX2KR.js.map +0 -1
- package/dist/chunk-4SWOVHZ2.js.map +0 -1
- package/dist/chunk-AFCJLUPK.js.map +0 -1
- package/dist/chunk-AW7W3HAC.js.map +0 -1
- package/dist/chunk-BIFANV7M.js +0 -22
- package/dist/chunk-BIFANV7M.js.map +0 -1
- package/dist/chunk-BQCTDU5V.js.map +0 -1
- package/dist/chunk-SWMIIUXR.js.map +0 -1
- package/dist/chunk-ZDY2I6KY.js +0 -3
- package/dist/schema-TfyBU6JW.d.ts +0 -107
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-FIRSA7EH.js"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-JFDQZ3AC.js"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { consentPackSchema } from './chunk-NDSBIB3N.js';
|
|
2
|
+
import { DEFAULT_REQUEST_TIMEOUT_MS, fetchWithTimeout, OnboardingNetworkError, OnboardingRateLimitError, OnboardingServerError, OnboardingSpecInvalidError, OnboardingValidationError } from './chunk-XJTJPBJB.js';
|
|
2
3
|
import { z } from 'zod/v3';
|
|
3
4
|
|
|
4
5
|
var FIELD_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
@@ -22,10 +23,11 @@ var specStepSchema = z.object({
|
|
|
22
23
|
var onboardingSpecSchema = z.object({
|
|
23
24
|
specVersion: z.string().min(1),
|
|
24
25
|
onboarderTier: z.enum(["platform", "tenant"]),
|
|
26
|
+
// D1444: omitted when the onboarder has explicitly disabled bot checking.
|
|
25
27
|
botChallenge: z.object({
|
|
26
28
|
provider: z.literal("recaptcha"),
|
|
27
29
|
siteKey: z.string()
|
|
28
|
-
}),
|
|
30
|
+
}).optional(),
|
|
29
31
|
forms: z.object({
|
|
30
32
|
// ADVISORY-ONLY (by design, plan §12.2 / D540): the Contact-Us form is
|
|
31
33
|
// INTENTIONALLY a fixed barebones shape (name/email/phone/message) and is
|
|
@@ -35,7 +37,13 @@ var onboardingSpecSchema = z.object({
|
|
|
35
37
|
// no rendering. Only forms.signup is spec-driven (the dynamic stepper).
|
|
36
38
|
contact: z.object({ fields: z.array(specFieldSchema) }),
|
|
37
39
|
signup: z.object({ steps: z.array(specStepSchema) })
|
|
38
|
-
})
|
|
40
|
+
}),
|
|
41
|
+
// D543 §4 ConsentPack extension. OPTIONAL for backward-compat (D543
|
|
42
|
+
// "pre-ConsentPack submits still parse"): an onboarder with no required
|
|
43
|
+
// legal docs serves no block, and the consent stage is omitted from the
|
|
44
|
+
// flow. When PRESENT it is validated fully (fail-closed) — a malformed
|
|
45
|
+
// consentPack throws OnboardingSpecInvalidError, never rendered half-parsed.
|
|
46
|
+
consentPack: consentPackSchema.optional()
|
|
39
47
|
});
|
|
40
48
|
function validateOnboardingSpec(value) {
|
|
41
49
|
return onboardingSpecSchema.safeParse(value);
|
|
@@ -159,6 +167,53 @@ async function fetchOnboardingSpec(options) {
|
|
|
159
167
|
return parsed.data;
|
|
160
168
|
}
|
|
161
169
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
170
|
+
// src/spec/pin-state.ts
|
|
171
|
+
async function fetchPinState(options) {
|
|
172
|
+
const pin = options.pin.trim();
|
|
173
|
+
if (!/^[1-9][0-9]{5}$/.test(pin)) {
|
|
174
|
+
throw new OnboardingValidationError(
|
|
175
|
+
"PIN must be a 6-digit Indian PIN code"
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
let response;
|
|
179
|
+
try {
|
|
180
|
+
response = await fetchWithTimeout(
|
|
181
|
+
`${options.baseUrl.replace(/\/$/, "")}/api/v1/public/onboarding/pin-state?pin=${encodeURIComponent(pin)}`,
|
|
182
|
+
{
|
|
183
|
+
method: "GET",
|
|
184
|
+
fetchImpl: options.fetch ?? globalThis.fetch,
|
|
185
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
throw new OnboardingNetworkError(err);
|
|
190
|
+
}
|
|
191
|
+
if (!response.ok) throw new OnboardingServerError(response.status);
|
|
192
|
+
let value;
|
|
193
|
+
try {
|
|
194
|
+
value = await response.json();
|
|
195
|
+
} catch {
|
|
196
|
+
throw new OnboardingServerError(
|
|
197
|
+
response.status,
|
|
198
|
+
"PIN-state response is not JSON"
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
if (typeof value !== "object" || value === null) {
|
|
202
|
+
throw new OnboardingServerError(
|
|
203
|
+
response.status,
|
|
204
|
+
"Invalid PIN-state response"
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
const answer = value;
|
|
208
|
+
if (answer.pin !== pin || answer.state_iso !== null && typeof answer.state_iso !== "string") {
|
|
209
|
+
throw new OnboardingServerError(
|
|
210
|
+
response.status,
|
|
211
|
+
"Invalid PIN-state response"
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return { pin, stateIso: answer.state_iso };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export { FIELD_KEY_PATTERN, OnboardingSpecCache, defaultSpecCache, diffSpecFields, fetchOnboardingSpec, fetchPinState, onboardingSpecSchema, specFieldSchema, specStepSchema, validateOnboardingSpec };
|
|
218
|
+
//# sourceMappingURL=chunk-KFHVZGX6.js.map
|
|
219
|
+
//# sourceMappingURL=chunk-KFHVZGX6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/spec/model.ts","../src/spec/client.ts","../src/spec/pin-state.ts"],"names":[],"mappings":"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","file":"chunk-KFHVZGX6.js","sourcesContent":["/**\n * D540 §1 onboarding-spec model — mirrors the LOCKED hub REST contract:\n * GET /api/v1/public/onboarding/spec (PublicOnboardingSpecResponse)\n *\n * Response shape (hub JSON Schema, both tiers identical):\n * {\n * specVersion: string (monotonic; echo on submit; stale ⇒ 409),\n * onboarderTier: \"platform\" | \"tenant\",\n * botChallenge: { provider: \"recaptcha\", siteKey: string },\n * forms: {\n * contact: { fields: SpecField[] },\n * signup: { steps: [{ stage: \"lead\"|\"prospect\"|\"activation\",\n * fields: SpecField[] }] }\n * }\n * }\n *\n * SpecField (required: key, type, label, required):\n * { key, type, label, required, validation?, options?, serviceSelection?,\n * piiClass?: \"none\"|\"context\"|\"always\"|\"reference\" }\n *\n * The Zod schema below validates a fetched spec BEFORE the SDK renders\n * anything from it — a malformed spec throws OnboardingSpecInvalidError\n * (a half-parsed spec could silently drop required fields).\n */\n\nimport { z } from \"zod/v3\";\nimport { consentPackSchema } from \"../consent/model.js\";\n\nexport type OnboarderTier = \"platform\" | \"tenant\";\nexport type PiiClass = \"none\" | \"context\" | \"always\" | \"reference\";\nexport type SpecStage = \"lead\" | \"prospect\" | \"activation\";\nexport type TerminalStage = \"prospect\" | \"inactive\" | \"active\";\n\n/**\n * Field keys are interpolated into DOM element ids and (historically) CSS\n * selectors by the widget layer. Constrain them to a strict identifier\n * alphabet so a MALICIOUS or compromised onboarder spec cannot smuggle CSS\n * metacharacters or selector-breakout payloads (e.g. `evil], [onclick=…`)\n * through the fetched spec. A spec with an out-of-alphabet key is REJECTED\n * at parse time → OnboardingSpecInvalidError (fail-closed; never rendered).\n * This is layer 1; the widget also CSS.escapes ids as defense-in-depth.\n */\nexport const FIELD_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;\n\nexport const specFieldSchema = z.object({\n key: z\n .string()\n .min(1)\n .regex(\n FIELD_KEY_PATTERN,\n \"field key must contain only letters, digits, underscore, or hyphen\",\n ),\n type: z.string().min(1),\n label: z.string(),\n required: z.boolean(),\n validation: z.record(z.unknown()).optional(),\n options: z.array(z.unknown()).optional(),\n serviceSelection: z.boolean().optional(),\n piiClass: z.enum([\"none\", \"context\", \"always\", \"reference\"]).optional(),\n});\n\nexport type SpecField = z.infer<typeof specFieldSchema>;\n\nexport const specStepSchema = z.object({\n stage: z.enum([\"lead\", \"prospect\", \"activation\"]),\n fields: z.array(specFieldSchema),\n});\n\nexport type SpecStep = z.infer<typeof specStepSchema>;\n\nexport const onboardingSpecSchema = z.object({\n specVersion: z.string().min(1),\n onboarderTier: z.enum([\"platform\", \"tenant\"]),\n // D1444: omitted when the onboarder has explicitly disabled bot checking.\n botChallenge: z\n .object({\n provider: z.literal(\"recaptcha\"),\n siteKey: z.string(),\n })\n .optional(),\n forms: z.object({\n // ADVISORY-ONLY (by design, plan §12.2 / D540): the Contact-Us form is\n // INTENTIONALLY a fixed barebones shape (name/email/phone/message) and is\n // NOT onboarder-configurable. `mountContactForm` renders that fixed set\n // directly and does NOT read forms.contact.fields — the field is modeled\n // (and validated) so the SDK can round-trip the full spec, but it drives\n // no rendering. Only forms.signup is spec-driven (the dynamic stepper).\n contact: z.object({ fields: z.array(specFieldSchema) }),\n signup: z.object({ steps: z.array(specStepSchema) }),\n }),\n // D543 §4 ConsentPack extension. OPTIONAL for backward-compat (D543\n // \"pre-ConsentPack submits still parse\"): an onboarder with no required\n // legal docs serves no block, and the consent stage is omitted from the\n // flow. When PRESENT it is validated fully (fail-closed) — a malformed\n // consentPack throws OnboardingSpecInvalidError, never rendered half-parsed.\n consentPack: consentPackSchema.optional(),\n});\n\nexport type OnboardingSpec = z.infer<typeof onboardingSpecSchema>;\n\n/**\n * Parse an unknown value as an OnboardingSpec.\n * Returns a SafeParseReturnType (inferred).\n */\nexport function validateOnboardingSpec(value: unknown) {\n return onboardingSpecSchema.safeParse(value);\n}\n","/**\n * Config-fetch client for GET /api/v1/public/onboarding/spec (D540 §1/§3).\n *\n * - The onboarder is derived SERVER-side from the request Host (D536) —\n * it is NOT sent by the client. The `onboarder` value here is purely the\n * CLIENT-side cache key (D540 §3: \"cache key (onboarder, specVersion)\").\n * - The spec response is validated against the LOCKED D540 shape before\n * it is returned; a malformed spec throws OnboardingSpecInvalidError.\n * - Cache semantics: every successful fetch is stored under\n * (onboarder, specVersion) and as the onboarder's \"latest\". The versioned\n * entries let the 409 spec_stale flow diff the previously-served version\n * against the refetched one (see `diffSpecFields` + the signup widget's\n * handleSpecStale, which reads the cached prior version before refetching).\n */\n\nimport {\n OnboardingNetworkError,\n OnboardingRateLimitError,\n OnboardingServerError,\n OnboardingSpecInvalidError,\n} from \"../submit/errors.js\";\nimport {\n DEFAULT_REQUEST_TIMEOUT_MS,\n fetchWithTimeout,\n} from \"../submit/http.js\";\nimport {\n validateOnboardingSpec,\n type OnboardingSpec,\n type SpecField,\n} from \"./model.js\";\n\nconst SPEC_PATH = \"/api/v1/public/onboarding/spec\";\n\n/**\n * In-memory spec cache keyed by (onboarder, specVersion), with a \"latest\"\n * pointer per onboarder. Purely client-side; no TTL — the reject-stale\n * policy (409 spec_stale on submit) is the authoritative staleness signal,\n * and a refetch overwrites \"latest\".\n */\nexport class OnboardingSpecCache {\n private readonly byVersion = new Map<string, OnboardingSpec>();\n private readonly latest = new Map<string, OnboardingSpec>();\n\n /**\n * Collision-proof composite key. Uses JSON.stringify of the tuple so an\n * onboarder or version containing the delimiter can never alias another\n * entry — and, critically, contains NO control characters (an earlier\n * NUL-separator made git treat this whole file as binary/undiffable).\n */\n private static versionKey(onboarder: string, specVersion: string): string {\n return JSON.stringify([onboarder, specVersion]);\n }\n\n get(onboarder: string, specVersion: string): OnboardingSpec | undefined {\n return this.byVersion.get(\n OnboardingSpecCache.versionKey(onboarder, specVersion),\n );\n }\n\n getLatest(onboarder: string): OnboardingSpec | undefined {\n return this.latest.get(onboarder);\n }\n\n set(onboarder: string, spec: OnboardingSpec): void {\n this.byVersion.set(\n OnboardingSpecCache.versionKey(onboarder, spec.specVersion),\n spec,\n );\n this.latest.set(onboarder, spec);\n }\n\n clear(): void {\n this.byVersion.clear();\n this.latest.clear();\n }\n}\n\n/** Module-level default cache (one per page). Injectable for tests/apps. */\nexport const defaultSpecCache = new OnboardingSpecCache();\n\n/**\n * Diff two spec versions' signup field sets — used by the 409 spec_stale\n * flow to tell the user WHAT changed. Compares by field key across all\n * signup steps.\n */\nexport interface SpecFieldDiff {\n added: string[];\n removed: string[];\n changed: string[];\n}\n\nfunction signupFieldMap(spec: OnboardingSpec): Map<string, SpecField> {\n const map = new Map<string, SpecField>();\n for (const step of spec.forms.signup.steps) {\n for (const field of step.fields) map.set(field.key, field);\n }\n return map;\n}\n\nexport function diffSpecFields(\n prev: OnboardingSpec,\n next: OnboardingSpec,\n): SpecFieldDiff {\n const prevMap = signupFieldMap(prev);\n const nextMap = signupFieldMap(next);\n const added: string[] = [];\n const removed: string[] = [];\n const changed: string[] = [];\n for (const key of nextMap.keys()) {\n if (!prevMap.has(key)) added.push(key);\n }\n for (const [key, prevField] of prevMap) {\n const nextField = nextMap.get(key);\n if (!nextField) {\n removed.push(key);\n continue;\n }\n if (JSON.stringify(prevField) !== JSON.stringify(nextField)) {\n changed.push(key);\n }\n }\n return {\n added: added.sort(),\n removed: removed.sort(),\n changed: changed.sort(),\n };\n}\n\nexport interface SpecFetchOptions {\n baseUrl: string;\n /**\n * Client-side cache key ONLY (never transmitted; trust is host-derived\n * per D536). Use the onboarder slug/id your embed is configured with.\n */\n onboarder: string;\n /** Custom fetch implementation (defaults to globalThis.fetch). */\n fetch?: typeof globalThis.fetch;\n /** Cache to read/write (defaults to the module-level cache). */\n cache?: OnboardingSpecCache;\n /**\n * Request timeout in ms (default 15s). A stalled intake aborts with\n * OnboardingNetworkError so the boot doesn't hang on \"Loading signup form…\".\n */\n timeoutMs?: number;\n /**\n * When true, return the cached \"latest\" for this onboarder if present\n * WITHOUT a network round-trip. Default false (spec fetches are cheap\n * and the server bumps specVersion on any change). The signup widget's\n * boot passes preferCached:true so a remount reuses the loaded spec.\n */\n preferCached?: boolean;\n}\n\nfunction buildUrl(baseUrl: string, path: string): string {\n const base = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n return `${base}${path}`;\n}\n\n/**\n * Fetch (and validate, and cache) the onboarding spec.\n *\n * @throws {OnboardingSpecInvalidError} response does not match D540 §1\n * @throws {OnboardingRateLimitError} on 429\n * @throws {OnboardingServerError} on any other non-2xx status\n * @throws {OnboardingNetworkError} on network failure OR timeout\n */\nexport async function fetchOnboardingSpec(\n options: SpecFetchOptions,\n): Promise<OnboardingSpec> {\n const cache = options.cache ?? defaultSpecCache;\n\n if (options.preferCached) {\n const cached = cache.getLatest(options.onboarder);\n if (cached) return cached;\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n const timeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const url = buildUrl(options.baseUrl, SPEC_PATH);\n\n let response: Response;\n try {\n response = await fetchWithTimeout(url, {\n method: \"GET\",\n timeoutMs,\n fetchImpl,\n });\n } catch (err) {\n throw new OnboardingNetworkError(err);\n }\n\n if (response.status === 429) {\n const retryAfterHeader = response.headers.get(\"retry-after\");\n const retryAfter = retryAfterHeader\n ? Number.parseInt(retryAfterHeader, 10)\n : Number.NaN;\n throw new OnboardingRateLimitError(\n Number.isNaN(retryAfter) ? null : retryAfter,\n );\n }\n if (response.status < 200 || response.status >= 300) {\n throw new OnboardingServerError(\n response.status,\n `Spec fetch failed with status ${response.status}`,\n );\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n throw new OnboardingSpecInvalidError(\"response body is not JSON\");\n }\n\n const parsed = validateOnboardingSpec(body);\n if (!parsed.success) {\n const detail = parsed.error.issues\n .slice(0, 5)\n .map((i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`)\n .join(\"; \");\n throw new OnboardingSpecInvalidError(detail);\n }\n\n cache.set(options.onboarder, parsed.data);\n return parsed.data;\n}\n","import {\n OnboardingNetworkError,\n OnboardingServerError,\n OnboardingValidationError,\n} from \"../submit/errors.js\";\nimport {\n DEFAULT_REQUEST_TIMEOUT_MS,\n fetchWithTimeout,\n} from \"../submit/http.js\";\n\nexport interface PinStateResult {\n pin: string;\n stateIso: string | null;\n}\n\nexport async function fetchPinState(options: {\n baseUrl: string;\n pin: string;\n fetch?: typeof globalThis.fetch;\n timeoutMs?: number;\n}): Promise<PinStateResult> {\n const pin = options.pin.trim();\n if (!/^[1-9][0-9]{5}$/.test(pin)) {\n throw new OnboardingValidationError(\n \"PIN must be a 6-digit Indian PIN code\",\n );\n }\n // Every failure surfaces as a typed Onboarding error, as from the spec and\n // submit clients: a caller branches on the class, never on a raw TypeError\n // or a SyntaxError from a proxy's HTML error page.\n let response: Response;\n try {\n response = await fetchWithTimeout(\n `${options.baseUrl.replace(/\\/$/, \"\")}/api/v1/public/onboarding/pin-state?pin=${encodeURIComponent(pin)}`,\n {\n method: \"GET\",\n fetchImpl: options.fetch ?? globalThis.fetch,\n timeoutMs: options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,\n },\n );\n } catch (err) {\n throw new OnboardingNetworkError(err);\n }\n if (!response.ok) throw new OnboardingServerError(response.status);\n let value: unknown;\n try {\n value = await response.json();\n } catch {\n throw new OnboardingServerError(\n response.status,\n \"PIN-state response is not JSON\",\n );\n }\n // JSON that is not an object (`null`, a number, a string) is a bad answer\n // too, never a TypeError from reading its fields.\n if (typeof value !== \"object\" || value === null) {\n throw new OnboardingServerError(\n response.status,\n \"Invalid PIN-state response\",\n );\n }\n const answer = value as { pin?: unknown; state_iso?: unknown };\n if (\n answer.pin !== pin ||\n (answer.state_iso !== null && typeof answer.state_iso !== \"string\")\n ) {\n throw new OnboardingServerError(\n response.status,\n \"Invalid PIN-state response\",\n );\n }\n return { pin, stateIso: answer.state_iso };\n}\n"]}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { z } from 'zod/v3';
|
|
2
|
+
|
|
3
|
+
// src/consent/model.ts
|
|
4
|
+
|
|
5
|
+
// src/consent/url.ts
|
|
6
|
+
function isNavigableUrl(raw) {
|
|
7
|
+
try {
|
|
8
|
+
const { protocol } = new URL(raw, "https://relative.invalid/");
|
|
9
|
+
return protocol === "https:" || protocol === "http:";
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/consent/model.ts
|
|
16
|
+
var CUSTOM_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
17
|
+
var ACCEPTANCE_LEVELS = [
|
|
18
|
+
"checkbox",
|
|
19
|
+
"scroll_then_accept",
|
|
20
|
+
"open_then_accept"
|
|
21
|
+
];
|
|
22
|
+
var acceptanceLevelSchema = z.enum(ACCEPTANCE_LEVELS);
|
|
23
|
+
var DOC_TYPES = ["tos", "privacy", "dpa", "custom"];
|
|
24
|
+
var consentDocumentSchema = z.object({
|
|
25
|
+
// Server-side opaque registry id (D543 §3 legal_documents.id). Echoed
|
|
26
|
+
// verbatim on submit as consent.documents[].legalDocumentId (D543 §4);
|
|
27
|
+
// the SDK never synthesizes it. See DRIFT 5e3df7a3 (module doc).
|
|
28
|
+
legalDocumentId: z.string().min(1),
|
|
29
|
+
docType: z.enum(DOC_TYPES),
|
|
30
|
+
// Nullable (NOT omitted): non-custom docs send customKey:null.
|
|
31
|
+
customKey: z.string().min(1).regex(
|
|
32
|
+
CUSTOM_KEY_PATTERN,
|
|
33
|
+
"customKey must contain only letters, digits, underscore, or hyphen"
|
|
34
|
+
).nullable(),
|
|
35
|
+
title: z.string().min(1),
|
|
36
|
+
// Monotonic version of the live document. This is the SUBMIT PIN: it is
|
|
37
|
+
// echoed on submit (consent.documents[].versionNo) and the server matches
|
|
38
|
+
// it against the current live version (mismatch ⇒ 409 consent_stale). Any
|
|
39
|
+
// content change publishes a NEW version, so versionNo is a complete pin.
|
|
40
|
+
versionNo: z.number().int().nonnegative(),
|
|
41
|
+
// sha256 of the live artifact — spec-side integrity metadata (P4). NOT
|
|
42
|
+
// echoed on submit: versionNo is the pin (a content change = new version),
|
|
43
|
+
// so the submit array carries versionNo only, per the canonical wire shape.
|
|
44
|
+
// The SDK exposes contentHash so a consumer can display/verify integrity;
|
|
45
|
+
// the server does NOT require it on the submit array.
|
|
46
|
+
contentHash: z.string().min(1),
|
|
47
|
+
// Short-TTL presigned GET for the artifact bytes (D543 §4 / 04-pii). It
|
|
48
|
+
// lands in an <a href> and the viewer's fetch, so only an http(s) URL or a
|
|
49
|
+
// relative one is accepted: a spec carrying `javascript:` fails closed.
|
|
50
|
+
downloadUrl: z.string().min(1).refine(isNavigableUrl, "downloadUrl must be an http(s) URL"),
|
|
51
|
+
// The artifact's stored MIME (e.g. "application/pdf"). The platform intake
|
|
52
|
+
// has served it since 2026-07-20 (tenant #334) so a renderer can pick a
|
|
53
|
+
// PDF viewer over a text body; D543 §4 does not list it, and a strict
|
|
54
|
+
// schema without it rejected every live spec (drift filed 2026-10-24).
|
|
55
|
+
// Optional: an onboarder that does not send it still parses.
|
|
56
|
+
contentType: z.string().min(1).optional(),
|
|
57
|
+
acceptanceLevel: acceptanceLevelSchema,
|
|
58
|
+
required: z.boolean()
|
|
59
|
+
}).strict().superRefine((doc, ctx) => {
|
|
60
|
+
if (doc.docType === "custom" && doc.customKey === null) {
|
|
61
|
+
ctx.addIssue({
|
|
62
|
+
code: z.ZodIssueCode.custom,
|
|
63
|
+
path: ["customKey"],
|
|
64
|
+
message: "customKey is required (non-null) when docType is 'custom'"
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (doc.docType !== "custom" && doc.customKey !== null) {
|
|
68
|
+
ctx.addIssue({
|
|
69
|
+
code: z.ZodIssueCode.custom,
|
|
70
|
+
path: ["customKey"],
|
|
71
|
+
message: "customKey must be null when docType is not 'custom'"
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
var consentPackSchema = z.object({
|
|
76
|
+
documents: z.array(consentDocumentSchema)
|
|
77
|
+
}).strict().superRefine((pack, ctx) => {
|
|
78
|
+
const seen = /* @__PURE__ */ new Set();
|
|
79
|
+
for (let i = 0; i < pack.documents.length; i++) {
|
|
80
|
+
const doc = pack.documents[i];
|
|
81
|
+
if (!doc) continue;
|
|
82
|
+
const key = consentDocKey(doc);
|
|
83
|
+
if (seen.has(key)) {
|
|
84
|
+
ctx.addIssue({
|
|
85
|
+
code: z.ZodIssueCode.custom,
|
|
86
|
+
path: ["documents", i],
|
|
87
|
+
message: `duplicate consent document key "${key}"`
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
seen.add(key);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
function consentDocKey(doc) {
|
|
94
|
+
return doc.docType === "custom" ? `custom:${doc.customKey ?? ""}` : doc.docType;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { ACCEPTANCE_LEVELS, DOC_TYPES, acceptanceLevelSchema, consentDocKey, consentDocumentSchema, consentPackSchema, isNavigableUrl };
|
|
98
|
+
//# sourceMappingURL=chunk-NDSBIB3N.js.map
|
|
99
|
+
//# sourceMappingURL=chunk-NDSBIB3N.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/consent/url.ts","../src/consent/model.ts"],"names":[],"mappings":"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","file":"chunk-NDSBIB3N.js","sourcesContent":["/**\n * A document link the page may put in an `<a href>` or fetch: an http(s) URL,\n * absolute or relative. Any origin is fine: the intake's presigned links are\n * on the document store's own host. Anything else (`javascript:`, `data:`,\n * `vbscript:` …) is refused, so a tampered spec cannot run script in the host\n * page through a consent or privacy-notice link.\n *\n * The URL parser strips the whitespace and control characters a browser\n * ignores, so `\" javascript:…\"` and `\"java\\tscript:…\"` are refused too.\n */\nexport function isNavigableUrl(raw: string): boolean {\n try {\n const { protocol } = new URL(raw, \"https://relative.invalid/\");\n return protocol === \"https:\" || protocol === \"http:\";\n } catch {\n return false;\n }\n}\n","/**\n * D543 §4 ConsentPack spec model — the `consentPack` block that GET\n * /api/v1/public/onboarding/spec grows on top of the D540 shape:\n *\n * consentPack: {\n * documents: [\n * { doc_type, custom_key?, title, version_no, content_hash,\n * download_url, acceptance_level, required }\n * ]\n * }\n *\n * The intake projects ONLY `live` versions into this block (D543 §4);\n * `download_url` is a short-TTL presigned GET the SDK renders/opens.\n *\n * `acceptance_level` (D543 §2) is a per-document GATE:\n * - \"checkbox\" — a single accept checkbox (basic clickwrap).\n * - \"scroll_then_accept\" — accept disabled until the doc is scrolled to\n * the end (client asserts evidence.scrolled_to_end).\n * - \"open_then_accept\" — accept disabled until the doc is opened/viewed\n * (client asserts evidence.opened).\n * (E-signature is a deferred future level, D543 §2 — NOT modeled here.)\n *\n * FAIL-CLOSED (R1 + D543 §5 / D540 §1): a malformed consentPack throws\n * OnboardingSpecInvalidError at parse time — never rendered half-parsed\n * (a dropped `required` doc would let a user submit without consent, and a\n * missing `content_hash`/`version_no` would break the server's re-validation\n * match). The SDK refuses to render a consent step it cannot trust.\n *\n * DOM-ID HARDENING: a document's DOM-id token is derived from `doc_type`\n * (a fixed enum — safe) or, for custom docs, from `custom_key` (free-form\n * from the served spec). Custom keys are interpolated into element ids and\n * (historically) selectors by the widget layer, so they are constrained to\n * the SAME strict identifier alphabet as field keys (CUSTOM_KEY_PATTERN,\n * identical to the spec model's FIELD_KEY_PATTERN) and REJECTED at parse time\n * if they carry CSS/selector metacharacters. This is layer 1; the widget also\n * CSS.escapes every id token as defense-in-depth.\n *\n * SUBMIT PIN (P4): the submit array carries {legalDocumentId, versionNo,\n * accepted, evidence} — the server matches on legalDocumentId + versionNo\n * (contentHash is spec-side integrity metadata, NOT echoed on submit; a\n * content change publishes a new versionNo, so versionNo is a complete pin).\n *\n * DRIFT 5e3df7a3 (2026-07-14, consumer-visible) — RESOLVED by the canonical\n * shape: D543 §4 as literally written omitted `legal_document_id` from the\n * spec block, yet the submit array is KEYED on it (the server matches\n * legalDocumentId + versionNo against the live version). The\n * canonical alignment (both tiers, camelCase) INCLUDES `legalDocumentId` in\n * the spec `consentPack.documents[]`, exactly as the drift proposed. This\n * model REQUIRES it (fail-closed OnboardingSpecInvalidError if missing).\n */\n\nimport { z } from \"zod/v3\";\nimport { isNavigableUrl } from \"./url.js\";\n\n/**\n * DOM-id-safe custom-key alphabet. INTENTIONALLY identical to the spec\n * model's FIELD_KEY_PATTERN (`/^[A-Za-z0-9_-]+$/`), duplicated here rather\n * than imported to avoid a spec↔consent import cycle (spec/model.ts imports\n * consentPackSchema from this file). Any change to one must mirror the other.\n */\nconst CUSTOM_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;\n\n/** D543 §2 acceptance-level enum. Extensible (adding a level is additive). */\nexport const ACCEPTANCE_LEVELS = [\n \"checkbox\",\n \"scroll_then_accept\",\n \"open_then_accept\",\n] as const;\n\nexport type AcceptanceLevel = (typeof ACCEPTANCE_LEVELS)[number];\n\nexport const acceptanceLevelSchema = z.enum(ACCEPTANCE_LEVELS);\n\n/** D543 §3 docType enum. */\nexport const DOC_TYPES = [\"tos\", \"privacy\", \"dpa\", \"custom\"] as const;\nexport type ConsentDocType = (typeof DOC_TYPES)[number];\n\n/**\n * One live legal document projected into the consent step. Mirrors the D543\n * §4 `consentPack.documents[]` element — CANONICAL camelCase wire shape (both\n * tenant + platform tiers aligned to this): { legalDocumentId, docType,\n * customKey (nullable), title, versionNo, contentHash, downloadUrl,\n * acceptanceLevel, required }. The `acceptanceLevel` enum VALUES stay snake\n * (checkbox | scroll_then_accept | open_then_accept).\n *\n * Cross-field invariant (D543 §3): `customKey` is a non-null identifier when\n * `docType === \"custom\"` and MUST be null otherwise (a canonical tos/privacy/\n * dpa doc is keyed by docType; only custom docs carry a key). The custom-key\n * alphabet is DOM-id safe.\n */\nexport const consentDocumentSchema = z\n .object({\n // Server-side opaque registry id (D543 §3 legal_documents.id). Echoed\n // verbatim on submit as consent.documents[].legalDocumentId (D543 §4);\n // the SDK never synthesizes it. See DRIFT 5e3df7a3 (module doc).\n legalDocumentId: z.string().min(1),\n docType: z.enum(DOC_TYPES),\n // Nullable (NOT omitted): non-custom docs send customKey:null.\n customKey: z\n .string()\n .min(1)\n .regex(\n CUSTOM_KEY_PATTERN,\n \"customKey must contain only letters, digits, underscore, or hyphen\",\n )\n .nullable(),\n title: z.string().min(1),\n // Monotonic version of the live document. This is the SUBMIT PIN: it is\n // echoed on submit (consent.documents[].versionNo) and the server matches\n // it against the current live version (mismatch ⇒ 409 consent_stale). Any\n // content change publishes a NEW version, so versionNo is a complete pin.\n versionNo: z.number().int().nonnegative(),\n // sha256 of the live artifact — spec-side integrity metadata (P4). NOT\n // echoed on submit: versionNo is the pin (a content change = new version),\n // so the submit array carries versionNo only, per the canonical wire shape.\n // The SDK exposes contentHash so a consumer can display/verify integrity;\n // the server does NOT require it on the submit array.\n contentHash: z.string().min(1),\n // Short-TTL presigned GET for the artifact bytes (D543 §4 / 04-pii). It\n // lands in an <a href> and the viewer's fetch, so only an http(s) URL or a\n // relative one is accepted: a spec carrying `javascript:` fails closed.\n downloadUrl: z\n .string()\n .min(1)\n .refine(isNavigableUrl, \"downloadUrl must be an http(s) URL\"),\n // The artifact's stored MIME (e.g. \"application/pdf\"). The platform intake\n // has served it since 2026-07-20 (tenant #334) so a renderer can pick a\n // PDF viewer over a text body; D543 §4 does not list it, and a strict\n // schema without it rejected every live spec (drift filed 2026-10-24).\n // Optional: an onboarder that does not send it still parses.\n contentType: z.string().min(1).optional(),\n acceptanceLevel: acceptanceLevelSchema,\n required: z.boolean(),\n })\n .strict()\n .superRefine((doc, ctx) => {\n if (doc.docType === \"custom\" && doc.customKey === null) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: [\"customKey\"],\n message: \"customKey is required (non-null) when docType is 'custom'\",\n });\n }\n if (doc.docType !== \"custom\" && doc.customKey !== null) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: [\"customKey\"],\n message: \"customKey must be null when docType is not 'custom'\",\n });\n }\n });\n\nexport type ConsentDocument = z.infer<typeof consentDocumentSchema>;\n\n/**\n * The `consentPack` block. `documents` may be empty (an onboarder with no\n * required legal docs) — the consent stage is then omitted from the flow.\n *\n * Uniqueness: no two documents may share the same STABLE key (docType, or\n * custom:<customKey> for custom docs). A duplicate would collide their DOM\n * ids AND their consent records — fail-closed at parse (the server keys the\n * registry on (scope, docType) / (scope, customKey) per D543 §3, so a\n * duplicate here is a projection bug, not a renderable spec).\n */\nexport const consentPackSchema = z\n .object({\n documents: z.array(consentDocumentSchema),\n })\n .strict()\n .superRefine((pack, ctx) => {\n const seen = new Set<string>();\n for (let i = 0; i < pack.documents.length; i++) {\n const doc = pack.documents[i];\n if (!doc) continue;\n const key = consentDocKey(doc);\n if (seen.has(key)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: [\"documents\", i],\n message: `duplicate consent document key \"${key}\"`,\n });\n }\n seen.add(key);\n }\n });\n\nexport type ConsentPack = z.infer<typeof consentPackSchema>;\n\n/**\n * Stable, collision-proof identity for a consent document, used as the map\n * key in the controller and the DOM-id token in the widget. `docType` is a\n * fixed enum; `custom:` is prefixed for custom docs so a customKey of\n * \"tos\" can never alias the canonical tos doc.\n *\n * Note: this is NOT the submit key — the submit array keys on the server's\n * opaque `legalDocumentId` (D543 §4), which the SDK does not synthesize.\n * This is a purely CLIENT-side identity for state + DOM.\n */\nexport function consentDocKey(doc: {\n docType: ConsentDocType;\n customKey?: string | null | undefined;\n}): string {\n return doc.docType === \"custom\"\n ? `custom:${doc.customKey ?? \"\"}`\n : doc.docType;\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/submit/subdomain.ts
|
|
2
|
+
var DNS_LABEL_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
3
|
+
var SUBDOMAIN_SHAPE_MESSAGE = "Use letters, digits and hyphens only, with no hyphen at the start or end";
|
|
4
|
+
function isSubdomainLabelShape(raw) {
|
|
5
|
+
return DNS_LABEL_RE.test(raw.trim().toLowerCase());
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export { SUBDOMAIN_SHAPE_MESSAGE, isSubdomainLabelShape };
|
|
9
|
+
//# sourceMappingURL=chunk-OLWR5COK.js.map
|
|
10
|
+
//# sourceMappingURL=chunk-OLWR5COK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/submit/subdomain.ts"],"names":[],"mappings":";AAUA,IAAM,YAAA,GAAe,sCAAA;AAEd,IAAM,uBAAA,GACX;AAEK,SAAS,sBAAsB,GAAA,EAAsB;AAC1D,EAAA,OAAO,aAAa,IAAA,CAAK,GAAA,CAAI,IAAA,EAAK,CAAE,aAAa,CAAA;AACnD","file":"chunk-OLWR5COK.js","sourcesContent":["/**\n * The workspace subdomain's shape (D836), checked before a submit so a\n * malformed label is a message on the field instead of a server refusal.\n *\n * Mirrors the intake's `normalizeSubdomainLabel` (nodii-tenant-service\n * src/services/tenancy/subdomain.ts): after trimming and lowercasing, a DNS\n * label of 1–63 characters from a-z, 0-9 and hyphens, with no hyphen at\n * either end. Upper case is fine; the intake lowercases it. Reservation and\n * availability stay the intake's call (`subdomain_taken`).\n */\nconst DNS_LABEL_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;\n\nexport const SUBDOMAIN_SHAPE_MESSAGE =\n \"Use letters, digits and hyphens only, with no hyphen at the start or end\";\n\nexport function isSubdomainLabelShape(raw: string): boolean {\n return DNS_LABEL_RE.test(raw.trim().toLowerCase());\n}\n"]}
|