@returningai/widget-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,393 @@
1
+ # ReturningAI Widget SDK
2
+
3
+ A Shadow DOM-isolated Web Component SDK for embedding ReturningAI widgets on any customer website. Drop-in replacement for the legacy `widget-loader.js` — identical embed attributes, zero CSS conflicts.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ - [Architecture](#architecture)
10
+ - [Shadow DOM Implementation](#shadow-dom-implementation)
11
+ - [Dependencies](#dependencies)
12
+ - [Usage](#usage)
13
+ - [Script Tag (Legacy Embed)](#script-tag-legacy-embed)
14
+ - [Web Component](#web-component)
15
+ - [Configuration Attributes](#configuration-attributes)
16
+ - [Public API](#public-api)
17
+ - [Build Process](#build-process)
18
+ - [Project Structure](#project-structure)
19
+
20
+ ---
21
+
22
+ ## Architecture
23
+
24
+ ```
25
+ Customer page DOM
26
+ └── <div id="returning-ai-widget-{id}"> ← customer's container (unchanged)
27
+ └── <rai-widget> ← custom element (auto-mounted)
28
+ └── Shadow Root [closed]
29
+ ├── <style> ← all CSS scoped here, never leaks
30
+ ├── <div class="rai-loader"> ← animated loader (hidden after ready)
31
+ ├── <div class="rai-error"> ← error state (hidden until error)
32
+ └── <iframe src="widget-url"> ← actual widget content
33
+ ```
34
+
35
+ ### Module Responsibilities
36
+
37
+ | Module | File | Purpose |
38
+ |--------|------|---------|
39
+ | Base Web Component | `src/BaseWidget.ts` | Abstract `HTMLElement` subclass; owns the Shadow Root, auth lifecycle, iframe creation, public API |
40
+ | Store Widget | `src/StoreWidget.ts` | Extends `BaseWidget`; implements `buildWidgetUrl()` for the store micro-frontend |
41
+ | Channel Widget | `src/ChannelWidget.ts` | Extends `BaseWidget`; appends `widgetId` to base URL or passes full URL through |
42
+ | Milestone Widget | `src/MilestoneWidget.ts` | Extends `BaseWidget`; same URL strategy as `ChannelWidget` |
43
+ | Social Widget | `src/SocialWidget.ts` | Extends `BaseWidget`; same URL strategy as `ChannelWidget` |
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 |
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 |
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
+
51
+ ### Auth Flow
52
+
53
+ ```
54
+ Page load
55
+
56
+ ├─ loadFromStorage() ── refresh token in localStorage?
57
+ │ Yes ──► refreshAccessToken() ──► createIframe()
58
+ │ No ──► authenticateServerless() ──► createIframe()
59
+
60
+ └─ iframe.onload
61
+ └─ sendTokenToWidget() ← postMessage with access token
62
+ schedulePeriodicSync() ← resend every 2 min
63
+
64
+ Token nearing expiry (1 min early)
65
+ └─ refreshAccessToken()
66
+ ├─ 401/403 ──► re-authenticate serverless (token family rotated)
67
+ └─ success ──► sendTokenToWidget()
68
+ ```
69
+
70
+ **Token storage strategy**
71
+
72
+ | Token | Location | TTL |
73
+ |-------|----------|-----|
74
+ | Access token | Memory only (`WidgetState.accessToken`) | ~5 min |
75
+ | Refresh token | `localStorage` | 7 days |
76
+
77
+ The access token is never written to `localStorage`. On every page load the refresh token is exchanged for a fresh access token before the iframe mounts.
78
+
79
+ ### postMessage Protocol
80
+
81
+ Messages the SDK sends **to** the widget iframe:
82
+
83
+ | Type | Payload | When |
84
+ |------|---------|------|
85
+ | `RETURNINGAI_WIDGET_TOKEN` | `{ widgetId, token }` | After iframe load, on refresh, every 2 min |
86
+
87
+ Messages the SDK **receives** from the widget iframe:
88
+
89
+ | Type | Action |
90
+ |------|--------|
91
+ | `WIDGET_READY` | Hides loader, fades in iframe |
92
+ | `WIDGET_HEIGHT_UPDATE` | Resizes iframe to `payload.height` |
93
+ | `WIDGET_ERROR` | Hides loader |
94
+ | `WIDGET_LOGOUT` | Calls logout endpoint, removes iframe |
95
+ | `RETURNINGAI_WIDGET_REQUEST_TOKEN` | Responds immediately with a fresh token |
96
+
97
+ All messages are origin-validated against `config.widgetDomain`.
98
+
99
+ ---
100
+
101
+ ## Shadow DOM Implementation
102
+
103
+ The SDK uses a **closed** Shadow Root (`mode: 'closed'`), which means:
104
+
105
+ - Customer page CSS cannot reach any element inside the widget
106
+ - The widget's loader and error styles are fully encapsulated
107
+ - No class name collisions with customer frameworks (Tailwind, Bootstrap, etc.)
108
+
109
+ ### How it is attached
110
+
111
+ ```typescript
112
+ // StoreWidget.ts — constructor
113
+ this.shadow = this.attachShadow({ mode: 'closed' })
114
+
115
+ // connectedCallback — CSS injected as a <style> tag inside the shadow root
116
+ const style = document.createElement('style')
117
+ style.textContent = widgetCSS // inlined at build time via Vite ?inline import
118
+ this.shadow.appendChild(style)
119
+ ```
120
+
121
+ CSS custom properties (`--rai-accent`, `--rai-loader-bg`, `--rai-text4`) are set on the host element and cascade into the Shadow Root, allowing theme-aware colours without breaking encapsulation.
122
+
123
+ ### Theme variables
124
+
125
+ | Variable | Light | Dark |
126
+ |----------|-------|------|
127
+ | `--rai-accent` | `#000000` | `#ffffff` |
128
+ | `--rai-text4` | `#6b7280` | `#9ca3af` |
129
+ | `--rai-loader-bg` | `#ffffff` | `#1a1a1a` |
130
+ | `--rai-error-bg` | `#ffffff` | `#1a1a1a` |
131
+
132
+ ---
133
+
134
+ ## Dependencies
135
+
136
+ ### Runtime
137
+
138
+ None. The SDK ships as a single self-contained IIFE with no external runtime dependencies.
139
+
140
+ ### Dev / Build
141
+
142
+ | Package | Version | Purpose |
143
+ |---------|---------|---------|
144
+ | `vite` | ^5.1 | Bundles TypeScript + inlines CSS → single IIFE |
145
+ | `typescript` | ^5.3 | Type checking and compilation |
146
+ | `terser` | ^5.46 | Minification for production builds |
147
+
148
+ ### Browser requirements
149
+
150
+ | Feature | Minimum |
151
+ |---------|---------|
152
+ | Custom Elements v1 | Chrome 67, Firefox 63, Safari 13 |
153
+ | Shadow DOM v1 | Chrome 53, Firefox 63, Safari 10 |
154
+ | `crypto.randomUUID()` | Chrome 92, Firefox 95, Safari 15.4 |
155
+ | `localStorage` | All modern browsers |
156
+
157
+ ---
158
+
159
+ ## Usage
160
+
161
+ ### Script Tag (Legacy Embed)
162
+
163
+ Zero changes required to existing embed HTML. Point `src` at the SDK and keep all `data-*` attributes as-is:
164
+
165
+ ```html
166
+ <!-- 1. Container div (unchanged from current embed) -->
167
+ <div
168
+ id="returning-ai-widget-6502c9e514a3e564c5c09c0a"
169
+ style="width: 100%; height: 600px;"
170
+ ></div>
171
+
172
+ <!-- 2. SDK script tag -->
173
+ <script
174
+ src="https://cdn.returningai.com/widget-sdk/v1.0.0/rai-widget.iife.js"
175
+ data-widget-id="6502c9e514a3e564c5c09c0a"
176
+ data-widget-type="store"
177
+ data-container="returning-ai-widget-6502c9e514a3e564c5c09c0a"
178
+ data-theme="dark"
179
+ data-width="100%"
180
+ data-height="600px"
181
+ data-api-url="https://sgtr-eks-widgets.genesiv.org"
182
+ data-widget-url="https://widget.returningai.com/widgets/store/6502c9e514a3e564c5c09c0a/open-widget"
183
+ data-auto-refresh="true"
184
+ data-email="user@example.com"
185
+ ></script>
186
+ ```
187
+
188
+ The SDK scans for the loader `<script>` tag, reads its `data-*` attributes, creates a `<rai-widget>` element, and mounts it inside the container div.
189
+
190
+ ### Web Component
191
+
192
+ For customers using a JavaScript framework (React, Vue, Angular), import the SDK once and use the custom element directly. Each widget type has its own tag:
193
+
194
+ | Tag | Widget type |
195
+ |-----|-------------|
196
+ | `<rai-widget>` | `store` |
197
+ | `<rai-channel-widget>` | `channel` |
198
+ | `<rai-milestone-widget>` | `milestone` |
199
+ | `<rai-social-widget>` | `social` |
200
+ | `<rai-currency-widget>` | `currency-view` |
201
+
202
+ ```html
203
+ <script src="https://cdn.returningai.com/widget-sdk/v1.0.0/rai-widget.iife.js"></script>
204
+
205
+ <!-- Store widget -->
206
+ <rai-widget
207
+ widget-id="6502c9e514a3e564c5c09c0a"
208
+ widget-type="store"
209
+ theme="dark"
210
+ width="100%"
211
+ height="600px"
212
+ api-url="https://sgtr-eks-widgets.genesiv.org"
213
+ widget-url="https://widget.returningai.com/widgets/store/6502c9e514a3e564c5c09c0a/open-widget"
214
+ data-email="user@example.com"
215
+ ></rai-widget>
216
+
217
+ <!-- Channel widget -->
218
+ <rai-channel-widget
219
+ widget-id="NjdmNmExNDU4NDQ5NDRmZjYwYWEzYzEz"
220
+ widget-type="channel"
221
+ theme="dark"
222
+ api-url="https://sgtr-eks-widgets.genesiv.org"
223
+ widget-url="https://sgtr-eks-widgets.genesiv.org/channel-widget"
224
+ data-email="user@example.com"
225
+ ></rai-channel-widget>
226
+ ```
227
+
228
+ In React (TypeScript):
229
+
230
+ ```tsx
231
+ // JSX types are included in the package — no manual declarations needed
232
+ import '@returningai/widget-sdk'
233
+
234
+ export function WidgetEmbed() {
235
+ return (
236
+ <rai-widget
237
+ widget-id="6502c9e514a3e564c5c09c0a"
238
+ widget-type="store"
239
+ theme="dark"
240
+ height="600px"
241
+ data-email={currentUser.email}
242
+ />
243
+ )
244
+ }
245
+ ```
246
+
247
+ ### Configuration Attributes
248
+
249
+ All attributes can be provided with or without the `data-` prefix.
250
+
251
+ | Attribute | Required | Default | Description |
252
+ |-----------|----------|---------|-------------|
253
+ | `widget-id` | Yes | — | MongoDB ObjectId of the widget |
254
+ | `widget-type` | No | `store` | `store`, `channel`, `milestone`, `social`, `currency-view` |
255
+ | `theme` | No | `light` | `light` or `dark` |
256
+ | `container` | No | `returning-ai-widget-{id}` | ID of the container element |
257
+ | `width` | No | `100%` | CSS width of the iframe |
258
+ | `height` | No | `600px` | Initial CSS height (auto-resized by `WIDGET_HEIGHT_UPDATE`) |
259
+ | `api-url` | No | `https://sgtr-eks-widgets.genesiv.org` | Auth API base URL |
260
+ | `widget-url` | No | `https://widget.returningai.com` | URL served inside the iframe |
261
+ | `auto-refresh` | No | `true` | Automatically refresh access token before expiry |
262
+ | `debug` | No | `false` | Enable verbose console logging |
263
+ | `data-email` | No | — | User identifier passed to auth |
264
+ | `data-*` | No | — | Any additional `data-*` attributes are forwarded as `userIdentifiers` to the auth API |
265
+
266
+ ### Public API
267
+
268
+ After the SDK loads, `window.ReturningAIWidget` is available:
269
+
270
+ ```javascript
271
+ // Check the loaded version
272
+ window.ReturningAIWidget.version // e.g. "1.0.0"
273
+
274
+ // Reload the widget (re-runs auth flow)
275
+ await window.ReturningAIWidget.reload()
276
+
277
+ // Log out and remove the iframe
278
+ await window.ReturningAIWidget.logout()
279
+
280
+ // Check authentication state
281
+ window.ReturningAIWidget.isAuthenticated() // boolean
282
+
283
+ // Inspect token metadata (no token values exposed)
284
+ window.ReturningAIWidget.getTokenInfo()
285
+ // {
286
+ // hasAccessToken: true,
287
+ // hasRefreshToken: true,
288
+ // accessTokenExpiry: Date,
289
+ // refreshTokenExpiry: Date,
290
+ // isAccessTokenValid: true,
291
+ // isRefreshTokenValid: true
292
+ // }
293
+ ```
294
+
295
+ ---
296
+
297
+ ## Build Process
298
+
299
+ ### Prerequisites
300
+
301
+ ```bash
302
+ node >= 18
303
+ npm >= 9
304
+ ```
305
+
306
+ ### Install
307
+
308
+ ```bash
309
+ cd rai-widget-sdks
310
+ npm install
311
+ ```
312
+
313
+ ### Development build (unminified, fast)
314
+
315
+ ```bash
316
+ npm run build
317
+ # → dist/rai-widget.iife.js (~17 kB, IIFE for CDN/script-tag)
318
+ # → dist/rai-widget.js (~17 kB, ESM for bundlers/npm)
319
+ ```
320
+
321
+ ### Production build (minified with Terser)
322
+
323
+ ```bash
324
+ npm run build:min
325
+ # → dist/rai-widget.iife.js (~6 kB gzip, IIFE for CDN/script-tag)
326
+ # → dist/rai-widget.js (~6 kB gzip, ESM for bundlers/npm)
327
+ ```
328
+
329
+ ### Local dev server
330
+
331
+ ```bash
332
+ npm run dev
333
+ # Opens Vite dev server — open test/index.html via the dev server URL
334
+ ```
335
+
336
+ ### How the build works
337
+
338
+ 1. **Entry**: `src/index.ts` — imports `StoreWidget`, registers the custom element, runs the bootstrap
339
+ 2. **CSS inlining**: `src/styles/widget.css` is imported with Vite's `?inline` suffix, which converts it to a JavaScript string at build time — no separate CSS file is emitted
340
+ 3. **Output formats**: `iife` (Immediately Invoked Function Expression) for CDN/script-tag embeds → `dist/rai-widget.iife.js`; `es` (ES module) for bundlers and npm consumers → `dist/rai-widget.js`
341
+ 4. **Version injection**: `vite.config.ts` reads `version` from `package.json` and replaces the `__WIDGET_VERSION__` placeholder at build time
342
+
343
+ ```
344
+ src/index.ts
345
+ ├── src/StoreWidget.ts
346
+ │ └── src/BaseWidget.ts ← shared auth/iframe/Shadow DOM logic
347
+ │ ├── src/types.ts
348
+ │ ├── src/core/auth.ts
349
+ │ │ └── src/core/storage.ts
350
+ │ ├── src/core/storage.ts
351
+ │ ├── src/core/postmessage.ts
352
+ │ └── src/styles/widget.css [inlined as string]
353
+ ├── src/ChannelWidget.ts └── src/BaseWidget.ts
354
+ ├── src/MilestoneWidget.ts └── src/BaseWidget.ts
355
+ ├── src/SocialWidget.ts └── src/BaseWidget.ts
356
+ └── src/CurrencyWidget.ts └── src/BaseWidget.ts
357
+ ```
358
+
359
+ To release a new version, bump `version` in `package.json`, rebuild, and upload `dist/rai-widget.iife.js` to the CDN under the new version path. Run `npm publish` to push the ESM build to the npm registry (`prepublishOnly` handles the rebuild automatically).
360
+
361
+ ---
362
+
363
+ ## Project Structure
364
+
365
+ ```
366
+ rai-widget-sdks/
367
+ ├── src/
368
+ │ ├── types.ts # WidgetConfig, WidgetState, TokenData interfaces
369
+ │ ├── BaseWidget.ts # Abstract HTMLElement subclass — Shadow Root, auth, iframe
370
+ │ ├── StoreWidget.ts # Extends BaseWidget; store micro-frontend URL builder
371
+ │ ├── ChannelWidget.ts # Extends BaseWidget; channel SSR URL builder
372
+ │ ├── MilestoneWidget.ts # Extends BaseWidget; milestone SSR URL builder
373
+ │ ├── SocialWidget.ts # Extends BaseWidget; social SSR URL builder
374
+ │ ├── CurrencyWidget.ts # Extends BaseWidget; currency-view SSR URL builder
375
+ │ ├── index.ts # Registers all 5 custom elements + IIFE bootstrap
376
+ │ ├── jsx.d.ts # React JSX IntrinsicElements + Vue GlobalComponents type shims
377
+ │ ├── vite-env.d.ts # Type declarations for ?inline imports + __WIDGET_VERSION__
378
+ │ ├── core/
379
+ │ │ ├── auth.ts # Serverless auth, refresh, logout, error settings
380
+ │ │ ├── storage.ts # localStorage helpers (refresh token persistence)
381
+ │ │ └── postmessage.ts # iframe token delivery + message listener
382
+ │ └── styles/
383
+ │ └── widget.css # All loader/error/iframe CSS (inlined into Shadow DOM)
384
+ ├── test/
385
+ │ └── index.html # Local test page with config panel and verification checklist
386
+ ├── dist/ # Built output (gitignored)
387
+ │ ├── rai-widget.iife.js # IIFE bundle for CDN / <script src> embeds
388
+ │ ├── rai-widget.js # ESM bundle for bundlers / npm consumers
389
+ │ └── types/ # TypeScript declarations (generated by tsc)
390
+ ├── package.json
391
+ ├── tsconfig.json
392
+ └── vite.config.ts
393
+ ```
@@ -0,0 +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||"",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.frameBorder="0",e.scrolling="no",e.style.cssText=`width: ${this.config.width}; height: ${this.config.height}; border: none; 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.0");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.0",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}({});
@@ -0,0 +1,683 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ function storageKey(config) {
5
+ return `${config.storagePrefix}-${config.widgetId}-auth`;
6
+ }
7
+ function errorSettingsKey(config) {
8
+ return `${config.storagePrefix}-${config.widgetId}-error-settings`;
9
+ }
10
+ function saveToStorage(config, state) {
11
+ try {
12
+ const data = {
13
+ refreshToken: state.refreshToken,
14
+ tokenFamily: state.tokenFamily,
15
+ refreshTokenExpiry: state.refreshTokenExpiry
16
+ };
17
+ localStorage.setItem(storageKey(config), JSON.stringify(data));
18
+ } catch {
19
+ }
20
+ }
21
+ function loadFromStorage(config, state) {
22
+ try {
23
+ const raw = localStorage.getItem(storageKey(config));
24
+ if (!raw) return false;
25
+ const parsed = JSON.parse(raw);
26
+ if (parsed.refreshToken && !isTokenExpired(parsed.refreshTokenExpiry)) {
27
+ state.refreshToken = parsed.refreshToken;
28
+ state.tokenFamily = parsed.tokenFamily;
29
+ state.refreshTokenExpiry = parsed.refreshTokenExpiry;
30
+ return true;
31
+ }
32
+ clearStorage(config);
33
+ return false;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+ function clearStorage(config) {
39
+ try {
40
+ localStorage.removeItem(storageKey(config));
41
+ } catch {
42
+ }
43
+ }
44
+ function loadErrorSettingsFromStorage(config) {
45
+ const ERROR_SETTINGS_CACHE_TTL = 36e5;
46
+ try {
47
+ const raw = localStorage.getItem(errorSettingsKey(config));
48
+ if (!raw) return null;
49
+ const parsed = JSON.parse(raw);
50
+ if (parsed.cachedAt && Date.now() - parsed.cachedAt < ERROR_SETTINGS_CACHE_TTL) {
51
+ return parsed;
52
+ }
53
+ localStorage.removeItem(errorSettingsKey(config));
54
+ return null;
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+ function saveErrorSettingsToStorage(config, settings) {
60
+ try {
61
+ localStorage.setItem(errorSettingsKey(config), JSON.stringify({ ...settings, cachedAt: Date.now() }));
62
+ } catch {
63
+ }
64
+ }
65
+ function isTokenExpired(expiry) {
66
+ if (!expiry) return true;
67
+ return Date.now() >= expiry - 6e4;
68
+ }
69
+ function setTokens(config, state, data, onRefreshScheduled) {
70
+ state.accessToken = data.accessToken;
71
+ state.refreshToken = data.refreshToken;
72
+ state.tokenFamily = data.tokenFamily ?? null;
73
+ const now = Date.now();
74
+ state.accessTokenExpiry = now + data.accessTokenTTL * 1e3;
75
+ state.refreshTokenExpiry = now + data.refreshTokenTTL * 1e3;
76
+ saveToStorage(config, state);
77
+ if (config.autoRefresh && onRefreshScheduled) {
78
+ onRefreshScheduled();
79
+ }
80
+ }
81
+ async function authenticateServerless(config, state, onRefreshScheduled) {
82
+ const nonce = crypto.randomUUID();
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}`);
99
+ }
100
+ const data = await response.json();
101
+ if (!data.accessToken || !data.refreshToken) {
102
+ throw new Error("Invalid authentication response");
103
+ }
104
+ setTokens(config, state, data, onRefreshScheduled);
105
+ state.isAuthenticated = true;
106
+ return true;
107
+ } catch {
108
+ state.isAuthenticated = false;
109
+ return false;
110
+ }
111
+ }
112
+ async function refreshAccessToken(config, state, onRefreshScheduled, sendToken) {
113
+ if (state.isRefreshing) {
114
+ return state.refreshPromise;
115
+ }
116
+ if (!state.refreshToken) {
117
+ return false;
118
+ }
119
+ state.isRefreshing = true;
120
+ state.refreshPromise = (async () => {
121
+ try {
122
+ const response = await fetch(`${config.apiUrl}/${config.widgetId}/auth/refresh`, {
123
+ method: "POST",
124
+ headers: {
125
+ "Content-Type": "application/json",
126
+ Authorization: `Bearer ${state.refreshToken}`
127
+ },
128
+ body: JSON.stringify({ widgetType: config.widgetType })
129
+ });
130
+ if (!response.ok) {
131
+ if (response.status === 401 || response.status === 403) {
132
+ clearState(state);
133
+ clearStorage(config);
134
+ return authenticateServerless(config, state, onRefreshScheduled);
135
+ }
136
+ const error = await response.json().catch(() => ({ error: "Token refresh failed" }));
137
+ throw new Error(error.error || `HTTP ${response.status}`);
138
+ }
139
+ const data = await response.json();
140
+ if (!data.accessToken || !data.refreshToken) {
141
+ throw new Error("Invalid refresh response");
142
+ }
143
+ setTokens(config, state, data, onRefreshScheduled);
144
+ if (sendToken) sendToken();
145
+ return true;
146
+ } catch {
147
+ clearState(state);
148
+ clearStorage(config);
149
+ return authenticateServerless(config, state, onRefreshScheduled);
150
+ } finally {
151
+ state.isRefreshing = false;
152
+ state.refreshPromise = null;
153
+ }
154
+ })();
155
+ return state.refreshPromise;
156
+ }
157
+ async function getValidToken(config, state, onRefreshScheduled) {
158
+ if (state.accessToken && !isTokenExpired(state.accessTokenExpiry)) {
159
+ return state.accessToken;
160
+ }
161
+ const refreshed = await refreshAccessToken(config, state, onRefreshScheduled);
162
+ if (refreshed && state.accessToken) return state.accessToken;
163
+ const authed = await authenticateServerless(config, state, onRefreshScheduled);
164
+ if (authed && state.accessToken) return state.accessToken;
165
+ throw new Error("Unable to obtain valid token");
166
+ }
167
+ async function logout(config, state) {
168
+ if (state.accessToken) {
169
+ try {
170
+ await fetch(`${config.apiUrl}/${config.widgetId}/auth/logout`, {
171
+ method: "POST",
172
+ headers: {
173
+ "Content-Type": "application/json",
174
+ Authorization: `Bearer ${state.accessToken}`
175
+ },
176
+ body: JSON.stringify({ refreshToken: state.refreshToken })
177
+ });
178
+ } catch {
179
+ }
180
+ }
181
+ clearState(state);
182
+ clearStorage(config);
183
+ }
184
+ async function fetchErrorSettings(config, state) {
185
+ const cached = loadErrorSettingsFromStorage(config);
186
+ if (cached) {
187
+ if (cached.configured) {
188
+ state.errorSettings = {
189
+ errorMessage: cached.errorMessage,
190
+ modalColor: cached.modalColor,
191
+ backgroundImage: cached.backgroundImage
192
+ };
193
+ }
194
+ return state.errorSettings;
195
+ }
196
+ try {
197
+ const response = await fetch(
198
+ `${config.apiUrl}/${config.widgetId}/auth/error-settings?widgetType=${config.widgetType}`,
199
+ { method: "GET", headers: { "Content-Type": "application/json" } }
200
+ );
201
+ if (!response.ok) return null;
202
+ const data = await response.json();
203
+ saveErrorSettingsToStorage(config, data);
204
+ if (data.configured) {
205
+ state.errorSettings = {
206
+ errorMessage: data.errorMessage,
207
+ modalColor: data.modalColor,
208
+ backgroundImage: data.backgroundImage
209
+ };
210
+ }
211
+ return state.errorSettings;
212
+ } catch {
213
+ return null;
214
+ }
215
+ }
216
+ function clearState(state) {
217
+ state.accessToken = null;
218
+ state.refreshToken = null;
219
+ state.tokenFamily = null;
220
+ state.accessTokenExpiry = null;
221
+ state.refreshTokenExpiry = null;
222
+ state.isAuthenticated = false;
223
+ state.isRefreshing = false;
224
+ state.refreshPromise = null;
225
+ if (state.refreshTimer) {
226
+ clearTimeout(state.refreshTimer);
227
+ state.refreshTimer = null;
228
+ }
229
+ if (state.syncTimer) {
230
+ clearInterval(state.syncTimer);
231
+ state.syncTimer = null;
232
+ }
233
+ }
234
+ function sendTokenToWidget(config, state, iframe) {
235
+ var _a;
236
+ if (!state.accessToken) return;
237
+ try {
238
+ (_a = iframe.contentWindow) == null ? void 0 : _a.postMessage(
239
+ {
240
+ type: "RETURNINGAI_WIDGET_TOKEN",
241
+ value: {
242
+ widgetId: config.widgetId,
243
+ token: state.accessToken
244
+ }
245
+ },
246
+ config.widgetDomain
247
+ );
248
+ } catch {
249
+ }
250
+ }
251
+ function setupMessageListener(config, state, _shadow, iframe, onRefreshScheduled, onLogout, hideLoader) {
252
+ const handler = async (event) => {
253
+ var _a;
254
+ if (event.origin !== config.widgetDomain) return;
255
+ if (!event.data || typeof event.data.type !== "string") return;
256
+ const { type, containerId, widgetId, payload } = event.data;
257
+ switch (type) {
258
+ case "RETURNINGAI_WIDGET_REQUEST_TOKEN": {
259
+ try {
260
+ const token = await getValidToken(config, state, onRefreshScheduled);
261
+ (_a = iframe.contentWindow) == null ? void 0 : _a.postMessage(
262
+ {
263
+ type: "RETURNINGAI_WIDGET_TOKEN",
264
+ value: { token, widgetId: config.widgetId }
265
+ },
266
+ config.widgetDomain
267
+ );
268
+ } catch {
269
+ }
270
+ break;
271
+ }
272
+ case "WIDGET_HEIGHT_UPDATE": {
273
+ const h = Number(payload == null ? void 0 : payload.height);
274
+ if (Number.isFinite(h) && h > 0) {
275
+ iframe.style.height = `${h}px`;
276
+ }
277
+ break;
278
+ }
279
+ case "WIDGET_READY": {
280
+ if (widgetId !== void 0 && widgetId !== config.widgetId) break;
281
+ if (containerId !== void 0 && containerId !== config.container) break;
282
+ iframe.classList.add("loaded");
283
+ if (hideLoader) hideLoader();
284
+ break;
285
+ }
286
+ case "WIDGET_ERROR": {
287
+ if (hideLoader) hideLoader();
288
+ break;
289
+ }
290
+ case "WIDGET_LOGOUT": {
291
+ if (onLogout) await onLogout();
292
+ break;
293
+ }
294
+ }
295
+ };
296
+ window.addEventListener("message", handler);
297
+ return () => window.removeEventListener("message", handler);
298
+ }
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)}}";
300
+ const DEFINED_ATTRS = /* @__PURE__ */ new Set([
301
+ "widget-id",
302
+ "widget-type",
303
+ "theme",
304
+ "container",
305
+ "width",
306
+ "height",
307
+ "api-url",
308
+ "widget-url",
309
+ "auto-refresh",
310
+ "debug"
311
+ ]);
312
+ function readConfig(el, existingId) {
313
+ const get = (name) => el.getAttribute(name) ?? el.getAttribute(`data-${name}`) ?? "";
314
+ const rawWidgetUrl = get("widget-url") || "https://widget.returningai.com";
315
+ let widgetUrl = rawWidgetUrl;
316
+ if (widgetUrl.endsWith("store-widget")) {
317
+ widgetUrl = `${widgetUrl}/${get("widget-id")}/open-widget`;
318
+ }
319
+ let widgetDomain;
320
+ try {
321
+ widgetDomain = new URL(widgetUrl).origin;
322
+ } catch {
323
+ widgetDomain = widgetUrl;
324
+ }
325
+ if (widgetUrl.includes("store-widget")) {
326
+ const u = new URL(widgetUrl);
327
+ u.searchParams.set("color", get("theme") || "light");
328
+ widgetUrl = u.toString();
329
+ }
330
+ const widgetId = get("widget-id") || existingId || "";
331
+ const container = get("container") || get("data-container") || `returning-ai-widget-${widgetId}`;
332
+ const userIdentifiers = {};
333
+ Array.from(el.attributes).forEach((attr) => {
334
+ const name = attr.name.toLowerCase();
335
+ if (!name.startsWith("data-")) return;
336
+ const bare = name.slice(5);
337
+ if (!DEFINED_ATTRS.has(bare)) {
338
+ userIdentifiers[name] = attr.value;
339
+ }
340
+ });
341
+ return {
342
+ widgetId,
343
+ widgetType: get("widget-type") || "store",
344
+ theme: get("theme") || "light",
345
+ container,
346
+ width: get("width") || "100%",
347
+ height: get("height") || "600px",
348
+ apiUrl: get("api-url") || "https://sgtr-eks-widgets.genesiv.org",
349
+ widgetUrl,
350
+ widgetDomain,
351
+ autoRefresh: get("auto-refresh") !== "false",
352
+ debug: get("debug") === "true",
353
+ storagePrefix: "returning-ai-widget",
354
+ userIdentifiers
355
+ };
356
+ }
357
+ function createInitialState() {
358
+ return {
359
+ accessToken: null,
360
+ refreshToken: null,
361
+ tokenFamily: null,
362
+ accessTokenExpiry: null,
363
+ refreshTokenExpiry: null,
364
+ refreshTimer: null,
365
+ syncTimer: null,
366
+ iframe: null,
367
+ isAuthenticated: false,
368
+ isRefreshing: false,
369
+ refreshPromise: null,
370
+ errorSettings: null
371
+ };
372
+ }
373
+ class BaseWidget extends HTMLElement {
374
+ constructor() {
375
+ super();
376
+ __publicField(this, "shadow");
377
+ __publicField(this, "config");
378
+ __publicField(this, "state", createInitialState());
379
+ __publicField(this, "loaderEl", null);
380
+ __publicField(this, "errorEl", null);
381
+ __publicField(this, "cleanupListener");
382
+ this.shadow = this.attachShadow({ mode: "closed" });
383
+ }
384
+ connectedCallback() {
385
+ this.config = readConfig(this, void 0);
386
+ const style = document.createElement("style");
387
+ style.textContent = widgetCSS;
388
+ this.shadow.appendChild(style);
389
+ const theme = this.config.theme;
390
+ const accentColor = theme === "dark" ? "#ffffff" : "#000000";
391
+ const text4Color = theme === "dark" ? "#9ca3af" : "#6b7280";
392
+ const bgColor = theme === "dark" ? "#1a1a1a" : "#ffffff";
393
+ this.style.setProperty("--rai-accent", accentColor);
394
+ this.style.setProperty("--rai-text4", text4Color);
395
+ this.style.setProperty("--rai-loader-bg", bgColor);
396
+ this.style.setProperty("--rai-error-bg", bgColor);
397
+ this.renderShell();
398
+ this.init();
399
+ }
400
+ disconnectedCallback() {
401
+ if (this.cleanupListener) this.cleanupListener();
402
+ clearState(this.state);
403
+ }
404
+ // ── Rendering ─────────────────────────────────────────────────────────
405
+ renderShell() {
406
+ this.loaderEl = document.createElement("div");
407
+ this.loaderEl.className = "rai-loader";
408
+ this.loaderEl.appendChild(this.createDefaultLoader());
409
+ this.shadow.appendChild(this.loaderEl);
410
+ this.errorEl = document.createElement("div");
411
+ this.errorEl.className = "rai-error";
412
+ this.errorEl.textContent = "Authentication failed. Please try again later.";
413
+ this.shadow.appendChild(this.errorEl);
414
+ }
415
+ createDefaultLoader() {
416
+ const loader = document.createElement("div");
417
+ loader.className = "loader";
418
+ for (let i = 0; i < 5; i++) {
419
+ const bar = document.createElement("div");
420
+ bar.className = "loader__bar";
421
+ loader.appendChild(bar);
422
+ }
423
+ const ball = document.createElement("div");
424
+ ball.className = "loader__ball";
425
+ loader.appendChild(ball);
426
+ return loader;
427
+ }
428
+ hideLoader() {
429
+ if (!this.loaderEl) return;
430
+ this.loaderEl.classList.add("fade-out");
431
+ setTimeout(() => {
432
+ var _a;
433
+ return (_a = this.loaderEl) == null ? void 0 : _a.remove();
434
+ }, 300);
435
+ this.loaderEl = null;
436
+ }
437
+ showError() {
438
+ var _a;
439
+ this.hideLoader();
440
+ if (this.errorEl) {
441
+ if ((_a = this.state.errorSettings) == null ? void 0 : _a.errorMessage) {
442
+ this.errorEl.textContent = this.state.errorSettings.errorMessage;
443
+ }
444
+ this.errorEl.classList.add("visible");
445
+ }
446
+ }
447
+ // ── Initialization ─────────────────────────────────────────────────────
448
+ async init() {
449
+ if (!this.config.widgetId) {
450
+ this.showError();
451
+ return;
452
+ }
453
+ await fetchErrorSettings(this.config, this.state);
454
+ const hasStored = loadFromStorage(this.config, this.state);
455
+ if (hasStored) {
456
+ const refreshed = await refreshAccessToken(
457
+ this.config,
458
+ this.state,
459
+ () => this.scheduleRefresh()
460
+ );
461
+ if (!refreshed) {
462
+ this.showError();
463
+ return;
464
+ }
465
+ this.state.isAuthenticated = true;
466
+ this.createIframe();
467
+ return;
468
+ }
469
+ const authed = await authenticateServerless(
470
+ this.config,
471
+ this.state,
472
+ () => this.scheduleRefresh()
473
+ );
474
+ if (authed) {
475
+ this.createIframe();
476
+ } else {
477
+ this.showError();
478
+ }
479
+ }
480
+ createIframe() {
481
+ const iframe = document.createElement("iframe");
482
+ iframe.src = this.buildWidgetUrl(this.config);
483
+ iframe.allow = "clipboard-write";
484
+ iframe.frameBorder = "0";
485
+ iframe.scrolling = "no";
486
+ iframe.style.cssText = `width: ${this.config.width}; height: ${this.config.height}; border: none; display: block;`;
487
+ iframe.onload = () => {
488
+ sendTokenToWidget(this.config, this.state, iframe);
489
+ this.schedulePeriodicSync(iframe);
490
+ };
491
+ iframe.onerror = () => {
492
+ this.showError();
493
+ };
494
+ this.state.iframe = iframe;
495
+ this.shadow.appendChild(iframe);
496
+ this.cleanupListener = setupMessageListener(
497
+ this.config,
498
+ this.state,
499
+ this.shadow,
500
+ iframe,
501
+ () => this.scheduleRefresh(),
502
+ () => this.logoutAndClear(),
503
+ () => this.hideLoader()
504
+ );
505
+ }
506
+ // ── Token scheduling ──────────────────────────────────────────────────
507
+ scheduleRefresh() {
508
+ if (this.state.refreshTimer) clearTimeout(this.state.refreshTimer);
509
+ if (!this.state.accessTokenExpiry) return;
510
+ const delay = this.state.accessTokenExpiry - Date.now() - 6e4;
511
+ if (delay > 0) {
512
+ this.state.refreshTimer = setTimeout(async () => {
513
+ await refreshAccessToken(
514
+ this.config,
515
+ this.state,
516
+ () => this.scheduleRefresh(),
517
+ () => {
518
+ if (this.state.iframe) sendTokenToWidget(this.config, this.state, this.state.iframe);
519
+ }
520
+ );
521
+ }, delay);
522
+ } else {
523
+ refreshAccessToken(
524
+ this.config,
525
+ this.state,
526
+ () => this.scheduleRefresh(),
527
+ () => {
528
+ if (this.state.iframe) sendTokenToWidget(this.config, this.state, this.state.iframe);
529
+ }
530
+ );
531
+ }
532
+ }
533
+ schedulePeriodicSync(iframe) {
534
+ if (this.state.syncTimer) clearInterval(this.state.syncTimer);
535
+ this.state.syncTimer = setInterval(() => {
536
+ if (this.state.accessToken) sendTokenToWidget(this.config, this.state, iframe);
537
+ }, 12e4);
538
+ }
539
+ // ── Logout helper ─────────────────────────────────────────────────────
540
+ async logoutAndClear() {
541
+ await logout(this.config, this.state);
542
+ this.shadow.querySelectorAll("iframe").forEach((el) => el.remove());
543
+ }
544
+ // ── Public API ────────────────────────────────────────────────────────
545
+ async reload() {
546
+ clearState(this.state);
547
+ this.shadow.querySelectorAll("iframe").forEach((el) => el.remove());
548
+ await this.init();
549
+ }
550
+ async logoutPublic() {
551
+ await this.logoutAndClear();
552
+ }
553
+ isAuthenticated() {
554
+ return this.state.isAuthenticated && this.state.accessToken !== null;
555
+ }
556
+ getTokenInfo() {
557
+ return {
558
+ hasAccessToken: !!this.state.accessToken,
559
+ hasRefreshToken: !!this.state.refreshToken,
560
+ accessTokenExpiry: this.state.accessTokenExpiry ? new Date(this.state.accessTokenExpiry) : null,
561
+ refreshTokenExpiry: this.state.refreshTokenExpiry ? new Date(this.state.refreshTokenExpiry) : null,
562
+ isAccessTokenValid: !!this.state.accessToken && !isTokenExpired(this.state.accessTokenExpiry),
563
+ isRefreshTokenValid: !!this.state.refreshToken && !isTokenExpired(this.state.refreshTokenExpiry)
564
+ };
565
+ }
566
+ }
567
+ class StoreWidget extends BaseWidget {
568
+ buildWidgetUrl(config) {
569
+ const url = new URL(config.widgetUrl);
570
+ url.searchParams.set("color", config.theme);
571
+ url.searchParams.set("containerId", config.container);
572
+ url.searchParams.set("connectType", "simple");
573
+ url.searchParams.set("mode", "private");
574
+ return url.toString();
575
+ }
576
+ }
577
+ class ChannelWidget extends BaseWidget {
578
+ buildWidgetUrl(config) {
579
+ if (config.widgetUrl.endsWith("channel-widget")) {
580
+ return `${config.widgetUrl}/${config.widgetId}`;
581
+ }
582
+ return config.widgetUrl;
583
+ }
584
+ }
585
+ class MilestoneWidget extends BaseWidget {
586
+ buildWidgetUrl(config) {
587
+ if (config.widgetUrl.endsWith("milestone-widget")) {
588
+ return `${config.widgetUrl}/${config.widgetId}`;
589
+ }
590
+ return config.widgetUrl;
591
+ }
592
+ }
593
+ class SocialWidget extends BaseWidget {
594
+ buildWidgetUrl(config) {
595
+ if (config.widgetUrl.endsWith("social-widget")) {
596
+ return `${config.widgetUrl}/${config.widgetId}`;
597
+ }
598
+ return config.widgetUrl;
599
+ }
600
+ }
601
+ class CurrencyWidget extends BaseWidget {
602
+ buildWidgetUrl(config) {
603
+ if (config.widgetUrl.endsWith("currency-overview-widget")) {
604
+ return `${config.widgetUrl}/${config.widgetId}`;
605
+ }
606
+ return config.widgetUrl;
607
+ }
608
+ }
609
+ console.log(`[rai-widget] v${"1.0.0"}`);
610
+ const WIDGET_REGISTRY = [
611
+ ["rai-widget", StoreWidget],
612
+ ["rai-channel-widget", ChannelWidget],
613
+ ["rai-milestone-widget", MilestoneWidget],
614
+ ["rai-social-widget", SocialWidget],
615
+ ["rai-currency-widget", CurrencyWidget]
616
+ ];
617
+ for (const [tag, cls] of WIDGET_REGISTRY) {
618
+ if (!customElements.get(tag)) {
619
+ customElements.define(tag, cls);
620
+ }
621
+ }
622
+ const WIDGET_CLASS_MAP = {
623
+ store: StoreWidget,
624
+ channel: ChannelWidget,
625
+ milestone: MilestoneWidget,
626
+ social: SocialWidget,
627
+ "currency-view": CurrencyWidget
628
+ };
629
+ const ALL_WIDGET_SELECTOR = WIDGET_REGISTRY.map(([tag]) => tag).join(", ");
630
+ function bootstrapFromScriptTag() {
631
+ var _a;
632
+ const script = (((_a = document.currentScript) == null ? void 0 : _a.hasAttribute("data-widget-id")) ? document.currentScript : null) || document.querySelector("script[data-widget-id]");
633
+ if (!script) return;
634
+ const widgetId = script.getAttribute("data-widget-id");
635
+ if (!widgetId) return;
636
+ const containerId = script.getAttribute("data-container") || `returning-ai-widget-${widgetId}`;
637
+ const container = document.getElementById(containerId);
638
+ if (!container) return;
639
+ if (getComputedStyle(container).position === "static") {
640
+ container.style.position = "relative";
641
+ }
642
+ const widgetType = script.getAttribute("data-widget-type") ?? "store";
643
+ const WidgetClass = WIDGET_CLASS_MAP[widgetType] ?? StoreWidget;
644
+ const widget = new WidgetClass();
645
+ Array.from(script.attributes).forEach((attr) => {
646
+ widget.setAttribute(attr.name, attr.value);
647
+ });
648
+ container.appendChild(widget);
649
+ }
650
+ if (document.readyState === "loading") {
651
+ document.addEventListener("DOMContentLoaded", bootstrapFromScriptTag);
652
+ } else {
653
+ bootstrapFromScriptTag();
654
+ }
655
+ function exposePublicApi() {
656
+ const container = (() => {
657
+ var _a;
658
+ const script = (((_a = document.currentScript) == null ? void 0 : _a.hasAttribute("data-widget-id")) ? document.currentScript : null) || document.querySelector("script[data-widget-id]");
659
+ if (!script) return null;
660
+ const id = script.getAttribute("data-container") || `returning-ai-widget-${script.getAttribute("data-widget-id")}`;
661
+ return document.getElementById(id);
662
+ })();
663
+ const widget = container == null ? void 0 : container.querySelector(ALL_WIDGET_SELECTOR);
664
+ window.ReturningAIWidget = {
665
+ version: "1.0.0",
666
+ reload: () => (widget == null ? void 0 : widget.reload()) ?? Promise.resolve(),
667
+ logout: () => (widget == null ? void 0 : widget.logoutPublic()) ?? Promise.resolve(),
668
+ isAuthenticated: () => (widget == null ? void 0 : widget.isAuthenticated()) ?? false,
669
+ getTokenInfo: () => (widget == null ? void 0 : widget.getTokenInfo()) ?? {}
670
+ };
671
+ }
672
+ if (document.readyState === "loading") {
673
+ document.addEventListener("DOMContentLoaded", exposePublicApi);
674
+ } else {
675
+ exposePublicApi();
676
+ }
677
+ export {
678
+ ChannelWidget,
679
+ CurrencyWidget,
680
+ MilestoneWidget,
681
+ SocialWidget,
682
+ StoreWidget
683
+ };
@@ -0,0 +1,34 @@
1
+ import type { WidgetConfig } from './types';
2
+ export declare function readConfig(el: Element, existingId?: string): WidgetConfig;
3
+ export declare abstract class BaseWidget extends HTMLElement {
4
+ protected shadow: ShadowRoot;
5
+ protected config: WidgetConfig;
6
+ private state;
7
+ private loaderEl;
8
+ private errorEl;
9
+ private cleanupListener?;
10
+ constructor();
11
+ protected abstract buildWidgetUrl(config: WidgetConfig): string;
12
+ connectedCallback(): void;
13
+ disconnectedCallback(): void;
14
+ private renderShell;
15
+ private createDefaultLoader;
16
+ private hideLoader;
17
+ private showError;
18
+ private init;
19
+ private createIframe;
20
+ private scheduleRefresh;
21
+ private schedulePeriodicSync;
22
+ private logoutAndClear;
23
+ reload(): Promise<void>;
24
+ logoutPublic(): Promise<void>;
25
+ isAuthenticated(): boolean;
26
+ getTokenInfo(): {
27
+ hasAccessToken: boolean;
28
+ hasRefreshToken: boolean;
29
+ accessTokenExpiry: Date | null;
30
+ refreshTokenExpiry: Date | null;
31
+ isAccessTokenValid: boolean;
32
+ isRefreshTokenValid: boolean;
33
+ };
34
+ }
@@ -0,0 +1,5 @@
1
+ import type { WidgetConfig } from './types';
2
+ import { BaseWidget } from './BaseWidget';
3
+ export declare class ChannelWidget extends BaseWidget {
4
+ protected buildWidgetUrl(config: WidgetConfig): string;
5
+ }
@@ -0,0 +1,5 @@
1
+ import type { WidgetConfig } from './types';
2
+ import { BaseWidget } from './BaseWidget';
3
+ export declare class CurrencyWidget extends BaseWidget {
4
+ protected buildWidgetUrl(config: WidgetConfig): string;
5
+ }
@@ -0,0 +1,5 @@
1
+ import type { WidgetConfig } from './types';
2
+ import { BaseWidget } from './BaseWidget';
3
+ export declare class MilestoneWidget extends BaseWidget {
4
+ protected buildWidgetUrl(config: WidgetConfig): string;
5
+ }
@@ -0,0 +1,5 @@
1
+ import type { WidgetConfig } from './types';
2
+ import { BaseWidget } from './BaseWidget';
3
+ export declare class SocialWidget extends BaseWidget {
4
+ protected buildWidgetUrl(config: WidgetConfig): string;
5
+ }
@@ -0,0 +1,5 @@
1
+ import type { WidgetConfig } from './types';
2
+ import { BaseWidget } from './BaseWidget';
3
+ export declare class StoreWidget extends BaseWidget {
4
+ protected buildWidgetUrl(config: WidgetConfig): string;
5
+ }
@@ -0,0 +1,8 @@
1
+ import type { WidgetConfig, WidgetState } from '../types';
2
+ export declare function authenticateServerless(config: WidgetConfig, state: WidgetState, onRefreshScheduled?: () => void): Promise<boolean>;
3
+ export declare function refreshAccessToken(config: WidgetConfig, state: WidgetState, onRefreshScheduled?: () => void, sendToken?: () => void): Promise<boolean>;
4
+ export declare function getValidToken(config: WidgetConfig, state: WidgetState, onRefreshScheduled?: () => void): Promise<string>;
5
+ export declare function logout(config: WidgetConfig, state: WidgetState): Promise<void>;
6
+ export declare function fetchErrorSettings(config: WidgetConfig, state: WidgetState): Promise<typeof state.errorSettings>;
7
+ declare function clearState(state: WidgetState): void;
8
+ export { clearState };
@@ -0,0 +1,3 @@
1
+ import type { WidgetConfig, WidgetState } from '../types';
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;
@@ -0,0 +1,8 @@
1
+ import type { WidgetConfig, WidgetState } from '../types';
2
+ export declare function saveToStorage(config: WidgetConfig, state: WidgetState): void;
3
+ export declare function loadFromStorage(config: WidgetConfig, state: WidgetState): boolean;
4
+ export declare function clearStorage(config: WidgetConfig): void;
5
+ export declare function loadErrorSettingsFromStorage(config: WidgetConfig): Record<string, unknown> | null;
6
+ export declare function saveErrorSettingsToStorage(config: WidgetConfig, settings: Record<string, unknown>): void;
7
+ declare function isTokenExpired(expiry: number | null): boolean;
8
+ export { isTokenExpired };
@@ -0,0 +1,17 @@
1
+ import { StoreWidget } from './StoreWidget';
2
+ import { ChannelWidget } from './ChannelWidget';
3
+ import { MilestoneWidget } from './MilestoneWidget';
4
+ import { SocialWidget } from './SocialWidget';
5
+ import { CurrencyWidget } from './CurrencyWidget';
6
+ declare global {
7
+ interface Window {
8
+ ReturningAIWidget: {
9
+ version: string;
10
+ reload: () => Promise<void>;
11
+ logout: () => Promise<void>;
12
+ isAuthenticated: () => boolean;
13
+ getTokenInfo: () => object;
14
+ };
15
+ }
16
+ }
17
+ export { StoreWidget, ChannelWidget, MilestoneWidget, SocialWidget, CurrencyWidget };
@@ -0,0 +1,40 @@
1
+ export interface WidgetConfig {
2
+ widgetId: string;
3
+ widgetType: 'store' | 'channel' | 'milestone' | 'social' | 'currency-view';
4
+ theme: 'light' | 'dark';
5
+ container: string;
6
+ width: string;
7
+ height: string;
8
+ apiUrl: string;
9
+ widgetUrl: string;
10
+ widgetDomain: string;
11
+ autoRefresh: boolean;
12
+ debug: boolean;
13
+ storagePrefix: string;
14
+ userIdentifiers: Record<string, string>;
15
+ }
16
+ export interface WidgetState {
17
+ accessToken: string | null;
18
+ refreshToken: string | null;
19
+ tokenFamily: string | null;
20
+ accessTokenExpiry: number | null;
21
+ refreshTokenExpiry: number | null;
22
+ refreshTimer: ReturnType<typeof setTimeout> | null;
23
+ syncTimer: ReturnType<typeof setInterval> | null;
24
+ iframe: HTMLIFrameElement | null;
25
+ isAuthenticated: boolean;
26
+ isRefreshing: boolean;
27
+ refreshPromise: Promise<boolean> | null;
28
+ errorSettings: {
29
+ errorMessage?: string;
30
+ modalColor?: string;
31
+ backgroundImage?: string;
32
+ } | null;
33
+ }
34
+ export interface TokenData {
35
+ accessToken: string;
36
+ refreshToken: string;
37
+ tokenFamily?: string;
38
+ accessTokenTTL: number;
39
+ refreshTokenTTL: number;
40
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@returningai/widget-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Shadow DOM isolated widget SDK for ReturningAI",
5
+ "main": "dist/rai-widget.iife.js",
6
+ "module": "dist/rai-widget.js",
7
+ "types": "dist/types/index.d.ts",
8
+ "sideEffects": true,
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/rai-widget.js",
12
+ "default": "./dist/rai-widget.iife.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "dev": "vite",
20
+ "build": "vite build",
21
+ "build:min": "vite build --mode production",
22
+ "types": "tsc --emitDeclarationOnly --outDir dist/types",
23
+ "prepublishOnly": "npm run build:min && npm run types"
24
+ },
25
+ "devDependencies": {
26
+ "terser": "^5.46.0",
27
+ "typescript": "^5.3.3",
28
+ "vite": "^5.1.0"
29
+ }
30
+ }