implementation of basic chat page which can show history and not much else
This commit is contained in:
parent
6bebc3fae7
commit
34f44cb5d5
@ -172,7 +172,7 @@ func (a *api) handler() http.Handler {
|
|||||||
)))
|
)))
|
||||||
|
|
||||||
var apiHandler http.Handler = apiMux
|
var apiHandler http.Handler = apiMux
|
||||||
apiHandler = allowedMethod("POST", apiHandler)
|
apiHandler = postOnlyMiddleware(apiHandler)
|
||||||
apiHandler = checkCSRFMiddleware(apiHandler)
|
apiHandler = checkCSRFMiddleware(apiHandler)
|
||||||
apiHandler = logMiddleware(a.params.Logger, apiHandler)
|
apiHandler = logMiddleware(a.params.Logger, apiHandler)
|
||||||
apiHandler = annotateMiddleware(apiHandler)
|
apiHandler = annotateMiddleware(apiHandler)
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/api/apiutils"
|
"github.com/mediocregopher/blog.mediocregopher.com/srv/api/apiutils"
|
||||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/chat"
|
"github.com/mediocregopher/blog.mediocregopher.com/srv/chat"
|
||||||
)
|
)
|
||||||
@ -16,6 +18,8 @@ type chatHandler struct {
|
|||||||
|
|
||||||
room chat.Room
|
room chat.Room
|
||||||
userIDCalc *chat.UserIDCalculator
|
userIDCalc *chat.UserIDCalculator
|
||||||
|
|
||||||
|
wsUpgrader websocket.Upgrader
|
||||||
}
|
}
|
||||||
|
|
||||||
func newChatHandler(
|
func newChatHandler(
|
||||||
@ -26,11 +30,14 @@ func newChatHandler(
|
|||||||
ServeMux: http.NewServeMux(),
|
ServeMux: http.NewServeMux(),
|
||||||
room: room,
|
room: room,
|
||||||
userIDCalc: userIDCalc,
|
userIDCalc: userIDCalc,
|
||||||
|
|
||||||
|
wsUpgrader: websocket.Upgrader{},
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Handle("/history", c.historyHandler())
|
c.Handle("/history", c.historyHandler())
|
||||||
c.Handle("/user-id", requirePowMiddleware(c.userIDHandler()))
|
c.Handle("/user-id", requirePowMiddleware(c.userIDHandler()))
|
||||||
c.Handle("/append", requirePowMiddleware(c.appendHandler()))
|
c.Handle("/append", requirePowMiddleware(c.appendHandler()))
|
||||||
|
c.Handle("/listen", c.listenHandler())
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
@ -148,3 +155,57 @@ func (c *chatHandler) appendHandler() http.Handler {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *chatHandler) listenHandler() http.Handler {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
sinceID := r.FormValue("sinceID")
|
||||||
|
|
||||||
|
conn, err := c.wsUpgrader.Upgrade(rw, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
apiutils.BadRequest(rw, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
it, err := c.room.Listen(ctx, sinceID)
|
||||||
|
|
||||||
|
if errors.As(err, new(chat.ErrInvalidArg)) {
|
||||||
|
apiutils.BadRequest(rw, r, err)
|
||||||
|
return
|
||||||
|
|
||||||
|
} else if errors.Is(err, context.Canceled) {
|
||||||
|
return
|
||||||
|
|
||||||
|
} else if err != nil {
|
||||||
|
apiutils.InternalServerError(rw, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer it.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
msg, err := it.Next(ctx)
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
|
return
|
||||||
|
|
||||||
|
} else if err != nil {
|
||||||
|
apiutils.InternalServerError(rw, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conn.WriteJSON(struct {
|
||||||
|
Message chat.Message `json:"message"`
|
||||||
|
}{
|
||||||
|
Message: msg,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
apiutils.GetRequestLogger(r).Error(ctx, "couldn't write message", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -41,8 +41,14 @@ func checkCSRFMiddleware(h http.Handler) http.Handler {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
apiutils.InternalServerError(rw, r, err)
|
apiutils.InternalServerError(rw, r, err)
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
} else if csrfTok == "" || r.Header.Get(csrfTokenHeaderName) != csrfTok {
|
givenCSRFTok := r.Header.Get(csrfTokenHeaderName)
|
||||||
|
if givenCSRFTok == "" {
|
||||||
|
givenCSRFTok = r.FormValue("csrfToken")
|
||||||
|
}
|
||||||
|
|
||||||
|
if csrfTok == "" || givenCSRFTok != csrfTok {
|
||||||
apiutils.BadRequest(rw, r, errors.New("invalid CSRF token"))
|
apiutils.BadRequest(rw, r, errors.New("invalid CSRF token"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -40,12 +40,15 @@ func annotateMiddleware(h http.Handler) http.Handler {
|
|||||||
|
|
||||||
type logResponseWriter struct {
|
type logResponseWriter struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
statusCode int
|
statusCode int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLogResponseWriter(rw http.ResponseWriter) *logResponseWriter {
|
func newLogResponseWriter(rw http.ResponseWriter) *logResponseWriter {
|
||||||
|
h, _ := rw.(http.Hijacker)
|
||||||
return &logResponseWriter{
|
return &logResponseWriter{
|
||||||
ResponseWriter: rw,
|
ResponseWriter: rw,
|
||||||
|
Hijacker: h,
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,9 +81,11 @@ func logMiddleware(logger *mlog.Logger, h http.Handler) http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func allowedMethod(method string, h http.Handler) http.Handler {
|
func postOnlyMiddleware(h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == method {
|
|
||||||
|
// we allow websockets to not be POSTs because, well, they can't be
|
||||||
|
if r.Method == "POST" || r.Header.Get("Upgrade") == "websocket" {
|
||||||
h.ServeHTTP(rw, r)
|
h.ServeHTTP(rw, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -27,14 +27,14 @@ func (a *api) newPowChallengeHandler() http.Handler {
|
|||||||
func (a *api) requirePowMiddleware(h http.Handler) http.Handler {
|
func (a *api) requirePowMiddleware(h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
seedHex := r.PostFormValue("powSeed")
|
seedHex := r.FormValue("powSeed")
|
||||||
seed, err := hex.DecodeString(seedHex)
|
seed, err := hex.DecodeString(seedHex)
|
||||||
if err != nil || len(seed) == 0 {
|
if err != nil || len(seed) == 0 {
|
||||||
apiutils.BadRequest(rw, r, errors.New("invalid powSeed"))
|
apiutils.BadRequest(rw, r, errors.New("invalid powSeed"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
solutionHex := r.PostFormValue("powSolution")
|
solutionHex := r.FormValue("powSolution")
|
||||||
solution, err := hex.DecodeString(solutionHex)
|
solution, err := hex.DecodeString(solutionHex)
|
||||||
if err != nil || len(seed) == 0 {
|
if err != nil || len(seed) == 0 {
|
||||||
apiutils.BadRequest(rw, r, errors.New("invalid powSolution"))
|
apiutils.BadRequest(rw, r, errors.New("invalid powSolution"))
|
||||||
|
@ -34,6 +34,7 @@ type Message struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
UserID UserID `json:"userID"`
|
UserID UserID `json:"userID"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
|
CreatedAt int64 `json:"createdAt,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func msgFromStreamEntry(entry radix.StreamEntry) (Message, error) {
|
func msgFromStreamEntry(entry radix.StreamEntry) (Message, error) {
|
||||||
@ -59,6 +60,7 @@ func msgFromStreamEntry(entry radix.StreamEntry) (Message, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
msg.ID = entry.ID.String()
|
msg.ID = entry.ID.String()
|
||||||
|
msg.CreatedAt = int64(entry.ID.Time / 1000)
|
||||||
return msg, nil
|
return msg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +213,7 @@ func (r *room) Append(ctx context.Context, msg Message) (Message, error) {
|
|||||||
maxLen := strconv.Itoa(r.params.MaxMessages)
|
maxLen := strconv.Itoa(r.params.MaxMessages)
|
||||||
body := string(b)
|
body := string(b)
|
||||||
|
|
||||||
var id string
|
var id radix.StreamEntryID
|
||||||
|
|
||||||
err = r.params.Redis.Do(ctx, radix.Cmd(
|
err = r.params.Redis.Do(ctx, radix.Cmd(
|
||||||
&id, "XADD", key, "MAXLEN", "=", maxLen, "*", "json", body,
|
&id, "XADD", key, "MAXLEN", "=", maxLen, "*", "json", body,
|
||||||
@ -221,7 +223,8 @@ func (r *room) Append(ctx context.Context, msg Message) (Message, error) {
|
|||||||
return Message{}, fmt.Errorf("posting message to redis: %w", err)
|
return Message{}, fmt.Errorf("posting message to redis: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.ID = id
|
msg.ID = id.String()
|
||||||
|
msg.CreatedAt = int64(id.Time / 1000)
|
||||||
return msg, nil
|
return msg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
pname = "mediocre-blog-srv";
|
pname = "mediocre-blog-srv";
|
||||||
version = "dev";
|
version = "dev";
|
||||||
src = ./.;
|
src = ./.;
|
||||||
vendorSha256 = "0c6j989q6r2q967gx90cl4l8skflkx2npmxd3f5l16bwj2ldw11j";
|
vendorSha256 = "02szg1lisfjk8pk9pflbyv97ykg9362r4fhd0w0p2a7c81kf9b8y";
|
||||||
|
|
||||||
# disable tests
|
# disable tests
|
||||||
checkPhase = '''';
|
checkPhase = '''';
|
||||||
|
@ -6,6 +6,7 @@ require (
|
|||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
||||||
github.com/emersion/go-smtp v0.15.0
|
github.com/emersion/go-smtp v0.15.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
|
github.com/gorilla/websocket v1.4.2 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.8
|
github.com/mattn/go-sqlite3 v1.14.8
|
||||||
github.com/mediocregopher/mediocre-go-lib/v2 v2.0.0-beta.0
|
github.com/mediocregopher/mediocre-go-lib/v2 v2.0.0-beta.0
|
||||||
github.com/mediocregopher/radix/v4 v4.0.0-beta.1.0.20210726230805-d62fa1b2e3cb // indirect
|
github.com/mediocregopher/radix/v4 v4.0.0-beta.1.0.20210726230805-d62fa1b2e3cb // indirect
|
||||||
|
@ -61,6 +61,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import * as utils from "/assets/utils.js";
|
import * as utils from "/assets/utils.js";
|
||||||
|
|
||||||
|
const csrfTokenCookie = "csrf_token";
|
||||||
|
|
||||||
const doFetch = async (req) => {
|
const doFetch = async (req) => {
|
||||||
let res, jsonRes;
|
let res, jsonRes;
|
||||||
try {
|
try {
|
||||||
@ -53,13 +55,13 @@ const call = async (route, opts = {}) => {
|
|||||||
requiresPow = false,
|
requiresPow = false,
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
if (!utils.cookies["csrf_token"])
|
if (!utils.cookies[csrfTokenCookie])
|
||||||
throw "csrf_token cookie not set, can't make api call";
|
throw `${csrfTokenCookie} cookie not set, can't make api call`;
|
||||||
|
|
||||||
const reqOpts = {
|
const reqOpts = {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
"X-CSRF-Token": utils.cookies["csrf_token"],
|
"X-CSRF-Token": utils.cookies[csrfTokenCookie],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,6 +82,50 @@ const call = async (route, opts = {}) => {
|
|||||||
return doFetch(req);
|
return doFetch(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ws = async (route, opts = {}) => {
|
||||||
|
const {
|
||||||
|
requiresPow = false,
|
||||||
|
} = opts;
|
||||||
|
|
||||||
|
const docURL = new URL(document.URL);
|
||||||
|
const protocol = docURL.protocol == "http:" ? "ws:" : "wss:";
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
const csrfToken = utils.cookies[csrfTokenCookie];
|
||||||
|
|
||||||
|
if (!csrfToken)
|
||||||
|
throw `${csrfTokenCookie} cookie not set, can't make api call`;
|
||||||
|
|
||||||
|
params.set("csrfToken", csrfToken);
|
||||||
|
|
||||||
|
if (requiresPow) {
|
||||||
|
const {seed, solution} = await solvePow();
|
||||||
|
params.set("powSeed", seed);
|
||||||
|
params.set("powSolution", solution);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawConn = new WebSocket(`${protocol}//${docURL.host}${route}?${params.toString()}`);
|
||||||
|
|
||||||
|
const conn = {
|
||||||
|
next: () => new Promise((resolve, reject) => {
|
||||||
|
rawConn.onmessage = (m) => {
|
||||||
|
const mj = JSON.parse(m.data);
|
||||||
|
resolve(mj);
|
||||||
|
};
|
||||||
|
rawConn.onerror = reject;
|
||||||
|
rawConn.onclose = reject;
|
||||||
|
}),
|
||||||
|
|
||||||
|
close: rawConn.close,
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
rawConn.onopen = () => resolve(conn);
|
||||||
|
rawConn.onerror = reject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
call,
|
call,
|
||||||
|
ws
|
||||||
}
|
}
|
||||||
|
126
static/src/chat.md
Normal file
126
static/src/chat.md
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
---
|
||||||
|
layout: page
|
||||||
|
---
|
||||||
|
|
||||||
|
<script async type="module" src="/assets/api.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#messages {
|
||||||
|
max-height: 65vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages .message {
|
||||||
|
border: 1px solid #AAA;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding: 2rem;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages .message .title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 120%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages .message .secondaryTitle {
|
||||||
|
font-family: monospace;
|
||||||
|
color: #CCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages .message p {
|
||||||
|
font-family: monospace;
|
||||||
|
margin: 1rem 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="messages"></div>
|
||||||
|
|
||||||
|
<span id="fail" style="color: red;"></span>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const messagesEl = document.getElementById("messages");
|
||||||
|
|
||||||
|
function renderMessages(msgs) {
|
||||||
|
|
||||||
|
msgs = [...msgs].reverse();
|
||||||
|
|
||||||
|
messagesEl.innerHTML = '';
|
||||||
|
|
||||||
|
msgs.forEach((msg) => {
|
||||||
|
console.log(msg);
|
||||||
|
const el = document.createElement("div");
|
||||||
|
el.className = "row message"
|
||||||
|
|
||||||
|
const elWithTextContents = (tag, body) => {
|
||||||
|
const el = document.createElement(tag);
|
||||||
|
el.appendChild(document.createTextNode(body));
|
||||||
|
return el;
|
||||||
|
};
|
||||||
|
|
||||||
|
const titleEl = document.createElement("div");
|
||||||
|
titleEl.className = "title";
|
||||||
|
el.appendChild(titleEl);
|
||||||
|
|
||||||
|
const userNameEl = elWithTextContents("span", msg.userID.name);
|
||||||
|
titleEl.appendChild(userNameEl);
|
||||||
|
|
||||||
|
const secondaryTitleEl = document.createElement("div");
|
||||||
|
secondaryTitleEl.className = "secondaryTitle";
|
||||||
|
el.appendChild(secondaryTitleEl);
|
||||||
|
|
||||||
|
const dt = new Date(msg.createdAt*1000);
|
||||||
|
const dtStr
|
||||||
|
= `${dt.getFullYear()}-${dt.getMonth()+1}-${dt.getDate()}`
|
||||||
|
+ ` ${dt.getHours()}:${dt.getMinutes()}:${dt.getSeconds()}`;
|
||||||
|
|
||||||
|
const userIDEl = elWithTextContents("span", `userID:${msg.userID.id} @ ${dtStr}`);
|
||||||
|
secondaryTitleEl.appendChild(userIDEl);
|
||||||
|
|
||||||
|
const bodyEl = document.createElement("p");
|
||||||
|
|
||||||
|
const bodyParts = msg.body.split("\n");
|
||||||
|
for (const i in bodyParts) {
|
||||||
|
if (i > 0) bodyEl.appendChild(document.createElement("br"));
|
||||||
|
bodyEl.appendChild(document.createTextNode(bodyParts[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
el.appendChild(bodyEl);
|
||||||
|
|
||||||
|
messagesEl.appendChild(el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
|
||||||
|
const failEl = document.getElementById("fail");
|
||||||
|
|
||||||
|
setErr = (msg) => failEl.innerHTML = `${msg} (please refresh the page to retry)`;
|
||||||
|
|
||||||
|
const api = await import("/assets/api.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const history = await api.call("/api/chat/global/history");
|
||||||
|
renderMessages(history.messages);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
e = `Failed to fetch message history: ${e}`
|
||||||
|
setErr(e);
|
||||||
|
console.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//const ws = await api.ws("/api/chat/global/listen");
|
||||||
|
|
||||||
|
//while (true) {
|
||||||
|
// const msg = await ws.next();
|
||||||
|
// console.log("got msg", msg);
|
||||||
|
//}
|
||||||
|
|
||||||
|
})()
|
||||||
|
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user