From 63919cf685a98c5ddd851058bfdeb1bcb7debb96 Mon Sep 17 00:00:00 2001 From: reis <1l0@users.noreply.github.com> Date: Thu, 2 Jan 2025 21:42:04 +0900 Subject: [PATCH] support wasm (#163) --- README.md | 8 +++ connection.go | 6 ++ connection_js.go | 55 ++++++++++++++++++ go.mod | 1 + go.sum | 2 + nip13/nip13_test.go | 4 +- nip96/nip96_test.go | 2 + relay.go | 4 +- relay_js_test.go | 84 ++++++++++++++++++++++++++++ relay_test.go | 2 + sdk/hints/test/libsql_test.go | 2 +- sdk/hints/test/moderncsqlite_test.go | 2 + sdk/hints/test/wasmsqlite_test.go | 2 +- 13 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 connection_js.go create mode 100644 relay_js_test.go diff --git a/README.md b/README.md index 474c2cf..d81eb60 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,14 @@ But to use it you need the host to have it installed as a shared library and CGO To use it, use `-tags=libsecp256k1` whenever you're compiling your program that uses this library. +### Test for Wasm + +Install [wasmbrowsertest](https://github.com/agnivade/wasmbrowsertest), then run tests: + +```sh +TEST_RELAY_URL= GOOS=js GOARCH=wasm go test -short ./... +``` + ## Warning: risk of goroutine bloat (if used incorrectly) Remember to cancel subscriptions, either by calling `.Unsub()` on them or ensuring their `context.Context` will be canceled at some point. diff --git a/connection.go b/connection.go index da1161d..0d08c7d 100644 --- a/connection.go +++ b/connection.go @@ -1,3 +1,5 @@ +//go:build !js + package nostr import ( @@ -179,3 +181,7 @@ func (c *Connection) ReadMessage(ctx context.Context, buf io.Writer) error { func (c *Connection) Close() error { return c.conn.Close() } + +func (c *Connection) Ping(ctx context.Context) error { + return wsutil.WriteClientMessage(c.conn, ws.OpPing, nil) +} diff --git a/connection_js.go b/connection_js.go new file mode 100644 index 0000000..ddcfee0 --- /dev/null +++ b/connection_js.go @@ -0,0 +1,55 @@ +//go:build js + +package nostr + +import ( + "context" + "crypto/tls" + "fmt" + "io" + "net/http" + + ws "github.com/coder/websocket" +) + +type Connection struct { + conn *ws.Conn +} + +func NewConnection(ctx context.Context, url string, requestHeader http.Header, tlsConfig *tls.Config) (*Connection, error) { + c, _, err := ws.Dial(ctx, url, nil) + if err != nil { + return nil, err + } + + return &Connection{ + conn: c, + }, nil +} + +func (c *Connection) WriteMessage(ctx context.Context, data []byte) error { + if err := c.conn.Write(ctx, ws.MessageBinary, data); err != nil { + return fmt.Errorf("failed to write message: %w", err) + } + + return nil +} + +func (c *Connection) ReadMessage(ctx context.Context, buf io.Writer) error { + _, reader, err := c.conn.Reader(ctx) + if err != nil { + return fmt.Errorf("failed to get reader: %w", err) + } + if _, err := io.Copy(buf, reader); err != nil { + return fmt.Errorf("failed to read message: %w", err) + } + return nil +} + +func (c *Connection) Close() error { + return c.conn.Close(ws.StatusNormalClosure, "") +} + +func (c *Connection) Ping(ctx context.Context) error { + return c.conn.Ping(ctx) +} diff --git a/go.mod b/go.mod index 284cf76..f6e762f 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/bluekeyes/go-gitdiff v0.7.1 github.com/btcsuite/btcd/btcec/v2 v2.3.4 github.com/btcsuite/btcd/btcutil v1.1.3 + github.com/coder/websocket v1.8.12 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/dgraph-io/ristretto v1.0.0 github.com/fiatjaf/eventstore v0.9.0 diff --git a/go.sum b/go.sum index 0fd5981..e8fd669 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e h1:0XBUw73chJ1VYSsfvcPvVT7auykAJce9FpRr10L6Qhw= github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:P13beTBKr5Q18lJe1rIoLUqjM+CB1zYrRg44ZqGuQSA= +github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= +github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/nip13/nip13_test.go b/nip13/nip13_test.go index 9dedb2a..9cac8be 100644 --- a/nip13/nip13_test.go +++ b/nip13/nip13_test.go @@ -59,11 +59,11 @@ func TestDoWorkShort(t *testing.T) { Content: "It's just me mining my own business", PubKey: "a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243", } - pow, err := DoWork(context.Background(), event, 0) + pow, err := DoWork(context.Background(), event, 2) if err != nil { t.Fatal(err) } - testNonceTag(t, pow, 0) + testNonceTag(t, pow, 2) } func TestDoWorkLong(t *testing.T) { diff --git a/nip96/nip96_test.go b/nip96/nip96_test.go index dc95e0e..3ddf82e 100644 --- a/nip96/nip96_test.go +++ b/nip96/nip96_test.go @@ -1,3 +1,5 @@ +//go:build !js + package nip96 import ( diff --git a/relay.go b/relay.go index 2ef239f..795b4ab 100644 --- a/relay.go +++ b/relay.go @@ -12,8 +12,6 @@ import ( "sync/atomic" "time" - "github.com/gobwas/ws" - "github.com/gobwas/ws/wsutil" "github.com/puzpuzpuz/xsync/v3" ) @@ -183,7 +181,7 @@ func (r *Relay) ConnectWithTLS(ctx context.Context, tlsConfig *tls.Config) error select { case <-ticker.C: if r.Connection != nil { - err := wsutil.WriteClientMessage(r.Connection.conn, ws.OpPing, nil) + err := r.Connection.Ping(ctx) if err != nil { InfoLogger.Printf("{%s} error writing ping: %v; closing websocket", r.URL, err) r.Close() // this should trigger a context cancelation diff --git a/relay_js_test.go b/relay_js_test.go new file mode 100644 index 0000000..f087917 --- /dev/null +++ b/relay_js_test.go @@ -0,0 +1,84 @@ +//go:build js + +package nostr + +import ( + "context" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestConnectContext(t *testing.T) { + url := os.Getenv("TEST_RELAY_URL") + if url == "" { + t.Fatal("please set the environment: $TEST_RELAY_URL") + } + + // relay client + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + r, err := RelayConnect(ctx, url) + assert.NoError(t, err) + + defer r.Close() +} + +func TestConnectContextCanceled(t *testing.T) { + url := os.Getenv("TEST_RELAY_URL") + if url == "" { + t.Fatal("please set the environment: $TEST_RELAY_URL") + } + + // relay client + ctx, cancel := context.WithCancel(context.Background()) + cancel() // make ctx expired + _, err := RelayConnect(ctx, url) + assert.ErrorIs(t, err, context.Canceled) +} + +func TestPublish(t *testing.T) { + url := os.Getenv("TEST_RELAY_URL") + if url == "" { + t.Fatal("please set the environment: $TEST_RELAY_URL") + } + + // test note to be sent over websocket + priv, pub := makeKeyPair(t) + textNote := Event{ + Kind: KindTextNote, + Content: "hello", + CreatedAt: Timestamp(1672068534), // random fixed timestamp + Tags: Tags{[]string{"foo", "bar"}}, + PubKey: pub, + } + err := textNote.Sign(priv) + assert.NoError(t, err) + + // connect a client and send the text note + rl := mustRelayConnect(t, url) + err = rl.Publish(context.Background(), textNote) + assert.NoError(t, err) +} + +func makeKeyPair(t *testing.T) (priv, pub string) { + t.Helper() + + privkey := GeneratePrivateKey() + pubkey, err := GetPublicKey(privkey) + assert.NoError(t, err) + + return privkey, pubkey +} + +func mustRelayConnect(t *testing.T, url string) *Relay { + t.Helper() + + rl, err := RelayConnect(context.Background(), url) + require.NoError(t, err) + + return rl +} diff --git a/relay_test.go b/relay_test.go index 97f5bc1..7dc7187 100644 --- a/relay_test.go +++ b/relay_test.go @@ -1,3 +1,5 @@ +//go:build !js + package nostr import ( diff --git a/sdk/hints/test/libsql_test.go b/sdk/hints/test/libsql_test.go index 18bb8d0..a221652 100644 --- a/sdk/hints/test/libsql_test.go +++ b/sdk/hints/test/libsql_test.go @@ -1,4 +1,4 @@ -//go:build !sqlite_math_functions +//go:build !js && !sqlite_math_functions package test diff --git a/sdk/hints/test/moderncsqlite_test.go b/sdk/hints/test/moderncsqlite_test.go index fe5e0d0..6f32dfc 100644 --- a/sdk/hints/test/moderncsqlite_test.go +++ b/sdk/hints/test/moderncsqlite_test.go @@ -1,3 +1,5 @@ +//go:build !js + package test import ( diff --git a/sdk/hints/test/wasmsqlite_test.go b/sdk/hints/test/wasmsqlite_test.go index 2dba133..c4dbba6 100644 --- a/sdk/hints/test/wasmsqlite_test.go +++ b/sdk/hints/test/wasmsqlite_test.go @@ -1,4 +1,4 @@ -//go:build !sqlite_math_functions +//go:build !js && !sqlite_math_functions package test