@syndew/oluso-sdk 0.0.0-stage → 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 +509 -2
- package/dist/auth/embed.global.js +210 -0
- package/dist/auth/embed.global.js.map +1 -0
- package/dist/auth/index.d.mts +535 -0
- package/dist/auth/index.d.ts +535 -0
- package/dist/auth/index.js +2118 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/index.mjs +2088 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/embed.global.js +448 -0
- package/dist/embed.global.js.map +1 -0
- package/dist/index.d.mts +810 -0
- package/dist/index.d.ts +810 -0
- package/dist/index.js +2127 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2095 -0
- package/dist/index.mjs.map +1 -0
- package/dist/react.d.mts +559 -0
- package/dist/react.d.ts +559 -0
- package/dist/react.js +1674 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +1642 -0
- package/dist/react.mjs.map +1 -0
- package/package.json +67 -4
package/dist/react.js
ADDED
|
@@ -0,0 +1,1674 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/react.tsx
|
|
31
|
+
var react_exports = {};
|
|
32
|
+
__export(react_exports, {
|
|
33
|
+
Journey: () => Journey,
|
|
34
|
+
JourneyEmbed: () => JourneyEmbed,
|
|
35
|
+
JourneyForm: () => JourneyForm,
|
|
36
|
+
OlusoProvider: () => OlusoProvider,
|
|
37
|
+
useJourney: () => useJourney,
|
|
38
|
+
useOluso: () => useOluso
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(react_exports);
|
|
41
|
+
var import_react = __toESM(require("react"));
|
|
42
|
+
|
|
43
|
+
// src/client.ts
|
|
44
|
+
var OlusoClient = class {
|
|
45
|
+
constructor(config) {
|
|
46
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
47
|
+
if (!config.serverUrl) {
|
|
48
|
+
throw new Error("serverUrl is required");
|
|
49
|
+
}
|
|
50
|
+
this.config = {
|
|
51
|
+
...config,
|
|
52
|
+
serverUrl: config.serverUrl.replace(/\/$/, "")
|
|
53
|
+
// Remove trailing slash
|
|
54
|
+
};
|
|
55
|
+
this.fetchFn = config.fetch || globalThis.fetch.bind(globalThis);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get journey information including form fields. Journeys are named by slug.
|
|
59
|
+
*/
|
|
60
|
+
async getJourney(slug) {
|
|
61
|
+
const response = await this.request(`/api/public/journeys/${encodeURIComponent(slug)}`);
|
|
62
|
+
return response;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Submit data to a data collection journey (waitlist, contact, survey, etc.)
|
|
66
|
+
*/
|
|
67
|
+
async submit(slug, data, options) {
|
|
68
|
+
try {
|
|
69
|
+
const result = await this.request(`/api/public/journeys/${encodeURIComponent(slug)}/submit`, {
|
|
70
|
+
method: "POST",
|
|
71
|
+
body: {
|
|
72
|
+
data,
|
|
73
|
+
metadata: {
|
|
74
|
+
...options?.metadata,
|
|
75
|
+
referrer: typeof document !== "undefined" ? document.referrer : void 0,
|
|
76
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
|
|
77
|
+
},
|
|
78
|
+
honeypot: options?.honeypot
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
this.emit({
|
|
82
|
+
type: "submission:success",
|
|
83
|
+
slug,
|
|
84
|
+
data: result
|
|
85
|
+
});
|
|
86
|
+
return result;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
this.emit({
|
|
89
|
+
type: "submission:error",
|
|
90
|
+
slug,
|
|
91
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
92
|
+
});
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Start a multi-step journey and run its first step. Without data the session's `step`
|
|
98
|
+
* is the first step to render; with data, that is the first step's input.
|
|
99
|
+
*/
|
|
100
|
+
async startJourney(slug, data) {
|
|
101
|
+
const session = await this.request(
|
|
102
|
+
`/api/public/journeys/${encodeURIComponent(slug)}/start`,
|
|
103
|
+
{ method: "POST", body: { data } }
|
|
104
|
+
);
|
|
105
|
+
this.emit({
|
|
106
|
+
type: "journey:started",
|
|
107
|
+
slug: session.slug,
|
|
108
|
+
sessionId: session.sessionId,
|
|
109
|
+
data: session
|
|
110
|
+
});
|
|
111
|
+
return session;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Start a sign-in (or any authentication) journey for a public client. When the session completes it carries an
|
|
115
|
+
* authorization code, redeemed at the token endpoint with the PKCE verifier and redirect_uri "postmessage". Use
|
|
116
|
+
* this when your app keeps its own tokens; OlusoAuth.mountSignIn does the whole round trip for you.
|
|
117
|
+
*/
|
|
118
|
+
async startAuthJourney(slug, auth, data) {
|
|
119
|
+
const session = await this.request(
|
|
120
|
+
`/api/public/journeys/${encodeURIComponent(slug)}/start`,
|
|
121
|
+
{
|
|
122
|
+
method: "POST",
|
|
123
|
+
body: { data, clientId: auth.clientId, codeChallenge: auth.codeChallenge, codeChallengeMethod: "S256", scope: auth.scope }
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
this.emit({ type: "journey:started", slug: session.slug, sessionId: session.sessionId, data: session });
|
|
127
|
+
return session;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Submit input (or an action such as "resend") for the session's current step.
|
|
131
|
+
* The result is the session afterwards: the next step to render, or the completion.
|
|
132
|
+
*/
|
|
133
|
+
async submitStep(sessionId, data, action) {
|
|
134
|
+
const result = await this.request(
|
|
135
|
+
`/api/public/journeys/sessions/${encodeURIComponent(sessionId)}/step`,
|
|
136
|
+
{ method: "POST", body: { data, action } }
|
|
137
|
+
);
|
|
138
|
+
this.emit({
|
|
139
|
+
type: result.completed ? "journey:completed" : result.success ? "journey:step" : "journey:error",
|
|
140
|
+
slug: result.slug,
|
|
141
|
+
sessionId,
|
|
142
|
+
data: result
|
|
143
|
+
});
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Submit the current step the way a form posts it: every field, files under their field's name, and the
|
|
148
|
+
* clicked button's name and value. Use this for steps with file fields; `action` is a step action such as "init".
|
|
149
|
+
*/
|
|
150
|
+
async submitStepForm(sessionId, form, action) {
|
|
151
|
+
if (action) form.set("__journey_action", action);
|
|
152
|
+
const result = await this.request(
|
|
153
|
+
`/api/public/journeys/sessions/${encodeURIComponent(sessionId)}/step`,
|
|
154
|
+
{ method: "POST", body: form }
|
|
155
|
+
);
|
|
156
|
+
this.emit({
|
|
157
|
+
type: result.completed ? "journey:completed" : result.success ? "journey:step" : "journey:error",
|
|
158
|
+
slug: result.slug,
|
|
159
|
+
sessionId,
|
|
160
|
+
data: result
|
|
161
|
+
});
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
/** The server this client talks to */
|
|
165
|
+
get serverUrl() {
|
|
166
|
+
return this.config.serverUrl;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get where a multi-step session stands (status, current step), without running it
|
|
170
|
+
*/
|
|
171
|
+
async getSession(sessionId) {
|
|
172
|
+
return this.request(`/api/public/journeys/sessions/${encodeURIComponent(sessionId)}`);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* URL of the hosted journey page (/journey/start/{slug}) to redirect to
|
|
176
|
+
*/
|
|
177
|
+
getJourneyUrl(slug, options) {
|
|
178
|
+
const params = new URLSearchParams();
|
|
179
|
+
if (this.config.tenant) params.set("tenant", this.config.tenant);
|
|
180
|
+
if (options?.redirectUri) {
|
|
181
|
+
params.set("redirect_uri", options.redirectUri);
|
|
182
|
+
}
|
|
183
|
+
if (options?.state) {
|
|
184
|
+
params.set("state", options.state);
|
|
185
|
+
}
|
|
186
|
+
return `${this.config.serverUrl}/journey/start/${encodeURIComponent(slug)}?${params}`;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* URL of the hosted journey page (/journey/start/{slug}) for an iframe
|
|
190
|
+
*/
|
|
191
|
+
getEmbedUrl(slug, options) {
|
|
192
|
+
const params = new URLSearchParams({ embed: "true" });
|
|
193
|
+
if (this.config.tenant) params.set("tenant", this.config.tenant);
|
|
194
|
+
if (options?.theme) {
|
|
195
|
+
params.set("theme", options.theme);
|
|
196
|
+
}
|
|
197
|
+
if (options?.hideHeader) {
|
|
198
|
+
params.set("hideHeader", "true");
|
|
199
|
+
}
|
|
200
|
+
return `${this.config.serverUrl}/journey/start/${encodeURIComponent(slug)}?${params}`;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Subscribe to SDK events
|
|
204
|
+
*/
|
|
205
|
+
on(event, listener) {
|
|
206
|
+
if (!this.listeners.has(event)) {
|
|
207
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
208
|
+
}
|
|
209
|
+
this.listeners.get(event).add(listener);
|
|
210
|
+
return () => {
|
|
211
|
+
this.listeners.get(event)?.delete(listener);
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Unsubscribe from SDK events
|
|
216
|
+
*/
|
|
217
|
+
off(event, listener) {
|
|
218
|
+
this.listeners.get(event)?.delete(listener);
|
|
219
|
+
}
|
|
220
|
+
emit(event) {
|
|
221
|
+
this.listeners.get(event.type)?.forEach((listener) => listener(event));
|
|
222
|
+
this.listeners.get("*")?.forEach((listener) => listener(event));
|
|
223
|
+
}
|
|
224
|
+
async request(path, options) {
|
|
225
|
+
const url = `${this.config.serverUrl}${path}`;
|
|
226
|
+
const method = options?.method || "GET";
|
|
227
|
+
const isForm = typeof FormData !== "undefined" && options?.body instanceof FormData;
|
|
228
|
+
const headers = {};
|
|
229
|
+
if (this.config.tenant) {
|
|
230
|
+
headers["X-Tenant-ID"] = this.config.tenant;
|
|
231
|
+
}
|
|
232
|
+
if (!isForm) {
|
|
233
|
+
headers["Content-Type"] = "application/json";
|
|
234
|
+
}
|
|
235
|
+
if (this.config.apiKey) {
|
|
236
|
+
headers["X-API-Key"] = this.config.apiKey;
|
|
237
|
+
}
|
|
238
|
+
if (this.config.headers) {
|
|
239
|
+
Object.assign(headers, await this.config.headers({ method, url }));
|
|
240
|
+
}
|
|
241
|
+
const response = await this.fetchFn(url, {
|
|
242
|
+
method,
|
|
243
|
+
headers,
|
|
244
|
+
body: options?.body === void 0 ? void 0 : isForm ? options.body : JSON.stringify(options.body),
|
|
245
|
+
credentials: "include"
|
|
246
|
+
// Include cookies for session management
|
|
247
|
+
});
|
|
248
|
+
if (!response.ok) {
|
|
249
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
250
|
+
throw new OlusoError(
|
|
251
|
+
error.message || `Request failed: ${response.status}`,
|
|
252
|
+
response.status,
|
|
253
|
+
error.code,
|
|
254
|
+
error.errors
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return response.json();
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
var OlusoError = class extends Error {
|
|
261
|
+
constructor(message, status, code, fieldErrors) {
|
|
262
|
+
super(message);
|
|
263
|
+
this.status = status;
|
|
264
|
+
this.code = code;
|
|
265
|
+
this.fieldErrors = fieldErrors;
|
|
266
|
+
this.name = "OlusoError";
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// src/journey/conditions.ts
|
|
271
|
+
var ALIASES = {
|
|
272
|
+
eq: "eq",
|
|
273
|
+
equals: "eq",
|
|
274
|
+
"==": "eq",
|
|
275
|
+
"=": "eq",
|
|
276
|
+
ne: "ne",
|
|
277
|
+
neq: "ne",
|
|
278
|
+
notequals: "ne",
|
|
279
|
+
"!=": "ne",
|
|
280
|
+
gt: "gt",
|
|
281
|
+
">": "gt",
|
|
282
|
+
greaterthan: "gt",
|
|
283
|
+
gte: "gte",
|
|
284
|
+
">=": "gte",
|
|
285
|
+
greaterthanorequals: "gte",
|
|
286
|
+
lt: "lt",
|
|
287
|
+
"<": "lt",
|
|
288
|
+
lessthan: "lt",
|
|
289
|
+
lte: "lte",
|
|
290
|
+
"<=": "lte",
|
|
291
|
+
lessthanorequals: "lte",
|
|
292
|
+
contains: "contains",
|
|
293
|
+
notcontains: "not_contains",
|
|
294
|
+
"!contains": "not_contains",
|
|
295
|
+
startswith: "starts_with",
|
|
296
|
+
endswith: "ends_with",
|
|
297
|
+
exists: "exists",
|
|
298
|
+
notnull: "exists",
|
|
299
|
+
isset: "exists",
|
|
300
|
+
notexists: "not_exists",
|
|
301
|
+
null: "not_exists",
|
|
302
|
+
isnotset: "not_exists",
|
|
303
|
+
empty: "empty",
|
|
304
|
+
notempty: "not_empty",
|
|
305
|
+
in: "in",
|
|
306
|
+
notin: "not_in",
|
|
307
|
+
"!in": "not_in",
|
|
308
|
+
regex: "regex",
|
|
309
|
+
matches: "regex",
|
|
310
|
+
true: "true",
|
|
311
|
+
false: "false"
|
|
312
|
+
};
|
|
313
|
+
function normalizeOperator(op) {
|
|
314
|
+
if (!op) return void 0;
|
|
315
|
+
return ALIASES[op.trim().toLowerCase().replace(/_/g, "")];
|
|
316
|
+
}
|
|
317
|
+
var same = (a, b) => a.localeCompare(b, void 0, { sensitivity: "accent" }) === 0;
|
|
318
|
+
function order(actual, expected) {
|
|
319
|
+
if (expected === void 0) return void 0;
|
|
320
|
+
const a = Number(actual);
|
|
321
|
+
const e = Number(expected);
|
|
322
|
+
if (actual.trim() !== "" && expected.trim() !== "" && !Number.isNaN(a) && !Number.isNaN(e)) return a === e ? 0 : a < e ? -1 : 1;
|
|
323
|
+
const ad = Date.parse(actual);
|
|
324
|
+
const ed = Date.parse(expected);
|
|
325
|
+
if (!Number.isNaN(ad) && !Number.isNaN(ed)) return ad === ed ? 0 : ad < ed ? -1 : 1;
|
|
326
|
+
return actual.localeCompare(expected, void 0, { sensitivity: "accent" });
|
|
327
|
+
}
|
|
328
|
+
function compare(actual, op, expected) {
|
|
329
|
+
const lower = actual.toLowerCase();
|
|
330
|
+
const want = (expected ?? "").toLowerCase();
|
|
331
|
+
switch (op) {
|
|
332
|
+
case "eq":
|
|
333
|
+
return same(actual, expected ?? "");
|
|
334
|
+
case "ne":
|
|
335
|
+
return !same(actual, expected ?? "");
|
|
336
|
+
case "gt": {
|
|
337
|
+
const o = order(actual, expected);
|
|
338
|
+
return o !== void 0 && o > 0;
|
|
339
|
+
}
|
|
340
|
+
case "gte": {
|
|
341
|
+
const o = order(actual, expected);
|
|
342
|
+
return o !== void 0 && o >= 0;
|
|
343
|
+
}
|
|
344
|
+
case "lt": {
|
|
345
|
+
const o = order(actual, expected);
|
|
346
|
+
return o !== void 0 && o < 0;
|
|
347
|
+
}
|
|
348
|
+
case "lte": {
|
|
349
|
+
const o = order(actual, expected);
|
|
350
|
+
return o !== void 0 && o <= 0;
|
|
351
|
+
}
|
|
352
|
+
case "contains":
|
|
353
|
+
return lower.includes(want);
|
|
354
|
+
case "not_contains":
|
|
355
|
+
return !lower.includes(want);
|
|
356
|
+
case "starts_with":
|
|
357
|
+
return lower.startsWith(want);
|
|
358
|
+
case "ends_with":
|
|
359
|
+
return lower.endsWith(want);
|
|
360
|
+
case "regex":
|
|
361
|
+
if (!expected) return false;
|
|
362
|
+
try {
|
|
363
|
+
return new RegExp(expected, "i").test(actual);
|
|
364
|
+
} catch {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
default:
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function expectedList(expected) {
|
|
372
|
+
if (!expected || !expected.trim()) return [];
|
|
373
|
+
const trimmed = expected.trim();
|
|
374
|
+
if (trimmed.startsWith("[")) {
|
|
375
|
+
try {
|
|
376
|
+
const parsed = JSON.parse(trimmed);
|
|
377
|
+
if (Array.isArray(parsed)) return parsed.map(String);
|
|
378
|
+
} catch {
|
|
379
|
+
return [];
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return trimmed.split(",").map((v) => v.trim()).filter(Boolean);
|
|
383
|
+
}
|
|
384
|
+
var truthy = (v) => v.toLowerCase() !== "false" && v !== "0";
|
|
385
|
+
function conditionMet(condition, values) {
|
|
386
|
+
const op = normalizeOperator(condition.operator);
|
|
387
|
+
const present = values.filter((v) => v !== "");
|
|
388
|
+
const members = () => present.flatMap((v) => v.split(",").map((p) => p.trim()).filter(Boolean));
|
|
389
|
+
switch (op) {
|
|
390
|
+
case "exists":
|
|
391
|
+
case "not_empty":
|
|
392
|
+
return present.length > 0;
|
|
393
|
+
case "not_exists":
|
|
394
|
+
case "empty":
|
|
395
|
+
return present.length === 0;
|
|
396
|
+
case "ne":
|
|
397
|
+
return !present.some((v) => compare(v, "eq", condition.value));
|
|
398
|
+
case "not_contains":
|
|
399
|
+
return !present.some((v) => compare(v, "contains", condition.value));
|
|
400
|
+
case "in": {
|
|
401
|
+
const list = expectedList(condition.value);
|
|
402
|
+
return members().some((m) => list.some((e) => same(m, e)));
|
|
403
|
+
}
|
|
404
|
+
case "not_in": {
|
|
405
|
+
const list = expectedList(condition.value);
|
|
406
|
+
return !members().some((m) => list.some((e) => same(m, e)));
|
|
407
|
+
}
|
|
408
|
+
case "true":
|
|
409
|
+
return present.some(truthy);
|
|
410
|
+
case "false":
|
|
411
|
+
return !present.some(truthy);
|
|
412
|
+
case void 0:
|
|
413
|
+
return false;
|
|
414
|
+
default:
|
|
415
|
+
return present.some((v) => compare(v, op, condition.value));
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/journey/styles.ts
|
|
420
|
+
var JOURNEY_STYLES = `
|
|
421
|
+
.oluso-journey {
|
|
422
|
+
--oluso-accent: #4f46e5;
|
|
423
|
+
--oluso-accent-contrast: #ffffff;
|
|
424
|
+
--oluso-fg: #111827;
|
|
425
|
+
--oluso-muted: #6b7280;
|
|
426
|
+
--oluso-bg: #ffffff;
|
|
427
|
+
--oluso-field-bg: #ffffff;
|
|
428
|
+
--oluso-border: #d1d5db;
|
|
429
|
+
--oluso-danger: #b91c1c;
|
|
430
|
+
--oluso-danger-bg: #fef2f2;
|
|
431
|
+
--oluso-notice-bg: #eff6ff;
|
|
432
|
+
--oluso-notice: #1d4ed8;
|
|
433
|
+
--oluso-radius: 8px;
|
|
434
|
+
--oluso-gap: 14px;
|
|
435
|
+
--oluso-font: inherit;
|
|
436
|
+
font-family: var(--oluso-font);
|
|
437
|
+
color: var(--oluso-fg);
|
|
438
|
+
font-size: 15px;
|
|
439
|
+
line-height: 1.5;
|
|
440
|
+
}
|
|
441
|
+
.oluso-journey[data-theme="dark"] {
|
|
442
|
+
--oluso-fg: #f3f4f6;
|
|
443
|
+
--oluso-muted: #9ca3af;
|
|
444
|
+
--oluso-bg: #111827;
|
|
445
|
+
--oluso-field-bg: #1f2937;
|
|
446
|
+
--oluso-border: #374151;
|
|
447
|
+
--oluso-danger: #fca5a5;
|
|
448
|
+
--oluso-danger-bg: rgba(185, 28, 28, 0.15);
|
|
449
|
+
--oluso-notice-bg: rgba(59, 130, 246, 0.15);
|
|
450
|
+
--oluso-notice: #93c5fd;
|
|
451
|
+
}
|
|
452
|
+
@media (prefers-color-scheme: dark) {
|
|
453
|
+
.oluso-journey[data-theme="auto"] {
|
|
454
|
+
--oluso-fg: #f3f4f6;
|
|
455
|
+
--oluso-muted: #9ca3af;
|
|
456
|
+
--oluso-bg: #111827;
|
|
457
|
+
--oluso-field-bg: #1f2937;
|
|
458
|
+
--oluso-border: #374151;
|
|
459
|
+
--oluso-danger: #fca5a5;
|
|
460
|
+
--oluso-danger-bg: rgba(185, 28, 28, 0.15);
|
|
461
|
+
--oluso-notice-bg: rgba(59, 130, 246, 0.15);
|
|
462
|
+
--oluso-notice: #93c5fd;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
.oluso-journey *, .oluso-journey *::before, .oluso-journey *::after { box-sizing: border-box; }
|
|
466
|
+
.oluso-journey__form { display: flex; flex-direction: column; gap: var(--oluso-gap); margin: 0; }
|
|
467
|
+
.oluso-journey__title { font-size: 1.25em; font-weight: 600; margin: 0; }
|
|
468
|
+
.oluso-journey__description { color: var(--oluso-muted); margin: 0; white-space: pre-line; }
|
|
469
|
+
.oluso-journey__alert { border-radius: var(--oluso-radius); padding: 10px 12px; margin: 0; }
|
|
470
|
+
.oluso-journey__alert--error { background: var(--oluso-danger-bg); color: var(--oluso-danger); }
|
|
471
|
+
.oluso-journey__alert--notice { background: var(--oluso-notice-bg); color: var(--oluso-notice); }
|
|
472
|
+
.oluso-journey__group { display: flex; flex-direction: column; gap: var(--oluso-gap); border: 0; margin: 0; padding: 0; }
|
|
473
|
+
.oluso-journey__field { display: flex; flex-direction: column; gap: 6px; }
|
|
474
|
+
.oluso-journey__field[hidden] { display: none; }
|
|
475
|
+
.oluso-journey__label { font-weight: 500; }
|
|
476
|
+
.oluso-journey__required { color: var(--oluso-danger); margin-left: 2px; }
|
|
477
|
+
.oluso-journey__input, .oluso-journey__select, .oluso-journey__textarea {
|
|
478
|
+
width: 100%; font: inherit; color: var(--oluso-fg); background: var(--oluso-field-bg);
|
|
479
|
+
border: 1px solid var(--oluso-border); border-radius: var(--oluso-radius); padding: 9px 11px; min-height: 40px;
|
|
480
|
+
}
|
|
481
|
+
.oluso-journey__input:focus, .oluso-journey__select:focus, .oluso-journey__textarea:focus {
|
|
482
|
+
outline: 2px solid var(--oluso-accent); outline-offset: 1px; border-color: transparent;
|
|
483
|
+
}
|
|
484
|
+
.oluso-journey__input[aria-invalid="true"], .oluso-journey__select[aria-invalid="true"], .oluso-journey__textarea[aria-invalid="true"] {
|
|
485
|
+
border-color: var(--oluso-danger);
|
|
486
|
+
}
|
|
487
|
+
.oluso-journey__input--otp { letter-spacing: 0.4em; font-variant-numeric: tabular-nums; text-align: center; font-size: 1.2em; }
|
|
488
|
+
.oluso-journey__phone { display: flex; gap: 8px; }
|
|
489
|
+
.oluso-journey__phone .oluso-journey__select { width: auto; max-width: 45%; }
|
|
490
|
+
.oluso-journey__choices { display: flex; flex-direction: column; gap: 8px; }
|
|
491
|
+
.oluso-journey__choice { display: flex; gap: 10px; align-items: flex-start; cursor: pointer; }
|
|
492
|
+
.oluso-journey__choice input { margin-top: 3px; accent-color: var(--oluso-accent); }
|
|
493
|
+
.oluso-journey__choice-description { display: block; color: var(--oluso-muted); font-size: 0.9em; }
|
|
494
|
+
.oluso-journey__help, .oluso-journey__hint { color: var(--oluso-muted); font-size: 0.875em; margin: 0; }
|
|
495
|
+
.oluso-journey__error { color: var(--oluso-danger); font-size: 0.875em; margin: 0; }
|
|
496
|
+
.oluso-journey__content { margin: 0; white-space: pre-line; }
|
|
497
|
+
.oluso-journey__actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
498
|
+
.oluso-journey__button {
|
|
499
|
+
font: inherit; font-weight: 500; border-radius: var(--oluso-radius); padding: 9px 16px; min-height: 40px; cursor: pointer;
|
|
500
|
+
border: 1px solid var(--oluso-border); background: var(--oluso-field-bg); color: var(--oluso-fg);
|
|
501
|
+
}
|
|
502
|
+
.oluso-journey__button--primary { background: var(--oluso-accent); color: var(--oluso-accent-contrast); border-color: var(--oluso-accent); }
|
|
503
|
+
.oluso-journey__button--primary:only-child, .oluso-journey__actions--stacked .oluso-journey__button--primary { flex: 1 1 auto; }
|
|
504
|
+
.oluso-journey__button--link { background: none; border: 0; color: var(--oluso-accent); padding: 4px 0; min-height: 0; }
|
|
505
|
+
.oluso-journey__button--cancel { background: none; }
|
|
506
|
+
.oluso-journey__button:disabled { opacity: 0.6; cursor: progress; }
|
|
507
|
+
.oluso-journey__enter { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
|
|
508
|
+
.oluso-journey__action-line { display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; margin: 0; }
|
|
509
|
+
.oluso-journey__links { display: flex; flex-wrap: wrap; gap: 12px; font-size: 0.9em; }
|
|
510
|
+
.oluso-journey__links a { color: var(--oluso-accent); }
|
|
511
|
+
.oluso-journey__qr { display: flex; flex-direction: column; align-items: center; gap: 8px; }
|
|
512
|
+
.oluso-journey__qr img { width: 180px; height: 180px; background: #fff; padding: 8px; border-radius: var(--oluso-radius); }
|
|
513
|
+
.oluso-journey__code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }
|
|
514
|
+
.oluso-journey__codes { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; margin: 0; padding: 0; list-style: none; }
|
|
515
|
+
.oluso-journey__codes li { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; padding: 6px 8px; border: 1px solid var(--oluso-border); border-radius: 6px; text-align: center; }
|
|
516
|
+
.oluso-journey__status { color: var(--oluso-muted); text-align: center; padding: 16px 0; }
|
|
517
|
+
.oluso-journey__done { text-align: center; padding: 8px 0; }
|
|
518
|
+
`;
|
|
519
|
+
var injected = false;
|
|
520
|
+
function injectJourneyStyles() {
|
|
521
|
+
if (injected || typeof document === "undefined") return;
|
|
522
|
+
const style = document.createElement("style");
|
|
523
|
+
style.setAttribute("data-oluso-journey", "");
|
|
524
|
+
style.textContent = JOURNEY_STYLES;
|
|
525
|
+
document.head.appendChild(style);
|
|
526
|
+
injected = true;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// src/journey/webauthn.ts
|
|
530
|
+
function base64UrlToBuffer(value) {
|
|
531
|
+
const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
532
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
533
|
+
const binary = atob(padded);
|
|
534
|
+
const bytes = new Uint8Array(binary.length);
|
|
535
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
536
|
+
return bytes.buffer;
|
|
537
|
+
}
|
|
538
|
+
function bufferToBase64Url(buffer) {
|
|
539
|
+
if (!buffer) return null;
|
|
540
|
+
const bytes = new Uint8Array(buffer);
|
|
541
|
+
let binary = "";
|
|
542
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
543
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
544
|
+
}
|
|
545
|
+
function credentialDescriptors(list) {
|
|
546
|
+
if (!Array.isArray(list)) return void 0;
|
|
547
|
+
return list.map((c) => ({
|
|
548
|
+
type: c.type ?? "public-key",
|
|
549
|
+
id: base64UrlToBuffer(String(c.id)),
|
|
550
|
+
transports: c.transports
|
|
551
|
+
}));
|
|
552
|
+
}
|
|
553
|
+
function requestOptions(publicKey) {
|
|
554
|
+
return {
|
|
555
|
+
...publicKey,
|
|
556
|
+
challenge: base64UrlToBuffer(String(publicKey.challenge)),
|
|
557
|
+
allowCredentials: credentialDescriptors(publicKey.allowCredentials)
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
function creationOptions(publicKey) {
|
|
561
|
+
const user = publicKey.user ?? {};
|
|
562
|
+
return {
|
|
563
|
+
...publicKey,
|
|
564
|
+
challenge: base64UrlToBuffer(String(publicKey.challenge)),
|
|
565
|
+
user: { ...user, id: base64UrlToBuffer(String(user.id)) },
|
|
566
|
+
excludeCredentials: credentialDescriptors(publicKey.excludeCredentials)
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
async function getAssertion(publicKey) {
|
|
570
|
+
const credential = await navigator.credentials.get({ publicKey: requestOptions(publicKey) });
|
|
571
|
+
if (!credential) throw new Error("No passkey was chosen");
|
|
572
|
+
const response = credential.response;
|
|
573
|
+
return JSON.stringify({
|
|
574
|
+
id: credential.id,
|
|
575
|
+
rawId: bufferToBase64Url(credential.rawId),
|
|
576
|
+
type: credential.type,
|
|
577
|
+
response: {
|
|
578
|
+
clientDataJSON: bufferToBase64Url(response.clientDataJSON),
|
|
579
|
+
authenticatorData: bufferToBase64Url(response.authenticatorData),
|
|
580
|
+
signature: bufferToBase64Url(response.signature),
|
|
581
|
+
userHandle: bufferToBase64Url(response.userHandle)
|
|
582
|
+
},
|
|
583
|
+
authenticatorAttachment: credential.authenticatorAttachment ?? null
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
async function createCredential(publicKey) {
|
|
587
|
+
const credential = await navigator.credentials.create({ publicKey: creationOptions(publicKey) });
|
|
588
|
+
if (!credential) throw new Error("No passkey was created");
|
|
589
|
+
const response = credential.response;
|
|
590
|
+
return JSON.stringify({
|
|
591
|
+
id: credential.id,
|
|
592
|
+
rawId: bufferToBase64Url(credential.rawId),
|
|
593
|
+
type: credential.type,
|
|
594
|
+
response: {
|
|
595
|
+
clientDataJSON: bufferToBase64Url(response.clientDataJSON),
|
|
596
|
+
attestationObject: bufferToBase64Url(response.attestationObject),
|
|
597
|
+
transports: typeof response.getTransports === "function" ? response.getTransports() : void 0
|
|
598
|
+
},
|
|
599
|
+
authenticatorAttachment: credential.authenticatorAttachment ?? null
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// src/journey/render.ts
|
|
604
|
+
var DEFAULT_LABELS = {
|
|
605
|
+
loading: "Loading\u2026",
|
|
606
|
+
completed: "All done. Thank you.",
|
|
607
|
+
failed: "Something went wrong. Please try again.",
|
|
608
|
+
expired: "This session has expired. Please start again.",
|
|
609
|
+
restart: "Start again",
|
|
610
|
+
takePhoto: "Take a photo",
|
|
611
|
+
copy: "Copy",
|
|
612
|
+
copied: "Copied",
|
|
613
|
+
passkey: "Use your passkey",
|
|
614
|
+
retry: "Try again",
|
|
615
|
+
waiting: "Waiting\u2026",
|
|
616
|
+
unavailable: "This step can\u2019t be shown here."
|
|
617
|
+
};
|
|
618
|
+
function el(tag, className, text) {
|
|
619
|
+
const node = document.createElement(tag);
|
|
620
|
+
if (className) node.className = className;
|
|
621
|
+
if (text !== void 0) node.textContent = text;
|
|
622
|
+
return node;
|
|
623
|
+
}
|
|
624
|
+
var uid = 0;
|
|
625
|
+
function mountJourney(container, options) {
|
|
626
|
+
const labels = { ...DEFAULT_LABELS, ...options.labels };
|
|
627
|
+
const client = options.client;
|
|
628
|
+
const id = `oluso-j${++uid}`;
|
|
629
|
+
let session;
|
|
630
|
+
let destroyed = false;
|
|
631
|
+
let timers = [];
|
|
632
|
+
if (!options.unstyled) injectJourneyStyles();
|
|
633
|
+
const root = el("div", "oluso-journey");
|
|
634
|
+
root.setAttribute("data-theme", options.theme ?? "auto");
|
|
635
|
+
container.appendChild(root);
|
|
636
|
+
const clearTimers = () => {
|
|
637
|
+
timers.forEach((t) => window.clearInterval(t));
|
|
638
|
+
timers = [];
|
|
639
|
+
};
|
|
640
|
+
const status = (text) => {
|
|
641
|
+
root.replaceChildren(el("p", "oluso-journey__status", text));
|
|
642
|
+
};
|
|
643
|
+
const fail = (error) => {
|
|
644
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
645
|
+
options.onError?.(err, session);
|
|
646
|
+
return err;
|
|
647
|
+
};
|
|
648
|
+
const canRestart = () => Boolean(options.slug || options.start);
|
|
649
|
+
const begin = async () => {
|
|
650
|
+
clearTimers();
|
|
651
|
+
status(labels.loading);
|
|
652
|
+
try {
|
|
653
|
+
const started = options.start ? await options.start() : options.slug ? await client.startJourney(options.slug, options.data) : options.session;
|
|
654
|
+
if (!started) throw new Error("mountJourney needs a slug, a session or a start function");
|
|
655
|
+
await show(started);
|
|
656
|
+
} catch (error) {
|
|
657
|
+
showProblem(fail(error).message);
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
const show = async (next) => {
|
|
661
|
+
if (destroyed) return;
|
|
662
|
+
clearTimers();
|
|
663
|
+
session = next;
|
|
664
|
+
options.onStep?.(next);
|
|
665
|
+
if (next.completed) {
|
|
666
|
+
if (next.redirectUrl) {
|
|
667
|
+
await options.onComplete?.(next);
|
|
668
|
+
redirect(next.redirectUrl, next);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
if (options.showCompletion !== false) {
|
|
672
|
+
const done = el("div", "oluso-journey__done");
|
|
673
|
+
done.appendChild(el("p", "oluso-journey__content", next.message || labels.completed));
|
|
674
|
+
root.replaceChildren(done);
|
|
675
|
+
} else {
|
|
676
|
+
root.replaceChildren();
|
|
677
|
+
}
|
|
678
|
+
await options.onComplete?.(next);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
if (next.status !== "in_progress") {
|
|
682
|
+
const message = next.status === "expired" ? labels.expired : next.errorDescription || labels.failed;
|
|
683
|
+
fail(new Error(message));
|
|
684
|
+
showProblem(message);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (next.redirectUrl) {
|
|
688
|
+
redirect(next.redirectUrl, next);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (next.step?.form) {
|
|
692
|
+
renderForm(next.step.form);
|
|
693
|
+
} else if (next.step?.html) {
|
|
694
|
+
renderHtml(next.step.html);
|
|
695
|
+
} else {
|
|
696
|
+
showProblem(labels.unavailable);
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
const redirect = (url, current) => {
|
|
700
|
+
if (options.onRedirect) options.onRedirect(url, current);
|
|
701
|
+
else window.location.assign(url);
|
|
702
|
+
};
|
|
703
|
+
const showProblem = (message) => {
|
|
704
|
+
const box = el("div", "oluso-journey__form");
|
|
705
|
+
box.appendChild(el("p", "oluso-journey__alert oluso-journey__alert--error", message));
|
|
706
|
+
if (canRestart()) {
|
|
707
|
+
const again = el("button", "oluso-journey__button oluso-journey__button--primary", labels.restart);
|
|
708
|
+
again.type = "button";
|
|
709
|
+
again.addEventListener("click", () => void begin());
|
|
710
|
+
const row = el("div", "oluso-journey__actions");
|
|
711
|
+
row.appendChild(again);
|
|
712
|
+
box.appendChild(row);
|
|
713
|
+
}
|
|
714
|
+
root.replaceChildren(box);
|
|
715
|
+
};
|
|
716
|
+
const post = async (form, action) => {
|
|
717
|
+
if (!session) return;
|
|
718
|
+
const next = await client.submitStepForm(session.sessionId, form, action);
|
|
719
|
+
await show(next);
|
|
720
|
+
};
|
|
721
|
+
const submitting = (form, busy) => {
|
|
722
|
+
form.setAttribute("aria-busy", String(busy));
|
|
723
|
+
form.querySelectorAll("button").forEach((b) => b.disabled = busy);
|
|
724
|
+
};
|
|
725
|
+
const send = async (form, action, extra) => {
|
|
726
|
+
const data = action?.postsFields === false ? new FormData() : new FormData(form);
|
|
727
|
+
if (action?.name) data.append(action.name, action.value ?? "");
|
|
728
|
+
if (action?.values) for (const [k, v] of Object.entries(action.values)) data.append(k, v);
|
|
729
|
+
if (extra) for (const [k, v] of Object.entries(extra)) data.set(k, v);
|
|
730
|
+
submitting(form, true);
|
|
731
|
+
try {
|
|
732
|
+
await post(data);
|
|
733
|
+
} catch (error) {
|
|
734
|
+
submitting(form, false);
|
|
735
|
+
const message = fail(error).message;
|
|
736
|
+
let alert = form.querySelector("[data-oluso-request-error]");
|
|
737
|
+
if (!alert) {
|
|
738
|
+
alert = el("p", "oluso-journey__alert oluso-journey__alert--error");
|
|
739
|
+
alert.setAttribute("data-oluso-request-error", "");
|
|
740
|
+
alert.setAttribute("role", "alert");
|
|
741
|
+
form.prepend(alert);
|
|
742
|
+
}
|
|
743
|
+
alert.textContent = message;
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
const renderForm = (step) => {
|
|
747
|
+
const form = el("form", "oluso-journey__form");
|
|
748
|
+
if (step.title) form.appendChild(el("h2", "oluso-journey__title", step.title));
|
|
749
|
+
if (step.description) form.appendChild(el("p", "oluso-journey__description", step.description));
|
|
750
|
+
if (step.error) {
|
|
751
|
+
const alert = el("p", "oluso-journey__alert oluso-journey__alert--error", step.error);
|
|
752
|
+
alert.setAttribute("role", "alert");
|
|
753
|
+
form.appendChild(alert);
|
|
754
|
+
}
|
|
755
|
+
if (step.notice) {
|
|
756
|
+
const notice = el("p", "oluso-journey__alert oluso-journey__alert--notice", step.notice);
|
|
757
|
+
notice.setAttribute("role", "status");
|
|
758
|
+
form.appendChild(notice);
|
|
759
|
+
}
|
|
760
|
+
const wrappers = [];
|
|
761
|
+
for (const field of step.fields) {
|
|
762
|
+
const rendered = renderField(field);
|
|
763
|
+
if (!rendered) continue;
|
|
764
|
+
form.appendChild(rendered);
|
|
765
|
+
if (field.showWhen || field.showWhenAll?.length) wrappers.push({ field, wrapper: rendered });
|
|
766
|
+
}
|
|
767
|
+
if (step.widget) {
|
|
768
|
+
const widget = renderWidget(step.widget, form);
|
|
769
|
+
if (widget) form.appendChild(widget);
|
|
770
|
+
}
|
|
771
|
+
if (step.links.length > 0) {
|
|
772
|
+
const links = el("div", "oluso-journey__links");
|
|
773
|
+
for (const link of step.links) {
|
|
774
|
+
const a = el("a", void 0, link.label);
|
|
775
|
+
a.href = link.url;
|
|
776
|
+
a.target = "_blank";
|
|
777
|
+
a.rel = "noopener noreferrer";
|
|
778
|
+
links.appendChild(a);
|
|
779
|
+
}
|
|
780
|
+
form.appendChild(links);
|
|
781
|
+
}
|
|
782
|
+
if (step.actions.length > 0) {
|
|
783
|
+
const row = el("div", "oluso-journey__actions");
|
|
784
|
+
const primary = step.actions.findIndex((a) => a.kind === "primary");
|
|
785
|
+
if (primary >= 0) {
|
|
786
|
+
const enter = el("button", "oluso-journey__enter");
|
|
787
|
+
enter.type = "submit";
|
|
788
|
+
enter.tabIndex = -1;
|
|
789
|
+
enter.setAttribute("aria-hidden", "true");
|
|
790
|
+
enter.dataset.olusoAction = String(primary);
|
|
791
|
+
form.prepend(enter);
|
|
792
|
+
}
|
|
793
|
+
const described = [];
|
|
794
|
+
step.actions.forEach((action, index) => {
|
|
795
|
+
const button = el("button", `oluso-journey__button oluso-journey__button--${action.kind}`, action.label);
|
|
796
|
+
button.type = "submit";
|
|
797
|
+
button.formNoValidate = action.skipValidation || action.postsFields === false;
|
|
798
|
+
button.dataset.olusoAction = String(index);
|
|
799
|
+
if (action.description) {
|
|
800
|
+
const line = el("p", "oluso-journey__action-line");
|
|
801
|
+
line.appendChild(el("span", "oluso-journey__help", action.description));
|
|
802
|
+
line.appendChild(button);
|
|
803
|
+
described.push(line);
|
|
804
|
+
} else {
|
|
805
|
+
row.appendChild(button);
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
if (row.childElementCount > 0) form.appendChild(row);
|
|
809
|
+
described.forEach((line) => form.appendChild(line));
|
|
810
|
+
}
|
|
811
|
+
const applyConditions = () => {
|
|
812
|
+
for (const { field, wrapper } of wrappers) {
|
|
813
|
+
const conditions = [...field.showWhen ? [field.showWhen] : [], ...field.showWhenAll ?? []];
|
|
814
|
+
const shown = conditions.every((c) => conditionMet(c, currentValues(form.elements.namedItem(c.field))));
|
|
815
|
+
wrapper.hidden = !shown;
|
|
816
|
+
wrapper.querySelectorAll("input, select, textarea").forEach((input) => {
|
|
817
|
+
input.disabled = !shown || input.dataset.olusoReadonly === "true";
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
form.addEventListener("input", (event) => {
|
|
822
|
+
applyConditions();
|
|
823
|
+
const target = event.target;
|
|
824
|
+
const wrapper = target.closest(".oluso-journey__field");
|
|
825
|
+
wrapper?.querySelectorAll('[aria-invalid="true"]').forEach((n) => n.removeAttribute("aria-invalid"));
|
|
826
|
+
wrapper?.querySelector(".oluso-journey__error")?.remove();
|
|
827
|
+
});
|
|
828
|
+
form.addEventListener("change", applyConditions);
|
|
829
|
+
applyConditions();
|
|
830
|
+
form.addEventListener("submit", (event) => {
|
|
831
|
+
event.preventDefault();
|
|
832
|
+
const submitter = event.submitter;
|
|
833
|
+
const index = submitter?.dataset.olusoAction;
|
|
834
|
+
const action = index !== void 0 ? step.actions[Number(index)] : step.actions.find((a) => a.kind === "primary");
|
|
835
|
+
if (action?.confirm && submitter && submitter.dataset.olusoConfirming !== "true") {
|
|
836
|
+
submitter.dataset.olusoConfirming = "true";
|
|
837
|
+
submitter.textContent = action.confirm;
|
|
838
|
+
submitter.setAttribute("aria-live", "polite");
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
void send(form, action);
|
|
842
|
+
});
|
|
843
|
+
root.replaceChildren(form);
|
|
844
|
+
const focus = form.querySelector("[autofocus]") ?? form.querySelector("input:not([type=hidden]):not([disabled]), select, textarea");
|
|
845
|
+
focus?.focus({ preventScroll: true });
|
|
846
|
+
};
|
|
847
|
+
const renderField = (field) => {
|
|
848
|
+
const fieldId = `${id}-${field.name}`;
|
|
849
|
+
if (field.type === "hidden") {
|
|
850
|
+
const input = el("input");
|
|
851
|
+
input.type = "hidden";
|
|
852
|
+
input.name = field.name;
|
|
853
|
+
input.value = field.value ?? "";
|
|
854
|
+
return input;
|
|
855
|
+
}
|
|
856
|
+
const wrapper = el("div", "oluso-journey__field");
|
|
857
|
+
if (field.group) wrapper.dataset.group = field.group;
|
|
858
|
+
if (field.type === "content") {
|
|
859
|
+
if (field.label) wrapper.appendChild(el("p", "oluso-journey__label", field.label));
|
|
860
|
+
wrapper.appendChild(el("p", "oluso-journey__content", field.value ?? ""));
|
|
861
|
+
return wrapper;
|
|
862
|
+
}
|
|
863
|
+
const label = () => {
|
|
864
|
+
if (!field.label) return;
|
|
865
|
+
const l = el("label", "oluso-journey__label", field.label);
|
|
866
|
+
l.htmlFor = fieldId;
|
|
867
|
+
if (field.required) {
|
|
868
|
+
const marker = el("span", "oluso-journey__required", "*");
|
|
869
|
+
marker.setAttribute("aria-hidden", "true");
|
|
870
|
+
l.appendChild(marker);
|
|
871
|
+
}
|
|
872
|
+
wrapper.appendChild(l);
|
|
873
|
+
};
|
|
874
|
+
const describedBy = [];
|
|
875
|
+
const common = (input) => {
|
|
876
|
+
input.id = fieldId;
|
|
877
|
+
input.name = field.name;
|
|
878
|
+
input.required = field.required;
|
|
879
|
+
if (field.readOnly) {
|
|
880
|
+
if (input instanceof HTMLSelectElement) input.disabled = true;
|
|
881
|
+
else input.readOnly = true;
|
|
882
|
+
input.dataset.olusoReadonly = String(input instanceof HTMLSelectElement);
|
|
883
|
+
}
|
|
884
|
+
if (field.autofocus) input.autofocus = true;
|
|
885
|
+
if (field.autocomplete) input.setAttribute("autocomplete", field.autocomplete);
|
|
886
|
+
if (field.error) input.setAttribute("aria-invalid", "true");
|
|
887
|
+
};
|
|
888
|
+
switch (field.type) {
|
|
889
|
+
case "textarea": {
|
|
890
|
+
label();
|
|
891
|
+
const input = el("textarea", "oluso-journey__textarea");
|
|
892
|
+
common(input);
|
|
893
|
+
input.value = field.value ?? "";
|
|
894
|
+
if (field.placeholder) input.placeholder = field.placeholder;
|
|
895
|
+
if (field.rows) input.rows = field.rows;
|
|
896
|
+
if (field.minLength) input.minLength = field.minLength;
|
|
897
|
+
if (field.maxLength) input.maxLength = field.maxLength;
|
|
898
|
+
wrapper.appendChild(input);
|
|
899
|
+
break;
|
|
900
|
+
}
|
|
901
|
+
case "select": {
|
|
902
|
+
label();
|
|
903
|
+
const input = el("select", "oluso-journey__select");
|
|
904
|
+
common(input);
|
|
905
|
+
const blank = el("option", void 0, field.placeholder ?? "");
|
|
906
|
+
blank.value = "";
|
|
907
|
+
input.appendChild(blank);
|
|
908
|
+
for (const option of field.options ?? []) {
|
|
909
|
+
const o = el("option", void 0, option.label);
|
|
910
|
+
o.value = option.value;
|
|
911
|
+
o.disabled = option.disabled;
|
|
912
|
+
o.selected = option.value === field.value;
|
|
913
|
+
input.appendChild(o);
|
|
914
|
+
}
|
|
915
|
+
wrapper.appendChild(input);
|
|
916
|
+
break;
|
|
917
|
+
}
|
|
918
|
+
case "radio": {
|
|
919
|
+
const group = el("fieldset", "oluso-journey__group");
|
|
920
|
+
if (field.label) {
|
|
921
|
+
const legend = el("legend", "oluso-journey__label", field.label);
|
|
922
|
+
if (field.required) legend.appendChild(el("span", "oluso-journey__required", "*"));
|
|
923
|
+
group.appendChild(legend);
|
|
924
|
+
}
|
|
925
|
+
const choices = el("div", "oluso-journey__choices");
|
|
926
|
+
(field.options ?? []).forEach((option, i) => {
|
|
927
|
+
const choice = el("label", "oluso-journey__choice");
|
|
928
|
+
const input = el("input");
|
|
929
|
+
input.type = "radio";
|
|
930
|
+
input.name = field.name;
|
|
931
|
+
input.value = option.value;
|
|
932
|
+
input.id = `${fieldId}-${i}`;
|
|
933
|
+
input.required = field.required;
|
|
934
|
+
input.disabled = option.disabled || field.readOnly;
|
|
935
|
+
input.checked = option.value === field.value;
|
|
936
|
+
const text = el("span", void 0, option.label);
|
|
937
|
+
if (option.description) text.appendChild(el("span", "oluso-journey__choice-description", option.description));
|
|
938
|
+
choice.append(input, text);
|
|
939
|
+
choices.appendChild(choice);
|
|
940
|
+
});
|
|
941
|
+
group.appendChild(choices);
|
|
942
|
+
wrapper.appendChild(group);
|
|
943
|
+
break;
|
|
944
|
+
}
|
|
945
|
+
case "checkbox": {
|
|
946
|
+
if (field.placeholder) label();
|
|
947
|
+
const choice = el("label", "oluso-journey__choice");
|
|
948
|
+
const input = el("input");
|
|
949
|
+
input.type = "checkbox";
|
|
950
|
+
input.value = "true";
|
|
951
|
+
common(input);
|
|
952
|
+
input.checked = field.value === "true";
|
|
953
|
+
if (field.readOnly) {
|
|
954
|
+
input.setAttribute("aria-readonly", "true");
|
|
955
|
+
input.addEventListener("click", (e) => e.preventDefault());
|
|
956
|
+
}
|
|
957
|
+
const text = el("span", void 0, field.placeholder ?? field.label ?? "");
|
|
958
|
+
if (!field.placeholder && field.required) text.appendChild(el("span", "oluso-journey__required", "*"));
|
|
959
|
+
choice.append(input, text);
|
|
960
|
+
wrapper.appendChild(choice);
|
|
961
|
+
break;
|
|
962
|
+
}
|
|
963
|
+
case "file": {
|
|
964
|
+
label();
|
|
965
|
+
const input = el("input", "oluso-journey__input");
|
|
966
|
+
input.type = "file";
|
|
967
|
+
common(input);
|
|
968
|
+
input.readOnly = false;
|
|
969
|
+
input.disabled = field.readOnly;
|
|
970
|
+
if (field.accept) input.accept = field.accept;
|
|
971
|
+
input.multiple = (field.maxFiles ?? 1) > 1;
|
|
972
|
+
input.required = field.required && !field.capture;
|
|
973
|
+
wrapper.appendChild(input);
|
|
974
|
+
if (field.capture && !field.readOnly) {
|
|
975
|
+
const cameraLabel = el("label", "oluso-journey__hint", labels.takePhoto);
|
|
976
|
+
cameraLabel.htmlFor = `${fieldId}-camera`;
|
|
977
|
+
const camera = el("input", "oluso-journey__input");
|
|
978
|
+
camera.type = "file";
|
|
979
|
+
camera.id = `${fieldId}-camera`;
|
|
980
|
+
camera.name = field.name;
|
|
981
|
+
camera.accept = "image/*";
|
|
982
|
+
camera.setAttribute("capture", field.capture);
|
|
983
|
+
wrapper.append(cameraLabel, camera);
|
|
984
|
+
}
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
987
|
+
case "tel": {
|
|
988
|
+
label();
|
|
989
|
+
if (field.countries && field.countries.length > 0) {
|
|
990
|
+
const row = el("div", "oluso-journey__phone");
|
|
991
|
+
const country = el("select", "oluso-journey__select");
|
|
992
|
+
country.name = `${field.name}__country`;
|
|
993
|
+
country.setAttribute("aria-label", field.label ?? field.name);
|
|
994
|
+
country.disabled = field.readOnly;
|
|
995
|
+
for (const c of field.countries) {
|
|
996
|
+
const o = el("option", void 0, `${c.flag ? c.flag + " " : ""}${c.name} (${c.dialCode})`);
|
|
997
|
+
o.value = c.code;
|
|
998
|
+
o.selected = c.code === field.country;
|
|
999
|
+
country.appendChild(o);
|
|
1000
|
+
}
|
|
1001
|
+
const input = el("input", "oluso-journey__input");
|
|
1002
|
+
input.type = "tel";
|
|
1003
|
+
input.inputMode = "tel";
|
|
1004
|
+
common(input);
|
|
1005
|
+
input.value = field.value ?? "";
|
|
1006
|
+
if (field.placeholder) input.placeholder = field.placeholder;
|
|
1007
|
+
row.append(country, input);
|
|
1008
|
+
wrapper.appendChild(row);
|
|
1009
|
+
} else {
|
|
1010
|
+
const input = textInput(field, "tel");
|
|
1011
|
+
common(input);
|
|
1012
|
+
wrapper.appendChild(input);
|
|
1013
|
+
}
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
1016
|
+
case "otp": {
|
|
1017
|
+
label();
|
|
1018
|
+
const input = el("input", "oluso-journey__input oluso-journey__input--otp");
|
|
1019
|
+
input.type = "text";
|
|
1020
|
+
input.inputMode = "numeric";
|
|
1021
|
+
common(input);
|
|
1022
|
+
input.setAttribute("autocomplete", field.autocomplete ?? "one-time-code");
|
|
1023
|
+
if (field.maxLength) {
|
|
1024
|
+
input.maxLength = field.maxLength;
|
|
1025
|
+
input.minLength = field.minLength ?? field.maxLength;
|
|
1026
|
+
}
|
|
1027
|
+
input.pattern = field.pattern ?? "[0-9]*";
|
|
1028
|
+
input.value = field.value ?? "";
|
|
1029
|
+
wrapper.appendChild(input);
|
|
1030
|
+
break;
|
|
1031
|
+
}
|
|
1032
|
+
default: {
|
|
1033
|
+
label();
|
|
1034
|
+
const known = ["text", "email", "password", "number", "date", "url", "tel"];
|
|
1035
|
+
const input = textInput(field, known.includes(field.type) ? field.type : "text");
|
|
1036
|
+
common(input);
|
|
1037
|
+
wrapper.appendChild(input);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
if (field.description) {
|
|
1041
|
+
const help = el("p", "oluso-journey__help", field.description);
|
|
1042
|
+
help.id = `${fieldId}-help`;
|
|
1043
|
+
describedBy.push(help.id);
|
|
1044
|
+
wrapper.appendChild(help);
|
|
1045
|
+
}
|
|
1046
|
+
if (field.hint) {
|
|
1047
|
+
const hint = el("p", "oluso-journey__hint", field.hint);
|
|
1048
|
+
hint.id = `${fieldId}-hint`;
|
|
1049
|
+
describedBy.push(hint.id);
|
|
1050
|
+
wrapper.appendChild(hint);
|
|
1051
|
+
}
|
|
1052
|
+
if (field.error) {
|
|
1053
|
+
const error = el("p", "oluso-journey__error", field.error);
|
|
1054
|
+
error.id = `${fieldId}-error`;
|
|
1055
|
+
describedBy.push(error.id);
|
|
1056
|
+
wrapper.appendChild(error);
|
|
1057
|
+
}
|
|
1058
|
+
if (describedBy.length > 0) {
|
|
1059
|
+
Array.from(wrapper.querySelectorAll("[id]")).find((n) => n.id === fieldId)?.setAttribute("aria-describedby", describedBy.join(" "));
|
|
1060
|
+
}
|
|
1061
|
+
return wrapper;
|
|
1062
|
+
};
|
|
1063
|
+
const textInput = (field, type) => {
|
|
1064
|
+
const input = el("input", "oluso-journey__input");
|
|
1065
|
+
input.type = type;
|
|
1066
|
+
input.value = field.value ?? "";
|
|
1067
|
+
if (field.placeholder) input.placeholder = field.placeholder;
|
|
1068
|
+
if (field.pattern) input.pattern = field.pattern;
|
|
1069
|
+
if (field.patternError) {
|
|
1070
|
+
input.title = field.patternError;
|
|
1071
|
+
input.addEventListener("invalid", () => {
|
|
1072
|
+
if (input.validity.patternMismatch) input.setCustomValidity(field.patternError);
|
|
1073
|
+
});
|
|
1074
|
+
input.addEventListener("input", () => input.setCustomValidity(""));
|
|
1075
|
+
}
|
|
1076
|
+
if (field.minLength) input.minLength = field.minLength;
|
|
1077
|
+
if (field.maxLength) input.maxLength = field.maxLength;
|
|
1078
|
+
if (field.min) input.min = field.min;
|
|
1079
|
+
if (field.max) input.max = field.max;
|
|
1080
|
+
if (type === "number") input.step = "any";
|
|
1081
|
+
return input;
|
|
1082
|
+
};
|
|
1083
|
+
const renderWidget = (widget, form) => {
|
|
1084
|
+
const data = widget.data ?? {};
|
|
1085
|
+
const box = el("div", "oluso-journey__widget");
|
|
1086
|
+
const result = (value) => {
|
|
1087
|
+
if (!widget.resultField) return;
|
|
1088
|
+
let input = Array.from(form.querySelectorAll("input[type=hidden]")).find((n) => n.name === widget.resultField) ?? null;
|
|
1089
|
+
if (!input) {
|
|
1090
|
+
input = el("input");
|
|
1091
|
+
input.type = "hidden";
|
|
1092
|
+
input.name = widget.resultField;
|
|
1093
|
+
form.appendChild(input);
|
|
1094
|
+
}
|
|
1095
|
+
input.value = value;
|
|
1096
|
+
};
|
|
1097
|
+
switch (widget.type) {
|
|
1098
|
+
case "webauthn_get":
|
|
1099
|
+
case "webauthn_create": {
|
|
1100
|
+
const run = el("button", "oluso-journey__button oluso-journey__button--primary", widget.submit?.label ?? labels.passkey);
|
|
1101
|
+
run.type = "button";
|
|
1102
|
+
const go = async () => {
|
|
1103
|
+
run.disabled = true;
|
|
1104
|
+
try {
|
|
1105
|
+
const options2 = await webAuthnOptions(data);
|
|
1106
|
+
const publicKey = options2.publicKey;
|
|
1107
|
+
let extra = options2.extra;
|
|
1108
|
+
const credential = widget.type === "webauthn_get" ? await getAssertion(publicKey) : await createCredential(publicKey);
|
|
1109
|
+
result(credential);
|
|
1110
|
+
const naming = data.credentialName;
|
|
1111
|
+
if (naming?.field) {
|
|
1112
|
+
const attachment = JSON.parse(credential).authenticatorAttachment;
|
|
1113
|
+
const name = attachment === "platform" ? naming.platform : attachment === "cross-platform" ? naming.crossPlatform : naming.other;
|
|
1114
|
+
(extra ?? (extra = {}))[naming.field] = name ?? "";
|
|
1115
|
+
}
|
|
1116
|
+
await send(form, widget.submit, extra);
|
|
1117
|
+
} catch (error) {
|
|
1118
|
+
run.disabled = false;
|
|
1119
|
+
run.textContent = labels.retry;
|
|
1120
|
+
options.onError?.(error instanceof Error ? error : new Error(String(error)), session);
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
run.addEventListener("click", () => void go());
|
|
1124
|
+
box.appendChild(run);
|
|
1125
|
+
if (typeof PublicKeyCredential !== "undefined") queueMicrotask(() => void go());
|
|
1126
|
+
return box;
|
|
1127
|
+
}
|
|
1128
|
+
case "verification_connect": {
|
|
1129
|
+
const options2 = data.options ?? {};
|
|
1130
|
+
const reference = typeof data.referenceOption === "string" ? options2[data.referenceOption] : void 0;
|
|
1131
|
+
const field = typeof data.referenceField === "string" ? data.referenceField : "reference_id";
|
|
1132
|
+
const callback = async (withReference) => {
|
|
1133
|
+
const values = new FormData();
|
|
1134
|
+
if (withReference) values.set(field, reference == null ? "" : String(reference));
|
|
1135
|
+
submitting(form, true);
|
|
1136
|
+
try {
|
|
1137
|
+
await post(values, "callback");
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
submitting(form, false);
|
|
1140
|
+
fail(error);
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
const start = async () => {
|
|
1144
|
+
await loadScript(String(data.scriptUrl));
|
|
1145
|
+
const Ctor = window[typeof data.global === "string" ? data.global : "Connect"];
|
|
1146
|
+
if (!Ctor) throw new Error("The verification widget could not be loaded");
|
|
1147
|
+
const connect = new Ctor({
|
|
1148
|
+
...options2,
|
|
1149
|
+
onSuccess: () => void callback(true),
|
|
1150
|
+
onError: () => void callback(false),
|
|
1151
|
+
onClose: () => void 0
|
|
1152
|
+
});
|
|
1153
|
+
connect.setup?.();
|
|
1154
|
+
connect.open();
|
|
1155
|
+
return connect;
|
|
1156
|
+
};
|
|
1157
|
+
let instance;
|
|
1158
|
+
const open = () => {
|
|
1159
|
+
if (instance) {
|
|
1160
|
+
void instance.then((c) => c.open());
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
instance = start();
|
|
1164
|
+
instance.catch((error) => {
|
|
1165
|
+
instance = void 0;
|
|
1166
|
+
fail(error);
|
|
1167
|
+
});
|
|
1168
|
+
};
|
|
1169
|
+
if (widget.submit) {
|
|
1170
|
+
const button = el("button", `oluso-journey__button oluso-journey__button--${widget.submit.kind}`, widget.submit.label);
|
|
1171
|
+
button.type = "button";
|
|
1172
|
+
button.addEventListener("click", open);
|
|
1173
|
+
box.appendChild(button);
|
|
1174
|
+
}
|
|
1175
|
+
open();
|
|
1176
|
+
return box;
|
|
1177
|
+
}
|
|
1178
|
+
case "captcha": {
|
|
1179
|
+
const target = el("div", "oluso-journey__captcha");
|
|
1180
|
+
box.appendChild(target);
|
|
1181
|
+
void renderCaptcha(target, data, (token) => {
|
|
1182
|
+
result(token);
|
|
1183
|
+
if (widget.submit) void send(form, widget.submit);
|
|
1184
|
+
}).catch((error) => options.onError?.(error instanceof Error ? error : new Error(String(error)), session));
|
|
1185
|
+
return box;
|
|
1186
|
+
}
|
|
1187
|
+
case "qr": {
|
|
1188
|
+
const qr = el("div", "oluso-journey__qr");
|
|
1189
|
+
if (typeof data.image === "string") {
|
|
1190
|
+
const img = el("img");
|
|
1191
|
+
img.src = data.image;
|
|
1192
|
+
img.alt = "";
|
|
1193
|
+
qr.appendChild(img);
|
|
1194
|
+
}
|
|
1195
|
+
if (typeof data.secret === "string") {
|
|
1196
|
+
if (typeof data.secretLabel === "string") qr.appendChild(el("span", "oluso-journey__help", data.secretLabel));
|
|
1197
|
+
qr.appendChild(el("code", "oluso-journey__code", data.secret));
|
|
1198
|
+
} else if (typeof data.value === "string" && typeof data.image !== "string") qr.appendChild(el("code", "oluso-journey__code", data.value));
|
|
1199
|
+
box.appendChild(qr);
|
|
1200
|
+
return box;
|
|
1201
|
+
}
|
|
1202
|
+
case "codes": {
|
|
1203
|
+
const codes = Array.isArray(data.codes) ? data.codes.map(String) : [];
|
|
1204
|
+
const list = el("ul", "oluso-journey__codes");
|
|
1205
|
+
codes.forEach((c) => list.appendChild(el("li", void 0, c)));
|
|
1206
|
+
const copy = el("button", "oluso-journey__button", labels.copy);
|
|
1207
|
+
copy.type = "button";
|
|
1208
|
+
copy.addEventListener("click", () => {
|
|
1209
|
+
void navigator.clipboard?.writeText(codes.join("\n")).then(() => copy.textContent = labels.copied);
|
|
1210
|
+
});
|
|
1211
|
+
box.append(list, copy);
|
|
1212
|
+
return box;
|
|
1213
|
+
}
|
|
1214
|
+
case "poll": {
|
|
1215
|
+
const seconds = Math.max(2, Number(data.intervalSeconds) || 5);
|
|
1216
|
+
const stopAt = Number(data.maxSeconds) > 0 ? Date.now() + Number(data.maxSeconds) * 1e3 : Infinity;
|
|
1217
|
+
const rendered = session?.currentStep;
|
|
1218
|
+
timers.push(window.setInterval(() => {
|
|
1219
|
+
if (Date.now() > stopAt) {
|
|
1220
|
+
clearTimers();
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
if (!session || document.hidden) return;
|
|
1224
|
+
void client.getSession(session.sessionId).then(async (now) => {
|
|
1225
|
+
if (now.status !== "in_progress" || now.currentStep !== rendered) {
|
|
1226
|
+
clearTimers();
|
|
1227
|
+
await post(new FormData(), "init");
|
|
1228
|
+
}
|
|
1229
|
+
}).catch(() => void 0);
|
|
1230
|
+
}, seconds * 1e3));
|
|
1231
|
+
return null;
|
|
1232
|
+
}
|
|
1233
|
+
default:
|
|
1234
|
+
return null;
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
const webAuthnOptions = async (data) => {
|
|
1238
|
+
if (data.publicKey) return { publicKey: data.publicKey, extra: void 0 };
|
|
1239
|
+
const url = String(data.optionsUrl);
|
|
1240
|
+
const absolute = /^https?:/i.test(url) ? url : `${client.serverUrl}${url.startsWith("/") ? "" : "/"}${url}`;
|
|
1241
|
+
const method = String(data.optionsMethod ?? "POST").toUpperCase();
|
|
1242
|
+
const response = await fetch(absolute, {
|
|
1243
|
+
method,
|
|
1244
|
+
headers: { "Content-Type": "application/json" },
|
|
1245
|
+
body: method === "GET" ? void 0 : JSON.stringify(data.optionsBody ?? {}),
|
|
1246
|
+
credentials: "include"
|
|
1247
|
+
});
|
|
1248
|
+
if (!response.ok) throw new Error(labels.failed);
|
|
1249
|
+
const json = await response.json();
|
|
1250
|
+
const extra = {};
|
|
1251
|
+
if (typeof data.idProperty === "string" && typeof data.idField === "string" && json[data.idProperty] != null) {
|
|
1252
|
+
extra[data.idField] = String(json[data.idProperty]);
|
|
1253
|
+
}
|
|
1254
|
+
return { publicKey: json.options ?? json, extra };
|
|
1255
|
+
};
|
|
1256
|
+
const renderHtml = (html) => {
|
|
1257
|
+
const wrapper = el("div", "oluso-journey__html");
|
|
1258
|
+
wrapper.innerHTML = html;
|
|
1259
|
+
wrapper.querySelectorAll("script").forEach((s) => s.remove());
|
|
1260
|
+
wrapper.addEventListener("submit", (event) => {
|
|
1261
|
+
const form = event.target;
|
|
1262
|
+
event.preventDefault();
|
|
1263
|
+
const submitter = event.submitter;
|
|
1264
|
+
const data = new FormData(form);
|
|
1265
|
+
if (submitter?.name) data.append(submitter.name, submitter.value);
|
|
1266
|
+
data.delete("__RequestVerificationToken");
|
|
1267
|
+
submitting(form, true);
|
|
1268
|
+
post(data).catch((error) => {
|
|
1269
|
+
submitting(form, false);
|
|
1270
|
+
fail(error);
|
|
1271
|
+
});
|
|
1272
|
+
});
|
|
1273
|
+
root.replaceChildren(wrapper);
|
|
1274
|
+
};
|
|
1275
|
+
void (options.session ? show(options.session) : begin());
|
|
1276
|
+
return {
|
|
1277
|
+
get session() {
|
|
1278
|
+
return session;
|
|
1279
|
+
},
|
|
1280
|
+
restart: begin,
|
|
1281
|
+
destroy() {
|
|
1282
|
+
destroyed = true;
|
|
1283
|
+
clearTimers();
|
|
1284
|
+
root.remove();
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
function currentValues(item) {
|
|
1289
|
+
if (!item) return [];
|
|
1290
|
+
const inputs = item instanceof RadioNodeList ? Array.from(item) : [item];
|
|
1291
|
+
return inputs.flatMap((node) => {
|
|
1292
|
+
const input = node;
|
|
1293
|
+
if (input.disabled) return [];
|
|
1294
|
+
if (input.type === "checkbox" || input.type === "radio") return input.checked ? [input.value] : [];
|
|
1295
|
+
if (input instanceof HTMLSelectElement && input.multiple) return Array.from(input.selectedOptions).map((o) => o.value);
|
|
1296
|
+
return [input.value];
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
var loadedScripts = /* @__PURE__ */ new Map();
|
|
1300
|
+
function loadScript(src) {
|
|
1301
|
+
let loading = loadedScripts.get(src);
|
|
1302
|
+
if (!loading) {
|
|
1303
|
+
loading = new Promise((resolve, reject) => {
|
|
1304
|
+
const tag = document.createElement("script");
|
|
1305
|
+
tag.src = src;
|
|
1306
|
+
tag.async = true;
|
|
1307
|
+
tag.onload = () => resolve();
|
|
1308
|
+
tag.onerror = () => {
|
|
1309
|
+
loadedScripts.delete(src);
|
|
1310
|
+
reject(new Error("A script the step needs could not be loaded"));
|
|
1311
|
+
};
|
|
1312
|
+
document.head.appendChild(tag);
|
|
1313
|
+
});
|
|
1314
|
+
loadedScripts.set(src, loading);
|
|
1315
|
+
}
|
|
1316
|
+
return loading;
|
|
1317
|
+
}
|
|
1318
|
+
var CAPTCHA_SCRIPTS = {
|
|
1319
|
+
recaptcha: { src: "https://www.google.com/recaptcha/api.js?render=explicit&onload=", global: "grecaptcha" },
|
|
1320
|
+
hcaptcha: { src: "https://js.hcaptcha.com/1/api.js?render=explicit&onload=", global: "hcaptcha" },
|
|
1321
|
+
turnstile: { src: "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=", global: "turnstile" }
|
|
1322
|
+
};
|
|
1323
|
+
async function renderCaptcha(target, data, done) {
|
|
1324
|
+
const provider = String(data.provider ?? "recaptcha").toLowerCase();
|
|
1325
|
+
const script = CAPTCHA_SCRIPTS[provider];
|
|
1326
|
+
if (!script) throw new Error(`Captcha provider '${provider}' isn't supported`);
|
|
1327
|
+
const w = window;
|
|
1328
|
+
if (!w[script.global]) {
|
|
1329
|
+
const callback = `__olusoCaptcha${Date.now()}`;
|
|
1330
|
+
await new Promise((resolve, reject) => {
|
|
1331
|
+
w[callback] = () => resolve();
|
|
1332
|
+
const tag = document.createElement("script");
|
|
1333
|
+
tag.src = script.src + callback + (data.language ? `&hl=${encodeURIComponent(String(data.language))}` : "");
|
|
1334
|
+
tag.async = true;
|
|
1335
|
+
tag.onerror = () => reject(new Error("The captcha could not be loaded"));
|
|
1336
|
+
document.head.appendChild(tag);
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
const api = w[script.global];
|
|
1340
|
+
const siteKey = String(data.siteKey ?? "");
|
|
1341
|
+
if (provider === "recaptcha" && data.version === "v3" && api.execute) {
|
|
1342
|
+
api.ready?.(() => void api.execute(siteKey, { action: String(data.action ?? "submit") }).then(done));
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
api.render(target, {
|
|
1346
|
+
sitekey: siteKey,
|
|
1347
|
+
theme: data.theme,
|
|
1348
|
+
size: data.size,
|
|
1349
|
+
action: data.action,
|
|
1350
|
+
callback: (token) => done(token)
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// src/react.tsx
|
|
1355
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1356
|
+
var OlusoContext = (0, import_react.createContext)(null);
|
|
1357
|
+
function OlusoProvider({ config, children }) {
|
|
1358
|
+
const client = (0, import_react.useMemo)(() => new OlusoClient(config), [config]);
|
|
1359
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OlusoContext.Provider, { value: { client }, children });
|
|
1360
|
+
}
|
|
1361
|
+
function useOluso() {
|
|
1362
|
+
const context = (0, import_react.useContext)(OlusoContext);
|
|
1363
|
+
if (!context) {
|
|
1364
|
+
throw new Error("useOluso must be used within an OlusoProvider");
|
|
1365
|
+
}
|
|
1366
|
+
return context.client;
|
|
1367
|
+
}
|
|
1368
|
+
function useJourney(options) {
|
|
1369
|
+
const client = useOluso();
|
|
1370
|
+
const [journey, setJourney] = (0, import_react.useState)(null);
|
|
1371
|
+
const [isLoading, setIsLoading] = (0, import_react.useState)(true);
|
|
1372
|
+
const [isSubmitting, setIsSubmitting] = (0, import_react.useState)(false);
|
|
1373
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
1374
|
+
const [values, setValues] = (0, import_react.useState)(options.defaultValues || {});
|
|
1375
|
+
const [fieldErrors, setFieldErrors] = (0, import_react.useState)({});
|
|
1376
|
+
(0, import_react.useEffect)(() => {
|
|
1377
|
+
let cancelled = false;
|
|
1378
|
+
async function fetchJourney() {
|
|
1379
|
+
try {
|
|
1380
|
+
setIsLoading(true);
|
|
1381
|
+
setError(null);
|
|
1382
|
+
const data = await client.getJourney(options.slug);
|
|
1383
|
+
if (!cancelled) {
|
|
1384
|
+
setJourney(data);
|
|
1385
|
+
}
|
|
1386
|
+
} catch (err) {
|
|
1387
|
+
if (!cancelled) {
|
|
1388
|
+
setError(err instanceof OlusoError ? err : new OlusoError(String(err), 0));
|
|
1389
|
+
}
|
|
1390
|
+
} finally {
|
|
1391
|
+
if (!cancelled) {
|
|
1392
|
+
setIsLoading(false);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
fetchJourney();
|
|
1397
|
+
return () => {
|
|
1398
|
+
cancelled = true;
|
|
1399
|
+
};
|
|
1400
|
+
}, [client, options.slug]);
|
|
1401
|
+
const setValue = (0, import_react.useCallback)((field, value) => {
|
|
1402
|
+
setValues((prev) => ({ ...prev, [field]: value }));
|
|
1403
|
+
setFieldErrors((prev) => {
|
|
1404
|
+
if (prev[field]) {
|
|
1405
|
+
const { [field]: _, ...rest } = prev;
|
|
1406
|
+
return rest;
|
|
1407
|
+
}
|
|
1408
|
+
return prev;
|
|
1409
|
+
});
|
|
1410
|
+
}, []);
|
|
1411
|
+
const submit = (0, import_react.useCallback)(async () => {
|
|
1412
|
+
if (!journey) return null;
|
|
1413
|
+
try {
|
|
1414
|
+
setIsSubmitting(true);
|
|
1415
|
+
setError(null);
|
|
1416
|
+
setFieldErrors({});
|
|
1417
|
+
const result = await client.submit(options.slug, values);
|
|
1418
|
+
if (result.success) {
|
|
1419
|
+
options.onSuccess?.(result);
|
|
1420
|
+
}
|
|
1421
|
+
return result;
|
|
1422
|
+
} catch (err) {
|
|
1423
|
+
const olusError = err instanceof OlusoError ? err : new OlusoError(String(err), 0);
|
|
1424
|
+
setError(olusError);
|
|
1425
|
+
if (olusError.fieldErrors) {
|
|
1426
|
+
setFieldErrors(olusError.fieldErrors);
|
|
1427
|
+
}
|
|
1428
|
+
options.onError?.(olusError);
|
|
1429
|
+
return null;
|
|
1430
|
+
} finally {
|
|
1431
|
+
setIsSubmitting(false);
|
|
1432
|
+
}
|
|
1433
|
+
}, [client, journey, options, values]);
|
|
1434
|
+
const reset = (0, import_react.useCallback)(() => {
|
|
1435
|
+
setValues(options.defaultValues || {});
|
|
1436
|
+
setFieldErrors({});
|
|
1437
|
+
setError(null);
|
|
1438
|
+
}, [options.defaultValues]);
|
|
1439
|
+
return {
|
|
1440
|
+
journey,
|
|
1441
|
+
isLoading,
|
|
1442
|
+
isSubmitting,
|
|
1443
|
+
error,
|
|
1444
|
+
values,
|
|
1445
|
+
fieldErrors,
|
|
1446
|
+
setValue,
|
|
1447
|
+
submit,
|
|
1448
|
+
reset
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
function JourneyForm({
|
|
1452
|
+
slug,
|
|
1453
|
+
onSuccess,
|
|
1454
|
+
onError,
|
|
1455
|
+
className,
|
|
1456
|
+
submitText = "Submit",
|
|
1457
|
+
loadingText = "Loading...",
|
|
1458
|
+
renderField,
|
|
1459
|
+
renderSubmit,
|
|
1460
|
+
showSuccessMessage = true,
|
|
1461
|
+
renderSuccess
|
|
1462
|
+
}) {
|
|
1463
|
+
const {
|
|
1464
|
+
journey,
|
|
1465
|
+
isLoading,
|
|
1466
|
+
isSubmitting,
|
|
1467
|
+
error,
|
|
1468
|
+
values,
|
|
1469
|
+
fieldErrors,
|
|
1470
|
+
setValue,
|
|
1471
|
+
submit
|
|
1472
|
+
} = useJourney({ slug, onSuccess, onError });
|
|
1473
|
+
const [submitted, setSubmitted] = (0, import_react.useState)(null);
|
|
1474
|
+
const handleSubmit = (0, import_react.useCallback)(async (e) => {
|
|
1475
|
+
e.preventDefault();
|
|
1476
|
+
const result = await submit();
|
|
1477
|
+
if (result?.success && showSuccessMessage) {
|
|
1478
|
+
setSubmitted(result);
|
|
1479
|
+
}
|
|
1480
|
+
}, [submit, showSuccessMessage]);
|
|
1481
|
+
if (isLoading) {
|
|
1482
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, children: loadingText });
|
|
1483
|
+
}
|
|
1484
|
+
if (!journey) {
|
|
1485
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, children: "Journey not found" });
|
|
1486
|
+
}
|
|
1487
|
+
if (submitted) {
|
|
1488
|
+
if (renderSuccess) {
|
|
1489
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: renderSuccess(submitted) });
|
|
1490
|
+
}
|
|
1491
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: submitted.message || "Thank you for your submission!" }) });
|
|
1492
|
+
}
|
|
1493
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: handleSubmit, className, children: [
|
|
1494
|
+
journey.fields.map((field) => {
|
|
1495
|
+
const fieldProps = {
|
|
1496
|
+
value: values[field.name],
|
|
1497
|
+
onChange: (value) => setValue(field.name, value),
|
|
1498
|
+
error: fieldErrors[field.name]
|
|
1499
|
+
};
|
|
1500
|
+
if (renderField) {
|
|
1501
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.default.Fragment, { children: renderField(field, fieldProps) }, field.name);
|
|
1502
|
+
}
|
|
1503
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultField, { field, ...fieldProps }, field.name);
|
|
1504
|
+
}),
|
|
1505
|
+
error && !error.fieldErrors && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { color: "red", marginBottom: "1rem" }, children: error.message }),
|
|
1506
|
+
renderSubmit ? renderSubmit({ isSubmitting, onClick: () => submit() }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", disabled: isSubmitting, children: isSubmitting ? "Submitting..." : submitText })
|
|
1507
|
+
] });
|
|
1508
|
+
}
|
|
1509
|
+
function DefaultField({ field, value, onChange, error }) {
|
|
1510
|
+
const id = `oluso-field-${field.name}`;
|
|
1511
|
+
const inputProps = {
|
|
1512
|
+
id,
|
|
1513
|
+
name: field.name,
|
|
1514
|
+
required: field.required,
|
|
1515
|
+
placeholder: field.placeholder,
|
|
1516
|
+
"aria-describedby": field.description ? `${id}-desc` : void 0,
|
|
1517
|
+
"aria-invalid": !!error
|
|
1518
|
+
};
|
|
1519
|
+
let input;
|
|
1520
|
+
switch (field.type) {
|
|
1521
|
+
case "textarea":
|
|
1522
|
+
input = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1523
|
+
"textarea",
|
|
1524
|
+
{
|
|
1525
|
+
...inputProps,
|
|
1526
|
+
value: value || "",
|
|
1527
|
+
onChange: (e) => onChange(e.target.value),
|
|
1528
|
+
rows: 4
|
|
1529
|
+
}
|
|
1530
|
+
);
|
|
1531
|
+
break;
|
|
1532
|
+
case "select":
|
|
1533
|
+
input = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1534
|
+
"select",
|
|
1535
|
+
{
|
|
1536
|
+
...inputProps,
|
|
1537
|
+
value: value || "",
|
|
1538
|
+
onChange: (e) => onChange(e.target.value),
|
|
1539
|
+
children: [
|
|
1540
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "Select..." }),
|
|
1541
|
+
field.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: opt.value, children: opt.label }, opt.value))
|
|
1542
|
+
]
|
|
1543
|
+
}
|
|
1544
|
+
);
|
|
1545
|
+
break;
|
|
1546
|
+
case "checkbox":
|
|
1547
|
+
input = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1548
|
+
"input",
|
|
1549
|
+
{
|
|
1550
|
+
...inputProps,
|
|
1551
|
+
type: "checkbox",
|
|
1552
|
+
checked: !!value,
|
|
1553
|
+
onChange: (e) => onChange(e.target.checked)
|
|
1554
|
+
}
|
|
1555
|
+
);
|
|
1556
|
+
break;
|
|
1557
|
+
case "radio":
|
|
1558
|
+
input = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "radiogroup", children: field.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
|
|
1559
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1560
|
+
"input",
|
|
1561
|
+
{
|
|
1562
|
+
type: "radio",
|
|
1563
|
+
name: field.name,
|
|
1564
|
+
value: opt.value,
|
|
1565
|
+
checked: value === opt.value,
|
|
1566
|
+
onChange: (e) => onChange(e.target.value)
|
|
1567
|
+
}
|
|
1568
|
+
),
|
|
1569
|
+
opt.label
|
|
1570
|
+
] }, opt.value)) });
|
|
1571
|
+
break;
|
|
1572
|
+
default:
|
|
1573
|
+
input = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1574
|
+
"input",
|
|
1575
|
+
{
|
|
1576
|
+
...inputProps,
|
|
1577
|
+
type: field.type,
|
|
1578
|
+
value: value || "",
|
|
1579
|
+
onChange: (e) => onChange(e.target.value),
|
|
1580
|
+
minLength: field.validation?.minLength,
|
|
1581
|
+
maxLength: field.validation?.maxLength,
|
|
1582
|
+
pattern: field.validation?.pattern,
|
|
1583
|
+
min: field.validation?.min,
|
|
1584
|
+
max: field.validation?.max
|
|
1585
|
+
}
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1588
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: "1rem" }, children: [
|
|
1589
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { htmlFor: id, style: { display: "block", marginBottom: "0.25rem" }, children: [
|
|
1590
|
+
field.label,
|
|
1591
|
+
field.required && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "red" }, children: " *" })
|
|
1592
|
+
] }),
|
|
1593
|
+
input,
|
|
1594
|
+
field.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { id: `${id}-desc`, style: { display: "block", color: "#666" }, children: field.description }),
|
|
1595
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { style: { display: "block", color: "red" }, children: error })
|
|
1596
|
+
] });
|
|
1597
|
+
}
|
|
1598
|
+
function Journey({ className, style, ...options }) {
|
|
1599
|
+
const client = useOluso();
|
|
1600
|
+
const host = (0, import_react.useRef)(null);
|
|
1601
|
+
const latest = (0, import_react.useRef)(options);
|
|
1602
|
+
latest.current = options;
|
|
1603
|
+
(0, import_react.useEffect)(() => {
|
|
1604
|
+
if (!host.current) return;
|
|
1605
|
+
const mount = mountJourney(host.current, {
|
|
1606
|
+
client,
|
|
1607
|
+
slug: options.slug,
|
|
1608
|
+
data: options.data,
|
|
1609
|
+
session: options.session,
|
|
1610
|
+
start: options.start,
|
|
1611
|
+
showCompletion: options.showCompletion,
|
|
1612
|
+
theme: options.theme,
|
|
1613
|
+
unstyled: options.unstyled,
|
|
1614
|
+
labels: options.labels,
|
|
1615
|
+
onStep: (s) => latest.current.onStep?.(s),
|
|
1616
|
+
onComplete: (s) => latest.current.onComplete?.(s),
|
|
1617
|
+
onError: (e, s) => latest.current.onError?.(e, s),
|
|
1618
|
+
onRedirect: latest.current.onRedirect ? (url, s) => latest.current.onRedirect?.(url, s) : void 0
|
|
1619
|
+
});
|
|
1620
|
+
return () => mount.destroy();
|
|
1621
|
+
}, [client, options.slug, options.session?.sessionId]);
|
|
1622
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: host, className, style });
|
|
1623
|
+
}
|
|
1624
|
+
function JourneyEmbed({
|
|
1625
|
+
slug,
|
|
1626
|
+
width = "100%",
|
|
1627
|
+
height = 500,
|
|
1628
|
+
theme,
|
|
1629
|
+
hideHeader,
|
|
1630
|
+
className,
|
|
1631
|
+
style,
|
|
1632
|
+
onComplete
|
|
1633
|
+
}) {
|
|
1634
|
+
const client = useOluso();
|
|
1635
|
+
const src = client.getEmbedUrl(slug, { theme, hideHeader });
|
|
1636
|
+
(0, import_react.useEffect)(() => {
|
|
1637
|
+
if (!onComplete) return;
|
|
1638
|
+
const handleMessage = (event) => {
|
|
1639
|
+
if (!client.getJourneyUrl(slug).startsWith(event.origin)) {
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
if (event.data?.type === "oluso:complete") {
|
|
1643
|
+
onComplete(event.data.result);
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
window.addEventListener("message", handleMessage);
|
|
1647
|
+
return () => window.removeEventListener("message", handleMessage);
|
|
1648
|
+
}, [client, slug, onComplete]);
|
|
1649
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1650
|
+
"iframe",
|
|
1651
|
+
{
|
|
1652
|
+
src,
|
|
1653
|
+
width,
|
|
1654
|
+
height,
|
|
1655
|
+
className,
|
|
1656
|
+
style: {
|
|
1657
|
+
border: "none",
|
|
1658
|
+
...style
|
|
1659
|
+
},
|
|
1660
|
+
title: `Oluso Journey: ${slug}`,
|
|
1661
|
+
allow: "clipboard-write; camera; microphone"
|
|
1662
|
+
}
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1666
|
+
0 && (module.exports = {
|
|
1667
|
+
Journey,
|
|
1668
|
+
JourneyEmbed,
|
|
1669
|
+
JourneyForm,
|
|
1670
|
+
OlusoProvider,
|
|
1671
|
+
useJourney,
|
|
1672
|
+
useOluso
|
|
1673
|
+
});
|
|
1674
|
+
//# sourceMappingURL=react.js.map
|