@rehers/rehers-roleplay-sdk 2.5.0 → 2.5.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 CHANGED
@@ -1,13 +1,4 @@
1
- # Roleplay SDK for the Seamless.AI Dashboard
2
-
3
- This SDK is only for embedding Roleplay inside the Seamless.AI dashboard.
4
-
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.
1
+ # Roleplay SDK for Seamless.AI
11
2
 
12
3
  ## Install
13
4
 
@@ -17,512 +8,233 @@ npm install @rehers/rehers-roleplay-sdk
17
8
 
18
9
  ---
19
10
 
20
- ## React Integration (Recommended)
11
+ ## 1. Wrap your app with the Provider
21
12
 
22
- If the Seamless dashboard is built with React, use the React bindings. They handle SDK lifecycle, cleanup, and stale closures automatically.
13
+ Add this once, above all your routes. It initializes the SDK for the logged-in Seamless user.
23
14
 
24
15
  ```tsx
25
- import {
26
- SeamlessRoleplayProvider,
27
- RoleplayDialog,
28
- RoleplayEmbed,
29
- AddToScenarioDialog,
30
- useSeamlessRoleplay,
31
- } from "@rehers/rehers-roleplay-sdk/react";
32
- ```
33
-
34
- ### Get the logged-in Seamless user
35
-
36
- Use the existing Seamless dashboard session and call:
37
-
38
- ```ts
39
- const meResponse = await fetch("https://api.seamless.ai/api/users/me", {
40
- method: "GET",
41
- credentials: "include",
42
- headers: {
43
- accept: "application/json, text/plain, */*",
44
- },
45
- }).then((res) => res.json());
46
-
47
- const me = meResponse.data ?? meResponse;
48
-
49
- const seamlessUserId = String(me.id);
50
- const seamlessUserEmail = me.username;
51
- const seamlessUserRole =
52
- me.orgRole === "owner" ? "owner" :
53
- me.isOrgAdmin ? "admin" :
54
- "member";
55
- ```
56
-
57
- ### Wrap your app with the Provider
16
+ import { SeamlessRoleplayProvider } from "@rehers/rehers-roleplay-sdk/react";
58
17
 
59
- Add `SeamlessRoleplayProvider` once, near the root of the Seamless dashboard. It initializes the SDK and makes it available to all child components.
60
-
61
- ```tsx
62
18
  function App() {
19
+ // You already have the logged-in user from your auth/session.
20
+ // The SDK needs their id and email.
21
+ const me = useCurrentUser(); // however you get the logged-in user
22
+
63
23
  return (
64
24
  <SeamlessRoleplayProvider
65
- publishableKey={ROLEPLAY_PUBLISHABLE_KEY}
66
- userId={seamlessUserId}
67
- userEmail={seamlessUserEmail}
68
- userRole={seamlessUserRole}
25
+ publishableKey="pk_live_..."
26
+ userId={String(me.id)}
27
+ userEmail={me.username}
28
+ userRole={me.orgRole}
69
29
  onReady={() => console.log("Roleplay SDK ready")}
70
30
  onError={(err) => console.error("Roleplay SDK error", err)}
71
31
  >
72
- <Dashboard />
32
+ <Routes>
33
+ <Route path="/roleplay" element={<RoleplayPage />} />
34
+ <Route path="/contacts" element={<ContactSearch />} />
35
+ {/* ...your other routes */}
36
+ </Routes>
73
37
  </SeamlessRoleplayProvider>
74
38
  );
75
39
  }
76
40
  ```
77
41
 
78
- ### Embed the full Roleplay page
42
+ If you fetch the user via API:
43
+
44
+ ```ts
45
+ const res = await fetch("https://api.seamless.ai/api/users/me", {
46
+ credentials: "include",
47
+ }).then((r) => r.json());
48
+
49
+ const me = res.data ?? res;
50
+ // me.id → pass as userId (convert to string)
51
+ // me.username → pass as userEmail
52
+ // me.orgRole → pass as userRole directly
53
+ ```
54
+
55
+ That's the only setup. Everything below just works.
56
+
57
+ ---
58
+
59
+ ## 2. Full Roleplay page
79
60
 
80
- Use `RoleplayEmbed` when Seamless has a dedicated Roleplay page or tab and the full content area should be the Roleplay app.
61
+ For the dedicated Roleplay tab/page, drop in `RoleplayEmbed`. It fills its container.
81
62
 
82
63
  ```tsx
64
+ import { RoleplayEmbed } from "@rehers/rehers-roleplay-sdk/react";
65
+
83
66
  function RoleplayPage() {
84
67
  return (
85
- <RoleplayEmbed
86
- style={{ width: "100%", height: "100vh" }}
87
- onCallStarted={(data) => console.log("Call started", data.callId)}
88
- onCallEnded={(data) => console.log("Call ended", data.callId, data.duration)}
89
- onError={(err) => console.error("Roleplay error", err)}
90
- />
68
+ <RoleplayEmbed style={{ width: "100%", height: "100%" }} />
91
69
  );
92
70
  }
93
71
  ```
94
72
 
95
- The embed mounts when the component mounts and cleans up automatically when it unmounts. No manual teardown needed.
73
+ Make sure the parent has a height (`height: 100%`, `height: 100vh`, `flex: 1`, etc).
74
+
75
+ Mounts automatically. Cleans up automatically when the user navigates away.
76
+
77
+ ---
96
78
 
97
- ### Open a dialog from Contact Search
79
+ ## 3. Roleplay dialog on Contact Search
98
80
 
99
- Use `RoleplayDialog` when the user clicks a `Roleplay` button from a single contact row on the Contact Search screen.
81
+ When a user clicks "Roleplay" on a contact row, a dialog opens over the page.
100
82
 
101
83
  ```tsx
102
- function ContactRow({ contact }) {
103
- const [showRoleplay, setShowRoleplay] = useState(false);
84
+ import { useState } from "react";
85
+ import { RoleplayDialog } from "@rehers/rehers-roleplay-sdk/react";
86
+
87
+ function ContactSearch() {
88
+ const [activeContact, setActiveContact] = useState(null);
104
89
 
105
90
  return (
106
91
  <>
107
- <button onClick={() => setShowRoleplay(true)}>Roleplay</button>
108
-
92
+ {/* Your existing contact table — just add an onClick to each row's button */}
93
+ <table>
94
+ {contacts.map((contact) => (
95
+ <tr key={contact.id}>
96
+ <td>{contact.name}</td>
97
+ <td>{contact.company}</td>
98
+ <td>{contact.title}</td>
99
+ <td>
100
+ <button onClick={() => setActiveContact(contact)}>
101
+ Roleplay
102
+ </button>
103
+ </td>
104
+ </tr>
105
+ ))}
106
+ </table>
107
+
108
+ {/* This renders nothing visible — the SDK creates the overlay */}
109
109
  <RoleplayDialog
110
- open={showRoleplay}
111
- name={contact.name}
112
- domain={contact.domain}
113
- company={contact.company}
114
- title={contact.title}
115
- liUrl={contact.linkedinUrl}
116
- companyDescription={contact.companyDescription}
117
- onCallStarted={(data) => {
118
- console.log("Roleplay call started", data.callId);
119
- }}
120
- onCallEnded={(data) => {
121
- console.log("Roleplay call ended", data.callId, data.duration);
122
- }}
123
- onClose={() => setShowRoleplay(false)}
124
- onError={(err) => {
125
- console.error("Roleplay dialog error", err);
126
- }}
110
+ open={activeContact !== null}
111
+ name={activeContact?.name ?? ""}
112
+ domain={activeContact?.domain ?? ""}
113
+ company={activeContact?.company ?? ""}
114
+ title={activeContact?.title ?? ""}
115
+ liUrl={activeContact?.linkedinUrl}
116
+ companyDescription={activeContact?.companyDescription}
117
+ onClose={() => setActiveContact(null)}
118
+ onError={(err) => console.error("Error", err)}
127
119
  />
128
120
  </>
129
121
  );
130
122
  }
131
123
  ```
132
124
 
133
- The dialog opens when `open` becomes `true` and closes when it becomes `false`. All callbacks always see the latest props and state no stale closures.
125
+ **How it works:** Set `activeContact` to a contact object to open the dialog. Set it back to `null` to close. The `onClose` callback fires when the user closes it themselves.
126
+
127
+ ---
134
128
 
135
- ### Add contacts to a scenario
129
+ ## 4. Add to Scenario (bulk)
136
130
 
137
- Use `AddToScenarioDialog` to send multiple contacts into a scenario picker dialog.
131
+ When users select multiple contacts and click "Add to Scenario", a scenario picker dialog opens.
138
132
 
139
133
  ```tsx
140
- function BulkActions({ selectedContacts }) {
141
- const [showATS, setShowATS] = useState(false);
134
+ import { useState } from "react";
135
+ import { AddToScenarioDialog } from "@rehers/rehers-roleplay-sdk/react";
136
+
137
+ function ContactSearch() {
138
+ const [selectedContacts, setSelectedContacts] = useState([]);
139
+ const [showScenario, setShowScenario] = useState(false);
142
140
 
143
141
  return (
144
142
  <>
145
- <button onClick={() => setShowATS(true)}>Add to Scenario</button>
143
+ {/* Your existing table with checkboxes that populate selectedContacts */}
144
+
145
+ <button
146
+ disabled={selectedContacts.length === 0}
147
+ onClick={() => setShowScenario(true)}
148
+ >
149
+ Add {selectedContacts.length} to Scenario
150
+ </button>
146
151
 
147
152
  <AddToScenarioDialog
148
- open={showATS}
149
- contacts={selectedContacts.map((contact) => ({
150
- name: contact.name,
151
- company: contact.company,
152
- title: contact.title,
153
- domain: contact.domain,
154
- liUrl: contact.linkedinUrl,
155
- companyDescription: contact.companyDescription,
153
+ open={showScenario}
154
+ contacts={selectedContacts.map((c) => ({
155
+ name: c.name,
156
+ company: c.company,
157
+ title: c.title,
158
+ domain: c.domain,
159
+ liUrl: c.linkedinUrl,
156
160
  }))}
157
161
  onComplete={(data) => {
158
- console.log("Scenario import complete", data);
159
- setShowATS(false);
160
- }}
161
- onClose={() => setShowATS(false)}
162
- onError={(err) => {
163
- console.error("Add to scenario error", err);
162
+ console.log(`Added ${data.addedCount} to ${data.scenarioName}`);
163
+ setShowScenario(false);
164
+ setSelectedContacts([]);
164
165
  }}
166
+ onClose={() => setShowScenario(false)}
167
+ onError={(err) => console.error("Error", err)}
165
168
  />
166
169
  </>
167
170
  );
168
171
  }
169
172
  ```
170
173
 
171
- Supports 1 to 25 contacts per call. Each contact requires `name`, `company`, `title`, and `domain`.
172
-
173
- ### Use the hook for imperative access
174
-
175
- If you need direct access to SDK methods instead of declarative components:
176
-
177
- ```tsx
178
- function CustomButton({ contact }) {
179
- const { isReady, open, close } = useSeamlessRoleplay();
180
-
181
- return (
182
- <button
183
- disabled={!isReady}
184
- onClick={() =>
185
- open({
186
- ...contact,
187
- onClose: () => console.log("closed"),
188
- })
189
- }
190
- >
191
- Start Roleplay
192
- </button>
193
- );
194
- }
195
- ```
196
-
197
- ### React props reference
198
-
199
- #### `SeamlessRoleplayProvider`
200
-
201
- | Prop | Type | Required | Description |
202
- |---|---|---|---|
203
- | `publishableKey` | `string` | Yes | Publishable API key (`pk_live_...` or `pk_test_...`) |
204
- | `userId` | `string` | Yes | `String(me.id)` from Seamless `/api/users/me` |
205
- | `userEmail` | `string` | Yes | `me.username` from Seamless `/api/users/me` |
206
- | `userRole` | `"owner" \| "admin" \| "member"` | No | User role for syncing permissions |
207
- | `userToken` | `string` | No | Signed JWT for identity verification |
208
- | `origin` | `string` | No | Override iframe origin (dev/testing only) |
209
- | `onReady` | `() => void` | No | Called when SDK session is ready |
210
- | `onError` | `(error) => void` | No | Called on initialization error |
211
-
212
- #### `RoleplayEmbed`
213
-
214
- | Prop | Type | Required | Description |
215
- |---|---|---|---|
216
- | `className` | `string` | No | CSS class for the container div |
217
- | `style` | `CSSProperties` | No | Inline styles for the container div |
218
- | `onCallStarted` | `(data) => void` | No | Called when a roleplay call starts |
219
- | `onCallEnded` | `(data) => void` | No | Called when a roleplay call ends |
220
- | `onClose` | `() => void` | No | Called when the embed is closed |
221
- | `onError` | `(data) => void` | No | Called on error |
222
-
223
- #### `RoleplayDialog`
224
-
225
- | Prop | Type | Required | Description |
226
- |---|---|---|---|
227
- | `open` | `boolean` | Yes | Whether the dialog is open |
228
- | `name` | `string` | Yes | Contact full name |
229
- | `domain` | `string` | Yes | Company domain (e.g. `"stripe.com"`) |
230
- | `company` | `string` | Yes | Company name |
231
- | `title` | `string` | Yes | Contact job title |
232
- | `companyDescription` | `string` | No | Company description |
233
- | `liUrl` | `string` | No | LinkedIn profile URL |
234
- | `onCallStarted` | `(data) => void` | No | Called when the roleplay call starts |
235
- | `onCallEnded` | `(data) => void` | No | Called when the roleplay call ends |
236
- | `onClose` | `() => void` | No | Called when the dialog is closed |
237
- | `onError` | `(data) => void` | No | Called on error |
238
-
239
- #### `AddToScenarioDialog`
240
-
241
- | Prop | Type | Required | Description |
242
- |---|---|---|---|
243
- | `open` | `boolean` | Yes | Whether the dialog is open |
244
- | `contacts` | `AddToScenarioContact[]` | Yes | Array of contacts (1–25) |
245
- | `onComplete` | `(data) => void` | No | Called on successful import |
246
- | `onClose` | `() => void` | No | Called when dialog is closed |
247
- | `onError` | `(error) => void` | No | Called on error |
248
-
249
- #### `useSeamlessRoleplay()`
250
-
251
- Returns `{ isReady, error, sdk }`. Must be used inside a `SeamlessRoleplayProvider`.
174
+ Accepts 1 to 25 contacts. Each contact needs `name`, `company`, `title`, and `domain`.
252
175
 
253
176
  ---
254
177
 
255
- ## Raw JavaScript Integration
256
-
257
- If the Seamless dashboard does not use React, or if you need direct imperative control, use the vanilla SDK directly.
258
-
259
- ### Load the SDK
260
-
261
- #### Option 1: npm import
178
+ ## Contact field mapping
262
179
 
263
- ```js
264
- import SeamlessRoleplay from "@rehers/rehers-roleplay-sdk";
265
- ```
180
+ When passing contact data to `RoleplayDialog` or `AddToScenarioDialog`:
266
181
 
267
- #### Option 2: Script tag (hosted file)
268
-
269
- ```html
270
- <script src="/path/to/roleplay-sdk.js"></script>
271
- ```
272
-
273
- #### Option 3: CDN script
274
-
275
- ```html
276
- <script src="https://unpkg.com/@rehers/rehers-roleplay-sdk"></script>
277
- ```
278
-
279
- ### Get the logged-in Seamless user
280
-
281
- Use the existing Seamless dashboard session and call:
282
-
283
- ```js
284
- const meResponse = await fetch("https://api.seamless.ai/api/users/me", {
285
- method: "GET",
286
- credentials: "include",
287
- headers: {
288
- accept: "application/json, text/plain, */*",
289
- },
290
- }).then((res) => res.json());
291
- ```
292
-
293
- Normalize the response before reading fields:
294
-
295
- ```js
296
- const me = meResponse.data ?? meResponse;
297
- ```
298
-
299
- ### Required mapping for `init()`
300
-
301
- These are the values Seamless must pass into `SeamlessRoleplay.init(...)`:
302
-
303
- | SDK field | Seamless `/api/users/me` field |
182
+ | SDK prop | Your contact field |
304
183
  |---|---|
305
- | `userId` | `String(me.id)` |
306
- | `userEmail` | `me.username` |
307
- | `userRole` | Optional. Suggested mapping from `me.orgRole` / `me.isOrgAdmin` |
308
-
309
- Example:
310
-
311
- ```js
312
- const seamlessUserId = String(me.id);
313
- const seamlessUserEmail = me.username;
314
- const seamlessUserRole =
315
- me.orgRole === "owner" ? "owner" :
316
- me.isOrgAdmin ? "admin" :
317
- "member";
318
- ```
319
-
320
- ### Initialize the SDK once
321
-
322
- 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.
323
-
324
- ```js
325
- let roleplayReadyPromise;
326
-
327
- function ensureRoleplaySdkReady() {
328
- if (roleplayReadyPromise) return roleplayReadyPromise;
329
-
330
- roleplayReadyPromise = (async () => {
331
- const meResponse = await fetch("https://api.seamless.ai/api/users/me", {
332
- method: "GET",
333
- credentials: "include",
334
- headers: {
335
- accept: "application/json, text/plain, */*",
336
- },
337
- }).then((res) => res.json());
338
-
339
- const me = meResponse.data ?? meResponse;
340
-
341
- const seamlessUserId = String(me.id);
342
- const seamlessUserEmail = me.username;
343
- const seamlessUserRole =
344
- me.orgRole === "owner" ? "owner" :
345
- me.isOrgAdmin ? "admin" :
346
- "member";
347
-
348
- await new Promise((resolve, reject) => {
349
- SeamlessRoleplay.init({
350
- publishableKey: ROLEPLAY_PUBLISHABLE_KEY,
351
- userId: seamlessUserId,
352
- userEmail: seamlessUserEmail,
353
- userRole: seamlessUserRole,
354
- onReady: resolve,
355
- onError: reject,
356
- });
357
- });
358
- })();
359
-
360
- return roleplayReadyPromise;
361
- }
362
- ```
363
-
364
- ### Embed in the full Roleplay page
365
-
366
- Use `mount(container)` when Seamless has a dedicated Roleplay page or tab and the full content area should be the Roleplay app.
367
-
368
- ```html
369
- <div id="roleplay-root" style="width: 100%; height: 100%;"></div>
370
- ```
184
+ | `name` | Full name |
185
+ | `domain` | Company domain (e.g. `"stripe.com"`) |
186
+ | `company` | Company name |
187
+ | `title` | Job title |
188
+ | `liUrl` | LinkedIn URL (optional) |
189
+ | `companyDescription` | Company description (optional) |
371
190
 
372
- ```js
373
- async function mountRoleplayPage() {
374
- await ensureRoleplaySdkReady();
191
+ ---
375
192
 
376
- const container = document.getElementById("roleplay-root");
377
- SeamlessRoleplay.mount(container);
378
- }
379
- ```
193
+ ## That's it
380
194
 
381
- If the Seamless page or tab is torn down, unmount the SDK:
195
+ - **Provider** wraps your app once handles auth, sessions, cleanup
196
+ - **RoleplayEmbed** is your full Roleplay page — one component
197
+ - **RoleplayDialog** opens per-contact from Contact Search — controlled by a boolean
198
+ - **AddToScenarioDialog** opens for bulk import — controlled by a boolean
382
199
 
383
- ```js
384
- SeamlessRoleplay.unmount();
385
- ```
200
+ All three clean up after themselves. No manual `destroy()`, no `useEffect`, no refs.
386
201
 
387
- ### Embed in Contact Search
202
+ TypeScript types are included — your editor will autocomplete all props.
388
203
 
389
- Use `open(contactData)` when the user clicks a `Roleplay` button from a single contact row on the Contact Search screen.
204
+ ---
390
205
 
391
- ```js
392
- async function openRoleplayForContact(contact) {
393
- await ensureRoleplaySdkReady();
394
-
395
- SeamlessRoleplay.open({
396
- name: contact.name,
397
- domain: contact.domain,
398
- company: contact.company,
399
- title: contact.title,
400
- liUrl: contact.linkedinUrl,
401
- companyDescription: contact.companyDescription,
402
- onCallStarted(data) {
403
- console.log("Roleplay call started", data.callId);
404
- },
405
- onCallEnded(data) {
406
- console.log("Roleplay call ended", data.callId, data.duration);
407
- },
408
- onClose() {
409
- console.log("Roleplay dialog closed");
410
- },
411
- onError(err) {
412
- console.error("Roleplay dialog error", err);
413
- },
414
- });
415
- }
416
- ```
206
+ ## Trial and upgrade handling
417
207
 
418
- The contact object passed to `open(...)` should map to:
208
+ If a user doesn't have a Roleplay subscription, the SDK shows the upgrade/trial UI automatically inside the iframe. No client-side logic needed.
419
209
 
420
- | SDK field | Contact Search value |
421
- |---|---|
422
- | `name` | Contact full name |
423
- | `domain` | Company domain |
424
- | `company` | Company name |
425
- | `title` | Contact title |
426
- | `liUrl` | LinkedIn profile URL, if available |
427
- | `companyDescription` | Company description, if available |
210
+ ---
428
211
 
429
- ### Add contacts to a scenario
212
+ ## Advanced: Vanilla JavaScript
430
213
 
431
- If Seamless wants to send multiple contacts into a scenario picker dialog, use `addToScenario(...)`.
214
+ If you need the vanilla SDK without React:
432
215
 
433
216
  ```js
434
- async function addContactsToScenario(contacts) {
435
- await ensureRoleplaySdkReady();
436
-
437
- SeamlessRoleplay.addToScenario({
438
- contacts: contacts.map((contact) => ({
439
- name: contact.name,
440
- company: contact.company,
441
- title: contact.title,
442
- domain: contact.domain,
443
- liUrl: contact.linkedinUrl,
444
- companyDescription: contact.companyDescription,
445
- })),
446
- onComplete(data) {
447
- console.log("Scenario import complete", data);
448
- },
449
- onClose() {
450
- console.log("Add to scenario dialog closed");
451
- },
452
- onError(err) {
453
- console.error("Add to scenario error", err);
454
- },
455
- });
456
- }
457
- ```
458
-
459
- ### API reference
460
-
461
- #### `SeamlessRoleplay.init(options)`
462
-
463
- Initializes the SDK with the logged-in Seamless user.
217
+ import "@rehers/rehers-roleplay-sdk";
464
218
 
465
- ```js
219
+ // Initialize once
466
220
  SeamlessRoleplay.init({
467
- publishableKey,
468
- userId,
469
- userEmail,
470
- userRole,
471
- onReady,
472
- onError,
221
+ publishableKey: "pk_live_...",
222
+ userId: "...",
223
+ userEmail: "...",
224
+ onReady() { console.log("ready"); },
473
225
  });
474
- ```
475
-
476
- #### `SeamlessRoleplay.mount(container)`
477
-
478
- Mounts the full Roleplay app into a dashboard container.
479
226
 
480
- #### `SeamlessRoleplay.open(contactData)`
227
+ // Full page embed
228
+ SeamlessRoleplay.mount(document.getElementById("container"));
481
229
 
482
- Opens the Roleplay dialog for a single contact.
230
+ // Contact dialog
231
+ SeamlessRoleplay.open({ name: "...", domain: "...", company: "...", title: "..." });
483
232
 
484
- #### `SeamlessRoleplay.addToScenario(options)`
233
+ // Bulk import
234
+ SeamlessRoleplay.addToScenario({ contacts: [...], onComplete(data) { } });
485
235
 
486
- Opens the bulk add-to-scenario dialog for 1 to 25 contacts.
487
-
488
- #### `SeamlessRoleplay.close()`
489
-
490
- Closes the active dialog.
491
-
492
- #### `SeamlessRoleplay.unmount()`
493
-
494
- Unmounts the full-page Roleplay embed.
495
-
496
- #### `SeamlessRoleplay.destroy()`
497
-
498
- Destroys the SDK state, timers, mount, and dialogs.
499
-
500
- ---
501
-
502
- ## Trial and upgrade handling
503
-
504
- 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.
505
-
506
- ## TypeScript
507
-
508
- Type declarations are included with the SDK package.
509
-
510
- ```ts
511
- // Vanilla SDK types
512
- import type {
513
- SeamlessRoleplaySDK,
514
- SeamlessRoleplayInitOptions,
515
- SeamlessRoleplayOpenData,
516
- AddToScenarioOptions,
517
- } from "@rehers/rehers-roleplay-sdk";
518
-
519
- // React bindings types
520
- import type {
521
- SeamlessRoleplayProviderProps,
522
- RoleplayDialogProps,
523
- RoleplayEmbedProps,
524
- AddToScenarioDialogProps,
525
- AddToScenarioContact,
526
- AddToScenarioCompleteData,
527
- } from "@rehers/rehers-roleplay-sdk/react";
236
+ // Cleanup
237
+ SeamlessRoleplay.close(); // close dialog
238
+ SeamlessRoleplay.unmount(); // remove embed
239
+ SeamlessRoleplay.destroy(); // full cleanup
528
240
  ```
package/index.d.ts CHANGED
@@ -30,10 +30,6 @@ export interface SeamlessRoleplayOpenData {
30
30
  companyDescription?: string;
31
31
  /** Optional LinkedIn profile URL */
32
32
  liUrl?: string;
33
- /** Called when the roleplay call starts */
34
- onCallStarted?: (data: { callId: string }) => void;
35
- /** Called when the roleplay call ends */
36
- onCallEnded?: (data: { callId: string; duration?: number }) => void;
37
33
  /** Called when the dialog/mount is closed */
38
34
  onClose?: () => void;
39
35
  /** Called on error during the session */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rehers/rehers-roleplay-sdk",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
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/react.d.ts CHANGED
@@ -34,37 +34,18 @@ export interface RoleplayDialogProps {
34
34
  title: string;
35
35
  companyDescription?: string;
36
36
  liUrl?: string;
37
- onCallStarted?: (data: {
38
- callId: string;
39
- }) => void;
40
- onCallEnded?: (data: {
41
- callId: string;
42
- duration?: number;
43
- }) => void;
44
37
  onClose?: () => void;
45
38
  onError?: (data: {
46
39
  code: string;
47
40
  message: string;
48
41
  }) => void;
49
42
  }
50
- export declare function RoleplayDialog({ open: isOpen, name, domain, company, title, companyDescription, liUrl, onCallStarted, onCallEnded, onClose, onError, }: RoleplayDialogProps): null;
43
+ export declare function RoleplayDialog({ open: isOpen, name, domain, company, title, companyDescription, liUrl, onClose, onError, }: RoleplayDialogProps): null;
51
44
  export interface RoleplayEmbedProps {
52
45
  className?: string;
53
46
  style?: React.CSSProperties;
54
- onCallStarted?: (data: {
55
- callId: string;
56
- }) => void;
57
- onCallEnded?: (data: {
58
- callId: string;
59
- duration?: number;
60
- }) => void;
61
- onClose?: () => void;
62
- onError?: (data: {
63
- code: string;
64
- message: string;
65
- }) => void;
66
47
  }
67
- export declare function RoleplayEmbed({ className, style, onCallStarted, onCallEnded, onClose, onError, }: RoleplayEmbedProps): import("react/jsx-runtime").JSX.Element;
48
+ export declare function RoleplayEmbed({ className, style }: RoleplayEmbedProps): import("react/jsx-runtime").JSX.Element;
68
49
  export interface AddToScenarioDialogProps {
69
50
  open: boolean;
70
51
  contacts: AddToScenarioContact[];
package/react.js CHANGED
@@ -75,10 +75,8 @@ export function useSeamlessRoleplay() {
75
75
  }
76
76
  return ctx;
77
77
  }
78
- export function RoleplayDialog({ open: isOpen, name, domain, company, title, companyDescription, liUrl, onCallStarted, onCallEnded, onClose, onError, }) {
78
+ export function RoleplayDialog({ open: isOpen, name, domain, company, title, companyDescription, liUrl, onClose, onError, }) {
79
79
  const { isReady, sdk } = useSeamlessRoleplay();
80
- const onCallStartedRef = useCallbackRef(onCallStarted);
81
- const onCallEndedRef = useCallbackRef(onCallEnded);
82
80
  const onCloseRef = useCallbackRef(onClose);
83
81
  const onErrorRef = useCallbackRef(onError);
84
82
  const isOpenRef = useRef(false);
@@ -94,8 +92,6 @@ export function RoleplayDialog({ open: isOpen, name, domain, company, title, com
94
92
  title,
95
93
  companyDescription,
96
94
  liUrl,
97
- onCallStarted: (data) => { var _a; return (_a = onCallStartedRef.current) === null || _a === void 0 ? void 0 : _a.call(onCallStartedRef, data); },
98
- onCallEnded: (data) => { var _a; return (_a = onCallEndedRef.current) === null || _a === void 0 ? void 0 : _a.call(onCallEndedRef, data); },
99
95
  onClose: () => {
100
96
  var _a;
101
97
  isOpenRef.current = false;
@@ -117,63 +113,14 @@ export function RoleplayDialog({ open: isOpen, name, domain, company, title, com
117
113
  }, [isReady, isOpen, name, domain, company, title, companyDescription, liUrl, sdk]);
118
114
  return null;
119
115
  }
120
- // ── RoleplayEmbed ───────────────────────────────────────────────────
121
- // The vanilla SDK's mount() doesn't accept callbacks, so RoleplayEmbed
122
- // sets up its own postMessage listener after mount() appends the iframe.
123
- const DEFAULT_APP_ORIGIN = "https://app.roleplaywithseamless.ai";
124
- export function RoleplayEmbed({ className, style, onCallStarted, onCallEnded, onClose, onError, }) {
116
+ export function RoleplayEmbed({ className, style }) {
125
117
  const { isReady, sdk } = useSeamlessRoleplay();
126
118
  const containerRef = useRef(null);
127
- const onCallStartedRef = useCallbackRef(onCallStarted);
128
- const onCallEndedRef = useCallbackRef(onCallEnded);
129
- const onCloseRef = useCallbackRef(onClose);
130
- const onErrorRef = useCallbackRef(onError);
131
119
  useEffect(() => {
132
120
  if (!isReady || !containerRef.current)
133
121
  return;
134
122
  sdk.mount(containerRef.current);
135
- // Grab the iframe that mount() just appended
136
- const iframe = containerRef.current.querySelector("iframe");
137
- if (!iframe)
138
- return;
139
- const hasCallbacks = onCallStarted || onCallEnded || onClose || onError;
140
- let listener = null;
141
- if (hasCallbacks) {
142
- listener = (event) => {
143
- var _a, _b, _c, _d;
144
- if (event.origin !== DEFAULT_APP_ORIGIN)
145
- return;
146
- if (!iframe.contentWindow || event.source !== iframe.contentWindow)
147
- return;
148
- const data = event.data;
149
- if (!data || typeof data.type !== "string")
150
- return;
151
- switch (data.type) {
152
- case "ROLEPLAY_CALL_STARTED":
153
- (_a = onCallStartedRef.current) === null || _a === void 0 ? void 0 : _a.call(onCallStartedRef, { callId: data.callId });
154
- break;
155
- case "ROLEPLAY_CALL_ENDED":
156
- (_b = onCallEndedRef.current) === null || _b === void 0 ? void 0 : _b.call(onCallEndedRef, {
157
- callId: data.callId,
158
- duration: data.duration,
159
- });
160
- break;
161
- case "ROLEPLAY_ERROR":
162
- (_c = onErrorRef.current) === null || _c === void 0 ? void 0 : _c.call(onErrorRef, {
163
- code: data.code,
164
- message: data.message,
165
- });
166
- break;
167
- case "ROLEPLAY_CLOSED":
168
- (_d = onCloseRef.current) === null || _d === void 0 ? void 0 : _d.call(onCloseRef);
169
- break;
170
- }
171
- };
172
- window.addEventListener("message", listener);
173
- }
174
123
  return () => {
175
- if (listener)
176
- window.removeEventListener("message", listener);
177
124
  sdk.unmount();
178
125
  };
179
126
  }, [isReady, sdk]);
package/roleplay-sdk.js CHANGED
@@ -35,7 +35,7 @@
35
35
  // ── Mount state (persistent embed — survives dialog open/close) ───
36
36
  var mountIframe = null;
37
37
  var mountContainer = null;
38
- var mountCallbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
38
+ var mountCallbacks = { onClose: null, onError: null };
39
39
  var mountListener = null;
40
40
 
41
41
  // ── Dialog state (overlay — dialog or add-to-scenario) ────────────
@@ -43,7 +43,7 @@
43
43
  var dialogIframe = null;
44
44
  var dialogMode = null; // "dialog" | "add-to-scenario"
45
45
  var dialogContactData = null;
46
- var dialogCallbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
46
+ var dialogCallbacks = { onClose: null, onError: null };
47
47
  var dialogAddToScenarioCallbacks = { onComplete: null, onClose: null, onError: null };
48
48
  var dialogAddToScenarioPendingContacts = null;
49
49
  var dialogListener = null;
@@ -199,7 +199,7 @@
199
199
  dialogContactData = null;
200
200
  dialogAddToScenarioPendingContacts = null;
201
201
  dialogMode = null;
202
- dialogCallbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
202
+ dialogCallbacks = { onClose: null, onError: null };
203
203
  dialogAddToScenarioCallbacks = { onComplete: null, onClose: null, onError: null };
204
204
  }
205
205
 
@@ -218,7 +218,7 @@
218
218
 
219
219
  mountIframe = null;
220
220
  mountContainer = null;
221
- mountCallbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
221
+ mountCallbacks = { onClose: null, onError: null };
222
222
  }
223
223
 
224
224
  // ── Message dispatch ──────────────────────────────────────────────
@@ -284,18 +284,6 @@
284
284
  });
285
285
  break;
286
286
 
287
- case "ROLEPLAY_CALL_STARTED":
288
- if (mountCallbacks.onCallStarted) {
289
- mountCallbacks.onCallStarted({ callId: data.callId });
290
- }
291
- break;
292
-
293
- case "ROLEPLAY_CALL_ENDED":
294
- if (mountCallbacks.onCallEnded) {
295
- mountCallbacks.onCallEnded({ callId: data.callId, duration: data.duration });
296
- }
297
- break;
298
-
299
287
  case "ROLEPLAY_ERROR":
300
288
  if (mountCallbacks.onError) {
301
289
  mountCallbacks.onError({ code: data.code, message: data.message });
@@ -334,18 +322,6 @@
334
322
  });
335
323
  break;
336
324
 
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
325
  case "ROLEPLAY_ERROR":
350
326
  if (dialogCallbacks.onError) {
351
327
  dialogCallbacks.onError({ code: data.code, message: data.message });
@@ -486,8 +462,6 @@
486
462
  if (dialogOverlay || dialogIframe) teardownDialog();
487
463
 
488
464
  dialogContactData = data;
489
- dialogCallbacks.onCallStarted = data.onCallStarted || null;
490
- dialogCallbacks.onCallEnded = data.onCallEnded || null;
491
465
  dialogCallbacks.onClose = data.onClose || null;
492
466
  dialogCallbacks.onError = data.onError || null;
493
467
  dialogMode = "dialog";
@@ -585,7 +559,7 @@
585
559
  // Tear down any existing mount (re-mount)
586
560
  if (mountIframe) teardownMount();
587
561
 
588
- mountCallbacks = { onCallStarted: null, onCallEnded: null, onClose: null, onError: null };
562
+ mountCallbacks = { onClose: null, onError: null };
589
563
  mountContainer = container;
590
564
 
591
565
  // Listen for messages