@rehers/rehers-roleplay-sdk 2.4.1 → 2.5.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.
Files changed (4) hide show
  1. package/README.md +285 -26
  2. package/package.json +23 -3
  3. package/react.d.ts +78 -0
  4. package/react.js +224 -0
package/README.md CHANGED
@@ -9,21 +9,274 @@ Use it in these two places:
9
9
 
10
10
  The SDK must be initialized with the currently logged-in Seamless user before either embed flow is used.
11
11
 
12
- ## Load the SDK
12
+ ## Install
13
13
 
14
- If Seamless is using the downloaded SDK file directly:
14
+ ```bash
15
+ npm install @rehers/rehers-roleplay-sdk
16
+ ```
17
+
18
+ ---
19
+
20
+ ## React Integration (Recommended)
21
+
22
+ If the Seamless dashboard is built with React, use the React bindings. They handle SDK lifecycle, cleanup, and stale closures automatically.
23
+
24
+ ```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
58
+
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
+ function App() {
63
+ return (
64
+ <SeamlessRoleplayProvider
65
+ publishableKey={ROLEPLAY_PUBLISHABLE_KEY}
66
+ userId={seamlessUserId}
67
+ userEmail={seamlessUserEmail}
68
+ userRole={seamlessUserRole}
69
+ onReady={() => console.log("Roleplay SDK ready")}
70
+ onError={(err) => console.error("Roleplay SDK error", err)}
71
+ >
72
+ <Dashboard />
73
+ </SeamlessRoleplayProvider>
74
+ );
75
+ }
76
+ ```
77
+
78
+ ### Embed the full Roleplay page
79
+
80
+ Use `RoleplayEmbed` when Seamless has a dedicated Roleplay page or tab and the full content area should be the Roleplay app.
81
+
82
+ ```tsx
83
+ function RoleplayPage() {
84
+ 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
+ />
91
+ );
92
+ }
93
+ ```
94
+
95
+ The embed mounts when the component mounts and cleans up automatically when it unmounts. No manual teardown needed.
96
+
97
+ ### Open a dialog from Contact Search
98
+
99
+ Use `RoleplayDialog` when the user clicks a `Roleplay` button from a single contact row on the Contact Search screen.
100
+
101
+ ```tsx
102
+ function ContactRow({ contact }) {
103
+ const [showRoleplay, setShowRoleplay] = useState(false);
104
+
105
+ return (
106
+ <>
107
+ <button onClick={() => setShowRoleplay(true)}>Roleplay</button>
108
+
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
+ }}
127
+ />
128
+ </>
129
+ );
130
+ }
131
+ ```
132
+
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.
134
+
135
+ ### Add contacts to a scenario
136
+
137
+ Use `AddToScenarioDialog` to send multiple contacts into a scenario picker dialog.
138
+
139
+ ```tsx
140
+ function BulkActions({ selectedContacts }) {
141
+ const [showATS, setShowATS] = useState(false);
142
+
143
+ return (
144
+ <>
145
+ <button onClick={() => setShowATS(true)}>Add to Scenario</button>
146
+
147
+ <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,
156
+ }))}
157
+ 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);
164
+ }}
165
+ />
166
+ </>
167
+ );
168
+ }
169
+ ```
170
+
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`.
252
+
253
+ ---
254
+
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
262
+
263
+ ```js
264
+ import SeamlessRoleplay from "@rehers/rehers-roleplay-sdk";
265
+ ```
266
+
267
+ #### Option 2: Script tag (hosted file)
15
268
 
16
269
  ```html
17
270
  <script src="/path/to/roleplay-sdk.js"></script>
18
271
  ```
19
272
 
20
- If Seamless is using the npm package:
273
+ #### Option 3: CDN script
21
274
 
22
- ```bash
23
- npm install @rehers/rehers-roleplay-sdk
275
+ ```html
276
+ <script src="https://unpkg.com/@rehers/rehers-roleplay-sdk"></script>
24
277
  ```
25
278
 
26
- ## Get the logged-in Seamless user
279
+ ### Get the logged-in Seamless user
27
280
 
28
281
  Use the existing Seamless dashboard session and call:
29
282
 
@@ -43,7 +296,7 @@ Normalize the response before reading fields:
43
296
  const me = meResponse.data ?? meResponse;
44
297
  ```
45
298
 
46
- ## Required mapping for `init()`
299
+ ### Required mapping for `init()`
47
300
 
48
301
  These are the values Seamless must pass into `SeamlessRoleplay.init(...)`:
49
302
 
@@ -64,14 +317,7 @@ const seamlessUserRole =
64
317
  "member";
65
318
  ```
66
319
 
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
320
+ ### Initialize the SDK once
75
321
 
76
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.
77
323
 
@@ -115,7 +361,7 @@ function ensureRoleplaySdkReady() {
115
361
  }
116
362
  ```
117
363
 
118
- ## Embed in the full Roleplay page
364
+ ### Embed in the full Roleplay page
119
365
 
120
366
  Use `mount(container)` when Seamless has a dedicated Roleplay page or tab and the full content area should be the Roleplay app.
121
367
 
@@ -138,7 +384,7 @@ If the Seamless page or tab is torn down, unmount the SDK:
138
384
  SeamlessRoleplay.unmount();
139
385
  ```
140
386
 
141
- ## Embed in Contact Search
387
+ ### Embed in Contact Search
142
388
 
143
389
  Use `open(contactData)` when the user clicks a `Roleplay` button from a single contact row on the Contact Search screen.
144
390
 
@@ -180,7 +426,7 @@ The contact object passed to `open(...)` should map to:
180
426
  | `liUrl` | LinkedIn profile URL, if available |
181
427
  | `companyDescription` | Company description, if available |
182
428
 
183
- ## Optional: add contacts to a scenario
429
+ ### Add contacts to a scenario
184
430
 
185
431
  If Seamless wants to send multiple contacts into a scenario picker dialog, use `addToScenario(...)`.
186
432
 
@@ -210,9 +456,9 @@ async function addContactsToScenario(contacts) {
210
456
  }
211
457
  ```
212
458
 
213
- ## Minimal API surface
459
+ ### API reference
214
460
 
215
- ### `SeamlessRoleplay.init(options)`
461
+ #### `SeamlessRoleplay.init(options)`
216
462
 
217
463
  Initializes the SDK with the logged-in Seamless user.
218
464
 
@@ -227,30 +473,32 @@ SeamlessRoleplay.init({
227
473
  });
228
474
  ```
229
475
 
230
- ### `SeamlessRoleplay.mount(container)`
476
+ #### `SeamlessRoleplay.mount(container)`
231
477
 
232
478
  Mounts the full Roleplay app into a dashboard container.
233
479
 
234
- ### `SeamlessRoleplay.open(contactData)`
480
+ #### `SeamlessRoleplay.open(contactData)`
235
481
 
236
482
  Opens the Roleplay dialog for a single contact.
237
483
 
238
- ### `SeamlessRoleplay.addToScenario(options)`
484
+ #### `SeamlessRoleplay.addToScenario(options)`
239
485
 
240
486
  Opens the bulk add-to-scenario dialog for 1 to 25 contacts.
241
487
 
242
- ### `SeamlessRoleplay.close()`
488
+ #### `SeamlessRoleplay.close()`
243
489
 
244
490
  Closes the active dialog.
245
491
 
246
- ### `SeamlessRoleplay.unmount()`
492
+ #### `SeamlessRoleplay.unmount()`
247
493
 
248
494
  Unmounts the full-page Roleplay embed.
249
495
 
250
- ### `SeamlessRoleplay.destroy()`
496
+ #### `SeamlessRoleplay.destroy()`
251
497
 
252
498
  Destroys the SDK state, timers, mount, and dialogs.
253
499
 
500
+ ---
501
+
254
502
  ## Trial and upgrade handling
255
503
 
256
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.
@@ -260,10 +508,21 @@ If the backend responds with `USER_NOT_FOUND` during SDK session creation, the S
260
508
  Type declarations are included with the SDK package.
261
509
 
262
510
  ```ts
511
+ // Vanilla SDK types
263
512
  import type {
264
513
  SeamlessRoleplaySDK,
265
514
  SeamlessRoleplayInitOptions,
266
515
  SeamlessRoleplayOpenData,
267
516
  AddToScenarioOptions,
268
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";
269
528
  ```
package/package.json CHANGED
@@ -1,14 +1,34 @@
1
1
  {
2
2
  "name": "@rehers/rehers-roleplay-sdk",
3
- "version": "2.4.1",
3
+ "version": "2.5.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",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./index.d.ts",
10
+ "default": "./roleplay-sdk.js"
11
+ },
12
+ "./react": {
13
+ "types": "./react.d.ts",
14
+ "default": "./react.js"
15
+ }
16
+ },
7
17
  "files": [
8
18
  "roleplay-sdk.js",
9
- "index.d.ts"
19
+ "index.d.ts",
20
+ "react.js",
21
+ "react.d.ts"
10
22
  ],
11
- "keywords": ["seamless", "roleplay", "sdk", "sales", "training"],
23
+ "peerDependencies": {
24
+ "react": ">=18.0.0",
25
+ "react-dom": ">=18.0.0"
26
+ },
27
+ "peerDependenciesMeta": {
28
+ "react": { "optional": true },
29
+ "react-dom": { "optional": true }
30
+ },
31
+ "keywords": ["seamless", "roleplay", "sdk", "sales", "training", "react"],
12
32
  "license": "UNLICENSED",
13
33
  "repository": {
14
34
  "type": "git",
package/react.d.ts ADDED
@@ -0,0 +1,78 @@
1
+ import { type ReactNode } from "react";
2
+ import type { SeamlessRoleplaySDK, AddToScenarioContact, AddToScenarioCompleteData } from "@rehers/rehers-roleplay-sdk";
3
+ import "@rehers/rehers-roleplay-sdk";
4
+ export type { AddToScenarioContact, AddToScenarioCompleteData };
5
+ interface SeamlessRoleplayContextValue {
6
+ isReady: boolean;
7
+ error: {
8
+ code: string;
9
+ message: string;
10
+ } | null;
11
+ sdk: SeamlessRoleplaySDK;
12
+ }
13
+ export interface SeamlessRoleplayProviderProps {
14
+ publishableKey: string;
15
+ userId: string;
16
+ userEmail: string;
17
+ userRole?: "owner" | "admin" | "member";
18
+ userToken?: string;
19
+ origin?: string;
20
+ onReady?: () => void;
21
+ onError?: (error: {
22
+ code: string;
23
+ message: string;
24
+ }) => void;
25
+ children: ReactNode;
26
+ }
27
+ export declare function SeamlessRoleplayProvider({ publishableKey, userId, userEmail, userRole, userToken, origin, onReady, onError, children, }: SeamlessRoleplayProviderProps): import("react/jsx-runtime").JSX.Element;
28
+ export declare function useSeamlessRoleplay(): SeamlessRoleplayContextValue;
29
+ export interface RoleplayDialogProps {
30
+ open: boolean;
31
+ name: string;
32
+ domain: string;
33
+ company: string;
34
+ title: string;
35
+ companyDescription?: string;
36
+ liUrl?: string;
37
+ onCallStarted?: (data: {
38
+ callId: string;
39
+ }) => void;
40
+ onCallEnded?: (data: {
41
+ callId: string;
42
+ duration?: number;
43
+ }) => void;
44
+ onClose?: () => void;
45
+ onError?: (data: {
46
+ code: string;
47
+ message: string;
48
+ }) => void;
49
+ }
50
+ export declare function RoleplayDialog({ open: isOpen, name, domain, company, title, companyDescription, liUrl, onCallStarted, onCallEnded, onClose, onError, }: RoleplayDialogProps): null;
51
+ export interface RoleplayEmbedProps {
52
+ className?: string;
53
+ 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
+ }
67
+ export declare function RoleplayEmbed({ className, style, onCallStarted, onCallEnded, onClose, onError, }: RoleplayEmbedProps): import("react/jsx-runtime").JSX.Element;
68
+ export interface AddToScenarioDialogProps {
69
+ open: boolean;
70
+ contacts: AddToScenarioContact[];
71
+ onComplete?: (data: AddToScenarioCompleteData) => void;
72
+ onClose?: () => void;
73
+ onError?: (error: {
74
+ code: string;
75
+ message: string;
76
+ }) => void;
77
+ }
78
+ export declare function AddToScenarioDialog({ open: isOpen, contacts, onComplete, onClose, onError, }: AddToScenarioDialogProps): null;
package/react.js ADDED
@@ -0,0 +1,224 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
3
+ // Side-effect import: the SDK IIFE runs and sets window.SeamlessRoleplay.
4
+ // Works with bundlers (Vite, webpack) and script tags alike.
5
+ import "@rehers/rehers-roleplay-sdk";
6
+ // ── Callback ref helper ─────────────────────────────────────────────
7
+ // Keeps a ref in sync with the latest callback to avoid stale closures.
8
+ function useCallbackRef(cb) {
9
+ const ref = useRef(cb);
10
+ useLayoutEffect(() => {
11
+ ref.current = cb;
12
+ });
13
+ return ref;
14
+ }
15
+ // ── SDK singleton access ────────────────────────────────────────────
16
+ function getSDK() {
17
+ if (typeof window !== "undefined" && window.SeamlessRoleplay) {
18
+ return window.SeamlessRoleplay;
19
+ }
20
+ throw new Error("[SeamlessRoleplay/React] Could not find SeamlessRoleplay SDK. " +
21
+ 'Make sure "@rehers/rehers-roleplay-sdk" is installed and loaded before the React wrapper.');
22
+ }
23
+ const SeamlessRoleplayContext = createContext(null);
24
+ let providerMountCount = 0;
25
+ export function SeamlessRoleplayProvider({ publishableKey, userId, userEmail, userRole, userToken, origin, onReady, onError, children, }) {
26
+ const [state, setState] = useState({ isReady: false, error: null });
27
+ const mountedRef = useRef(false);
28
+ const onReadyRef = useCallbackRef(onReady);
29
+ const onErrorRef = useCallbackRef(onError);
30
+ const sdk = useMemo(() => getSDK(), []);
31
+ useEffect(() => {
32
+ providerMountCount++;
33
+ if (providerMountCount > 1 && process.env.NODE_ENV !== "production") {
34
+ console.warn("[SeamlessRoleplay/React] Multiple SeamlessRoleplayProvider instances detected. " +
35
+ "The SDK is a singleton — only one Provider should be mounted at a time.");
36
+ }
37
+ mountedRef.current = true;
38
+ setState({ isReady: false, error: null });
39
+ sdk.init({
40
+ publishableKey,
41
+ userId,
42
+ userEmail,
43
+ userRole,
44
+ userToken,
45
+ origin,
46
+ onReady: () => {
47
+ var _a;
48
+ if (!mountedRef.current)
49
+ return;
50
+ setState({ isReady: true, error: null });
51
+ (_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef);
52
+ },
53
+ onError: (err) => {
54
+ var _a;
55
+ if (!mountedRef.current)
56
+ return;
57
+ setState({ isReady: false, error: err });
58
+ (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, err);
59
+ },
60
+ });
61
+ return () => {
62
+ mountedRef.current = false;
63
+ providerMountCount--;
64
+ sdk.destroy();
65
+ };
66
+ }, [sdk, publishableKey, userId, userEmail, userRole, userToken, origin]);
67
+ const contextValue = useMemo(() => ({ ...state, sdk }), [state, sdk]);
68
+ return (_jsx(SeamlessRoleplayContext.Provider, { value: contextValue, children: children }));
69
+ }
70
+ // ── Hook ────────────────────────────────────────────────────────────
71
+ export function useSeamlessRoleplay() {
72
+ const ctx = useContext(SeamlessRoleplayContext);
73
+ if (!ctx) {
74
+ throw new Error("[SeamlessRoleplay/React] useSeamlessRoleplay() must be used inside a <SeamlessRoleplayProvider>.");
75
+ }
76
+ return ctx;
77
+ }
78
+ export function RoleplayDialog({ open: isOpen, name, domain, company, title, companyDescription, liUrl, onCallStarted, onCallEnded, onClose, onError, }) {
79
+ const { isReady, sdk } = useSeamlessRoleplay();
80
+ const onCallStartedRef = useCallbackRef(onCallStarted);
81
+ const onCallEndedRef = useCallbackRef(onCallEnded);
82
+ const onCloseRef = useCallbackRef(onClose);
83
+ const onErrorRef = useCallbackRef(onError);
84
+ const isOpenRef = useRef(false);
85
+ useEffect(() => {
86
+ if (!isReady)
87
+ return;
88
+ if (isOpen) {
89
+ isOpenRef.current = true;
90
+ sdk.open({
91
+ name,
92
+ domain,
93
+ company,
94
+ title,
95
+ companyDescription,
96
+ 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
+ onClose: () => {
100
+ var _a;
101
+ isOpenRef.current = false;
102
+ (_a = onCloseRef.current) === null || _a === void 0 ? void 0 : _a.call(onCloseRef);
103
+ },
104
+ onError: (data) => { var _a; return (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, data); },
105
+ });
106
+ }
107
+ else if (isOpenRef.current) {
108
+ isOpenRef.current = false;
109
+ sdk.close();
110
+ }
111
+ return () => {
112
+ if (isOpenRef.current) {
113
+ isOpenRef.current = false;
114
+ sdk.close();
115
+ }
116
+ };
117
+ }, [isReady, isOpen, name, domain, company, title, companyDescription, liUrl, sdk]);
118
+ return null;
119
+ }
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, }) {
125
+ const { isReady, sdk } = useSeamlessRoleplay();
126
+ 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
+ useEffect(() => {
132
+ if (!isReady || !containerRef.current)
133
+ return;
134
+ 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
+ return () => {
175
+ if (listener)
176
+ window.removeEventListener("message", listener);
177
+ sdk.unmount();
178
+ };
179
+ }, [isReady, sdk]);
180
+ return _jsx("div", { ref: containerRef, className: className, style: style });
181
+ }
182
+ export function AddToScenarioDialog({ open: isOpen, contacts, onComplete, onClose, onError, }) {
183
+ const { isReady, sdk } = useSeamlessRoleplay();
184
+ const onCompleteRef = useCallbackRef(onComplete);
185
+ const onCloseRef = useCallbackRef(onClose);
186
+ const onErrorRef = useCallbackRef(onError);
187
+ const isOpenRef = useRef(false);
188
+ const contactsRef = useRef(contacts);
189
+ useLayoutEffect(() => {
190
+ contactsRef.current = contacts;
191
+ });
192
+ useEffect(() => {
193
+ if (!isReady)
194
+ return;
195
+ if (isOpen) {
196
+ isOpenRef.current = true;
197
+ sdk.addToScenario({
198
+ contacts: contactsRef.current,
199
+ onComplete: (data) => {
200
+ var _a;
201
+ isOpenRef.current = false;
202
+ (_a = onCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onCompleteRef, data);
203
+ },
204
+ onClose: () => {
205
+ var _a;
206
+ isOpenRef.current = false;
207
+ (_a = onCloseRef.current) === null || _a === void 0 ? void 0 : _a.call(onCloseRef);
208
+ },
209
+ onError: (err) => { var _a; return (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, err); },
210
+ });
211
+ }
212
+ else if (isOpenRef.current) {
213
+ isOpenRef.current = false;
214
+ sdk.close();
215
+ }
216
+ return () => {
217
+ if (isOpenRef.current) {
218
+ isOpenRef.current = false;
219
+ sdk.close();
220
+ }
221
+ };
222
+ }, [isReady, isOpen, sdk]);
223
+ return null;
224
+ }