@rehers/rehers-roleplay-sdk 2.2.0 → 2.4.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 +9 -3
- package/index.d.ts +6 -4
- package/package.json +1 -1
- package/roleplay-sdk.js +233 -145
package/README.md
CHANGED
|
@@ -47,7 +47,10 @@ Or load via CDN:
|
|
|
47
47
|
onError: function(data) { console.error('Error:', data.code, data.message); },
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
// 2b. Or mount into a container (full-page embed)
|
|
50
|
+
// 2b. Or mount the full app into a container (full-page embed)
|
|
51
|
+
SeamlessRoleplay.mount(document.getElementById('roleplay-container'));
|
|
52
|
+
|
|
53
|
+
// 2c. Or mount a call for a specific contact into a container
|
|
51
54
|
SeamlessRoleplay.mount(document.getElementById('roleplay-container'), {
|
|
52
55
|
name: 'Jane Smith',
|
|
53
56
|
domain: 'acme.com',
|
|
@@ -110,9 +113,12 @@ Opens the roleplay in a modal dialog overlay.
|
|
|
110
113
|
| `onClose` | `function` | No | Called when dialog closes |
|
|
111
114
|
| `onError` | `function` | No | `({ code, message })` |
|
|
112
115
|
|
|
113
|
-
### `SeamlessRoleplay.mount(container, data)`
|
|
116
|
+
### `SeamlessRoleplay.mount(container, data?)`
|
|
117
|
+
|
|
118
|
+
Mounts into a DOM element. Two modes:
|
|
114
119
|
|
|
115
|
-
|
|
120
|
+
- **`mount(container)`** — embeds the full Roleplay app (dashboard, scenarios, call logs)
|
|
121
|
+
- **`mount(container, data)`** — embeds a roleplay call for a specific contact (same `data` options as `open()`)
|
|
116
122
|
|
|
117
123
|
### `SeamlessRoleplay.addToScenario(options)`
|
|
118
124
|
|
package/index.d.ts
CHANGED
|
@@ -78,13 +78,15 @@ export interface SeamlessRoleplaySDK {
|
|
|
78
78
|
init(options: SeamlessRoleplayInitOptions): void;
|
|
79
79
|
/** Open the roleplay modal for a contact (dialog mode). */
|
|
80
80
|
open(data: SeamlessRoleplayOpenData): void;
|
|
81
|
-
/** Mount the
|
|
82
|
-
mount(container: HTMLElement, data
|
|
81
|
+
/** Mount the full Roleplay app into a container (no data), or a call embed for a specific contact (with data). */
|
|
82
|
+
mount(container: HTMLElement, data?: SeamlessRoleplayOpenData): void;
|
|
83
83
|
/** Open the add-to-scenario dialog for bulk contact import. */
|
|
84
84
|
addToScenario(options: AddToScenarioOptions): void;
|
|
85
|
-
/** Close the
|
|
85
|
+
/** Close the active dialog. Does not affect mount. */
|
|
86
86
|
close(): void;
|
|
87
|
-
/**
|
|
87
|
+
/** Unmount the mounted embed. Does not affect dialogs. */
|
|
88
|
+
unmount(): void;
|
|
89
|
+
/** Destroy the SDK — clears state, timers, and DOM (both mount and dialogs). */
|
|
88
90
|
destroy(): void;
|
|
89
91
|
}
|
|
90
92
|
|
package/package.json
CHANGED
package/roleplay-sdk.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
var SESSION_TIMEOUT_MS = 15000;
|
|
16
16
|
var SDK_LOG_PREFIX = "[SeamlessRoleplay]";
|
|
17
17
|
|
|
18
|
-
// ──
|
|
18
|
+
// ── Auth state ────────────────────────────────────────────────────
|
|
19
19
|
var publishableKey = null;
|
|
20
20
|
var userId = null;
|
|
21
21
|
var userEmail = null;
|
|
@@ -30,18 +30,25 @@
|
|
|
30
30
|
var fetchingSession = null; // single-flight Promise
|
|
31
31
|
|
|
32
32
|
var initCallbacks = { onReady: null, onError: null };
|
|
33
|
-
var
|
|
34
|
-
var addToScenarioCallbacks = { onComplete: null, onClose: null, onError: null };
|
|
35
|
-
var addToScenarioPendingContacts = null;
|
|
33
|
+
var initCalled = false;
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
var
|
|
35
|
+
// ── Mount state (persistent embed — survives dialog open/close) ───
|
|
36
|
+
var mountIframe = null;
|
|
39
37
|
var mountContainer = null;
|
|
40
|
-
var
|
|
41
|
-
var
|
|
42
|
-
var
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
var mountContactData = null;
|
|
39
|
+
var mountCallbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
|
|
40
|
+
var mountListener = null;
|
|
41
|
+
|
|
42
|
+
// ── Dialog state (overlay — dialog or add-to-scenario) ────────────
|
|
43
|
+
var dialogOverlay = null;
|
|
44
|
+
var dialogIframe = null;
|
|
45
|
+
var dialogMode = null; // "dialog" | "add-to-scenario"
|
|
46
|
+
var dialogContactData = null;
|
|
47
|
+
var dialogCallbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
|
|
48
|
+
var dialogAddToScenarioCallbacks = { onComplete: null, onClose: null, onError: null };
|
|
49
|
+
var dialogAddToScenarioPendingContacts = null;
|
|
50
|
+
var dialogListener = null;
|
|
51
|
+
var dialogCloseTeardownTimer = null;
|
|
45
52
|
|
|
46
53
|
// ── Safe logging ──────────────────────────────────────────────────
|
|
47
54
|
|
|
@@ -65,17 +72,17 @@
|
|
|
65
72
|
return DEFAULT_API_ORIGIN;
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
function
|
|
75
|
+
function sendMsg(iframeEl, msg) {
|
|
69
76
|
try {
|
|
70
|
-
if (
|
|
71
|
-
|
|
77
|
+
if (iframeEl && iframeEl.contentWindow) {
|
|
78
|
+
iframeEl.contentWindow.postMessage(msg, getOrigin());
|
|
72
79
|
}
|
|
73
80
|
} catch (e) {
|
|
74
|
-
logError("
|
|
81
|
+
logError("sendMsg", e);
|
|
75
82
|
}
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
// ── Session management
|
|
85
|
+
// ── Session management ────────────────────────────────────────────
|
|
79
86
|
|
|
80
87
|
function fetchSession() {
|
|
81
88
|
if (fetchingSession) return fetchingSession;
|
|
@@ -167,79 +174,90 @@
|
|
|
167
174
|
}, delay);
|
|
168
175
|
}
|
|
169
176
|
|
|
170
|
-
// ── Teardown
|
|
177
|
+
// ── Teardown (dialog only — mount is independent) ─────────────────
|
|
171
178
|
|
|
172
|
-
function
|
|
179
|
+
function teardownDialog() {
|
|
173
180
|
try {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
closeTeardownTimer = null;
|
|
181
|
+
if (dialogCloseTeardownTimer) {
|
|
182
|
+
clearTimeout(dialogCloseTeardownTimer);
|
|
183
|
+
dialogCloseTeardownTimer = null;
|
|
178
184
|
}
|
|
179
185
|
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
overlay.parentNode.removeChild(overlay);
|
|
183
|
-
}
|
|
184
|
-
overlay = null;
|
|
185
|
-
} else if (mode === "mount") {
|
|
186
|
-
if (iframe && iframe.parentNode) {
|
|
187
|
-
iframe.parentNode.removeChild(iframe);
|
|
188
|
-
}
|
|
189
|
-
mountContainer = null;
|
|
186
|
+
if (dialogOverlay && dialogOverlay.parentNode) {
|
|
187
|
+
dialogOverlay.parentNode.removeChild(dialogOverlay);
|
|
190
188
|
}
|
|
189
|
+
dialogOverlay = null;
|
|
190
|
+
|
|
191
|
+
if (dialogListener) {
|
|
192
|
+
window.removeEventListener("message", dialogListener);
|
|
193
|
+
dialogListener = null;
|
|
194
|
+
}
|
|
195
|
+
} catch (e) {
|
|
196
|
+
logError("teardownDialog", e);
|
|
197
|
+
}
|
|
191
198
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
199
|
+
dialogIframe = null;
|
|
200
|
+
dialogContactData = null;
|
|
201
|
+
dialogAddToScenarioPendingContacts = null;
|
|
202
|
+
dialogMode = null;
|
|
203
|
+
dialogCallbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
|
|
204
|
+
dialogAddToScenarioCallbacks = { onComplete: null, onClose: null, onError: null };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function teardownMount() {
|
|
208
|
+
try {
|
|
209
|
+
if (mountIframe && mountIframe.parentNode) {
|
|
210
|
+
mountIframe.parentNode.removeChild(mountIframe);
|
|
211
|
+
}
|
|
212
|
+
if (mountListener) {
|
|
213
|
+
window.removeEventListener("message", mountListener);
|
|
214
|
+
mountListener = null;
|
|
195
215
|
}
|
|
196
216
|
} catch (e) {
|
|
197
|
-
logError("
|
|
217
|
+
logError("teardownMount", e);
|
|
198
218
|
}
|
|
199
219
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
mode = null;
|
|
205
|
-
callbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
|
|
206
|
-
addToScenarioCallbacks = { onComplete: null, onClose: null, onError: null };
|
|
220
|
+
mountIframe = null;
|
|
221
|
+
mountContainer = null;
|
|
222
|
+
mountContactData = null;
|
|
223
|
+
mountCallbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
|
|
207
224
|
}
|
|
208
225
|
|
|
209
|
-
// ── Message
|
|
226
|
+
// ── Message dispatch ──────────────────────────────────────────────
|
|
210
227
|
|
|
211
|
-
function
|
|
212
|
-
if (
|
|
228
|
+
function dispatchInitToTarget(targetIframe, contactData, atsContacts) {
|
|
229
|
+
if (atsContacts) {
|
|
213
230
|
var msg = {
|
|
214
231
|
type: "seamless-add-to-scenario-init",
|
|
215
|
-
sessionToken:
|
|
232
|
+
sessionToken: sessionToken,
|
|
216
233
|
publishableKey: publishableKey,
|
|
217
234
|
userId: userId,
|
|
218
|
-
contacts:
|
|
235
|
+
contacts: atsContacts,
|
|
219
236
|
};
|
|
220
237
|
if (paymentLink) msg.paymentLink = paymentLink;
|
|
221
|
-
|
|
222
|
-
} else if (
|
|
223
|
-
|
|
238
|
+
sendMsg(targetIframe, msg);
|
|
239
|
+
} else if (sessionToken) {
|
|
240
|
+
var msg = {
|
|
224
241
|
type: "seamless-session-init",
|
|
225
|
-
sessionToken:
|
|
226
|
-
contact: {
|
|
227
|
-
name:
|
|
228
|
-
domain:
|
|
229
|
-
company:
|
|
230
|
-
title:
|
|
231
|
-
companyDescription:
|
|
232
|
-
liUrl:
|
|
233
|
-
},
|
|
234
|
-
}
|
|
242
|
+
sessionToken: sessionToken,
|
|
243
|
+
contact: contactData ? {
|
|
244
|
+
name: contactData.name,
|
|
245
|
+
domain: contactData.domain,
|
|
246
|
+
company: contactData.company,
|
|
247
|
+
title: contactData.title,
|
|
248
|
+
companyDescription: contactData.companyDescription || undefined,
|
|
249
|
+
liUrl: contactData.liUrl || undefined,
|
|
250
|
+
} : null,
|
|
251
|
+
};
|
|
252
|
+
sendMsg(targetIframe, msg);
|
|
235
253
|
} else if (paymentLink) {
|
|
236
|
-
|
|
254
|
+
sendMsg(targetIframe, {
|
|
237
255
|
type: "seamless-session-init",
|
|
238
256
|
paymentLink: paymentLink,
|
|
239
257
|
contact: null,
|
|
240
258
|
});
|
|
241
259
|
} else {
|
|
242
|
-
|
|
260
|
+
sendMsg(targetIframe, {
|
|
243
261
|
type: "seamless-session-init",
|
|
244
262
|
paymentLink: null,
|
|
245
263
|
contact: null,
|
|
@@ -247,53 +265,104 @@
|
|
|
247
265
|
}
|
|
248
266
|
}
|
|
249
267
|
|
|
250
|
-
|
|
268
|
+
// ── Mount message handler ─────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
function handleMountMessage(event) {
|
|
271
|
+
try {
|
|
272
|
+
if (event.origin !== getOrigin()) return;
|
|
273
|
+
if (!mountIframe || !event.source || event.source !== mountIframe.contentWindow) return;
|
|
274
|
+
|
|
275
|
+
var data = event.data;
|
|
276
|
+
if (!data || typeof data.type !== "string") return;
|
|
277
|
+
|
|
278
|
+
switch (data.type) {
|
|
279
|
+
case "ROLEPLAY_READY":
|
|
280
|
+
getSessionToken()
|
|
281
|
+
.then(function (token) {
|
|
282
|
+
dispatchInitToTarget(mountIframe, mountContactData, null);
|
|
283
|
+
})
|
|
284
|
+
.catch(function () {
|
|
285
|
+
dispatchInitToTarget(mountIframe, mountContactData, null);
|
|
286
|
+
});
|
|
287
|
+
break;
|
|
288
|
+
|
|
289
|
+
case "ROLEPLAY_CALL_STARTED":
|
|
290
|
+
if (mountCallbacks.onCallStarted) {
|
|
291
|
+
mountCallbacks.onCallStarted({ callId: data.callId });
|
|
292
|
+
}
|
|
293
|
+
break;
|
|
294
|
+
|
|
295
|
+
case "ROLEPLAY_CALL_ENDED":
|
|
296
|
+
if (mountCallbacks.onCallEnded) {
|
|
297
|
+
mountCallbacks.onCallEnded({ callId: data.callId, duration: data.duration });
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
|
|
301
|
+
case "ROLEPLAY_ERROR":
|
|
302
|
+
if (mountCallbacks.onError) {
|
|
303
|
+
mountCallbacks.onError({ code: data.code, message: data.message });
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
|
|
307
|
+
case "ROLEPLAY_CLOSED":
|
|
308
|
+
var onClose = mountCallbacks.onClose;
|
|
309
|
+
teardownMount();
|
|
310
|
+
if (onClose) onClose();
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
} catch (e) {
|
|
314
|
+
logError("handleMountMessage", e);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ── Dialog message handler ────────────────────────────────────────
|
|
319
|
+
|
|
320
|
+
function handleDialogMessage(event) {
|
|
251
321
|
try {
|
|
252
322
|
if (event.origin !== getOrigin()) return;
|
|
323
|
+
if (!dialogIframe || !event.source || event.source !== dialogIframe.contentWindow) return;
|
|
253
324
|
|
|
254
325
|
var data = event.data;
|
|
255
326
|
if (!data || typeof data.type !== "string") return;
|
|
256
327
|
|
|
257
328
|
switch (data.type) {
|
|
258
329
|
case "ROLEPLAY_READY":
|
|
259
|
-
// Ensure we have a fresh token before sending init to iframe
|
|
260
330
|
getSessionToken()
|
|
261
331
|
.then(function (token) {
|
|
262
|
-
|
|
332
|
+
dispatchInitToTarget(dialogIframe, dialogContactData, dialogAddToScenarioPendingContacts);
|
|
263
333
|
})
|
|
264
334
|
.catch(function () {
|
|
265
|
-
|
|
266
|
-
dispatchInitToIframe(sessionToken);
|
|
335
|
+
dispatchInitToTarget(dialogIframe, dialogContactData, dialogAddToScenarioPendingContacts);
|
|
267
336
|
});
|
|
268
337
|
break;
|
|
269
338
|
|
|
270
339
|
case "ROLEPLAY_CALL_STARTED":
|
|
271
|
-
if (
|
|
272
|
-
|
|
340
|
+
if (dialogCallbacks.onCallStarted) {
|
|
341
|
+
dialogCallbacks.onCallStarted({ callId: data.callId });
|
|
273
342
|
}
|
|
274
343
|
break;
|
|
275
344
|
|
|
276
345
|
case "ROLEPLAY_CALL_ENDED":
|
|
277
|
-
if (
|
|
278
|
-
|
|
346
|
+
if (dialogCallbacks.onCallEnded) {
|
|
347
|
+
dialogCallbacks.onCallEnded({ callId: data.callId, duration: data.duration });
|
|
279
348
|
}
|
|
280
349
|
break;
|
|
281
350
|
|
|
282
351
|
case "ROLEPLAY_ERROR":
|
|
283
|
-
if (
|
|
284
|
-
|
|
352
|
+
if (dialogCallbacks.onError) {
|
|
353
|
+
dialogCallbacks.onError({ code: data.code, message: data.message });
|
|
285
354
|
}
|
|
286
355
|
break;
|
|
287
356
|
|
|
288
357
|
case "ROLEPLAY_CLOSED":
|
|
289
|
-
var onClose =
|
|
290
|
-
|
|
358
|
+
var onClose = dialogCallbacks.onClose;
|
|
359
|
+
teardownDialog();
|
|
291
360
|
if (onClose) onClose();
|
|
292
361
|
break;
|
|
293
362
|
|
|
294
363
|
case "ADD_TO_SCENARIO_COMPLETE":
|
|
295
|
-
var atsOnComplete =
|
|
296
|
-
|
|
364
|
+
var atsOnComplete = dialogAddToScenarioCallbacks.onComplete;
|
|
365
|
+
teardownDialog();
|
|
297
366
|
if (atsOnComplete) {
|
|
298
367
|
atsOnComplete({
|
|
299
368
|
scenarioId: data.scenarioId,
|
|
@@ -305,40 +374,40 @@
|
|
|
305
374
|
break;
|
|
306
375
|
|
|
307
376
|
case "ADD_TO_SCENARIO_ERROR":
|
|
308
|
-
if (
|
|
309
|
-
|
|
377
|
+
if (dialogAddToScenarioCallbacks.onError) {
|
|
378
|
+
dialogAddToScenarioCallbacks.onError({ code: data.code, message: data.message });
|
|
310
379
|
}
|
|
311
380
|
break;
|
|
312
381
|
|
|
313
382
|
case "ADD_TO_SCENARIO_CLOSED":
|
|
314
|
-
var atsOnClose =
|
|
315
|
-
|
|
383
|
+
var atsOnClose = dialogAddToScenarioCallbacks.onClose;
|
|
384
|
+
teardownDialog();
|
|
316
385
|
if (atsOnClose) atsOnClose();
|
|
317
386
|
break;
|
|
318
387
|
}
|
|
319
388
|
} catch (e) {
|
|
320
|
-
logError("
|
|
389
|
+
logError("handleDialogMessage", e);
|
|
321
390
|
}
|
|
322
391
|
}
|
|
323
392
|
|
|
324
|
-
// ── Close
|
|
393
|
+
// ── Close (dialog only) ───────────────────────────────────────────
|
|
325
394
|
|
|
326
|
-
function
|
|
395
|
+
function closeDialog() {
|
|
327
396
|
try {
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
|
|
397
|
+
sendMsg(dialogIframe, { type: "roleplay-close" });
|
|
398
|
+
if (dialogCloseTeardownTimer) clearTimeout(dialogCloseTeardownTimer);
|
|
399
|
+
dialogCloseTeardownTimer = setTimeout(teardownDialog, 300);
|
|
331
400
|
} catch (e) {
|
|
332
401
|
logError("close", e);
|
|
333
|
-
|
|
402
|
+
teardownDialog();
|
|
334
403
|
}
|
|
335
404
|
}
|
|
336
405
|
|
|
337
|
-
// ── Create iframe
|
|
406
|
+
// ── Create iframe ─────────────────────────────────────────────────
|
|
338
407
|
|
|
339
|
-
function createIframe() {
|
|
408
|
+
function createIframe(path) {
|
|
340
409
|
var iframeEl = document.createElement("iframe");
|
|
341
|
-
iframeEl.src = getOrigin() + "/embed/roleplay-call";
|
|
410
|
+
iframeEl.src = getOrigin() + (path || "/embed/roleplay-call");
|
|
342
411
|
iframeEl.allow = "camera; microphone; display-capture; autoplay";
|
|
343
412
|
iframeEl.style.width = "100%";
|
|
344
413
|
iframeEl.style.height = "100%";
|
|
@@ -347,7 +416,7 @@
|
|
|
347
416
|
return iframeEl;
|
|
348
417
|
}
|
|
349
418
|
|
|
350
|
-
// ── SDK API
|
|
419
|
+
// ── SDK API ───────────────────────────────────────────────────────
|
|
351
420
|
|
|
352
421
|
var SeamlessRoleplay = {
|
|
353
422
|
/**
|
|
@@ -415,19 +484,19 @@
|
|
|
415
484
|
return;
|
|
416
485
|
}
|
|
417
486
|
|
|
418
|
-
//
|
|
419
|
-
if (
|
|
487
|
+
// Tear down any existing dialog (NOT the mount)
|
|
488
|
+
if (dialogOverlay || dialogIframe) teardownDialog();
|
|
420
489
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
490
|
+
dialogContactData = data;
|
|
491
|
+
dialogCallbacks.onCallStarted = data.onCallStarted || null;
|
|
492
|
+
dialogCallbacks.onCallEnded = data.onCallEnded || null;
|
|
493
|
+
dialogCallbacks.onClose = data.onClose || null;
|
|
494
|
+
dialogCallbacks.onError = data.onError || null;
|
|
495
|
+
dialogMode = "dialog";
|
|
427
496
|
|
|
428
497
|
// Listen for messages
|
|
429
|
-
|
|
430
|
-
window.addEventListener("message",
|
|
498
|
+
dialogListener = handleDialogMessage;
|
|
499
|
+
window.addEventListener("message", dialogListener);
|
|
431
500
|
|
|
432
501
|
// Build overlay
|
|
433
502
|
var el = document.createElement("div");
|
|
@@ -477,7 +546,7 @@
|
|
|
477
546
|
cbs.alignItems = "center";
|
|
478
547
|
cbs.justifyContent = "center";
|
|
479
548
|
cbs.lineHeight = "1";
|
|
480
|
-
closeBtn.addEventListener("click",
|
|
549
|
+
closeBtn.addEventListener("click", closeDialog);
|
|
481
550
|
|
|
482
551
|
var iframeEl = createIframe();
|
|
483
552
|
|
|
@@ -486,15 +555,15 @@
|
|
|
486
555
|
el.appendChild(container);
|
|
487
556
|
|
|
488
557
|
el.addEventListener("click", function (e) {
|
|
489
|
-
if (e.target === el)
|
|
558
|
+
if (e.target === el) closeDialog();
|
|
490
559
|
});
|
|
491
560
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
document.body.appendChild(
|
|
561
|
+
dialogOverlay = el;
|
|
562
|
+
dialogIframe = iframeEl;
|
|
563
|
+
document.body.appendChild(dialogOverlay);
|
|
495
564
|
} catch (e) {
|
|
496
565
|
logError("open", e);
|
|
497
|
-
|
|
566
|
+
teardownDialog();
|
|
498
567
|
if (data && typeof data.onError === "function") {
|
|
499
568
|
try { data.onError({ code: "SDK_ERROR", message: e.message || "Unexpected error during open" }); } catch (_) {}
|
|
500
569
|
}
|
|
@@ -502,7 +571,11 @@
|
|
|
502
571
|
},
|
|
503
572
|
|
|
504
573
|
/**
|
|
505
|
-
* Mount the roleplay into a container element
|
|
574
|
+
* Mount the roleplay into a container element.
|
|
575
|
+
*
|
|
576
|
+
* Two modes:
|
|
577
|
+
* mount(container) — full app embed (dashboard, scenarios, call logs, etc.)
|
|
578
|
+
* mount(container, contactData) — roleplay call embed for a specific contact
|
|
506
579
|
*/
|
|
507
580
|
mount: function (container, data) {
|
|
508
581
|
try {
|
|
@@ -514,32 +587,35 @@
|
|
|
514
587
|
logError("mount", "requires a DOM element as first argument");
|
|
515
588
|
return;
|
|
516
589
|
}
|
|
517
|
-
|
|
518
|
-
|
|
590
|
+
|
|
591
|
+
// If contact data is provided, validate required fields
|
|
592
|
+
var hasContactData = data && data.name && data.domain && data.company && data.title;
|
|
593
|
+
if (data && !hasContactData && (data.name || data.domain || data.company || data.title)) {
|
|
594
|
+
logError("mount", "contact data requires { name, domain, company, title }");
|
|
519
595
|
return;
|
|
520
596
|
}
|
|
521
597
|
|
|
522
|
-
//
|
|
523
|
-
if (
|
|
598
|
+
// Tear down any existing mount (re-mount)
|
|
599
|
+
if (mountIframe) teardownMount();
|
|
524
600
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
mode = "mount";
|
|
601
|
+
mountContactData = hasContactData ? data : null;
|
|
602
|
+
mountCallbacks.onCallStarted = (data && data.onCallStarted) || null;
|
|
603
|
+
mountCallbacks.onCallEnded = (data && data.onCallEnded) || null;
|
|
604
|
+
mountCallbacks.onClose = (data && data.onClose) || null;
|
|
605
|
+
mountCallbacks.onError = (data && data.onError) || null;
|
|
531
606
|
mountContainer = container;
|
|
532
607
|
|
|
533
608
|
// Listen for messages
|
|
534
|
-
|
|
535
|
-
window.addEventListener("message",
|
|
609
|
+
mountListener = handleMountMessage;
|
|
610
|
+
window.addEventListener("message", mountListener);
|
|
536
611
|
|
|
537
|
-
|
|
538
|
-
|
|
612
|
+
// No contact data → full app embed at root; with contact → /embed/roleplay-call
|
|
613
|
+
var iframeEl = createIframe(hasContactData ? "/embed/roleplay-call" : "/");
|
|
614
|
+
mountIframe = iframeEl;
|
|
539
615
|
container.appendChild(iframeEl);
|
|
540
616
|
} catch (e) {
|
|
541
617
|
logError("mount", e);
|
|
542
|
-
|
|
618
|
+
teardownMount();
|
|
543
619
|
if (data && typeof data.onError === "function") {
|
|
544
620
|
try { data.onError({ code: "SDK_ERROR", message: e.message || "Unexpected error during mount" }); } catch (_) {}
|
|
545
621
|
}
|
|
@@ -579,18 +655,18 @@
|
|
|
579
655
|
}
|
|
580
656
|
}
|
|
581
657
|
|
|
582
|
-
// Tear down any existing dialog (
|
|
583
|
-
if (
|
|
658
|
+
// Tear down any existing dialog (NOT the mount)
|
|
659
|
+
if (dialogOverlay || dialogIframe) teardownDialog();
|
|
584
660
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
661
|
+
dialogAddToScenarioPendingContacts = opts.contacts;
|
|
662
|
+
dialogAddToScenarioCallbacks.onComplete = opts.onComplete || null;
|
|
663
|
+
dialogAddToScenarioCallbacks.onClose = opts.onClose || null;
|
|
664
|
+
dialogAddToScenarioCallbacks.onError = opts.onError || null;
|
|
665
|
+
dialogMode = "add-to-scenario";
|
|
590
666
|
|
|
591
667
|
// Listen for messages
|
|
592
|
-
|
|
593
|
-
window.addEventListener("message",
|
|
668
|
+
dialogListener = handleDialogMessage;
|
|
669
|
+
window.addEventListener("message", dialogListener);
|
|
594
670
|
|
|
595
671
|
// Build overlay — wide, compact dialog
|
|
596
672
|
var el = document.createElement("div");
|
|
@@ -654,22 +730,22 @@
|
|
|
654
730
|
cbs.transition = "background 0.15s";
|
|
655
731
|
closeBtn.addEventListener("mouseenter", function () { cbs.background = "rgba(0,0,0,0.15)"; });
|
|
656
732
|
closeBtn.addEventListener("mouseleave", function () { cbs.background = "rgba(0,0,0,0.08)"; });
|
|
657
|
-
closeBtn.addEventListener("click",
|
|
733
|
+
closeBtn.addEventListener("click", closeDialog);
|
|
658
734
|
|
|
659
735
|
container.appendChild(iframeEl);
|
|
660
736
|
container.appendChild(closeBtn);
|
|
661
737
|
el.appendChild(container);
|
|
662
738
|
|
|
663
739
|
el.addEventListener("click", function (e) {
|
|
664
|
-
if (e.target === el)
|
|
740
|
+
if (e.target === el) closeDialog();
|
|
665
741
|
});
|
|
666
742
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
document.body.appendChild(
|
|
743
|
+
dialogOverlay = el;
|
|
744
|
+
dialogIframe = iframeEl;
|
|
745
|
+
document.body.appendChild(dialogOverlay);
|
|
670
746
|
} catch (e) {
|
|
671
747
|
logError("addToScenario", e);
|
|
672
|
-
|
|
748
|
+
teardownDialog();
|
|
673
749
|
if (opts && typeof opts.onError === "function") {
|
|
674
750
|
try { opts.onError({ code: "SDK_ERROR", message: e.message || "Unexpected error during addToScenario" }); } catch (_) {}
|
|
675
751
|
}
|
|
@@ -677,19 +753,30 @@
|
|
|
677
753
|
},
|
|
678
754
|
|
|
679
755
|
/**
|
|
680
|
-
* Close the
|
|
756
|
+
* Close the active dialog. Does not affect mount.
|
|
681
757
|
*/
|
|
682
758
|
close: function () {
|
|
683
759
|
try {
|
|
684
|
-
|
|
760
|
+
closeDialog();
|
|
685
761
|
} catch (e) {
|
|
686
762
|
logError("close", e);
|
|
687
|
-
|
|
763
|
+
teardownDialog();
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Unmount the mounted embed. Does not affect dialogs.
|
|
769
|
+
*/
|
|
770
|
+
unmount: function () {
|
|
771
|
+
try {
|
|
772
|
+
teardownMount();
|
|
773
|
+
} catch (e) {
|
|
774
|
+
logError("unmount", e);
|
|
688
775
|
}
|
|
689
776
|
},
|
|
690
777
|
|
|
691
778
|
/**
|
|
692
|
-
* Destroy the SDK — clears state, timers, and DOM.
|
|
779
|
+
* Destroy the SDK — clears state, timers, and DOM (both mount and dialogs).
|
|
693
780
|
*/
|
|
694
781
|
destroy: function () {
|
|
695
782
|
try {
|
|
@@ -697,7 +784,8 @@
|
|
|
697
784
|
clearTimeout(refreshTimer);
|
|
698
785
|
refreshTimer = null;
|
|
699
786
|
}
|
|
700
|
-
|
|
787
|
+
teardownDialog();
|
|
788
|
+
teardownMount();
|
|
701
789
|
publishableKey = null;
|
|
702
790
|
userId = null;
|
|
703
791
|
userEmail = null;
|