forked from mystiq/hydrogen-web
Merge pull request #588 from vector-im/ts-conversion-matrix-net
Convert /matrix/net to typescript
This commit is contained in:
commit
589a002d67
15 changed files with 384 additions and 184 deletions
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
import {ViewModel} from "../ViewModel.js";
|
||||
import {createEnum} from "../../utils/enum";
|
||||
import {ConnectionStatus} from "../../matrix/net/Reconnector.js";
|
||||
import {ConnectionStatus} from "../../matrix/net/Reconnector";
|
||||
import {SyncStatus} from "../../matrix/Sync.js";
|
||||
|
||||
const SessionStatus = createEnum(
|
||||
|
|
|
@ -50,6 +50,8 @@ export interface ILogItem {
|
|||
ensureRefId(): void;
|
||||
catch(err: Error): Error;
|
||||
serialize(filter: LogFilter, parentStartTime: number | undefined, forced: boolean): ISerializedItem | undefined;
|
||||
finish(): void;
|
||||
child(labelOrValues: LabelOrValues, logLevel?: LogLevel, filterCreator?: FilterCreator): ILogItem;
|
||||
}
|
||||
|
||||
export interface ILogger {
|
||||
|
|
|
@ -19,11 +19,11 @@ import {createEnum} from "../utils/enum";
|
|||
import {lookupHomeserver} from "./well-known.js";
|
||||
import {AbortableOperation} from "../utils/AbortableOperation";
|
||||
import {ObservableValue} from "../observable/ObservableValue";
|
||||
import {HomeServerApi} from "./net/HomeServerApi.js";
|
||||
import {Reconnector, ConnectionStatus} from "./net/Reconnector.js";
|
||||
import {ExponentialRetryDelay} from "./net/ExponentialRetryDelay.js";
|
||||
import {MediaRepository} from "./net/MediaRepository.js";
|
||||
import {RequestScheduler} from "./net/RequestScheduler.js";
|
||||
import {HomeServerApi} from "./net/HomeServerApi";
|
||||
import {Reconnector, ConnectionStatus} from "./net/Reconnector";
|
||||
import {ExponentialRetryDelay} from "./net/ExponentialRetryDelay";
|
||||
import {MediaRepository} from "./net/MediaRepository";
|
||||
import {RequestScheduler} from "./net/RequestScheduler";
|
||||
import {Sync, SyncStatus} from "./Sync.js";
|
||||
import {Session} from "./Session.js";
|
||||
import {PasswordLoginMethod} from "./login/PasswordLoginMethod";
|
||||
|
|
|
@ -15,18 +15,28 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import {AbortError} from "../../utils/error";
|
||||
import type {Timeout} from "../../platform/web/dom/Clock.js";
|
||||
|
||||
type TimeoutCreator = (ms: number) => Timeout;
|
||||
|
||||
const enum Default { start = 2000 }
|
||||
|
||||
export class ExponentialRetryDelay {
|
||||
constructor(createTimeout) {
|
||||
private readonly _start: number = Default.start;
|
||||
private _current: number = Default.start;
|
||||
private readonly _createTimeout: TimeoutCreator;
|
||||
private readonly _max: number;
|
||||
private _timeout?: Timeout;
|
||||
|
||||
constructor(createTimeout: TimeoutCreator) {
|
||||
const start = 2000;
|
||||
this._start = start;
|
||||
this._current = start;
|
||||
this._createTimeout = createTimeout;
|
||||
this._max = 60 * 5 * 1000; //5 min
|
||||
this._timeout = null;
|
||||
}
|
||||
|
||||
async waitForRetry() {
|
||||
async waitForRetry(): Promise<void> {
|
||||
this._timeout = this._createTimeout(this._current);
|
||||
try {
|
||||
await this._timeout.elapsed();
|
||||
|
@ -39,22 +49,22 @@ export class ExponentialRetryDelay {
|
|||
throw err;
|
||||
}
|
||||
} finally {
|
||||
this._timeout = null;
|
||||
this._timeout = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
abort() {
|
||||
abort(): void {
|
||||
if (this._timeout) {
|
||||
this._timeout.abort();
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
reset(): void {
|
||||
this._current = this._start;
|
||||
this.abort();
|
||||
}
|
||||
|
||||
get nextValue() {
|
||||
get nextValue(): number {
|
||||
return this._current;
|
||||
}
|
||||
}
|
|
@ -15,14 +15,33 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {encodeQueryParams, encodeBody} from "./common.js";
|
||||
import {HomeServerRequest} from "./HomeServerRequest.js";
|
||||
import {encodeQueryParams, encodeBody} from "./common";
|
||||
import {HomeServerRequest} from "./HomeServerRequest";
|
||||
import type {IHomeServerRequest} from "./HomeServerRequest";
|
||||
import type {Reconnector} from "./Reconnector";
|
||||
import type {EncodedBody} from "./common";
|
||||
import type {IRequestOptions, RequestFunction} from "../../platform/types/types";
|
||||
import type {ILogItem} from "../../logging/types";
|
||||
|
||||
type RequestMethod = "POST" | "GET" | "PUT";
|
||||
|
||||
const CS_R0_PREFIX = "/_matrix/client/r0";
|
||||
const DEHYDRATION_PREFIX = "/_matrix/client/unstable/org.matrix.msc2697.v2";
|
||||
|
||||
type Options = {
|
||||
homeserver: string;
|
||||
accessToken: string;
|
||||
request: RequestFunction;
|
||||
reconnector: Reconnector;
|
||||
};
|
||||
|
||||
export class HomeServerApi {
|
||||
constructor({homeserver, accessToken, request, reconnector}) {
|
||||
private readonly _homeserver: string;
|
||||
private readonly _accessToken: string;
|
||||
private readonly _requestFn: RequestFunction;
|
||||
private readonly _reconnector: Reconnector;
|
||||
|
||||
constructor({homeserver, accessToken, request, reconnector}: Options) {
|
||||
// store these both in a closure somehow so it's harder to get at in case of XSS?
|
||||
// one could change the homeserver as well so the token gets sent there, so both must be protected from read/write
|
||||
this._homeserver = homeserver;
|
||||
|
@ -31,14 +50,14 @@ export class HomeServerApi {
|
|||
this._reconnector = reconnector;
|
||||
}
|
||||
|
||||
_url(csPath, prefix = CS_R0_PREFIX) {
|
||||
private _url(csPath: string, prefix: string = CS_R0_PREFIX): string {
|
||||
return this._homeserver + prefix + csPath;
|
||||
}
|
||||
|
||||
_baseRequest(method, url, queryParams, body, options, accessToken) {
|
||||
private _baseRequest(method: RequestMethod, url: string, queryParams?: Record<string, any>, body?: Record<string, any>, options?: IRequestOptions, accessToken?: string): IHomeServerRequest {
|
||||
const queryString = encodeQueryParams(queryParams);
|
||||
url = `${url}?${queryString}`;
|
||||
let log;
|
||||
let log: ILogItem | undefined;
|
||||
if (options?.log) {
|
||||
const parent = options?.log;
|
||||
log = parent.child({
|
||||
|
@ -47,8 +66,8 @@ export class HomeServerApi {
|
|||
method,
|
||||
}, parent.level.Info);
|
||||
}
|
||||
let encodedBody;
|
||||
const headers = new Map();
|
||||
let encodedBody: EncodedBody["body"];
|
||||
const headers: Map<string, string | number> = new Map();
|
||||
if (accessToken) {
|
||||
headers.set("Authorization", `Bearer ${accessToken}`);
|
||||
}
|
||||
|
@ -56,7 +75,6 @@ export class HomeServerApi {
|
|||
if (body) {
|
||||
const encoded = encodeBody(body);
|
||||
headers.set("Content-Type", encoded.mimeType);
|
||||
headers.set("Content-Length", encoded.length);
|
||||
encodedBody = encoded.body;
|
||||
}
|
||||
|
||||
|
@ -86,63 +104,63 @@ export class HomeServerApi {
|
|||
return hsRequest;
|
||||
}
|
||||
|
||||
_unauthedRequest(method, url, queryParams, body, options) {
|
||||
return this._baseRequest(method, url, queryParams, body, options, null);
|
||||
private _unauthedRequest(method: RequestMethod, url: string, queryParams?: Record<string, any>, body?: Record<string, any>, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._baseRequest(method, url, queryParams, body, options);
|
||||
}
|
||||
|
||||
_authedRequest(method, url, queryParams, body, options) {
|
||||
private _authedRequest(method: RequestMethod, url: string, queryParams?: Record<string, any>, body?: Record<string, any>, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._baseRequest(method, url, queryParams, body, options, this._accessToken);
|
||||
}
|
||||
|
||||
_post(csPath, queryParams, body, options) {
|
||||
private _post(csPath: string, queryParams: Record<string, any>, body: Record<string, any>, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._authedRequest("POST", this._url(csPath, options?.prefix || CS_R0_PREFIX), queryParams, body, options);
|
||||
}
|
||||
|
||||
_put(csPath, queryParams, body, options) {
|
||||
private _put(csPath: string, queryParams: Record<string, any>, body?: Record<string, any>, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._authedRequest("PUT", this._url(csPath, options?.prefix || CS_R0_PREFIX), queryParams, body, options);
|
||||
}
|
||||
|
||||
_get(csPath, queryParams, body, options) {
|
||||
private _get(csPath: string, queryParams?: Record<string, any>, body?: Record<string, any>, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._authedRequest("GET", this._url(csPath, options?.prefix || CS_R0_PREFIX), queryParams, body, options);
|
||||
}
|
||||
|
||||
sync(since, filter, timeout, options = null) {
|
||||
return this._get("/sync", {since, timeout, filter}, null, options);
|
||||
sync(since: string, filter: string, timeout: number, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._get("/sync", {since, timeout, filter}, undefined, options);
|
||||
}
|
||||
|
||||
// params is from, dir and optionally to, limit, filter.
|
||||
messages(roomId, params, options = null) {
|
||||
return this._get(`/rooms/${encodeURIComponent(roomId)}/messages`, params, null, options);
|
||||
messages(roomId: string, params: Record<string, any>, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._get(`/rooms/${encodeURIComponent(roomId)}/messages`, params, undefined, options);
|
||||
}
|
||||
|
||||
// params is at, membership and not_membership
|
||||
members(roomId, params, options = null) {
|
||||
return this._get(`/rooms/${encodeURIComponent(roomId)}/members`, params, null, options);
|
||||
members(roomId: string, params: Record<string, any>, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._get(`/rooms/${encodeURIComponent(roomId)}/members`, params, undefined, options);
|
||||
}
|
||||
|
||||
send(roomId, eventType, txnId, content, options = null) {
|
||||
send(roomId: string, eventType: string, txnId: string, content: Record<string, any>, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._put(`/rooms/${encodeURIComponent(roomId)}/send/${encodeURIComponent(eventType)}/${encodeURIComponent(txnId)}`, {}, content, options);
|
||||
}
|
||||
|
||||
redact(roomId, eventId, txnId, content, options = null) {
|
||||
redact(roomId: string, eventId: string, txnId: string, content: Record<string, any>, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._put(`/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${encodeURIComponent(txnId)}`, {}, content, options);
|
||||
}
|
||||
|
||||
receipt(roomId, receiptType, eventId, options = null) {
|
||||
receipt(roomId: string, receiptType: string, eventId: string, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._post(`/rooms/${encodeURIComponent(roomId)}/receipt/${encodeURIComponent(receiptType)}/${encodeURIComponent(eventId)}`,
|
||||
{}, {}, options);
|
||||
}
|
||||
|
||||
state(roomId, eventType, stateKey, options = null) {
|
||||
return this._get(`/rooms/${encodeURIComponent(roomId)}/state/${encodeURIComponent(eventType)}/${encodeURIComponent(stateKey)}`, {}, null, options);
|
||||
state(roomId: string, eventType: string, stateKey: string, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._get(`/rooms/${encodeURIComponent(roomId)}/state/${encodeURIComponent(eventType)}/${encodeURIComponent(stateKey)}`, {}, undefined, options);
|
||||
}
|
||||
|
||||
getLoginFlows() {
|
||||
return this._unauthedRequest("GET", this._url("/login"), null, null, null);
|
||||
getLoginFlows(): IHomeServerRequest {
|
||||
return this._unauthedRequest("GET", this._url("/login"));
|
||||
}
|
||||
|
||||
passwordLogin(username, password, initialDeviceDisplayName, options = null) {
|
||||
return this._unauthedRequest("POST", this._url("/login"), null, {
|
||||
passwordLogin(username: string, password: string, initialDeviceDisplayName: string, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._unauthedRequest("POST", this._url("/login"), undefined, {
|
||||
"type": "m.login.password",
|
||||
"identifier": {
|
||||
"type": "m.id.user",
|
||||
|
@ -153,8 +171,8 @@ export class HomeServerApi {
|
|||
}, options);
|
||||
}
|
||||
|
||||
tokenLogin(loginToken, txnId, initialDeviceDisplayName, options = null) {
|
||||
return this._unauthedRequest("POST", this._url("/login"), null, {
|
||||
tokenLogin(loginToken: string, txnId: string, initialDeviceDisplayName: string, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._unauthedRequest("POST", this._url("/login"), undefined, {
|
||||
"type": "m.login.token",
|
||||
"identifier": {
|
||||
"type": "m.id.user",
|
||||
|
@ -165,91 +183,91 @@ export class HomeServerApi {
|
|||
}, options);
|
||||
}
|
||||
|
||||
createFilter(userId, filter, options = null) {
|
||||
return this._post(`/user/${encodeURIComponent(userId)}/filter`, null, filter, options);
|
||||
createFilter(userId: string, filter: Record<string, any>, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._post(`/user/${encodeURIComponent(userId)}/filter`, {}, filter, options);
|
||||
}
|
||||
|
||||
versions(options = null) {
|
||||
return this._unauthedRequest("GET", `${this._homeserver}/_matrix/client/versions`, null, null, options);
|
||||
versions(options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._unauthedRequest("GET", `${this._homeserver}/_matrix/client/versions`, undefined, undefined, options);
|
||||
}
|
||||
|
||||
uploadKeys(dehydratedDeviceId, payload, options = null) {
|
||||
uploadKeys(dehydratedDeviceId: string, payload: Record<string, any>, options?: IRequestOptions): IHomeServerRequest {
|
||||
let path = "/keys/upload";
|
||||
if (dehydratedDeviceId) {
|
||||
path = path + `/${encodeURIComponent(dehydratedDeviceId)}`;
|
||||
}
|
||||
return this._post(path, null, payload, options);
|
||||
return this._post(path, {}, payload, options);
|
||||
}
|
||||
|
||||
queryKeys(queryRequest, options = null) {
|
||||
return this._post("/keys/query", null, queryRequest, options);
|
||||
queryKeys(queryRequest: Record<string, any>, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._post("/keys/query", {}, queryRequest, options);
|
||||
}
|
||||
|
||||
claimKeys(payload, options = null) {
|
||||
return this._post("/keys/claim", null, payload, options);
|
||||
claimKeys(payload: Record<string, any>, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._post("/keys/claim", {}, payload, options);
|
||||
}
|
||||
|
||||
sendToDevice(type, payload, txnId, options = null) {
|
||||
return this._put(`/sendToDevice/${encodeURIComponent(type)}/${encodeURIComponent(txnId)}`, null, payload, options);
|
||||
sendToDevice(type: string, payload: Record<string, any>, txnId: string, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._put(`/sendToDevice/${encodeURIComponent(type)}/${encodeURIComponent(txnId)}`, {}, payload, options);
|
||||
}
|
||||
|
||||
roomKeysVersion(version = null, options = null) {
|
||||
roomKeysVersion(version?: string, options?: IRequestOptions): IHomeServerRequest {
|
||||
let versionPart = "";
|
||||
if (version) {
|
||||
versionPart = `/${encodeURIComponent(version)}`;
|
||||
}
|
||||
return this._get(`/room_keys/version${versionPart}`, null, null, options);
|
||||
return this._get(`/room_keys/version${versionPart}`, undefined, undefined, options);
|
||||
}
|
||||
|
||||
roomKeyForRoomAndSession(version, roomId, sessionId, options = null) {
|
||||
return this._get(`/room_keys/keys/${encodeURIComponent(roomId)}/${encodeURIComponent(sessionId)}`, {version}, null, options);
|
||||
roomKeyForRoomAndSession(version: string, roomId: string, sessionId: string, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._get(`/room_keys/keys/${encodeURIComponent(roomId)}/${encodeURIComponent(sessionId)}`, {version}, undefined, options);
|
||||
}
|
||||
|
||||
uploadAttachment(blob, filename, options = null) {
|
||||
uploadAttachment(blob: Blob, filename: string, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._authedRequest("POST", `${this._homeserver}/_matrix/media/r0/upload`, {filename}, blob, options);
|
||||
}
|
||||
|
||||
setPusher(pusher, options = null) {
|
||||
return this._post("/pushers/set", null, pusher, options);
|
||||
setPusher(pusher: Record<string, any>, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._post("/pushers/set", {}, pusher, options);
|
||||
}
|
||||
|
||||
getPushers(options = null) {
|
||||
return this._get("/pushers", null, null, options);
|
||||
getPushers(options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._get("/pushers", undefined, undefined, options);
|
||||
}
|
||||
|
||||
join(roomId, options = null) {
|
||||
return this._post(`/rooms/${encodeURIComponent(roomId)}/join`, null, null, options);
|
||||
join(roomId: string, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._post(`/rooms/${encodeURIComponent(roomId)}/join`, {}, {}, options);
|
||||
}
|
||||
|
||||
joinIdOrAlias(roomIdOrAlias, options = null) {
|
||||
return this._post(`/join/${encodeURIComponent(roomIdOrAlias)}`, null, null, options);
|
||||
joinIdOrAlias(roomIdOrAlias: string, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._post(`/join/${encodeURIComponent(roomIdOrAlias)}`, {}, {}, options);
|
||||
}
|
||||
|
||||
leave(roomId, options = null) {
|
||||
return this._post(`/rooms/${encodeURIComponent(roomId)}/leave`, null, null, options);
|
||||
leave(roomId: string, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._post(`/rooms/${encodeURIComponent(roomId)}/leave`, {}, {}, options);
|
||||
}
|
||||
|
||||
forget(roomId, options = null) {
|
||||
return this._post(`/rooms/${encodeURIComponent(roomId)}/forget`, null, null, options);
|
||||
forget(roomId: string, options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._post(`/rooms/${encodeURIComponent(roomId)}/forget`, {}, {}, options);
|
||||
}
|
||||
|
||||
logout(options = null) {
|
||||
return this._post(`/logout`, null, null, options);
|
||||
logout(options?: IRequestOptions): IHomeServerRequest {
|
||||
return this._post(`/logout`, {}, {}, options);
|
||||
}
|
||||
|
||||
getDehydratedDevice(options = {}) {
|
||||
getDehydratedDevice(options: IRequestOptions): IHomeServerRequest {
|
||||
options.prefix = DEHYDRATION_PREFIX;
|
||||
return this._get(`/dehydrated_device`, null, null, options);
|
||||
return this._get(`/dehydrated_device`, undefined, undefined, options);
|
||||
}
|
||||
|
||||
createDehydratedDevice(payload, options = {}) {
|
||||
createDehydratedDevice(payload: Record<string, any>, options: IRequestOptions): IHomeServerRequest {
|
||||
options.prefix = DEHYDRATION_PREFIX;
|
||||
return this._put(`/dehydrated_device`, null, payload, options);
|
||||
return this._put(`/dehydrated_device`, {}, payload, options);
|
||||
}
|
||||
|
||||
claimDehydratedDevice(deviceId, options = {}) {
|
||||
claimDehydratedDevice(deviceId: string, options: IRequestOptions): IHomeServerRequest {
|
||||
options.prefix = DEHYDRATION_PREFIX;
|
||||
return this._post(`/dehydrated_device/claim`, null, {device_id: deviceId}, options);
|
||||
return this._post(`/dehydrated_device/claim`, {}, {device_id: deviceId}, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -258,11 +276,13 @@ import {Request as MockRequest} from "../../mocks/Request.js";
|
|||
export function tests() {
|
||||
return {
|
||||
"superficial happy path for GET": async assert => {
|
||||
// @ts-ignore
|
||||
const hsApi = new HomeServerApi({
|
||||
request: () => new MockRequest().respond(200, 42),
|
||||
homeserver: "https://hs.tld"
|
||||
homeserver: "https://hs.tld",
|
||||
});
|
||||
const result = await hsApi._get("foo", null, null, null).response();
|
||||
// @ts-ignore
|
||||
const result = await hsApi._get("foo").response();
|
||||
assert.strictEqual(result, 42);
|
||||
}
|
||||
}
|
|
@ -16,9 +16,21 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import {HomeServerError, ConnectionError} from "../error.js";
|
||||
import type {RequestResult} from "../../platform/web/dom/request/fetch.js";
|
||||
import type {ILogItem} from "../../logging/types";
|
||||
|
||||
export class HomeServerRequest {
|
||||
constructor(method, url, sourceRequest, log) {
|
||||
export interface IHomeServerRequest {
|
||||
abort(): void;
|
||||
response(): Promise<any>;
|
||||
}
|
||||
|
||||
export class HomeServerRequest implements IHomeServerRequest {
|
||||
private readonly _log?: ILogItem;
|
||||
private _sourceRequest?: RequestResult;
|
||||
// as we add types for expected responses from hs, this could be a generic class instead
|
||||
private readonly _promise: Promise<any>;
|
||||
|
||||
constructor(method: string, url: string, sourceRequest: RequestResult, log?: ILogItem) {
|
||||
this._log = log;
|
||||
this._sourceRequest = sourceRequest;
|
||||
this._promise = sourceRequest.response().then(response => {
|
||||
|
@ -80,16 +92,16 @@ export class HomeServerRequest {
|
|||
});
|
||||
}
|
||||
|
||||
abort() {
|
||||
abort(): void {
|
||||
if (this._sourceRequest) {
|
||||
this._log?.set("aborted", true);
|
||||
this._sourceRequest.abort();
|
||||
// to mark that it was on purpose in above rejection handler
|
||||
this._sourceRequest = null;
|
||||
this._sourceRequest = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
response() {
|
||||
response(): Promise<any> {
|
||||
return this._promise;
|
||||
}
|
||||
}
|
|
@ -14,16 +14,22 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {encodeQueryParams} from "./common.js";
|
||||
import {encodeQueryParams} from "./common";
|
||||
import {decryptAttachment} from "../e2ee/attachment.js";
|
||||
import {Platform} from "../../platform/web/Platform.js";
|
||||
import {BlobHandle} from "../../platform/web/dom/BlobHandle.js";
|
||||
import type {Attachment, EncryptedFile} from "./types/response";
|
||||
|
||||
export class MediaRepository {
|
||||
constructor({homeserver, platform}) {
|
||||
private readonly _homeserver: string;
|
||||
private readonly _platform: Platform;
|
||||
|
||||
constructor({homeserver, platform}: {homeserver:string, platform: Platform}) {
|
||||
this._homeserver = homeserver;
|
||||
this._platform = platform;
|
||||
}
|
||||
|
||||
mxcUrlThumbnail(url, width, height, method) {
|
||||
mxcUrlThumbnail(url: string, width: number, height: number, method: "crop" | "scale"): string | null {
|
||||
const parts = this._parseMxcUrl(url);
|
||||
if (parts) {
|
||||
const [serverName, mediaId] = parts;
|
||||
|
@ -33,7 +39,7 @@ export class MediaRepository {
|
|||
return null;
|
||||
}
|
||||
|
||||
mxcUrl(url) {
|
||||
mxcUrl(url: string): string | null {
|
||||
const parts = this._parseMxcUrl(url);
|
||||
if (parts) {
|
||||
const [serverName, mediaId] = parts;
|
||||
|
@ -43,7 +49,7 @@ export class MediaRepository {
|
|||
}
|
||||
}
|
||||
|
||||
_parseMxcUrl(url) {
|
||||
private _parseMxcUrl(url: string): string[] | null {
|
||||
const prefix = "mxc://";
|
||||
if (url.startsWith(prefix)) {
|
||||
return url.substr(prefix.length).split("/", 2);
|
||||
|
@ -52,24 +58,24 @@ export class MediaRepository {
|
|||
}
|
||||
}
|
||||
|
||||
async downloadEncryptedFile(fileEntry, cache = false) {
|
||||
async downloadEncryptedFile(fileEntry: EncryptedFile, cache: boolean = false): Promise<BlobHandle> {
|
||||
const url = this.mxcUrl(fileEntry.url);
|
||||
const {body: encryptedBuffer} = await this._platform.request(url, {method: "GET", format: "buffer", cache}).response();
|
||||
const decryptedBuffer = await decryptAttachment(this._platform, encryptedBuffer, fileEntry);
|
||||
return this._platform.createBlob(decryptedBuffer, fileEntry.mimetype);
|
||||
}
|
||||
|
||||
async downloadPlaintextFile(mxcUrl, mimetype, cache = false) {
|
||||
async downloadPlaintextFile(mxcUrl: string, mimetype: string, cache: boolean = false): Promise<BlobHandle> {
|
||||
const url = this.mxcUrl(mxcUrl);
|
||||
const {body: buffer} = await this._platform.request(url, {method: "GET", format: "buffer", cache}).response();
|
||||
return this._platform.createBlob(buffer, mimetype);
|
||||
}
|
||||
|
||||
async downloadAttachment(content, cache = false) {
|
||||
async downloadAttachment(content: Attachment, cache: boolean = false): Promise<BlobHandle> {
|
||||
if (content.file) {
|
||||
return this.downloadEncryptedFile(content.file, cache);
|
||||
} else {
|
||||
return this.downloadPlaintextFile(content.url, content.info?.mimetype, cache);
|
||||
return this.downloadPlaintextFile(content.url!, content.info?.mimetype, cache);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,42 +14,59 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {createEnum} from "../../utils/enum";
|
||||
import {ObservableValue} from "../../observable/ObservableValue";
|
||||
import type {ExponentialRetryDelay} from "./ExponentialRetryDelay";
|
||||
import type {TimeMeasure} from "../../platform/web/dom/Clock.js";
|
||||
import type {OnlineStatus} from "../../platform/web/dom/OnlineStatus.js";
|
||||
import type {VersionResponse} from "./types/response";
|
||||
import type {HomeServerApi} from "./HomeServerApi";
|
||||
|
||||
export const ConnectionStatus = createEnum(
|
||||
export enum ConnectionStatus {
|
||||
"Waiting",
|
||||
"Reconnecting",
|
||||
"Online"
|
||||
);
|
||||
};
|
||||
|
||||
type Ctor = {
|
||||
retryDelay: ExponentialRetryDelay;
|
||||
createMeasure: () => TimeMeasure;
|
||||
onlineStatus: OnlineStatus
|
||||
};
|
||||
|
||||
export class Reconnector {
|
||||
constructor({retryDelay, createMeasure, onlineStatus}) {
|
||||
private readonly _retryDelay: ExponentialRetryDelay;
|
||||
private readonly _createTimeMeasure: () => TimeMeasure;
|
||||
private readonly _onlineStatus: OnlineStatus;
|
||||
private readonly _state: ObservableValue<ConnectionStatus>;
|
||||
private _isReconnecting: boolean;
|
||||
private _versionsResponse?: VersionResponse;
|
||||
private _stateSince: TimeMeasure;
|
||||
|
||||
constructor({retryDelay, createMeasure, onlineStatus}: Ctor) {
|
||||
this._onlineStatus = onlineStatus;
|
||||
this._retryDelay = retryDelay;
|
||||
this._createTimeMeasure = createMeasure;
|
||||
// assume online, and do our thing when something fails
|
||||
this._state = new ObservableValue(ConnectionStatus.Online);
|
||||
this._isReconnecting = false;
|
||||
this._versionsResponse = null;
|
||||
}
|
||||
|
||||
get lastVersionsResponse() {
|
||||
get lastVersionsResponse(): VersionResponse | undefined {
|
||||
return this._versionsResponse;
|
||||
}
|
||||
|
||||
get connectionStatus() {
|
||||
get connectionStatus(): ObservableValue<ConnectionStatus> {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
get retryIn() {
|
||||
get retryIn(): number {
|
||||
if (this._state.get() === ConnectionStatus.Waiting) {
|
||||
return this._retryDelay.nextValue - this._stateSince.measure();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
async onRequestFailed(hsApi) {
|
||||
async onRequestFailed(hsApi: HomeServerApi): Promise<void> {
|
||||
if (!this._isReconnecting) {
|
||||
this._isReconnecting = true;
|
||||
|
||||
|
@ -75,14 +92,14 @@ export class Reconnector {
|
|||
}
|
||||
}
|
||||
|
||||
tryNow() {
|
||||
tryNow(): void {
|
||||
if (this._retryDelay) {
|
||||
// this will interrupt this._retryDelay.waitForRetry() in _reconnectLoop
|
||||
this._retryDelay.abort();
|
||||
}
|
||||
}
|
||||
|
||||
_setState(state) {
|
||||
private _setState(state: ConnectionStatus): void {
|
||||
if (state !== this._state.get()) {
|
||||
if (state === ConnectionStatus.Waiting) {
|
||||
this._stateSince = this._createTimeMeasure();
|
||||
|
@ -93,8 +110,8 @@ export class Reconnector {
|
|||
}
|
||||
}
|
||||
|
||||
async _reconnectLoop(hsApi) {
|
||||
this._versionsResponse = null;
|
||||
private async _reconnectLoop(hsApi: HomeServerApi): Promise<void> {
|
||||
this._versionsResponse = undefined;
|
||||
this._retryDelay.reset();
|
||||
|
||||
while (!this._versionsResponse) {
|
||||
|
@ -120,7 +137,7 @@ export class Reconnector {
|
|||
|
||||
|
||||
import {Clock as MockClock} from "../../mocks/Clock.js";
|
||||
import {ExponentialRetryDelay} from "./ExponentialRetryDelay.js";
|
||||
import {ExponentialRetryDelay as _ExponentialRetryDelay} from "./ExponentialRetryDelay";
|
||||
import {ConnectionError} from "../error.js"
|
||||
|
||||
export function tests() {
|
||||
|
@ -146,13 +163,14 @@ export function tests() {
|
|||
const clock = new MockClock();
|
||||
const {createMeasure} = clock;
|
||||
const onlineStatus = new ObservableValue(false);
|
||||
const retryDelay = new ExponentialRetryDelay(clock.createTimeout);
|
||||
const retryDelay = new _ExponentialRetryDelay(clock.createTimeout);
|
||||
const reconnector = new Reconnector({retryDelay, onlineStatus, createMeasure});
|
||||
const {connectionStatus} = reconnector;
|
||||
const statuses = [];
|
||||
const statuses: ConnectionStatus[] = [];
|
||||
const subscription = reconnector.connectionStatus.subscribe(s => {
|
||||
statuses.push(s);
|
||||
});
|
||||
// @ts-ignore
|
||||
reconnector.onRequestFailed(createHsApiMock(1));
|
||||
await connectionStatus.waitFor(s => s === ConnectionStatus.Waiting).promise;
|
||||
clock.elapse(2000);
|
||||
|
@ -170,9 +188,10 @@ export function tests() {
|
|||
const clock = new MockClock();
|
||||
const {createMeasure} = clock;
|
||||
const onlineStatus = new ObservableValue(false);
|
||||
const retryDelay = new ExponentialRetryDelay(clock.createTimeout);
|
||||
const retryDelay = new _ExponentialRetryDelay(clock.createTimeout);
|
||||
const reconnector = new Reconnector({retryDelay, onlineStatus, createMeasure});
|
||||
const {connectionStatus} = reconnector;
|
||||
// @ts-ignore
|
||||
reconnector.onRequestFailed(createHsApiMock(1));
|
||||
await connectionStatus.waitFor(s => s === ConnectionStatus.Waiting).promise;
|
||||
onlineStatus.set(true); //skip waiting
|
|
@ -17,35 +17,45 @@ limitations under the License.
|
|||
|
||||
import {AbortError} from "../../utils/error";
|
||||
import {HomeServerError} from "../error.js";
|
||||
import {HomeServerApi} from "./HomeServerApi.js";
|
||||
import {ExponentialRetryDelay} from "./ExponentialRetryDelay.js";
|
||||
import {HomeServerApi} from "./HomeServerApi";
|
||||
import {ExponentialRetryDelay} from "./ExponentialRetryDelay";
|
||||
import {Clock} from "../../platform/web/dom/Clock.js";
|
||||
import type {IHomeServerRequest} from "./HomeServerRequest.js";
|
||||
|
||||
class Request {
|
||||
constructor(methodName, args) {
|
||||
this._methodName = methodName;
|
||||
this._args = args;
|
||||
class Request implements IHomeServerRequest {
|
||||
public readonly methodName: string;
|
||||
public readonly args: any[];
|
||||
public resolve: (result: any) => void;
|
||||
public reject: (error: Error) => void;
|
||||
public requestResult?: IHomeServerRequest;
|
||||
private readonly _responsePromise: Promise<any>;
|
||||
|
||||
constructor(methodName: string, args: any[]) {
|
||||
this.methodName = methodName;
|
||||
this.args = args;
|
||||
this._responsePromise = new Promise((resolve, reject) => {
|
||||
this._resolve = resolve;
|
||||
this._reject = reject;
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
});
|
||||
this._requestResult = null;
|
||||
}
|
||||
|
||||
abort() {
|
||||
if (this._requestResult) {
|
||||
this._requestResult.abort();
|
||||
abort(): void {
|
||||
if (this.requestResult) {
|
||||
this.requestResult.abort();
|
||||
} else {
|
||||
this._reject(new AbortError());
|
||||
this.reject(new AbortError());
|
||||
}
|
||||
}
|
||||
|
||||
response() {
|
||||
response(): Promise<any> {
|
||||
return this._responsePromise;
|
||||
}
|
||||
}
|
||||
|
||||
class HomeServerApiWrapper {
|
||||
constructor(scheduler) {
|
||||
private readonly _scheduler: RequestScheduler;
|
||||
|
||||
constructor(scheduler: RequestScheduler) {
|
||||
this._scheduler = scheduler;
|
||||
}
|
||||
}
|
||||
|
@ -60,21 +70,22 @@ for (const methodName of Object.getOwnPropertyNames(HomeServerApi.prototype)) {
|
|||
}
|
||||
|
||||
export class RequestScheduler {
|
||||
constructor({hsApi, clock}) {
|
||||
private readonly _hsApi: HomeServerApi;
|
||||
private readonly _clock: Clock;
|
||||
private readonly _requests: Set<Request> = new Set();
|
||||
private _stopped = false;
|
||||
private _wrapper = new HomeServerApiWrapper(this);
|
||||
|
||||
constructor({ hsApi, clock }: { hsApi: HomeServerApi; clock: Clock }) {
|
||||
this._hsApi = hsApi;
|
||||
this._clock = clock;
|
||||
this._requests = new Set();
|
||||
this._isRateLimited = false;
|
||||
this._isDrainingRateLimit = false;
|
||||
this._stopped = true;
|
||||
this._wrapper = new HomeServerApiWrapper(this);
|
||||
}
|
||||
|
||||
get hsApi() {
|
||||
return this._wrapper;
|
||||
get hsApi(): HomeServerApi {
|
||||
return this._wrapper as unknown as HomeServerApi;
|
||||
}
|
||||
|
||||
stop() {
|
||||
stop(): void {
|
||||
this._stopped = true;
|
||||
for (const request of this._requests) {
|
||||
request.abort();
|
||||
|
@ -82,40 +93,49 @@ export class RequestScheduler {
|
|||
this._requests.clear();
|
||||
}
|
||||
|
||||
start() {
|
||||
start(): void {
|
||||
this._stopped = false;
|
||||
}
|
||||
|
||||
_hsApiRequest(name, args) {
|
||||
private _hsApiRequest(name: string, args: any[]): Request {
|
||||
const request = new Request(name, args);
|
||||
this._doSend(request);
|
||||
return request;
|
||||
}
|
||||
|
||||
async _doSend(request) {
|
||||
private async _doSend(request: Request): Promise<void> {
|
||||
this._requests.add(request);
|
||||
try {
|
||||
let retryDelay;
|
||||
let retryDelay: ExponentialRetryDelay | undefined;
|
||||
while (!this._stopped) {
|
||||
try {
|
||||
const requestResult = this._hsApi[request._methodName].apply(this._hsApi, request._args);
|
||||
const requestResult = this._hsApi[
|
||||
request.methodName
|
||||
].apply(this._hsApi, request.args);
|
||||
// so the request can be aborted
|
||||
request._requestResult = requestResult;
|
||||
request.requestResult = requestResult;
|
||||
const response = await requestResult.response();
|
||||
request._resolve(response);
|
||||
request.resolve(response);
|
||||
return;
|
||||
} catch (err) {
|
||||
if (err instanceof HomeServerError && err.errcode === "M_LIMIT_EXCEEDED") {
|
||||
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();
|
||||
await this._clock
|
||||
.createTimeout(err.retry_after_ms)
|
||||
.elapsed();
|
||||
} else {
|
||||
if (!retryDelay) {
|
||||
retryDelay = new ExponentialRetryDelay(this._clock.createTimeout);
|
||||
retryDelay = new ExponentialRetryDelay(
|
||||
this._clock.createTimeout
|
||||
);
|
||||
}
|
||||
await retryDelay.waitForRetry();
|
||||
}
|
||||
} else {
|
||||
request._reject(err);
|
||||
request.reject(err);
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -15,7 +15,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
export function encodeQueryParams(queryParams) {
|
||||
import {BlobHandle} from "../../platform/web/dom/BlobHandle.js";
|
||||
|
||||
export type EncodedBody = {
|
||||
mimeType: string;
|
||||
body: BlobHandle | string;
|
||||
}
|
||||
|
||||
export function encodeQueryParams(queryParams?: object): string {
|
||||
return Object.entries(queryParams || {})
|
||||
.filter(([, value]) => value !== undefined)
|
||||
.map(([name, value]) => {
|
||||
|
@ -27,21 +34,19 @@ export function encodeQueryParams(queryParams) {
|
|||
.join("&");
|
||||
}
|
||||
|
||||
export function encodeBody(body) {
|
||||
if (body.nativeBlob && body.mimeType) {
|
||||
const blob = body;
|
||||
export function encodeBody(body: BlobHandle | object): EncodedBody {
|
||||
if (body instanceof BlobHandle) {
|
||||
const blob = body as BlobHandle;
|
||||
return {
|
||||
mimeType: blob.mimeType,
|
||||
body: blob, // will be unwrapped in request fn
|
||||
length: blob.size
|
||||
body: blob // will be unwrapped in request fn
|
||||
};
|
||||
} else if (typeof body === "object") {
|
||||
const json = JSON.stringify(body);
|
||||
return {
|
||||
mimeType: "application/json",
|
||||
body: json,
|
||||
length: body.length
|
||||
};
|
||||
body: json
|
||||
}
|
||||
} else {
|
||||
throw new Error("Unknown body type: " + body);
|
||||
}
|
|
@ -14,29 +14,36 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
AbortError,
|
||||
ConnectionError
|
||||
} from "../../error.js";
|
||||
import {AbortError, ConnectionError} from "../../error.js";
|
||||
import type {IRequestOptions, RequestFunction} from "../../../platform/types/types";
|
||||
import type {RequestResult} from "../../../platform/web/dom/request/fetch.js";
|
||||
|
||||
type Options = IRequestOptions & {
|
||||
method?: any;
|
||||
delay?: boolean;
|
||||
}
|
||||
|
||||
class RequestLogItem {
|
||||
constructor(url, options) {
|
||||
public readonly url: string;
|
||||
public readonly options: Options;
|
||||
public error: {aborted: boolean, network: boolean, message: string};
|
||||
public status: number;
|
||||
public body: Response["body"];
|
||||
public start: number = performance.now();
|
||||
public end: number = 0;
|
||||
|
||||
constructor(url: string, options: 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) {
|
||||
async handleResponse(response: Response) {
|
||||
this.end = performance.now();
|
||||
this.status = response.status;
|
||||
this.body = response.body;
|
||||
}
|
||||
|
||||
handleError(err) {
|
||||
handleError(err: Error): void {
|
||||
this.end = performance.now();
|
||||
this.error = {
|
||||
aborted: err instanceof AbortError,
|
||||
|
@ -47,13 +54,15 @@ class RequestLogItem {
|
|||
}
|
||||
|
||||
export class RecordRequester {
|
||||
constructor(request) {
|
||||
private readonly _origRequest: RequestFunction;
|
||||
private readonly _requestLog: RequestLogItem[] = [];
|
||||
|
||||
constructor(request: RequestFunction) {
|
||||
this._origRequest = request;
|
||||
this._requestLog = [];
|
||||
this.request = this.request.bind(this);
|
||||
}
|
||||
|
||||
request(url, options) {
|
||||
request(url: string, options: Options): RequestResult {
|
||||
const requestItem = new RequestLogItem(url, options);
|
||||
this._requestLog.push(requestItem);
|
||||
try {
|
||||
|
@ -68,24 +77,27 @@ export class RecordRequester {
|
|||
}
|
||||
}
|
||||
|
||||
log() {
|
||||
log(): RequestLogItem[] {
|
||||
return this._requestLog;
|
||||
}
|
||||
}
|
||||
|
||||
export class ReplayRequester {
|
||||
constructor(log, options) {
|
||||
private readonly _log: RequestLogItem[];
|
||||
private readonly _options: Options;
|
||||
|
||||
constructor(log: RequestLogItem[], options: Options) {
|
||||
this._log = log.slice();
|
||||
this._options = options;
|
||||
this.request = this.request.bind(this);
|
||||
}
|
||||
|
||||
request(url, options) {
|
||||
const idx = this._log.findIndex(item => {
|
||||
request(url: string, options: Options): ReplayRequestResult {
|
||||
const idx = this._log.findIndex((item) => {
|
||||
return item.url === url && options.method === item.options.method;
|
||||
});
|
||||
if (idx === -1) {
|
||||
return new ReplayRequestResult({status: 404}, options);
|
||||
return new ReplayRequestResult({ status: 404 } as RequestLogItem, options);
|
||||
} else {
|
||||
const [item] = this._log.splice(idx, 1);
|
||||
return new ReplayRequestResult(item, options);
|
||||
|
@ -94,17 +106,21 @@ export class ReplayRequester {
|
|||
}
|
||||
|
||||
class ReplayRequestResult {
|
||||
constructor(item, options) {
|
||||
private readonly _item: RequestLogItem;
|
||||
private readonly _options: Options;
|
||||
private _aborted: boolean;
|
||||
|
||||
constructor(item: RequestLogItem, options: Options) {
|
||||
this._item = item;
|
||||
this._options = options;
|
||||
this._aborted = false;
|
||||
}
|
||||
|
||||
abort() {
|
||||
abort(): void {
|
||||
this._aborted = true;
|
||||
}
|
||||
|
||||
async response() {
|
||||
async response(): Promise<RequestLogItem> {
|
||||
if (this._options.delay) {
|
||||
const delay = this._item.end - this._item.start;
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
58
src/matrix/net/types/response.ts
Normal file
58
src/matrix/net/types/response.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
export type Attachment = {
|
||||
body: string;
|
||||
info: AttachmentInfo;
|
||||
msgtype: string;
|
||||
url?: string;
|
||||
file?: EncryptedFile;
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
export type EncryptedFile = {
|
||||
key: JsonWebKey;
|
||||
iv: string;
|
||||
hashes: {
|
||||
sha256: string;
|
||||
};
|
||||
url: string;
|
||||
v: string;
|
||||
mimetype?: string;
|
||||
}
|
||||
|
||||
type AttachmentInfo = {
|
||||
h?: number;
|
||||
w?: number;
|
||||
mimetype: string;
|
||||
size: number;
|
||||
duration?: number;
|
||||
thumbnail_url?: string;
|
||||
thumbnail_file?: EncryptedFile;
|
||||
thumbnail_info?: ThumbnailInfo;
|
||||
}
|
||||
|
||||
type ThumbnailInfo = {
|
||||
h: number;
|
||||
w: number;
|
||||
mimetype: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export type VersionResponse = {
|
||||
versions: string[];
|
||||
unstable_features?: Record<string, boolean>;
|
||||
}
|
33
src/platform/types/types.ts
Normal file
33
src/platform/types/types.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import type {RequestResult} from "../web/dom/request/fetch.js";
|
||||
import type {EncodedBody} from "../../matrix/net/common";
|
||||
import type {ILogItem} from "../../logging/types";
|
||||
|
||||
export interface IRequestOptions {
|
||||
uploadProgress?: (loadedBytes: number) => void;
|
||||
timeout?: number;
|
||||
body?: EncodedBody;
|
||||
headers?: Map<string, string|number>;
|
||||
cache?: boolean;
|
||||
log?: ILogItem;
|
||||
prefix?: string;
|
||||
method?: string;
|
||||
format?: string;
|
||||
}
|
||||
|
||||
export type RequestFunction = (url: string, options: IRequestOptions) => RequestResult;
|
|
@ -60,7 +60,6 @@ class Interval {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class TimeMeasure {
|
||||
constructor() {
|
||||
this._start = window.performance.now();
|
||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// import {RecordRequester, ReplayRequester} from "./matrix/net/request/replay.js";
|
||||
// import {RecordRequester, ReplayRequester} from "./matrix/net/request/replay";
|
||||
import {SessionContainer} from "../../matrix/SessionContainer.js";
|
||||
import {RootViewModel} from "../../domain/RootViewModel.js";
|
||||
import {createNavigation, createRouter} from "../../domain/navigation/index.js";
|
||||
|
|
Loading…
Reference in a new issue