@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 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
- Mounts the roleplay into a DOM element (full-page embed). Same `data` options as `open()`.
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 roleplay into a container element (full-page embed). */
82
- mount(container: HTMLElement, data: SeamlessRoleplayOpenData): void;
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 roleplay dialog or unmount. */
85
+ /** Close the active dialog. Does not affect mount. */
86
86
  close(): void;
87
- /** Destroy the SDK clears state, timers, and DOM. */
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rehers/rehers-roleplay-sdk",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Seamless Roleplay SDK — embed roleplay call sessions via a modal + iframe",
5
5
  "main": "roleplay-sdk.js",
6
6
  "types": "index.d.ts",
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
- // ── State ───────────────────────────────────────────────────────────
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 callbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
34
- var addToScenarioCallbacks = { onComplete: null, onClose: null, onError: null };
35
- var addToScenarioPendingContacts = null;
33
+ var initCalled = false;
36
34
 
37
- var overlay = null;
38
- var iframe = null;
35
+ // ── Mount state (persistent embed — survives dialog open/close) ───
36
+ var mountIframe = null;
39
37
  var mountContainer = null;
40
- var pendingContactData = null;
41
- var mode = null; // "dialog" | "mount" | "add-to-scenario"
42
- var listener = null;
43
- var initCalled = false;
44
- var closeTeardownTimer = null;
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 sendToIframe(msg) {
75
+ function sendMsg(iframeEl, msg) {
69
76
  try {
70
- if (iframe && iframe.contentWindow) {
71
- iframe.contentWindow.postMessage(msg, getOrigin());
77
+ if (iframeEl && iframeEl.contentWindow) {
78
+ iframeEl.contentWindow.postMessage(msg, getOrigin());
72
79
  }
73
80
  } catch (e) {
74
- logError("sendToIframe", e);
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 teardown() {
179
+ function teardownDialog() {
173
180
  try {
174
- // Cancel any pending close→teardown timer so it can't destroy a new dialog
175
- if (closeTeardownTimer) {
176
- clearTimeout(closeTeardownTimer);
177
- closeTeardownTimer = null;
181
+ if (dialogCloseTeardownTimer) {
182
+ clearTimeout(dialogCloseTeardownTimer);
183
+ dialogCloseTeardownTimer = null;
178
184
  }
179
185
 
180
- if (mode === "dialog" || mode === "add-to-scenario") {
181
- if (overlay && overlay.parentNode) {
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
- if (listener) {
193
- window.removeEventListener("message", listener);
194
- listener = null;
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("teardown", e);
217
+ logError("teardownMount", e);
198
218
  }
199
219
 
200
- // Always reset state even if DOM cleanup failed
201
- iframe = null;
202
- pendingContactData = null;
203
- addToScenarioPendingContacts = null;
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 handler ─────────────────────────────────────────────────
226
+ // ── Message dispatch ──────────────────────────────────────────────
210
227
 
211
- function dispatchInitToIframe(token) {
212
- if (addToScenarioPendingContacts) {
228
+ function dispatchInitToTarget(targetIframe, contactData, atsContacts) {
229
+ if (atsContacts) {
213
230
  var msg = {
214
231
  type: "seamless-add-to-scenario-init",
215
- sessionToken: token,
232
+ sessionToken: sessionToken,
216
233
  publishableKey: publishableKey,
217
234
  userId: userId,
218
- contacts: addToScenarioPendingContacts,
235
+ contacts: atsContacts,
219
236
  };
220
237
  if (paymentLink) msg.paymentLink = paymentLink;
221
- sendToIframe(msg);
222
- } else if (token && pendingContactData) {
223
- sendToIframe({
238
+ sendMsg(targetIframe, msg);
239
+ } else if (sessionToken) {
240
+ var msg = {
224
241
  type: "seamless-session-init",
225
- sessionToken: token,
226
- contact: {
227
- name: pendingContactData.name,
228
- domain: pendingContactData.domain,
229
- company: pendingContactData.company,
230
- title: pendingContactData.title,
231
- companyDescription: pendingContactData.companyDescription || undefined,
232
- liUrl: pendingContactData.liUrl || undefined,
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
- sendToIframe({
254
+ sendMsg(targetIframe, {
237
255
  type: "seamless-session-init",
238
256
  paymentLink: paymentLink,
239
257
  contact: null,
240
258
  });
241
259
  } else {
242
- sendToIframe({
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
- function handleMessage(event) {
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
- dispatchInitToIframe(token);
332
+ dispatchInitToTarget(dialogIframe, dialogContactData, dialogAddToScenarioPendingContacts);
263
333
  })
264
334
  .catch(function () {
265
- // Token refresh failed — send whatever we have (iframe will handle auth errors)
266
- dispatchInitToIframe(sessionToken);
335
+ dispatchInitToTarget(dialogIframe, dialogContactData, dialogAddToScenarioPendingContacts);
267
336
  });
268
337
  break;
269
338
 
270
339
  case "ROLEPLAY_CALL_STARTED":
271
- if (callbacks.onCallStarted) {
272
- callbacks.onCallStarted({ callId: data.callId });
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 (callbacks.onCallEnded) {
278
- callbacks.onCallEnded({ callId: data.callId, duration: data.duration });
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 (callbacks.onError) {
284
- callbacks.onError({ code: data.code, message: data.message });
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 = callbacks.onClose;
290
- teardown();
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 = addToScenarioCallbacks.onComplete;
296
- teardown();
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 (addToScenarioCallbacks.onError) {
309
- addToScenarioCallbacks.onError({ code: data.code, message: data.message });
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 = addToScenarioCallbacks.onClose;
315
- teardown();
383
+ var atsOnClose = dialogAddToScenarioCallbacks.onClose;
384
+ teardownDialog();
316
385
  if (atsOnClose) atsOnClose();
317
386
  break;
318
387
  }
319
388
  } catch (e) {
320
- logError("handleMessage", e);
389
+ logError("handleDialogMessage", e);
321
390
  }
322
391
  }
323
392
 
324
- // ── Close ───────────────────────────────────────────────────────────
393
+ // ── Close (dialog only) ───────────────────────────────────────────
325
394
 
326
- function close() {
395
+ function closeDialog() {
327
396
  try {
328
- sendToIframe({ type: "roleplay-close" });
329
- if (closeTeardownTimer) clearTimeout(closeTeardownTimer);
330
- closeTeardownTimer = setTimeout(teardown, 300);
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
- teardown();
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
- // If already open, tear down first (also cancels any pending close timer)
419
- if (overlay || (iframe && mode)) teardown();
487
+ // Tear down any existing dialog (NOT the mount)
488
+ if (dialogOverlay || dialogIframe) teardownDialog();
420
489
 
421
- pendingContactData = data;
422
- callbacks.onCallStarted = data.onCallStarted || null;
423
- callbacks.onCallEnded = data.onCallEnded || null;
424
- callbacks.onClose = data.onClose || null;
425
- callbacks.onError = data.onError || null;
426
- mode = "dialog";
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
- listener = handleMessage;
430
- window.addEventListener("message", listener);
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", close);
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) close();
558
+ if (e.target === el) closeDialog();
490
559
  });
491
560
 
492
- overlay = el;
493
- iframe = iframeEl;
494
- document.body.appendChild(overlay);
561
+ dialogOverlay = el;
562
+ dialogIframe = iframeEl;
563
+ document.body.appendChild(dialogOverlay);
495
564
  } catch (e) {
496
565
  logError("open", e);
497
- teardown();
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 (full-page embed).
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
- if (!data || !data.name || !data.domain || !data.company || !data.title) {
518
- logError("mount", "requires { name, domain, company, title }");
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
- // If already open, tear down first (also cancels any pending close timer)
523
- if (overlay || (iframe && mode)) teardown();
598
+ // Tear down any existing mount (re-mount)
599
+ if (mountIframe) teardownMount();
524
600
 
525
- pendingContactData = data;
526
- callbacks.onCallStarted = data.onCallStarted || null;
527
- callbacks.onCallEnded = data.onCallEnded || null;
528
- callbacks.onClose = data.onClose || null;
529
- callbacks.onError = data.onError || null;
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
- listener = handleMessage;
535
- window.addEventListener("message", listener);
609
+ mountListener = handleMountMessage;
610
+ window.addEventListener("message", mountListener);
536
611
 
537
- var iframeEl = createIframe();
538
- iframe = iframeEl;
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
- teardown();
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 (also cancels any pending close timer)
583
- if (overlay || (iframe && mode)) teardown();
658
+ // Tear down any existing dialog (NOT the mount)
659
+ if (dialogOverlay || dialogIframe) teardownDialog();
584
660
 
585
- addToScenarioPendingContacts = opts.contacts;
586
- addToScenarioCallbacks.onComplete = opts.onComplete || null;
587
- addToScenarioCallbacks.onClose = opts.onClose || null;
588
- addToScenarioCallbacks.onError = opts.onError || null;
589
- mode = "add-to-scenario";
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
- listener = handleMessage;
593
- window.addEventListener("message", listener);
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", close);
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) close();
740
+ if (e.target === el) closeDialog();
665
741
  });
666
742
 
667
- overlay = el;
668
- iframe = iframeEl;
669
- document.body.appendChild(overlay);
743
+ dialogOverlay = el;
744
+ dialogIframe = iframeEl;
745
+ document.body.appendChild(dialogOverlay);
670
746
  } catch (e) {
671
747
  logError("addToScenario", e);
672
- teardown();
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 roleplay.
756
+ * Close the active dialog. Does not affect mount.
681
757
  */
682
758
  close: function () {
683
759
  try {
684
- close();
760
+ closeDialog();
685
761
  } catch (e) {
686
762
  logError("close", e);
687
- teardown();
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
- teardown();
787
+ teardownDialog();
788
+ teardownMount();
701
789
  publishableKey = null;
702
790
  userId = null;
703
791
  userEmail = null;