@rehers/rehers-roleplay-sdk 2.5.1 → 2.5.3
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 +17 -14
- package/index.d.ts +19 -6
- package/package.json +2 -2
- package/react.d.ts +3 -14
- package/react.js +45 -22
- package/roleplay-sdk.js +82 -19
package/README.md
CHANGED
|
@@ -6,26 +6,32 @@
|
|
|
6
6
|
npm install @rehers/rehers-roleplay-sdk
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+
For a working end-to-end example in this repo, use `sdk-demo/`.
|
|
10
|
+
|
|
9
11
|
---
|
|
10
12
|
|
|
11
13
|
## 1. Wrap your app with the Provider
|
|
12
14
|
|
|
13
15
|
Add this once, above all your routes. It initializes the SDK for the logged-in Seamless user.
|
|
14
16
|
|
|
17
|
+
Production flow:
|
|
18
|
+
|
|
19
|
+
1. Your backend requests a short-lived `userToken` from `POST /api/seamless/auth/user-token`
|
|
20
|
+
2. Your frontend receives that `userToken`
|
|
21
|
+
3. You pass `publishableKey` + `userToken` into the SDK
|
|
22
|
+
|
|
23
|
+
The browser should not mint sessions from raw `userId`, `userEmail`, or `userRole` in production.
|
|
24
|
+
|
|
15
25
|
```tsx
|
|
16
26
|
import { SeamlessRoleplayProvider } from "@rehers/rehers-roleplay-sdk/react";
|
|
17
27
|
|
|
18
28
|
function App() {
|
|
19
|
-
|
|
20
|
-
// The SDK needs their id and email.
|
|
21
|
-
const me = useCurrentUser(); // however you get the logged-in user
|
|
29
|
+
const userToken = useRoleplayUserToken(); // fetched from your backend
|
|
22
30
|
|
|
23
31
|
return (
|
|
24
32
|
<SeamlessRoleplayProvider
|
|
25
33
|
publishableKey="pk_live_..."
|
|
26
|
-
|
|
27
|
-
userEmail={me.username}
|
|
28
|
-
userRole={me.orgRole}
|
|
34
|
+
userToken={userToken}
|
|
29
35
|
onReady={() => console.log("Roleplay SDK ready")}
|
|
30
36
|
onError={(err) => console.error("Roleplay SDK error", err)}
|
|
31
37
|
>
|
|
@@ -39,17 +45,15 @@ function App() {
|
|
|
39
45
|
}
|
|
40
46
|
```
|
|
41
47
|
|
|
42
|
-
If you
|
|
48
|
+
If you need a backend shape, the secure flow looks like this:
|
|
43
49
|
|
|
44
50
|
```ts
|
|
45
|
-
const
|
|
51
|
+
const tokenRes = await fetch("/api/roleplay/user-token", {
|
|
52
|
+
method: "POST",
|
|
46
53
|
credentials: "include",
|
|
47
54
|
}).then((r) => r.json());
|
|
48
55
|
|
|
49
|
-
const
|
|
50
|
-
// me.id → pass as userId (convert to string)
|
|
51
|
-
// me.username → pass as userEmail
|
|
52
|
-
// me.orgRole → pass as userRole directly
|
|
56
|
+
const userToken = tokenRes.userToken;
|
|
53
57
|
```
|
|
54
58
|
|
|
55
59
|
That's the only setup. Everything below just works.
|
|
@@ -219,8 +223,7 @@ import "@rehers/rehers-roleplay-sdk";
|
|
|
219
223
|
// Initialize once
|
|
220
224
|
SeamlessRoleplay.init({
|
|
221
225
|
publishableKey: "pk_live_...",
|
|
222
|
-
|
|
223
|
-
userEmail: "...",
|
|
226
|
+
userToken: "...",
|
|
224
227
|
onReady() { console.log("ready"); },
|
|
225
228
|
});
|
|
226
229
|
|
package/index.d.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
interface SeamlessRoleplayInitBase {
|
|
2
2
|
/** Publishable API key (starts with pk_live_ or pk_test_) */
|
|
3
3
|
publishableKey: string;
|
|
4
|
-
/** Logged-in Seamless user ID. Pass String(me.id) from GET /api/users/me */
|
|
5
|
-
userId: string;
|
|
6
|
-
/** Logged-in Seamless user email. Pass me.username from GET /api/users/me */
|
|
7
|
-
userEmail: string;
|
|
8
4
|
/** Optional user role for syncing permissions ("owner" | "admin" | "member") */
|
|
9
5
|
userRole?: "owner" | "admin" | "member";
|
|
10
|
-
/** Optional signed JWT for identity verification */
|
|
6
|
+
/** Optional short-lived signed JWT for identity verification */
|
|
11
7
|
userToken?: string;
|
|
12
8
|
/** Override the app origin — where the iframe loads from (for dev/testing only) */
|
|
13
9
|
origin?: string;
|
|
@@ -17,6 +13,23 @@ export interface SeamlessRoleplayInitOptions {
|
|
|
17
13
|
onError?: (error: { code: string; message: string }) => void;
|
|
18
14
|
}
|
|
19
15
|
|
|
16
|
+
export type SeamlessRoleplayInitOptions =
|
|
17
|
+
| (SeamlessRoleplayInitBase & {
|
|
18
|
+
/** Preferred production auth path: signed Seamless bootstrap token */
|
|
19
|
+
userToken: string;
|
|
20
|
+
/** Optional fallback fields kept for local demos/internal tools */
|
|
21
|
+
userId?: string;
|
|
22
|
+
userEmail?: string;
|
|
23
|
+
})
|
|
24
|
+
| (SeamlessRoleplayInitBase & {
|
|
25
|
+
/** Legacy/demo fallback path when no signed token is available */
|
|
26
|
+
userToken?: string;
|
|
27
|
+
/** Logged-in Seamless user ID. Pass String(me.id) from GET /api/users/me */
|
|
28
|
+
userId: string;
|
|
29
|
+
/** Logged-in Seamless user email. Pass me.username from GET /api/users/me */
|
|
30
|
+
userEmail: string;
|
|
31
|
+
});
|
|
32
|
+
|
|
20
33
|
export interface SeamlessRoleplayOpenData {
|
|
21
34
|
/** Full name of the contact */
|
|
22
35
|
name: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rehers/rehers-roleplay-sdk",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.3",
|
|
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",
|
|
@@ -32,6 +32,6 @@
|
|
|
32
32
|
"license": "UNLICENSED",
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|
|
35
|
-
"url": "https://github.com/rehers/seamless-frontend-independent"
|
|
35
|
+
"url": "git+https://github.com/rehers/seamless-frontend-independent.git"
|
|
36
36
|
}
|
|
37
37
|
}
|
package/react.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import type { SeamlessRoleplaySDK, AddToScenarioContact, AddToScenarioCompleteData } from "@rehers/rehers-roleplay-sdk";
|
|
2
|
+
import type { SeamlessRoleplaySDK, AddToScenarioContact, AddToScenarioCompleteData, SeamlessRoleplayInitOptions } from "@rehers/rehers-roleplay-sdk";
|
|
3
3
|
import "@rehers/rehers-roleplay-sdk";
|
|
4
4
|
export type { AddToScenarioContact, AddToScenarioCompleteData };
|
|
5
5
|
interface SeamlessRoleplayContextValue {
|
|
@@ -10,20 +10,9 @@ interface SeamlessRoleplayContextValue {
|
|
|
10
10
|
} | null;
|
|
11
11
|
sdk: SeamlessRoleplaySDK;
|
|
12
12
|
}
|
|
13
|
-
export
|
|
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;
|
|
13
|
+
export type SeamlessRoleplayProviderProps = SeamlessRoleplayInitOptions & {
|
|
25
14
|
children: ReactNode;
|
|
26
|
-
}
|
|
15
|
+
};
|
|
27
16
|
export declare function SeamlessRoleplayProvider({ publishableKey, userId, userEmail, userRole, userToken, origin, onReady, onError, children, }: SeamlessRoleplayProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
28
17
|
export declare function useSeamlessRoleplay(): SeamlessRoleplayContextValue;
|
|
29
18
|
export interface RoleplayDialogProps {
|
package/react.js
CHANGED
|
@@ -36,28 +36,51 @@ export function SeamlessRoleplayProvider({ publishableKey, userId, userEmail, us
|
|
|
36
36
|
}
|
|
37
37
|
mountedRef.current = true;
|
|
38
38
|
setState({ isReady: false, error: null });
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
39
|
+
const initOptions = userToken
|
|
40
|
+
? {
|
|
41
|
+
publishableKey,
|
|
42
|
+
userToken,
|
|
43
|
+
userId,
|
|
44
|
+
userEmail,
|
|
45
|
+
userRole,
|
|
46
|
+
origin,
|
|
47
|
+
onReady: () => {
|
|
48
|
+
var _a;
|
|
49
|
+
if (!mountedRef.current)
|
|
50
|
+
return;
|
|
51
|
+
setState({ isReady: true, error: null });
|
|
52
|
+
(_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef);
|
|
53
|
+
},
|
|
54
|
+
onError: (err) => {
|
|
55
|
+
var _a;
|
|
56
|
+
if (!mountedRef.current)
|
|
57
|
+
return;
|
|
58
|
+
setState({ isReady: false, error: err });
|
|
59
|
+
(_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, err);
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
: {
|
|
63
|
+
publishableKey,
|
|
64
|
+
userId: userId || "",
|
|
65
|
+
userEmail: userEmail || "",
|
|
66
|
+
userRole,
|
|
67
|
+
origin,
|
|
68
|
+
onReady: () => {
|
|
69
|
+
var _a;
|
|
70
|
+
if (!mountedRef.current)
|
|
71
|
+
return;
|
|
72
|
+
setState({ isReady: true, error: null });
|
|
73
|
+
(_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef);
|
|
74
|
+
},
|
|
75
|
+
onError: (err) => {
|
|
76
|
+
var _a;
|
|
77
|
+
if (!mountedRef.current)
|
|
78
|
+
return;
|
|
79
|
+
setState({ isReady: false, error: err });
|
|
80
|
+
(_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, err);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
sdk.init(initOptions);
|
|
61
84
|
return () => {
|
|
62
85
|
mountedRef.current = false;
|
|
63
86
|
providerMountCount--;
|
package/roleplay-sdk.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Publishable-key auth model. No build step required.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* SeamlessRoleplay.init({ publishableKey: 'pk_live_...',
|
|
7
|
+
* SeamlessRoleplay.init({ publishableKey: 'pk_live_...', userToken: 'jwt...' });
|
|
8
8
|
* SeamlessRoleplay.open({ name: '...', domain: '...', company: '...', title: '...' });
|
|
9
9
|
*/
|
|
10
10
|
(function () {
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
var sessionExpiresAt = 0; // epoch ms
|
|
29
29
|
var refreshTimer = null;
|
|
30
30
|
var fetchingSession = null; // single-flight Promise
|
|
31
|
+
var activeSessionXhr = null;
|
|
32
|
+
var activeInitVersion = 0;
|
|
31
33
|
|
|
32
34
|
var initCallbacks = { onReady: null, onError: null };
|
|
33
35
|
var initCalled = false;
|
|
@@ -71,6 +73,16 @@
|
|
|
71
73
|
return DEFAULT_API_ORIGIN;
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
function buildIframeSrc(path) {
|
|
77
|
+
var targetPath = path || "/embed/roleplay-call";
|
|
78
|
+
var url = new URL(targetPath, getOrigin());
|
|
79
|
+
// Force the embedded app to drop any stale auth before it handles the
|
|
80
|
+
// SDK session-init message. Without this, hosted app sessions can leak
|
|
81
|
+
// through when switching users or entering trial mode in the demo.
|
|
82
|
+
url.searchParams.set("seamlessResetAuth", "1");
|
|
83
|
+
return url.toString();
|
|
84
|
+
}
|
|
85
|
+
|
|
74
86
|
function sendMsg(iframeEl, msg) {
|
|
75
87
|
try {
|
|
76
88
|
if (iframeEl && iframeEl.contentWindow) {
|
|
@@ -83,25 +95,54 @@
|
|
|
83
95
|
|
|
84
96
|
// ── Session management ────────────────────────────────────────────
|
|
85
97
|
|
|
98
|
+
function clearSessionRequest() {
|
|
99
|
+
if (activeSessionXhr) {
|
|
100
|
+
try {
|
|
101
|
+
activeSessionXhr.abort();
|
|
102
|
+
} catch (_) {}
|
|
103
|
+
activeSessionXhr = null;
|
|
104
|
+
}
|
|
105
|
+
fetchingSession = null;
|
|
106
|
+
}
|
|
107
|
+
|
|
86
108
|
function fetchSession() {
|
|
87
|
-
|
|
109
|
+
var requestInitVersion = activeInitVersion;
|
|
110
|
+
|
|
111
|
+
if (fetchingSession && fetchingSession.initVersion === requestInitVersion) {
|
|
112
|
+
return fetchingSession.promise;
|
|
113
|
+
}
|
|
88
114
|
|
|
89
|
-
|
|
115
|
+
var requestPromise = new Promise(function (resolve, reject) {
|
|
90
116
|
var url = getApiOrigin() + "/api/sdk/session";
|
|
91
|
-
var body =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (userToken) body.userToken = userToken;
|
|
117
|
+
var body = userToken
|
|
118
|
+
? { userToken: userToken }
|
|
119
|
+
: { userId: userId, userEmail: userEmail, userRole: userRole };
|
|
95
120
|
|
|
96
121
|
var xhr = new XMLHttpRequest();
|
|
122
|
+
activeSessionXhr = xhr;
|
|
97
123
|
xhr.open("POST", url, true);
|
|
98
124
|
xhr.setRequestHeader("Content-Type", "application/json");
|
|
99
125
|
xhr.setRequestHeader("X-Publishable-Key", publishableKey);
|
|
100
126
|
xhr.withCredentials = false;
|
|
101
127
|
xhr.timeout = SESSION_TIMEOUT_MS;
|
|
102
128
|
|
|
129
|
+
function cleanupRequest() {
|
|
130
|
+
if (activeSessionXhr === xhr) {
|
|
131
|
+
activeSessionXhr = null;
|
|
132
|
+
}
|
|
133
|
+
if (fetchingSession && fetchingSession.promise === requestPromise) {
|
|
134
|
+
fetchingSession = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
103
138
|
xhr.onload = function () {
|
|
104
|
-
|
|
139
|
+
cleanupRequest();
|
|
140
|
+
|
|
141
|
+
if (requestInitVersion !== activeInitVersion) {
|
|
142
|
+
reject({ code: "ABORTED", message: "Stale session response ignored" });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
105
146
|
var data;
|
|
106
147
|
try {
|
|
107
148
|
data = JSON.parse(xhr.responseText);
|
|
@@ -134,24 +175,29 @@
|
|
|
134
175
|
};
|
|
135
176
|
|
|
136
177
|
xhr.onerror = function () {
|
|
137
|
-
|
|
178
|
+
cleanupRequest();
|
|
138
179
|
reject({ code: "NETWORK_ERROR", message: "Network error contacting session endpoint" });
|
|
139
180
|
};
|
|
140
181
|
|
|
141
182
|
xhr.ontimeout = function () {
|
|
142
|
-
|
|
183
|
+
cleanupRequest();
|
|
143
184
|
reject({ code: "TIMEOUT", message: "Session request timed out after " + SESSION_TIMEOUT_MS + "ms" });
|
|
144
185
|
};
|
|
145
186
|
|
|
146
187
|
xhr.onabort = function () {
|
|
147
|
-
|
|
188
|
+
cleanupRequest();
|
|
148
189
|
reject({ code: "ABORTED", message: "Session request was aborted" });
|
|
149
190
|
};
|
|
150
191
|
|
|
151
192
|
xhr.send(JSON.stringify(body));
|
|
152
193
|
});
|
|
153
194
|
|
|
154
|
-
|
|
195
|
+
fetchingSession = {
|
|
196
|
+
initVersion: requestInitVersion,
|
|
197
|
+
promise: requestPromise,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
return requestPromise;
|
|
155
201
|
}
|
|
156
202
|
|
|
157
203
|
function getSessionToken() {
|
|
@@ -381,7 +427,7 @@
|
|
|
381
427
|
|
|
382
428
|
function createIframe(path) {
|
|
383
429
|
var iframeEl = document.createElement("iframe");
|
|
384
|
-
iframeEl.src =
|
|
430
|
+
iframeEl.src = buildIframeSrc(path);
|
|
385
431
|
iframeEl.allow = "camera; microphone; display-capture; autoplay";
|
|
386
432
|
iframeEl.style.width = "100%";
|
|
387
433
|
iframeEl.style.height = "100%";
|
|
@@ -398,8 +444,19 @@
|
|
|
398
444
|
*/
|
|
399
445
|
init: function (opts) {
|
|
400
446
|
try {
|
|
401
|
-
|
|
402
|
-
|
|
447
|
+
var initVersion = activeInitVersion + 1;
|
|
448
|
+
var hasUserToken = !!(opts && opts.userToken && String(opts.userToken).trim());
|
|
449
|
+
var hasLegacyIdentity = !!(opts && opts.userId && opts.userEmail);
|
|
450
|
+
|
|
451
|
+
if (!opts || !opts.publishableKey || (!hasUserToken && !hasLegacyIdentity)) {
|
|
452
|
+
var error = {
|
|
453
|
+
code: "INVALID_INIT",
|
|
454
|
+
message: "requires { publishableKey, userToken } or legacy { publishableKey, userId, userEmail }",
|
|
455
|
+
};
|
|
456
|
+
logError("init", error.message);
|
|
457
|
+
if (opts && typeof opts.onError === "function") {
|
|
458
|
+
try { opts.onError(error); } catch (_) {}
|
|
459
|
+
}
|
|
403
460
|
return;
|
|
404
461
|
}
|
|
405
462
|
|
|
@@ -408,12 +465,14 @@
|
|
|
408
465
|
clearTimeout(refreshTimer);
|
|
409
466
|
refreshTimer = null;
|
|
410
467
|
}
|
|
411
|
-
|
|
468
|
+
activeInitVersion = initVersion;
|
|
469
|
+
clearSessionRequest();
|
|
412
470
|
sessionToken = null;
|
|
413
471
|
sessionExpiresAt = 0;
|
|
472
|
+
paymentLink = null;
|
|
414
473
|
|
|
415
474
|
publishableKey = opts.publishableKey;
|
|
416
|
-
userId = opts.userId;
|
|
475
|
+
userId = opts.userId || null;
|
|
417
476
|
userEmail = opts.userEmail || null;
|
|
418
477
|
userRole = opts.userRole || null;
|
|
419
478
|
userToken = opts.userToken || null;
|
|
@@ -425,6 +484,7 @@
|
|
|
425
484
|
// Fetch session immediately
|
|
426
485
|
fetchSession()
|
|
427
486
|
.then(function (result) {
|
|
487
|
+
if (initVersion !== activeInitVersion) return;
|
|
428
488
|
if (result.trialMode && !paymentLink) {
|
|
429
489
|
// User not found and no payment link from server — still call onReady
|
|
430
490
|
// The open() will show an error state in the iframe
|
|
@@ -432,6 +492,8 @@
|
|
|
432
492
|
if (initCallbacks.onReady) initCallbacks.onReady();
|
|
433
493
|
})
|
|
434
494
|
.catch(function (err) {
|
|
495
|
+
if (initVersion !== activeInitVersion) return;
|
|
496
|
+
if (err && err.code === "ABORTED") return;
|
|
435
497
|
if (initCallbacks.onError) {
|
|
436
498
|
initCallbacks.onError({ code: err.code || "INIT_ERROR", message: err.message || "Initialization failed" });
|
|
437
499
|
}
|
|
@@ -650,7 +712,7 @@
|
|
|
650
712
|
cs.boxShadow = "0 25px 60px rgba(0,0,0,0.3)";
|
|
651
713
|
|
|
652
714
|
var iframeEl = document.createElement("iframe");
|
|
653
|
-
iframeEl.src =
|
|
715
|
+
iframeEl.src = buildIframeSrc("/embed/add-to-scenario");
|
|
654
716
|
iframeEl.style.width = "100%";
|
|
655
717
|
iframeEl.style.height = "100%";
|
|
656
718
|
iframeEl.style.border = "none";
|
|
@@ -733,10 +795,12 @@
|
|
|
733
795
|
*/
|
|
734
796
|
destroy: function () {
|
|
735
797
|
try {
|
|
798
|
+
activeInitVersion++;
|
|
736
799
|
if (refreshTimer) {
|
|
737
800
|
clearTimeout(refreshTimer);
|
|
738
801
|
refreshTimer = null;
|
|
739
802
|
}
|
|
803
|
+
clearSessionRequest();
|
|
740
804
|
teardownDialog();
|
|
741
805
|
teardownMount();
|
|
742
806
|
publishableKey = null;
|
|
@@ -747,7 +811,6 @@
|
|
|
747
811
|
paymentLink = null;
|
|
748
812
|
sessionToken = null;
|
|
749
813
|
sessionExpiresAt = 0;
|
|
750
|
-
fetchingSession = null;
|
|
751
814
|
initCallbacks = { onReady: null, onError: null };
|
|
752
815
|
initCalled = false;
|
|
753
816
|
} catch (e) {
|