Compare commits

...

5 commits

4 changed files with 126 additions and 12 deletions

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
tests/index.html -linguist-detectable
tests/index.html linguist-vendored

View file

@ -1,7 +1,8 @@
# jsStreams
Go library to communicate with the JS Stream API by bridging the JS ReadableStream object to a Go io.ReaderCloser.
Go library to communicate with the JS Stream API by bridging the JS ReadableStream and WritableStream objects to a Go io.ReaderCloser and io.WriterCloser.
It also works vice versa, and with pipe readers/writers.
[![Go Report Card](https://goreportcard.com/badge/git.ailur.dev/ailur/jsStreams)](https://goreportcard.com/report/git.ailur.dev/ailur/jsStreams) [![Go Reference](https://pkg.go.dev/badge/git.ailur.dev/ailur/jsStreams.svg)](https://pkg.go.dev/git.ailur.dev/ailur/jsStreams)
The API is pretty self-explanatory - it provides a function to create an io.ReaderCloser from a JS ReadableStream object.
The API is pretty self-explanatory, see the Go Reference badge above for the full documentation.

67
main.go
View file

@ -39,11 +39,12 @@ func (r *ReadableStream) Read(p []byte) (n int, err error) {
readResult.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
defer waitGroup.Done()
if args[0].Get("done").Bool() || args[0].Get("value").Length() == 0 {
err = io.EOF
return nil
}
data := args[0].Get("value")
js.CopyBytesToGo(p, data)
if args[0].Get("done").Bool() {
err = io.EOF
}
n = data.Length()
return nil
}))
@ -66,8 +67,10 @@ func (r *ReadableStream) Close() (err error) {
defer func() {
// We don't want any errors to be thrown if the stream is already closed.
recovery := recover()
if !strings.Contains(recovery.(string), "Can not close stream after closing or error") {
err = fmt.Errorf("panic: %v", recovery)
if !strings.Contains(fmt.Sprint(recovery), "Can not close stream after closing or error") {
if recovery != nil {
err = fmt.Errorf("panic: %v", recovery)
}
}
}()
@ -140,8 +143,10 @@ func (w *WritableStream) Close() (err error) {
defer func() {
// We don't want any errors to be thrown if the stream is already closed.
recovery := recover()
if !strings.Contains(recovery.(string), "Can not close stream after closing or error") {
err = fmt.Errorf("panic: %v", recovery)
if !strings.Contains(fmt.Sprint(recovery), "Can not close stream after closing or error") {
if recovery != nil {
err = fmt.Errorf("panic: %v", recovery)
}
}
}()
@ -162,3 +167,51 @@ func NewWritableStream(stream ...js.Value) *WritableStream {
return &WritableStream{stream: stream}
}
}
// Now we do the vice versa: Reader to ReadableStream and Writer to WritableStream.
// ReaderToReadableStream converts an io.Reader to a JavaScript ReadableStream.
func ReaderToReadableStream(r io.Reader) js.Value {
return js.Global().Get("ReadableStream").New(map[string]interface{}{
"pull": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
readController := args[0]
return js.Global().Get("Promise").New(js.FuncOf(func(this js.Value, args []js.Value) interface{} {
var buffer []byte
buffer, err := io.ReadAll(r)
if err != nil {
panic(err.Error())
}
if len(buffer) == 0 {
readController.Call("close")
return nil
}
jsBuffer := js.Global().Get("Uint8Array").New(len(buffer))
js.CopyBytesToJS(jsBuffer, buffer)
readController.Call("enqueue", jsBuffer)
readController.Call("close")
args[0].Invoke()
return nil
}))
}),
"type": "bytes",
})
}
// WriterToWritableStream converts an io.Writer to a JavaScript WritableStream.
func WriterToWritableStream(w io.Writer) js.Value {
return js.Global().Get("WritableStream").New(map[string]interface{}{
"write": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
writeBuffer := args[0]
return js.Global().Get("Promise").New(js.FuncOf(func(this js.Value, args []js.Value) interface{} {
buffer := make([]byte, writeBuffer.Length())
js.CopyBytesToGo(buffer, writeBuffer)
_, err := w.Write(buffer)
if err != nil {
panic(err.Error())
}
args[0].Invoke()
return nil
}))
}),
})
}

View file

@ -1,11 +1,9 @@
package main
import (
"git.ailur.dev/ailur/jsStreams"
"fmt"
"git.ailur.dev/ailur/jsStreams"
"io"
"syscall/js"
)
@ -23,6 +21,10 @@ func main() {
return
}
fmt.Println(string(buffer))
err = readStream.Close()
if err != nil {
fmt.Println(err)
}
}()
return nil
@ -36,6 +38,62 @@ func main() {
fmt.Println(err)
return
}
err = writeStream.Close()
if err != nil {
fmt.Println(err)
}
}()
return nil
}))
js.Global().Set("TryWriterConversions", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
go func() {
reader, writer := io.Pipe()
go func() {
writeStream := jsStreams.WriterToWritableStream(writer)
buffer := js.Global().Get("Uint8Array").New(45)
js.CopyBytesToJS(buffer, []byte("Hi, I've been piped through a WritableStream!"))
writeStream.Call("getWriter").Call("write", buffer).Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
go func() {
err := writer.Close()
if err != nil {
fmt.Println(err)
}
}()
return nil
}))
}()
go func() {
fmt.Println("Reading stream...")
m, _ := io.ReadAll(reader)
fmt.Println(string(m))
}()
}()
return nil
}))
js.Global().Set("TryReaderConversions", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
go func() {
reader, writer := io.Pipe()
go func() {
_, err := writer.Write([]byte("Hi, I've been piped through a ReadableStream!"))
if err != nil {
fmt.Println(err)
return
}
err = writer.Close()
if err != nil {
fmt.Println(err)
return
}
}()
go func() {
fmt.Println("Reading stream...")
m, _ := io.ReadAll(jsStreams.NewReadableStream(jsStreams.ReaderToReadableStream(reader)))
fmt.Println(string(m))
}()
}()
return nil