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>
<title>Sync v3 experiments</title>
<script>
let restart = false;
const doSyncLoop = async(accessToken) => {
let activeSessionId;
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 rooms = [];
while (!restart) {
let resp = await doSyncRequest(accessToken, currentPos, [0,9]);
while (sessionId === activeSessionId) {
let resp = await doSyncRequest(accessToken, currentPos, [0,99], sessionId);
currentPos = resp.pos;
if (!resp.ops) {
continue;
@ -60,28 +84,12 @@
}
}
});
let output = "";
rooms.forEach((r) => {
if (!r) {
output += "_____\r\n";
return;
render(document.getElementById("listContainer"), rooms);
}
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");
document.getElementById("list").textContent = "";
restart = false;
doSyncLoop(accessToken);
console.log("active session: ", activeSessionId, " this session: ", sessionId, " terminating.");
}
// 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 : ""), {
method: "POST",
headers: {
@ -89,7 +97,8 @@
"Content-Type": "application/json",
},
body: JSON.stringify({
rooms: [ranges]
rooms: [ranges],
session_id: (sessionId ? sessionId : undefined),
})
});
let respBody = await resp.json();
@ -98,12 +107,17 @@
}
window.addEventListener('load', (event) => {
const storedAccessToken = window.localStorage.getItem("accessToken");
if (storedAccessToken) {
document.getElementById("accessToken").value = storedAccessToken;
}
document.getElementById("syncButton").onclick = () => {
const accessToken = document.getElementById("accessToken").value;
doSyncLoop(accessToken);
window.localStorage.setItem("accessToken", accessToken);
doSyncLoop(accessToken, activeSessionId);
}
document.getElementById("resetButton").onclick = () => {
restart = true;
activeSessionId = new Date().getTime() + "";
}
});
</script>
@ -112,8 +126,14 @@
<div>
<input id="accessToken" type="password" placeholder="matrix.org access token" />
<input id="syncButton" type="button" value="Sync" />
<input id="resetButton" type="button" value="Reset" />
<p id="list" style="white-space: pre;"></p>
<input id="resetButton" type="button" value="New Session" />
<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>
</body>
</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 {
hlog.FromRequest(req).Err(err).Msg("failed to get or create Conn")
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.
// 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.
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)
var conn *Conn
@ -142,7 +145,7 @@ func (h *SyncLiveHandler) setupConnection(req *http.Request, syncReq *Request) (
}
// client thinks they have a connection
if syncReq.SessionID != "" {
if containsPos {
// 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)
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
// twice for a new connection and get the same session ID.
conn, created := h.ConnMap.GetOrCreateConn(ConnID{
SessionID: DefaultSessionID,
SessionID: syncReq.SessionID,
DeviceID: deviceID,
}, v2device.UserID)
if created {
@ -210,7 +213,6 @@ func (h *SyncLiveHandler) setupConnection(req *http.Request, syncReq *Request) (
} else {
log.Info().Str("conn_id", conn.ConnID.String()).Msg("using existing connection")
}
syncReq.SessionID = conn.ConnID.SessionID
return conn, nil
}

26
v3.go
View File

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