webhanger 1.0.8 → 1.1.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 +599 -313
- package/bin/cli.js +304 -7
- package/core/devServer.js +337 -0
- package/helper/analyzer.js +32 -5
- package/helper/authConfig.js +68 -0
- package/helper/bundler.js +3 -2
- package/helper/dbHandler.js +11 -4
- package/helper/oauthHandler.js +149 -0
- package/helper/personalization.js +138 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,34 +1,32 @@
|
|
|
1
1
|
# WebHanger
|
|
2
2
|
|
|
3
|
-
> **Component-as-a-Service (CaaS)** — Bundle once.
|
|
3
|
+
> **Component-as-a-Service (CaaS)** — Bundle once. AES-256 encrypt. Deploy to edge CDN. Load anywhere with zero code.
|
|
4
4
|
|
|
5
|
-
WebHanger is a secure, edge-delivered component distribution platform.
|
|
5
|
+
WebHanger is a secure, edge-delivered component distribution platform. Deploy encrypted UI components to a CDN and load them into any website or framework with a single tag — no tokens in HTML, no exposed secrets, no configuration.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
## Packages
|
|
10
11
|
|
|
11
12
|
| Package | Install | Description |
|
|
12
13
|
|---|---|---|
|
|
13
14
|
| `webhanger` | `npm install -g webhanger` | CLI + Node.js library |
|
|
14
|
-
| `webhanger-front` | `npm install webhanger-front` | Browser SDK |
|
|
15
|
+
| `webhanger-front` | `npm install webhanger-front` | Browser + ESM SDK |
|
|
16
|
+
| `webhanger-admin` | `npm install webhanger-admin` | Admin SDK + dashboard |
|
|
17
|
+
| `webhanger-auth` | `npm install webhanger-auth` | OAuth authentication SDK |
|
|
15
18
|
|
|
16
19
|
---
|
|
17
20
|
|
|
18
21
|
## Quick Start
|
|
19
22
|
|
|
20
23
|
```bash
|
|
21
|
-
# 1. Install CLI
|
|
22
24
|
npm install -g webhanger
|
|
23
|
-
|
|
24
|
-
# 2. Setup your project (provisions S3 + CloudFront automatically)
|
|
25
25
|
wh init
|
|
26
|
-
|
|
27
|
-
# 3. Deploy all components, build site, zip for upload — one command
|
|
28
|
-
wh ship ./components ./docs 1.0.0 ./dist
|
|
26
|
+
wh ship ./components ./site 1.0.0 ./dist
|
|
29
27
|
```
|
|
30
28
|
|
|
31
|
-
Load in any
|
|
29
|
+
Load in any HTML:
|
|
32
30
|
|
|
33
31
|
```html
|
|
34
32
|
<script src="https://unpkg.com/webhanger-front@latest/browser.min.js"></script>
|
|
@@ -36,7 +34,17 @@ Load in any website — zero JS required:
|
|
|
36
34
|
|
|
37
35
|
<wh-component name="navbar"></wh-component>
|
|
38
36
|
<wh-component name="hero"></wh-component>
|
|
39
|
-
<wh-component name="footer"></wh-component>
|
|
37
|
+
<wh-component name="footer" sandbox></wh-component>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Load in Next.js / React / Vite:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
import { load } from "webhanger-front";
|
|
44
|
+
|
|
45
|
+
const manifest = await fetch("/wh-manifest.json").then(r => r.json());
|
|
46
|
+
const c = manifest.components["navbar"];
|
|
47
|
+
await load(c.urls || c.url, manifest.pid, c.token, c.expires, "#nav-mount");
|
|
40
48
|
```
|
|
41
49
|
|
|
42
50
|
---
|
|
@@ -44,177 +52,105 @@ Load in any website — zero JS required:
|
|
|
44
52
|
## CLI Reference
|
|
45
53
|
|
|
46
54
|
### `wh init`
|
|
55
|
+
Interactive setup. Provisions S3 bucket + CloudFront automatically. Supports Firebase, Supabase, MongoDB. Optional Cloudflare Edge Worker setup.
|
|
47
56
|
|
|
48
|
-
|
|
57
|
+
### `wh dev` ⭐ Development server
|
|
58
|
+
Local dev server with hot reload, live preview, and manifest auto-refresh.
|
|
49
59
|
|
|
50
60
|
```bash
|
|
51
|
-
wh
|
|
61
|
+
wh dev ./components 4242
|
|
52
62
|
```
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
- Project name
|
|
56
|
-
- Storage provider: `s3` | `r2` | `minio` | `local`
|
|
57
|
-
- Database provider: `firebase` | `supabase` | `mongodb`
|
|
58
|
-
- Credentials
|
|
59
|
-
|
|
60
|
-
For **S3**: automatically creates the bucket, configures CORS + versioning, spins up a CloudFront distribution. No AWS Console needed.
|
|
61
|
-
|
|
62
|
-
Optional: setup Cloudflare Edge Worker for token validation + geo routing at the edge.
|
|
63
|
-
|
|
64
|
-
Generates `webhanger.config.json`.
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
### `wh ship` ⭐ The main command
|
|
69
|
-
|
|
70
|
-
Deploy + build + zip in one shot.
|
|
64
|
+
Edit any component file → auto-deploys in 300ms → browser hot-reloads. Open `http://localhost:4242` for live preview with a dev status bar at the bottom.
|
|
71
65
|
|
|
72
66
|
```bash
|
|
73
|
-
|
|
67
|
+
# Custom port + manifest output
|
|
68
|
+
wh dev ./components 3000 ./public/wh-manifest.json
|
|
74
69
|
```
|
|
75
70
|
|
|
71
|
+
### `wh ship` ⭐
|
|
72
|
+
Deploy + build + zip in one shot.
|
|
76
73
|
```bash
|
|
77
|
-
wh ship ./components ./
|
|
74
|
+
wh ship ./components ./site 1.0.0 ./dist
|
|
78
75
|
```
|
|
79
|
-
|
|
80
|
-
What it does:
|
|
81
|
-
1. Deploys all components (bundle → AES encrypt → upload → sign → register)
|
|
76
|
+
1. Deploys all components (bundle → AES-256 encrypt → upload → HMAC sign → register)
|
|
82
77
|
2. Resolves dependency graph
|
|
83
|
-
3. Writes `wh-manifest.json`
|
|
84
|
-
4. Production builds the site
|
|
85
|
-
5. Zips
|
|
86
|
-
|
|
87
|
-
```
|
|
88
|
-
🚀 [1/4] Deploying components...
|
|
89
|
-
navbar@1.0.0... ✅
|
|
90
|
-
hero@1.0.0... ✅
|
|
91
|
-
|
|
92
|
-
🔍 [2/4] Resolving dependency graph...
|
|
93
|
-
|
|
94
|
-
🏗️ [3/4] Building ./docs → ./dist...
|
|
95
|
-
index.html 4.2 kB
|
|
96
|
-
|
|
97
|
-
📦 [4/4] Zipping ./dist...
|
|
98
|
-
|
|
99
|
-
✅ Ship complete!
|
|
100
|
-
Deploy zip: ./deploy.zip (12.4 kB)
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Upload `deploy.zip` to Netlify, S3, cPanel, or any static host.
|
|
104
|
-
|
|
105
|
-
---
|
|
78
|
+
3. Writes `wh-manifest.json`
|
|
79
|
+
4. Production builds the site
|
|
80
|
+
5. Zips for upload
|
|
106
81
|
|
|
107
82
|
### `wh deploy`
|
|
108
|
-
|
|
109
|
-
Deploy a single component.
|
|
110
|
-
|
|
111
83
|
```bash
|
|
112
|
-
wh deploy <component-dir> <name> <version>
|
|
113
84
|
wh deploy ./components/navbar navbar 1.0.0
|
|
114
85
|
```
|
|
115
86
|
|
|
116
|
-
Prompts for token (auto-generate or custom) and expiry (never or seconds).
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
87
|
### `wh graph-deploy`
|
|
121
|
-
|
|
122
|
-
Deploy all components in a directory and resolve the dependency graph.
|
|
123
|
-
|
|
124
88
|
```bash
|
|
125
|
-
wh graph-deploy <components-dir> [version] [out-dir]
|
|
126
89
|
wh graph-deploy ./components 1.0.0 ./output
|
|
127
90
|
```
|
|
128
91
|
|
|
129
|
-
|
|
92
|
+
### `wh atomize`
|
|
93
|
+
Split a single HTML page into CDN-powered components.
|
|
94
|
+
```bash
|
|
95
|
+
wh atomize ./docs/index.html ./atomized 1.0.0
|
|
96
|
+
```
|
|
130
97
|
|
|
131
98
|
### `wh build`
|
|
132
|
-
|
|
133
|
-
Production build — minifies HTML, inlines local CSS/JS.
|
|
134
|
-
|
|
99
|
+
Production build — minifies HTML, extracts CSS/JS to hashed files.
|
|
135
100
|
```bash
|
|
136
|
-
wh build
|
|
137
|
-
wh build ./docs ./dist
|
|
101
|
+
wh build ./site ./dist
|
|
138
102
|
```
|
|
139
103
|
|
|
140
|
-
---
|
|
141
|
-
|
|
142
104
|
### `wh zip`
|
|
143
|
-
|
|
144
|
-
Zip a directory for deployment.
|
|
145
|
-
|
|
146
105
|
```bash
|
|
147
|
-
wh zip <src-dir> [out-file]
|
|
148
106
|
wh zip ./dist ./deploy.zip
|
|
149
107
|
```
|
|
150
108
|
|
|
151
|
-
---
|
|
152
|
-
|
|
153
109
|
### `wh analyze`
|
|
154
|
-
|
|
155
|
-
Detect framework, styling approach, and CDN dependencies automatically.
|
|
156
|
-
|
|
157
110
|
```bash
|
|
158
111
|
wh analyze ./components/navbar
|
|
159
112
|
```
|
|
160
113
|
|
|
161
|
-
```
|
|
162
|
-
🔍 Component Analysis
|
|
163
|
-
|
|
164
|
-
Framework : vanilla
|
|
165
|
-
Styling : tailwind
|
|
166
|
-
Imports : gsap, axios
|
|
167
|
-
CDN Assets:
|
|
168
|
-
[script] https://cdn.tailwindcss.com
|
|
169
|
-
[script] https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
114
|
### `wh convert`
|
|
175
|
-
|
|
176
|
-
Convert a vanilla HTML/CSS/JS component to any framework.
|
|
177
|
-
|
|
178
115
|
```bash
|
|
179
|
-
wh convert <dir> <name> <target> [out-dir]
|
|
180
116
|
wh convert ./components/navbar navbar react ./output
|
|
117
|
+
# targets: react | vue | svelte | next | angular | astro
|
|
181
118
|
```
|
|
182
119
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
120
|
+
### `wh breakdown`
|
|
121
|
+
Extract embedded CSS/JS from a single HTML file.
|
|
122
|
+
```bash
|
|
123
|
+
wh breakdown ./components/navbar
|
|
124
|
+
```
|
|
186
125
|
|
|
187
126
|
### `wh access`
|
|
188
|
-
|
|
189
|
-
Role-based access control for teams.
|
|
190
|
-
|
|
191
127
|
```bash
|
|
192
|
-
wh access grant
|
|
193
|
-
wh access revoke <key>
|
|
194
|
-
wh access list
|
|
128
|
+
wh access grant
|
|
129
|
+
wh access revoke <key>
|
|
130
|
+
wh access list
|
|
195
131
|
```
|
|
196
132
|
|
|
197
|
-
Roles:
|
|
198
|
-
|
|
199
|
-
| Role | deploy | read | delete | manage_access |
|
|
200
|
-
|---|---|---|---|---|
|
|
201
|
-
| owner | ✅ | ✅ | ✅ | ✅ |
|
|
202
|
-
| admin | ✅ | ✅ | ✅ | ✅ |
|
|
203
|
-
| deployer | ✅ | ✅ | ❌ | ❌ |
|
|
204
|
-
| viewer | ❌ | ✅ | ❌ | ❌ |
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
133
|
### `wh edge-init`
|
|
209
|
-
|
|
210
|
-
Setup Cloudflare Edge Worker for production-grade delivery.
|
|
211
|
-
|
|
212
134
|
```bash
|
|
213
135
|
wh edge-init
|
|
214
|
-
# then:
|
|
215
136
|
cd edge && wrangler deploy
|
|
216
137
|
```
|
|
217
138
|
|
|
139
|
+
### `wh auth init`
|
|
140
|
+
Interactive OAuth setup — Google, GitHub, Facebook.
|
|
141
|
+
```bash
|
|
142
|
+
wh auth init
|
|
143
|
+
```
|
|
144
|
+
Prompts for providers, base URL, client IDs/secrets. Generates `wh-auth.config.json` with callback URLs.
|
|
145
|
+
|
|
146
|
+
### `wh auth serve`
|
|
147
|
+
Start the OAuth callback server.
|
|
148
|
+
```bash
|
|
149
|
+
wh auth serve
|
|
150
|
+
wh auth serve 3001 # custom port
|
|
151
|
+
```
|
|
152
|
+
Handles OAuth redirects, token exchange, JWT issuance.
|
|
153
|
+
|
|
218
154
|
---
|
|
219
155
|
|
|
220
156
|
## Component Structure
|
|
@@ -222,216 +158,546 @@ cd edge && wrangler deploy
|
|
|
222
158
|
```
|
|
223
159
|
components/
|
|
224
160
|
navbar/
|
|
225
|
-
index.html
|
|
226
|
-
style.css
|
|
227
|
-
script.js
|
|
228
|
-
webhanger.component.json
|
|
161
|
+
index.html
|
|
162
|
+
style.css
|
|
163
|
+
script.js
|
|
164
|
+
webhanger.component.json
|
|
229
165
|
```
|
|
230
166
|
|
|
231
167
|
### `webhanger.component.json`
|
|
232
|
-
|
|
233
168
|
```json
|
|
234
169
|
{
|
|
235
170
|
"assets": [
|
|
236
|
-
{ "type": "script", "url": "https://cdn.tailwindcss.com" }
|
|
237
|
-
{ "type": "script", "url": "https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js" }
|
|
171
|
+
{ "type": "script", "url": "https://cdn.tailwindcss.com" }
|
|
238
172
|
],
|
|
239
|
-
"dependencies": ["
|
|
173
|
+
"dependencies": ["chart@1.0.0"],
|
|
174
|
+
"props": {
|
|
175
|
+
"brand": { "type": "string", "default": "MyApp" },
|
|
176
|
+
"ctaText": { "type": "string", "default": "Get Started" },
|
|
177
|
+
"ctaHref": { "type": "string", "default": "/signup" }
|
|
178
|
+
}
|
|
240
179
|
}
|
|
241
180
|
```
|
|
242
181
|
|
|
243
|
-
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Component Props System
|
|
185
|
+
|
|
186
|
+
Pass dynamic data into components via HTML attributes — no redeployment needed.
|
|
187
|
+
|
|
188
|
+
### In your component HTML, use `{{wh.propName}}` placeholders:
|
|
189
|
+
```html
|
|
190
|
+
<!-- index.html -->
|
|
191
|
+
<nav>
|
|
192
|
+
<span class="brand">{{wh.brand}}</span>
|
|
193
|
+
<a href="{{wh.ctaHref}}" class="cta">{{wh.ctaText}}</a>
|
|
194
|
+
</nav>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Pass props via attributes:
|
|
198
|
+
```html
|
|
199
|
+
<wh-component
|
|
200
|
+
name="navbar"
|
|
201
|
+
wh-brand="Acme Corp"
|
|
202
|
+
wh-cta-text="Sign Up Free"
|
|
203
|
+
wh-cta-href="/register">
|
|
204
|
+
</wh-component>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Or programmatically:
|
|
208
|
+
```js
|
|
209
|
+
await load(url, pid, token, 0, "#mount", null, [], {
|
|
210
|
+
props: {
|
|
211
|
+
brand: "Acme Corp",
|
|
212
|
+
ctaText: "Sign Up Free",
|
|
213
|
+
ctaHref: "/register"
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Props are resolved **after decryption** — the encrypted payload on CDN contains `{{wh.brand}}` literally. The SDK substitutes values in memory before DOM injection. Defaults from `webhanger.component.json` are used when no prop is passed.
|
|
244
219
|
|
|
245
220
|
---
|
|
246
221
|
|
|
247
|
-
##
|
|
222
|
+
## Security
|
|
248
223
|
|
|
249
|
-
|
|
224
|
+
### AES-256-GCM Encryption
|
|
225
|
+
```
|
|
226
|
+
Key = SHA-256(projectId + salt)
|
|
227
|
+
Payload = iv:tag:ciphertext (base64)
|
|
228
|
+
Salts = "::html" | "::css" | "::js"
|
|
229
|
+
```
|
|
250
230
|
|
|
231
|
+
### HMAC-SHA256 Signed URLs
|
|
251
232
|
```
|
|
252
|
-
|
|
253
|
-
├── navbar@1.0.0
|
|
254
|
-
└── statsbar@1.0.0
|
|
255
|
-
└── chart@1.0.0
|
|
233
|
+
token = HMAC-SHA256(projectId:path:expires, secretKey)
|
|
256
234
|
```
|
|
257
235
|
|
|
258
|
-
|
|
259
|
-
-
|
|
260
|
-
- Deps load before parent component
|
|
261
|
-
- Stored in Firestore per project
|
|
236
|
+
### Integrity Check
|
|
237
|
+
SHA-256 hash verified after decryption — detects tampering.
|
|
262
238
|
|
|
263
|
-
|
|
264
|
-
|
|
239
|
+
### Domain Restriction
|
|
240
|
+
```js
|
|
241
|
+
load(url, pid, token, 0, "[data-wh]", null, [], {
|
|
242
|
+
allowedDomains: ["mysite.com"]
|
|
243
|
+
});
|
|
265
244
|
```
|
|
266
245
|
|
|
267
|
-
|
|
246
|
+
### Manifest-based Delivery
|
|
247
|
+
Tokens, projectId, CDN URLs never in HTML. Fetched at runtime from `wh-manifest.json`.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Dependency Graph
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
dashboard@1.0.0
|
|
255
|
+
├── navbar@1.0.0
|
|
256
|
+
└── statsbar@1.0.0
|
|
257
|
+
└── chart@1.0.0
|
|
258
|
+
```
|
|
268
259
|
|
|
269
260
|
```js
|
|
270
261
|
import { resolveGraph } from "webhanger";
|
|
271
262
|
const graph = await resolveGraph(config.db, projectId, "dashboard", "1.0.0");
|
|
272
|
-
// Returns: [chart, navbar, statsbar, dashboard]
|
|
263
|
+
// Returns: [chart, navbar, statsbar, dashboard]
|
|
273
264
|
```
|
|
274
265
|
|
|
275
266
|
---
|
|
276
267
|
|
|
277
|
-
##
|
|
268
|
+
## Multi-CDN Failover
|
|
278
269
|
|
|
279
|
-
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"cdn": {
|
|
273
|
+
"url": "https://primary.cloudfront.net",
|
|
274
|
+
"fallbacks": ["https://fallback.r2.dev"]
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Edge Worker (Cloudflare Workers)
|
|
282
|
+
|
|
283
|
+
- HMAC token validation at edge
|
|
284
|
+
- Version resolution (`latest` → `1.2.0`)
|
|
285
|
+
- Geo-based routing
|
|
286
|
+
- Rate limiting (100 req/min per IP)
|
|
280
287
|
|
|
281
|
-
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Edge Personalization
|
|
291
|
+
|
|
292
|
+
Serve different component variants to different users based on rules — country, device, role, A/B test, subscription plan — all resolved client-side or at the Cloudflare edge.
|
|
293
|
+
|
|
294
|
+
### Setup
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
# Scaffold rules config
|
|
298
|
+
wh personalize init
|
|
282
299
|
|
|
300
|
+
# Test rule resolution (slot, config, country, device, role)
|
|
301
|
+
wh personalize test ./wh-personalization.json IN mobile premium
|
|
302
|
+
# Output:
|
|
303
|
+
# Context: country=IN device=mobile role=premium
|
|
304
|
+
# Resolved: hero → hero-india (first matching rule: country=IN)
|
|
283
305
|
```
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
306
|
+
|
|
307
|
+
### `wh-personalization.json`
|
|
308
|
+
|
|
309
|
+
> **Key rule:** The slot key (e.g. `"hero"`) **must exactly match** the `name` attribute on `<wh-component name="hero">`. The SDK uses the component name as the slot lookup key.
|
|
310
|
+
|
|
311
|
+
```json
|
|
312
|
+
{
|
|
313
|
+
"hero": {
|
|
314
|
+
"rules": [
|
|
315
|
+
{ "if": { "country": "IN" }, "component": "hero-india" },
|
|
316
|
+
{ "if": { "country": "US" }, "component": "hero-us" },
|
|
317
|
+
{ "if": { "device": "mobile" }, "component": "hero-mobile" },
|
|
318
|
+
{ "if": { "role": "premium" }, "component": "hero-premium" },
|
|
319
|
+
{ "if": { "abTest": "variant-b" }, "component": "hero-variant-b"},
|
|
320
|
+
{ "if": { "hour": { "min": 9, "max": 17 } }, "component": "hero-business" }
|
|
321
|
+
],
|
|
322
|
+
"default": "hero"
|
|
323
|
+
},
|
|
324
|
+
"navbar": {
|
|
325
|
+
"rules": [
|
|
326
|
+
{ "if": { "role": "admin" }, "component": "navbar-admin" },
|
|
327
|
+
{ "if": { "plan": "premium" }, "component": "navbar-premium" }
|
|
328
|
+
],
|
|
329
|
+
"default": "navbar"
|
|
330
|
+
}
|
|
331
|
+
}
|
|
287
332
|
```
|
|
288
333
|
|
|
289
|
-
###
|
|
334
|
+
### Zero-code usage
|
|
335
|
+
|
|
336
|
+
```html
|
|
337
|
+
<script src="https://unpkg.com/webhanger-front@latest/browser.min.js"></script>
|
|
338
|
+
<script>
|
|
339
|
+
// Load rules first, then initialize
|
|
340
|
+
WebHangerFront.loadPersonalization("./wh-personalization.json");
|
|
341
|
+
WebHangerFront.initialize("./wh-manifest.json");
|
|
342
|
+
</script>
|
|
290
343
|
|
|
344
|
+
<!-- personalize attribute enables rule resolution -->
|
|
345
|
+
<wh-component name="hero" personalize></wh-component>
|
|
346
|
+
<wh-component name="navbar" personalize></wh-component>
|
|
291
347
|
```
|
|
292
|
-
|
|
348
|
+
|
|
349
|
+
### Programmatic
|
|
350
|
+
|
|
351
|
+
```js
|
|
352
|
+
import { load, loadPersonalization } from "webhanger-front";
|
|
353
|
+
|
|
354
|
+
await loadPersonalization("./wh-personalization.json");
|
|
355
|
+
|
|
356
|
+
// Override context for testing
|
|
357
|
+
window._wh_ctx_override = { country: "IN", role: "premium" };
|
|
358
|
+
|
|
359
|
+
// Component resolves to hero-india (first matching rule)
|
|
360
|
+
await load(manifest.components["hero"].url, pid, token, 0, "#hero");
|
|
293
361
|
```
|
|
294
362
|
|
|
295
|
-
###
|
|
363
|
+
### Rule conditions
|
|
296
364
|
|
|
297
|
-
|
|
365
|
+
| Condition | Source | Example |
|
|
366
|
+
|---|---|---|
|
|
367
|
+
| `country` | `window._wh_ctx_override` or IP (edge) | `"IN"`, `["IN","PK"]` |
|
|
368
|
+
| `device` | User-Agent | `"mobile"`, `"tablet"`, `"desktop"` |
|
|
369
|
+
| `role` | JWT from `webhanger-auth` | `"premium"`, `"admin"` |
|
|
370
|
+
| `abTest` | localStorage bucket (stable per user) | `"variant-a"`, `"variant-b"` |
|
|
371
|
+
| `lang` | `navigator.language` | `"en"`, `"hi"` |
|
|
372
|
+
| `hour` | time of day | `{ "min": 9, "max": 17 }` |
|
|
373
|
+
| `plan` | localStorage `wh_plan` | `"free"`, `"premium"` |
|
|
298
374
|
|
|
299
|
-
|
|
375
|
+
Rules are evaluated top-to-bottom — first match wins. Falls back to `default` if no rule matches.
|
|
376
|
+
|
|
377
|
+
### Events
|
|
300
378
|
|
|
301
379
|
```js
|
|
302
|
-
WebHangerFront.
|
|
303
|
-
|
|
380
|
+
WebHangerFront.on("personalized", ({ slot, resolved, ctx }) => {
|
|
381
|
+
console.log(`${slot} → ${resolved} (country: ${ctx.country})`);
|
|
304
382
|
});
|
|
305
383
|
```
|
|
306
384
|
|
|
307
|
-
###
|
|
385
|
+
### A/B Testing
|
|
386
|
+
|
|
387
|
+
Each user is automatically assigned a stable bucket (`variant-a` or `variant-b`) stored in localStorage. 50/50 split by default.
|
|
308
388
|
|
|
309
389
|
```js
|
|
310
|
-
//
|
|
311
|
-
|
|
390
|
+
// Check current bucket
|
|
391
|
+
console.log(localStorage.getItem("wh_ab_bucket")); // "variant-a" or "variant-b"
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Edge resolution (Cloudflare Workers)
|
|
395
|
+
|
|
396
|
+
When using `wh edge-init`, the worker resolves personalization server-side using Cloudflare headers — no client-side JS needed:
|
|
312
397
|
|
|
313
|
-
|
|
314
|
-
|
|
398
|
+
```
|
|
399
|
+
CF-IPCountry: IN → worker resolves hero-india → serves encrypted component
|
|
315
400
|
```
|
|
316
401
|
|
|
317
|
-
|
|
402
|
+
Store rules in KV:
|
|
403
|
+
```bash
|
|
404
|
+
wrangler kv:key put --binding=WH_VERSIONS "personalization:hero" '{"rules":[...],"default":"hero"}'
|
|
405
|
+
```
|
|
318
406
|
|
|
319
|
-
|
|
407
|
+
### Test it
|
|
320
408
|
|
|
321
|
-
|
|
409
|
+
```bash
|
|
410
|
+
node personalization-test/deploy.js
|
|
411
|
+
npx serve personalization-test/site
|
|
412
|
+
```
|
|
322
413
|
|
|
323
|
-
|
|
414
|
+
Open `http://localhost:3000` — use the simulator buttons to switch country, device, role, and A/B bucket. Watch the hero component change in real time.
|
|
324
415
|
|
|
325
|
-
|
|
416
|
+
---
|
|
326
417
|
|
|
327
|
-
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
- Rate limiting (100 req/min per IP)
|
|
418
|
+
Drop-in OAuth for any website. Supports Google, GitHub, Facebook. Zero backend code required beyond `wh auth serve`.
|
|
419
|
+
|
|
420
|
+
### Setup
|
|
331
421
|
|
|
332
422
|
```bash
|
|
333
|
-
|
|
334
|
-
|
|
423
|
+
npm install -g webhanger # CLI
|
|
424
|
+
npm install webhanger-auth # browser SDK
|
|
425
|
+
|
|
426
|
+
wh auth init # configure providers
|
|
427
|
+
wh auth serve # start OAuth server (default port 3001)
|
|
335
428
|
```
|
|
336
429
|
|
|
337
|
-
|
|
430
|
+
### Zero-code HTML
|
|
338
431
|
|
|
339
|
-
|
|
432
|
+
```html
|
|
433
|
+
<script src="https://unpkg.com/webhanger-auth/browser.min.js"></script>
|
|
340
434
|
|
|
341
|
-
|
|
435
|
+
<!-- Renders a styled OAuth button, handles the full flow -->
|
|
436
|
+
<wh-auth
|
|
437
|
+
provider="google"
|
|
438
|
+
on-success="/dashboard?name={{name}}&email={{email}}"
|
|
439
|
+
on-error="/login?error={{message}}"
|
|
440
|
+
theme="dark">
|
|
441
|
+
</wh-auth>
|
|
442
|
+
|
|
443
|
+
<wh-auth provider="github" on-success="/dashboard"></wh-auth>
|
|
444
|
+
<wh-auth provider="facebook" on-success="/dashboard"></wh-auth>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Available `on-success` template variables: `{{name}}` `{{email}}` `{{avatar}}` `{{provider}}` `{{id}}`
|
|
448
|
+
|
|
449
|
+
### Programmatic
|
|
450
|
+
|
|
451
|
+
```js
|
|
452
|
+
// Login
|
|
453
|
+
WHAuth.login("google", {
|
|
454
|
+
onSuccess: (user) => {
|
|
455
|
+
console.log(user.email, user.name, user.avatar, user.provider);
|
|
456
|
+
redirect("/dashboard");
|
|
457
|
+
},
|
|
458
|
+
onError: (err) => showError(err.message)
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Check session
|
|
462
|
+
WHAuth.isLoggedIn(); // true/false
|
|
463
|
+
WHAuth.getUser(); // { email, name, avatar, provider, id }
|
|
464
|
+
WHAuth.getToken(); // JWT string
|
|
465
|
+
|
|
466
|
+
// Logout
|
|
467
|
+
WHAuth.logout();
|
|
468
|
+
|
|
469
|
+
// Events
|
|
470
|
+
WHAuth.on("success", ({ user, token }) => analytics.track("login", user));
|
|
471
|
+
WHAuth.on("error", ({ message }) => errorTracker.capture(message));
|
|
472
|
+
WHAuth.on("logout", () => clearSession());
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### `wh-auth.config.json` (generated by `wh auth init`)
|
|
342
476
|
|
|
343
477
|
```json
|
|
344
478
|
{
|
|
345
|
-
"
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
479
|
+
"baseUrl": "https://myapp.com",
|
|
480
|
+
"callbackPath": "/auth/callback",
|
|
481
|
+
"port": 3001,
|
|
482
|
+
"providers": {
|
|
483
|
+
"google": {
|
|
484
|
+
"clientId": "...",
|
|
485
|
+
"clientSecret": "...",
|
|
486
|
+
"callbackUrl": "https://myapp.com/auth/callback/google"
|
|
487
|
+
},
|
|
488
|
+
"github": {
|
|
489
|
+
"clientId": "...",
|
|
490
|
+
"clientSecret": "...",
|
|
491
|
+
"callbackUrl": "https://myapp.com/auth/callback/github"
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
"session": {
|
|
495
|
+
"secret": "auto-generated",
|
|
496
|
+
"expiresIn": "7d"
|
|
351
497
|
}
|
|
352
498
|
}
|
|
353
499
|
```
|
|
354
500
|
|
|
355
|
-
|
|
501
|
+
### Verify JWT server-side
|
|
502
|
+
|
|
503
|
+
```js
|
|
504
|
+
import { verifyAuthToken } from "webhanger-auth";
|
|
505
|
+
|
|
506
|
+
const user = await verifyAuthToken(req.headers.authorization?.split(" ")[1]);
|
|
507
|
+
// { email, name, avatar, provider, id, exp, iat }
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Auth flow
|
|
511
|
+
|
|
512
|
+
```
|
|
513
|
+
User clicks <wh-auth provider="google">
|
|
514
|
+
└── popup opens → /auth/google
|
|
515
|
+
└── redirect to Google OAuth consent
|
|
516
|
+
└── Google → /auth/callback/google?code=xxx
|
|
517
|
+
└── exchange code → fetch profile
|
|
518
|
+
└── issue JWT
|
|
519
|
+
└── postMessage to opener
|
|
520
|
+
└── WHAuth.on("success") fires
|
|
521
|
+
└── redirect to on-success URL
|
|
522
|
+
```
|
|
356
523
|
|
|
357
524
|
---
|
|
358
525
|
|
|
359
|
-
|
|
526
|
+
A local web UI + SDK for managing deployed components.
|
|
527
|
+
|
|
528
|
+
```bash
|
|
529
|
+
npm install webhanger-admin
|
|
530
|
+
|
|
531
|
+
# Start dashboard
|
|
532
|
+
npx wh-admin ./webhanger.config.json 5000
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Open `http://localhost:5000`
|
|
536
|
+
|
|
537
|
+
Or use programmatically:
|
|
538
|
+
|
|
539
|
+
```js
|
|
540
|
+
import { WebHangerAdmin } from "webhanger-admin";
|
|
360
541
|
|
|
361
|
-
|
|
542
|
+
const admin = new WebHangerAdmin("./webhanger.config.json");
|
|
362
543
|
|
|
544
|
+
const components = await admin.listComponents();
|
|
545
|
+
const manifest = await admin.generateManifest();
|
|
546
|
+
await admin.saveManifest("./public/wh-manifest.json");
|
|
547
|
+
|
|
548
|
+
const { apiKey } = await admin.grantAccess("deployer", "CI/CD");
|
|
549
|
+
await admin.resignComponent("navbar", "1.0.0", 86400);
|
|
550
|
+
await admin.deleteComponent("navbar", "1.0.0");
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
See `webhanger-admin/README.md` for the full API reference.
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
## Browser SDK
|
|
558
|
+
|
|
559
|
+
### Zero-code Custom Element
|
|
363
560
|
```html
|
|
364
561
|
<script src="https://unpkg.com/webhanger-front@latest/browser.min.js"></script>
|
|
365
562
|
<script>WebHangerFront.initialize("./wh-manifest.json");</script>
|
|
366
563
|
|
|
367
|
-
<wh-component name="navbar"></wh-component>
|
|
368
|
-
<wh-component name="hero"></wh-component>
|
|
564
|
+
<wh-component name="navbar" wh-brand="MyApp" wh-cta-text="Start Free"></wh-component>
|
|
369
565
|
<wh-component name="footer" sandbox></wh-component>
|
|
370
566
|
```
|
|
371
567
|
|
|
372
|
-
###
|
|
373
|
-
|
|
568
|
+
### ESM (Next.js / React / Vite)
|
|
374
569
|
```js
|
|
375
|
-
|
|
376
|
-
cdnUrl, // string or array (multi-CDN)
|
|
377
|
-
projectId, // decrypt key
|
|
378
|
-
token, // HMAC token
|
|
379
|
-
expires, // unix timestamp, 0 = never
|
|
380
|
-
selector, // CSS selector, default "[data-wh]"
|
|
381
|
-
onSignal, // optional signal callback
|
|
382
|
-
deps, // optional pre-resolved deps array
|
|
383
|
-
options // optional options object
|
|
384
|
-
);
|
|
570
|
+
import { load, use, on, registerSW, clearCache, metrics, gpu } from "webhanger-front";
|
|
385
571
|
```
|
|
386
572
|
|
|
387
|
-
###
|
|
388
|
-
|
|
573
|
+
### Manual load with props
|
|
389
574
|
```js
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
575
|
+
await load(
|
|
576
|
+
cdnUrl,
|
|
577
|
+
projectId,
|
|
578
|
+
token,
|
|
579
|
+
expires,
|
|
580
|
+
selector,
|
|
581
|
+
onSignal,
|
|
582
|
+
deps,
|
|
583
|
+
{
|
|
584
|
+
props: { brand: "MyApp", ctaText: "Get Started" },
|
|
585
|
+
sandbox: true,
|
|
586
|
+
allowedDomains: ["mysite.com"],
|
|
587
|
+
beforeMount: () => showSpinner(),
|
|
588
|
+
afterMount: () => hideSpinner(),
|
|
589
|
+
onError: (err) => showFallback(err)
|
|
590
|
+
}
|
|
591
|
+
);
|
|
397
592
|
```
|
|
398
593
|
|
|
399
594
|
### Signal callback
|
|
400
|
-
|
|
401
595
|
```js
|
|
402
|
-
|
|
596
|
+
load(url, pid, token, 0, "[data-wh]", ({ stage, time, source }) => {
|
|
403
597
|
// stages: start → fetching → assets → deps → injecting → done | error
|
|
404
|
-
console.log(stage, detail.time, detail.source);
|
|
405
598
|
});
|
|
406
599
|
```
|
|
407
600
|
|
|
408
601
|
### Plugin system
|
|
409
|
-
|
|
410
602
|
```js
|
|
411
|
-
|
|
412
|
-
install({ on
|
|
603
|
+
use({
|
|
604
|
+
install({ on }) {
|
|
413
605
|
on("load", ({ time, source }) => analytics.track("load", { time, source }));
|
|
414
606
|
on("error", ({ message }) => errorTracker.capture(message));
|
|
415
607
|
on("metric", ({ name, value }) => dashboard.update(name, value));
|
|
608
|
+
on("gpu", ({ supported }) => console.log("WebGPU:", supported));
|
|
609
|
+
on("sw", ({ scope }) => console.log("SW:", scope));
|
|
416
610
|
}
|
|
417
611
|
});
|
|
418
612
|
```
|
|
419
613
|
|
|
420
614
|
### Observability
|
|
615
|
+
```js
|
|
616
|
+
on("load", ({ time, source }) => console.log(time, source));
|
|
617
|
+
console.log(metrics); // { loads, cacheHits, errors, totalTime }
|
|
618
|
+
```
|
|
421
619
|
|
|
620
|
+
### WebGPU
|
|
422
621
|
```js
|
|
423
|
-
|
|
424
|
-
|
|
622
|
+
console.log(gpu.supported);
|
|
623
|
+
on("gpu", ({ supported }) => console.log("GPU:", supported));
|
|
624
|
+
```
|
|
425
625
|
|
|
426
|
-
|
|
427
|
-
|
|
626
|
+
### Offline + Service Worker
|
|
627
|
+
```js
|
|
628
|
+
await registerSW("./webhanger.sw.js");
|
|
629
|
+
|
|
630
|
+
await setOfflinePage(
|
|
631
|
+
"<h1>Offline</h1><p>Back soon.</p>",
|
|
632
|
+
"body { background: #030712; color: white; }"
|
|
633
|
+
);
|
|
428
634
|
```
|
|
429
635
|
|
|
430
|
-
|
|
636
|
+
Offline behavior:
|
|
637
|
+
- Online first visit → loads from CDN, caches everything
|
|
638
|
+
- Online repeat visit → loads from SW cache instantly, badge shows
|
|
639
|
+
- Offline with cache → full page works
|
|
640
|
+
- Offline no cache → custom offline page with "⬡ Served by WebHanger" badge
|
|
641
|
+
|
|
642
|
+
### Smart Cache Invalidation
|
|
643
|
+
|
|
644
|
+
Only re-fetches from CDN when components actually changed. Checks admin server on every page load — if nothing changed, loads instantly from cache.
|
|
645
|
+
|
|
646
|
+
```js
|
|
647
|
+
// Instead of initialize(), use smartInitialize()
|
|
648
|
+
WebHangerFront.smartInitialize(
|
|
649
|
+
"./wh-manifest.json",
|
|
650
|
+
"http://localhost:5000" // wh-admin server URL
|
|
651
|
+
);
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
Flow:
|
|
655
|
+
```
|
|
656
|
+
Page loads
|
|
657
|
+
└── GET /api/last-updated from admin server (2s timeout)
|
|
658
|
+
├── same as localStorage "wh_last_updated"
|
|
659
|
+
│ └── load all components from cache (instant, 0ms)
|
|
660
|
+
└── different (components updated since last visit)
|
|
661
|
+
└── clear component cache
|
|
662
|
+
└── reload from CDN
|
|
663
|
+
└── update localStorage timestamp
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
Events:
|
|
667
|
+
```js
|
|
668
|
+
WebHangerFront.on("cache-invalidated", ({ serverTs, cachedTs }) => {
|
|
669
|
+
console.log("Components updated — reloading from CDN");
|
|
670
|
+
});
|
|
671
|
+
WebHangerFront.on("cache-hit", ({ serverTs }) => {
|
|
672
|
+
console.log("Up to date — loaded from cache");
|
|
673
|
+
});
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
If admin server is unreachable (offline, not running), falls back to normal cache behavior silently — zero errors.
|
|
677
|
+
|
|
678
|
+
Test it:
|
|
679
|
+
```bash
|
|
680
|
+
# 1. Deploy a component
|
|
681
|
+
node smart-cache-test/deploy.js 1.0.0
|
|
431
682
|
|
|
683
|
+
# 2. Start admin server
|
|
684
|
+
node admin/server.js ./webhanger.config.json 5000
|
|
685
|
+
|
|
686
|
+
# 3. Serve the test page
|
|
687
|
+
npx serve smart-cache-test/site
|
|
688
|
+
|
|
689
|
+
# 4. Open http://localhost:3000 — loads from CDN, caches timestamp
|
|
690
|
+
# 5. Refresh — loads from cache instantly (banner: "✓ Up to date")
|
|
691
|
+
# 6. Redeploy with new version
|
|
692
|
+
node smart-cache-test/deploy.js 2.0.0
|
|
693
|
+
# 7. Refresh — cache invalidates, loads fresh (banner: "🔄 Components updated")
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
See `smart-cache-test/` for the full working demo.
|
|
697
|
+
|
|
698
|
+
### Hard flush
|
|
432
699
|
```js
|
|
433
|
-
await
|
|
434
|
-
// Clears: localStorage, IndexedDB, SW caches, sessionStorage, unregisters SW
|
|
700
|
+
await clearCache();
|
|
435
701
|
```
|
|
436
702
|
|
|
437
703
|
---
|
|
@@ -442,9 +708,23 @@ await WebHangerFront.clearCache();
|
|
|
442
708
|
|---|---|
|
|
443
709
|
| `localStorage` | Components < 50KB |
|
|
444
710
|
| `IndexedDB` | Components ≥ 50KB |
|
|
445
|
-
| Service Worker | Offline
|
|
711
|
+
| Service Worker | Offline + navigation cache |
|
|
712
|
+
| `wh_last_updated` | Smart cache invalidation timestamp |
|
|
446
713
|
|
|
447
|
-
**Stale-while-revalidate** — returns cached
|
|
714
|
+
**Stale-while-revalidate** — returns cached instantly, refreshes in background.
|
|
715
|
+
|
|
716
|
+
**Smart cache invalidation** — `smartInitialize()` checks admin server on every load. Only re-fetches from CDN when components actually changed. Zero CDN requests on cache hits.
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
## Access Control
|
|
721
|
+
|
|
722
|
+
| Role | deploy | read | delete | manage_access |
|
|
723
|
+
|---|---|---|---|---|
|
|
724
|
+
| owner | ✅ | ✅ | ✅ | ✅ |
|
|
725
|
+
| admin | ✅ | ✅ | ✅ | ✅ |
|
|
726
|
+
| deployer | ✅ | ✅ | ❌ | ❌ |
|
|
727
|
+
| viewer | ❌ | ✅ | ❌ | ❌ |
|
|
448
728
|
|
|
449
729
|
---
|
|
450
730
|
|
|
@@ -454,93 +734,105 @@ await WebHangerFront.clearCache();
|
|
|
454
734
|
import { WebHanger } from "webhanger";
|
|
455
735
|
const wh = new WebHanger();
|
|
456
736
|
|
|
457
|
-
// Deploy
|
|
458
737
|
const result = await wh.deploy("./components/navbar", "navbar", "1.0.0", {
|
|
459
|
-
expiresInSeconds: 86400,
|
|
460
|
-
token: "custom-token", // optional
|
|
738
|
+
expiresInSeconds: 86400,
|
|
461
739
|
dependencies: ["chart@1.0.0"]
|
|
462
740
|
});
|
|
463
|
-
// { cdnUrl, cdnUrls, token, expires, dependencies }
|
|
464
741
|
|
|
465
|
-
// Resolve
|
|
466
742
|
const comp = await wh.resolve("navbar", "1.0.0");
|
|
467
|
-
|
|
468
|
-
// Rotate token without redeploying
|
|
469
743
|
await wh.resign("navbar", "1.0.0", { expiresInSeconds: 3600 });
|
|
470
|
-
|
|
471
|
-
// Delete from storage
|
|
472
744
|
await wh.remove("navbar", "1.0.0");
|
|
745
|
+
```
|
|
473
746
|
|
|
474
|
-
|
|
475
|
-
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
## Next.js Integration
|
|
750
|
+
|
|
751
|
+
```bash
|
|
752
|
+
npm install webhanger-front
|
|
476
753
|
```
|
|
477
754
|
|
|
478
|
-
|
|
755
|
+
```tsx
|
|
756
|
+
"use client";
|
|
757
|
+
import { useEffect, useRef } from "react";
|
|
758
|
+
import { load } from "webhanger-front";
|
|
479
759
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
760
|
+
export default function WebHangerComponent({ name, props = {} }: { name: string; props?: Record<string, string> }) {
|
|
761
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
762
|
+
|
|
763
|
+
useEffect(() => {
|
|
764
|
+
fetch("/wh-manifest.json")
|
|
765
|
+
.then(r => r.json())
|
|
766
|
+
.then(m => {
|
|
767
|
+
const c = m.components[name];
|
|
768
|
+
if (!c || !ref.current) return;
|
|
769
|
+
ref.current.id = `wh-${name}`;
|
|
770
|
+
load(c.urls || c.url, m.pid, c.token, c.expires, `#wh-${name}`, null, [], { props });
|
|
771
|
+
});
|
|
772
|
+
}, [name]);
|
|
773
|
+
|
|
774
|
+
return <div ref={ref} />;
|
|
775
|
+
}
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
```tsx
|
|
779
|
+
// app/page.tsx
|
|
780
|
+
<WebHangerComponent name="navbar" props={{ brand: "MyApp", ctaText: "Sign Up" }} />
|
|
493
781
|
```
|
|
494
782
|
|
|
495
783
|
---
|
|
496
784
|
|
|
785
|
+
## Examples & Tests
|
|
786
|
+
|
|
787
|
+
| Folder | What it tests |
|
|
788
|
+
|---|---|
|
|
789
|
+
| `examples/` | Full component deploy + browser SDK demo |
|
|
790
|
+
| `showcase/` | All 4 packages together |
|
|
791
|
+
| `auth-test/` | OAuth login flow (Google, GitHub) |
|
|
792
|
+
| `smart-cache-test/` | Smart cache invalidation demo |
|
|
793
|
+
| `personalization-test/` | Edge personalization — country, device, role, A/B |
|
|
794
|
+
|
|
795
|
+
```bash
|
|
796
|
+
# Examples
|
|
797
|
+
node examples/deploy.js && npx serve examples/site
|
|
798
|
+
|
|
799
|
+
# Showcase (all packages)
|
|
800
|
+
node showcase/deploy.js && npx serve showcase/site
|
|
801
|
+
|
|
802
|
+
# Auth test
|
|
803
|
+
wh auth init && wh auth serve &
|
|
804
|
+
npx serve . # open /auth-test/login.html
|
|
805
|
+
|
|
806
|
+
# Smart cache test
|
|
807
|
+
node smart-cache-test/deploy.js 1.0.0
|
|
808
|
+
node admin/server.js ./webhanger.config.json 5000 &
|
|
809
|
+
npx serve smart-cache-test/site
|
|
810
|
+
|
|
811
|
+
# Personalization test
|
|
812
|
+
node personalization-test/deploy.js
|
|
813
|
+
npx serve personalization-test/site
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
See `examples/EXAMPLE.md` for the full step-by-step guide.
|
|
817
|
+
|
|
818
|
+
---
|
|
819
|
+
|
|
497
820
|
## Storage Providers
|
|
498
821
|
|
|
499
822
|
| Provider | Notes |
|
|
500
823
|
|---|---|
|
|
501
824
|
| `s3` | AWS S3 — auto-provisions bucket + CloudFront |
|
|
502
825
|
| `r2` | Cloudflare R2 — zero egress fees |
|
|
503
|
-
| `minio` | Self-hosted MinIO
|
|
504
|
-
| `local` | Local disk — dev
|
|
826
|
+
| `minio` | Self-hosted MinIO |
|
|
827
|
+
| `local` | Local disk — dev only |
|
|
505
828
|
|
|
506
829
|
## Database Providers
|
|
507
830
|
|
|
508
831
|
| Provider | Notes |
|
|
509
832
|
|---|---|
|
|
510
|
-
| `firebase` | Firebase Firestore — free tier
|
|
511
|
-
| `supabase` | Supabase Postgres
|
|
512
|
-
| `mongodb` | MongoDB Atlas
|
|
513
|
-
|
|
514
|
-
---
|
|
515
|
-
|
|
516
|
-
## `webhanger.config.json`
|
|
517
|
-
|
|
518
|
-
```json
|
|
519
|
-
{
|
|
520
|
-
"project": "my-app",
|
|
521
|
-
"projectId": "wh_1234567890",
|
|
522
|
-
"secretKey": "64-char-hex-secret",
|
|
523
|
-
"webHangerVersion": "1.0.0",
|
|
524
|
-
"storage": {
|
|
525
|
-
"provider": "s3",
|
|
526
|
-
"accessKey": "...",
|
|
527
|
-
"secretKey": "...",
|
|
528
|
-
"bucket": "my-bucket",
|
|
529
|
-
"region": "ap-south-1",
|
|
530
|
-
"distributionId": "EXXXXX"
|
|
531
|
-
},
|
|
532
|
-
"cdn": {
|
|
533
|
-
"url": "https://primary.cloudfront.net",
|
|
534
|
-
"fallbacks": ["https://fallback.r2.dev"]
|
|
535
|
-
},
|
|
536
|
-
"db": {
|
|
537
|
-
"provider": "firebase",
|
|
538
|
-
"serviceAccountPath": "./firebase-service-account.json"
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
> Keep this file private. Never commit it. Add to `.gitignore`.
|
|
833
|
+
| `firebase` | Firebase Firestore — free tier |
|
|
834
|
+
| `supabase` | Supabase Postgres |
|
|
835
|
+
| `mongodb` | MongoDB Atlas |
|
|
544
836
|
|
|
545
837
|
---
|
|
546
838
|
|
|
@@ -548,47 +840,41 @@ import {
|
|
|
548
840
|
|
|
549
841
|
```
|
|
550
842
|
Developer
|
|
551
|
-
└── wh ship ./components ./
|
|
843
|
+
└── wh ship ./components ./site
|
|
552
844
|
├── wh analyze → detect Tailwind, GSAP, deps
|
|
553
|
-
├── wh
|
|
554
|
-
├──
|
|
845
|
+
├── wh breakdown → extract CSS/JS from single HTML
|
|
846
|
+
├── bundle → html + css + js → single payload
|
|
847
|
+
├── props schema → stored in payload for runtime resolution
|
|
848
|
+
├── AES-256-GCM → encrypt each chunk
|
|
555
849
|
├── SHA-256 hash → integrity fingerprint
|
|
556
850
|
├── S3 upload → store encrypted payload
|
|
557
851
|
├── HMAC sign → project-scoped signed URL
|
|
558
|
-
├──
|
|
559
|
-
├── wh build → minify HTML,
|
|
852
|
+
├── DB register → metadata + dep graph
|
|
853
|
+
├── wh build → minify HTML, extract CSS/JS
|
|
560
854
|
└── wh zip → deploy.zip ready for upload
|
|
561
855
|
|
|
562
856
|
Browser
|
|
563
|
-
└── <wh-component name="navbar">
|
|
857
|
+
└── <wh-component name="navbar" wh-brand="MyApp">
|
|
564
858
|
├── fetch wh-manifest.json
|
|
565
859
|
├── check token expiry
|
|
566
860
|
├── stale-while-revalidate cache
|
|
567
|
-
├── fetch from CloudFront
|
|
568
|
-
|
|
569
|
-
├──
|
|
570
|
-
├──
|
|
571
|
-
├── resolve + load dependency graph
|
|
861
|
+
├── fetch from CloudFront / Edge Worker
|
|
862
|
+
├── multi-CDN failover
|
|
863
|
+
├── load CDN assets
|
|
864
|
+
├── resolve dependency graph
|
|
572
865
|
├── AES-256-GCM decrypt in memory
|
|
573
866
|
├── SHA-256 integrity verify
|
|
867
|
+
├── resolve props ({{wh.brand}} → "MyApp")
|
|
574
868
|
├── domain restriction check
|
|
575
869
|
├── inject CSS → HTML → JS (or Shadow DOM)
|
|
576
870
|
├── fire lifecycle hooks
|
|
577
|
-
|
|
871
|
+
├── emit metrics + plugin events
|
|
872
|
+
├── WebGPU detection
|
|
873
|
+
└── Service Worker caches for offline
|
|
578
874
|
```
|
|
579
875
|
|
|
580
876
|
---
|
|
581
877
|
|
|
582
|
-
## Real-World Use Cases
|
|
583
|
-
|
|
584
|
-
- **Enterprise micro-frontends** — shared UI across 50+ apps, update once
|
|
585
|
-
- **Education platforms** — push UI updates to all school sites instantly
|
|
586
|
-
- **Low-bandwidth regions** — cache once, serve offline via Service Worker
|
|
587
|
-
- **Security platforms** — inject warnings/banners dynamically across sites
|
|
588
|
-
- **White-label SaaS** — per-tenant component customization
|
|
589
|
-
|
|
590
|
-
---
|
|
591
|
-
|
|
592
878
|
## License
|
|
593
879
|
|
|
594
880
|
ISC
|