@rehers/rehers-roleplay-sdk 2.3.0 → 2.4.1
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 +223 -131
- package/index.d.ts +8 -6
- package/package.json +1 -1
- package/roleplay-sdk.js +213 -155
package/README.md
CHANGED
|
@@ -1,177 +1,269 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Roleplay SDK for the Seamless.AI Dashboard
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This SDK is only for embedding Roleplay inside the Seamless.AI dashboard.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use it in these two places:
|
|
6
|
+
|
|
7
|
+
- The dedicated Roleplay page or tab inside the Seamless dashboard
|
|
8
|
+
- The Contact Search screen, where clicking `Roleplay` opens a dialog for a single contact
|
|
9
|
+
|
|
10
|
+
The SDK must be initialized with the currently logged-in Seamless user before either embed flow is used.
|
|
11
|
+
|
|
12
|
+
## Load the SDK
|
|
13
|
+
|
|
14
|
+
If Seamless is using the downloaded SDK file directly:
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<script src="/path/to/roleplay-sdk.js"></script>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If Seamless is using the npm package:
|
|
6
21
|
|
|
7
22
|
```bash
|
|
8
23
|
npm install @rehers/rehers-roleplay-sdk
|
|
9
24
|
```
|
|
10
25
|
|
|
11
|
-
|
|
26
|
+
## Get the logged-in Seamless user
|
|
12
27
|
|
|
13
|
-
|
|
14
|
-
|
|
28
|
+
Use the existing Seamless dashboard session and call:
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
const meResponse = await fetch("https://api.seamless.ai/api/users/me", {
|
|
32
|
+
method: "GET",
|
|
33
|
+
credentials: "include",
|
|
34
|
+
headers: {
|
|
35
|
+
accept: "application/json, text/plain, */*",
|
|
36
|
+
},
|
|
37
|
+
}).then((res) => res.json());
|
|
15
38
|
```
|
|
16
39
|
|
|
17
|
-
|
|
40
|
+
Normalize the response before reading fields:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
const me = meResponse.data ?? meResponse;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Required mapping for `init()`
|
|
47
|
+
|
|
48
|
+
These are the values Seamless must pass into `SeamlessRoleplay.init(...)`:
|
|
49
|
+
|
|
50
|
+
| SDK field | Seamless `/api/users/me` field |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `userId` | `String(me.id)` |
|
|
53
|
+
| `userEmail` | `me.username` |
|
|
54
|
+
| `userRole` | Optional. Suggested mapping from `me.orgRole` / `me.isOrgAdmin` |
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
const seamlessUserId = String(me.id);
|
|
60
|
+
const seamlessUserEmail = me.username;
|
|
61
|
+
const seamlessUserRole =
|
|
62
|
+
me.orgRole === "owner" ? "owner" :
|
|
63
|
+
me.isOrgAdmin ? "admin" :
|
|
64
|
+
"member";
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
For the response shape currently returned by Seamless, this means:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
const seamlessUserId = String(me.id);
|
|
71
|
+
const seamlessUserEmail = me.username;
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Initialize the SDK once
|
|
75
|
+
|
|
76
|
+
Initialize the SDK once per page load using the logged-in Seamless user. Reuse that same initialized SDK for both the full-page embed and the Contact Search dialog.
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
let roleplayReadyPromise;
|
|
80
|
+
|
|
81
|
+
function ensureRoleplaySdkReady() {
|
|
82
|
+
if (roleplayReadyPromise) return roleplayReadyPromise;
|
|
83
|
+
|
|
84
|
+
roleplayReadyPromise = (async () => {
|
|
85
|
+
const meResponse = await fetch("https://api.seamless.ai/api/users/me", {
|
|
86
|
+
method: "GET",
|
|
87
|
+
credentials: "include",
|
|
88
|
+
headers: {
|
|
89
|
+
accept: "application/json, text/plain, */*",
|
|
90
|
+
},
|
|
91
|
+
}).then((res) => res.json());
|
|
92
|
+
|
|
93
|
+
const me = meResponse.data ?? meResponse;
|
|
94
|
+
|
|
95
|
+
const seamlessUserId = String(me.id);
|
|
96
|
+
const seamlessUserEmail = me.username;
|
|
97
|
+
const seamlessUserRole =
|
|
98
|
+
me.orgRole === "owner" ? "owner" :
|
|
99
|
+
me.isOrgAdmin ? "admin" :
|
|
100
|
+
"member";
|
|
101
|
+
|
|
102
|
+
await new Promise((resolve, reject) => {
|
|
103
|
+
SeamlessRoleplay.init({
|
|
104
|
+
publishableKey: ROLEPLAY_PUBLISHABLE_KEY,
|
|
105
|
+
userId: seamlessUserId,
|
|
106
|
+
userEmail: seamlessUserEmail,
|
|
107
|
+
userRole: seamlessUserRole,
|
|
108
|
+
onReady: resolve,
|
|
109
|
+
onError: reject,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
})();
|
|
113
|
+
|
|
114
|
+
return roleplayReadyPromise;
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Embed in the full Roleplay page
|
|
119
|
+
|
|
120
|
+
Use `mount(container)` when Seamless has a dedicated Roleplay page or tab and the full content area should be the Roleplay app.
|
|
18
121
|
|
|
19
122
|
```html
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
123
|
+
<div id="roleplay-root" style="width: 100%; height: 100%;"></div>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
async function mountRoleplayPage() {
|
|
128
|
+
await ensureRoleplaySdkReady();
|
|
129
|
+
|
|
130
|
+
const container = document.getElementById("roleplay-root");
|
|
131
|
+
SeamlessRoleplay.mount(container);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
If the Seamless page or tab is torn down, unmount the SDK:
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
SeamlessRoleplay.unmount();
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Embed in Contact Search
|
|
142
|
+
|
|
143
|
+
Use `open(contactData)` when the user clicks a `Roleplay` button from a single contact row on the Contact Search screen.
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
async function openRoleplayForContact(contact) {
|
|
147
|
+
await ensureRoleplaySdkReady();
|
|
148
|
+
|
|
149
|
+
SeamlessRoleplay.open({
|
|
150
|
+
name: contact.name,
|
|
151
|
+
domain: contact.domain,
|
|
152
|
+
company: contact.company,
|
|
153
|
+
title: contact.title,
|
|
154
|
+
liUrl: contact.linkedinUrl,
|
|
155
|
+
companyDescription: contact.companyDescription,
|
|
156
|
+
onCallStarted(data) {
|
|
157
|
+
console.log("Roleplay call started", data.callId);
|
|
158
|
+
},
|
|
159
|
+
onCallEnded(data) {
|
|
160
|
+
console.log("Roleplay call ended", data.callId, data.duration);
|
|
30
161
|
},
|
|
31
|
-
|
|
32
|
-
console.
|
|
162
|
+
onClose() {
|
|
163
|
+
console.log("Roleplay dialog closed");
|
|
164
|
+
},
|
|
165
|
+
onError(err) {
|
|
166
|
+
console.error("Roleplay dialog error", err);
|
|
33
167
|
},
|
|
34
168
|
});
|
|
169
|
+
}
|
|
170
|
+
```
|
|
35
171
|
|
|
36
|
-
|
|
37
|
-
SeamlessRoleplay.open({
|
|
38
|
-
name: 'Jane Smith',
|
|
39
|
-
domain: 'acme.com',
|
|
40
|
-
company: 'Acme Corp',
|
|
41
|
-
title: 'VP of Sales',
|
|
42
|
-
companyDescription: 'Enterprise software company', // optional
|
|
43
|
-
liUrl: 'https://linkedin.com/in/jane-smith', // optional
|
|
44
|
-
onCallStarted: function(data) { console.log('Call started:', data.callId); },
|
|
45
|
-
onCallEnded: function(data) { console.log('Call ended:', data.callId, data.duration); },
|
|
46
|
-
onClose: function() { console.log('Dialog closed'); },
|
|
47
|
-
onError: function(data) { console.error('Error:', data.code, data.message); },
|
|
48
|
-
});
|
|
172
|
+
The contact object passed to `open(...)` should map to:
|
|
49
173
|
|
|
50
|
-
|
|
51
|
-
|
|
174
|
+
| SDK field | Contact Search value |
|
|
175
|
+
|---|---|
|
|
176
|
+
| `name` | Contact full name |
|
|
177
|
+
| `domain` | Company domain |
|
|
178
|
+
| `company` | Company name |
|
|
179
|
+
| `title` | Contact title |
|
|
180
|
+
| `liUrl` | LinkedIn profile URL, if available |
|
|
181
|
+
| `companyDescription` | Company description, if available |
|
|
52
182
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
183
|
+
## Optional: add contacts to a scenario
|
|
184
|
+
|
|
185
|
+
If Seamless wants to send multiple contacts into a scenario picker dialog, use `addToScenario(...)`.
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
async function addContactsToScenario(contacts) {
|
|
189
|
+
await ensureRoleplaySdkReady();
|
|
60
190
|
|
|
61
|
-
// 3. Add contacts to a scenario in bulk
|
|
62
191
|
SeamlessRoleplay.addToScenario({
|
|
63
|
-
contacts:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
192
|
+
contacts: contacts.map((contact) => ({
|
|
193
|
+
name: contact.name,
|
|
194
|
+
company: contact.company,
|
|
195
|
+
title: contact.title,
|
|
196
|
+
domain: contact.domain,
|
|
197
|
+
liUrl: contact.linkedinUrl,
|
|
198
|
+
companyDescription: contact.companyDescription,
|
|
199
|
+
})),
|
|
200
|
+
onComplete(data) {
|
|
201
|
+
console.log("Scenario import complete", data);
|
|
202
|
+
},
|
|
203
|
+
onClose() {
|
|
204
|
+
console.log("Add to scenario dialog closed");
|
|
205
|
+
},
|
|
206
|
+
onError(err) {
|
|
207
|
+
console.error("Add to scenario error", err);
|
|
69
208
|
},
|
|
70
|
-
onClose: function() { console.log('Dialog closed'); },
|
|
71
|
-
onError: function(err) { console.error('Error:', err.code, err.message); },
|
|
72
209
|
});
|
|
73
|
-
|
|
74
|
-
// Close and destroy
|
|
75
|
-
SeamlessRoleplay.close();
|
|
76
|
-
SeamlessRoleplay.destroy();
|
|
77
|
-
</script>
|
|
210
|
+
}
|
|
78
211
|
```
|
|
79
212
|
|
|
80
|
-
##
|
|
213
|
+
## Minimal API surface
|
|
214
|
+
|
|
215
|
+
### `SeamlessRoleplay.init(options)`
|
|
216
|
+
|
|
217
|
+
Initializes the SDK with the logged-in Seamless user.
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
SeamlessRoleplay.init({
|
|
221
|
+
publishableKey,
|
|
222
|
+
userId,
|
|
223
|
+
userEmail,
|
|
224
|
+
userRole,
|
|
225
|
+
onReady,
|
|
226
|
+
onError,
|
|
227
|
+
});
|
|
228
|
+
```
|
|
81
229
|
|
|
82
|
-
|
|
230
|
+
### `SeamlessRoleplay.mount(container)`
|
|
83
231
|
|
|
84
|
-
|
|
232
|
+
Mounts the full Roleplay app into a dashboard container.
|
|
85
233
|
|
|
86
|
-
### `SeamlessRoleplay.
|
|
234
|
+
### `SeamlessRoleplay.open(contactData)`
|
|
87
235
|
|
|
88
|
-
|
|
89
|
-
|---|---|---|---|
|
|
90
|
-
| `publishableKey` | `string` | Yes | Publishable API key |
|
|
91
|
-
| `userId` | `string` | Yes | Your user's unique identifier |
|
|
92
|
-
| `userEmail` | `string` | Yes | User email for secure account matching |
|
|
93
|
-
| `userRole` | `string` | No | User role — `"owner"`, `"admin"`, or `"member"` |
|
|
94
|
-
| `userToken` | `string` | No | Signed JWT for identity verification |
|
|
95
|
-
| `origin` | `string` | No | Override app origin (dev only) |
|
|
96
|
-
| `onReady` | `function` | No | Called when session is ready |
|
|
97
|
-
| `onError` | `function` | No | Called on init error `({ code, message })` |
|
|
98
|
-
|
|
99
|
-
### `SeamlessRoleplay.open(data)`
|
|
100
|
-
|
|
101
|
-
Opens the roleplay in a modal dialog overlay.
|
|
102
|
-
|
|
103
|
-
| Field | Type | Required | Description |
|
|
104
|
-
|---|---|---|---|
|
|
105
|
-
| `name` | `string` | Yes | Full name of the contact |
|
|
106
|
-
| `domain` | `string` | Yes | Company domain (e.g. "stripe.com") |
|
|
107
|
-
| `company` | `string` | Yes | Company name |
|
|
108
|
-
| `title` | `string` | Yes | Job title |
|
|
109
|
-
| `companyDescription` | `string` | No | Brief company description |
|
|
110
|
-
| `liUrl` | `string` | No | LinkedIn profile URL |
|
|
111
|
-
| `onCallStarted` | `function` | No | `({ callId })` |
|
|
112
|
-
| `onCallEnded` | `function` | No | `({ callId, duration? })` |
|
|
113
|
-
| `onClose` | `function` | No | Called when dialog closes |
|
|
114
|
-
| `onError` | `function` | No | `({ code, message })` |
|
|
115
|
-
|
|
116
|
-
### `SeamlessRoleplay.mount(container, data?)`
|
|
117
|
-
|
|
118
|
-
Mounts into a DOM element. Two modes:
|
|
119
|
-
|
|
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()`)
|
|
236
|
+
Opens the Roleplay dialog for a single contact.
|
|
122
237
|
|
|
123
238
|
### `SeamlessRoleplay.addToScenario(options)`
|
|
124
239
|
|
|
125
|
-
Opens
|
|
126
|
-
|
|
127
|
-
| Field | Type | Required | Description |
|
|
128
|
-
|---|---|---|---|
|
|
129
|
-
| `contacts` | `array` | Yes | 1–25 contacts to import |
|
|
130
|
-
| `contacts[].name` | `string` | Yes | Full name |
|
|
131
|
-
| `contacts[].company` | `string` | Yes | Company name |
|
|
132
|
-
| `contacts[].title` | `string` | Yes | Job title |
|
|
133
|
-
| `contacts[].domain` | `string` | Yes | Company domain |
|
|
134
|
-
| `contacts[].liUrl` | `string` | No | LinkedIn profile URL |
|
|
135
|
-
| `contacts[].companyDescription` | `string` | No | Brief company description |
|
|
136
|
-
| `onComplete` | `function` | No | `({ scenarioId, scenarioName, addedCount, skippedCount })` |
|
|
137
|
-
| `onClose` | `function` | No | Called when dialog closes |
|
|
138
|
-
| `onError` | `function` | No | `({ code, message })` |
|
|
240
|
+
Opens the bulk add-to-scenario dialog for 1 to 25 contacts.
|
|
139
241
|
|
|
140
242
|
### `SeamlessRoleplay.close()`
|
|
141
243
|
|
|
142
|
-
Closes the active dialog
|
|
244
|
+
Closes the active dialog.
|
|
143
245
|
|
|
144
|
-
### `SeamlessRoleplay.
|
|
246
|
+
### `SeamlessRoleplay.unmount()`
|
|
145
247
|
|
|
146
|
-
|
|
248
|
+
Unmounts the full-page Roleplay embed.
|
|
147
249
|
|
|
148
|
-
|
|
250
|
+
### `SeamlessRoleplay.destroy()`
|
|
149
251
|
|
|
150
|
-
|
|
151
|
-
SDK Backend
|
|
152
|
-
───────────────── ─────────────────
|
|
153
|
-
POST /api/sdk/session
|
|
154
|
-
X-Publishable-Key: pk_live_abc123
|
|
155
|
-
Body: { userId, userEmail, userRole? }
|
|
156
|
-
1. Validate key + origin
|
|
157
|
-
2. Validate userId + userEmail combo
|
|
158
|
-
3. Find/create user
|
|
159
|
-
4. Mint JWT (1hr TTL)
|
|
160
|
-
←──── { sessionToken, expiresIn }
|
|
161
|
-
OR { error: "USER_NOT_FOUND" }
|
|
162
|
-
|
|
163
|
-
Token stored in memory (not localStorage)
|
|
164
|
-
Auto-refreshes at 80% of TTL
|
|
165
|
-
```
|
|
252
|
+
Destroys the SDK state, timers, mount, and dialogs.
|
|
166
253
|
|
|
167
|
-
##
|
|
254
|
+
## Trial and upgrade handling
|
|
168
255
|
|
|
169
|
-
|
|
256
|
+
If the backend responds with `USER_NOT_FOUND` during SDK session creation, the SDK handles the upgrade or trial UI automatically. Seamless does not need separate client-side logic for that case.
|
|
170
257
|
|
|
171
258
|
## TypeScript
|
|
172
259
|
|
|
173
|
-
|
|
260
|
+
Type declarations are included with the SDK package.
|
|
174
261
|
|
|
175
262
|
```ts
|
|
176
|
-
import type {
|
|
263
|
+
import type {
|
|
264
|
+
SeamlessRoleplaySDK,
|
|
265
|
+
SeamlessRoleplayInitOptions,
|
|
266
|
+
SeamlessRoleplayOpenData,
|
|
267
|
+
AddToScenarioOptions,
|
|
268
|
+
} from "@rehers/rehers-roleplay-sdk";
|
|
177
269
|
```
|
package/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export interface SeamlessRoleplayInitOptions {
|
|
2
2
|
/** Publishable API key (starts with pk_live_ or pk_test_) */
|
|
3
3
|
publishableKey: string;
|
|
4
|
-
/**
|
|
4
|
+
/** Logged-in Seamless user ID. Pass String(me.id) from GET /api/users/me */
|
|
5
5
|
userId: string;
|
|
6
|
-
/**
|
|
6
|
+
/** Logged-in Seamless user email. Pass me.username from GET /api/users/me */
|
|
7
7
|
userEmail: string;
|
|
8
8
|
/** Optional user role for syncing permissions ("owner" | "admin" | "member") */
|
|
9
9
|
userRole?: "owner" | "admin" | "member";
|
|
@@ -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 full Roleplay app into a container
|
|
82
|
-
mount(container: HTMLElement
|
|
81
|
+
/** Mount the full Roleplay app into a container. */
|
|
82
|
+
mount(container: HTMLElement): 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,24 @@
|
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
var
|
|
38
|
+
var mountCallbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
|
|
39
|
+
var mountListener = null;
|
|
40
|
+
|
|
41
|
+
// ── Dialog state (overlay — dialog or add-to-scenario) ────────────
|
|
42
|
+
var dialogOverlay = null;
|
|
43
|
+
var dialogIframe = null;
|
|
44
|
+
var dialogMode = null; // "dialog" | "add-to-scenario"
|
|
45
|
+
var dialogContactData = null;
|
|
46
|
+
var dialogCallbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
|
|
47
|
+
var dialogAddToScenarioCallbacks = { onComplete: null, onClose: null, onError: null };
|
|
48
|
+
var dialogAddToScenarioPendingContacts = null;
|
|
49
|
+
var dialogListener = null;
|
|
50
|
+
var dialogCloseTeardownTimer = null;
|
|
45
51
|
|
|
46
52
|
// ── Safe logging ──────────────────────────────────────────────────
|
|
47
53
|
|
|
@@ -65,17 +71,17 @@
|
|
|
65
71
|
return DEFAULT_API_ORIGIN;
|
|
66
72
|
}
|
|
67
73
|
|
|
68
|
-
function
|
|
74
|
+
function sendMsg(iframeEl, msg) {
|
|
69
75
|
try {
|
|
70
|
-
if (
|
|
71
|
-
|
|
76
|
+
if (iframeEl && iframeEl.contentWindow) {
|
|
77
|
+
iframeEl.contentWindow.postMessage(msg, getOrigin());
|
|
72
78
|
}
|
|
73
79
|
} catch (e) {
|
|
74
|
-
logError("
|
|
80
|
+
logError("sendMsg", e);
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
83
|
|
|
78
|
-
// ── Session management
|
|
84
|
+
// ── Session management ────────────────────────────────────────────
|
|
79
85
|
|
|
80
86
|
function fetchSession() {
|
|
81
87
|
if (fetchingSession) return fetchingSession;
|
|
@@ -167,80 +173,89 @@
|
|
|
167
173
|
}, delay);
|
|
168
174
|
}
|
|
169
175
|
|
|
170
|
-
// ── Teardown
|
|
176
|
+
// ── Teardown (dialog only — mount is independent) ─────────────────
|
|
171
177
|
|
|
172
|
-
function
|
|
178
|
+
function teardownDialog() {
|
|
173
179
|
try {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
closeTeardownTimer = null;
|
|
180
|
+
if (dialogCloseTeardownTimer) {
|
|
181
|
+
clearTimeout(dialogCloseTeardownTimer);
|
|
182
|
+
dialogCloseTeardownTimer = null;
|
|
178
183
|
}
|
|
179
184
|
|
|
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;
|
|
185
|
+
if (dialogOverlay && dialogOverlay.parentNode) {
|
|
186
|
+
dialogOverlay.parentNode.removeChild(dialogOverlay);
|
|
190
187
|
}
|
|
188
|
+
dialogOverlay = null;
|
|
189
|
+
|
|
190
|
+
if (dialogListener) {
|
|
191
|
+
window.removeEventListener("message", dialogListener);
|
|
192
|
+
dialogListener = null;
|
|
193
|
+
}
|
|
194
|
+
} catch (e) {
|
|
195
|
+
logError("teardownDialog", e);
|
|
196
|
+
}
|
|
191
197
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
198
|
+
dialogIframe = null;
|
|
199
|
+
dialogContactData = null;
|
|
200
|
+
dialogAddToScenarioPendingContacts = null;
|
|
201
|
+
dialogMode = null;
|
|
202
|
+
dialogCallbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
|
|
203
|
+
dialogAddToScenarioCallbacks = { onComplete: null, onClose: null, onError: null };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function teardownMount() {
|
|
207
|
+
try {
|
|
208
|
+
if (mountIframe && mountIframe.parentNode) {
|
|
209
|
+
mountIframe.parentNode.removeChild(mountIframe);
|
|
210
|
+
}
|
|
211
|
+
if (mountListener) {
|
|
212
|
+
window.removeEventListener("message", mountListener);
|
|
213
|
+
mountListener = null;
|
|
195
214
|
}
|
|
196
215
|
} catch (e) {
|
|
197
|
-
logError("
|
|
216
|
+
logError("teardownMount", e);
|
|
198
217
|
}
|
|
199
218
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
addToScenarioPendingContacts = null;
|
|
204
|
-
mode = null;
|
|
205
|
-
callbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
|
|
206
|
-
addToScenarioCallbacks = { onComplete: null, onClose: null, onError: null };
|
|
219
|
+
mountIframe = null;
|
|
220
|
+
mountContainer = null;
|
|
221
|
+
mountCallbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
|
|
207
222
|
}
|
|
208
223
|
|
|
209
|
-
// ── Message
|
|
224
|
+
// ── Message dispatch ──────────────────────────────────────────────
|
|
210
225
|
|
|
211
|
-
function
|
|
212
|
-
if (
|
|
226
|
+
function dispatchInitToTarget(targetIframe, contactData, atsContacts) {
|
|
227
|
+
if (atsContacts) {
|
|
213
228
|
var msg = {
|
|
214
229
|
type: "seamless-add-to-scenario-init",
|
|
215
|
-
sessionToken:
|
|
230
|
+
sessionToken: sessionToken,
|
|
216
231
|
publishableKey: publishableKey,
|
|
217
232
|
userId: userId,
|
|
218
|
-
contacts:
|
|
233
|
+
contacts: atsContacts,
|
|
219
234
|
};
|
|
220
235
|
if (paymentLink) msg.paymentLink = paymentLink;
|
|
221
|
-
|
|
222
|
-
} else if (
|
|
236
|
+
sendMsg(targetIframe, msg);
|
|
237
|
+
} else if (sessionToken) {
|
|
223
238
|
var msg = {
|
|
224
239
|
type: "seamless-session-init",
|
|
225
|
-
sessionToken:
|
|
226
|
-
contact:
|
|
227
|
-
name:
|
|
228
|
-
domain:
|
|
229
|
-
company:
|
|
230
|
-
title:
|
|
231
|
-
companyDescription:
|
|
232
|
-
liUrl:
|
|
240
|
+
sessionToken: sessionToken,
|
|
241
|
+
contact: contactData ? {
|
|
242
|
+
name: contactData.name,
|
|
243
|
+
domain: contactData.domain,
|
|
244
|
+
company: contactData.company,
|
|
245
|
+
title: contactData.title,
|
|
246
|
+
companyDescription: contactData.companyDescription || undefined,
|
|
247
|
+
liUrl: contactData.liUrl || undefined,
|
|
233
248
|
} : null,
|
|
234
249
|
};
|
|
235
|
-
|
|
250
|
+
sendMsg(targetIframe, msg);
|
|
236
251
|
} else if (paymentLink) {
|
|
237
|
-
|
|
252
|
+
sendMsg(targetIframe, {
|
|
238
253
|
type: "seamless-session-init",
|
|
239
254
|
paymentLink: paymentLink,
|
|
240
255
|
contact: null,
|
|
241
256
|
});
|
|
242
257
|
} else {
|
|
243
|
-
|
|
258
|
+
sendMsg(targetIframe, {
|
|
244
259
|
type: "seamless-session-init",
|
|
245
260
|
paymentLink: null,
|
|
246
261
|
contact: null,
|
|
@@ -248,53 +263,104 @@
|
|
|
248
263
|
}
|
|
249
264
|
}
|
|
250
265
|
|
|
251
|
-
|
|
266
|
+
// ── Mount message handler ─────────────────────────────────────────
|
|
267
|
+
|
|
268
|
+
function handleMountMessage(event) {
|
|
252
269
|
try {
|
|
253
270
|
if (event.origin !== getOrigin()) return;
|
|
271
|
+
if (!mountIframe || !event.source || event.source !== mountIframe.contentWindow) return;
|
|
254
272
|
|
|
255
273
|
var data = event.data;
|
|
256
274
|
if (!data || typeof data.type !== "string") return;
|
|
257
275
|
|
|
258
276
|
switch (data.type) {
|
|
259
277
|
case "ROLEPLAY_READY":
|
|
260
|
-
// Ensure we have a fresh token before sending init to iframe
|
|
261
278
|
getSessionToken()
|
|
262
279
|
.then(function (token) {
|
|
263
|
-
|
|
280
|
+
dispatchInitToTarget(mountIframe, null, null);
|
|
264
281
|
})
|
|
265
282
|
.catch(function () {
|
|
266
|
-
|
|
267
|
-
dispatchInitToIframe(sessionToken);
|
|
283
|
+
dispatchInitToTarget(mountIframe, null, null);
|
|
268
284
|
});
|
|
269
285
|
break;
|
|
270
286
|
|
|
271
287
|
case "ROLEPLAY_CALL_STARTED":
|
|
272
|
-
if (
|
|
273
|
-
|
|
288
|
+
if (mountCallbacks.onCallStarted) {
|
|
289
|
+
mountCallbacks.onCallStarted({ callId: data.callId });
|
|
274
290
|
}
|
|
275
291
|
break;
|
|
276
292
|
|
|
277
293
|
case "ROLEPLAY_CALL_ENDED":
|
|
278
|
-
if (
|
|
279
|
-
|
|
294
|
+
if (mountCallbacks.onCallEnded) {
|
|
295
|
+
mountCallbacks.onCallEnded({ callId: data.callId, duration: data.duration });
|
|
280
296
|
}
|
|
281
297
|
break;
|
|
282
298
|
|
|
283
299
|
case "ROLEPLAY_ERROR":
|
|
284
|
-
if (
|
|
285
|
-
|
|
300
|
+
if (mountCallbacks.onError) {
|
|
301
|
+
mountCallbacks.onError({ code: data.code, message: data.message });
|
|
286
302
|
}
|
|
287
303
|
break;
|
|
288
304
|
|
|
289
305
|
case "ROLEPLAY_CLOSED":
|
|
290
|
-
var onClose =
|
|
291
|
-
|
|
306
|
+
var onClose = mountCallbacks.onClose;
|
|
307
|
+
teardownMount();
|
|
308
|
+
if (onClose) onClose();
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
} catch (e) {
|
|
312
|
+
logError("handleMountMessage", e);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ── Dialog message handler ────────────────────────────────────────
|
|
317
|
+
|
|
318
|
+
function handleDialogMessage(event) {
|
|
319
|
+
try {
|
|
320
|
+
if (event.origin !== getOrigin()) return;
|
|
321
|
+
if (!dialogIframe || !event.source || event.source !== dialogIframe.contentWindow) return;
|
|
322
|
+
|
|
323
|
+
var data = event.data;
|
|
324
|
+
if (!data || typeof data.type !== "string") return;
|
|
325
|
+
|
|
326
|
+
switch (data.type) {
|
|
327
|
+
case "ROLEPLAY_READY":
|
|
328
|
+
getSessionToken()
|
|
329
|
+
.then(function (token) {
|
|
330
|
+
dispatchInitToTarget(dialogIframe, dialogContactData, dialogAddToScenarioPendingContacts);
|
|
331
|
+
})
|
|
332
|
+
.catch(function () {
|
|
333
|
+
dispatchInitToTarget(dialogIframe, dialogContactData, dialogAddToScenarioPendingContacts);
|
|
334
|
+
});
|
|
335
|
+
break;
|
|
336
|
+
|
|
337
|
+
case "ROLEPLAY_CALL_STARTED":
|
|
338
|
+
if (dialogCallbacks.onCallStarted) {
|
|
339
|
+
dialogCallbacks.onCallStarted({ callId: data.callId });
|
|
340
|
+
}
|
|
341
|
+
break;
|
|
342
|
+
|
|
343
|
+
case "ROLEPLAY_CALL_ENDED":
|
|
344
|
+
if (dialogCallbacks.onCallEnded) {
|
|
345
|
+
dialogCallbacks.onCallEnded({ callId: data.callId, duration: data.duration });
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
|
|
349
|
+
case "ROLEPLAY_ERROR":
|
|
350
|
+
if (dialogCallbacks.onError) {
|
|
351
|
+
dialogCallbacks.onError({ code: data.code, message: data.message });
|
|
352
|
+
}
|
|
353
|
+
break;
|
|
354
|
+
|
|
355
|
+
case "ROLEPLAY_CLOSED":
|
|
356
|
+
var onClose = dialogCallbacks.onClose;
|
|
357
|
+
teardownDialog();
|
|
292
358
|
if (onClose) onClose();
|
|
293
359
|
break;
|
|
294
360
|
|
|
295
361
|
case "ADD_TO_SCENARIO_COMPLETE":
|
|
296
|
-
var atsOnComplete =
|
|
297
|
-
|
|
362
|
+
var atsOnComplete = dialogAddToScenarioCallbacks.onComplete;
|
|
363
|
+
teardownDialog();
|
|
298
364
|
if (atsOnComplete) {
|
|
299
365
|
atsOnComplete({
|
|
300
366
|
scenarioId: data.scenarioId,
|
|
@@ -306,36 +372,36 @@
|
|
|
306
372
|
break;
|
|
307
373
|
|
|
308
374
|
case "ADD_TO_SCENARIO_ERROR":
|
|
309
|
-
if (
|
|
310
|
-
|
|
375
|
+
if (dialogAddToScenarioCallbacks.onError) {
|
|
376
|
+
dialogAddToScenarioCallbacks.onError({ code: data.code, message: data.message });
|
|
311
377
|
}
|
|
312
378
|
break;
|
|
313
379
|
|
|
314
380
|
case "ADD_TO_SCENARIO_CLOSED":
|
|
315
|
-
var atsOnClose =
|
|
316
|
-
|
|
381
|
+
var atsOnClose = dialogAddToScenarioCallbacks.onClose;
|
|
382
|
+
teardownDialog();
|
|
317
383
|
if (atsOnClose) atsOnClose();
|
|
318
384
|
break;
|
|
319
385
|
}
|
|
320
386
|
} catch (e) {
|
|
321
|
-
logError("
|
|
387
|
+
logError("handleDialogMessage", e);
|
|
322
388
|
}
|
|
323
389
|
}
|
|
324
390
|
|
|
325
|
-
// ── Close
|
|
391
|
+
// ── Close (dialog only) ───────────────────────────────────────────
|
|
326
392
|
|
|
327
|
-
function
|
|
393
|
+
function closeDialog() {
|
|
328
394
|
try {
|
|
329
|
-
|
|
330
|
-
if (
|
|
331
|
-
|
|
395
|
+
sendMsg(dialogIframe, { type: "roleplay-close" });
|
|
396
|
+
if (dialogCloseTeardownTimer) clearTimeout(dialogCloseTeardownTimer);
|
|
397
|
+
dialogCloseTeardownTimer = setTimeout(teardownDialog, 300);
|
|
332
398
|
} catch (e) {
|
|
333
399
|
logError("close", e);
|
|
334
|
-
|
|
400
|
+
teardownDialog();
|
|
335
401
|
}
|
|
336
402
|
}
|
|
337
403
|
|
|
338
|
-
// ── Create iframe
|
|
404
|
+
// ── Create iframe ─────────────────────────────────────────────────
|
|
339
405
|
|
|
340
406
|
function createIframe(path) {
|
|
341
407
|
var iframeEl = document.createElement("iframe");
|
|
@@ -348,7 +414,7 @@
|
|
|
348
414
|
return iframeEl;
|
|
349
415
|
}
|
|
350
416
|
|
|
351
|
-
// ── SDK API
|
|
417
|
+
// ── SDK API ───────────────────────────────────────────────────────
|
|
352
418
|
|
|
353
419
|
var SeamlessRoleplay = {
|
|
354
420
|
/**
|
|
@@ -416,19 +482,19 @@
|
|
|
416
482
|
return;
|
|
417
483
|
}
|
|
418
484
|
|
|
419
|
-
//
|
|
420
|
-
if (
|
|
485
|
+
// Tear down any existing dialog (NOT the mount)
|
|
486
|
+
if (dialogOverlay || dialogIframe) teardownDialog();
|
|
421
487
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
488
|
+
dialogContactData = data;
|
|
489
|
+
dialogCallbacks.onCallStarted = data.onCallStarted || null;
|
|
490
|
+
dialogCallbacks.onCallEnded = data.onCallEnded || null;
|
|
491
|
+
dialogCallbacks.onClose = data.onClose || null;
|
|
492
|
+
dialogCallbacks.onError = data.onError || null;
|
|
493
|
+
dialogMode = "dialog";
|
|
428
494
|
|
|
429
495
|
// Listen for messages
|
|
430
|
-
|
|
431
|
-
window.addEventListener("message",
|
|
496
|
+
dialogListener = handleDialogMessage;
|
|
497
|
+
window.addEventListener("message", dialogListener);
|
|
432
498
|
|
|
433
499
|
// Build overlay
|
|
434
500
|
var el = document.createElement("div");
|
|
@@ -478,7 +544,7 @@
|
|
|
478
544
|
cbs.alignItems = "center";
|
|
479
545
|
cbs.justifyContent = "center";
|
|
480
546
|
cbs.lineHeight = "1";
|
|
481
|
-
closeBtn.addEventListener("click",
|
|
547
|
+
closeBtn.addEventListener("click", closeDialog);
|
|
482
548
|
|
|
483
549
|
var iframeEl = createIframe();
|
|
484
550
|
|
|
@@ -487,15 +553,15 @@
|
|
|
487
553
|
el.appendChild(container);
|
|
488
554
|
|
|
489
555
|
el.addEventListener("click", function (e) {
|
|
490
|
-
if (e.target === el)
|
|
556
|
+
if (e.target === el) closeDialog();
|
|
491
557
|
});
|
|
492
558
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
document.body.appendChild(
|
|
559
|
+
dialogOverlay = el;
|
|
560
|
+
dialogIframe = iframeEl;
|
|
561
|
+
document.body.appendChild(dialogOverlay);
|
|
496
562
|
} catch (e) {
|
|
497
563
|
logError("open", e);
|
|
498
|
-
|
|
564
|
+
teardownDialog();
|
|
499
565
|
if (data && typeof data.onError === "function") {
|
|
500
566
|
try { data.onError({ code: "SDK_ERROR", message: e.message || "Unexpected error during open" }); } catch (_) {}
|
|
501
567
|
}
|
|
@@ -503,13 +569,9 @@
|
|
|
503
569
|
},
|
|
504
570
|
|
|
505
571
|
/**
|
|
506
|
-
* Mount the
|
|
507
|
-
*
|
|
508
|
-
* Two modes:
|
|
509
|
-
* mount(container) — full app embed (dashboard, scenarios, call logs, etc.)
|
|
510
|
-
* mount(container, contactData) — roleplay call embed for a specific contact
|
|
572
|
+
* Mount the full Roleplay app into a container element.
|
|
511
573
|
*/
|
|
512
|
-
mount: function (container
|
|
574
|
+
mount: function (container) {
|
|
513
575
|
try {
|
|
514
576
|
if (!initCalled) {
|
|
515
577
|
logError("mount", "init() must be called first");
|
|
@@ -520,38 +582,22 @@
|
|
|
520
582
|
return;
|
|
521
583
|
}
|
|
522
584
|
|
|
523
|
-
//
|
|
524
|
-
|
|
525
|
-
if (data && !hasContactData && (data.name || data.domain || data.company || data.title)) {
|
|
526
|
-
logError("mount", "contact data requires { name, domain, company, title }");
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// If already open, tear down first (also cancels any pending close timer)
|
|
531
|
-
if (overlay || (iframe && mode)) teardown();
|
|
585
|
+
// Tear down any existing mount (re-mount)
|
|
586
|
+
if (mountIframe) teardownMount();
|
|
532
587
|
|
|
533
|
-
|
|
534
|
-
callbacks.onCallStarted = (data && data.onCallStarted) || null;
|
|
535
|
-
callbacks.onCallEnded = (data && data.onCallEnded) || null;
|
|
536
|
-
callbacks.onClose = (data && data.onClose) || null;
|
|
537
|
-
callbacks.onError = (data && data.onError) || null;
|
|
538
|
-
mode = "mount";
|
|
588
|
+
mountCallbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
|
|
539
589
|
mountContainer = container;
|
|
540
590
|
|
|
541
591
|
// Listen for messages
|
|
542
|
-
|
|
543
|
-
window.addEventListener("message",
|
|
592
|
+
mountListener = handleMountMessage;
|
|
593
|
+
window.addEventListener("message", mountListener);
|
|
544
594
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
iframe = iframeEl;
|
|
595
|
+
var iframeEl = createIframe("/");
|
|
596
|
+
mountIframe = iframeEl;
|
|
548
597
|
container.appendChild(iframeEl);
|
|
549
598
|
} catch (e) {
|
|
550
599
|
logError("mount", e);
|
|
551
|
-
|
|
552
|
-
if (data && typeof data.onError === "function") {
|
|
553
|
-
try { data.onError({ code: "SDK_ERROR", message: e.message || "Unexpected error during mount" }); } catch (_) {}
|
|
554
|
-
}
|
|
600
|
+
teardownMount();
|
|
555
601
|
}
|
|
556
602
|
},
|
|
557
603
|
|
|
@@ -588,18 +634,18 @@
|
|
|
588
634
|
}
|
|
589
635
|
}
|
|
590
636
|
|
|
591
|
-
// Tear down any existing dialog (
|
|
592
|
-
if (
|
|
637
|
+
// Tear down any existing dialog (NOT the mount)
|
|
638
|
+
if (dialogOverlay || dialogIframe) teardownDialog();
|
|
593
639
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
640
|
+
dialogAddToScenarioPendingContacts = opts.contacts;
|
|
641
|
+
dialogAddToScenarioCallbacks.onComplete = opts.onComplete || null;
|
|
642
|
+
dialogAddToScenarioCallbacks.onClose = opts.onClose || null;
|
|
643
|
+
dialogAddToScenarioCallbacks.onError = opts.onError || null;
|
|
644
|
+
dialogMode = "add-to-scenario";
|
|
599
645
|
|
|
600
646
|
// Listen for messages
|
|
601
|
-
|
|
602
|
-
window.addEventListener("message",
|
|
647
|
+
dialogListener = handleDialogMessage;
|
|
648
|
+
window.addEventListener("message", dialogListener);
|
|
603
649
|
|
|
604
650
|
// Build overlay — wide, compact dialog
|
|
605
651
|
var el = document.createElement("div");
|
|
@@ -663,22 +709,22 @@
|
|
|
663
709
|
cbs.transition = "background 0.15s";
|
|
664
710
|
closeBtn.addEventListener("mouseenter", function () { cbs.background = "rgba(0,0,0,0.15)"; });
|
|
665
711
|
closeBtn.addEventListener("mouseleave", function () { cbs.background = "rgba(0,0,0,0.08)"; });
|
|
666
|
-
closeBtn.addEventListener("click",
|
|
712
|
+
closeBtn.addEventListener("click", closeDialog);
|
|
667
713
|
|
|
668
714
|
container.appendChild(iframeEl);
|
|
669
715
|
container.appendChild(closeBtn);
|
|
670
716
|
el.appendChild(container);
|
|
671
717
|
|
|
672
718
|
el.addEventListener("click", function (e) {
|
|
673
|
-
if (e.target === el)
|
|
719
|
+
if (e.target === el) closeDialog();
|
|
674
720
|
});
|
|
675
721
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
document.body.appendChild(
|
|
722
|
+
dialogOverlay = el;
|
|
723
|
+
dialogIframe = iframeEl;
|
|
724
|
+
document.body.appendChild(dialogOverlay);
|
|
679
725
|
} catch (e) {
|
|
680
726
|
logError("addToScenario", e);
|
|
681
|
-
|
|
727
|
+
teardownDialog();
|
|
682
728
|
if (opts && typeof opts.onError === "function") {
|
|
683
729
|
try { opts.onError({ code: "SDK_ERROR", message: e.message || "Unexpected error during addToScenario" }); } catch (_) {}
|
|
684
730
|
}
|
|
@@ -686,19 +732,30 @@
|
|
|
686
732
|
},
|
|
687
733
|
|
|
688
734
|
/**
|
|
689
|
-
* Close the
|
|
735
|
+
* Close the active dialog. Does not affect mount.
|
|
690
736
|
*/
|
|
691
737
|
close: function () {
|
|
692
738
|
try {
|
|
693
|
-
|
|
739
|
+
closeDialog();
|
|
694
740
|
} catch (e) {
|
|
695
741
|
logError("close", e);
|
|
696
|
-
|
|
742
|
+
teardownDialog();
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Unmount the mounted embed. Does not affect dialogs.
|
|
748
|
+
*/
|
|
749
|
+
unmount: function () {
|
|
750
|
+
try {
|
|
751
|
+
teardownMount();
|
|
752
|
+
} catch (e) {
|
|
753
|
+
logError("unmount", e);
|
|
697
754
|
}
|
|
698
755
|
},
|
|
699
756
|
|
|
700
757
|
/**
|
|
701
|
-
* Destroy the SDK — clears state, timers, and DOM.
|
|
758
|
+
* Destroy the SDK — clears state, timers, and DOM (both mount and dialogs).
|
|
702
759
|
*/
|
|
703
760
|
destroy: function () {
|
|
704
761
|
try {
|
|
@@ -706,7 +763,8 @@
|
|
|
706
763
|
clearTimeout(refreshTimer);
|
|
707
764
|
refreshTimer = null;
|
|
708
765
|
}
|
|
709
|
-
|
|
766
|
+
teardownDialog();
|
|
767
|
+
teardownMount();
|
|
710
768
|
publishableKey = null;
|
|
711
769
|
userId = null;
|
|
712
770
|
userEmail = null;
|