diff --git a/.gitignore b/.gitignore index c950518..a23e4b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ js-sdk/node_modules js-sdk/dist -internal/api/dist +internal/api/js/dist __pycache__ \ No newline at end of file diff --git a/FAQ.md b/FAQ.md index c47bd5b..042c0cf 100644 --- a/FAQ.md +++ b/FAQ.md @@ -42,7 +42,7 @@ Internally, we use Vite to bundle JS SDK into a single page app, which has no UI #### Add console logs -If you want to add console logging to the JS SDK, it is easiest to _modify the bundled output_ as it is not minified. To do this, `grep` for function names in `internal/api/dist/assests/index.....js` then use an editor to add `console.log` lines. These lines will appear in JS SDK log files. +If you want to add console logging to the JS SDK, it is easiest to _modify the bundled output_ as it is not minified. To do this, `grep` for function names in `internal/api/js/dist/assests/index.....js` then use an editor to add `console.log` lines. These lines will appear in JS SDK log files. ### Rust SDK FFI diff --git a/internal/api/client.go b/internal/api/client.go index 59fac31..d45f49c 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -183,15 +183,15 @@ func CheckEventHasMembership(target, membership string) func(e Event) bool { const ansiRedForeground = "\x1b[31m" const ansiResetForeground = "\x1b[39m" -// errorf is a wrapper around t.Errorf which prints the failing error message in red. -func errorf(t *testing.T, format string, args ...any) { +// Errorf is a wrapper around t.Errorf which prints the failing error message in red. +func Errorf(t *testing.T, format string, args ...any) { t.Helper() format = ansiRedForeground + format + ansiResetForeground t.Errorf(format, args...) } -// fatalf is a wrapper around t.Fatalf which prints the failing error message in red. -func fatalf(t *testing.T, format string, args ...any) { +// Fatalf is a wrapper around t.Fatalf which prints the failing error message in red. +func Fatalf(t *testing.T, format string, args ...any) { t.Helper() format = ansiRedForeground + format + ansiResetForeground t.Fatalf(format, args...) diff --git a/internal/api/js.go b/internal/api/js/js.go similarity index 91% rename from internal/api/js.go rename to internal/api/js/js.go index aeed210..91c2cd7 100644 --- a/internal/api/js.go +++ b/internal/api/js/js.go @@ -1,4 +1,4 @@ -package api +package js import ( "context" @@ -18,6 +18,7 @@ import ( "github.com/chromedp/cdproto/runtime" "github.com/chromedp/chromedp" + "github.com/matrix-org/complement-crypto/internal/api" "github.com/matrix-org/complement-crypto/internal/chrome" "github.com/matrix-org/complement/must" "github.com/tidwall/gjson" @@ -55,19 +56,19 @@ type JSClient struct { ctx context.Context cancel func() baseJSURL string - listeners map[int32]func(roomID string, ev Event) + listeners map[int32]func(roomID string, ev api.Event) listenerID atomic.Int32 userID string } -func NewJSClient(t *testing.T, opts ClientCreationOpts) (Client, error) { +func NewJSClient(t *testing.T, opts api.ClientCreationOpts) (api.Client, error) { // start a headless chrome ctx, cancel := chromedp.NewContext(context.Background(), chromedp.WithBrowserOption( chromedp.WithBrowserLogf(colorifyError), chromedp.WithBrowserErrorf(colorifyError), //chromedp.WithBrowserDebugf(log.Printf), )) jsc := &JSClient{ - listeners: make(map[int32]func(roomID string, ev Event)), + listeners: make(map[int32]func(roomID string, ev api.Event)), userID: opts.UserID, } // Listen for console logs for debugging AND to communicate live updates @@ -200,7 +201,7 @@ func NewJSClient(t *testing.T, opts ClientCreationOpts) (Client, error) { jsc.ctx = ctx jsc.cancel = cancel jsc.baseJSURL = baseJSURL - return &LoggedClient{Client: jsc}, nil + return &api.LoggedClient{Client: jsc}, nil } // Close is called to clean up resources. @@ -209,14 +210,14 @@ func NewJSClient(t *testing.T, opts ClientCreationOpts) (Client, error) { // log messages. func (c *JSClient) Close(t *testing.T) { c.cancel() - c.listeners = make(map[int32]func(roomID string, ev Event)) + c.listeners = make(map[int32]func(roomID string, ev api.Event)) } func (c *JSClient) UserID() string { return c.userID } -func (c *JSClient) MustGetEvent(t *testing.T, roomID, eventID string) Event { +func (c *JSClient) MustGetEvent(t *testing.T, roomID, eventID string) api.Event { t.Helper() // serialised output (if encrypted): // { @@ -231,7 +232,7 @@ func (c *JSClient) MustGetEvent(t *testing.T, roomID, eventID string) Event { })[0].toJSON()); `, roomID, eventID)) if !gjson.Valid(evSerialised) { - fatalf(t, "MustGetEvent(%s, %s) %s (js): invalid event, got %s", roomID, eventID, c.userID, evSerialised) + api.Fatalf(t, "MustGetEvent(%s, %s) %s (js): invalid event, got %s", roomID, eventID, c.userID, evSerialised) } result := gjson.Parse(evSerialised) decryptedEvent := result.Get("decrypted") @@ -240,7 +241,7 @@ func (c *JSClient) MustGetEvent(t *testing.T, roomID, eventID string) Event { } encryptedEvent := result.Get("encrypted") //fmt.Printf("DECRYPTED: %s\nENCRYPTED: %s\n\n", decryptedEvent.Raw, encryptedEvent.Raw) - ev := Event{ + ev := api.Event{ ID: decryptedEvent.Get("event_id").Str, Text: decryptedEvent.Get("content.body").Str, Sender: decryptedEvent.Get("sender").Str, @@ -271,7 +272,7 @@ func (c *JSClient) StartSyncing(t *testing.T) (stopSyncing func()) { }; window.__client.on("sync", fn);`, CONSOLE_LOG_CONTROL_STRING)) ch := make(chan struct{}) - cancel := c.listenForUpdates(func(roomID string, ev Event) { + cancel := c.listenForUpdates(func(roomID string, ev api.Event) { if roomID != "sync" { return } @@ -280,7 +281,7 @@ func (c *JSClient) StartSyncing(t *testing.T) (stopSyncing func()) { chrome.AwaitExecute(t, c.ctx, `window.__client.startClient({});`) select { case <-time.After(5 * time.Second): - fatalf(t, "[%s](js) took >5s to StartSyncing", c.userID) + api.Fatalf(t, "[%s](js) took >5s to StartSyncing", c.userID) case <-ch: } cancel() @@ -349,7 +350,7 @@ func (c *JSClient) MustBackupKeys(t *testing.T) (recoveryKey string) { return recoveryKey.encodedPrivateKey; })()`) if err != nil { - fatalf(t, "MustBackupKeys: %s", err) + api.Fatalf(t, "MustBackupKeys: %s", err) } // the backup loop which sends keys will wait between 0-10s before uploading keys... // See https://github.com/matrix-org/matrix-js-sdk/blob/49624d5d7308e772ebee84322886a39d2e866869/src/rust-crypto/backup.ts#L319 @@ -377,7 +378,7 @@ func (c *JSClient) MustLoadBackup(t *testing.T, recoveryKey string) { })()`, recoveryKey)) } -func (c *JSClient) WaitUntilEventInRoom(t *testing.T, roomID string, checker func(e Event) bool) Waiter { +func (c *JSClient) WaitUntilEventInRoom(t *testing.T, roomID string, checker func(e api.Event) bool) api.Waiter { t.Helper() return &jsTimelineWaiter{ roomID: roomID, @@ -393,11 +394,11 @@ func (c *JSClient) Logf(t *testing.T, format string, args ...interface{}) { t.Logf(format, args...) } -func (c *JSClient) Type() ClientTypeLang { - return ClientTypeJS +func (c *JSClient) Type() api.ClientTypeLang { + return api.ClientTypeJS } -func (c *JSClient) listenForUpdates(callback func(roomID string, ev Event)) (cancel func()) { +func (c *JSClient) listenForUpdates(callback func(roomID string, ev api.Event)) (cancel func()) { id := c.listenerID.Add(1) c.listeners[id] = callback return func() { @@ -407,14 +408,14 @@ func (c *JSClient) listenForUpdates(callback func(roomID string, ev Event)) (can type jsTimelineWaiter struct { roomID string - checker func(e Event) bool + checker func(e api.Event) bool client *JSClient } func (w *jsTimelineWaiter) Wait(t *testing.T, s time.Duration) { t.Helper() updates := make(chan bool, 3) - cancel := w.client.listenForUpdates(func(roomID string, ev Event) { + cancel := w.client.listenForUpdates(func(roomID string, ev api.Event) { if w.roomID != roomID { return } @@ -436,17 +437,20 @@ func (w *jsTimelineWaiter) Wait(t *testing.T, s time.Duration) { for { timeLeft := s - time.Since(start) if timeLeft <= 0 { - fatalf(t, "%s (js): Wait[%s]: timed out", w.client.userID, w.roomID) + api.Fatalf(t, "%s (js): Wait[%s]: timed out", w.client.userID, w.roomID) } select { case <-time.After(timeLeft): - fatalf(t, "%s (js): Wait[%s]: timed out", w.client.userID, w.roomID) + api.Fatalf(t, "%s (js): Wait[%s]: timed out", w.client.userID, w.roomID) case <-updates: return } } } +const ansiRedForeground = "\x1b[31m" +const ansiResetForeground = "\x1b[39m" + func colorifyError(format string, args ...any) { format = ansiRedForeground + time.Now().Format(time.RFC3339) + " " + format + ansiResetForeground fmt.Printf(format, args...) @@ -460,8 +464,8 @@ type JSEvent struct { ID string `json:"event_id"` } -func jsToEvent(j JSEvent) Event { - var ev Event +func jsToEvent(j JSEvent) api.Event { + var ev api.Event ev.Sender = j.Sender ev.ID = j.ID switch j.Type { diff --git a/internal/api/rust.go b/internal/api/rust/rust.go similarity index 93% rename from internal/api/rust.go rename to internal/api/rust/rust.go index d18a3aa..d8059e3 100644 --- a/internal/api/rust.go +++ b/internal/api/rust/rust.go @@ -1,4 +1,4 @@ -package api +package rust import ( "fmt" @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/matrix-org/complement-crypto/internal/api" "github.com/matrix-org/complement-crypto/rust/matrix_sdk_ffi" "github.com/matrix-org/complement/must" "golang.org/x/exp/slices" @@ -28,7 +29,7 @@ var zero uint32 type RustRoomInfo struct { stream *matrix_sdk_ffi.TaskHandle room *matrix_sdk_ffi.Room - timeline []*Event + timeline []*api.Event } type RustClient struct { @@ -41,7 +42,7 @@ type RustClient struct { userID string } -func NewRustClient(t *testing.T, opts ClientCreationOpts, ssURL string) (Client, error) { +func NewRustClient(t *testing.T, opts api.ClientCreationOpts, ssURL string) (api.Client, error) { t.Logf("NewRustClient[%s] creating...", opts.UserID) ab := matrix_sdk_ffi.NewClientBuilder().HomeserverUrl(opts.BaseURL).SlidingSyncProxy(&ssURL) client, err := ab.Build() @@ -64,7 +65,7 @@ func NewRustClient(t *testing.T, opts ClientCreationOpts, ssURL string) (Client, roomsMu: &sync.RWMutex{}, } c.Logf(t, "NewRustClient[%s] created client", opts.UserID) - return &LoggedClient{Client: c}, nil + return &api.LoggedClient{Client: c}, nil } func (c *RustClient) Close(t *testing.T) { @@ -80,16 +81,16 @@ func (c *RustClient) Close(t *testing.T) { c.FFIClient.Destroy() } -func (c *RustClient) MustGetEvent(t *testing.T, roomID, eventID string) Event { +func (c *RustClient) MustGetEvent(t *testing.T, roomID, eventID string) api.Event { t.Helper() room := c.findRoom(t, roomID) timelineItem, err := room.Timeline().GetEventTimelineItemByEventId(eventID) if err != nil { - fatalf(t, "MustGetEvent(rust) %s (%s, %s): %s", c.userID, roomID, eventID, err) + api.Fatalf(t, "MustGetEvent(rust) %s (%s, %s): %s", c.userID, roomID, eventID, err) } ev := eventTimelineItemToEvent(timelineItem) if ev == nil { - fatalf(t, "MustGetEvent(rust) %s (%s, %s): found timeline item but failed to convert it to an Event", c.userID, roomID, eventID) + api.Fatalf(t, "MustGetEvent(rust) %s (%s, %s): found timeline item but failed to convert it to an Event", c.userID, roomID, eventID) } return *ev } @@ -113,7 +114,7 @@ func (c *RustClient) StartSyncing(t *testing.T) (stopSyncing func()) { for !isSyncing { select { case <-time.After(5 * time.Second): - fatalf(t, "[%s](rust) timed out after 5s StartSyncing", c.userID) + api.Fatalf(t, "[%s](rust) timed out after 5s StartSyncing", c.userID) case state := <-genericListener.ch: fmt.Println(state) switch state.(type) { @@ -173,7 +174,7 @@ func (c *RustClient) MustLoadBackup(t *testing.T, recoveryKey string) { must.NotError(t, "Recover", c.FFIClient.Encryption().Recover(recoveryKey)) } -func (c *RustClient) WaitUntilEventInRoom(t *testing.T, roomID string, checker func(Event) bool) Waiter { +func (c *RustClient) WaitUntilEventInRoom(t *testing.T, roomID string, checker func(api.Event) bool) api.Waiter { t.Helper() c.ensureListening(t, roomID) return &timelineWaiter{ @@ -183,8 +184,8 @@ func (c *RustClient) WaitUntilEventInRoom(t *testing.T, roomID string, checker f } } -func (c *RustClient) Type() ClientTypeLang { - return ClientTypeRust +func (c *RustClient) Type() api.ClientTypeLang { + return api.ClientTypeRust } // SendMessage sends the given text as an m.room.message with msgtype:m.text into the given @@ -193,7 +194,7 @@ func (c *RustClient) SendMessage(t *testing.T, roomID, text string) (eventID str t.Helper() eventID, err := c.TrySendMessage(t, roomID, text) if err != nil { - fatalf(t, err.Error()) + api.Fatalf(t, err.Error()) } return eventID } @@ -316,7 +317,7 @@ func (c *RustClient) ensureListening(t *testing.T, roomID string) *matrix_sdk_ff // we need a timeline listener before we can send messages result := r.Timeline().AddListener(&timelineListener{fn: func(diff []*matrix_sdk_ffi.TimelineDiff) { timeline := c.rooms[roomID].timeline - var newEvents []*Event + var newEvents []*api.Event c.Logf(t, "[%s]AddTimelineListener[%s] TimelineDiff len=%d", c.userID, roomID, len(diff)) for _, d := range diff { switch d.Change() { @@ -378,7 +379,7 @@ func (c *RustClient) ensureListening(t *testing.T, roomID string) *matrix_sdk_ff c.Logf(t, "TimelineDiff change: %+v", e) } }}) - events := make([]*Event, len(result.Items)) + events := make([]*api.Event, len(result.Items)) for i := range result.Items { events[i] = timelineItemToEvent(result.Items[i]) } @@ -403,7 +404,7 @@ func (c *RustClient) listenForUpdates(callback func(roomID string)) (cancel func type timelineWaiter struct { roomID string - checker func(e Event) bool + checker func(e api.Event) bool client *RustClient } @@ -452,11 +453,11 @@ func (w *timelineWaiter) Wait(t *testing.T, s time.Duration) { for { timeLeft := s - time.Since(start) if timeLeft <= 0 { - fatalf(t, "%s (rust): Wait[%s]: timed out", w.client.userID, w.roomID) + api.Fatalf(t, "%s (rust): Wait[%s]: timed out", w.client.userID, w.roomID) } select { case <-time.After(timeLeft): - fatalf(t, "%s (rust): Wait[%s]: timed out", w.client.userID, w.roomID) + api.Fatalf(t, "%s (rust): Wait[%s]: timed out", w.client.userID, w.roomID) case <-updates: return } @@ -471,7 +472,7 @@ func (l *timelineListener) OnUpdate(diff []*matrix_sdk_ffi.TimelineDiff) { l.fn(diff) } -func timelineItemToEvent(item *matrix_sdk_ffi.TimelineItem) *Event { +func timelineItemToEvent(item *matrix_sdk_ffi.TimelineItem) *api.Event { ev := item.AsEvent() if ev == nil { // e.g day divider return nil @@ -479,7 +480,7 @@ func timelineItemToEvent(item *matrix_sdk_ffi.TimelineItem) *Event { return eventTimelineItemToEvent(*ev) } -func eventTimelineItemToEvent(item *matrix_sdk_ffi.EventTimelineItem) *Event { +func eventTimelineItemToEvent(item *matrix_sdk_ffi.EventTimelineItem) *api.Event { if item == nil { return nil } @@ -487,7 +488,7 @@ func eventTimelineItemToEvent(item *matrix_sdk_ffi.EventTimelineItem) *Event { if item.EventId() != nil { eventID = *item.EventId() } - complementEvent := Event{ + complementEvent := api.Event{ ID: eventID, Sender: item.Sender(), } diff --git a/rebuild_js_sdk.sh b/rebuild_js_sdk.sh index 2b33f8c..c69b9ae 100755 --- a/rebuild_js_sdk.sh +++ b/rebuild_js_sdk.sh @@ -16,5 +16,5 @@ then fi (cd js-sdk && yarn add $1 && yarn install && yarn build) -rm -rf ./internal/api/dist || echo 'no dist directory detected'; -cp -r ./js-sdk/dist/. ./internal/api/dist +rm -rf ./internal/api/js/dist || echo 'no dist directory detected'; +cp -r ./js-sdk/dist/. ./internal/api/js/dist diff --git a/tests/main_test.go b/tests/main_test.go index d19141a..ff59309 100644 --- a/tests/main_test.go +++ b/tests/main_test.go @@ -7,6 +7,8 @@ import ( "github.com/matrix-org/complement" "github.com/matrix-org/complement-crypto/internal/api" + "github.com/matrix-org/complement-crypto/internal/api/js" + "github.com/matrix-org/complement-crypto/internal/api/rust" "github.com/matrix-org/complement-crypto/internal/config" "github.com/matrix-org/complement-crypto/internal/deploy" "github.com/matrix-org/complement/client" @@ -23,14 +25,14 @@ var ( func TestMain(m *testing.M) { complementCryptoConfig = config.NewComplementCryptoConfigFromEnvVars() ssMutex = &sync.Mutex{} - api.SetupJSLogs("js_sdk.log") // rust sdk logs on its own + js.SetupJSLogs("js_sdk.log") // rust sdk logs on its own complement.TestMainWithCleanup(m, "crypto", func() { // always teardown even if panicking ssMutex.Lock() if ssDeployment != nil { ssDeployment.Teardown(complementCryptoConfig.WriteContainerLogs) } ssMutex.Unlock() - api.WriteJSLogs() + js.WriteJSLogs() }) } @@ -56,11 +58,11 @@ func ClientTypeMatrix(t *testing.T, subTest func(tt *testing.T, a, b api.ClientT func MustLoginClient(t *testing.T, clientType api.ClientType, opts api.ClientCreationOpts, ssURL string) api.Client { switch clientType.Lang { case api.ClientTypeRust: - c, err := api.NewRustClient(t, opts, ssURL) + c, err := rust.NewRustClient(t, opts, ssURL) must.NotError(t, "NewRustClient: %s", err) return c case api.ClientTypeJS: - c, err := api.NewJSClient(t, opts) + c, err := js.NewJSClient(t, opts) must.NotError(t, "NewJSClient: %s", err) return c default: