1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-26 17:03:12 +03:00

Merge pull request #1907 from matrix-org/gsouquet/ts-last-push

This commit is contained in:
Germain
2021-09-15 08:30:52 +01:00
committed by GitHub
4 changed files with 129 additions and 198 deletions

View File

@@ -17,79 +17,19 @@ limitations under the License.
/** @module auto-discovery */ /** @module auto-discovery */
import { IClientWellKnown, IWellKnownConfig } from "./client";
import { logger } from './logger'; import { logger } from './logger';
import { URL as NodeURL } from "url"; import { URL as NodeURL } from "url";
// Dev note: Auto discovery is part of the spec. // Dev note: Auto discovery is part of the spec.
// See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery // See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
/** export enum AutoDiscoveryAction {
* Description for what an automatically discovered client configuration SUCCESS = "SUCCESS",
* would look like. Although this is a class, it is recommended that it IGNORE = "IGNORE",
* be treated as an interface definition rather than as a class. PROMPT = "PROMPT",
* FAIL_PROMPT = "FAIL_PROMPT",
* Additional properties than those defined here may be present, and FAIL_ERROR = "FAIL_ERROR",
* should follow the Java package naming convention.
*/
class DiscoveredClientConfig { // eslint-disable-line no-unused-vars
// Dev note: this is basically a copy/paste of the .well-known response
// object as defined in the spec. It does have additional information,
// however. Overall, this exists to serve as a place for documentation
// and not functionality.
// See https://matrix.org/docs/spec/client_server/r0.4.0.html#get-well-known-matrix-client
constructor() {
/**
* The homeserver configuration the client should use. This will
* always be present on the object.
* @type {{state: string, base_url: string}} The configuration.
*/
this["m.homeserver"] = {
/**
* The lookup result state. If this is anything other than
* AutoDiscovery.SUCCESS then base_url may be falsey. Additionally,
* if this is not AutoDiscovery.SUCCESS then the client should
* assume the other properties in the client config (such as
* the identity server configuration) are not valid.
*/
state: AutoDiscovery.PROMPT,
/**
* If the state is AutoDiscovery.FAIL_ERROR or .FAIL_PROMPT
* then this will contain a human-readable (English) message
* for what went wrong. If the state is none of those previously
* mentioned, this will be falsey.
*/
error: "Something went wrong",
/**
* The base URL clients should use to talk to the homeserver,
* particularly for the login process. May be falsey if the
* state is not AutoDiscovery.SUCCESS.
*/
base_url: "https://matrix.org",
};
/**
* The identity server configuration the client should use. This
* will always be present on teh object.
* @type {{state: string, base_url: string}} The configuration.
*/
this["m.identity_server"] = {
/**
* The lookup result state. If this is anything other than
* AutoDiscovery.SUCCESS then base_url may be falsey.
*/
state: AutoDiscovery.PROMPT,
/**
* The base URL clients should use for interacting with the
* identity server. May be falsey if the state is not
* AutoDiscovery.SUCCESS.
*/
base_url: "https://vector.im",
};
}
} }
/** /**
@@ -102,44 +42,26 @@ export class AutoDiscovery {
// translate the meaning of the states in the spec, but also // translate the meaning of the states in the spec, but also
// support our own if needed. // support our own if needed.
static get ERROR_INVALID() { public static readonly ERROR_INVALID = "Invalid homeserver discovery response";
return "Invalid homeserver discovery response";
}
static get ERROR_GENERIC_FAILURE() { public static readonly ERROR_GENERIC_FAILURE = "Failed to get autodiscovery configuration from server";
return "Failed to get autodiscovery configuration from server";
}
static get ERROR_INVALID_HS_BASE_URL() { public static readonly ERROR_INVALID_HS_BASE_URL = "Invalid base_url for m.homeserver";
return "Invalid base_url for m.homeserver";
}
static get ERROR_INVALID_HOMESERVER() { public static readonly ERROR_INVALID_HOMESERVER = "Homeserver URL does not appear to be a valid Matrix homeserver";
return "Homeserver URL does not appear to be a valid Matrix homeserver";
}
static get ERROR_INVALID_IS_BASE_URL() { public static readonly ERROR_INVALID_IS_BASE_URL = "Invalid base_url for m.identity_server";
return "Invalid base_url for m.identity_server";
}
static get ERROR_INVALID_IDENTITY_SERVER() { // eslint-disable-next-line
return "Identity server URL does not appear to be a valid identity server"; public static readonly ERROR_INVALID_IDENTITY_SERVER = "Identity server URL does not appear to be a valid identity server";
}
static get ERROR_INVALID_IS() { public static readonly ERROR_INVALID_IS = "Invalid identity server discovery response";
return "Invalid identity server discovery response";
}
static get ERROR_MISSING_WELLKNOWN() { public static readonly ERROR_MISSING_WELLKNOWN = "No .well-known JSON file found";
return "No .well-known JSON file found";
}
static get ERROR_INVALID_JSON() { public static readonly ERROR_INVALID_JSON = "Invalid JSON";
return "Invalid JSON";
}
static get ALL_ERRORS() { public static readonly ALL_ERRORS = [
return [
AutoDiscovery.ERROR_INVALID, AutoDiscovery.ERROR_INVALID,
AutoDiscovery.ERROR_GENERIC_FAILURE, AutoDiscovery.ERROR_GENERIC_FAILURE,
AutoDiscovery.ERROR_INVALID_HS_BASE_URL, AutoDiscovery.ERROR_INVALID_HS_BASE_URL,
@@ -150,7 +72,6 @@ export class AutoDiscovery {
AutoDiscovery.ERROR_MISSING_WELLKNOWN, AutoDiscovery.ERROR_MISSING_WELLKNOWN,
AutoDiscovery.ERROR_INVALID_JSON, AutoDiscovery.ERROR_INVALID_JSON,
]; ];
}
/** /**
* The auto discovery failed. The client is expected to communicate * The auto discovery failed. The client is expected to communicate
@@ -158,7 +79,7 @@ export class AutoDiscovery {
* @return {string} * @return {string}
* @constructor * @constructor
*/ */
static get FAIL_ERROR() { return "FAIL_ERROR"; } public static readonly FAIL_ERROR = AutoDiscoveryAction.FAIL_ERROR;
/** /**
* The auto discovery failed, however the client may still recover * The auto discovery failed, however the client may still recover
@@ -169,7 +90,7 @@ export class AutoDiscovery {
* @return {string} * @return {string}
* @constructor * @constructor
*/ */
static get FAIL_PROMPT() { return "FAIL_PROMPT"; } public static readonly FAIL_PROMPT = AutoDiscoveryAction.FAIL_PROMPT;
/** /**
* The auto discovery didn't fail but did not find anything of * The auto discovery didn't fail but did not find anything of
@@ -178,14 +99,14 @@ export class AutoDiscovery {
* @return {string} * @return {string}
* @constructor * @constructor
*/ */
static get PROMPT() { return "PROMPT"; } public static readonly PROMPT = AutoDiscoveryAction.PROMPT;
/** /**
* The auto discovery was successful. * The auto discovery was successful.
* @return {string} * @return {string}
* @constructor * @constructor
*/ */
static get SUCCESS() { return "SUCCESS"; } public static readonly SUCCESS = AutoDiscoveryAction.SUCCESS;
/** /**
* Validates and verifies client configuration information for purposes * Validates and verifies client configuration information for purposes
@@ -199,7 +120,7 @@ export class AutoDiscovery {
* configuration, which may include error states. Rejects on unexpected * configuration, which may include error states. Rejects on unexpected
* failure, not when verification fails. * failure, not when verification fails.
*/ */
static async fromDiscoveryConfig(wellknown) { public static async fromDiscoveryConfig(wellknown: string): Promise<IClientWellKnown> {
// Step 1 is to get the config, which is provided to us here. // Step 1 is to get the config, which is provided to us here.
// We default to an error state to make the first few checks easier to // We default to an error state to make the first few checks easier to
@@ -240,7 +161,7 @@ export class AutoDiscovery {
// Step 2: Make sure the homeserver URL is valid *looking*. We'll make // Step 2: Make sure the homeserver URL is valid *looking*. We'll make
// sure it points to a homeserver in Step 3. // sure it points to a homeserver in Step 3.
const hsUrl = this._sanitizeWellKnownUrl( const hsUrl = this.sanitizeWellKnownUrl(
wellknown["m.homeserver"]["base_url"], wellknown["m.homeserver"]["base_url"],
); );
if (!hsUrl) { if (!hsUrl) {
@@ -250,7 +171,7 @@ export class AutoDiscovery {
} }
// Step 3: Make sure the homeserver URL points to a homeserver. // Step 3: Make sure the homeserver URL points to a homeserver.
const hsVersions = await this._fetchWellKnownObject( const hsVersions = await this.fetchWellKnownObject(
`${hsUrl}/_matrix/client/versions`, `${hsUrl}/_matrix/client/versions`,
); );
if (!hsVersions || !hsVersions.raw["versions"]) { if (!hsVersions || !hsVersions.raw["versions"]) {
@@ -272,7 +193,7 @@ export class AutoDiscovery {
}; };
// Step 5: Try to pull out the identity server configuration // Step 5: Try to pull out the identity server configuration
let isUrl = ""; let isUrl: string | boolean = "";
if (wellknown["m.identity_server"]) { if (wellknown["m.identity_server"]) {
// We prepare a failing identity server response to save lines later // We prepare a failing identity server response to save lines later
// in this branch. // in this branch.
@@ -287,7 +208,7 @@ export class AutoDiscovery {
// Step 5a: Make sure the URL is valid *looking*. We'll make sure it // Step 5a: Make sure the URL is valid *looking*. We'll make sure it
// points to an identity server in Step 5b. // points to an identity server in Step 5b.
isUrl = this._sanitizeWellKnownUrl( isUrl = this.sanitizeWellKnownUrl(
wellknown["m.identity_server"]["base_url"], wellknown["m.identity_server"]["base_url"],
); );
if (!isUrl) { if (!isUrl) {
@@ -299,10 +220,10 @@ export class AutoDiscovery {
// Step 5b: Verify there is an identity server listening on the provided // Step 5b: Verify there is an identity server listening on the provided
// URL. // URL.
const isResponse = await this._fetchWellKnownObject( const isResponse = await this.fetchWellKnownObject(
`${isUrl}/_matrix/identity/api/v1`, `${isUrl}/_matrix/identity/api/v1`,
); );
if (!isResponse || !isResponse.raw || isResponse.action !== "SUCCESS") { if (!isResponse || !isResponse.raw || isResponse.action !== AutoDiscoveryAction.SUCCESS) {
logger.error("Invalid /api/v1 response"); logger.error("Invalid /api/v1 response");
failingClientConfig["m.identity_server"].error = failingClientConfig["m.identity_server"].error =
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER; AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER;
@@ -317,7 +238,7 @@ export class AutoDiscovery {
// Step 6: Now that the identity server is valid, or never existed, // Step 6: Now that the identity server is valid, or never existed,
// populate the IS section. // populate the IS section.
if (isUrl && isUrl.length > 0) { if (isUrl && isUrl.toString().length > 0) {
clientConfig["m.identity_server"] = { clientConfig["m.identity_server"] = {
state: AutoDiscovery.SUCCESS, state: AutoDiscovery.SUCCESS,
error: null, error: null,
@@ -359,7 +280,7 @@ export class AutoDiscovery {
* configuration, which may include error states. Rejects on unexpected * configuration, which may include error states. Rejects on unexpected
* failure, not when discovery fails. * failure, not when discovery fails.
*/ */
static async findClientConfig(domain) { public static async findClientConfig(domain: string): Promise<IClientWellKnown> {
if (!domain || typeof(domain) !== "string" || domain.length === 0) { if (!domain || typeof(domain) !== "string" || domain.length === 0) {
throw new Error("'domain' must be a string of non-zero length"); throw new Error("'domain' must be a string of non-zero length");
} }
@@ -395,13 +316,13 @@ export class AutoDiscovery {
// Step 1: Actually request the .well-known JSON file and make sure it // Step 1: Actually request the .well-known JSON file and make sure it
// at least has a homeserver definition. // at least has a homeserver definition.
const wellknown = await this._fetchWellKnownObject( const wellknown = await this.fetchWellKnownObject(
`https://${domain}/.well-known/matrix/client`, `https://${domain}/.well-known/matrix/client`,
); );
if (!wellknown || wellknown.action !== "SUCCESS") { if (!wellknown || wellknown.action !== AutoDiscoveryAction.SUCCESS) {
logger.error("No response or error when parsing .well-known"); logger.error("No response or error when parsing .well-known");
if (wellknown.reason) logger.error(wellknown.reason); if (wellknown.reason) logger.error(wellknown.reason);
if (wellknown.action === "IGNORE") { if (wellknown.action === AutoDiscoveryAction.IGNORE) {
clientConfig["m.homeserver"] = { clientConfig["m.homeserver"] = {
state: AutoDiscovery.PROMPT, state: AutoDiscovery.PROMPT,
error: null, error: null,
@@ -427,12 +348,12 @@ export class AutoDiscovery {
* @returns {Promise<object>} Resolves to the domain's client config. Can * @returns {Promise<object>} Resolves to the domain's client config. Can
* be an empty object. * be an empty object.
*/ */
static async getRawClientConfig(domain) { public static async getRawClientConfig(domain: string): Promise<IClientWellKnown> {
if (!domain || typeof(domain) !== "string" || domain.length === 0) { if (!domain || typeof(domain) !== "string" || domain.length === 0) {
throw new Error("'domain' must be a string of non-zero length"); throw new Error("'domain' must be a string of non-zero length");
} }
const response = await this._fetchWellKnownObject( const response = await this.fetchWellKnownObject(
`https://${domain}/.well-known/matrix/client`, `https://${domain}/.well-known/matrix/client`,
); );
if (!response) return {}; if (!response) return {};
@@ -447,7 +368,7 @@ export class AutoDiscovery {
* @return {string|boolean} The sanitized URL or a falsey value if the URL is invalid. * @return {string|boolean} The sanitized URL or a falsey value if the URL is invalid.
* @private * @private
*/ */
static _sanitizeWellKnownUrl(url) { private static sanitizeWellKnownUrl(url: string): string | boolean {
if (!url) return false; if (!url) return false;
try { try {
@@ -495,8 +416,9 @@ export class AutoDiscovery {
* @return {Promise<object>} Resolves to the returned state. * @return {Promise<object>} Resolves to the returned state.
* @private * @private
*/ */
static async _fetchWellKnownObject(url) { private static async fetchWellKnownObject(url: string): Promise<IWellKnownConfig> {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
// eslint-disable-next-line
const request = require("./matrix").getRequest(); const request = require("./matrix").getRequest();
if (!request) throw new Error("No request library available"); if (!request) throw new Error("No request library available");
request( request(
@@ -505,10 +427,10 @@ export class AutoDiscovery {
if (err || response && if (err || response &&
(response.statusCode < 200 || response.statusCode >= 300) (response.statusCode < 200 || response.statusCode >= 300)
) { ) {
let action = "FAIL_PROMPT"; let action = AutoDiscoveryAction.FAIL_PROMPT;
let reason = (err ? err.message : null) || "General failure"; let reason = (err ? err.message : null) || "General failure";
if (response && response.statusCode === 404) { if (response && response.statusCode === 404) {
action = "IGNORE"; action = AutoDiscoveryAction.IGNORE;
reason = AutoDiscovery.ERROR_MISSING_WELLKNOWN; reason = AutoDiscovery.ERROR_MISSING_WELLKNOWN;
} }
resolve({ raw: {}, action: action, reason: reason, error: err }); resolve({ raw: {}, action: action, reason: reason, error: err });
@@ -516,7 +438,7 @@ export class AutoDiscovery {
} }
try { try {
resolve({ raw: JSON.parse(body), action: "SUCCESS" }); resolve({ raw: JSON.parse(body), action: AutoDiscoveryAction.SUCCESS });
} catch (e) { } catch (e) {
let reason = AutoDiscovery.ERROR_INVALID; let reason = AutoDiscovery.ERROR_INVALID;
if (e.name === "SyntaxError") { if (e.name === "SyntaxError") {
@@ -524,7 +446,7 @@ export class AutoDiscovery {
} }
resolve({ resolve({
raw: {}, raw: {},
action: "FAIL_PROMPT", action: AutoDiscoveryAction.FAIL_PROMPT,
reason: reason, reason: reason,
error: e, error: e,
}); });

View File

@@ -31,7 +31,7 @@ import { sleep } from './utils';
import { Group } from "./models/group"; import { Group } from "./models/group";
import { Direction, EventTimeline } from "./models/event-timeline"; import { Direction, EventTimeline } from "./models/event-timeline";
import { IActionsObject, PushProcessor } from "./pushprocessor"; import { IActionsObject, PushProcessor } from "./pushprocessor";
import { AutoDiscovery } from "./autodiscovery"; import { AutoDiscovery, AutoDiscoveryAction } from "./autodiscovery";
import * as olmlib from "./crypto/olmlib"; import * as olmlib from "./crypto/olmlib";
import { decodeBase64, encodeBase64 } from "./crypto/olmlib"; import { decodeBase64, encodeBase64 } from "./crypto/olmlib";
import { IExportedDevice as IOlmDevice } from "./crypto/OlmDevice"; import { IExportedDevice as IOlmDevice } from "./crypto/OlmDevice";
@@ -476,14 +476,19 @@ interface IServerVersions {
unstable_features: Record<string, boolean>; unstable_features: Record<string, boolean>;
} }
interface IClientWellKnown { export interface IClientWellKnown {
[key: string]: any; [key: string]: any;
"m.homeserver": { "m.homeserver"?: IWellKnownConfig;
base_url: string; "m.identity_server"?: IWellKnownConfig;
}; }
"m.identity_server"?: {
base_url: string; export interface IWellKnownConfig {
}; raw?: any; // todo typings
action?: AutoDiscoveryAction;
reason?: string;
error?: Error | string;
// eslint-disable-next-line
base_url?: string | null;
} }
interface IKeyBackupPath { interface IKeyBackupPath {

View File

@@ -1,6 +1,6 @@
// can't just do InvalidStoreError extends Error // can't just do InvalidStoreError extends Error
// because of http://babeljs.io/docs/usage/caveats/#classes // because of http://babeljs.io/docs/usage/caveats/#classes
export function InvalidStoreError(reason, value) { export function InvalidStoreError(reason: string, value: boolean): void {
const message = `Store is invalid because ${reason}, ` + const message = `Store is invalid because ${reason}, ` +
`please stop the client, delete all data and start the client again`; `please stop the client, delete all data and start the client again`;
const instance = Reflect.construct(Error, [message]); const instance = Reflect.construct(Error, [message]);
@@ -22,7 +22,7 @@ InvalidStoreError.prototype = Object.create(Error.prototype, {
}); });
Reflect.setPrototypeOf(InvalidStoreError, Error); Reflect.setPrototypeOf(InvalidStoreError, Error);
export function InvalidCryptoStoreError(reason) { export function InvalidCryptoStoreError(reason: string): void {
const message = `Crypto store is invalid because ${reason}, ` + const message = `Crypto store is invalid because ${reason}, ` +
`please stop the client, delete all data and start the client again`; `please stop the client, delete all data and start the client again`;
const instance = Reflect.construct(Error, [message]); const instance = Reflect.construct(Error, [message]);
@@ -45,8 +45,7 @@ InvalidCryptoStoreError.prototype = Object.create(Error.prototype, {
Reflect.setPrototypeOf(InvalidCryptoStoreError, Error); Reflect.setPrototypeOf(InvalidCryptoStoreError, Error);
export class KeySignatureUploadError extends Error { export class KeySignatureUploadError extends Error {
constructor(message, value) { constructor(message: string, public value: { failures: any }) { // TODO: types
super(message); super(message);
this.value = value;
} }
} }

View File

@@ -31,17 +31,22 @@ import { logger } from './logger';
const TIMER_CHECK_PERIOD_MS = 1000; const TIMER_CHECK_PERIOD_MS = 1000;
// counter, for making up ids to return from setTimeout // counter, for making up ids to return from setTimeout
let _count = 0; let count = 0;
// the key for our callback with the real global.setTimeout // the key for our callback with the real global.setTimeout
let _realCallbackKey; let realCallbackKey: NodeJS.Timeout | number;
// a sorted list of the callbacks to be run. // a sorted list of the callbacks to be run.
// each is an object with keys [runAt, func, params, key]. // each is an object with keys [runAt, func, params, key].
const _callbackList = []; const callbackList: {
runAt: number;
func: (...params: any[]) => void;
params: any[];
key: number;
}[] = [];
// var debuglog = logger.log.bind(logger); // var debuglog = logger.log.bind(logger);
const debuglog = function() {}; const debuglog = function(...params: any[]) {};
/** /**
* Replace the function used by this module to get the current time. * Replace the function used by this module to get the current time.
@@ -52,10 +57,10 @@ const debuglog = function() {};
* *
* @internal * @internal
*/ */
export function setNow(f) { export function setNow(f: () => number): void {
_now = f || Date.now; now = f || Date.now;
} }
let _now = Date.now; let now = Date.now;
/** /**
* reimplementation of window.setTimeout, which will call the callback if * reimplementation of window.setTimeout, which will call the callback if
@@ -67,15 +72,14 @@ let _now = Date.now;
* @return {Number} an identifier for this callback, which may be passed into * @return {Number} an identifier for this callback, which may be passed into
* clearTimeout later. * clearTimeout later.
*/ */
export function setTimeout(func, delayMs) { export function setTimeout(func: (...params: any[]) => void, delayMs: number, ...params: any[]): number {
delayMs = delayMs || 0; delayMs = delayMs || 0;
if (delayMs < 0) { if (delayMs < 0) {
delayMs = 0; delayMs = 0;
} }
const params = Array.prototype.slice.call(arguments, 2); const runAt = now() + delayMs;
const runAt = _now() + delayMs; const key = count++;
const key = _count++;
debuglog("setTimeout: scheduling cb", key, "at", runAt, debuglog("setTimeout: scheduling cb", key, "at", runAt,
"(delay", delayMs, ")"); "(delay", delayMs, ")");
const data = { const data = {
@@ -87,13 +91,13 @@ export function setTimeout(func, delayMs) {
// figure out where it goes in the list // figure out where it goes in the list
const idx = binarySearch( const idx = binarySearch(
_callbackList, function(el) { callbackList, function(el) {
return el.runAt - runAt; return el.runAt - runAt;
}, },
); );
_callbackList.splice(idx, 0, data); callbackList.splice(idx, 0, data);
_scheduleRealCallback(); scheduleRealCallback();
return key; return key;
} }
@@ -103,68 +107,69 @@ export function setTimeout(func, delayMs) {
* *
* @param {Number} key result from an earlier setTimeout call * @param {Number} key result from an earlier setTimeout call
*/ */
export function clearTimeout(key) { export function clearTimeout(key: number): void {
if (_callbackList.length === 0) { if (callbackList.length === 0) {
return; return;
} }
// remove the element from the list // remove the element from the list
let i; let i;
for (i = 0; i < _callbackList.length; i++) { for (i = 0; i < callbackList.length; i++) {
const cb = _callbackList[i]; const cb = callbackList[i];
if (cb.key == key) { if (cb.key == key) {
_callbackList.splice(i, 1); callbackList.splice(i, 1);
break; break;
} }
} }
// iff it was the first one in the list, reschedule our callback. // iff it was the first one in the list, reschedule our callback.
if (i === 0) { if (i === 0) {
_scheduleRealCallback(); scheduleRealCallback();
} }
} }
// use the real global.setTimeout to schedule a callback to _runCallbacks. // use the real global.setTimeout to schedule a callback to runCallbacks.
function _scheduleRealCallback() { function scheduleRealCallback(): void {
if (_realCallbackKey) { if (realCallbackKey) {
global.clearTimeout(_realCallbackKey); global.clearTimeout(realCallbackKey as NodeJS.Timeout);
} }
const first = _callbackList[0]; const first = callbackList[0];
if (!first) { if (!first) {
debuglog("_scheduleRealCallback: no more callbacks, not rescheduling"); debuglog("scheduleRealCallback: no more callbacks, not rescheduling");
return; return;
} }
const now = _now(); const timestamp = now();
const delayMs = Math.min(first.runAt - now, TIMER_CHECK_PERIOD_MS); const delayMs = Math.min(first.runAt - timestamp, TIMER_CHECK_PERIOD_MS);
debuglog("_scheduleRealCallback: now:", now, "delay:", delayMs); debuglog("scheduleRealCallback: now:", timestamp, "delay:", delayMs);
_realCallbackKey = global.setTimeout(_runCallbacks, delayMs); realCallbackKey = global.setTimeout(runCallbacks, delayMs);
} }
function _runCallbacks() { function runCallbacks(): void {
let cb; let cb;
const now = _now(); const timestamp = now();
debuglog("_runCallbacks: now:", now); debuglog("runCallbacks: now:", timestamp);
// get the list of things to call // get the list of things to call
const callbacksToRun = []; const callbacksToRun = [];
// eslint-disable-next-line
while (true) { while (true) {
const first = _callbackList[0]; const first = callbackList[0];
if (!first || first.runAt > now) { if (!first || first.runAt > timestamp) {
break; break;
} }
cb = _callbackList.shift(); cb = callbackList.shift();
debuglog("_runCallbacks: popping", cb.key); debuglog("runCallbacks: popping", cb.key);
callbacksToRun.push(cb); callbacksToRun.push(cb);
} }
// reschedule the real callback before running our functions, to // reschedule the real callback before running our functions, to
// keep the codepaths the same whether or not our functions // keep the codepaths the same whether or not our functions
// register their own setTimeouts. // register their own setTimeouts.
_scheduleRealCallback(); scheduleRealCallback();
for (let i = 0; i < callbacksToRun.length; i++) { for (let i = 0; i < callbacksToRun.length; i++) {
cb = callbacksToRun[i]; cb = callbacksToRun[i];
@@ -182,7 +187,7 @@ function _runCallbacks() {
* returns the index of the last element for which func returns * returns the index of the last element for which func returns
* greater than zero, or array.length if no such element exists. * greater than zero, or array.length if no such element exists.
*/ */
function binarySearch(array, func) { function binarySearch<T>(array: T[], func: (v: T) => number): number {
// min is inclusive, max exclusive. // min is inclusive, max exclusive.
let min = 0; let min = 0;
let max = array.length; let max = array.length;