webhanger-front 1.0.6 → 1.0.8

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
@@ -2,6 +2,8 @@
2
2
 
3
3
  Browser SDK for WebHanger. Loads encrypted UI components from CDN and injects them into the DOM. No eval, no readable source — decrypted in memory and applied directly.
4
4
 
5
+ > **Note:** This package uses `fetch` to load components from your CDN. This is expected behavior and is flagged as an informational notice by npm's security scanner — not a vulnerability.
6
+
5
7
  ---
6
8
 
7
9
  ## Install
package/browser.min.js CHANGED
@@ -1 +1 @@
1
- !function(e){const t="components",n="wh_";function r(e,t,n){if(!e)return"";const r=t+n,o=Uint8Array.from(atob(e),e=>e.charCodeAt(0)),a=(new TextEncoder).encode(r),s=new Uint8Array(o.length);for(let e=0;e<o.length;e++)s[e]=o[e]^a[e%a.length];return(new TextDecoder).decode(s)}function o(){return new Promise((e,n)=>{const r=indexedDB.open("webhanger_cache",1);r.onupgradeneeded=e=>e.target.result.createObjectStore(t),r.onsuccess=t=>e(t.target.result),r.onerror=()=>n(r.error)})}function a(e){return new Promise(t=>{if("style"===e.type?document.querySelector(`link[href="${e.url}"]`):document.querySelector(`script[src="${e.url}"]`))return t();if("style"===e.type){const n=document.createElement("link");n.rel="stylesheet",n.href=e.url,n.onload=t,n.onerror=t,document.head.appendChild(n)}else{const n=document.createElement("script");n.src=e.url,e.defer&&(n.defer=!0),e.async&&(n.async=!0),n.onload=t,n.onerror=t,document.head.appendChild(n)}})}e.WebHangerFront={load:async function(e,s,c,i,d="[data-wh]",l=null){if(!e||!s||!c||null==i)return void console.error("[WebHanger] Missing required params: cdnUrl, projectId, token, expires");if(0!==i&&Math.floor(Date.now()/1e3)>i)return void console.warn("[WebHanger] Token expired.");const u=function(e){return function(t,n={}){"function"==typeof e&&e({stage:t,...n})}}(l),h=function(e){const t=document.querySelector(e||"[data-wh]");if(!t)return{show:()=>{},hide:()=>{}};const n=document.createElement("div");return n.setAttribute("data-wh-loader",""),n.style.cssText="\n display:flex; align-items:center; justify-content:center;\n padding: 24px; width:100%; box-sizing:border-box;\n ",n.innerHTML='\n <div style="\n width:28px; height:28px;\n border:3px solid #e5e7eb;\n border-top-color:#6366f1;\n border-radius:50%;\n animation:wh-spin 0.7s linear infinite;\n "></div>\n <style>\n @keyframes wh-spin { to { transform: rotate(360deg); } }\n </style>\n ',{show(){t.appendChild(n)},hide(){n.parentNode&&n.parentNode.removeChild(n)}}}(d);u("start",{cdnUrl:e,selector:d}),h.show();const f=`${e}@${i}`;try{u("fetching");let l,m=await async function(e){try{const t=localStorage.getItem(n+e);if(t)return t}catch(e){}return await async function(e){const n=await o();return new Promise((r,o)=>{const a=n.transaction(t,"readonly").objectStore(t).get(e);a.onsuccess=()=>r(a.result),a.onerror=()=>o(a.error)})}(e)}(f);m||(m=await async function(e,t,n){const r=n?`${e}?token=${t}&expires=${n}`:`${e}?token=${t}`,o=await fetch(r);if(!o.ok)throw Error("Failed to fetch component: "+o.status);return await o.text()}(e,c,i),await async function(e,r){try{if(r.length<51200)return void localStorage.setItem(n+e,r)}catch(e){}await async function(e,n){const r=await o();return new Promise((o,a)=>{const s=r.transaction(t,"readwrite").objectStore(t).put(n,e);s.onsuccess=()=>o(),s.onerror=()=>a(s.error)})}(e,r)}(f,m));try{l=JSON.parse(m)}catch(e){l={}}l.assets&&l.assets.length&&(u("assets",{count:l.assets.length,assets:l.assets}),await async function(e=[]){for(const t of e)await a(t)}(l.assets)),u("injecting"),h.hide(),function(e,t,n){const o=document.querySelector(n||"[data-wh]");if(!o)return void console.warn("[WebHanger] No mount target found.");let a;try{a=JSON.parse(e)}catch(e){return void console.error("[WebHanger] Invalid component payload.")}const s=r(a.c,t,"::css");if(s){const e=document.createElement("style");e.textContent=s,document.head.appendChild(e)}const c=r(a.h,t,"::html");c&&(o.innerHTML=c);const i=r(a.j,t,"::js");if(i){const e=document.createElement("script");e.textContent=i,document.head.appendChild(e),document.head.removeChild(e)}}(m,s,d),u("done")}catch(e){h.hide(),u("error",{message:e.message}),console.error("[WebHanger] Load failed:",e.message)}},clearCache:async function(){Object.keys(localStorage).filter(e=>e.startsWith(n)).forEach(e=>localStorage.removeItem(e));try{(await o()).transaction(t,"readwrite").objectStore(t).clear()}catch(e){}if("caches"in window){const e=await caches.keys();await Promise.all(e.map(e=>caches.delete(e)))}if("serviceWorker"in navigator){const e=await navigator.serviceWorker.getRegistrations();await Promise.all(e.map(e=>e.unregister()))}Object.keys(sessionStorage).filter(e=>e.startsWith(n)).forEach(e=>sessionStorage.removeItem(e))},version:"1.0.0"}}(window);
1
+ !function(e){const t="components",n="wh2_",r=[],o={};function a(e,t){o[e]||(o[e]=[]),o[e].push(t)}function s(e,t){(o[e]||[]).forEach(e=>e(t))}const c={loads:0,cacheHits:0,errors:0,totalTime:0};function i(e,t){e in c&&(c[e]+=t),s("metric",{name:e,value:t,metrics:{...c}})}const d=new Map;async function l(e,t,n){if(!e)return"";try{const r=e.split(":");if(3!==r.length)return console.warn("[WebHanger] decryptChunk: unexpected format, parts:",r.length),"";const[o,a,s]=r,c=Uint8Array.from(atob(o),e=>e.charCodeAt(0)),i=Uint8Array.from(atob(a),e=>e.charCodeAt(0)),l=Uint8Array.from(atob(s),e=>e.charCodeAt(0)),u=new Uint8Array(l.length+i.length);u.set(l),u.set(i,l.length);const h=await async function(e,t){const n=e+t;if(d.has(n))return d.get(n);const r=new TextEncoder,o=await crypto.subtle.digest("SHA-256",r.encode(e+t)),a=await crypto.subtle.importKey("raw",o,{name:"AES-GCM"},!1,["decrypt"]);return d.set(n,a),a}(t,n),f=await crypto.subtle.decrypt({name:"AES-GCM",iv:c},h,u);return(new TextDecoder).decode(f)}catch(e){return console.warn("[WebHanger] decryptChunk failed:",n,e.message),""}}function u(){return new Promise((e,n)=>{const r=indexedDB.open("webhanger_v2",1);r.onupgradeneeded=e=>e.target.result.createObjectStore(t),r.onsuccess=t=>e(t.target.result),r.onerror=()=>n(r.error)})}async function h(e,r){try{if(r.length<51200)return void localStorage.setItem(n+e,r)}catch(e){}await async function(e,n){const r=await u();return new Promise((o,a)=>{const s=r.transaction(t,"readwrite").objectStore(t).put(n,e);s.onsuccess=()=>o(),s.onerror=()=>a(s.error)})}(e,r)}async function f(e,r){const o=await async function(e){try{const t=localStorage.getItem(n+e);if(t)return t}catch(e){}return await async function(e){const n=await u();return new Promise((r,o)=>{const a=n.transaction(t,"readonly").objectStore(t).get(e);a.onsuccess=()=>r(a.result),a.onerror=()=>o(a.error)})}(e)}(e);if(o)return i("cacheHits",1),r().then(t=>{t&&t!==o&&h(e,t)}).catch(()=>{}),{data:o,source:"cache"};const a=await r();return a&&await h(e,a),{data:a,source:"cdn"}}async function m(e,t,n){const r=Array.isArray(e)?e:[e];let o;for(const e of r)try{const r=n?`${e}?token=${t}&expires=${n}`:`${e}?token=${t}`,o=await fetch(r);if(!o.ok)throw Error("HTTP "+o.status);return await o.text()}catch(t){console.warn(`[WebHanger] CDN failed (${e}): ${t.message} \u2014 trying next...`),o=t}throw Error("All CDN endpoints failed. Last error: "+o?.message)}function w(e){return new Promise(t=>{if("style"===e.type?document.querySelector(`link[href="${e.url}"]`):document.querySelector(`script[src="${e.url}"]`))return t();if("style"===e.type){const n=document.createElement("link");n.rel="stylesheet",n.href=e.url,n.onload=t,n.onerror=t,document.head.appendChild(n)}else{const n=document.createElement("script");n.src=e.url,e.defer&&(n.defer=!0),e.async&&(n.async=!0),n.onload=t,n.onerror=t,document.head.appendChild(n)}})}async function p(e=[]){for(const t of e)await w(t)}async function y(e,t){const n=`${e.cdnUrl}@${e.expires}`,{data:r}=await f(n,()=>m(e.cdnUrl,e.token,e.expires));if(!r)return;let o;try{o=JSON.parse(r)}catch(e){return}o.assets&&o.assets.length&&await p(o.assets);const a=await l(o.c,t,"::css");if(a&&!document.querySelector(`style[data-wh-dep="${e.name}@${e.version}"]`)){const t=document.createElement("style");t.setAttribute("data-wh-dep",`${e.name}@${e.version}`),t.textContent=a,document.head.appendChild(t)}const s=await l(o.h,t,"::html");if(s){const t=document.querySelector(`[data-wh-${e.name}]`);t&&(t.innerHTML=s)}const c=await l(o.j,t,"::js");if(c){const e=document.createElement("script");e.textContent=c,document.head.appendChild(e),document.head.removeChild(e)}}async function g(t,n,r,o,a="[data-wh]",c=null,d=[],u={}){if(!t||!n||!r||null==o)return void console.error("[WebHanger] Missing required params");if(0!==o&&Math.floor(Date.now()/1e3)>o)return void console.warn("[WebHanger] Token expired.");const h=function(e){return(t,n={})=>{"function"==typeof e&&e({stage:t,...n}),s(t,n)}}(c),w=function(e){const t=document.querySelector(e||"[data-wh]");if(!t)return{show:()=>{},hide:()=>{}};const n=document.createElement("div");return n.setAttribute("data-wh-loader",""),n.style.cssText="display:flex;align-items:center;justify-content:center;padding:24px;width:100%;box-sizing:border-box;",n.innerHTML='<div style="width:28px;height:28px;border:3px solid #e5e7eb;border-top-color:#6366f1;border-radius:50%;animation:wh-spin 0.7s linear infinite;"></div><style>@keyframes wh-spin{to{transform:rotate(360deg);}}</style>',{show(){t.appendChild(n)},hide(){n.parentNode&&n.parentNode.removeChild(n)}}}(a),g=performance.now();h("start",{cdnUrl:t,selector:a}),w.show(),i("loads",1);const b=`${t}@${o}`;try{h("fetching");const{data:c,source:v}=await f(b,()=>m(t,r,o));let x;try{x=JSON.parse(c)}catch(e){x={}}x.assets&&x.assets.length&&(h("assets",{count:x.assets.length}),await p(x.assets));const C=[...d,...x.dependencies||[]];if(C.length){h("deps",{count:C.length});for(const e of C)"object"==typeof e&&e.cdnUrl&&await y(e,n)}h("injecting"),w.hide(),await async function(t,n,r,o={}){const{sandbox:a=!1,allowedDomains:c,beforeMount:i,afterMount:d}=o,u=document.querySelector(r||"[data-wh]");if(!u)return void console.warn("[WebHanger] No mount target found.");let h;try{h=JSON.parse(t)}catch(e){return void console.error("[WebHanger] Invalid payload.")}if(!function(t){if(!t||!t.length)return!0;const n=e.location?.hostname||"";return t.some(e=>n===e||n.endsWith("."+e))}(c))return console.error("[WebHanger] Domain not allowed: "+e.location?.hostname),void s("error",{reason:"domain_restricted"});const f=await l(h.c,n,"::css"),m=await l(h.h,n,"::html"),w=await l(h.j,n,"::js");if(h.integrity&&(m||w)){const e=await async function(e,t,n,r){if(!r)return!0;const o=(new TextEncoder).encode(e+t+n),a=await crypto.subtle.digest("SHA-256",o);return Array.from(new Uint8Array(a)).map(e=>e.toString(16).padStart(2,"0")).join("")===r}(m,f,w,h.integrity);if(!e)return console.error("[WebHanger] Integrity check failed \u2014 bundle may be tampered or re-deploy needed."),void s("error",{reason:"integrity_failed"})}if("function"==typeof i&&i({target:u,html:m,css:f}),s("beforeMount",{target:u,selector:r}),a)!function(e,t,n){const r=e.attachShadow({mode:"closed"});if(n){const e=document.createElement("style");e.textContent=n,r.appendChild(e)}if(t){const e=document.createElement("div");e.innerHTML=t,r.appendChild(e)}}(u,m,f);else{if(f){const e=document.createElement("style");e.textContent=f,document.head.appendChild(e)}m&&(u.innerHTML=m)}if(w){const e=document.createElement("script");e.textContent=w,document.head.appendChild(e),document.head.removeChild(e)}"function"==typeof d&&d({target:u}),s("afterMount",{target:u,selector:r})}(c,n,a,u);const S=Math.round(performance.now()-g);i("totalTime",S),h("done",{time:S,source:v}),s("load",{cdnUrl:t,time:S,source:v,selector:a})}catch(e){w.hide(),i("errors",1),h("error",{message:e.message}),"function"==typeof u.onError&&u.onError(e),console.error("[WebHanger] Load failed:",e.message)}}let b=null,v="./wh-manifest.json";"undefined"!=typeof customElements&&customElements.define("wh-component",class extends HTMLElement{async connectedCallback(){b&&await this._load()}async _load(){const e=this.getAttribute("name"),t=this.hasAttribute("sandbox");if(!e)return void console.error("[WebHanger] <wh-component> missing 'name' attribute");const n=b||await fetch(this.getAttribute("src")||v).then(e=>e.json()),r=n.components[e];if(!r)return void console.error(`[WebHanger] <wh-component>: "${e}" not found in manifest`);this.setAttribute("data-wh-el",e);const o=`wh-component[data-wh-el="${e}"]`;await g(r.urls||r.url,n.pid,r.token,r.expires,o,null,[],{sandbox:t})}}),e.WebHangerFront={load:g,clearCache:async function(){Object.keys(localStorage).filter(e=>e.startsWith(n)).forEach(e=>localStorage.removeItem(e));try{(await u()).transaction(t,"readwrite").objectStore(t).clear()}catch(e){}if("caches"in window){const e=await caches.keys();await Promise.all(e.map(e=>caches.delete(e)))}if("serviceWorker"in navigator){const e=await navigator.serviceWorker.getRegistrations();await Promise.all(e.map(e=>e.unregister()))}Object.keys(sessionStorage).filter(e=>e.startsWith(n)).forEach(e=>sessionStorage.removeItem(e))},use:function(e){"function"==typeof e.install&&e.install({on:a,emit:s}),r.push(e)},on:a,metrics:c,initialize:async function(e="./wh-manifest.json"){v=e;const t=await fetch(e);b=await t.json(),document.querySelectorAll("wh-component[name]").forEach(e=>e._load())},version:"2.0.0"}}(window);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webhanger-front",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "WebHanger browser SDK — load encrypted UI components from CDN",
5
5
  "main": "index.js",
6
6
  "module": "index.js",