@rehers/rehers-roleplay-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -0
- package/index.d.ts +47 -0
- package/package.json +17 -0
- package/roleplay-sdk.js +289 -0
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# @rehers/seamless-sdk
|
|
2
|
+
|
|
3
|
+
Lightweight vanilla JS SDK for embedding Seamless roleplay call sessions. Opens a modal with an iframe — no framework dependencies.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rehers/seamless-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or load via CDN:
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<script src="https://unpkg.com/@rehers/seamless-sdk"></script>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<script src="https://unpkg.com/@rehers/seamless-sdk"></script>
|
|
21
|
+
<script>
|
|
22
|
+
// 1. Initialize with a login pass from your backend
|
|
23
|
+
SeamlessRoleplay.init({
|
|
24
|
+
loginPass: 'lp_abc123...',
|
|
25
|
+
onCallEnd: function (data) {
|
|
26
|
+
console.log('Call ended:', data.callId, data.duration);
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// 2. Open the roleplay modal for a contact
|
|
31
|
+
SeamlessRoleplay.open({
|
|
32
|
+
contactName: 'Jane Smith',
|
|
33
|
+
contactCompany: 'Acme Corp',
|
|
34
|
+
contactTitle: 'VP of Sales',
|
|
35
|
+
linkedinUrl: 'https://linkedin.com/in/jane-smith', // optional
|
|
36
|
+
scenarioId: 'sc_123', // optional — omit to show scenario picker
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// 3. Close programmatically (user can also close via X button)
|
|
40
|
+
SeamlessRoleplay.close();
|
|
41
|
+
|
|
42
|
+
// 4. Full cleanup when done
|
|
43
|
+
SeamlessRoleplay.destroy();
|
|
44
|
+
</script>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## API
|
|
48
|
+
|
|
49
|
+
### `SeamlessRoleplay.init(options)`
|
|
50
|
+
|
|
51
|
+
| Option | Type | Required | Description |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| `loginPass` | `string` | Yes | Seamless login pass obtained from your backend |
|
|
54
|
+
| `appOrigin` | `string` | No | Override app origin (defaults to production) |
|
|
55
|
+
| `onClose` | `() => void` | No | Called when modal closes |
|
|
56
|
+
| `onCallStart` | `({ callId }) => void` | No | Called when a call begins |
|
|
57
|
+
| `onCallEnd` | `({ callId, duration? }) => void` | No | Called when a call ends |
|
|
58
|
+
| `onError` | `({ code, message }) => void` | No | Called on error |
|
|
59
|
+
|
|
60
|
+
### `SeamlessRoleplay.open(contactData)`
|
|
61
|
+
|
|
62
|
+
| Field | Type | Required | Description |
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| `contactName` | `string` | Yes | Full name of the contact |
|
|
65
|
+
| `contactCompany` | `string` | Yes | Company name |
|
|
66
|
+
| `contactTitle` | `string` | Yes | Job title |
|
|
67
|
+
| `linkedinUrl` | `string` | No | LinkedIn profile URL |
|
|
68
|
+
| `scenarioId` | `string` | No | Auto-select a scenario (omit to show picker) |
|
|
69
|
+
|
|
70
|
+
### `SeamlessRoleplay.close()`
|
|
71
|
+
|
|
72
|
+
Closes the modal and notifies the iframe to end any active call.
|
|
73
|
+
|
|
74
|
+
### `SeamlessRoleplay.destroy()`
|
|
75
|
+
|
|
76
|
+
Full cleanup — removes all DOM elements and event listeners.
|
|
77
|
+
|
|
78
|
+
## TypeScript
|
|
79
|
+
|
|
80
|
+
Full type declarations are included. Import types with:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import type { SeamlessRoleplaySDK, SeamlessRoleplayInitOptions, SeamlessRoleplayContactData } from '@rehers/seamless-sdk';
|
|
84
|
+
```
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface SeamlessRoleplayInitOptions {
|
|
2
|
+
/** Seamless login pass for authentication */
|
|
3
|
+
loginPass: string;
|
|
4
|
+
/** Override the app origin (default: production URL) */
|
|
5
|
+
appOrigin?: string;
|
|
6
|
+
/** Called when the modal is closed */
|
|
7
|
+
onClose?: () => void;
|
|
8
|
+
/** Called when a call begins */
|
|
9
|
+
onCallStart?: (data: { callId: string }) => void;
|
|
10
|
+
/** Called when a call ends */
|
|
11
|
+
onCallEnd?: (data: { callId: string; duration?: number }) => void;
|
|
12
|
+
/** Called on error */
|
|
13
|
+
onError?: (data: { code: string; message: string }) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SeamlessRoleplayContactData {
|
|
17
|
+
/** Full name of the contact */
|
|
18
|
+
contactName: string;
|
|
19
|
+
/** Company of the contact */
|
|
20
|
+
contactCompany: string;
|
|
21
|
+
/** Job title of the contact */
|
|
22
|
+
contactTitle: string;
|
|
23
|
+
/** Optional LinkedIn profile URL */
|
|
24
|
+
linkedinUrl?: string;
|
|
25
|
+
/** Optional scenario ID to auto-select */
|
|
26
|
+
scenarioId?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SeamlessRoleplaySDK {
|
|
30
|
+
/** Initialize the SDK. Must be called before open(). */
|
|
31
|
+
init(options: SeamlessRoleplayInitOptions): void;
|
|
32
|
+
/** Open the roleplay modal with contact data. */
|
|
33
|
+
open(data: SeamlessRoleplayContactData): void;
|
|
34
|
+
/** Close the modal and notify the iframe. */
|
|
35
|
+
close(): void;
|
|
36
|
+
/** Full cleanup — remove DOM and event listeners. */
|
|
37
|
+
destroy(): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
declare global {
|
|
41
|
+
interface Window {
|
|
42
|
+
SeamlessRoleplay: SeamlessRoleplaySDK;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
declare const SeamlessRoleplay: SeamlessRoleplaySDK;
|
|
47
|
+
export default SeamlessRoleplay;
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rehers/rehers-roleplay-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Seamless Roleplay SDK — embed roleplay call sessions via a modal + iframe",
|
|
5
|
+
"main": "roleplay-sdk.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"roleplay-sdk.js",
|
|
9
|
+
"index.d.ts"
|
|
10
|
+
],
|
|
11
|
+
"keywords": ["seamless", "roleplay", "sdk", "sales", "training"],
|
|
12
|
+
"license": "UNLICENSED",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/rehers/seamless-frontend-independent"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/roleplay-sdk.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SeamlessRoleplay SDK
|
|
3
|
+
* Lightweight vanilla JS SDK for embedding roleplay call sessions.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* SeamlessRoleplay.init({ loginPass: '...', onCallEnd: (data) => {} });
|
|
7
|
+
* SeamlessRoleplay.open({ contactName: '...', contactCompany: '...', contactTitle: '...' });
|
|
8
|
+
* SeamlessRoleplay.close();
|
|
9
|
+
* SeamlessRoleplay.destroy();
|
|
10
|
+
*/
|
|
11
|
+
(function () {
|
|
12
|
+
"use strict";
|
|
13
|
+
|
|
14
|
+
var DEFAULT_APP_ORIGIN = "https://roleplaywithseamless.ai";
|
|
15
|
+
|
|
16
|
+
var config = null;
|
|
17
|
+
var overlay = null;
|
|
18
|
+
var iframe = null;
|
|
19
|
+
var pendingContactData = null;
|
|
20
|
+
var iframeAuthenticated = false;
|
|
21
|
+
var globalListener = null;
|
|
22
|
+
|
|
23
|
+
function resolveOrigin() {
|
|
24
|
+
return (config && config.appOrigin) || DEFAULT_APP_ORIGIN;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function sendToIframe(msg) {
|
|
28
|
+
if (iframe && iframe.contentWindow) {
|
|
29
|
+
iframe.contentWindow.postMessage(msg, resolveOrigin());
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function handleMessage(event) {
|
|
34
|
+
if (!config) return;
|
|
35
|
+
var origin = resolveOrigin();
|
|
36
|
+
// Allow messages from the configured app origin
|
|
37
|
+
if (event.origin !== origin) return;
|
|
38
|
+
|
|
39
|
+
var data = event.data;
|
|
40
|
+
if (!data || typeof data.type !== "string") return;
|
|
41
|
+
|
|
42
|
+
switch (data.type) {
|
|
43
|
+
case "ROLEPLAY_READY":
|
|
44
|
+
// Iframe is loaded — send the login pass
|
|
45
|
+
if (config.loginPass) {
|
|
46
|
+
sendToIframe({
|
|
47
|
+
type: "seamless-login-pass",
|
|
48
|
+
loginPass: config.loginPass,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
|
|
53
|
+
case "ROLEPLAY_EMBED_AUTHENTICATED":
|
|
54
|
+
iframeAuthenticated = true;
|
|
55
|
+
// Send any pending contact data
|
|
56
|
+
if (pendingContactData) {
|
|
57
|
+
sendToIframe({
|
|
58
|
+
type: "roleplay-contact-data",
|
|
59
|
+
contactName: pendingContactData.contactName,
|
|
60
|
+
contactCompany: pendingContactData.contactCompany,
|
|
61
|
+
contactTitle: pendingContactData.contactTitle,
|
|
62
|
+
linkedinUrl: pendingContactData.linkedinUrl || undefined,
|
|
63
|
+
scenarioId: pendingContactData.scenarioId || undefined,
|
|
64
|
+
});
|
|
65
|
+
pendingContactData = null;
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
|
|
69
|
+
case "ROLEPLAY_CALL_STARTED":
|
|
70
|
+
if (typeof config.onCallStart === "function") {
|
|
71
|
+
config.onCallStart({ callId: data.callId });
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
|
|
75
|
+
case "ROLEPLAY_CALL_ENDED":
|
|
76
|
+
if (typeof config.onCallEnd === "function") {
|
|
77
|
+
config.onCallEnd({
|
|
78
|
+
callId: data.callId,
|
|
79
|
+
duration: data.duration,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
|
|
84
|
+
case "ROLEPLAY_CLOSED":
|
|
85
|
+
doClose();
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
case "ROLEPLAY_ERROR":
|
|
89
|
+
if (typeof config.onError === "function") {
|
|
90
|
+
config.onError({ code: data.code, message: data.message });
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function createOverlay() {
|
|
97
|
+
var el = document.createElement("div");
|
|
98
|
+
el.id = "seamless-roleplay-overlay";
|
|
99
|
+
var s = el.style;
|
|
100
|
+
s.position = "fixed";
|
|
101
|
+
s.top = "0";
|
|
102
|
+
s.left = "0";
|
|
103
|
+
s.width = "100%";
|
|
104
|
+
s.height = "100%";
|
|
105
|
+
s.zIndex = "2147483647";
|
|
106
|
+
s.display = "flex";
|
|
107
|
+
s.alignItems = "center";
|
|
108
|
+
s.justifyContent = "center";
|
|
109
|
+
s.background = "rgba(0, 0, 0, 0.6)";
|
|
110
|
+
s.backdropFilter = "blur(4px)";
|
|
111
|
+
|
|
112
|
+
// Container
|
|
113
|
+
var container = document.createElement("div");
|
|
114
|
+
var cs = container.style;
|
|
115
|
+
cs.position = "relative";
|
|
116
|
+
cs.width = "90vw";
|
|
117
|
+
cs.maxWidth = "1100px";
|
|
118
|
+
cs.height = "85vh";
|
|
119
|
+
cs.maxHeight = "800px";
|
|
120
|
+
cs.borderRadius = "16px";
|
|
121
|
+
cs.overflow = "hidden";
|
|
122
|
+
cs.background = "#fff";
|
|
123
|
+
cs.boxShadow = "0 25px 60px rgba(0,0,0,0.3)";
|
|
124
|
+
|
|
125
|
+
// Close button
|
|
126
|
+
var closeBtn = document.createElement("button");
|
|
127
|
+
closeBtn.type = "button";
|
|
128
|
+
closeBtn.innerHTML = "×";
|
|
129
|
+
var cbs = closeBtn.style;
|
|
130
|
+
cbs.position = "absolute";
|
|
131
|
+
cbs.top = "12px";
|
|
132
|
+
cbs.right = "12px";
|
|
133
|
+
cbs.zIndex = "10";
|
|
134
|
+
cbs.width = "32px";
|
|
135
|
+
cbs.height = "32px";
|
|
136
|
+
cbs.border = "none";
|
|
137
|
+
cbs.borderRadius = "50%";
|
|
138
|
+
cbs.background = "rgba(0,0,0,0.08)";
|
|
139
|
+
cbs.color = "#333";
|
|
140
|
+
cbs.fontSize = "20px";
|
|
141
|
+
cbs.cursor = "pointer";
|
|
142
|
+
cbs.display = "flex";
|
|
143
|
+
cbs.alignItems = "center";
|
|
144
|
+
cbs.justifyContent = "center";
|
|
145
|
+
cbs.lineHeight = "1";
|
|
146
|
+
closeBtn.addEventListener("click", function () {
|
|
147
|
+
SeamlessRoleplay.close();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Iframe
|
|
151
|
+
var appOrigin = resolveOrigin();
|
|
152
|
+
var iframeEl = document.createElement("iframe");
|
|
153
|
+
iframeEl.src = appOrigin + "/embed/roleplay-call";
|
|
154
|
+
iframeEl.allow = "camera; microphone; display-capture";
|
|
155
|
+
var ifs = iframeEl.style;
|
|
156
|
+
ifs.width = "100%";
|
|
157
|
+
ifs.height = "100%";
|
|
158
|
+
ifs.border = "none";
|
|
159
|
+
ifs.display = "block";
|
|
160
|
+
|
|
161
|
+
container.appendChild(closeBtn);
|
|
162
|
+
container.appendChild(iframeEl);
|
|
163
|
+
el.appendChild(container);
|
|
164
|
+
|
|
165
|
+
// Close on backdrop click
|
|
166
|
+
el.addEventListener("click", function (e) {
|
|
167
|
+
if (e.target === el) {
|
|
168
|
+
SeamlessRoleplay.close();
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return { overlay: el, iframe: iframeEl };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function doClose() {
|
|
176
|
+
if (overlay && overlay.parentNode) {
|
|
177
|
+
overlay.parentNode.removeChild(overlay);
|
|
178
|
+
}
|
|
179
|
+
overlay = null;
|
|
180
|
+
iframe = null;
|
|
181
|
+
iframeAuthenticated = false;
|
|
182
|
+
pendingContactData = null;
|
|
183
|
+
if (typeof config.onClose === "function") {
|
|
184
|
+
config.onClose();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
var SeamlessRoleplay = {
|
|
189
|
+
/**
|
|
190
|
+
* Initialize the SDK. Call once.
|
|
191
|
+
* @param {Object} opts
|
|
192
|
+
* @param {string} opts.loginPass - Seamless login pass for auth
|
|
193
|
+
* @param {string} [opts.appOrigin] - Override app origin (default: production)
|
|
194
|
+
* @param {Function} [opts.onClose] - Called when modal closes
|
|
195
|
+
* @param {Function} [opts.onCallStart] - Called when call begins
|
|
196
|
+
* @param {Function} [opts.onCallEnd] - Called when call ends ({ callId, duration })
|
|
197
|
+
* @param {Function} [opts.onError] - Called on error ({ code, message })
|
|
198
|
+
*/
|
|
199
|
+
init: function (opts) {
|
|
200
|
+
if (!opts || !opts.loginPass) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
"SeamlessRoleplay.init() requires { loginPass: string }"
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
config = {
|
|
206
|
+
loginPass: opts.loginPass,
|
|
207
|
+
appOrigin: opts.appOrigin || DEFAULT_APP_ORIGIN,
|
|
208
|
+
onClose: opts.onClose || null,
|
|
209
|
+
onCallStart: opts.onCallStart || null,
|
|
210
|
+
onCallEnd: opts.onCallEnd || null,
|
|
211
|
+
onError: opts.onError || null,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Set up the global message listener
|
|
215
|
+
if (globalListener) {
|
|
216
|
+
window.removeEventListener("message", globalListener);
|
|
217
|
+
}
|
|
218
|
+
globalListener = handleMessage;
|
|
219
|
+
window.addEventListener("message", globalListener);
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Open the roleplay modal with contact data.
|
|
224
|
+
* @param {Object} data
|
|
225
|
+
* @param {string} data.contactName
|
|
226
|
+
* @param {string} data.contactCompany
|
|
227
|
+
* @param {string} data.contactTitle
|
|
228
|
+
* @param {string} [data.linkedinUrl]
|
|
229
|
+
* @param {string} [data.scenarioId]
|
|
230
|
+
*/
|
|
231
|
+
open: function (data) {
|
|
232
|
+
if (!config) {
|
|
233
|
+
throw new Error("Call SeamlessRoleplay.init() before open()");
|
|
234
|
+
}
|
|
235
|
+
if (!data || !data.contactName || !data.contactCompany || !data.contactTitle) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
"SeamlessRoleplay.open() requires { contactName, contactCompany, contactTitle }"
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// If already open, close first
|
|
242
|
+
if (overlay) {
|
|
243
|
+
doClose();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
iframeAuthenticated = false;
|
|
247
|
+
pendingContactData = data;
|
|
248
|
+
|
|
249
|
+
var dom = createOverlay();
|
|
250
|
+
overlay = dom.overlay;
|
|
251
|
+
iframe = dom.iframe;
|
|
252
|
+
document.body.appendChild(overlay);
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Close the modal. Sends roleplay-close to iframe, tears down DOM.
|
|
257
|
+
*/
|
|
258
|
+
close: function () {
|
|
259
|
+
if (!overlay) return;
|
|
260
|
+
sendToIframe({ type: "roleplay-close" });
|
|
261
|
+
// Give iframe a moment to clean up, then force-close
|
|
262
|
+
setTimeout(doClose, 300);
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Full cleanup — remove all event listeners and DOM.
|
|
267
|
+
*/
|
|
268
|
+
destroy: function () {
|
|
269
|
+
if (overlay) {
|
|
270
|
+
doClose();
|
|
271
|
+
}
|
|
272
|
+
if (globalListener) {
|
|
273
|
+
window.removeEventListener("message", globalListener);
|
|
274
|
+
globalListener = null;
|
|
275
|
+
}
|
|
276
|
+
config = null;
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Expose globally
|
|
281
|
+
if (typeof window !== "undefined") {
|
|
282
|
+
window.SeamlessRoleplay = SeamlessRoleplay;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Support CommonJS / ESM
|
|
286
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
287
|
+
module.exports = SeamlessRoleplay;
|
|
288
|
+
}
|
|
289
|
+
})();
|