Skip to content

Instantly share code, notes, and snippets.

@brandonpayton
Created April 4, 2024 19:07
Show Gist options
  • Save brandonpayton/d1c47a3b498bb82db83cafd8c211126c to your computer and use it in GitHub Desktop.
Save brandonpayton/d1c47a3b498bb82db83cafd8c211126c to your computer and use it in GitHub Desktop.
Sample of transpiled Service Worker script for playground.wordpress.net
function isURLScoped(e){return e.pathname.startsWith("/scope:")}function getURLScope(e){return isURLScoped(e)?e.pathname.split("/")[1].split(":")[1]:null}function setURLScope(e,t){let r=new URL(e);if(isURLScoped(r))if(t){const s=r.pathname.split("/");s[1]=`scope:${t}`,r.pathname=s.join("/")}else r=removeURLScope(r);else if(t){const s=r.pathname==="/"?"":r.pathname;r.pathname=`/scope:${t}${s}`}return r}function removeURLScope(e){if(!isURLScoped(e))return e;const t=new URL(e),r=t.pathname.split("/");return t.pathname="/"+r.slice(2).join("/"),t}const currentJsRuntime=function(){return typeof process<"u"&&process.release?.name==="node"?"NODE":typeof window<"u"?"WEB":typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope?"WORKER":"NODE"}();if(currentJsRuntime==="NODE"){let e=function(r){return new Promise(function(s,n){r.onload=r.onerror=function(i){r.onload=r.onerror=null,i.type==="load"?s(r.result):n(new Error("Failed to read the blob/file"))}})},t=function(){const r=new Uint8Array([1,2,3,4]),n=new File([r],"test").stream();try{return n.getReader({mode:"byob"}),!0}catch{return!1}};if(typeof File>"u"){class r extends Blob{constructor(n,i,o){super(n);let a;o?.lastModified&&(a=new Date),(!a||isNaN(a.getFullYear()))&&(a=new Date),this.lastModifiedDate=a,this.lastModified=a.getMilliseconds(),this.name=i||""}}global.File=r}typeof Blob.prototype.arrayBuffer>"u"&&(Blob.prototype.arrayBuffer=function(){const s=new FileReader;return s.readAsArrayBuffer(this),e(s)}),typeof Blob.prototype.text>"u"&&(Blob.prototype.text=function(){const s=new FileReader;return s.readAsText(this),e(s)}),(typeof Blob.prototype.stream>"u"||!t())&&(Blob.prototype.stream=function(){let r=0;const s=this;return new ReadableStream({type:"bytes",autoAllocateChunkSize:512*1024,async pull(n){const i=n.byobRequest.view,a=await s.slice(r,r+i.byteLength).arrayBuffer(),l=new Uint8Array(a);new Uint8Array(i.buffer).set(l);const c=l.byteLength;n.byobRequest.respond(c),r+=c,r>=s.size&&n.close()}})})}if(currentJsRuntime==="NODE"&&typeof CustomEvent>"u"){class e extends Event{constructor(r,s={}){super(r,s),this.detail=s.detail}initCustomEvent(){}}globalThis.CustomEvent=e}const kError=Symbol("error"),kMessage=Symbol("message");class ErrorEvent2 extends Event{constructor(t,r={}){super(t),this[kError]=r.error===void 0?null:r.error,this[kMessage]=r.message===void 0?"":r.message}get error(){return this[kError]}get message(){return this[kMessage]}}Object.defineProperty(ErrorEvent2.prototype,"error",{enumerable:!0});Object.defineProperty(ErrorEvent2.prototype,"message",{enumerable:!0});const ErrorEvent=typeof globalThis.ErrorEvent=="function"?globalThis.ErrorEvent:ErrorEvent2;function isExitCodeZero(e){return e instanceof Error?"exitCode"in e&&e?.exitCode===0||e?.name==="ExitStatus"&&"status"in e&&e.status===0:!1}class UnhandledRejectionsTarget extends EventTarget{constructor(){super(...arguments),this.listenersCount=0}addEventListener(t,r){++this.listenersCount,super.addEventListener(t,r)}removeEventListener(t,r){--this.listenersCount,super.removeEventListener(t,r)}hasListeners(){return this.listenersCount>0}}function improveWASMErrorReporting(e){e.asm={...e.asm};const t=new UnhandledRejectionsTarget;for(const r in e.asm)if(typeof e.asm[r]=="function"){const s=e.asm[r];e.asm[r]=function(...n){try{return s(...n)}catch(i){if(!(i instanceof Error))throw i;const o=clarifyErrorMessage(i,e.lastAsyncifyStackSource?.stack);if(e.lastAsyncifyStackSource&&(i.cause=e.lastAsyncifyStackSource),t.hasListeners()){t.dispatchEvent(new ErrorEvent("error",{error:i,message:o}));return}throw isExitCodeZero(i)||showCriticalErrorBox(o),i}}}return t}let functionsMaybeMissingFromAsyncify=[];function getFunctionsMaybeMissingFromAsyncify(){return functionsMaybeMissingFromAsyncify}function clarifyErrorMessage(e,t){if(e.message==="unreachable"){let r=UNREACHABLE_ERROR;t||(r+=`
This stack trace is lacking. For a better one initialize
the PHP runtime with { debug: true }, e.g. PHPNode.load('8.1', { debug: true }).
`),functionsMaybeMissingFromAsyncify=extractPHPFunctionsFromStack(t||e.stack||"");for(const s of functionsMaybeMissingFromAsyncify)r+=` * ${s}
`;return r}return e.message}const UNREACHABLE_ERROR=`
"unreachable" WASM instruction executed.
The typical reason is a PHP function missing from the ASYNCIFY_ONLY
list when building PHP.wasm.
You will need to file a new issue in the WordPress Playground repository
and paste this error message there:
https://github.com/WordPress/wordpress-playground/issues/new
If you're a core developer, the typical fix is to:
* Isolate a minimal reproduction of the error
* Add a reproduction of the error to php-asyncify.spec.ts in the WordPress Playground repository
* Run 'npm run fix-asyncify'
* Commit the changes, push to the repo, release updated NPM packages
Below is a list of all the PHP functions found in the stack trace to
help with the minimal reproduction. If they're all already listed in
the Dockerfile, you'll need to trigger this error again with long stack
traces enabled. In node.js, you can do it using the --stack-trace-limit=100
CLI option:
`,redBg="\x1B[41m",bold="\x1B[1m",reset="\x1B[0m",eol="\x1B[K";let logged=!1;function showCriticalErrorBox(e){if(!logged&&(logged=!0,!e?.trim().startsWith("Program terminated with exit"))){console.log(`${redBg}
${eol}
${bold} WASM ERROR${reset}${redBg}`);for(const t of e.split(`
`))console.log(`${eol} ${t} `);console.log(`${reset}`)}}function extractPHPFunctionsFromStack(e){try{const t=e.split(`
`).slice(1).map(r=>{const s=r.trim().substring(3).split(" ");return{fn:s.length>=2?s[0]:"<unknown>",isWasm:r.includes("wasm://")}}).filter(({fn:r,isWasm:s})=>s&&!r.startsWith("dynCall_")&&!r.startsWith("invoke_")).map(({fn:r})=>r);return Array.from(new Set(t))}catch{return[]}}class Semaphore{constructor({concurrency:t}){this._running=0,this.concurrency=t,this.queue=[]}get running(){return this._running}async acquire(){for(;;)if(this._running>=this.concurrency)await new Promise(t=>this.queue.push(t));else{this._running++;let t=!1;return()=>{t||(t=!0,this._running--,this.queue.length>0&&this.queue.shift()())}}}async run(t){const r=await this.acquire();try{return await t()}finally{r()}}}function joinPaths(...e){let t=e.join("/");const r=t[0]==="/",s=t.substring(t.length-1)==="/";return t=normalizePath(t),!t&&!r&&(t="."),t&&s&&(t+="/"),t}function normalizePath(e){const t=e[0]==="/";return e=normalizePathsArray(e.split("/").filter(r=>!!r),!t).join("/"),(t?"/":"")+e.replace(/\/$/,"")}function normalizePathsArray(e,t){let r=0;for(let s=e.length-1;s>=0;s--){const n=e[s];n==="."?e.splice(s,1):n===".."?(e.splice(s,1),r++):r&&(e.splice(s,1),r--)}if(t)for(;r;r--)e.unshift("..");return e}function splitShellCommand(e){let s=0,n="";const i=[];let o="";for(let a=0;a<e.length;a++){const l=e[a];l==="\\"?((e[a+1]==='"'||e[a+1]==="'")&&a++,o+=e[a]):s===0?l==='"'||l==="'"?(s=1,n=l):l.match(/\s/)?(o.trim().length&&i.push(o.trim()),o=l):i.length&&!o?o=i.pop()+l:o+=l:s===1&&(l===n?(s=0,n=""):o+=l)}return o&&i.push(o.trim()),i}function createSpawnHandler(e){return function(t,r=[],s={}){const n=new ChildProcess,i=new ProcessApi(n);return setTimeout(async()=>{let o=[];if(r.length)o=[t,...r];else if(typeof t=="string")o=splitShellCommand(t);else if(Array.isArray(t))o=t;else throw new Error("Invalid command ",t);await e(o,i,s),n.emit("spawn",!0)}),n}}class EventEmitter{constructor(){this.listeners={}}emit(t,r){this.listeners[t]&&this.listeners[t].forEach(function(s){s(r)})}on(t,r){this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(r)}}class ProcessApi extends EventEmitter{constructor(t){super(),this.childProcess=t,this.exited=!1,this.stdinData=[],t.on("stdin",r=>{this.stdinData?this.stdinData.push(r.slice()):this.emit("stdin",r)})}stdout(t){typeof t=="string"&&(t=new TextEncoder().encode(t)),this.childProcess.stdout.emit("data",t)}stdoutEnd(){this.childProcess.stdout.emit("end",{})}stderr(t){typeof t=="string"&&(t=new TextEncoder().encode(t)),this.childProcess.stderr.emit("data",t)}stderrEnd(){this.childProcess.stderr.emit("end",{})}exit(t){this.exited||(this.exited=!0,this.childProcess.emit("exit",t))}flushStdin(){if(this.stdinData)for(let t=0;t<this.stdinData.length;t++)this.emit("stdin",this.stdinData[t]);this.stdinData=null}}let lastPid=9743;class ChildProcess extends EventEmitter{constructor(t=lastPid++){super(),this.pid=t,this.stdout=new EventEmitter,this.stderr=new EventEmitter;const r=this;this.stdin={write:s=>{r.emit("stdin",s)}}}}ReadableStream.prototype[Symbol.asyncIterator]||(ReadableStream.prototype[Symbol.asyncIterator]=async function*(){const e=this.getReader();try{for(;;){const{done:t,value:r}=await e.read();if(t)return;yield r}}finally{e.releaseLock()}},ReadableStream.prototype.iterate=ReadableStream.prototype[Symbol.asyncIterator]);class PHPResponse{constructor(t,r,s,n="",i=0){this.httpStatusCode=t,this.headers=r,this.bytes=s,this.exitCode=i,this.errors=n}static fromRawData(t){return new PHPResponse(t.httpStatusCode,t.headers,t.bytes,t.errors,t.exitCode)}toRawData(){return{headers:this.headers,bytes:this.bytes,errors:this.errors,exitCode:this.exitCode,httpStatusCode:this.httpStatusCode}}get json(){return JSON.parse(this.text)}get text(){return new TextDecoder().decode(this.bytes)}}class PHPBrowser{#e;#t;constructor(t,r={}){this.requestHandler=t,this.#e={},this.#t={handleRedirects:!1,maxRedirects:4,...r}}async request(t,r=0){const s=await this.requestHandler.request({...t,headers:{...t.headers,cookie:this.serializeCookies()}});if(s.headers["set-cookie"]&&this.setCookies(s.headers["set-cookie"]),this.#t.handleRedirects&&s.headers.location&&r<this.#t.maxRedirects){const n=new URL(s.headers.location[0],this.requestHandler.absoluteUrl);return this.request({url:n.toString(),method:"GET",headers:{}},r+1)}return s}pathToInternalUrl(t){return this.requestHandler.pathToInternalUrl(t)}internalUrlToPath(t){return this.requestHandler.internalUrlToPath(t)}get absoluteUrl(){return this.requestHandler.absoluteUrl}get documentRoot(){return this.requestHandler.documentRoot}setCookies(t){for(const r of t)try{if(!r.includes("="))continue;const s=r.indexOf("="),n=r.substring(0,s),i=r.substring(s+1).split(";")[0];this.#e[n]=i}catch(s){console.error(s)}}serializeCookies(){const t=[];for(const r in this.#e)t.push(`${r}=${this.#e[r]}`);return t.join("; ")}}const DEFAULT_BASE_URL="http://example.com";function toRelativeUrl(e){return e.toString().substring(e.origin.length)}function removePathPrefix(e,t){return!t||!e.startsWith(t)?e:e.substring(t.length)}function ensurePathPrefix(e,t){return!t||e.startsWith(t)?e:t+e}async function encodeAsMultipart(e){const t=`----${Math.random().toString(36).slice(2)}`,r=`multipart/form-data; boundary=${t}`,s=new TextEncoder,n=[];for(const[l,c]of Object.entries(e))n.push(`--${t}\r
`),n.push(`Content-Disposition: form-data; name="${l}"`),c instanceof File&&n.push(`; filename="${c.name}"`),n.push(`\r
`),c instanceof File&&(n.push("Content-Type: application/octet-stream"),n.push(`\r
`)),n.push(`\r
`),c instanceof File?n.push(await fileToUint8Array(c)):n.push(c),n.push(`\r
`);n.push(`--${t}--\r
`);const i=n.reduce((l,c)=>l+c.length,0),o=new Uint8Array(i);let a=0;for(const l of n)o.set(typeof l=="string"?s.encode(l):l,a),a+=l.length;return{bytes:o,contentType:r}}function fileToUint8Array(e){return new Promise(t=>{const r=new FileReader;r.onload=()=>{t(new Uint8Array(r.result))},r.readAsArrayBuffer(e)})}class PHPRequestHandler{#e;#t;#a;#s;#i;#r;#o;#n;constructor(t,r={}){this.#n=new Semaphore({concurrency:1});const{documentRoot:s="/www/",absoluteUrl:n=typeof location=="object"?location?.href:"",rewriteRules:i=[]}=r;this.php=t,this.#e=s;const o=new URL(n);this.#a=o.hostname,this.#s=o.port?Number(o.port):o.protocol==="https:"?443:80,this.#t=(o.protocol||"").replace(":","");const a=this.#s!==443&&this.#s!==80;this.#i=[this.#a,a?`:${this.#s}`:""].join(""),this.#r=o.pathname.replace(/\/+$/,""),this.#o=[`${this.#t}://`,this.#i,this.#r].join(""),this.rewriteRules=i}pathToInternalUrl(t){return`${this.absoluteUrl}${t}`}internalUrlToPath(t){const r=new URL(t);return r.pathname.startsWith(this.#r)&&(r.pathname=r.pathname.slice(this.#r.length)),toRelativeUrl(r)}get isRequestRunning(){return this.#n.running>0}get absoluteUrl(){return this.#o}get documentRoot(){return this.#e}async request(t){const r=t.url.startsWith("http://")||t.url.startsWith("https://"),s=new URL(t.url.split("#")[0],r?void 0:DEFAULT_BASE_URL),n=applyRewriteRules(removePathPrefix(s.pathname,this.#r),this.rewriteRules),i=`${this.#e}${n}`;return seemsLikeAPHPRequestHandlerPath(i)?await this.#c(t,s):this.#l(i)}#l(t){if(!this.php.fileExists(t))return new PHPResponse(404,{"x-file-type":["static"]},new TextEncoder().encode("404 File not found"));const r=this.php.readFileAsBuffer(t);return new PHPResponse(200,{"content-length":[`${r.byteLength}`],"content-type":[inferMimeType(t)],"accept-ranges":["bytes"],"cache-control":["public, max-age=0"]},r)}async#c(t,r){if(this.#n.running>0&&t.headers?.["x-request-issuer"]==="php")return console.warn("Possible deadlock: Called request() before the previous request() have finished. PHP likely issued an HTTP call to itself. Normally this would lead to infinite waiting as Request 1 holds the lock that the Request 2 is waiting to acquire. That's not useful, so PHPRequestHandler will return error 502 instead."),new PHPResponse(502,{},new TextEncoder().encode("502 Bad Gateway"));const s=await this.#n.acquire();try{this.php.addServerGlobalEntry("REMOTE_ADDR","127.0.0.1"),this.php.addServerGlobalEntry("DOCUMENT_ROOT",this.#e),this.php.addServerGlobalEntry("HTTPS",this.#o.startsWith("https://")?"on":"");let n="GET";const i={host:this.#i,...normalizeHeaders(t.headers||{})};let o=t.body;if(typeof o=="object"&&!(o instanceof Uint8Array)){n="POST";const{bytes:l,contentType:c}=await encodeAsMultipart(o);o=l,i["content-type"]=c}let a;try{a=this.#u(r.pathname)}catch{return new PHPResponse(404,{},new TextEncoder().encode("404 File not found"))}return await this.php.run({relativeUri:ensurePathPrefix(toRelativeUrl(r),this.#r),protocol:this.#t,method:t.method||n,body:o,scriptPath:a,headers:i})}finally{s()}}#u(t){let r=removePathPrefix(t,this.#r);r=applyRewriteRules(r,this.rewriteRules),r.includes(".php")?r=r.split(".php")[0]+".php":this.php.isDir(`${this.#e}${r}`)?(r.endsWith("/")||(r=`${r}/`),r=`${r}index.php`):r="/index.php";const s=`${this.#e}${r}`;if(this.php.fileExists(s))return s;throw new Error(`File not found: ${s}`)}}function inferMimeType(e){switch(e.split(".").pop()){case"css":return"text/css";case"js":return"application/javascript";case"png":return"image/png";case"jpg":case"jpeg":return"image/jpeg";case"gif":return"image/gif";case"svg":return"image/svg+xml";case"woff":return"font/woff";case"woff2":return"font/woff2";case"ttf":return"font/ttf";case"otf":return"font/otf";case"eot":return"font/eot";case"ico":return"image/x-icon";case"html":return"text/html";case"json":return"application/json";case"xml":return"application/xml";case"txt":case"md":return"text/plain";default:return"application-octet-stream"}}function seemsLikeAPHPRequestHandlerPath(e){return seemsLikeAPHPFile(e)||seemsLikeADirectoryRoot(e)}function seemsLikeAPHPFile(e){return e.endsWith(".php")||e.includes(".php/")}function seemsLikeADirectoryRoot(e){return!e.split("/").pop().includes(".")}function applyRewriteRules(e,t){for(const r of t)if(new RegExp(r.match).test(e))return e.replace(r.match,r.replacement);return e}const FileErrorCodes={0:"No error occurred. System call completed successfully.",1:"Argument list too long.",2:"Permission denied.",3:"Address in use.",4:"Address not available.",5:"Address family not supported.",6:"Resource unavailable, or operation would block.",7:"Connection already in progress.",8:"Bad file descriptor.",9:"Bad message.",10:"Device or resource busy.",11:"Operation canceled.",12:"No child processes.",13:"Connection aborted.",14:"Connection refused.",15:"Connection reset.",16:"Resource deadlock would occur.",17:"Destination address required.",18:"Mathematics argument out of domain of function.",19:"Reserved.",20:"File exists.",21:"Bad address.",22:"File too large.",23:"Host is unreachable.",24:"Identifier removed.",25:"Illegal byte sequence.",26:"Operation in progress.",27:"Interrupted function.",28:"Invalid argument.",29:"I/O error.",30:"Socket is connected.",31:"There is a directory under that path.",32:"Too many levels of symbolic links.",33:"File descriptor value too large.",34:"Too many links.",35:"Message too large.",36:"Reserved.",37:"Filename too long.",38:"Network is down.",39:"Connection aborted by network.",40:"Network unreachable.",41:"Too many files open in system.",42:"No buffer space available.",43:"No such device.",44:"There is no such file or directory OR the parent directory does not exist.",45:"Executable file format error.",46:"No locks available.",47:"Reserved.",48:"Not enough space.",49:"No message of the desired type.",50:"Protocol not available.",51:"No space left on device.",52:"Function not supported.",53:"The socket is not connected.",54:"Not a directory or a symbolic link to a directory.",55:"Directory not empty.",56:"State not recoverable.",57:"Not a socket.",58:"Not supported, or operation not supported on socket.",59:"Inappropriate I/O control operation.",60:"No such device or address.",61:"Value too large to be stored in data type.",62:"Previous owner died.",63:"Operation not permitted.",64:"Broken pipe.",65:"Protocol error.",66:"Protocol not supported.",67:"Protocol wrong type for socket.",68:"Result too large.",69:"Read-only file system.",70:"Invalid seek.",71:"No such process.",72:"Reserved.",73:"Connection timed out.",74:"Text file busy.",75:"Cross-device link.",76:"Extension: Capabilities insufficient."};function getEmscriptenFsError(e){const t=typeof e=="object"?e?.errno:null;if(t in FileErrorCodes)return FileErrorCodes[t]}function rethrowFileSystemError(e=""){return function(r,s,n){const i=n.value;n.value=function(...o){try{return i.apply(this,o)}catch(a){const l=typeof a=="object"?a?.errno:null;if(l in FileErrorCodes){const c=FileErrorCodes[l],u=typeof o[0]=="string"?o[0]:null,h=u!==null?e.replaceAll("{path}",u):e;throw new Error(`${h}: ${c}`,{cause:a})}throw a}}}}const loadedRuntimes=new Map;function getLoadedRuntime(e){return loadedRuntimes.get(e)}(function(){return typeof process<"u"&&process.release?.name==="node"?"NODE":typeof window<"u"?"WEB":typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope?"WORKER":"NODE"})();var __defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__decorateClass=(e,t,r,s)=>{for(var n=s>1?void 0:s?__getOwnPropDesc(t,r):t,i=e.length-1,o;i>=0;i--)(o=e[i])&&(n=(s?o(t,r,n):o(n))||n);return s&&n&&__defProp(t,r,n),n};const STRING="string",NUMBER="number",__private__dont__use=Symbol("__private__dont__use");class BasePHP{constructor(e,t){this.#phpIniOverrides=[],this.#webSapiInitialized=!1,this.#wasmErrorsTarget=null,this.#serverEntries={},this.#eventListeners=new Map,this.#messageListeners=[],this.semaphore=new Semaphore({concurrency:1}),e!==void 0&&this.initializeRuntime(e),t&&(this.requestHandler=new PHPBrowser(new PHPRequestHandler(this,t)))}#phpIniOverrides;#phpIniPath;#sapiName;#webSapiInitialized;#wasmErrorsTarget;#serverEntries;#eventListeners;#messageListeners;addEventListener(e,t){this.#eventListeners.has(e)||this.#eventListeners.set(e,new Set),this.#eventListeners.get(e).add(t)}removeEventListener(e,t){this.#eventListeners.get(e)?.delete(t)}dispatchEvent(e){const t=this.#eventListeners.get(e.type);if(t)for(const r of t)r(e)}async onMessage(e){this.#messageListeners.push(e)}async setSpawnHandler(handler){typeof handler=="string"&&(handler=createSpawnHandler(eval(handler))),this[__private__dont__use].spawnProcess=handler}get absoluteUrl(){return this.requestHandler.requestHandler.absoluteUrl}get documentRoot(){return this.requestHandler.requestHandler.documentRoot}pathToInternalUrl(e){return this.requestHandler.requestHandler.pathToInternalUrl(e)}internalUrlToPath(e){return this.requestHandler.requestHandler.internalUrlToPath(e)}initializeRuntime(e){if(this[__private__dont__use])throw new Error("PHP runtime already initialized.");const t=getLoadedRuntime(e);if(!t)throw new Error("Invalid PHP runtime id.");this[__private__dont__use]=t,t.onMessage=async r=>{for(const s of this.#messageListeners){const n=await s(r);if(n)return n}return""},this.#wasmErrorsTarget=improveWASMErrorReporting(t),this.dispatchEvent({type:"runtime.initialized"})}async setSapiName(e){if(this[__private__dont__use].ccall("wasm_set_sapi_name",NUMBER,[STRING],[e])!==0)throw new Error("Could not set SAPI name. This can only be done before the PHP WASM module is initialized.Did you already dispatch any requests?");this.#sapiName=e}setPhpIniPath(e){if(this.#webSapiInitialized)throw new Error("Cannot set PHP ini path after calling run().");this.#phpIniPath=e,this[__private__dont__use].ccall("wasm_set_phpini_path",null,["string"],[e])}setPhpIniEntry(e,t){if(this.#webSapiInitialized)throw new Error("Cannot set PHP ini entries after calling run().");this.#phpIniOverrides.push([e,t])}chdir(e){this[__private__dont__use].FS.chdir(e)}async request(e,t){if(!this.requestHandler)throw new Error("No request handler available.");return this.requestHandler.request(e,t)}async run(e){const t=await this.semaphore.acquire();let r;try{if(this.#webSapiInitialized||(this.#initWebRuntime(),this.#webSapiInitialized=!0),e.scriptPath&&!this.fileExists(e.scriptPath))throw new Error(`The script path "${e.scriptPath}" does not exist.`);this.#setScriptPath(e.scriptPath||""),this.#setRelativeRequestUri(e.relativeUri||""),this.#setRequestMethod(e.method||"GET");const s=normalizeHeaders(e.headers||{}),n=s.host||"example.com:443";this.#setRequestHostAndProtocol(n,e.protocol||"http"),this.#setRequestHeaders(s),e.body&&(r=this.#setRequestBody(e.body)),typeof e.code=="string"&&this.#setPHPCode(" ?>"+e.code),this.#addServerGlobalEntriesInWasm();const i=e.env||{};for(const a in i)this.#setEnv(a,i[a]);const o=await this.#handleRequest();if(o.exitCode!==0){const a={stdout:o.text,stderr:o.errors};console.warn("PHP.run() output was:",a);const l=new Error(`PHP.run() failed with exit code ${o.exitCode} and the following output: `+o.errors);throw l.output=a,l.source="request",console.error(l),l}return o}catch(s){throw this.dispatchEvent({type:"request.error",error:s,source:s.source??"php-wasm"}),s}finally{try{r&&this[__private__dont__use].free(r)}finally{t(),this.dispatchEvent({type:"request.end"})}}}#initWebRuntime(){if(this.setPhpIniEntry("auto_prepend_file","/internal/consts.php"),this.fileExists("/internal/consts.php")||this.writeFile("/internal/consts.php",`<?php
if(file_exists('/internal/consts.json')) {
$consts = json_decode(file_get_contents('/internal/consts.json'), true);
foreach ($consts as $const => $value) {
if (!defined($const) && is_scalar($value)) {
define($const, $value);
}
}
}`),this.#phpIniOverrides.length>0){const e=this.#phpIniOverrides.map(([t,r])=>`${t}=${r}`).join(`
`)+`
`;this[__private__dont__use].ccall("wasm_set_phpini_entries",null,[STRING],[e])}this[__private__dont__use].ccall("php_wasm_init",null,[],[])}#getResponseHeaders(){const e="/internal/headers.json";if(!this.fileExists(e))throw new Error("SAPI Error: Could not find response headers file.");const t=JSON.parse(this.readFileAsText(e)),r={};for(const s of t.headers){if(!s.includes(": "))continue;const n=s.indexOf(": "),i=s.substring(0,n).toLowerCase(),o=s.substring(n+2);i in r||(r[i]=[]),r[i].push(o)}return{headers:r,httpStatusCode:t.status}}#setRelativeRequestUri(e){if(this[__private__dont__use].ccall("wasm_set_request_uri",null,[STRING],[e]),e.includes("?")){const t=e.substring(e.indexOf("?")+1);this[__private__dont__use].ccall("wasm_set_query_string",null,[STRING],[t])}}#setRequestHostAndProtocol(e,t){this[__private__dont__use].ccall("wasm_set_request_host",null,[STRING],[e]);let r;try{r=parseInt(new URL(e).port,10)}catch{}(!r||isNaN(r)||r===80)&&(r=t==="https"?443:80),this[__private__dont__use].ccall("wasm_set_request_port",null,[NUMBER],[r]),(t==="https"||!t&&r===443)&&this.addServerGlobalEntry("HTTPS","on")}#setRequestMethod(e){this[__private__dont__use].ccall("wasm_set_request_method",null,[STRING],[e])}#setRequestHeaders(e){e.cookie&&this[__private__dont__use].ccall("wasm_set_cookies",null,[STRING],[e.cookie]),e["content-type"]&&this[__private__dont__use].ccall("wasm_set_content_type",null,[STRING],[e["content-type"]]),e["content-length"]&&this[__private__dont__use].ccall("wasm_set_content_length",null,[NUMBER],[parseInt(e["content-length"],10)]);for(const t in e){let r="HTTP_";["content-type","content-length"].includes(t.toLowerCase())&&(r=""),this.addServerGlobalEntry(`${r}${t.toUpperCase().replace(/-/g,"_")}`,e[t])}}#setRequestBody(e){let t,r;typeof e=="string"?(console.warn("Passing a string as the request body is deprecated. Please use a Uint8Array instead. See https://github.com/WordPress/wordpress-playground/issues/997 for more details"),r=this[__private__dont__use].lengthBytesUTF8(e),t=r+1):(r=e.byteLength,t=e.byteLength);const s=this[__private__dont__use].malloc(t);if(!s)throw new Error("Could not allocate memory for the request body.");return typeof e=="string"?this[__private__dont__use].stringToUTF8(e,s,t+1):this[__private__dont__use].HEAPU8.set(e,s),this[__private__dont__use].ccall("wasm_set_request_body",null,[NUMBER],[s]),this[__private__dont__use].ccall("wasm_set_content_length",null,[NUMBER],[r]),s}#setScriptPath(e){this[__private__dont__use].ccall("wasm_set_path_translated",null,[STRING],[e])}addServerGlobalEntry(e,t){this.#serverEntries[e]=t}#addServerGlobalEntriesInWasm(){for(const e in this.#serverEntries)this[__private__dont__use].ccall("wasm_add_SERVER_entry",null,[STRING,STRING],[e,this.#serverEntries[e]])}#setEnv(e,t){this[__private__dont__use].ccall("wasm_add_ENV_entry",null,[STRING,STRING],[e,t])}defineConstant(e,t){let r={};try{r=JSON.parse(this.fileExists("/internal/consts.json")&&this.readFileAsText("/internal/consts.json")||"{}")}catch{}this.writeFile("/internal/consts.json",JSON.stringify({...r,[e]:t}))}#setPHPCode(e){this[__private__dont__use].ccall("wasm_set_php_code",null,[STRING],[e])}async#handleRequest(){let e,t;try{e=await new Promise((n,i)=>{t=a=>{console.error(a),console.error(a.error);const l=new Error("Rethrown");l.cause=a.error,l.betterMessage=a.message,i(l)},this.#wasmErrorsTarget?.addEventListener("error",t);const o=this[__private__dont__use].ccall("wasm_sapi_handle_request",NUMBER,[],[],{async:!0});return o instanceof Promise?o.then(n,i):n(o)})}catch(n){for(const l in this)typeof this[l]=="function"&&(this[l]=()=>{throw new Error("PHP runtime has crashed – see the earlier error for details.")});this.functionsMaybeMissingFromAsyncify=getFunctionsMaybeMissingFromAsyncify();const i=n,o="betterMessage"in i?i.betterMessage:i.message,a=new Error(o);throw a.cause=i,console.error(a),a}finally{this.#wasmErrorsTarget?.removeEventListener("error",t),this.#serverEntries={}}const{headers:r,httpStatusCode:s}=this.#getResponseHeaders();return new PHPResponse(s,r,this.readFileAsBuffer("/internal/stdout"),this.readFileAsText("/internal/stderr"),e)}mkdir(e){this[__private__dont__use].FS.mkdirTree(e)}mkdirTree(e){this.mkdir(e)}readFileAsText(e){return new TextDecoder().decode(this.readFileAsBuffer(e))}readFileAsBuffer(e){return this[__private__dont__use].FS.readFile(e)}writeFile(e,t){this[__private__dont__use].FS.writeFile(e,t)}unlink(e){this[__private__dont__use].FS.unlink(e)}mv(e,t){try{this[__private__dont__use].FS.rename(e,t)}catch(r){const s=getEmscriptenFsError(r);throw s?new Error(`Could not move ${e} to ${t}: ${s}`,{cause:r}):r}}rmdir(e,t={recursive:!0}){t?.recursive&&this.listFiles(e).forEach(r=>{const s=`${e}/${r}`;this.isDir(s)?this.rmdir(s,t):this.unlink(s)}),this[__private__dont__use].FS.rmdir(e)}listFiles(e,t={prependPath:!1}){if(!this.fileExists(e))return[];try{const r=this[__private__dont__use].FS.readdir(e).filter(s=>s!=="."&&s!=="..");if(t.prependPath){const s=e.replace(/\/$/,"");return r.map(n=>`${s}/${n}`)}return r}catch(r){return console.error(r,{path:e}),[]}}isDir(e){return this.fileExists(e)?this[__private__dont__use].FS.isDir(this[__private__dont__use].FS.lookupPath(e).node.mode):!1}fileExists(e){try{return this[__private__dont__use].FS.lookupPath(e),!0}catch{return!1}}hotSwapPHPRuntime(e){const t=this[__private__dont__use].FS;try{this.exit()}catch{}if(this.initializeRuntime(e),this.#phpIniPath&&this.setPhpIniPath(this.#phpIniPath),this.#sapiName&&this.setSapiName(this.#sapiName),this.requestHandler){const r=this.documentRoot;copyFS(t,this[__private__dont__use].FS,r)}}exit(e=0){this.dispatchEvent({type:"runtime.beforedestroy"});try{this[__private__dont__use]._exit(e)}catch{}this.#webSapiInitialized=!1,this.#wasmErrorsTarget=null,delete this[__private__dont__use].onMessage,delete this[__private__dont__use]}}__decorateClass([rethrowFileSystemError('Could not create directory "{path}"')],BasePHP.prototype,"mkdir",1);__decorateClass([rethrowFileSystemError('Could not create directory "{path}"')],BasePHP.prototype,"mkdirTree",1);__decorateClass([rethrowFileSystemError('Could not read "{path}"')],BasePHP.prototype,"readFileAsText",1);__decorateClass([rethrowFileSystemError('Could not read "{path}"')],BasePHP.prototype,"readFileAsBuffer",1);__decorateClass([rethrowFileSystemError('Could not write to "{path}"')],BasePHP.prototype,"writeFile",1);__decorateClass([rethrowFileSystemError('Could not unlink "{path}"')],BasePHP.prototype,"unlink",1);__decorateClass([rethrowFileSystemError('Could not remove directory "{path}"')],BasePHP.prototype,"rmdir",1);__decorateClass([rethrowFileSystemError('Could not list files in "{path}"')],BasePHP.prototype,"listFiles",1);__decorateClass([rethrowFileSystemError('Could not stat "{path}"')],BasePHP.prototype,"isDir",1);__decorateClass([rethrowFileSystemError('Could not stat "{path}"')],BasePHP.prototype,"fileExists",1);function normalizeHeaders(e){const t={};for(const r in e)t[r.toLowerCase()]=e[r];return t}function copyFS(e,t,r){let s;try{s=e.lookupPath(r)}catch{return}if(!("contents"in s.node))return;if(!e.isDir(s.node.mode)){t.writeFile(r,e.readFile(r));return}t.mkdirTree(r);const n=e.readdir(r).filter(i=>i!=="."&&i!=="..");for(const i of n)copyFS(e,t,joinPaths(r,i))}const DEFAULT_RESPONSE_TIMEOUT=25e3;let lastRequestId=0;function getNextRequestId(){return++lastRequestId}function awaitReply(e,t,r=DEFAULT_RESPONSE_TIMEOUT){return new Promise((s,n)=>{const i=a=>{a.data.type==="response"&&a.data.requestId===t&&(e.removeEventListener("message",i),clearTimeout(o),s(a.data.response))},o=setTimeout(()=>{n(new Error("Request timed out")),e.removeEventListener("message",i)},r);e.addEventListener("message",i)})}function initializeServiceWorker(e){const{handleRequest:t=defaultRequestHandler}=e;self.addEventListener("fetch",r=>{const s=new URL(r.request.url);if(s.pathname.startsWith(self.location.pathname))return;if(!isURLScoped(s)){let i;try{i=new URL(r.request.referrer)}catch{return}if(!isURLScoped(i))return}const n=t(r);n&&r.respondWith(n)})}async function defaultRequestHandler(e){e.preventDefault();const t=new URL(e.request.url),r=await convertFetchEventToPHPRequest(e);if(r.status===404&&r.headers.get("x-file-type")==="static"){const s=await cloneRequest(e.request,{url:t});return fetch(s)}return r}async function convertFetchEventToPHPRequest(e){let t=new URL(e.request.url);if(!isURLScoped(t))try{const o=new URL(e.request.referrer);t=setURLScope(t,getURLScope(o))}catch{}const r=e.request.headers.get("content-type"),s=e.request.method==="POST"?new Uint8Array(await e.request.clone().arrayBuffer()):void 0,n={};for(const o of e.request.headers.entries())n[o[0]]=o[1];let i;try{const o={method:"request",args:[{body:s,url:t.toString(),method:e.request.method,headers:{...n,Host:t.host,"User-agent":self.navigator.userAgent,"Content-type":r}}]},a=getURLScope(t);if(a===null)throw new Error(`The URL ${t.toString()} is not scoped. This should not happen.`);const l=await broadcastMessageExpectReply(o,a);i=await awaitReply(self,l),delete i.headers["x-frame-options"]}catch(o){throw console.error(o,{url:t.toString()}),o}return new Response(i.bytes,{headers:i.headers,status:i.httpStatusCode})}async function broadcastMessageExpectReply(e,t){const r=getNextRequestId();for(const s of await self.clients.matchAll({includeUncontrolled:!0}))s.postMessage({...e,scope:t,requestId:r});return r}async function cloneRequest(e,t){const r=["GET","HEAD"].includes(e.method)||"body"in t?void 0:await e.blob();return new Request(t.url||e.url,{body:r,method:e.method,headers:e.headers,referrer:e.referrer,referrerPolicy:e.referrerPolicy,mode:e.mode==="navigate"?"same-origin":e.mode,credentials:e.credentials,cache:e.cache,redirect:e.redirect,integrity:e.integrity,...t})}const wordPressRewriteRules=[{match:/^\/(.*?)(\/wp-(content|admin|includes).*)/g,replacement:"$2"}],nightly="nightly",beta="6.5-RC4";var SupportedWordPressVersions={nightly,beta,"6.5":"6.5","6.4":"6.4.3","6.3":"6.3.3","6.2":"6.2.4","6.1":"6.1.5"};const SupportedWordPressVersionsList=Object.keys(SupportedWordPressVersions);SupportedWordPressVersionsList.filter(e=>e.match(/^\d/))[0];self.document||(self.document={});initializeServiceWorker({handleRequest(e){const t=new URL(e.request.url);let r=getURLScope(t);if(!r)try{r=getURLScope(new URL(e.request.referrer))}catch{}const s=removeURLScope(t);if(s.pathname.startsWith("/plugin-proxy")||s.pathname.startsWith("/client/index.js"))return;e.preventDefault();async function i(){if(t.pathname.endsWith("/wp-includes/empty.html"))return emptyHtml();const{staticAssetsDirectory:o}=await getScopedWpDetails(r),a=await convertFetchEventToPHPRequest(e);if(a.status===404&&a.headers.get("x-file-type")==="static"){const l=new URL(e.request.url),c=removeURLScope(l);c.pathname=applyRewriteRules(c.pathname,wordPressRewriteRules),!c.pathname.startsWith("/@fs")&&!c.pathname.startsWith("/assets")&&(c.pathname=`/${o}${c.pathname}`);const u=await cloneRequest(e.request,{url:c});return fetch(u).catch(h=>{if(h?.name==="TypeError")return new Promise(d=>{setTimeout(()=>{d(fetch(u))},Math.random()*1500)});throw h})}if(s.pathname.endsWith("/js/dist/block-editor.js")||s.pathname.endsWith("/js/dist/block-editor.min.js")||s.pathname.endsWith("build/block-editor/index.js")||s.pathname.endsWith("build/block-editor/index.min.js")){const l=await a.text(),c=`${controlledIframe} ${l.replace(/\(\s*"iframe",/,"(__playground_ControlledIframe,")}`;return new Response(c,{status:a.status,statusText:a.statusText,headers:a.headers})}return a}return i()}});const controlledIframe=`
/**
* A synchronous function to read a blob URL as text.
*
* @param {string} url
* @returns {string}
*/
const __playground_readBlobAsText = function (url) {
try {
let xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.overrideMimeType('text/plain;charset=utf-8');
xhr.send();
return xhr.responseText;
} catch(e) {
return '';
} finally {
URL.revokeObjectURL(url);
}
}
window.__playground_ControlledIframe = window.wp.element.forwardRef(function (props, ref) {
const source = window.wp.element.useMemo(function () {
if (props.srcDoc) {
// WordPress <= 6.2 uses a srcDoc that only contains a doctype.
return '/wp-includes/empty.html';
} else if (props.src && props.src.startsWith('blob:')) {
// WordPress 6.3 uses a blob URL with doctype and a list of static assets.
// Let's pass the document content to empty.html and render it there.
return '/wp-includes/empty.html#' + encodeURIComponent(__playground_readBlobAsText(props.src));
} else {
// WordPress >= 6.4 uses a plain HTTPS URL that needs no correction.
return props.src;
}
}, [props.src]);
return (
window.wp.element.createElement('iframe', {
...props,
ref: ref,
src: source,
// Make sure there's no srcDoc, as it would interfere with the src.
srcDoc: undefined
})
)
});`;function emptyHtml(){return new Response("<!doctype html><script>const hash = window.location.hash.substring(1); if ( hash ) document.write(decodeURIComponent(hash))<\/script>",{status:200,headers:{"content-type":"text/html"}})}const scopeToWpModule={};async function getScopedWpDetails(e){if(!scopeToWpModule[e]){const t=await broadcastMessageExpectReply({method:"getWordPressModuleDetails"},e);scopeToWpModule[e]=await awaitReply(self,t)}return scopeToWpModule[e]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment