An idea for a dynamically re-programmable Roguelike.

lovebird.lua 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. --
  2. -- lovebird
  3. --
  4. -- Copyright (c) 2017 rxi
  5. --
  6. -- This library is free software; you can redistribute it and/or modify it
  7. -- under the terms of the MIT license. See LICENSE for details.
  8. --
  9. local socket = require "socket"
  10. local lovebird = { _version = "0.4.3" }
  11. lovebird.loadstring = loadstring or load
  12. lovebird.inited = false
  13. lovebird.host = "*"
  14. lovebird.buffer = ""
  15. lovebird.lines = {}
  16. lovebird.connections = {}
  17. lovebird.pages = {}
  18. lovebird.wrapprint = true
  19. lovebird.timestamp = true
  20. lovebird.allowhtml = false
  21. lovebird.echoinput = true
  22. lovebird.port = 8000
  23. lovebird.whitelist = { "127.0.0.1" }
  24. lovebird.maxlines = 200
  25. lovebird.updateinterval = .5
  26. lovebird.pages["index"] = [[
  27. <?lua
  28. -- Handle console input
  29. if req.parsedbody.input then
  30. local str = req.parsedbody.input
  31. if lovebird.echoinput then
  32. lovebird.pushline({ type = 'input', str = str })
  33. end
  34. if str:find("^=") then
  35. str = "print(" .. str:sub(2) .. ")"
  36. end
  37. xpcall(function() assert(lovebird.loadstring(str, "input"))() end,
  38. lovebird.onerror)
  39. end
  40. ?>
  41. <!doctype html>
  42. <html>
  43. <head>
  44. <meta http-equiv="x-ua-compatible" content="IE=Edge"/>
  45. <meta charset="utf-8">
  46. <title>lovebird</title>
  47. <style>
  48. body {
  49. margin: 0px;
  50. font-size: 14px;
  51. font-family: helvetica, verdana, sans;
  52. background: #FFFFFF;
  53. }
  54. form {
  55. margin-bottom: 0px;
  56. }
  57. .timestamp {
  58. color: #909090;
  59. padding-right: 4px;
  60. }
  61. .repeatcount {
  62. color: #F0F0F0;
  63. background: #505050;
  64. font-size: 11px;
  65. font-weight: bold;
  66. text-align: center;
  67. padding-left: 4px;
  68. padding-right: 4px;
  69. padding-top: 0px;
  70. padding-bottom: 0px;
  71. border-radius: 7px;
  72. display: inline-block;
  73. }
  74. .errormarker {
  75. color: #F0F0F0;
  76. background: #8E0000;
  77. font-size: 11px;
  78. font-weight: bold;
  79. text-align: center;
  80. border-radius: 8px;
  81. width: 17px;
  82. padding-top: 0px;
  83. padding-bottom: 0px;
  84. display: inline-block;
  85. }
  86. .greybordered {
  87. margin: 12px;
  88. background: #F0F0F0;
  89. border: 1px solid #E0E0E0;
  90. border-radius: 3px;
  91. }
  92. .inputline {
  93. font-family: mono, courier;
  94. font-size: 13px;
  95. color: #606060;
  96. }
  97. .inputline:before {
  98. content: '\00B7\00B7\00B7';
  99. padding-right: 5px;
  100. }
  101. .errorline {
  102. color: #8E0000;
  103. }
  104. #header {
  105. background: #101010;
  106. height: 25px;
  107. color: #F0F0F0;
  108. padding: 9px
  109. }
  110. #title {
  111. float: left;
  112. font-size: 20px;
  113. }
  114. #title a {
  115. color: #F0F0F0;
  116. text-decoration: none;
  117. }
  118. #title a:hover {
  119. color: #FFFFFF;
  120. }
  121. #version {
  122. font-size: 10px;
  123. }
  124. #status {
  125. float: right;
  126. font-size: 14px;
  127. padding-top: 4px;
  128. }
  129. #main a {
  130. color: #000000;
  131. text-decoration: none;
  132. background: #E0E0E0;
  133. border: 1px solid #D0D0D0;
  134. border-radius: 3px;
  135. padding-left: 2px;
  136. padding-right: 2px;
  137. display: inline-block;
  138. }
  139. #main a:hover {
  140. background: #D0D0D0;
  141. border: 1px solid #C0C0C0;
  142. }
  143. #console {
  144. position: absolute;
  145. top: 40px; bottom: 0px; left: 0px; right: 312px;
  146. }
  147. #input {
  148. position: absolute;
  149. margin: 10px;
  150. bottom: 0px; left: 0px; right: 0px;
  151. }
  152. #inputbox {
  153. width: 100%;
  154. font-family: mono, courier;
  155. font-size: 13px;
  156. }
  157. #output {
  158. overflow-y: scroll;
  159. position: absolute;
  160. margin: 10px;
  161. line-height: 17px;
  162. top: 0px; bottom: 36px; left: 0px; right: 0px;
  163. }
  164. #env {
  165. position: absolute;
  166. top: 40px; bottom: 0px; right: 0px;
  167. width: 300px;
  168. }
  169. #envheader {
  170. padding: 5px;
  171. background: #E0E0E0;
  172. }
  173. #envvars {
  174. position: absolute;
  175. left: 0px; right: 0px; top: 25px; bottom: 0px;
  176. margin: 10px;
  177. overflow-y: scroll;
  178. font-size: 12px;
  179. }
  180. </style>
  181. </head>
  182. <body>
  183. <div id="header">
  184. <div id="title">
  185. <a href="https://github.com/rxi/lovebird">lovebird</a>
  186. <span id="version"><?lua echo(lovebird._version) ?></span>
  187. </div>
  188. <div id="status"></div>
  189. </div>
  190. <div id="main">
  191. <div id="console" class="greybordered">
  192. <div id="output"> <?lua echo(lovebird.buffer) ?> </div>
  193. <div id="input">
  194. <form method="post"
  195. onkeydown="return onInputKeyDown(event);"
  196. onsubmit="onInputSubmit(); return false;">
  197. <input id="inputbox" name="input" type="text"
  198. autocomplete="off"></input>
  199. </form>
  200. </div>
  201. </div>
  202. <div id="env" class="greybordered">
  203. <div id="envheader"></div>
  204. <div id="envvars"></div>
  205. </div>
  206. </div>
  207. <script>
  208. document.getElementById("inputbox").focus();
  209. var changeFavicon = function(href) {
  210. var old = document.getElementById("favicon");
  211. if (old) document.head.removeChild(old);
  212. var link = document.createElement("link");
  213. link.id = "favicon";
  214. link.rel = "shortcut icon";
  215. link.href = href;
  216. document.head.appendChild(link);
  217. }
  218. var truncate = function(str, len) {
  219. if (str.length <= len) return str;
  220. return str.substring(0, len - 3) + "...";
  221. }
  222. var geturl = function(url, onComplete, onFail) {
  223. var req = new XMLHttpRequest();
  224. req.onreadystatechange = function() {
  225. if (req.readyState != 4) return;
  226. if (req.status == 200) {
  227. if (onComplete) onComplete(req.responseText);
  228. } else {
  229. if (onFail) onFail(req.responseText);
  230. }
  231. }
  232. url += (url.indexOf("?") > -1 ? "&_=" : "?_=") + Math.random();
  233. req.open("GET", url, true);
  234. req.send();
  235. }
  236. var divContentCache = {}
  237. var updateDivContent = function(id, content) {
  238. if (divContentCache[id] != content) {
  239. document.getElementById(id).innerHTML = content;
  240. divContentCache[id] = content
  241. return true;
  242. }
  243. return false;
  244. }
  245. var onInputSubmit = function() {
  246. var b = document.getElementById("inputbox");
  247. var req = new XMLHttpRequest();
  248. req.open("POST", "/", true);
  249. req.send("input=" + encodeURIComponent(b.value));
  250. /* Do input history */
  251. if (b.value && inputHistory[0] != b.value) {
  252. inputHistory.unshift(b.value);
  253. }
  254. inputHistory.index = -1;
  255. /* Reset */
  256. b.value = "";
  257. refreshOutput();
  258. }
  259. /* Input box history */
  260. var inputHistory = [];
  261. inputHistory.index = 0;
  262. var onInputKeyDown = function(e) {
  263. var key = e.which || e.keyCode;
  264. if (key != 38 && key != 40) return true;
  265. var b = document.getElementById("inputbox");
  266. if (key == 38 && inputHistory.index < inputHistory.length - 1) {
  267. /* Up key */
  268. inputHistory.index++;
  269. }
  270. if (key == 40 && inputHistory.index >= 0) {
  271. /* Down key */
  272. inputHistory.index--;
  273. }
  274. b.value = inputHistory[inputHistory.index] || "";
  275. b.selectionStart = b.value.length;
  276. return false;
  277. }
  278. /* Output buffer and status */
  279. var refreshOutput = function() {
  280. geturl("/buffer", function(text) {
  281. updateDivContent("status", "connected &#9679;");
  282. if (updateDivContent("output", text)) {
  283. var div = document.getElementById("output");
  284. div.scrollTop = div.scrollHeight;
  285. }
  286. /* Update favicon */
  287. changeFavicon("data:image/png;base64," +
  288. "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAP1BMVEUAAAAAAAAAAAD////19fUO"+
  289. "Dg7v7+/h4eGzs7MlJSUeHh7n5+fY2NjJycnGxsa3t7eioqKfn5+QkJCHh4d+fn7zU+b5AAAAAnRS"+
  290. "TlPlAFWaypEAAABRSURBVBjTfc9HDoAwDERRQ+w0ern/WQkZaUBC4e/mrWzppH9VJjbjZg1Ii2rM"+
  291. "DyR1JZ8J0dVWggIGggcEwgbYCRbuPRqgyjHNpzUP+39GPu9fgloC5L9DO0sAAAAASUVORK5CYII="
  292. );
  293. },
  294. function(text) {
  295. updateDivContent("status", "disconnected &#9675;");
  296. /* Update favicon */
  297. changeFavicon("data:image/png;base64," +
  298. "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAYFBMVEUAAAAAAAAAAADZ2dm4uLgM"+
  299. "DAz29vbz8/Pv7+/h4eHIyMiwsLBtbW0lJSUeHh4QEBDn5+fS0tLDw8O0tLSioqKfn5+QkJCHh4d+"+
  300. "fn5ycnJmZmZgYGBXV1dLS0tFRUUGBgZ0He44AAAAAnRSTlPlAFWaypEAAABeSURBVBjTfY9HDoAw"+
  301. "DAQD6Z3ey/9/iXMxkVDYw0g7F3tJReosUKHnwY4pCM+EtOEVXrb7wVRA0dMbaAcUwiVeDQq1Jp4a"+
  302. "xUg5kE0ooqZu68Di2Tgbs/DiY/9jyGf+AyFKBAK7KD2TAAAAAElFTkSuQmCC"
  303. );
  304. });
  305. }
  306. setInterval(refreshOutput,
  307. <?lua echo(lovebird.updateinterval) ?> * 1000);
  308. /* Environment variable view */
  309. var envPath = "";
  310. var refreshEnv = function() {
  311. geturl("/env.json?p=" + envPath, function(text) {
  312. var json = eval("(" + text + ")");
  313. /* Header */
  314. var html = "<a href='#' onclick=\"setEnvPath('')\">env</a>";
  315. var acc = "";
  316. var p = json.path != "" ? json.path.split(".") : [];
  317. for (var i = 0; i < p.length; i++) {
  318. acc += "." + p[i];
  319. html += " <a href='#' onclick=\"setEnvPath('" + acc + "')\">" +
  320. truncate(p[i], 10) + "</a>";
  321. }
  322. updateDivContent("envheader", html);
  323. /* Handle invalid table path */
  324. if (!json.valid) {
  325. updateDivContent("envvars", "Bad path");
  326. return;
  327. }
  328. /* Variables */
  329. var html = "<table>";
  330. for (var i = 0; json.vars[i]; i++) {
  331. var x = json.vars[i];
  332. var fullpath = (json.path + "." + x.key).replace(/^\./, "");
  333. var k = truncate(x.key, 15);
  334. if (x.type == "table") {
  335. k = "<a href='#' onclick=\"setEnvPath('" + fullpath + "')\">" +
  336. k + "</a>";
  337. }
  338. var v = "<a href='#' onclick=\"insertVar('" +
  339. fullpath.replace(/\.(-?[0-9]+)/g, "[$1]") +
  340. "');\">" + x.value + "</a>"
  341. html += "<tr><td>" + k + "</td><td>" + v + "</td></tr>";
  342. }
  343. html += "</table>";
  344. updateDivContent("envvars", html);
  345. });
  346. }
  347. var setEnvPath = function(p) {
  348. envPath = p;
  349. refreshEnv();
  350. }
  351. var insertVar = function(p) {
  352. var b = document.getElementById("inputbox");
  353. b.value += p;
  354. b.focus();
  355. }
  356. setInterval(refreshEnv, <?lua echo(lovebird.updateinterval) ?> * 1000);
  357. </script>
  358. </body>
  359. </html>
  360. ]]
  361. lovebird.pages["buffer"] = [[ <?lua echo(lovebird.buffer) ?> ]]
  362. lovebird.pages["env.json"] = [[
  363. <?lua
  364. local t = _G
  365. local p = req.parsedurl.query.p or ""
  366. p = p:gsub("%.+", "."):match("^[%.]*(.*)[%.]*$")
  367. if p ~= "" then
  368. for x in p:gmatch("[^%.]+") do
  369. t = t[x] or t[tonumber(x)]
  370. -- Return early if path does not exist
  371. if type(t) ~= "table" then
  372. echo('{ "valid": false, "path": ' .. string.format("%q", p) .. ' }')
  373. return
  374. end
  375. end
  376. end
  377. ?>
  378. {
  379. "valid": true,
  380. "path": "<?lua echo(p) ?>",
  381. "vars": [
  382. <?lua
  383. local keys = {}
  384. for k in pairs(t) do
  385. if type(k) == "number" or type(k) == "string" then
  386. table.insert(keys, k)
  387. end
  388. end
  389. table.sort(keys, lovebird.compare)
  390. for _, k in pairs(keys) do
  391. local v = t[k]
  392. ?>
  393. {
  394. "key": "<?lua echo(k) ?>",
  395. "value": <?lua echo(
  396. string.format("%q",
  397. lovebird.truncate(
  398. lovebird.htmlescape(
  399. tostring(v)), 26))) ?>,
  400. "type": "<?lua echo(type(v)) ?>",
  401. },
  402. <?lua end ?>
  403. ]
  404. }
  405. ]]
  406. function lovebird.init()
  407. -- Init server
  408. lovebird.server = assert(socket.bind(lovebird.host, lovebird.port))
  409. lovebird.addr, lovebird.port = lovebird.server:getsockname()
  410. lovebird.server:settimeout(0)
  411. -- Wrap print
  412. lovebird.origprint = print
  413. if lovebird.wrapprint then
  414. local oldprint = print
  415. print = function(...)
  416. oldprint(...)
  417. lovebird.print(...)
  418. end
  419. end
  420. -- Compile page templates
  421. for k, page in pairs(lovebird.pages) do
  422. lovebird.pages[k] = lovebird.template(page, "lovebird, req",
  423. "pages." .. k)
  424. end
  425. lovebird.inited = true
  426. end
  427. function lovebird.template(str, params, chunkname)
  428. params = params and ("," .. params) or ""
  429. local f = function(x) return string.format(" echo(%q)", x) end
  430. str = ("?>"..str.."<?lua"):gsub("%?>(.-)<%?lua", f)
  431. str = "local echo " .. params .. " = ..." .. str
  432. local fn = assert(lovebird.loadstring(str, chunkname))
  433. return function(...)
  434. local output = {}
  435. local echo = function(str) table.insert(output, str) end
  436. fn(echo, ...)
  437. return table.concat(lovebird.map(output, tostring))
  438. end
  439. end
  440. function lovebird.map(t, fn)
  441. local res = {}
  442. for k, v in pairs(t) do res[k] = fn(v) end
  443. return res
  444. end
  445. function lovebird.trace(...)
  446. local str = "[lovebird] " .. table.concat(lovebird.map({...}, tostring), " ")
  447. print(str)
  448. if not lovebird.wrapprint then lovebird.print(str) end
  449. end
  450. function lovebird.unescape(str)
  451. local f = function(x) return string.char(tonumber("0x"..x)) end
  452. return (str:gsub("%+", " "):gsub("%%(..)", f))
  453. end
  454. function lovebird.parseurl(url)
  455. local res = {}
  456. res.path, res.search = url:match("/([^%?]*)%??(.*)")
  457. res.query = {}
  458. for k, v in res.search:gmatch("([^&^?]-)=([^&^#]*)") do
  459. res.query[k] = lovebird.unescape(v)
  460. end
  461. return res
  462. end
  463. local htmlescapemap = {
  464. ["<"] = "&lt;",
  465. ["&"] = "&amp;",
  466. ['"'] = "&quot;",
  467. ["'"] = "&#039;",
  468. }
  469. function lovebird.htmlescape(str)
  470. return ( str:gsub("[<&\"']", htmlescapemap) )
  471. end
  472. function lovebird.truncate(str, len)
  473. if #str <= len then
  474. return str
  475. end
  476. return str:sub(1, len - 3) .. "..."
  477. end
  478. function lovebird.compare(a, b)
  479. local na, nb = tonumber(a), tonumber(b)
  480. if na then
  481. if nb then return na < nb end
  482. return false
  483. elseif nb then
  484. return true
  485. end
  486. return tostring(a) < tostring(b)
  487. end
  488. function lovebird.checkwhitelist(addr)
  489. if lovebird.whitelist == nil then return true end
  490. for _, a in pairs(lovebird.whitelist) do
  491. local ptn = "^" .. a:gsub("%.", "%%."):gsub("%*", "%%d*") .. "$"
  492. if addr:match(ptn) then return true end
  493. end
  494. return false
  495. end
  496. function lovebird.clear()
  497. lovebird.lines = {}
  498. lovebird.buffer = ""
  499. end
  500. function lovebird.pushline(line)
  501. line.time = os.time()
  502. line.count = 1
  503. table.insert(lovebird.lines, line)
  504. if #lovebird.lines > lovebird.maxlines then
  505. table.remove(lovebird.lines, 1)
  506. end
  507. lovebird.recalcbuffer()
  508. end
  509. function lovebird.recalcbuffer()
  510. local function doline(line)
  511. local str = line.str
  512. if not lovebird.allowhtml then
  513. str = lovebird.htmlescape(line.str):gsub("\n", "<br>")
  514. end
  515. if line.type == "input" then
  516. str = '<span class="inputline">' .. str .. '</span>'
  517. else
  518. if line.type == "error" then
  519. str = '<span class="errormarker">!</span> ' .. str
  520. str = '<span class="errorline">' .. str .. '</span>'
  521. end
  522. if line.count > 1 then
  523. str = '<span class="repeatcount">' .. line.count .. '</span> ' .. str
  524. end
  525. if lovebird.timestamp then
  526. str = os.date('<span class="timestamp">%H:%M:%S</span> ', line.time) ..
  527. str
  528. end
  529. end
  530. return str
  531. end
  532. lovebird.buffer = table.concat(lovebird.map(lovebird.lines, doline), "<br>")
  533. end
  534. function lovebird.print(...)
  535. local t = {}
  536. for i = 1, select("#", ...) do
  537. table.insert(t, tostring(select(i, ...)))
  538. end
  539. local str = table.concat(t, " ")
  540. local last = lovebird.lines[#lovebird.lines]
  541. if last and str == last.str then
  542. -- Update last line if this line is a duplicate of it
  543. last.time = os.time()
  544. last.count = last.count + 1
  545. lovebird.recalcbuffer()
  546. else
  547. -- Create new line
  548. lovebird.pushline({ type = "output", str = str })
  549. end
  550. end
  551. function lovebird.onerror(err)
  552. lovebird.pushline({ type = "error", str = err })
  553. if lovebird.wrapprint then
  554. lovebird.origprint("[lovebird] ERROR: " .. err)
  555. end
  556. end
  557. function lovebird.onrequest(req, client)
  558. local page = req.parsedurl.path
  559. page = page ~= "" and page or "index"
  560. -- Handle "page not found"
  561. if not lovebird.pages[page] then
  562. return "HTTP/1.1 404\r\nContent-Length: 8\r\n\r\nBad page"
  563. end
  564. -- Handle page
  565. local str
  566. xpcall(function()
  567. local data = lovebird.pages[page](lovebird, req)
  568. local contenttype = "text/html"
  569. if string.match(page, "%.json$") then
  570. contenttype = "application/json"
  571. end
  572. str = "HTTP/1.1 200 OK\r\n" ..
  573. "Content-Type: " .. contenttype .. "\r\n" ..
  574. "Content-Length: " .. #data .. "\r\n" ..
  575. "\r\n" .. data
  576. end, lovebird.onerror)
  577. return str
  578. end
  579. function lovebird.receive(client, pattern)
  580. while 1 do
  581. local data, msg = client:receive(pattern)
  582. if not data then
  583. if msg == "timeout" then
  584. -- Wait for more data
  585. coroutine.yield(true)
  586. else
  587. -- Disconnected -- yielding nil means we're done
  588. coroutine.yield(nil)
  589. end
  590. else
  591. return data
  592. end
  593. end
  594. end
  595. function lovebird.send(client, data)
  596. local idx = 1
  597. while idx < #data do
  598. local res, msg = client:send(data, idx)
  599. if not res and msg == "closed" then
  600. -- Handle disconnect
  601. coroutine.yield(nil)
  602. else
  603. idx = idx + res
  604. coroutine.yield(true)
  605. end
  606. end
  607. end
  608. function lovebird.onconnect(client)
  609. -- Create request table
  610. local requestptn = "(%S*)%s*(%S*)%s*(%S*)"
  611. local req = {}
  612. req.socket = client
  613. req.addr, req.port = client:getsockname()
  614. req.request = lovebird.receive(client, "*l")
  615. req.method, req.url, req.proto = req.request:match(requestptn)
  616. req.headers = {}
  617. while 1 do
  618. local line, msg = lovebird.receive(client, "*l")
  619. if not line or #line == 0 then break end
  620. local k, v = line:match("(.-):%s*(.*)$")
  621. req.headers[k] = v
  622. end
  623. if req.headers["Content-Length"] then
  624. req.body = lovebird.receive(client, req.headers["Content-Length"])
  625. end
  626. -- Parse body
  627. req.parsedbody = {}
  628. if req.body then
  629. for k, v in req.body:gmatch("([^&]-)=([^&^#]*)") do
  630. req.parsedbody[k] = lovebird.unescape(v)
  631. end
  632. end
  633. -- Parse request line's url
  634. req.parsedurl = lovebird.parseurl(req.url)
  635. -- Handle request; get data to send and send
  636. local data = lovebird.onrequest(req)
  637. lovebird.send(client, data)
  638. -- Clear up
  639. client:close()
  640. end
  641. function lovebird.update()
  642. if not lovebird.inited then lovebird.init() end
  643. -- Handle new connections
  644. while 1 do
  645. -- Accept new connections
  646. local client = lovebird.server:accept()
  647. if not client then break end
  648. client:settimeout(0)
  649. local addr = client:getsockname()
  650. if lovebird.checkwhitelist(addr) then
  651. -- Connection okay -- create and add coroutine to set
  652. local conn = coroutine.wrap(function()
  653. xpcall(function() lovebird.onconnect(client) end, function() end)
  654. end)
  655. lovebird.connections[conn] = true
  656. else
  657. -- Reject connection not on whitelist
  658. lovebird.trace("got non-whitelisted connection attempt: ", addr)
  659. client:close()
  660. end
  661. end
  662. -- Handle existing connections
  663. for conn in pairs(lovebird.connections) do
  664. -- Resume coroutine, remove if it has finished
  665. local status = conn()
  666. if status == nil then
  667. lovebird.connections[conn] = nil
  668. end
  669. end
  670. end
  671. return lovebird