diff --git a/src/matrix/net/replay.js b/src/matrix/net/replay.js new file mode 100644 index 00000000..b6ddcb74 --- /dev/null +++ b/src/matrix/net/replay.js @@ -0,0 +1,108 @@ +import { + RequestAbortError, + NetworkError +} from "../error.js"; + +class RequestLogItem { + constructor(url, options) { + this.url = url; + this.options = options; + this.error = null; + this.body = null; + this.status = status; + this.start = performance.now(); + this.end = 0; + } + + async handleResponse(response) { + this.end = performance.now(); + this.status = response.status; + this.body = response.body; + } + + handleError(err) { + this.end = performance.now(); + this.error = { + aborted: err instanceof RequestAbortError, + network: err instanceof NetworkError, + message: err.message, + }; + } +} + +export class RecordRequester { + constructor(request) { + this._origRequest = request; + this._requestLog = []; + this.request = this.request.bind(this); + } + + request(url, options) { + const requestItem = new RequestLogItem(url, options); + this._requestLog.push(requestItem); + try { + const requestResult = this._origRequest(url, options); + requestResult.response().then(response => { + requestItem.handleResponse(response); + }); + return requestResult; + } catch (err) { + requestItem.handleError(err); + throw err; + } + } + + log() { + return this._requestLog; + } +} + +export class ReplayRequester { + constructor(log, options) { + this._log = log.slice(); + this._options = options; + this.request = this.request.bind(this); + } + + request(url, options) { + const idx = this._log.findIndex(item => { + return item.url === url && options.method === item.options.method; + }); + if (idx === -1) { + return new ReplayRequestResult({status: 404}, options); + } else { + const [item] = this._log.splice(idx, 1); + return new ReplayRequestResult(item, options); + } + } +} + +class ReplayRequestResult { + constructor(item, options) { + this._item = item; + this._options = options; + this._aborted = false; + } + + abort() { + this._aborted = true; + } + + async response() { + if (this._options.delay) { + const delay = this._item.end - this._item.start; + await new Promise(resolve => setTimeout(resolve, delay)); + } + if (this._item.error || this._aborted) { + const error = this._item.error; + if (error.aborted || this._aborted) { + throw new RequestAbortError(error.message); + } else if (error.network) { + throw new NetworkError(error.message); + } else { + throw new Error(error.message); + } + } + return this._item; + } +}