@returningai/widget-sdk 1.2.16 → 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.
@@ -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 error = await response.json().catch(() => ({ error: "Authentication failed" }));
108
- throw new Error(error.error || `HTTP ${response.status}`);
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
- async function validateEmbedToken(config) {
250
- try {
251
- const baseUrl = config.v2ApiUrl || config.apiUrl;
252
- const res = await fetch(`${baseUrl}/v2/api/widget-access-keys/validate`, {
253
- method: "POST",
254
- headers: { "Content-Type": "application/json" },
255
- body: JSON.stringify({ embedToken: config.embedToken })
256
- });
257
- return res.ok;
258
- } catch {
259
- return false;
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
- const cached = loadErrorSettingsFromStorage(config);
264
- if (cached) {
265
- if (cached.configured) {
266
- state.errorSettings = {
267
- errorMessage: cached.errorMessage,
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
- try {
278
- const response = await fetch(
279
- `${config.apiUrl}/${config.widgetId}/auth/error-settings?widgetType=${config.widgetType}`,
280
- { method: "GET", headers: { "Content-Type": "application/json" } }
281
- );
282
- if (!response.ok) return null;
283
- const data = await response.json();
284
- saveErrorSettingsToStorage(config, data);
285
- if (data.configured) {
286
- state.errorSettings = {
287
- errorMessage: data.errorMessage,
288
- modalColor: data.modalColor,
289
- backgroundImage: data.backgroundImage
290
- };
291
- }
292
- if (data.loadingAnimation) {
293
- state.loadingAnimation = data.loadingAnimation;
294
- }
295
- return state.errorSettings;
296
- } catch {
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
634
+ domainKey: get("domain-key") || void 0,
572
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
- * Fetches the community's loading animation config from the error-settings
675
- * API (which now also returns loadingAnimation) and renders the appropriate
676
- * loader. Falls back to the default bar loader if the API fails or if no
677
- * custom animation is configured.
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
- * This runs in parallel with auth — the API call is fire-and-forget and
680
- * the loader is rendered as soon as the response arrives. The result is
681
- * also cached by fetchErrorSettings so init() doesn't re-fetch.
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
- (_a = this.loaderEl) == null ? void 0 : _a.appendChild(this.createDefaultLoader());
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.loaderEl.innerHTML = "";
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.renderLottieLoader(themeConfig.lottieData);
857
+ this.renderLottieInto(animContainer, themeConfig.lottieData);
858
+ this.loaderEl.insertBefore(animContainer, this.loaderEl.firstChild);
699
859
  return;
700
860
  }
701
- if (themeConfig.type === "custom" && themeConfig.html) {
702
- const wrapper = document.createElement("div");
703
- wrapper.innerHTML = themeConfig.html;
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
- this.loaderEl.appendChild(this.createPresetLoader(anim.preset));
869
+ animContainer.appendChild(this.createPresetLoader(anim.preset));
870
+ this.loaderEl.insertBefore(animContainer, this.loaderEl.firstChild);
711
871
  return;
712
872
  }
713
- this.loaderEl.appendChild(this.createDefaultLoader());
873
+ animContainer.appendChild(this.createDefaultLoader());
874
+ this.loaderEl.insertBefore(animContainer, this.loaderEl.firstChild);
714
875
  }
715
- renderLottieLoader(animationData) {
876
+ renderLottieInto(parent, animationData) {
716
877
  const lottieContainer = document.createElement("div");
717
878
  lottieContainer.className = "rai-lottie-loader";
718
- this.loaderEl.appendChild(lottieContainer);
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
- if (settings == null ? void 0 : settings.errorMessage) {
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
- this.emit("rai-error", { message: (settings == null ? void 0 : settings.errorMessage) ?? "Authentication failed" });
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("sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox");
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(`window.${globalName}.mount not found after loading bundle`);
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.style.cssText = "position: relative; width: 100%; height: 100%;";
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
- if (this.loaderEl) {
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: this.config.apiUrl,
1211
+ apiUrl: bundleApiUrl,
961
1212
  token: this.state.accessToken,
962
1213
  basePath: "/",
963
1214
  isPreview: false,
964
- isOpenWidget: true,
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) sendTokenToWidget(this.config, this.state, 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(this.config, this.state, () => this.scheduleRefresh());
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(this.config, this.state, () => this.scheduleRefresh(), pushToken);
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) sendTokenToWidget(this.config, this.state, iframe);
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("locale", config.locale);
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
- return `${base}?locale=${encodeURIComponent(config.locale)}`;
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("locale", config.locale);
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
- return `${base}?locale=${encodeURIComponent(config.locale)}`;
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("locale", config.locale);
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
- return `${base}?locale=${encodeURIComponent(config.locale)}`;
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("locale", config.locale);
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
- return `${base}?locale=${encodeURIComponent(config.locale)}`;
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("locale", config.locale);
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
- return `${base}?locale=${encodeURIComponent(config.locale)}`;
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("locale", config.locale);
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
- return `${base}?locale=${encodeURIComponent(config.locale)}`;
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.2.16"}`);
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.2.16",
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,