Wait for outbound images to be sent

Also prevent possible race condition in waiting for inbound images, and
generally tighten up file sending/receiving.
This commit is contained in:
Andrew Ferrazzutti 2021-03-29 01:25:05 -04:00
parent 5a33500765
commit afcc206a93
2 changed files with 85 additions and 40 deletions

View File

@ -150,6 +150,10 @@ class MautrixController {
* @property {string} [image] - The URL to the image in the message.
*/
_isLoadedImageURL(src) {
return src?.startsWith("blob:")
}
/**
* Parse a message element (mws-message-wrapper)
*
@ -213,22 +217,27 @@ class MautrixController {
} else if (messageElement.classList.contains("mdRGT07Image")) {
const img = messageElement.querySelector(".mdRGT07MsgImg > img")
if (img) {
if (img.src.startsWith("blob:")) {
messageData.image_url = img.src
} else {
let resolve
// TODO Should reject on "#_chat_message_image_failure"
let observer = new MutationObserver((changes) => {
for (const change of changes) {
if (change.target.src.startsWith("blob:")) {
observer.disconnect()
observer = null
resolve(change.target.src)
return
}
let resolve
// TODO Should reject on "#_chat_message_image_failure"
let observer = new MutationObserver((changes) => {
for (const change of changes) {
if (this._isLoadedImageURL(change.target.src) && observer) {
observer.disconnect()
observer = null
resolve(change.target.src)
return
}
})
observer.observe(img, { attributes: true, attributeFilter: ["src"] })
}
})
observer.observe(img, { attributes: true, attributeFilter: ["src"] })
if (this._isLoadedImageURL(img.src)) {
// Check for this AFTER attaching the observer, in case
// the image loaded after the img element was found but
// before the observer was attached.
messageData.image_url = img.src
observer.disconnect()
} else {
messageData.image_url = await new Promise((realResolve, reject) => {
resolve = realResolve
setTimeout(() => {
@ -236,7 +245,7 @@ class MautrixController {
observer.disconnect()
resolve(img.src)
}
}, 5000)
}, 10000) // Longer timeout for image downloads
})
}
}
@ -245,10 +254,11 @@ class MautrixController {
}
promiseOwnMessage() {
promiseOwnMessage(timeoutLimitMillis, successSelector, failureSelector=null) {
let observer
let msgID = -1
let resolve
let reject
const resolveMessage = () => {
observer.disconnect()
@ -257,50 +267,81 @@ class MautrixController {
resolve(msgID)
}
const invisibleTimeCallback = (changes) => {
const rejectMessage = failureElement => {
observer.disconnect()
observer = null
reject(failureElement)
}
const changeCallback = (changes) => {
for (const change of changes) {
for (const addedNode of change.addedNodes) {
if (addedNode.classList.contains("mdRGT07Own")) {
const timeElement = addedNode.querySelector("time")
if (timeElement) {
const successElement = addedNode.querySelector(successSelector)
if (successElement) {
console.log("Found success element")
console.log(successElement)
msgID = +addedNode.getAttribute("data-local-id")
if (timeElement.classList.contains(".MdNonDisp")) {
if (successElement.classList.contains("MdNonDisp")) {
console.log("Invisible success, wait")
observer.disconnect()
observer = new MutationObserver(visibleTimeCallback)
observer.observe(timeElement, { attributes: true, attributeFilter: ["class"] })
observer = new MutationObserver(getVisibleCallback(true))
observer.observe(successElement, { attributes: true, attributeFilter: ["class"] })
} else {
console.log("Already visible success")
resolveMessage()
}
return
} else if (failureSelector) {
const failureElement = addedNode.querySelector(failureSelector)
if (failureElement) {
console.log("Found failure element")
console.log(failureElement)
if (failureElement.classList.contains("MdNonDisp")) {
console.log("Invisible failure, wait")
observer.disconnect()
observer = new MutationObserver(getVisibleCallback(false))
observer.observe(successElement, { attributes: true, attributeFilter: ["class"] })
} else {
console.log("Already visible failure")
rejectMessage(failureElement)
}
return
}
}
}
}
}
}
const visibleTimeCallback = (changes) => {
for (const change of changes) {
if (!change.target.classList.contains("MdNonDisp")) {
resolveMessage()
return
const getVisibleCallback = isSuccess => {
return changes => {
for (const change of changes) {
if (!change.target.classList.contains("MdNonDisp")) {
console.log(`Waited for visible ${isSuccess ? "success" : "failure"}`)
console.log(change.target)
isSuccess ? resolveMessage() : rejectMessage(change.target)
return
}
}
}
}
observer = new MutationObserver(invisibleTimeCallback)
observer = new MutationObserver(changeCallback)
observer.observe(
document.querySelector("#_chat_room_msg_list"),
{ childList: true })
return new Promise((realResolve, reject) => {
return new Promise((realResolve, realReject) => {
resolve = realResolve
// TODO Handle a timeout better than this
reject = realReject
setTimeout(() => {
if (observer) {
console.log("Timeout!")
observer.disconnect()
reject()
}
}, 5000)
}, timeoutLimitMillis)
})
}

View File

@ -403,7 +403,10 @@ export default class MessagesPuppeteer {
async _sendFileUnsafe(chatID, filePath) {
await this._switchChat(chatID)
const promise = this.page.evaluate(
() => window.__mautrixController.promiseOwnMessage())
() => window.__mautrixController.promiseOwnMessage(
10000, // Use longer timeout for file uploads
"#_chat_message_success_menu",
"#_chat_message_fail_menu"))
try {
this.log(`About to ask for file chooser in ${chatID}`)
@ -420,13 +423,14 @@ export default class MessagesPuppeteer {
// TODO Commonize with text message sending
try {
this.log("Waiting for message to be sent")
this.log("Waiting for file to be sent")
const id = await promise
this.log(`Successfully sent message ${id} to ${chatID}`)
this.log(`Successfully sent file in message ${id} to ${chatID}`)
return id
} catch (e) {
// TODO Handle a timeout better than this
this.error(`Timed out waiting for message to ${chatID}`)
this.error(`Error sending file to ${chatID}`)
// TODO Figure out why e is undefined...
//this.error(e)
return -1
}
}
@ -551,7 +555,7 @@ export default class MessagesPuppeteer {
async _sendMessageUnsafe(chatID, text) {
await this._switchChat(chatID)
const promise = this.page.evaluate(
() => window.__mautrixController.promiseOwnMessage())
() => window.__mautrixController.promiseOwnMessage(5000, "time"))
const input = await this.page.$("#_chat_room_input")
await input.click()
@ -564,8 +568,8 @@ export default class MessagesPuppeteer {
this.log(`Successfully sent message ${id} to ${chatID}`)
return id
} catch (e) {
// TODO Handle a timeout better than this
this.error(`Timed out waiting for message to ${chatID}`)
// TODO Catch if something other than a timeout
this.error(`Timed out sending message to ${chatID}`)
return -1
}
}