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 CHANGED
@@ -1,34 +1,32 @@
1
1
  # WebHanger
2
2
 
3
- > **Component-as-a-Service (CaaS)** — Bundle once. Encrypt with AES-256. Deliver via edge CDN. Load anywhere with zero code.
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. Think of it as npm for UI components — but instead of installing packages, you deploy encrypted components to a CDN and load them into any website with a single HTML tag.
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 website — zero JS required:
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
- Interactive project setup. Provisions infrastructure automatically.
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 init
61
+ wh dev ./components 4242
52
62
  ```
53
63
 
54
- Prompts:
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
- wh ship <components-dir> <site-dir> [version] [out-dir]
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 ./docs 1.0.0 ./dist
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` (no sensitive data in HTML)
84
- 4. Production builds the site (minify HTML, inline CSS/JS)
85
- 5. Zips output for direct upload
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 <src-dir> [out-dir]
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
- Supported targets: `react` `vue` `svelte` `next` `angular` `astro`
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 # generate API key with role
193
- wh access revoke <key> # revoke a key
194
- wh access list # list all keys
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 ← markup
226
- style.css ← styles
227
- script.js ← behaviour
228
- webhanger.component.json ← assets + dependencies
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": ["navbar@1.0.0", "chart@2.0.0"]
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
- CDN assets are auto-detected by `wh analyze` and merged automatically on deploy.
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
- ## Dependency Graph
222
+ ## Security
248
223
 
249
- npm-like dependency resolution for UI components.
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
- dashboard@1.0.0
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
- - Depth-first resolution
259
- - Circular dependency detection with full chain error
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
- ```bash
264
- wh graph-deploy ./components 1.0.0 ./output
239
+ ### Domain Restriction
240
+ ```js
241
+ load(url, pid, token, 0, "[data-wh]", null, [], {
242
+ allowedDomains: ["mysite.com"]
243
+ });
265
244
  ```
266
245
 
267
- Programmatic:
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] — deps first
263
+ // Returns: [chart, navbar, statsbar, dashboard]
273
264
  ```
274
265
 
275
266
  ---
276
267
 
277
- ## Security
268
+ ## Multi-CDN Failover
278
269
 
279
- ### AES-256-GCM Encryption
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
- Every component chunk (HTML/CSS/JS) is encrypted before upload.
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
- Key = SHA-256(projectId + salt)
285
- Payload = iv:tag:ciphertext (base64)
286
- Salts = "::html" | "::css" | "::js"
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
- ### HMAC-SHA256 Signed URLs
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
- token = HMAC-SHA256(projectId:componentPath:expires, secretKey)
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
- ### Integrity Check
363
+ ### Rule conditions
296
364
 
297
- SHA-256 hash of raw content stored in payload. Verified after decryption — detects tampering.
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
- ### Domain Restriction
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.load(url, pid, token, 0, "[data-wh]", null, [], {
303
- allowedDomains: ["mysite.com", "app.io"]
380
+ WebHangerFront.on("personalized", ({ slot, resolved, ctx }) => {
381
+ console.log(`${slot} → ${resolved} (country: ${ctx.country})`);
304
382
  });
305
383
  ```
306
384
 
307
- ### Token Expiry
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
- // Never expires
311
- wh.deploy("./navbar", "navbar", "1.0.0");
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
- // Expires in 24 hours
314
- wh.deploy("./navbar", "navbar", "1.0.0", { expiresInSeconds: 86400 });
398
+ ```
399
+ CF-IPCountry: IN → worker resolves hero-india → serves encrypted component
315
400
  ```
316
401
 
317
- ### Manifest-based Delivery
402
+ Store rules in KV:
403
+ ```bash
404
+ wrangler kv:key put --binding=WH_VERSIONS "personalization:hero" '{"rules":[...],"default":"hero"}'
405
+ ```
318
406
 
319
- Tokens, projectId, and CDN URLs never appear in HTML. Fetched at runtime from `wh-manifest.json`.
407
+ ### Test it
320
408
 
321
- ---
409
+ ```bash
410
+ node personalization-test/deploy.js
411
+ npx serve personalization-test/site
412
+ ```
322
413
 
323
- ## Edge Worker (Cloudflare Workers)
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
- Runs at the edge before S3:
416
+ ---
326
417
 
327
- - HMAC token validation
328
- - Version resolution (`latest` → `1.2.0` from KV)
329
- - Geo-based routing (India → ap-south-1, Europe → eu-west-1)
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
- wh init # answer yes to edge worker setup
334
- cd edge && wrangler deploy
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
- Update `cdn.url` in `webhanger.config.json` to your worker URL — all requests now go through the edge.
430
+ ### Zero-code HTML
338
431
 
339
- ---
432
+ ```html
433
+ <script src="https://unpkg.com/webhanger-auth/browser.min.js"></script>
340
434
 
341
- ## Multi-CDN Failover
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
- "cdn": {
346
- "url": "https://primary.cloudfront.net",
347
- "fallbacks": [
348
- "https://fallback.r2.dev",
349
- "https://backup.b-cdn.net"
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
- SDK tries each URL in order. If primary fails, automatically falls back — zero code changes needed.
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
- ## Browser SDK
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
- ### Zero-code (recommended)
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
- ### Manual load
373
-
568
+ ### ESM (Next.js / React / Vite)
374
569
  ```js
375
- WebHangerFront.load(
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
- ### Lifecycle hooks
388
-
573
+ ### Manual load with props
389
574
  ```js
390
- WebHangerFront.load(url, pid, token, 0, "[data-wh]", null, [], {
391
- beforeMount: ({ html }) => showSpinner(),
392
- afterMount: ({ target }) => hideSpinner(),
393
- onError: (err) => showFallback(err),
394
- sandbox: true, // Shadow DOM isolation
395
- allowedDomains: ["mysite.com"] // domain restriction
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
- WebHangerFront.load(url, pid, token, 0, "[data-wh]", ({ stage, ...detail }) => {
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
- WebHangerFront.use({
412
- install({ on, emit }) {
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
- WebHangerFront.on("load", ({ time, source }) => console.log(time, source));
424
- WebHangerFront.on("metric", ({ name, value }) => console.log(name, value));
622
+ console.log(gpu.supported);
623
+ on("gpu", ({ supported }) => console.log("GPU:", supported));
624
+ ```
425
625
 
426
- console.log(WebHangerFront.metrics);
427
- // { loads: 5, cacheHits: 3, errors: 0, totalTime: 420 }
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
- ### Hard flush
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 WebHangerFront.clearCache();
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 fallback |
711
+ | Service Worker | Offline + navigation cache |
712
+ | `wh_last_updated` | Smart cache invalidation timestamp |
446
713
 
447
- **Stale-while-revalidate** — returns cached version instantly, refreshes in background.
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, // optional
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
- // Get config
475
- const config = wh.getConfig();
747
+ ---
748
+
749
+ ## Next.js Integration
750
+
751
+ ```bash
752
+ npm install webhanger-front
476
753
  ```
477
754
 
478
- ### Named exports
755
+ ```tsx
756
+ "use client";
757
+ import { useEffect, useRef } from "react";
758
+ import { load } from "webhanger-front";
479
759
 
480
- ```js
481
- import {
482
- bundle, encrypt, decrypt, integrityHash,
483
- signUrl, verifyToken, generateSecretKey,
484
- upload, remove,
485
- registerComponent, getComponent,
486
- deploy, resolveGraph,
487
- convert, analyzeComponent,
488
- build,
489
- grantAccess, revokeAccess, checkPermission, listAccess, generateApiKey,
490
- provisionBucket, provisionCloudFront,
491
- loadConfig
492
- } from "webhanger";
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 — S3-compatible |
504
- | `local` | Local disk — dev/testing only |
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, real-time |
511
- | `supabase` | Supabase Postgres — open source |
512
- | `mongodb` | MongoDB Atlas — flexible documents |
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 ./docs
843
+ └── wh ship ./components ./site
552
844
  ├── wh analyze → detect Tailwind, GSAP, deps
553
- ├── wh bundle html + css + js → single payload
554
- ├── AES-256-GCM encrypt each chunk (SHA-256 key)
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
- ├── Firestore → register metadata + dep graph
559
- ├── wh build → minify HTML, inline assets
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 (or Edge Worker)
568
- │ └── Edge: validate token + geo route + rate limit
569
- ├── multi-CDN failover if primary fails
570
- ├── load CDN assets (Tailwind, GSAP, etc.)
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
- └── emit metrics + plugin events
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