Flesh out client, fix session management

- Request first 100 rooms, with placeholder icons
- Add a `http.FileServer` for everything under `/client` for images.
- Sessions are only 400d if they provide a `?pos=` with the session ID.
This commit is contained in:
Kegan Dougal 2021-09-28 13:48:42 +01:00
parent 3f9794ef33
commit 415575d942
4 changed files with 68 additions and 51 deletions

View File

@ -2,12 +2,36 @@
<head> <head>
<title>Sync v3 experiments</title> <title>Sync v3 experiments</title>
<script> <script>
let restart = false; let activeSessionId;
const doSyncLoop = async(accessToken) => { const roomIdToRoom = {};
const roomIdToDiv = {}; // cached innerHTML
const render = (container, rooms) => {
// TODO: don't nuke all cells
while (container.firstChild) {
container.removeChild(container.firstChild);
}
rooms.forEach((r) => {
roomIdToRoom[r.room_id] = r;
const template = document.getElementById("roomCellTemplate");
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template#avoiding_documentfragment_pitfall
const roomCell = template.content.firstElementChild.cloneNode(true);
roomCell.addEventListener("click", (e) => {
console.log(r, e);
});
roomIdToDiv[r.room_id] = roomCell;
roomCell.getElementsByClassName("roomName")[0].textContent = r.name;
container.appendChild(roomCell);
});
}
const doSyncLoop = async(accessToken, sessionId) => {
console.log("Starting sync loop. Active: ", activeSessionId, " this:", sessionId);
let currentPos; let currentPos;
let rooms = []; let rooms = [];
while (!restart) { while (sessionId === activeSessionId) {
let resp = await doSyncRequest(accessToken, currentPos, [0,9]); let resp = await doSyncRequest(accessToken, currentPos, [0,99], sessionId);
currentPos = resp.pos; currentPos = resp.pos;
if (!resp.ops) { if (!resp.ops) {
continue; continue;
@ -60,28 +84,12 @@
} }
} }
}); });
render(document.getElementById("listContainer"), rooms);
let output = "";
rooms.forEach((r) => {
if (!r) {
output += "_____\r\n";
return;
}
output += (r.name || r.room_id) + "\r\n";
})
// re-render the rooms array
console.log(rooms);
console.log(output);
document.getElementById("list").textContent = output;
} }
console.log("restarting"); console.log("active session: ", activeSessionId, " this session: ", sessionId, " terminating.");
document.getElementById("list").textContent = "";
restart = false;
doSyncLoop(accessToken);
} }
// accessToken = string, pos = int, ranges = [2]int e.g [0,99] // accessToken = string, pos = int, ranges = [2]int e.g [0,99]
const doSyncRequest = async (accessToken, pos, ranges) => { const doSyncRequest = async (accessToken, pos, ranges, sessionId) => {
let resp = await fetch("/_matrix/client/v3/sync" + (pos ? "?pos=" + pos : ""), { let resp = await fetch("/_matrix/client/v3/sync" + (pos ? "?pos=" + pos : ""), {
method: "POST", method: "POST",
headers: { headers: {
@ -89,7 +97,8 @@
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
rooms: [ranges] rooms: [ranges],
session_id: (sessionId ? sessionId : undefined),
}) })
}); });
let respBody = await resp.json(); let respBody = await resp.json();
@ -98,12 +107,17 @@
} }
window.addEventListener('load', (event) => { window.addEventListener('load', (event) => {
const storedAccessToken = window.localStorage.getItem("accessToken");
if (storedAccessToken) {
document.getElementById("accessToken").value = storedAccessToken;
}
document.getElementById("syncButton").onclick = () => { document.getElementById("syncButton").onclick = () => {
const accessToken = document.getElementById("accessToken").value; const accessToken = document.getElementById("accessToken").value;
doSyncLoop(accessToken); window.localStorage.setItem("accessToken", accessToken);
doSyncLoop(accessToken, activeSessionId);
} }
document.getElementById("resetButton").onclick = () => { document.getElementById("resetButton").onclick = () => {
restart = true; activeSessionId = new Date().getTime() + "";
} }
}); });
</script> </script>
@ -112,8 +126,14 @@
<div> <div>
<input id="accessToken" type="password" placeholder="matrix.org access token" /> <input id="accessToken" type="password" placeholder="matrix.org access token" />
<input id="syncButton" type="button" value="Sync" /> <input id="syncButton" type="button" value="Sync" />
<input id="resetButton" type="button" value="Reset" /> <input id="resetButton" type="button" value="New Session" />
<p id="list" style="white-space: pre;"></p> <div id="listContainer" ></div>
<template id="roomCellTemplate">
<div style="padding: 5px; display: flex; align-items: center;">
<img src="/client/placeholder.svg" style="width: 32px; height: 32px;"/>
<span class="roomName"> </span>
</div>
</template>
</div> </div>
</body> </body>
</html> </html>

5
client/placeholder.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.0" width="150" height="150">
<path fill="#ccc" d="M 104.68731,56.689353 C 102.19435,80.640493 93.104981,97.26875 74.372196,97.26875 55.639402,97.26875 46.988823,82.308034 44.057005,57.289941 41.623314,34.938838 55.639402,15.800152 74.372196,15.800152 c 18.732785,0 32.451944,18.493971 30.315114,40.889201 z"/>
<path fill="#ccc" d="M 92.5675 89.6048 C 90.79484 93.47893 89.39893 102.4504 94.86478 106.9039 C 103.9375 114.2963 106.7064 116.4723 118.3117 118.9462 C 144.0432 124.4314 141.6492 138.1543 146.5244 149.2206 L 4.268444 149.1023 C 8.472223 138.6518 6.505799 124.7812 32.40051 118.387 C 41.80992 116.0635 45.66513 113.8823 53.58659 107.0158 C 58.52744 102.7329 57.52583 93.99267 56.43084 89.26926 C 52.49275 88.83011 94.1739 88.14054 92.5675 89.6048 z" />
</svg>

After

Width:  |  Height:  |  Size: 918 B

View File

@ -86,8 +86,11 @@ func (h *SyncLiveHandler) serve(w http.ResponseWriter, req *http.Request) error
} }
} }
} }
if requestBody.SessionID == "" {
requestBody.SessionID = DefaultSessionID
}
conn, err := h.setupConnection(req, &requestBody) conn, err := h.setupConnection(req, &requestBody, req.URL.Query().Get("pos") != "")
if err != nil { if err != nil {
hlog.FromRequest(req).Err(err).Msg("failed to get or create Conn") hlog.FromRequest(req).Err(err).Msg("failed to get or create Conn")
return err return err
@ -127,7 +130,7 @@ func (h *SyncLiveHandler) serve(w http.ResponseWriter, req *http.Request) error
// setupConnection associates this request with an existing connection or makes a new connection. // setupConnection associates this request with an existing connection or makes a new connection.
// It also sets a v2 sync poll loop going if one didn't exist already for this user. // It also sets a v2 sync poll loop going if one didn't exist already for this user.
// When this function returns, the connection is alive and active. // When this function returns, the connection is alive and active.
func (h *SyncLiveHandler) setupConnection(req *http.Request, syncReq *Request) (*Conn, error) { func (h *SyncLiveHandler) setupConnection(req *http.Request, syncReq *Request, containsPos bool) (*Conn, error) {
log := hlog.FromRequest(req) log := hlog.FromRequest(req)
var conn *Conn var conn *Conn
@ -142,7 +145,7 @@ func (h *SyncLiveHandler) setupConnection(req *http.Request, syncReq *Request) (
} }
// client thinks they have a connection // client thinks they have a connection
if syncReq.SessionID != "" { if containsPos {
// Lookup the connection // Lookup the connection
// we need to map based on both as the session ID isn't crypto secure but the device ID is (Auth header) // we need to map based on both as the session ID isn't crypto secure but the device ID is (Auth header)
conn = h.ConnMap.Conn(ConnID{ conn = h.ConnMap.Conn(ConnID{
@ -202,7 +205,7 @@ func (h *SyncLiveHandler) setupConnection(req *http.Request, syncReq *Request) (
// to check for an existing connection though, as it's possible for the client to call /sync // to check for an existing connection though, as it's possible for the client to call /sync
// twice for a new connection and get the same session ID. // twice for a new connection and get the same session ID.
conn, created := h.ConnMap.GetOrCreateConn(ConnID{ conn, created := h.ConnMap.GetOrCreateConn(ConnID{
SessionID: DefaultSessionID, SessionID: syncReq.SessionID,
DeviceID: deviceID, DeviceID: deviceID,
}, v2device.UserID) }, v2device.UserID)
if created { if created {
@ -210,7 +213,6 @@ func (h *SyncLiveHandler) setupConnection(req *http.Request, syncReq *Request) (
} else { } else {
log.Info().Str("conn_id", conn.ConnID.String()).Msg("using existing connection") log.Info().Str("conn_id", conn.ConnID.String()).Msg("using existing connection")
} }
syncReq.SessionID = conn.ConnID.SessionID
return conn, nil return conn, nil
} }

26
v3.go
View File

@ -3,7 +3,6 @@ package syncv3
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"time" "time"
@ -31,7 +30,7 @@ func (s *server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
h.ServeHTTP(w, req) h.ServeHTTP(w, req)
} }
func jsClient(file []byte) http.HandlerFunc { func allowCORS(next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) { return func(w http.ResponseWriter, req *http.Request) {
if req.Method == "OPTIONS" { if req.Method == "OPTIONS" {
w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Origin", "*")
@ -40,29 +39,20 @@ func jsClient(file []byte) http.HandlerFunc {
w.WriteHeader(200) w.WriteHeader(200)
return return
} }
// TODO: remove when don't need live updates next.ServeHTTP(w, req)
jsFile, err := ioutil.ReadFile("client.html")
if err != nil {
logger.Fatal().Err(err).Msg("failed to read client.html")
}
w.WriteHeader(200)
w.Write(jsFile)
} }
} }
// RunSyncV3Server is the main entry point to the server // RunSyncV3Server is the main entry point to the server
func RunSyncV3Server(h http.Handler, bindAddr string) { func RunSyncV3Server(h http.Handler, bindAddr string) {
jsFile, err := ioutil.ReadFile("client.html")
if err != nil {
logger.Fatal().Err(err).Msg("failed to read client.html")
}
// HTTP path routing // HTTP path routing
r := mux.NewRouter() r := mux.NewRouter()
r.Handle("/_matrix/client/v3/sync", h) r.Handle("/_matrix/client/v3/sync", h)
r.HandleFunc("/client", jsClient(jsFile)).Methods("GET", "OPTIONS", "HEAD") r.PathPrefix("/client/").HandlerFunc(
allowCORS(
http.StripPrefix("/client/", http.FileServer(http.Dir("./client"))),
),
)
srv := &server{ srv := &server{
chain: []func(next http.Handler) http.Handler{ chain: []func(next http.Handler) http.Handler{
@ -73,7 +63,7 @@ func RunSyncV3Server(h http.Handler, bindAddr string) {
Int("status", status). Int("status", status).
Int("size", size). Int("size", size).
Dur("duration", duration). Dur("duration", duration).
Str("since", r.URL.Query().Get("since")). Str("path", r.URL.Path).
Msg("") Msg("")
}), }),
hlog.RemoteAddrHandler("ip"), hlog.RemoteAddrHandler("ip"),