@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 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
+ }
@@ -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 = "&times;";
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
+ })();