@returningai/widget-sdk 1.0.1 → 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 +57 -27
- package/dist/rai-widget.iife.js +1 -1
- package/dist/rai-widget.js +273 -71
- package/dist/types/BaseWidget.d.ts +3 -0
- package/dist/types/core/auth.d.ts +1 -0
- package/dist/types/core/postmessage.d.ts +1 -1
- package/dist/types/types.d.ts +6 -0
- package/package.json +1 -1
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
|
|
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 (
|
|
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;
|
|
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
|
-
├─
|
|
57
|
-
│ Yes ──►
|
|
58
|
-
│ No ──►
|
|
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()
|
|
63
|
+
└─ sendTokenToWidget() ← postMessage with access token
|
|
62
64
|
schedulePeriodicSync() ← resend every 2 min
|
|
63
65
|
|
|
64
66
|
Token nearing expiry (1 min early)
|
|
65
|
-
|
|
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
|
---
|
|
@@ -165,21 +169,21 @@ Zero changes required to existing embed HTML. Point `src` at the SDK and keep al
|
|
|
165
169
|
```html
|
|
166
170
|
<!-- 1. Container div (unchanged from current embed) -->
|
|
167
171
|
<div
|
|
168
|
-
id="returning-ai-widget-
|
|
172
|
+
id="returning-ai-widget-YOUR_WIDGET_ID"
|
|
169
173
|
style="width: 100%; height: 600px;"
|
|
170
174
|
></div>
|
|
171
175
|
|
|
172
176
|
<!-- 2. SDK script tag -->
|
|
173
177
|
<script
|
|
174
|
-
src="https://
|
|
175
|
-
data-widget-id="
|
|
178
|
+
src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
|
|
179
|
+
data-widget-id="YOUR_WIDGET_ID"
|
|
176
180
|
data-widget-type="store"
|
|
177
|
-
data-container="returning-ai-widget-
|
|
181
|
+
data-container="returning-ai-widget-YOUR_WIDGET_ID"
|
|
178
182
|
data-theme="dark"
|
|
179
183
|
data-width="100%"
|
|
180
184
|
data-height="600px"
|
|
181
|
-
data-api-url="
|
|
182
|
-
data-widget-url="
|
|
185
|
+
data-api-url="YOUR_API_URL"
|
|
186
|
+
data-widget-url="YOUR_WIDGET_URL"
|
|
183
187
|
data-auto-refresh="true"
|
|
184
188
|
data-email="user@example.com"
|
|
185
189
|
></script>
|
|
@@ -200,27 +204,27 @@ For customers using a JavaScript framework (React, Vue, Angular), import the SDK
|
|
|
200
204
|
| `<rai-currency-widget>` | `currency-view` |
|
|
201
205
|
|
|
202
206
|
```html
|
|
203
|
-
<script src="https://
|
|
207
|
+
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"></script>
|
|
204
208
|
|
|
205
209
|
<!-- Store widget -->
|
|
206
210
|
<rai-widget
|
|
207
|
-
widget-id="
|
|
211
|
+
widget-id="YOUR_WIDGET_ID"
|
|
208
212
|
widget-type="store"
|
|
209
213
|
theme="dark"
|
|
210
214
|
width="100%"
|
|
211
215
|
height="600px"
|
|
212
|
-
api-url="
|
|
213
|
-
widget-url="
|
|
216
|
+
api-url="YOUR_API_URL"
|
|
217
|
+
widget-url="YOUR_WIDGET_URL"
|
|
214
218
|
data-email="user@example.com"
|
|
215
219
|
></rai-widget>
|
|
216
220
|
|
|
217
221
|
<!-- Channel widget -->
|
|
218
222
|
<rai-channel-widget
|
|
219
|
-
widget-id="
|
|
223
|
+
widget-id="YOUR_WIDGET_ID"
|
|
220
224
|
widget-type="channel"
|
|
221
225
|
theme="dark"
|
|
222
|
-
api-url="
|
|
223
|
-
widget-url="
|
|
226
|
+
api-url="YOUR_API_URL"
|
|
227
|
+
widget-url="YOUR_WIDGET_URL"
|
|
224
228
|
data-email="user@example.com"
|
|
225
229
|
></rai-channel-widget>
|
|
226
230
|
```
|
|
@@ -234,7 +238,7 @@ import '@returningai/widget-sdk'
|
|
|
234
238
|
export function WidgetEmbed() {
|
|
235
239
|
return (
|
|
236
240
|
<rai-widget
|
|
237
|
-
widget-id="
|
|
241
|
+
widget-id="YOUR_WIDGET_ID"
|
|
238
242
|
widget-type="store"
|
|
239
243
|
theme="dark"
|
|
240
244
|
height="600px"
|
|
@@ -256,13 +260,39 @@ All attributes can be provided with or without the `data-` prefix.
|
|
|
256
260
|
| `container` | No | `returning-ai-widget-{id}` | ID of the container element |
|
|
257
261
|
| `width` | No | `100%` | CSS width of the iframe |
|
|
258
262
|
| `height` | No | `600px` | Initial CSS height (auto-resized by `WIDGET_HEIGHT_UPDATE`) |
|
|
259
|
-
| `api-url` | No |
|
|
260
|
-
| `widget-url` | No |
|
|
263
|
+
| `api-url` | No | — | Auth API base URL — from Community Settings |
|
|
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
|
-
| `
|
|
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:
|
package/dist/rai-widget.iife.js
CHANGED
|
@@ -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.1");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.1",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}({});
|
package/dist/rai-widget.js
CHANGED
|
@@ -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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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: {
|
|
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
|
-
|
|
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 () =>
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
587
|
-
|
|
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
|
-
|
|
595
|
-
|
|
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
|
-
|
|
603
|
-
|
|
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
|
-
|
|
611
|
-
|
|
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.
|
|
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.
|
|
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;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -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;
|