├── .github └── workflows │ ├── codeql.yml │ ├── deploy.yml │ ├── deployConfig.js │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.js ├── demo ├── index.html ├── index.js ├── package-lock.json └── package.json ├── lib ├── builtinWorker.html.js ├── createCallbackStore.js ├── createSuperJSON.js ├── generateUniqueId.js ├── index.d.ts └── index.js ├── package-lock.json ├── package.json ├── server ├── index.html └── worker.js └── test ├── helpers ├── bundle.js ├── createServer.js └── run.js ├── index.js └── suite.js /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '18 5 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Use only 'java' to analyze code written in Java, Kotlin or both 38 | # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both 39 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 40 | 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v3 44 | 45 | # Initializes the CodeQL tools for scanning. 46 | - name: Initialize CodeQL 47 | uses: github/codeql-action/init@v2 48 | with: 49 | languages: ${{ matrix.language }} 50 | # If you wish to specify custom queries, you can do so here or in a config file. 51 | # By default, queries listed here will override any specified in a config file. 52 | # Prefix the list here with "+" to use these queries and those in the config file. 53 | 54 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 55 | # queries: security-extended,security-and-quality 56 | 57 | 58 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 59 | # If this step fails, then you should remove it and run the build manually (see below) 60 | - name: Autobuild 61 | uses: github/codeql-action/autobuild@v2 62 | 63 | # ℹ️ Command-line programs to run using the OS shell. 64 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 65 | 66 | # If the Autobuild fails above, remove it and uncomment the following three lines. 67 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 68 | 69 | # - run: | 70 | # echo "Run, Build Application using script" 71 | # ./location_of_script_within_repo/buildscript.sh 72 | 73 | - name: Perform CodeQL Analysis 74 | uses: github/codeql-action/analyze@v2 75 | with: 76 | category: "/language:${{matrix.language}}" 77 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | concurrency: deploy 4 | 5 | on: 6 | release: 7 | types: [created] 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Use Node.js 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: 20 20 | 21 | - name: Install and Build 22 | run: | 23 | npm install 24 | npm run build 25 | cd demo 26 | npm install 27 | npm run build 28 | rm -rf node_modules 29 | 30 | - id: npmjsVersion 31 | name: Extract version from package.json 32 | run: | 33 | echo "npmjsVersion=`awk '/version/{gsub(/("|",)/,"",$2);print $2};' package.json`" >> $GITHUB_OUTPUT 34 | 35 | - name: Sync production 36 | run: | 37 | ./node_modules/@markwylde/ftp-deploy/index.js .github/workflows/deployConfig.js 38 | env: 39 | TOP_FTP_HOSTNAME: ${{ secrets.top_ftp_server }} 40 | TOP_FTP_USERNAME: ${{ secrets.top_ftp_username }} 41 | TOP_FTP_PASSWORD: ${{ secrets.top_ftp_password }} 42 | SUB_FTP_HOSTNAME: ${{ secrets.sub_ftp_server }} 43 | SUB_FTP_USERNAME: ${{ secrets.sub_ftp_username }} 44 | SUB_FTP_PASSWORD: ${{ secrets.sub_ftp_password }} 45 | -------------------------------------------------------------------------------- /.github/workflows/deployConfig.js: -------------------------------------------------------------------------------- 1 | import info from '../../package.json' assert { type: 'json' }; 2 | 3 | export default { 4 | tasks: [{ 5 | hostname: process.env.SUB_FTP_HOSTNAME, 6 | username: process.env.SUB_FTP_USERNAME, 7 | password: process.env.SUB_FTP_PASSWORD, 8 | source: './server/dist', 9 | destination: 'v' + info.version 10 | }, { 11 | hostname: process.env.TOP_FTP_HOSTNAME, 12 | username: process.env.TOP_FTP_USERNAME, 13 | password: process.env.TOP_FTP_PASSWORD, 14 | source: './demo', 15 | destination: '', 16 | clearDestination: true 17 | }] 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Use Node.js 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: 19 20 | 21 | - name: Install and Build 22 | run: | 23 | npm install 24 | npm run build 25 | 26 | - name: Run tests 27 | run: | 28 | node test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | demo/index.min.js 3 | server/dist 4 | .env 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Mark Wylde 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WorkerBox 2 | A secure sandbox to execute untrusted user JavaScript, in a web browser, without any risk to your own domain/site/page. 3 | 4 | ## Installation 5 | ``` 6 | npm install --save workerboxjs 7 | ``` 8 | 9 | ## Usage 10 | ```javascript 11 | import createWorkerBox from 'workerboxjs'; 12 | 13 | // Note each `workerbox` instance has it's own sandbox 14 | const { run, destroy } = await createWorkerBox(); 15 | 16 | let callback; 17 | const scope = { 18 | name: 'Mark', 19 | getMessage: () => 'Have a great day!', 20 | setCallback: fn => { 21 | // You can store arguments, objects, arrays and returned values 22 | // outside of the scope of your main app, and then call them 23 | // from anywhere, so long as the worker is not destroyed. 24 | callback = fn; 25 | } 26 | }; 27 | 28 | setInterval(() => { 29 | if (callback) { 30 | // This will communicate with the workerbox transparently. 31 | callback(); 32 | } 33 | }); 34 | 35 | // You can save state between running code 36 | // But this will not save between different workerbox instances. 37 | await run(` 38 | globalThis.sharedVariable = 123 39 | `); 40 | 41 | const result = await run(` 42 | // globalThis.sharedVariable === 123; 43 | 44 | async function sayHello (who) { 45 | return 'Hello ' + who + '. ' + await getMessage(); 46 | } 47 | 48 | return sayHello(name); 49 | `, scope); 50 | 51 | // result === 'Hello Mark. Have a great day!' 52 | 53 | // Destroys the workerbox, terminating the webworker 54 | destroy() 55 | ``` 56 | 57 | ## Errors and Stack traces 58 | Runtime errors should have readable stacktraces, for example: 59 | 60 | **The following code:** 61 | ```javascript 62 | await run(` 63 | const a = 1; 64 | a(); 65 | `); 66 | ``` 67 | 68 | **Should return the following error:** 69 | ```text 70 | TypeError: a is not a function 71 | at sandbox (:2:2) 72 | ``` 73 | 74 | However syntax errors will not have a stack trace, for example: 75 | 76 | **The following code:** 77 | ```javascript 78 | await run(` 79 | return 1 + 80 | `); 81 | ``` 82 | 83 | **Should return the following error:** 84 | ```text 85 | Unexpected token '}' 86 | ``` 87 | 88 | It would be helpful for your users if you ran the script through a linter or ast parser, to ensure the JavaScript is valid, and provide useful errors if not. 89 | 90 | ## Development 91 | If you want to check this project out locally, you can do the following: 92 | 93 | ### Run your own local server 94 | ``` 95 | git clone https://github.com/markwylde/workerbox.git 96 | cd workerbox 97 | npm install 98 | npm run start 99 | ``` 100 | 101 | Visit https://0.0.0.0:8002 in your browser and make sure to ignore the TLS security errors. 102 | Web workers will only work in secure contexts, so we need to do this locally. 103 | 104 | ### Run the demo project 105 | ``` 106 | cd demo 107 | npm install 108 | npm run start 109 | ``` 110 | 111 | Visit https://0.0.0.0:8000 in your browser. 112 | 113 | ### Run the tests 114 | 115 | Build the server side component and run the tests: 116 | 117 | ``` 118 | npm run build 119 | npm test 120 | ``` 121 | 122 | ## How does it work? 123 | An iframe is inserted into the page (optionally from a completely separate domain). 124 | 125 | The iframe then creates a web worker, and handles posting messages between the iframe, webworker and your own app. 126 | 127 | Because the only communication between the user code and the workerbox is done through messaging, the argument inputs and outputs must all be serializable. We use [SuperJSON](https://github.com/flightcontrolhq/superjson) for serialization and deserialization. 128 | 129 | ### Separate domain 130 | While the iframe has the `sandbox="allow-scripts"` attribute set, and therefore acts like it's on another domain, you can still run the server on another domain if you wish. 131 | 132 | ```javascript 133 | const { run } = await createWorkerBox({ 134 | serverUrl: 'https://sandbox.workerbox.net', 135 | appendVersion: true 136 | }); 137 | ``` 138 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { minify } from 'minify'; 3 | import esbuild from 'esbuild'; 4 | import chokidar from 'chokidar'; 5 | import debounce from 'debounce'; 6 | 7 | const isWatching = process.argv[2] === '--watch'; 8 | 9 | const minifyOptions = { 10 | js: { 11 | type: 'terser', 12 | terser: { 13 | compress: { 14 | keep_fnames: true 15 | }, 16 | mangle: { 17 | keep_fnames: true 18 | } 19 | } 20 | } 21 | }; 22 | 23 | async function build () { 24 | console.log(new Date(), 'rebuilding...'); 25 | await fs.promises.rm('server/dist', { recursive: true, force: true }); 26 | await fs.promises.mkdir('server/dist'); 27 | 28 | await esbuild.build({ 29 | entryPoints: ['./server/worker.js'], 30 | bundle: true, 31 | outfile: './server/dist/worker.js' 32 | }).then(console.log); 33 | 34 | if (!isWatching) { 35 | const minifiedHtml = await minify('server/dist/worker.js', minifyOptions); 36 | await fs.promises.writeFile('./server/dist/worker.js', minifiedHtml); 37 | } 38 | 39 | const jsData = await fs.promises.readFile('./server/dist/worker.js', 'utf8'); 40 | let htmlData = await fs.promises.readFile('./server/index.html', 'utf8'); 41 | htmlData = htmlData.replace('{{WORKERSCRIPT}}', jsData); 42 | await fs.promises.writeFile('./server/dist/index.html', htmlData); 43 | await fs.promises.writeFile( 44 | './lib/builtinWorker.html.js', 45 | '// built from the ./server/dist/index.html file during npm run build\nconst builtinWorker = atob(`' + Buffer.from(htmlData).toString('base64') + '`); export default builtinWorker;' 46 | ); 47 | await fs.promises.rm('./server/dist/worker.js'); 48 | } 49 | 50 | if (isWatching) { 51 | const debouncedBuild = debounce(build, 300); 52 | chokidar.watch( 53 | ['./lib/*', './server'], { 54 | ignored: ['**/dist/**', './lib/builtinWorker.html.js'] 55 | }).on('all', (why, what) => { 56 | console.log(new Date(), why, what); 57 | debouncedBuild(); 58 | } 59 | ); 60 | } else { 61 | build(); 62 | } 63 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | WorkerBox Demo 8 | 9 | 31 | 32 | 33 |

WorkerBox.net

34 |

35 | Free and Open Source, on Github 36 |

37 |

A secure sandbox for you to run end user JavaScript safely away from 38 | your own application. 39 |

40 | 44 | 45 |

Demo

46 |

The following is a demo of using WorkerBox to create a plugin 47 | architecture for a web UI. There are messages on the right, 48 | and an action bar that you can add commands to. 49 |

50 | 51 |

It will be impossible for you to effect the DOM of this page, without 52 | using the deliberately exposed methods on the scope. 53 |

54 | 55 |

Scope

56 |

The scope is how you communicate with your users code.

57 | 58 |

For this demo, you can't edit the scope, as it's hard coded into the demo app.

59 | 60 |
61 | 62 |
63 |
64 |

User Playground

65 |

You can edit the code below and see the results on the right.

66 |
67 |
68 | 69 |
70 |

Result

71 |

Toolbar

72 |
73 |

Messages

74 |
    75 |
  • Message one
  • 76 |
77 | 78 |

Return:

79 |
Running
80 |
81 |
82 | 83 |

How does it work?

84 |

An iframe is inserted into the page from a completely separate domain.

85 | 86 |

The iframe then creates a web worker, and handles posting messages between 87 | the iframe, webworker and your own app.

88 | 89 |

Because the only communication between the user code and the workerbox instead 90 | is done through messaging, the argument inputs and outputs must all be 91 | serializable. We use SuperJSON 92 | for serialization and deserialization.

93 | 94 | 95 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | import createWorkerBox from 'workerboxjs'; 2 | import debounce from 'debounce'; 3 | import { EditorView, basicSetup } from 'codemirror'; 4 | import { javascript } from '@codemirror/lang-javascript'; 5 | import { EditorState } from '@codemirror/state'; 6 | 7 | const scopeEditor = new EditorView({ 8 | doc: ` 9 | const scope = { 10 | name: 'Mark', 11 | 12 | getFullName: (first, last) => \`\${first} \${last}\`, 13 | 14 | clearToolbar: () => { 15 | toolbar.innerHTML = ''; 16 | }, 17 | 18 | addToolbarButton: (title, onClick) => { 19 | const element = document.createElement('button'); 20 | element.textContent = title; 21 | element.addEventListener('click', () => { 22 | timesCalled = timesCalled + 1; 23 | onClick(timesCalled); 24 | }); 25 | toolbar.appendChild(element); 26 | }, 27 | 28 | addMessage: (message) => { 29 | const element = document.createElement('li'); 30 | element.textContent = message; 31 | messages.appendChild(element); 32 | } 33 | } 34 | `.slice(1), 35 | extensions: [basicSetup, javascript(), EditorState.readOnly.of(true)], 36 | parent: document.querySelector('#scopePreview') 37 | }); 38 | 39 | const editor = new EditorView({ 40 | extensions: [basicSetup, javascript()], 41 | doc: ` 42 | // Let's clear our example toolbar between runs 43 | clearToolbar(); 44 | 45 | // Scoped function calls can have arguments 46 | addToolbarButton('Add Message', (timesCalled) => { 47 | console.log('Add message has been called', timesCalled, 'times'); 48 | const time = (new Date()).toLocaleTimeString(); 49 | addMessage(\`\${name}: \${time}\`); 50 | }); 51 | 52 | // Note all functions are converted to promises 53 | const fullName = await getFullName('Mark', 'Wylde'); 54 | console.log(fullName); 55 | 56 | // You can define new functions 57 | function add (a, b) { 58 | return a + b; 59 | } 60 | 61 | // And return the answer to be returned after execution 62 | return add(1, 2); 63 | `.slice(1), 64 | parent: document.querySelector('#codeInput') 65 | }); 66 | 67 | const codeOutput = document.querySelector('#codeOutput'); 68 | 69 | const runPromise = process.env.NODE_ENV === 'development' 70 | ? createWorkerBox('https://localhost:8002/', { 71 | appendVersion: false 72 | }) 73 | : createWorkerBox('https://sandbox.workerbox.net/'); 74 | 75 | const toolbar = document.querySelector('#toolbar'); 76 | const messages = document.querySelector('#messages'); 77 | 78 | let timesCalled = 0; 79 | async function execute () { 80 | const { run } = await runPromise; 81 | const code = editor.state.doc.toString(); 82 | const scope = { 83 | name: 'Mark', 84 | 85 | getFullName: (first, last) => `${first} ${last}`, 86 | 87 | clearToolbar: () => { 88 | toolbar.innerHTML = ''; 89 | }, 90 | 91 | addToolbarButton: (title, onClick) => { 92 | const element = document.createElement('button'); 93 | element.textContent = title; 94 | element.addEventListener('click', () => { 95 | timesCalled = timesCalled + 1; 96 | onClick(timesCalled); 97 | }); 98 | toolbar.appendChild(element); 99 | }, 100 | 101 | addMessage: (message) => { 102 | const element = document.createElement('li'); 103 | element.textContent = message; 104 | messages.appendChild(element); 105 | } 106 | }; 107 | 108 | try { 109 | const result = await run(code, scope); 110 | codeOutput.innerHTML = result; 111 | } catch (error) { 112 | codeOutput.innerHTML = error; 113 | } 114 | } 115 | execute(); 116 | const executeDebounce = debounce(execute, 500); 117 | codeInput.addEventListener('input', executeDebounce); 118 | codeInput.addEventListener('change', executeDebounce); 119 | codeInput.addEventListener('keypress', executeDebounce); 120 | codeInput.addEventListener('keyup', executeDebounce); 121 | codeInput.addEventListener('paste', executeDebounce); 122 | -------------------------------------------------------------------------------- /demo/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workerbox", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "workerbox", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@codemirror/lang-javascript": "^6.2.2", 13 | "@codemirror/state": "^6.4.1", 14 | "codemirror": "^6.0.1", 15 | "debounce": "^2.1.0", 16 | "workerboxjs": "file:../" 17 | }, 18 | "devDependencies": { 19 | "esbuild": "^0.21.5", 20 | "servatron": "^2.5.0" 21 | } 22 | }, 23 | "..": { 24 | "name": "workerboxjs", 25 | "version": "6.2.0", 26 | "license": "MIT", 27 | "devDependencies": { 28 | "@markwylde/ftp-deploy": "^2.0.3", 29 | "chokidar": "^3.6.0", 30 | "debounce": "^2.1.0", 31 | "esbuild": "^0.21.5", 32 | "just-tap": "^2.9.4", 33 | "minify": "^9.2.0", 34 | "puppeteer": "^22.12.1", 35 | "servatron": "^2.5.0" 36 | } 37 | }, 38 | "node_modules/@codemirror/autocomplete": { 39 | "version": "6.3.3", 40 | "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.3.3.tgz", 41 | "integrity": "sha512-8MGqPuKvE0W0XjabDYX3BFJNJRsYFAYpDBol+YCg6o5t2JyoDeoMYrqRMVnJv1B1FBgNAkvdDVrNrVE/7c9qvQ==", 42 | "dependencies": { 43 | "@codemirror/language": "^6.0.0", 44 | "@codemirror/state": "^6.0.0", 45 | "@codemirror/view": "^6.5.0", 46 | "@lezer/common": "^1.0.0" 47 | }, 48 | "peerDependencies": { 49 | "@codemirror/language": "^6.0.0", 50 | "@codemirror/state": "^6.0.0", 51 | "@codemirror/view": "^6.0.0", 52 | "@lezer/common": "^1.0.0" 53 | } 54 | }, 55 | "node_modules/@codemirror/commands": { 56 | "version": "6.1.2", 57 | "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.2.tgz", 58 | "integrity": "sha512-sO3jdX1s0pam6lIdeSJLMN3DQ6mPEbM4yLvyKkdqtmd/UDwhXA5+AwFJ89rRXm6vTeOXBsE5cAmlos/t7MJdgg==", 59 | "dependencies": { 60 | "@codemirror/language": "^6.0.0", 61 | "@codemirror/state": "^6.0.0", 62 | "@codemirror/view": "^6.0.0", 63 | "@lezer/common": "^1.0.0" 64 | } 65 | }, 66 | "node_modules/@codemirror/lang-javascript": { 67 | "version": "6.2.2", 68 | "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz", 69 | "integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==", 70 | "dependencies": { 71 | "@codemirror/autocomplete": "^6.0.0", 72 | "@codemirror/language": "^6.6.0", 73 | "@codemirror/lint": "^6.0.0", 74 | "@codemirror/state": "^6.0.0", 75 | "@codemirror/view": "^6.17.0", 76 | "@lezer/common": "^1.0.0", 77 | "@lezer/javascript": "^1.0.0" 78 | } 79 | }, 80 | "node_modules/@codemirror/language": { 81 | "version": "6.8.0", 82 | "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.8.0.tgz", 83 | "integrity": "sha512-r1paAyWOZkfY0RaYEZj3Kul+MiQTEbDvYqf8gPGaRvNneHXCmfSaAVFjwRUPlgxS8yflMxw2CTu6uCMp8R8A2g==", 84 | "dependencies": { 85 | "@codemirror/state": "^6.0.0", 86 | "@codemirror/view": "^6.0.0", 87 | "@lezer/common": "^1.0.0", 88 | "@lezer/highlight": "^1.0.0", 89 | "@lezer/lr": "^1.0.0", 90 | "style-mod": "^4.0.0" 91 | } 92 | }, 93 | "node_modules/@codemirror/lint": { 94 | "version": "6.1.0", 95 | "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.1.0.tgz", 96 | "integrity": "sha512-mdvDQrjRmYPvQ3WrzF6Ewaao+NWERYtpthJvoQ3tK3t/44Ynhk8ZGjTSL9jMEv8CgSMogmt75X8ceOZRDSXHtQ==", 97 | "dependencies": { 98 | "@codemirror/state": "^6.0.0", 99 | "@codemirror/view": "^6.0.0", 100 | "crelt": "^1.0.5" 101 | } 102 | }, 103 | "node_modules/@codemirror/search": { 104 | "version": "6.2.3", 105 | "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.2.3.tgz", 106 | "integrity": "sha512-V9n9233lopQhB1dyjsBK2Wc1i+8hcCqxl1wQ46c5HWWLePoe4FluV3TGHoZ04rBRlGjNyz9DTmpJErig8UE4jw==", 107 | "dependencies": { 108 | "@codemirror/state": "^6.0.0", 109 | "@codemirror/view": "^6.0.0", 110 | "crelt": "^1.0.5" 111 | } 112 | }, 113 | "node_modules/@codemirror/state": { 114 | "version": "6.4.1", 115 | "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", 116 | "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==" 117 | }, 118 | "node_modules/@codemirror/view": { 119 | "version": "6.28.2", 120 | "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.28.2.tgz", 121 | "integrity": "sha512-A3DmyVfjgPsGIjiJqM/zvODUAPQdQl3ci0ghehYNnbt5x+o76xq+dL5+mMBuysDXnI3kapgOkoeJ0sbtL/3qPw==", 122 | "dependencies": { 123 | "@codemirror/state": "^6.4.0", 124 | "style-mod": "^4.1.0", 125 | "w3c-keyname": "^2.2.4" 126 | } 127 | }, 128 | "node_modules/@esbuild/aix-ppc64": { 129 | "version": "0.21.5", 130 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", 131 | "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", 132 | "cpu": [ 133 | "ppc64" 134 | ], 135 | "dev": true, 136 | "optional": true, 137 | "os": [ 138 | "aix" 139 | ], 140 | "engines": { 141 | "node": ">=12" 142 | } 143 | }, 144 | "node_modules/@esbuild/android-arm": { 145 | "version": "0.21.5", 146 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", 147 | "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", 148 | "cpu": [ 149 | "arm" 150 | ], 151 | "dev": true, 152 | "optional": true, 153 | "os": [ 154 | "android" 155 | ], 156 | "engines": { 157 | "node": ">=12" 158 | } 159 | }, 160 | "node_modules/@esbuild/android-arm64": { 161 | "version": "0.21.5", 162 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", 163 | "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", 164 | "cpu": [ 165 | "arm64" 166 | ], 167 | "dev": true, 168 | "optional": true, 169 | "os": [ 170 | "android" 171 | ], 172 | "engines": { 173 | "node": ">=12" 174 | } 175 | }, 176 | "node_modules/@esbuild/android-x64": { 177 | "version": "0.21.5", 178 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", 179 | "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", 180 | "cpu": [ 181 | "x64" 182 | ], 183 | "dev": true, 184 | "optional": true, 185 | "os": [ 186 | "android" 187 | ], 188 | "engines": { 189 | "node": ">=12" 190 | } 191 | }, 192 | "node_modules/@esbuild/darwin-arm64": { 193 | "version": "0.21.5", 194 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", 195 | "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", 196 | "cpu": [ 197 | "arm64" 198 | ], 199 | "dev": true, 200 | "optional": true, 201 | "os": [ 202 | "darwin" 203 | ], 204 | "engines": { 205 | "node": ">=12" 206 | } 207 | }, 208 | "node_modules/@esbuild/darwin-x64": { 209 | "version": "0.21.5", 210 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", 211 | "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", 212 | "cpu": [ 213 | "x64" 214 | ], 215 | "dev": true, 216 | "optional": true, 217 | "os": [ 218 | "darwin" 219 | ], 220 | "engines": { 221 | "node": ">=12" 222 | } 223 | }, 224 | "node_modules/@esbuild/freebsd-arm64": { 225 | "version": "0.21.5", 226 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", 227 | "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", 228 | "cpu": [ 229 | "arm64" 230 | ], 231 | "dev": true, 232 | "optional": true, 233 | "os": [ 234 | "freebsd" 235 | ], 236 | "engines": { 237 | "node": ">=12" 238 | } 239 | }, 240 | "node_modules/@esbuild/freebsd-x64": { 241 | "version": "0.21.5", 242 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", 243 | "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", 244 | "cpu": [ 245 | "x64" 246 | ], 247 | "dev": true, 248 | "optional": true, 249 | "os": [ 250 | "freebsd" 251 | ], 252 | "engines": { 253 | "node": ">=12" 254 | } 255 | }, 256 | "node_modules/@esbuild/linux-arm": { 257 | "version": "0.21.5", 258 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", 259 | "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", 260 | "cpu": [ 261 | "arm" 262 | ], 263 | "dev": true, 264 | "optional": true, 265 | "os": [ 266 | "linux" 267 | ], 268 | "engines": { 269 | "node": ">=12" 270 | } 271 | }, 272 | "node_modules/@esbuild/linux-arm64": { 273 | "version": "0.21.5", 274 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", 275 | "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", 276 | "cpu": [ 277 | "arm64" 278 | ], 279 | "dev": true, 280 | "optional": true, 281 | "os": [ 282 | "linux" 283 | ], 284 | "engines": { 285 | "node": ">=12" 286 | } 287 | }, 288 | "node_modules/@esbuild/linux-ia32": { 289 | "version": "0.21.5", 290 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", 291 | "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", 292 | "cpu": [ 293 | "ia32" 294 | ], 295 | "dev": true, 296 | "optional": true, 297 | "os": [ 298 | "linux" 299 | ], 300 | "engines": { 301 | "node": ">=12" 302 | } 303 | }, 304 | "node_modules/@esbuild/linux-loong64": { 305 | "version": "0.21.5", 306 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", 307 | "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", 308 | "cpu": [ 309 | "loong64" 310 | ], 311 | "dev": true, 312 | "optional": true, 313 | "os": [ 314 | "linux" 315 | ], 316 | "engines": { 317 | "node": ">=12" 318 | } 319 | }, 320 | "node_modules/@esbuild/linux-mips64el": { 321 | "version": "0.21.5", 322 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", 323 | "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", 324 | "cpu": [ 325 | "mips64el" 326 | ], 327 | "dev": true, 328 | "optional": true, 329 | "os": [ 330 | "linux" 331 | ], 332 | "engines": { 333 | "node": ">=12" 334 | } 335 | }, 336 | "node_modules/@esbuild/linux-ppc64": { 337 | "version": "0.21.5", 338 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", 339 | "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", 340 | "cpu": [ 341 | "ppc64" 342 | ], 343 | "dev": true, 344 | "optional": true, 345 | "os": [ 346 | "linux" 347 | ], 348 | "engines": { 349 | "node": ">=12" 350 | } 351 | }, 352 | "node_modules/@esbuild/linux-riscv64": { 353 | "version": "0.21.5", 354 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", 355 | "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", 356 | "cpu": [ 357 | "riscv64" 358 | ], 359 | "dev": true, 360 | "optional": true, 361 | "os": [ 362 | "linux" 363 | ], 364 | "engines": { 365 | "node": ">=12" 366 | } 367 | }, 368 | "node_modules/@esbuild/linux-s390x": { 369 | "version": "0.21.5", 370 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", 371 | "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", 372 | "cpu": [ 373 | "s390x" 374 | ], 375 | "dev": true, 376 | "optional": true, 377 | "os": [ 378 | "linux" 379 | ], 380 | "engines": { 381 | "node": ">=12" 382 | } 383 | }, 384 | "node_modules/@esbuild/linux-x64": { 385 | "version": "0.21.5", 386 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", 387 | "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", 388 | "cpu": [ 389 | "x64" 390 | ], 391 | "dev": true, 392 | "optional": true, 393 | "os": [ 394 | "linux" 395 | ], 396 | "engines": { 397 | "node": ">=12" 398 | } 399 | }, 400 | "node_modules/@esbuild/netbsd-x64": { 401 | "version": "0.21.5", 402 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", 403 | "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", 404 | "cpu": [ 405 | "x64" 406 | ], 407 | "dev": true, 408 | "optional": true, 409 | "os": [ 410 | "netbsd" 411 | ], 412 | "engines": { 413 | "node": ">=12" 414 | } 415 | }, 416 | "node_modules/@esbuild/openbsd-x64": { 417 | "version": "0.21.5", 418 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", 419 | "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", 420 | "cpu": [ 421 | "x64" 422 | ], 423 | "dev": true, 424 | "optional": true, 425 | "os": [ 426 | "openbsd" 427 | ], 428 | "engines": { 429 | "node": ">=12" 430 | } 431 | }, 432 | "node_modules/@esbuild/sunos-x64": { 433 | "version": "0.21.5", 434 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", 435 | "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", 436 | "cpu": [ 437 | "x64" 438 | ], 439 | "dev": true, 440 | "optional": true, 441 | "os": [ 442 | "sunos" 443 | ], 444 | "engines": { 445 | "node": ">=12" 446 | } 447 | }, 448 | "node_modules/@esbuild/win32-arm64": { 449 | "version": "0.21.5", 450 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", 451 | "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", 452 | "cpu": [ 453 | "arm64" 454 | ], 455 | "dev": true, 456 | "optional": true, 457 | "os": [ 458 | "win32" 459 | ], 460 | "engines": { 461 | "node": ">=12" 462 | } 463 | }, 464 | "node_modules/@esbuild/win32-ia32": { 465 | "version": "0.21.5", 466 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", 467 | "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", 468 | "cpu": [ 469 | "ia32" 470 | ], 471 | "dev": true, 472 | "optional": true, 473 | "os": [ 474 | "win32" 475 | ], 476 | "engines": { 477 | "node": ">=12" 478 | } 479 | }, 480 | "node_modules/@esbuild/win32-x64": { 481 | "version": "0.21.5", 482 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", 483 | "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", 484 | "cpu": [ 485 | "x64" 486 | ], 487 | "dev": true, 488 | "optional": true, 489 | "os": [ 490 | "win32" 491 | ], 492 | "engines": { 493 | "node": ">=12" 494 | } 495 | }, 496 | "node_modules/@lezer/common": { 497 | "version": "1.0.1", 498 | "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.1.tgz", 499 | "integrity": "sha512-8TR5++Q/F//tpDsLd5zkrvEX5xxeemafEaek7mUp7Y+bI8cKQXdSqhzTOBaOogETcMOVr0pT3BBPXp13477ciw==" 500 | }, 501 | "node_modules/@lezer/highlight": { 502 | "version": "1.1.2", 503 | "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.2.tgz", 504 | "integrity": "sha512-CAun1WR1glxG9ZdOokTZwXbcwB7PXkIEyZRUMFBVwSrhTcogWq634/ByNImrkUnQhjju6xsIaOBIxvcRJtplXQ==", 505 | "dependencies": { 506 | "@lezer/common": "^1.0.0" 507 | } 508 | }, 509 | "node_modules/@lezer/javascript": { 510 | "version": "1.1.1", 511 | "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.1.1.tgz", 512 | "integrity": "sha512-bPsN0nria42StUTU6AZmYFDm/jDXAwMWT1x2EuiNfwVEZlJGJezie1r1wJIvEtw6zoRph3fo0adLxiis1mgh/Q==", 513 | "dependencies": { 514 | "@lezer/highlight": "^1.0.0", 515 | "@lezer/lr": "^1.0.0" 516 | } 517 | }, 518 | "node_modules/@lezer/lr": { 519 | "version": "1.2.5", 520 | "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.5.tgz", 521 | "integrity": "sha512-f9319YG1A/3ysgUE3bqCHEd7g+3ZZ71MWlwEc42mpnLVYXgfJJgtu1XAyBB4Kz8FmqmnFe9caopDqKeMMMAU6g==", 522 | "dependencies": { 523 | "@lezer/common": "^1.0.0" 524 | } 525 | }, 526 | "node_modules/codemirror": { 527 | "version": "6.0.1", 528 | "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", 529 | "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", 530 | "dependencies": { 531 | "@codemirror/autocomplete": "^6.0.0", 532 | "@codemirror/commands": "^6.0.0", 533 | "@codemirror/language": "^6.0.0", 534 | "@codemirror/lint": "^6.0.0", 535 | "@codemirror/search": "^6.0.0", 536 | "@codemirror/state": "^6.0.0", 537 | "@codemirror/view": "^6.0.0" 538 | } 539 | }, 540 | "node_modules/crelt": { 541 | "version": "1.0.5", 542 | "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz", 543 | "integrity": "sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==" 544 | }, 545 | "node_modules/debounce": { 546 | "version": "2.1.0", 547 | "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.1.0.tgz", 548 | "integrity": "sha512-OkL3+0pPWCqoBc/nhO9u6TIQNTK44fnBnzuVtJAbp13Naxw9R6u21x+8tVTka87AhDZ3htqZ2pSSsZl9fqL2Wg==", 549 | "engines": { 550 | "node": ">=18" 551 | }, 552 | "funding": { 553 | "url": "https://github.com/sponsors/sindresorhus" 554 | } 555 | }, 556 | "node_modules/esbuild": { 557 | "version": "0.21.5", 558 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", 559 | "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", 560 | "dev": true, 561 | "hasInstallScript": true, 562 | "bin": { 563 | "esbuild": "bin/esbuild" 564 | }, 565 | "engines": { 566 | "node": ">=12" 567 | }, 568 | "optionalDependencies": { 569 | "@esbuild/aix-ppc64": "0.21.5", 570 | "@esbuild/android-arm": "0.21.5", 571 | "@esbuild/android-arm64": "0.21.5", 572 | "@esbuild/android-x64": "0.21.5", 573 | "@esbuild/darwin-arm64": "0.21.5", 574 | "@esbuild/darwin-x64": "0.21.5", 575 | "@esbuild/freebsd-arm64": "0.21.5", 576 | "@esbuild/freebsd-x64": "0.21.5", 577 | "@esbuild/linux-arm": "0.21.5", 578 | "@esbuild/linux-arm64": "0.21.5", 579 | "@esbuild/linux-ia32": "0.21.5", 580 | "@esbuild/linux-loong64": "0.21.5", 581 | "@esbuild/linux-mips64el": "0.21.5", 582 | "@esbuild/linux-ppc64": "0.21.5", 583 | "@esbuild/linux-riscv64": "0.21.5", 584 | "@esbuild/linux-s390x": "0.21.5", 585 | "@esbuild/linux-x64": "0.21.5", 586 | "@esbuild/netbsd-x64": "0.21.5", 587 | "@esbuild/openbsd-x64": "0.21.5", 588 | "@esbuild/sunos-x64": "0.21.5", 589 | "@esbuild/win32-arm64": "0.21.5", 590 | "@esbuild/win32-ia32": "0.21.5", 591 | "@esbuild/win32-x64": "0.21.5" 592 | } 593 | }, 594 | "node_modules/mime": { 595 | "version": "3.0.0", 596 | "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", 597 | "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", 598 | "dev": true, 599 | "bin": { 600 | "mime": "cli.js" 601 | }, 602 | "engines": { 603 | "node": ">=10.0.0" 604 | } 605 | }, 606 | "node_modules/minimist": { 607 | "version": "1.2.7", 608 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", 609 | "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", 610 | "dev": true, 611 | "funding": { 612 | "url": "https://github.com/sponsors/ljharb" 613 | } 614 | }, 615 | "node_modules/servatron": { 616 | "version": "2.5.0", 617 | "resolved": "https://registry.npmjs.org/servatron/-/servatron-2.5.0.tgz", 618 | "integrity": "sha512-o4cFiEucyCemZbO4gKI41kHGAnSDU7RaSr/uUwEVawRBAjJWbr3/Rv9oIS/yzj/hdUeFD75/lO5oFTREis29/g==", 619 | "dev": true, 620 | "dependencies": { 621 | "mime": "^3.0.0", 622 | "minimist": "^1.2.7" 623 | }, 624 | "bin": { 625 | "servatron": "bin/cli.js" 626 | } 627 | }, 628 | "node_modules/style-mod": { 629 | "version": "4.1.2", 630 | "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", 631 | "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" 632 | }, 633 | "node_modules/w3c-keyname": { 634 | "version": "2.2.6", 635 | "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", 636 | "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==" 637 | }, 638 | "node_modules/workerboxjs": { 639 | "resolved": "..", 640 | "link": true 641 | } 642 | }, 643 | "dependencies": { 644 | "@codemirror/autocomplete": { 645 | "version": "6.3.3", 646 | "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.3.3.tgz", 647 | "integrity": "sha512-8MGqPuKvE0W0XjabDYX3BFJNJRsYFAYpDBol+YCg6o5t2JyoDeoMYrqRMVnJv1B1FBgNAkvdDVrNrVE/7c9qvQ==", 648 | "requires": { 649 | "@codemirror/language": "^6.0.0", 650 | "@codemirror/state": "^6.0.0", 651 | "@codemirror/view": "^6.5.0", 652 | "@lezer/common": "^1.0.0" 653 | } 654 | }, 655 | "@codemirror/commands": { 656 | "version": "6.1.2", 657 | "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.2.tgz", 658 | "integrity": "sha512-sO3jdX1s0pam6lIdeSJLMN3DQ6mPEbM4yLvyKkdqtmd/UDwhXA5+AwFJ89rRXm6vTeOXBsE5cAmlos/t7MJdgg==", 659 | "requires": { 660 | "@codemirror/language": "^6.0.0", 661 | "@codemirror/state": "^6.0.0", 662 | "@codemirror/view": "^6.0.0", 663 | "@lezer/common": "^1.0.0" 664 | } 665 | }, 666 | "@codemirror/lang-javascript": { 667 | "version": "6.2.2", 668 | "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz", 669 | "integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==", 670 | "requires": { 671 | "@codemirror/autocomplete": "^6.0.0", 672 | "@codemirror/language": "^6.6.0", 673 | "@codemirror/lint": "^6.0.0", 674 | "@codemirror/state": "^6.0.0", 675 | "@codemirror/view": "^6.17.0", 676 | "@lezer/common": "^1.0.0", 677 | "@lezer/javascript": "^1.0.0" 678 | } 679 | }, 680 | "@codemirror/language": { 681 | "version": "6.8.0", 682 | "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.8.0.tgz", 683 | "integrity": "sha512-r1paAyWOZkfY0RaYEZj3Kul+MiQTEbDvYqf8gPGaRvNneHXCmfSaAVFjwRUPlgxS8yflMxw2CTu6uCMp8R8A2g==", 684 | "requires": { 685 | "@codemirror/state": "^6.0.0", 686 | "@codemirror/view": "^6.0.0", 687 | "@lezer/common": "^1.0.0", 688 | "@lezer/highlight": "^1.0.0", 689 | "@lezer/lr": "^1.0.0", 690 | "style-mod": "^4.0.0" 691 | } 692 | }, 693 | "@codemirror/lint": { 694 | "version": "6.1.0", 695 | "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.1.0.tgz", 696 | "integrity": "sha512-mdvDQrjRmYPvQ3WrzF6Ewaao+NWERYtpthJvoQ3tK3t/44Ynhk8ZGjTSL9jMEv8CgSMogmt75X8ceOZRDSXHtQ==", 697 | "requires": { 698 | "@codemirror/state": "^6.0.0", 699 | "@codemirror/view": "^6.0.0", 700 | "crelt": "^1.0.5" 701 | } 702 | }, 703 | "@codemirror/search": { 704 | "version": "6.2.3", 705 | "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.2.3.tgz", 706 | "integrity": "sha512-V9n9233lopQhB1dyjsBK2Wc1i+8hcCqxl1wQ46c5HWWLePoe4FluV3TGHoZ04rBRlGjNyz9DTmpJErig8UE4jw==", 707 | "requires": { 708 | "@codemirror/state": "^6.0.0", 709 | "@codemirror/view": "^6.0.0", 710 | "crelt": "^1.0.5" 711 | } 712 | }, 713 | "@codemirror/state": { 714 | "version": "6.4.1", 715 | "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", 716 | "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==" 717 | }, 718 | "@codemirror/view": { 719 | "version": "6.28.2", 720 | "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.28.2.tgz", 721 | "integrity": "sha512-A3DmyVfjgPsGIjiJqM/zvODUAPQdQl3ci0ghehYNnbt5x+o76xq+dL5+mMBuysDXnI3kapgOkoeJ0sbtL/3qPw==", 722 | "requires": { 723 | "@codemirror/state": "^6.4.0", 724 | "style-mod": "^4.1.0", 725 | "w3c-keyname": "^2.2.4" 726 | } 727 | }, 728 | "@esbuild/aix-ppc64": { 729 | "version": "0.21.5", 730 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", 731 | "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", 732 | "dev": true, 733 | "optional": true 734 | }, 735 | "@esbuild/android-arm": { 736 | "version": "0.21.5", 737 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", 738 | "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", 739 | "dev": true, 740 | "optional": true 741 | }, 742 | "@esbuild/android-arm64": { 743 | "version": "0.21.5", 744 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", 745 | "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", 746 | "dev": true, 747 | "optional": true 748 | }, 749 | "@esbuild/android-x64": { 750 | "version": "0.21.5", 751 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", 752 | "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", 753 | "dev": true, 754 | "optional": true 755 | }, 756 | "@esbuild/darwin-arm64": { 757 | "version": "0.21.5", 758 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", 759 | "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", 760 | "dev": true, 761 | "optional": true 762 | }, 763 | "@esbuild/darwin-x64": { 764 | "version": "0.21.5", 765 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", 766 | "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", 767 | "dev": true, 768 | "optional": true 769 | }, 770 | "@esbuild/freebsd-arm64": { 771 | "version": "0.21.5", 772 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", 773 | "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", 774 | "dev": true, 775 | "optional": true 776 | }, 777 | "@esbuild/freebsd-x64": { 778 | "version": "0.21.5", 779 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", 780 | "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", 781 | "dev": true, 782 | "optional": true 783 | }, 784 | "@esbuild/linux-arm": { 785 | "version": "0.21.5", 786 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", 787 | "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", 788 | "dev": true, 789 | "optional": true 790 | }, 791 | "@esbuild/linux-arm64": { 792 | "version": "0.21.5", 793 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", 794 | "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", 795 | "dev": true, 796 | "optional": true 797 | }, 798 | "@esbuild/linux-ia32": { 799 | "version": "0.21.5", 800 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", 801 | "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", 802 | "dev": true, 803 | "optional": true 804 | }, 805 | "@esbuild/linux-loong64": { 806 | "version": "0.21.5", 807 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", 808 | "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", 809 | "dev": true, 810 | "optional": true 811 | }, 812 | "@esbuild/linux-mips64el": { 813 | "version": "0.21.5", 814 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", 815 | "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", 816 | "dev": true, 817 | "optional": true 818 | }, 819 | "@esbuild/linux-ppc64": { 820 | "version": "0.21.5", 821 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", 822 | "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", 823 | "dev": true, 824 | "optional": true 825 | }, 826 | "@esbuild/linux-riscv64": { 827 | "version": "0.21.5", 828 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", 829 | "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", 830 | "dev": true, 831 | "optional": true 832 | }, 833 | "@esbuild/linux-s390x": { 834 | "version": "0.21.5", 835 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", 836 | "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", 837 | "dev": true, 838 | "optional": true 839 | }, 840 | "@esbuild/linux-x64": { 841 | "version": "0.21.5", 842 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", 843 | "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", 844 | "dev": true, 845 | "optional": true 846 | }, 847 | "@esbuild/netbsd-x64": { 848 | "version": "0.21.5", 849 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", 850 | "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", 851 | "dev": true, 852 | "optional": true 853 | }, 854 | "@esbuild/openbsd-x64": { 855 | "version": "0.21.5", 856 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", 857 | "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", 858 | "dev": true, 859 | "optional": true 860 | }, 861 | "@esbuild/sunos-x64": { 862 | "version": "0.21.5", 863 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", 864 | "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", 865 | "dev": true, 866 | "optional": true 867 | }, 868 | "@esbuild/win32-arm64": { 869 | "version": "0.21.5", 870 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", 871 | "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", 872 | "dev": true, 873 | "optional": true 874 | }, 875 | "@esbuild/win32-ia32": { 876 | "version": "0.21.5", 877 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", 878 | "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", 879 | "dev": true, 880 | "optional": true 881 | }, 882 | "@esbuild/win32-x64": { 883 | "version": "0.21.5", 884 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", 885 | "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", 886 | "dev": true, 887 | "optional": true 888 | }, 889 | "@lezer/common": { 890 | "version": "1.0.1", 891 | "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.1.tgz", 892 | "integrity": "sha512-8TR5++Q/F//tpDsLd5zkrvEX5xxeemafEaek7mUp7Y+bI8cKQXdSqhzTOBaOogETcMOVr0pT3BBPXp13477ciw==" 893 | }, 894 | "@lezer/highlight": { 895 | "version": "1.1.2", 896 | "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.2.tgz", 897 | "integrity": "sha512-CAun1WR1glxG9ZdOokTZwXbcwB7PXkIEyZRUMFBVwSrhTcogWq634/ByNImrkUnQhjju6xsIaOBIxvcRJtplXQ==", 898 | "requires": { 899 | "@lezer/common": "^1.0.0" 900 | } 901 | }, 902 | "@lezer/javascript": { 903 | "version": "1.1.1", 904 | "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.1.1.tgz", 905 | "integrity": "sha512-bPsN0nria42StUTU6AZmYFDm/jDXAwMWT1x2EuiNfwVEZlJGJezie1r1wJIvEtw6zoRph3fo0adLxiis1mgh/Q==", 906 | "requires": { 907 | "@lezer/highlight": "^1.0.0", 908 | "@lezer/lr": "^1.0.0" 909 | } 910 | }, 911 | "@lezer/lr": { 912 | "version": "1.2.5", 913 | "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.5.tgz", 914 | "integrity": "sha512-f9319YG1A/3ysgUE3bqCHEd7g+3ZZ71MWlwEc42mpnLVYXgfJJgtu1XAyBB4Kz8FmqmnFe9caopDqKeMMMAU6g==", 915 | "requires": { 916 | "@lezer/common": "^1.0.0" 917 | } 918 | }, 919 | "codemirror": { 920 | "version": "6.0.1", 921 | "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", 922 | "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", 923 | "requires": { 924 | "@codemirror/autocomplete": "^6.0.0", 925 | "@codemirror/commands": "^6.0.0", 926 | "@codemirror/language": "^6.0.0", 927 | "@codemirror/lint": "^6.0.0", 928 | "@codemirror/search": "^6.0.0", 929 | "@codemirror/state": "^6.0.0", 930 | "@codemirror/view": "^6.0.0" 931 | } 932 | }, 933 | "crelt": { 934 | "version": "1.0.5", 935 | "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz", 936 | "integrity": "sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==" 937 | }, 938 | "debounce": { 939 | "version": "2.1.0", 940 | "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.1.0.tgz", 941 | "integrity": "sha512-OkL3+0pPWCqoBc/nhO9u6TIQNTK44fnBnzuVtJAbp13Naxw9R6u21x+8tVTka87AhDZ3htqZ2pSSsZl9fqL2Wg==" 942 | }, 943 | "esbuild": { 944 | "version": "0.21.5", 945 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", 946 | "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", 947 | "dev": true, 948 | "requires": { 949 | "@esbuild/aix-ppc64": "0.21.5", 950 | "@esbuild/android-arm": "0.21.5", 951 | "@esbuild/android-arm64": "0.21.5", 952 | "@esbuild/android-x64": "0.21.5", 953 | "@esbuild/darwin-arm64": "0.21.5", 954 | "@esbuild/darwin-x64": "0.21.5", 955 | "@esbuild/freebsd-arm64": "0.21.5", 956 | "@esbuild/freebsd-x64": "0.21.5", 957 | "@esbuild/linux-arm": "0.21.5", 958 | "@esbuild/linux-arm64": "0.21.5", 959 | "@esbuild/linux-ia32": "0.21.5", 960 | "@esbuild/linux-loong64": "0.21.5", 961 | "@esbuild/linux-mips64el": "0.21.5", 962 | "@esbuild/linux-ppc64": "0.21.5", 963 | "@esbuild/linux-riscv64": "0.21.5", 964 | "@esbuild/linux-s390x": "0.21.5", 965 | "@esbuild/linux-x64": "0.21.5", 966 | "@esbuild/netbsd-x64": "0.21.5", 967 | "@esbuild/openbsd-x64": "0.21.5", 968 | "@esbuild/sunos-x64": "0.21.5", 969 | "@esbuild/win32-arm64": "0.21.5", 970 | "@esbuild/win32-ia32": "0.21.5", 971 | "@esbuild/win32-x64": "0.21.5" 972 | } 973 | }, 974 | "mime": { 975 | "version": "3.0.0", 976 | "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", 977 | "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", 978 | "dev": true 979 | }, 980 | "minimist": { 981 | "version": "1.2.7", 982 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", 983 | "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", 984 | "dev": true 985 | }, 986 | "servatron": { 987 | "version": "2.5.0", 988 | "resolved": "https://registry.npmjs.org/servatron/-/servatron-2.5.0.tgz", 989 | "integrity": "sha512-o4cFiEucyCemZbO4gKI41kHGAnSDU7RaSr/uUwEVawRBAjJWbr3/Rv9oIS/yzj/hdUeFD75/lO5oFTREis29/g==", 990 | "dev": true, 991 | "requires": { 992 | "mime": "^3.0.0", 993 | "minimist": "^1.2.7" 994 | } 995 | }, 996 | "style-mod": { 997 | "version": "4.1.2", 998 | "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", 999 | "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" 1000 | }, 1001 | "w3c-keyname": { 1002 | "version": "2.2.6", 1003 | "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", 1004 | "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==" 1005 | }, 1006 | "workerboxjs": { 1007 | "version": "file:..", 1008 | "requires": { 1009 | "@markwylde/ftp-deploy": "^2.0.3", 1010 | "chokidar": "^3.6.0", 1011 | "debounce": "^2.1.0", 1012 | "esbuild": "^0.21.5", 1013 | "just-tap": "^2.9.4", 1014 | "minify": "^9.2.0", 1015 | "puppeteer": "^22.12.1", 1016 | "servatron": "^2.5.0" 1017 | } 1018 | } 1019 | } 1020 | } 1021 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workerbox", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "servatron --http2 --port 8000 & NODE_ENV=development npm run watch", 8 | "build": "esbuild --define:process.env.NODE_ENV=\\'production\\' --bundle --outfile=index.min.js index.js", 9 | "watch": "esbuild --watch --define:process.env.NODE_ENV=\\'development\\' --bundle --outfile=index.min.js index.js", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "keywords": [ 13 | "sandbox", 14 | "eval", 15 | "execute", 16 | "javascript", 17 | "untrusted" 18 | ], 19 | "author": { 20 | "email": "me@markwylde.com", 21 | "name": "Mark Wylde", 22 | "url": "https://github.com/markwylde" 23 | }, 24 | "license": "MIT", 25 | "dependencies": { 26 | "@codemirror/lang-javascript": "^6.2.2", 27 | "@codemirror/state": "^6.4.1", 28 | "codemirror": "^6.0.1", 29 | "debounce": "^2.1.0", 30 | "workerboxjs": "file:../" 31 | }, 32 | "devDependencies": { 33 | "esbuild": "^0.21.5", 34 | "servatron": "^2.5.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/builtinWorker.html.js: -------------------------------------------------------------------------------- 1 | // built from the ./server/dist/index.html file during npm run build 2 | const builtinWorker = atob(`PGEgaHJlZj0iaHR0cHM6Ly93b3JrZXJib3gubmV0Ij5pbmZvPzwvYT4uCgo8c2NyaXB0PgogIGZ1bmN0aW9uIHdvcmtlclNjcmlwdCAoKSB7CigoKT0+e3ZhciBnZW5lcmF0ZVVuaXF1ZUlkX2RlZmF1bHQ9KCk9PihnbG9iYWxUaGlzLmluY3JlbWVudG9yPShnbG9iYWxUaGlzLmluY3JlbWVudG9yfHwwKSsxLGdsb2JhbFRoaXMuaW5jcmVtZW50b3IrIl8iK0FycmF5KDIwKS5maWxsKCIhQCMkJV4mKigpXystPTAxMjM0NTY3ODlBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6IikubWFwKChmdW5jdGlvbihlKXtyZXR1cm4gZVtNYXRoLmZsb29yKE1hdGgucmFuZG9tKCkqZS5sZW5ndGgpXX0pKS5qb2luKCIiKSk7dmFyIGU9ZnVuY3Rpb24gY3JlYXRlQ2FsbGJhY2tTdG9yZSgpe2NvbnN0IGU9e307cmV0dXJue3N0b3JlOmUsYWRkOnQ9Pntjb25zdCByPWdlbmVyYXRlVW5pcXVlSWRfZGVmYXVsdCgpO3JldHVybiBlW3JdPXQscn0sZ2V0OnQ9PmVbdF19fSx0PWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy5rZXlUb1ZhbHVlPW5ldyBNYXAsdGhpcy52YWx1ZVRvS2V5PW5ldyBNYXB9c2V0KGUsdCl7dGhpcy5rZXlUb1ZhbHVlLnNldChlLHQpLHRoaXMudmFsdWVUb0tleS5zZXQodCxlKX1nZXRCeUtleShlKXtyZXR1cm4gdGhpcy5rZXlUb1ZhbHVlLmdldChlKX1nZXRCeVZhbHVlKGUpe3JldHVybiB0aGlzLnZhbHVlVG9LZXkuZ2V0KGUpfWNsZWFyKCl7dGhpcy5rZXlUb1ZhbHVlLmNsZWFyKCksdGhpcy52YWx1ZVRvS2V5LmNsZWFyKCl9fSxyPWNsYXNze2NvbnN0cnVjdG9yKGUpe3RoaXMuZ2VuZXJhdGVJZGVudGlmaWVyPWUsdGhpcy5rdj1uZXcgdH1yZWdpc3RlcihlLHQpe3RoaXMua3YuZ2V0QnlWYWx1ZShlKXx8KHR8fCh0PXRoaXMuZ2VuZXJhdGVJZGVudGlmaWVyKGUpKSx0aGlzLmt2LnNldCh0LGUpKX1jbGVhcigpe3RoaXMua3YuY2xlYXIoKX1nZXRJZGVudGlmaWVyKGUpe3JldHVybiB0aGlzLmt2LmdldEJ5VmFsdWUoZSl9Z2V0VmFsdWUoZSl7cmV0dXJuIHRoaXMua3YuZ2V0QnlLZXkoZSl9fSxuPWNsYXNzIGV4dGVuZHMgcntjb25zdHJ1Y3Rvcigpe3N1cGVyKChlPT5lLm5hbWUpKSx0aGlzLmNsYXNzVG9BbGxvd2VkUHJvcHM9bmV3IE1hcH1yZWdpc3RlcihlLHQpeyJvYmplY3QiPT10eXBlb2YgdD8odC5hbGxvd1Byb3BzJiZ0aGlzLmNsYXNzVG9BbGxvd2VkUHJvcHMuc2V0KGUsdC5hbGxvd1Byb3BzKSxzdXBlci5yZWdpc3RlcihlLHQuaWRlbnRpZmllcikpOnN1cGVyLnJlZ2lzdGVyKGUsdCl9Z2V0QWxsb3dlZFByb3BzKGUpe3JldHVybiB0aGlzLmNsYXNzVG9BbGxvd2VkUHJvcHMuZ2V0KGUpfX07ZnVuY3Rpb24gZmluZChlLHQpe2NvbnN0IHI9ZnVuY3Rpb24gdmFsdWVzT2ZPYmooZSl7aWYoInZhbHVlcyJpbiBPYmplY3QpcmV0dXJuIE9iamVjdC52YWx1ZXMoZSk7Y29uc3QgdD1bXTtmb3IoY29uc3QgciBpbiBlKWUuaGFzT3duUHJvcGVydHkocikmJnQucHVzaChlW3JdKTtyZXR1cm4gdH0oZSk7aWYoImZpbmQiaW4gcilyZXR1cm4gci5maW5kKHQpO2NvbnN0IG49cjtmb3IobGV0IGU9MDtlPG4ubGVuZ3RoO2UrKyl7Y29uc3Qgcj1uW2VdO2lmKHQocikpcmV0dXJuIHJ9fWZ1bmN0aW9uIGZvckVhY2goZSx0KXtPYmplY3QuZW50cmllcyhlKS5mb3JFYWNoKCgoW2Uscl0pPT50KHIsZSkpKX1mdW5jdGlvbiBpbmNsdWRlcyhlLHQpe3JldHVybi0xIT09ZS5pbmRleE9mKHQpfWZ1bmN0aW9uIGZpbmRBcnIoZSx0KXtmb3IobGV0IHI9MDtyPGUubGVuZ3RoO3IrKyl7Y29uc3Qgbj1lW3JdO2lmKHQobikpcmV0dXJuIG59fXZhciBzPWNsYXNze2NvbnN0cnVjdG9yKCl7dGhpcy50cmFuc2ZvbWVycz17fX1yZWdpc3RlcihlKXt0aGlzLnRyYW5zZm9tZXJzW2UubmFtZV09ZX1maW5kQXBwbGljYWJsZShlKXtyZXR1cm4gZmluZCh0aGlzLnRyYW5zZm9tZXJzLCh0PT50LmlzQXBwbGljYWJsZShlKSkpfWZpbmRCeU5hbWUoZSl7cmV0dXJuIHRoaXMudHJhbnNmb21lcnNbZV19fSxpc1VuZGVmaW5lZD1lPT52b2lkIDA9PT1lLGlzUGxhaW5PYmplY3Q9ZT0+Im9iamVjdCI9PXR5cGVvZiBlJiZudWxsIT09ZSYmKGUhPT1PYmplY3QucHJvdG90eXBlJiYobnVsbD09PU9iamVjdC5nZXRQcm90b3R5cGVPZihlKXx8T2JqZWN0LmdldFByb3RvdHlwZU9mKGUpPT09T2JqZWN0LnByb3RvdHlwZSkpLGlzRW1wdHlPYmplY3Q9ZT0+aXNQbGFpbk9iamVjdChlKSYmMD09PU9iamVjdC5rZXlzKGUpLmxlbmd0aCxpc0FycmF5PWU9PkFycmF5LmlzQXJyYXkoZSksaXNNYXA9ZT0+ZSBpbnN0YW5jZW9mIE1hcCxpc1NldD1lPT5lIGluc3RhbmNlb2YgU2V0LGlzU3ltYm9sPWU9PiJTeW1ib2wiPT09KGU9Pk9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbChlKS5zbGljZSg4LC0xKSkoZSksaXNOYU5WYWx1ZT1lPT4ibnVtYmVyIj09dHlwZW9mIGUmJmlzTmFOKGUpLGlzUHJpbWl0aXZlPWU9PihlPT4iYm9vbGVhbiI9PXR5cGVvZiBlKShlKXx8KGU9Pm51bGw9PT1lKShlKXx8aXNVbmRlZmluZWQoZSl8fChlPT4ibnVtYmVyIj09dHlwZW9mIGUmJiFpc05hTihlKSkoZSl8fChlPT4ic3RyaW5nIj09dHlwZW9mIGUpKGUpfHxpc1N5bWJvbChlKSxlc2NhcGVLZXk9ZT0+ZS5yZXBsYWNlKC9cLi9nLCJcXC4iKSxzdHJpbmdpZnlQYXRoPWU9PmUubWFwKFN0cmluZykubWFwKGVzY2FwZUtleSkuam9pbigiLiIpLHBhcnNlUGF0aD1lPT57Y29uc3QgdD1bXTtsZXQgcj0iIjtmb3IobGV0IG49MDtuPGUubGVuZ3RoO24rKyl7bGV0IHM9ZS5jaGFyQXQobik7aWYoIlxcIj09PXMmJiIuIj09PWUuY2hhckF0KG4rMSkpe3IrPSIuIixuKys7Y29udGludWV9Ii4iPT09cz8odC5wdXNoKHIpLHI9IiIpOnIrPXN9Y29uc3Qgbj1yO3JldHVybiB0LnB1c2gobiksdH07ZnVuY3Rpb24gc2ltcGxlVHJhbnNmb3JtYXRpb24oZSx0LHIsbil7cmV0dXJue2lzQXBwbGljYWJsZTplLGFubm90YXRpb246dCx0cmFuc2Zvcm06cix1bnRyYW5zZm9ybTpufX12YXIgYT1bc2ltcGxlVHJhbnNmb3JtYXRpb24oaXNVbmRlZmluZWQsInVuZGVmaW5lZCIsKCgpPT5udWxsKSwoKCk9Pnt9KSksc2ltcGxlVHJhbnNmb3JtYXRpb24oKGU9PiJiaWdpbnQiPT10eXBlb2YgZSksImJpZ2ludCIsKGU9PmUudG9TdHJpbmcoKSksKGU9PiJ1bmRlZmluZWQiIT10eXBlb2YgQmlnSW50P0JpZ0ludChlKTooY29uc29sZS5lcnJvcigiUGxlYXNlIGFkZCBhIEJpZ0ludCBwb2x5ZmlsbC4iKSxlKSkpLHNpbXBsZVRyYW5zZm9ybWF0aW9uKChlPT5lIGluc3RhbmNlb2YgRGF0ZSYmIWlzTmFOKGUudmFsdWVPZigpKSksIkRhdGUiLChlPT5lLnRvSVNPU3RyaW5nKCkpLChlPT5uZXcgRGF0ZShlKSkpLHNpbXBsZVRyYW5zZm9ybWF0aW9uKChlPT5lIGluc3RhbmNlb2YgRXJyb3IpLCJFcnJvciIsKChlLHQpPT57Y29uc3Qgcj17bmFtZTplLm5hbWUsbWVzc2FnZTplLm1lc3NhZ2V9O3JldHVybiB0LmFsbG93ZWRFcnJvclByb3BzLmZvckVhY2goKHQ9PntyW3RdPWVbdF19KSkscn0pLCgoZSx0KT0+e2NvbnN0IHI9bmV3IEVycm9yKGUubWVzc2FnZSk7cmV0dXJuIHIubmFtZT1lLm5hbWUsci5zdGFjaz1lLnN0YWNrLHQuYWxsb3dlZEVycm9yUHJvcHMuZm9yRWFjaCgodD0+e3JbdF09ZVt0XX0pKSxyfSkpLHNpbXBsZVRyYW5zZm9ybWF0aW9uKChlPT5lIGluc3RhbmNlb2YgUmVnRXhwKSwicmVnZXhwIiwoZT0+IiIrZSksKGU9Pntjb25zdCB0PWUuc2xpY2UoMSxlLmxhc3RJbmRleE9mKCIvIikpLHI9ZS5zbGljZShlLmxhc3RJbmRleE9mKCIvIikrMSk7cmV0dXJuIG5ldyBSZWdFeHAodCxyKX0pKSxzaW1wbGVUcmFuc2Zvcm1hdGlvbihpc1NldCwic2V0IiwoZT0+Wy4uLmUudmFsdWVzKCldKSwoZT0+bmV3IFNldChlKSkpLHNpbXBsZVRyYW5zZm9ybWF0aW9uKGlzTWFwLCJtYXAiLChlPT5bLi4uZS5lbnRyaWVzKCldKSwoZT0+bmV3IE1hcChlKSkpLHNpbXBsZVRyYW5zZm9ybWF0aW9uKChlPT57cmV0dXJuIGlzTmFOVmFsdWUoZSl8fCgodD1lKT09PTEvMHx8dD09PS0xLzApO3ZhciB0fSksIm51bWJlciIsKGU9PmlzTmFOVmFsdWUoZSk/Ik5hTiI6ZT4wPyJJbmZpbml0eSI6Ii1JbmZpbml0eSIpLE51bWJlciksc2ltcGxlVHJhbnNmb3JtYXRpb24oKGU9PjA9PT1lJiYxL2U9PS0xLzApLCJudW1iZXIiLCgoKT0+Ii0wIiksTnVtYmVyKSxzaW1wbGVUcmFuc2Zvcm1hdGlvbigoZT0+ZSBpbnN0YW5jZW9mIFVSTCksIlVSTCIsKGU9PmUudG9TdHJpbmcoKSksKGU9Pm5ldyBVUkwoZSkpKV07ZnVuY3Rpb24gY29tcG9zaXRlVHJhbnNmb3JtYXRpb24oZSx0LHIsbil7cmV0dXJue2lzQXBwbGljYWJsZTplLGFubm90YXRpb246dCx0cmFuc2Zvcm06cix1bnRyYW5zZm9ybTpufX12YXIgbz1jb21wb3NpdGVUcmFuc2Zvcm1hdGlvbigoKGUsdCk9PntpZihpc1N5bWJvbChlKSl7cmV0dXJuISF0LnN5bWJvbFJlZ2lzdHJ5LmdldElkZW50aWZpZXIoZSl9cmV0dXJuITF9KSwoKGUsdCk9Plsic3ltYm9sIix0LnN5bWJvbFJlZ2lzdHJ5LmdldElkZW50aWZpZXIoZSldKSwoZT0+ZS5kZXNjcmlwdGlvbiksKChlLHQscik9Pntjb25zdCBuPXIuc3ltYm9sUmVnaXN0cnkuZ2V0VmFsdWUodFsxXSk7aWYoIW4pdGhyb3cgbmV3IEVycm9yKCJUcnlpbmcgdG8gZGVzZXJpYWxpemUgdW5rbm93biBzeW1ib2wiKTtyZXR1cm4gbn0pKSxpPVtJbnQ4QXJyYXksVWludDhBcnJheSxJbnQxNkFycmF5LFVpbnQxNkFycmF5LEludDMyQXJyYXksVWludDMyQXJyYXksRmxvYXQzMkFycmF5LEZsb2F0NjRBcnJheSxVaW50OENsYW1wZWRBcnJheV0ucmVkdWNlKCgoZSx0KT0+KGVbdC5uYW1lXT10LGUpKSx7fSksbD1jb21wb3NpdGVUcmFuc2Zvcm1hdGlvbigoZT0+QXJyYXlCdWZmZXIuaXNWaWV3KGUpJiYhKGUgaW5zdGFuY2VvZiBEYXRhVmlldykpLChlPT5bInR5cGVkLWFycmF5IixlLmNvbnN0cnVjdG9yLm5hbWVdKSwoZT0+Wy4uLmVdKSwoKGUsdCk9Pntjb25zdCByPWlbdFsxXV07aWYoIXIpdGhyb3cgbmV3IEVycm9yKCJUcnlpbmcgdG8gZGVzZXJpYWxpemUgdW5rbm93biB0eXBlZCBhcnJheSIpO3JldHVybiBuZXcgcihlKX0pKTtmdW5jdGlvbiBpc0luc3RhbmNlT2ZSZWdpc3RlcmVkQ2xhc3MoZSx0KXtpZihlPy5jb25zdHJ1Y3Rvcil7cmV0dXJuISF0LmNsYXNzUmVnaXN0cnkuZ2V0SWRlbnRpZmllcihlLmNvbnN0cnVjdG9yKX1yZXR1cm4hMX12YXIgYz1jb21wb3NpdGVUcmFuc2Zvcm1hdGlvbihpc0luc3RhbmNlT2ZSZWdpc3RlcmVkQ2xhc3MsKChlLHQpPT5bImNsYXNzIix0LmNsYXNzUmVnaXN0cnkuZ2V0SWRlbnRpZmllcihlLmNvbnN0cnVjdG9yKV0pLCgoZSx0KT0+e2NvbnN0IHI9dC5jbGFzc1JlZ2lzdHJ5LmdldEFsbG93ZWRQcm9wcyhlLmNvbnN0cnVjdG9yKTtpZighcilyZXR1cm57Li4uZX07Y29uc3Qgbj17fTtyZXR1cm4gci5mb3JFYWNoKCh0PT57blt0XT1lW3RdfSkpLG59KSwoKGUsdCxyKT0+e2NvbnN0IG49ci5jbGFzc1JlZ2lzdHJ5LmdldFZhbHVlKHRbMV0pO2lmKCFuKXRocm93IG5ldyBFcnJvcihgVHJ5aW5nIHRvIGRlc2VyaWFsaXplIHVua25vd24gY2xhc3MgJyR7dFsxXX0nIC0gY2hlY2sgaHR0cHM6Ly9naXRodWIuY29tL2JsaXR6LWpzL3N1cGVyanNvbi9pc3N1ZXMvMTE2I2lzc3VlY29tbWVudC03NzM5OTY1NjRgKTtyZXR1cm4gT2JqZWN0LmFzc2lnbihPYmplY3QuY3JlYXRlKG4ucHJvdG90eXBlKSxlKX0pKSx1PWNvbXBvc2l0ZVRyYW5zZm9ybWF0aW9uKCgoZSx0KT0+ISF0LmN1c3RvbVRyYW5zZm9ybWVyUmVnaXN0cnkuZmluZEFwcGxpY2FibGUoZSkpLCgoZSx0KT0+WyJjdXN0b20iLHQuY3VzdG9tVHJhbnNmb3JtZXJSZWdpc3RyeS5maW5kQXBwbGljYWJsZShlKS5uYW1lXSksKChlLHQpPT50LmN1c3RvbVRyYW5zZm9ybWVyUmVnaXN0cnkuZmluZEFwcGxpY2FibGUoZSkuc2VyaWFsaXplKGUpKSwoKGUsdCxyKT0+e2NvbnN0IG49ci5jdXN0b21UcmFuc2Zvcm1lclJlZ2lzdHJ5LmZpbmRCeU5hbWUodFsxXSk7aWYoIW4pdGhyb3cgbmV3IEVycm9yKCJUcnlpbmcgdG8gZGVzZXJpYWxpemUgdW5rbm93biBjdXN0b20gdmFsdWUiKTtyZXR1cm4gbi5kZXNlcmlhbGl6ZShlKX0pKSxmPVtjLG8sdSxsXSx0cmFuc2Zvcm1WYWx1ZT0oZSx0KT0+e2NvbnN0IHI9ZmluZEFycihmLChyPT5yLmlzQXBwbGljYWJsZShlLHQpKSk7aWYocilyZXR1cm57dmFsdWU6ci50cmFuc2Zvcm0oZSx0KSx0eXBlOnIuYW5ub3RhdGlvbihlLHQpfTtjb25zdCBuPWZpbmRBcnIoYSwocj0+ci5pc0FwcGxpY2FibGUoZSx0KSkpO3JldHVybiBuP3t2YWx1ZTpuLnRyYW5zZm9ybShlLHQpLHR5cGU6bi5hbm5vdGF0aW9ufTp2b2lkIDB9LHA9e307YS5mb3JFYWNoKChlPT57cFtlLmFubm90YXRpb25dPWV9KSk7dmFyIGdldE50aEtleT0oZSx0KT0+e2lmKHQ+ZS5zaXplKXRocm93IG5ldyBFcnJvcigiaW5kZXggb3V0IG9mIGJvdW5kcyIpO2NvbnN0IHI9ZS5rZXlzKCk7Zm9yKDt0PjA7KXIubmV4dCgpLHQtLTtyZXR1cm4gci5uZXh0KCkudmFsdWV9O2Z1bmN0aW9uIHZhbGlkYXRlUGF0aChlKXtpZihpbmNsdWRlcyhlLCJfX3Byb3RvX18iKSl0aHJvdyBuZXcgRXJyb3IoIl9fcHJvdG9fXyBpcyBub3QgYWxsb3dlZCBhcyBhIHByb3BlcnR5Iik7aWYoaW5jbHVkZXMoZSwicHJvdG90eXBlIikpdGhyb3cgbmV3IEVycm9yKCJwcm90b3R5cGUgaXMgbm90IGFsbG93ZWQgYXMgYSBwcm9wZXJ0eSIpO2lmKGluY2x1ZGVzKGUsImNvbnN0cnVjdG9yIikpdGhyb3cgbmV3IEVycm9yKCJjb25zdHJ1Y3RvciBpcyBub3QgYWxsb3dlZCBhcyBhIHByb3BlcnR5Iil9dmFyIHNldERlZXA9KGUsdCxyKT0+e2lmKHZhbGlkYXRlUGF0aCh0KSwwPT09dC5sZW5ndGgpcmV0dXJuIHIoZSk7bGV0IG49ZTtmb3IobGV0IGU9MDtlPHQubGVuZ3RoLTE7ZSsrKXtjb25zdCByPXRbZV07aWYoaXNBcnJheShuKSl7bj1uWytyXX1lbHNlIGlmKGlzUGxhaW5PYmplY3Qobikpbj1uW3JdO2Vsc2UgaWYoaXNTZXQobikpe249Z2V0TnRoS2V5KG4sK3IpfWVsc2UgaWYoaXNNYXAobikpe2lmKGU9PT10Lmxlbmd0aC0yKWJyZWFrO2NvbnN0IHM9K3IsYT0wPT0rdFsrK2VdPyJrZXkiOiJ2YWx1ZSIsbz1nZXROdGhLZXkobixzKTtzd2l0Y2goYSl7Y2FzZSJrZXkiOm49bzticmVhaztjYXNlInZhbHVlIjpuPW4uZ2V0KG8pfX19Y29uc3Qgcz10W3QubGVuZ3RoLTFdO2lmKGlzQXJyYXkobik/blsrc109cihuWytzXSk6aXNQbGFpbk9iamVjdChuKSYmKG5bc109cihuW3NdKSksaXNTZXQobikpe2NvbnN0IGU9Z2V0TnRoS2V5KG4sK3MpLHQ9cihlKTtlIT09dCYmKG4uZGVsZXRlKGUpLG4uYWRkKHQpKX1pZihpc01hcChuKSl7Y29uc3QgZT0rdFt0Lmxlbmd0aC0yXSxhPWdldE50aEtleShuLGUpO3N3aXRjaCgwPT0rcz8ia2V5IjoidmFsdWUiKXtjYXNlImtleSI6e2NvbnN0IGU9cihhKTtuLnNldChlLG4uZ2V0KGEpKSxlIT09YSYmbi5kZWxldGUoYSk7YnJlYWt9Y2FzZSJ2YWx1ZSI6bi5zZXQoYSxyKG4uZ2V0KGEpKSl9fXJldHVybiBlfTtmdW5jdGlvbiB0cmF2ZXJzZShlLHQscj1bXSl7aWYoIWUpcmV0dXJuO2lmKCFpc0FycmF5KGUpKXJldHVybiB2b2lkIGZvckVhY2goZSwoKGUsbik9PnRyYXZlcnNlKGUsdCxbLi4uciwuLi5wYXJzZVBhdGgobildKSkpO2NvbnN0W24sc109ZTtzJiZmb3JFYWNoKHMsKChlLG4pPT57dHJhdmVyc2UoZSx0LFsuLi5yLC4uLnBhcnNlUGF0aChuKV0pfSkpLHQobixyKX1mdW5jdGlvbiBhcHBseVZhbHVlQW5ub3RhdGlvbnMoZSx0LHIpe3JldHVybiB0cmF2ZXJzZSh0LCgodCxuKT0+e2U9c2V0RGVlcChlLG4sKGU9PigoZSx0LHIpPT57aWYoIWlzQXJyYXkodCkpe2NvbnN0IG49cFt0XTtpZighbil0aHJvdyBuZXcgRXJyb3IoIlVua25vd24gdHJhbnNmb3JtYXRpb246ICIrdCk7cmV0dXJuIG4udW50cmFuc2Zvcm0oZSxyKX1zd2l0Y2godFswXSl7Y2FzZSJzeW1ib2wiOnJldHVybiBvLnVudHJhbnNmb3JtKGUsdCxyKTtjYXNlImNsYXNzIjpyZXR1cm4gYy51bnRyYW5zZm9ybShlLHQscik7Y2FzZSJjdXN0b20iOnJldHVybiB1LnVudHJhbnNmb3JtKGUsdCxyKTtjYXNlInR5cGVkLWFycmF5IjpyZXR1cm4gbC51bnRyYW5zZm9ybShlLHQscik7ZGVmYXVsdDp0aHJvdyBuZXcgRXJyb3IoIlVua25vd24gdHJhbnNmb3JtYXRpb246ICIrdCl9fSkoZSx0LHIpKSl9KSksZX1mdW5jdGlvbiBhcHBseVJlZmVyZW50aWFsRXF1YWxpdHlBbm5vdGF0aW9ucyhlLHQpe2Z1bmN0aW9uIGFwcGx5KHQscil7Y29uc3Qgbj0oKGUsdCk9Pnt2YWxpZGF0ZVBhdGgodCk7Zm9yKGxldCByPTA7cjx0Lmxlbmd0aDtyKyspe2NvbnN0IG49dFtyXTtpZihpc1NldChlKSllPWdldE50aEtleShlLCtuKTtlbHNlIGlmKGlzTWFwKGUpKXtjb25zdCBzPStuLGE9MD09K3RbKytyXT8ia2V5IjoidmFsdWUiLG89Z2V0TnRoS2V5KGUscyk7c3dpdGNoKGEpe2Nhc2Uia2V5IjplPW87YnJlYWs7Y2FzZSJ2YWx1ZSI6ZT1lLmdldChvKX19ZWxzZSBlPWVbbl19cmV0dXJuIGV9KShlLHBhcnNlUGF0aChyKSk7dC5tYXAocGFyc2VQYXRoKS5mb3JFYWNoKCh0PT57ZT1zZXREZWVwKGUsdCwoKCk9Pm4pKX0pKX1pZihpc0FycmF5KHQpKXtjb25zdFtyLG5dPXQ7ci5mb3JFYWNoKCh0PT57ZT1zZXREZWVwKGUscGFyc2VQYXRoKHQpLCgoKT0+ZSkpfSkpLG4mJmZvckVhY2gobixhcHBseSl9ZWxzZSBmb3JFYWNoKHQsYXBwbHkpO3JldHVybiBlfXZhciB3YWxrZXI9KGUsdCxyLG4scz1bXSxhPVtdLG89bmV3IE1hcCk9Pntjb25zdCBpPWlzUHJpbWl0aXZlKGUpO2lmKCFpKXshZnVuY3Rpb24gYWRkSWRlbnRpdHkoZSx0LHIpe2NvbnN0IG49ci5nZXQoZSk7bj9uLnB1c2godCk6ci5zZXQoZSxbdF0pfShlLHMsdCk7Y29uc3Qgcj1vLmdldChlKTtpZihyKXJldHVybiBuP3t0cmFuc2Zvcm1lZFZhbHVlOm51bGx9OnJ9aWYoISgoZSx0KT0+aXNQbGFpbk9iamVjdChlKXx8aXNBcnJheShlKXx8aXNNYXAoZSl8fGlzU2V0KGUpfHxpc0luc3RhbmNlT2ZSZWdpc3RlcmVkQ2xhc3MoZSx0KSkoZSxyKSl7Y29uc3QgdD10cmFuc2Zvcm1WYWx1ZShlLHIpLG49dD97dHJhbnNmb3JtZWRWYWx1ZTp0LnZhbHVlLGFubm90YXRpb25zOlt0LnR5cGVdfTp7dHJhbnNmb3JtZWRWYWx1ZTplfTtyZXR1cm4gaXx8by5zZXQoZSxuKSxufWlmKGluY2x1ZGVzKGEsZSkpcmV0dXJue3RyYW5zZm9ybWVkVmFsdWU6bnVsbH07Y29uc3QgbD10cmFuc2Zvcm1WYWx1ZShlLHIpLGM9bD8udmFsdWU/P2UsdT1pc0FycmF5KGMpP1tdOnt9LGY9e307Zm9yRWFjaChjLCgoaSxsKT0+e2lmKCJfX3Byb3RvX18iPT09bHx8ImNvbnN0cnVjdG9yIj09PWx8fCJwcm90b3R5cGUiPT09bCl0aHJvdyBuZXcgRXJyb3IoYERldGVjdGVkIHByb3BlcnR5ICR7bH0uIFRoaXMgaXMgYSBwcm90b3R5cGUgcG9sbHV0aW9uIHJpc2ssIHBsZWFzZSByZW1vdmUgaXQgZnJvbSB5b3VyIG9iamVjdC5gKTtjb25zdCBjPXdhbGtlcihpLHQscixuLFsuLi5zLGxdLFsuLi5hLGVdLG8pO3VbbF09Yy50cmFuc2Zvcm1lZFZhbHVlLGlzQXJyYXkoYy5hbm5vdGF0aW9ucyk/ZltsXT1jLmFubm90YXRpb25zOmlzUGxhaW5PYmplY3QoYy5hbm5vdGF0aW9ucykmJmZvckVhY2goYy5hbm5vdGF0aW9ucywoKGUsdCk9PntmW2VzY2FwZUtleShsKSsiLiIrdF09ZX0pKX0pKTtjb25zdCBwPWlzRW1wdHlPYmplY3QoZik/e3RyYW5zZm9ybWVkVmFsdWU6dSxhbm5vdGF0aW9uczpsP1tsLnR5cGVdOnZvaWQgMH06e3RyYW5zZm9ybWVkVmFsdWU6dSxhbm5vdGF0aW9uczpsP1tsLnR5cGUsZl06Zn07cmV0dXJuIGl8fG8uc2V0KGUscCkscH07ZnVuY3Rpb24gZ2V0VHlwZTIoZSl7cmV0dXJuIE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbChlKS5zbGljZSg4LC0xKX1mdW5jdGlvbiBpc0FycmF5MihlKXtyZXR1cm4iQXJyYXkiPT09Z2V0VHlwZTIoZSl9KGZ1bmN0aW9uIGlzT25lT2YoZSx0LHIsbixzKXtyZXR1cm4gYT0+ZShhKXx8dChhKXx8ISFyJiZyKGEpfHwhIW4mJm4oYSl8fCEhcyYmcyhhKX0pKChmdW5jdGlvbiBpc051bGwyKGUpe3JldHVybiJOdWxsIj09PWdldFR5cGUyKGUpfSksKGZ1bmN0aW9uIGlzVW5kZWZpbmVkMihlKXtyZXR1cm4iVW5kZWZpbmVkIj09PWdldFR5cGUyKGUpfSkpO2Z1bmN0aW9uIGNvcHkoZSx0PXt9KXtpZihpc0FycmF5MihlKSlyZXR1cm4gZS5tYXAoKGU9PmNvcHkoZSx0KSkpO2lmKCFmdW5jdGlvbiBpc1BsYWluT2JqZWN0MihlKXtpZigiT2JqZWN0IiE9PWdldFR5cGUyKGUpKXJldHVybiExO2NvbnN0IHQ9T2JqZWN0LmdldFByb3RvdHlwZU9mKGUpO3JldHVybiEhdCYmdC5jb25zdHJ1Y3Rvcj09PU9iamVjdCYmdD09PU9iamVjdC5wcm90b3R5cGV9KGUpKXJldHVybiBlO3JldHVyblsuLi5PYmplY3QuZ2V0T3duUHJvcGVydHlOYW1lcyhlKSwuLi5PYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzKGUpXS5yZWR1Y2UoKChyLG4pPT57aWYoaXNBcnJheTIodC5wcm9wcykmJiF0LnByb3BzLmluY2x1ZGVzKG4pKXJldHVybiByO3JldHVybiBmdW5jdGlvbiBhc3NpZ25Qcm9wKGUsdCxyLG4scyl7Y29uc3QgYT17fS5wcm9wZXJ0eUlzRW51bWVyYWJsZS5jYWxsKG4sdCk/ImVudW1lcmFibGUiOiJub25lbnVtZXJhYmxlIjsiZW51bWVyYWJsZSI9PT1hJiYoZVt0XT1yKSxzJiYibm9uZW51bWVyYWJsZSI9PT1hJiZPYmplY3QuZGVmaW5lUHJvcGVydHkoZSx0LHt2YWx1ZTpyLGVudW1lcmFibGU6ITEsd3JpdGFibGU6ITAsY29uZmlndXJhYmxlOiEwfSl9KHIsbixjb3B5KGVbbl0sdCksZSx0Lm5vbmVudW1lcmFibGUpLHJ9KSx7fSl9dmFyIHk9Y2xhc3N7Y29uc3RydWN0b3Ioe2RlZHVwZTplPSExfT17fSl7dGhpcy5jbGFzc1JlZ2lzdHJ5PW5ldyBuLHRoaXMuc3ltYm9sUmVnaXN0cnk9bmV3IHIoKGU9PmUuZGVzY3JpcHRpb24/PyIiKSksdGhpcy5jdXN0b21UcmFuc2Zvcm1lclJlZ2lzdHJ5PW5ldyBzLHRoaXMuYWxsb3dlZEVycm9yUHJvcHM9W10sdGhpcy5kZWR1cGU9ZX1zZXJpYWxpemUoZSl7Y29uc3QgdD1uZXcgTWFwLHI9d2Fsa2VyKGUsdCx0aGlzLHRoaXMuZGVkdXBlKSxuPXtqc29uOnIudHJhbnNmb3JtZWRWYWx1ZX07ci5hbm5vdGF0aW9ucyYmKG4ubWV0YT17Li4ubi5tZXRhLHZhbHVlczpyLmFubm90YXRpb25zfSk7Y29uc3Qgcz1mdW5jdGlvbiBnZW5lcmF0ZVJlZmVyZW50aWFsRXF1YWxpdHlBbm5vdGF0aW9ucyhlLHQpe2NvbnN0IHI9e307bGV0IG47cmV0dXJuIGUuZm9yRWFjaCgoZT0+e2lmKGUubGVuZ3RoPD0xKXJldHVybjt0fHwoZT1lLm1hcCgoZT0+ZS5tYXAoU3RyaW5nKSkpLnNvcnQoKChlLHQpPT5lLmxlbmd0aC10Lmxlbmd0aCkpKTtjb25zdFtzLC4uLmFdPWU7MD09PXMubGVuZ3RoP249YS5tYXAoc3RyaW5naWZ5UGF0aCk6cltzdHJpbmdpZnlQYXRoKHMpXT1hLm1hcChzdHJpbmdpZnlQYXRoKX0pKSxuP2lzRW1wdHlPYmplY3Qocik/W25dOltuLHJdOmlzRW1wdHlPYmplY3Qocik/dm9pZCAwOnJ9KHQsdGhpcy5kZWR1cGUpO3JldHVybiBzJiYobi5tZXRhPXsuLi5uLm1ldGEscmVmZXJlbnRpYWxFcXVhbGl0aWVzOnN9KSxufWRlc2VyaWFsaXplKGUpe2NvbnN0e2pzb246dCxtZXRhOnJ9PWU7bGV0IG49Y29weSh0KTtyZXR1cm4gcj8udmFsdWVzJiYobj1hcHBseVZhbHVlQW5ub3RhdGlvbnMobixyLnZhbHVlcyx0aGlzKSkscj8ucmVmZXJlbnRpYWxFcXVhbGl0aWVzJiYobj1hcHBseVJlZmVyZW50aWFsRXF1YWxpdHlBbm5vdGF0aW9ucyhuLHIucmVmZXJlbnRpYWxFcXVhbGl0aWVzKSksbn1zdHJpbmdpZnkoZSl7cmV0dXJuIEpTT04uc3RyaW5naWZ5KHRoaXMuc2VyaWFsaXplKGUpKX1wYXJzZShlKXtyZXR1cm4gdGhpcy5kZXNlcmlhbGl6ZShKU09OLnBhcnNlKGUpKX1yZWdpc3RlckNsYXNzKGUsdCl7dGhpcy5jbGFzc1JlZ2lzdHJ5LnJlZ2lzdGVyKGUsdCl9cmVnaXN0ZXJTeW1ib2woZSx0KXt0aGlzLnN5bWJvbFJlZ2lzdHJ5LnJlZ2lzdGVyKGUsdCl9cmVnaXN0ZXJDdXN0b20oZSx0KXt0aGlzLmN1c3RvbVRyYW5zZm9ybWVyUmVnaXN0cnkucmVnaXN0ZXIoe25hbWU6dCwuLi5lfSl9YWxsb3dFcnJvclByb3BzKC4uLmUpe3RoaXMuYWxsb3dlZEVycm9yUHJvcHMucHVzaCguLi5lKX19O3kuZGVmYXVsdEluc3RhbmNlPW5ldyB5LHkuc2VyaWFsaXplPXkuZGVmYXVsdEluc3RhbmNlLnNlcmlhbGl6ZS5iaW5kKHkuZGVmYXVsdEluc3RhbmNlKSx5LmRlc2VyaWFsaXplPXkuZGVmYXVsdEluc3RhbmNlLmRlc2VyaWFsaXplLmJpbmQoeS5kZWZhdWx0SW5zdGFuY2UpLHkuc3RyaW5naWZ5PXkuZGVmYXVsdEluc3RhbmNlLnN0cmluZ2lmeS5iaW5kKHkuZGVmYXVsdEluc3RhbmNlKSx5LnBhcnNlPXkuZGVmYXVsdEluc3RhbmNlLnBhcnNlLmJpbmQoeS5kZWZhdWx0SW5zdGFuY2UpLHkucmVnaXN0ZXJDbGFzcz15LmRlZmF1bHRJbnN0YW5jZS5yZWdpc3RlckNsYXNzLmJpbmQoeS5kZWZhdWx0SW5zdGFuY2UpLHkucmVnaXN0ZXJTeW1ib2w9eS5kZWZhdWx0SW5zdGFuY2UucmVnaXN0ZXJTeW1ib2wuYmluZCh5LmRlZmF1bHRJbnN0YW5jZSkseS5yZWdpc3RlckN1c3RvbT15LmRlZmF1bHRJbnN0YW5jZS5yZWdpc3RlckN1c3RvbS5iaW5kKHkuZGVmYXVsdEluc3RhbmNlKSx5LmFsbG93RXJyb3JQcm9wcz15LmRlZmF1bHRJbnN0YW5jZS5hbGxvd0Vycm9yUHJvcHMuYmluZCh5LmRlZmF1bHRJbnN0YW5jZSk7eS5zZXJpYWxpemUseS5kZXNlcmlhbGl6ZSx5LnN0cmluZ2lmeSx5LnBhcnNlLHkucmVnaXN0ZXJDbGFzcyx5LnJlZ2lzdGVyQ3VzdG9tLHkucmVnaXN0ZXJTeW1ib2wseS5hbGxvd0Vycm9yUHJvcHM7dmFyIGQ9ZnVuY3Rpb24gY3JlYXRlU3VwZXJKU09OKGUsdCl7Y29uc3Qgcj1uZXcgeTtyZXR1cm4gci5yZWdpc3RlckN1c3RvbSh7aXNBcHBsaWNhYmxlOmU9PiJmdW5jdGlvbiI9PXR5cGVvZiBlLHNlcmlhbGl6ZTplLGRlc2VyaWFsaXplOmU9PiguLi5uKT0+dChlLHIuc3RyaW5naWZ5KG4pKX0sImNhbGxiYWNrIikscn07dmFyIGdldFN0YWNrPShlLHQpPT57Y29uc3Qgcj1lLnN0YWNrLnNwbGl0KCJcbiIpO3JldHVybltyWzBdLC4uLnIuZmlsdGVyKChlPT5lLmluY2x1ZGVzKCIoZXZhbCBhdCBzY29wZWRFdmFsIikpKS5tYXAoKGU9Pntjb25zdCB0PWUuc3BsaXQoIihldmFsIGF0IHNjb3BlZEV2YWwgKCIpLFsscl09ZS5zcGxpdCgiPGFub255bW91cz4iKSxbLG4sc109ci5zbGljZSgwLC0xKS5zcGxpdCgiOiIpO3JldHVybmAke3RbMF19KDxzYW5kYm94Pjoke24tM306JHtzfSlgfSkpXS5zbGljZSgwLHQpLmpvaW4oIlxuIil9O3NlbGYuYWRkRXZlbnRMaXN0ZW5lcigibWVzc2FnZSIsKGFzeW5jIHQ9Pntjb25zdCByPXQucG9ydHNbMF0sbj1lKCkscz1kKG4uYWRkLCgoZSx0KT0+bmV3IFByb21pc2UoKHM9PntyLnBvc3RNZXNzYWdlKFsiY2FsbGJhY2siLHtpZDplLGFyZ3M6dCxyZXNvbHZlOm4uYWRkKHMpfV0pfSkpKSk7ci5vbm1lc3NhZ2U9YXN5bmMgZT0+e2NvbnN0W3QsYV09ZS5kYXRhLHtpZDpvLGVycm9ySWQ6aSxjb2RlOmwsc2NvcGU6YyxhcmdzOnUscmVzb2x2ZTpmLHJlamVjdDpwfT1hO2lmKCJleGVjdXRlIj09PXQpe2NvbnN0IGU9cy5wYXJzZShjKTt0cnl7Y29uc3QgdD1hd2FpdCBhc3luYyBmdW5jdGlvbiBzY29wZWRFdmFsKGUsdCl7cmV0dXJuIEZ1bmN0aW9uLmFwcGx5KG51bGwsWy4uLk9iamVjdC5rZXlzKGUpLGByZXR1cm4gKGFzeW5jIGZ1bmN0aW9uIHNhbmRib3ggKCkgeyR7dH0gfSkoKWBdKS5hcHBseShudWxsLFsuLi5PYmplY3QudmFsdWVzKGUpXSl9KGUsbCk7ci5wb3N0TWVzc2FnZShbInJldHVybiIse2lkOm8sYXJnczpzLnN0cmluZ2lmeShbdF0pfV0pfWNhdGNoKGUpe3RyeXtjb25zdCB0PWdldFN0YWNrKGUsLTEpO3IucG9zdE1lc3NhZ2UoWyJlcnJvciIse2lkOmksYXJnczpzLnN0cmluZ2lmeShbdHx8ZS5tZXNzYWdlXSl9XSl9Y2F0Y2godCl7ci5wb3N0TWVzc2FnZShbImVycm9yIix7aWQ6aSxhcmdzOnMuc3RyaW5naWZ5KFtlLm1lc3NhZ2VdKX1dKX19fWlmKCJjYWxsYmFjayI9PT10KXtjb25zdCBlPXMucGFyc2UodSksdD1uLmdldChvKTtpZighdClyZXR1cm47dHJ5e2NvbnN0IG49YXdhaXQgdCguLi5lKTtyLnBvc3RNZXNzYWdlKFsicmV0dXJuIix7aWQ6ZixhcmdzOnMuc3RyaW5naWZ5KFtuXSl9XSl9Y2F0Y2goZSl7Y29uc3QgdD1nZXRTdGFjayhlKTtyLnBvc3RNZXNzYWdlKFsiZXJyb3IiLHtpZDpwLGFyZ3M6cy5zdHJpbmdpZnkoW3R8fGUubWVzc2FnZV0pfV0pfX19fSkpfSkoKTsKICB9CgogIHNlbGYuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIGFzeW5jIChldmVudCkgPT4gewogICAgY29uc3QgY29kZSA9IHdvcmtlclNjcmlwdC50b1N0cmluZygpLnNwbGl0KCdcbicpLnNsaWNlKDEsIC0xKS5qb2luKCdcbicpOwogICAgY29uc3QgYmxvYiA9IG5ldyBCbG9iKFtjb2RlXSwge3R5cGU6ICdhcHBsaWNhdGlvbi9qYXZhc2NyaXB0J30pCiAgICBjb25zdCB3b3JrZXIgPSBuZXcgV29ya2VyKFVSTC5jcmVhdGVPYmplY3RVUkwoYmxvYikpCgogICAgd29ya2VyLnBvc3RNZXNzYWdlKCdPSycsIGV2ZW50LnBvcnRzKTsKICB9KTsKPC9zY3JpcHQ+Cg==`); export default builtinWorker; -------------------------------------------------------------------------------- /lib/createCallbackStore.js: -------------------------------------------------------------------------------- 1 | import generateUniqueId from './generateUniqueId.js'; 2 | 3 | function createCallbackStore () { 4 | const store = {}; 5 | 6 | const add = fn => { 7 | const id = generateUniqueId(); 8 | store[id] = fn; 9 | return id; 10 | }; 11 | 12 | const get = id => { 13 | return store[id]; 14 | }; 15 | 16 | return { 17 | store, 18 | add, 19 | get 20 | }; 21 | } 22 | 23 | export default createCallbackStore; 24 | -------------------------------------------------------------------------------- /lib/createSuperJSON.js: -------------------------------------------------------------------------------- 1 | import SuperJSON from 'superjson'; 2 | 3 | function createSuperJSON (addCallback, runCallback) { 4 | const superjson = new SuperJSON(); 5 | superjson.registerCustom( 6 | { 7 | isApplicable: value => typeof value === 'function', 8 | serialize: addCallback, 9 | deserialize: id => (...args) => runCallback(id, superjson.stringify(args)), 10 | }, 11 | 'callback' 12 | ); 13 | return superjson; 14 | } 15 | 16 | export default createSuperJSON 17 | -------------------------------------------------------------------------------- /lib/generateUniqueId.js: -------------------------------------------------------------------------------- 1 | const generateUniqueId = () => { 2 | globalThis.incrementor = (globalThis.incrementor || 0) + 1; 3 | return globalThis.incrementor + '_' + Array(20) 4 | .fill('!@#$%^&*()_+-=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') 5 | .map(function (x) { return x[Math.floor(Math.random() * x.length)]; }).join(''); 6 | }; 7 | 8 | export default generateUniqueId; 9 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | declare type WorkerBoxOptions = { 2 | scriptUrl?: string, 3 | appendVersion?: boolean; 4 | }; 5 | 6 | declare type Scope = object; 7 | 8 | export type WorkerBox = { 9 | run: (code: string, scope?: Scope) => Promise; 10 | destroy: () => void; 11 | }; 12 | 13 | declare function createWorkerBox(options?: WorkerBoxOptions): Promise; 14 | 15 | export default createWorkerBox; 16 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import createCallbackStore from './createCallbackStore.js'; 2 | import builtinWorker from './builtinWorker.html.js'; 3 | import createSuperJSON from './createSuperJSON.js'; 4 | 5 | const instances = { 6 | count: 0 7 | }; 8 | function createWorkerboxInstance (url, onMessage) { 9 | instances.count = instances.count + 1; 10 | const channel = new MessageChannel(); 11 | const iframe = document.createElement('iframe'); 12 | iframe.sandbox = 'allow-scripts'; 13 | iframe.id = `workerBox-${instances.count}`; 14 | iframe.style = 15 | 'position: fixed; height: 0; width: 0; opacity: 0; top: -100px;'; 16 | if (url) { 17 | iframe.src = url; 18 | } else { 19 | iframe.srcdoc = url || builtinWorker; 20 | } 21 | document.body.appendChild(iframe); 22 | channel.port1.onmessage = onMessage; 23 | 24 | return new Promise(resolve => { 25 | iframe.addEventListener('load', () => { 26 | iframe.contentWindow.postMessage('OK', '*', [channel.port2]); 27 | resolve({ 28 | postMessage: message => channel.port1.postMessage(message), 29 | destroy: () => iframe.remove() 30 | }); 31 | }); 32 | }); 33 | } 34 | 35 | export async function createWorkerBox (options) { 36 | options = { 37 | serverUrl: null, 38 | appendVersion: true, 39 | ...options 40 | }; 41 | 42 | if (options.serverUrl && options.serverUrl.slice(-1) === '/') { 43 | options.serverUrl = options.serverUrl.slice(0, -1); 44 | } 45 | 46 | if (options.serverUrl && options.appendVersion) { 47 | options.serverUrl = options.serverUrl + '/v6.4.5/'; 48 | } 49 | 50 | options.serverUrl = options.serverUrl && (new URL(options.serverUrl)).href; 51 | 52 | const callbacks = createCallbackStore(); 53 | const run = (id, args) => 54 | new Promise((resolve, reject) => { 55 | instance.postMessage(['callback', { 56 | id, args, resolve: callbacks.add(resolve), reject: callbacks.add(reject) 57 | }]); 58 | }); 59 | const superjson = createSuperJSON(callbacks.add, run); 60 | 61 | const instance = await createWorkerboxInstance(options.serverUrl, async message => { 62 | const [action, { id, args, resolve, reject }] = message.data; 63 | 64 | const parsedArgs = superjson.parse(args); 65 | 66 | if (action === 'error') { 67 | callbacks.get(id)?.(new Error(parsedArgs[0])); 68 | return; 69 | } 70 | 71 | if (action === 'return') { 72 | callbacks.get(id)?.(parsedArgs[0]); 73 | return; 74 | } 75 | 76 | const fn = callbacks.get(id); 77 | if (!fn) { 78 | return; 79 | } 80 | 81 | try { 82 | const result = await fn(...parsedArgs); 83 | instance.postMessage(['callback', { id: resolve, args: superjson.stringify([result]) }]); 84 | } catch (error) { 85 | instance.postMessage(['callback', { id: reject, args: superjson.stringify([error.message]) }]); 86 | } 87 | }); 88 | 89 | return { 90 | run: async (code, originalScope) => { 91 | return new Promise((resolve, reject) => { 92 | const id = callbacks.add(resolve); 93 | const errorId = callbacks.add(reject); 94 | const scope = superjson.stringify(originalScope || {}); 95 | instance.postMessage(['execute', { id, errorId, code, scope }]); 96 | }); 97 | }, 98 | destroy: () => instance.destroy(), 99 | options 100 | }; 101 | } 102 | 103 | export default createWorkerBox; 104 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workerboxjs", 3 | "version": "6.4.5", 4 | "type": "module", 5 | "description": "A secure sandbox to execute untrusted user JavaScript, in a web browser, without any risk to your own domain/site/page.", 6 | "main": "lib/index.js", 7 | "types": "lib/index.d.ts", 8 | "scripts": { 9 | "start": "node build.js --watch & servatron --http2 --port 8002 --directory server/dist", 10 | "build": "node build.js", 11 | "test": "node test" 12 | }, 13 | "files": [ 14 | "lib" 15 | ], 16 | "keywords": [ 17 | "sandbox", 18 | "eval", 19 | "execute", 20 | "javascript", 21 | "untrusted" 22 | ], 23 | "author": "Mark Wylde (https://github.com/markwylde)", 24 | "license": "MIT", 25 | "directories": { 26 | "lib": "lib" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/markwylde/workerbox.git" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/markwylde/workerbox/issues" 34 | }, 35 | "homepage": "https://github.com/markwylde/workerbox#readme", 36 | "dependencies": { 37 | "superjson": "^2.2.2" 38 | }, 39 | "devDependencies": { 40 | "@markwylde/ftp-deploy": "^2.0.3", 41 | "chokidar": "^4.0.3", 42 | "debounce": "^2.2.0", 43 | "esbuild": "^0.25.2", 44 | "just-tap": "^2.9.4", 45 | "minify": "^14.0.0", 46 | "puppeteer": "^24.4.0", 47 | "servatron": "^2.7.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /server/index.html: -------------------------------------------------------------------------------- 1 | info?. 2 | 3 | 16 | -------------------------------------------------------------------------------- /server/worker.js: -------------------------------------------------------------------------------- 1 | import createCallbackStore from '../lib/createCallbackStore.js'; 2 | import createSuperJSON from '../lib/createSuperJSON.js'; 3 | 4 | async function scopedEval (context, expr) { 5 | const evaluator = Function.apply(null, [ 6 | ...Object.keys(context), 7 | `return (async function sandbox () {${expr} })()` 8 | ]); 9 | return evaluator.apply(null, [...Object.values(context)]); 10 | } 11 | 12 | const getStack = (error, slice) => { 13 | const lines = error.stack.split('\n'); 14 | const stack = [ 15 | lines[0], 16 | ...lines 17 | .filter(line => line.includes('(eval at scopedEval')) 18 | .map(line => { 19 | const splitted = line.split('(eval at scopedEval ('); 20 | const [, mixedPosition] = line.split(''); 21 | const [, lineNumber, charNumber] = mixedPosition.slice(0, -1).split(':'); 22 | return `${splitted[0]}(:${lineNumber - 3}:${charNumber})` 23 | }) 24 | ].slice(0, slice).join('\n'); 25 | return stack; 26 | } 27 | 28 | self.addEventListener('message', async (event) => { 29 | const port = event.ports[0]; 30 | 31 | const callbacks = createCallbackStore(); 32 | const run = (id, args) => 33 | new Promise(resolve => { 34 | port.postMessage(['callback', { id, args, resolve: callbacks.add(resolve) }]); 35 | }); 36 | const superjson = createSuperJSON(callbacks.add, run); 37 | 38 | port.onmessage = async event => { 39 | const [action, message] = event.data; 40 | const { id, errorId, code, scope, args, resolve, reject } = message; 41 | 42 | if (action === 'execute') { 43 | const parsedScope = superjson.parse(scope); 44 | 45 | try { 46 | const result = await scopedEval(parsedScope, code); 47 | 48 | port.postMessage(['return', { id, args: superjson.stringify([result]) }]); 49 | } catch (error) { 50 | try { 51 | const stack = getStack(error, -1); 52 | port.postMessage(['error', { id: errorId, args: superjson.stringify([stack || error.message]) }]); 53 | } catch (error2) { 54 | port.postMessage(['error', { id: errorId, args: superjson.stringify([error.message]) }]); 55 | } 56 | } 57 | } 58 | 59 | if (action === 'callback') { 60 | const parsedArgs = superjson.parse(args); 61 | 62 | const fn = callbacks.get(id); 63 | if (!fn) { 64 | return; 65 | } 66 | try { 67 | const result = await fn(...parsedArgs); 68 | port.postMessage(['return', { id: resolve, args: superjson.stringify([result]) }]); 69 | } catch (error) { 70 | const stack = getStack(error); 71 | port.postMessage(['error', { id: reject, args: superjson.stringify([stack || error.message]) }]); 72 | } 73 | } 74 | }; 75 | }); 76 | -------------------------------------------------------------------------------- /test/helpers/bundle.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import esbuild from 'esbuild'; 3 | 4 | async function bundle (file, define) { 5 | const randomFileName = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); 6 | 7 | const { errors, warnings } = await esbuild.build({ 8 | entryPoints: [file], 9 | sourcemap: 'both', 10 | outfile: `/tmp/${randomFileName}.min.js`, 11 | bundle: true, 12 | define 13 | }); 14 | if (errors.length > 0 || warnings.length > 0) { 15 | console.log({ errors, warnings }); 16 | throw new Error('code build has errors'); 17 | } 18 | 19 | const bundledCode = await fs.promises.readFile(`/tmp/${randomFileName}.min.js`, 'utf8'); 20 | 21 | await fs.promises.rm(`/tmp/${randomFileName}.min.js`); 22 | 23 | return bundledCode; 24 | } 25 | 26 | export default bundle; 27 | -------------------------------------------------------------------------------- /test/helpers/createServer.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import http2 from 'http2'; 3 | import servatron from 'servatron/http2.js'; 4 | 5 | async function createServer (code) { 6 | const server = http2.createSecureServer({ 7 | key: fs.readFileSync('./node_modules/servatron/defaultCerts/key.pem'), 8 | cert: fs.readFileSync('./node_modules/servatron/defaultCerts/cert.pem') 9 | }); 10 | server.on('error', (error) => console.error(error)); 11 | 12 | const staticHandler = servatron({ 13 | directory: './server/dist' 14 | }); 15 | 16 | if (!code) { 17 | server.on('stream', staticHandler); 18 | } 19 | 20 | if (code) { 21 | server.on('stream', function (stream, headers) { 22 | if (headers[':path'] === '/') { 23 | stream.end(` 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Document 32 | 33 | 34 | 35 | 36 | 37 | `.trim()); 38 | return; 39 | } 40 | 41 | if (headers[':path'] === '/code.js') { 42 | stream.end(code); 43 | return; 44 | } 45 | 46 | staticHandler(stream, headers); 47 | }); 48 | } 49 | 50 | server.listen(null, '0.0.0.0'); 51 | 52 | return new Promise(resolve => { 53 | server.on('listening', () => { 54 | const close = () => server.close(); 55 | resolve({ 56 | url: `https://0.0.0.0:${server.address().port}`, 57 | close, 58 | server 59 | }); 60 | }); 61 | }); 62 | } 63 | 64 | export default createServer; 65 | -------------------------------------------------------------------------------- /test/helpers/run.js: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | import debounce from 'debounce'; 3 | import createServer from './createServer.js'; 4 | 5 | async function run (code) { 6 | const server = await createServer(code); 7 | const browser = await puppeteer.launch({ 8 | headless: 'new', 9 | defaultViewport: null, 10 | devtools: true, 11 | args: ['--ignore-certificate-errors', '--no-sandbox', '--disable-setuid-sandbox'] 12 | }); 13 | 14 | const cleanup = debounce(() => { 15 | browser.close(); 16 | server.close(); 17 | }, 300); 18 | 19 | const [page] = await browser.pages(); 20 | 21 | const result = new Promise((resolve, reject) => { 22 | page.on('error', console.log); 23 | page.on('pageerror', (error) => { 24 | cleanup(); 25 | setTimeout(() => reject(error), 300); 26 | }); 27 | page.on('console', async message => { 28 | const describe = async arg => { 29 | const value = await arg.jsonValue(); 30 | if (value instanceof Error) { 31 | return value.stack; 32 | } 33 | if (typeof value === 'object') { 34 | const getCircularReplacer = () => { 35 | const seen = new WeakSet(); 36 | return (key, value) => { 37 | if (typeof value === 'object' && value !== null) { 38 | if (seen.has(value)) { 39 | return; 40 | } 41 | seen.add(value); 42 | } 43 | return value; 44 | }; 45 | }; 46 | return JSON.stringify(value, getCircularReplacer(), 2); 47 | } 48 | return value; 49 | }; 50 | 51 | const messages = []; 52 | for (const arg of message.args()) { 53 | const parsedMessage = await describe(arg); 54 | if (!parsedMessage?.startsWith?.('$$TEST_BROWSER_CLOSE$$:')) { 55 | messages.push(parsedMessage); 56 | } 57 | } 58 | console.log(...messages); 59 | 60 | const text = message.text(); 61 | 62 | if (text.startsWith('$$TEST_BROWSER_CLOSE$$:')) { 63 | const result = text.slice('$$TEST_BROWSER_CLOSE$$:'.length); 64 | cleanup(); 65 | resolve(JSON.parse(result)); 66 | } 67 | }); 68 | }); 69 | 70 | await page.goto(server.url); 71 | 72 | return result; 73 | } 74 | 75 | export default run; 76 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import bundle from './helpers/bundle.js'; 3 | import run from './helpers/run.js'; 4 | import createServer from './helpers/createServer.js'; 5 | 6 | const server = await createServer(); 7 | 8 | const testSuiteBundle = await bundle(path.resolve('./test/suite.js'), { 9 | serverUrl: `"${server.url}"` 10 | }); 11 | 12 | const results = await run(testSuiteBundle) 13 | .catch(console.log); 14 | server.close(); 15 | 16 | if (!results?.success) { 17 | throw new Error('not all tests passed'); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /test/suite.js: -------------------------------------------------------------------------------- 1 | /* global serverUrl */ 2 | import packageJson from '../package.json' assert {type: 'json'}; 3 | 4 | import createTestSuite from 'just-tap'; 5 | import createWorkerBox from '../lib/index.js'; 6 | import createSuperJSON from '../lib/createSuperJSON.js'; 7 | 8 | const { test, run } = createTestSuite({ concurrency: 1 }); 9 | 10 | test('createWorkerBox with no trailing slash', async (t) => { 11 | const { options } = await createWorkerBox({ serverUrl: 'https://example.test' }); 12 | t.equal(options.serverUrl, `https://example.test/v${packageJson.version}/`); 13 | }); 14 | 15 | test('createWorkerBox with trailing slash', async (t) => { 16 | const { options } = await createWorkerBox({ serverUrl: 'https://example.test/' }); 17 | t.equal(options.serverUrl, `https://example.test/v${packageJson.version}/`); 18 | }); 19 | 20 | test('createWorkerBox with no options', async (t) => { 21 | const { options } = await createWorkerBox(); 22 | t.equal(options.serverUrl, null); 23 | }); 24 | 25 | test('createWorkerBox with empty options', async (t) => { 26 | const { options } = await createWorkerBox({}); 27 | t.equal(options.serverUrl, null); 28 | }); 29 | 30 | test('createWorkerBox with appendVersion false', async (t) => { 31 | const { options } = await createWorkerBox({ serverUrl: 'https://example.test/', appendVersion: false }); 32 | t.equal(options.serverUrl, 'https://example.test/'); 33 | }); 34 | 35 | test('createWorkerBox with appendVersion true', async (t) => { 36 | const { options } = await createWorkerBox({ serverUrl: 'https://example.test/', appendVersion: true }); 37 | t.equal(options.serverUrl, `https://example.test/v${packageJson.version}/`); 38 | }); 39 | 40 | test('simple evaluation while using serverUrl', async (t) => { 41 | t.plan(1); 42 | 43 | const { run } = await createWorkerBox({ serverUrl, appendVersion: false }); 44 | const result = await run('return 1 + 1'); 45 | 46 | t.equal(result, 2); 47 | }); 48 | 49 | test('simple evaluation', async (t) => { 50 | t.plan(1); 51 | 52 | const { run } = await createWorkerBox(); 53 | const result = await run('return 1 + 1'); 54 | 55 | t.equal(result, 2); 56 | }); 57 | 58 | test('returning an object with functions on it', async (t) => { 59 | t.plan(1); 60 | 61 | const { run } = await createWorkerBox({ appendVersion: false }); 62 | const returned = await run(` 63 | return { 64 | add: (a, b) => a + b 65 | } 66 | `); 67 | 68 | const result = await returned.add(1, 1); 69 | 70 | t.deepEqual(result, 2); 71 | }); 72 | 73 | test('returning an array', async (t) => { 74 | t.plan(1); 75 | 76 | const { run } = await createWorkerBox(); 77 | const result = await run('return [1]'); 78 | 79 | t.deepEqual(result, [1], `${result} should equal [1]`); 80 | }); 81 | 82 | test("returning null", async (t) => { 83 | t.plan(1); 84 | 85 | const { run } = await createWorkerBox({ appendVersion: false }); 86 | const result = await run("return [1]"); 87 | 88 | t.deepEqual(result, [1], `${result} should equal [1]`); 89 | }); 90 | 91 | test('consecutive runs work', async (t) => { 92 | t.plan(2); 93 | 94 | const { run } = await createWorkerBox(); 95 | const result1 = await run('return 1'); 96 | const result2 = await run('return 2'); 97 | 98 | t.equal(result1, 1, `${result1} should equal 1`); 99 | t.equal(result2, 2, `${result2} should equal 2`); 100 | }); 101 | 102 | test('same workerbox instance should share globalThis', async (t) => { 103 | t.plan(1); 104 | 105 | const { run } = await createWorkerBox(); 106 | await run('globalThis.a = 1'); 107 | const result = await run('return globalThis.a'); 108 | 109 | t.equal(result, 1); 110 | }); 111 | 112 | test('same workerbox instance should share self', async (t) => { 113 | t.plan(1); 114 | 115 | const { run } = await createWorkerBox(); 116 | await run('self.a = 1'); 117 | const result = await run('return self.a'); 118 | 119 | t.equal(result, 1); 120 | }); 121 | 122 | test('two workerbox instances do not share globalThis', async (t) => { 123 | t.plan(2); 124 | 125 | const { run: run1 } = await createWorkerBox(); 126 | const { run: run2 } = await createWorkerBox(); 127 | const result1 = await run1('globalThis.a = 1; return globalThis.a;'); 128 | const result2 = await run2('return globalThis.a'); 129 | 130 | t.equal(result1, 1); 131 | t.equal(result2, undefined); 132 | }); 133 | 134 | test('two workerbox instances do not share self', async (t) => { 135 | t.plan(2); 136 | 137 | const { run: run1 } = await createWorkerBox(); 138 | const { run: run2 } = await createWorkerBox(); 139 | const result1 = await run1('self.a = 1; return self.a;'); 140 | const result2 = await run2('return self.a'); 141 | 142 | t.equal(result1, 1); 143 | t.equal(result2, undefined); 144 | }); 145 | 146 | test('destroy works', async (t) => { 147 | t.plan(1); 148 | 149 | const { run, destroy } = await createWorkerBox(); 150 | const scope = { 151 | fail: () => t.fail('should not have been called') 152 | }; 153 | 154 | const result = await run('setTimeout(fail, 200); return 1 + 1;', scope); 155 | destroy(); 156 | t.ok(result === 2, `${result} should equal 2`); 157 | }); 158 | 159 | test('syntax error throws', async (t) => { 160 | t.plan(1); 161 | 162 | const { run } = await createWorkerBox(); 163 | 164 | await run(` 165 | const a = 1; 166 | const b = 2; 167 | return 1 + 168 | `) 169 | .catch(error => { 170 | t.equal(error.message, 'Unexpected token \'}\''); 171 | }); 172 | }); 173 | 174 | test('syntax error throws inside function', async (t) => { 175 | t.plan(1); 176 | 177 | const { run } = await createWorkerBox({ appendVersion: false }); 178 | 179 | const result = await run(` 180 | return { 181 | add: (a, b) => { 182 | ohno(); 183 | } 184 | } 185 | `); 186 | 187 | result.add().catch(error => { 188 | t.equal(error.message, 'ReferenceError: ohno is not defined\n at add (:3:9)'); 189 | }); 190 | }); 191 | 192 | test('runtime error throws', async (t) => { 193 | t.plan(1); 194 | 195 | const { run } = await createWorkerBox({ appendVersion: false }); 196 | 197 | await run(` 198 | const a = 1; 199 | const b = 2; 200 | return b(); 201 | `) 202 | .catch(error => { 203 | t.equal(error.message, 'TypeError: b is not a function\n at sandbox (:3:10)'); 204 | }); 205 | }); 206 | 207 | test('simple evaluation with function', async (t) => { 208 | t.plan(1); 209 | 210 | const { run } = await createWorkerBox(); 211 | 212 | const result = await run(` 213 | function add (a, b) { 214 | return a + b 215 | } 216 | 217 | return add(1, 2); 218 | `); 219 | 220 | t.ok(result === 3, `${result} should equal 3`); 221 | }); 222 | 223 | test('function on scope can get called', async (t) => { 224 | t.plan(2); 225 | 226 | const { run } = await createWorkerBox(); 227 | const scope = { 228 | finish: () => { 229 | t.ok(true, 'finish should be called'); 230 | } 231 | }; 232 | 233 | const result = await run(` 234 | setTimeout(() => finish('withArg'), 1); 235 | return 'yes' 236 | `, scope); 237 | 238 | t.ok(result === 'yes', `${result} should equal 'yes'`); 239 | }); 240 | 241 | test('function on scope can have callback as an argument', async (t) => { 242 | t.plan(3); 243 | 244 | const { run } = await createWorkerBox(); 245 | const scope = { 246 | first: (arg1, returnHello) => { 247 | t.ok(true, 'first should be called'); 248 | returnHello(); 249 | }, 250 | finish: () => { 251 | t.ok(true, 'finish should be called'); 252 | } 253 | }; 254 | 255 | const result = await run(` 256 | setTimeout(() => first('withArg', () => finish()), 1); 257 | return 'yes' 258 | `, scope); 259 | 260 | t.ok(result === 'yes', `${result} should equal 'yes'`); 261 | }); 262 | 263 | test('returns a promise', async (t) => { 264 | t.plan(1); 265 | 266 | const { run } = await createWorkerBox(); 267 | const scope = { 268 | test: () => 200 269 | }; 270 | 271 | const result = await run(` 272 | return new Promise(resolve => { 273 | setTimeout(() => resolve('hi'), 1); 274 | }); 275 | `, scope); 276 | 277 | t.ok(result === 'hi', `${result} should equal 'hi'`); 278 | }); 279 | 280 | test('function on scope can return value', async (t) => { 281 | t.plan(1); 282 | 283 | const { run } = await createWorkerBox(); 284 | const scope = { 285 | test: () => { 286 | return 200; 287 | } 288 | }; 289 | 290 | const result = await run(` 291 | return test() 292 | `, scope); 293 | 294 | t.equal(result, 200); 295 | }); 296 | 297 | test('callback as a function can return a value', async (t) => { 298 | t.plan(1); 299 | 300 | const { run } = await createWorkerBox(); 301 | 302 | let storedCallback; 303 | const scope = { 304 | setCallback: function setCB (fn) { 305 | storedCallback = fn; 306 | } 307 | }; 308 | 309 | await run(` 310 | setCallback(function myCB (){ 311 | return 'worked'; 312 | }); 313 | `, scope); 314 | 315 | await new Promise(resolve => setTimeout(resolve, 300)); 316 | 317 | t.equal(await storedCallback(), 'worked'); 318 | }); 319 | 320 | test('SuperJSON', async (t) => { 321 | const input = [{ 322 | foo: 'bar', 323 | nested: { 324 | asdf: 'qwer' 325 | }, 326 | array: [ 327 | 1, 328 | { 329 | three: 3 330 | }, 331 | null 332 | ] 333 | }]; 334 | 335 | const superjson = createSuperJSON(f => f, f => { 336 | t.fail('runCallback should not be called'); 337 | }); 338 | 339 | const stringified = superjson.stringify(input); 340 | const result = superjson.parse(stringified); 341 | t.deepEqual(result, input); 342 | }); 343 | 344 | run().then(stats => { 345 | console.log('$$TEST_BROWSER_CLOSE$$:' + JSON.stringify(stats)); 346 | }); 347 | --------------------------------------------------------------------------------