Exploring the possibilities with hs.webview and hs.webview.usercontent. The primary feature here is WKUserContentController, which allows for injecting user scripts into a web view and for JavaScript to post messages directly back to the native runtime.
The good thing is it's very easy to design the web portion in JS sketchbooks such as jsbin/codepen/jsfiddle because the snippet code structure maps cleanly to this. The bad part is that WKWebView doesn't always behave consistently or have a clean slate. Maybe this will help resolve state issues in the future. It will randomly not be able to find injected scripts without any code changes, and I haven't figured out why.
In this, a two-column table in HTML can be filled with values from Lua tables, and can also send them back. This is used for HTTP requests with arbitrary headers in a fun and exciting way.
callback = function(input)
print(hs.inspect(input))
local data = input.body;
if data.url then
hs.http.asyncPost(data.url, data.payload, data.headers, function(status, body, headers)
print(status,headers,body)
view:evaluateJavaScript("document.getElementById('response').textContent = '"..body.."'")
end)
end
end;
name = hs.host.uuid():gsub("-","");
frame = hs.geometry.rect(hs.screen.mainScreen():frame().center, "500x700");
ucc = hs.webview.usercontent.new(name):setCallback(callback)
ucc:injectScript({source="function sendData(d) {webkit.messageHandlers."..name..".postMessage(d)}", mainFrame=false});
loadTable = function(t) for k,v in pairs(t) do view:evaluateJavaScript("addRow('"..k.."','"..v.."')") end end;
view = hs.webview.new(frame, {developerExtrasEnabled=true}, ucc):allowTextEntry(true):windowStyle(1|2|4|8);
html=[[
<button id="addRow">Add row</button>
<button id="sendTable">Send table</button>
<button id="sendPost">Send POST</button>
<table id="tableData">
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</table>
<input id="url" type="text" placeholder="url">
<textarea id="payload" rows="4" cols="50" placeholder="request payload"></textarea>
<textarea id="response" rows="20" cols="50" placeholder="response"></textarea>
]]
js = [[
<script type="text/javascript">
function addRow(key, val) {
var row = document.getElementById("tableData").insertRow();
[key, val].forEach(function(v, i, a) {
var cell = row.insertCell(); cell.contentEditable = true; cell.className = "dataCell"
cell.textContent = ["string", "number"].includes(typeof v) ? v : ""
cell.addEventListener("keydown", function(e) {e.keyCode === 13 && e.preventDefault()})
})
}
function makeObj() {
var data = {}, empty = [], cells = document.getElementsByClassName("dataCell")
for (var i = 0; i < cells.length; i += 2) {
var key = cells[i].textContent, val = cells[i + 1].textContent
if (key === "" && val === "") {empty.push(cells[i]); continue}
data[key] = val
}
empty.forEach(function(v, i, a) {v.parentNode.remove()})
return data
}
function makePost() {
return {
headers: makeObj(),
url: document.getElementById("url").value,
payload: document.getElementById("payload").value
}
}
window.onload = function() {
document.getElementById("addRow").onclick = addRow
document.getElementById("sendTable").onclick = function() {sendData(makeObj())}
document.getElementById("sendPost").onclick = function() {sendData(makePost())}
}
</script>
]]
css = [[
<style>
table { border-collapse: collapse; }
th, td { border: 2px solid black; width:200px; }
th { background: #cccccc;}
tr:nth-child(even) { background-color: #eeeeff; }
input { width: 400px; }
textarea, .dataCell { font-family: monospace; }
textarea, input { display: block; }
</style>
]]
view:html(html..js..css):show()
I know this is an old post, but if you change it a litte bit, it would be working much better, since the injected script needs a name with a letter in front.
And thanks for the source. It's very useful.