mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-01 19:44:39 +03:00
Feat/proxy devserver (#603)
This commit is contained in:
parent
0aeb87a26c
commit
fef54ad6e8
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tauri",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.1",
|
||||
"description": "Multi-binding collection of libraries and templates for building Tauri apps",
|
||||
"bin": {
|
||||
"tauri": "./bin/tauri.js"
|
||||
@ -51,6 +51,7 @@
|
||||
"cross-spawn": "7.0.2",
|
||||
"fast-glob": "3.2.2",
|
||||
"fs-extra": "9.0.0",
|
||||
"http-proxy": "^1.18.0",
|
||||
"imagemin": "7.0.1",
|
||||
"imagemin-optipng": "7.1.0",
|
||||
"imagemin-pngquant": "8.0.0",
|
||||
@ -73,6 +74,7 @@
|
||||
"@babel/preset-typescript": "7.9.0",
|
||||
"@types/cross-spawn": "6.0.1",
|
||||
"@types/fs-extra": "8.1.0",
|
||||
"@types/http-proxy": "^1.17.4",
|
||||
"@types/imagemin": "7.0.0",
|
||||
"@types/imagemin-optipng": "5.2.0",
|
||||
"@types/jsdom": "16.2.1",
|
||||
|
@ -3,17 +3,16 @@
|
||||
import net from 'net'
|
||||
|
||||
async function findClosestOpenPort(port: number, host: string): Promise<number> {
|
||||
let portProposal = port
|
||||
|
||||
do {
|
||||
if (await isPortAvailable(portProposal, host)) {
|
||||
return portProposal
|
||||
}
|
||||
portProposal++
|
||||
}
|
||||
while (portProposal < 65535)
|
||||
|
||||
throw new Error('ERROR_NETWORK_PORT_NOT_AVAIL')
|
||||
return await isPortAvailable(port, host)
|
||||
.then(isAvailable => {
|
||||
if (isAvailable) {
|
||||
return port
|
||||
} else if (port < 65535) {
|
||||
return findClosestOpenPort(port + 1, host)
|
||||
} else {
|
||||
throw new Error('ERROR_NETWORK_PORT_NOT_AVAIL')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function isPortAvailable(port: number, host: string): Promise<boolean> {
|
||||
|
@ -14,8 +14,10 @@ import { tauriDir, appDir } from './helpers/app-paths'
|
||||
import logger from './helpers/logger'
|
||||
import onShutdown from './helpers/on-shutdown'
|
||||
import { spawn, spawnSync } from './helpers/spawn'
|
||||
import { exec } from 'child_process'
|
||||
import { TauriConfig } from './types/config'
|
||||
import getTauriConfig from './helpers/tauri-config'
|
||||
import httpProxy from 'http-proxy'
|
||||
|
||||
const log = logger('app:tauri', 'green')
|
||||
const warn = logger('app:tauri (runner)', 'red')
|
||||
@ -51,10 +53,18 @@ class Runner {
|
||||
|
||||
if (!this.ranBeforeDevCommand && cfg.build.beforeDevCommand) {
|
||||
this.ranBeforeDevCommand = true // prevent calling it twice on recursive call on our watcher
|
||||
const [command, ...args] = cfg.build.beforeDevCommand.split(' ')
|
||||
spawn(command, args, appDir, code => {
|
||||
process.exit(code)
|
||||
log('Running `' + cfg.build.beforeDevCommand + '`')
|
||||
const ls = exec(cfg.build.beforeDevCommand, {
|
||||
cwd: appDir,
|
||||
env: process.env
|
||||
}, error => {
|
||||
if (error) {
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
ls.stderr && ls.stderr.pipe(process.stderr)
|
||||
ls.stdout && ls.stdout.pipe(process.stdout)
|
||||
}
|
||||
|
||||
const tomlContents = this.__getManifest()
|
||||
@ -68,51 +78,51 @@ class Runner {
|
||||
let inlinedAssets: string[] = []
|
||||
|
||||
if (runningDevServer) {
|
||||
const self = this
|
||||
const devUrl = new URL(devPath)
|
||||
const app = http.createServer((req, res) => {
|
||||
const options = {
|
||||
hostname: devUrl.hostname,
|
||||
port: devUrl.port,
|
||||
path: req.url,
|
||||
method: req.method
|
||||
}
|
||||
|
||||
const self = this
|
||||
|
||||
const proxy = http.request(options, function (originalResponse) {
|
||||
if (options.path === '/') {
|
||||
let body: Uint8Array[] = []
|
||||
originalResponse.on('data', chunk => {
|
||||
body.push(chunk)
|
||||
})
|
||||
originalResponse.on('end', () => {
|
||||
const originalHtml = body.join('')
|
||||
const indexDir = os.tmpdir()
|
||||
writeFileSync(path.join(indexDir, 'index.html'), originalHtml)
|
||||
self.__parseHtml(cfg, indexDir, false)
|
||||
.then(({ html }) => {
|
||||
res.writeHead(200)
|
||||
res.end(html)
|
||||
}).catch(err => {
|
||||
res.writeHead(500, JSON.stringify(err))
|
||||
res.end()
|
||||
})
|
||||
})
|
||||
} else {
|
||||
res.writeHead(res.statusCode, originalResponse.headers)
|
||||
originalResponse.pipe(res, {
|
||||
end: true
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
req.pipe(proxy, {
|
||||
end: true
|
||||
})
|
||||
const proxy = httpProxy.createProxyServer({
|
||||
ws: true,
|
||||
target: {
|
||||
host: devUrl.hostname,
|
||||
port: devUrl.port
|
||||
},
|
||||
selfHandleResponse: true
|
||||
})
|
||||
|
||||
proxy.on('proxyRes', function (proxyRes, req, res) {
|
||||
if (req.url === '/') {
|
||||
let body: Uint8Array[] = []
|
||||
proxyRes.on('data', function (chunk) {
|
||||
body.push(chunk)
|
||||
})
|
||||
proxyRes.on('end', function () {
|
||||
let bodyStr = body.join('')
|
||||
const indexDir = os.tmpdir()
|
||||
writeFileSync(path.join(indexDir, 'index.html'), bodyStr)
|
||||
self.__parseHtml(cfg, indexDir, false)
|
||||
.then(({ html }) => {
|
||||
res.end(html)
|
||||
}).catch(err => {
|
||||
res.writeHead(500, JSON.stringify(err))
|
||||
res.end()
|
||||
})
|
||||
})
|
||||
} else {
|
||||
proxyRes.pipe(res)
|
||||
}
|
||||
})
|
||||
|
||||
const proxyServer = http.createServer((req, res) => {
|
||||
delete req.headers['accept-encoding']
|
||||
proxy.web(req, res)
|
||||
})
|
||||
proxyServer.on('upgrade', (req, socket, head) => {
|
||||
proxy.ws(req, socket, head)
|
||||
})
|
||||
|
||||
const port = await findClosestOpenPort(parseInt(devUrl.port) + 1, devUrl.hostname)
|
||||
const server = app.listen(port)
|
||||
this.devServer = server
|
||||
const devServer = proxyServer.listen(port)
|
||||
this.devServer = devServer
|
||||
devPath = `${devUrl.protocol}//localhost:${port}`
|
||||
cfg.build.devPath = devPath
|
||||
process.env.TAURI_CONFIG = JSON.stringify(cfg)
|
||||
|
@ -1234,6 +1234,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/http-proxy@^1.17.4":
|
||||
version "1.17.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.4.tgz#e7c92e3dbe3e13aa799440ff42e6d3a17a9d045b"
|
||||
integrity sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/imagemin-optipng@5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/imagemin-optipng/-/imagemin-optipng-5.2.0.tgz#83046e0695739661fa738ad253bdbf51bc4f9e9d"
|
||||
@ -3056,6 +3063,13 @@ debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^3.0.0:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
|
||||
@ -3776,6 +3790,11 @@ esutils@^2.0.2:
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
||||
|
||||
eventemitter3@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb"
|
||||
integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==
|
||||
|
||||
events@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59"
|
||||
@ -4195,6 +4214,13 @@ flush-write-stream@^1.0.0:
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.3.6"
|
||||
|
||||
follow-redirects@^1.0.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb"
|
||||
integrity sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==
|
||||
dependencies:
|
||||
debug "^3.0.0"
|
||||
|
||||
for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
@ -4716,6 +4742,15 @@ http-cache-semantics@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
|
||||
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
|
||||
|
||||
http-proxy@^1.18.0:
|
||||
version "1.18.0"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a"
|
||||
integrity sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==
|
||||
dependencies:
|
||||
eventemitter3 "^4.0.0"
|
||||
follow-redirects "^1.0.0"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
http-signature@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
||||
@ -7776,6 +7811,11 @@ require-main-filename@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
|
||||
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
|
||||
|
||||
requires-port@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||
|
||||
resolve-cwd@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
||||
|
@ -624,4 +624,4 @@ if (document.readyState === 'complete' || document.readyState === 'interactive')
|
||||
__openLinks()
|
||||
}, true)
|
||||
}
|
||||
</script> <div> <button id="log">Call Log API</button> <button id="request">Call Request (async) API</button> <button id="event">Send event to Rust</button> </div> <div style="margin-top:24px"> <select id="dir"> <option value="">None</option> </select> <input id="path-to-read" placeholder="Type the path to read..."> <button id="read">Read</button> </div> <div style="margin-top:24px"> <input id="url" value="https://tauri.studio"> <button id="open-url">Open URL</button> </div> <div style="margin-top:24px"> <input id="title" value="Awesome Tauri Example!"> <button id="set-title">Set title</button> </div> <div style="margin-top:24px"> <input id="dialog-default-path" placeholder="Default path"> <input id="dialog-filter" placeholder="Extensions filter"> <div> <input type="checkbox" id="dialog-multiple"> <label>Multiple</label> </div> <div> <input type="checkbox" id="dialog-directory"> <label>Directory</label> </div> <button id="open-dialog">Open dialog</button> <button id="save-dialog">Open save dialog</button> </div> <div style="margin-top:24px"> <select id="request-method"> <option value="GET">GET</option> <option value="POST">POST</option> <option value="PUT">PUT</option> <option value="PATCH">PATCH</option> <option value="DELETE">DELETE</option> </select> <input id="request-url" placeholder="Type the request URL..."> <textarea id="request-body" placeholder="Request body"></textarea> <button id="make-request">Make request</button> </div> <div id="response"></div> <script>function registerResponse(e){document.getElementById("response").innerHTML="object"==typeof e?JSON.stringify(e):e}function addClickEnterHandler(e,n,t){e.addEventListener("click",t),n.addEventListener("keyup",function(e){13===e.keyCode&&t()})}window.tauri.listen("rust-event",function(e){document.getElementById("response").innerHTML=JSON.stringify(e)});var dirSelect=document.getElementById("dir");for(var key in window.tauri.Dir){var value=window.tauri.Dir[key],opt=document.createElement("option");opt.value=value,opt.innerHTML=key,dirSelect.appendChild(opt)}</script> <script>document.getElementById("log").addEventListener("click",function(){window.tauri.invoke({cmd:"logOperation",event:"tauri-click",payload:"this payload is optional because we used Option in Rust"})}),document.getElementById("request").addEventListener("click",function(){window.tauri.promisified({cmd:"performRequest",endpoint:"dummy endpoint arg",body:{id:5,name:"test"}}).then(registerResponse).catch(registerResponse)}),document.getElementById("event").addEventListener("click",function(){window.tauri.emit("js-event","this is the payload string")});</script> <script>var dirSelect=document.getElementById("dir");function getDir(){return dirSelect.value?parseInt(dir.value):null}function arrayBufferToBase64(e,n){var t=new Blob([e],{type:"application/octet-binary"}),r=new FileReader;r.onload=function(e){var t=e.target.result;n(t.substr(t.indexOf(",")+1))},r.readAsDataURL(t)}var pathInput=document.getElementById("path-to-read");addClickEnterHandler(document.getElementById("read"),pathInput,function(){var r=pathInput.value,a=r.match(/\S+\.\S+$/g),e={dir:getDir()};(a?window.tauri.readBinaryFile(r,e):window.tauri.readDir(r,e)).then(function(e){if(a)if(r.includes(".png")||r.includes(".jpg"))arrayBufferToBase64(new Uint8Array(e),function(e){registerResponse('<img src="'+("data:image/png;base64,"+e)+'"></img>')});else{var t=String.fromCharCode.apply(null,e);registerResponse('<textarea id="file-response" style="height: 400px"></textarea><button id="file-save">Save</button>');var n=document.getElementById("file-response");n.value=t,document.getElementById("file-save").addEventListener("click",function(){window.tauri.writeFile({file:r,contents:n.value},{dir:getDir()}).catch(registerResponse)})}else registerResponse(e)}).catch(registerResponse)});</script> <script>var urlInput=document.getElementById("url");addClickEnterHandler(document.getElementById("open-url"),urlInput,function(){window.tauri.open(urlInput.value)});var titleInput=document.getElementById("title");addClickEnterHandler(document.getElementById("set-title"),titleInput,function(){window.tauri.setTitle(titleInput.value)});</script> <script>var defaultPathInput=document.getElementById("dialog-default-path"),filterInput=document.getElementById("dialog-filter"),multipleInput=document.getElementById("dialog-multiple"),directoryInput=document.getElementById("dialog-directory");document.getElementById("open-dialog").addEventListener("click",function(){window.tauri.openDialog({defaultPath:defaultPathInput.value||null,filter:filterInput.value||null,multiple:multipleInput.checked,directory:directoryInput.checked}).then(registerResponse).catch(registerResponse)}),document.getElementById("save-dialog").addEventListener("click",function(){window.tauri.saveDialog({defaultPath:defaultPathInput.value||null,filter:filterInput.value||null}).then(registerResponse).catch(registerResponse)});</script> <script></script> </body></html>
|
||||
</script> <div> <button id="log">Call Log API</button> <button id="request">Call Request (async) API</button> <button id="event">Send event to Rust</button> </div> <div style="margin-top:24px"> <select id="dir"> <option value="">None</option> </select> <input id="path-to-read" placeholder="Type the path to read..."> <button id="read">Read</button> </div> <div style="margin-top:24px"> <input id="url" value="https://tauri.studio"> <button id="open-url">Open URL</button> </div> <div style="margin-top:24px"> <input id="title" value="Awesome Tauri Example!"> <button id="set-title">Set title</button> </div> <div style="margin-top:24px"> <input id="dialog-default-path" placeholder="Default path"> <input id="dialog-filter" placeholder="Extensions filter"> <div> <input type="checkbox" id="dialog-multiple"> <label>Multiple</label> </div> <div> <input type="checkbox" id="dialog-directory"> <label>Directory</label> </div> <button id="open-dialog">Open dialog</button> <button id="save-dialog">Open save dialog</button> </div> <div id="response"></div> <script>function registerResponse(e){document.getElementById("response").innerHTML="object"==typeof e?JSON.stringify(e):e}function addClickEnterHandler(e,n,t){e.addEventListener("click",t),n.addEventListener("keyup",function(e){13===e.keyCode&&t()})}window.tauri.listen("rust-event",function(e){document.getElementById("response").innerHTML=JSON.stringify(e)});var dirSelect=document.getElementById("dir");for(var key in window.tauri.Dir){var value=window.tauri.Dir[key],opt=document.createElement("option");opt.value=value,opt.innerHTML=key,dirSelect.appendChild(opt)}</script> <script>document.getElementById("log").addEventListener("click",function(){window.tauri.invoke({cmd:"logOperation",event:"tauri-click",payload:"this payload is optional because we used Option in Rust"})}),document.getElementById("request").addEventListener("click",function(){window.tauri.promisified({cmd:"performRequest",endpoint:"dummy endpoint arg",body:{id:5,name:"test"}}).then(registerResponse).catch(registerResponse)}),document.getElementById("event").addEventListener("click",function(){window.tauri.emit("js-event","this is the payload string")});</script> <script>var dirSelect=document.getElementById("dir");function getDir(){return dirSelect.value?parseInt(dir.value):null}function arrayBufferToBase64(e,n){var t=new Blob([e],{type:"application/octet-binary"}),r=new FileReader;r.onload=function(e){var t=e.target.result;n(t.substr(t.indexOf(",")+1))},r.readAsDataURL(t)}var pathInput=document.getElementById("path-to-read");addClickEnterHandler(document.getElementById("read"),pathInput,function(){var r=pathInput.value,a=r.match(/\S+\.\S+$/g),e={dir:getDir()};(a?window.tauri.readBinaryFile(r,e):window.tauri.readDir(r,e)).then(function(e){if(a)if(r.includes(".png")||r.includes(".jpg"))arrayBufferToBase64(new Uint8Array(e),function(e){registerResponse('<img src="'+("data:image/png;base64,"+e)+'"></img>')});else{var t=String.fromCharCode.apply(null,e);registerResponse('<textarea id="file-response" style="height: 400px"></textarea><button id="file-save">Save</button>');var n=document.getElementById("file-response");n.value=t,document.getElementById("file-save").addEventListener("click",function(){window.tauri.writeFile({file:r,contents:n.value},{dir:getDir()}).catch(registerResponse)})}else registerResponse(e)}).catch(registerResponse)});</script> <script>var urlInput=document.getElementById("url");addClickEnterHandler(document.getElementById("open-url"),urlInput,function(){window.tauri.open(urlInput.value)});var titleInput=document.getElementById("title");addClickEnterHandler(document.getElementById("set-title"),titleInput,function(){window.tauri.setTitle(titleInput.value)});</script> <script>var defaultPathInput=document.getElementById("dialog-default-path"),filterInput=document.getElementById("dialog-filter"),multipleInput=document.getElementById("dialog-multiple"),directoryInput=document.getElementById("dialog-directory");document.getElementById("open-dialog").addEventListener("click",function(){window.tauri.openDialog({defaultPath:defaultPathInput.value||null,filter:filterInput.value||null,multiple:multipleInput.checked,directory:directoryInput.checked}).then(registerResponse).catch(registerResponse)}),document.getElementById("save-dialog").addEventListener("click",function(){window.tauri.saveDialog({defaultPath:defaultPathInput.value||null,filter:filterInput.value||null}).then(registerResponse).catch(registerResponse)});</script> </body></html>
|
Loading…
Reference in New Issue
Block a user