@returningai/widget-sdk 1.5.0 → 1.6.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.
@@ -17,6 +17,7 @@ export declare abstract class BaseWidget extends HTMLElement {
17
17
  private initGeneration;
18
18
  private lottieInstances;
19
19
  private callbackRegistry;
20
+ private bannerNode;
20
21
  constructor();
21
22
  registerCallback(callbackName: string, callback: ReturningAIWidgetCallback): void;
22
23
  lockCallbacks(): void;
@@ -92,6 +93,15 @@ export declare abstract class BaseWidget extends HTMLElement {
92
93
  private deferLoaderRemovalUntilReactPaints;
93
94
  private showError;
94
95
  private renderCustomError;
96
+ private resolveBannerApiUrl;
97
+ private removeBanner;
98
+ /**
99
+ * After the widget mounts, fetch + render any eligible sdk-surface banner
100
+ * targeted at this widget type. Core widgets send their communityId + type;
101
+ * custom widgets send the decoded widget id so the server resolves the precise
102
+ * sub-type + community. Best-effort: never throws into the host page.
103
+ */
104
+ private mountBannerForWidget;
95
105
  private init;
96
106
  private isCurrentInit;
97
107
  private teardownMountedWidget;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `<rai-banner>` — the external SDK runtime (M5).
3
+ *
4
+ * Drop-in custom element for partner sites. On connect it: mints/loads an
5
+ * anonymous visitor id, asks the eligibility engine for a banner on the `sdk`
6
+ * surface, renders it (overlay → portaled to <body>, inline → into a target or
7
+ * itself), and fires impression/click/dismiss tracking. Emits DOM events so the
8
+ * host page can react. Never throws into the host page.
9
+ *
10
+ * Attributes:
11
+ * community-id (required) the community ObjectId
12
+ * api-url explicit API base (overrides domain-key)
13
+ * domain-key LOCAL | SGTR | STG | GEN (default LOCAL)
14
+ * surface defaults to 'sdk'
15
+ * page-path / page-hostname / page-url override page context (default: window.location)
16
+ * storage-prefix override the localStorage key prefix
17
+ *
18
+ * Events: rai-banner-impression | rai-banner-click | rai-banner-dismiss |
19
+ * rai-banner-empty | rai-banner-error
20
+ */
21
+ export declare class BannerRuntime extends HTMLElement {
22
+ private rendered;
23
+ private impressionFired;
24
+ connectedCallback(): void;
25
+ disconnectedCallback(): void;
26
+ private resolveApiUrl;
27
+ private device;
28
+ private init;
29
+ private mount;
30
+ private cleanup;
31
+ private dispatch;
32
+ }
@@ -0,0 +1,33 @@
1
+ import type { BannerSurface, EligibilityResult } from './types';
2
+ export interface EligibleRequest {
3
+ /** Optional when `widgetId` is supplied (custom widgets resolve community server-side). */
4
+ communityId?: string;
5
+ surface: BannerSurface;
6
+ /** Widget-type key for the sdk surface (e.g. 'channel', 'store'). */
7
+ widgetType?: string;
8
+ /** Custom-widget id (raw ObjectId) — server resolves its sub-type + community. */
9
+ widgetId?: string;
10
+ channelId?: string;
11
+ page?: {
12
+ url?: string;
13
+ hostname?: string;
14
+ path?: string;
15
+ };
16
+ viewport?: {
17
+ width: number;
18
+ height: number;
19
+ device: 'desktop' | 'tablet' | 'mobile';
20
+ };
21
+ identifier?: {
22
+ type: 'anonymous_session';
23
+ value: string;
24
+ };
25
+ /** SDK embed token — gates `access: 'domain' | 'authenticated'` banners server-side. */
26
+ embedToken?: string;
27
+ }
28
+ /**
29
+ * Calls the public eligibility endpoint. CORS is `origin: '*'` server-side, so a
30
+ * plain cross-origin `fetch` works from any embedding site. Returns
31
+ * `{ banner: null }` on any failure — the SDK must never throw into the host page.
32
+ */
33
+ export declare function fetchEligibleBanner(apiUrl: string, request: EligibleRequest): Promise<EligibilityResult>;
@@ -0,0 +1,12 @@
1
+ import type { EligibleBanner } from "./types";
2
+ export interface BannerHandlers {
3
+ onCtaClick: () => void;
4
+ onDismiss: () => void;
5
+ }
6
+ /**
7
+ * Builds the banner DOM (plain elements + inline styles, so it renders correctly
8
+ * on any host site without external CSS). Honors `placement.mode`:
9
+ * overlay/popup → fixed centered card; inline → in-flow full-width bar.
10
+ * The caller decides where to attach it (body for overlay, target for inline).
11
+ */
12
+ export declare function renderBanner(banner: EligibleBanner, handlers: BannerHandlers): HTMLElement;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Sanitize admin-authored banner HTML before injecting it into the host page.
3
+ *
4
+ * Mirrors the allow-list used by the in-app renderer (tradingroom banner-preview)
5
+ * and the M6 server-side sanitizer. With M6 the server is the primary gate; this
6
+ * is defense-in-depth on the client so a compromised/legacy response can never
7
+ * inject script into an embedding site.
8
+ */
9
+ export declare function sanitizeBannerHtml(html: string | undefined): string;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Anonymous visitor identity for the SDK surface.
3
+ *
4
+ * External sites have no logged-in user, so we mint a stable per-community UUID
5
+ * and persist it in localStorage (same `storagePrefix` convention as core/storage).
6
+ * This becomes the `anonymous_session` identifier the eligibility engine hashes —
7
+ * enabling per-visitor frequency caps and dismiss-memory without any PII.
8
+ */
9
+ export declare function getOrCreateVisitorId(communityId: string, storagePrefix: string): string;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Fire a signed tracking URL (impression / click / dismiss). These are one-time,
3
+ * HMAC-signed GET URLs from the eligibility response. An image beacon avoids a
4
+ * CORS preflight and survives navigation. Best-effort: tracking must never break
5
+ * rendering or throw into the host page.
6
+ */
7
+ export declare function fireTracking(url: string | undefined): void;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Banner SDK runtime types — mirror the server's `RenderableBanner` contract
3
+ * (tradingroom `bannerEligibility.service.ts`). Kept in sync by hand; there is no
4
+ * shared types package between the two repos.
5
+ */
6
+ export type BannerSurface = 'channel' | 'community' | 'sdk';
7
+ export interface BannerPlacement {
8
+ mode: 'overlay' | 'inline' | 'popup';
9
+ position: 'top' | 'bottom' | 'center';
10
+ pushPage?: boolean;
11
+ targetSelector?: string | null;
12
+ insertion?: 'prepend' | 'append' | 'before' | 'after';
13
+ zIndex?: number;
14
+ mobileBehavior?: 'same' | 'bottom_overlay' | 'hide';
15
+ }
16
+ export interface BannerCta {
17
+ enabled: boolean;
18
+ label?: string;
19
+ url?: string;
20
+ openInNewTab?: boolean;
21
+ style?: Record<string, string>;
22
+ backgroundColor?: string;
23
+ buttonColor?: string;
24
+ textColor?: string;
25
+ color?: string;
26
+ position?: string;
27
+ }
28
+ export interface BannerImage {
29
+ desktopUrl?: string;
30
+ mobileUrl?: string;
31
+ format?: string;
32
+ linkUrl?: string;
33
+ openInNewTab?: boolean;
34
+ cornerRadius?: string;
35
+ horizontalAlignment?: 'left' | 'center' | 'right';
36
+ verticalAlignment?: 'top' | 'center' | 'bottom';
37
+ }
38
+ export interface BannerStyle {
39
+ backgroundColor?: string;
40
+ color?: string;
41
+ }
42
+ export interface BannerTracking {
43
+ impressionUrl: string;
44
+ clickUrl: string;
45
+ dismissUrl: string;
46
+ }
47
+ export interface EligibleBanner {
48
+ id: string;
49
+ communityId: string;
50
+ type: 'text' | 'image';
51
+ title?: string;
52
+ text?: string;
53
+ style?: BannerStyle;
54
+ cta?: BannerCta;
55
+ ctaStyle?: Record<string, string>;
56
+ image?: BannerImage;
57
+ placement: BannerPlacement;
58
+ tracking: BannerTracking;
59
+ }
60
+ export interface EligibilityResult {
61
+ banner: EligibleBanner | null;
62
+ reason?: string;
63
+ nextCheckAfterMs?: number;
64
+ }
@@ -0,0 +1,2 @@
1
+ import { BannerRuntime } from './banner/BannerRuntime';
2
+ export { BannerRuntime };
@@ -22,20 +22,32 @@ interface RaiWidgetAttributes {
22
22
  style?: string
23
23
  }
24
24
 
25
+ type RaiWidgetElementAttributes = RaiWidgetAttributes & {
26
+ [attributeName: string]: unknown
27
+ }
28
+
29
+ interface RaiWidgetIntrinsicElements {
30
+ 'rai-store-widget': RaiWidgetElementAttributes
31
+ 'rai-channel-widget': RaiWidgetElementAttributes
32
+ 'rai-milestone-widget': RaiWidgetElementAttributes
33
+ 'rai-social-widget': RaiWidgetElementAttributes
34
+ 'rai-currency-widget': RaiWidgetElementAttributes
35
+ 'rai-referral-widget': RaiWidgetElementAttributes
36
+ 'rai-custom-widget': RaiWidgetElementAttributes
37
+ 'rai-widget': RaiWidgetElementAttributes // deprecated
38
+ }
39
+
25
40
  // ── React ──────────────────────────────────────────────────────────────────
26
- declare namespace React {
27
- namespace JSX {
28
- interface IntrinsicElements {
29
- 'rai-store-widget': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & RaiWidgetAttributes
30
- 'rai-channel-widget': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & RaiWidgetAttributes
31
- 'rai-milestone-widget': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & RaiWidgetAttributes
32
- 'rai-social-widget': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & RaiWidgetAttributes
33
- 'rai-currency-widget': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & RaiWidgetAttributes
34
- 'rai-referral-widget': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & RaiWidgetAttributes
35
- 'rai-custom-widget': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & RaiWidgetAttributes
36
- 'rai-widget': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & RaiWidgetAttributes // deprecated
41
+ declare global {
42
+ namespace React {
43
+ namespace JSX {
44
+ interface IntrinsicElements extends RaiWidgetIntrinsicElements {}
37
45
  }
38
46
  }
47
+
48
+ namespace JSX {
49
+ interface IntrinsicElements extends RaiWidgetIntrinsicElements {}
50
+ }
39
51
  }
40
52
 
41
53
  // ── Vue 3 (Volar / vue-tsc) ────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@returningai/widget-sdk",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Shadow DOM isolated widget SDK for ReturningAI",
5
5
  "main": "dist/rai-widget.iife.js",
6
6
  "module": "dist/rai-widget.js",
@@ -21,10 +21,15 @@
21
21
  "dev": "vite",
22
22
  "build": "vite build",
23
23
  "build:min": "vite build --mode production",
24
+ "build:banner": "vite build --config vite.banner.config.ts",
25
+ "build:banner:min": "vite build --config vite.banner.config.ts --mode production",
26
+ "build:all": "npm run build && npm run build:banner",
27
+ "build:all:min": "npm run build:min && npm run build:banner:min",
24
28
  "types": "tsc --emitDeclarationOnly --outDir dist/types && cp src/jsx.d.ts dist/types/jsx.d.ts",
25
- "test": "npm run test:host-callback",
29
+ "test": "npm run test:host-callback && npm run test:jsx-types",
26
30
  "test:host-callback": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/postmessage-host-callback.test.js",
27
- "prepublishOnly": "npm run build:min && npm run types"
31
+ "test:jsx-types": "tsc --noEmit --strict --lib ES2020,DOM --skipLibCheck false src/jsx.d.ts",
32
+ "prepublishOnly": "npm run build:all:min && npm run types"
28
33
  },
29
34
  "devDependencies": {
30
35
  "terser": "^5.46.0",
@@ -32,6 +37,7 @@
32
37
  "vite": "^5.1.0"
33
38
  },
34
39
  "dependencies": {
40
+ "dompurify": "^3.4.9",
35
41
  "lottie-web": "^5.13.0"
36
42
  }
37
43
  }