@uservibesos/web-component 1.0.0 → 1.0.1
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/dist/widget.d.ts +27 -0
- package/dist/widget.js +128 -12
- package/package.json +16 -2
- package/build.js +0 -47
- package/src/widget.ts +0 -244
package/dist/widget.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface InitConfig {
|
|
2
|
+
projectId: string;
|
|
3
|
+
jwt?: string;
|
|
4
|
+
container: string | HTMLElement;
|
|
5
|
+
theme?: string;
|
|
6
|
+
height?: string;
|
|
7
|
+
mode?: string;
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface WidgetInstance {
|
|
12
|
+
element: HTMLElement;
|
|
13
|
+
setJWT: (jwt: string) => void;
|
|
14
|
+
destroy: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export declare function init(config: InitConfig): WidgetInstance | null;
|
|
18
|
+
|
|
19
|
+
export declare class UserVibesWidget extends HTMLElement {
|
|
20
|
+
setJWT(jwt: string): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export declare const UserVibesOS: {
|
|
24
|
+
init: typeof init;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default UserVibesOS;
|
package/dist/widget.js
CHANGED
|
@@ -1,15 +1,102 @@
|
|
|
1
|
-
"
|
|
1
|
+
function f(o){if(typeof document>"u"||document.querySelector(`link[href="${o}"]`))return;let s=document.createElement("link");s.rel="preconnect",s.href=o,document.head.appendChild(s)}var c=class extends HTMLElement{constructor(){super();this.baseUrl="https://app.uservibesos.com";this.iframe=null;this.skeleton=null;this.loaded=!1;this.attachShadow({mode:"open"})}static get observedAttributes(){return["project","jwt","theme","height","mode","base-url"]}connectedCallback(){this.render()}attributeChangedCallback(e,t,r){t!==r&&this.render()}render(){let e=this.getAttribute("project"),t=this.getAttribute("jwt"),r=this.getAttribute("theme")||"light",d=this.getAttribute("height")||"600px",l=this.getAttribute("mode")||"feature-request",a=this.getAttribute("base-url");if(!e){this.renderError('Missing required "project" attribute (project ID)');return}a?this.baseUrl=a:typeof window<"u"&&window.location.hostname==="localhost"&&(this.baseUrl=window.location.origin),f(this.baseUrl);let n=`${this.baseUrl}/embed?projectId=${encodeURIComponent(e)}&mode=${l}`;t&&(n+=`&jwt=${encodeURIComponent(t)}`);let i=`
|
|
2
2
|
<style>
|
|
3
3
|
:host {
|
|
4
4
|
display: block;
|
|
5
5
|
width: 100%;
|
|
6
6
|
}
|
|
7
|
+
.widget-container {
|
|
8
|
+
position: relative;
|
|
9
|
+
min-height: ${d};
|
|
10
|
+
}
|
|
7
11
|
iframe {
|
|
8
12
|
width: 100%;
|
|
9
|
-
height: ${
|
|
13
|
+
height: ${d};
|
|
10
14
|
border: none;
|
|
11
15
|
border-radius: 8px;
|
|
12
16
|
overflow: hidden;
|
|
17
|
+
opacity: 0;
|
|
18
|
+
transition: opacity 0.2s ease-in-out;
|
|
19
|
+
}
|
|
20
|
+
iframe.loaded {
|
|
21
|
+
opacity: 1;
|
|
22
|
+
}
|
|
23
|
+
.skeleton {
|
|
24
|
+
position: absolute;
|
|
25
|
+
top: 0;
|
|
26
|
+
left: 0;
|
|
27
|
+
right: 0;
|
|
28
|
+
padding: 1.5rem;
|
|
29
|
+
background: #f9fafb;
|
|
30
|
+
border-radius: 8px;
|
|
31
|
+
font-family: system-ui, sans-serif;
|
|
32
|
+
}
|
|
33
|
+
.skeleton.hidden {
|
|
34
|
+
display: none;
|
|
35
|
+
}
|
|
36
|
+
.skeleton-header {
|
|
37
|
+
display: flex;
|
|
38
|
+
justify-content: space-between;
|
|
39
|
+
align-items: center;
|
|
40
|
+
margin-bottom: 1.5rem;
|
|
41
|
+
padding-bottom: 1rem;
|
|
42
|
+
border-bottom: 1px solid #e5e7eb;
|
|
43
|
+
}
|
|
44
|
+
.skeleton-title {
|
|
45
|
+
height: 1.5rem;
|
|
46
|
+
width: 180px;
|
|
47
|
+
background: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%);
|
|
48
|
+
background-size: 200% 100%;
|
|
49
|
+
animation: shimmer 1.5s infinite;
|
|
50
|
+
border-radius: 4px;
|
|
51
|
+
}
|
|
52
|
+
.skeleton-button {
|
|
53
|
+
height: 2.25rem;
|
|
54
|
+
width: 100px;
|
|
55
|
+
background: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%);
|
|
56
|
+
background-size: 200% 100%;
|
|
57
|
+
animation: shimmer 1.5s infinite;
|
|
58
|
+
border-radius: 6px;
|
|
59
|
+
}
|
|
60
|
+
.skeleton-tabs {
|
|
61
|
+
display: flex;
|
|
62
|
+
gap: 0.5rem;
|
|
63
|
+
margin-bottom: 1rem;
|
|
64
|
+
}
|
|
65
|
+
.skeleton-tab {
|
|
66
|
+
height: 2rem;
|
|
67
|
+
width: 60px;
|
|
68
|
+
background: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%);
|
|
69
|
+
background-size: 200% 100%;
|
|
70
|
+
animation: shimmer 1.5s infinite;
|
|
71
|
+
border-radius: 4px;
|
|
72
|
+
}
|
|
73
|
+
.skeleton-item {
|
|
74
|
+
padding: 1rem;
|
|
75
|
+
margin-bottom: 0.75rem;
|
|
76
|
+
background: white;
|
|
77
|
+
border: 1px solid #e5e7eb;
|
|
78
|
+
border-radius: 8px;
|
|
79
|
+
}
|
|
80
|
+
.skeleton-item-title {
|
|
81
|
+
height: 1rem;
|
|
82
|
+
width: 70%;
|
|
83
|
+
background: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%);
|
|
84
|
+
background-size: 200% 100%;
|
|
85
|
+
animation: shimmer 1.5s infinite;
|
|
86
|
+
border-radius: 4px;
|
|
87
|
+
margin-bottom: 0.5rem;
|
|
88
|
+
}
|
|
89
|
+
.skeleton-item-desc {
|
|
90
|
+
height: 0.75rem;
|
|
91
|
+
width: 90%;
|
|
92
|
+
background: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%);
|
|
93
|
+
background-size: 200% 100%;
|
|
94
|
+
animation: shimmer 1.5s infinite;
|
|
95
|
+
border-radius: 4px;
|
|
96
|
+
}
|
|
97
|
+
@keyframes shimmer {
|
|
98
|
+
0% { background-position: 200% 0; }
|
|
99
|
+
100% { background-position: -200% 0; }
|
|
13
100
|
}
|
|
14
101
|
.error {
|
|
15
102
|
padding: 2rem;
|
|
@@ -18,16 +105,44 @@
|
|
|
18
105
|
border-radius: 8px;
|
|
19
106
|
background: #fef2f2;
|
|
20
107
|
color: #991b1b;
|
|
108
|
+
font-family: system-ui, sans-serif;
|
|
21
109
|
}
|
|
22
110
|
</style>
|
|
23
|
-
`,
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
111
|
+
`,u=`
|
|
112
|
+
<div class="widget-container">
|
|
113
|
+
|
|
114
|
+
<div class="skeleton" id="skeleton">
|
|
115
|
+
<div class="skeleton-header">
|
|
116
|
+
<div class="skeleton-title"></div>
|
|
117
|
+
<div class="skeleton-button"></div>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="skeleton-tabs">
|
|
120
|
+
<div class="skeleton-tab"></div>
|
|
121
|
+
<div class="skeleton-tab"></div>
|
|
122
|
+
<div class="skeleton-tab"></div>
|
|
123
|
+
</div>
|
|
124
|
+
<div class="skeleton-item">
|
|
125
|
+
<div class="skeleton-item-title"></div>
|
|
126
|
+
<div class="skeleton-item-desc"></div>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="skeleton-item">
|
|
129
|
+
<div class="skeleton-item-title"></div>
|
|
130
|
+
<div class="skeleton-item-desc"></div>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="skeleton-item">
|
|
133
|
+
<div class="skeleton-item-title"></div>
|
|
134
|
+
<div class="skeleton-item-desc"></div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<iframe
|
|
139
|
+
src="${n}"
|
|
140
|
+
title="Feature Requests"
|
|
141
|
+
loading="lazy"
|
|
142
|
+
allow="clipboard-write"
|
|
143
|
+
></iframe>
|
|
144
|
+
</div>
|
|
145
|
+
`;this.shadowRoot&&(this.shadowRoot.innerHTML=i+u,this.iframe=this.shadowRoot.querySelector("iframe"),this.skeleton=this.shadowRoot.querySelector("#skeleton"),this.setupLoadHandler(),this.setupMessageListener())}setupLoadHandler(){this.iframe&&(this.iframe.addEventListener("load",()=>{this.loaded=!0,this.iframe.classList.add("loaded"),this.skeleton&&this.skeleton.classList.add("hidden"),this.iframe.contentWindow&&this.iframe.contentWindow.postMessage({type:"USERVIBES_INIT"},this.baseUrl)}),this.iframe.addEventListener("error",()=>{this.renderError("Failed to load widget. Please try again later.")}),setTimeout(()=>{!this.loaded&&this.iframe&&(this.iframe.classList.add("loaded"),this.skeleton&&this.skeleton.classList.add("hidden"))},1e4))}renderError(e){let r=`
|
|
31
146
|
<style>
|
|
32
147
|
.error {
|
|
33
148
|
padding: 2rem;
|
|
@@ -36,9 +151,10 @@
|
|
|
36
151
|
border-radius: 8px;
|
|
37
152
|
background: #fef2f2;
|
|
38
153
|
color: #991b1b;
|
|
154
|
+
font-family: system-ui, sans-serif;
|
|
39
155
|
}
|
|
40
156
|
</style>
|
|
41
157
|
<div class="error">
|
|
42
|
-
<strong>Widget Error:</strong> ${e}
|
|
158
|
+
<strong>Widget Error:</strong> ${this.escapeHtml(e)}
|
|
43
159
|
</div>
|
|
44
|
-
`;this.shadowRoot&&(this.shadowRoot.innerHTML=t)}setupMessageListener(){window.addEventListener("message",e=>{e.origin===this.baseUrl&&(e.data.type==="USERVIBES_HEIGHT_UPDATE"&&this.iframe&&(this.iframe.style.height=`${e.data.height}px`),e.data.type==="USERVIBES_REQUEST_SUBMITTED"&&this.dispatchEvent(new CustomEvent("request-submitted",{detail:e.data.payload})),e.data.type==="USERVIBES_VOTE_ADDED"&&this.dispatchEvent(new CustomEvent("vote-added",{detail:e.data.payload})))})}};typeof window<"u"&&!customElements.get("uservibes-widget")&&customElements.define("uservibes-widget",
|
|
160
|
+
`;this.shadowRoot&&(this.shadowRoot.innerHTML=r)}escapeHtml(e){if(!e||typeof e!="string")return"";let t=document.createElement("div");return t.textContent=e,t.innerHTML}setupMessageListener(){window.addEventListener("message",e=>{e.origin===this.baseUrl&&(e.data.type==="USERVIBES_HEIGHT_UPDATE"&&this.iframe&&(this.iframe.style.height=`${e.data.height}px`),e.data.type==="USERVIBES_REQUEST_SUBMITTED"&&this.dispatchEvent(new CustomEvent("request-submitted",{detail:e.data.payload})),e.data.type==="USERVIBES_VOTE_ADDED"&&this.dispatchEvent(new CustomEvent("vote-added",{detail:e.data.payload})))})}setJWT(e){this.setAttribute("jwt",e)}};typeof window<"u"&&typeof customElements<"u"&&!customElements.get("uservibes-widget")&&customElements.define("uservibes-widget",c);function b(o){let{projectId:s,jwt:e,container:t,theme:r,height:d,mode:l,baseUrl:a}=o;if(!s)return console.error('[UserVibesOS] Missing required "projectId" in config'),null;if(!t)return console.error('[UserVibesOS] Missing required "container" in config'),null;let n=typeof t=="string"?document.querySelector(t):t;if(!n)return console.error(`[UserVibesOS] Container "${t}" not found`),null;let i=document.createElement("uservibes-widget");return i.setAttribute("project",s),e&&i.setAttribute("jwt",e),r&&i.setAttribute("theme",r),d&&i.setAttribute("height",d),l&&i.setAttribute("mode",l),a&&i.setAttribute("base-url",a),n.innerHTML="",n.appendChild(i),{element:i,setJWT:m=>i.setJWT(m),destroy:()=>n.removeChild(i)}}var h={init:b};var g=h;typeof window<"u"&&(window.UserVibesOS=h);export{h as UserVibesOS,c as UserVibesWidget,g as default,b as init};
|
package/package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uservibesos/web-component",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Feature request widget as a Web Component",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "dist/widget.js",
|
|
7
|
+
"module": "dist/widget.js",
|
|
8
|
+
"types": "dist/widget.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/widget.js",
|
|
12
|
+
"types": "./dist/widget.d.ts",
|
|
13
|
+
"default": "./dist/widget.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
6
19
|
"scripts": {
|
|
7
20
|
"build": "node build.js",
|
|
8
21
|
"dev": "node build.js --watch"
|
|
@@ -11,7 +24,8 @@
|
|
|
11
24
|
"feature-request",
|
|
12
25
|
"web-component",
|
|
13
26
|
"custom-element",
|
|
14
|
-
"widget"
|
|
27
|
+
"widget",
|
|
28
|
+
"uservibesos"
|
|
15
29
|
],
|
|
16
30
|
"author": "UserVibesOS",
|
|
17
31
|
"license": "MIT",
|
package/build.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
const esbuild = require('esbuild');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
|
|
5
|
-
const isWatch = process.argv.includes('--watch');
|
|
6
|
-
|
|
7
|
-
const buildOptions = {
|
|
8
|
-
entryPoints: ['src/widget.ts'],
|
|
9
|
-
bundle: true,
|
|
10
|
-
minify: !isWatch,
|
|
11
|
-
sourcemap: isWatch,
|
|
12
|
-
format: 'iife',
|
|
13
|
-
target: ['es2020'],
|
|
14
|
-
outfile: 'dist/widget.js',
|
|
15
|
-
platform: 'browser',
|
|
16
|
-
logLevel: 'info',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
async function build() {
|
|
20
|
-
try {
|
|
21
|
-
if (isWatch) {
|
|
22
|
-
const ctx = await esbuild.context(buildOptions);
|
|
23
|
-
await ctx.watch();
|
|
24
|
-
console.log('👀 Watching for changes...');
|
|
25
|
-
} else {
|
|
26
|
-
await esbuild.build(buildOptions);
|
|
27
|
-
|
|
28
|
-
// Also copy to public directory for CDN
|
|
29
|
-
const publicDir = path.join(__dirname, '../../public/widget-assets/v1.0.0');
|
|
30
|
-
if (!fs.existsSync(publicDir)) {
|
|
31
|
-
fs.mkdirSync(publicDir, { recursive: true });
|
|
32
|
-
}
|
|
33
|
-
fs.copyFileSync(
|
|
34
|
-
path.join(__dirname, 'dist/widget.js'),
|
|
35
|
-
path.join(publicDir, 'widget.js')
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
console.log('✅ Build complete!');
|
|
39
|
-
console.log('📦 Copied to public/widget-assets/v1.0.0/widget.js');
|
|
40
|
-
}
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.error('❌ Build failed:', error);
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
build();
|
package/src/widget.ts
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UserVibesOS Feature Request Widget - Web Component
|
|
3
|
-
*
|
|
4
|
-
* A custom element that embeds authenticated feature request widgets.
|
|
5
|
-
*
|
|
6
|
-
* TWO-TIER SECURITY MODEL:
|
|
7
|
-
*
|
|
8
|
-
* TIER 1: Public Keys (PK) - Recommended for most users
|
|
9
|
-
* - Origin enforcement + Rate limiting + Fingerprinting
|
|
10
|
-
* - Safe to use with server-side rendering
|
|
11
|
-
*
|
|
12
|
-
* TIER 2: JWT Tokens - Maximum security
|
|
13
|
-
* - Short-lived tokens (5min) + Backend proxy required
|
|
14
|
-
* - One-time use + Request signing
|
|
15
|
-
* - Auto-detected when no api-key is present
|
|
16
|
-
*
|
|
17
|
-
* ========================================
|
|
18
|
-
* TIER 1 USAGE (Public Keys - Recommended)
|
|
19
|
-
* ========================================
|
|
20
|
-
*
|
|
21
|
-
* Step 1: Create Public Key (PK) in UserVibesOS dashboard with allowed origins
|
|
22
|
-
* Step 2: Add to .env.local
|
|
23
|
-
* USERVIBES_PUBLIC_KEY=pk_live_your_public_key_here
|
|
24
|
-
*
|
|
25
|
-
* Step 3: Use in server component
|
|
26
|
-
* <script src="https://app.uservibesos.com/widget-assets/v1.0.0/widget.js"></script>
|
|
27
|
-
* <uservibes-widget
|
|
28
|
-
* project="my-project-slug"
|
|
29
|
-
* api-key={process.env.USERVIBES_PUBLIC_KEY}
|
|
30
|
-
* ></uservibes-widget>
|
|
31
|
-
*
|
|
32
|
-
* ========================================
|
|
33
|
-
* TIER 2 USAGE (Maximum Security - Auto-detected)
|
|
34
|
-
* ========================================
|
|
35
|
-
*
|
|
36
|
-
* Step 1: Enable Tier 2 mode in project settings
|
|
37
|
-
* Step 2: Implement backend proxy at /api/uservibes-proxy
|
|
38
|
-
* Step 3: Use widget WITHOUT api-key (auto-detects Tier 2)
|
|
39
|
-
*
|
|
40
|
-
* <script src="https://app.uservibesos.com/widget-assets/v1.0.0/widget.js"></script>
|
|
41
|
-
* <uservibes-widget
|
|
42
|
-
* project="my-project-slug"
|
|
43
|
-
* ></uservibes-widget>
|
|
44
|
-
*
|
|
45
|
-
* The widget automatically detects Tier 2 mode when:
|
|
46
|
-
* - No api-key attribute is present
|
|
47
|
-
* - Makes requests to /api/uservibes-proxy (standard path)
|
|
48
|
-
*
|
|
49
|
-
* Attributes:
|
|
50
|
-
* - project (required): Your project slug
|
|
51
|
-
* - api-key (Tier 1 only): Public Key from environment variables
|
|
52
|
-
* - mode (optional): "feature" or "roadmap" (default: "feature")
|
|
53
|
-
* - theme (optional): "light" or "dark" (default: "light")
|
|
54
|
-
* - height (optional): Height of the widget (default: "600px")
|
|
55
|
-
* - base-url (optional): Custom base URL (for development/self-hosting)
|
|
56
|
-
*
|
|
57
|
-
* DEPRECATED (for backward compatibility only):
|
|
58
|
-
* - jwt-mode: No longer needed, auto-detected
|
|
59
|
-
* - proxy-url: Uses standard /api/uservibes-proxy path
|
|
60
|
-
*
|
|
61
|
-
* SECURITY:
|
|
62
|
-
* - TIER 1: Store Public Key in environment variables
|
|
63
|
-
* - TIER 2: No keys in client code, all handled by proxy
|
|
64
|
-
* - NEVER commit .env files to version control
|
|
65
|
-
*/
|
|
66
|
-
|
|
67
|
-
class UserVibesWidget extends HTMLElement {
|
|
68
|
-
private iframe: HTMLIFrameElement | null = null;
|
|
69
|
-
private baseUrl: string = 'https://app.uservibesos.com';
|
|
70
|
-
|
|
71
|
-
constructor() {
|
|
72
|
-
super();
|
|
73
|
-
|
|
74
|
-
// Use Shadow DOM for style isolation
|
|
75
|
-
this.attachShadow({ mode: 'open' });
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
static get observedAttributes() {
|
|
79
|
-
return ['project', 'theme', 'height', 'mode', 'base-url', 'api-key', 'jwt-mode', 'proxy-url'];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
connectedCallback() {
|
|
83
|
-
this.render();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
|
|
87
|
-
if (oldValue !== newValue) {
|
|
88
|
-
this.render();
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
private render() {
|
|
93
|
-
const project = this.getAttribute('project');
|
|
94
|
-
const theme = this.getAttribute('theme') || 'light';
|
|
95
|
-
const height = this.getAttribute('height') || '600px';
|
|
96
|
-
const mode = this.getAttribute('mode') || 'feature';
|
|
97
|
-
const customBaseUrl = this.getAttribute('base-url');
|
|
98
|
-
const apiKey = this.getAttribute('api-key');
|
|
99
|
-
|
|
100
|
-
// For backward compatibility, check legacy jwt-mode and proxy-url attributes
|
|
101
|
-
const legacyJwtMode = this.getAttribute('jwt-mode') === 'true';
|
|
102
|
-
const legacyProxyUrl = this.getAttribute('proxy-url');
|
|
103
|
-
|
|
104
|
-
if (!project) {
|
|
105
|
-
this.renderError('Missing required "project" attribute');
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Use custom base URL if provided, otherwise use local URL in development
|
|
110
|
-
if (customBaseUrl) {
|
|
111
|
-
this.baseUrl = customBaseUrl;
|
|
112
|
-
} else if (typeof window !== 'undefined' && window.location.hostname === 'localhost') {
|
|
113
|
-
this.baseUrl = window.location.origin;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Build widget URL - ALWAYS points to UserVibesOS
|
|
117
|
-
let widgetUrl = `${this.baseUrl}/widget/${project}`;
|
|
118
|
-
|
|
119
|
-
// AUTO-DETECT SECURITY TIER
|
|
120
|
-
if (!apiKey && !legacyJwtMode) {
|
|
121
|
-
// TIER 2: Auto-detected - No api-key means proxy mode
|
|
122
|
-
// Build absolute proxy URL from current page origin
|
|
123
|
-
const standardProxyPath = '/api/uservibes-proxy';
|
|
124
|
-
const absoluteProxyUrl = typeof window !== 'undefined'
|
|
125
|
-
? `${window.location.origin}${standardProxyPath}`
|
|
126
|
-
: standardProxyPath;
|
|
127
|
-
widgetUrl += `?tier=2&proxyUrl=${encodeURIComponent(absoluteProxyUrl)}&mode=${mode}`;
|
|
128
|
-
console.log('[Web Component] TIER 2 auto-detected (no api-key) - using proxy:', absoluteProxyUrl);
|
|
129
|
-
} else if (legacyJwtMode && legacyProxyUrl) {
|
|
130
|
-
// LEGACY: Explicit JWT mode with custom proxy URL (backward compatibility)
|
|
131
|
-
widgetUrl += `?tier=2&proxyUrl=${encodeURIComponent(legacyProxyUrl)}&mode=${mode}`;
|
|
132
|
-
console.log('[Web Component] TIER 2 legacy mode - using custom proxy:', legacyProxyUrl);
|
|
133
|
-
} else if (apiKey) {
|
|
134
|
-
// TIER 1: Public key mode - pass key directly
|
|
135
|
-
widgetUrl += `?publicKey=${encodeURIComponent(apiKey)}&mode=${mode}`;
|
|
136
|
-
console.log('[Web Component] TIER 1 mode - using public key');
|
|
137
|
-
} else {
|
|
138
|
-
// No valid configuration found
|
|
139
|
-
this.renderError('Widget requires either an "api-key" attribute for Tier 1 security, or no api-key for Tier 2 security (proxy mode). For Tier 2, ensure you have implemented the proxy at /api/uservibes-proxy.');
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const styles = `
|
|
144
|
-
<style>
|
|
145
|
-
:host {
|
|
146
|
-
display: block;
|
|
147
|
-
width: 100%;
|
|
148
|
-
}
|
|
149
|
-
iframe {
|
|
150
|
-
width: 100%;
|
|
151
|
-
height: ${height};
|
|
152
|
-
border: none;
|
|
153
|
-
border-radius: 8px;
|
|
154
|
-
overflow: hidden;
|
|
155
|
-
}
|
|
156
|
-
.error {
|
|
157
|
-
padding: 2rem;
|
|
158
|
-
text-align: center;
|
|
159
|
-
border: 1px solid #ef4444;
|
|
160
|
-
border-radius: 8px;
|
|
161
|
-
background: #fef2f2;
|
|
162
|
-
color: #991b1b;
|
|
163
|
-
}
|
|
164
|
-
</style>
|
|
165
|
-
`;
|
|
166
|
-
|
|
167
|
-
const iframe = `
|
|
168
|
-
<iframe
|
|
169
|
-
src="${widgetUrl}"
|
|
170
|
-
title="${project} Feature Requests"
|
|
171
|
-
loading="lazy"
|
|
172
|
-
allow="clipboard-write"
|
|
173
|
-
></iframe>
|
|
174
|
-
`;
|
|
175
|
-
|
|
176
|
-
if (this.shadowRoot) {
|
|
177
|
-
this.shadowRoot.innerHTML = styles + iframe;
|
|
178
|
-
this.iframe = this.shadowRoot.querySelector('iframe');
|
|
179
|
-
|
|
180
|
-
// Listen for messages from iframe (for height adjustment)
|
|
181
|
-
this.setupMessageListener();
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
private renderError(message: string) {
|
|
186
|
-
const html = `
|
|
187
|
-
<style>
|
|
188
|
-
.error {
|
|
189
|
-
padding: 2rem;
|
|
190
|
-
text-align: center;
|
|
191
|
-
border: 1px solid #ef4444;
|
|
192
|
-
border-radius: 8px;
|
|
193
|
-
background: #fef2f2;
|
|
194
|
-
color: #991b1b;
|
|
195
|
-
}
|
|
196
|
-
</style>
|
|
197
|
-
<div class="error">
|
|
198
|
-
<strong>Widget Error:</strong> ${message}
|
|
199
|
-
</div>
|
|
200
|
-
`;
|
|
201
|
-
|
|
202
|
-
if (this.shadowRoot) {
|
|
203
|
-
this.shadowRoot.innerHTML = html;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
private setupMessageListener() {
|
|
208
|
-
window.addEventListener('message', (event) => {
|
|
209
|
-
// Verify origin for security
|
|
210
|
-
if (event.origin !== this.baseUrl) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Handle height updates from iframe
|
|
215
|
-
if (event.data.type === 'USERVIBES_HEIGHT_UPDATE' && this.iframe) {
|
|
216
|
-
this.iframe.style.height = `${event.data.height}px`;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Dispatch custom events for parent page
|
|
220
|
-
if (event.data.type === 'USERVIBES_REQUEST_SUBMITTED') {
|
|
221
|
-
this.dispatchEvent(
|
|
222
|
-
new CustomEvent('request-submitted', {
|
|
223
|
-
detail: event.data.payload,
|
|
224
|
-
})
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (event.data.type === 'USERVIBES_VOTE_ADDED') {
|
|
229
|
-
this.dispatchEvent(
|
|
230
|
-
new CustomEvent('vote-added', {
|
|
231
|
-
detail: event.data.payload,
|
|
232
|
-
})
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Register the custom element
|
|
240
|
-
if (typeof window !== 'undefined' && !customElements.get('uservibes-widget')) {
|
|
241
|
-
customElements.define('uservibes-widget', UserVibesWidget);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
export default UserVibesWidget;
|