Merge pull request #373 from vector-im/bwindels/fix-send-sync-race

Fix race between /send and /sync
This commit is contained in:
Bruno Windels 2021-06-02 10:41:50 +00:00 committed by GitHub
commit aa2e1aad19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 372 additions and 55 deletions

View file

@ -36,6 +36,7 @@
"commander": "^6.0.0", "commander": "^6.0.0",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"eslint": "^7.25.0", "eslint": "^7.25.0",
"fake-indexeddb": "^3.1.2",
"finalhandler": "^1.1.1", "finalhandler": "^1.1.1",
"impunity": "^1.0.0", "impunity": "^1.0.0",
"mdn-polyfills": "^5.20.0", "mdn-polyfills": "^5.20.0",

View file

@ -191,6 +191,8 @@ async function buildJs(mainFile, extraFiles, importOverrides) {
plugins.push(overridesAsRollupPlugin(importOverrides)); plugins.push(overridesAsRollupPlugin(importOverrides));
} }
const bundle = await rollup({ const bundle = await rollup({
// for fake-indexeddb, so usage for tests only doesn't put it in bundle
treeshake: {moduleSideEffects: false},
input: extraFiles.concat(mainFile), input: extraFiles.concat(mainFile),
plugins plugins
}); });
@ -230,6 +232,8 @@ async function buildJsLegacy(mainFile, extraFiles, importOverrides) {
plugins.push(nodeResolve(), babelPlugin); plugins.push(nodeResolve(), babelPlugin);
// create js bundle // create js bundle
const rollupConfig = { const rollupConfig = {
// for fake-indexeddb, so usage for tests only doesn't put it in bundle
treeshake: {moduleSideEffects: false},
// important the extraFiles come first, // important the extraFiles come first,
// so polyfills are available in the global scope // so polyfills are available in the global scope
// if needed for the mainfile // if needed for the mainfile

View file

@ -0,0 +1,4 @@
// we have our own main file for this module as we need both these symbols to
// be exported, and we also don't want to auto behaviour that modifies global vars
exports.FDBFactory = require("fake-indexeddb/lib/FDBFactory.js");
exports.FDBKeyRange = require("fake-indexeddb/lib/FDBKeyRange.js");

View file

@ -51,6 +51,7 @@ function packageIterator(request, start, defaultIterator) {
async function commonjsToESM(src, dst) { async function commonjsToESM(src, dst) {
// create js bundle // create js bundle
const bundle = await rollup({ const bundle = await rollup({
treeshake: {moduleSideEffects: false},
input: src, input: src,
plugins: [commonjs(), nodeResolve({ plugins: [commonjs(), nodeResolve({
browser: true, browser: true,
@ -111,6 +112,14 @@ async function populateLib() {
require.resolve('es6-promise/lib/es6-promise/promise.js'), require.resolve('es6-promise/lib/es6-promise/promise.js'),
path.join(libDir, "es6-promise/index.js") path.join(libDir, "es6-promise/index.js")
); );
// fake-indexeddb, used for tests (but unresolvable bare imports also makes the build complain)
// and might want to use it for in-memory storage too, although we probably do ts->es6 with esm
// directly rather than ts->es5->es6 as we do now. The bundle is 240K currently.
await fs.mkdir(path.join(libDir, "fake-indexeddb/"));
await commonjsToESM(
path.join(projectDir, "/scripts/package-overrides/fake-indexeddb.js"),
path.join(libDir, "fake-indexeddb/index.js")
);
} }
populateLib(); populateLib();

View file

@ -31,6 +31,7 @@ export class SendQueue {
this._isSending = false; this._isSending = false;
this._offline = false; this._offline = false;
this._roomEncryption = null; this._roomEncryption = null;
this._currentQueueIndex = 0;
} }
_createPendingEvent(data, attachments = null) { _createPendingEvent(data, attachments = null) {
@ -55,6 +56,7 @@ export class SendQueue {
await log.wrap("send event", async log => { await log.wrap("send event", async log => {
log.set("queueIndex", pendingEvent.queueIndex); log.set("queueIndex", pendingEvent.queueIndex);
try { try {
this._currentQueueIndex = pendingEvent.queueIndex;
await this._sendEvent(pendingEvent, log); await this._sendEvent(pendingEvent, log);
} catch(err) { } catch(err) {
if (err instanceof ConnectionError) { if (err instanceof ConnectionError) {
@ -75,6 +77,8 @@ export class SendQueue {
pendingEvent.setError(err); pendingEvent.setError(err);
} }
} }
} finally {
this._currentQueueIndex = 0;
} }
}); });
} }
@ -241,7 +245,7 @@ export class SendQueue {
relatedTxnId = pe.txnId; relatedTxnId = pe.txnId;
} }
} }
log.set("relatedTxnId", eventIdOrTxnId); log.set("relatedTxnId", relatedTxnId);
log.set("relatedEventId", relatedEventId); log.set("relatedEventId", relatedEventId);
await this._enqueueEvent(REDACTION_TYPE, {reason}, null, relatedTxnId, relatedEventId, log); await this._enqueueEvent(REDACTION_TYPE, {reason}, null, relatedTxnId, relatedEventId, log);
} }
@ -274,7 +278,11 @@ export class SendQueue {
let pendingEvent; let pendingEvent;
try { try {
const pendingEventsStore = txn.pendingEvents; const pendingEventsStore = txn.pendingEvents;
const maxQueueIndex = await pendingEventsStore.getMaxQueueIndex(this._roomId) || 0; const maxStorageQueueIndex = await pendingEventsStore.getMaxQueueIndex(this._roomId) || 0;
// don't use the queueIndex of the pendingEvent currently waiting for /send to return
// if the remote echo already removed the pendingEvent in storage, as the send loop
// wouldn't be able to detect the remote echo already arrived and end up overwriting the new event
const maxQueueIndex = Math.max(maxStorageQueueIndex, this._currentQueueIndex);
const queueIndex = maxQueueIndex + 1; const queueIndex = maxQueueIndex + 1;
const needsEncryption = eventType !== REDACTION_TYPE && !!this._roomEncryption; const needsEncryption = eventType !== REDACTION_TYPE && !!this._roomEncryption;
pendingEvent = this._createPendingEvent({ pendingEvent = this._createPendingEvent({
@ -303,3 +311,45 @@ export class SendQueue {
} }
} }
} }
import {HomeServer as MockHomeServer} from "../../../mocks/HomeServer.js";
import {createMockStorage} from "../../../mocks/Storage.js";
import {NullLogger} from "../../../logging/NullLogger.js";
import {event, withTextBody, withTxnId} from "../../../mocks/event.js";
import {poll} from "../../../mocks/poll.js";
export function tests() {
const logger = new NullLogger();
return {
"enqueue second message when remote echo of first arrives before /send returns": async assert => {
const storage = await createMockStorage();
const hs = new MockHomeServer();
// 1. enqueue and start send event 1
const queue = new SendQueue({roomId: "!abc", storage, hsApi: hs.api});
const event1 = withTextBody(event("m.room.message", "$123"), "message 1");
await logger.run("event1", log => queue.enqueueEvent(event1.type, event1.content, null, log));
assert.equal(queue.pendingEvents.length, 1);
const sendRequest1 = hs.requests.send[0];
// 2. receive remote echo, before /send has returned
const remoteEcho = withTxnId(event1, sendRequest1.arguments[2]);
const txn = await storage.readWriteTxn([storage.storeNames.pendingEvents]);
const removal = await logger.run("remote echo", log => queue.removeRemoteEchos([remoteEcho], txn, log));
await txn.complete();
assert.equal(removal.length, 1);
queue.emitRemovals(removal);
assert.equal(queue.pendingEvents.length, 0);
// 3. now enqueue event 2
const event2 = withTextBody(event("m.room.message", "$456"), "message 2");
await logger.run("event2", log => queue.enqueueEvent(event2.type, event2.content, null, log));
// even though the first pending event has been removed by the remote echo,
// the second should get the next index, as the send loop is still blocking on the first one
assert.equal(Array.from(queue.pendingEvents)[0].queueIndex, 2);
// 4. send for event 1 comes back
sendRequest1.respond({event_id: event1.event_id});
// 5. now expect second send request for event 2
const sendRequest2 = await poll(() => hs.requests.send[1]);
sendRequest2.respond({event_id: event2.event_id});
await poll(() => !queue._isSending);
}
}
}

View file

@ -21,8 +21,9 @@ import { reqAsPromise } from "./utils.js";
const WEBKITEARLYCLOSETXNBUG_BOGUS_KEY = "782rh281re38-boguskey"; const WEBKITEARLYCLOSETXNBUG_BOGUS_KEY = "782rh281re38-boguskey";
export class Storage { export class Storage {
constructor(idbDatabase, hasWebkitEarlyCloseTxnBug) { constructor(idbDatabase, IDBKeyRange, hasWebkitEarlyCloseTxnBug) {
this._db = idbDatabase; this._db = idbDatabase;
this._IDBKeyRange = IDBKeyRange;
this._hasWebkitEarlyCloseTxnBug = hasWebkitEarlyCloseTxnBug; this._hasWebkitEarlyCloseTxnBug = hasWebkitEarlyCloseTxnBug;
const nameMap = STORE_NAMES.reduce((nameMap, name) => { const nameMap = STORE_NAMES.reduce((nameMap, name) => {
nameMap[name] = name; nameMap[name] = name;
@ -47,7 +48,7 @@ export class Storage {
if (this._hasWebkitEarlyCloseTxnBug) { if (this._hasWebkitEarlyCloseTxnBug) {
await reqAsPromise(txn.objectStore(storeNames[0]).get(WEBKITEARLYCLOSETXNBUG_BOGUS_KEY)); await reqAsPromise(txn.objectStore(storeNames[0]).get(WEBKITEARLYCLOSETXNBUG_BOGUS_KEY));
} }
return new Transaction(txn, storeNames); return new Transaction(txn, storeNames, this._IDBKeyRange);
} catch(err) { } catch(err) {
throw new StorageError("readTxn failed", err); throw new StorageError("readTxn failed", err);
} }
@ -62,7 +63,7 @@ export class Storage {
if (this._hasWebkitEarlyCloseTxnBug) { if (this._hasWebkitEarlyCloseTxnBug) {
await reqAsPromise(txn.objectStore(storeNames[0]).get(WEBKITEARLYCLOSETXNBUG_BOGUS_KEY)); await reqAsPromise(txn.objectStore(storeNames[0]).get(WEBKITEARLYCLOSETXNBUG_BOGUS_KEY));
} }
return new Transaction(txn, storeNames); return new Transaction(txn, storeNames, this._IDBKeyRange);
} catch(err) { } catch(err) {
throw new StorageError("readWriteTxn failed", err); throw new StorageError("readWriteTxn failed", err);
} }

View file

@ -21,14 +21,18 @@ import { schema } from "./schema.js";
import { detectWebkitEarlyCloseTxnBug } from "./quirks.js"; import { detectWebkitEarlyCloseTxnBug } from "./quirks.js";
const sessionName = sessionId => `hydrogen_session_${sessionId}`; const sessionName = sessionId => `hydrogen_session_${sessionId}`;
const openDatabaseWithSessionId = sessionId => openDatabase(sessionName(sessionId), createStores, schema.length); const openDatabaseWithSessionId = function(sessionId, idbFactory) {
return openDatabase(sessionName(sessionId), createStores, schema.length, idbFactory);
}
async function requestPersistedStorage() { async function requestPersistedStorage() {
if (navigator?.storage?.persist) { // don't assume browser so we can run in node with fake-idb
return await navigator.storage.persist(); const glob = this;
} else if (document.requestStorageAccess) { if (glob?.navigator?.storage?.persist) {
return await glob.navigator.storage.persist();
} else if (glob?.document.requestStorageAccess) {
try { try {
await document.requestStorageAccess(); await glob.document.requestStorageAccess();
return true; return true;
} catch (err) { } catch (err) {
return false; return false;
@ -39,8 +43,10 @@ async function requestPersistedStorage() {
} }
export class StorageFactory { export class StorageFactory {
constructor(serviceWorkerHandler) { constructor(serviceWorkerHandler, idbFactory = window.indexedDB, IDBKeyRange = window.IDBKeyRange) {
this._serviceWorkerHandler = serviceWorkerHandler; this._serviceWorkerHandler = serviceWorkerHandler;
this._idbFactory = idbFactory;
this._IDBKeyRange = IDBKeyRange;
} }
async create(sessionId) { async create(sessionId) {
@ -52,24 +58,24 @@ export class StorageFactory {
} }
}); });
const hasWebkitEarlyCloseTxnBug = await detectWebkitEarlyCloseTxnBug(); const hasWebkitEarlyCloseTxnBug = await detectWebkitEarlyCloseTxnBug(this._idbFactory);
const db = await openDatabaseWithSessionId(sessionId); const db = await openDatabaseWithSessionId(sessionId, this._idbFactory);
return new Storage(db, hasWebkitEarlyCloseTxnBug); return new Storage(db, this._IDBKeyRange, hasWebkitEarlyCloseTxnBug);
} }
delete(sessionId) { delete(sessionId) {
const databaseName = sessionName(sessionId); const databaseName = sessionName(sessionId);
const req = indexedDB.deleteDatabase(databaseName); const req = this._idbFactory.deleteDatabase(databaseName);
return reqAsPromise(req); return reqAsPromise(req);
} }
async export(sessionId) { async export(sessionId) {
const db = await openDatabaseWithSessionId(sessionId); const db = await openDatabaseWithSessionId(sessionId, this._idbFactory);
return await exportSession(db); return await exportSession(db);
} }
async import(sessionId, data) { async import(sessionId, data) {
const db = await openDatabaseWithSessionId(sessionId); const db = await openDatabaseWithSessionId(sessionId, this._idbFactory);
return await importSession(db, data); return await importSession(db, data);
} }
} }

View file

@ -126,6 +126,10 @@ export class Store extends QueryTarget {
this._transaction = transaction; this._transaction = transaction;
} }
get IDBKeyRange() {
return this._transaction.IDBKeyRange;
}
get _idbStore() { get _idbStore() {
return this._target; return this._target;
} }

View file

@ -35,10 +35,11 @@ import {OperationStore} from "./stores/OperationStore.js";
import {AccountDataStore} from "./stores/AccountDataStore.js"; import {AccountDataStore} from "./stores/AccountDataStore.js";
export class Transaction { export class Transaction {
constructor(txn, allowedStoreNames) { constructor(txn, allowedStoreNames, IDBKeyRange) {
this._txn = txn; this._txn = txn;
this._allowedStoreNames = allowedStoreNames; this._allowedStoreNames = allowedStoreNames;
this._stores = {}; this._stores = {};
this.IDBKeyRange = IDBKeyRange;
} }
_idbStore(name) { _idbStore(name) {
@ -46,7 +47,7 @@ export class Transaction {
// more specific error? this is a bug, so maybe not ... // more specific error? this is a bug, so maybe not ...
throw new StorageError(`Invalid store for transaction: ${name}, only ${this._allowedStoreNames.join(", ")} are allowed.`); throw new StorageError(`Invalid store for transaction: ${name}, only ${this._allowedStoreNames.join(", ")} are allowed.`);
} }
return new Store(this._txn.objectStore(name)); return new Store(this._txn.objectStore(name), this);
} }
_store(name, mapStore) { _store(name, mapStore) {

View file

@ -18,12 +18,12 @@ limitations under the License.
import {openDatabase, txnAsPromise, reqAsPromise} from "./utils.js"; import {openDatabase, txnAsPromise, reqAsPromise} from "./utils.js";
// filed as https://bugs.webkit.org/show_bug.cgi?id=222746 // filed as https://bugs.webkit.org/show_bug.cgi?id=222746
export async function detectWebkitEarlyCloseTxnBug() { export async function detectWebkitEarlyCloseTxnBug(idbFactory) {
const dbName = "hydrogen_webkit_test_inactive_txn_bug"; const dbName = "hydrogen_webkit_test_inactive_txn_bug";
try { try {
const db = await openDatabase(dbName, db => { const db = await openDatabase(dbName, db => {
db.createObjectStore("test", {keyPath: "key"}); db.createObjectStore("test", {keyPath: "key"});
}, 1); }, 1, idbFactory);
const readTxn = db.transaction(["test"], "readonly"); const readTxn = db.transaction(["test"], "readonly");
await reqAsPromise(readTxn.objectStore("test").get("somekey")); await reqAsPromise(readTxn.objectStore("test").get("somekey"));
// schedule a macro task in between the two txns // schedule a macro task in between the two txns

View file

@ -31,7 +31,7 @@ export class DeviceIdentityStore {
} }
getAllForUserId(userId) { getAllForUserId(userId) {
const range = IDBKeyRange.lowerBound(encodeKey(userId, "")); const range = this._store.IDBKeyRange.lowerBound(encodeKey(userId, ""));
return this._store.selectWhile(range, device => { return this._store.selectWhile(range, device => {
return device.userId === userId; return device.userId === userId;
}); });
@ -39,7 +39,7 @@ export class DeviceIdentityStore {
async getAllDeviceIds(userId) { async getAllDeviceIds(userId) {
const deviceIds = []; const deviceIds = [];
const range = IDBKeyRange.lowerBound(encodeKey(userId, "")); const range = this._store.IDBKeyRange.lowerBound(encodeKey(userId, ""));
await this._store.iterateKeys(range, key => { await this._store.iterateKeys(range, key => {
const decodedKey = decodeKey(key); const decodedKey = decodeKey(key);
// prevent running into the next room // prevent running into the next room
@ -72,7 +72,7 @@ export class DeviceIdentityStore {
removeAllForUser(userId) { removeAllForUser(userId) {
// exclude both keys as they are theoretical min and max, // exclude both keys as they are theoretical min and max,
// but we should't have a match for just the room id, or room id with max // but we should't have a match for just the room id, or room id with max
const range = IDBKeyRange.bound(encodeKey(userId, MIN_UNICODE), encodeKey(userId, MAX_UNICODE), true, true); const range = this._store.IDBKeyRange.bound(encodeKey(userId, MIN_UNICODE), encodeKey(userId, MAX_UNICODE), true, true);
this._store.delete(range); this._store.delete(range);
} }
} }

View file

@ -35,7 +35,7 @@ export class GroupSessionDecryptionStore {
} }
removeAllForRoom(roomId) { removeAllForRoom(roomId) {
const range = IDBKeyRange.bound( const range = this._store.IDBKeyRange.bound(
encodeKey(roomId, MIN_UNICODE, MIN_UNICODE), encodeKey(roomId, MIN_UNICODE, MIN_UNICODE),
encodeKey(roomId, MAX_UNICODE, MAX_UNICODE) encodeKey(roomId, MAX_UNICODE, MAX_UNICODE)
); );

View file

@ -41,7 +41,7 @@ export class InboundGroupSessionStore {
} }
removeAllForRoom(roomId) { removeAllForRoom(roomId) {
const range = IDBKeyRange.bound( const range = this._store.IDBKeyRange.bound(
encodeKey(roomId, MIN_UNICODE, MIN_UNICODE), encodeKey(roomId, MIN_UNICODE, MIN_UNICODE),
encodeKey(roomId, MAX_UNICODE, MAX_UNICODE) encodeKey(roomId, MAX_UNICODE, MAX_UNICODE)
); );

View file

@ -30,7 +30,7 @@ export class OlmSessionStore {
async getSessionIds(senderKey) { async getSessionIds(senderKey) {
const sessionIds = []; const sessionIds = [];
const range = IDBKeyRange.lowerBound(encodeKey(senderKey, "")); const range = this._store.IDBKeyRange.lowerBound(encodeKey(senderKey, ""));
await this._store.iterateKeys(range, key => { await this._store.iterateKeys(range, key => {
const decodedKey = decodeKey(key); const decodedKey = decodeKey(key);
// prevent running into the next room // prevent running into the next room
@ -44,7 +44,7 @@ export class OlmSessionStore {
} }
getAll(senderKey) { getAll(senderKey) {
const range = IDBKeyRange.lowerBound(encodeKey(senderKey, "")); const range = this._store.IDBKeyRange.lowerBound(encodeKey(senderKey, ""));
return this._store.selectWhile(range, session => { return this._store.selectWhile(range, session => {
return session.senderKey === senderKey; return session.senderKey === senderKey;
}); });

View file

@ -55,7 +55,7 @@ export class OperationStore {
} }
async removeAllForScope(scope) { async removeAllForScope(scope) {
const range = IDBKeyRange.bound( const range = this._store.IDBKeyRange.bound(
encodeScopeTypeKey(scope, MIN_UNICODE), encodeScopeTypeKey(scope, MIN_UNICODE),
encodeScopeTypeKey(scope, MAX_UNICODE) encodeScopeTypeKey(scope, MAX_UNICODE)
); );

View file

@ -33,7 +33,7 @@ export class PendingEventStore {
} }
async getMaxQueueIndex(roomId) { async getMaxQueueIndex(roomId) {
const range = IDBKeyRange.bound( const range = this._eventStore.IDBKeyRange.bound(
encodeKey(roomId, KeyLimits.minStorageKey), encodeKey(roomId, KeyLimits.minStorageKey),
encodeKey(roomId, KeyLimits.maxStorageKey), encodeKey(roomId, KeyLimits.maxStorageKey),
false, false,
@ -46,12 +46,12 @@ export class PendingEventStore {
} }
remove(roomId, queueIndex) { remove(roomId, queueIndex) {
const keyRange = IDBKeyRange.only(encodeKey(roomId, queueIndex)); const keyRange = this._eventStore.IDBKeyRange.only(encodeKey(roomId, queueIndex));
this._eventStore.delete(keyRange); this._eventStore.delete(keyRange);
} }
async exists(roomId, queueIndex) { async exists(roomId, queueIndex) {
const keyRange = IDBKeyRange.only(encodeKey(roomId, queueIndex)); const keyRange = this._eventStore.IDBKeyRange.only(encodeKey(roomId, queueIndex));
const key = await this._eventStore.getKey(keyRange); const key = await this._eventStore.getKey(keyRange);
return !!key; return !!key;
} }
@ -72,7 +72,7 @@ export class PendingEventStore {
removeAllForRoom(roomId) { removeAllForRoom(roomId) {
const minKey = encodeKey(roomId, KeyLimits.minStorageKey); const minKey = encodeKey(roomId, KeyLimits.minStorageKey);
const maxKey = encodeKey(roomId, KeyLimits.maxStorageKey); const maxKey = encodeKey(roomId, KeyLimits.maxStorageKey);
const range = IDBKeyRange.bound(minKey, maxKey); const range = this._eventStore.IDBKeyRange.bound(minKey, maxKey);
this._eventStore.delete(range); this._eventStore.delete(range);
} }
} }

View file

@ -42,7 +42,7 @@ export class RoomMemberStore {
} }
getAll(roomId) { getAll(roomId) {
const range = IDBKeyRange.lowerBound(encodeKey(roomId, "")); const range = this._roomMembersStore.IDBKeyRange.lowerBound(encodeKey(roomId, ""));
return this._roomMembersStore.selectWhile(range, member => { return this._roomMembersStore.selectWhile(range, member => {
return member.roomId === roomId; return member.roomId === roomId;
}); });
@ -50,7 +50,7 @@ export class RoomMemberStore {
async getAllUserIds(roomId) { async getAllUserIds(roomId) {
const userIds = []; const userIds = [];
const range = IDBKeyRange.lowerBound(encodeKey(roomId, "")); const range = this._roomMembersStore.IDBKeyRange.lowerBound(encodeKey(roomId, ""));
await this._roomMembersStore.iterateKeys(range, key => { await this._roomMembersStore.iterateKeys(range, key => {
const decodedKey = decodeKey(key); const decodedKey = decodeKey(key);
// prevent running into the next room // prevent running into the next room
@ -66,7 +66,7 @@ export class RoomMemberStore {
removeAllForRoom(roomId) { removeAllForRoom(roomId) {
// exclude both keys as they are theoretical min and max, // exclude both keys as they are theoretical min and max,
// but we should't have a match for just the room id, or room id with max // but we should't have a match for just the room id, or room id with max
const range = IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true); const range = this._roomMembersStore.IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true);
this._roomMembersStore.delete(range); this._roomMembersStore.delete(range);
} }
} }

View file

@ -44,7 +44,7 @@ export class RoomStateStore {
removeAllForRoom(roomId) { removeAllForRoom(roomId) {
// exclude both keys as they are theoretical min and max, // exclude both keys as they are theoretical min and max,
// but we should't have a match for just the room id, or room id with max // but we should't have a match for just the room id, or room id with max
const range = IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true); const range = this._roomStateStore.IDBKeyRange.bound(roomId, `${roomId}|${MAX_UNICODE}`, true, true);
this._roomStateStore.delete(range); this._roomStateStore.delete(range);
} }
} }

View file

@ -33,7 +33,8 @@ function decodeEventIdKey(eventIdKey) {
} }
class Range { class Range {
constructor(only, lower, upper, lowerOpen, upperOpen) { constructor(IDBKeyRange, only, lower, upper, lowerOpen, upperOpen) {
this._IDBKeyRange = IDBKeyRange;
this._only = only; this._only = only;
this._lower = lower; this._lower = lower;
this._upper = upper; this._upper = upper;
@ -45,12 +46,12 @@ class Range {
try { try {
// only // only
if (this._only) { if (this._only) {
return IDBKeyRange.only(encodeKey(roomId, this._only.fragmentId, this._only.eventIndex)); return this._IDBKeyRange.only(encodeKey(roomId, this._only.fragmentId, this._only.eventIndex));
} }
// lowerBound // lowerBound
// also bound as we don't want to move into another roomId // also bound as we don't want to move into another roomId
if (this._lower && !this._upper) { if (this._lower && !this._upper) {
return IDBKeyRange.bound( return this._IDBKeyRange.bound(
encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex), encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex),
encodeKey(roomId, this._lower.fragmentId, KeyLimits.maxStorageKey), encodeKey(roomId, this._lower.fragmentId, KeyLimits.maxStorageKey),
this._lowerOpen, this._lowerOpen,
@ -60,7 +61,7 @@ class Range {
// upperBound // upperBound
// also bound as we don't want to move into another roomId // also bound as we don't want to move into another roomId
if (!this._lower && this._upper) { if (!this._lower && this._upper) {
return IDBKeyRange.bound( return this._IDBKeyRange.bound(
encodeKey(roomId, this._upper.fragmentId, KeyLimits.minStorageKey), encodeKey(roomId, this._upper.fragmentId, KeyLimits.minStorageKey),
encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex), encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex),
false, false,
@ -69,7 +70,7 @@ class Range {
} }
// bound // bound
if (this._lower && this._upper) { if (this._lower && this._upper) {
return IDBKeyRange.bound( return this._IDBKeyRange.bound(
encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex), encodeKey(roomId, this._lower.fragmentId, this._lower.eventIndex),
encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex), encodeKey(roomId, this._upper.fragmentId, this._upper.eventIndex),
this._lowerOpen, this._lowerOpen,
@ -107,7 +108,7 @@ export class TimelineEventStore {
* @return {Range} the created range * @return {Range} the created range
*/ */
onlyRange(eventKey) { onlyRange(eventKey) {
return new Range(eventKey); return new Range(this._timelineStore.IDBKeyRange, eventKey);
} }
/** Creates a range that includes all keys before eventKey, and optionally also the key itself. /** Creates a range that includes all keys before eventKey, and optionally also the key itself.
@ -116,7 +117,7 @@ export class TimelineEventStore {
* @return {Range} the created range * @return {Range} the created range
*/ */
upperBoundRange(eventKey, open=false) { upperBoundRange(eventKey, open=false) {
return new Range(undefined, undefined, eventKey, undefined, open); return new Range(this._timelineStore.IDBKeyRange, undefined, undefined, eventKey, undefined, open);
} }
/** Creates a range that includes all keys after eventKey, and optionally also the key itself. /** Creates a range that includes all keys after eventKey, and optionally also the key itself.
@ -125,7 +126,7 @@ export class TimelineEventStore {
* @return {Range} the created range * @return {Range} the created range
*/ */
lowerBoundRange(eventKey, open=false) { lowerBoundRange(eventKey, open=false) {
return new Range(undefined, eventKey, undefined, open); return new Range(this._timelineStore.IDBKeyRange, undefined, eventKey, undefined, open);
} }
/** Creates a range that includes all keys between `lower` and `upper`, and optionally the given keys as well. /** Creates a range that includes all keys between `lower` and `upper`, and optionally the given keys as well.
@ -136,7 +137,7 @@ export class TimelineEventStore {
* @return {Range} the created range * @return {Range} the created range
*/ */
boundRange(lower, upper, lowerOpen=false, upperOpen=false) { boundRange(lower, upper, lowerOpen=false, upperOpen=false) {
return new Range(undefined, lower, upper, lowerOpen, upperOpen); return new Range(this._timelineStore.IDBKeyRange, undefined, lower, upper, lowerOpen, upperOpen);
} }
/** Looks up the last `amount` entries in the timeline for `roomId`. /** Looks up the last `amount` entries in the timeline for `roomId`.
@ -261,7 +262,7 @@ export class TimelineEventStore {
removeAllForRoom(roomId) { removeAllForRoom(roomId) {
const minKey = encodeKey(roomId, KeyLimits.minStorageKey, KeyLimits.minStorageKey); const minKey = encodeKey(roomId, KeyLimits.minStorageKey, KeyLimits.minStorageKey);
const maxKey = encodeKey(roomId, KeyLimits.maxStorageKey, KeyLimits.maxStorageKey); const maxKey = encodeKey(roomId, KeyLimits.maxStorageKey, KeyLimits.maxStorageKey);
const range = IDBKeyRange.bound(minKey, maxKey); const range = this._timelineStore.IDBKeyRange.bound(minKey, maxKey);
this._timelineStore.delete(range); this._timelineStore.delete(range);
} }
} }

View file

@ -29,7 +29,7 @@ export class TimelineFragmentStore {
_allRange(roomId) { _allRange(roomId) {
try { try {
return IDBKeyRange.bound( return this._store.IDBKeyRange.bound(
encodeKey(roomId, KeyLimits.minStorageKey), encodeKey(roomId, KeyLimits.minStorageKey),
encodeKey(roomId, KeyLimits.maxStorageKey) encodeKey(roomId, KeyLimits.maxStorageKey)
); );

View file

@ -64,8 +64,8 @@ export function decodeUint32(str) {
return parseInt(str, 16); return parseInt(str, 16);
} }
export function openDatabase(name, createObjectStore, version) { export function openDatabase(name, createObjectStore, version, idbFactory = window.indexedDB) {
const req = indexedDB.open(name, version); const req = idbFactory.open(name, version);
req.onupgradeneeded = (ev) => { req.onupgradeneeded = (ev) => {
const db = ev.target.result; const db = ev.target.result;
const txn = ev.target.transaction; const txn = ev.target.transaction;

62
src/mocks/HomeServer.js Normal file
View file

@ -0,0 +1,62 @@
/*
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 {BaseRequest} from "./Request.js";
// a request as returned by the HomeServerApi
class HomeServerRequest extends BaseRequest {
constructor(args) {
super();
this.arguments = args;
}
respond(body) {
return this._respond(body);
}
}
class Target {
constructor() {
this.requests = {};
}
}
function handleMethod(target, name, ...args) {
let requests = target.requests[name]
if (!requests) {
target.requests[name] = requests = [];
}
const request = new HomeServerRequest(args);
requests.push(request);
return request;
}
class Handler {
get(target, prop) {
return handleMethod.bind(null, target, prop);
}
}
export class HomeServer {
constructor() {
this._target = new Target();
this.api = new Proxy(this._target, new Handler());
}
get requests() {
return this._target.requests;
}
}

View file

@ -16,17 +16,19 @@ limitations under the License.
import {AbortError} from "../utils/error.js"; import {AbortError} from "../utils/error.js";
export class Request { export class BaseRequest {
constructor() { constructor() {
this._responsePromise = new Promise((resolve, reject) => { this._responsePromise = new Promise((resolve, reject) => {
this.resolve = resolve; this.resolve = resolve;
this.reject = reject; this.reject = reject;
}); });
this.responded = false;
this.aborted = false; this.aborted = false;
} }
respond(status, body) { _respond(value) {
this.resolve({status, body}); this.responded = true;
this.resolve(value);
return this; return this;
} }
@ -39,3 +41,10 @@ export class Request {
return this._responsePromise; return this._responsePromise;
} }
} }
// this is a NetworkRequest as used by HomeServerApi
export class Request extends BaseRequest {
respond(status, body) {
return this._respond({status, body});
}
}

22
src/mocks/Storage.js Normal file
View file

@ -0,0 +1,22 @@
/*
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 {FDBFactory, FDBKeyRange} from "../../lib/fake-indexeddb/index.js";
import {StorageFactory} from "../matrix/storage/idb/StorageFactory.js";
export function createMockStorage() {
return new StorageFactory(null, new FDBFactory(), FDBKeyRange).create(1);
}

31
src/mocks/event.js Normal file
View file

@ -0,0 +1,31 @@
/*
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 function event(type, id = null) {
return {type, event_id: id};
}
export function withContent(event, content) {
return Object.assign({}, event, {content});
}
export function withTextBody(event, body) {
return withContent(event, {body, msgtype: "m.text"});
}
export function withTxnId(event, txnId) {
return Object.assign({}, event, {unsigned: {transaction_id: txnId}});
}

27
src/mocks/poll.js Normal file
View file

@ -0,0 +1,27 @@
/*
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 async function poll(fn) {
let result;
do {
const result = fn();
if (result) {
return result;
} else {
await new Promise(setImmediate); //eslint-disable-line no-undef
}
} while (1); //eslint-disable-line no-constant-condition
}

View file

@ -1045,6 +1045,11 @@ base-x@^3.0.2:
dependencies: dependencies:
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
base64-arraybuffer-es6@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.7.0.tgz#dbe1e6c87b1bf1ca2875904461a7de40f21abc86"
integrity sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw==
base64-arraybuffer@^0.2.0: base64-arraybuffer@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz#4b944fac0191aa5907afe2d8c999ccc57ce80f45" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz#4b944fac0191aa5907afe2d8c999ccc57ce80f45"
@ -1209,6 +1214,11 @@ core-js-compat@^3.6.2:
browserslist "^4.8.5" browserslist "^4.8.5"
semver "7.0.0" semver "7.0.0"
core-js@^2.5.3:
version "2.6.12"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
core-js@^3.6.5: core-js@^3.6.5:
version "3.6.5" version "3.6.5"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
@ -1324,6 +1334,13 @@ domelementtype@^2.0.1:
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d"
integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==
domexception@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==
dependencies:
webidl-conversions "^4.0.2"
domhandler@^2.3.0: domhandler@^2.3.0:
version "2.4.2" version "2.4.2"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
@ -1549,6 +1566,14 @@ extend@^3.0.1:
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
fake-indexeddb@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-3.1.2.tgz#8073a12ed3b254f7afc064f3cc2629f0110a5303"
integrity sha512-W60eRBrE8r9o/EePyyUc63sr2I/MI9p3zVwLlC1WI1xdmQVqqM6+wec9KDWDz2EZyvJKhrDvy3cGC6hK8L1pfg==
dependencies:
realistic-structured-clone "^2.0.1"
setimmediate "^1.0.5"
fast-deep-equal@^3.1.1: fast-deep-equal@^3.1.1:
version "3.1.3" version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@ -1914,7 +1939,7 @@ lodash@^4.17.19:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
lodash@^4.17.21: lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -2218,7 +2243,7 @@ progress@^2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
punycode@^2.1.0: punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
@ -2244,6 +2269,16 @@ readable-stream@^3.1.1:
string_decoder "^1.1.1" string_decoder "^1.1.1"
util-deprecate "^1.0.1" util-deprecate "^1.0.1"
realistic-structured-clone@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/realistic-structured-clone/-/realistic-structured-clone-2.0.2.tgz#2f8ec225b1f9af20efc79ac96a09043704414959"
integrity sha512-5IEvyfuMJ4tjQOuKKTFNvd+H9GSbE87IcendSBannE28PTrbolgaVg5DdEApRKhtze794iXqVUFKV60GLCNKEg==
dependencies:
core-js "^2.5.3"
domexception "^1.0.1"
typeson "^5.8.2"
typeson-registry "^1.0.0-alpha.20"
regenerate-unicode-properties@^8.2.0: regenerate-unicode-properties@^8.2.0:
version "8.2.0" version "8.2.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
@ -2411,6 +2446,11 @@ serve-static@^1.13.2:
parseurl "~1.3.3" parseurl "~1.3.3"
send "0.17.1" send "0.17.1"
setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
setprototypeof@1.1.1: setprototypeof@1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
@ -2548,6 +2588,13 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
tr46@^2.0.2:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240"
integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==
dependencies:
punycode "^2.1.1"
type-check@^0.4.0, type-check@~0.4.0: type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0" version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
@ -2565,6 +2612,25 @@ type-fest@^0.8.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
typeson-registry@^1.0.0-alpha.20:
version "1.0.0-alpha.39"
resolved "https://registry.yarnpkg.com/typeson-registry/-/typeson-registry-1.0.0-alpha.39.tgz#9e0f5aabd5eebfcffd65a796487541196f4b1211"
integrity sha512-NeGDEquhw+yfwNhguLPcZ9Oj0fzbADiX4R0WxvoY8nGhy98IbzQy1sezjoEFWOywOboj/DWehI+/aUlRVrJnnw==
dependencies:
base64-arraybuffer-es6 "^0.7.0"
typeson "^6.0.0"
whatwg-url "^8.4.0"
typeson@^5.8.2:
version "5.18.2"
resolved "https://registry.yarnpkg.com/typeson/-/typeson-5.18.2.tgz#0d217fc0e11184a66aa7ca0076d9aa7707eb7bc2"
integrity sha512-Vetd+OGX05P4qHyHiSLdHZ5Z5GuQDrHHwSdjkqho9NSCYVSLSfRMjklD/unpHH8tXBR9Z/R05rwJSuMpMFrdsw==
typeson@^6.0.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/typeson/-/typeson-6.1.0.tgz#5b2a53705a5f58ff4d6f82f965917cabd0d7448b"
integrity sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA==
unicode-canonical-property-names-ecmascript@^1.0.4: unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@ -2610,6 +2676,25 @@ v8-compile-cache@^2.0.3:
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
webidl-conversions@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
whatwg-url@^8.4.0:
version "8.5.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.5.0.tgz#7752b8464fc0903fec89aa9846fc9efe07351fd3"
integrity sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==
dependencies:
lodash "^4.7.0"
tr46 "^2.0.2"
webidl-conversions "^6.1.0"
which@^2.0.1: which@^2.0.1:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"