injectProxyCompatibilityScript static method

String injectProxyCompatibilityScript(
  1. String html
)

Injects a small compatibility patch for proxied documents rendered from data: origin, preventing hard failures on unsupported APIs.

Implementation

static String injectProxyCompatibilityScript(String html) {
  if (html.isEmpty) return html;

  const marker = 'data-swebview-proxy-compat';
  if (html.contains(marker)) return html;

  // The script patches six things that break when a page is served from a
  // data: URL (null origin):
  //
  // 1. Service-worker registration — SW scope must match origin; fails silently.
  // 2. IndexedDB — browsers block IDBFactory.open() from null origin and throw
  //    a synchronous SecurityError. We patch IDBFactory.prototype.open with a
  //    tiny in-memory shim that resolves successfully for common get/put paths.
  //    This helps feature-gated plugins that only need lightweight persistence.
  // 3. history.replaceState / pushState — cannot push external URLs from null
  //    origin; we wrap both and swallow the SecurityError.
  // 4. "Unlegal embed" overlay — some apps (e.g. Windy) check
  //    window.location.origin and show a blocking overlay when it is not their
  //    own domain. We inject CSS and also continuously force-hide the node
  //    because page JS can toggle inline styles after load.
  // 5. API proxy rewriting — when a page makes cross-origin API calls (e.g.,
  //    Axios to dir.aviapages.com), the null origin blocks them. We extract
  //    the proxy base from the injected <base> tag and rewrite fetch/XHR/Axios
  //    requests to *.aviapages.com URLs through that same proxy.
  // 6. Known benign noisy errors (Unlegal embed / IDBFactory / gl-particles)
  //    are swallowed to reduce console spam while keeping page execution alive.
  const script = '<script $marker="1">(function(){try{'
      'var _stats=(window.__swebviewCompatStats&&typeof window.__swebviewCompatStats==="object")'
      '?window.__swebviewCompatStats:{errors:0,swallowedErrors:0,unhandledRejections:0,pluginFailures:0,messages:[]};'
      'window.__swebviewCompatStats=_stats;'
      'var _pushMsg=function(m){try{m=String(m||"");_stats.messages.push(m);'
      'if(_stats.messages.length>25)_stats.messages.splice(0,_stats.messages.length-25);}catch(e){}};'
      // Extract proxy base from <base> tag for API rewriting
      'var _proxyBase="";'
      'try{var b=document.querySelector("base[href]");'
      'if(b&&b.href){'
      'var m=b.href.match(/^(https?:\\/\\/[^\\/]+(?:\\/[^\\/]+)*(?:\\?[^=]+=)?)/);'
      'if(m)_proxyBase=m[1];'
      '}}catch(e){}'
      // 1. Service workers
      'if(typeof navigator!=="undefined"&&"serviceWorker" in navigator){'
      'navigator.serviceWorker.register=function(){'
      'return Promise.reject(new Error("sw-disabled-proxy"));};}'
      // 2. IndexedDB
      'if(typeof IDBFactory!=="undefined"&&window.indexedDB){'
      'var _o=IDBFactory.prototype.open;'
      'var _dbMem={};'
      'var _mkReq=function(result,error){'
      'var r={readyState:"pending",result:undefined,error:error||null,'
      'onsuccess:null,onerror:null,onblocked:null,onupgradeneeded:null};'
      'setTimeout(function(){'
      'r.readyState="done";'
      'if(error){if(typeof r.onerror==="function")r.onerror({target:r,type:"error"});return;}'
      'r.result=result;'
      'if(typeof r.onsuccess==="function")r.onsuccess({target:r,type:"success"});'
      '},0);'
      'return r;};'
      'var _mkStore=function(name){'
      'if(!_dbMem[name])_dbMem[name]={};'
      'var s=_dbMem[name];'
      'return {'
      'get:function(k){return _mkReq(s[String(k)]);},'
      'put:function(v,k){if(typeof k==="undefined"&&v&&typeof v==="object"&&("id" in v))k=v.id;'
      's[String(k)]=v;return _mkReq(k);},'
      'add:function(v,k){if(typeof k==="undefined"&&v&&typeof v==="object"&&("id" in v))k=v.id;'
      'if(s.hasOwnProperty(String(k)))return _mkReq(undefined,new Error("ConstraintError"));'
      's[String(k)]=v;return _mkReq(k);},'
      'delete:function(k){delete s[String(k)];return _mkReq(undefined);},'
      'clear:function(){for(var p in s){if(Object.prototype.hasOwnProperty.call(s,p))delete s[p];}return _mkReq(undefined);}'
      '};};'
      'var _storeNames=[];'
      'var _ensureStore=function(n){'
      'n=String(n);'
      'if(_storeNames.indexOf(n)===-1)_storeNames.push(n);'
      'if(!_dbMem[n])_dbMem[n]={};'
      '};'
      'var _dropStore=function(n){'
      'n=String(n);'
      'delete _dbMem[n];'
      'var i=_storeNames.indexOf(n);'
      'if(i!==-1)_storeNames.splice(i,1);'
      '};'
      'var _mkStoreNamesObj=function(){return {'
      'contains:function(n){return _storeNames.indexOf(String(n))!==-1;},'
      'item:function(i){return _storeNames[i]||null;},'
      'get length(){return _storeNames.length;}'
      '};};'
      'var _mkDb=function(){return {'
      'close:function(){},'
      'get objectStoreNames(){return _mkStoreNamesObj();},'
      'createObjectStore:function(n){_ensureStore(n);return _mkStore(n);},'
      'deleteObjectStore:function(n){_dropStore(n);},'
      'transaction:function(names){'
      'var list=Array.isArray(names)?names:[names];'
      'for(var i=0;i<list.length;i++){_ensureStore(list[i]);}'
      'return {'
      'oncomplete:null,onerror:null,onabort:null,'
      'objectStore:function(n){_ensureStore(n);if(list.indexOf(n)===-1)list.push(n);return _mkStore(n);}'
      '};'
      '}'
      '};};'
      'IDBFactory.prototype.open=function(){'
      'try{return _o.apply(this,arguments);}catch(e){'
      'var req=_mkReq(_mkDb());'
      'setTimeout(function(){if(typeof req.onupgradeneeded==="function")req.onupgradeneeded({target:req,type:"upgradeneeded"});},0);'
      'return req;}};}'
      // 3. history
      'if(typeof history!=="undefined"){'
      'var _rs=history.replaceState;'
      'history.replaceState=function(s,t,u){try{_rs.call(history,s,t,u);}catch(e){}};'
      'var _ps=history.pushState;'
      'history.pushState=function(s,t,u){try{_ps.call(history,s,t,u);}catch(e){}};}'
      // 4. Keep windy "unlegal-embed" overlay hidden even if script toggles it
      'var _hideUE=function(){try{var n=document.getElementById("unlegal-embed");'
      'if(n){n.style.setProperty("display","none","important");'
      'n.setAttribute("aria-hidden","true");}}catch(e){}};'
      '_hideUE();setInterval(_hideUE,500);'
      // 3.5 API proxy rewriting for cross-origin API calls (Axios/fetch)
      'if(_proxyBase){'
      'var _rewriteApiUrl=function(u){try{u=String(u||"");'
      'if((u.indexOf("dir.aviapages.com")!==-1||u.indexOf("api.aviapages.com")!==-1)'
      '&&(u.indexOf("http://")===0||u.indexOf("https://")===0)){'
      'return _proxyBase+encodeURIComponent(u);'
      '}}catch(e){}_pushMsg("API rewrite failed: "+e);}return u;};'
      // Wrap fetch()
      'if(typeof window.fetch==="function"){'
      'var _origFetch=window.fetch;'
      'window.fetch=function(r){if(typeof r==="string")r=_rewriteApiUrl(r);'
      'return _origFetch.apply(this,arguments);};'
      '}'
      // Wrap XMLHttpRequest.open()
      'if(typeof XMLHttpRequest!=="undefined"){'
      'var _origOpen=XMLHttpRequest.prototype.open;'
      'XMLHttpRequest.prototype.open=function(method,url){'
      'if(typeof url==="string")url=_rewriteApiUrl(url);'
      'return _origOpen.apply(this,arguments);};'
      '}'
      // Wrap Axios if available
      'if(typeof window.axios!=="undefined"&&window.axios){'
      'var _origRequest=window.axios.request;'
      'if(typeof _origRequest==="function"){'
      'window.axios.request=function(cfg){if(cfg&&cfg.url)cfg.url=_rewriteApiUrl(cfg.url);'
      'return _origRequest.apply(this,arguments);};'
      '}'
      '}'
      '}'
      // 5. Reduce known noisy errors in proxied data-origin mode
      'var _swallow=function(msg){msg=String(msg||"");'
      'return msg.indexOf("Unlegal embed")!==-1||'
      'msg.indexOf("IDBFactory")!==-1||'
      'msg.indexOf("gl-particles")!==-1;};'
      'var _prevOnError=window.onerror;'
      'window.onerror=function(m,s,l,c,e){'
      '_stats.errors=(_stats.errors||0)+1;'
      '_pushMsg(m||(e&&e.message)||"");'
      'if(_swallow(m)||(e&&_swallow(e.message))){_stats.swallowedErrors=(_stats.swallowedErrors||0)+1;return true;}'
      'if(typeof _prevOnError==="function"){return _prevOnError.apply(this,arguments);}'
      'return false;};'
      'window.addEventListener("unhandledrejection",function(ev){'
      '_stats.unhandledRejections=(_stats.unhandledRejections||0)+1;'
      'var r=ev&&ev.reason;var m=(r&&r.message)||r;'
      '_pushMsg(m);'
      'if(_swallow(m)){ev.preventDefault();}});'
      'if(window.console&&typeof window.console.error==="function"){'
      'var _prevErr=window.console.error.bind(window.console);'
      'window.console.error=function(){'
      'try{var a=Array.prototype.slice.call(arguments).join(" ");'
      '_pushMsg(a);if(String(a).indexOf("plugin")!==-1){_stats.pluginFailures=(_stats.pluginFailures||0)+1;}}catch(e){}'
      'return _prevErr.apply(window.console,arguments);'
      '};}'
      '}catch(_){}})();</script>'
      // 4. Unlegal-embed overlay (CSS !important beats any inline style JS may set)
      '<style $marker="1">#unlegal-embed{display:none!important}</style>';

  if (_headTag.hasMatch(html)) {
    return html.replaceFirstMapped(_headTag, (m) => '${m.group(0)}$script');
  }

  return '$script$html';
}