Beeper extension for client side media dedup (#267)

The client can send a unique id (like a hash) of a file it intends to
upload in the create request. If the server already has the file it will
return a "completed" response.

The unique id is meant to be opaque for the server. For privacy reasons
it is recommended not to use the raw hash of a file.  The returned MXC
should be stable for the same unique id for the same user but is not
guaranteed.

The room id is used to tie the lifecycle of created media to an existing
room on the homeserver. If a room is purged from the homeserver the
media will be purged along with it.

If the file has been created but not uploaded the response will not have
a completed timestamp which allows the client to retry sending the file.
If the upload has already been completed the upload URL will be empty.

It is possible for multiple clients to send a create request
simultaneously with the same unique id and upload the file at the same
time. It is also possible for the server to forget the unique id and
allow reuploading the same file again returning a new MXC.

This commit also fixes UnusedExpiresAt type in the response which is a
breaking change.
This commit is contained in:
Toni Spets 2024-08-21 19:19:03 +03:00 committed by GitHub
commit ad20a9218f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 23 additions and 4 deletions

View file

@ -1581,12 +1581,27 @@ func (cli *Client) DownloadBytes(ctx context.Context, mxcURL id.ContentURI) ([]b
return io.ReadAll(resp.Body)
}
type ReqCreateMXC struct {
BeeperUniqueID string
BeeperRoomID id.RoomID
}
// CreateMXC creates a blank Matrix content URI to allow uploading the content asynchronously later.
//
// See https://spec.matrix.org/v1.7/client-server-api/#post_matrixmediav1create
func (cli *Client) CreateMXC(ctx context.Context) (*RespCreateMXC, error) {
func (cli *Client) CreateMXC(ctx context.Context, extra ...ReqCreateMXC) (*RespCreateMXC, error) {
var m RespCreateMXC
_, err := cli.MakeRequest(ctx, http.MethodPost, cli.BuildURL(MediaURLPath{"v1", "create"}), nil, &m)
query := map[string]string{}
if len(extra) > 0 {
if extra[0].BeeperUniqueID != "" {
query["com.beeper.unique_id"] = extra[0].BeeperUniqueID
}
if extra[0].BeeperRoomID != "" {
query["com.beeper.room_id"] = string(extra[0].BeeperRoomID)
}
}
createURL := cli.BuildURLWithQuery(MediaURLPath{"v1", "create"}, query)
_, err := cli.MakeRequest(ctx, http.MethodPost, createURL, nil, &m)
return &m, err
}

View file

@ -111,10 +111,14 @@ type RespMediaUpload struct {
// RespCreateMXC is the JSON response for https://spec.matrix.org/v1.7/client-server-api/#post_matrixmediav1create
type RespCreateMXC struct {
ContentURI id.ContentURI `json:"content_uri"`
UnusedExpiresAt int `json:"unused_expires_at,omitempty"`
ContentURI id.ContentURI `json:"content_uri"`
UnusedExpiresAt jsontime.UnixMilli `json:"unused_expires_at,omitempty"`
UnstableUploadURL string `json:"com.beeper.msc3870.upload_url,omitempty"`
// Beeper extensions for uploading unique media only once
BeeperUniqueID string `json:"com.beeper.unique_id,omitempty"`
BeeperCompletedAt jsontime.UnixMilli `json:"com.beeper.completed_at,omitempty"`
}
// RespPreviewURL is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#get_matrixmediav3preview_url