@rehers/rehers-roleplay-sdk 2.5.2 → 2.5.4
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/package.json +2 -2
- package/roleplay-sdk.js +137 -16
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.4",
|
|
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/roleplay-sdk.js
CHANGED
|
@@ -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;
|
|
@@ -48,6 +50,17 @@
|
|
|
48
50
|
var dialogAddToScenarioPendingContacts = null;
|
|
49
51
|
var dialogListener = null;
|
|
50
52
|
var dialogCloseTeardownTimer = null;
|
|
53
|
+
var dialogRevealTimer = null;
|
|
54
|
+
var mountRevealTimer = null;
|
|
55
|
+
|
|
56
|
+
// Max wait before we reveal the iframe even if it never sends ROLEPLAY_READY.
|
|
57
|
+
// Prevents users from staring at an empty container if the hosted app is slow
|
|
58
|
+
// or fails to signal. 3s is conservative; the handshake normally lands in < 500ms.
|
|
59
|
+
var IFRAME_REVEAL_FALLBACK_MS = 3000;
|
|
60
|
+
|
|
61
|
+
function revealIframe(iframeEl) {
|
|
62
|
+
if (iframeEl) iframeEl.style.opacity = "1";
|
|
63
|
+
}
|
|
51
64
|
|
|
52
65
|
// ── Safe logging ──────────────────────────────────────────────────
|
|
53
66
|
|
|
@@ -93,24 +106,54 @@
|
|
|
93
106
|
|
|
94
107
|
// ── Session management ────────────────────────────────────────────
|
|
95
108
|
|
|
109
|
+
function clearSessionRequest() {
|
|
110
|
+
if (activeSessionXhr) {
|
|
111
|
+
try {
|
|
112
|
+
activeSessionXhr.abort();
|
|
113
|
+
} catch (_) {}
|
|
114
|
+
activeSessionXhr = null;
|
|
115
|
+
}
|
|
116
|
+
fetchingSession = null;
|
|
117
|
+
}
|
|
118
|
+
|
|
96
119
|
function fetchSession() {
|
|
97
|
-
|
|
120
|
+
var requestInitVersion = activeInitVersion;
|
|
121
|
+
|
|
122
|
+
if (fetchingSession && fetchingSession.initVersion === requestInitVersion) {
|
|
123
|
+
return fetchingSession.promise;
|
|
124
|
+
}
|
|
98
125
|
|
|
99
|
-
|
|
126
|
+
var requestPromise = new Promise(function (resolve, reject) {
|
|
100
127
|
var url = getApiOrigin() + "/api/sdk/session";
|
|
101
128
|
var body = userToken
|
|
102
129
|
? { userToken: userToken }
|
|
103
130
|
: { userId: userId, userEmail: userEmail, userRole: userRole };
|
|
104
131
|
|
|
105
132
|
var xhr = new XMLHttpRequest();
|
|
133
|
+
activeSessionXhr = xhr;
|
|
106
134
|
xhr.open("POST", url, true);
|
|
107
135
|
xhr.setRequestHeader("Content-Type", "application/json");
|
|
108
136
|
xhr.setRequestHeader("X-Publishable-Key", publishableKey);
|
|
109
137
|
xhr.withCredentials = false;
|
|
110
138
|
xhr.timeout = SESSION_TIMEOUT_MS;
|
|
111
139
|
|
|
140
|
+
function cleanupRequest() {
|
|
141
|
+
if (activeSessionXhr === xhr) {
|
|
142
|
+
activeSessionXhr = null;
|
|
143
|
+
}
|
|
144
|
+
if (fetchingSession && fetchingSession.promise === requestPromise) {
|
|
145
|
+
fetchingSession = null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
112
149
|
xhr.onload = function () {
|
|
113
|
-
|
|
150
|
+
cleanupRequest();
|
|
151
|
+
|
|
152
|
+
if (requestInitVersion !== activeInitVersion) {
|
|
153
|
+
reject({ code: "ABORTED", message: "Stale session response ignored" });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
114
157
|
var data;
|
|
115
158
|
try {
|
|
116
159
|
data = JSON.parse(xhr.responseText);
|
|
@@ -143,24 +186,29 @@
|
|
|
143
186
|
};
|
|
144
187
|
|
|
145
188
|
xhr.onerror = function () {
|
|
146
|
-
|
|
189
|
+
cleanupRequest();
|
|
147
190
|
reject({ code: "NETWORK_ERROR", message: "Network error contacting session endpoint" });
|
|
148
191
|
};
|
|
149
192
|
|
|
150
193
|
xhr.ontimeout = function () {
|
|
151
|
-
|
|
194
|
+
cleanupRequest();
|
|
152
195
|
reject({ code: "TIMEOUT", message: "Session request timed out after " + SESSION_TIMEOUT_MS + "ms" });
|
|
153
196
|
};
|
|
154
197
|
|
|
155
198
|
xhr.onabort = function () {
|
|
156
|
-
|
|
199
|
+
cleanupRequest();
|
|
157
200
|
reject({ code: "ABORTED", message: "Session request was aborted" });
|
|
158
201
|
};
|
|
159
202
|
|
|
160
203
|
xhr.send(JSON.stringify(body));
|
|
161
204
|
});
|
|
162
205
|
|
|
163
|
-
|
|
206
|
+
fetchingSession = {
|
|
207
|
+
initVersion: requestInitVersion,
|
|
208
|
+
promise: requestPromise,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
return requestPromise;
|
|
164
212
|
}
|
|
165
213
|
|
|
166
214
|
function getSessionToken() {
|
|
@@ -190,6 +238,10 @@
|
|
|
190
238
|
clearTimeout(dialogCloseTeardownTimer);
|
|
191
239
|
dialogCloseTeardownTimer = null;
|
|
192
240
|
}
|
|
241
|
+
if (dialogRevealTimer) {
|
|
242
|
+
clearTimeout(dialogRevealTimer);
|
|
243
|
+
dialogRevealTimer = null;
|
|
244
|
+
}
|
|
193
245
|
|
|
194
246
|
if (dialogOverlay && dialogOverlay.parentNode) {
|
|
195
247
|
dialogOverlay.parentNode.removeChild(dialogOverlay);
|
|
@@ -214,6 +266,10 @@
|
|
|
214
266
|
|
|
215
267
|
function teardownMount() {
|
|
216
268
|
try {
|
|
269
|
+
if (mountRevealTimer) {
|
|
270
|
+
clearTimeout(mountRevealTimer);
|
|
271
|
+
mountRevealTimer = null;
|
|
272
|
+
}
|
|
217
273
|
if (mountIframe && mountIframe.parentNode) {
|
|
218
274
|
mountIframe.parentNode.removeChild(mountIframe);
|
|
219
275
|
}
|
|
@@ -284,6 +340,11 @@
|
|
|
284
340
|
|
|
285
341
|
switch (data.type) {
|
|
286
342
|
case "ROLEPLAY_READY":
|
|
343
|
+
if (mountRevealTimer) {
|
|
344
|
+
clearTimeout(mountRevealTimer);
|
|
345
|
+
mountRevealTimer = null;
|
|
346
|
+
}
|
|
347
|
+
revealIframe(mountIframe);
|
|
287
348
|
getSessionToken()
|
|
288
349
|
.then(function (token) {
|
|
289
350
|
dispatchInitToTarget(mountIframe, null, null);
|
|
@@ -322,6 +383,11 @@
|
|
|
322
383
|
|
|
323
384
|
switch (data.type) {
|
|
324
385
|
case "ROLEPLAY_READY":
|
|
386
|
+
if (dialogRevealTimer) {
|
|
387
|
+
clearTimeout(dialogRevealTimer);
|
|
388
|
+
dialogRevealTimer = null;
|
|
389
|
+
}
|
|
390
|
+
revealIframe(dialogIframe);
|
|
325
391
|
getSessionToken()
|
|
326
392
|
.then(function (token) {
|
|
327
393
|
dispatchInitToTarget(dialogIframe, dialogContactData, dialogAddToScenarioPendingContacts);
|
|
@@ -332,9 +398,9 @@
|
|
|
332
398
|
break;
|
|
333
399
|
|
|
334
400
|
case "ROLEPLAY_ERROR":
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
401
|
+
var regularOnError = dialogCallbacks.onError;
|
|
402
|
+
teardownDialog();
|
|
403
|
+
if (regularOnError) regularOnError({ code: data.code, message: data.message });
|
|
338
404
|
break;
|
|
339
405
|
|
|
340
406
|
case "ROLEPLAY_CLOSED":
|
|
@@ -357,9 +423,9 @@
|
|
|
357
423
|
break;
|
|
358
424
|
|
|
359
425
|
case "ADD_TO_SCENARIO_ERROR":
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
426
|
+
var atsOnError = dialogAddToScenarioCallbacks.onError;
|
|
427
|
+
teardownDialog();
|
|
428
|
+
if (atsOnError) atsOnError({ code: data.code, message: data.message });
|
|
363
429
|
break;
|
|
364
430
|
|
|
365
431
|
case "ADD_TO_SCENARIO_CLOSED":
|
|
@@ -379,10 +445,30 @@
|
|
|
379
445
|
try {
|
|
380
446
|
sendMsg(dialogIframe, { type: "roleplay-close" });
|
|
381
447
|
if (dialogCloseTeardownTimer) clearTimeout(dialogCloseTeardownTimer);
|
|
382
|
-
dialogCloseTeardownTimer = setTimeout(
|
|
448
|
+
dialogCloseTeardownTimer = setTimeout(function () {
|
|
449
|
+
// Fallback path: iframe didn't respond with ROLEPLAY_CLOSED or
|
|
450
|
+
// ADD_TO_SCENARIO_CLOSED. Fire the host's onClose ourselves so
|
|
451
|
+
// React state (or any controlled caller) can re-render cleanly
|
|
452
|
+
// and reopen the dialog later.
|
|
453
|
+
var mode = dialogMode;
|
|
454
|
+
var atsClose = dialogAddToScenarioCallbacks.onClose;
|
|
455
|
+
var regularClose = dialogCallbacks.onClose;
|
|
456
|
+
teardownDialog();
|
|
457
|
+
try {
|
|
458
|
+
if (mode === "add-to-scenario" && atsClose) atsClose();
|
|
459
|
+
else if (regularClose) regularClose();
|
|
460
|
+
} catch (_) {}
|
|
461
|
+
}, 300);
|
|
383
462
|
} catch (e) {
|
|
384
463
|
logError("close", e);
|
|
464
|
+
var mode2 = dialogMode;
|
|
465
|
+
var atsClose2 = dialogAddToScenarioCallbacks.onClose;
|
|
466
|
+
var regularClose2 = dialogCallbacks.onClose;
|
|
385
467
|
teardownDialog();
|
|
468
|
+
try {
|
|
469
|
+
if (mode2 === "add-to-scenario" && atsClose2) atsClose2();
|
|
470
|
+
else if (regularClose2) regularClose2();
|
|
471
|
+
} catch (_) {}
|
|
386
472
|
}
|
|
387
473
|
}
|
|
388
474
|
|
|
@@ -396,6 +482,11 @@
|
|
|
396
482
|
iframeEl.style.height = "100%";
|
|
397
483
|
iframeEl.style.border = "none";
|
|
398
484
|
iframeEl.style.display = "block";
|
|
485
|
+
// Start invisible. The host app flashes a brief unstyled loader before it's
|
|
486
|
+
// ready; we reveal the iframe once ROLEPLAY_READY arrives (or after a
|
|
487
|
+
// fallback timeout). See revealIframe() callers.
|
|
488
|
+
iframeEl.style.opacity = "0";
|
|
489
|
+
iframeEl.style.transition = "opacity 120ms ease-out";
|
|
399
490
|
return iframeEl;
|
|
400
491
|
}
|
|
401
492
|
|
|
@@ -407,6 +498,7 @@
|
|
|
407
498
|
*/
|
|
408
499
|
init: function (opts) {
|
|
409
500
|
try {
|
|
501
|
+
var initVersion = activeInitVersion + 1;
|
|
410
502
|
var hasUserToken = !!(opts && opts.userToken && String(opts.userToken).trim());
|
|
411
503
|
var hasLegacyIdentity = !!(opts && opts.userId && opts.userEmail);
|
|
412
504
|
|
|
@@ -427,7 +519,8 @@
|
|
|
427
519
|
clearTimeout(refreshTimer);
|
|
428
520
|
refreshTimer = null;
|
|
429
521
|
}
|
|
430
|
-
|
|
522
|
+
activeInitVersion = initVersion;
|
|
523
|
+
clearSessionRequest();
|
|
431
524
|
sessionToken = null;
|
|
432
525
|
sessionExpiresAt = 0;
|
|
433
526
|
paymentLink = null;
|
|
@@ -445,6 +538,7 @@
|
|
|
445
538
|
// Fetch session immediately
|
|
446
539
|
fetchSession()
|
|
447
540
|
.then(function (result) {
|
|
541
|
+
if (initVersion !== activeInitVersion) return;
|
|
448
542
|
if (result.trialMode && !paymentLink) {
|
|
449
543
|
// User not found and no payment link from server — still call onReady
|
|
450
544
|
// The open() will show an error state in the iframe
|
|
@@ -452,6 +546,8 @@
|
|
|
452
546
|
if (initCallbacks.onReady) initCallbacks.onReady();
|
|
453
547
|
})
|
|
454
548
|
.catch(function (err) {
|
|
549
|
+
if (initVersion !== activeInitVersion) return;
|
|
550
|
+
if (err && err.code === "ABORTED") return;
|
|
455
551
|
if (initCallbacks.onError) {
|
|
456
552
|
initCallbacks.onError({ code: err.code || "INIT_ERROR", message: err.message || "Initialization failed" });
|
|
457
553
|
}
|
|
@@ -553,6 +649,13 @@
|
|
|
553
649
|
dialogOverlay = el;
|
|
554
650
|
dialogIframe = iframeEl;
|
|
555
651
|
document.body.appendChild(dialogOverlay);
|
|
652
|
+
|
|
653
|
+
// Fallback: reveal the iframe even if ROLEPLAY_READY never arrives.
|
|
654
|
+
if (dialogRevealTimer) clearTimeout(dialogRevealTimer);
|
|
655
|
+
dialogRevealTimer = setTimeout(function () {
|
|
656
|
+
revealIframe(dialogIframe);
|
|
657
|
+
dialogRevealTimer = null;
|
|
658
|
+
}, IFRAME_REVEAL_FALLBACK_MS);
|
|
556
659
|
} catch (e) {
|
|
557
660
|
logError("open", e);
|
|
558
661
|
teardownDialog();
|
|
@@ -589,6 +692,13 @@
|
|
|
589
692
|
var iframeEl = createIframe("/");
|
|
590
693
|
mountIframe = iframeEl;
|
|
591
694
|
container.appendChild(iframeEl);
|
|
695
|
+
|
|
696
|
+
// Fallback: reveal the iframe even if ROLEPLAY_READY never arrives.
|
|
697
|
+
if (mountRevealTimer) clearTimeout(mountRevealTimer);
|
|
698
|
+
mountRevealTimer = setTimeout(function () {
|
|
699
|
+
revealIframe(mountIframe);
|
|
700
|
+
mountRevealTimer = null;
|
|
701
|
+
}, IFRAME_REVEAL_FALLBACK_MS);
|
|
592
702
|
} catch (e) {
|
|
593
703
|
logError("mount", e);
|
|
594
704
|
teardownMount();
|
|
@@ -679,6 +789,9 @@
|
|
|
679
789
|
iframeEl.style.top = "0";
|
|
680
790
|
iframeEl.style.left = "0";
|
|
681
791
|
iframeEl.style.zIndex = "1";
|
|
792
|
+
// Hide until ROLEPLAY_READY so we don't flash the host app's pre-init state.
|
|
793
|
+
iframeEl.style.opacity = "0";
|
|
794
|
+
iframeEl.style.transition = "opacity 120ms ease-out";
|
|
682
795
|
|
|
683
796
|
var closeBtn = document.createElement("button");
|
|
684
797
|
closeBtn.type = "button";
|
|
@@ -716,6 +829,13 @@
|
|
|
716
829
|
dialogOverlay = el;
|
|
717
830
|
dialogIframe = iframeEl;
|
|
718
831
|
document.body.appendChild(dialogOverlay);
|
|
832
|
+
|
|
833
|
+
// Fallback: reveal the iframe even if ROLEPLAY_READY never arrives.
|
|
834
|
+
if (dialogRevealTimer) clearTimeout(dialogRevealTimer);
|
|
835
|
+
dialogRevealTimer = setTimeout(function () {
|
|
836
|
+
revealIframe(dialogIframe);
|
|
837
|
+
dialogRevealTimer = null;
|
|
838
|
+
}, IFRAME_REVEAL_FALLBACK_MS);
|
|
719
839
|
} catch (e) {
|
|
720
840
|
logError("addToScenario", e);
|
|
721
841
|
teardownDialog();
|
|
@@ -753,10 +873,12 @@
|
|
|
753
873
|
*/
|
|
754
874
|
destroy: function () {
|
|
755
875
|
try {
|
|
876
|
+
activeInitVersion++;
|
|
756
877
|
if (refreshTimer) {
|
|
757
878
|
clearTimeout(refreshTimer);
|
|
758
879
|
refreshTimer = null;
|
|
759
880
|
}
|
|
881
|
+
clearSessionRequest();
|
|
760
882
|
teardownDialog();
|
|
761
883
|
teardownMount();
|
|
762
884
|
publishableKey = null;
|
|
@@ -767,7 +889,6 @@
|
|
|
767
889
|
paymentLink = null;
|
|
768
890
|
sessionToken = null;
|
|
769
891
|
sessionExpiresAt = 0;
|
|
770
|
-
fetchingSession = null;
|
|
771
892
|
initCallbacks = { onReady: null, onError: null };
|
|
772
893
|
initCalled = false;
|
|
773
894
|
} catch (e) {
|