!7 concept AToken { S token; long lastSeen; } concept ALine { S token; S line; } concept ALineToSend > ALine { } concept AIncomingLine > ALine { } concept AAction > ALine { bool verified; } cmodule AssistantWebServer > DynPrintLog { int httpPort = 8083, interval = 200, longPollTimeout = 60000; start-thread { logModuleOutput(); db(); dm_serveHttpFromFunction(httpPort, func(S uri, SS params) enter { if (eq(uri, "/register")) ret serveText((S) dm_call(dm_assistant_usersCRUD(), 'registerUser, params.get('email), params.get('pwHash))); if (eq(uri, "/checkPW")) { S mail = params.get('email); if (!isValidEmailAddress(mail)) ret serveText("Not a valid email address"); S userID = cast dm_call(dm_assistant_usersCRUD(), 'userIDForEmail, mail); if (userID == null) ret serveText("User not found"); bool ok = cast dm_call(dm_assistant_passwordsCRUD(), 'checkPassword, userID, params.get('pwHash)); ret serveText(ok ? "OK" : "Wrong password"); } // token-based S token = params.get('token); if (l(token) == 24) { pingToken(token); if (eq(uri, "/poll")) { ret serveLongPoll(longPollTimeout, interval, func { temp enter(); S text = withDBLock(func -> S { L l = conceptsWhere(ALineToSend, +token); if (empty(l)) null; deleteConcepts(l); S text = lines_rtrim(collect line(sortConceptsByID(l))); ret text; }); if (text != null) printWithTime("Served output to " + token + ": " + text); ret text; }); } if (eq(uri, "/heard")) { S line = params.get('line); if (nempty(line)) { cnew/*_sync*/(AIncomingLine, +token, +line); print("Heard from " + token + ": " + line); } ret "OK"; } } ret serveText("hä"); }); } void pingToken(S token) { cset(uniq_sync(AToken, +token), lastSeen := now()); } }