@rehers/rehers-roleplay-sdk 2.5.6 → 3.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 +23 -9
- package/index.d.ts +10 -23
- package/package.json +1 -1
- package/react.d.ts +1 -1
- package/react.js +20 -46
- package/roleplay-sdk.js +247 -97
package/README.md
CHANGED
|
@@ -17,21 +17,27 @@ Add this once, above all your routes. It initializes the SDK for the logged-in S
|
|
|
17
17
|
Production flow:
|
|
18
18
|
|
|
19
19
|
1. Your backend requests a short-lived `userToken` from `POST /api/seamless/auth/user-token`
|
|
20
|
-
2. Your frontend
|
|
21
|
-
3.
|
|
20
|
+
2. Your frontend provides a `getUserToken()` callback that calls your backend route
|
|
21
|
+
3. The SDK calls `getUserToken()` on startup and before the iframe session expires
|
|
22
22
|
|
|
23
|
-
The browser should not mint sessions from raw
|
|
23
|
+
The browser should not mint sessions from raw identity fields in production.
|
|
24
24
|
|
|
25
25
|
```tsx
|
|
26
26
|
import { SeamlessRoleplayProvider } from "@rehers/rehers-roleplay-sdk/react";
|
|
27
27
|
|
|
28
28
|
function App() {
|
|
29
|
-
|
|
29
|
+
async function getUserToken() {
|
|
30
|
+
const tokenRes = await fetch("/api/roleplay/user-token", {
|
|
31
|
+
method: "POST",
|
|
32
|
+
credentials: "include",
|
|
33
|
+
}).then((r) => r.json());
|
|
34
|
+
|
|
35
|
+
return tokenRes.userToken;
|
|
36
|
+
}
|
|
30
37
|
|
|
31
38
|
return (
|
|
32
39
|
<SeamlessRoleplayProvider
|
|
33
|
-
|
|
34
|
-
userToken={userToken}
|
|
40
|
+
getUserToken={getUserToken}
|
|
35
41
|
onReady={() => console.log("Roleplay SDK ready")}
|
|
36
42
|
onError={(err) => console.error("Roleplay SDK error", err)}
|
|
37
43
|
>
|
|
@@ -56,7 +62,9 @@ const tokenRes = await fetch("/api/roleplay/user-token", {
|
|
|
56
62
|
const userToken = tokenRes.userToken;
|
|
57
63
|
```
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
The SDK owns session refresh timing and will call `getUserToken()` again before
|
|
66
|
+
the embedded app session expires. That's the only setup. Everything below just
|
|
67
|
+
works.
|
|
60
68
|
|
|
61
69
|
---
|
|
62
70
|
|
|
@@ -222,8 +230,14 @@ import "@rehers/rehers-roleplay-sdk";
|
|
|
222
230
|
|
|
223
231
|
// Initialize once
|
|
224
232
|
SeamlessRoleplay.init({
|
|
225
|
-
|
|
226
|
-
|
|
233
|
+
getUserToken: async () => {
|
|
234
|
+
const res = await fetch("/api/roleplay/user-token", {
|
|
235
|
+
method: "POST",
|
|
236
|
+
credentials: "include",
|
|
237
|
+
});
|
|
238
|
+
const data = await res.json();
|
|
239
|
+
return data.userToken;
|
|
240
|
+
},
|
|
227
241
|
onReady() { console.log("ready"); },
|
|
228
242
|
});
|
|
229
243
|
|
package/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
export type SeamlessRoleplayUserTokenProvider = () => string | Promise<string>;
|
|
2
|
+
|
|
1
3
|
interface SeamlessRoleplayInitBase {
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Returns a fresh short-lived signed user JWT minted by your backend for the
|
|
6
|
+
* currently signed-in Seamless user. The SDK calls this on startup and before
|
|
7
|
+
* its iframe session expires.
|
|
8
|
+
*/
|
|
9
|
+
getUserToken: SeamlessRoleplayUserTokenProvider;
|
|
8
10
|
/** Override the app origin — where the iframe loads from (for dev/testing only) */
|
|
9
11
|
origin?: string;
|
|
10
12
|
/** Called when the SDK session is ready */
|
|
@@ -13,22 +15,7 @@ interface SeamlessRoleplayInitBase {
|
|
|
13
15
|
onError?: (error: { code: string; message: string }) => void;
|
|
14
16
|
}
|
|
15
17
|
|
|
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
|
-
});
|
|
18
|
+
export type SeamlessRoleplayInitOptions = SeamlessRoleplayInitBase;
|
|
32
19
|
|
|
33
20
|
export interface SeamlessRoleplayOpenData {
|
|
34
21
|
/** Full name of the contact */
|
|
@@ -83,7 +70,7 @@ export interface AddToScenarioOptions {
|
|
|
83
70
|
}
|
|
84
71
|
|
|
85
72
|
export interface SeamlessRoleplaySDK {
|
|
86
|
-
/** Initialize the SDK with a
|
|
73
|
+
/** Initialize the SDK with a host-provided user token callback. */
|
|
87
74
|
init(options: SeamlessRoleplayInitOptions): void;
|
|
88
75
|
/** Open the roleplay modal for a contact (dialog mode). */
|
|
89
76
|
open(data: SeamlessRoleplayOpenData): void;
|
package/package.json
CHANGED
package/react.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ interface SeamlessRoleplayContextValue {
|
|
|
13
13
|
export type SeamlessRoleplayProviderProps = SeamlessRoleplayInitOptions & {
|
|
14
14
|
children: ReactNode;
|
|
15
15
|
};
|
|
16
|
-
export declare function SeamlessRoleplayProvider({
|
|
16
|
+
export declare function SeamlessRoleplayProvider({ getUserToken, origin, onReady, onError, children, }: SeamlessRoleplayProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
17
17
|
export declare function useSeamlessRoleplay(): SeamlessRoleplayContextValue;
|
|
18
18
|
export interface RoleplayDialogProps {
|
|
19
19
|
open: boolean;
|
package/react.js
CHANGED
|
@@ -22,7 +22,7 @@ function getSDK() {
|
|
|
22
22
|
}
|
|
23
23
|
const SeamlessRoleplayContext = createContext(null);
|
|
24
24
|
let providerMountCount = 0;
|
|
25
|
-
export function SeamlessRoleplayProvider({
|
|
25
|
+
export function SeamlessRoleplayProvider({ getUserToken, origin, onReady, onError, children, }) {
|
|
26
26
|
const [state, setState] = useState({ isReady: false, error: null });
|
|
27
27
|
const mountedRef = useRef(false);
|
|
28
28
|
const onReadyRef = useCallbackRef(onReady);
|
|
@@ -36,57 +36,31 @@ export function SeamlessRoleplayProvider({ publishableKey, userId, userEmail, us
|
|
|
36
36
|
}
|
|
37
37
|
mountedRef.current = true;
|
|
38
38
|
setState({ isReady: false, error: null });
|
|
39
|
-
const initOptions =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
};
|
|
39
|
+
const initOptions = {
|
|
40
|
+
getUserToken,
|
|
41
|
+
origin,
|
|
42
|
+
onReady: () => {
|
|
43
|
+
var _a;
|
|
44
|
+
if (!mountedRef.current)
|
|
45
|
+
return;
|
|
46
|
+
setState({ isReady: true, error: null });
|
|
47
|
+
(_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef);
|
|
48
|
+
},
|
|
49
|
+
onError: (err) => {
|
|
50
|
+
var _a;
|
|
51
|
+
if (!mountedRef.current)
|
|
52
|
+
return;
|
|
53
|
+
setState({ isReady: false, error: err });
|
|
54
|
+
(_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, err);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
83
57
|
sdk.init(initOptions);
|
|
84
58
|
return () => {
|
|
85
59
|
mountedRef.current = false;
|
|
86
60
|
providerMountCount--;
|
|
87
61
|
sdk.destroy();
|
|
88
62
|
};
|
|
89
|
-
}, [sdk,
|
|
63
|
+
}, [sdk, getUserToken, origin]);
|
|
90
64
|
const contextValue = useMemo(() => ({ ...state, sdk }), [state, sdk]);
|
|
91
65
|
return (_jsx(SeamlessRoleplayContext.Provider, { value: contextValue, children: children }));
|
|
92
66
|
}
|
package/roleplay-sdk.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SeamlessRoleplay SDK
|
|
2
|
+
* SeamlessRoleplay SDK v3
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* User-token auth model. No build step required.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* SeamlessRoleplay.init({
|
|
7
|
+
* SeamlessRoleplay.init({ getUserToken: async () => 'jwt...' });
|
|
8
8
|
* SeamlessRoleplay.open({ name: '...', domain: '...', company: '...', title: '...' });
|
|
9
9
|
*/
|
|
10
10
|
(function () {
|
|
@@ -16,17 +16,17 @@
|
|
|
16
16
|
var SDK_LOG_PREFIX = "[SeamlessRoleplay]";
|
|
17
17
|
|
|
18
18
|
// ── Auth state ────────────────────────────────────────────────────
|
|
19
|
-
var
|
|
20
|
-
var
|
|
21
|
-
var
|
|
22
|
-
var userRole = null;
|
|
23
|
-
var userToken = null;
|
|
19
|
+
var getUserToken = null;
|
|
20
|
+
var latestUserToken = null;
|
|
21
|
+
var userTokenRequest = null;
|
|
24
22
|
var paymentLink = null;
|
|
25
23
|
var appOrigin = null;
|
|
26
24
|
|
|
27
25
|
var sessionToken = null;
|
|
28
26
|
var sessionExpiresAt = 0; // epoch ms
|
|
27
|
+
var sessionRefreshBufferMs = 30000;
|
|
29
28
|
var refreshTimer = null;
|
|
29
|
+
var refreshRetryDelayMs = 5000;
|
|
30
30
|
var fetchingSession = null; // single-flight Promise
|
|
31
31
|
var activeSessionXhr = null;
|
|
32
32
|
var activeInitVersion = 0;
|
|
@@ -93,6 +93,16 @@
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
function normalizeToken(value) {
|
|
97
|
+
if (typeof value !== "string") return null;
|
|
98
|
+
var trimmed = value.trim();
|
|
99
|
+
return trimmed ? trimmed : null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function makeError(code, message) {
|
|
103
|
+
return { code: code, message: message };
|
|
104
|
+
}
|
|
105
|
+
|
|
96
106
|
// ── Session management ────────────────────────────────────────────
|
|
97
107
|
|
|
98
108
|
function clearSessionRequest() {
|
|
@@ -105,91 +115,236 @@
|
|
|
105
115
|
fetchingSession = null;
|
|
106
116
|
}
|
|
107
117
|
|
|
108
|
-
function
|
|
118
|
+
function resolveUserToken() {
|
|
119
|
+
if (typeof getUserToken !== "function") {
|
|
120
|
+
return Promise.reject(
|
|
121
|
+
makeError("INVALID_INIT", "requires { getUserToken }")
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (userTokenRequest) return userTokenRequest;
|
|
126
|
+
|
|
127
|
+
userTokenRequest = Promise.resolve()
|
|
128
|
+
.then(function () {
|
|
129
|
+
return getUserToken();
|
|
130
|
+
})
|
|
131
|
+
.then(
|
|
132
|
+
function (value) {
|
|
133
|
+
userTokenRequest = null;
|
|
134
|
+
var token = normalizeToken(value);
|
|
135
|
+
if (!token) {
|
|
136
|
+
throw makeError(
|
|
137
|
+
"USER_TOKEN_ERROR",
|
|
138
|
+
"getUserToken() did not return a valid userToken"
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
latestUserToken = token;
|
|
142
|
+
return token;
|
|
143
|
+
},
|
|
144
|
+
function (err) {
|
|
145
|
+
userTokenRequest = null;
|
|
146
|
+
throw makeError(
|
|
147
|
+
(err && err.code) || "USER_TOKEN_ERROR",
|
|
148
|
+
(err && err.message) || "Failed to get a fresh userToken"
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return userTokenRequest;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function dispatchRefreshToTarget(targetIframe) {
|
|
157
|
+
if (!sessionToken) return;
|
|
158
|
+
sendMsg(targetIframe, {
|
|
159
|
+
type: "seamless-session-refresh",
|
|
160
|
+
sessionToken: sessionToken,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function broadcastSessionRefresh() {
|
|
165
|
+
if (mountIframe) dispatchRefreshToTarget(mountIframe);
|
|
166
|
+
if (dialogIframe) dispatchRefreshToTarget(dialogIframe);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function notifySessionRefreshFailed(err) {
|
|
170
|
+
var error = {
|
|
171
|
+
code: "SESSION_REFRESH_FAILED",
|
|
172
|
+
message:
|
|
173
|
+
(err && err.message) ||
|
|
174
|
+
"Failed to refresh the SDK session before it expired",
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
if (initCallbacks.onError) initCallbacks.onError(error);
|
|
179
|
+
} catch (_) {}
|
|
180
|
+
try {
|
|
181
|
+
if (mountCallbacks.onError) mountCallbacks.onError(error);
|
|
182
|
+
} catch (_) {}
|
|
183
|
+
try {
|
|
184
|
+
if (dialogCallbacks.onError) dialogCallbacks.onError(error);
|
|
185
|
+
} catch (_) {}
|
|
186
|
+
try {
|
|
187
|
+
if (dialogAddToScenarioCallbacks.onError) {
|
|
188
|
+
dialogAddToScenarioCallbacks.onError(error);
|
|
189
|
+
}
|
|
190
|
+
} catch (_) {}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function computeRefreshBuffer(ttlMs) {
|
|
194
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0) return 30000;
|
|
195
|
+
return Math.min(30000, Math.max(1000, Math.floor(ttlMs * 0.1)));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function computeRefreshDelay(ttlMs) {
|
|
199
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0) return 5000;
|
|
200
|
+
|
|
201
|
+
var eightyPercent = Math.floor(ttlMs * 0.8);
|
|
202
|
+
var oneMinuteBeforeExpiry = ttlMs - 60000;
|
|
203
|
+
var target =
|
|
204
|
+
oneMinuteBeforeExpiry > 0
|
|
205
|
+
? Math.min(eightyPercent, oneMinuteBeforeExpiry)
|
|
206
|
+
: Math.floor(ttlMs * 0.5);
|
|
207
|
+
|
|
208
|
+
return Math.max(target, 1000);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function scheduleRefreshRetry(err) {
|
|
212
|
+
if (!initCalled || !sessionExpiresAt) return;
|
|
213
|
+
if (refreshTimer) clearTimeout(refreshTimer);
|
|
214
|
+
|
|
215
|
+
var remainingMs = sessionExpiresAt - Date.now();
|
|
216
|
+
if (remainingMs <= sessionRefreshBufferMs) {
|
|
217
|
+
notifySessionRefreshFailed(err);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
var delay = Math.min(
|
|
222
|
+
refreshRetryDelayMs,
|
|
223
|
+
Math.max(1000, remainingMs - sessionRefreshBufferMs)
|
|
224
|
+
);
|
|
225
|
+
refreshRetryDelayMs = Math.min(refreshRetryDelayMs * 2, 60000);
|
|
226
|
+
|
|
227
|
+
refreshTimer = setTimeout(function () {
|
|
228
|
+
fetchSession({ broadcastRefresh: true }).catch(scheduleRefreshRetry);
|
|
229
|
+
}, delay);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function fetchSession(options) {
|
|
233
|
+
options = options || {};
|
|
109
234
|
var requestInitVersion = activeInitVersion;
|
|
110
235
|
|
|
111
236
|
if (fetchingSession && fetchingSession.initVersion === requestInitVersion) {
|
|
237
|
+
if (options.broadcastRefresh) {
|
|
238
|
+
return fetchingSession.promise.then(function (result) {
|
|
239
|
+
if (
|
|
240
|
+
requestInitVersion === activeInitVersion &&
|
|
241
|
+
result &&
|
|
242
|
+
result.sessionToken
|
|
243
|
+
) {
|
|
244
|
+
broadcastSessionRefresh();
|
|
245
|
+
}
|
|
246
|
+
return result;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
112
249
|
return fetchingSession.promise;
|
|
113
250
|
}
|
|
114
251
|
|
|
115
|
-
var requestPromise =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
? { userToken: userToken }
|
|
119
|
-
: { userId: userId, userEmail: userEmail, userRole: userRole };
|
|
120
|
-
|
|
121
|
-
var xhr = new XMLHttpRequest();
|
|
122
|
-
activeSessionXhr = xhr;
|
|
123
|
-
xhr.open("POST", url, true);
|
|
124
|
-
xhr.setRequestHeader("Content-Type", "application/json");
|
|
125
|
-
xhr.setRequestHeader("X-Publishable-Key", publishableKey);
|
|
126
|
-
xhr.withCredentials = false;
|
|
127
|
-
xhr.timeout = SESSION_TIMEOUT_MS;
|
|
128
|
-
|
|
129
|
-
function cleanupRequest() {
|
|
130
|
-
if (activeSessionXhr === xhr) {
|
|
131
|
-
activeSessionXhr = null;
|
|
132
|
-
}
|
|
133
|
-
if (fetchingSession && fetchingSession.promise === requestPromise) {
|
|
134
|
-
fetchingSession = null;
|
|
135
|
-
}
|
|
252
|
+
var requestPromise = resolveUserToken().then(function (freshUserToken) {
|
|
253
|
+
if (requestInitVersion !== activeInitVersion) {
|
|
254
|
+
throw makeError("ABORTED", "Stale session request ignored");
|
|
136
255
|
}
|
|
137
256
|
|
|
138
|
-
|
|
139
|
-
|
|
257
|
+
return new Promise(function (resolve, reject) {
|
|
258
|
+
var url = getApiOrigin() + "/api/sdk/session";
|
|
259
|
+
var body = { userToken: freshUserToken };
|
|
140
260
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
261
|
+
var xhr = new XMLHttpRequest();
|
|
262
|
+
activeSessionXhr = xhr;
|
|
263
|
+
xhr.open("POST", url, true);
|
|
264
|
+
xhr.setRequestHeader("Content-Type", "application/json");
|
|
265
|
+
xhr.withCredentials = false;
|
|
266
|
+
xhr.timeout = SESSION_TIMEOUT_MS;
|
|
145
267
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
268
|
+
function cleanupRequest() {
|
|
269
|
+
if (activeSessionXhr === xhr) {
|
|
270
|
+
activeSessionXhr = null;
|
|
271
|
+
}
|
|
272
|
+
if (fetchingSession && fetchingSession.promise === requestPromise) {
|
|
273
|
+
fetchingSession = null;
|
|
274
|
+
}
|
|
152
275
|
}
|
|
153
276
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
var ttl = (data.expiresIn || 3600) * 1000;
|
|
157
|
-
sessionExpiresAt = Date.now() + ttl;
|
|
158
|
-
scheduleRefresh(ttl);
|
|
159
|
-
resolve({ sessionToken: sessionToken });
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
277
|
+
xhr.onload = function () {
|
|
278
|
+
cleanupRequest();
|
|
162
279
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
resolve({ trialMode: true });
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
280
|
+
if (requestInitVersion !== activeInitVersion) {
|
|
281
|
+
reject({ code: "ABORTED", message: "Stale session response ignored" });
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
170
284
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
285
|
+
var data;
|
|
286
|
+
try {
|
|
287
|
+
data = JSON.parse(xhr.responseText);
|
|
288
|
+
} catch (e) {
|
|
289
|
+
reject({ code: "PARSE_ERROR", message: "Invalid response from session endpoint" });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
176
292
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
293
|
+
if (xhr.status === 200 && data.sessionToken) {
|
|
294
|
+
sessionToken = data.sessionToken;
|
|
295
|
+
var ttl = (data.expiresIn || 3600) * 1000;
|
|
296
|
+
sessionRefreshBufferMs = computeRefreshBuffer(ttl);
|
|
297
|
+
sessionExpiresAt = Date.now() + ttl;
|
|
298
|
+
refreshRetryDelayMs = 5000;
|
|
299
|
+
scheduleRefresh(ttl);
|
|
300
|
+
if (options.broadcastRefresh) broadcastSessionRefresh();
|
|
301
|
+
resolve({ sessionToken: sessionToken });
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
181
304
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
305
|
+
if (data.error === "USER_NOT_FOUND") {
|
|
306
|
+
// Trial mode — not a fatal error
|
|
307
|
+
sessionToken = null;
|
|
308
|
+
sessionExpiresAt = 0;
|
|
309
|
+
if (refreshTimer) {
|
|
310
|
+
clearTimeout(refreshTimer);
|
|
311
|
+
refreshTimer = null;
|
|
312
|
+
}
|
|
313
|
+
if (data.paymentLink) paymentLink = data.paymentLink;
|
|
314
|
+
resolve({ trialMode: true });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
186
317
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
318
|
+
reject({
|
|
319
|
+
code: data.error || "SESSION_ERROR",
|
|
320
|
+
message: data.message || "Failed to create session (HTTP " + xhr.status + ")",
|
|
321
|
+
});
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
xhr.onerror = function () {
|
|
325
|
+
cleanupRequest();
|
|
326
|
+
reject({ code: "NETWORK_ERROR", message: "Network error contacting session endpoint" });
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
xhr.ontimeout = function () {
|
|
330
|
+
cleanupRequest();
|
|
331
|
+
reject({ code: "TIMEOUT", message: "Session request timed out after " + SESSION_TIMEOUT_MS + "ms" });
|
|
332
|
+
};
|
|
191
333
|
|
|
192
|
-
|
|
334
|
+
xhr.onabort = function () {
|
|
335
|
+
cleanupRequest();
|
|
336
|
+
reject({ code: "ABORTED", message: "Session request was aborted" });
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
xhr.send(JSON.stringify(body));
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
requestPromise = requestPromise.catch(function (err) {
|
|
344
|
+
if (fetchingSession && fetchingSession.promise === requestPromise) {
|
|
345
|
+
fetchingSession = null;
|
|
346
|
+
}
|
|
347
|
+
throw err;
|
|
193
348
|
});
|
|
194
349
|
|
|
195
350
|
fetchingSession = {
|
|
@@ -201,7 +356,7 @@
|
|
|
201
356
|
}
|
|
202
357
|
|
|
203
358
|
function getSessionToken() {
|
|
204
|
-
if (sessionToken && Date.now() < sessionExpiresAt -
|
|
359
|
+
if (sessionToken && Date.now() < sessionExpiresAt - sessionRefreshBufferMs) {
|
|
205
360
|
return Promise.resolve(sessionToken);
|
|
206
361
|
}
|
|
207
362
|
return fetchSession().then(function (result) {
|
|
@@ -211,11 +366,9 @@
|
|
|
211
366
|
|
|
212
367
|
function scheduleRefresh(ttlMs) {
|
|
213
368
|
if (refreshTimer) clearTimeout(refreshTimer);
|
|
214
|
-
var delay =
|
|
369
|
+
var delay = computeRefreshDelay(ttlMs);
|
|
215
370
|
refreshTimer = setTimeout(function () {
|
|
216
|
-
fetchSession().catch(
|
|
217
|
-
// Silent — next open() will retry
|
|
218
|
-
});
|
|
371
|
+
fetchSession({ broadcastRefresh: true }).catch(scheduleRefreshRetry);
|
|
219
372
|
}, delay);
|
|
220
373
|
}
|
|
221
374
|
|
|
@@ -274,8 +427,6 @@
|
|
|
274
427
|
var msg = {
|
|
275
428
|
type: "seamless-add-to-scenario-init",
|
|
276
429
|
sessionToken: sessionToken,
|
|
277
|
-
publishableKey: publishableKey,
|
|
278
|
-
userId: userId,
|
|
279
430
|
contacts: atsContacts,
|
|
280
431
|
};
|
|
281
432
|
if (paymentLink) msg.paymentLink = paymentLink;
|
|
@@ -460,18 +611,17 @@
|
|
|
460
611
|
|
|
461
612
|
var SeamlessRoleplay = {
|
|
462
613
|
/**
|
|
463
|
-
* Initialize the SDK with a
|
|
614
|
+
* Initialize the SDK with a host-provided user token callback.
|
|
464
615
|
*/
|
|
465
616
|
init: function (opts) {
|
|
466
617
|
try {
|
|
467
618
|
var initVersion = activeInitVersion + 1;
|
|
468
|
-
var
|
|
469
|
-
var hasLegacyIdentity = !!(opts && opts.userId && opts.userEmail);
|
|
619
|
+
var hasUserTokenProvider = !!(opts && typeof opts.getUserToken === "function");
|
|
470
620
|
|
|
471
|
-
if (!opts || !
|
|
621
|
+
if (!opts || !hasUserTokenProvider) {
|
|
472
622
|
var error = {
|
|
473
623
|
code: "INVALID_INIT",
|
|
474
|
-
message: "requires {
|
|
624
|
+
message: "requires { getUserToken }",
|
|
475
625
|
};
|
|
476
626
|
logError("init", error.message);
|
|
477
627
|
if (opts && typeof opts.onError === "function") {
|
|
@@ -489,13 +639,13 @@
|
|
|
489
639
|
clearSessionRequest();
|
|
490
640
|
sessionToken = null;
|
|
491
641
|
sessionExpiresAt = 0;
|
|
642
|
+
sessionRefreshBufferMs = 30000;
|
|
643
|
+
refreshRetryDelayMs = 5000;
|
|
492
644
|
paymentLink = null;
|
|
645
|
+
latestUserToken = null;
|
|
646
|
+
userTokenRequest = null;
|
|
493
647
|
|
|
494
|
-
|
|
495
|
-
userId = opts.userId || null;
|
|
496
|
-
userEmail = opts.userEmail || null;
|
|
497
|
-
userRole = opts.userRole || null;
|
|
498
|
-
userToken = opts.userToken || null;
|
|
648
|
+
getUserToken = opts.getUserToken;
|
|
499
649
|
appOrigin = opts.origin || null;
|
|
500
650
|
initCallbacks.onReady = opts.onReady || null;
|
|
501
651
|
initCallbacks.onError = opts.onError || null;
|
|
@@ -823,14 +973,14 @@
|
|
|
823
973
|
clearSessionRequest();
|
|
824
974
|
teardownDialog();
|
|
825
975
|
teardownMount();
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
userRole = null;
|
|
830
|
-
userToken = null;
|
|
976
|
+
getUserToken = null;
|
|
977
|
+
latestUserToken = null;
|
|
978
|
+
userTokenRequest = null;
|
|
831
979
|
paymentLink = null;
|
|
832
980
|
sessionToken = null;
|
|
833
981
|
sessionExpiresAt = 0;
|
|
982
|
+
sessionRefreshBufferMs = 30000;
|
|
983
|
+
refreshRetryDelayMs = 5000;
|
|
834
984
|
initCallbacks = { onReady: null, onError: null };
|
|
835
985
|
initCalled = false;
|
|
836
986
|
} catch (e) {
|