@returningai/widget-sdk 1.0.2 → 1.0.3

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 CHANGED
@@ -36,16 +36,16 @@ Customer page DOM
36
36
 
37
37
  | Module | File | Purpose |
38
38
  |--------|------|---------|
39
- | Base Web Component | `src/BaseWidget.ts` | Abstract `HTMLElement` subclass; owns the Shadow Root, auth lifecycle, iframe creation, public API |
39
+ | Base Web Component | `src/BaseWidget.ts` | Abstract `HTMLElement` subclass; owns Shadow Root, auth lifecycle, iframe creation, lazy loading, DOM events, public API |
40
40
  | Store Widget | `src/StoreWidget.ts` | Extends `BaseWidget`; implements `buildWidgetUrl()` for the store micro-frontend |
41
41
  | Channel Widget | `src/ChannelWidget.ts` | Extends `BaseWidget`; appends `widgetId` to base URL or passes full URL through |
42
42
  | Milestone Widget | `src/MilestoneWidget.ts` | Extends `BaseWidget`; same URL strategy as `ChannelWidget` |
43
43
  | Social Widget | `src/SocialWidget.ts` | Extends `BaseWidget`; same URL strategy as `ChannelWidget` |
44
44
  | Currency Widget | `src/CurrencyWidget.ts` | Extends `BaseWidget`; same URL strategy as `ChannelWidget` |
45
- | Auth | `src/core/auth.ts` | Serverless auth, token refresh (with concurrency lock), logout, error settings fetch |
45
+ | Auth | `src/core/auth.ts` | Serverless auth with exponential backoff retry, proxy auth (authenticated embed), token refresh (concurrency lock), logout, error settings fetch |
46
46
  | Storage | `src/core/storage.ts` | `localStorage` helpers scoped to `{prefix}-{widgetId}-*`; access token never written to disk |
47
- | postMessage | `src/core/postmessage.ts` | Sends token to iframe; receives `WIDGET_READY`, `WIDGET_HEIGHT_UPDATE`, `WIDGET_LOGOUT`, `RETURNINGAI_WIDGET_REQUEST_TOKEN` |
48
- | Styles | `src/styles/widget.css` | Loader animation, error state, iframe fade-in — all scoped inside Shadow DOM |
47
+ | postMessage | `src/core/postmessage.ts` | Sends token (+ optional `customData`) to iframe; debounced height updates; emits DOM events; handles `WIDGET_READY`, `WIDGET_HEIGHT_UPDATE`, `WIDGET_LOGOUT`, `RETURNINGAI_WIDGET_REQUEST_TOKEN` |
48
+ | Styles | `src/styles/widget.css` | Loader animation, error state, retry button, iframe fade-in — all scoped inside Shadow DOM |
49
49
  | Entry | `src/index.ts` | Registers all 5 custom elements; bootstraps from `<script>` tag for legacy embeds; routes by `widget-type`; exposes `window.ReturningAIWidget` |
50
50
 
51
51
  ### Auth Flow
@@ -53,16 +53,19 @@ Customer page DOM
53
53
  ```
54
54
  Page load
55
55
 
56
- ├─ loadFromStorage() ── refresh token in localStorage?
57
- │ Yes ──► refreshAccessToken() ──► createIframe()
58
- │ No ──► authenticateServerless() ──► createIframe()
56
+ ├─ auth-url present? (authenticated embed)
57
+ │ Yes ──► authenticateViaProxy() ──► createIframe()
58
+ │ No ──► loadFromStorage()
59
+ │ ├─ refresh token found ──► refreshAccessToken() ──► createIframe()
60
+ │ └─ no token ──► authenticateServerless() ──► createIframe()
59
61
 
60
62
  └─ iframe.onload
61
- └─ sendTokenToWidget() ← postMessage with access token
63
+ └─ sendTokenToWidget() ← postMessage with access token
62
64
  schedulePeriodicSync() ← resend every 2 min
63
65
 
64
66
  Token nearing expiry (1 min early)
65
- └─ refreshAccessToken()
67
+ ├─ authenticated embed ──► authenticateViaProxy() ──► sendTokenToWidget()
68
+ └─ public embed ──► refreshAccessToken()
66
69
  ├─ 401/403 ──► re-authenticate serverless (token family rotated)
67
70
  └─ success ──► sendTokenToWidget()
68
71
  ```
@@ -82,7 +85,7 @@ Messages the SDK sends **to** the widget iframe:
82
85
 
83
86
  | Type | Payload | When |
84
87
  |------|---------|------|
85
- | `RETURNINGAI_WIDGET_TOKEN` | `{ widgetId, token }` | After iframe load, on refresh, every 2 min |
88
+ | `RETURNINGAI_WIDGET_TOKEN` | `{ widgetId, token, customData? }` | After iframe load, on refresh, every 2 min |
86
89
 
87
90
  Messages the SDK **receives** from the widget iframe:
88
91
 
@@ -152,6 +155,7 @@ None. The SDK ships as a single self-contained IIFE with no external runtime dep
152
155
  | Custom Elements v1 | Chrome 67, Firefox 63, Safari 13 |
153
156
  | Shadow DOM v1 | Chrome 53, Firefox 63, Safari 10 |
154
157
  | `crypto.randomUUID()` | Chrome 92, Firefox 95, Safari 15.4 |
158
+ | `IntersectionObserver` | Chrome 58, Firefox 55, Safari 12.1 |
155
159
  | `localStorage` | All modern browsers |
156
160
 
157
161
  ---
@@ -260,9 +264,35 @@ All attributes can be provided with or without the `data-` prefix.
260
264
  | `widget-url` | No | — | URL served inside the iframe — from Community Settings |
261
265
  | `auto-refresh` | No | `true` | Automatically refresh access token before expiry |
262
266
  | `debug` | No | `false` | Enable verbose console logging |
263
- | `data-email` | No | — | User identifier passed to auth |
267
+ | `eager` | No | — | Boolean skip `IntersectionObserver`, init immediately on mount |
268
+ | `locale` | No | — | BCP 47 tag appended as `?locale=` to the widget URL (e.g. `fr-FR`) |
269
+ | `max-retries` | No | `3` | Max auth retry attempts on network error or 5xx |
270
+ | `retry-delay` | No | `500` | Base backoff delay in ms; doubles each attempt |
271
+ | `height-debounce` | No | `100` | Debounce window in ms for `WIDGET_HEIGHT_UPDATE` |
272
+ | `storage-prefix` | No | `returning-ai-widget` | `localStorage` key prefix — set per tenant to avoid collisions |
273
+ | `retry-label` | No | `Retry` | Text for the retry button on the error screen |
274
+ | `custom-data` | No | — | JSON string forwarded as `customData` in the token postMessage |
275
+ | `auth-url` | No | — | Backend proxy endpoint for authenticated embed — if present, serverless auth is skipped entirely |
276
+ | `data-email` | No | — | User identifier passed to auth (public embed only) |
264
277
  | `data-*` | No | — | Any additional `data-*` attributes are forwarded as `userIdentifiers` to the auth API |
265
278
 
279
+ ### DOM Events
280
+
281
+ All events bubble and are `composed: true` (cross the Shadow DOM boundary).
282
+
283
+ | Event | `detail` | Fired when |
284
+ |-------|----------|-----------|
285
+ | `rai-authenticated` | `{}` | Auth succeeded, before iframe mounts |
286
+ | `rai-ready` | `{}` | `WIDGET_READY` received, loader hidden |
287
+ | `rai-error` | `{ message }` | Auth failed after all retries |
288
+ | `rai-logout` | `{}` | Widget logged out |
289
+ | `rai-height-change` | `{ height }` | iframe resized (after debounce) |
290
+
291
+ ```js
292
+ document.querySelector('rai-channel-widget')
293
+ .addEventListener('rai-error', (e) => showToast(e.detail.message))
294
+ ```
295
+
266
296
  ### Public API
267
297
 
268
298
  After the SDK loads, `window.ReturningAIWidget` is available:
@@ -1 +1 @@
1
- var RaiWidget=function(e){"use strict";var t=Object.defineProperty,r=(e,r,a)=>((e,r,a)=>r in e?t(e,r,{enumerable:!0,configurable:!0,writable:!0,value:a}):e[r]=a)(e,"symbol"!=typeof r?r+"":r,a);function a(e){return`${e.storagePrefix}-${e.widgetId}-auth`}function i(e){return`${e.storagePrefix}-${e.widgetId}-error-settings`}function s(e){try{localStorage.removeItem(a(e))}catch{}}function n(e){return!e||Date.now()>=e-6e4}function o(e,t,r,i){t.accessToken=r.accessToken,t.refreshToken=r.refreshToken,t.tokenFamily=r.tokenFamily??null;const s=Date.now();t.accessTokenExpiry=s+1e3*r.accessTokenTTL,t.refreshTokenExpiry=s+1e3*r.refreshTokenTTL,function(e,t){try{const r={refreshToken:t.refreshToken,tokenFamily:t.tokenFamily,refreshTokenExpiry:t.refreshTokenExpiry};localStorage.setItem(a(e),JSON.stringify(r))}catch{}}(e,t),e.autoRefresh&&i&&i()}async function l(e,t,r){const a=crypto.randomUUID(),i=Date.now();try{const s=await fetch(`${e.apiUrl}/${e.widgetId}/auth/serverless`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({nonce:a,timestamp:i,widgetType:e.widgetType,userIdentifiers:e.userIdentifiers}),credentials:"include"});if(!s.ok){const e=await s.json().catch(()=>({error:"Authentication failed"}));throw new Error(e.error||`HTTP ${s.status}`)}const n=await s.json();if(!n.accessToken||!n.refreshToken)throw new Error("Invalid authentication response");return o(e,t,n,r),t.isAuthenticated=!0,!0}catch{return t.isAuthenticated=!1,!1}}async function c(e,t,r,a){return t.isRefreshing?t.refreshPromise:!!t.refreshToken&&(t.isRefreshing=!0,t.refreshPromise=(async()=>{try{const i=await fetch(`${e.apiUrl}/${e.widgetId}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.refreshToken}`},body:JSON.stringify({widgetType:e.widgetType})});if(!i.ok){if(401===i.status||403===i.status)return h(t),s(e),l(e,t,r);const a=await i.json().catch(()=>({error:"Token refresh failed"}));throw new Error(a.error||`HTTP ${i.status}`)}const n=await i.json();if(!n.accessToken||!n.refreshToken)throw new Error("Invalid refresh response");return o(e,t,n,r),a&&a(),!0}catch{return h(t),s(e),l(e,t,r)}finally{t.isRefreshing=!1,t.refreshPromise=null}})(),t.refreshPromise)}async function d(e,t){const r=function(e){try{const t=localStorage.getItem(i(e));if(!t)return null;const r=JSON.parse(t);return r.cachedAt&&Date.now()-r.cachedAt<36e5?r:(localStorage.removeItem(i(e)),null)}catch{return null}}(e);if(r)return r.configured&&(t.errorSettings={errorMessage:r.errorMessage,modalColor:r.modalColor,backgroundImage:r.backgroundImage}),t.errorSettings;try{const r=await fetch(`${e.apiUrl}/${e.widgetId}/auth/error-settings?widgetType=${e.widgetType}`,{method:"GET",headers:{"Content-Type":"application/json"}});if(!r.ok)return null;const a=await r.json();return function(e,t){try{localStorage.setItem(i(e),JSON.stringify({...t,cachedAt:Date.now()}))}catch{}}(e,a),a.configured&&(t.errorSettings={errorMessage:a.errorMessage,modalColor:a.modalColor,backgroundImage:a.backgroundImage}),t.errorSettings}catch{return null}}function h(e){e.accessToken=null,e.refreshToken=null,e.tokenFamily=null,e.accessTokenExpiry=null,e.refreshTokenExpiry=null,e.isAuthenticated=!1,e.isRefreshing=!1,e.refreshPromise=null,e.refreshTimer&&(clearTimeout(e.refreshTimer),e.refreshTimer=null),e.syncTimer&&(clearInterval(e.syncTimer),e.syncTimer=null)}function f(e,t,r){var a;if(t.accessToken)try{null==(a=r.contentWindow)||a.postMessage({type:"RETURNINGAI_WIDGET_TOKEN",value:{widgetId:e.widgetId,token:t.accessToken}},e.widgetDomain)}catch{}}function u(e,t,r,a,i,s,o){const d=async r=>{var d;if(r.origin!==e.widgetDomain)return;if(!r.data||"string"!=typeof r.data.type)return;const{type:h,containerId:f,widgetId:u,payload:g}=r.data;switch(h){case"RETURNINGAI_WIDGET_REQUEST_TOKEN":try{const r=await async function(e,t,r){if(t.accessToken&&!n(t.accessTokenExpiry))return t.accessToken;if(await c(e,t,r)&&t.accessToken)return t.accessToken;if(await l(e,t,r)&&t.accessToken)return t.accessToken;throw new Error("Unable to obtain valid token")}(e,t,i);null==(d=a.contentWindow)||d.postMessage({type:"RETURNINGAI_WIDGET_TOKEN",value:{token:r,widgetId:e.widgetId}},e.widgetDomain)}catch{}break;case"WIDGET_HEIGHT_UPDATE":{const e=Number(null==g?void 0:g.height);Number.isFinite(e)&&e>0&&(a.style.height=`${e}px`);break}case"WIDGET_READY":if(void 0!==u&&u!==e.widgetId)break;if(void 0!==f&&f!==e.container)break;a.classList.add("loaded"),o&&o();break;case"WIDGET_ERROR":o&&o();break;case"WIDGET_LOGOUT":s&&await s()}};return window.addEventListener("message",d),()=>window.removeEventListener("message",d)}const g=new Set(["widget-id","widget-type","theme","container","width","height","api-url","widget-url","auto-refresh","debug"]);class m extends HTMLElement{constructor(){super(),r(this,"shadow"),r(this,"config"),r(this,"state",{accessToken:null,refreshToken:null,tokenFamily:null,accessTokenExpiry:null,refreshTokenExpiry:null,refreshTimer:null,syncTimer:null,iframe:null,isAuthenticated:!1,isRefreshing:!1,refreshPromise:null,errorSettings:null}),r(this,"loaderEl",null),r(this,"errorEl",null),r(this,"cleanupListener"),this.shadow=this.attachShadow({mode:"closed"})}connectedCallback(){this.config=function(e,t){const r=t=>e.getAttribute(t)??e.getAttribute(`data-${t}`)??"";let a,i=r("widget-url")||"https://widget.returningai.com";i.endsWith("store-widget")&&(i=`${i}/${r("widget-id")}/open-widget`);try{a=new URL(i).origin}catch{a=i}if(i.includes("store-widget")){const e=new URL(i);e.searchParams.set("color",r("theme")||"light"),i=e.toString()}const s=r("widget-id")||t||"";if(s&&!/^[a-zA-Z0-9_\-=]{8,}$/.test(s))throw new Error(`[rai-widget] Invalid widget-id format: "${s}"`);const n=r("container")||r("data-container")||`returning-ai-widget-${s}`,o={};return Array.from(e.attributes).forEach(e=>{const t=e.name.toLowerCase();if(!t.startsWith("data-"))return;const r=t.slice(5);g.has(r)||(o[t]=e.value)}),{widgetId:s,widgetType:r("widget-type")||"store",theme:r("theme")||"light",container:n,width:r("width")||"100%",height:r("height")||"600px",apiUrl:r("api-url")||"https://sgtr-eks-widgets.genesiv.org",widgetUrl:i,widgetDomain:a,autoRefresh:"false"!==r("auto-refresh"),debug:"true"===r("debug"),storagePrefix:"returning-ai-widget",userIdentifiers:o}}(this,void 0);const e=document.createElement("style");e.textContent=":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}.loader{position:relative;width:75px;height:100px}.loader__bar{position:absolute;bottom:0;width:10px;height:50%;background:var(--rai-accent, #000000);transform-origin:center bottom;box-shadow:1px 1px #0003}.loader__bar:nth-child(1){left:0;transform:scaleY(.2);animation:barUp1 4s infinite}.loader__bar:nth-child(2){left:15px;transform:scaleY(.4);animation:barUp2 4s infinite}.loader__bar:nth-child(3){left:30px;transform:scaleY(.6);animation:barUp3 4s infinite}.loader__bar:nth-child(4){left:45px;transform:scaleY(.8);animation:barUp4 4s infinite}.loader__bar:nth-child(5){left:60px;transform:scale(1);animation:barUp5 4s infinite}.loader__ball{position:absolute;bottom:10px;left:0;width:10px;height:10px;background:var(--rai-accent, #000000);border-radius:50%;animation:ball 4s infinite}@keyframes 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 barUp1{0%{transform:scaleY(.2)}40%{transform:scaleY(.2)}50%{transform:scale(1)}90%{transform:scale(1)}to{transform:scaleY(.2)}}@keyframes barUp2{0%{transform:scaleY(.4)}40%{transform:scaleY(.4)}50%{transform:scaleY(.8)}90%{transform:scaleY(.8)}to{transform:scaleY(.4)}}@keyframes barUp3{0%{transform:scaleY(.6)}to{transform:scaleY(.6)}}@keyframes barUp4{0%{transform:scaleY(.8)}40%{transform:scaleY(.8)}50%{transform:scaleY(.4)}90%{transform:scaleY(.4)}to{transform:scaleY(.8)}}@keyframes 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)}}",this.shadow.appendChild(e);const t=this.config.theme,r="dark"===t?"#ffffff":"#000000",a="dark"===t?"#9ca3af":"#6b7280",i="dark"===t?"#1a1a1a":"#ffffff";this.style.setProperty("--rai-accent",r),this.style.setProperty("--rai-text4",a),this.style.setProperty("--rai-loader-bg",i),this.style.setProperty("--rai-error-bg",i),this.renderShell(),this.init()}disconnectedCallback(){this.cleanupListener&&this.cleanupListener(),h(this.state)}renderShell(){this.loaderEl=document.createElement("div"),this.loaderEl.className="rai-loader",this.loaderEl.appendChild(this.createDefaultLoader()),this.shadow.appendChild(this.loaderEl),this.errorEl=document.createElement("div"),this.errorEl.className="rai-error",this.errorEl.textContent="Authentication failed. Please try again later.",this.shadow.appendChild(this.errorEl)}createDefaultLoader(){const e=document.createElement("div");e.className="loader";for(let r=0;r<5;r++){const t=document.createElement("div");t.className="loader__bar",e.appendChild(t)}const t=document.createElement("div");return t.className="loader__ball",e.appendChild(t),e}hideLoader(){this.loaderEl&&(this.loaderEl.classList.add("fade-out"),setTimeout(()=>{var e;return null==(e=this.loaderEl)?void 0:e.remove()},300),this.loaderEl=null)}showError(){var e;this.hideLoader(),this.errorEl&&((null==(e=this.state.errorSettings)?void 0:e.errorMessage)&&(this.errorEl.textContent=this.state.errorSettings.errorMessage),this.errorEl.classList.add("visible"))}async init(){if(!this.config.widgetId)return void this.showError();await d(this.config,this.state);if(function(e,t){try{const r=localStorage.getItem(a(e));if(!r)return!1;const i=JSON.parse(r);return i.refreshToken&&!n(i.refreshTokenExpiry)?(t.refreshToken=i.refreshToken,t.tokenFamily=i.tokenFamily,t.refreshTokenExpiry=i.refreshTokenExpiry,!0):(s(e),!1)}catch{return!1}}(this.config,this.state)){return await c(this.config,this.state,()=>this.scheduleRefresh())?(this.state.isAuthenticated=!0,void this.createIframe()):void this.showError()}await l(this.config,this.state,()=>this.scheduleRefresh())?this.createIframe():this.showError()}createIframe(){const e=document.createElement("iframe");e.src=this.buildWidgetUrl(this.config),e.allow="clipboard-write",e.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"),e.frameBorder="0",e.scrolling="no",e.style.width=this.config.width,e.style.height=this.config.height,e.style.border="none",e.style.display="block",e.onload=()=>{f(this.config,this.state,e),this.schedulePeriodicSync(e)},e.onerror=()=>{this.showError()},this.state.iframe=e,this.shadow.appendChild(e),this.cleanupListener=u(this.config,this.state,this.shadow,e,()=>this.scheduleRefresh(),()=>this.logoutAndClear(),()=>this.hideLoader())}scheduleRefresh(){if(this.state.refreshTimer&&clearTimeout(this.state.refreshTimer),!this.state.accessTokenExpiry)return;const e=this.state.accessTokenExpiry-Date.now()-6e4;e>0?this.state.refreshTimer=setTimeout(async()=>{await c(this.config,this.state,()=>this.scheduleRefresh(),()=>{this.state.iframe&&f(this.config,this.state,this.state.iframe)})},e):c(this.config,this.state,()=>this.scheduleRefresh(),()=>{this.state.iframe&&f(this.config,this.state,this.state.iframe)})}schedulePeriodicSync(e){this.state.syncTimer&&clearInterval(this.state.syncTimer),this.state.syncTimer=setInterval(()=>{this.state.accessToken&&f(this.config,this.state,e)},12e4)}async logoutAndClear(){await async function(e,t){if(t.accessToken)try{await fetch(`${e.apiUrl}/${e.widgetId}/auth/logout`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.accessToken}`},body:JSON.stringify({refreshToken:t.refreshToken})})}catch{}h(t),s(e)}(this.config,this.state),this.shadow.querySelectorAll("iframe").forEach(e=>e.remove())}async reload(){h(this.state),this.shadow.querySelectorAll("iframe").forEach(e=>e.remove()),await this.init()}async logoutPublic(){await this.logoutAndClear()}isAuthenticated(){return this.state.isAuthenticated&&null!==this.state.accessToken}getTokenInfo(){return{hasAccessToken:!!this.state.accessToken,hasRefreshToken:!!this.state.refreshToken,accessTokenExpiry:this.state.accessTokenExpiry?new Date(this.state.accessTokenExpiry):null,refreshTokenExpiry:this.state.refreshTokenExpiry?new Date(this.state.refreshTokenExpiry):null,isAccessTokenValid:!!this.state.accessToken&&!n(this.state.accessTokenExpiry),isRefreshTokenValid:!!this.state.refreshToken&&!n(this.state.refreshTokenExpiry)}}}class p extends m{buildWidgetUrl(e){const t=new URL(e.widgetUrl);return t.searchParams.set("color",e.theme),t.searchParams.set("containerId",e.container),t.searchParams.set("connectType","simple"),t.searchParams.set("mode","private"),t.toString()}}class w extends m{buildWidgetUrl(e){return e.widgetUrl.endsWith("channel-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl}}class y extends m{buildWidgetUrl(e){return e.widgetUrl.endsWith("milestone-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl}}class T extends m{buildWidgetUrl(e){return e.widgetUrl.endsWith("social-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl}}class k extends m{buildWidgetUrl(e){return e.widgetUrl.endsWith("currency-overview-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl}}console.log("[rai-widget] v1.0.2");const b=[["rai-widget",p],["rai-channel-widget",w],["rai-milestone-widget",y],["rai-social-widget",T],["rai-currency-widget",k]];for(const[S,U]of b)customElements.get(S)||customElements.define(S,U);const x={store:p,channel:w,milestone:y,social:T,"currency-view":k},E=b.map(([e])=>e).join(", ");function v(){var e;const t=((null==(e=document.currentScript)?void 0:e.hasAttribute("data-widget-id"))?document.currentScript:null)||document.querySelector("script[data-widget-id]");if(!t)return;const r=t.getAttribute("data-widget-id");if(!r)return;const a=t.getAttribute("data-container")||`returning-ai-widget-${r}`,i=document.getElementById(a);if(!i)return;"static"===getComputedStyle(i).position&&(i.style.position="relative");const s=t.getAttribute("data-widget-type")??"store",n=new(x[s]??p);Array.from(t.attributes).forEach(e=>{n.setAttribute(e.name,e.value)}),i.appendChild(n)}function I(){const e=(()=>{var e;const t=((null==(e=document.currentScript)?void 0:e.hasAttribute("data-widget-id"))?document.currentScript:null)||document.querySelector("script[data-widget-id]");if(!t)return null;const r=t.getAttribute("data-container")||`returning-ai-widget-${t.getAttribute("data-widget-id")}`;return document.getElementById(r)})(),t=null==e?void 0:e.querySelector(E);window.ReturningAIWidget={version:"1.0.2",reload:()=>(null==t?void 0:t.reload())??Promise.resolve(),logout:()=>(null==t?void 0:t.logoutPublic())??Promise.resolve(),isAuthenticated:()=>(null==t?void 0:t.isAuthenticated())??!1,getTokenInfo:()=>(null==t?void 0:t.getTokenInfo())??{}}}return"loading"===document.readyState?document.addEventListener("DOMContentLoaded",v):v(),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",I):I(),e.ChannelWidget=w,e.CurrencyWidget=k,e.MilestoneWidget=y,e.SocialWidget=T,e.StoreWidget=p,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),e}({});
1
+ var RaiWidget=function(e){"use strict";var t=Object.defineProperty,r=(e,r,a)=>((e,r,a)=>r in e?t(e,r,{enumerable:!0,configurable:!0,writable:!0,value:a}):e[r]=a)(e,"symbol"!=typeof r?r+"":r,a);function a(e){return`${e.storagePrefix}-${e.widgetId}-auth`}function i(e){return`${e.storagePrefix}-${e.widgetId}-error-settings`}function s(e){try{localStorage.removeItem(a(e))}catch{}}function n(e){return!e||Date.now()>=e-6e4}function o(e,t,r,i){t.accessToken=r.accessToken,t.refreshToken=r.refreshToken,t.tokenFamily=r.tokenFamily??null;const s=Date.now();t.accessTokenExpiry=s+1e3*r.accessTokenTTL,t.refreshTokenExpiry=s+1e3*r.refreshTokenTTL,function(e,t){try{const r={refreshToken:t.refreshToken,tokenFamily:t.tokenFamily,refreshTokenExpiry:t.refreshTokenExpiry};localStorage.setItem(a(e),JSON.stringify(r))}catch{}}(e,t),e.autoRefresh&&i&&i()}async function l(e,t,r){const a=crypto.randomUUID(),i=Date.now(),s=e.maxRetries??3,n=e.retryDelay??500;for(let l=0;l<=s;l++){l>0&&await new Promise(e=>setTimeout(e,n*(1<<l-1)));let c=!1;try{const s=await fetch(`${e.apiUrl}/${e.widgetId}/auth/serverless`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({nonce:a,timestamp:i,widgetType:e.widgetType,userIdentifiers:e.userIdentifiers}),credentials:"include"});if(!s.ok){c=s.status>=400&&s.status<500;const e=await s.json().catch(()=>({error:"Authentication failed"}));throw new Error(e.error||`HTTP ${s.status}`)}const n=await s.json();if(!n.accessToken||!n.refreshToken)throw new Error("Invalid authentication response");return o(e,t,n,r),t.isAuthenticated=!0,!0}catch{if(c||l===s)return t.isAuthenticated=!1,!1}}return t.isAuthenticated=!1,!1}async function c(e,t,r){const a=e.maxRetries??3,i=e.retryDelay??500;for(let s=0;s<=a;s++){s>0&&await new Promise(e=>setTimeout(e,i*(1<<s-1)));let n=!1;try{const a=await fetch(e.authUrl,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"}});if(!a.ok)throw n=a.status>=400&&a.status<500,new Error(`HTTP ${a.status}`);const i=await a.json();if(!i.token)throw new Error("Invalid proxy auth response");const s=i.expiresIn??300;return t.accessToken=i.token,t.refreshToken=null,t.tokenFamily=null,t.accessTokenExpiry=Date.now()+1e3*s,t.refreshTokenExpiry=null,t.isAuthenticated=!0,e.autoRefresh&&r&&r(),!0}catch{if(n||s===a)return t.isAuthenticated=!1,!1}}return t.isAuthenticated=!1,!1}async function d(e,t,r,a){return t.isRefreshing?t.refreshPromise:!!t.refreshToken&&(t.isRefreshing=!0,t.refreshPromise=(async()=>{try{const i=await fetch(`${e.apiUrl}/${e.widgetId}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.refreshToken}`},body:JSON.stringify({widgetType:e.widgetType})});if(!i.ok){if(401===i.status||403===i.status)return u(t),s(e),l(e,t,r);const a=await i.json().catch(()=>({error:"Token refresh failed"}));throw new Error(a.error||`HTTP ${i.status}`)}const n=await i.json();if(!n.accessToken||!n.refreshToken)throw new Error("Invalid refresh response");return o(e,t,n,r),a&&a(),!0}catch{return u(t),s(e),l(e,t,r)}finally{t.isRefreshing=!1,t.refreshPromise=null}})(),t.refreshPromise)}async function h(e,t){const r=function(e){try{const t=localStorage.getItem(i(e));if(!t)return null;const r=JSON.parse(t);return r.cachedAt&&Date.now()-r.cachedAt<36e5?r:(localStorage.removeItem(i(e)),null)}catch{return null}}(e);if(r)return r.configured&&(t.errorSettings={errorMessage:r.errorMessage,modalColor:r.modalColor,backgroundImage:r.backgroundImage}),t.errorSettings;try{const r=await fetch(`${e.apiUrl}/${e.widgetId}/auth/error-settings?widgetType=${e.widgetType}`,{method:"GET",headers:{"Content-Type":"application/json"}});if(!r.ok)return null;const a=await r.json();return function(e,t){try{localStorage.setItem(i(e),JSON.stringify({...t,cachedAt:Date.now()}))}catch{}}(e,a),a.configured&&(t.errorSettings={errorMessage:a.errorMessage,modalColor:a.modalColor,backgroundImage:a.backgroundImage}),t.errorSettings}catch{return null}}function u(e){e.accessToken=null,e.refreshToken=null,e.tokenFamily=null,e.accessTokenExpiry=null,e.refreshTokenExpiry=null,e.isAuthenticated=!1,e.isRefreshing=!1,e.refreshPromise=null,e.refreshTimer&&(clearTimeout(e.refreshTimer),e.refreshTimer=null),e.syncTimer&&(clearInterval(e.syncTimer),e.syncTimer=null)}function f(e,t,r){var a;if(t.accessToken)try{null==(a=r.contentWindow)||a.postMessage({type:"RETURNINGAI_WIDGET_TOKEN",value:{widgetId:e.widgetId,token:t.accessToken,...void 0!==e.customData&&{customData:e.customData}}},e.widgetDomain)}catch{}}function g(e,t,r,a,i,s,o,h){const u=function(e,t){let r=null;return{call(a){r&&clearTimeout(r),r=setTimeout(()=>{e(a),r=null},t)},cancel(){r&&(clearTimeout(r),r=null)}}}(e=>{a.style.height=`${e}px`,null==h||h("rai-height-change",{height:e})},e.heightDebounce??100),f=async r=>{var f;if(r.origin!==e.widgetDomain)return;if(!r.data||"string"!=typeof r.data.type)return;const{type:g,containerId:m,widgetId:p,payload:w}=r.data;switch(g){case"RETURNINGAI_WIDGET_REQUEST_TOKEN":try{const r=await async function(e,t,r){if(t.accessToken&&!n(t.accessTokenExpiry))return t.accessToken;if(e.authUrl){if(await c(e,t,r)&&t.accessToken)return t.accessToken;throw new Error("Unable to obtain valid token")}if(await d(e,t,r)&&t.accessToken)return t.accessToken;if(await l(e,t,r)&&t.accessToken)return t.accessToken;throw new Error("Unable to obtain valid token")}(e,t,i);null==(f=a.contentWindow)||f.postMessage({type:"RETURNINGAI_WIDGET_TOKEN",value:{token:r,widgetId:e.widgetId,...void 0!==e.customData&&{customData:e.customData}}},e.widgetDomain)}catch{}break;case"WIDGET_HEIGHT_UPDATE":{const e=Number(null==w?void 0:w.height);Number.isFinite(e)&&e>0&&u.call(e);break}case"WIDGET_READY":if(void 0!==p&&p!==e.widgetId)break;if(void 0!==m&&m!==e.container)break;a.classList.add("loaded"),o&&o(),null==h||h("rai-ready");break;case"WIDGET_ERROR":o&&o();break;case"WIDGET_LOGOUT":s&&await s()}};return window.addEventListener("message",f),()=>{window.removeEventListener("message",f),u.cancel()}}const m=new Set(["widget-id","widget-type","theme","container","width","height","api-url","widget-url","auto-refresh","debug","storage-prefix","max-retries","retry-delay","height-debounce","locale","custom-data","retry-label","auth-url"]);class p extends HTMLElement{constructor(){super(),r(this,"shadow"),r(this,"config"),r(this,"state",{accessToken:null,refreshToken:null,tokenFamily:null,accessTokenExpiry:null,refreshTokenExpiry:null,refreshTimer:null,syncTimer:null,iframe:null,isAuthenticated:!1,isRefreshing:!1,refreshPromise:null,errorSettings:null}),r(this,"loaderEl",null),r(this,"errorEl",null),r(this,"msgEl",null),r(this,"cleanupListener"),r(this,"intersectionObserver"),this.shadow=this.attachShadow({mode:"closed"})}connectedCallback(){this.config=function(e,t){const r=t=>e.getAttribute(t)??e.getAttribute(`data-${t}`)??"",a=(e,t)=>{const a=parseInt(r(e),10);return Number.isFinite(a)&&a>=0?a:t};let i,s=r("widget-url")||"https://widget.returningai.com";s.endsWith("store-widget")&&(s=`${s}/${r("widget-id")}/open-widget`);try{i=new URL(s).origin}catch{i=s}if(s.includes("store-widget")){const e=new URL(s);e.searchParams.set("color",r("theme")||"light"),s=e.toString()}const n=r("widget-id")||t||"";if(n&&!/^[a-zA-Z0-9_\-=]{8,}$/.test(n))throw new Error(`[rai-widget] Invalid widget-id format: "${n}"`);const o=r("container")||r("data-container")||`returning-ai-widget-${n}`,l={};Array.from(e.attributes).forEach(e=>{const t=e.name.toLowerCase();if(!t.startsWith("data-"))return;const r=t.slice(5);m.has(r)||(l[t]=e.value)});const c=(()=>{const e=r("custom-data");if(e)try{return JSON.parse(e)}catch{return}})();return{widgetId:n,widgetType:r("widget-type")||"store",theme:r("theme")||"light",container:o,width:r("width")||"100%",height:r("height")||"600px",apiUrl:r("api-url")||"https://sgtr-eks-widgets.genesiv.org",widgetUrl:s,widgetDomain:i,autoRefresh:"false"!==r("auto-refresh"),debug:"true"===r("debug"),storagePrefix:r("storage-prefix")||"returning-ai-widget",userIdentifiers:l,maxRetries:a("max-retries",3),retryDelay:a("retry-delay",500),heightDebounce:a("height-debounce",100),locale:r("locale")||void 0,customData:c,authUrl:r("auth-url")||void 0}}(this,void 0);const e=document.createElement("style");e.textContent=":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}.loader{position:relative;width:75px;height:100px}.loader__bar{position:absolute;bottom:0;width:10px;height:50%;background:var(--rai-accent, #000000);transform-origin:center bottom;box-shadow:1px 1px #0003}.loader__bar:nth-child(1){left:0;transform:scaleY(.2);animation:barUp1 4s infinite}.loader__bar:nth-child(2){left:15px;transform:scaleY(.4);animation:barUp2 4s infinite}.loader__bar:nth-child(3){left:30px;transform:scaleY(.6);animation:barUp3 4s infinite}.loader__bar:nth-child(4){left:45px;transform:scaleY(.8);animation:barUp4 4s infinite}.loader__bar:nth-child(5){left:60px;transform:scale(1);animation:barUp5 4s infinite}.loader__ball{position:absolute;bottom:10px;left:0;width:10px;height:10px;background:var(--rai-accent, #000000);border-radius:50%;animation:ball 4s infinite}@keyframes 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 barUp1{0%{transform:scaleY(.2)}40%{transform:scaleY(.2)}50%{transform:scale(1)}90%{transform:scale(1)}to{transform:scaleY(.2)}}@keyframes barUp2{0%{transform:scaleY(.4)}40%{transform:scaleY(.4)}50%{transform:scaleY(.8)}90%{transform:scaleY(.8)}to{transform:scaleY(.4)}}@keyframes barUp3{0%{transform:scaleY(.6)}to{transform:scaleY(.6)}}@keyframes barUp4{0%{transform:scaleY(.8)}40%{transform:scaleY(.8)}50%{transform:scaleY(.4)}90%{transform:scaleY(.4)}to{transform:scaleY(.8)}}@keyframes 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-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}",this.shadow.appendChild(e);const t=this.config.theme,r="dark"===t?"#ffffff":"#000000",a="dark"===t?"#9ca3af":"#6b7280",i="dark"===t?"#1a1a1a":"#ffffff";this.style.setProperty("--rai-accent",r),this.style.setProperty("--rai-text4",a),this.style.setProperty("--rai-loader-bg",i),this.style.setProperty("--rai-error-bg",i),this.renderShell(),this.hasAttribute("eager")?this.init():(this.intersectionObserver=new IntersectionObserver(e=>{e[0].isIntersecting&&(this.intersectionObserver.disconnect(),this.intersectionObserver=void 0,this.init())}),this.intersectionObserver.observe(this))}disconnectedCallback(){var e;null==(e=this.intersectionObserver)||e.disconnect(),this.cleanupListener&&this.cleanupListener(),u(this.state)}emit(e,t){this.dispatchEvent(new CustomEvent(e,{bubbles:!0,composed:!0,detail:t??{}}))}renderShell(){this.loaderEl=document.createElement("div"),this.loaderEl.className="rai-loader",this.loaderEl.appendChild(this.createDefaultLoader()),this.shadow.appendChild(this.loaderEl),this.errorEl=document.createElement("div"),this.errorEl.className="rai-error",this.msgEl=document.createElement("span"),this.msgEl.className="rai-error-msg",this.msgEl.textContent="Authentication failed. Please try again later.",this.errorEl.appendChild(this.msgEl);const e=this.getAttribute("retry-label")||this.getAttribute("data-retry-label")||"Retry",t=document.createElement("button");t.className="rai-retry-btn",t.textContent=e,t.addEventListener("click",()=>this.reload()),this.errorEl.appendChild(t),this.shadow.appendChild(this.errorEl)}createDefaultLoader(){const e=document.createElement("div");e.className="loader";for(let r=0;r<5;r++){const t=document.createElement("div");t.className="loader__bar",e.appendChild(t)}const t=document.createElement("div");return t.className="loader__ball",e.appendChild(t),e}hideLoader(){this.loaderEl&&(this.loaderEl.classList.add("fade-out"),setTimeout(()=>{var e;return null==(e=this.loaderEl)?void 0:e.remove()},300),this.loaderEl=null)}showError(){var e,t;this.hideLoader(),this.errorEl&&((null==(e=this.state.errorSettings)?void 0:e.errorMessage)&&this.msgEl&&(this.msgEl.textContent=this.state.errorSettings.errorMessage),this.errorEl.classList.add("visible")),this.emit("rai-error",{message:(null==(t=this.msgEl)?void 0:t.textContent)??"Authentication failed"})}async init(){if(!this.config.widgetId)return void this.showError();if(await h(this.config,this.state),this.config.authUrl){return void(await c(this.config,this.state,()=>this.scheduleRefresh())?(this.emit("rai-authenticated"),this.createIframe()):this.showError())}if(function(e,t){try{const r=localStorage.getItem(a(e));if(!r)return!1;const i=JSON.parse(r);return i.refreshToken&&!n(i.refreshTokenExpiry)?(t.refreshToken=i.refreshToken,t.tokenFamily=i.tokenFamily,t.refreshTokenExpiry=i.refreshTokenExpiry,!0):(s(e),!1)}catch{return!1}}(this.config,this.state)){return await d(this.config,this.state,()=>this.scheduleRefresh())?(this.state.isAuthenticated=!0,this.emit("rai-authenticated"),void this.createIframe()):void this.showError()}await l(this.config,this.state,()=>this.scheduleRefresh())?(this.emit("rai-authenticated"),this.createIframe()):this.showError()}createIframe(){const e=document.createElement("iframe");e.src=this.buildWidgetUrl(this.config),e.allow="clipboard-write",e.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"),e.frameBorder="0",e.scrolling="no",e.style.width=this.config.width,e.style.height=this.config.height,e.style.border="none",e.style.display="block",e.onload=()=>{f(this.config,this.state,e),this.schedulePeriodicSync(e)},e.onerror=()=>{this.showError()},this.state.iframe=e,this.shadow.appendChild(e),this.cleanupListener=g(this.config,this.state,this.shadow,e,()=>this.scheduleRefresh(),()=>this.logoutAndClear(),()=>this.hideLoader(),(e,t)=>this.emit(e,t))}scheduleRefresh(){if(this.state.refreshTimer&&clearTimeout(this.state.refreshTimer),!this.state.accessTokenExpiry)return;const e=this.state.accessTokenExpiry-Date.now()-6e4,t=()=>{this.state.iframe&&f(this.config,this.state,this.state.iframe)},r=async()=>{if(this.config.authUrl){await c(this.config,this.state,()=>this.scheduleRefresh())&&t()}else await d(this.config,this.state,()=>this.scheduleRefresh(),t)};e>0?this.state.refreshTimer=setTimeout(r,e):r()}schedulePeriodicSync(e){this.state.syncTimer&&clearInterval(this.state.syncTimer),this.state.syncTimer=setInterval(()=>{this.state.accessToken&&f(this.config,this.state,e)},12e4)}async logoutAndClear(){await async function(e,t){if(t.accessToken)try{await fetch(`${e.apiUrl}/${e.widgetId}/auth/logout`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t.accessToken}`},body:JSON.stringify({refreshToken:t.refreshToken})})}catch{}u(t),s(e)}(this.config,this.state),this.shadow.querySelectorAll("iframe").forEach(e=>e.remove()),this.emit("rai-logout")}async reload(){u(this.state),this.shadow.querySelectorAll("iframe").forEach(e=>e.remove()),await this.init()}async logoutPublic(){await this.logoutAndClear()}isAuthenticated(){return this.state.isAuthenticated&&null!==this.state.accessToken}getTokenInfo(){return{hasAccessToken:!!this.state.accessToken,hasRefreshToken:!!this.state.refreshToken,accessTokenExpiry:this.state.accessTokenExpiry?new Date(this.state.accessTokenExpiry):null,refreshTokenExpiry:this.state.refreshTokenExpiry?new Date(this.state.refreshTokenExpiry):null,isAccessTokenValid:!!this.state.accessToken&&!n(this.state.accessTokenExpiry),isRefreshTokenValid:!!this.state.refreshToken&&!n(this.state.refreshTokenExpiry)}}}class w extends p{buildWidgetUrl(e){const t=new URL(e.widgetUrl);return t.searchParams.set("color",e.theme),t.searchParams.set("containerId",e.container),t.searchParams.set("connectType","simple"),t.searchParams.set("mode","private"),e.locale&&t.searchParams.set("locale",e.locale),t.toString()}}class y extends p{buildWidgetUrl(e){const t=e.widgetUrl.endsWith("channel-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl;if(!e.locale)return t;try{const r=new URL(t);return r.searchParams.set("locale",e.locale),r.toString()}catch{return`${t}?locale=${encodeURIComponent(e.locale)}`}}}class b extends p{buildWidgetUrl(e){const t=e.widgetUrl.endsWith("milestone-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl;if(!e.locale)return t;try{const r=new URL(t);return r.searchParams.set("locale",e.locale),r.toString()}catch{return`${t}?locale=${encodeURIComponent(e.locale)}`}}}class T extends p{buildWidgetUrl(e){const t=e.widgetUrl.endsWith("social-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl;if(!e.locale)return t;try{const r=new URL(t);return r.searchParams.set("locale",e.locale),r.toString()}catch{return`${t}?locale=${encodeURIComponent(e.locale)}`}}}class k extends p{buildWidgetUrl(e){const t=e.widgetUrl.endsWith("currency-overview-widget")?`${e.widgetUrl}/${e.widgetId}`:e.widgetUrl;if(!e.locale)return t;try{const r=new URL(t);return r.searchParams.set("locale",e.locale),r.toString()}catch{return`${t}?locale=${encodeURIComponent(e.locale)}`}}}console.log("[rai-widget] v1.0.3");const x=[["rai-widget",w],["rai-channel-widget",y],["rai-milestone-widget",b],["rai-social-widget",T],["rai-currency-widget",k]];for(const[S,A]of x)customElements.get(S)||customElements.define(S,A);const v={store:w,channel:y,milestone:b,social:T,"currency-view":k},E=x.map(([e])=>e).join(", ");function I(){var e;const t=((null==(e=document.currentScript)?void 0:e.hasAttribute("data-widget-id"))?document.currentScript:null)||document.querySelector("script[data-widget-id]");if(!t)return;const r=t.getAttribute("data-widget-id");if(!r)return;const a=t.getAttribute("data-container")||`returning-ai-widget-${r}`,i=document.getElementById(a);if(!i)return;"static"===getComputedStyle(i).position&&(i.style.position="relative");const s=t.getAttribute("data-widget-type")??"store",n=new(v[s]??w);Array.from(t.attributes).forEach(e=>{n.setAttribute(e.name,e.value)}),i.appendChild(n)}function U(){const e=(()=>{var e;const t=((null==(e=document.currentScript)?void 0:e.hasAttribute("data-widget-id"))?document.currentScript:null)||document.querySelector("script[data-widget-id]");if(!t)return null;const r=t.getAttribute("data-container")||`returning-ai-widget-${t.getAttribute("data-widget-id")}`;return document.getElementById(r)})(),t=null==e?void 0:e.querySelector(E);window.ReturningAIWidget={version:"1.0.3",reload:()=>(null==t?void 0:t.reload())??Promise.resolve(),logout:()=>(null==t?void 0:t.logoutPublic())??Promise.resolve(),isAuthenticated:()=>(null==t?void 0:t.isAuthenticated())??!1,getTokenInfo:()=>(null==t?void 0:t.getTokenInfo())??{}}}return"loading"===document.readyState?document.addEventListener("DOMContentLoaded",I):I(),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",U):U(),e.ChannelWidget=y,e.CurrencyWidget=k,e.MilestoneWidget=b,e.SocialWidget=T,e.StoreWidget=w,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),e}({});
@@ -81,33 +81,93 @@ function setTokens(config, state, data, onRefreshScheduled) {
81
81
  async function authenticateServerless(config, state, onRefreshScheduled) {
82
82
  const nonce = crypto.randomUUID();
83
83
  const timestamp = Date.now();
84
- try {
85
- const response = await fetch(`${config.apiUrl}/${config.widgetId}/auth/serverless`, {
86
- method: "POST",
87
- headers: { "Content-Type": "application/json" },
88
- body: JSON.stringify({
89
- nonce,
90
- timestamp,
91
- widgetType: config.widgetType,
92
- userIdentifiers: config.userIdentifiers
93
- }),
94
- credentials: "include"
95
- });
96
- if (!response.ok) {
97
- const error = await response.json().catch(() => ({ error: "Authentication failed" }));
98
- throw new Error(error.error || `HTTP ${response.status}`);
84
+ const maxRetries = config.maxRetries ?? 3;
85
+ const retryDelay = config.retryDelay ?? 500;
86
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
87
+ if (attempt > 0) {
88
+ await new Promise(
89
+ (resolve) => setTimeout(resolve, retryDelay * (1 << attempt - 1))
90
+ );
99
91
  }
100
- const data = await response.json();
101
- if (!data.accessToken || !data.refreshToken) {
102
- throw new Error("Invalid authentication response");
92
+ let is4xx = false;
93
+ try {
94
+ const response = await fetch(`${config.apiUrl}/${config.widgetId}/auth/serverless`, {
95
+ method: "POST",
96
+ headers: { "Content-Type": "application/json" },
97
+ body: JSON.stringify({
98
+ nonce,
99
+ timestamp,
100
+ widgetType: config.widgetType,
101
+ userIdentifiers: config.userIdentifiers
102
+ }),
103
+ credentials: "include"
104
+ });
105
+ if (!response.ok) {
106
+ 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}`);
109
+ }
110
+ const data = await response.json();
111
+ if (!data.accessToken || !data.refreshToken) {
112
+ throw new Error("Invalid authentication response");
113
+ }
114
+ setTokens(config, state, data, onRefreshScheduled);
115
+ state.isAuthenticated = true;
116
+ return true;
117
+ } catch {
118
+ if (is4xx || attempt === maxRetries) {
119
+ state.isAuthenticated = false;
120
+ return false;
121
+ }
122
+ }
123
+ }
124
+ state.isAuthenticated = false;
125
+ return false;
126
+ }
127
+ async function authenticateViaProxy(config, state, onRefreshScheduled) {
128
+ const maxRetries = config.maxRetries ?? 3;
129
+ const retryDelay = config.retryDelay ?? 500;
130
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
131
+ if (attempt > 0) {
132
+ await new Promise(
133
+ (resolve) => setTimeout(resolve, retryDelay * (1 << attempt - 1))
134
+ );
135
+ }
136
+ let is4xx = false;
137
+ try {
138
+ const response = await fetch(config.authUrl, {
139
+ method: "POST",
140
+ credentials: "include",
141
+ headers: { "Content-Type": "application/json" }
142
+ });
143
+ if (!response.ok) {
144
+ is4xx = response.status >= 400 && response.status < 500;
145
+ throw new Error(`HTTP ${response.status}`);
146
+ }
147
+ const data = await response.json();
148
+ if (!data.token) {
149
+ throw new Error("Invalid proxy auth response");
150
+ }
151
+ const ttl = data.expiresIn ?? 300;
152
+ state.accessToken = data.token;
153
+ state.refreshToken = null;
154
+ state.tokenFamily = null;
155
+ state.accessTokenExpiry = Date.now() + ttl * 1e3;
156
+ state.refreshTokenExpiry = null;
157
+ state.isAuthenticated = true;
158
+ if (config.autoRefresh && onRefreshScheduled) {
159
+ onRefreshScheduled();
160
+ }
161
+ return true;
162
+ } catch {
163
+ if (is4xx || attempt === maxRetries) {
164
+ state.isAuthenticated = false;
165
+ return false;
166
+ }
103
167
  }
104
- setTokens(config, state, data, onRefreshScheduled);
105
- state.isAuthenticated = true;
106
- return true;
107
- } catch {
108
- state.isAuthenticated = false;
109
- return false;
110
168
  }
169
+ state.isAuthenticated = false;
170
+ return false;
111
171
  }
112
172
  async function refreshAccessToken(config, state, onRefreshScheduled, sendToken) {
113
173
  if (state.isRefreshing) {
@@ -158,6 +218,11 @@ async function getValidToken(config, state, onRefreshScheduled) {
158
218
  if (state.accessToken && !isTokenExpired(state.accessTokenExpiry)) {
159
219
  return state.accessToken;
160
220
  }
221
+ if (config.authUrl) {
222
+ const authed2 = await authenticateViaProxy(config, state, onRefreshScheduled);
223
+ if (authed2 && state.accessToken) return state.accessToken;
224
+ throw new Error("Unable to obtain valid token");
225
+ }
161
226
  const refreshed = await refreshAccessToken(config, state, onRefreshScheduled);
162
227
  if (refreshed && state.accessToken) return state.accessToken;
163
228
  const authed = await authenticateServerless(config, state, onRefreshScheduled);
@@ -231,6 +296,24 @@ function clearState(state) {
231
296
  state.syncTimer = null;
232
297
  }
233
298
  }
299
+ function debounce(fn, wait) {
300
+ let timer = null;
301
+ return {
302
+ call(h) {
303
+ if (timer) clearTimeout(timer);
304
+ timer = setTimeout(() => {
305
+ fn(h);
306
+ timer = null;
307
+ }, wait);
308
+ },
309
+ cancel() {
310
+ if (timer) {
311
+ clearTimeout(timer);
312
+ timer = null;
313
+ }
314
+ }
315
+ };
316
+ }
234
317
  function sendTokenToWidget(config, state, iframe) {
235
318
  var _a;
236
319
  if (!state.accessToken) return;
@@ -240,7 +323,8 @@ function sendTokenToWidget(config, state, iframe) {
240
323
  type: "RETURNINGAI_WIDGET_TOKEN",
241
324
  value: {
242
325
  widgetId: config.widgetId,
243
- token: state.accessToken
326
+ token: state.accessToken,
327
+ ...config.customData !== void 0 && { customData: config.customData }
244
328
  }
245
329
  },
246
330
  config.widgetDomain
@@ -248,7 +332,11 @@ function sendTokenToWidget(config, state, iframe) {
248
332
  } catch {
249
333
  }
250
334
  }
251
- function setupMessageListener(config, state, _shadow, iframe, onRefreshScheduled, onLogout, hideLoader) {
335
+ function setupMessageListener(config, state, _shadow, iframe, onRefreshScheduled, onLogout, hideLoader, emit) {
336
+ const heightSetter = debounce((h) => {
337
+ iframe.style.height = `${h}px`;
338
+ emit == null ? void 0 : emit("rai-height-change", { height: h });
339
+ }, config.heightDebounce ?? 100);
252
340
  const handler = async (event) => {
253
341
  var _a;
254
342
  if (event.origin !== config.widgetDomain) return;
@@ -261,7 +349,11 @@ function setupMessageListener(config, state, _shadow, iframe, onRefreshScheduled
261
349
  (_a = iframe.contentWindow) == null ? void 0 : _a.postMessage(
262
350
  {
263
351
  type: "RETURNINGAI_WIDGET_TOKEN",
264
- value: { token, widgetId: config.widgetId }
352
+ value: {
353
+ token,
354
+ widgetId: config.widgetId,
355
+ ...config.customData !== void 0 && { customData: config.customData }
356
+ }
265
357
  },
266
358
  config.widgetDomain
267
359
  );
@@ -272,7 +364,7 @@ function setupMessageListener(config, state, _shadow, iframe, onRefreshScheduled
272
364
  case "WIDGET_HEIGHT_UPDATE": {
273
365
  const h = Number(payload == null ? void 0 : payload.height);
274
366
  if (Number.isFinite(h) && h > 0) {
275
- iframe.style.height = `${h}px`;
367
+ heightSetter.call(h);
276
368
  }
277
369
  break;
278
370
  }
@@ -281,6 +373,7 @@ function setupMessageListener(config, state, _shadow, iframe, onRefreshScheduled
281
373
  if (containerId !== void 0 && containerId !== config.container) break;
282
374
  iframe.classList.add("loaded");
283
375
  if (hideLoader) hideLoader();
376
+ emit == null ? void 0 : emit("rai-ready");
284
377
  break;
285
378
  }
286
379
  case "WIDGET_ERROR": {
@@ -294,9 +387,12 @@ function setupMessageListener(config, state, _shadow, iframe, onRefreshScheduled
294
387
  }
295
388
  };
296
389
  window.addEventListener("message", handler);
297
- return () => window.removeEventListener("message", handler);
390
+ return () => {
391
+ window.removeEventListener("message", handler);
392
+ heightSetter.cancel();
393
+ };
298
394
  }
299
- 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}.loader{position:relative;width:75px;height:100px}.loader__bar{position:absolute;bottom:0;width:10px;height:50%;background:var(--rai-accent, #000000);transform-origin:center bottom;box-shadow:1px 1px #0003}.loader__bar:nth-child(1){left:0;transform:scaleY(.2);animation:barUp1 4s infinite}.loader__bar:nth-child(2){left:15px;transform:scaleY(.4);animation:barUp2 4s infinite}.loader__bar:nth-child(3){left:30px;transform:scaleY(.6);animation:barUp3 4s infinite}.loader__bar:nth-child(4){left:45px;transform:scaleY(.8);animation:barUp4 4s infinite}.loader__bar:nth-child(5){left:60px;transform:scale(1);animation:barUp5 4s infinite}.loader__ball{position:absolute;bottom:10px;left:0;width:10px;height:10px;background:var(--rai-accent, #000000);border-radius:50%;animation:ball 4s infinite}@keyframes 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 barUp1{0%{transform:scaleY(.2)}40%{transform:scaleY(.2)}50%{transform:scale(1)}90%{transform:scale(1)}to{transform:scaleY(.2)}}@keyframes barUp2{0%{transform:scaleY(.4)}40%{transform:scaleY(.4)}50%{transform:scaleY(.8)}90%{transform:scaleY(.8)}to{transform:scaleY(.4)}}@keyframes barUp3{0%{transform:scaleY(.6)}to{transform:scaleY(.6)}}@keyframes barUp4{0%{transform:scaleY(.8)}40%{transform:scaleY(.8)}50%{transform:scaleY(.4)}90%{transform:scaleY(.4)}to{transform:scaleY(.8)}}@keyframes 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)}}";
395
+ 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}.loader{position:relative;width:75px;height:100px}.loader__bar{position:absolute;bottom:0;width:10px;height:50%;background:var(--rai-accent, #000000);transform-origin:center bottom;box-shadow:1px 1px #0003}.loader__bar:nth-child(1){left:0;transform:scaleY(.2);animation:barUp1 4s infinite}.loader__bar:nth-child(2){left:15px;transform:scaleY(.4);animation:barUp2 4s infinite}.loader__bar:nth-child(3){left:30px;transform:scaleY(.6);animation:barUp3 4s infinite}.loader__bar:nth-child(4){left:45px;transform:scaleY(.8);animation:barUp4 4s infinite}.loader__bar:nth-child(5){left:60px;transform:scale(1);animation:barUp5 4s infinite}.loader__ball{position:absolute;bottom:10px;left:0;width:10px;height:10px;background:var(--rai-accent, #000000);border-radius:50%;animation:ball 4s infinite}@keyframes 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 barUp1{0%{transform:scaleY(.2)}40%{transform:scaleY(.2)}50%{transform:scale(1)}90%{transform:scale(1)}to{transform:scaleY(.2)}}@keyframes barUp2{0%{transform:scaleY(.4)}40%{transform:scaleY(.4)}50%{transform:scaleY(.8)}90%{transform:scaleY(.8)}to{transform:scaleY(.4)}}@keyframes barUp3{0%{transform:scaleY(.6)}to{transform:scaleY(.6)}}@keyframes barUp4{0%{transform:scaleY(.8)}40%{transform:scaleY(.8)}50%{transform:scaleY(.4)}90%{transform:scaleY(.4)}to{transform:scaleY(.8)}}@keyframes 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-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}";
300
396
  const DEFINED_ATTRS = /* @__PURE__ */ new Set([
301
397
  "widget-id",
302
398
  "widget-type",
@@ -307,10 +403,23 @@ const DEFINED_ATTRS = /* @__PURE__ */ new Set([
307
403
  "api-url",
308
404
  "widget-url",
309
405
  "auto-refresh",
310
- "debug"
406
+ "debug",
407
+ // New attrs — excluded from userIdentifiers
408
+ "storage-prefix",
409
+ "max-retries",
410
+ "retry-delay",
411
+ "height-debounce",
412
+ "locale",
413
+ "custom-data",
414
+ "retry-label",
415
+ "auth-url"
311
416
  ]);
312
417
  function readConfig(el, existingId) {
313
418
  const get = (name) => el.getAttribute(name) ?? el.getAttribute(`data-${name}`) ?? "";
419
+ const num = (name, def) => {
420
+ const v = parseInt(get(name), 10);
421
+ return Number.isFinite(v) && v >= 0 ? v : def;
422
+ };
314
423
  const rawWidgetUrl = get("widget-url") || "https://widget.returningai.com";
315
424
  let widgetUrl = rawWidgetUrl;
316
425
  if (widgetUrl.endsWith("store-widget")) {
@@ -341,6 +450,15 @@ function readConfig(el, existingId) {
341
450
  userIdentifiers[name] = attr.value;
342
451
  }
343
452
  });
453
+ const customData = (() => {
454
+ const raw = get("custom-data");
455
+ if (!raw) return void 0;
456
+ try {
457
+ return JSON.parse(raw);
458
+ } catch {
459
+ return void 0;
460
+ }
461
+ })();
344
462
  return {
345
463
  widgetId,
346
464
  widgetType: get("widget-type") || "store",
@@ -353,8 +471,21 @@ function readConfig(el, existingId) {
353
471
  widgetDomain,
354
472
  autoRefresh: get("auto-refresh") !== "false",
355
473
  debug: get("debug") === "true",
356
- storagePrefix: "returning-ai-widget",
357
- userIdentifiers
474
+ storagePrefix: get("storage-prefix") || "returning-ai-widget",
475
+ // #7
476
+ userIdentifiers,
477
+ maxRetries: num("max-retries", 3),
478
+ // #2
479
+ retryDelay: num("retry-delay", 500),
480
+ // #2
481
+ heightDebounce: num("height-debounce", 100),
482
+ // #4
483
+ locale: get("locale") || void 0,
484
+ // #5
485
+ customData,
486
+ // #8
487
+ authUrl: get("auth-url") || void 0
488
+ // authenticated embed
358
489
  };
359
490
  }
360
491
  function createInitialState() {
@@ -374,6 +505,7 @@ function createInitialState() {
374
505
  };
375
506
  }
376
507
  class BaseWidget extends HTMLElement {
508
+ // #3
377
509
  constructor() {
378
510
  super();
379
511
  __publicField(this, "shadow");
@@ -381,7 +513,9 @@ class BaseWidget extends HTMLElement {
381
513
  __publicField(this, "state", createInitialState());
382
514
  __publicField(this, "loaderEl", null);
383
515
  __publicField(this, "errorEl", null);
516
+ __publicField(this, "msgEl", null);
384
517
  __publicField(this, "cleanupListener");
518
+ __publicField(this, "intersectionObserver");
385
519
  this.shadow = this.attachShadow({ mode: "closed" });
386
520
  }
387
521
  connectedCallback() {
@@ -398,12 +532,35 @@ class BaseWidget extends HTMLElement {
398
532
  this.style.setProperty("--rai-loader-bg", bgColor);
399
533
  this.style.setProperty("--rai-error-bg", bgColor);
400
534
  this.renderShell();
401
- this.init();
535
+ if (this.hasAttribute("eager")) {
536
+ this.init();
537
+ } else {
538
+ this.intersectionObserver = new IntersectionObserver((entries) => {
539
+ if (entries[0].isIntersecting) {
540
+ this.intersectionObserver.disconnect();
541
+ this.intersectionObserver = void 0;
542
+ this.init();
543
+ }
544
+ });
545
+ this.intersectionObserver.observe(this);
546
+ }
402
547
  }
403
548
  disconnectedCallback() {
549
+ var _a;
550
+ (_a = this.intersectionObserver) == null ? void 0 : _a.disconnect();
404
551
  if (this.cleanupListener) this.cleanupListener();
405
552
  clearState(this.state);
406
553
  }
554
+ // ── DOM event helper (#1) ─────────────────────────────────────────────
555
+ emit(type, detail) {
556
+ this.dispatchEvent(
557
+ new CustomEvent(type, {
558
+ bubbles: true,
559
+ composed: true,
560
+ detail: detail ?? {}
561
+ })
562
+ );
563
+ }
407
564
  // ── Rendering ─────────────────────────────────────────────────────────
408
565
  renderShell() {
409
566
  this.loaderEl = document.createElement("div");
@@ -412,7 +569,16 @@ class BaseWidget extends HTMLElement {
412
569
  this.shadow.appendChild(this.loaderEl);
413
570
  this.errorEl = document.createElement("div");
414
571
  this.errorEl.className = "rai-error";
415
- this.errorEl.textContent = "Authentication failed. Please try again later.";
572
+ this.msgEl = document.createElement("span");
573
+ this.msgEl.className = "rai-error-msg";
574
+ this.msgEl.textContent = "Authentication failed. Please try again later.";
575
+ this.errorEl.appendChild(this.msgEl);
576
+ const retryLabel = this.getAttribute("retry-label") || this.getAttribute("data-retry-label") || "Retry";
577
+ const btn = document.createElement("button");
578
+ btn.className = "rai-retry-btn";
579
+ btn.textContent = retryLabel;
580
+ btn.addEventListener("click", () => this.reload());
581
+ this.errorEl.appendChild(btn);
416
582
  this.shadow.appendChild(this.errorEl);
417
583
  }
418
584
  createDefaultLoader() {
@@ -438,14 +604,15 @@ class BaseWidget extends HTMLElement {
438
604
  this.loaderEl = null;
439
605
  }
440
606
  showError() {
441
- var _a;
607
+ var _a, _b;
442
608
  this.hideLoader();
443
609
  if (this.errorEl) {
444
- if ((_a = this.state.errorSettings) == null ? void 0 : _a.errorMessage) {
445
- this.errorEl.textContent = this.state.errorSettings.errorMessage;
610
+ if (((_a = this.state.errorSettings) == null ? void 0 : _a.errorMessage) && this.msgEl) {
611
+ this.msgEl.textContent = this.state.errorSettings.errorMessage;
446
612
  }
447
613
  this.errorEl.classList.add("visible");
448
614
  }
615
+ this.emit("rai-error", { message: ((_b = this.msgEl) == null ? void 0 : _b.textContent) ?? "Authentication failed" });
449
616
  }
450
617
  // ── Initialization ─────────────────────────────────────────────────────
451
618
  async init() {
@@ -454,6 +621,20 @@ class BaseWidget extends HTMLElement {
454
621
  return;
455
622
  }
456
623
  await fetchErrorSettings(this.config, this.state);
624
+ if (this.config.authUrl) {
625
+ const authed2 = await authenticateViaProxy(
626
+ this.config,
627
+ this.state,
628
+ () => this.scheduleRefresh()
629
+ );
630
+ if (authed2) {
631
+ this.emit("rai-authenticated");
632
+ this.createIframe();
633
+ } else {
634
+ this.showError();
635
+ }
636
+ return;
637
+ }
457
638
  const hasStored = loadFromStorage(this.config, this.state);
458
639
  if (hasStored) {
459
640
  const refreshed = await refreshAccessToken(
@@ -466,6 +647,7 @@ class BaseWidget extends HTMLElement {
466
647
  return;
467
648
  }
468
649
  this.state.isAuthenticated = true;
650
+ this.emit("rai-authenticated");
469
651
  this.createIframe();
470
652
  return;
471
653
  }
@@ -475,6 +657,7 @@ class BaseWidget extends HTMLElement {
475
657
  () => this.scheduleRefresh()
476
658
  );
477
659
  if (authed) {
660
+ this.emit("rai-authenticated");
478
661
  this.createIframe();
479
662
  } else {
480
663
  this.showError();
@@ -507,7 +690,9 @@ class BaseWidget extends HTMLElement {
507
690
  iframe,
508
691
  () => this.scheduleRefresh(),
509
692
  () => this.logoutAndClear(),
510
- () => this.hideLoader()
693
+ () => this.hideLoader(),
694
+ (type, detail) => this.emit(type, detail)
695
+ // #1
511
696
  );
512
697
  }
513
698
  // ── Token scheduling ──────────────────────────────────────────────────
@@ -515,26 +700,21 @@ class BaseWidget extends HTMLElement {
515
700
  if (this.state.refreshTimer) clearTimeout(this.state.refreshTimer);
516
701
  if (!this.state.accessTokenExpiry) return;
517
702
  const delay = this.state.accessTokenExpiry - Date.now() - 6e4;
703
+ const pushToken = () => {
704
+ if (this.state.iframe) sendTokenToWidget(this.config, this.state, this.state.iframe);
705
+ };
706
+ const doRefresh = async () => {
707
+ if (this.config.authUrl) {
708
+ const ok = await authenticateViaProxy(this.config, this.state, () => this.scheduleRefresh());
709
+ if (ok) pushToken();
710
+ } else {
711
+ await refreshAccessToken(this.config, this.state, () => this.scheduleRefresh(), pushToken);
712
+ }
713
+ };
518
714
  if (delay > 0) {
519
- this.state.refreshTimer = setTimeout(async () => {
520
- await refreshAccessToken(
521
- this.config,
522
- this.state,
523
- () => this.scheduleRefresh(),
524
- () => {
525
- if (this.state.iframe) sendTokenToWidget(this.config, this.state, this.state.iframe);
526
- }
527
- );
528
- }, delay);
715
+ this.state.refreshTimer = setTimeout(doRefresh, delay);
529
716
  } else {
530
- refreshAccessToken(
531
- this.config,
532
- this.state,
533
- () => this.scheduleRefresh(),
534
- () => {
535
- if (this.state.iframe) sendTokenToWidget(this.config, this.state, this.state.iframe);
536
- }
537
- );
717
+ doRefresh();
538
718
  }
539
719
  }
540
720
  schedulePeriodicSync(iframe) {
@@ -547,6 +727,7 @@ class BaseWidget extends HTMLElement {
547
727
  async logoutAndClear() {
548
728
  await logout(this.config, this.state);
549
729
  this.shadow.querySelectorAll("iframe").forEach((el) => el.remove());
730
+ this.emit("rai-logout");
550
731
  }
551
732
  // ── Public API ────────────────────────────────────────────────────────
552
733
  async reload() {
@@ -578,42 +759,63 @@ class StoreWidget extends BaseWidget {
578
759
  url.searchParams.set("containerId", config.container);
579
760
  url.searchParams.set("connectType", "simple");
580
761
  url.searchParams.set("mode", "private");
762
+ if (config.locale) url.searchParams.set("locale", config.locale);
581
763
  return url.toString();
582
764
  }
583
765
  }
584
766
  class ChannelWidget extends BaseWidget {
585
767
  buildWidgetUrl(config) {
586
- if (config.widgetUrl.endsWith("channel-widget")) {
587
- return `${config.widgetUrl}/${config.widgetId}`;
768
+ const base = config.widgetUrl.endsWith("channel-widget") ? `${config.widgetUrl}/${config.widgetId}` : config.widgetUrl;
769
+ if (!config.locale) return base;
770
+ try {
771
+ const u = new URL(base);
772
+ u.searchParams.set("locale", config.locale);
773
+ return u.toString();
774
+ } catch {
775
+ return `${base}?locale=${encodeURIComponent(config.locale)}`;
588
776
  }
589
- return config.widgetUrl;
590
777
  }
591
778
  }
592
779
  class MilestoneWidget extends BaseWidget {
593
780
  buildWidgetUrl(config) {
594
- if (config.widgetUrl.endsWith("milestone-widget")) {
595
- return `${config.widgetUrl}/${config.widgetId}`;
781
+ const base = config.widgetUrl.endsWith("milestone-widget") ? `${config.widgetUrl}/${config.widgetId}` : config.widgetUrl;
782
+ if (!config.locale) return base;
783
+ try {
784
+ const u = new URL(base);
785
+ u.searchParams.set("locale", config.locale);
786
+ return u.toString();
787
+ } catch {
788
+ return `${base}?locale=${encodeURIComponent(config.locale)}`;
596
789
  }
597
- return config.widgetUrl;
598
790
  }
599
791
  }
600
792
  class SocialWidget extends BaseWidget {
601
793
  buildWidgetUrl(config) {
602
- if (config.widgetUrl.endsWith("social-widget")) {
603
- return `${config.widgetUrl}/${config.widgetId}`;
794
+ const base = config.widgetUrl.endsWith("social-widget") ? `${config.widgetUrl}/${config.widgetId}` : config.widgetUrl;
795
+ if (!config.locale) return base;
796
+ try {
797
+ const u = new URL(base);
798
+ u.searchParams.set("locale", config.locale);
799
+ return u.toString();
800
+ } catch {
801
+ return `${base}?locale=${encodeURIComponent(config.locale)}`;
604
802
  }
605
- return config.widgetUrl;
606
803
  }
607
804
  }
608
805
  class CurrencyWidget extends BaseWidget {
609
806
  buildWidgetUrl(config) {
610
- if (config.widgetUrl.endsWith("currency-overview-widget")) {
611
- return `${config.widgetUrl}/${config.widgetId}`;
807
+ const base = config.widgetUrl.endsWith("currency-overview-widget") ? `${config.widgetUrl}/${config.widgetId}` : config.widgetUrl;
808
+ if (!config.locale) return base;
809
+ try {
810
+ const u = new URL(base);
811
+ u.searchParams.set("locale", config.locale);
812
+ return u.toString();
813
+ } catch {
814
+ return `${base}?locale=${encodeURIComponent(config.locale)}`;
612
815
  }
613
- return config.widgetUrl;
614
816
  }
615
817
  }
616
- console.log(`[rai-widget] v${"1.0.2"}`);
818
+ console.log(`[rai-widget] v${"1.0.3"}`);
617
819
  const WIDGET_REGISTRY = [
618
820
  ["rai-widget", StoreWidget],
619
821
  ["rai-channel-widget", ChannelWidget],
@@ -669,7 +871,7 @@ function exposePublicApi() {
669
871
  })();
670
872
  const widget = container == null ? void 0 : container.querySelector(ALL_WIDGET_SELECTOR);
671
873
  window.ReturningAIWidget = {
672
- version: "1.0.2",
874
+ version: "1.0.3",
673
875
  reload: () => (widget == null ? void 0 : widget.reload()) ?? Promise.resolve(),
674
876
  logout: () => (widget == null ? void 0 : widget.logoutPublic()) ?? Promise.resolve(),
675
877
  isAuthenticated: () => (widget == null ? void 0 : widget.isAuthenticated()) ?? false,
@@ -6,11 +6,14 @@ export declare abstract class BaseWidget extends HTMLElement {
6
6
  private state;
7
7
  private loaderEl;
8
8
  private errorEl;
9
+ private msgEl;
9
10
  private cleanupListener?;
11
+ private intersectionObserver?;
10
12
  constructor();
11
13
  protected abstract buildWidgetUrl(config: WidgetConfig): string;
12
14
  connectedCallback(): void;
13
15
  disconnectedCallback(): void;
16
+ private emit;
14
17
  private renderShell;
15
18
  private createDefaultLoader;
16
19
  private hideLoader;
@@ -1,5 +1,6 @@
1
1
  import type { WidgetConfig, WidgetState } from '../types';
2
2
  export declare function authenticateServerless(config: WidgetConfig, state: WidgetState, onRefreshScheduled?: () => void): Promise<boolean>;
3
+ export declare function authenticateViaProxy(config: WidgetConfig, state: WidgetState, onRefreshScheduled?: () => void): Promise<boolean>;
3
4
  export declare function refreshAccessToken(config: WidgetConfig, state: WidgetState, onRefreshScheduled?: () => void, sendToken?: () => void): Promise<boolean>;
4
5
  export declare function getValidToken(config: WidgetConfig, state: WidgetState, onRefreshScheduled?: () => void): Promise<string>;
5
6
  export declare function logout(config: WidgetConfig, state: WidgetState): Promise<void>;
@@ -1,3 +1,3 @@
1
1
  import type { WidgetConfig, WidgetState } from '../types';
2
2
  export declare function sendTokenToWidget(config: WidgetConfig, state: WidgetState, iframe: HTMLIFrameElement): void;
3
- export declare function setupMessageListener(config: WidgetConfig, state: WidgetState, _shadow: ShadowRoot, iframe: HTMLIFrameElement, onRefreshScheduled?: () => void, onLogout?: () => Promise<void>, hideLoader?: () => void): () => void;
3
+ export declare function setupMessageListener(config: WidgetConfig, state: WidgetState, _shadow: ShadowRoot, iframe: HTMLIFrameElement, onRefreshScheduled?: () => void, onLogout?: () => Promise<void>, hideLoader?: () => void, emit?: (type: string, detail?: Record<string, unknown>) => void): () => void;
@@ -12,6 +12,12 @@ export interface WidgetConfig {
12
12
  debug: boolean;
13
13
  storagePrefix: string;
14
14
  userIdentifiers: Record<string, string>;
15
+ maxRetries: number;
16
+ retryDelay: number;
17
+ heightDebounce: number;
18
+ locale?: string;
19
+ customData?: Record<string, unknown>;
20
+ authUrl?: string;
15
21
  }
16
22
  export interface WidgetState {
17
23
  accessToken: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@returningai/widget-sdk",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Shadow DOM isolated widget SDK for ReturningAI",
5
5
  "main": "dist/rai-widget.iife.js",
6
6
  "module": "dist/rai-widget.js",