@rehers/rehers-roleplay-sdk 2.5.7 → 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 +22 -6
- package/index.d.ts +9 -3
- package/package.json +1 -1
- package/react.d.ts +1 -1
- package/react.js +3 -3
- package/roleplay-sdk.js +245 -77
package/README.md
CHANGED
|
@@ -17,8 +17,8 @@ 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
23
|
The browser should not mint sessions from raw identity fields in production.
|
|
24
24
|
|
|
@@ -26,11 +26,18 @@ The browser should not mint sessions from raw identity fields in production.
|
|
|
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
|
-
|
|
40
|
+
getUserToken={getUserToken}
|
|
34
41
|
onReady={() => console.log("Roleplay SDK ready")}
|
|
35
42
|
onError={(err) => console.error("Roleplay SDK error", err)}
|
|
36
43
|
>
|
|
@@ -55,7 +62,9 @@ const tokenRes = await fetch("/api/roleplay/user-token", {
|
|
|
55
62
|
const userToken = tokenRes.userToken;
|
|
56
63
|
```
|
|
57
64
|
|
|
58
|
-
|
|
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.
|
|
59
68
|
|
|
60
69
|
---
|
|
61
70
|
|
|
@@ -221,7 +230,14 @@ import "@rehers/rehers-roleplay-sdk";
|
|
|
221
230
|
|
|
222
231
|
// Initialize once
|
|
223
232
|
SeamlessRoleplay.init({
|
|
224
|
-
|
|
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
|
+
},
|
|
225
241
|
onReady() { console.log("ready"); },
|
|
226
242
|
});
|
|
227
243
|
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
export type SeamlessRoleplayUserTokenProvider = () => string | Promise<string>;
|
|
2
|
+
|
|
1
3
|
interface SeamlessRoleplayInitBase {
|
|
2
|
-
/**
|
|
3
|
-
|
|
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;
|
|
4
10
|
/** Override the app origin — where the iframe loads from (for dev/testing only) */
|
|
5
11
|
origin?: string;
|
|
6
12
|
/** Called when the SDK session is ready */
|
|
@@ -64,7 +70,7 @@ export interface AddToScenarioOptions {
|
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
export interface SeamlessRoleplaySDK {
|
|
67
|
-
/** Initialize the SDK with a
|
|
73
|
+
/** Initialize the SDK with a host-provided user token callback. */
|
|
68
74
|
init(options: SeamlessRoleplayInitOptions): void;
|
|
69
75
|
/** Open the roleplay modal for a contact (dialog mode). */
|
|
70
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);
|
|
@@ -37,7 +37,7 @@ export function SeamlessRoleplayProvider({ userToken, origin, onReady, onError,
|
|
|
37
37
|
mountedRef.current = true;
|
|
38
38
|
setState({ isReady: false, error: null });
|
|
39
39
|
const initOptions = {
|
|
40
|
-
|
|
40
|
+
getUserToken,
|
|
41
41
|
origin,
|
|
42
42
|
onReady: () => {
|
|
43
43
|
var _a;
|
|
@@ -60,7 +60,7 @@ export function SeamlessRoleplayProvider({ userToken, origin, onReady, onError,
|
|
|
60
60
|
providerMountCount--;
|
|
61
61
|
sdk.destroy();
|
|
62
62
|
};
|
|
63
|
-
}, [sdk,
|
|
63
|
+
}, [sdk, getUserToken, origin]);
|
|
64
64
|
const contextValue = useMemo(() => ({ ...state, sdk }), [state, sdk]);
|
|
65
65
|
return (_jsx(SeamlessRoleplayContext.Provider, { value: contextValue, children: children }));
|
|
66
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,13 +16,17 @@
|
|
|
16
16
|
var SDK_LOG_PREFIX = "[SeamlessRoleplay]";
|
|
17
17
|
|
|
18
18
|
// ── Auth state ────────────────────────────────────────────────────
|
|
19
|
-
var
|
|
19
|
+
var getUserToken = null;
|
|
20
|
+
var latestUserToken = null;
|
|
21
|
+
var userTokenRequest = null;
|
|
20
22
|
var paymentLink = null;
|
|
21
23
|
var appOrigin = null;
|
|
22
24
|
|
|
23
25
|
var sessionToken = null;
|
|
24
26
|
var sessionExpiresAt = 0; // epoch ms
|
|
27
|
+
var sessionRefreshBufferMs = 30000;
|
|
25
28
|
var refreshTimer = null;
|
|
29
|
+
var refreshRetryDelayMs = 5000;
|
|
26
30
|
var fetchingSession = null; // single-flight Promise
|
|
27
31
|
var activeSessionXhr = null;
|
|
28
32
|
var activeInitVersion = 0;
|
|
@@ -89,6 +93,16 @@
|
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
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
|
+
|
|
92
106
|
// ── Session management ────────────────────────────────────────────
|
|
93
107
|
|
|
94
108
|
function clearSessionRequest() {
|
|
@@ -101,88 +115,236 @@
|
|
|
101
115
|
fetchingSession = null;
|
|
102
116
|
}
|
|
103
117
|
|
|
104
|
-
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 || {};
|
|
105
234
|
var requestInitVersion = activeInitVersion;
|
|
106
235
|
|
|
107
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
|
+
}
|
|
108
249
|
return fetchingSession.promise;
|
|
109
250
|
}
|
|
110
251
|
|
|
111
|
-
var requestPromise =
|
|
112
|
-
|
|
113
|
-
|
|
252
|
+
var requestPromise = resolveUserToken().then(function (freshUserToken) {
|
|
253
|
+
if (requestInitVersion !== activeInitVersion) {
|
|
254
|
+
throw makeError("ABORTED", "Stale session request ignored");
|
|
255
|
+
}
|
|
114
256
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
xhr.setRequestHeader("Content-Type", "application/json");
|
|
119
|
-
xhr.withCredentials = false;
|
|
120
|
-
xhr.timeout = SESSION_TIMEOUT_MS;
|
|
257
|
+
return new Promise(function (resolve, reject) {
|
|
258
|
+
var url = getApiOrigin() + "/api/sdk/session";
|
|
259
|
+
var body = { userToken: freshUserToken };
|
|
121
260
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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;
|
|
267
|
+
|
|
268
|
+
function cleanupRequest() {
|
|
269
|
+
if (activeSessionXhr === xhr) {
|
|
270
|
+
activeSessionXhr = null;
|
|
271
|
+
}
|
|
272
|
+
if (fetchingSession && fetchingSession.promise === requestPromise) {
|
|
273
|
+
fetchingSession = null;
|
|
274
|
+
}
|
|
128
275
|
}
|
|
129
|
-
}
|
|
130
276
|
|
|
131
|
-
|
|
132
|
-
|
|
277
|
+
xhr.onload = function () {
|
|
278
|
+
cleanupRequest();
|
|
133
279
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
280
|
+
if (requestInitVersion !== activeInitVersion) {
|
|
281
|
+
reject({ code: "ABORTED", message: "Stale session response ignored" });
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
138
284
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
+
}
|
|
146
292
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
+
}
|
|
155
304
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
}
|
|
163
317
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
318
|
+
reject({
|
|
319
|
+
code: data.error || "SESSION_ERROR",
|
|
320
|
+
message: data.message || "Failed to create session (HTTP " + xhr.status + ")",
|
|
321
|
+
});
|
|
322
|
+
};
|
|
169
323
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
324
|
+
xhr.onerror = function () {
|
|
325
|
+
cleanupRequest();
|
|
326
|
+
reject({ code: "NETWORK_ERROR", message: "Network error contacting session endpoint" });
|
|
327
|
+
};
|
|
174
328
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
329
|
+
xhr.ontimeout = function () {
|
|
330
|
+
cleanupRequest();
|
|
331
|
+
reject({ code: "TIMEOUT", message: "Session request timed out after " + SESSION_TIMEOUT_MS + "ms" });
|
|
332
|
+
};
|
|
179
333
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
334
|
+
xhr.onabort = function () {
|
|
335
|
+
cleanupRequest();
|
|
336
|
+
reject({ code: "ABORTED", message: "Session request was aborted" });
|
|
337
|
+
};
|
|
184
338
|
|
|
185
|
-
|
|
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;
|
|
186
348
|
});
|
|
187
349
|
|
|
188
350
|
fetchingSession = {
|
|
@@ -194,7 +356,7 @@
|
|
|
194
356
|
}
|
|
195
357
|
|
|
196
358
|
function getSessionToken() {
|
|
197
|
-
if (sessionToken && Date.now() < sessionExpiresAt -
|
|
359
|
+
if (sessionToken && Date.now() < sessionExpiresAt - sessionRefreshBufferMs) {
|
|
198
360
|
return Promise.resolve(sessionToken);
|
|
199
361
|
}
|
|
200
362
|
return fetchSession().then(function (result) {
|
|
@@ -204,11 +366,9 @@
|
|
|
204
366
|
|
|
205
367
|
function scheduleRefresh(ttlMs) {
|
|
206
368
|
if (refreshTimer) clearTimeout(refreshTimer);
|
|
207
|
-
var delay =
|
|
369
|
+
var delay = computeRefreshDelay(ttlMs);
|
|
208
370
|
refreshTimer = setTimeout(function () {
|
|
209
|
-
fetchSession().catch(
|
|
210
|
-
// Silent — next open() will retry
|
|
211
|
-
});
|
|
371
|
+
fetchSession({ broadcastRefresh: true }).catch(scheduleRefreshRetry);
|
|
212
372
|
}, delay);
|
|
213
373
|
}
|
|
214
374
|
|
|
@@ -451,17 +611,17 @@
|
|
|
451
611
|
|
|
452
612
|
var SeamlessRoleplay = {
|
|
453
613
|
/**
|
|
454
|
-
* Initialize the SDK with a
|
|
614
|
+
* Initialize the SDK with a host-provided user token callback.
|
|
455
615
|
*/
|
|
456
616
|
init: function (opts) {
|
|
457
617
|
try {
|
|
458
618
|
var initVersion = activeInitVersion + 1;
|
|
459
|
-
var
|
|
619
|
+
var hasUserTokenProvider = !!(opts && typeof opts.getUserToken === "function");
|
|
460
620
|
|
|
461
|
-
if (!opts || !
|
|
621
|
+
if (!opts || !hasUserTokenProvider) {
|
|
462
622
|
var error = {
|
|
463
623
|
code: "INVALID_INIT",
|
|
464
|
-
message: "requires {
|
|
624
|
+
message: "requires { getUserToken }",
|
|
465
625
|
};
|
|
466
626
|
logError("init", error.message);
|
|
467
627
|
if (opts && typeof opts.onError === "function") {
|
|
@@ -479,9 +639,13 @@
|
|
|
479
639
|
clearSessionRequest();
|
|
480
640
|
sessionToken = null;
|
|
481
641
|
sessionExpiresAt = 0;
|
|
642
|
+
sessionRefreshBufferMs = 30000;
|
|
643
|
+
refreshRetryDelayMs = 5000;
|
|
482
644
|
paymentLink = null;
|
|
645
|
+
latestUserToken = null;
|
|
646
|
+
userTokenRequest = null;
|
|
483
647
|
|
|
484
|
-
|
|
648
|
+
getUserToken = opts.getUserToken;
|
|
485
649
|
appOrigin = opts.origin || null;
|
|
486
650
|
initCallbacks.onReady = opts.onReady || null;
|
|
487
651
|
initCallbacks.onError = opts.onError || null;
|
|
@@ -809,10 +973,14 @@
|
|
|
809
973
|
clearSessionRequest();
|
|
810
974
|
teardownDialog();
|
|
811
975
|
teardownMount();
|
|
812
|
-
|
|
976
|
+
getUserToken = null;
|
|
977
|
+
latestUserToken = null;
|
|
978
|
+
userTokenRequest = null;
|
|
813
979
|
paymentLink = null;
|
|
814
980
|
sessionToken = null;
|
|
815
981
|
sessionExpiresAt = 0;
|
|
982
|
+
sessionRefreshBufferMs = 30000;
|
|
983
|
+
refreshRetryDelayMs = 5000;
|
|
816
984
|
initCallbacks = { onReady: null, onError: null };
|
|
817
985
|
initCalled = false;
|
|
818
986
|
} catch (e) {
|