@returningai/widget-sdk 1.2.15 → 1.3.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 +505 -527
- package/dist/rai-widget.iife.js +1 -1
- package/dist/rai-widget.js +401 -120
- package/dist/types/BaseWidget.d.ts +50 -9
- package/dist/types/core/auth.d.ts +10 -2
- package/dist/types/types.d.ts +19 -0
- package/package.json +1 -1
package/dist/rai-widget.js
CHANGED
|
@@ -41,8 +41,8 @@ function clearStorage(config) {
|
|
|
41
41
|
} catch {
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
const ERROR_SETTINGS_CACHE_TTL = 36e5;
|
|
44
45
|
function loadErrorSettingsFromStorage(config) {
|
|
45
|
-
const ERROR_SETTINGS_CACHE_TTL = 36e5;
|
|
46
46
|
try {
|
|
47
47
|
const raw = localStorage.getItem(errorSettingsKey(config));
|
|
48
48
|
if (!raw) return null;
|
|
@@ -91,22 +91,41 @@ async function authenticateServerless(config, state, onRefreshScheduled) {
|
|
|
91
91
|
}
|
|
92
92
|
let is4xx = false;
|
|
93
93
|
try {
|
|
94
|
+
const body = {
|
|
95
|
+
nonce,
|
|
96
|
+
timestamp,
|
|
97
|
+
widgetType: config.widgetType
|
|
98
|
+
};
|
|
99
|
+
if (config.embedToken) {
|
|
100
|
+
body.embedToken = config.embedToken;
|
|
101
|
+
}
|
|
102
|
+
if (config.userIdentifiers && Object.keys(config.userIdentifiers).length > 0) {
|
|
103
|
+
body.userIdentifiers = config.userIdentifiers;
|
|
104
|
+
}
|
|
94
105
|
const response = await fetch(`${config.apiUrl}/${config.widgetId}/auth/serverless`, {
|
|
95
106
|
method: "POST",
|
|
96
107
|
headers: { "Content-Type": "application/json" },
|
|
97
|
-
body: JSON.stringify(
|
|
98
|
-
nonce,
|
|
99
|
-
timestamp,
|
|
100
|
-
widgetType: config.widgetType,
|
|
101
|
-
userIdentifiers: config.userIdentifiers
|
|
102
|
-
}),
|
|
108
|
+
body: JSON.stringify(body),
|
|
103
109
|
credentials: "include"
|
|
104
110
|
});
|
|
105
111
|
if (!response.ok) {
|
|
106
112
|
is4xx = response.status >= 400 && response.status < 500;
|
|
107
|
-
const
|
|
108
|
-
|
|
113
|
+
const errorBody = await response.json().catch(() => ({ error: "Authentication failed" }));
|
|
114
|
+
if (is4xx) {
|
|
115
|
+
state.lastAuthError = {
|
|
116
|
+
status: response.status,
|
|
117
|
+
error: errorBody == null ? void 0 : errorBody.error,
|
|
118
|
+
hint: errorBody == null ? void 0 : errorBody.hint
|
|
119
|
+
};
|
|
120
|
+
console.error(
|
|
121
|
+
`[rai-widget] /auth/serverless ${response.status}:`,
|
|
122
|
+
(errorBody == null ? void 0 : errorBody.error) || "Authentication failed",
|
|
123
|
+
(errorBody == null ? void 0 : errorBody.hint) ? `— ${errorBody.hint}` : ""
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
throw new Error((errorBody == null ? void 0 : errorBody.error) || `HTTP ${response.status}`);
|
|
109
127
|
}
|
|
128
|
+
state.lastAuthError = null;
|
|
110
129
|
const data = await response.json();
|
|
111
130
|
if (!data.accessToken || !data.refreshToken) {
|
|
112
131
|
throw new Error("Invalid authentication response");
|
|
@@ -246,56 +265,61 @@ async function logout(config, state) {
|
|
|
246
265
|
clearState(state);
|
|
247
266
|
clearStorage(config);
|
|
248
267
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
268
|
+
const inflightErrorSettings = /* @__PURE__ */ new Map();
|
|
269
|
+
function applyErrorSettingsToState(state, data) {
|
|
270
|
+
if (data.configured) {
|
|
271
|
+
state.errorSettings = {
|
|
272
|
+
errorMessage: data.errorMessage,
|
|
273
|
+
modalColor: data.modalColor,
|
|
274
|
+
backgroundImage: data.backgroundImage
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (data.loadingAnimation) {
|
|
278
|
+
state.loadingAnimation = data.loadingAnimation;
|
|
279
|
+
}
|
|
280
|
+
if (data.themePalette) {
|
|
281
|
+
state.themePalette = data.themePalette;
|
|
282
|
+
}
|
|
283
|
+
if (typeof data.font === "string" && data.font) {
|
|
284
|
+
state.font = data.font;
|
|
285
|
+
}
|
|
286
|
+
if (data.defaultColorSchema === "light" || data.defaultColorSchema === "dark") {
|
|
287
|
+
state.defaultColorSchema = data.defaultColorSchema;
|
|
260
288
|
}
|
|
261
289
|
}
|
|
262
|
-
async function fetchErrorSettings(config, state) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (cached
|
|
266
|
-
state
|
|
267
|
-
|
|
268
|
-
modalColor: cached.modalColor,
|
|
269
|
-
backgroundImage: cached.backgroundImage
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
if (cached.loadingAnimation) {
|
|
273
|
-
state.loadingAnimation = cached.loadingAnimation;
|
|
290
|
+
async function fetchErrorSettings(config, state, opts = {}) {
|
|
291
|
+
if (!opts.skipCache) {
|
|
292
|
+
const cached = loadErrorSettingsFromStorage(config);
|
|
293
|
+
if (cached) {
|
|
294
|
+
applyErrorSettingsToState(state, cached);
|
|
295
|
+
return state.errorSettings;
|
|
274
296
|
}
|
|
275
|
-
return state.errorSettings;
|
|
276
297
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
return null;
|
|
298
|
+
const url = `${config.apiUrl}/${config.widgetId}/auth/error-settings?widgetType=${config.widgetType}`;
|
|
299
|
+
let promise = inflightErrorSettings.get(url);
|
|
300
|
+
if (!promise) {
|
|
301
|
+
promise = (async () => {
|
|
302
|
+
try {
|
|
303
|
+
const response = await fetch(url, {
|
|
304
|
+
method: "GET",
|
|
305
|
+
headers: { "Content-Type": "application/json" }
|
|
306
|
+
});
|
|
307
|
+
if (!response.ok) return null;
|
|
308
|
+
const data2 = await response.json();
|
|
309
|
+
saveErrorSettingsToStorage(config, data2);
|
|
310
|
+
return data2;
|
|
311
|
+
} catch {
|
|
312
|
+
return null;
|
|
313
|
+
} finally {
|
|
314
|
+
inflightErrorSettings.delete(url);
|
|
315
|
+
}
|
|
316
|
+
})();
|
|
317
|
+
inflightErrorSettings.set(url, promise);
|
|
298
318
|
}
|
|
319
|
+
const data = await promise;
|
|
320
|
+
if (!data) return null;
|
|
321
|
+
applyErrorSettingsToState(state, data);
|
|
322
|
+
return state.errorSettings;
|
|
299
323
|
}
|
|
300
324
|
function clearState(state) {
|
|
301
325
|
state.accessToken = null;
|
|
@@ -306,6 +330,7 @@ function clearState(state) {
|
|
|
306
330
|
state.isAuthenticated = false;
|
|
307
331
|
state.isRefreshing = false;
|
|
308
332
|
state.refreshPromise = null;
|
|
333
|
+
state.lastAuthError = null;
|
|
309
334
|
if (state.refreshTimer) {
|
|
310
335
|
clearTimeout(state.refreshTimer);
|
|
311
336
|
state.refreshTimer = null;
|
|
@@ -411,7 +436,7 @@ function setupMessageListener(config, state, _shadow, iframe, onRefreshScheduled
|
|
|
411
436
|
heightSetter.cancel();
|
|
412
437
|
};
|
|
413
438
|
}
|
|
414
|
-
const widgetCSS = ':host{display:block;position:relative;width:100%;height:100%}.rai-loader{position:absolute;top:0;left:0;display:flex;align-items:center;justify-content:center;width:100%;height:100%;background:var(--rai-loader-bg, #ffffff);border-radius:8px;z-index:10;transition:opacity .3s ease-out}.rai-loader.fade-out{opacity:0;pointer-events:none}.rai-error{display:none;position:absolute;top:0;left:0;width:100%;height:100%;align-items:center;justify-content:center;flex-direction:column;gap:12px;background:var(--rai-error-bg, #1a1a1a);border-radius:8px;padding:24px;box-sizing:border-box;text-align:center;color:var(--rai-error-text, #9ca3af);font-family:system-ui,-apple-system,sans-serif;z-index:10}.rai-error.visible{display:flex}iframe{display:block;border:none;opacity:0;transition:opacity .3s ease-in}iframe.loaded{opacity:1}.rai-lottie-loader{width:100px;height:100px}.rai-lottie-loader svg{width:100%!important;height:100%!important}.rai-lottie-spinner{width:32px;height:32px;margin:34px auto;border:3px solid var(--rai-text4, #6b7280);border-top-color:var(--rai-accent, #000000);border-radius:50%;animation:rai-spin .7s linear infinite}@keyframes rai-spin{to{transform:rotate(360deg)}}.rai-sdk-loader{position:relative;width:75px;height:100px}.rai-sdk-loader__bar{position:absolute;bottom:0;width:10px;height:50%;background:var(--rai-accent, #000000);transform-origin:center bottom;box-shadow:1px 1px #0003}.rai-sdk-loader__bar:nth-child(1){left:0;transform:scaleY(.2);animation:rai-sdk-barUp1 4s infinite}.rai-sdk-loader__bar:nth-child(2){left:15px;transform:scaleY(.4);animation:rai-sdk-barUp2 4s infinite}.rai-sdk-loader__bar:nth-child(3){left:30px;transform:scaleY(.6);animation:rai-sdk-barUp3 4s infinite}.rai-sdk-loader__bar:nth-child(4){left:45px;transform:scaleY(.8);animation:rai-sdk-barUp4 4s infinite}.rai-sdk-loader__bar:nth-child(5){left:60px;transform:scale(1);animation:rai-sdk-barUp5 4s infinite}.rai-sdk-loader__ball{position:absolute;bottom:10px;left:0;width:10px;height:10px;background:var(--rai-accent, #000000);border-radius:50%;animation:rai-sdk-ball 4s infinite}@keyframes rai-sdk-ball{0%{transform:translate(0)}5%{transform:translate(8px,-14px)}10%{transform:translate(15px,-10px)}17%{transform:translate(23px,-24px)}20%{transform:translate(30px,-20px)}27%{transform:translate(38px,-34px)}30%{transform:translate(45px,-30px)}37%{transform:translate(53px,-44px)}40%{transform:translate(60px,-40px)}50%{transform:translate(60px)}57%{transform:translate(53px,-14px)}60%{transform:translate(45px,-10px)}67%{transform:translate(37px,-24px)}70%{transform:translate(30px,-20px)}77%{transform:translate(22px,-34px)}80%{transform:translate(15px,-30px)}87%{transform:translate(7px,-44px)}90%{transform:translateY(-40px)}to{transform:translate(0)}}@keyframes rai-sdk-barUp1{0%{transform:scaleY(.2)}40%{transform:scaleY(.2)}50%{transform:scale(1)}90%{transform:scale(1)}to{transform:scaleY(.2)}}@keyframes rai-sdk-barUp2{0%{transform:scaleY(.4)}40%{transform:scaleY(.4)}50%{transform:scaleY(.8)}90%{transform:scaleY(.8)}to{transform:scaleY(.4)}}@keyframes rai-sdk-barUp3{0%{transform:scaleY(.6)}to{transform:scaleY(.6)}}@keyframes rai-sdk-barUp4{0%{transform:scaleY(.8)}40%{transform:scaleY(.8)}50%{transform:scaleY(.4)}90%{transform:scaleY(.4)}to{transform:scaleY(.8)}}@keyframes rai-sdk-barUp5{0%{transform:scale(1)}40%{transform:scale(1)}50%{transform:scaleY(.2)}90%{transform:scaleY(.2)}to{transform:scale(1)}}#loading-square{width:75px;aspect-ratio:1;display:flex;color:var(--rai-accent, #000000);background:linear-gradient(currentColor 0 0) right / 51% 100%,linear-gradient(currentColor 0 0) bottom / 100% 51%;background-repeat:no-repeat;animation:l16-0 2s infinite linear .25s}#loading-square>div{width:50%;height:50%;background:currentColor;animation:l16-1 .5s infinite linear}@keyframes l16-0{0%,12.49%{transform:rotate(0)}12.5%,37.49%{transform:rotate(90deg)}37.5%,62.49%{transform:rotate(180deg)}62.5%,87.49%{transform:rotate(270deg)}87.5%,to{transform:rotate(360deg)}}@keyframes l16-1{0%{transform:perspective(80px) rotate3d(-1,-1,0,0)}80%,to{transform:perspective(80px) rotate3d(-1,-1,0,-180deg)}}#loading-circle{width:75px;aspect-ratio:1;display:grid;grid:50%/50%;color:var(--rai-accent, #000000);border-radius:50%;--_g: no-repeat linear-gradient(currentColor 0 0);background:var(--_g),var(--_g),var(--_g);background-size:50.1% 50.1%;animation:l9-0 1.5s infinite steps(1) alternate,l9-0-0 3s infinite steps(1) alternate}#loading-circle>div{background:var(--rai-text4, #6b7280);border-top-left-radius:100px;transform:perspective(150px) rotateY(0) rotateX(0);transform-origin:bottom right;animation:l9-1 1.5s infinite linear alternate}@keyframes l9-0{0%{background-position:0 100%,100% 100%,100% 0}33%{background-position:100% 100%,100% 100%,100% 0}66%{background-position:100% 0,100% 0,100% 0}}@keyframes l9-0-0{0%{transform:scaleX(1) rotate(0)}50%{transform:scaleX(-1) rotate(-90deg)}}@keyframes l9-1{16.5%{transform:perspective(150px) rotateX(-90deg) rotateY(0) rotateX(0);filter:grayscale(.8)}33%{transform:perspective(150px) rotateX(-180deg) rotateY(0) rotateX(0)}66%{transform:perspective(150px) rotateX(-180deg) rotateY(-180deg) rotateX(0)}to{transform:perspective(150px) rotateX(-180deg) rotateY(-180deg) rotateX(-180deg);filter:grayscale(.8)}}.rai-custom-error{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.tiptap strong.tw-font-bold,.tiptap b.tw-font-bold{font-weight:700}.tiptap em.tw-font-italic,.tiptap i.tw-font-italic{font-style:italic}.tiptap a.tiptap-rich-text-link{color:#3b82f6;text-decoration:underline;position:relative;display:inline-block;width:fit-content}.tiptap a.tiptap-rich-text-link:hover{color:#3b82f6cc;text-decoration:none}.tiptap a.tiptap-rich-text-link:not([title=""]):before{content:attr(title);position:absolute;top:-2rem;left:50%;transform:translate(-50%);white-space:nowrap;border-radius:.25rem;background-color:#000;padding:.25rem .5rem;color:#fff;opacity:0;transition:opacity .2s;pointer-events:none}.tiptap a.tiptap-rich-text-link:not([title=""]):hover:before{opacity:1}.tiptap p[style*="text-align: left"]{text-align:left}.tiptap p[style*="text-align: center"]{text-align:center}.tiptap p[style*="text-align: right"]{text-align:right}.tiptap p[style*="text-align: justify"]{text-align:justify}.tiptap u{text-decoration:underline}.tiptap.ProseMirror{background-color:transparent;height:fit-content!important;word-wrap:break-word;white-space:pre-wrap;white-space:break-spaces;-webkit-font-variant-ligatures:none;font-variant-ligatures:none;font-feature-settings:"liga" 0;padding:0;width:100%;overflow-y:auto}.tiptap{line-height:1.6;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.tiptap p{margin:.5em 0}.tiptap p:first-child{margin-top:0}.tiptap p:last-child{margin-bottom:0}.tiptap img{max-width:100%;height:auto;display:block}.tiptap p[style*="text-align: left"] img{margin-left:0;margin-right:auto}.tiptap p[style*="text-align: center"] img{margin-left:auto;margin-right:auto}.tiptap p[style*="text-align: right"] img{margin-left:auto;margin-right:0}.rai-retry-btn{padding:8px 20px;border:none;border-radius:6px;background:var(--rai-accent, #000000);color:var(--rai-error-bg, #ffffff);font-size:14px;font-family:system-ui,-apple-system,sans-serif;cursor:pointer}.rai-retry-btn:hover{opacity:.85}';
|
|
439
|
+
const widgetCSS = ':host{display:block;position:relative;width:100%;height:100%}.rai-loader{position:absolute;top:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;width:100%;height:100%;background:var(--background-5, var(--rai-loader-bg, #ffffff));border-radius:8px;z-index:10;transition:opacity .3s ease-out;font-family:var(--rai-loader-font, "Roboto", system-ui, -apple-system, sans-serif)}.rai-loader.fade-out{opacity:0;pointer-events:none}.rai-loader-text{margin:.5rem 0 0;font-size:1.125rem;line-height:1.75rem;font-weight:400;letter-spacing:normal;color:var(--accent-1, var(--rai-accent, #000000));text-align:center}@media (min-width: 768px){.rai-loader-text{font-size:1.5rem;line-height:2rem}}.rai-error{display:none;position:absolute;top:0;left:0;width:100%;height:100%;align-items:center;justify-content:center;flex-direction:column;gap:12px;background:var(--rai-error-bg, #1a1a1a);border-radius:8px;padding:24px;box-sizing:border-box;text-align:center;color:var(--rai-error-text, #9ca3af);font-family:system-ui,-apple-system,sans-serif;z-index:10}.rai-error.visible{display:flex}iframe{display:block;border:none;opacity:0;transition:opacity .3s ease-in}iframe.loaded{opacity:1}.rai-lottie-loader{width:100px;height:100px}.rai-lottie-loader svg{width:100%!important;height:100%!important}.rai-lottie-spinner{width:32px;height:32px;margin:34px auto;border:3px solid var(--rai-text4, #6b7280);border-top-color:var(--rai-accent, #000000);border-radius:50%;animation:rai-spin .7s linear infinite}@keyframes rai-spin{to{transform:rotate(360deg)}}.rai-sdk-loader{position:relative;width:75px;height:100px}.rai-sdk-loader__bar{position:absolute;bottom:0;width:10px;height:50%;background:var(--rai-accent, #000000);transform-origin:center bottom;box-shadow:1px 1px #0003}.rai-sdk-loader__bar:nth-child(1){left:0;transform:scaleY(.2);animation:rai-sdk-barUp1 4s infinite}.rai-sdk-loader__bar:nth-child(2){left:15px;transform:scaleY(.4);animation:rai-sdk-barUp2 4s infinite}.rai-sdk-loader__bar:nth-child(3){left:30px;transform:scaleY(.6);animation:rai-sdk-barUp3 4s infinite}.rai-sdk-loader__bar:nth-child(4){left:45px;transform:scaleY(.8);animation:rai-sdk-barUp4 4s infinite}.rai-sdk-loader__bar:nth-child(5){left:60px;transform:scale(1);animation:rai-sdk-barUp5 4s infinite}.rai-sdk-loader__ball{position:absolute;bottom:10px;left:0;width:10px;height:10px;background:var(--rai-accent, #000000);border-radius:50%;animation:rai-sdk-ball 4s infinite}@keyframes rai-sdk-ball{0%{transform:translate(0)}5%{transform:translate(8px,-14px)}10%{transform:translate(15px,-10px)}17%{transform:translate(23px,-24px)}20%{transform:translate(30px,-20px)}27%{transform:translate(38px,-34px)}30%{transform:translate(45px,-30px)}37%{transform:translate(53px,-44px)}40%{transform:translate(60px,-40px)}50%{transform:translate(60px)}57%{transform:translate(53px,-14px)}60%{transform:translate(45px,-10px)}67%{transform:translate(37px,-24px)}70%{transform:translate(30px,-20px)}77%{transform:translate(22px,-34px)}80%{transform:translate(15px,-30px)}87%{transform:translate(7px,-44px)}90%{transform:translateY(-40px)}to{transform:translate(0)}}@keyframes rai-sdk-barUp1{0%{transform:scaleY(.2)}40%{transform:scaleY(.2)}50%{transform:scale(1)}90%{transform:scale(1)}to{transform:scaleY(.2)}}@keyframes rai-sdk-barUp2{0%{transform:scaleY(.4)}40%{transform:scaleY(.4)}50%{transform:scaleY(.8)}90%{transform:scaleY(.8)}to{transform:scaleY(.4)}}@keyframes rai-sdk-barUp3{0%{transform:scaleY(.6)}to{transform:scaleY(.6)}}@keyframes rai-sdk-barUp4{0%{transform:scaleY(.8)}40%{transform:scaleY(.8)}50%{transform:scaleY(.4)}90%{transform:scaleY(.4)}to{transform:scaleY(.8)}}@keyframes rai-sdk-barUp5{0%{transform:scale(1)}40%{transform:scale(1)}50%{transform:scaleY(.2)}90%{transform:scaleY(.2)}to{transform:scale(1)}}#loading-square{width:75px;aspect-ratio:1;display:flex;color:var(--rai-accent, #000000);background:linear-gradient(currentColor 0 0) right / 51% 100%,linear-gradient(currentColor 0 0) bottom / 100% 51%;background-repeat:no-repeat;animation:l16-0 2s infinite linear .25s}#loading-square>div{width:50%;height:50%;background:currentColor;animation:l16-1 .5s infinite linear}@keyframes l16-0{0%,12.49%{transform:rotate(0)}12.5%,37.49%{transform:rotate(90deg)}37.5%,62.49%{transform:rotate(180deg)}62.5%,87.49%{transform:rotate(270deg)}87.5%,to{transform:rotate(360deg)}}@keyframes l16-1{0%{transform:perspective(80px) rotate3d(-1,-1,0,0)}80%,to{transform:perspective(80px) rotate3d(-1,-1,0,-180deg)}}#loading-circle{width:75px;aspect-ratio:1;display:grid;grid:50%/50%;color:var(--rai-accent, #000000);border-radius:50%;--_g: no-repeat linear-gradient(currentColor 0 0);background:var(--_g),var(--_g),var(--_g);background-size:50.1% 50.1%;animation:l9-0 1.5s infinite steps(1) alternate,l9-0-0 3s infinite steps(1) alternate}#loading-circle>div{background:var(--rai-text4, #6b7280);border-top-left-radius:100px;transform:perspective(150px) rotateY(0) rotateX(0);transform-origin:bottom right;animation:l9-1 1.5s infinite linear alternate}@keyframes l9-0{0%{background-position:0 100%,100% 100%,100% 0}33%{background-position:100% 100%,100% 100%,100% 0}66%{background-position:100% 0,100% 0,100% 0}}@keyframes l9-0-0{0%{transform:scaleX(1) rotate(0)}50%{transform:scaleX(-1) rotate(-90deg)}}@keyframes l9-1{16.5%{transform:perspective(150px) rotateX(-90deg) rotateY(0) rotateX(0);filter:grayscale(.8)}33%{transform:perspective(150px) rotateX(-180deg) rotateY(0) rotateX(0)}66%{transform:perspective(150px) rotateX(-180deg) rotateY(-180deg) rotateX(0)}to{transform:perspective(150px) rotateX(-180deg) rotateY(-180deg) rotateX(-180deg);filter:grayscale(.8)}}.rai-custom-error{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.tiptap strong.tw-font-bold,.tiptap b.tw-font-bold{font-weight:700}.tiptap em.tw-font-italic,.tiptap i.tw-font-italic{font-style:italic}.tiptap a.tiptap-rich-text-link{color:#3b82f6;text-decoration:underline;position:relative;display:inline-block;width:fit-content}.tiptap a.tiptap-rich-text-link:hover{color:#3b82f6cc;text-decoration:none}.tiptap a.tiptap-rich-text-link:not([title=""]):before{content:attr(title);position:absolute;top:-2rem;left:50%;transform:translate(-50%);white-space:nowrap;border-radius:.25rem;background-color:#000;padding:.25rem .5rem;color:#fff;opacity:0;transition:opacity .2s;pointer-events:none}.tiptap a.tiptap-rich-text-link:not([title=""]):hover:before{opacity:1}.tiptap p[style*="text-align: left"]{text-align:left}.tiptap p[style*="text-align: center"]{text-align:center}.tiptap p[style*="text-align: right"]{text-align:right}.tiptap p[style*="text-align: justify"]{text-align:justify}.tiptap u{text-decoration:underline}.tiptap.ProseMirror{background-color:transparent;height:fit-content!important;word-wrap:break-word;white-space:pre-wrap;white-space:break-spaces;-webkit-font-variant-ligatures:none;font-variant-ligatures:none;font-feature-settings:"liga" 0;padding:0;width:100%;overflow-y:auto}.tiptap{line-height:1.6;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.tiptap p{margin:.5em 0}.tiptap p:first-child{margin-top:0}.tiptap p:last-child{margin-bottom:0}.tiptap img{max-width:100%;height:auto;display:block}.tiptap p[style*="text-align: left"] img{margin-left:0;margin-right:auto}.tiptap p[style*="text-align: center"] img{margin-left:auto;margin-right:auto}.tiptap p[style*="text-align: right"] img{margin-left:auto;margin-right:0}.rai-retry-btn{padding:8px 20px;border:none;border-radius:6px;background:var(--rai-accent, #000000);color:var(--rai-error-bg, #ffffff);font-size:14px;font-family:system-ui,-apple-system,sans-serif;cursor:pointer}.rai-retry-btn:hover{opacity:.85}';
|
|
415
440
|
const DEFINED_ATTRS = /* @__PURE__ */ new Set([
|
|
416
441
|
"community-id",
|
|
417
442
|
"channel-id",
|
|
@@ -438,7 +463,12 @@ const DEFINED_ATTRS = /* @__PURE__ */ new Set([
|
|
|
438
463
|
"bundle-url",
|
|
439
464
|
"embed-token",
|
|
440
465
|
"v2-api-url",
|
|
441
|
-
"domain-key"
|
|
466
|
+
"domain-key",
|
|
467
|
+
"loading-text",
|
|
468
|
+
"bg",
|
|
469
|
+
"text",
|
|
470
|
+
"accent",
|
|
471
|
+
"font"
|
|
442
472
|
]);
|
|
443
473
|
const WIDGET_GLOBALS = {
|
|
444
474
|
store: "RaiStoreWidget",
|
|
@@ -469,6 +499,38 @@ function loadScript(url) {
|
|
|
469
499
|
document.head.appendChild(s);
|
|
470
500
|
});
|
|
471
501
|
}
|
|
502
|
+
const loadedFonts = /* @__PURE__ */ new Set();
|
|
503
|
+
function ensureGoogleFontLoaded(family) {
|
|
504
|
+
if (!family || loadedFonts.has(family)) return;
|
|
505
|
+
loadedFonts.add(family);
|
|
506
|
+
const linkId = `rai-font-${family.toLowerCase().replace(/\s+/g, "-")}`;
|
|
507
|
+
if (document.getElementById(linkId)) return;
|
|
508
|
+
const link = document.createElement("link");
|
|
509
|
+
link.id = linkId;
|
|
510
|
+
link.rel = "stylesheet";
|
|
511
|
+
link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}&display=swap`;
|
|
512
|
+
document.head.appendChild(link);
|
|
513
|
+
}
|
|
514
|
+
const DANGEROUS_ELEMENTS = ["script", "iframe", "object", "embed"];
|
|
515
|
+
const URL_ATTRS = ["src", "href", "xlink:href"];
|
|
516
|
+
function sanitizeLoaderHtml(html) {
|
|
517
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
518
|
+
doc.body.querySelectorAll(DANGEROUS_ELEMENTS.join(",")).forEach((el) => el.remove());
|
|
519
|
+
doc.body.querySelectorAll("*").forEach((el) => {
|
|
520
|
+
Array.from(el.attributes).forEach((attr) => {
|
|
521
|
+
const name = attr.name.toLowerCase();
|
|
522
|
+
if (name.startsWith("on")) {
|
|
523
|
+
el.removeAttribute(attr.name);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (URL_ATTRS.includes(name)) {
|
|
527
|
+
const val = attr.value.trim().toLowerCase();
|
|
528
|
+
if (val.startsWith("javascript:")) el.removeAttribute(attr.name);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
return doc.body.innerHTML;
|
|
533
|
+
}
|
|
472
534
|
function readConfig(el, existingId) {
|
|
473
535
|
const get = (name) => el.getAttribute(name) ?? el.getAttribute(`data-${name}`) ?? "";
|
|
474
536
|
const num = (name, def) => {
|
|
@@ -539,6 +601,7 @@ function readConfig(el, existingId) {
|
|
|
539
601
|
// auth token — base64-encoded per widget type for auth URLs + iframe URL building
|
|
540
602
|
widgetType,
|
|
541
603
|
theme: get("theme") || "light",
|
|
604
|
+
themeExplicit: !!get("theme"),
|
|
542
605
|
container,
|
|
543
606
|
width: get("width") || "100%",
|
|
544
607
|
height: get("height") || "600px",
|
|
@@ -568,8 +631,17 @@ function readConfig(el, existingId) {
|
|
|
568
631
|
// required — issued by customer's server
|
|
569
632
|
v2ApiUrl: get("v2-api-url") || void 0,
|
|
570
633
|
// separate V2 API host for embed-token validation
|
|
571
|
-
domainKey: get("domain-key") || void 0
|
|
572
|
-
// SGTR, STG, PROD — controls IIFE environment resolution
|
|
634
|
+
domainKey: get("domain-key") || void 0,
|
|
635
|
+
// SGTR, STG, PROD — controls IIFE environment resolution
|
|
636
|
+
loadingText: get("loading-text") || "Loading ...",
|
|
637
|
+
// matches React WidgetLoadingPage default
|
|
638
|
+
firstPaintColors: (() => {
|
|
639
|
+
const bg = get("bg") || void 0;
|
|
640
|
+
const text = get("text") || void 0;
|
|
641
|
+
const accent = get("accent") || void 0;
|
|
642
|
+
return bg || text || accent ? { bg, text, accent } : void 0;
|
|
643
|
+
})(),
|
|
644
|
+
firstPaintFont: get("font") || void 0
|
|
573
645
|
};
|
|
574
646
|
}
|
|
575
647
|
function createInitialState() {
|
|
@@ -586,7 +658,11 @@ function createInitialState() {
|
|
|
586
658
|
isRefreshing: false,
|
|
587
659
|
refreshPromise: null,
|
|
588
660
|
errorSettings: null,
|
|
589
|
-
loadingAnimation: null
|
|
661
|
+
loadingAnimation: null,
|
|
662
|
+
themePalette: null,
|
|
663
|
+
font: null,
|
|
664
|
+
defaultColorSchema: null,
|
|
665
|
+
lastAuthError: null
|
|
590
666
|
};
|
|
591
667
|
}
|
|
592
668
|
class BaseWidget extends HTMLElement {
|
|
@@ -619,6 +695,7 @@ class BaseWidget extends HTMLElement {
|
|
|
619
695
|
this.style.setProperty("--rai-text4", text4Color);
|
|
620
696
|
this.style.setProperty("--rai-loader-bg", bgColor);
|
|
621
697
|
this.style.setProperty("--rai-error-bg", bgColor);
|
|
698
|
+
this.applyFirstPaintColors();
|
|
622
699
|
this.renderShell();
|
|
623
700
|
if (this.hasAttribute("eager")) {
|
|
624
701
|
this.init();
|
|
@@ -651,9 +728,61 @@ class BaseWidget extends HTMLElement {
|
|
|
651
728
|
);
|
|
652
729
|
}
|
|
653
730
|
// ── Rendering ─────────────────────────────────────────────────────────
|
|
731
|
+
/**
|
|
732
|
+
* Apply widget-defined CSS colors before first paint so the loader uses
|
|
733
|
+
* the community's actual theme — not generic light/dark — from frame 1.
|
|
734
|
+
*
|
|
735
|
+
* Priority:
|
|
736
|
+
* 1. Cached themePalette (synchronous localStorage read)
|
|
737
|
+
* 2. HTML attribute hints (`bg`, `text`, `accent`)
|
|
738
|
+
* 3. Generic light/dark fallback (--rai-* vars set in connectedCallback)
|
|
739
|
+
*
|
|
740
|
+
* After the API response arrives, fetchAndRenderLoader() re-applies the
|
|
741
|
+
* fresh palette via applyPaletteFromState(). On every load except the
|
|
742
|
+
* absolute first-ever, the cached palette is already correct.
|
|
743
|
+
*/
|
|
744
|
+
applyFirstPaintColors() {
|
|
745
|
+
var _a;
|
|
746
|
+
const cached = loadErrorSettingsFromStorage(this.config);
|
|
747
|
+
if (!this.config.themeExplicit) {
|
|
748
|
+
const cachedDefault = cached == null ? void 0 : cached.defaultColorSchema;
|
|
749
|
+
if (cachedDefault === "light" || cachedDefault === "dark") {
|
|
750
|
+
this.config.theme = cachedDefault;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
const theme = this.config.theme === "dark" ? "dark" : "light";
|
|
754
|
+
const cachedPalette = (_a = cached == null ? void 0 : cached.themePalette) == null ? void 0 : _a[theme];
|
|
755
|
+
if (cachedPalette) this.applyPalette(cachedPalette);
|
|
756
|
+
const cachedFont = typeof (cached == null ? void 0 : cached.font) === "string" ? cached.font : void 0;
|
|
757
|
+
const font = cachedFont || this.config.firstPaintFont;
|
|
758
|
+
if (font) this.applyFont(font);
|
|
759
|
+
if (cachedPalette) return;
|
|
760
|
+
const hints = this.config.firstPaintColors;
|
|
761
|
+
if (hints == null ? void 0 : hints.bg) this.style.setProperty("--background-5", hints.bg);
|
|
762
|
+
if (hints == null ? void 0 : hints.text) this.style.setProperty("--text-1", hints.text);
|
|
763
|
+
if (hints == null ? void 0 : hints.accent) this.style.setProperty("--accent-1", hints.accent);
|
|
764
|
+
}
|
|
765
|
+
applyFont(font) {
|
|
766
|
+
this.style.setProperty(
|
|
767
|
+
"--rai-loader-font",
|
|
768
|
+
`'${font}', system-ui, -apple-system, sans-serif`
|
|
769
|
+
);
|
|
770
|
+
ensureGoogleFontLoaded(font);
|
|
771
|
+
}
|
|
772
|
+
applyPalette(palette) {
|
|
773
|
+
Object.entries(palette).forEach(([key, value]) => {
|
|
774
|
+
if (key.startsWith("--") && value) {
|
|
775
|
+
this.style.setProperty(key, value);
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
}
|
|
654
779
|
renderShell() {
|
|
655
780
|
this.loaderEl = document.createElement("div");
|
|
656
781
|
this.loaderEl.className = "rai-loader";
|
|
782
|
+
const textEl = document.createElement("h1");
|
|
783
|
+
textEl.className = "rai-loader-text";
|
|
784
|
+
textEl.textContent = this.config.loadingText;
|
|
785
|
+
this.loaderEl.appendChild(textEl);
|
|
657
786
|
this.shadow.appendChild(this.loaderEl);
|
|
658
787
|
this.fetchAndRenderLoader();
|
|
659
788
|
this.errorEl = document.createElement("div");
|
|
@@ -671,51 +800,83 @@ class BaseWidget extends HTMLElement {
|
|
|
671
800
|
this.shadow.appendChild(this.errorEl);
|
|
672
801
|
}
|
|
673
802
|
/**
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
803
|
+
* Loads the community loading animation + theme palette from cache or API
|
|
804
|
+
* and renders the animation above the "Loading ..." text. The text is
|
|
805
|
+
* already painted by renderShell so this method only ADDS the animation;
|
|
806
|
+
* users see two states at most: text-only → text + animation.
|
|
678
807
|
*
|
|
679
|
-
*
|
|
680
|
-
*
|
|
681
|
-
*
|
|
808
|
+
* Stale-while-revalidate:
|
|
809
|
+
* - Cache hit → apply palette & render animation synchronously, fire
|
|
810
|
+
* background refresh.
|
|
811
|
+
* - Cache miss → text remains visible; await network; apply palette
|
|
812
|
+
* + render animation when response arrives.
|
|
682
813
|
*/
|
|
683
814
|
async fetchAndRenderLoader() {
|
|
684
815
|
var _a;
|
|
685
|
-
|
|
816
|
+
const cached = loadErrorSettingsFromStorage(this.config);
|
|
817
|
+
if (cached) {
|
|
818
|
+
applyErrorSettingsToState(this.state, cached);
|
|
819
|
+
this.renderLoaderAnimation();
|
|
820
|
+
fetchErrorSettings(this.config, this.state, { skipCache: true }).catch(
|
|
821
|
+
() => {
|
|
822
|
+
}
|
|
823
|
+
);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
686
826
|
try {
|
|
687
827
|
await fetchErrorSettings(this.config, this.state);
|
|
688
828
|
} catch {
|
|
689
829
|
}
|
|
690
830
|
if (!this.loaderEl) return;
|
|
691
|
-
this.
|
|
831
|
+
if (!this.config.themeExplicit && (this.state.defaultColorSchema === "light" || this.state.defaultColorSchema === "dark") && this.state.defaultColorSchema !== this.config.theme) {
|
|
832
|
+
this.config.theme = this.state.defaultColorSchema;
|
|
833
|
+
}
|
|
834
|
+
const theme = this.config.theme === "dark" ? "dark" : "light";
|
|
835
|
+
const palette = (_a = this.state.themePalette) == null ? void 0 : _a[theme];
|
|
836
|
+
if (palette) this.applyPalette(palette);
|
|
837
|
+
if (this.state.font) this.applyFont(this.state.font);
|
|
838
|
+
this.renderLoaderAnimation();
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Append the loading animation above the text. Removes any previous
|
|
842
|
+
* animation first so this is safe to call repeatedly (e.g. after a
|
|
843
|
+
* background refresh changes the configured loader).
|
|
844
|
+
*/
|
|
845
|
+
renderLoaderAnimation() {
|
|
846
|
+
if (!this.loaderEl) return;
|
|
847
|
+
const existing = this.loaderEl.querySelector(".rai-loader-anim");
|
|
848
|
+
if (existing) existing.remove();
|
|
849
|
+
const animContainer = document.createElement("div");
|
|
850
|
+
animContainer.className = "rai-loader-anim";
|
|
692
851
|
const anim = this.state.loadingAnimation;
|
|
693
852
|
const theme = this.config.theme === "dark" ? "dark" : "light";
|
|
694
853
|
if ((anim == null ? void 0 : anim.type) === "custom" && anim.custom) {
|
|
695
854
|
const themeConfig = anim.custom[theme] || anim.custom.light || anim.custom.dark;
|
|
696
855
|
if (themeConfig) {
|
|
697
856
|
if (themeConfig.type === "lottie" && themeConfig.lottieData) {
|
|
698
|
-
this.
|
|
857
|
+
this.renderLottieInto(animContainer, themeConfig.lottieData);
|
|
858
|
+
this.loaderEl.insertBefore(animContainer, this.loaderEl.firstChild);
|
|
699
859
|
return;
|
|
700
860
|
}
|
|
701
|
-
if (themeConfig.type === "
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
this.loaderEl.appendChild(wrapper);
|
|
861
|
+
if (themeConfig.type === "html" && themeConfig.html) {
|
|
862
|
+
animContainer.innerHTML = sanitizeLoaderHtml(themeConfig.html);
|
|
863
|
+
this.loaderEl.insertBefore(animContainer, this.loaderEl.firstChild);
|
|
705
864
|
return;
|
|
706
865
|
}
|
|
707
866
|
}
|
|
708
867
|
}
|
|
709
868
|
if ((anim == null ? void 0 : anim.type) === "preset" && anim.preset) {
|
|
710
|
-
|
|
869
|
+
animContainer.appendChild(this.createPresetLoader(anim.preset));
|
|
870
|
+
this.loaderEl.insertBefore(animContainer, this.loaderEl.firstChild);
|
|
711
871
|
return;
|
|
712
872
|
}
|
|
713
|
-
|
|
873
|
+
animContainer.appendChild(this.createDefaultLoader());
|
|
874
|
+
this.loaderEl.insertBefore(animContainer, this.loaderEl.firstChild);
|
|
714
875
|
}
|
|
715
|
-
|
|
876
|
+
renderLottieInto(parent, animationData) {
|
|
716
877
|
const lottieContainer = document.createElement("div");
|
|
717
878
|
lottieContainer.className = "rai-lottie-loader";
|
|
718
|
-
|
|
879
|
+
parent.appendChild(lottieContainer);
|
|
719
880
|
const w = window;
|
|
720
881
|
if (typeof w.lottie !== "undefined") {
|
|
721
882
|
w.lottie.loadAnimation({
|
|
@@ -737,6 +898,10 @@ class BaseWidget extends HTMLElement {
|
|
|
737
898
|
animationData
|
|
738
899
|
});
|
|
739
900
|
};
|
|
901
|
+
script.onerror = () => {
|
|
902
|
+
lottieContainer.remove();
|
|
903
|
+
parent.appendChild(this.createDefaultLoader());
|
|
904
|
+
};
|
|
740
905
|
document.head.appendChild(script);
|
|
741
906
|
}
|
|
742
907
|
}
|
|
@@ -779,15 +944,62 @@ class BaseWidget extends HTMLElement {
|
|
|
779
944
|
}, 300);
|
|
780
945
|
this.loaderEl = null;
|
|
781
946
|
}
|
|
947
|
+
/**
|
|
948
|
+
* Bundle mode: keep the SDK loader visible until React has actually
|
|
949
|
+
* painted into `container`, eliminating the brief bg-color gap that
|
|
950
|
+
* happens when the loader is removed before the React app's first frame.
|
|
951
|
+
*
|
|
952
|
+
* Strategy:
|
|
953
|
+
* 1. MutationObserver fires when React commits any DOM mutation into
|
|
954
|
+
* the container — synchronous inside mount(), but the callback runs
|
|
955
|
+
* as a microtask, so it observes the first child addition.
|
|
956
|
+
* 2. requestAnimationFrame waits one paint cycle so the React content
|
|
957
|
+
* is actually visible before the SDK loader fades out.
|
|
958
|
+
* 3. 2s fallback covers silent React mount errors so the loader doesn't
|
|
959
|
+
* hang forever.
|
|
960
|
+
*
|
|
961
|
+
* Calls hideLoader() (with its 300ms fade-out) so the transition is smooth.
|
|
962
|
+
*/
|
|
963
|
+
deferLoaderRemovalUntilReactPaints(container) {
|
|
964
|
+
if (!this.loaderEl) return;
|
|
965
|
+
let removed = false;
|
|
966
|
+
const remove = () => {
|
|
967
|
+
if (removed) return;
|
|
968
|
+
removed = true;
|
|
969
|
+
this.hideLoader();
|
|
970
|
+
};
|
|
971
|
+
const observer = new MutationObserver(() => {
|
|
972
|
+
if (container.children.length > 0) {
|
|
973
|
+
observer.disconnect();
|
|
974
|
+
requestAnimationFrame(remove);
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
observer.observe(container, { childList: true });
|
|
978
|
+
setTimeout(() => {
|
|
979
|
+
observer.disconnect();
|
|
980
|
+
remove();
|
|
981
|
+
}, 2e3);
|
|
982
|
+
}
|
|
782
983
|
showError() {
|
|
783
984
|
this.hideLoader();
|
|
784
985
|
const settings = this.state.errorSettings;
|
|
785
|
-
|
|
986
|
+
const lastAuthError = this.state.lastAuthError;
|
|
987
|
+
const isUserLookupError = (lastAuthError == null ? void 0 : lastAuthError.error) === "User not found" || (lastAuthError == null ? void 0 : lastAuthError.error) === "Missing required user identifier fields" || (lastAuthError == null ? void 0 : lastAuthError.error) === "Missing user identifier fields";
|
|
988
|
+
const shouldUseCustomMessage = !!(settings == null ? void 0 : settings.errorMessage) && (isUserLookupError || !lastAuthError);
|
|
989
|
+
if (shouldUseCustomMessage) {
|
|
786
990
|
this.renderCustomError(settings);
|
|
787
991
|
} else if (this.errorEl) {
|
|
992
|
+
if ((lastAuthError == null ? void 0 : lastAuthError.error) && this.msgEl) {
|
|
993
|
+
this.msgEl.textContent = lastAuthError.hint ? `${lastAuthError.error}: ${lastAuthError.hint}` : lastAuthError.error;
|
|
994
|
+
}
|
|
788
995
|
this.errorEl.classList.add("visible");
|
|
789
996
|
}
|
|
790
|
-
|
|
997
|
+
const emittedMessage = shouldUseCustomMessage ? settings.errorMessage : (lastAuthError == null ? void 0 : lastAuthError.error) || "Authentication failed";
|
|
998
|
+
this.emit("rai-error", {
|
|
999
|
+
message: emittedMessage,
|
|
1000
|
+
hint: lastAuthError == null ? void 0 : lastAuthError.hint,
|
|
1001
|
+
status: lastAuthError == null ? void 0 : lastAuthError.status
|
|
1002
|
+
});
|
|
791
1003
|
}
|
|
792
1004
|
renderCustomError(settings) {
|
|
793
1005
|
var _a, _b;
|
|
@@ -835,13 +1047,6 @@ class BaseWidget extends HTMLElement {
|
|
|
835
1047
|
return;
|
|
836
1048
|
}
|
|
837
1049
|
await fetchErrorSettings(this.config, this.state);
|
|
838
|
-
if (this.config.embedToken) {
|
|
839
|
-
const valid = await validateEmbedToken(this.config);
|
|
840
|
-
if (!valid) {
|
|
841
|
-
this.showError();
|
|
842
|
-
return;
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
1050
|
const launchWidget = () => {
|
|
846
1051
|
if (this.config.bundleUrl) {
|
|
847
1052
|
this.mountWidget();
|
|
@@ -863,6 +1068,22 @@ class BaseWidget extends HTMLElement {
|
|
|
863
1068
|
}
|
|
864
1069
|
return;
|
|
865
1070
|
}
|
|
1071
|
+
if (this.config.embedToken) {
|
|
1072
|
+
clearState(this.state);
|
|
1073
|
+
clearStorage(this.config);
|
|
1074
|
+
const authed2 = await authenticateServerless(
|
|
1075
|
+
this.config,
|
|
1076
|
+
this.state,
|
|
1077
|
+
() => this.scheduleRefresh()
|
|
1078
|
+
);
|
|
1079
|
+
if (authed2) {
|
|
1080
|
+
this.emit("rai-authenticated");
|
|
1081
|
+
launchWidget();
|
|
1082
|
+
} else {
|
|
1083
|
+
this.showError();
|
|
1084
|
+
}
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
866
1087
|
const hasStored = loadFromStorage(this.config, this.state);
|
|
867
1088
|
if (hasStored) {
|
|
868
1089
|
const refreshed = await refreshAccessToken(
|
|
@@ -895,7 +1116,10 @@ class BaseWidget extends HTMLElement {
|
|
|
895
1116
|
const iframe = document.createElement("iframe");
|
|
896
1117
|
iframe.src = this.buildWidgetUrl(this.config);
|
|
897
1118
|
iframe.allow = "clipboard-write";
|
|
898
|
-
iframe.setAttribute(
|
|
1119
|
+
iframe.setAttribute(
|
|
1120
|
+
"sandbox",
|
|
1121
|
+
"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
|
|
1122
|
+
);
|
|
899
1123
|
iframe.frameBorder = "0";
|
|
900
1124
|
iframe.scrolling = "no";
|
|
901
1125
|
iframe.style.width = this.config.width;
|
|
@@ -926,30 +1150,57 @@ class BaseWidget extends HTMLElement {
|
|
|
926
1150
|
// ── Bundle mode (no-iframe) ──────────────────────────────────────────
|
|
927
1151
|
async mountWidget() {
|
|
928
1152
|
try {
|
|
1153
|
+
const bundleApiUrl = this.config.v2ApiUrl || this.config.apiUrl;
|
|
929
1154
|
const w = window;
|
|
930
1155
|
if (!w.DomainKey && this.config.domainKey) {
|
|
931
1156
|
w.DomainKey = this.config.domainKey === "PROD" ? "GEN" : this.config.domainKey;
|
|
932
1157
|
}
|
|
933
|
-
|
|
1158
|
+
const endpointVars = [
|
|
1159
|
+
"api_url",
|
|
1160
|
+
"base_url",
|
|
1161
|
+
"auth_api",
|
|
1162
|
+
"socket_path_v2",
|
|
1163
|
+
"socket_path",
|
|
1164
|
+
"channel_api"
|
|
1165
|
+
];
|
|
1166
|
+
for (const v of endpointVars) {
|
|
1167
|
+
if (!w[v]) w[v] = bundleApiUrl;
|
|
1168
|
+
}
|
|
934
1169
|
window.__IS_WIDGET__ = true;
|
|
935
1170
|
window.guest_token = this.state.accessToken;
|
|
1171
|
+
const colorMode = this.config.theme === "dark" ? "dark" : "light";
|
|
1172
|
+
const defaultThemeName = colorMode === "dark" ? "dark-knight" : "white-smoke";
|
|
1173
|
+
document.documentElement.setAttribute("data-theme", defaultThemeName);
|
|
1174
|
+
window.currentTheme = colorMode;
|
|
1175
|
+
window.__FORCE_COLOR_MODE__ = colorMode;
|
|
1176
|
+
window.__WIDGET_STORAGE_KEY__ = `color-schema-${this.config.widgetType}-${this.config.widgetId}`;
|
|
1177
|
+
if (this.state.loadingAnimation) {
|
|
1178
|
+
window.loadingAnimation = this.state.loadingAnimation;
|
|
1179
|
+
}
|
|
936
1180
|
await loadScript(this.config.bundleUrl);
|
|
937
1181
|
const globalName = WIDGET_GLOBALS[this.config.widgetType];
|
|
938
1182
|
const widgetGlobal = window[globalName];
|
|
939
1183
|
if (!(widgetGlobal == null ? void 0 : widgetGlobal.mount)) {
|
|
940
|
-
throw new Error(
|
|
1184
|
+
throw new Error(
|
|
1185
|
+
`window.${globalName}.mount not found after loading bundle`
|
|
1186
|
+
);
|
|
941
1187
|
}
|
|
942
1188
|
const container = document.createElement("div");
|
|
943
|
-
container.
|
|
1189
|
+
container.className = "rai-widget-root";
|
|
1190
|
+
container.style.cssText = [
|
|
1191
|
+
"position: relative",
|
|
1192
|
+
"width: 100%",
|
|
1193
|
+
"height: 100%",
|
|
1194
|
+
"color: var(--text-1)",
|
|
1195
|
+
"background: var(--background-1)",
|
|
1196
|
+
"font-family: var(--rai-loader-font, 'Roboto', system-ui, -apple-system, sans-serif)"
|
|
1197
|
+
].join("; ");
|
|
944
1198
|
this.appendChild(container);
|
|
945
1199
|
if (!this.shadow.querySelector("slot")) {
|
|
946
1200
|
const slot = document.createElement("slot");
|
|
947
1201
|
this.shadow.appendChild(slot);
|
|
948
1202
|
}
|
|
949
|
-
|
|
950
|
-
this.loaderEl.remove();
|
|
951
|
-
this.loaderEl = null;
|
|
952
|
-
}
|
|
1203
|
+
this.deferLoaderRemovalUntilReactPaints(container);
|
|
953
1204
|
widgetGlobal.mount(container, {
|
|
954
1205
|
widgetType: this.config.widgetType,
|
|
955
1206
|
communityId: this.config.communityId,
|
|
@@ -957,11 +1208,18 @@ class BaseWidget extends HTMLElement {
|
|
|
957
1208
|
// channel widget only; others receive undefined and ignore it
|
|
958
1209
|
widgetId: this.config.communityId,
|
|
959
1210
|
// deprecated alias kept for widget main.tsx compatibility
|
|
960
|
-
apiUrl:
|
|
1211
|
+
apiUrl: bundleApiUrl,
|
|
961
1212
|
token: this.state.accessToken,
|
|
962
1213
|
basePath: "/",
|
|
963
1214
|
isPreview: false,
|
|
964
|
-
|
|
1215
|
+
// Only claim "open widget" status (which tradingroom uses to render
|
|
1216
|
+
// the chat input as read-only) when the bundle has no auth token —
|
|
1217
|
+
// i.e. genuinely public/guest embeds. Authenticated embeds need
|
|
1218
|
+
// sending enabled; an unconditional `true` here disables the input
|
|
1219
|
+
// for every signed-in user, even though they should be able to
|
|
1220
|
+
// post. Mirrors the iframe behavior, where `isOpenWidget` is only
|
|
1221
|
+
// injected by SSR for the explicit `/open-widget` route.
|
|
1222
|
+
isOpenWidget: !this.state.accessToken,
|
|
965
1223
|
defaultColorSchema: this.config.theme === "dark" ? "dark" : "light"
|
|
966
1224
|
});
|
|
967
1225
|
this.emit("rai-mounted");
|
|
@@ -976,17 +1234,27 @@ class BaseWidget extends HTMLElement {
|
|
|
976
1234
|
if (!this.state.accessTokenExpiry) return;
|
|
977
1235
|
const delay = this.state.accessTokenExpiry - Date.now() - 6e4;
|
|
978
1236
|
const pushToken = () => {
|
|
979
|
-
if (this.state.iframe)
|
|
1237
|
+
if (this.state.iframe)
|
|
1238
|
+
sendTokenToWidget(this.config, this.state, this.state.iframe);
|
|
980
1239
|
if (this.config.bundleUrl && this.state.accessToken) {
|
|
981
1240
|
window.guest_token = this.state.accessToken;
|
|
982
1241
|
}
|
|
983
1242
|
};
|
|
984
1243
|
const doRefresh = async () => {
|
|
985
1244
|
if (this.config.authUrl) {
|
|
986
|
-
const ok = await authenticateViaProxy(
|
|
1245
|
+
const ok = await authenticateViaProxy(
|
|
1246
|
+
this.config,
|
|
1247
|
+
this.state,
|
|
1248
|
+
() => this.scheduleRefresh()
|
|
1249
|
+
);
|
|
987
1250
|
if (ok) pushToken();
|
|
988
1251
|
} else {
|
|
989
|
-
await refreshAccessToken(
|
|
1252
|
+
await refreshAccessToken(
|
|
1253
|
+
this.config,
|
|
1254
|
+
this.state,
|
|
1255
|
+
() => this.scheduleRefresh(),
|
|
1256
|
+
pushToken
|
|
1257
|
+
);
|
|
990
1258
|
}
|
|
991
1259
|
};
|
|
992
1260
|
if (delay > 0) {
|
|
@@ -998,7 +1266,8 @@ class BaseWidget extends HTMLElement {
|
|
|
998
1266
|
schedulePeriodicSync(iframe) {
|
|
999
1267
|
if (this.state.syncTimer) clearInterval(this.state.syncTimer);
|
|
1000
1268
|
this.state.syncTimer = setInterval(() => {
|
|
1001
|
-
if (this.state.accessToken)
|
|
1269
|
+
if (this.state.accessToken)
|
|
1270
|
+
sendTokenToWidget(this.config, this.state, iframe);
|
|
1002
1271
|
}, 12e4);
|
|
1003
1272
|
}
|
|
1004
1273
|
// ── Logout helper ─────────────────────────────────────────────────────
|
|
@@ -1044,82 +1313,94 @@ class StoreWidget extends BaseWidget {
|
|
|
1044
1313
|
class ChannelWidget extends BaseWidget {
|
|
1045
1314
|
buildWidgetUrl(config) {
|
|
1046
1315
|
const base = config.widgetUrl.endsWith("channel-widget") ? `${config.widgetUrl}/${config.widgetId}` : config.widgetUrl;
|
|
1047
|
-
if (!config.locale) return base;
|
|
1048
1316
|
try {
|
|
1049
1317
|
const u = new URL(base);
|
|
1050
|
-
u.searchParams.set("
|
|
1318
|
+
u.searchParams.set("color", config.theme);
|
|
1319
|
+
if (config.locale) u.searchParams.set("locale", config.locale);
|
|
1051
1320
|
return u.toString();
|
|
1052
1321
|
} catch {
|
|
1053
|
-
|
|
1322
|
+
const params = [`color=${config.theme}`];
|
|
1323
|
+
if (config.locale) params.push(`locale=${encodeURIComponent(config.locale)}`);
|
|
1324
|
+
return `${base}?${params.join("&")}`;
|
|
1054
1325
|
}
|
|
1055
1326
|
}
|
|
1056
1327
|
}
|
|
1057
1328
|
class MilestoneWidget extends BaseWidget {
|
|
1058
1329
|
buildWidgetUrl(config) {
|
|
1059
1330
|
const base = config.widgetUrl.endsWith("milestone-widget") ? `${config.widgetUrl}/${config.widgetId}` : config.widgetUrl;
|
|
1060
|
-
if (!config.locale) return base;
|
|
1061
1331
|
try {
|
|
1062
1332
|
const u = new URL(base);
|
|
1063
|
-
u.searchParams.set("
|
|
1333
|
+
u.searchParams.set("color", config.theme);
|
|
1334
|
+
if (config.locale) u.searchParams.set("locale", config.locale);
|
|
1064
1335
|
return u.toString();
|
|
1065
1336
|
} catch {
|
|
1066
|
-
|
|
1337
|
+
const params = [`color=${config.theme}`];
|
|
1338
|
+
if (config.locale) params.push(`locale=${encodeURIComponent(config.locale)}`);
|
|
1339
|
+
return `${base}?${params.join("&")}`;
|
|
1067
1340
|
}
|
|
1068
1341
|
}
|
|
1069
1342
|
}
|
|
1070
1343
|
class SocialWidget extends BaseWidget {
|
|
1071
1344
|
buildWidgetUrl(config) {
|
|
1072
1345
|
const base = config.widgetUrl.endsWith("social-widget") ? `${config.widgetUrl}/${config.widgetId}` : config.widgetUrl;
|
|
1073
|
-
if (!config.locale) return base;
|
|
1074
1346
|
try {
|
|
1075
1347
|
const u = new URL(base);
|
|
1076
|
-
u.searchParams.set("
|
|
1348
|
+
u.searchParams.set("color", config.theme);
|
|
1349
|
+
if (config.locale) u.searchParams.set("locale", config.locale);
|
|
1077
1350
|
return u.toString();
|
|
1078
1351
|
} catch {
|
|
1079
|
-
|
|
1352
|
+
const params = [`color=${config.theme}`];
|
|
1353
|
+
if (config.locale) params.push(`locale=${encodeURIComponent(config.locale)}`);
|
|
1354
|
+
return `${base}?${params.join("&")}`;
|
|
1080
1355
|
}
|
|
1081
1356
|
}
|
|
1082
1357
|
}
|
|
1083
1358
|
class CurrencyWidget extends BaseWidget {
|
|
1084
1359
|
buildWidgetUrl(config) {
|
|
1085
1360
|
const base = config.widgetUrl.endsWith("currency-overview-widget") ? `${config.widgetUrl}/${config.widgetId}` : config.widgetUrl;
|
|
1086
|
-
if (!config.locale) return base;
|
|
1087
1361
|
try {
|
|
1088
1362
|
const u = new URL(base);
|
|
1089
|
-
u.searchParams.set("
|
|
1363
|
+
u.searchParams.set("color", config.theme);
|
|
1364
|
+
if (config.locale) u.searchParams.set("locale", config.locale);
|
|
1090
1365
|
return u.toString();
|
|
1091
1366
|
} catch {
|
|
1092
|
-
|
|
1367
|
+
const params = [`color=${config.theme}`];
|
|
1368
|
+
if (config.locale) params.push(`locale=${encodeURIComponent(config.locale)}`);
|
|
1369
|
+
return `${base}?${params.join("&")}`;
|
|
1093
1370
|
}
|
|
1094
1371
|
}
|
|
1095
1372
|
}
|
|
1096
1373
|
class ReferralWidget extends BaseWidget {
|
|
1097
1374
|
buildWidgetUrl(config) {
|
|
1098
1375
|
const base = config.widgetUrl.endsWith("referral-conditions-widget") ? `${config.widgetUrl}/${config.widgetId}` : config.widgetUrl;
|
|
1099
|
-
if (!config.locale) return base;
|
|
1100
1376
|
try {
|
|
1101
1377
|
const u = new URL(base);
|
|
1102
|
-
u.searchParams.set("
|
|
1378
|
+
u.searchParams.set("color", config.theme);
|
|
1379
|
+
if (config.locale) u.searchParams.set("locale", config.locale);
|
|
1103
1380
|
return u.toString();
|
|
1104
1381
|
} catch {
|
|
1105
|
-
|
|
1382
|
+
const params = [`color=${config.theme}`];
|
|
1383
|
+
if (config.locale) params.push(`locale=${encodeURIComponent(config.locale)}`);
|
|
1384
|
+
return `${base}?${params.join("&")}`;
|
|
1106
1385
|
}
|
|
1107
1386
|
}
|
|
1108
1387
|
}
|
|
1109
1388
|
class CustomWidget extends BaseWidget {
|
|
1110
1389
|
buildWidgetUrl(config) {
|
|
1111
1390
|
const base = config.widgetUrl.endsWith("custom-widget") ? `${config.widgetUrl}/${config.widgetId}` : config.widgetUrl;
|
|
1112
|
-
if (!config.locale) return base;
|
|
1113
1391
|
try {
|
|
1114
1392
|
const u = new URL(base);
|
|
1115
|
-
u.searchParams.set("
|
|
1393
|
+
u.searchParams.set("color", config.theme);
|
|
1394
|
+
if (config.locale) u.searchParams.set("locale", config.locale);
|
|
1116
1395
|
return u.toString();
|
|
1117
1396
|
} catch {
|
|
1118
|
-
|
|
1397
|
+
const params = [`color=${config.theme}`];
|
|
1398
|
+
if (config.locale) params.push(`locale=${encodeURIComponent(config.locale)}`);
|
|
1399
|
+
return `${base}?${params.join("&")}`;
|
|
1119
1400
|
}
|
|
1120
1401
|
}
|
|
1121
1402
|
}
|
|
1122
|
-
console.log(`[rai-widget] v${"1.
|
|
1403
|
+
console.log(`[rai-widget] v${"1.3.0"}`);
|
|
1123
1404
|
class StoreWidgetCompat extends StoreWidget {
|
|
1124
1405
|
}
|
|
1125
1406
|
const WIDGET_REGISTRY = [
|
|
@@ -1183,7 +1464,7 @@ function exposePublicApi() {
|
|
|
1183
1464
|
})();
|
|
1184
1465
|
const widget = container == null ? void 0 : container.querySelector(ALL_WIDGET_SELECTOR);
|
|
1185
1466
|
window.ReturningAIWidget = {
|
|
1186
|
-
version: "1.
|
|
1467
|
+
version: "1.3.0",
|
|
1187
1468
|
reload: () => (widget == null ? void 0 : widget.reload()) ?? Promise.resolve(),
|
|
1188
1469
|
logout: () => (widget == null ? void 0 : widget.logoutPublic()) ?? Promise.resolve(),
|
|
1189
1470
|
isAuthenticated: () => (widget == null ? void 0 : widget.isAuthenticated()) ?? false,
|