Skip to content

Commit

Permalink
Shuffle chrome functions around, add RunAsyncFn
Browse files Browse the repository at this point in the history
Everything will end up using RunAsyncFn to reduce the surface area
when interacting with the browser.
  • Loading branch information
kegsay committed Jan 10, 2024
1 parent d19bbb1 commit 8bfc360
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 11 deletions.
37 changes: 37 additions & 0 deletions internal/chrome/exec.go → internal/api/js/chrome/chrome.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// package chrome provides helper functions to execute JS in a Chrome browser
//
// This would ordinarily be done via a Chrome struct but Go does not allow
// generic methods, only generic static functions, producing "method must have no type parameters".
package chrome

import (
Expand All @@ -6,9 +10,42 @@ import (

"github.com/chromedp/cdproto/runtime"
"github.com/chromedp/chromedp"
"github.com/matrix-org/complement-crypto/internal/api"
"github.com/matrix-org/complement/must"
)

// Void is a type which can be used when you want to run an async function without returning anything.
// It can stop large responses causing errors "Object reference chain is too long (-32000)"
// when we don't care about the response.
type Void *runtime.RemoteObject

// Run an anonymous async iffe in the browser. Set the type parameter to a basic data type
// which can be returned as JSON e.g string, map[string]any, []string. If you do not want
// to return anything, use chrome.Void
func RunAsyncFn[T any](t *testing.T, ctx context.Context, js string) (*T, error) {
t.Helper()
out := new(T)
err := chromedp.Run(ctx,
chromedp.Evaluate(`(async () => {`+js+`})()`, &out, func(p *runtime.EvaluateParams) *runtime.EvaluateParams {
return p.WithAwaitPromise(true)
}),
)
if err != nil {
return nil, err
}
return out, nil
}

// MustRunAsyncFn is RunAsyncFn but fails the test if an error is returned when executing.
func MustRunAsyncFn[T any](t *testing.T, ctx context.Context, js string) *T {
t.Helper()
result, err := RunAsyncFn[T](t, ctx, js)
if err != nil {
api.Fatalf(t, "MustRunAsyncFn: %s", err)
}
return result
}

func MustExecuteInto[T any](t *testing.T, ctx context.Context, js string) T {
t.Helper()
out, err := ExecuteInto[T](t, ctx, js)
Expand Down
17 changes: 7 additions & 10 deletions internal/api/js/js.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,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-crypto/internal/api/js/chrome"
"github.com/matrix-org/complement/must"
"github.com/tidwall/gjson"
)
Expand Down Expand Up @@ -336,7 +336,8 @@ func (c *JSClient) MustBackpaginate(t *testing.T, roomID string, count int) {
}

func (c *JSClient) MustBackupKeys(t *testing.T) (recoveryKey string) {
key, err := chrome.AwaitExecuteInto[string](t, c.ctx, `(async () => {
t.Helper()
key := chrome.MustRunAsyncFn[string](t, c.ctx, `
// we need to ensure that we have a recovery key first, though we don't actually care about it..?
const recoveryKey = await window.__client.getCrypto().createRecoveryKeyFromPassphrase();
// now use said key to make backups
Expand All @@ -347,11 +348,7 @@ func (c *JSClient) MustBackupKeys(t *testing.T) (recoveryKey string) {
});
// now we can enable key backups
await window.__client.getCrypto().checkKeyBackupAndEnable();
return recoveryKey.encodedPrivateKey;
})()`)
if err != nil {
api.Fatalf(t, "MustBackupKeys: %s", err)
}
return recoveryKey.encodedPrivateKey;`)
// 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
// Ideally this would be configurable..
Expand All @@ -360,7 +357,7 @@ func (c *JSClient) MustBackupKeys(t *testing.T) (recoveryKey string) {
}

func (c *JSClient) MustLoadBackup(t *testing.T, recoveryKey string) {
chrome.MustAwaitExecute(t, c.ctx, fmt.Sprintf(`(async () => {
chrome.MustRunAsyncFn[chrome.Void](t, c.ctx, fmt.Sprintf(`
// we assume the recovery key is the private key for the default key id so
// figure out what that key id is.
const keyId = await window.__client.secretStorage.getDefaultKeyId();
Expand All @@ -374,8 +371,8 @@ func (c *JSClient) MustLoadBackup(t *testing.T, recoveryKey string) {
console.log("key backup: ", JSON.stringify(keyBackupCheck));
// FIXME: this just doesn't seem to work, causing 'Error: getSecretStorageKey callback returned invalid data' because the key ID
// cannot be found...
await window.__client.restoreKeyBackupWithSecretStorage(keyBackupCheck ? keyBackupCheck.backupInfo : null, undefined, undefined);
})()`, recoveryKey))
await window.__client.restoreKeyBackupWithSecretStorage(keyBackupCheck ? keyBackupCheck.backupInfo : null, undefined, undefined);`,
recoveryKey))
}

func (c *JSClient) WaitUntilEventInRoom(t *testing.T, roomID string, checker func(e api.Event) bool) api.Waiter {
Expand Down
1 change: 0 additions & 1 deletion internal/chrome/callbacks.go

This file was deleted.

37 changes: 37 additions & 0 deletions tests/addons/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
### mitmproxy

This directory contains code that will be used as a [mitmproxy addon](https://docs.mitmproxy.org/stable/addons-overview/).

How this works:
- A vanilla `mitmproxy` is run in the same network as the homeservers.
- It is told to proxy both hs1 and hs2 i.e `mitmdump --mode reverse:http://hs1:8008@3000`
- It is also told to run a normal proxy, to which a Flask HTTP server is attached.
- The Flask HTTP server can be used to control mitmproxy at test runtime. This is done via the Controller HTTP API.


### Controller HTTP API

**This is highly experimental and will change without warning.**

`mitmproxy` is run once for all tests. To avoid test pollution, the controller is "locked" for the duration
of a test and must be "unlocked" afterwards. When acquiring the lock, options can be set on `mitmproxy`.

```
POST /options/lock
{
"options": {
"body_size_limit": "3m",
}
}
HTTP/1.1 200 OK
{
"reset_id": "some_opaque_string"
}
```

```
POST /options/unlock
{
"reset_id": "some_opaque_string"
}
```

0 comments on commit 8bfc360

Please sign in to comment.