jfyne / live Goto Github PK
View Code? Open in Web Editor NEWLive views and components for golang
Home Page: https://discord.gg/TuMNaXJMUG
License: MIT License
Live views and components for golang
Home Page: https://discord.gg/TuMNaXJMUG
License: MIT License
When more than one goroutine is calling socket.Self
for the same socket, the page might not be rendered correctly.
You can observe that in this sample: https://github.com/philippseith/gorx/blob/b1370a626b3b5018d700e55979a4cede273ace6a/ext/rxlive_test/rxlive_test.go#L18
I can see that baseEngine.handleSelf
is synchronized, but not the rendering part in handleEmittedEvent.
When I synchronize the call to socket.Self
(see here: https://github.com/philippseith/gorx/blob/957e88ddca118650351122095f80f681c5ea920f/ext/rxlive/viewmodel.go#L40), the page renders correctly.
IMHO all the steps executed in handleEmittedEvent
for the socket should be synchronized, but just putting a mutex into this method would sync over all sockets, not only this one. Should there be a mutex in baseSocket.Self
?
When navigating on a https page, there's a mixed content problem when trying to connect to websocket
Line 19 in a92e46c
Nice library so far! Good work, I really enjoy using this on my new application. Thanks for the work.
Is there a reason, why "PatchURL" and its counterpart "HandleParams" only support query parameters and not the full path?
In my use-case I would like to handle the overall navigation with live, so the user clicks an a
-Tag (e.g. user is on /list
and clicks /detail/lorem-ipsum
), which triggers popstate and the server pushes the changed content without a full page reload and the user stays connected with the websocket.
It makes no sense to handle different complex pages with this single handler, but for mostly similar pages with the same events allover this could save some bandwith for the user.
Hi,
I'v created a pull request some time ago for fixing integration issues with recent Fiber framework. Its in contrib repo see here https://github.com/jfyne/live-contrib/pulls . Can anyone take a look at it, please? If there is any issue with it, I can update this pull request, if needed.
Btw: I still must say, it's an amazing project! Needs more publicity.
thank you,
David
Hi,
is there a way how to use broadcasting events on app global level? I mean, in app we are using live.Socket.Broadcast for sending information to all connected users. This is related to live.Socket from particular user, so its user triggered event actually. What if we would like to broadcast general events like "current time" to all connected users. When Live app starts, we should start broadcasting events. Is there way how to do it right now? Hopefully it's clear ...
David
How can I read URL query string value in handleMount func? How can I access http.Request in this context?
And thanks for this great project!!
Server logs:
2021/07/22 09:02:40 ws closed with status (-1): writing to socket error: failed writeTimeout: json: error calling MarshalJSON for type json.RawMessage: invalid character 'i' in literal false (expecting 'l')
It appears to be trying to render a string starting with the word "fail" as if it were raw, unquoted JSON.
I'm not sure of the intention around the handling of internalErrors, so I'm not submitting a PR, but something like this silences the error, by turning the error into a JSON string:
diff --git a/handler.go b/handler.go
index f0f80cb..f719909 100644
--- a/handler.go
+++ b/handler.go
@@ -354,14 +354,18 @@ func (h *Handler) _serveWS(r *http.Request, session Session, c *websocket.Conn)
if err := writeTimeout(ctx, time.Second*5, c, Event{T: EventError, Data: d}); err != nil {
return fmt.Errorf("writing to socket error: %w", err)
}
- case err := <-internalErrors:
- if err != nil {
- if err := writeTimeout(ctx, time.Second*5, c, Event{T: EventError, Data: []byte(err.Error())}); err != nil {
+ case ie := <-internalErrors:
+ if ie != nil {
+ d, err := json.Marshal(ie.Error())
+ if err != nil {
+ return fmt.Errorf("writing to socket error: %w", err)
+ }
+ if err := writeTimeout(ctx, time.Second*5, c, Event{T: EventError, Data: d}); err != nil {
return fmt.Errorf("writing to socket error: %w", err)
}
}
// Something catastrophic has happened.
- return fmt.Errorf("read error: %w", err)
+ return fmt.Errorf("internal error: %w", ie)
case <-ctx.Done():
return nil
}
I like your simple approach . It’s very clean .
Also your using a web socket lib that works on everything including when I compile to wasm
so I don’t know if you are into the idea but you can use gioui to render on the client
then your sending the data change event to the client only . A CUD of Created, Updated or Deleted .
There are plenty examples in gioui
if your curious I can point you to some
It’s also crazy fast and will work on web, desktop and mobile
mit can also work inside a webview .
I'll do that today! It's early here so gonna get a coffee first. Also as an aside, thanks for this repo! I started using Phoenix live view and instantly started thinking about how great it would be to do that in golang, and here it is!
Originally posted by @AnaelBerrouet in #25 (comment)
Was playing around with this project and wanted to be able to do file uploads. My form submits the event, but I'm noticing that the event is missing all the data.
{"t":"upload","i":1,"d":{"myFile":{}}}
I just skimmed the project, but from what I can tell, you'd need to slice the file into smaller chunks to send over WebSocket stream, then reassemble the file on the server side. It'd probably require reworking LiveEvent
and Socket.send
to support this lower level operation?
Maybe I'm missing something and this already works, if so it'd be a good example to have.
I've seen an invalid render on firefox and had a minimal way to reproduce the issue with a minimal example. It seems like an issue with the DOM patching?
This is using v0.12.1
Click the Inside
button first.
package main
import (
"bytes"
"context"
"io"
"log"
"net/http"
"sync"
"text/template"
"github.com/jfyne/live"
)
type State struct {
Inside int
Outside int
}
func main() {
var mu sync.Mutex
var inside, outside int
h, err := live.NewHandler(live.NewCookieStore("session-name", []byte("weak-secret")))
if err != nil {
log.Fatal("could not create handler")
}
h.Render = func(ctx context.Context, data interface{}) (io.Reader, error) {
tmpl, err := template.New("thermo").Parse(`
Hello<br/>
Outside is: {{.Outside}}
<button live-click="inside">Inside</button>
<button live-click="outside">Outside</button>
<button live-click="nothing">Nothing</button>
<details>
<summary>Advanced</summary>
Inside is: {{.Inside}}
<button live-click="inside">Inside</button>
<button live-click="outside">Outside</button>
<button live-click="nothing">Nothing</button>
</details>
<script src="/live.js"></script>
`)
if err != nil {
return nil, err
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
return nil, err
}
return &buf, nil
}
h.HandleEvent("inside", func(context.Context, *live.Socket, live.Params) (interface{}, error) {
mu.Lock()
defer mu.Unlock()
inside += 1
return &State{inside, outside}, nil
})
h.HandleEvent("outside", func(context.Context, *live.Socket, live.Params) (interface{}, error) {
mu.Lock()
defer mu.Unlock()
outside += 1
return &State{inside, outside}, nil
})
h.HandleEvent("nothing", func(context.Context, *live.Socket, live.Params) (interface{}, error) {
mu.Lock()
defer mu.Unlock()
return &State{inside, outside}, nil
})
http.Handle("/", h)
http.Handle("/live.js", live.Javascript{})
http.ListenAndServe(":8080", nil)
}
build.sh was not running the correct typescript compiler
fix is:
cd web && npm install typescript
go generate web/build.go
go generate internal/embed/embed.go
embedmd -w README.md
Firefox often takes a long time to connect to the web socket.
I'm exploring the examples in the project and noticed that in the examples/form, when typing less than 10 characters we get the proper validation error message. But once we cross the length of 10 character and the error div clears, so does the current value in the input text field. I've been trying to figure out the way to preserve this value in Live and can't tell if its something straightforward on the client page side, or something specific to the Live dom updates not preserving the text input value. I've also tried to add a form struct field to capture the LastMessage
and propagate it back to the input value, but that has the unwanted effect of the cursor position staying at the 0 index as you type, and get backwards values.
Is it possible to update the examples/form to show how to properly preserve the input value when the validation logic swaps away the error div?
I'm testing this in latest Chrome on Ubuntu and Go 1.15.5
This is using v0.12.1 on Firefox.
The details
element will always revert to being closed whenever the DOM is patched. Try by opening the 'Advanced' details section, then pressing the Inside
or Outside
buttons. I added the Nothing
button to show that it is only when things are modified (and not just the triggering of a live event).
Here is the source code used:
package main
import (
"bytes"
"context"
"io"
"log"
"net/http"
"sync"
"text/template"
"github.com/jfyne/live"
)
type State struct {
Inside int
Outside int
}
func main() {
var mu sync.Mutex
var inside, outside int
h, err := live.NewHandler(live.NewCookieStore("session-name", []byte("weak-secret")))
if err != nil {
log.Fatal("could not create handler")
}
h.Render = func(ctx context.Context, data interface{}) (io.Reader, error) {
tmpl, err := template.New("thermo").Parse(`
Hello<br/>
Outside is: {{.Outside}}
<button live-click="inside">Inside</button>
<button live-click="outside">Outside</button>
<button live-click="nothing">Nothing</button>
<details>
<summary>Advanced</summary>
Inside is: {{.Inside}}
<button live-click="inside">Inside</button>
<button live-click="outside">Outside</button>
<button live-click="nothing">Nothing</button>
</details>
<script src="/live.js"></script>
`)
if err != nil {
return nil, err
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
return nil, err
}
return &buf, nil
}
h.HandleEvent("inside", func(context.Context, *live.Socket, live.Params) (interface{}, error) {
mu.Lock()
defer mu.Unlock()
inside += 1
return &State{inside, outside}, nil
})
h.HandleEvent("outside", func(context.Context, *live.Socket, live.Params) (interface{}, error) {
mu.Lock()
defer mu.Unlock()
outside += 1
return &State{inside, outside}, nil
})
h.HandleEvent("nothing", func(context.Context, *live.Socket, live.Params) (interface{}, error) {
mu.Lock()
defer mu.Unlock()
return &State{inside, outside}, nil
})
http.Handle("/", h)
http.Handle("/live.js", live.Javascript{})
http.ListenAndServe(":8080", nil)
}
go version go1.16.5 darwin/amd64
macOS 10.14.6
chrome 91.0.4472.164
firefox 90.0.1
git clone [email protected]:jfyne/live-examples.git
cd live-examples/clock
go build .
cd ..
clock/clock
Point browser at http://localhost:8080/clock
Incrementing clock
With Firefox, everything is OK.
With Chrome, current time is displayed, but does not increment.
tcpdump shows that websocket messages are being sent every second:
$ sudo tcpdump -i lo0 -nn -s0 -A tcp port 8080
...
08:47:28.831040 IP6 ::1.8080 > ::1.50406: Flags [P.], seq 1:269, ack 810, win 6359, options [nop,nop,TS val 1005537220 ecr 1005537219], length 268: HTTP: HTTP/1.1 101 Switching Protocols
`....,[email protected].#...v.....4.....
;.G.;.G.HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Sec-Websocket-Accept: J1GHBBuREgeNFGWhZQUIYbi8YQ4=
Sec-Websocket-Extensions: permessage-deflate; client_no_context_takeover; server_no_context_takeover
Upgrade: websocket
Date: Thu, 22 Jul 2021 07:47:28 GMT
08:47:28.831077 IP6 ::1.50406 > ::1.8080: Flags [.], ack 269, win 6367, options [nop,nop,TS val 1005537220 ecr 1005537220], length 0
`..f. [email protected] /.....(.....
;.G.;.G.
08:47:28.831177 IP6 ::1.8080 > ::1.50406: Flags [P.], seq 269:288, ack 810, win 6359, options [nop,nop,TS val 1005537220 ecr 1005537220], length 19: HTTP
`[email protected] /...v.....;.....
;.G.;.G...{"t":"connect"}..
08:47:28.831197 IP6 ::1.50406 > ::1.8080: Flags [.], ack 288, win 6367, options [nop,nop,TS val 1005537220 ecr 1005537220], length 0
`..f. [email protected] B.....(.....
;.G.;.G.
08:47:29.834266 IP6 ::1.8080 > ::1.50406: Flags [P.], seq 288:471, ack 810, win 6359, options [nop,nop,TS val 1005538221 ecr 1005537220], length 183: HTTP
`[email protected] B...v...........
;.K.;.G..~..{"t":"patch","d":[{"Path":[0,0],"Action":2,"HTML":"\u003ctitle\u003e 08:47:29 \u003c/title\u003e"},{"Path":[1,0],"Action":2,"HTML":"\u003ctime\u003e08:47:29\u003c/time\u003e"}]}..
08:47:29.834302 IP6 ::1.50406 > ::1.8080: Flags [.], ack 471, win 6364, options [nop,nop,TS val 1005538221 ecr 1005538221], length 0
`..f. [email protected] ......(.....
;.K.;.K.
08:47:30.839156 IP6 ::1.8080 > ::1.50406: Flags [P.], seq 471:654, ack 810, win 6359, options [nop,nop,TS val 1005539224 ecr 1005538221], length 183: HTTP
`[email protected] ....v...........
;.O.;.K..~..{"t":"patch","d":[{"Path":[0,0],"Action":2,"HTML":"\u003ctitle\u003e 08:47:30 \u003c/title\u003e"},{"Path":[1,0],"Action":2,"HTML":"\u003ctime\u003e08:47:30\u003c/time\u003e"}]}..
08:47:30.839203 IP6 ::1.50406 > ::1.8080: Flags [.], ack 654, win 6361, options [nop,nop,TS val 1005539224 ecr 1005539224], length 0
`..f. [email protected]!......(.....
;.O.;.O.
08:47:31.842699 IP6 ::1.8080 > ::1.50406: Flags [P.], seq 654:837, ack 810, win 6359, options [nop,nop,TS val 1005540226 ecr 1005539224], length 183: HTTP
`[email protected]!....v...........
;.S.;.O..~..{"t":"patch","d":[{"Path":[0,0],"Action":2,"HTML":"\u003ctitle\u003e 08:47:31 \u003c/title\u003e"},{"Path":[1,0],"Action":2,"HTML":"\u003ctime\u003e08:47:31\u003c/time\u003e"}]}..
08:47:31.842764 IP6 ::1.50406 > ::1.8080: Flags [.], ack 837, win 6358, options [nop,nop,TS val 1005540226 ecr 1005540226], length 0
`..f. [email protected]"g.....(.....
;.S.;.S.
I don't see any errors in browser console, although network shows stuck at "101 Switching Proto..."
With both Chrome and Firefox, closing the browser tab logs the following at the server side:
2021/07/22 08:47:01 ws closed with status (-1): writing to socket error: failed writeTimeout: json: error calling MarshalJSON for type json.RawMessage: invalid character 'i' in literal false (expecting 'l')
I suspect an error message starting with the word "fail" has been put in RawMessage field without being quoted, e.g. see https://play.golang.org/p/w9Vuc8Ko_fN
EDIT: now raised separately as #26
Could there be any chatgroup for this project?
For example a discord server.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.