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-09-22 13:43:18 +02:00
|
|
|
import {AbortError} from "../utils/error.js";
|
|
|
|
import {HomeServerError} from "./error.js";
|
|
|
|
import {HomeServerApi} from "./net/HomeServerApi.js";
|
|
|
|
import {ExponentialRetryDelay} from "./net/ExponentialRetryDelay.js";
|
|
|
|
|
|
|
|
class Request {
|
|
|
|
constructor(methodName, args) {
|
|
|
|
this._methodName = methodName;
|
|
|
|
this._args = args;
|
|
|
|
this._responsePromise = new Promise((resolve, reject) => {
|
|
|
|
this._resolve = resolve;
|
|
|
|
this._reject = reject;
|
|
|
|
});
|
|
|
|
this._requestResult = null;
|
2019-07-26 22:03:57 +02:00
|
|
|
}
|
|
|
|
|
2020-09-22 13:43:18 +02:00
|
|
|
abort() {
|
|
|
|
if (this._requestResult) {
|
|
|
|
this._requestResult.abort();
|
|
|
|
} else {
|
|
|
|
this._reject(new AbortError());
|
2019-07-26 22:03:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-22 13:43:18 +02:00
|
|
|
response() {
|
|
|
|
return this._responsePromise;
|
|
|
|
}
|
|
|
|
}
|
2019-07-26 22:03:57 +02:00
|
|
|
|
2020-09-22 13:43:18 +02:00
|
|
|
class HomeServerApiWrapper {
|
|
|
|
constructor(scheduler) {
|
|
|
|
this._scheduler = scheduler;
|
2019-07-26 22:03:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-22 13:43:18 +02:00
|
|
|
// add request-wrapping methods to prototype
|
|
|
|
for (const methodName of Object.getOwnPropertyNames(HomeServerApi.prototype)) {
|
|
|
|
if (methodName !== "constructor" && !methodName.startsWith("_")) {
|
|
|
|
HomeServerApiWrapper.prototype[methodName] = function(...args) {
|
|
|
|
return this._scheduler._hsApiRequest(methodName, args);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2019-07-26 22:03:57 +02:00
|
|
|
|
|
|
|
export class SendScheduler {
|
2020-09-22 13:43:18 +02:00
|
|
|
constructor({hsApi, clock}) {
|
2019-07-26 22:03:57 +02:00
|
|
|
this._hsApi = hsApi;
|
2020-09-22 13:43:18 +02:00
|
|
|
this._clock = clock;
|
|
|
|
this._requests = new Set();
|
|
|
|
this._isRateLimited = false;
|
|
|
|
this._isDrainingRateLimit = false;
|
2020-04-20 19:48:21 +02:00
|
|
|
this._stopped = false;
|
2020-09-22 13:43:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
createHomeServerApiWrapper() {
|
|
|
|
return new HomeServerApiWrapper(this);
|
2019-07-26 22:03:57 +02:00
|
|
|
}
|
|
|
|
|
2020-04-18 19:16:16 +02:00
|
|
|
stop() {
|
2020-09-22 13:43:18 +02:00
|
|
|
this._stopped = true;
|
|
|
|
for (const request of this._requests) {
|
|
|
|
request.abort();
|
|
|
|
}
|
|
|
|
this._requests.clear();
|
2020-04-18 19:16:16 +02:00
|
|
|
}
|
|
|
|
|
2020-04-20 19:48:21 +02:00
|
|
|
start() {
|
|
|
|
this._stopped = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
get isStarted() {
|
|
|
|
return !this._stopped;
|
|
|
|
}
|
|
|
|
|
2020-09-22 13:43:18 +02:00
|
|
|
_hsApiRequest(name, args) {
|
|
|
|
const request = new Request(name, args);
|
|
|
|
this._doSend(request);
|
|
|
|
return request;
|
2019-07-26 22:03:57 +02:00
|
|
|
}
|
|
|
|
|
2020-09-22 13:43:18 +02:00
|
|
|
async _doSend(request) {
|
|
|
|
this._requests.add(request);
|
|
|
|
try {
|
|
|
|
let retryDelay;
|
|
|
|
while (!this._stopped) {
|
|
|
|
try {
|
|
|
|
const requestResult = this._hsApi[request._methodName].apply(this._hsApi, request._args);
|
|
|
|
// so the request can be aborted
|
|
|
|
request._requestResult = requestResult;
|
|
|
|
const response = await requestResult.response();
|
|
|
|
request._resolve(response);
|
|
|
|
return;
|
|
|
|
} catch (err) {
|
|
|
|
if (err instanceof HomeServerError && err.errcode === "M_LIMIT_EXCEEDED") {
|
|
|
|
if (Number.isSafeInteger(err.retry_after_ms)) {
|
|
|
|
await this._clock.createTimeout(err.retry_after_ms).elapsed();
|
|
|
|
} else {
|
|
|
|
if (!retryDelay) {
|
|
|
|
retryDelay = new ExponentialRetryDelay(this._clock.createTimeout);
|
|
|
|
}
|
|
|
|
await retryDelay.waitForRetry();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
request._reject(err);
|
|
|
|
return;
|
2019-07-26 22:03:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-22 13:43:18 +02:00
|
|
|
if (this._stopped) {
|
|
|
|
request.abort();
|
2019-07-26 22:03:57 +02:00
|
|
|
}
|
2020-09-22 13:43:18 +02:00
|
|
|
} finally {
|
|
|
|
this._requests.delete(request);
|
2019-07-26 22:03:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|