2020-08-05 18:38:55 +02:00
|
|
|
/*
|
|
|
|
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
2020-11-18 13:02:38 +01:00
|
|
|
import {createEnum} from "../../../utils/enum.js";
|
|
|
|
import {AbortError} from "../../../utils/error.js";
|
2021-05-20 10:01:30 +02:00
|
|
|
import {REDACTION_TYPE} from "../common.js";
|
2021-05-19 16:41:07 +02:00
|
|
|
import {isTxnId} from "../../common.js";
|
2020-11-18 13:02:38 +01:00
|
|
|
|
|
|
|
export const SendStatus = createEnum(
|
|
|
|
"Waiting",
|
|
|
|
"EncryptingAttachments",
|
|
|
|
"UploadingAttachments",
|
|
|
|
"Encrypting",
|
|
|
|
"Sending",
|
|
|
|
"Sent",
|
|
|
|
"Error",
|
|
|
|
);
|
2020-08-05 18:38:55 +02:00
|
|
|
|
2020-04-20 21:26:39 +02:00
|
|
|
export class PendingEvent {
|
2020-11-18 13:02:38 +01:00
|
|
|
constructor({data, remove, emitUpdate, attachments}) {
|
2019-07-26 22:03:57 +02:00
|
|
|
this._data = data;
|
2020-11-18 13:02:38 +01:00
|
|
|
this._attachments = attachments;
|
2021-02-23 19:58:01 +01:00
|
|
|
this._emitUpdate = emitUpdate;
|
2020-11-18 13:02:38 +01:00
|
|
|
this._removeFromQueueCallback = remove;
|
|
|
|
this._aborted = false;
|
|
|
|
this._status = SendStatus.Waiting;
|
|
|
|
this._sendRequest = null;
|
2020-11-20 11:45:38 +01:00
|
|
|
this._attachmentsTotalBytes = 0;
|
|
|
|
if (this._attachments) {
|
|
|
|
this._attachmentsTotalBytes = Object.values(this._attachments).reduce((t, a) => t + a.size, 0);
|
|
|
|
}
|
2019-06-28 00:52:54 +02:00
|
|
|
}
|
|
|
|
|
2019-07-26 22:03:57 +02:00
|
|
|
get roomId() { return this._data.roomId; }
|
|
|
|
get queueIndex() { return this._data.queueIndex; }
|
|
|
|
get eventType() { return this._data.eventType; }
|
|
|
|
get txnId() { return this._data.txnId; }
|
|
|
|
get remoteId() { return this._data.remoteId; }
|
|
|
|
get content() { return this._data.content; }
|
|
|
|
get data() { return this._data; }
|
2020-09-03 15:36:48 +02:00
|
|
|
|
2020-11-18 13:02:38 +01:00
|
|
|
getAttachment(key) {
|
|
|
|
return this._attachments && this._attachments[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
get needsSending() {
|
|
|
|
return !this.remoteId && !this.aborted;
|
|
|
|
}
|
|
|
|
|
|
|
|
get needsEncryption() {
|
|
|
|
return this._data.needsEncryption && !this.aborted;
|
|
|
|
}
|
|
|
|
|
|
|
|
get needsUpload() {
|
|
|
|
return this._data.needsUpload && !this.aborted;
|
|
|
|
}
|
|
|
|
|
2020-11-19 14:42:29 +01:00
|
|
|
get isMissingAttachments() {
|
|
|
|
return this.needsUpload && !this._attachments;
|
|
|
|
}
|
|
|
|
|
2020-11-18 13:02:38 +01:00
|
|
|
setEncrypting() {
|
|
|
|
this._status = SendStatus.Encrypting;
|
|
|
|
this._emitUpdate("status");
|
|
|
|
}
|
|
|
|
|
2020-09-03 15:36:48 +02:00
|
|
|
setEncrypted(type, content) {
|
2020-11-18 13:02:38 +01:00
|
|
|
this._data.encryptedEventType = type;
|
|
|
|
this._data.encryptedContent = content;
|
2020-09-03 15:36:48 +02:00
|
|
|
this._data.needsEncryption = false;
|
|
|
|
}
|
2020-11-18 13:02:38 +01:00
|
|
|
|
|
|
|
setError(error) {
|
|
|
|
this._status = SendStatus.Error;
|
|
|
|
this._error = error;
|
|
|
|
this._emitUpdate("status");
|
|
|
|
}
|
|
|
|
|
|
|
|
get status() { return this._status; }
|
|
|
|
get error() { return this._error; }
|
|
|
|
|
|
|
|
get attachmentsTotalBytes() {
|
2020-11-20 11:45:38 +01:00
|
|
|
return this._attachmentsTotalBytes;
|
2020-11-18 13:02:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
get attachmentsSentBytes() {
|
2020-11-18 20:09:07 +01:00
|
|
|
return this._attachments && Object.values(this._attachments).reduce((t, a) => t + a.sentBytes, 0);
|
2020-11-18 13:02:38 +01:00
|
|
|
}
|
|
|
|
|
2021-02-23 19:22:59 +01:00
|
|
|
async uploadAttachments(hsApi, log) {
|
2020-11-18 13:02:38 +01:00
|
|
|
if (!this.needsUpload) {
|
|
|
|
return;
|
|
|
|
}
|
2020-11-19 14:42:29 +01:00
|
|
|
if (!this._attachments) {
|
|
|
|
throw new Error("attachments missing");
|
|
|
|
}
|
2020-11-18 13:02:38 +01:00
|
|
|
if (this.needsEncryption) {
|
|
|
|
this._status = SendStatus.EncryptingAttachments;
|
|
|
|
this._emitUpdate("status");
|
|
|
|
for (const attachment of Object.values(this._attachments)) {
|
2021-02-23 19:22:59 +01:00
|
|
|
await log.wrap("encrypt", () => {
|
|
|
|
log.set("size", attachment.size);
|
2021-02-24 10:38:19 +01:00
|
|
|
return attachment.encrypt();
|
2021-02-23 19:22:59 +01:00
|
|
|
});
|
2020-11-18 13:02:38 +01:00
|
|
|
if (this.aborted) {
|
|
|
|
throw new AbortError();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this._status = SendStatus.UploadingAttachments;
|
|
|
|
this._emitUpdate("status");
|
2020-11-18 20:09:27 +01:00
|
|
|
const entries = Object.entries(this._attachments);
|
|
|
|
// upload smallest attachments first
|
|
|
|
entries.sort(([, a1], [, a2]) => a1.size - a2.size);
|
|
|
|
for (const [urlPath, attachment] of entries) {
|
2021-02-23 19:22:59 +01:00
|
|
|
await log.wrap("upload", log => {
|
|
|
|
log.set("size", attachment.size);
|
|
|
|
return attachment.upload(hsApi, () => {
|
|
|
|
this._emitUpdate("attachmentsSentBytes");
|
|
|
|
}, log);
|
2020-11-18 13:02:38 +01:00
|
|
|
});
|
|
|
|
attachment.applyToContent(urlPath, this.content);
|
|
|
|
}
|
|
|
|
this._data.needsUpload = false;
|
|
|
|
}
|
|
|
|
|
2021-05-19 16:41:07 +02:00
|
|
|
async abort() {
|
2020-11-18 13:02:38 +01:00
|
|
|
if (!this._aborted) {
|
|
|
|
this._aborted = true;
|
|
|
|
if (this._attachments) {
|
|
|
|
for (const attachment of Object.values(this._attachments)) {
|
|
|
|
attachment.abort();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this._sendRequest?.abort();
|
2021-05-19 16:41:07 +02:00
|
|
|
await this._removeFromQueueCallback();
|
2020-11-18 13:02:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
get aborted() {
|
|
|
|
return this._aborted;
|
|
|
|
}
|
|
|
|
|
2021-02-23 19:22:59 +01:00
|
|
|
async send(hsApi, log) {
|
2020-11-18 13:02:38 +01:00
|
|
|
this._status = SendStatus.Sending;
|
|
|
|
this._emitUpdate("status");
|
|
|
|
const eventType = this._data.encryptedEventType || this._data.eventType;
|
|
|
|
const content = this._data.encryptedContent || this._data.content;
|
2021-05-19 16:41:07 +02:00
|
|
|
if (eventType === REDACTION_TYPE) {
|
|
|
|
// TODO: should we double check here that this._data.redacts is not a txnId here anymore?
|
|
|
|
this._sendRequest = hsApi.redact(
|
|
|
|
this.roomId,
|
|
|
|
this._data.redacts,
|
|
|
|
this.txnId,
|
|
|
|
content,
|
|
|
|
{log}
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
this._sendRequest = hsApi.send(
|
|
|
|
this.roomId,
|
|
|
|
eventType,
|
|
|
|
this.txnId,
|
|
|
|
content,
|
|
|
|
{log}
|
|
|
|
);
|
|
|
|
}
|
2020-11-18 13:02:38 +01:00
|
|
|
const response = await this._sendRequest.response();
|
|
|
|
this._sendRequest = null;
|
2021-05-19 16:41:07 +02:00
|
|
|
// both /send and /redact have the same response format
|
2020-11-18 13:02:38 +01:00
|
|
|
this._data.remoteId = response.event_id;
|
2021-02-23 19:58:01 +01:00
|
|
|
log.set("id", this._data.remoteId);
|
2020-11-18 13:02:38 +01:00
|
|
|
this._status = SendStatus.Sent;
|
|
|
|
this._emitUpdate("status");
|
|
|
|
}
|
2020-11-18 20:08:42 +01:00
|
|
|
|
|
|
|
dispose() {
|
|
|
|
if (this._attachments) {
|
|
|
|
for (const attachment of Object.values(this._attachments)) {
|
|
|
|
attachment.dispose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-05-19 16:41:07 +02:00
|
|
|
|
|
|
|
get relatedTxnId() {
|
|
|
|
if (isTxnId(this._data.redacts)) {
|
|
|
|
return this._data.redacts;
|
|
|
|
}
|
2021-05-20 11:42:56 +02:00
|
|
|
return null;
|
2021-05-19 16:41:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
setRelatedEventId(eventId) {
|
|
|
|
if (this._data.redacts) {
|
|
|
|
this._data.redacts = eventId;
|
|
|
|
}
|
|
|
|
}
|
2019-06-28 00:52:54 +02:00
|
|
|
}
|