UNPKG

code-server

Version:

Run VS Code on a remote server.

6 lines (5 loc) • 9.21 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ const sw=self,VERSION=6,maxActiveHostResourceResponseBodies=32,resourceCacheName=`vscode-resource-cache-${VERSION}`,rootPath=sw.location.pathname.replace(/\/service-worker.js$/,""),searchParams=new URL(location.toString()).searchParams,remoteAuthority=searchParams.get("remoteAuthority"),shouldLimitHostResourceResponseBodies=searchParams.get("platform")==="electron",resourceBaseAuthority=searchParams.get("vscode-resource-base-authority"),perfMark=(t,r={})=>{performance.mark(`webview/service-worker/${t}`,{detail:{...r}})};perfMark("scriptStart");const resolveTimeout=3e4;class RequestStore{constructor(){this.map=new Map,this.requestPool=0}create(){const r=++this.requestPool;let e;const s=new Promise(u=>e=u),n={resolve:e,promise:s};this.map.set(r,n);const a=setTimeout(()=>{clearTimeout(a);const u=this.map.get(r);u===n&&(u.resolve({status:"timeout"}),this.map.delete(r))},resolveTimeout);return{requestId:r,promise:s}}resolve(r,e){const s=this.map.get(r);return s?(s.resolve({status:"ok",value:e}),this.map.delete(r),!0):!1}}class AsyncSemaphore{constructor(r){this.maxConcurrency=r,this.activeCount=0,this.waiters=[]}acquire(){return this.activeCount<this.maxConcurrency&&this.waiters.length===0?(this.activeCount++,Promise.resolve(this.createReleaser())):new Promise(r=>this.waiters.push(r))}createReleaser(){let r=!1;return()=>{if(r)return;r=!0;const e=this.waiters.shift();e?e(this.createReleaser()):this.activeCount--}}}function createJoinedFinalizer(t,r){let e=r,s=!1;return()=>{s||(e--,e===0&&(s=!0,t()))}}function trackReadableStreamLifetime(t,r){const e=t.getReader();let s=!1;const n=()=>{s||(s=!0,r())};return new ReadableStream({async pull(c){try{const{done:a,value:u}=await e.read();if(a){n(),c.close();return}c.enqueue(u)}catch(a){throw n(),a}},cancel(c){const a=e.cancel(c).catch(()=>{});return shouldLimitHostResourceResponseBodies?a.finally(n):(n(),a)}})}const resourceRequestStore=new RequestStore,hostResourceResponseBodySemaphore=new AsyncSemaphore(maxActiveHostResourceResponseBodies),safariResourceStreams=new Map,localhostRequestStore=new RequestStore,unauthorized=()=>new Response("Unauthorized",{status:401}),notFound=()=>new Response("Not Found",{status:404}),methodNotAllowed=()=>new Response("Method Not Allowed",{status:405}),requestTimeout=()=>new Response("Request Timeout",{status:408});sw.addEventListener("message",async t=>{if(!t.source)return;const r=t.source;switch(t.data.channel){case"did-load-resource":{const e=t.data.data;if(e.status===200||e.status===206){let s;if(e.stream)s=e.stream;else{const n=new TransformStream,c=n.writable.getWriter();safariResourceStreams.set(e.id,c),c.closed.then(()=>safariResourceStreams.delete(e.id),()=>safariResourceStreams.delete(e.id)),s=n.readable}e.stream?resourceRequestStore.resolve(e.id,{status:e.status,id:e.id,path:e.path,mime:e.mime,etag:e.etag,mtime:e.mtime,stream:s,range:e.range})||t.waitUntil(s.cancel().catch(()=>{})):resourceRequestStore.resolve(e.id,{status:e.status,id:e.id,path:e.path,mime:e.mime,etag:e.etag,mtime:e.mtime,stream:s,range:e.range})}else resourceRequestStore.resolve(e.id,e)||console.log("Could not resolve unknown resource",e.path);return}case"did-load-resource-chunk":{const e=t.data.data,s=safariResourceStreams.get(e.id);s&&s.write(e.data).catch(()=>{safariResourceStreams.delete(e.id)});return}case"did-load-resource-end":{const e=t.data.data,s=safariResourceStreams.get(e.id);s&&(e.error?s.abort(new Error("Stream error")).catch(()=>{}):s.close().catch(()=>{}),safariResourceStreams.delete(e.id));return}case"did-load-localhost":{const e=t.data.data;localhostRequestStore.resolve(e.id,e.location)||console.log("Could not resolve unknown localhost",e.origin);return}default:{console.log("Unknown message");return}}}),sw.addEventListener("fetch",t=>{const r=new URL(t.request.url);if(typeof resourceBaseAuthority=="string"&&r.protocol==="https:"&&r.hostname.endsWith("."+resourceBaseAuthority))switch(t.request.method){case"GET":case"HEAD":{const e=r.hostname.slice(0,r.hostname.length-(resourceBaseAuthority.length+1)),s=e.split("+",1)[0],n=e.slice(s.length+1);return t.respondWith(processResourceRequest(t,{scheme:s,authority:n,path:r.pathname,query:r.search.replace(/^\?/,"")}))}default:return t.respondWith(methodNotAllowed())}if(r.origin!==sw.origin&&r.host===remoteAuthority)switch(t.request.method){case"GET":case"HEAD":return t.respondWith(processResourceRequest(t,{path:r.pathname,scheme:r.protocol.slice(0,r.protocol.length-1),authority:r.host,query:r.search.replace(/^\?/,"")}));default:return t.respondWith(methodNotAllowed())}if(r.origin!==sw.origin&&r.host.match(/^(localhost|127.0.0.1|0.0.0.0):(\d+)$/))return t.respondWith(processLocalhostRequest(t,r))}),sw.addEventListener("install",t=>{t.waitUntil(sw.skipWaiting())}),sw.addEventListener("activate",t=>{t.waitUntil(sw.clients.claim())});async function processResourceRequest(t,r){let e=await sw.clients.get(t.clientId);if(!e&&(e=await getWorkerClientForId(t.clientId),!e))return console.error("Could not find inner client for request"),notFound();const s=getWebviewIdForClient(e);if(!s&&e.type!=="worker"&&e.type!=="sharedworker")return console.error("Could not resolve webview id"),notFound();const n=t.request.method==="GET"&&!t.request.headers.get("range"),c=(l,f,m)=>{if(l.status==="timeout")return m(),requestTimeout();const q={"Access-Control-Allow-Origin":"*","Cross-Origin-Resource-Policy":"cross-origin"},o=l.value;if(o.status===304)if(f){m();const w=f.clone();for(const[C,g]of Object.entries(q))w.headers.set(C,g);return w}else throw new Error("No cache found");if(o.status===401)return m(),unauthorized();if(o.status!==200&&o.status!==206)return m(),notFound();const i={...q,"Content-Type":o.mime};o.etag&&(i.ETag=o.etag,i["Cache-Control"]="no-cache"),o.mtime&&(i["Last-Modified"]=new Date(o.mtime).toUTCString());const y=new URL(t.request.url).searchParams.get("vscode-coi");if(y==="3"?(i["Cross-Origin-Opener-Policy"]="same-origin",i["Cross-Origin-Embedder-Policy"]="require-corp"):y==="2"?i["Cross-Origin-Embedder-Policy"]="require-corp":y==="1"&&(i["Cross-Origin-Opener-Policy"]="same-origin"),o.stream){if(o.status===206&&o.range)return i["Content-Range"]=o.range,i["Cache-Control"]="no-store",new Response(trackReadableStreamLifetime(o.stream,m),{status:206,headers:i});if(n&&o.etag){const[w,C]=o.stream.tee(),g=createJoinedFinalizer(m,2),E=new Response(trackReadableStreamLifetime(w,g),{status:200,headers:i}),b=new Response(C,{status:200,headers:i});return caches.open(resourceCacheName).then(A=>A.put(t.request,b)).then(void 0,()=>{}).finally(g),E}return new Response(trackReadableStreamLifetime(o.stream,m),{status:200,headers:i})}};let a;n&&(a=await(await caches.open(resourceCacheName)).match(t.request));let u;const d=t.request.headers.get("range");if(d){const l=d.match(/^bytes\=(\d+)\-(\d+)?$/);if(l)u={start:Number(l[1]),end:l[2]!==void 0?Number(l[2]):void 0};else return new Response(null,{status:416,headers:{"Access-Control-Allow-Origin":"*","Content-Range":"*/*"}})}let h;if(s&&(h=await getOuterIframeClient(s),!h.length))return console.log("Could not find parent client for request"),notFound();const p=shouldLimitHostResourceResponseBodies&&s?await hostResourceResponseBodySemaphore.acquire():()=>{},{requestId:R,promise:k}=resourceRequestStore.create();try{if(h)for(const f of h)f.postMessage({channel:"load-resource",id:R,scheme:r.scheme,authority:r.authority,path:r.path,query:r.query,ifNoneMatch:a?.headers.get("ETag"),range:u});const l=await k;try{return c(l,a,p)}catch(f){throw p(),f}}catch(l){throw p(),l}}async function processLocalhostRequest(t,r){const e=await sw.clients.get(t.clientId);if(!e)return fetch(t.request);const s=getWebviewIdForClient(e);if(!s&&e.type!=="worker"&&e.type!=="sharedworker")return console.error("Could not resolve webview id"),fetch(t.request);const n=r.origin,c=async function(d){if(d.status!=="ok"||!d.value)return fetch(t.request);const h=d.value,p=t.request.url.replace(new RegExp(`^${r.origin}(/|$)`),`${h}$1`);return new Response(null,{status:302,headers:{Location:p}})},{requestId:a,promise:u}=localhostRequestStore.create();if(s){const d=await getOuterIframeClient(s);if(!d.length)return console.log("Could not find parent client for request"),notFound();for(const h of d)h.postMessage({channel:"load-localhost",origin:n,id:a})}return u.then(c)}function getWebviewIdForClient(t){return new URL(t.url).searchParams.get("id")}async function getOuterIframeClient(t){return(await sw.clients.matchAll({includeUncontrolled:!0})).filter(e=>{const s=new URL(e.url);return(s.pathname===`${rootPath}/`||s.pathname===`${rootPath}/index.html`||s.pathname===`${rootPath}/index-no-csp.html`)&&s.searchParams.get("id")===t})}async function getWorkerClientForId(t){const r=await sw.clients.matchAll({type:"worker"}),e=await sw.clients.matchAll({type:"sharedworker"});return[...r,...e].find(n=>n.id===t)}