Only report undecryptable events once (#12501)
* persist previously-reported event IDs as a bloom filter * Pin to older `@types/seedrandom` ... to work around https://github.com/Callidon/bloom-filters/issues/72 * Inline `DecryptionFailureTracker.addDecryptionFailure` * Remove redundant TRACK_INTERVAL There really doesn't seem to be much point to this batching up of decryption failure reports. We still call the analytics callback the same number of times. * Rename `trackedEvents` to `reportedEvents` * Fix incorrect documentation on `visibleEvents` This *does* overlap with `failures`. * Combine `addFailure` and `reportFailure` * Calculate client properties before starting reporting * Clear localstorage after each test ... otherwise they interfere * Remove redundant comment * Ensure that reports are cleared on a logout/login cycle * make private const private and const --------- Co-authored-by: Richard van der Hoff <richard@matrix.org>
This commit is contained in:
parent
3e103941d6
commit
1bb70c5b3b
5 changed files with 197 additions and 73 deletions
|
@ -81,6 +81,7 @@
|
|||
"@zxcvbn-ts/language-common": "^3.0.4",
|
||||
"@zxcvbn-ts/language-en": "^3.0.2",
|
||||
"await-lock": "^2.1.0",
|
||||
"bloom-filters": "^3.0.1",
|
||||
"blurhash": "^2.0.3",
|
||||
"classnames": "^2.2.6",
|
||||
"commonmark": "^0.31.0",
|
||||
|
@ -182,6 +183,7 @@
|
|||
"@types/react-transition-group": "^4.4.0",
|
||||
"@types/sanitize-html": "2.11.0",
|
||||
"@types/sdp-transform": "^2.4.6",
|
||||
"@types/seedrandom": "<3.0.5",
|
||||
"@types/tar-js": "^0.3.2",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@types/uuid": "^9.0.2",
|
||||
|
|
|
@ -14,12 +14,16 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ScalableBloomFilter } from "bloom-filters";
|
||||
import { CryptoEvent, HttpApiEvent, MatrixClient, MatrixEventEvent, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { Error as ErrorEvent } from "@matrix-org/analytics-events/types/typescript/Error";
|
||||
import { DecryptionFailureCode } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import { PosthogAnalytics } from "./PosthogAnalytics";
|
||||
|
||||
/** The key that we use to store the `reportedEvents` bloom filter in localstorage */
|
||||
const DECRYPTION_FAILURE_STORAGE_KEY = "mx_decryption_failure_event_ids";
|
||||
|
||||
export class DecryptionFailure {
|
||||
/**
|
||||
* The time between our initial failure to decrypt and our successful
|
||||
|
@ -104,8 +108,8 @@ export class DecryptionFailureTracker {
|
|||
*/
|
||||
public visibleEvents: Set<string> = new Set();
|
||||
|
||||
/** Event IDs of failures that were reported previously */
|
||||
private reportedEvents: Set<string> = new Set();
|
||||
/** Bloom filter tracking event IDs of failures that were reported previously */
|
||||
private reportedEvents: ScalableBloomFilter = new ScalableBloomFilter();
|
||||
|
||||
/** Set to an interval ID when `start` is called */
|
||||
public checkInterval: number | null = null;
|
||||
|
@ -172,13 +176,18 @@ export class DecryptionFailureTracker {
|
|||
return DecryptionFailureTracker.internalInstance;
|
||||
}
|
||||
|
||||
// loadReportedEvents() {
|
||||
// this.reportedEvents = new Set(JSON.parse(localStorage.getItem('mx-decryption-failure-event-ids')) || []);
|
||||
// }
|
||||
private loadReportedEvents(): void {
|
||||
const storedFailures = localStorage.getItem(DECRYPTION_FAILURE_STORAGE_KEY);
|
||||
if (storedFailures) {
|
||||
this.reportedEvents = ScalableBloomFilter.fromJSON(JSON.parse(storedFailures));
|
||||
} else {
|
||||
this.reportedEvents = new ScalableBloomFilter();
|
||||
}
|
||||
}
|
||||
|
||||
// saveReportedEvents() {
|
||||
// localStorage.setItem('mx-decryption-failure-event-ids', JSON.stringify([...this.reportedEvents]));
|
||||
// }
|
||||
private saveReportedEvents(): void {
|
||||
localStorage.setItem(DECRYPTION_FAILURE_STORAGE_KEY, JSON.stringify(this.reportedEvents.saveAsJSON()));
|
||||
}
|
||||
|
||||
/** Callback for when an event is decrypted.
|
||||
*
|
||||
|
@ -290,6 +299,7 @@ export class DecryptionFailureTracker {
|
|||
* Start checking for and tracking failures.
|
||||
*/
|
||||
public async start(client: MatrixClient): Promise<void> {
|
||||
this.loadReportedEvents();
|
||||
await this.calculateClientProperties(client);
|
||||
this.registerHandlers(client);
|
||||
this.checkInterval = window.setInterval(
|
||||
|
@ -385,9 +395,7 @@ export class DecryptionFailureTracker {
|
|||
}
|
||||
this.failures = failuresNotReady;
|
||||
|
||||
// Commented out for now for expediency, we need to consider unbound nature of storing
|
||||
// this in localStorage
|
||||
// this.saveReportedEvents();
|
||||
this.saveReportedEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,8 +23,8 @@ import {
|
|||
MatrixClient,
|
||||
MatrixEvent,
|
||||
RoomType,
|
||||
SyncStateData,
|
||||
SyncState,
|
||||
SyncStateData,
|
||||
TimelineEvents,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { defer, IDeferred, QueryDict } from "matrix-js-sdk/src/utils";
|
||||
|
@ -128,7 +128,7 @@ import { TimelineRenderingType } from "../../contexts/RoomContext";
|
|||
import { UseCaseSelection } from "../views/elements/UseCaseSelection";
|
||||
import { ValidatedServerConfig } from "../../utils/ValidatedServerConfig";
|
||||
import { isLocalRoom } from "../../utils/localRoom/isLocalRoom";
|
||||
import { SdkContextClass, SDKContext } from "../../contexts/SDKContext";
|
||||
import { SDKContext, SdkContextClass } from "../../contexts/SDKContext";
|
||||
import { viewUserDeviceSettings } from "../../actions/handlers/viewUserDeviceSettings";
|
||||
import { cleanUpBroadcasts, VoiceBroadcastResumer } from "../../voice-broadcast";
|
||||
import GenericToast from "../views/toasts/GenericToast";
|
||||
|
@ -1585,13 +1585,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
);
|
||||
});
|
||||
|
||||
const dft = DecryptionFailureTracker.instance;
|
||||
|
||||
// Shelved for later date when we have time to think about persisting history of
|
||||
// tracked events across sessions.
|
||||
// dft.loadTrackedEventHashMap();
|
||||
|
||||
dft.start(cli).catch((e) => logger.error("Unable to start DecryptionFailureTracker", e));
|
||||
DecryptionFailureTracker.instance
|
||||
.start(cli)
|
||||
.catch((e) => logger.error("Unable to start DecryptionFailureTracker", e));
|
||||
|
||||
cli.on(ClientEvent.Room, (room) => {
|
||||
if (cli.isCryptoEnabled()) {
|
||||
|
|
|
@ -14,14 +14,15 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { mocked, Mocked } from "jest-mock";
|
||||
import { CryptoEvent, HttpApiEvent, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { mocked, Mocked, MockedObject } from "jest-mock";
|
||||
import { CryptoEvent, HttpApiEvent, MatrixClient, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { decryptExistingEvent, mkDecryptionFailureMatrixEvent } from "matrix-js-sdk/src/testing";
|
||||
import { CryptoApi, DecryptionFailureCode, UserVerificationStatus } from "matrix-js-sdk/src/crypto-api";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { DecryptionFailureTracker, ErrorProperties } from "../src/DecryptionFailureTracker";
|
||||
import { stubClient } from "./test-utils";
|
||||
import * as Lifecycle from "../src/Lifecycle";
|
||||
|
||||
async function createFailedDecryptionEvent(opts: { sender?: string; code?: DecryptionFailureCode } = {}) {
|
||||
return await mkDecryptionFailureMatrixEvent({
|
||||
|
@ -39,6 +40,10 @@ function eventDecrypted(tracker: DecryptionFailureTracker, e: MatrixEvent, nowTs
|
|||
}
|
||||
|
||||
describe("DecryptionFailureTracker", function () {
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it("tracks a failed decryption for a visible event", async function () {
|
||||
const failedDecryptionEvent = await createFailedDecryptionEvent();
|
||||
|
||||
|
@ -247,6 +252,7 @@ describe("DecryptionFailureTracker", function () {
|
|||
() => count++,
|
||||
() => "UnknownError",
|
||||
);
|
||||
await tracker.start(mockClient());
|
||||
|
||||
tracker.addVisibleEvent(decryptedEvent);
|
||||
|
||||
|
@ -264,10 +270,7 @@ describe("DecryptionFailureTracker", function () {
|
|||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
it.skip("should not track a failure for an event that was tracked in a previous session", async () => {
|
||||
// This test uses localStorage, clear it beforehand
|
||||
localStorage.clear();
|
||||
|
||||
it("should not report a failure for an event that was reported in a previous session", async () => {
|
||||
const decryptedEvent = await createFailedDecryptionEvent();
|
||||
|
||||
let count = 0;
|
||||
|
@ -276,6 +279,7 @@ describe("DecryptionFailureTracker", function () {
|
|||
() => count++,
|
||||
() => "UnknownError",
|
||||
);
|
||||
await tracker.start(mockClient());
|
||||
|
||||
tracker.addVisibleEvent(decryptedEvent);
|
||||
|
||||
|
@ -289,14 +293,13 @@ describe("DecryptionFailureTracker", function () {
|
|||
// Simulate the browser refreshing by destroying tracker and creating a new tracker
|
||||
// @ts-ignore access to private constructor
|
||||
const secondTracker = new DecryptionFailureTracker(
|
||||
(total: number) => (count += total),
|
||||
() => count++,
|
||||
() => "UnknownError",
|
||||
);
|
||||
await secondTracker.start(mockClient());
|
||||
|
||||
secondTracker.addVisibleEvent(decryptedEvent);
|
||||
|
||||
//secondTracker.loadTrackedEvents();
|
||||
|
||||
eventDecrypted(secondTracker, decryptedEvent, Date.now());
|
||||
secondTracker.checkFailures(Infinity);
|
||||
|
||||
|
@ -304,6 +307,70 @@ describe("DecryptionFailureTracker", function () {
|
|||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
it("should report a failure for an event that was tracked but not reported in a previous session", async () => {
|
||||
const decryptedEvent = await createFailedDecryptionEvent();
|
||||
|
||||
let count = 0;
|
||||
|
||||
// @ts-ignore access to private constructor
|
||||
const tracker = new DecryptionFailureTracker(
|
||||
() => count++,
|
||||
() => "UnknownError",
|
||||
);
|
||||
await tracker.start(mockClient());
|
||||
|
||||
tracker.addVisibleEvent(decryptedEvent);
|
||||
|
||||
// Indicate decryption
|
||||
eventDecrypted(tracker, decryptedEvent, Date.now());
|
||||
|
||||
// we do *not* call `checkFailures` here
|
||||
expect(count).toBe(0);
|
||||
|
||||
// Simulate the browser refreshing by destroying tracker and creating a new tracker
|
||||
// @ts-ignore access to private constructor
|
||||
const secondTracker = new DecryptionFailureTracker(
|
||||
() => count++,
|
||||
() => "UnknownError",
|
||||
);
|
||||
await secondTracker.start(mockClient());
|
||||
|
||||
secondTracker.addVisibleEvent(decryptedEvent);
|
||||
|
||||
eventDecrypted(secondTracker, decryptedEvent, Date.now());
|
||||
secondTracker.checkFailures(Infinity);
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
it("should report a failure for an event that was reported before a logout/login cycle", async () => {
|
||||
const decryptedEvent = await createFailedDecryptionEvent();
|
||||
|
||||
let count = 0;
|
||||
|
||||
// @ts-ignore access to private constructor
|
||||
const tracker = new DecryptionFailureTracker(
|
||||
() => count++,
|
||||
() => "UnknownError",
|
||||
);
|
||||
await tracker.start(mockClient());
|
||||
|
||||
tracker.addVisibleEvent(decryptedEvent);
|
||||
|
||||
// Indicate decryption
|
||||
eventDecrypted(tracker, decryptedEvent, Date.now());
|
||||
tracker.checkFailures(Infinity);
|
||||
expect(count).toBe(1);
|
||||
|
||||
// Simulate a logout/login cycle
|
||||
await Lifecycle.onLoggedOut();
|
||||
await tracker.start(mockClient());
|
||||
|
||||
tracker.addVisibleEvent(decryptedEvent);
|
||||
eventDecrypted(tracker, decryptedEvent, Date.now());
|
||||
tracker.checkFailures(Infinity);
|
||||
expect(count).toBe(2);
|
||||
});
|
||||
|
||||
it("should count different error codes separately for multiple failures with different error codes", async () => {
|
||||
const counts: Record<string, number> = {};
|
||||
|
||||
|
@ -521,12 +588,7 @@ describe("DecryptionFailureTracker", function () {
|
|||
it("listens for client events", async () => {
|
||||
// Test that the decryption failure tracker registers the right event
|
||||
// handlers on start, and unregisters them when the client logs out.
|
||||
const client = mocked(stubClient());
|
||||
const mockCrypto = {
|
||||
getVersion: jest.fn().mockReturnValue("Rust SDK 0.7.0 (61b175b), Vodozemac 0.5.1"),
|
||||
getUserVerificationStatus: jest.fn().mockResolvedValue(new UserVerificationStatus(false, false, false)),
|
||||
} as unknown as Mocked<CryptoApi>;
|
||||
client.getCrypto.mockReturnValue(mockCrypto);
|
||||
const client = mockClient();
|
||||
|
||||
let errorCount: number = 0;
|
||||
// @ts-ignore access to private constructor
|
||||
|
@ -568,13 +630,7 @@ describe("DecryptionFailureTracker", function () {
|
|||
});
|
||||
|
||||
it("tracks client information", async () => {
|
||||
const client = mocked(stubClient());
|
||||
const mockCrypto = {
|
||||
getVersion: jest.fn().mockReturnValue("Rust SDK 0.7.0 (61b175b), Vodozemac 0.5.1"),
|
||||
getUserVerificationStatus: jest.fn().mockResolvedValue(new UserVerificationStatus(false, false, false)),
|
||||
} as unknown as Mocked<CryptoApi>;
|
||||
client.getCrypto.mockReturnValue(mockCrypto);
|
||||
|
||||
const client = mockClient();
|
||||
const propertiesByErrorCode: Record<string, ErrorProperties> = {};
|
||||
// @ts-ignore access to private constructor
|
||||
const tracker = new DecryptionFailureTracker(
|
||||
|
@ -610,7 +666,9 @@ describe("DecryptionFailureTracker", function () {
|
|||
const now = Date.now();
|
||||
eventDecrypted(tracker, federatedDecryption, now);
|
||||
|
||||
mockCrypto.getUserVerificationStatus.mockResolvedValue(new UserVerificationStatus(true, true, false));
|
||||
mocked(client.getCrypto()!.getUserVerificationStatus).mockResolvedValue(
|
||||
new UserVerificationStatus(true, true, false),
|
||||
);
|
||||
client.emit(CryptoEvent.KeysChanged, {});
|
||||
await sleep(100);
|
||||
eventDecrypted(tracker, localDecryption, now);
|
||||
|
@ -628,7 +686,7 @@ describe("DecryptionFailureTracker", function () {
|
|||
|
||||
// change client params, and make sure the reports the right values
|
||||
client.getDomain.mockReturnValue("example.com");
|
||||
mockCrypto.getVersion.mockReturnValue("Olm 0.0.0");
|
||||
mocked(client.getCrypto()!.getVersion).mockReturnValue("Olm 0.0.0");
|
||||
// @ts-ignore access to private method
|
||||
await tracker.calculateClientProperties(client);
|
||||
|
||||
|
@ -673,3 +731,21 @@ describe("DecryptionFailureTracker", function () {
|
|||
expect(failure?.timeToDecryptMillis).toEqual(50000);
|
||||
});
|
||||
});
|
||||
|
||||
function mockClient(): MockedObject<MatrixClient> {
|
||||
const client = mocked(stubClient());
|
||||
const mockCrypto = {
|
||||
getVersion: jest.fn().mockReturnValue("Rust SDK 0.7.0 (61b175b), Vodozemac 0.5.1"),
|
||||
getUserVerificationStatus: jest.fn().mockResolvedValue(new UserVerificationStatus(false, false, false)),
|
||||
} as unknown as Mocked<CryptoApi>;
|
||||
client.getCrypto.mockReturnValue(mockCrypto);
|
||||
|
||||
// @ts-ignore
|
||||
client.stopClient = jest.fn(() => {});
|
||||
// @ts-ignore
|
||||
client.removeAllListeners = jest.fn(() => {});
|
||||
|
||||
client.store = { destroy: jest.fn(() => {}) } as any;
|
||||
|
||||
return client;
|
||||
}
|
||||
|
|
100
yarn.lock
100
yarn.lock
|
@ -2907,6 +2907,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/sdp-transform/-/sdp-transform-2.4.9.tgz#26ef39f487a6909b0512f580b80920a366b27f52"
|
||||
integrity sha512-bVr+/OoZZy7wrHlNcEAAa6PAgKA4BoXPYVN2EijMC5WnGgQ4ZEuixmKnVs2roiAvr7RhIFVH17QD27cojgIZCg==
|
||||
|
||||
"@types/seedrandom@<3.0.5":
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-3.0.4.tgz#e4a8d0fca0168cacc7dba2af0e4a4ea645d3a190"
|
||||
integrity sha512-/rWdxeiuZenlawrHU+XV6ZHMTKOqrC2hMfeDfLTIWJhDZP5aVqXRysduYHBbhD7CeJO6FJr/D2uBVXB7GT6v7w==
|
||||
|
||||
"@types/semver@^7.5.8":
|
||||
version "7.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
|
||||
|
@ -3594,6 +3599,11 @@ base-x@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a"
|
||||
integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==
|
||||
|
||||
base64-arraybuffer@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
|
||||
integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
|
||||
|
||||
big-integer@^1.6.48:
|
||||
version "1.6.51"
|
||||
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686"
|
||||
|
@ -3614,6 +3624,21 @@ blob-polyfill@^7.0.0:
|
|||
resolved "https://registry.yarnpkg.com/blob-polyfill/-/blob-polyfill-7.0.20220408.tgz#38bf5e046c41a21bb13654d9d19f303233b8218c"
|
||||
integrity sha512-oD8Ydw+5lNoqq+en24iuPt1QixdPpe/nUF8azTHnviCZYu9zUC+TwdzIp5orpblJosNlgNbVmmAb//c6d6ImUQ==
|
||||
|
||||
bloom-filters@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bloom-filters/-/bloom-filters-3.0.1.tgz#13e28ed22febe2489cd00ba5bd98fdc90e820180"
|
||||
integrity sha512-rU9IU6bgZ1jmqcLWhlKSidrFjbIGjB89CJBsQqUj1+3/11tAJDwn+f7iRu4bbQ2srTjGgNeoWNwcnelumqdi0g==
|
||||
dependencies:
|
||||
base64-arraybuffer "^1.0.2"
|
||||
is-buffer "^2.0.5"
|
||||
lodash "^4.17.15"
|
||||
lodash.eq "^4.0.0"
|
||||
lodash.indexof "^4.0.5"
|
||||
long "^5.2.0"
|
||||
reflect-metadata "^0.1.13"
|
||||
seedrandom "^3.0.5"
|
||||
xxhashjs "^0.2.2"
|
||||
|
||||
blurhash@^2.0.3:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-2.0.5.tgz#efde729fc14a2f03571a6aa91b49cba80d1abe4b"
|
||||
|
@ -4155,6 +4180,11 @@ csstype@^3.0.2:
|
|||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
|
||||
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
|
||||
|
||||
cuint@^0.2.2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b"
|
||||
integrity sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==
|
||||
|
||||
damerau-levenshtein@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
|
||||
|
@ -5976,6 +6006,11 @@ is-boolean-object@^1.1.0:
|
|||
call-bind "^1.0.2"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-buffer@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
|
||||
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
|
||||
|
||||
is-buffer@~1.1.6:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||
|
@ -6961,6 +6996,16 @@ lodash.debounce@^4.0.8:
|
|||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
|
||||
|
||||
lodash.eq@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.eq/-/lodash.eq-4.0.0.tgz#a39f06779e72f9c0d1f310c90cd292c1661d5035"
|
||||
integrity sha512-vbrJpXL6kQNG6TkInxX12DZRfuYVllSxhwYqjYB78g2zF3UI15nFO/0AgmZnZRnaQ38sZtjCiVjGr2rnKt4v0g==
|
||||
|
||||
lodash.indexof@^4.0.5:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/lodash.indexof/-/lodash.indexof-4.0.5.tgz#53714adc2cddd6ed87638f893aa9b6c24e31ef3c"
|
||||
integrity sha512-t9wLWMQsawdVmf6/IcAgVGqAJkNzYVcn4BHYZKTPW//l7N5Oq7Bq138BaVk19agcsPZePcidSgTTw4NqS1nUAw==
|
||||
|
||||
lodash.isequal@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||
|
@ -6981,7 +7026,7 @@ lodash.truncate@^4.4.2:
|
|||
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
|
||||
integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==
|
||||
|
||||
lodash@^4.17.20, lodash@^4.17.21:
|
||||
lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
@ -6991,6 +7036,11 @@ loglevel@^1.7.1:
|
|||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.1.tgz#5c621f83d5b48c54ae93b6156353f555963377b4"
|
||||
integrity sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==
|
||||
|
||||
long@^5.2.0:
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
|
||||
integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
|
@ -8172,6 +8222,11 @@ redux@^4.0.0, redux@^4.0.4:
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.9.2"
|
||||
|
||||
reflect-metadata@^0.1.13:
|
||||
version "0.1.14"
|
||||
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.14.tgz#24cf721fe60677146bb77eeb0e1f9dece3d65859"
|
||||
integrity sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==
|
||||
|
||||
reflect.getprototypeof@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859"
|
||||
|
@ -8472,6 +8527,11 @@ sdp-transform@^2.14.1:
|
|||
resolved "https://registry.yarnpkg.com/sdp-transform/-/sdp-transform-2.14.1.tgz#2bb443583d478dee217df4caa284c46b870d5827"
|
||||
integrity sha512-RjZyX3nVwJyCuTo5tGPx+PZWkDMCg7oOLpSlhjDdZfwUoNqG1mM8nyj31IGHyaPWXhjbP7cdK3qZ2bmkJ1GzRw==
|
||||
|
||||
seedrandom@^3.0.5:
|
||||
version "3.0.5"
|
||||
resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7"
|
||||
integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.6.0:
|
||||
version "5.7.2"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
|
||||
|
@ -8729,16 +8789,7 @@ string-length@^4.0.1:
|
|||
char-regex "^1.0.2"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
|
@ -8832,14 +8883,7 @@ string_decoder@~1.1.1:
|
|||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
|
@ -9632,7 +9676,7 @@ which@^2.0.1:
|
|||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
|
@ -9650,15 +9694,6 @@ wrap-ansi@^6.2.0:
|
|||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
@ -9709,6 +9744,13 @@ xmlchars@^2.2.0:
|
|||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
||||
|
||||
xxhashjs@^0.2.2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8"
|
||||
integrity sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==
|
||||
dependencies:
|
||||
cuint "^0.2.2"
|
||||
|
||||
y18n@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
||||
|
|
Loading…
Reference in a new issue